Skip to content

Commit

Permalink
#39 - add integration test. Almost done! :)
Browse files Browse the repository at this point in the history
  • Loading branch information
cbellone committed Jul 12, 2015
1 parent de83576 commit 368924c
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 8 deletions.
1 change: 1 addition & 0 deletions src/main/java/alfio/config/Initializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public class Initializer extends AbstractAnnotationConfigDispatcherServletInitia
public static final String PROFILE_LIVE = "!dev";
public static final String PROFILE_HTTP = "http";
public static final String PROFILE_SPRING_BOOT = "spring-boot";
public static final String PROFILE_DISABLE_JOBS = "disable-jobs";
private Environment environment;

@Override
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/alfio/manager/Jobs.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@
*/
package alfio.manager;

import alfio.config.Initializer;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
@Profile("!"+ Initializer.PROFILE_DISABLE_JOBS)
public class Jobs {

private static final int ONE_MINUTE = 1000 * 60;
Expand Down
15 changes: 12 additions & 3 deletions src/main/java/alfio/manager/WaitingQueueSubscriptionProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,33 @@

import alfio.manager.system.ConfigurationManager;
import alfio.model.Event;
import alfio.model.WaitingQueueSubscription;
import alfio.model.modification.TicketReservationWithOptionalCodeModification;
import alfio.model.system.Configuration;
import alfio.model.system.ConfigurationKeys;
import alfio.repository.WaitingQueueRepository;
import alfio.util.TemplateManager;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.sql.Date;
import java.time.ZonedDateTime;
import java.util.*;

@Log4j2
@Component
@Transactional
public class WaitingQueueSubscriptionProcessor {

private final EventManager eventManager;
private final TicketReservationManager ticketReservationManager;
private final ConfigurationManager configurationManager;
private final WaitingQueueManager waitingQueueManager;
private final NotificationManager notificationManager;
private final WaitingQueueRepository waitingQueueRepository;
private final MessageSource messageSource;
private final TemplateManager templateManager;

Expand All @@ -49,13 +54,15 @@ public WaitingQueueSubscriptionProcessor(EventManager eventManager,
ConfigurationManager configurationManager,
WaitingQueueManager waitingQueueManager,
NotificationManager notificationManager,
WaitingQueueRepository waitingQueueRepository,
MessageSource messageSource,
TemplateManager templateManager) {
this.eventManager = eventManager;
this.ticketReservationManager = ticketReservationManager;
this.configurationManager = configurationManager;
this.waitingQueueManager = waitingQueueManager;
this.notificationManager = notificationManager;
this.waitingQueueRepository = waitingQueueRepository;
this.messageSource = messageSource;
this.templateManager = templateManager;
}
Expand All @@ -74,21 +81,23 @@ private boolean isWaitingListFormEnabled(Event event) {

void distributeAvailableSeats(Event event) {
waitingQueueManager.distributeSeats(event).forEach(triple -> {
Locale locale = triple.getLeft().getLocale();
WaitingQueueSubscription subscription = triple.getLeft();
Locale locale = subscription.getLocale();
ZonedDateTime expiration = triple.getRight();
String reservationId = createReservation(event.getId(), triple.getMiddle(), expiration, locale);
String eventShortName = event.getShortName();
String subject = messageSource.getMessage("email-waiting-queue-acquired.subject", new Object[]{eventShortName}, locale);
Map<String, Object> model = new HashMap<>();
model.put("event", event);
model.put("subscription", triple.getLeft());
model.put("subscription", subscription);
model.put("reservationUrl", ticketReservationManager.reservationUrl(reservationId, event));
model.put("reservationTimeout", expiration);
model.put("organization", eventManager.loadOrganizerUsingSystemPrincipal(event));
notificationManager.sendSimpleEmail(event,
triple.getLeft().getEmailAddress(),
subscription.getEmailAddress(),
subject,
() -> templateManager.renderClassPathResource("/alfio/templates/waiting-queue-reservation-email-txt.ms", model, locale, TemplateManager.TemplateOutput.TEXT));
waitingQueueRepository.flagAsPending(reservationId, subscription.getId());
});
}

Expand Down
5 changes: 4 additions & 1 deletion src/main/java/alfio/model/WaitingQueueSubscription.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
public class WaitingQueueSubscription {

public enum Status {
WAITING, ACQUIRED, EXPIRED
WAITING, PENDING, ACQUIRED, EXPIRED
}

private final int id;
Expand All @@ -35,6 +35,7 @@ public enum Status {
private final Status status;
private final String fullName;
private final String emailAddress;
private final String reservationId;
private final String userLanguage;

public WaitingQueueSubscription(@Column("id") int id,
Expand All @@ -43,13 +44,15 @@ public WaitingQueueSubscription(@Column("id") int id,
@Column("status") String status,
@Column("full_name") String fullName,
@Column("email_address") String emailAddress,
@Column("ticket_reservation_id") String reservationId,
@Column("user_language") String userLanguage) {
this.id = id;
this.creation = creation;
this.eventId = eventId;
this.userLanguage = userLanguage;
this.status = Status.valueOf(status);
this.fullName = fullName;
this.reservationId = reservationId;
this.emailAddress = emailAddress;
}

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/alfio/repository/TicketRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ int updateOptionalTicketInfo(@Bind("ticketIdentifier") String ticketIdentifier,
@Query("select distinct tickets_reservation_id from ticket where event_id = :eventId and status in('ACQUIRED', 'TO_BE_PAID') and (full_name is null or email_address is null)")
List<String> findAllReservationsConfirmedButNotAssigned(@Bind("eventId") int eventId);

@Query("update ticket set status = 'FREE', tickets_reservation_id = null where id = :ticketId and status = 'ACQUIRED' and tickets_reservation_id = :reservationId and event_id = :eventId")
@Query("update ticket set status = 'RELEASED', tickets_reservation_id = null where id = :ticketId and status = 'ACQUIRED' and tickets_reservation_id = :reservationId and event_id = :eventId")
int releaseTicket(@Bind("reservationId") String reservationId, @Bind("eventId") int eventId, @Bind("ticketId") int ticketId);

@Query("select count(*) from ticket where status = 'RELEASED' and event_id = :eventId")
Expand All @@ -168,7 +168,7 @@ int updateOptionalTicketInfo(@Bind("ticketIdentifier") String ticketIdentifier,
@Query("update ticket set status = 'FREE' where status = 'RELEASED' and event_id = :eventId")
int revertToFree(@Bind("eventId") int eventId);

@Query("select * from ticket where status = :status and event_id = :eventId and tickets_reservation_id is null order by id limit :amount for update")
@Query("select * from ticket where status = :status and event_id = :eventId order by id limit :amount for update")
List<Ticket> selectWaitingTicketsForUpdate(@Bind("eventId") int eventId, @Bind("status") String status, @Bind("amount") int amount);

@Query("select id from ticket where status = 'FREE' and event_id = :eventId order by id limit :amount for update")
Expand Down
8 changes: 7 additions & 1 deletion src/main/java/alfio/repository/WaitingQueueRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ public interface WaitingQueueRepository {
@Query("select * from waiting_queue where event_id = :eventId and status = 'WAITING' order by creation")
List<WaitingQueueSubscription> loadAllWaiting(@Bind("eventId") int eventId);

@Query("select * from waiting_queue where event_id = :eventId and status = 'WAITING' order by creation limit :max for update")
@Query("select * from waiting_queue where event_id = :eventId order by creation")
List<WaitingQueueSubscription> loadAll(@Bind("eventId") int eventId);

@Query("select * from waiting_queue where event_id = :eventId and status = 'WAITING' order by creation asc limit :max for update")
List<WaitingQueueSubscription> loadWaiting(@Bind("eventId") int eventId, @Bind("max") int maxNumber);

@Query("select * from waiting_queue where event_id = :eventId and status = 'WAITING' limit 1")
Expand All @@ -47,4 +50,7 @@ public interface WaitingQueueRepository {

@Query("select count(*) from waiting_queue where event_id = :eventId and status = 'WAITING'")
Integer countWaitingPeople(@Bind("eventId") int eventId);

@Query("update waiting_queue set ticket_reservation_id = :ticketReservationId, status = 'PENDING' where id = :id")
int flagAsPending(@Bind("ticketReservationId") String ticketReservationId, @Bind("id") int id);
}
170 changes: 170 additions & 0 deletions src/test/java/alfio/manager/WaitingQueueProcessorIntegrationTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/**
* This file is part of alf.io.
*
* alf.io is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* alf.io is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with alf.io. If not, see <http://www.gnu.org/licenses/>.
*/
package alfio.manager;

import alfio.TestConfiguration;
import alfio.config.DataSourceConfiguration;
import alfio.config.Initializer;
import alfio.manager.system.ConfigurationManager;
import alfio.manager.user.UserManager;
import alfio.model.Event;
import alfio.model.Ticket;
import alfio.model.TicketCategory;
import alfio.model.WaitingQueueSubscription;
import alfio.model.modification.DateTimeModification;
import alfio.model.modification.TicketCategoryModification;
import alfio.model.system.ConfigurationKeys;
import alfio.repository.TicketRepository;
import alfio.repository.TicketReservationRepository;
import alfio.repository.WaitingQueueRepository;
import alfio.repository.user.AuthorityRepository;
import alfio.repository.user.OrganizationRepository;
import alfio.repository.user.UserRepository;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalTime;
import java.util.*;

import static alfio.test.util.IntegrationTestUtil.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {DataSourceConfiguration.class, TestConfiguration.class})
@ActiveProfiles({Initializer.PROFILE_DEV, Initializer.PROFILE_DISABLE_JOBS})
@Transactional
public class WaitingQueueProcessorIntegrationTest {

@BeforeClass
public static void initEnv() {
initSystemProperties();
}

@Autowired
private EventManager eventManager;
@Autowired
private OrganizationRepository organizationRepository;
@Autowired
private UserManager userManager;
@Autowired
private UserRepository userRepository;
@Autowired
private AuthorityRepository authorityRepository;
@Autowired
private TicketRepository ticketRepository;
@Autowired
private WaitingQueueSubscriptionProcessor waitingQueueSubscriptionProcessor;
@Autowired
private WaitingQueueManager waitingQueueManager;
@Autowired
private WaitingQueueRepository waitingQueueRepository;
@Autowired
private ConfigurationManager configurationManager;
@Autowired
private TicketReservationRepository ticketReservationRepository;

@Before
public void setup() {
configurationManager.saveSystemConfiguration(ConfigurationKeys.ENABLE_PRE_REGISTRATION, "true");
configurationManager.saveSystemConfiguration(ConfigurationKeys.ENABLE_WAITING_QUEUE, "true");
initAdminUser(userRepository, authorityRepository);
}

@Test
public void testPreRegistration() {
List<TicketCategoryModification> categories = Collections.singletonList(
new TicketCategoryModification(null, "default", 10,
new DateTimeModification(LocalDate.now().plusDays(1), LocalTime.now()),
new DateTimeModification(LocalDate.now().plusDays(2), LocalTime.now()),
"desc", BigDecimal.TEN, false, "", false));
Pair<Event, String> pair = initEvent(categories, organizationRepository, userManager, eventManager);
Event event = pair.getKey();
waitingQueueManager.subscribe(event, "Giuseppe Garibaldi", "[email protected]", Locale.ENGLISH);
waitingQueueManager.subscribe(event, "Nino Bixio", "[email protected]", Locale.ITALIAN);
assertTrue(waitingQueueRepository.countWaitingPeople(event.getId()) == 2);

waitingQueueSubscriptionProcessor.distributeAvailableSeats(event);
assertEquals(18, ticketRepository.findFreeByEventId(event.getId()).size());

TicketCategoryModification tcm = new TicketCategoryModification(null, "default", 10,
new DateTimeModification(LocalDate.now().minusDays(1), LocalTime.now()),
new DateTimeModification(LocalDate.now().plusDays(5), LocalTime.now()),
"desc", BigDecimal.TEN, false, "", true);
eventManager.insertCategory(event.getId(), tcm, pair.getValue());

waitingQueueSubscriptionProcessor.distributeAvailableSeats(event);

List<WaitingQueueSubscription> subscriptions = waitingQueueRepository.loadAll(event.getId());
assertEquals(2, subscriptions.stream().filter(w -> StringUtils.isNotBlank(w.getReservationId())).count());
assertTrue(subscriptions.stream().allMatch(w -> w.getStatus().equals(WaitingQueueSubscription.Status.PENDING)));

}

@Test
public void testSoldOut() throws InterruptedException {
List<TicketCategoryModification> categories = Collections.singletonList(
new TicketCategoryModification(null, "default", 0,
new DateTimeModification(LocalDate.now().minusDays(1), LocalTime.now()),
new DateTimeModification(LocalDate.now().plusDays(2), LocalTime.now()),
"desc", BigDecimal.ZERO, false, "", true));

Pair<Event, String> pair = initEvent(categories, organizationRepository, userManager, eventManager);
Event event = pair.getKey();
TicketCategory ticketCategory = eventManager.loadTicketCategories(event).get(0);
List<Integer> reserved = ticketRepository.selectFreeTicketsForPreReservation(event.getId(), ticketCategory.getId(), 20);
String reservationId = UUID.randomUUID().toString();
ticketReservationRepository.createNewReservation(reservationId, DateUtils.addHours(new Date(), 1), null);
ticketRepository.reserveTickets(reservationId, reserved, ticketCategory.getId(), Locale.ITALIAN.getLanguage());
ticketRepository.updateTicketsStatusWithReservationId(reservationId, Ticket.TicketStatus.ACQUIRED.name());

//sold-out
waitingQueueManager.subscribe(event, "Giuseppe Garibaldi", "[email protected]", Locale.ENGLISH);
Thread.sleep(1L);//we are testing ordering, not concurrency...
waitingQueueManager.subscribe(event, "Nino Bixio", "[email protected]", Locale.ITALIAN);
assertTrue(waitingQueueRepository.countWaitingPeople(event.getId()) == 2);

//the following call shouldn't have any effect
waitingQueueSubscriptionProcessor.distributeAvailableSeats(event);
assertTrue(waitingQueueRepository.countWaitingPeople(event.getId()) == 2);

Ticket firstTicket = ticketRepository.findTicketsInReservation(reservationId).get(0);

ticketRepository.releaseTicket(reservationId, event.getId(), firstTicket.getId());

waitingQueueSubscriptionProcessor.distributeAvailableSeats(event);

List<WaitingQueueSubscription> subscriptions = waitingQueueRepository.loadAll(event.getId());
assertEquals(1, subscriptions.stream().filter(w -> StringUtils.isNotBlank(w.getReservationId())).count());
Optional<WaitingQueueSubscription> first = subscriptions.stream().filter(w -> w.getStatus().equals(WaitingQueueSubscription.Status.PENDING)).findFirst();
assertTrue(first.isPresent());
assertEquals("Giuseppe Garibaldi", first.get().getFullName());

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import alfio.model.modification.TicketReservationWithOptionalCodeModification;
import alfio.model.system.Configuration;
import alfio.model.system.ConfigurationKeys;
import alfio.repository.WaitingQueueRepository;
import alfio.util.TemplateManager;
import com.insightfullogic.lambdabehave.JunitSuiteRunner;
import org.apache.commons.lang3.tuple.Triple;
Expand All @@ -47,7 +48,8 @@ public class WaitingQueueSubscriptionProcessorTest {{
NotificationManager notificationManager = it.usesMock(NotificationManager.class);
MessageSource messageSource = it.usesMock(MessageSource.class);
TemplateManager templateManager = it.usesMock(TemplateManager.class);
WaitingQueueSubscriptionProcessor processor = new WaitingQueueSubscriptionProcessor(eventManager, ticketReservationManager, configurationManager, waitingQueueManager, notificationManager, messageSource, templateManager);
WaitingQueueRepository waitingQueueRepository = it.usesMock(WaitingQueueRepository.class);
WaitingQueueSubscriptionProcessor processor = new WaitingQueueSubscriptionProcessor(eventManager, ticketReservationManager, configurationManager, waitingQueueManager, notificationManager, waitingQueueRepository, messageSource, templateManager);
final int eventId = 1;
Event event = mock(Event.class);
final String reservationId = "reservation-id";
Expand Down
7 changes: 7 additions & 0 deletions src/test/java/alfio/test/util/IntegrationTestUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@
import alfio.model.modification.TicketCategoryModification;
import alfio.model.modification.support.LocationDescriptor;
import alfio.model.user.Organization;
import alfio.repository.user.AuthorityRepository;
import alfio.repository.user.OrganizationRepository;
import alfio.repository.user.UserRepository;
import org.apache.commons.lang3.tuple.Pair;

import java.math.BigDecimal;
Expand Down Expand Up @@ -68,4 +70,9 @@ public static Pair<Event, String> initEvent(List<TicketCategoryModification> cat
eventManager.createEvent(em);
return Pair.of(eventManager.getSingleEvent(eventName, username), username);
}

public static void initAdminUser(UserRepository userRepository, AuthorityRepository authorityRepository) {
userRepository.create(UserManager.ADMIN_USERNAME, "", "The", "Administrator", "admin@localhost", true);
authorityRepository.create(UserManager.ADMIN_USERNAME, AuthorityRepository.ROLE_ADMIN);
}
}

0 comments on commit 368924c

Please sign in to comment.