Files
ROLAC/API/ROLAC.API.Tests/Services/OfferingSessionServiceTests.cs
T

156 lines
5.8 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));
}
[Fact]
public async Task GetByIdAsync_ReturnsCheckZelleAndPayPalRefs()
{
using var db = BuildDb();
var catId = await SeedCategoryAsync(db);
var svc = new OfferingSessionService(db, BuildAccessor());
var req = new CreateOfferingSessionRequest
{
SessionDate = new DateOnly(2026, 6, 7), CashTotal = 0m, CheckTotal = 100m,
Givings = [ new() { GivingCategoryId = catId, Amount = 100m, PaymentMethod = "Zelle",
ZelleReferenceCode = "Z-123", PayPalTransactionId = "PP-456", CheckNumber = "C-789" } ],
};
var id = await svc.CreateAsync(req);
var dto = await svc.GetByIdAsync(id);
Assert.NotNull(dto);
var line = Assert.Single(dto!.Givings);
Assert.Equal("Z-123", line.ZelleReferenceCode);
Assert.Equal("PP-456", line.PayPalTransactionId);
Assert.Equal("C-789", line.CheckNumber);
}
}