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