/** * This file is part of alf.io. * * alf.io is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * alf.io is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with alf.io. If not, see <http://www.gnu.org/licenses/>. */ package alfio.manager; import alfio.manager.plugin.PluginManager; import alfio.manager.user.UserManager; import alfio.model.Event; import alfio.model.Ticket; import alfio.model.TicketCategory; import alfio.model.modification.TicketCategoryWithStatistic; import alfio.model.user.Organization; import alfio.repository.*; import com.insightfullogic.lambdabehave.JunitSuiteRunner; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; import static alfio.manager.testSupport.TicketCategoryGenerator.generateCategoryStream; import static com.insightfullogic.lambdabehave.Suite.describe; import static java.util.Collections.singletonList; import static org.mockito.Mockito.*; @RunWith(JunitSuiteRunner.class) public class EventManagerTest {{ PluginManager pluginManager = mock(PluginManager.class); describe("handleTicketNumberModification", it -> { Event event = mock(Event.class); int eventId = 10; when(event.getId()).thenReturn(eventId); TicketCategory original = mock(TicketCategory.class); TicketCategory updated = mock(TicketCategory.class); TicketRepository ticketRepository = it.usesMock(TicketRepository.class); NamedParameterJdbcTemplate jdbc = it.usesMock(NamedParameterJdbcTemplate.class); EventManager eventManager = new EventManager(null, null, null, null, null, null, ticketRepository, null, null, null, jdbc, null, pluginManager, null, null, null, null, null, null); when(original.getId()).thenReturn(20); when(updated.getId()).thenReturn(30); when(original.getSrcPriceCts()).thenReturn(1000); when(updated.getSrcPriceCts()).thenReturn(1000); when(original.getMaxTickets()).thenReturn(10); when(updated.getMaxTickets()).thenReturn(11); when(event.getZoneId()).thenReturn(ZoneId.systemDefault()); it.should("throw exception if there are tickets already sold", expect -> { when(ticketRepository.lockTicketsToInvalidate(eventId, 30, 2)).thenReturn(singletonList(1)); expect.exception(IllegalStateException.class, () -> eventManager.handleTicketNumberModification(event, original, updated, -2)); verify(ticketRepository, never()).invalidateTickets(anyListOf(Integer.class)); }); it.should("invalidate exceeding tickets", expect -> { final List<Integer> ids = Arrays.asList(1, 2); when(ticketRepository.lockTicketsToInvalidate(eventId, 30, 2)).thenReturn(ids); eventManager.handleTicketNumberModification(event, original, updated, -2); verify(ticketRepository, times(1)).invalidateTickets(ids); }); it.should("do nothing if the difference is zero", expect -> { eventManager.handleTicketNumberModification(event, original, updated, 0); verify(ticketRepository, never()).invalidateTickets(anyListOf(Integer.class)); verify(jdbc, never()).batchUpdate(anyString(), any(SqlParameterSource[].class)); }); it.should("insert a new Ticket if the difference is 1", expect -> { when(ticketRepository.selectNotAllocatedTicketsForUpdate(eq(eventId), eq(1), eq(singletonList(Ticket.TicketStatus.FREE.name())))).thenReturn(singletonList(1)); eventManager.handleTicketNumberModification(event, original, updated, 1); verify(ticketRepository, never()).invalidateTickets(anyListOf(Integer.class)); ArgumentCaptor<SqlParameterSource[]> captor = ArgumentCaptor.forClass(SqlParameterSource[].class); verify(jdbc, times(1)).batchUpdate(anyString(), captor.capture()); expect.that(captor.getValue().length).is(1); }); }); describe("handlePriceChange", it -> { TicketRepository ticketRepository = it.usesMock(TicketRepository.class); EventManager eventManager = new EventManager(null, null, null, null, null, null, ticketRepository, null, null, null, null, null, pluginManager, null, null, null, null, null, null); TicketCategory original = mock(TicketCategory.class); TicketCategory updated = mock(TicketCategory.class); Event event = mock(Event.class); when(event.getId()).thenReturn(1); it.should("do nothing if the price is not changed", expect -> { when(original.getSrcPriceCts()).thenReturn(10); when(updated.getSrcPriceCts()).thenReturn(10); eventManager.handlePriceChange(event, original, updated); verify(ticketRepository, never()).selectTicketInCategoryForUpdate(anyInt(), anyInt(), anyInt(), any()); verify(ticketRepository, never()).updateTicketPrice(anyInt(), anyInt(), anyInt(), eq(0), eq(0), eq(0)); }); it.should("throw an exception if there aren't enough tickets", expect -> { when(original.getSrcPriceCts()).thenReturn(10); when(updated.getSrcPriceCts()).thenReturn(11); when(updated.getMaxTickets()).thenReturn(2); when(updated.getId()).thenReturn(20); when(ticketRepository.selectTicketInCategoryForUpdate(eq(10), eq(20), eq(2), eq(singletonList(Ticket.TicketStatus.FREE.name())))).thenReturn(singletonList(1)); expect.exception(IllegalStateException.class, () -> eventManager.handlePriceChange(event, original, updated)); verify(ticketRepository, never()).updateTicketPrice(anyInt(), anyInt(), anyInt(), eq(0), eq(0), eq(0)); }); it.should("update tickets if constraints are verified", expect -> { when(original.getSrcPriceCts()).thenReturn(10); when(updated.getSrcPriceCts()).thenReturn(11); when(updated.getMaxTickets()).thenReturn(2); when(updated.getId()).thenReturn(20); when(ticketRepository.selectTicketInCategoryForUpdate(eq(1), eq(20), eq(2), eq(singletonList(Ticket.TicketStatus.FREE.name())))).thenReturn(Arrays.asList(1, 2)); eventManager.handlePriceChange(event, original, updated); verify(ticketRepository, times(1)).updateTicketPrice(20, 1, 11, 0, 0, 0); }); }); describe("handleTokenModification", it -> { SpecialPriceRepository specialPriceRepository = it.usesMock(SpecialPriceRepository.class); NamedParameterJdbcTemplate jdbc = it.usesMock(NamedParameterJdbcTemplate.class); EventManager eventManager = new EventManager(null, null, null, null, null, null, null, specialPriceRepository, null, null, jdbc, null, pluginManager, null, null, null, null, null, null); TicketCategory original = mock(TicketCategory.class); TicketCategory updated = mock(TicketCategory.class); it.should("do nothing if both categories are not 'access restricted'", expect -> { when(original.isAccessRestricted()).thenReturn(false); when(updated.isAccessRestricted()).thenReturn(false); eventManager.handleTokenModification(original, updated, 50); verify(jdbc, never()).batchUpdate(anyString(), any(SqlParameterSource[].class)); }); it.should("handle the activation of access restriction", expect -> { when(original.isAccessRestricted()).thenReturn(false); when(updated.isAccessRestricted()).thenReturn(true); when(updated.getMaxTickets()).thenReturn(50); eventManager.handleTokenModification(original, updated, 50); ArgumentCaptor<SqlParameterSource[]> captor = ArgumentCaptor.forClass(SqlParameterSource[].class); verify(jdbc, times(1)).batchUpdate(anyString(), captor.capture()); expect.that(captor.getValue().length).is(50); }); it.should("handle the deactivation of access restriction", expect -> { when(original.isAccessRestricted()).thenReturn(true); when(updated.isAccessRestricted()).thenReturn(false); when(updated.getId()).thenReturn(20); eventManager.handleTokenModification(original, updated, 50); verify(specialPriceRepository, times(1)).cancelExpiredTokens(eq(20)); }); it.should("handle the ticket addition", expect -> { when(original.isAccessRestricted()).thenReturn(true); when(updated.isAccessRestricted()).thenReturn(true); eventManager.handleTokenModification(original, updated, 50); ArgumentCaptor<SqlParameterSource[]> captor = ArgumentCaptor.forClass(SqlParameterSource[].class); verify(jdbc, times(1)).batchUpdate(anyString(), captor.capture()); expect.that(captor.getValue().length).is(50); }); it.should("handle the ticket removal", expect -> { when(original.isAccessRestricted()).thenReturn(true); when(updated.isAccessRestricted()).thenReturn(true); when(updated.getId()).thenReturn(20); final List<Integer> ids = Arrays.asList(1, 2); when(specialPriceRepository.lockTokens(eq(20), eq(2))).thenReturn(ids); eventManager.handleTokenModification(original, updated, -2); verify(specialPriceRepository, times(1)).cancelTokens(ids); }); it.should("fail if there are not enough tickets", expect -> { when(original.isAccessRestricted()).thenReturn(true); when(updated.isAccessRestricted()).thenReturn(true); when(updated.getId()).thenReturn(20); final List<Integer> ids = singletonList(1); when(specialPriceRepository.lockTokens(eq(20), eq(2))).thenReturn(ids); expect.exception(IllegalArgumentException.class, () -> eventManager.handleTokenModification(original, updated, -2)); verify(specialPriceRepository, never()).cancelTokens(anyListOf(Integer.class)); }); }); describe("unbounded categories handling", it -> { int eventId = 0; TicketCategoryRepository ticketCategoryRepository = it.usesMock(TicketCategoryRepository.class); TicketCategoryDescriptionRepository ticketCategoryDescriptionRepository = it.usesMock(TicketCategoryDescriptionRepository.class); EventManager eventManager = new EventManager(null, null, null, null, ticketCategoryRepository, ticketCategoryDescriptionRepository, null, null, null, null, null, null, pluginManager, null, null, null, null, null, null); Event event = mock(Event.class); int availableSeats = 20; when(event.getAvailableSeats()).thenReturn(availableSeats); it.should("create tickets for the unbounded category", expect -> { List<TicketCategory> categories = generateCategoryStream().limit(3).collect(Collectors.toList()); when(ticketCategoryRepository.findByEventId(eq(eventId))).thenReturn(categories); MapSqlParameterSource[] parameterSources = eventManager.prepareTicketsBulkInsertParameters(ZonedDateTime.now(), event); expect.that(parameterSources).isNotNull(); expect.that(parameterSources.length).is(availableSeats); }); it.should("create tickets for the unbounded categories", expect -> { List<TicketCategory> categories = generateCategoryStream().limit(6).collect(Collectors.toList()); when(ticketCategoryRepository.findByEventId(eq(eventId))).thenReturn(categories); MapSqlParameterSource[] parameterSources = eventManager.prepareTicketsBulkInsertParameters(ZonedDateTime.now(), event); expect.that(parameterSources).isNotNull(); expect.that(parameterSources.length).is(availableSeats); }); it.should("create tickets only for the bounded categories", expect -> { List<TicketCategory> categories = generateCategoryStream().limit(2).collect(Collectors.toList()); when(ticketCategoryRepository.findByEventId(eq(eventId))).thenReturn(categories); MapSqlParameterSource[] parameterSources = eventManager.prepareTicketsBulkInsertParameters(ZonedDateTime.now(), event); expect.that(parameterSources).isNotNull(); expect.that(parameterSources.length).is(20); expect.that(Arrays.stream(parameterSources).filter(p -> p.getValue("categoryId") != null).count()).is(4L); }); }); describe("unbind tickets from category", it -> { int eventId = 0; String eventName = "myEvent"; String username = "username"; int categoryId = 1; int organizationId = 2; Map<String, String> desc = Collections.singletonMap("en", "desc"); TicketCategoryRepository ticketCategoryRepository = it.usesMock(TicketCategoryRepository.class); TicketCategoryDescriptionRepository ticketCategoryDescriptionRepository = it.usesMock(TicketCategoryDescriptionRepository.class); TicketRepository ticketRepository = it.usesMock(TicketRepository.class); EventRepository eventRepository = it.usesMock(EventRepository.class); EventDescriptionRepository eventDescriptionRepository = it.usesMock(EventDescriptionRepository.class); UserManager userManager = it.usesMock(UserManager.class); SpecialPriceRepository specialPriceRepository = mock(SpecialPriceRepository.class); when(specialPriceRepository.findAllByCategoryId(eq(categoryId))).thenReturn(Collections.emptyList()); EventStatisticsManager esm = mock(EventStatisticsManager.class); Event event = mock(Event.class); TicketCategory ticketCategory = it.usesMock(TicketCategory.class); TicketCategoryWithStatistic tc = new TicketCategoryWithStatistic(ticketCategory, Collections.emptyList(), Collections.emptyList(), event, desc); when(esm.loadTicketCategoryWithStats(eq(categoryId), eq(event))).thenReturn(tc); EventManager eventManager = new EventManager(userManager, eventRepository, eventDescriptionRepository, esm, ticketCategoryRepository, ticketCategoryDescriptionRepository, ticketRepository, specialPriceRepository, null, null, null, null, pluginManager, null, null, null, null, null, null); when(event.getId()).thenReturn(eventId); when(event.getOrganizationId()).thenReturn(organizationId); Organization organization = mock(Organization.class); when(organization.getId()).thenReturn(organizationId); it.isSetupWith(() -> { when(eventRepository.findByShortName(eq(eventName))).thenReturn(event); when(ticketCategory.getId()).thenReturn(categoryId); }); it.should("not unbind from an event which doesn't contain unbounded categories", expect -> { when(ticketCategoryRepository.countUnboundedCategoriesByEventId(eq(eventId))).thenReturn(0); when(userManager.findUserOrganizations(eq(username))).thenReturn(singletonList(organization)); expect.exception(IllegalArgumentException.class, () -> eventManager.unbindTickets(eventName, categoryId, username)); verify(ticketCategoryRepository).countUnboundedCategoriesByEventId(eq(eventId)); verify(userManager).findUserOrganizations(eq(username)); verify(eventRepository).findByShortName(eq(eventName)); verifyNoMoreInteractions(ticketCategoryRepository, userManager, eventRepository, ticketRepository); }); it.should("not unbind from a category which is not bounded", expect -> { when(ticketCategoryRepository.countUnboundedCategoriesByEventId(eq(eventId))).thenReturn(1); when(userManager.findUserOrganizations(eq(username))).thenReturn(singletonList(organization)); when(ticketCategory.isBounded()).thenReturn(false); expect.exception(IllegalArgumentException.class, () -> eventManager.unbindTickets(eventName, categoryId, username)); verify(ticketCategoryRepository).countUnboundedCategoriesByEventId(eq(eventId)); verify(userManager).findUserOrganizations(eq(username)); verify(eventRepository).findByShortName(eq(eventName)); verifyNoMoreInteractions(ticketCategoryRepository, userManager, eventRepository, ticketRepository); }); it.should("unbind tickets from a bounded category", expect -> { when(ticketCategoryRepository.countUnboundedCategoriesByEventId(eq(eventId))).thenReturn(1); when(userManager.findUserOrganizations(eq(username))).thenReturn(singletonList(organization)); when(ticketCategory.isBounded()).thenReturn(true); int notSold = 2; when(ticketCategory.getMaxTickets()).thenReturn(notSold); List<Integer> lockedTickets = Arrays.asList(1, 2); when(ticketRepository.selectTicketInCategoryForUpdate(eq(eventId), eq(categoryId), eq(notSold), eq(singletonList(Ticket.TicketStatus.FREE.name())))).thenReturn(lockedTickets); when(ticketRepository.unbindTicketsFromCategory(eq(eventId), eq(categoryId), eq(lockedTickets))).thenReturn(notSold); eventManager.unbindTickets(eventName, categoryId, username); verify(ticketCategoryRepository).countUnboundedCategoriesByEventId(eq(eventId)); verify(userManager).findUserOrganizations(eq(username)); verify(eventRepository).findByShortName(eq(eventName)); verify(ticketRepository).selectTicketInCategoryForUpdate(eq(eventId), eq(categoryId), eq(notSold), eq(singletonList(Ticket.TicketStatus.FREE.name()))); verify(ticketRepository).unbindTicketsFromCategory(eq(eventId), eq(categoryId), eq(lockedTickets)); verify(ticketCategoryRepository).updateSeatsAvailability(eq(categoryId), eq(0)); verifyNoMoreInteractions(ticketCategoryRepository, userManager, eventRepository, ticketRepository); }); }); }}