From e7fd11beb20577774413f19452035c8c70086e25 Mon Sep 17 00:00:00 2001 From: Omid Marfavi <21163286+marfavi@users.noreply.github.com> Date: Mon, 4 May 2026 16:24:31 +0200 Subject: [PATCH 1/2] feat: add quick stats --- .../Services/v2/IStatisticsService.cs | 12 + .../Services/v2/StatisticsService.cs | 100 +++++++ .../v2/Statistics/QuickStatResponse.cs | 42 +++ .../Services/v2/StatisticsServiceTests.cs | 267 ++++++++++++++++++ .../Controllers/v2/StatisticsController.cs | 52 ++++ coffeecard/CoffeeCard.WebApi/Startup.cs | 1 + 6 files changed, 474 insertions(+) create mode 100644 coffeecard/CoffeeCard.Library/Services/v2/IStatisticsService.cs create mode 100644 coffeecard/CoffeeCard.Library/Services/v2/StatisticsService.cs create mode 100644 coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Statistics/QuickStatResponse.cs create mode 100644 coffeecard/CoffeeCard.Tests.Unit/Services/v2/StatisticsServiceTests.cs create mode 100644 coffeecard/CoffeeCard.WebApi/Controllers/v2/StatisticsController.cs diff --git a/coffeecard/CoffeeCard.Library/Services/v2/IStatisticsService.cs b/coffeecard/CoffeeCard.Library/Services/v2/IStatisticsService.cs new file mode 100644 index 00000000..eb370f40 --- /dev/null +++ b/coffeecard/CoffeeCard.Library/Services/v2/IStatisticsService.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using CoffeeCard.Models.DataTransferObjects.v2.Statistics; +using CoffeeCard.Models.Entities; + +namespace CoffeeCard.Library.Services.v2 +{ + public interface IStatisticsService + { + Task> GetQuickStatsAsync(User user); + } +} diff --git a/coffeecard/CoffeeCard.Library/Services/v2/StatisticsService.cs b/coffeecard/CoffeeCard.Library/Services/v2/StatisticsService.cs new file mode 100644 index 00000000..b6d46c4f --- /dev/null +++ b/coffeecard/CoffeeCard.Library/Services/v2/StatisticsService.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using CoffeeCard.Library.Persistence; +using CoffeeCard.Library.Utils; +using CoffeeCard.Models.DataTransferObjects.v2.Statistics; +using CoffeeCard.Models.Entities; +using Microsoft.EntityFrameworkCore; + +namespace CoffeeCard.Library.Services.v2 +{ + public class StatisticsService : IStatisticsService + { + private readonly CoffeeCardContext _context; + private readonly IDateTimeProvider _dateTimeProvider; + + public StatisticsService(CoffeeCardContext context, IDateTimeProvider dateTimeProvider) + { + _context = context; + _dateTimeProvider = dateTimeProvider; + } + + public async Task> GetQuickStatsAsync(User user) + { + var utcNow = _dateTimeProvider.UtcNow(); + var todayStart = utcNow.Date; + var todayEnd = todayStart.AddDays(1); + var weekStart = GetWeekStart(utcNow); + var weekEnd = weekStart.AddDays(7); + + var totalDrinks = await _context.Tickets.CountAsync(ticket => + ticket.OwnerId == user.Id && ticket.Status == TicketStatus.Used + ); + + var drinksToday = await _context.Tickets.CountAsync(ticket => + ticket.Status == TicketStatus.Used + && ticket.DateUsed >= todayStart + && ticket.DateUsed < todayEnd + ); + + var favouriteDrink = await _context + .Tickets.Where(ticket => + ticket.OwnerId == user.Id + && ticket.Status == TicketStatus.Used + && ticket.UsedOnMenuItemId != null + ) + .GroupBy(ticket => ticket.UsedOnMenuItem.Name) + .Select(group => new { MenuItemName = group.Key, Count = group.Count() }) + .OrderByDescending(group => group.Count) + .ThenBy(group => group.MenuItemName) + .FirstOrDefaultAsync(); + + var drinksThisWeek = await _context.Tickets.CountAsync(ticket => + ticket.OwnerId == user.Id + && ticket.Status == TicketStatus.Used + && ticket.DateUsed >= weekStart + && ticket.DateUsed < weekEnd + ); + + return new List + { + new QuickStatResponse + { + Key = "total-drinks-user", + Title = "Drinks consumed by you", + Value = totalDrinks, + SupportingText = null, + }, + new QuickStatResponse + { + Key = "global-drinks-today", + Title = "Drinks consumed by ITU today", + Value = drinksToday, + SupportingText = null, + }, + new QuickStatResponse + { + Key = "favourite-drink", + Title = "Your favourite drink", + Value = favouriteDrink?.Count ?? 0, + SupportingText = favouriteDrink?.MenuItemName ?? "No drinks yet", + }, + new QuickStatResponse + { + Key = "drinks-this-week-user", + Title = "Drinks this week", + Value = drinksThisWeek, + SupportingText = null, + }, + }; + } + + private static DateTime GetWeekStart(DateTime utcNow) + { + var daysSinceMonday = ((int)utcNow.DayOfWeek + 6) % 7; + return utcNow.Date.AddDays(-daysSinceMonday); + } + } +} diff --git a/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Statistics/QuickStatResponse.cs b/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Statistics/QuickStatResponse.cs new file mode 100644 index 00000000..78aa6275 --- /dev/null +++ b/coffeecard/CoffeeCard.Models/DataTransferObjects/v2/Statistics/QuickStatResponse.cs @@ -0,0 +1,42 @@ +using System.ComponentModel.DataAnnotations; + +namespace CoffeeCard.Models.DataTransferObjects.v2.Statistics +{ + /// + /// A small stat card used by the quick statistics endpoint. + /// + /// + /// { + /// "key": "favourite-drink", + /// "title": "Your favourite drink", + /// "value": "128", + /// "supportingText": "Filter Coffee" + /// } + /// + public class QuickStatResponse + { + /// + /// Stable stat identifier. + /// + [Required] + public required string Key { get; set; } + + /// + /// Card title. + /// + [Required] + public required string Title { get; set; } + + /// + /// Main value shown in the card. + /// + [Required] + public required int Value { get; set; } + + /// + /// Smaller supporting text shown below the main value. + /// + [Required] + public required string? SupportingText { get; set; } + } +} diff --git a/coffeecard/CoffeeCard.Tests.Unit/Services/v2/StatisticsServiceTests.cs b/coffeecard/CoffeeCard.Tests.Unit/Services/v2/StatisticsServiceTests.cs new file mode 100644 index 00000000..5a4bcc1e --- /dev/null +++ b/coffeecard/CoffeeCard.Tests.Unit/Services/v2/StatisticsServiceTests.cs @@ -0,0 +1,267 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using CoffeeCard.Common.Configuration; +using CoffeeCard.Library.Persistence; +using CoffeeCard.Library.Services.v2; +using CoffeeCard.Library.Utils; +using CoffeeCard.Models.DataTransferObjects.v2.Purchase; +using CoffeeCard.Models.Entities; +using Microsoft.EntityFrameworkCore; +using Moq; +using Xunit; + +namespace CoffeeCard.Tests.Unit.Services.v2 +{ + public class StatisticsServiceTests + { + [Fact(DisplayName = "GetQuickStatsAsync returns the four quick stats")] + public async Task GetQuickStatsAsyncReturnsFourQuickStats() + { + // Arrange + var builder = new DbContextOptionsBuilder().UseInMemoryDatabase( + nameof(GetQuickStatsAsyncReturnsFourQuickStats) + ); + + var databaseSettings = new DatabaseSettings { SchemaName = "test" }; + var environmentSettings = new EnvironmentSettings() + { + EnvironmentType = EnvironmentType.Test, + }; + + await using var context = new CoffeeCardContext( + builder.Options, + databaseSettings, + environmentSettings + ); + + var swu = new Programme + { + Id = 1, + ShortName = "SWU", + FullName = "Software Development", + SortPriority = 1, + }; + var ds = new Programme + { + Id = 2, + ShortName = "DS", + FullName = "Data Science", + SortPriority = 2, + }; + + var user = new User + { + Id = 1, + Name = "User1", + Email = "user1@itu.dk", + Password = "password", + Salt = "salt", + DateCreated = new DateTime(2025, 1, 1), + IsVerified = true, + PrivacyActivated = false, + UserGroup = UserGroup.Customer, + UserState = UserState.Active, + ProgrammeId = swu.Id, + Programme = swu, + }; + + var otherUser = new User + { + Id = 2, + Name = "User2", + Email = "user2@itu.dk", + Password = "password", + Salt = "salt", + DateCreated = new DateTime(2025, 1, 1), + IsVerified = true, + PrivacyActivated = false, + UserGroup = UserGroup.Customer, + UserState = UserState.Active, + ProgrammeId = ds.Id, + Programme = ds, + }; + + context.Programmes.AddRange(swu, ds); + context.Users.AddRange(user, otherUser); + + var currentTime = new DateTime(2026, 5, 3, 12, 0, 0, DateTimeKind.Utc); + + var latte = new MenuItem { Id = 1, Name = "Latte" }; + var americano = new MenuItem { Id = 2, Name = "Americano" }; + var espresso = new MenuItem { Id = 3, Name = "Espresso" }; + + context.MenuItems.AddRange(latte, americano, espresso); + + var purchases = new List + { + new Purchase + { + Id = 1, + ProductName = "Large", + ProductId = 1, + Price = 20, + NumberOfTickets = 1, + OrderId = "order-1", + Status = PurchaseStatus.Completed, + Type = PurchaseType.Free, + PurchasedById = user.Id, + PurchasedBy = user, + DateCreated = currentTime.AddDays(-1), + }, + new Purchase + { + Id = 2, + ProductName = "Small", + ProductId = 2, + Price = 20, + NumberOfTickets = 1, + OrderId = "order-2", + Status = PurchaseStatus.Completed, + Type = PurchaseType.Free, + PurchasedById = user.Id, + PurchasedBy = user, + DateCreated = currentTime.AddDays(-2), + }, + new Purchase + { + Id = 3, + ProductName = "Large", + ProductId = 1, + Price = 20, + NumberOfTickets = 1, + OrderId = "order-3", + Status = PurchaseStatus.Completed, + Type = PurchaseType.Free, + PurchasedById = user.Id, + PurchasedBy = user, + DateCreated = currentTime.AddDays(-3), + }, + new Purchase + { + Id = 4, + ProductName = "Small", + ProductId = 3, + Price = 20, + NumberOfTickets = 1, + OrderId = "order-4", + Status = PurchaseStatus.Completed, + Type = PurchaseType.Free, + PurchasedById = otherUser.Id, + PurchasedBy = otherUser, + DateCreated = currentTime.AddDays(-1), + }, + }; + + var tickets = new List + { + new Ticket + { + Id = 1, + DateCreated = currentTime.AddDays(-3), + DateUsed = currentTime.AddDays(-3), + ProductId = 1, + Status = TicketStatus.Used, + OwnerId = user.Id, + Owner = user, + PurchaseId = purchases[0].Id, + Purchase = purchases[0], + UsedOnMenuItemId = latte.Id, + UsedOnMenuItem = latte, + }, + new Ticket + { + Id = 2, + DateCreated = currentTime.AddDays(-2), + DateUsed = currentTime.AddDays(-2), + ProductId = 2, + Status = TicketStatus.Used, + OwnerId = user.Id, + Owner = user, + PurchaseId = purchases[1].Id, + Purchase = purchases[1], + UsedOnMenuItemId = americano.Id, + UsedOnMenuItem = americano, + }, + new Ticket + { + Id = 3, + DateCreated = currentTime.AddDays(-1), + DateUsed = currentTime.AddDays(-1), + ProductId = 1, + Status = TicketStatus.Used, + OwnerId = user.Id, + Owner = user, + PurchaseId = purchases[2].Id, + Purchase = purchases[2], + UsedOnMenuItemId = latte.Id, + UsedOnMenuItem = latte, + }, + new Ticket + { + Id = 4, + DateCreated = currentTime, + DateUsed = currentTime, + ProductId = 3, + Status = TicketStatus.Used, + OwnerId = otherUser.Id, + Owner = otherUser, + PurchaseId = purchases[3].Id, + Purchase = purchases[3], + UsedOnMenuItemId = espresso.Id, + UsedOnMenuItem = espresso, + }, + new Ticket + { + Id = 5, + DateCreated = currentTime, + DateUsed = currentTime, + ProductId = 3, + Status = TicketStatus.Used, + OwnerId = otherUser.Id, + Owner = otherUser, + PurchaseId = purchases[3].Id, + Purchase = purchases[3], + UsedOnMenuItemId = espresso.Id, + UsedOnMenuItem = espresso, + }, + }; + + context.Purchases.AddRange(purchases); + context.Tickets.AddRange(tickets); + await context.SaveChangesAsync(); + + var dateTimeProvider = new Mock(); + dateTimeProvider.Setup(provider => provider.UtcNow()).Returns(currentTime); + + var statisticsService = new StatisticsService(context, dateTimeProvider.Object); + + // Act + var result = (await statisticsService.GetQuickStatsAsync(user)).ToList(); + + // Assert + Assert.Equal(4, result.Count); + + var totalDrinks = Assert.Single(result.Where(stat => stat.Key == "total-drinks-user")); + Assert.Equal(3, totalDrinks.Value); + Assert.Null(totalDrinks.SupportingText); + + var drinksToday = Assert.Single( + result.Where(stat => stat.Key == "global-drinks-today") + ); + Assert.Equal(2, drinksToday.Value); + Assert.Null(drinksToday.SupportingText); + + var favouriteDrink = Assert.Single(result.Where(stat => stat.Key == "favourite-drink")); + Assert.Equal(2, favouriteDrink.Value); + Assert.Equal("Latte", favouriteDrink.SupportingText); + + var drinksThisWeek = Assert.Single( + result.Where(stat => stat.Key == "drinks-this-week-user") + ); + Assert.Equal(3, drinksThisWeek.Value); + Assert.Null(drinksThisWeek.SupportingText); + } + } +} diff --git a/coffeecard/CoffeeCard.WebApi/Controllers/v2/StatisticsController.cs b/coffeecard/CoffeeCard.WebApi/Controllers/v2/StatisticsController.cs new file mode 100644 index 00000000..505f9ecc --- /dev/null +++ b/coffeecard/CoffeeCard.WebApi/Controllers/v2/StatisticsController.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using CoffeeCard.Common.Errors; +using CoffeeCard.Library.Services.v2; +using CoffeeCard.Library.Utils; +using CoffeeCard.Models.DataTransferObjects.v2.Statistics; +using CoffeeCard.WebApi.Helpers; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; + +namespace CoffeeCard.WebApi.Controllers.v2 +{ + /// + /// Controller for public statistics endpoints. + /// + [ApiController] + [ApiVersion("2")] + [Route("api/v{version:apiVersion}/statistics")] + [Authorize] + public class StatisticsController : ControllerBase + { + private readonly IStatisticsService _statisticsService; + private readonly ClaimsUtilities _claimsUtilities; + + /// + /// Initializes a new instance of the class. + /// + public StatisticsController( + IStatisticsService statisticsService, + ClaimsUtilities claimsUtilities + ) + { + _statisticsService = statisticsService; + _claimsUtilities = claimsUtilities; + } + + /// + /// Get some quick stats about the user and general drink consumption at ITU. + /// + /// Quick stats + /// Invalid credentials + [HttpGet("quick")] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ApiError), StatusCodes.Status401Unauthorized)] + public async Task>> GetQuickStats() + { + var user = await _claimsUtilities.ValidateAndReturnUserFromClaimAsync(User.Claims); + return Ok(await _statisticsService.GetQuickStatsAsync(user)); + } + } +} diff --git a/coffeecard/CoffeeCard.WebApi/Startup.cs b/coffeecard/CoffeeCard.WebApi/Startup.cs index a2f5efd2..2190a51a 100644 --- a/coffeecard/CoffeeCard.WebApi/Startup.cs +++ b/coffeecard/CoffeeCard.WebApi/Startup.cs @@ -162,6 +162,7 @@ public void ConfigureServices(IServiceCollection services) >(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddFeatureManagement(); From 58b15aae00fe4c4b5a6b3468b01162b828204103 Mon Sep 17 00:00:00 2001 From: Omid Marfavi <21163286+marfavi@users.noreply.github.com> Date: Mon, 4 May 2026 16:36:29 +0200 Subject: [PATCH 2/2] Use Builders for test data --- .../Services/v2/StatisticsServiceTests.cs | 322 ++++++++---------- 1 file changed, 143 insertions(+), 179 deletions(-) diff --git a/coffeecard/CoffeeCard.Tests.Unit/Services/v2/StatisticsServiceTests.cs b/coffeecard/CoffeeCard.Tests.Unit/Services/v2/StatisticsServiceTests.cs index 5a4bcc1e..42623df6 100644 --- a/coffeecard/CoffeeCard.Tests.Unit/Services/v2/StatisticsServiceTests.cs +++ b/coffeecard/CoffeeCard.Tests.Unit/Services/v2/StatisticsServiceTests.cs @@ -8,6 +8,7 @@ using CoffeeCard.Library.Utils; using CoffeeCard.Models.DataTransferObjects.v2.Purchase; using CoffeeCard.Models.Entities; +using CoffeeCard.Tests.Common.Builders; using Microsoft.EntityFrameworkCore; using Moq; using Xunit; @@ -36,199 +37,162 @@ public async Task GetQuickStatsAsyncReturnsFourQuickStats() environmentSettings ); - var swu = new Programme - { - Id = 1, - ShortName = "SWU", - FullName = "Software Development", - SortPriority = 1, - }; - var ds = new Programme - { - Id = 2, - ShortName = "DS", - FullName = "Data Science", - SortPriority = 2, - }; + var user = UserBuilder.DefaultCustomer().WithId(1).Build(); - var user = new User - { - Id = 1, - Name = "User1", - Email = "user1@itu.dk", - Password = "password", - Salt = "salt", - DateCreated = new DateTime(2025, 1, 1), - IsVerified = true, - PrivacyActivated = false, - UserGroup = UserGroup.Customer, - UserState = UserState.Active, - ProgrammeId = swu.Id, - Programme = swu, - }; + var otherUser = UserBuilder.DefaultCustomer().WithId(2).Build(); - var otherUser = new User - { - Id = 2, - Name = "User2", - Email = "user2@itu.dk", - Password = "password", - Salt = "salt", - DateCreated = new DateTime(2025, 1, 1), - IsVerified = true, - PrivacyActivated = false, - UserGroup = UserGroup.Customer, - UserState = UserState.Active, - ProgrammeId = ds.Id, - Programme = ds, - }; - - context.Programmes.AddRange(swu, ds); context.Users.AddRange(user, otherUser); var currentTime = new DateTime(2026, 5, 3, 12, 0, 0, DateTimeKind.Utc); - var latte = new MenuItem { Id = 1, Name = "Latte" }; - var americano = new MenuItem { Id = 2, Name = "Americano" }; - var espresso = new MenuItem { Id = 3, Name = "Espresso" }; + var latte = MenuItemBuilder.Simple().WithId(1).WithName("Latte").Build(); + var americano = MenuItemBuilder.Simple().WithId(2).WithName("Americano").Build(); + var espresso = MenuItemBuilder.Simple().WithId(3).WithName("Espresso").Build(); context.MenuItems.AddRange(latte, americano, espresso); - var purchases = new List - { - new Purchase - { - Id = 1, - ProductName = "Large", - ProductId = 1, - Price = 20, - NumberOfTickets = 1, - OrderId = "order-1", - Status = PurchaseStatus.Completed, - Type = PurchaseType.Free, - PurchasedById = user.Id, - PurchasedBy = user, - DateCreated = currentTime.AddDays(-1), - }, - new Purchase - { - Id = 2, - ProductName = "Small", - ProductId = 2, - Price = 20, - NumberOfTickets = 1, - OrderId = "order-2", - Status = PurchaseStatus.Completed, - Type = PurchaseType.Free, - PurchasedById = user.Id, - PurchasedBy = user, - DateCreated = currentTime.AddDays(-2), - }, - new Purchase - { - Id = 3, - ProductName = "Large", - ProductId = 1, - Price = 20, - NumberOfTickets = 1, - OrderId = "order-3", - Status = PurchaseStatus.Completed, - Type = PurchaseType.Free, - PurchasedById = user.Id, - PurchasedBy = user, - DateCreated = currentTime.AddDays(-3), - }, - new Purchase - { - Id = 4, - ProductName = "Small", - ProductId = 3, - Price = 20, - NumberOfTickets = 1, - OrderId = "order-4", - Status = PurchaseStatus.Completed, - Type = PurchaseType.Free, - PurchasedById = otherUser.Id, - PurchasedBy = otherUser, - DateCreated = currentTime.AddDays(-1), - }, - }; + var largeProduct = ProductBuilder.Simple().WithId(1).WithName("Large").Build(); + var smallProduct = ProductBuilder.Simple().WithId(2).WithName("Small").Build(); + var fancyProduct = ProductBuilder.Simple().WithId(3).WithName("Fancy").Build(); + + context.Products.AddRange(largeProduct, smallProduct, fancyProduct); + + var purchase1 = PurchaseBuilder + .Simple() + .WithId(1) + .WithProductName("Large") + .WithProductId(largeProduct.Id) + .WithProduct(largeProduct) + .WithPrice(20) + .WithNumberOfTickets(1) + .WithOrderId("order-1") + .WithStatus(PurchaseStatus.Completed) + .WithType(PurchaseType.Free) + .WithPurchasedById(user.Id) + .WithPurchasedBy(user) + .WithDateCreated(currentTime.AddDays(-1)) + .Build(); + var purchase2 = PurchaseBuilder + .Simple() + .WithId(2) + .WithProductName("Small") + .WithProductId(smallProduct.Id) + .WithProduct(smallProduct) + .WithPrice(20) + .WithNumberOfTickets(1) + .WithOrderId("order-2") + .WithStatus(PurchaseStatus.Completed) + .WithType(PurchaseType.Free) + .WithPurchasedById(user.Id) + .WithPurchasedBy(user) + .WithDateCreated(currentTime.AddDays(-2)) + .Build(); + var purchase3 = PurchaseBuilder + .Simple() + .WithId(3) + .WithProductName("Large") + .WithProductId(largeProduct.Id) + .WithProduct(largeProduct) + .WithPrice(20) + .WithNumberOfTickets(1) + .WithOrderId("order-3") + .WithStatus(PurchaseStatus.Completed) + .WithType(PurchaseType.Free) + .WithPurchasedById(user.Id) + .WithPurchasedBy(user) + .WithDateCreated(currentTime.AddDays(-3)) + .Build(); + var purchase4 = PurchaseBuilder + .Simple() + .WithId(4) + .WithProductName("Small") + .WithProductId(fancyProduct.Id) + .WithProduct(fancyProduct) + .WithPrice(20) + .WithNumberOfTickets(1) + .WithOrderId("order-4") + .WithStatus(PurchaseStatus.Completed) + .WithType(PurchaseType.Free) + .WithPurchasedById(otherUser.Id) + .WithPurchasedBy(otherUser) + .WithDateCreated(currentTime.AddDays(-1)) + .Build(); var tickets = new List { - new Ticket - { - Id = 1, - DateCreated = currentTime.AddDays(-3), - DateUsed = currentTime.AddDays(-3), - ProductId = 1, - Status = TicketStatus.Used, - OwnerId = user.Id, - Owner = user, - PurchaseId = purchases[0].Id, - Purchase = purchases[0], - UsedOnMenuItemId = latte.Id, - UsedOnMenuItem = latte, - }, - new Ticket - { - Id = 2, - DateCreated = currentTime.AddDays(-2), - DateUsed = currentTime.AddDays(-2), - ProductId = 2, - Status = TicketStatus.Used, - OwnerId = user.Id, - Owner = user, - PurchaseId = purchases[1].Id, - Purchase = purchases[1], - UsedOnMenuItemId = americano.Id, - UsedOnMenuItem = americano, - }, - new Ticket - { - Id = 3, - DateCreated = currentTime.AddDays(-1), - DateUsed = currentTime.AddDays(-1), - ProductId = 1, - Status = TicketStatus.Used, - OwnerId = user.Id, - Owner = user, - PurchaseId = purchases[2].Id, - Purchase = purchases[2], - UsedOnMenuItemId = latte.Id, - UsedOnMenuItem = latte, - }, - new Ticket - { - Id = 4, - DateCreated = currentTime, - DateUsed = currentTime, - ProductId = 3, - Status = TicketStatus.Used, - OwnerId = otherUser.Id, - Owner = otherUser, - PurchaseId = purchases[3].Id, - Purchase = purchases[3], - UsedOnMenuItemId = espresso.Id, - UsedOnMenuItem = espresso, - }, - new Ticket - { - Id = 5, - DateCreated = currentTime, - DateUsed = currentTime, - ProductId = 3, - Status = TicketStatus.Used, - OwnerId = otherUser.Id, - Owner = otherUser, - PurchaseId = purchases[3].Id, - Purchase = purchases[3], - UsedOnMenuItemId = espresso.Id, - UsedOnMenuItem = espresso, - }, + TicketBuilder + .Simple() + .WithId(1) + .WithDateCreated(currentTime.AddDays(-3)) + .WithDateUsed(currentTime.AddDays(-3)) + .WithProductId(largeProduct.Id) + .WithStatus(TicketStatus.Used) + .WithOwnerId(user.Id) + .WithOwner(user) + .WithPurchaseId(purchase1.Id) + .WithPurchase(purchase1) + .WithUsedOnMenuItemId(latte.Id) + .WithUsedOnMenuItem(latte) + .Build(), + TicketBuilder + .Simple() + .WithId(2) + .WithDateCreated(currentTime.AddDays(-2)) + .WithDateUsed(currentTime.AddDays(-2)) + .WithProductId(smallProduct.Id) + .WithStatus(TicketStatus.Used) + .WithOwnerId(user.Id) + .WithOwner(user) + .WithPurchaseId(purchase2.Id) + .WithPurchase(purchase2) + .WithUsedOnMenuItemId(americano.Id) + .WithUsedOnMenuItem(americano) + .Build(), + TicketBuilder + .Simple() + .WithId(3) + .WithDateCreated(currentTime.AddDays(-1)) + .WithDateUsed(currentTime.AddDays(-1)) + .WithProductId(largeProduct.Id) + .WithStatus(TicketStatus.Used) + .WithOwnerId(user.Id) + .WithOwner(user) + .WithPurchaseId(purchase3.Id) + .WithPurchase(purchase3) + .WithUsedOnMenuItemId(latte.Id) + .WithUsedOnMenuItem(latte) + .Build(), + TicketBuilder + .Simple() + .WithId(4) + .WithDateCreated(currentTime) + .WithDateUsed(currentTime) + .WithProductId(fancyProduct.Id) + .WithStatus(TicketStatus.Used) + .WithOwnerId(otherUser.Id) + .WithOwner(otherUser) + .WithPurchaseId(purchase4.Id) + .WithPurchase(purchase4) + .WithUsedOnMenuItemId(espresso.Id) + .WithUsedOnMenuItem(espresso) + .Build(), + TicketBuilder + .Simple() + .WithId(5) + .WithDateCreated(currentTime) + .WithDateUsed(currentTime) + .WithProductId(fancyProduct.Id) + .WithStatus(TicketStatus.Used) + .WithOwnerId(otherUser.Id) + .WithOwner(otherUser) + .WithPurchaseId(purchase4.Id) + .WithPurchase(purchase4) + .WithUsedOnMenuItemId(espresso.Id) + .WithUsedOnMenuItem(espresso) + .Build(), }; - context.Purchases.AddRange(purchases); + context.Purchases.AddRange(purchase1, purchase2, purchase3, purchase4); context.Tickets.AddRange(tickets); await context.SaveChangesAsync();