/// <summary> /// Runs the find and hold seats ticket service /// </summary> /// <param name="numSeats">the number of seats to be held</param> /// <param name="email">the customer email address</param> /// <returns>The seathold object</returns> public static SeatHold FindAndHoldSeats(int numSeats, string email) { Console.WriteLine("Attempting to find " + numSeats.ToString() + " for " + email); SeatHold seatHold = null; try { seatHold = ticketService.findAndHoldSeats(numSeats, email); } catch (Exception e) { Console.WriteLine("SeatHold failed: " + e.Message.ToString()); return(seatHold); } if (seatHold != null) { Console.WriteLine("Seat hold successful. Expires in " + SeatHold.ExpiresAfter.TotalSeconds.ToString() + " seconds\n"); Console.WriteLine(seatHold.ToString()); return(seatHold); } Console.WriteLine("Seat Hold was not successful\n"); return(seatHold); }
/// <summary> /// Tests the reserveSeats function of the ticket service /// </summary> public static void TestReserveSeats() { if (ticketService == null || ticketedEvent == null) { Console.WriteLine("there is no ticket service. Run ts create or test case 1"); } int reservationStartCount = ticketedEvent.Reservations.Count; int availableSeatStartCount = ticketedEvent.NumberOfAvailableSeats; int seatHoldStartCount = ticketedEvent.SeatHolds.Count; TimeSpan seatHoldExpiresAfter = SeatHold.ExpiresAfter; SeatHold.SetSeatHoldDuration(new TimeSpan(0, 0, 3)); Console.WriteLine("Creating seat hold"); SeatHold seatHold = ticketService.findAndHoldSeats(10, "*****@*****.**"); Console.WriteLine("Creating reservation"); string confirmationCode = ticketService.reserveSeats(seatHold.Id, seatHold.CustomerEmail); Assert.IsTrue(!string.IsNullOrEmpty(confirmationCode)); Assert.AreEqual(ticketedEvent.SeatHolds.Count, seatHoldStartCount); Assert.AreEqual(ticketedEvent.Reservations.Count, reservationStartCount + 1); Assert.AreEqual(ticketedEvent.NumberOfAvailableSeats, availableSeatStartCount - 10); Assert.IsTrue(ticketedEvent.Reservations.Contains((confirmationCode, seatHold))); // wait for seat holds to expire. I'd want to use something other than thread sleep in production, but this works for now. Thread.Sleep((int)SeatHold.ExpiresAfter.TotalMilliseconds + 100); // reset seat hold expiration SeatHold.SetSeatHoldDuration(seatHoldExpiresAfter); }
/// <summary> /// Releases the specified seat hold. /// </summary> /// <param name="seatHold">The seat hold object.</param> private void ReleaseSeatHold(SeatHold seatHold) { foreach (Seat seat in seatHold.Seats) { this.MakeSeatAvailable(seat); } seatHold.Expired = true; }
/// <summary> /// Tests the findAndHoldSeats function of the ticket service /// </summary> public static void TestFindAndHoldSeats() { if (ticketService == null || ticketedEvent == null) { Console.WriteLine("there is no ticket service. Run ts create or test case 1"); } TimeSpan seatHoldExpiresAfter = SeatHold.ExpiresAfter; SeatHold.SetSeatHoldDuration(new TimeSpan(0, 0, 3)); int seatHoldStartCount = ticketedEvent.SeatHolds.Count; int numAvailableStartCount = ticketedEvent.NumberOfAvailableSeats; Console.WriteLine("Creating Seat Hold"); SeatHold seatHold = ticketService.findAndHoldSeats(10, "*****@*****.**"); System.Timers.Timer seatHoldExpiration = new System.Timers.Timer(SeatHold.ExpiresAfter.TotalMilliseconds + 1); seatHoldExpiration.Elapsed += (source, args) => { Console.WriteLine("Seat hold expired!"); Assert.AreEqual(ticketedEvent.NumberOfAvailableSeats, numAvailableStartCount); Assert.AreEqual(ticketedEvent.SeatHolds.Count, seatHoldStartCount); seatHoldExpiration.Stop(); }; seatHoldExpiration.Start(); Assert.AreEqual(ticketedEvent.SeatHolds.Count, seatHoldStartCount + 1); Assert.AreEqual(seatHold.CustomerEmail, "*****@*****.**"); Assert.AreEqual(seatHold.Seats.Count, 10); Assert.AreEqual(ticketedEvent.NumberOfAvailableSeats, numAvailableStartCount - 10); try { ticketService.findAndHoldSeats(0, "*****@*****.**"); Assert.Fail("find and hold seats failed to throw exception from passing 0 for numseats"); } catch (ArgumentException e) { Assert.IsTrue(e.Message.Contains("Cannot hold 0 or less seats.")); } try { ticketService.findAndHoldSeats(1, "invalidemailaddress"); Assert.Fail("find and hold seats failed to throw exception after invalid email address passed"); } catch (ArgumentException e) { Assert.IsTrue(e.Message.Contains("Invalid email address. ")); } Console.WriteLine("Waiting for seat holds to expire"); // wait for seat holds to expire. I'd want to use something other than thread sleep in production, but this works for now. Thread.Sleep((int)SeatHold.ExpiresAfter.TotalMilliseconds + 100); // reset seat hold expiration SeatHold.SetSeatHoldDuration(seatHoldExpiresAfter); }
public void ReserveSeats_WithDifferentEmail_ShouldThrowException() { // Arrange BasicVenue venue = new BasicVenue(3, 3); ITicketService ticketService = new BasicTicketService(venue, TestData.HourHoldDuration); // Act SeatHold seatHold = ticketService.FindAndHoldSeats(1, TestData.TestCustomerEmail); ticketService.ReserveSeats(seatHold.Id, TestData.SecondTestCustomerEmail); }
public void ReserveSeats_ExpiredSeatHold_ShouldThrowException() { // Arrange BasicVenue venue = new BasicVenue(3, 3); ITicketService ticketService = new BasicTicketService(venue, TestData.NegativeHoldDuration); // Act SeatHold seatHold = ticketService.FindAndHoldSeats(1, TestData.TestCustomerEmail); ticketService.ReserveSeats(seatHold.Id, TestData.TestCustomerEmail); }
public void FindAndHoldSeats_RequestingOneSeat_ShouldReturnOneSeat() { // Arrange BasicVenue singleSeatVenue = new BasicVenue(1, 1); ITicketService ticketService = new BasicTicketService(singleSeatVenue, TestData.HourHoldDuration); // Act SeatHold seatHold = ticketService.FindAndHoldSeats(1, TestData.TestCustomerEmail); // Assert Assert.AreEqual(1, seatHold.Seats.Count); }
public void ReserveSeats_ValidSeatHold_ShouldReserveSeats() { // Arrange BasicVenue venue = new BasicVenue(3, 3); ITicketService ticketService = new BasicTicketService(venue, TestData.HourHoldDuration, 8); // Act SeatHold seatHold = ticketService.FindAndHoldSeats(1, TestData.TestCustomerEmail); string reservationCode = ticketService.ReserveSeats(seatHold.Id, TestData.TestCustomerEmail); // Assert Assert.AreEqual(8, reservationCode.Length); }
public void FindAndHoldSeats_WhenReservationSizeLargerThanRowSize_ShouldSplitGroup() { // Arrange BasicVenue venue = new BasicVenue(3, 3); ITicketService ticketService = new BasicTicketService(venue, TestData.HourHoldDuration); // Act SeatHold seatHold = ticketService.FindAndHoldSeats(4, TestData.TestCustomerEmail); // Assert Assert.AreEqual(0, seatHold.Seats[0].RowNumber); Assert.AreEqual(0, seatHold.Seats[2].RowNumber); Assert.AreEqual(1, seatHold.Seats[3].RowNumber); }
public void FindAndHoldSeats_RequestingThreeSeats_ShouldReturnThreeAdjacentSeats() { // Arrange BasicVenue venue = new BasicVenue(5, 5); ITicketService ticketService = new BasicTicketService(venue, TestData.HourHoldDuration); // Act SeatHold seatHold = ticketService.FindAndHoldSeats(5, TestData.TestCustomerEmail); // Assert Assert.AreEqual(0, seatHold.Seats[0].SeatNumber); Assert.AreEqual(1, seatHold.Seats[1].SeatNumber); Assert.AreEqual(2, seatHold.Seats[2].SeatNumber); }
public void FindAndHoldSeats_FirstHoldReleased_ShouldGiveSecondHoldSameSeats() { // Arrange BasicVenue venue = new BasicVenue(3, 3); ITicketService ticketService = new BasicTicketService(venue, TestData.NegativeHoldDuration); // Act SeatHold firstSeatHold = ticketService.FindAndHoldSeats(3, TestData.TestCustomerEmail); SeatHold secondSeatHold = ticketService.FindAndHoldSeats(3, TestData.TestCustomerEmail); // Assert Assert.AreEqual(0, secondSeatHold.Seats[0].RowNumber); Assert.AreEqual(0, secondSeatHold.Seats[1].RowNumber); Assert.AreEqual(0, secondSeatHold.Seats[2].RowNumber); }
public void FindAndHoldSeats_ThreeSeatHolds_ShouldHaveIncreasingSeatIds() { // Arrange BasicVenue venue = new BasicVenue(3, 3); ITicketService ticketService = new BasicTicketService(venue, TestData.HourHoldDuration); // Act SeatHold firstSeatHold = ticketService.FindAndHoldSeats(1, TestData.TestCustomerEmail); SeatHold secondSeatHold = ticketService.FindAndHoldSeats(1, TestData.TestCustomerEmail); SeatHold thirdSeatHold = ticketService.FindAndHoldSeats(1, TestData.TestCustomerEmail); // Assert Assert.IsTrue(firstSeatHold.Id < secondSeatHold.Id); Assert.IsTrue(secondSeatHold.Id < thirdSeatHold.Id); }
public void ReserveSeats_TwoReservations_ShouldHaveDifferentReservationCodes() { // Arrange BasicVenue venue = new BasicVenue(3, 3); ITicketService ticketService = new BasicTicketService(venue, TestData.HourHoldDuration); // Act SeatHold firstSeatHold = ticketService.FindAndHoldSeats(1, TestData.TestCustomerEmail); SeatHold secondSeatHold = ticketService.FindAndHoldSeats(1, TestData.TestCustomerEmail); string firstReservationCode = ticketService.ReserveSeats(firstSeatHold.Id, TestData.TestCustomerEmail); string secondReservationCode = ticketService.ReserveSeats(secondSeatHold.Id, TestData.TestCustomerEmail); // Assert Assert.AreNotEqual(firstReservationCode, secondReservationCode, "Reservation codes should never be the same."); }
public void NumSeatsAvailable_AfterReservingSeatHold_ShouldReturnSameNumberOfAvailableSeats() { // Arrange BasicVenue oneRowFiveSeatsVenue = new BasicVenue(1, 5); ITicketService ticketService = new BasicTicketService(oneRowFiveSeatsVenue, TestData.HourHoldDuration); // Act SeatHold seatHold = ticketService.FindAndHoldSeats(3, TestData.TestCustomerEmail); int numSeatsAvailableAfterHold = ticketService.NumSeatsAvailable(); ticketService.ReserveSeats(seatHold.Id, TestData.TestCustomerEmail); int numSeatsAvailableAfterReservation = ticketService.NumSeatsAvailable(); // Assert Assert.AreEqual(2, numSeatsAvailableAfterHold); Assert.AreEqual(2, numSeatsAvailableAfterReservation); }
public void FindAndHoldSeats_WhenInsufficientAvailableSeatsInCloserRow_ShouldHoldSeatsInHigherRow() { // Arrange BasicVenue venue = new BasicVenue(3, 3); ITicketService ticketService = new BasicTicketService(venue, TestData.HourHoldDuration); // Act SeatHold firstSeatHold = ticketService.FindAndHoldSeats(2, TestData.TestCustomerEmail); SeatHold secondSeatHold = ticketService.FindAndHoldSeats(2, TestData.TestCustomerEmail); // Assert Assert.AreEqual(0, firstSeatHold.Seats[0].RowNumber); Assert.AreEqual(0, firstSeatHold.Seats[1].RowNumber); Assert.AreEqual(1, secondSeatHold.Seats[0].RowNumber); Assert.AreEqual(1, secondSeatHold.Seats[1].RowNumber); }
public void FindAndHoldSeats_WhenReservationSizeLargerThanAvailableSeatsInEachRow_ShouldSplitGroup() { // Arrange BasicVenue venue = new BasicVenue(2, 3); ITicketService ticketService = new BasicTicketService(venue, TestData.HourHoldDuration); // Act SeatHold firstSeatHold = ticketService.FindAndHoldSeats(2, TestData.TestCustomerEmail); SeatHold secondSeatHold = ticketService.FindAndHoldSeats(2, TestData.TestCustomerEmail); SeatHold thirdSeatHold = ticketService.FindAndHoldSeats(2, TestData.TestCustomerEmail); // Assert Assert.AreEqual(0, thirdSeatHold.Seats[0].RowNumber); Assert.AreEqual(2, thirdSeatHold.Seats[0].SeatNumber); Assert.AreEqual(1, thirdSeatHold.Seats[1].RowNumber); Assert.AreEqual(2, thirdSeatHold.Seats[1].SeatNumber); }
public async Task <SeatHold> FindAndHoldSeats(int numSeats, string customerEmail) { var unreservedSeats = (await FindUnreservedSeats()).Take(numSeats).ToList(); if (unreservedSeats.Count() < numSeats) { throw new NoSeatsAvailableException(); } var seatHold = new SeatHold() { HeldSeats = string.Join(',', unreservedSeats), CustomerEmail = customerEmail }; var options = new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = _settings.TicketHoldExpiration }; options.RegisterPostEvictionCallback((o, v, r, s) => { if (r == EvictionReason.Removed) { return; } var cnx = factory(); cnx.SeatHolds.Remove(v as SeatHold); cnx.SaveChanges(); }); await _context.SeatHolds.AddAsync(seatHold); await _context.SaveChangesAsync(); _cache.Set <SeatHold>(seatHold.SeatHoldId.ToString(), seatHold, options); return(seatHold); }
public void FindAndHoldSeats_FirstHoldExpiresWhileSecondStillValid_ShouldGiveLaterHoldsFirstHoldsSeats() { // Arrange BasicVenue venue = new BasicVenue(3, 3); // To mock DateTime.UtcNow, make it so if there are an even number of seats in the seat hold and there are an // even number of seat holds, it will expire immediately. Otherwise it will never expire. Func <bool, DateTime> dateTimeFunc = (b => b ? DateTime.MaxValue : DateTime.MinValue); ITicketService ticketService = new BasicTicketService(venue, TestData.HourHoldDuration, getNow: dateTimeFunc); // Act SeatHold firstSeatHold = ticketService.FindAndHoldSeats(2, TestData.TestCustomerEmail); SeatHold secondSeatHold = ticketService.FindAndHoldSeats(1, TestData.TestCustomerEmail); SeatHold thirdSeatHold = ticketService.FindAndHoldSeats(1, TestData.TestCustomerEmail); SeatHold fourthSeatHold = ticketService.FindAndHoldSeats(1, TestData.TestCustomerEmail); // Assert Assert.IsFalse(secondSeatHold.Expired); Assert.AreEqual(0, thirdSeatHold.Seats[0].SeatNumber); Assert.AreEqual(0, thirdSeatHold.Seats[0].RowNumber); Assert.AreEqual(1, fourthSeatHold.Seats[0].SeatNumber); Assert.AreEqual(0, fourthSeatHold.Seats[0].RowNumber); }
public SeatHold findAndHoldSeats(int numSeats, string customerEmail) { string error = string.Empty; if (!RegexUtilities.IsValidEmail(customerEmail)) { error += "Invalid email address. "; } if (numSeats < 1) { error += "Cannot hold 0 or less seats."; } if (!string.IsNullOrEmpty(error)) { throw new ArgumentException(error); } SeatHold seatHold = DataController.Instance.FindAndHoldSeats(TicketedEventName, numSeats, customerEmail); return(seatHold); }
/// <summary> /// Checks whether the seat hold has expired and outputs the number of seconds that the seat hold has been held. /// The argument for getNow is used for unit testing and should not affect production code. /// </summary> /// <param name="seatHold">The seat hold.</param> /// <param name="elapsedHoldTime">The time in seconds that the seat hold has been held.</param> /// <returns></returns> private bool SeatHoldHasExpired(SeatHold seatHold, out double elapsedHoldTime) { elapsedHoldTime = (this.getNow(this.seatHolds.Count % 2 == 0 && seatHold.Seats.Count % 2 == 0) - seatHold.HoldTime).TotalSeconds; return(elapsedHoldTime > this.maxHoldDuration); }
/// <summary> /// Find and hold the best available seats for a customer. /// </summary> /// <param name="numSeats">The number of seats to find and hold.</param> /// <param name="customerEmail">Unique identifier for the customer.</param> /// <returns>A SeatHold object identifying the specific seats and related information.</returns> public SeatHold FindAndHoldSeats(int numSeats, string customerEmail) { if (numSeats <= 0) { throw new ArgumentOutOfRangeException($"At least 1 seat must be requested."); } this.ReleaseExpiredSeatHolds(); int numSeatsAvailable = this.NumSeatsAvailable(); if (numSeatsAvailable < numSeats) { throw new TicketServiceException($"There are {numSeatsAvailable} seats available and so {numSeats} seats cannot be reserved."); } SeatHold seatHold = new SeatHold { CustomerEmail = customerEmail, Seats = new List <Seat>() }; // Try to find the closest row to the front with enough seats to seat everyone together. If there is no row in which everyone // can sit together, split group amongst rows closest to the front. Populate backup rows in case no single row with enough seats for whole group. int numSeatsToFill = numSeats; bool existsRowWithEnoughSeats = false; List <BasicRow> backupRows = new List <BasicRow>(); for (int i = firstRowWithAvailableSeats.RowNumber; i < this.Venue.NumRows; i++) { BasicRow row = this.Venue.Rows[i]; if (row.NumAvailableSeats >= numSeats) { existsRowWithEnoughSeats = true; List <Seat> heldSeats = this.HoldAvailableSeatsInRow(row, numSeats); seatHold.Seats.AddRange(heldSeats); break; } if (numSeatsToFill > 0 && row.NumAvailableSeats > 0) { backupRows.Add(row); numSeatsToFill -= row.NumAvailableSeats; } } if (!existsRowWithEnoughSeats) { numSeatsToFill = numSeats; foreach (BasicRow backupRow in backupRows) { List <Seat> heldSeats = this.HoldAvailableSeatsInRow(backupRow, numSeatsToFill); seatHold.Seats.AddRange(heldSeats); numSeatsToFill -= heldSeats.Count; } } this.Venue.TotalAvailableSeats -= seatHold.Seats.Count; seatHold.HoldTime = DateTime.UtcNow; seatHold.Id = Interlocked.Increment(ref incrementingSeatHoldId); this.seatHolds.Add(seatHold.Id, seatHold); return(seatHold); }