using Wiki.Core.Model; using Wiki.Core.Ports; namespace Wiki.Contracts; /// /// Every adapter derives from this. The adapter under test may talk to /// a fake server that echoes the evidence back; the contract checks the streaming shape, the /// usage record and that the model sees nothing but the request. /// public abstract class AnswerModelContract { protected abstract Task CreateModelAsync(); protected static AnswerRequest Request() => new( "Does the UDM intercept DNS?", [ new EvidenceParagraph(new Paragraph("udm-dns#p1", "udm-dns", "UDM intercepts DNS", "The UDM answers every UDP/53 query itself."), "UDM intercepts DNS", 0.84), new EvidenceParagraph(new Paragraph("udm-dns#p2", "udm-dns", "Details", "Even dig @1.1.1.1 is answered locally."), "UDM intercepts DNS", 0.41), ], new Dictionary { ["answered_1"] = new Judgement.Noul(0.93) }, 0.88); [Fact] public async Task StreamsTextThenExactlyOneDoneWithUsage() { var model = await CreateModelAsync(); var chunks = new List(); await foreach (var c in model.AnswerAsync(Request(), CancellationToken.None)) chunks.Add(c); Assert.NotEmpty(chunks); Assert.IsType(chunks[^1]); Assert.Single(chunks.OfType()); var text = string.Concat(chunks.OfType().Select(t => t.Delta)); Assert.False(string.IsNullOrWhiteSpace(text)); var done = (AnswerChunk.Done)chunks[^1]; Assert.Equal(model.ModelId, done.ModelId); Assert.True(done.InputTokens > 0); Assert.True(done.OutputTokens > 0); } [Fact] public async Task CancellationStopsTheStream() { var model = await CreateModelAsync(); using var cts = new CancellationTokenSource(); cts.Cancel(); await Assert.ThrowsAnyAsync(async () => { await foreach (var _ in model.AnswerAsync(Request(), cts.Token)) { } }); } [Fact] public async Task ModelIdAndHealth() { var model = await CreateModelAsync(); Assert.False(string.IsNullOrWhiteSpace(model.ModelId)); var health = await model.CheckAsync(CancellationToken.None); Assert.True(health.Ok, health.Detail); } }