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.Entities; using ROLAC.API.Services; using Xunit; namespace ROLAC.API.Tests.Services; public class Form990ReportServiceTests { private static AppDbContext BuildDb() { var ctx = new DefaultHttpContext { User = new(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "t") })) }; 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 async Task SeedAsync(AppDbContext db) { db.Form990ExpenseLines.Add(new Form990ExpenseLine { Id = 7, LineCode = "7", Name_en = "Salaries", SortOrder = 5 }); db.Form990ExpenseLines.Add(new Form990ExpenseLine { Id = 24, LineCode = "24", Name_en = "Other", SortOrder = 21 }); db.Ministries.Add(new Ministry { Id = 1, Name_en = "Admin", DefaultFunctionalClass = "ManagementGeneral" }); db.Ministries.Add(new Ministry { Id = 2, Name_en = "Worship", DefaultFunctionalClass = "Program" }); db.ExpenseCategoryGroups.Add(new ExpenseCategoryGroup { Id = 1, Name_en = "Personnel", Form990LineId = 24 }); db.ExpenseSubCategories.Add(new ExpenseSubCategory { Id = 1, GroupId = 1, Name_en = "Salary", Form990LineId = 7 }); db.ExpenseSubCategories.Add(new ExpenseSubCategory { Id = 2, GroupId = 1, Name_en = "Misc", Form990LineId = null }); await db.SaveChangesAsync(); } private static Expense Exp(int min, int sub, decimal amt, string status, string? fc = null) => new() { MinistryId = min, CategoryGroupId = 1, SubCategoryId = sub, Type = "VendorPayment", Status = status, Amount = amt, Description = "x", ExpenseDate = new DateOnly(2026, 5, 10), FunctionalClass = fc, }; [Fact] public async Task Statement_AggregatesByLineAndFunction_WithFallbackAndUnmappedCount() { using var db = BuildDb(); await SeedAsync(db); db.Expenses.Add(Exp(2, 1, 100m, "Paid")); db.Expenses.Add(Exp(1, 1, 40m, "Approved")); db.Expenses.Add(Exp(2, 2, 25m, "Paid")); db.Expenses.Add(Exp(2, 1, 999m, "Draft")); db.Expenses.Add(Exp(1, 1, 10m, "Paid", fc: "Program")); await db.SaveChangesAsync(); var svc = new Form990ReportService(db); var stmt = await svc.GetFunctionalExpenseStatementAsync(null, null); var line7 = stmt.Rows.Single(r => r.LineCode == "7"); Assert.Equal(110m, line7.Program); Assert.Equal(40m, line7.ManagementGeneral); Assert.Equal(150m, line7.Total); var line24 = stmt.Rows.Single(r => r.LineCode == "24"); Assert.Equal(25m, line24.Program); Assert.Equal(1, stmt.UnmappedExpenseCount); Assert.Equal(175m, stmt.GrandTotal); Assert.Equal(135m, stmt.ProgramTotal); Assert.Equal(40m, stmt.ManagementGeneralTotal); } [Fact] public async Task Statement_RespectsDateRange() { using var db = BuildDb(); await SeedAsync(db); db.Expenses.Add(Exp(2, 1, 100m, "Paid")); var older = Exp(2, 1, 500m, "Paid"); older.ExpenseDate = new DateOnly(2026, 1, 1); db.Expenses.Add(older); await db.SaveChangesAsync(); var svc = new Form990ReportService(db); var stmt = await svc.GetFunctionalExpenseStatementAsync(new DateOnly(2026, 5, 1), new DateOnly(2026, 5, 31)); Assert.Equal(100m, stmt.GrandTotal); } }