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.Expense; using ROLAC.API.Entities; using ROLAC.API.Services; using Xunit; namespace ROLAC.API.Tests.Services; public class MonthlyStatementServiceTests { private static AppDbContext BuildDb() { var claims = new[] { new Claim(ClaimTypes.NameIdentifier, "test-user") }; var ctx = new DefaultHttpContext { User = new(new ClaimsIdentity(claims)) }; var mock = new Mock(); mock.Setup(x => x.HttpContext).Returns(ctx); return new AppDbContext(new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .AddInterceptors(new AuditSaveChangesInterceptor(new ROLAC.API.Services.Logging.CurrentUserAccessor(mock.Object))).Options); } private static MonthlyStatementService Build(AppDbContext db) { var mock = new Mock(); var ctx = new DefaultHttpContext { User = new(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "test-user") })) }; mock.Setup(x => x.HttpContext).Returns(ctx); return new MonthlyStatementService(db, mock.Object, ROLAC.API.Tests.TestSupport.NullAuditLogger.Instance); } [Fact] public async Task Create_ComputesGivingAndPaidExpenses_ForMonthOnly() { using var db = BuildDb(); db.GivingCategories.Add(new GivingCategory { Id = 1, Name_en = "Tithe" }); db.Ministries.Add(new Ministry { Id = 1, Name_en = "Admin" }); db.ExpenseCategoryGroups.Add(new ExpenseCategoryGroup { Id = 1, Name_en = "Other" }); db.ExpenseSubCategories.Add(new ExpenseSubCategory { Id = 1, GroupId = 1, Name_en = "Misc" }); db.Givings.Add(new Giving { GivingCategoryId = 1, Amount = 1000m, PaymentMethod = "Cash", GivingDate = new DateOnly(2026, 5, 10) }); db.Givings.Add(new Giving { GivingCategoryId = 1, Amount = 500m, PaymentMethod = "Cash", GivingDate = new DateOnly(2026, 6, 1) }); db.Expenses.Add(new Expense { MinistryId = 1, CategoryGroupId = 1, SubCategoryId = 1, Type = "VendorPayment", Status = "Paid", Amount = 300m, Description = "x", ExpenseDate = new DateOnly(2026, 5, 20) }); db.Expenses.Add(new Expense { MinistryId = 1, CategoryGroupId = 1, SubCategoryId = 1, Type = "StaffReimbursement", Status = "Approved", Amount = 999m, Description = "not paid", ExpenseDate = new DateOnly(2026, 5, 21) }); await db.SaveChangesAsync(); var svc = Build(db); var id = await svc.CreateAsync(new CreateMonthlyStatementRequest { Year = 2026, Month = 5, OpeningBalance = 2000m, TotalOtherIncome = 100m, BankStatementBalance = 2800m }); var dto = await svc.GetByIdAsync(id); Assert.Equal(1000m, dto!.TotalGiving); Assert.Equal(300m, dto.TotalExpenses); Assert.Equal(2800m, dto.CalculatedClosingBalance); Assert.Equal(0m, dto.Difference); } [Fact] public async Task Create_Duplicate_Throws() { using var db = BuildDb(); var svc = Build(db); await svc.CreateAsync(new CreateMonthlyStatementRequest { Year = 2026, Month = 5 }); await Assert.ThrowsAsync(() => svc.CreateAsync(new CreateMonthlyStatementRequest { Year = 2026, Month = 5 })); } [Fact] public async Task Update_AfterFinalize_Throws() { using var db = BuildDb(); var svc = Build(db); var id = await svc.CreateAsync(new CreateMonthlyStatementRequest { Year = 2026, Month = 5 }); await svc.FinalizeAsync(id); await Assert.ThrowsAsync(() => svc.UpdateAsync(id, new UpdateMonthlyStatementRequest { OpeningBalance = 1m })); } }