using System.Security.Claims; using Microsoft.AspNetCore.Http; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Diagnostics; using Moq; using ROLAC.API.Data; using ROLAC.API.Data.Interceptors; using ROLAC.API.DTOs.Disbursement; using ROLAC.API.Entities; using ROLAC.API.Services; using ROLAC.API.Services.Logging; using Xunit; namespace ROLAC.API.Tests.Services; public class ChurchProfileServiceTests { // ChurchProfile is auditable, so the InMemory store rejects saves unless the // required CreatedBy/UpdatedBy fields are populated. Wire the same audit // interceptor the app uses so seeded entities save cleanly. private static AppDbContext NewDb() { var httpContext = new DefaultHttpContext { User = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.NameIdentifier, "test-user") })), }; var httpContextAccessor = new Mock(); httpContextAccessor.Setup(accessor => accessor.HttpContext).Returns(httpContext); return new AppDbContext(new DbContextOptionsBuilder() .UseInMemoryDatabase(Guid.NewGuid().ToString()) .ConfigureWarnings(warnings => warnings.Ignore(InMemoryEventId.TransactionIgnoredWarning)) .AddInterceptors(new AuditSaveChangesInterceptor(new CurrentUserAccessor(httpContextAccessor.Object))) .Options); } private static UpdateChurchProfileRequest Req( string provider = "Claude", string? claudeKey = null, string? geminiKey = null, string? claudeModel = "m", string? geminiModel = "m") => new() { Name = "C", NextCheckNumber = 1001, AiProvider = provider, ClaudeModel = claudeModel, GeminiModel = geminiModel, ClaudeApiKey = claudeKey, GeminiApiKey = geminiKey, }; [Fact] public async Task GetAsync_masks_stored_api_keys() { using var db = NewDb(); db.ChurchProfiles.Add(new ChurchProfile { Name = "C", ClaudeApiKey = "sk-ant-abcd1234", GeminiApiKey = "AIzaXYZ9876", }); await db.SaveChangesAsync(); var dto = await new ChurchProfileService(db).GetAsync(); Assert.Equal("••••••1234", dto.ClaudeApiKeyMasked); Assert.Equal("••••••9876", dto.GeminiApiKeyMasked); } [Fact] public async Task UpdateAsync_blank_key_keeps_existing() { using var db = NewDb(); db.ChurchProfiles.Add(new ChurchProfile { Name = "C", ClaudeApiKey = "sk-keep-0001" }); await db.SaveChangesAsync(); await new ChurchProfileService(db).UpdateAsync(Req(claudeKey: null)); var p = await db.ChurchProfiles.FirstAsync(); Assert.Equal("sk-keep-0001", p.ClaudeApiKey); } [Fact] public async Task UpdateAsync_nonblank_key_replaces() { using var db = NewDb(); db.ChurchProfiles.Add(new ChurchProfile { Name = "C", ClaudeApiKey = "sk-keep-0001" }); await db.SaveChangesAsync(); await new ChurchProfileService(db).UpdateAsync(Req(claudeKey: "sk-new-9999")); var p = await db.ChurchProfiles.FirstAsync(); Assert.Equal("sk-new-9999", p.ClaudeApiKey); } [Fact] public async Task UpdateAsync_sets_provider_and_models() { using var db = NewDb(); db.ChurchProfiles.Add(new ChurchProfile { Name = "C" }); await db.SaveChangesAsync(); await new ChurchProfileService(db).UpdateAsync( Req(provider: "Gemini", claudeModel: "claude-x", geminiModel: "gemini-y")); var p = await db.ChurchProfiles.FirstAsync(); Assert.Equal("Gemini", p.AiProvider); Assert.Equal("claude-x", p.ClaudeModel); Assert.Equal("gemini-y", p.GeminiModel); } }