feat(release): prepare 1.1.0 candidate

This commit is contained in:
Yun Chan 2026-08-29 18:33:45 +09:00
parent 5a34f66981
commit 5205dcdfa9
736 changed files with 115667 additions and 12203 deletions

View 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());
}
}