168 lines
6.6 KiB
C#
168 lines
6.6 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 ROLAC.API.Services.Storage;
|
|
using Xunit;
|
|
|
|
namespace ROLAC.API.Tests.Services;
|
|
|
|
public class OfferingSessionServiceTests
|
|
{
|
|
// Proof storage is not exercised by these tests; a no-op keeps the service constructible.
|
|
private sealed class NoOpFileStorage : IFileStorage
|
|
{
|
|
public Task<string> SaveAsync(Stream content, string relativePath, CancellationToken ct = default)
|
|
=> Task.FromResult(relativePath);
|
|
public Task<Stream?> OpenReadAsync(string relativePath, CancellationToken ct = default)
|
|
=> Task.FromResult<Stream?>(null);
|
|
public Task DeleteAsync(string relativePath, CancellationToken ct = default)
|
|
=> Task.CompletedTask;
|
|
}
|
|
|
|
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(new ROLAC.API.Services.Logging.CurrentUserAccessor(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(), new NoOpFileStorage());
|
|
|
|
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(), new NoOpFileStorage());
|
|
|
|
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(), new NoOpFileStorage());
|
|
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(), new NoOpFileStorage());
|
|
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(), new NoOpFileStorage());
|
|
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(), new NoOpFileStorage());
|
|
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);
|
|
}
|
|
}
|