Prevent sampleless speaker identities
PR and Push Build/Test / build-and-test (push) Successful in 8m38s

This commit is contained in:
2026-06-02 10:39:00 +02:00
parent a3bad1bdd4
commit e3f4e87319
10 changed files with 101 additions and 50 deletions
@@ -327,6 +327,22 @@ public sealed class SpeakerIdentityServiceTests
Assert.Contains(saved.Snippets, snippet => snippet.WavBytes.SequenceEqual(new byte[] { 8, 8, 8 }));
}
[Fact]
public async Task SpeakerOverrideDoesNotCreateNamedIdentityWithoutSourceSample()
{
await using var fixture = await SpeakerIdentityFixture.CreateAsync();
var service = fixture.CreateService();
var segment = new TranscriptionSegment(TimeSpan.Zero, TimeSpan.FromSeconds(5), "Guest-01", "hello from Sabrina");
var request = fixture.CreateRequest(
["Sabrina"],
[segment],
[new SpeakerAudioSample("Guest-01", segment, [8, 8, 8], 95)]);
await service.ApplySpeakerOverrideAsync(request, "Guest-5", "Sabrina", CancellationToken.None);
Assert.Empty(await fixture.Context.SpeakerIdentities.ToListAsync());
}
[Fact]
public async Task SpeakerIdentityDeletionRemovesAcceptedNameFromDatabase()
{
@@ -681,31 +681,34 @@ public sealed class WorkflowRulesEditorTests
public async Task RulesEditorToolsCrudAndSearchSpeakerIdentities()
{
await using var fixture = await WorkflowRulesEditorIdentityFixture.CreateAsync();
var identity = await fixture.AddIdentityAsync("Sabrina", aliases: ["Sabi"], candidates: ["Guest-01"], sample: [1]);
var tools = fixture.CreateTools();
var createResult = await tools.CreateIdentity(
"Sabrina",
["Sabi"],
["Guest-01"]);
using var created = JsonDocument.Parse(createResult);
var identityId = created.RootElement
.GetProperty("identity")
.GetProperty("id")
.GetInt32();
var searchResult = await tools.SearchIdentities("sabi");
var readResult = await tools.ReadIdentity(identityId);
var readResult = await tools.ReadIdentity(identity.Id);
var updateResult = await tools.UpdateIdentity(
identityId,
identity.Id,
"Sabrina M.",
["Sabrina"],
["Guest-02"]);
var deleteResult = await tools.DeleteIdentity(identityId);
var deleteResult = await tools.DeleteIdentity(identity.Id);
Assert.Contains("\"displayName\": \"Sabrina\"", searchResult);
Assert.Contains("\"canonicalName\": \"Sabrina\"", readResult);
Assert.Contains("\"canonicalName\": \"Sabrina M.\"", updateResult);
Assert.Equal($"Deleted identity {identityId}.", deleteResult);
Assert.Equal($"Deleted identity {identity.Id}.", deleteResult);
Assert.Empty(await fixture.Context.SpeakerIdentities.ToListAsync());
}
[Fact]
public async Task RulesEditorToolsRefusesSamplelessSpeakerIdentityCreation()
{
await using var fixture = await WorkflowRulesEditorIdentityFixture.CreateAsync();
var tools = fixture.CreateTools();
var result = await tools.CreateIdentity("Sabrina", ["Sabi"], ["Guest-01"]);
Assert.StartsWith("Refused: speaker identities require at least one audio sample.", result);
Assert.Empty(await fixture.Context.SpeakerIdentities.ToListAsync());
}
@@ -734,6 +737,13 @@ public sealed class WorkflowRulesEditorTests
await using var fixture = await WorkflowRulesEditorIdentityFixture.CreateAsync();
var identity = await fixture.AddIdentityAsync("Sabrina", sample: [4, 5, 6]);
var sampleId = identity.Snippets.Single().Id;
fixture.Context.SpeakerSnippets.Add(new SpeakerSnippet
{
SpeakerIdentityId = identity.Id,
WavBytes = [7, 8, 9],
CreatedAt = DateTimeOffset.UtcNow
});
await fixture.Context.SaveChangesAsync();
var queue = new CapturingSamplePlaybackQueue();
var tools = fixture.CreateTools(queue);
@@ -749,7 +759,25 @@ public sealed class WorkflowRulesEditorTests
Assert.Equal(sampleId, queued.SampleId);
Assert.Equal([4, 5, 6], queued.WavBytes);
Assert.Equal($"Deleted sample {sampleId}.", deleteResult);
Assert.Empty(await fixture.Context.SpeakerSnippets.ToListAsync());
var remaining = await fixture.Context.SpeakerSnippets.ToListAsync();
Assert.Single(remaining);
Assert.NotEqual(sampleId, remaining.Single().Id);
}
[Fact]
public async Task RulesEditorToolsRefusesDeletingLastSpeakerIdentitySample()
{
await using var fixture = await WorkflowRulesEditorIdentityFixture.CreateAsync();
var identity = await fixture.AddIdentityAsync("Sabrina", sample: [4, 5, 6]);
var sampleId = identity.Snippets.Single().Id;
var tools = fixture.CreateTools();
var result = await tools.DeleteIdentitySample(sampleId);
Assert.Equal(
$"Refused: sample {sampleId} is the last sample for identity {identity.Id}. Delete the identity instead.",
result);
Assert.Single(await fixture.Context.SpeakerSnippets.ToListAsync());
}
[Fact]