e04776460d
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
133 lines
4.9 KiB
C#
133 lines
4.9 KiB
C#
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<IHttpContextAccessor>();
|
|
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<AppDbContext>()
|
|
.UseInMemoryDatabase(Guid.NewGuid().ToString())
|
|
.AddInterceptors(interceptor)
|
|
.Options);
|
|
}
|
|
|
|
private static async Task<int> 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<InvalidOperationException>(() =>
|
|
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<InvalidOperationException>(() =>
|
|
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));
|
|
}
|
|
}
|