Search in sources :

Example 1 with TicketAndCheckInResult

use of alfio.manager.support.TicketAndCheckInResult in project alf.io by alfio-event.

the class ReservationFlowIntegrationTest method reservationFlowTest.

/**
 * Test a complete offline payment flow.
 * Will not check in detail...
 */
@Test
public void reservationFlowTest() throws Exception {
    String eventName = event.getShortName();
    assertTrue(checkInManager.findAllFullTicketInfo(event.getId()).isEmpty());
    List<EventStatistic> eventStatistic = eventStatisticsManager.getAllEventsWithStatistics(user);
    assertEquals(1, eventStatistic.size());
    assertTrue(eventStatisticsManager.getTicketSoldStatistics(event.getId(), new Date(0), DateUtils.addDays(new Date(), 1)).isEmpty());
    EventWithAdditionalInfo eventWithAdditionalInfo = eventStatisticsManager.getEventWithAdditionalInfo(event.getShortName(), user);
    assertEquals(0, eventWithAdditionalInfo.getNotSoldTickets());
    assertEquals(0, eventWithAdditionalInfo.getSoldTickets());
    assertEquals(20, eventWithAdditionalInfo.getAvailableSeats());
    eventManager.toggleActiveFlag(event.getId(), user, true);
    // list events
    String eventList = eventController.listEvents(new BindingAwareModelMap(), Locale.ENGLISH);
    if (eventManager.getPublishedEvents().size() == 1) {
        Assert.assertTrue(eventList.startsWith("redirect:/"));
    } else {
        assertEquals("/event/event-list", eventList);
    }
    // 
    // show event
    String showEvent = eventController.showEvent(eventName, new BindingAwareModelMap(), new MockHttpServletRequest(), Locale.ENGLISH);
    assertEquals("/event/show-event", showEvent);
    // 
    // check calendar
    checkCalendar(eventName);
    // 
    String redirectResult = reserveTicket(eventName);
    String redirectStart = "redirect:/event/" + eventName + "/reservation/";
    // check reservation success
    Assert.assertTrue(redirectResult.startsWith(redirectStart));
    Assert.assertTrue(redirectResult.endsWith("/book"));
    // 
    String reservationIdentifier = redirectResult.substring(redirectStart.length()).replace("/book", "");
    // check that the payment page is shown
    String reservationPage = reservationController.showPaymentPage(eventName, reservationIdentifier, null, null, null, null, null, null, null, null, null, null, null, null, new BindingAwareModelMap(), Locale.ENGLISH);
    assertEquals("/event/reservation-page", reservationPage);
    // 
    // pay offline
    String successPage = payOffline(eventName, reservationIdentifier);
    assertEquals("redirect:/event/" + eventName + "/reservation/" + reservationIdentifier + "/success", successPage);
    // 
    // go to success page, payment is still pending
    String confirmationPage = reservationController.showConfirmationPage(eventName, reservationIdentifier, false, false, new BindingAwareModelMap(), Locale.ENGLISH, new MockHttpServletRequest());
    Assert.assertTrue(confirmationPage.endsWith("/waitingPayment"));
    assertEquals("/event/reservation-waiting-for-payment", reservationController.showWaitingPaymentPage(eventName, reservationIdentifier, new BindingAwareModelMap(), Locale.ENGLISH));
    // 
    validatePayment(eventName, reservationIdentifier);
    // 
    Assert.assertTrue(reservationController.showWaitingPaymentPage(eventName, reservationIdentifier, new BindingAwareModelMap(), Locale.ENGLISH).endsWith("/success"));
    // 
    TicketDecorator ticketDecorator = checkReservationComplete(eventName, reservationIdentifier);
    // 
    String ticketIdentifier = ticketDecorator.getUuid();
    // ticket is still not assigned, will redirect
    Assert.assertTrue(ticketController.showTicket(eventName, ticketIdentifier, false, Locale.ENGLISH, new BindingAwareModelMap()).startsWith("redirect:/event/"));
    Assert.assertTrue(ticketController.showTicketForUpdate(eventName, ticketIdentifier, new BindingAwareModelMap(), Locale.ENGLISH).startsWith("redirect:/event/"));
    // 
    String fname1 = "Test";
    String lname1 = "McTest";
    // assign ticket to person
    assignTicket(eventName, reservationIdentifier, ticketIdentifier, fname1, lname1);
    assertEquals(1, checkInManager.findAllFullTicketInfo(event.getId()).size());
    assertEquals("/event/update-ticket", ticketController.showTicketForUpdate(eventName, ticketIdentifier, new BindingAwareModelMap(), Locale.ENGLISH));
    // 
    assertEquals("/event/show-ticket", ticketController.showTicket(eventName, ticketIdentifier, false, Locale.ENGLISH, new BindingAwareModelMap()));
    // 
    checkCSV(eventName, ticketIdentifier, fname1 + " " + lname1);
    // use api to update
    UpdateTicketOwnerForm updateTicketOwnerForm = new UpdateTicketOwnerForm();
    updateTicketOwnerForm.setFirstName("Test");
    updateTicketOwnerForm.setLastName("Testson");
    updateTicketOwnerForm.setEmail("testmctest@test.com");
    updateTicketOwnerForm.setUserLanguage("en");
    reservationApiController.assignTicketToPerson(eventName, ticketIdentifier, true, updateTicketOwnerForm, new BeanPropertyBindingResult(updateTicketOwnerForm, "updateTicketForm"), new MockHttpServletRequest(), new BindingAwareModelMap(), null);
    checkCSV(eventName, ticketIdentifier, "Test Testson");
    // 
    // update
    String fname2 = "Test";
    String lname2 = "OTest";
    assignTicket(eventName, reservationIdentifier, ticketIdentifier, fname2, lname2);
    checkCSV(eventName, ticketIdentifier, fname2 + " " + lname2);
    // lock ticket
    Principal principal = Mockito.mock(Principal.class);
    Mockito.when(principal.getName()).thenReturn(user);
    eventApiController.toggleTicketLocking(eventName, ticketDecorator.getCategoryId(), ticketDecorator.getId(), principal);
    assignTicket(eventName, reservationIdentifier, ticketIdentifier, fname1, fname2);
    checkCSV(eventName, ticketIdentifier, fname2 + " " + lname2);
    // ticket has changed, update
    ticketDecorator = checkReservationComplete(eventName, reservationIdentifier);
    // check stats after selling one ticket
    assertFalse(eventStatisticsManager.getTicketSoldStatistics(event.getId(), new Date(0), DateUtils.addDays(new Date(), 2)).isEmpty());
    EventWithAdditionalInfo eventWithAdditionalInfo2 = eventStatisticsManager.getEventWithAdditionalInfo(event.getShortName(), user);
    assertEquals(0, eventWithAdditionalInfo2.getNotSoldTickets());
    assertEquals(1, eventWithAdditionalInfo2.getSoldTickets());
    assertEquals(20, eventWithAdditionalInfo2.getAvailableSeats());
    assertEquals(0, eventWithAdditionalInfo2.getCheckedInTickets());
    // --- check in sequence
    String ticketCode = ticketDecorator.ticketCode(event.getPrivateKey());
    TicketAndCheckInResult ticketAndCheckInResult = checkInApiController.findTicketWithUUID(event.getId(), ticketIdentifier, ticketCode);
    assertEquals(CheckInStatus.OK_READY_TO_BE_CHECKED_IN, ticketAndCheckInResult.getResult().getStatus());
    CheckInApiController.TicketCode tc = new CheckInApiController.TicketCode();
    tc.setCode(ticketCode);
    assertEquals(CheckInStatus.SUCCESS, checkInApiController.checkIn(event.getId(), ticketIdentifier, tc, new TestingAuthenticationToken("ciccio", "ciccio")).getResult().getStatus());
    List<ScanAudit> audits = scanAuditRepository.findAllForEvent(event.getId());
    assertFalse(audits.isEmpty());
    assertTrue(audits.stream().anyMatch(sa -> sa.getTicketUuid().equals(ticketIdentifier)));
    TicketAndCheckInResult ticketAndCheckInResultOk = checkInApiController.findTicketWithUUID(event.getId(), ticketIdentifier, ticketCode);
    assertEquals(CheckInStatus.ALREADY_CHECK_IN, ticketAndCheckInResultOk.getResult().getStatus());
    // check stats after check in one ticket
    assertFalse(eventStatisticsManager.getTicketSoldStatistics(event.getId(), new Date(0), DateUtils.addDays(new Date(), 1)).isEmpty());
    EventWithAdditionalInfo eventWithAdditionalInfo3 = eventStatisticsManager.getEventWithAdditionalInfo(event.getShortName(), user);
    assertEquals(0, eventWithAdditionalInfo3.getNotSoldTickets());
    assertEquals(0, eventWithAdditionalInfo3.getSoldTickets());
    assertEquals(20, eventWithAdditionalInfo3.getAvailableSeats());
    assertEquals(1, eventWithAdditionalInfo3.getCheckedInTickets());
    // test revert check in
    assertTrue(checkInApiController.revertCheckIn(event.getId(), ticketIdentifier, principal));
    assertFalse(checkInApiController.revertCheckIn(event.getId(), ticketIdentifier, principal));
    TicketAndCheckInResult ticketAndCheckInResult2 = checkInApiController.findTicketWithUUID(event.getId(), ticketIdentifier, ticketCode);
    assertEquals(CheckInStatus.OK_READY_TO_BE_CHECKED_IN, ticketAndCheckInResult2.getResult().getStatus());
    UsersApiController.UserWithPasswordAndQRCode sponsorUser = usersApiController.insertUser(new UserModification(null, event.getOrganizationId(), "SPONSOR", "sponsor", "first", "last", "email@email.com"), "http://localhost:8080", principal);
    Principal sponsorPrincipal = Mockito.mock(Principal.class);
    Mockito.when(sponsorPrincipal.getName()).thenReturn(sponsorUser.getUsername());
    // check failures
    assertEquals(CheckInStatus.EVENT_NOT_FOUND, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest("not-existing-event", "not-existing-ticket"), sponsorPrincipal).getBody().getResult().getStatus());
    assertEquals(CheckInStatus.TICKET_NOT_FOUND, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, "not-existing-ticket"), sponsorPrincipal).getBody().getResult().getStatus());
    assertEquals(CheckInStatus.INVALID_TICKET_STATE, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticketIdentifier), sponsorPrincipal).getBody().getResult().getStatus());
    // 
    // check stats after revert check in one ticket
    assertFalse(eventStatisticsManager.getTicketSoldStatistics(event.getId(), new Date(0), DateUtils.addDays(new Date(), 1)).isEmpty());
    EventWithAdditionalInfo eventWithAdditionalInfo4 = eventStatisticsManager.getEventWithAdditionalInfo(event.getShortName(), user);
    assertEquals(0, eventWithAdditionalInfo4.getNotSoldTickets());
    assertEquals(1, eventWithAdditionalInfo4.getSoldTickets());
    assertEquals(20, eventWithAdditionalInfo4.getAvailableSeats());
    assertEquals(0, eventWithAdditionalInfo4.getCheckedInTickets());
    CheckInApiController.TicketCode tc2 = new CheckInApiController.TicketCode();
    tc2.setCode(ticketCode);
    TicketAndCheckInResult ticketAndcheckInResult = checkInApiController.checkIn(event.getId(), ticketIdentifier, tc2, new TestingAuthenticationToken("ciccio", "ciccio"));
    assertEquals(CheckInStatus.SUCCESS, ticketAndcheckInResult.getResult().getStatus());
    // 
    // 
    List<Integer> offlineIdentifiers = checkInApiController.getOfflineIdentifiers(event.getShortName(), 0L, new MockHttpServletResponse(), principal);
    assertTrue(offlineIdentifiers.isEmpty());
    configurationRepository.insertEventLevel(event.getOrganizationId(), event.getId(), ConfigurationKeys.OFFLINE_CHECKIN_ENABLED.name(), "true", null);
    configurationRepository.insert(ConfigurationKeys.ALFIO_PI_INTEGRATION_ENABLED.name(), "true", null);
    offlineIdentifiers = checkInApiController.getOfflineIdentifiers(event.getShortName(), 0L, new MockHttpServletResponse(), principal);
    assertFalse(offlineIdentifiers.isEmpty());
    Map<String, String> payload = checkInApiController.getOfflineEncryptedInfo(event.getShortName(), Collections.emptyList(), offlineIdentifiers, principal);
    assertEquals(1, payload.size());
    Ticket ticket = ticketAndcheckInResult.getTicket();
    String ticketKey = ticket.hmacTicketInfo(event.getPrivateKey());
    String hashedTicketKey = DigestUtils.sha256Hex(ticketKey);
    String encJson = payload.get(hashedTicketKey);
    assertNotNull(encJson);
    String ticketPayload = CheckInManager.decrypt(ticket.getUuid() + "/" + ticketKey, encJson);
    Map<String, String> jsonPayload = Json.fromJson(ticketPayload, new TypeReference<Map<String, String>>() {
    });
    assertNotNull(jsonPayload);
    assertEquals(8, jsonPayload.size());
    assertEquals("Test", jsonPayload.get("firstName"));
    assertEquals("OTest", jsonPayload.get("lastName"));
    assertEquals("Test OTest", jsonPayload.get("fullName"));
    assertEquals(ticket.getUuid(), jsonPayload.get("uuid"));
    assertEquals("testmctest@test.com", jsonPayload.get("email"));
    assertEquals("CHECKED_IN", jsonPayload.get("status"));
    String categoryName = ticketCategoryRepository.findByEventId(event.getId()).stream().findFirst().orElseThrow(IllegalStateException::new).getName();
    assertEquals(categoryName, jsonPayload.get("category"));
    // 
    // check register sponsor scan success flow
    assertTrue(attendeeApiController.getScannedBadges(event.getShortName(), EventUtil.JSON_DATETIME_FORMATTER.format(LocalDateTime.of(1970, 1, 1, 0, 0)), sponsorPrincipal).getBody().isEmpty());
    assertEquals(CheckInStatus.SUCCESS, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticket.getUuid()), sponsorPrincipal).getBody().getResult().getStatus());
    assertEquals(1, attendeeApiController.getScannedBadges(event.getShortName(), EventUtil.JSON_DATETIME_FORMATTER.format(LocalDateTime.of(1970, 1, 1, 0, 0)), sponsorPrincipal).getBody().size());
    // 
    eventManager.deleteEvent(event.getId(), principal.getName());
}
Also used : ScanAudit(alfio.model.audit.ScanAudit) UsersApiController(alfio.controller.api.admin.UsersApiController) ReservationForm(alfio.controller.form.ReservationForm) alfio.manager(alfio.manager) TicketReservationModification(alfio.model.modification.TicketReservationModification) TicketCategoryRepository(alfio.repository.TicketCategoryRepository) PaymentProxy(alfio.model.transaction.PaymentProxy) TestingAuthenticationToken(org.springframework.security.authentication.TestingAuthenticationToken) PaymentForm(alfio.controller.form.PaymentForm) Autowired(org.springframework.beans.factory.annotation.Autowired) TicketAndCheckInResult(alfio.manager.support.TicketAndCheckInResult) ActiveProfiles(org.springframework.test.context.ActiveProfiles) ServletWebRequest(org.springframework.web.context.request.ServletWebRequest) BigDecimal(java.math.BigDecimal) Json(alfio.util.Json) Model(org.springframework.ui.Model) SpringJUnit4ClassRunner(org.springframework.test.context.junit4.SpringJUnit4ClassRunner) Pair(org.apache.commons.lang3.tuple.Pair) LocalTime(java.time.LocalTime) RedirectAttributesModelMap(org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap) TypeReference(com.fasterxml.jackson.core.type.TypeReference) RedirectAttributes(org.springframework.web.servlet.mvc.support.RedirectAttributes) SerializablePair(alfio.controller.api.admin.SerializablePair) OrganizationRepository(alfio.repository.user.OrganizationRepository) TemplateManager(alfio.util.TemplateManager) MockHttpServletRequest(org.springframework.mock.web.MockHttpServletRequest) EventRepository(alfio.repository.EventRepository) ConfigurationRepository(alfio.repository.system.ConfigurationRepository) Configuration(org.springframework.context.annotation.Configuration) Initializer(alfio.config.Initializer) CheckInStatus(alfio.manager.support.CheckInStatus) Principal(java.security.Principal) alfio.model(alfio.model) CheckInApiController(alfio.controller.api.admin.CheckInApiController) LocalDate(java.time.LocalDate) DataSourceConfiguration(alfio.config.DataSourceConfiguration) java.util(java.util) TicketHelper(alfio.controller.api.support.TicketHelper) ScanAuditRepository(alfio.repository.audit.ScanAuditRepository) BeforeClass(org.junit.BeforeClass) TicketDecorator(alfio.controller.support.TicketDecorator) TicketReservationRepository(alfio.repository.TicketReservationRepository) CSVReader(com.opencsv.CSVReader) RunWith(org.junit.runner.RunWith) LocalDateTime(java.time.LocalDateTime) BindingResult(org.springframework.validation.BindingResult) MockHttpServletResponse(org.springframework.mock.web.MockHttpServletResponse) DateTimeModification(alfio.model.modification.DateTimeModification) RepositoryConfiguration(alfio.config.RepositoryConfiguration) AttendeeApiController(alfio.controller.api.AttendeeApiController) UserModification(alfio.model.modification.UserModification) BeanPropertyBindingResult(org.springframework.validation.BeanPropertyBindingResult) Before(org.junit.Before) BindingAwareModelMap(org.springframework.validation.support.BindingAwareModelMap) EventApiController(alfio.controller.api.admin.EventApiController) TicketCategoryModification(alfio.model.modification.TicketCategoryModification) UpdateTicketOwnerForm(alfio.controller.form.UpdateTicketOwnerForm) Test(org.junit.Test) IOException(java.io.IOException) IntegrationTestUtil(alfio.test.util.IntegrationTestUtil) ComponentScan(org.springframework.context.annotation.ComponentScan) DateUtils(org.apache.commons.lang3.time.DateUtils) Mockito(org.mockito.Mockito) StringReader(java.io.StringReader) ReservationApiController(alfio.controller.api.ReservationApiController) UserManager(alfio.manager.user.UserManager) ContextConfiguration(org.springframework.test.context.ContextConfiguration) EventUtil(alfio.util.EventUtil) I18nManager(alfio.manager.i18n.I18nManager) TestConfiguration(alfio.TestConfiguration) DigestUtils(org.apache.commons.codec.digest.DigestUtils) Assert(org.junit.Assert) ConfigurationKeys(alfio.model.system.ConfigurationKeys) Transactional(org.springframework.transaction.annotation.Transactional) UpdateTicketOwnerForm(alfio.controller.form.UpdateTicketOwnerForm) AttendeeApiController(alfio.controller.api.AttendeeApiController) TicketAndCheckInResult(alfio.manager.support.TicketAndCheckInResult) MockHttpServletResponse(org.springframework.mock.web.MockHttpServletResponse) TicketDecorator(alfio.controller.support.TicketDecorator) BeanPropertyBindingResult(org.springframework.validation.BeanPropertyBindingResult) MockHttpServletRequest(org.springframework.mock.web.MockHttpServletRequest) BindingAwareModelMap(org.springframework.validation.support.BindingAwareModelMap) CheckInApiController(alfio.controller.api.admin.CheckInApiController) TestingAuthenticationToken(org.springframework.security.authentication.TestingAuthenticationToken) LocalDate(java.time.LocalDate) UsersApiController(alfio.controller.api.admin.UsersApiController) UserModification(alfio.model.modification.UserModification) RedirectAttributesModelMap(org.springframework.web.servlet.mvc.support.RedirectAttributesModelMap) BindingAwareModelMap(org.springframework.validation.support.BindingAwareModelMap) Principal(java.security.Principal) ScanAudit(alfio.model.audit.ScanAudit) Test(org.junit.Test)

Example 2 with TicketAndCheckInResult

use of alfio.manager.support.TicketAndCheckInResult in project alf.io by alfio-event.

the class BaseReservationFlowTest method testBasicFlow.

protected void testBasicFlow(Supplier<ReservationFlowContext> contextSupplier) throws Exception {
    // as soon as the test starts, insert the extension in the database (prepare the environment)
    try (var extensionInputStream = requireNonNull(getClass().getResourceAsStream("/extension.js"))) {
        List<String> extensionStream = IOUtils.readLines(new InputStreamReader(extensionInputStream, StandardCharsets.UTF_8));
        String concatenation = String.join("\n", extensionStream).replace("EVENTS", Arrays.stream(ExtensionEvent.values()).map(ee -> "'" + ee.name() + "'").collect(Collectors.joining(",")));
        extensionService.createOrUpdate(null, null, new Extension("-", "syncName", concatenation.replace("placeHolder", "false"), true));
        extensionService.createOrUpdate(null, null, new Extension("-", "asyncName", concatenation.replace("placeHolder", "true"), true));
    }
    List<BasicEventInfo> body = eventApiV2Controller.listEvents(SearchOptions.empty()).getBody();
    assertNotNull(body);
    assertTrue(body.isEmpty());
    cleanupExtensionLog();
    var context = contextSupplier.get();
    ensureConfiguration(context);
    // check if EVENT_CREATED was logged
    List<ExtensionLog> extLogs = extensionLogRepository.getPage(null, null, null, 100, 0);
    assertEventLogged(extLogs, EVENT_METADATA_UPDATE, 8);
    assertEventLogged(extLogs, EVENT_CREATED, 8);
    {
        Principal p = Mockito.mock(Principal.class);
        Mockito.when(p.getName()).thenReturn(context.userId);
        assertTrue(usersApiController.getAllOrganizations(p).stream().anyMatch(o -> context.event.getOrganizationId() == o.getId()));
        assertEquals(context.event.getOrganizationId(), usersApiController.getOrganization(context.event.getOrganizationId(), p).getId());
    }
    // 
    assertEquals(ContentLanguage.ALL_LANGUAGES.size(), translationsApiController.getSupportedLanguages().size());
    assertEquals("or", translationsApiController.getPublicTranslations("en", true).get("common.or"));
    assertEquals("o", translationsApiController.getPublicTranslations("it", true).get("common.or"));
    assertEquals("oder", translationsApiController.getPublicTranslations("de", true).get("common.or"));
    // check all public translations
    ContentLanguage.ALL_LANGUAGES.forEach(cl -> assertFalse(translationsApiController.getPublicTranslations(cl.getLanguage(), true).isEmpty()));
    var alfioInfo = infoApiController.getInfo(new MockHttpSession());
    assertFalse(alfioInfo.isDemoModeEnabled());
    assertTrue(alfioInfo.isDevModeEnabled());
    assertFalse(alfioInfo.isProdModeEnabled());
    assertTrue(alfioInfo.getAnalyticsConfiguration().isGoogleAnalyticsScrambledInfo());
    assertNull(alfioInfo.getAnalyticsConfiguration().getGoogleAnalyticsKey());
    assertNull(alfioInfo.getAnalyticsConfiguration().getClientId());
    // 
    assertEquals("Switzerland", translationsApiController.getCountries("en").stream().filter(c -> "CH".equals(c.getIsoCode())).findFirst().get().getName());
    assertEquals("Greece", translationsApiController.getCountries("en").stream().filter(c -> "GR".equals(c.getIsoCode())).findFirst().get().getName());
    assertEquals("Suisse", translationsApiController.getCountries("fr").stream().filter(c -> "CH".equals(c.getIsoCode())).findFirst().get().getName());
    assertEquals("Svizzera", translationsApiController.getCountries("it").stream().filter(c -> "CH".equals(c.getIsoCode())).findFirst().get().getName());
    assertEquals("Schweiz", translationsApiController.getCountries("de").stream().filter(c -> "CH".equals(c.getIsoCode())).findFirst().get().getName());
    // EL -> greece for vat
    assertEquals("Greece", translationsApiController.getCountriesForVat("en").stream().filter(c -> "EL".equals(c.getIsoCode())).findFirst().get().getName());
    // 
    assertEquals(27, translationsApiController.getEuCountriesForVat("en").size());
    // 
    assertTrue(requireNonNull(eventApiV2Controller.listEvents(SearchOptions.empty()).getBody()).isEmpty());
    // 
    List<EventStatistic> eventStatistic = eventStatisticsManager.getAllEventsWithStatistics(context.userId);
    assertEquals(1, eventStatistic.size());
    var statisticsFrom = ZonedDateTime.now(context.event.getZoneId()).minusYears(1);
    var statisticsTo = ZonedDateTime.now(context.event.getZoneId()).plusDays(1);
    assertEquals(0L, eventStatisticsManager.getTicketSoldStatistics(context.event.getId(), statisticsFrom, statisticsTo, "day").stream().mapToLong(TicketsByDateStatistic::getCount).sum());
    EventWithAdditionalInfo eventWithAdditionalInfo = eventStatisticsManager.getEventWithAdditionalInfo(context.event.getShortName(), context.userId);
    // <- 2 tickets are the bounded category
    assertEquals(2, eventWithAdditionalInfo.getNotSoldTickets());
    assertEquals(0, eventWithAdditionalInfo.getSoldTickets());
    assertEquals(20, eventWithAdditionalInfo.getAvailableSeats());
    // 
    // publish the event
    eventManager.toggleActiveFlag(context.event.getId(), context.userId, true);
    // 
    var resListEvents = eventApiV2Controller.listEvents(SearchOptions.empty());
    var events = resListEvents.getBody();
    assertEquals(HttpStatus.OK, resListEvents.getStatusCode());
    assertNotNull(events);
    assertEquals(1, events.size());
    assertEquals(context.event.getShortName(), events.get(0).getShortName());
    // 
    assertEquals(HttpStatus.NOT_FOUND, eventApiV2Controller.getEvent("NOT_EXISTS", new MockHttpSession()).getStatusCode());
    // 
    var eventRes = eventApiV2Controller.getEvent(context.event.getShortName(), new MockHttpSession());
    assertEquals(HttpStatus.OK, eventRes.getStatusCode());
    var selectedEvent = eventRes.getBody();
    assertNotNull(selectedEvent);
    assertEquals("CHF", selectedEvent.getCurrency());
    assertFalse(selectedEvent.isFree());
    assertEquals(context.event.getSameDay(), selectedEvent.isSameDay());
    assertTrue(selectedEvent.isVatIncluded());
    assertEquals(context.event.getShortName(), selectedEvent.getShortName());
    assertEquals(context.event.getDisplayName(), selectedEvent.getDisplayName());
    assertEquals(context.event.getFileBlobId(), selectedEvent.getFileBlobId());
    assertTrue(selectedEvent.getI18nOverride().isEmpty());
    configurationRepository.insert("TRANSLATION_OVERRIDE", Json.toJson(Map.of("en", Map.of("show-context.event.tickets.left", "{0} left!"))), "");
    configurationRepository.insertEventLevel(context.event.getOrganizationId(), context.event.getId(), "TRANSLATION_OVERRIDE", Json.toJson(Map.of("en", Map.of("common.vat", "context.event.vat"))), "");
    eventRes = eventApiV2Controller.getEvent(context.event.getShortName(), new MockHttpSession());
    selectedEvent = eventRes.getBody();
    assertNotNull(selectedEvent);
    assertFalse(selectedEvent.getI18nOverride().isEmpty());
    assertEquals("context.event.vat", selectedEvent.getI18nOverride().get("en").get("common.vat"));
    assertEquals("{{0}} left!", selectedEvent.getI18nOverride().get("en").get("show-context.event.tickets.left"));
    checkCalendar(context.event.getShortName());
    // it, en, de
    assertEquals(3, selectedEvent.getContentLanguages().size());
    assertEquals(selectedEvent.getContentLanguages().stream().map(Language::getLocale).collect(Collectors.toSet()), Set.of("it", "en", "de"));
    // check if for each language we have the expected locale dependent entries
    for (String lang : Arrays.asList("it", "en", "de")) {
        assertNotNull(selectedEvent.getDescription().get(lang));
        // 
        assertNotNull(selectedEvent.getFormattedBeginDate().get(lang));
        assertNotNull(selectedEvent.getFormattedBeginTime().get(lang));
        assertNotNull(selectedEvent.getFormattedEndDate().get(lang));
        assertNotNull(selectedEvent.getFormattedEndTime().get(lang));
    }
    assertEquals("redirect:/api/v2/public/event/" + context.event.getShortName() + "/code/MY_CODE", indexController.redirectCode(context.event.getShortName(), "MY_CODE"));
    // check open graph & co
    {
        var res = new MockHttpServletResponse();
        indexController.replyToIndex(context.event.getShortName(), null, "not a social share", "en", new ServletWebRequest(new MockHttpServletRequest()), res, new MockHttpSession());
        var htmlParser = new Parser();
        var docWithoutOpenGraph = htmlParser.parse(new String(res.getContentAsByteArray(), StandardCharsets.UTF_8));
        assertTrue(docWithoutOpenGraph.getAllNodesMatching(Selector.select().element("meta").attrValEq("name", "twitter:card").toMatcher()).isEmpty());
        res = new MockHttpServletResponse();
        indexController.replyToIndex(context.event.getShortName(), null, "Twitterbot/42", "en", new ServletWebRequest(new MockHttpServletRequest()), res, new MockHttpSession());
        var docWithOpenGraph = htmlParser.parse(new String(res.getContentAsByteArray(), StandardCharsets.UTF_8));
        assertFalse(docWithOpenGraph.getAllNodesMatching(Selector.select().element("meta").attrValEq("name", "twitter:card").toMatcher()).isEmpty());
        var title = (Element) docWithOpenGraph.getAllNodesMatching(Selector.select().element("meta").attrValEq("property", "og:title").toMatcher(), true).get(0);
        assertEquals("Get your tickets for " + context.event.getDisplayName(), title.getAttribute("content"));
    }
    // 
    // check ticket & all, we have 2 ticket categories, 1 hidden
    assertEquals(HttpStatus.NOT_FOUND, eventApiV2Controller.getTicketCategories("NOT_EXISTING", null).getStatusCode());
    {
        var itemsRes = eventApiV2Controller.getTicketCategories(context.event.getShortName(), null);
        assertEquals(HttpStatus.OK, itemsRes.getStatusCode());
        var items = itemsRes.getBody();
        assertNotNull(items);
        assertEquals(1, items.getTicketCategories().size());
        var visibleCat = items.getTicketCategories().get(0);
        assertEquals("default", visibleCat.getName());
        assertEquals("10.00", visibleCat.getFormattedFinalPrice());
        assertFalse(visibleCat.isHasDiscount());
        assertEquals(1, items.getAdditionalServices().size());
        var additionalItem = items.getAdditionalServices().get(0);
        assertEquals("40.00", additionalItem.getFormattedFinalPrice());
        assertEquals("1.00", additionalItem.getVatPercentage());
        // TODO: check: if there are missing lang, we should at least copy them (?)
        assertEquals(1, additionalItem.getTitle().size());
        assertEquals(1, additionalItem.getDescription().size());
        assertEquals("additional title", additionalItem.getTitle().get("en"));
        assertEquals("<p>additional desc</p>\n", additionalItem.getDescription().get("en"));
        // check presence of reservation list
        assertFalse(items.isWaitingList());
        assertFalse(items.isPreSales());
        // 
        // fix dates to enable reservation list
        var tc = ticketCategoryRepository.getById(visibleCat.getId());
        ticketCategoryRepository.fixDates(visibleCat.getId(), tc.getInception(context.event.getZoneId()).plusDays(2), tc.getExpiration(context.event.getZoneId()));
        // 
        items = eventApiV2Controller.getTicketCategories(context.event.getShortName(), null).getBody();
        assertNotNull(items);
        assertTrue(items.isWaitingList());
        assertTrue(items.isPreSales());
        // 
        var subForm = new WaitingQueueSubscriptionForm();
        subForm.setFirstName("first");
        subForm.setLastName("last");
        subForm.setPrivacyPolicyAccepted(true);
        subForm.setTermAndConditionsAccepted(true);
        subForm.setUserLanguage(Locale.ENGLISH);
        var subRes = eventApiV2Controller.subscribeToWaitingList(context.event.getShortName(), subForm, new BeanPropertyBindingResult(subForm, "subForm"));
        assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, subRes.getStatusCode());
        assertNotNull(subRes.getBody());
        assertFalse(subRes.getBody().isSuccess());
        assertEquals(1, subRes.getBody().getValidationErrors().size());
        assertEquals("email", subRes.getBody().getValidationErrors().get(0).getFieldName());
        assertEquals("error.email", subRes.getBody().getValidationErrors().get(0).getCode());
        // 
        subForm.setEmail("email@email.com");
        subRes = eventApiV2Controller.subscribeToWaitingList(context.event.getShortName(), subForm, new BeanPropertyBindingResult(subForm, "subForm"));
        assertEquals(HttpStatus.OK, subRes.getStatusCode());
        assertNotNull(subRes.getBody());
        assertTrue(subRes.getBody().isSuccess());
        assertEquals(0, subRes.getBody().getErrorCount());
        assertTrue(subRes.getBody().getValue());
        // 
        ticketCategoryRepository.fixDates(visibleCat.getId(), tc.getInception(context.event.getZoneId()).minusDays(2), tc.getExpiration(context.event.getZoneId()));
    }
    // dynamic promo codes can be applied only automatically
    {
        eventManager.addPromoCode("DYNAMIC_CODE", context.event.getId(), null, ZonedDateTime.now(clockProvider.getClock()).minusDays(2), context.event.getEnd().plusDays(2), 10, PromoCodeDiscount.DiscountType.PERCENTAGE, null, 3, "description", "test@test.ch", PromoCodeDiscount.CodeType.DYNAMIC, null);
        assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, eventApiV2Controller.validateCode(context.event.getShortName(), "DYNAMIC_CODE").getStatusCode());
        // try to enter it anyway
        var form = new ReservationForm();
        var ticketReservation = new TicketReservationModification();
        form.setPromoCode("DYNAMIC_CODE");
        ticketReservation.setQuantity(1);
        ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(context.event.getShortName(), null).getBody().getTicketCategories().get(0).getId());
        form.setReservation(Collections.singletonList(ticketReservation));
        var res = eventApiV2Controller.reserveTickets(context.event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), null);
        assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, res.getStatusCode());
    }
    // hidden category check
    {
        assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, eventApiV2Controller.validateCode(context.event.getShortName(), "NOT_EXISTING").getStatusCode());
        var hiddenCodeRes = eventApiV2Controller.validateCode(context.event.getShortName(), HIDDEN_CODE);
        assertEquals(HttpStatus.OK, hiddenCodeRes.getStatusCode());
        var hiddenCode = hiddenCodeRes.getBody();
        assertNotNull(hiddenCode);
        assertEquals(EventCode.EventCodeType.ACCESS, hiddenCode.getValue().getType());
        var itemsRes2 = eventApiV2Controller.getTicketCategories(context.event.getShortName(), HIDDEN_CODE);
        var items2 = itemsRes2.getBody();
        assertNotNull(items2);
        assertEquals(2, items2.getTicketCategories().size());
        var hiddenCat = items2.getTicketCategories().stream().filter(t -> t.isAccessRestricted()).findFirst().get();
        assertEquals(hiddenCategoryId, hiddenCat.getId());
        assertEquals("hidden", hiddenCat.getName());
        assertEquals("1.00", hiddenCat.getFormattedFinalPrice());
        assertFalse(hiddenCat.isHasDiscount());
        assertTrue(hiddenCat.isAccessRestricted());
        // do a reservation for a hidden category+cancel
        var form = new ReservationForm();
        var ticketReservation = new TicketReservationModification();
        form.setPromoCode(HIDDEN_CODE);
        ticketReservation.setQuantity(1);
        ticketReservation.setTicketCategoryId(hiddenCat.getId());
        form.setReservation(Collections.singletonList(ticketReservation));
        var res = eventApiV2Controller.reserveTickets(context.event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), context.getPublicUser());
        assertEquals(HttpStatus.OK, res.getStatusCode());
        assertNotNull(res.getBody());
        var reservationInfo = reservationApiV2Controller.getReservationInfo(res.getBody().getValue(), context.getPublicUser());
        assertEquals(HttpStatus.OK, reservationInfo.getStatusCode());
        assertNotNull(reservationInfo.getBody());
        assertEquals("1.00", reservationInfo.getBody().getOrderSummary().getTotalPrice());
        assertEquals("hidden", reservationInfo.getBody().getOrderSummary().getSummary().get(0).getName());
        var activePaymentMethods = reservationInfo.getBody().getActivePaymentMethods();
        assertFalse(activePaymentMethods.isEmpty());
        assertTrue(activePaymentMethods.containsKey(PaymentMethod.BANK_TRANSFER));
        configurationRepository.insertTicketCategoryLevel(context.event.getOrganizationId(), context.event.getId(), hiddenCategoryId, ConfigurationKeys.PAYMENT_METHODS_BLACKLIST.name(), PaymentProxy.OFFLINE.name(), "");
        reservationInfo = reservationApiV2Controller.getReservationInfo(res.getBody().getValue(), context.getPublicUser());
        assertNotNull(reservationInfo.getBody());
        activePaymentMethods = reservationInfo.getBody().getActivePaymentMethods();
        assertTrue(activePaymentMethods.isEmpty());
        configurationRepository.deleteCategoryLevelByKey(ConfigurationKeys.PAYMENT_METHODS_BLACKLIST.name(), context.event.getId(), hiddenCategoryId);
        // clear the extension_log table so that we can check the very next additions
        // cannot have just one row in the log, every event adds EXACTLY two logs
        // log expected: RESERVATION_CANCELLED
        cleanupExtensionLog();
        reservationApiV2Controller.cancelPendingReservation(res.getBody().getValue());
        extLogs = extensionLogRepository.getPage(null, null, null, 100, 0);
        assertEventLogged(extLogs, RESERVATION_CANCELLED, 2);
        assertEquals(0, jdbcTemplate.queryForObject("select count(*) from ticket where status = 'FREE' and final_price_cts > 0", Map.of(), Integer.class));
        // this is run by a job, but given the fact that it's in another separate transaction, it cannot work in this test (WaitingQueueSubscriptionProcessor.handleWaitingTickets)
        assertEquals(1, ticketReservationManager.revertTicketsToFreeIfAccessRestricted(context.event.getId()));
    }
    // 
    // check reservation auto creation with code: TODO: will need to check all the flows
    {
        // code not found
        var notFoundRes = eventApiV2Controller.handleCode(context.event.getShortName(), "NOT_EXIST", new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), context.getPublicUser());
        assertEquals("/event/" + context.event.getShortName(), notFoundRes.getHeaders().getLocation().getPath());
        assertEquals("errors=error.STEP_1_CODE_NOT_FOUND", notFoundRes.getHeaders().getLocation().getQuery());
        // 
        // promo code, we expect a redirect to event with the code in the query string
        var redirectPromoCodeRes = eventApiV2Controller.handleCode(context.event.getShortName(), PROMO_CODE, new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), context.getPublicUser());
        assertEquals("/event/" + context.event.getShortName(), redirectPromoCodeRes.getHeaders().getLocation().getPath());
        assertEquals("code=MYPROMOCODE", redirectPromoCodeRes.getHeaders().getLocation().getQuery());
        // code existing
        assertEquals(2, specialPriceRepository.countFreeTokens(hiddenCategoryId).intValue());
        var res = eventApiV2Controller.handleCode(context.event.getShortName(), URL_CODE_HIDDEN, new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), context.getPublicUser());
        var location = requireNonNull(res.getHeaders().getLocation()).toString();
        var reservationId = location.substring(("/event/" + context.event.getShortName() + "/reservation/").length(), location.length() - "/book".length());
        var reservationInfo = reservationApiV2Controller.getReservationInfo(reservationId, context.getPublicUser());
        assertEquals(HttpStatus.OK, reservationInfo.getStatusCode());
        assertNotNull(reservationInfo.getBody());
        assertEquals(reservationId, reservationInfo.getBody().getId());
        assertEquals(1, specialPriceRepository.countFreeTokens(hiddenCategoryId).intValue());
        reservationApiV2Controller.cancelPendingReservation(reservationId);
        assertEquals(2, specialPriceRepository.countFreeTokens(hiddenCategoryId).intValue());
        // this is run by a job, but given the fact that it's in another separate transaction, it cannot work in this test (WaitingQueueSubscriptionProcessor.handleWaitingTickets)
        assertEquals(1, ticketReservationManager.revertTicketsToFreeIfAccessRestricted(context.event.getId()));
    }
    // check reservation auto creation with deletion from the admin side
    {
        assertEquals(2, specialPriceRepository.countFreeTokens(hiddenCategoryId).intValue());
        var res = eventApiV2Controller.handleCode(context.event.getShortName(), URL_CODE_HIDDEN, new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), context.getPublicUser());
        var location = requireNonNull(res.getHeaders().getLocation()).toString();
        var reservationId = location.substring(("/event/" + context.event.getShortName() + "/reservation/").length(), location.length() - "/book".length());
        var reservationInfo = reservationApiV2Controller.getReservationInfo(reservationId, context.getPublicUser());
        assertEquals(HttpStatus.OK, reservationInfo.getStatusCode());
        assertNotNull(reservationInfo.getBody());
        assertEquals(reservationId, reservationInfo.getBody().getId());
        assertEquals(1, reservationInfo.getBody().getActivePaymentMethods().size());
        assertTrue(reservationInfo.getBody().getActivePaymentMethods().containsKey(PaymentMethod.BANK_TRANSFER));
        assertEquals(1, specialPriceRepository.countFreeTokens(hiddenCategoryId).intValue());
        adminReservationManager.removeReservation(PurchaseContextType.event, context.event.getShortName(), reservationId, false, false, false, context.userId);
        assertEquals(2, specialPriceRepository.countFreeTokens(hiddenCategoryId).intValue());
        // this is run by a job, but given the fact that it's in another separate transaction, it cannot work in this test (WaitingQueueSubscriptionProcessor.handleWaitingTickets)
        assertEquals(1, ticketReservationManager.revertTicketsToFreeIfAccessRestricted(context.event.getId()));
    }
    // discount check
    {
        var discountCodeRes = eventApiV2Controller.validateCode(context.event.getShortName(), PROMO_CODE);
        var discountCode = discountCodeRes.getBody();
        assertNotNull(discountCode);
        assertEquals(EventCode.EventCodeType.DISCOUNT, discountCode.getValue().getType());
        var itemsRes3 = eventApiV2Controller.getTicketCategories(context.event.getShortName(), PROMO_CODE);
        var items3 = itemsRes3.getBody();
        assertNotNull(items3);
        assertEquals(1, items3.getTicketCategories().size());
        var visibleCat = items3.getTicketCategories().get(0);
        assertEquals("default", visibleCat.getName());
        assertEquals("10.00", visibleCat.getFormattedFinalPrice());
        assertTrue(visibleCat.isHasDiscount());
        assertEquals("9.00", visibleCat.getFormattedDiscountedPrice());
    }
    // validation error: select at least one
    {
        var form = new ReservationForm();
        var res = eventApiV2Controller.reserveTickets(context.event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), context.getPublicUser());
        assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, res.getStatusCode());
        var resBody = res.getBody();
        assertNotNull(resBody);
        assertFalse(resBody.isSuccess());
        assertEquals(1, resBody.getErrorCount());
    }
    // cancel a reservation
    {
        var form = new ReservationForm();
        var ticketReservation = new TicketReservationModification();
        ticketReservation.setQuantity(1);
        ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(context.event.getShortName(), null).getBody().getTicketCategories().get(0).getId());
        form.setReservation(Collections.singletonList(ticketReservation));
        var res = eventApiV2Controller.reserveTickets(context.event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), context.getPublicUser());
        assertEquals(HttpStatus.OK, res.getStatusCode());
        var resBody = res.getBody();
        assertNotNull(resBody);
        assertTrue(resBody.isSuccess());
        assertEquals(0, resBody.getErrorCount());
        var reservationId = resBody.getValue();
        checkStatus(reservationId, HttpStatus.OK, false, TicketReservation.TicketReservationStatus.PENDING, context);
        var cancelRes = reservationApiV2Controller.cancelPendingReservation(reservationId);
        assertEquals(HttpStatus.OK, cancelRes.getStatusCode());
        checkStatus(reservationId, HttpStatus.NOT_FOUND, null, null, context);
    }
    // check blacklist payment methods
    {
        var form = new ReservationForm();
        var categories = eventApiV2Controller.getTicketCategories(context.event.getShortName(), HIDDEN_CODE).getBody().getTicketCategories();
        var c1 = new TicketReservationModification();
        c1.setQuantity(1);
        int firstCategoryId = categories.get(0).getId();
        c1.setTicketCategoryId(firstCategoryId);
        var c2 = new TicketReservationModification();
        c2.setQuantity(1);
        c2.setTicketCategoryId(categories.get(1).getId());
        form.setReservation(List.of(c1, c2));
        form.setPromoCode(HIDDEN_CODE);
        var res = eventApiV2Controller.reserveTickets(context.event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), context.getPublicUser());
        assertEquals(HttpStatus.OK, res.getStatusCode());
        var resBody = res.getBody();
        assertNotNull(resBody);
        assertTrue(resBody.isSuccess());
        assertEquals(0, resBody.getErrorCount());
        var reservationId = resBody.getValue();
        checkStatus(reservationId, HttpStatus.OK, false, TicketReservation.TicketReservationStatus.PENDING, context);
        var cancelRes = reservationApiV2Controller.cancelPendingReservation(reservationId);
        assertEquals(HttpStatus.OK, cancelRes.getStatusCode());
        checkStatus(reservationId, HttpStatus.NOT_FOUND, null, null, context);
    }
    // buy 2 ticket, with additional service + field
    {
        var form = new ReservationForm();
        var ticketReservation = new TicketReservationModification();
        ticketReservation.setQuantity(2);
        ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(context.event.getShortName(), null).getBody().getTicketCategories().get(0).getId());
        form.setReservation(Collections.singletonList(ticketReservation));
        var additionalService = new AdditionalServiceReservationModification();
        additionalService.setAdditionalServiceId(additionalServiceId);
        additionalService.setQuantity(1);
        form.setAdditionalService(Collections.singletonList(additionalService));
        var res = eventApiV2Controller.reserveTickets(context.event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), context.getPublicUser());
        assertEquals(HttpStatus.OK, res.getStatusCode());
        var resBody = res.getBody();
        assertNotNull(resBody);
        assertTrue(resBody.isSuccess());
        assertEquals(0, resBody.getErrorCount());
        var reservationId = resBody.getValue();
        checkStatus(reservationId, HttpStatus.OK, false, TicketReservation.TicketReservationStatus.PENDING, context);
        var resInfoRes = reservationApiV2Controller.getReservationInfo(reservationId, context.getPublicUser());
        assertEquals(HttpStatus.OK, resInfoRes.getStatusCode());
        assertNotNull(resInfoRes.getBody());
        var ticketsByCat = resInfoRes.getBody().getTicketsByCategory();
        assertEquals(1, ticketsByCat.size());
        assertEquals(2, ticketsByCat.get(0).getTickets().size());
        var ticket1 = ticketsByCat.get(0).getTickets().get(0);
        // 1
        assertEquals(1, ticket1.getTicketFieldConfigurationBeforeStandard().size());
        // 1 + 1 additional service related field (appear only on first ticket)
        assertEquals(2, ticket1.getTicketFieldConfigurationAfterStandard().size());
        var ticket2 = ticketsByCat.get(0).getTickets().get(1);
        // 1
        assertEquals(1, ticket2.getTicketFieldConfigurationBeforeStandard().size());
        // 1
        assertEquals(1, ticket2.getTicketFieldConfigurationAfterStandard().size());
        var contactForm = new ContactAndTicketsForm();
        contactForm.setAddCompanyBillingDetails(true);
        contactForm.setSkipVatNr(false);
        contactForm.setInvoiceRequested(true);
        contactForm.setEmail("test@test.com");
        contactForm.setBillingAddress("my billing address");
        contactForm.setFirstName("full");
        contactForm.setLastName("name");
        var ticketForm1 = new UpdateTicketOwnerForm();
        ticketForm1.setFirstName("ticketfull");
        ticketForm1.setLastName("ticketname");
        ticketForm1.setEmail("tickettest@test.com");
        ticketForm1.setAdditional(new HashMap<>(Map.of("field1", Collections.singletonList("value"))));
        var ticketForm2 = new UpdateTicketOwnerForm();
        ticketForm2.setFirstName("ticketfull");
        ticketForm2.setLastName("ticketname");
        ticketForm2.setEmail("tickettest@test.com");
        ticketForm2.setAdditional(Map.of("field1", Collections.singletonList("value")));
        contactForm.setTickets(Map.of(ticket1.getUuid(), ticketForm1, ticket2.getUuid(), ticketForm2));
        var failure = reservationApiV2Controller.validateToOverview(reservationId, "en", false, contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), context.getPublicAuthentication());
        assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, failure.getStatusCode());
        assertNotNull(failure.getBody());
        // <- missing mandatory
        assertEquals(1, failure.getBody().getValidationErrors().stream().filter(f -> f.getFieldName().equals("tickets[" + ticket1.getUuid() + "].additional[field3][0]")).count());
        // check billing errors
        // <- missing mandatory
        assertEquals(1, failure.getBody().getValidationErrors().stream().filter(f -> f.getFieldName().equals("billingAddressLine1")).count());
        // <- missing mandatory
        assertEquals(1, failure.getBody().getValidationErrors().stream().filter(f -> f.getFieldName().equals("billingAddressZip")).count());
        // <- missing mandatory
        assertEquals(1, failure.getBody().getValidationErrors().stream().filter(f -> f.getFieldName().equals("billingAddressCity")).count());
        // <- missing mandatory
        assertEquals(1, failure.getBody().getValidationErrors().stream().filter(f -> f.getFieldName().equals("vatCountryCode")).count());
        // 
        contactForm.setVatCountryCode("CH");
        contactForm.setBillingAddressLine1("LINE 1");
        contactForm.setBillingAddressCity("CITY");
        contactForm.setBillingAddressZip("ZIP");
        ticketForm1.getAdditional().put("field3", Collections.singletonList("missing value"));
        var success = reservationApiV2Controller.validateToOverview(reservationId, "en", false, contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), context.getPublicAuthentication());
        assertEquals(HttpStatus.OK, success.getStatusCode());
        reservationApiV2Controller.cancelPendingReservation(reservationId);
    }
    // buy one ticket, without discount
    {
        var form = new ReservationForm();
        var ticketReservation = new TicketReservationModification();
        ticketReservation.setQuantity(1);
        ticketReservation.setTicketCategoryId(eventApiV2Controller.getTicketCategories(context.event.getShortName(), null).getBody().getTicketCategories().get(0).getId());
        form.setReservation(Collections.singletonList(ticketReservation));
        var res = eventApiV2Controller.reserveTickets(context.event.getShortName(), "en", form, new BeanPropertyBindingResult(form, "reservation"), new ServletWebRequest(new MockHttpServletRequest(), new MockHttpServletResponse()), context.getPublicUser());
        assertEquals(HttpStatus.OK, res.getStatusCode());
        var resBody = res.getBody();
        assertNotNull(resBody);
        assertTrue(resBody.isSuccess());
        assertEquals(0, resBody.getErrorCount());
        var reservationId = resBody.getValue();
        checkStatus(reservationId, HttpStatus.OK, false, TicketReservation.TicketReservationStatus.PENDING, context);
        var resInfoRes = reservationApiV2Controller.getReservationInfo(reservationId, context.getPublicUser());
        assertEquals(HttpStatus.OK, resInfoRes.getStatusCode());
        var reservation = resInfoRes.getBody();
        assertNotNull(reservation);
        assertEquals(reservationId, reservation.getId());
        assertEquals(1, reservation.getTicketsByCategory().size());
        assertEquals(1, reservation.getTicketsByCategory().get(0).getTickets().size());
        var selectedTicket = reservation.getTicketsByCategory().get(0).getTickets().get(0);
        assertEquals("field1", selectedTicket.getTicketFieldConfigurationBeforeStandard().get(0).getName());
        assertTrue(selectedTicket.getTicketFieldConfigurationBeforeStandard().get(0).isRequired());
        assertEquals("field2", selectedTicket.getTicketFieldConfigurationAfterStandard().get(0).getName());
        assertFalse(selectedTicket.getTicketFieldConfigurationAfterStandard().get(0).isRequired());
        var contactForm = new ContactAndTicketsForm();
        var validationErrorsRes = reservationApiV2Controller.validateToOverview(reservationId, "en", false, contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), context.getPublicAuthentication());
        assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, validationErrorsRes.getStatusCode());
        assertNotNull(validationErrorsRes.getBody());
        assertFalse(validationErrorsRes.getBody().isSuccess());
        // first name, last name, email + MISSING_ATTENDEE DATA
        assertEquals(4, validationErrorsRes.getBody().getErrorCount());
        // move to overview status
        contactForm = new ContactAndTicketsForm();
        contactForm.setEmail("test@test.com");
        contactForm.setBillingAddress("my billing address");
        contactForm.setFirstName("full");
        contactForm.setLastName("name");
        var ticketForm = new UpdateTicketOwnerForm();
        ticketForm.setFirstName("ticketfull");
        ticketForm.setLastName("ticketname");
        ticketForm.setEmail("tickettest@test.com");
        contactForm.setTickets(Collections.singletonMap(reservation.getTicketsByCategory().get(0).getTickets().get(0).getUuid(), ticketForm));
        var overviewResFailed = reservationApiV2Controller.validateToOverview(reservationId, "en", false, contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), context.getPublicAuthentication());
        assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, overviewResFailed.getStatusCode());
        checkStatus(reservationId, HttpStatus.OK, false, TicketReservation.TicketReservationStatus.PENDING, context);
        // add mandatory additional field
        ticketForm.setAdditional(Collections.singletonMap("field1", Collections.singletonList("value")));
        var overviewRes = reservationApiV2Controller.validateToOverview(reservationId, "en", false, contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), context.getPublicAuthentication());
        assertEquals(HttpStatus.OK, overviewRes.getStatusCode());
        checkStatus(reservationId, HttpStatus.OK, true, TicketReservation.TicketReservationStatus.PENDING, context);
        // 
        reservationApiV2Controller.backToBooking(reservationId);
        checkStatus(reservationId, HttpStatus.OK, false, TicketReservation.TicketReservationStatus.PENDING, context);
        overviewRes = reservationApiV2Controller.validateToOverview(reservationId, "en", false, contactForm, new BeanPropertyBindingResult(contactForm, "paymentForm"), context.getPublicAuthentication());
        assertNotNull(overviewRes.getBody());
        assertTrue(overviewRes.getBody().getValue());
        checkStatus(reservationId, HttpStatus.OK, true, TicketReservation.TicketReservationStatus.PENDING, context);
        var owner = ticketReservationRepository.getReservationOwnerAndOrganizationId(reservationId);
        if (context.publicUserId != null) {
            assertTrue(owner.isPresent());
            assertEquals(context.publicUserId, owner.get().getUserId());
            // make sure that the profile has been persisted
            var optionalProfile = userRepository.loadUserProfile(context.publicUserId);
            assertTrue(optionalProfile.isPresent());
            // access to the reservation must be denied for anonymous users
            assertThrows(ReservationAccessDenied.class, () -> reservationApiV2Controller.getReservationInfo(reservationId, null));
        } else {
            assertTrue(owner.isEmpty());
        }
        var paymentForm = new PaymentForm();
        var handleResError = reservationApiV2Controller.confirmOverview(reservationId, "en", paymentForm, new BeanPropertyBindingResult(paymentForm, "paymentForm"), new MockHttpServletRequest(), context.getPublicUser());
        assertEquals(HttpStatus.UNPROCESSABLE_ENTITY, handleResError.getStatusCode());
        paymentForm.setPrivacyPolicyAccepted(true);
        paymentForm.setTermAndConditionsAccepted(true);
        paymentForm.setPaymentProxy(PaymentProxy.OFFLINE);
        paymentForm.setSelectedPaymentMethod(PaymentMethod.BANK_TRANSFER);
        // bank transfer does not have a transaction, it's created on confirmOverview call
        var tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, "BANK_TRANSFER");
        assertEquals(HttpStatus.NOT_FOUND, tStatus.getStatusCode());
        // 
        var handleRes = reservationApiV2Controller.confirmOverview(reservationId, "en", paymentForm, new BeanPropertyBindingResult(paymentForm, "paymentForm"), new MockHttpServletRequest(), context.getPublicUser());
        assertEquals(HttpStatus.OK, handleRes.getStatusCode());
        checkStatus(reservationId, HttpStatus.OK, true, TicketReservation.TicketReservationStatus.OFFLINE_PAYMENT, context);
        tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, "BANK_TRANSFER");
        assertEquals(HttpStatus.OK, tStatus.getStatusCode());
        assertNotNull(tStatus.getBody());
        assertFalse(tStatus.getBody().isSuccess());
        reservation = reservationApiV2Controller.getReservationInfo(reservationId, context.getPublicUser()).getBody();
        assertNotNull(reservation);
        var orderSummary = reservation.getOrderSummary();
        assertTrue(orderSummary.isNotYetPaid());
        assertEquals("10.00", orderSummary.getTotalPrice());
        assertEquals("0.10", orderSummary.getTotalVAT());
        assertEquals("1.00", orderSummary.getVatPercentage());
        // clear the extension_log table so that we can check the expectation
        cleanupExtensionLog();
        validatePayment(context.event.getShortName(), reservationId, context);
        extLogs = extensionLogRepository.getPage(null, null, null, 100, 0);
        boolean online = containsOnlineTickets(context, reservationId);
        assertEventLogged(extLogs, RESERVATION_CONFIRMED, online ? 12 : 10);
        assertEventLogged(extLogs, CONFIRMATION_MAIL_CUSTOM_TEXT, online ? 12 : 10);
        assertEventLogged(extLogs, TICKET_ASSIGNED, online ? 12 : 10);
        if (online) {
            assertEventLogged(extLogs, CUSTOM_ONLINE_JOIN_URL, 12);
        }
        assertEventLogged(extLogs, TICKET_ASSIGNED_GENERATE_METADATA, online ? 12 : 10);
        assertEventLogged(extLogs, TICKET_MAIL_CUSTOM_TEXT, online ? 12 : 10);
        checkStatus(reservationId, HttpStatus.OK, true, TicketReservation.TicketReservationStatus.COMPLETE, context);
        tStatus = reservationApiV2Controller.getTransactionStatus(reservationId, "BANK_TRANSFER");
        assertEquals(HttpStatus.OK, tStatus.getStatusCode());
        assertNotNull(tStatus.getBody());
        assertTrue(tStatus.getBody().isSuccess());
        reservation = reservationApiV2Controller.getReservationInfo(reservationId, context.getPublicUser()).getBody();
        assertNotNull(reservation);
        orderSummary = reservation.getOrderSummary();
        assertFalse(orderSummary.isNotYetPaid());
        var confRes = reservationApiV2Controller.reSendReservationConfirmationEmail(PurchaseContextType.event, context.event.getShortName(), reservationId, "en", context.getPublicUser());
        assertEquals(HttpStatus.OK, confRes.getStatusCode());
        assertNotNull(confRes.getBody());
        assertTrue(confRes.getBody());
        // trigger email processing
        triggerEmailProcessingAndCheck(context, reservationId);
        var ticket = reservation.getTicketsByCategory().stream().findFirst().orElseThrow().getTickets().get(0);
        assertEquals("tickettest@test.com", ticket.getEmail());
        assertEquals("ticketfull", ticket.getFirstName());
        assertEquals("ticketname", ticket.getLastName());
        var ticketNotFoundRes = ticketApiV2Controller.getTicketInfo(context.event.getShortName(), "DONT_EXISTS");
        assertEquals(HttpStatus.NOT_FOUND, ticketNotFoundRes.getStatusCode());
        var ticketFoundRes = ticketApiV2Controller.getTicketInfo(context.event.getShortName(), ticket.getUuid());
        assertEquals(HttpStatus.OK, ticketFoundRes.getStatusCode());
        var ticketFoundBody = ticketFoundRes.getBody();
        assertNotNull(ticketFoundBody);
        assertEquals("tickettest@test.com", ticketFoundBody.getEmail());
        assertEquals("ticketfull ticketname", ticketFoundBody.getFullName());
        assertEquals("full name", ticketFoundBody.getReservationFullName());
        assertTrue(reservationId.startsWith(ticketFoundBody.getReservationId().toLowerCase(Locale.ENGLISH)));
        var sendTicketByEmailRes = ticketApiV2Controller.sendTicketByEmail(context.event.getShortName(), ticket.getUuid());
        assertEquals(HttpStatus.OK, sendTicketByEmailRes.getStatusCode());
        assertNotNull(sendTicketByEmailRes.getBody());
        assertTrue(sendTicketByEmailRes.getBody());
        // trigger email processing
        triggerEmailProcessingAndCheck(context, reservationId);
        // update ticket
        var updateTicketOwnerForm = new UpdateTicketOwnerForm();
        updateTicketOwnerForm.setFirstName("Test");
        updateTicketOwnerForm.setLastName("Testson");
        updateTicketOwnerForm.setEmail("testmctest@test.com");
        updateTicketOwnerForm.setAdditional(Collections.singletonMap("field1", Collections.singletonList("value")));
        var updateTicketRes = ticketApiV2Controller.updateTicketInfo(context.event.getShortName(), ticket.getUuid(), updateTicketOwnerForm, new BeanPropertyBindingResult(updateTicketOwnerForm, "ticket"), context.getPublicAuthentication());
        assertNotNull(updateTicketRes.getBody());
        assertTrue(updateTicketRes.getBody().isSuccess());
        // not found
        assertEquals(HttpStatus.NOT_FOUND, ticketApiV2Controller.updateTicketInfo(context.event.getShortName(), ticket.getUuid() + "42", updateTicketOwnerForm, new BeanPropertyBindingResult(updateTicketOwnerForm, "ticket"), context.getPublicAuthentication()).getStatusCode());
        ticketFoundRes = ticketApiV2Controller.getTicketInfo(context.event.getShortName(), ticket.getUuid());
        ticketFoundBody = ticketFoundRes.getBody();
        assertNotNull(ticketFoundBody);
        assertEquals("testmctest@test.com", ticketFoundBody.getEmail());
        assertEquals("Test Testson", ticketFoundBody.getFullName());
        assertEquals("full name", ticketFoundBody.getReservationFullName());
        reservation = reservationApiV2Controller.getReservationInfo(reservationId, context.getPublicUser()).getBody();
        assertNotNull(reservation);
        ticket = reservation.getTicketsByCategory().stream().findFirst().orElseThrow().getTickets().get(0);
        assertEquals("testmctest@test.com", ticket.getEmail());
        assertEquals("Test", ticket.getFirstName());
        assertEquals("Testson", ticket.getLastName());
        var ticketPdfMockResp = new MockHttpServletResponse();
        ticketApiV2Controller.generateTicketPdf(context.event.getShortName(), ticket.getUuid(), ticketPdfMockResp);
        assertEquals("application/pdf", ticketPdfMockResp.getContentType());
        var ticketQRCodeResp = new MockHttpServletResponse();
        ticketApiV2Controller.showQrCode(context.event.getShortName(), ticket.getUuid(), ticketQRCodeResp);
        assertEquals("image/png", ticketQRCodeResp.getContentType());
        var fullTicketInfo = ticketRepository.findByUUID(ticket.getUuid());
        var qrCodeReader = new QRCodeReader();
        var qrCodeRead = qrCodeReader.decode(new BinaryBitmap(new HybridBinarizer(new BufferedImageLuminanceSource(ImageIO.read(new ByteArrayInputStream(ticketQRCodeResp.getContentAsByteArray()))))), Map.of(DecodeHintType.PURE_BARCODE, Boolean.TRUE));
        assertEquals(fullTicketInfo.ticketCode(context.event.getPrivateKey()), qrCodeRead.getText());
        // can only be done for free tickets
        var releaseTicketFailure = ticketApiV2Controller.releaseTicket(context.event.getShortName(), ticket.getUuid());
        assertEquals(HttpStatus.BAD_REQUEST, releaseTicketFailure.getStatusCode());
        assertEquals(HttpStatus.OK, ticketApiV2Controller.getTicketInfo(context.event.getShortName(), ticket.getUuid()).getStatusCode());
        // no invoice, but receipt
        assertEquals(HttpStatus.NOT_FOUND, reservationApiV2Controller.getInvoice(context.event.getShortName(), reservationId, new MockHttpServletResponse(), context.getPublicAuthentication()).getStatusCode());
        assertEquals(HttpStatus.OK, reservationApiV2Controller.getReceipt(context.event.getShortName(), reservationId, new MockHttpServletResponse(), context.getPublicAuthentication()).getStatusCode());
        // 
        {
            // clear the extension_log table so that we can check the expectation
            cleanupExtensionLog();
            Principal principal = mock(Principal.class);
            Mockito.when(principal.getName()).thenReturn(context.userId);
            String ticketIdentifier = fullTicketInfo.getUuid();
            String eventName = context.event.getShortName();
            String ticketCode = fullTicketInfo.ticketCode(context.event.getPrivateKey());
            TicketAndCheckInResult ticketAndCheckInResult = checkInApiController.findTicketWithUUID(context.event.getId(), ticketIdentifier, ticketCode);
            assertEquals(CheckInStatus.OK_READY_TO_BE_CHECKED_IN, ticketAndCheckInResult.getResult().getStatus());
            CheckInApiController.TicketCode tc = new CheckInApiController.TicketCode();
            tc.setCode(ticketCode);
            assertEquals(CheckInStatus.SUCCESS, checkInApiController.checkIn(context.event.getId(), ticketIdentifier, tc, new TestingAuthenticationToken("ciccio", "ciccio")).getResult().getStatus());
            List<ScanAudit> audits = scanAuditRepository.findAllForEvent(context.event.getId());
            assertFalse(audits.isEmpty());
            assertTrue(audits.stream().anyMatch(sa -> sa.getTicketUuid().equals(ticketIdentifier)));
            extLogs = extensionLogRepository.getPage(null, null, null, 100, 0);
            assertEventLogged(extLogs, TICKET_CHECKED_IN, 2);
            TicketAndCheckInResult ticketAndCheckInResultOk = checkInApiController.findTicketWithUUID(context.event.getId(), ticketIdentifier, ticketCode);
            assertEquals(CheckInStatus.ALREADY_CHECK_IN, ticketAndCheckInResultOk.getResult().getStatus());
            // check stats after check in one ticket
            assertTrue(eventStatisticsManager.getTicketSoldStatistics(context.event.getId(), statisticsFrom, statisticsTo, "day").stream().mapToLong(TicketsByDateStatistic::getCount).sum() > 0);
            EventWithAdditionalInfo eventWithAdditionalInfo3 = eventStatisticsManager.getEventWithAdditionalInfo(context.event.getShortName(), context.userId);
            assertEquals(2, eventWithAdditionalInfo3.getNotSoldTickets());
            assertEquals(0, eventWithAdditionalInfo3.getSoldTickets());
            assertEquals(20, eventWithAdditionalInfo3.getAvailableSeats());
            assertEquals(1, eventWithAdditionalInfo3.getCheckedInTickets());
            // test revert check in
            assertTrue(checkInApiController.revertCheckIn(context.event.getId(), ticketIdentifier, principal));
            assertFalse(checkInApiController.revertCheckIn(context.event.getId(), ticketIdentifier, principal));
            TicketAndCheckInResult ticketAndCheckInResult2 = checkInApiController.findTicketWithUUID(context.event.getId(), ticketIdentifier, ticketCode);
            assertEquals(CheckInStatus.OK_READY_TO_BE_CHECKED_IN, ticketAndCheckInResult2.getResult().getStatus());
            UsersApiController.UserWithPasswordAndQRCode sponsorUser = usersApiController.insertUser(new UserModification(null, context.event.getOrganizationId(), "SPONSOR", "sponsor", "first", "last", "email@email.com", User.Type.INTERNAL, null, null), "http://localhost:8080", principal);
            Principal sponsorPrincipal = mock(Principal.class);
            Mockito.when(sponsorPrincipal.getName()).thenReturn(sponsorUser.getUsername());
            // check failures
            assertEquals(CheckInStatus.EVENT_NOT_FOUND, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest("not-existing-event", "not-existing-ticket", null, null), sponsorPrincipal).getBody().getResult().getStatus());
            assertEquals(CheckInStatus.TICKET_NOT_FOUND, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, "not-existing-ticket", null, null), sponsorPrincipal).getBody().getResult().getStatus());
            assertEquals(CheckInStatus.INVALID_TICKET_STATE, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticketIdentifier, null, null), sponsorPrincipal).getBody().getResult().getStatus());
            // 
            // check stats after revert check in one ticket
            assertTrue(eventStatisticsManager.getTicketSoldStatistics(context.event.getId(), statisticsFrom, statisticsTo, "day").stream().mapToLong(TicketsByDateStatistic::getCount).sum() > 0);
            EventWithAdditionalInfo eventWithAdditionalInfo4 = eventStatisticsManager.getEventWithAdditionalInfo(context.event.getShortName(), context.userId);
            assertEquals(2, eventWithAdditionalInfo4.getNotSoldTickets());
            assertEquals(1, eventWithAdditionalInfo4.getSoldTickets());
            assertEquals(20, eventWithAdditionalInfo4.getAvailableSeats());
            assertEquals(0, eventWithAdditionalInfo4.getCheckedInTickets());
            cleanupExtensionLog();
            CheckInApiController.TicketCode tc2 = new CheckInApiController.TicketCode();
            tc2.setCode(ticketCode);
            TicketAndCheckInResult ticketAndcheckInResult = checkInApiController.checkIn(context.event.getId(), ticketIdentifier, tc2, new TestingAuthenticationToken("ciccio", "ciccio"));
            assertEquals(CheckInStatus.SUCCESS, ticketAndcheckInResult.getResult().getStatus());
            extLogs = extensionLogRepository.getPage(null, null, null, 100, 0);
            assertEventLogged(extLogs, TICKET_CHECKED_IN, 2);
            var offlineIdentifiers = checkInApiController.getOfflineIdentifiers(context.event.getShortName(), 0L, new MockHttpServletResponse(), principal);
            if (context.checkInStationsEnabled) {
                assertFalse(offlineIdentifiers.isEmpty(), "Alf.io-PI integration must be enabled by default");
                // disable Alf.io-PI
                configurationRepository.insert(ConfigurationKeys.ALFIO_PI_INTEGRATION_ENABLED.name(), "false", null);
                offlineIdentifiers = checkInApiController.getOfflineIdentifiers(context.event.getShortName(), 0L, new MockHttpServletResponse(), principal);
                assertTrue(offlineIdentifiers.isEmpty());
                // re-enable Alf.io-PI
                configurationRepository.insertEventLevel(context.event.getOrganizationId(), context.event.getId(), ConfigurationKeys.OFFLINE_CHECKIN_ENABLED.name(), "true", null);
                configurationRepository.update(ConfigurationKeys.ALFIO_PI_INTEGRATION_ENABLED.name(), "true");
                offlineIdentifiers = checkInApiController.getOfflineIdentifiers(context.event.getShortName(), 0L, new MockHttpServletResponse(), principal);
                assertFalse(offlineIdentifiers.isEmpty());
                // download encrypted ticket data
                TicketWithCategory ticketwc = testEncryptedCheckInPayload(principal, ticketAndcheckInResult, offlineIdentifiers, false, context);
                // insert a poll and download again encrypted data. This time we expect a pin to be present because we haven't specified a tag
                var rowCountAndKey = pollRepository.insert(Map.of("en", "test poll"), null, List.of(), 0, context.event.getId(), context.event.getOrganizationId());
                testEncryptedCheckInPayload(principal, ticketAndcheckInResult, offlineIdentifiers, true, context);
                // we define a tag for the poll, this time we won't have a pin in the result
                pollRepository.update(Map.of("en", "test poll"), null, List.of("blabla"), 0, rowCountAndKey.getKey(), context.event.getId());
                testEncryptedCheckInPayload(principal, ticketAndcheckInResult, offlineIdentifiers, false, context);
                // now we add a matching tag to the ticket. As a result, the pin must be included in the result
                ticketRepository.updateTicketTags(List.of(ticketwc.getId()), List.of("blabla"));
                testEncryptedCheckInPayload(principal, ticketAndcheckInResult, offlineIdentifiers, true, context);
                // 
                // check register sponsor scan success flow
                assertTrue(attendeeApiController.getScannedBadges(context.event.getShortName(), EventUtil.JSON_DATETIME_FORMATTER.format(LocalDateTime.of(1970, 1, 1, 0, 0)), sponsorPrincipal).getBody().isEmpty());
                assertEquals(CheckInStatus.SUCCESS, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticketwc.getUuid(), null, null), sponsorPrincipal).getBody().getResult().getStatus());
                assertEquals(1, attendeeApiController.getScannedBadges(context.event.getShortName(), EventUtil.JSON_DATETIME_FORMATTER.format(LocalDateTime.of(1970, 1, 1, 0, 0)), sponsorPrincipal).getBody().size());
                // check export
                MockHttpServletResponse response = new MockHttpServletResponse();
                eventApiController.downloadSponsorScanExport(context.event.getShortName(), "csv", response, principal);
                response.getContentAsString();
                CSVReader csvReader = new CSVReader(new StringReader(response.getContentAsString()));
                List<String[]> csvSponsorScan = csvReader.readAll();
                assertEquals(2, csvSponsorScan.size());
                assertEquals("sponsor", csvSponsorScan.get(1)[0]);
                assertEquals("Test Testson", csvSponsorScan.get(1)[3]);
                assertEquals("testmctest@test.com", csvSponsorScan.get(1)[4]);
                assertEquals("", csvSponsorScan.get(1)[8]);
                assertEquals(SponsorScan.LeadStatus.WARM.name(), csvSponsorScan.get(1)[9]);
                // 
                // check update notes
                assertEquals(CheckInStatus.SUCCESS, attendeeApiController.scanBadge(new AttendeeApiController.SponsorScanRequest(eventName, ticket.getUuid(), "this is a very good lead!", "HOT"), sponsorPrincipal).getBody().getResult().getStatus());
                assertEquals(1, attendeeApiController.getScannedBadges(context.event.getShortName(), EventUtil.JSON_DATETIME_FORMATTER.format(LocalDateTime.of(1970, 1, 1, 0, 0)), sponsorPrincipal).getBody().size());
                response = new MockHttpServletResponse();
                eventApiController.downloadSponsorScanExport(context.event.getShortName(), "csv", response, principal);
                response.getContentAsString();
                csvReader = new CSVReader(new StringReader(response.getContentAsString()));
                csvSponsorScan = csvReader.readAll();
                assertEquals(2, csvSponsorScan.size());
                assertEquals("sponsor", csvSponsorScan.get(1)[0]);
                assertEquals("Test Testson", csvSponsorScan.get(1)[3]);
                assertEquals("testmctest@test.com", csvSponsorScan.get(1)[4]);
                assertEquals("this is a very good lead!", csvSponsorScan.get(1)[8]);
                assertEquals(SponsorScan.LeadStatus.HOT.name(), csvSponsorScan.get(1)[9]);
                // #742 - test multiple check-ins
                // since on the badge we don't have the full ticket info, we will pass in "null" as scanned code
                CheckInApiController.TicketCode badgeScan = new CheckInApiController.TicketCode();
                badgeScan.setCode(null);
                ticketAndcheckInResult = checkInApiController.checkIn(context.event.getId(), ticketIdentifier, badgeScan, new TestingAuthenticationToken("ciccio", "ciccio"));
                // ONCE_PER_DAY is disabled by default, therefore we get an error
                assertEquals(CheckInStatus.EMPTY_TICKET_CODE, ticketAndcheckInResult.getResult().getStatus());
                // enable ONCE_PER_DAYFalse
                TicketCategory category = ticketCategoryRepository.getById(ticketwc.getCategoryId());
                ticketCategoryRepository.update(category.getId(), category.getName(), category.getInception(context.event.getZoneId()), category.getExpiration(context.event.getZoneId()), category.getMaxTickets(), category.isAccessRestricted(), MonetaryUtil.unitToCents(category.getPrice(), category.getCurrencyCode()), category.getCode(), category.getValidCheckInFrom(), category.getValidCheckInTo(), category.getTicketValidityStart(), category.getTicketValidityEnd(), TicketCategory.TicketCheckInStrategy.ONCE_PER_DAY, category.getTicketAccessType());
                ticketAndcheckInResult = checkInApiController.checkIn(context.event.getId(), ticketIdentifier, badgeScan, new TestingAuthenticationToken("ciccio", "ciccio"));
                // the event start date is in one week, so we expect an error here
                assertEquals(CheckInStatus.INVALID_TICKET_CATEGORY_CHECK_IN_DATE, ticketAndcheckInResult.getResult().getStatus());
                eventRepository.updateHeader(context.event.getId(), context.event.getDisplayName(), context.event.getWebsiteUrl(), context.event.getExternalUrl(), context.event.getTermsAndConditionsUrl(), context.event.getPrivacyPolicyUrl(), context.event.getImageUrl(), context.event.getFileBlobId(), context.event.getLocation(), context.event.getLatitude(), context.event.getLongitude(), context.event.now(clockProvider).minusSeconds(1), context.event.getEnd(), context.event.getTimeZone(), context.event.getOrganizationId(), context.event.getLocales(), context.event.getFormat());
                ticketAndcheckInResult = checkInApiController.checkIn(context.event.getId(), ticketIdentifier, badgeScan, new TestingAuthenticationToken("ciccio", "ciccio"));
                // we have already scanned the ticket today, so we expect to receive a warning
                assertEquals(CheckInStatus.BADGE_SCAN_ALREADY_DONE, ticketAndcheckInResult.getResult().getStatus());
                assertEquals(1, (int) auditingRepository.countAuditsOfTypeForReservation(reservationId, Audit.EventType.BADGE_SCAN));
                // move the scans to yesterday
                // we expect 3 rows because:
                // 1 check-in
                // 1 revert
                // 1 badge scan
                assertEquals(3, jdbcTemplate.update("update auditing set event_time = event_time - interval '1 day' where reservation_id = :reservationId and event_type in ('BADGE_SCAN', 'CHECK_IN')", Map.of("reservationId", reservationId)));
                ticketAndcheckInResult = checkInApiController.checkIn(context.event.getId(), ticketIdentifier, badgeScan, new TestingAuthenticationToken("ciccio", "ciccio"));
                // we now expect to receive a successful message
                assertEquals(CheckInStatus.BADGE_SCAN_SUCCESS, ticketAndcheckInResult.getResult().getStatus());
                assertEquals(2, (int) auditingRepository.countAuditsOfTypeForReservation(reservationId, Audit.EventType.BADGE_SCAN));
            } else {
                assertTrue(offlineIdentifiers.isEmpty(), "Alf.io-PI integration must be disabled");
            }
        }
        performAdditionalTests(context);
        eventManager.deleteEvent(context.event.getId(), context.userId);
    }
}
Also used : ScanAudit(alfio.model.audit.ScanAudit) alfio.repository(alfio.repository) UsersApiController(alfio.controller.api.admin.UsersApiController) alfio.manager(alfio.manager) PaymentProxy(alfio.model.transaction.PaymentProxy) Parser(ch.digitalfondue.jfiveparse.Parser) TestingAuthenticationToken(org.springframework.security.authentication.TestingAuthenticationToken) ZonedDateTime(java.time.ZonedDateTime) RequiredArgsConstructor(lombok.RequiredArgsConstructor) NamedParameterJdbcTemplate(org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate) TicketAndCheckInResult(alfio.manager.support.TicketAndCheckInResult) Element(ch.digitalfondue.jfiveparse.Element) ServletWebRequest(org.springframework.web.context.request.ServletWebRequest) BigDecimal(java.math.BigDecimal) ByteArrayInputStream(java.io.ByteArrayInputStream) ImageIO(javax.imageio.ImageIO) ReservationApiV2Controller(alfio.controller.api.v2.user.ReservationApiV2Controller) QRCodeReader(com.google.zxing.qrcode.QRCodeReader) ExtensionEvent(alfio.manager.support.extension.ExtensionEvent) TypeReference(com.fasterxml.jackson.core.type.TypeReference) HybridBinarizer(com.google.zxing.common.HybridBinarizer) DecodeHintType(com.google.zxing.DecodeHintType) MockHttpServletRequest(org.springframework.mock.web.MockHttpServletRequest) MockHttpSession(org.springframework.mock.web.MockHttpSession) alfio.controller.form(alfio.controller.form) AdditionalServiceApiController(alfio.controller.api.admin.AdditionalServiceApiController) Selector(ch.digitalfondue.jfiveparse.Selector) Collectors(java.util.stream.Collectors) StandardCharsets(java.nio.charset.StandardCharsets) ConfigurationRepository(alfio.repository.system.ConfigurationRepository) IOUtils(org.apache.commons.io.IOUtils) CheckInStatus(alfio.manager.support.CheckInStatus) Principal(java.security.Principal) alfio.model(alfio.model) UserRepository(alfio.repository.user.UserRepository) CheckInApiController(alfio.controller.api.admin.CheckInApiController) alfio.model.modification(alfio.model.modification) Mockito.mock(org.mockito.Mockito.mock) java.util(java.util) TicketApiV2Controller(alfio.controller.api.v2.user.TicketApiV2Controller) ScanAuditRepository(alfio.repository.audit.ScanAuditRepository) CSVReader(com.opencsv.CSVReader) LocalDateTime(java.time.LocalDateTime) BasicEventInfo(alfio.controller.api.v2.model.BasicEventInfo) PurchaseContextType(alfio.model.PurchaseContext.PurchaseContextType) Supplier(java.util.function.Supplier) alfio.util(alfio.util) MockHttpServletResponse(org.springframework.mock.web.MockHttpServletResponse) IndexController(alfio.controller.IndexController) EventCode(alfio.controller.api.v2.model.EventCode) Language(alfio.controller.api.v2.model.Language) Objects.requireNonNull(java.util.Objects.requireNonNull) BeanPropertyBindingResult(org.springframework.validation.BeanPropertyBindingResult) TranslationsApiController(alfio.controller.api.v2.TranslationsApiController) EventApiController(alfio.controller.api.admin.EventApiController) ReservationAccessDenied(alfio.controller.api.v2.user.support.ReservationAccessDenied) PaymentMethod(alfio.model.transaction.PaymentMethod) User(alfio.model.user.User) AttendeeApiController(alfio.controller.api.v1.AttendeeApiController) BufferedImageLuminanceSource(com.google.zxing.client.j2se.BufferedImageLuminanceSource) IntegrationTestUtil(alfio.test.util.IntegrationTestUtil) InputStreamReader(java.io.InputStreamReader) InfoApiController(alfio.controller.api.v2.InfoApiController) Extension(alfio.extension.Extension) Mockito(org.mockito.Mockito) HttpStatus(org.springframework.http.HttpStatus) ExtensionService(alfio.extension.ExtensionService) EventApiV2Controller(alfio.controller.api.v2.user.EventApiV2Controller) StringReader(java.io.StringReader) Log4j2(lombok.extern.log4j.Log4j2) Assertions(org.junit.jupiter.api.Assertions) BinaryBitmap(com.google.zxing.BinaryBitmap) DigestUtils(org.apache.commons.codec.digest.DigestUtils) ConfigurationKeys(alfio.model.system.ConfigurationKeys) AttendeeApiController(alfio.controller.api.v1.AttendeeApiController) TicketAndCheckInResult(alfio.manager.support.TicketAndCheckInResult) BufferedImageLuminanceSource(com.google.zxing.client.j2se.BufferedImageLuminanceSource) ServletWebRequest(org.springframework.web.context.request.ServletWebRequest) MockHttpServletResponse(org.springframework.mock.web.MockHttpServletResponse) CSVReader(com.opencsv.CSVReader) TestingAuthenticationToken(org.springframework.security.authentication.TestingAuthenticationToken) Parser(ch.digitalfondue.jfiveparse.Parser) ByteArrayInputStream(java.io.ByteArrayInputStream) ScanAudit(alfio.model.audit.ScanAudit) QRCodeReader(com.google.zxing.qrcode.QRCodeReader) Language(alfio.controller.api.v2.model.Language) StringReader(java.io.StringReader) MockHttpSession(org.springframework.mock.web.MockHttpSession) BasicEventInfo(alfio.controller.api.v2.model.BasicEventInfo) BeanPropertyBindingResult(org.springframework.validation.BeanPropertyBindingResult) InputStreamReader(java.io.InputStreamReader) MockHttpServletRequest(org.springframework.mock.web.MockHttpServletRequest) CheckInApiController(alfio.controller.api.admin.CheckInApiController) HybridBinarizer(com.google.zxing.common.HybridBinarizer) Extension(alfio.extension.Extension) UsersApiController(alfio.controller.api.admin.UsersApiController) ReservationAccessDenied(alfio.controller.api.v2.user.support.ReservationAccessDenied) BinaryBitmap(com.google.zxing.BinaryBitmap) Principal(java.security.Principal)

Example 3 with TicketAndCheckInResult

use of alfio.manager.support.TicketAndCheckInResult in project alf.io by alfio-event.

the class AttendeeManager method registerSponsorScan.

public TicketAndCheckInResult registerSponsorScan(String eventShortName, String ticketUid, String notes, SponsorScan.LeadStatus leadStatus, String username) {
    int userId = userRepository.getByUsername(username).getId();
    Optional<EventAndOrganizationId> maybeEvent = eventRepository.findOptionalEventAndOrganizationIdByShortName(eventShortName);
    if (maybeEvent.isEmpty()) {
        return new TicketAndCheckInResult(null, new DefaultCheckInResult(CheckInStatus.EVENT_NOT_FOUND, "event not found"));
    }
    EventAndOrganizationId event = maybeEvent.get();
    Optional<Ticket> maybeTicket = ticketRepository.findOptionalByUUID(ticketUid);
    if (maybeTicket.isEmpty()) {
        return new TicketAndCheckInResult(null, new DefaultCheckInResult(CheckInStatus.TICKET_NOT_FOUND, "ticket not found"));
    }
    Ticket ticket = maybeTicket.get();
    if (ticket.getStatus() != Ticket.TicketStatus.CHECKED_IN) {
        return new TicketAndCheckInResult(new TicketWithCategory(ticket, null), new DefaultCheckInResult(CheckInStatus.INVALID_TICKET_STATE, "not checked-in"));
    }
    Optional<ZonedDateTime> existingRegistration = sponsorScanRepository.getRegistrationTimestamp(userId, event.getId(), ticket.getId());
    if (existingRegistration.isEmpty()) {
        ZoneId eventZoneId = eventRepository.getZoneIdByEventId(event.getId());
        sponsorScanRepository.insert(userId, ZonedDateTime.now(clockProvider.withZone(eventZoneId)), event.getId(), ticket.getId(), notes, leadStatus);
    } else {
        sponsorScanRepository.updateNotesAndLeadStatus(userId, event.getId(), ticket.getId(), notes, leadStatus);
    }
    return new TicketAndCheckInResult(new TicketWithCategory(ticket, null), new DefaultCheckInResult(CheckInStatus.SUCCESS, "success"));
}
Also used : TicketAndCheckInResult(alfio.manager.support.TicketAndCheckInResult) ZoneId(java.time.ZoneId) ZonedDateTime(java.time.ZonedDateTime) DefaultCheckInResult(alfio.manager.support.DefaultCheckInResult)

Example 4 with TicketAndCheckInResult

use of alfio.manager.support.TicketAndCheckInResult in project alf.io by alfio-event.

the class AttendeeManager method registerSponsorScan.

public TicketAndCheckInResult registerSponsorScan(String eventShortName, String ticketUid, String username) {
    int userId = userRepository.getByUsername(username).getId();
    Optional<Event> maybeEvent = eventRepository.findOptionalByShortName(eventShortName);
    if (!maybeEvent.isPresent()) {
        return new TicketAndCheckInResult(null, new DefaultCheckInResult(CheckInStatus.EVENT_NOT_FOUND, "event not found"));
    }
    Event event = maybeEvent.get();
    Optional<Ticket> maybeTicket = ticketRepository.findOptionalByUUID(ticketUid);
    if (!maybeTicket.isPresent()) {
        return new TicketAndCheckInResult(null, new DefaultCheckInResult(CheckInStatus.TICKET_NOT_FOUND, "ticket not found"));
    }
    Ticket ticket = maybeTicket.get();
    if (ticket.getStatus() != Ticket.TicketStatus.CHECKED_IN) {
        return new TicketAndCheckInResult(ticket, new DefaultCheckInResult(CheckInStatus.INVALID_TICKET_STATE, "not checked-in"));
    }
    Optional<ZonedDateTime> existingRegistration = sponsorScanRepository.getRegistrationTimestamp(userId, event.getId(), ticket.getId());
    if (!existingRegistration.isPresent()) {
        sponsorScanRepository.insert(userId, ZonedDateTime.now(event.getZoneId()), event.getId(), ticket.getId());
    }
    return new TicketAndCheckInResult(ticket, new DefaultCheckInResult(CheckInStatus.SUCCESS, "success"));
}
Also used : Ticket(alfio.model.Ticket) TicketAndCheckInResult(alfio.manager.support.TicketAndCheckInResult) ZonedDateTime(java.time.ZonedDateTime) Event(alfio.model.Event) DefaultCheckInResult(alfio.manager.support.DefaultCheckInResult)

Example 5 with TicketAndCheckInResult

use of alfio.manager.support.TicketAndCheckInResult in project alf.io by alfio-event.

the class CheckInManager method checkIn.

public TicketAndCheckInResult checkIn(int eventId, String ticketIdentifier, Optional<String> ticketCode, String user) {
    TicketAndCheckInResult descriptor = extractStatus(eventId, ticketRepository.findByUUIDForUpdate(ticketIdentifier), ticketIdentifier, ticketCode);
    if (descriptor.getResult().getStatus() == OK_READY_TO_BE_CHECKED_IN) {
        checkIn(ticketIdentifier);
        scanAuditRepository.insert(ticketIdentifier, eventId, ZonedDateTime.now(), user, SUCCESS, ScanAudit.Operation.SCAN);
        auditingRepository.insert(descriptor.getTicket().getTicketsReservationId(), userRepository.findIdByUserName(user).orElse(null), eventId, Audit.EventType.CHECK_IN, new Date(), Audit.EntityType.TICKET, Integer.toString(descriptor.getTicket().getId()));
        return new TicketAndCheckInResult(descriptor.getTicket(), new DefaultCheckInResult(SUCCESS, "success"));
    }
    return descriptor;
}
Also used : TicketAndCheckInResult(alfio.manager.support.TicketAndCheckInResult) DefaultCheckInResult(alfio.manager.support.DefaultCheckInResult)

Aggregations

TicketAndCheckInResult (alfio.manager.support.TicketAndCheckInResult)6 ZonedDateTime (java.time.ZonedDateTime)4 DefaultCheckInResult (alfio.manager.support.DefaultCheckInResult)3 CheckInApiController (alfio.controller.api.admin.CheckInApiController)2 EventApiController (alfio.controller.api.admin.EventApiController)2 UsersApiController (alfio.controller.api.admin.UsersApiController)2 alfio.manager (alfio.manager)2 CheckInStatus (alfio.manager.support.CheckInStatus)2 alfio.model (alfio.model)2 ScanAudit (alfio.model.audit.ScanAudit)2 ConfigurationKeys (alfio.model.system.ConfigurationKeys)2 PaymentProxy (alfio.model.transaction.PaymentProxy)2 ScanAuditRepository (alfio.repository.audit.ScanAuditRepository)2 ConfigurationRepository (alfio.repository.system.ConfigurationRepository)2 IntegrationTestUtil (alfio.test.util.IntegrationTestUtil)2 TypeReference (com.fasterxml.jackson.core.type.TypeReference)2 CSVReader (com.opencsv.CSVReader)2 StringReader (java.io.StringReader)2 BigDecimal (java.math.BigDecimal)2 Principal (java.security.Principal)2