using Microsoft.EntityFrameworkCore; using System.Security.Claims; using Microsoft.AspNetCore.Http; using Moq; using ROLAC.API.Data; using ROLAC.API.Data.Interceptors; using ROLAC.API.DTOs.Giving; using ROLAC.API.Entities; using ROLAC.API.Services; using Xunit; namespace ROLAC.API.Tests.Services; public class OfferingSessionServiceTests { private static IHttpContextAccessor BuildAccessor(string userId = "test-user") { var claims = new[] { new Claim(ClaimTypes.NameIdentifier, userId) }; var ctx = new DefaultHttpContext { User = new(new ClaimsIdentity(claims)) }; var mock = new Mock(); mock.Setup(x => x.HttpContext).Returns(ctx); return mock.Object; } private static AppDbContext BuildDb(string userId = "test-user") { var interceptor = new AuditSaveChangesInterceptor(BuildAccessor(userId)); return new AppDbContext( new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .AddInterceptors(interceptor) .Options); } private static async Task SeedCategoryAsync(AppDbContext db) { var c = new GivingCategory { Name_en = "Tithe", IsActive = true }; db.GivingCategories.Add(c); await db.SaveChangesAsync(); return c.Id; } private static CreateOfferingSessionRequest BuildRequest(int catId, DateOnly date) => new() { SessionDate = date, CashTotal = 150m, CheckTotal = 0m, Givings = [ new() { GivingCategoryId = catId, Amount = 100m, PaymentMethod = "Cash" }, new() { GivingCategoryId = catId, Amount = 50m, PaymentMethod = "Cash", IsAnonymous = true }, ], }; [Fact] public async Task CreateAsync_RecomputesSystemTotalAndDifference_ServerSide() { using var db = BuildDb(); var catId = await SeedCategoryAsync(db); var svc = new OfferingSessionService(db, BuildAccessor()); var id = await svc.CreateAsync(BuildRequest(catId, new DateOnly(2026, 5, 31))); var saved = await db.OfferingSessions.FindAsync(id); Assert.Equal("Submitted", saved!.Status); Assert.Equal(150m, saved.SystemTotal); Assert.Equal(0m, saved.Difference); Assert.NotNull(saved.SubmittedAt); Assert.Equal(2, await db.Givings.CountAsync(g => g.OfferingSessionId == id)); } [Fact] public async Task CreateAsync_LinesGetSessionDateAndAnonymousNullsMember() { using var db = BuildDb(); var catId = await SeedCategoryAsync(db); var svc = new OfferingSessionService(db, BuildAccessor()); var id = await svc.CreateAsync(BuildRequest(catId, new DateOnly(2026, 5, 31))); var lines = await db.Givings.Where(g => g.OfferingSessionId == id).ToListAsync(); Assert.All(lines, l => Assert.Equal(new DateOnly(2026,5,31), l.GivingDate)); Assert.Contains(lines, l => l.IsAnonymous && l.MemberId == null); } [Fact] public async Task CreateAsync_Throws_OnDuplicateSessionDate() { using var db = BuildDb(); var catId = await SeedCategoryAsync(db); var svc = new OfferingSessionService(db, BuildAccessor()); await svc.CreateAsync(BuildRequest(catId, new DateOnly(2026, 5, 31))); await Assert.ThrowsAsync(() => svc.CreateAsync(BuildRequest(catId, new DateOnly(2026, 5, 31)))); } [Fact] public async Task ReplaceAsync_Throws_WhenSessionIsSubmitted() { using var db = BuildDb(); var catId = await SeedCategoryAsync(db); var svc = new OfferingSessionService(db, BuildAccessor()); var id = await svc.CreateAsync(BuildRequest(catId, new DateOnly(2026, 5, 31))); await Assert.ThrowsAsync(() => svc.ReplaceAsync(id, BuildRequest(catId, new DateOnly(2026, 5, 31)))); } [Fact] public async Task ReopenThenReplace_SwapsLinesAndResubmits() { using var db = BuildDb(); var catId = await SeedCategoryAsync(db); var svc = new OfferingSessionService(db, BuildAccessor()); var id = await svc.CreateAsync(BuildRequest(catId, new DateOnly(2026, 5, 31))); await svc.ReopenAsync(id); var reopened = await db.OfferingSessions.FindAsync(id); Assert.Equal("Draft", reopened!.Status); var newReq = new CreateOfferingSessionRequest { SessionDate = new DateOnly(2026,5,31), CashTotal = 200m, CheckTotal = 0m, Givings = [ new() { GivingCategoryId = catId, Amount = 200m, PaymentMethod = "Cash" } ], }; await svc.ReplaceAsync(id, newReq); var after = await db.OfferingSessions.FindAsync(id); Assert.Equal("Submitted", after!.Status); Assert.Equal(200m, after.SystemTotal); Assert.Equal(1, await db.Givings.CountAsync(g => g.OfferingSessionId == id)); } }