feat(release): prepare 1.1.0 candidate
This commit is contained in:
parent
5a34f66981
commit
5205dcdfa9
736 changed files with 115667 additions and 12203 deletions
97
apps/api-server.Tests/AdminOperationServiceTests.cs
Normal file
97
apps/api-server.Tests/AdminOperationServiceTests.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
using D3ROVoice.Api.Data;
|
||||
using D3ROVoice.Api.Services;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Xunit;
|
||||
|
||||
namespace D3ROVoice.Api.Tests;
|
||||
|
||||
public sealed class AdminOperationServiceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task MutationIsAtomicAuditedAndIdempotent()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
.UseSqlite("Data Source=:memory:")
|
||||
.Options;
|
||||
await using var db = new AppDbContext(options);
|
||||
await db.Database.OpenConnectionAsync();
|
||||
await db.Database.EnsureCreatedAsync();
|
||||
var service = new AdminOperationService(db);
|
||||
var idempotencyKey = Guid.NewGuid().ToString("D");
|
||||
var mutationCalls = 0;
|
||||
|
||||
async Task<object> Mutate()
|
||||
{
|
||||
mutationCalls += 1;
|
||||
var endpoint = new ServiceModelEndpoint
|
||||
{
|
||||
ModelId = "real-model",
|
||||
ModelName = "Real Model",
|
||||
Provider = "Custom",
|
||||
EndpointUrl = "https://models.example.com/v1",
|
||||
ApiKey = "secret-never-in-response"
|
||||
};
|
||||
db.ModelEndpoints.Add(endpoint);
|
||||
await db.SaveChangesAsync();
|
||||
return new { endpoint.Id, endpoint.ModelId };
|
||||
}
|
||||
|
||||
var first = await service.ExecuteAsync(
|
||||
"ADMIN@EXAMPLE.COM", "model_endpoint.create", idempotencyKey,
|
||||
new { modelId = "real-model" }, "model_endpoint", _ => "real-model",
|
||||
"approved provider setup", () => Task.FromResult<object?>(null), Mutate);
|
||||
var replay = await service.ExecuteAsync(
|
||||
"admin@example.com", "model_endpoint.create", idempotencyKey,
|
||||
new { modelId = "real-model" }, "model_endpoint", _ => "real-model",
|
||||
"approved provider setup", () => Task.FromResult<object?>(null), Mutate);
|
||||
|
||||
Assert.Equal(1, mutationCalls);
|
||||
Assert.Equal(first.GetRawText(), replay.GetRawText());
|
||||
Assert.Equal(1, await db.ModelEndpoints.CountAsync());
|
||||
Assert.Equal(1, await db.AdminOperationRequests.CountAsync());
|
||||
var audit = await db.AdminAuditEntries.SingleAsync();
|
||||
Assert.Equal("admin@example.com", audit.ActorEmail);
|
||||
Assert.Equal("model_endpoint.create", audit.Action);
|
||||
Assert.Equal(idempotencyKey, audit.IdempotencyKey);
|
||||
|
||||
await Assert.ThrowsAsync<AdminOperationException>(() => service.ExecuteAsync(
|
||||
"admin@example.com", "model_endpoint.create", idempotencyKey,
|
||||
new { modelId = "different-model" }, "model_endpoint", _ => "different-model",
|
||||
"different request", () => Task.FromResult<object?>(null), Mutate));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task FailedMutationRollsBackDomainAndAuditWrites()
|
||||
{
|
||||
var options = new DbContextOptionsBuilder<AppDbContext>()
|
||||
.UseSqlite("Data Source=:memory:")
|
||||
.Options;
|
||||
await using var db = new AppDbContext(options);
|
||||
await db.Database.OpenConnectionAsync();
|
||||
await db.Database.EnsureCreatedAsync();
|
||||
var service = new AdminOperationService(db);
|
||||
|
||||
async Task<object> FailingMutation()
|
||||
{
|
||||
db.ModelEndpoints.Add(new ServiceModelEndpoint
|
||||
{
|
||||
ModelId = "rollback-model",
|
||||
ModelName = "Rollback Model",
|
||||
Provider = "Custom",
|
||||
EndpointUrl = "https://models.example.com/v1"
|
||||
});
|
||||
await db.SaveChangesAsync();
|
||||
throw new InvalidOperationException("simulated persistence failure");
|
||||
}
|
||||
|
||||
await Assert.ThrowsAsync<InvalidOperationException>(() => service.ExecuteAsync(
|
||||
"admin@example.com", "model_endpoint.create", Guid.NewGuid().ToString("D"),
|
||||
new { modelId = "rollback-model" }, "model_endpoint", _ => "rollback-model",
|
||||
"rollback verification", () => Task.FromResult<object?>(null), FailingMutation));
|
||||
|
||||
db.ChangeTracker.Clear();
|
||||
Assert.Empty(await db.ModelEndpoints.ToListAsync());
|
||||
Assert.Empty(await db.AdminOperationRequests.ToListAsync());
|
||||
Assert.Empty(await db.AdminAuditEntries.ToListAsync());
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue