/**
* 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.TestConfiguration;
import alfio.config.DataSourceConfiguration;
import alfio.config.Initializer;
import alfio.config.RepositoryConfiguration;
import alfio.manager.user.UserManager;
import alfio.model.*;
import alfio.model.modification.*;
import alfio.model.modification.AdminReservationModification.Attendee;
import alfio.model.modification.AdminReservationModification.Category;
import alfio.model.modification.AdminReservationModification.CustomerData;
import alfio.model.modification.AdminReservationModification.TicketsInfo;
import alfio.model.result.Result;
import alfio.repository.EmailMessageRepository;
import alfio.repository.SpecialPriceRepository;
import alfio.repository.TicketCategoryRepository;
import alfio.repository.TicketRepository;
import alfio.repository.user.OrganizationRepository;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.lang3.tuple.Triple;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.stream.IntStream;
import static alfio.test.util.IntegrationTestUtil.*;
import static java.util.stream.Collectors.toList;
import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RepositoryConfiguration.class, DataSourceConfiguration.class, TestConfiguration.class})
@ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS})
@Transactional
public class AdminReservationManagerIntegrationTest {
@BeforeClass
public static void initEnv() {
initSystemProperties();
}
@Autowired
private AdminReservationManager adminReservationManager;
@Autowired
private EventManager eventManager;
@Autowired
private OrganizationRepository organizationRepository;
@Autowired
private UserManager userManager;
@Autowired
private TicketCategoryRepository ticketCategoryRepository;
@Autowired
private TicketRepository ticketRepository;
@Autowired
private EmailMessageRepository emailMessageRepository;
@Autowired
private TicketReservationManager ticketReservationManager;
@Autowired
private SpecialPriceRepository specialPriceRepository;
@Test
public void testReserveFromExistingCategory() throws Exception {
List<TicketCategoryModification> categories = Collections.singletonList(
new TicketCategoryModification(null, "default", AVAILABLE_SEATS,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", false));
performExistingCategoryTest(categories, false, Collections.singletonList(1), false, true, 0, AVAILABLE_SEATS);
}
@Test
public void testReserveFromExistingMultipleCategories() throws Exception {
List<TicketCategoryModification> categories = Arrays.asList(
new TicketCategoryModification(null, "default", 10,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", false),
new TicketCategoryModification(null, "2nd", 10,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", false));
performExistingCategoryTest(categories, false, Arrays.asList(10,10), false, true, 0, AVAILABLE_SEATS);
}
@Test
public void testReserveFromExistingCategoryNotEnoughSeatsNotBounded() throws Exception {
List<TicketCategoryModification> categories = Collections.singletonList(
new TicketCategoryModification(null, "default", 1,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", false));
performExistingCategoryTest(categories, false, Collections.singletonList(1), false, true, 0, AVAILABLE_SEATS);
}
@Test
public void testReserveExistingCategoryNotEnoughSeatsNotBoundedSoldOut() throws Exception {
List<TicketCategoryModification> categories = Collections.singletonList(
new TicketCategoryModification(null, "default", 1,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", false));
performExistingCategoryTest(categories, false, Collections.singletonList(1), true, true, AVAILABLE_SEATS, AVAILABLE_SEATS+1);
}
@Test
public void testReserveFromExistingCategoryNotEnoughSeatsBounded() throws Exception {
List<TicketCategoryModification> categories = Collections.singletonList(
new TicketCategoryModification(null, "default", 1,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", true));
performExistingCategoryTest(categories, true, Collections.singletonList(2), false, true, 0, AVAILABLE_SEATS);
}
@Test
public void testReserveFromExistingCategoryNotEnoughSeatsNoExtensionAllowedBounded() throws Exception {
List<TicketCategoryModification> categories = Collections.singletonList(
new TicketCategoryModification(null, "default", AVAILABLE_SEATS,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", true));
performExistingCategoryTest(categories, true, Collections.singletonList(AVAILABLE_SEATS + 1), false, false, 0, AVAILABLE_SEATS);
}
@Test
public void testReserveFromExistingCategoryNotEnoughSeatsNoExtensionAllowedNotBounded() throws Exception {
List<TicketCategoryModification> categories = Collections.singletonList(
new TicketCategoryModification(null, "default", AVAILABLE_SEATS,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", false));
performExistingCategoryTest(categories, true, Collections.singletonList(AVAILABLE_SEATS + 1), false, false, 0, AVAILABLE_SEATS);
}
@Test
public void testReserveFromNewCategory() throws Exception {
List<TicketCategoryModification> categories = Collections.singletonList(
new TicketCategoryModification(null, "default", 1,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", true));
Pair<Event, String> eventWithUsername = initEvent(categories, organizationRepository, userManager, eventManager);
Event event = eventWithUsername.getKey();
String username = eventWithUsername.getValue();
DateTimeModification expiration = DateTimeModification.fromZonedDateTime(ZonedDateTime.now().plusDays(1));
CustomerData customerData = new CustomerData("Integration", "Test", "integration-test@test.ch", "Billing Address", "en");
Category category = new Category(null, "name", new BigDecimal("100.00"));
int attendees = AVAILABLE_SEATS;
List<TicketsInfo> ticketsInfoList = Collections.singletonList(new TicketsInfo(category, generateAttendees(attendees), true, false));
AdminReservationModification modification = new AdminReservationModification(expiration, customerData, ticketsInfoList, "en", false, null);
Result<Pair<TicketReservation, List<Ticket>>> result = adminReservationManager.createReservation(modification, event.getShortName(), username);
assertTrue(result.isSuccess());
Pair<TicketReservation, List<Ticket>> data = result.getData();
List<Ticket> tickets = data.getRight();
assertTrue(tickets.size() == attendees);
assertNotNull(data.getLeft());
int categoryId = tickets.get(0).getCategoryId();
Event modified = eventManager.getSingleEvent(event.getShortName(), username);
assertEquals(attendees + 1, modified.getAvailableSeats());
assertEquals(attendees, ticketRepository.findPendingTicketsInCategories(Collections.singletonList(categoryId)).size());
TicketCategory categoryModified = ticketCategoryRepository.getById(categoryId, event.getId());
assertEquals(categoryModified.getMaxTickets(), attendees);
ticketCategoryRepository.findByEventId(event.getId()).forEach(tc -> assertTrue(specialPriceRepository.findAllByCategoryId(tc.getId()).stream().allMatch(sp -> sp.getStatus() == SpecialPrice.Status.PENDING)));
adminReservationManager.confirmReservation(event.getShortName(), data.getLeft().getId(), username);
ticketCategoryRepository.findByEventId(event.getId()).forEach(tc -> assertTrue(specialPriceRepository.findAllByCategoryId(tc.getId()).stream().allMatch(sp -> sp.getStatus() == SpecialPrice.Status.TAKEN)));
assertFalse(ticketRepository.findAllReservationsConfirmedButNotAssigned(event.getId()).contains(data.getLeft().getId()));
}
@Test
public void testReserveMixed() throws Exception {
List<TicketCategoryModification> categories = Collections.singletonList(
new TicketCategoryModification(null, "default", 1,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", false));
Pair<Event, String> eventWithUsername = initEvent(categories, organizationRepository, userManager, eventManager);
Event event = eventWithUsername.getKey();
String username = eventWithUsername.getValue();
DateTimeModification expiration = DateTimeModification.fromZonedDateTime(ZonedDateTime.now().plusDays(1));
CustomerData customerData = new CustomerData("Integration", "Test", "integration-test@test.ch", "Billing Address", "en");
TicketCategory existingCategory = ticketCategoryRepository.findByEventId(event.getId()).get(0);
Category resExistingCategory = new Category(existingCategory.getId(), "", existingCategory.getPrice());
Category resNewCategory = new Category(null, "name", new BigDecimal("100.00"));
int attendees = 1;
List<TicketsInfo> ticketsInfoList = Arrays.asList(new TicketsInfo(resExistingCategory, generateAttendees(attendees), false, false), new TicketsInfo(resNewCategory, generateAttendees(attendees), false, false),new TicketsInfo(resExistingCategory, generateAttendees(attendees), false, false));
AdminReservationModification modification = new AdminReservationModification(expiration, customerData, ticketsInfoList, "en", false, null);
Result<Pair<TicketReservation, List<Ticket>>> result = adminReservationManager.createReservation(modification, event.getShortName(), username);
assertTrue(result.isSuccess());
Pair<TicketReservation, List<Ticket>> data = result.getData();
List<Ticket> tickets = data.getRight();
assertTrue(tickets.size() == 3);
assertNotNull(data.getLeft());
assertTrue(tickets.stream().allMatch(t -> t.getTicketsReservationId().equals(data.getKey().getId())));
int resExistingCategoryId = tickets.get(0).getCategoryId();
int resNewCategoryId = tickets.get(2).getCategoryId();
Event modified = eventManager.getSingleEvent(event.getShortName(), username);
assertEquals(AVAILABLE_SEATS, modified.getAvailableSeats());
assertEquals(3, ticketRepository.findPendingTicketsInCategories(Arrays.asList(resExistingCategoryId, resNewCategoryId)).size());
assertEquals(3, ticketRepository.findTicketsInReservation(data.getLeft().getId()).size());
String reservationId = data.getLeft().getId();
assertEquals(ticketRepository.findTicketsInReservation(reservationId).stream().findFirst().get().getId(),
ticketRepository.findFirstTicketInReservation(reservationId).get().getId());
ticketCategoryRepository.findByEventId(event.getId()).forEach(tc -> assertTrue(specialPriceRepository.findAllByCategoryId(tc.getId()).stream().allMatch(sp -> sp.getStatus() == SpecialPrice.Status.PENDING)));
adminReservationManager.confirmReservation(event.getShortName(), data.getLeft().getId(), username);
ticketCategoryRepository.findByEventId(event.getId()).forEach(tc -> assertTrue(specialPriceRepository.findAllByCategoryId(tc.getId()).stream().allMatch(sp -> sp.getStatus() == SpecialPrice.Status.TAKEN)));
assertFalse(ticketRepository.findAllReservationsConfirmedButNotAssigned(event.getId()).contains(data.getLeft().getId()));
}
@Test
public void testConfirmReservation() throws Exception {
List<TicketCategoryModification> categories = Collections.singletonList(
new TicketCategoryModification(null, "default", 1,
new DateTimeModification(LocalDate.now(), LocalTime.now()),
new DateTimeModification(LocalDate.now(), LocalTime.now()),
DESCRIPTION, BigDecimal.TEN, false, "", true));
Triple<Event, String, TicketReservation> testResult = performExistingCategoryTest(categories, true, Collections.singletonList(2), false, true, 0, AVAILABLE_SEATS);
assertNotNull(testResult);
Result<Triple<TicketReservation, List<Ticket>, Event>> result = adminReservationManager.confirmReservation(testResult.getLeft().getShortName(), testResult.getRight().getId(), testResult.getMiddle());
assertTrue(result.isSuccess());
Triple<TicketReservation, List<Ticket>, Event> triple = result.getData();
assertEquals(TicketReservation.TicketReservationStatus.COMPLETE, triple.getLeft().getStatus());
triple.getMiddle().forEach(t -> assertEquals(Ticket.TicketStatus.ACQUIRED, t.getStatus()));
assertTrue(emailMessageRepository.findByEventId(triple.getRight().getId()).isEmpty());
ticketCategoryRepository.findByEventId(triple.getRight().getId()).forEach(tc -> assertTrue(specialPriceRepository.findAllByCategoryId(tc.getId()).stream().allMatch(sp -> sp.getStatus() == SpecialPrice.Status.TAKEN)));
assertFalse(ticketRepository.findAllReservationsConfirmedButNotAssigned(triple.getRight().getId()).contains(triple.getLeft().getId()));
}
private Triple<Event, String, TicketReservation> performExistingCategoryTest(List<TicketCategoryModification> categories, boolean bounded,
List<Integer> attendeesNr, boolean addSeatsIfNotAvailable, boolean expectSuccess,
int reservedTickets, int expectedEventSeats) {
assertEquals("Test error: categories' size must be equal to attendees' size", categories.size(), attendeesNr.size());
Pair<Event, String> eventWithUsername = initEvent(categories, organizationRepository, userManager, eventManager);
Event event = eventWithUsername.getKey();
String username = eventWithUsername.getValue();
DateTimeModification expiration = DateTimeModification.fromZonedDateTime(ZonedDateTime.now().plusDays(1));
CustomerData customerData = new CustomerData("Integration", "Test", "integration-test@test.ch", "Billing Address", "en");
Iterator<Integer> attendeesIterator = attendeesNr.iterator();
List<TicketCategory> existingCategories = ticketCategoryRepository.findByEventId(event.getId());
List<Attendee> allAttendees = new ArrayList<>();
List<TicketsInfo> ticketsInfoList = existingCategories.stream()
.map(existingCategory -> {
Category category = new Category(existingCategory.getId(), existingCategory.getName(), existingCategory.getPrice());
List<Attendee> attendees = generateAttendees(attendeesIterator.next());
allAttendees.addAll(attendees);
return new TicketsInfo(category, attendees, addSeatsIfNotAvailable, false);
}).collect(toList());
AdminReservationModification modification = new AdminReservationModification(expiration, customerData, ticketsInfoList, "en", false, null);
if(reservedTickets > 0) {
TicketReservationModification trm = new TicketReservationModification();
trm.setAmount(reservedTickets);
trm.setTicketCategoryId(existingCategories.get(0).getId());
TicketReservationWithOptionalCodeModification r = new TicketReservationWithOptionalCodeModification(trm, Optional.empty());
ticketReservationManager.createTicketReservation(event, Collections.singletonList(r), Collections.emptyList(), DateUtils.addDays(new Date(), 1), Optional.empty(), Optional.empty(), Locale.ENGLISH, false);
}
Result<Pair<TicketReservation, List<Ticket>>> result = adminReservationManager.createReservation(modification, event.getShortName(), username);
if(expectSuccess) {
validateSuccess(bounded, attendeesNr, event, username, existingCategories, result, allAttendees, expectedEventSeats, reservedTickets);
} else {
assertFalse(result.isSuccess());
return null;
}
return Triple.of(eventWithUsername.getLeft(), eventWithUsername.getRight(), result.getData().getKey());
}
private void validateSuccess(boolean bounded, List<Integer> attendeesNr, Event event, String username, List<TicketCategory> existingCategories, Result<Pair<TicketReservation,
List<Ticket>>> result, List<Attendee> allAttendees, int expectedEventSeats, int reservedTickets) {
assertTrue(result.isSuccess());
Pair<TicketReservation, List<Ticket>> data = result.getData();
assertTrue(data.getRight().size() == attendeesNr.stream().mapToInt(i -> i).sum());
assertNotNull(data.getLeft());
Event modified = eventManager.getSingleEvent(event.getShortName(), username);
assertEquals(expectedEventSeats, modified.getAvailableSeats());
List<Ticket> tickets = ticketRepository.findPendingTicketsInCategories(existingCategories.stream().map(TicketCategory::getId).collect(toList()));
assertEquals(attendeesNr.stream().mapToInt(i -> i).sum(), tickets.size() - reservedTickets);
if(bounded) {
final Iterator<Integer> iterator = attendeesNr.iterator();
existingCategories.forEach(existingCategory -> {
TicketCategory categoryModified = ticketCategoryRepository.getById(existingCategory.getId(), event.getId());
assertEquals(categoryModified.getMaxTickets(), iterator.next().intValue());
});
}
for (int i = 0; i < tickets.size() - reservedTickets; i++) {
Attendee attendee = allAttendees.get(i);
if(!attendee.isEmpty()) {
Ticket ticket = data.getRight().get(i);
assertTrue(ticket.getAssigned());
assertNotNull(ticket.getFullName());
assertEquals(attendee.getFullName(), ticket.getFullName());
assertEquals(attendee.getEmailAddress(), ticket.getEmail());
assertEquals(Ticket.TicketStatus.PENDING, ticket.getStatus());
assertEquals(data.getLeft().getId(), ticket.getTicketsReservationId());
}
}
ticketCategoryRepository.findByEventId(modified.getId()).forEach(tc -> assertTrue(specialPriceRepository.findAllByCategoryId(tc.getId()).stream().allMatch(sp -> sp.getStatus() == SpecialPrice.Status.PENDING)));
assertFalse(ticketRepository.findAllReservationsConfirmedButNotAssigned(event.getId()).contains(data.getLeft().getId()));
}
private List<Attendee> generateAttendees(int count) {
return IntStream.range(0, count)
.mapToObj(i -> new Attendee(null, "Attendee "+i, "Test" + i, "attendee"+i+"@test.ch"))
.collect(toList());
}
}