feat(church-profile): masked-read + leave-unchanged write for AI keys
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
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<IHttpContextAccessor>();
|
||||
httpContextAccessor.Setup(accessor => accessor.HttpContext).Returns(httpContext);
|
||||
return new AppDbContext(new DbContextOptionsBuilder<AppDbContext>()
|
||||
.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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user