Upgraded to .NET 10 (#1147)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-01 20:32:16 +01:00
1 parent faae2951f7
commit fc7399e81b
327 files changed
+3059 -3049

No files matched your search

+2 -2
View File
@@ -36,12 +36,12 @@ public sealed class AttachingAFileAgainTests
File.WriteAllText(path, "The board met on Monday and on Tuesday.");
attachments.AttachInPlaceOfSameFile(FileAttachment.FromPath(path));
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(attachments.Count(attachment => attachment.FilePath == path), Is.EqualTo(1), "The message would go with the old and the new text of the file.");
Assert.That(attachments.Single(attachment => attachment.FilePath == path).ProvenanceId, Is.Null, "Attaching a file again means the file as it is now.");
Assert.That(attachments, Does.Contain(otherFile));
});
}
}
[Test]
+24 -24
View File
@@ -81,13 +81,13 @@ public sealed class AttachmentProvenanceTests
var firstRequest = await this.PrepareAsync(message);
var secondRequest = await this.PrepareAsync(message);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.Not.Null);
Assert.That(firstRequest, Does.Contain("The board met on Monday."));
Assert.That(secondRequest, Is.EqualTo(firstRequest));
Assert.That(this.runtime.ReadFiles, Has.Count.EqualTo(1), "Every request read every document anew, which a long chat with large documents paid for with every message.");
});
}
}
[Test]
@@ -100,11 +100,11 @@ public sealed class AttachmentProvenanceTests
await message.StoreAttachedDocumentsAsync(this.store.Client, Guid.NewGuid(), this.rustService, null, readChangedAgain: false);
var prepared = await this.PrepareAsync(message);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(prepared, Does.Contain("The board met on Monday."), "Every request of the chat sends the same text, which is what the user can look at.");
Assert.That(prepared, Does.Not.Contain("never met"));
});
}
}
[Test]
@@ -119,13 +119,13 @@ public sealed class AttachmentProvenanceTests
await message.StoreAttachedDocumentsAsync(this.store.Client, chatId, this.rustService, null, readChangedAgain: true);
var prepared = await this.PrepareAsync(message);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(prepared, Does.Contain("and on Tuesday"), "The user chose that the AI works with the files as they are now.");
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.Not.EqualTo(firstId), "The text sent before belongs to what the AI received then.");
Assert.That(message.FileAttachments.Single().Version, Is.EqualTo(FileVersion.Of(this.documentPath)));
Assert.That(this.runtime.ReadFiles, Has.Count.EqualTo(2));
});
}
}
[Test]
@@ -138,12 +138,12 @@ public sealed class AttachmentProvenanceTests
await message.StoreAttachedDocumentsAsync(this.store.Client, chatId, this.rustService, null, readChangedAgain: true);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(message.HasDocumentsToStore(readChangedAgain: true), Is.False);
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.EqualTo(firstId));
Assert.That(this.runtime.ReadFiles, Has.Count.EqualTo(1));
});
}
}
[Test]
@@ -155,11 +155,11 @@ public sealed class AttachmentProvenanceTests
File.Delete(this.documentPath);
var prepared = await this.PrepareAsync(message);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(message.HasDocumentsToStore(readChangedAgain: true), Is.False, "A file which is gone did not change: its stored text is all there is.");
Assert.That(prepared, Does.Contain("The board met on Monday."));
});
}
}
[Test]
@@ -184,12 +184,12 @@ public sealed class AttachmentProvenanceTests
var provenanceId = message.FileAttachments.Single().ProvenanceId!.Value;
var entries = await this.store.Client.GetEntriesAsync([provenanceId], CancellationToken.None);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(entries[provenanceId].Content, Does.Contain(FakeRuntime.REDACTION_MARKER), "The store keeps what the AI received, not what the file holds.");
Assert.That(entries[provenanceId].Content, Does.Not.Contain(SUSPICIOUS_PASSAGE));
Assert.That(entries[provenanceId].Kind, Is.EqualTo(ProvenanceKind.ATTACHMENT));
});
}
}
[Test]
@@ -202,13 +202,13 @@ public sealed class AttachmentProvenanceTests
var prepared = await this.PrepareAsync(message);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(prepared, Does.Contain("The board met on Monday."));
Assert.That(prepared, Does.Not.Contain(SUSPICIOUS_PASSAGE), "Every way of content to the AI goes through the filter, with the rules of today.");
Assert.That(prepared, Does.Contain(FakeRuntime.REDACTION_MARKER));
Assert.That(this.runtime.ReadFiles, Is.Empty, "The stored text is filtered, not the file read again.");
});
}
}
[Test]
@@ -222,13 +222,13 @@ public sealed class AttachmentProvenanceTests
await message.StoreAttachedDocumentsAsync(this.store.Client, chatId, this.rustService, null, readChangedAgain: false);
await using var context = this.store.CreateContext();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(message.HasDocumentsToStore(readChangedAgain: false), Is.False);
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.EqualTo(firstId));
Assert.That(context.Entries.Count(), Is.EqualTo(1));
Assert.That(this.runtime.ReadFiles, Has.Count.EqualTo(1));
});
}
}
[Test]
@@ -239,11 +239,11 @@ public sealed class AttachmentProvenanceTests
await message.StoreAttachedDocumentsAsync(new NoProvenanceStoreClient("SQLite", "not available"), Guid.NewGuid(), this.rustService, null, readChangedAgain: false);
var prepared = await this.PrepareAsync(message);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.Null);
Assert.That(prepared, Does.Contain("The board met on Monday."), "Without the store, a chat works as it always did.");
});
}
}
[Test]
@@ -253,11 +253,11 @@ public sealed class AttachmentProvenanceTests
var prepared = await this.PrepareAsync(message);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(prepared, Does.Contain("The board met on Monday."), "A store which lost the entry, e.g., one restored from an older backup, must not cost the document.");
Assert.That(this.runtime.ReadFiles, Has.Count.EqualTo(1));
});
}
}
[Test]
@@ -270,12 +270,12 @@ public sealed class AttachmentProvenanceTests
await message.StoreAttachedDocumentsAsync(this.store.Client, Guid.NewGuid(), this.rustService, null, readChangedAgain: false);
var chatsWithEntries = await this.store.Client.GetChatIdsAsync(CancellationToken.None);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(message.HasDocumentsToStore(readChangedAgain: false), Is.False, "An image goes to the AI as an image, not as a text to keep.");
Assert.That(message.FileAttachments.Single().ProvenanceId, Is.Null);
Assert.That(chatsWithEntries, Is.Empty);
});
}
}
[Test]
@@ -289,7 +289,7 @@ public sealed class AttachmentProvenanceTests
using var document = JsonDocument.Parse(json);
var storedAttachments = document.RootElement.GetProperty("file_attachments");
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(attachment.Normalize().ProvenanceId, Is.EqualTo(attachment.ProvenanceId), "Every request normalizes its attachments first.");
Assert.That(attachment.Normalize().Version, Is.EqualTo(attachment.Version), "Otherwise, a chat which reads changed files again would read every file again.");
@@ -300,7 +300,7 @@ public sealed class AttachmentProvenanceTests
Assert.That(transcript.WithFilePath(Path.Combine(this.directoryPath, "moved.md")).ProvenanceId, Is.EqualTo(transcript.ProvenanceId));
Assert.That(loaded.FileAttachments[0].ProvenanceId, Is.EqualTo(attachment.ProvenanceId));
Assert.That(storedAttachments[1].TryGetProperty("provenance_id", out _), Is.False, "An attachment never sent is stored as before, also in a chat template.");
});
}
}
private ContentText MessageWithDocument() => new() { Text = "Summarize the minutes.", FileAttachments = [FileAttachment.FromPath(this.documentPath)] };
+26 -26
View File
@@ -25,11 +25,11 @@ public sealed class ChatBlockActionsTests
var removed = WhileWriting(chat).TryRemove(chat, answer.Content!);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(removed, Is.False);
Assert.That(chat.Blocks.Select(x => x.Content), Is.EqualTo(new[] { question.Content, answer.Content }), "The answer stays, since the job goes on writing into it.");
});
}
}
[Test]
@@ -41,13 +41,13 @@ public sealed class ChatBlockActionsTests
var blocks = chat.Blocks.ToList();
var actions = WhileWriting(chat);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(actions.TryRemove(chat, firstQuestion.Content!), Is.False);
Assert.That(actions.TryRemove(chat, firstAnswer.Content!), Is.False);
Assert.That(actions.TryRemoveForRegenerate(chat, firstAnswer.Content!), Is.False);
Assert.That(chat.Blocks, Is.EqualTo(blocks), "The job stores the conversation meanwhile, so no message may change, not even an older one.");
});
}
}
[Test]
@@ -58,13 +58,13 @@ public sealed class ChatBlockActionsTests
var chat = Chat(question, answer);
var actions = WhileWriting(chat);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(actions.TryEditLastUserBlock(chat, question.Content!), Is.False);
Assert.That(actions.TryEditLastBlock(chat, question.Content!), Is.False);
Assert.That(chat.Blocks, Has.Count.EqualTo(2), "Both the question and the answer being written stay.");
Assert.That(chat.Draft.IsEmpty, Is.True, "The question does not become the draft, since it could not be sent anyway.");
});
}
}
[Test]
@@ -75,11 +75,11 @@ public sealed class ChatBlockActionsTests
var removed = WhileWriting(chat).TryRemoveForRegenerate(chat, answer.Content!);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(removed, Is.False, "Regenerating would leave a second answer next to the one being written.");
Assert.That(chat.Blocks, Has.Count.EqualTo(2));
});
}
}
[Test]
@@ -90,11 +90,11 @@ public sealed class ChatBlockActionsTests
var rolledBack = WhileWriting(chat).TryRollBack(chat, firstAnswer.Content!);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(rolledBack, Is.False);
Assert.That(chat.Blocks, Has.Count.EqualTo(4));
});
}
}
[Test]
@@ -106,11 +106,11 @@ public sealed class ChatBlockActionsTests
var removed = Done().TryRemove(chat, answer.Content!);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(removed, Is.True);
Assert.That(chat.Blocks.Select(x => x.Content), Is.EqualTo(new[] { question.Content }));
});
}
}
[Test]
@@ -122,11 +122,11 @@ public sealed class ChatBlockActionsTests
var removed = Done().TryRemoveForRegenerate(chat, answer.Content!);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(removed, Is.True);
Assert.That(chat.Blocks.Select(x => x.Content), Is.EqualTo(new[] { question.Content }), "The question stays, to be answered anew.");
});
}
}
[Test]
@@ -138,11 +138,11 @@ public sealed class ChatBlockActionsTests
var rolledBack = Done().TryRollBack(chat, firstAnswer.Content!);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(rolledBack, Is.True);
Assert.That(chat.Blocks.Select(x => x.Content), Is.EqualTo(new[] { firstQuestion.Content, firstAnswer.Content }));
});
}
}
[Test]
@@ -153,12 +153,12 @@ public sealed class ChatBlockActionsTests
var edited = Done().TryEditLastUserBlock(chat, question.Content!);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(edited, Is.True);
Assert.That(chat.Blocks, Is.Empty, "The question goes back into the composer, and the answer to it goes.");
Assert.That(chat.Draft.Text, Is.EqualTo("Question"));
});
}
}
[Test]
@@ -169,12 +169,12 @@ public sealed class ChatBlockActionsTests
var edited = Done().TryEditLastBlock(chat, question.Content!);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(edited, Is.True);
Assert.That(chat.Blocks, Is.Empty);
Assert.That(chat.Draft.Text, Is.EqualTo("Question"));
});
}
}
[Test]
@@ -185,12 +185,12 @@ public sealed class ChatBlockActionsTests
var edited = Done().TryEditLastUserBlock(chat, new ContentText { Text = "Question of another chat" });
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(edited, Is.False);
Assert.That(chat.Blocks, Has.Count.EqualTo(2), "Removing the last answer only makes sense together with the question to it.");
Assert.That(chat.Draft.IsEmpty, Is.True);
});
}
}
[Test]
@@ -208,11 +208,11 @@ public sealed class ChatBlockActionsTests
var removed = actions.TryRemove(chat, answer.Content!);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(removed, Is.True);
Assert.That(askedChatIds, Is.EqualTo(new[] { chat.ChatId }), "Only the chat being changed decides.");
});
}
}
[Test]
@@ -221,7 +221,7 @@ public sealed class ChatBlockActionsTests
var actions = Done();
var content = new ContentText { Text = "Question" };
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(actions.AreAllowed(null), Is.False);
Assert.That(actions.TryRemove(null, content), Is.False);
@@ -229,7 +229,7 @@ public sealed class ChatBlockActionsTests
Assert.That(actions.TryRollBack(null, content), Is.False);
Assert.That(actions.TryEditLastUserBlock(null, content), Is.False);
Assert.That(actions.TryEditLastBlock(null, content), Is.False);
});
}
}
/// <summary>
+2 -2
View File
@@ -62,11 +62,11 @@ public sealed class ChatDraftAutosaveTests
storage.IsBusy = false;
await storage.WaitForStoreAsync();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(storedWhileBusy, Is.Zero, "The AI job changes the conversation meanwhile, so storing it from elsewhere could read it in the middle of a change.");
Assert.That(storage.StoredChats, Is.EqualTo(new[] { chat }), "Once the answer is done, the change must still reach the disk.");
});
}
}
[Test]
+16 -16
View File
@@ -46,12 +46,12 @@ public sealed class ChatDraftTests
var added = draft.AddAttachments([TRANSCRIPT]);
var addedAgain = draft.AddAttachments([TRANSCRIPT with { FileSizeBytes = 1024 }]);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(added, Is.True);
Assert.That(addedAgain, Is.False, "The same file counts once, even in a newer state.");
Assert.That(draft.FileAttachments, Is.EqualTo(new FileAttachment[] { TRANSCRIPT }), "The composer would show the transcript twice otherwise.");
});
}
}
[Test]
@@ -74,11 +74,11 @@ public sealed class ChatDraftTests
draft.ReplaceAttachments(attachment => attachment with { FilePath = $"/moved{attachment.FilePath}" });
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(draft.FileAttachments.Select(attachment => attachment.FilePath), Is.EqualTo(new[] { "/moved/documents/agenda.pdf", "/moved/data/tempChats/meeting-transcript-0001.md" }));
Assert.That(draft.MediaTranscripts, Has.Count.EqualTo(1), "A transcript stays a transcript, so removing it from the composer still deletes its file.");
});
}
}
[Test]
@@ -90,11 +90,11 @@ public sealed class ChatDraftTests
draft.RestoreFromMessage(new ContentText { Text = "Summarize the minutes", FileAttachments = [MINUTES] });
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(draft.Text, Is.EqualTo("Summarize the minutes"), "The message to edit replaces the older draft.");
Assert.That(draft.FileAttachments, Is.EqualTo(new[] { MINUTES, TRANSCRIPT }), "The draft is the only owner of its transcripts, so dropping them would leave their files behind.");
});
}
}
[Test]
@@ -107,11 +107,11 @@ public sealed class ChatDraftTests
draft.Clear();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(draft.IsEmpty, Is.True, "The attachments belong to the sent message now, and a transcript has only one owner.");
Assert.That(draft.PreviewedTexts, Is.Empty, "A text the preview read went with the message, or is of no use to the next one.");
});
}
}
[Test]
@@ -126,13 +126,13 @@ public sealed class ChatDraftTests
draft.NotePreviewedText(otherFile);
var replacedBySecond = draft.NotePreviewedText(secondPreview);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(replacedByFirst, Is.Null);
Assert.That(replacedBySecond, Is.EqualTo(firstPreview.ProvenanceId), "Nothing refers to the text of the first preview any longer, so the caller removes it from the store.");
Assert.That(draft.PreviewedTexts, Is.EquivalentTo(new[] { otherFile, secondPreview }));
Assert.That(draft.GetPreviewedText(AGENDA.FilePath), Is.EqualTo(secondPreview));
});
}
}
[Test]
@@ -148,12 +148,12 @@ public sealed class ChatDraftTests
draft.MirrorAttachments([MINUTES]);
var forgotten = draft.ForgetPreviewsOfDetachedFiles();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(forgotten, Is.EqualTo(new[] { agendaPreview.ProvenanceId }), "Nothing refers to the text any longer, so the caller removes it from the store.");
Assert.That(draft.PreviewedTexts, Is.EqualTo(new[] { minutesPreview }));
Assert.That(draft.ForgetPreviewsOfDetachedFiles(), Is.Empty);
});
}
}
[Test]
@@ -178,12 +178,12 @@ public sealed class ChatDraftTests
var loaded = JsonSerializer.Deserialize<ChatThread>(JsonSerializer.Serialize(thread, WorkspaceBehaviour.JSON_OPTIONS), WorkspaceBehaviour.JSON_OPTIONS)!;
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(loaded.Draft.Text, Is.EqualTo("Summarize the meeting"));
Assert.That(loaded.Draft.FileAttachments, Is.EqualTo(new[] { AGENDA, CHART, STAGED_TRANSCRIPT }), "A transcript loaded as a plain document would no longer be managed, and its file would never be deleted.");
Assert.That(loaded.Draft.MediaTranscripts.Single().IsStaged, Is.True, "A staged transcript has to move into the chat directory once the chat is stored.");
});
}
}
[Test]
@@ -216,12 +216,12 @@ public sealed class ChatDraftTests
var loaded = JsonSerializer.Deserialize<ChatThread>(CHAT_WITH_PENDING_TRANSCRIPTS, WorkspaceBehaviour.JSON_OPTIONS)!;
var stored = JsonSerializer.Serialize(loaded, WorkspaceBehaviour.JSON_OPTIONS);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(loaded.Draft.FileAttachments, Is.EqualTo(new FileAttachment[] { TRANSCRIPT }), "Opening the chat has to bring the transcript back into the composer.");
Assert.That(stored, Does.Not.Contain("pending_media_transcripts"), "The draft holds the transcript now, so storing the chat must not write the former list again.");
Assert.That(stored, Does.Contain("\"draft\""));
});
}
}
[Test]
+25 -22
View File
@@ -56,14 +56,14 @@ public sealed class ChatRestrictionsTests
foreach (var combined in new[] { fromDataSource.StricterOf(fromMailbox), fromMailbox.StricterOf(fromDataSource) })
{
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(combined.ProviderConfidence, Is.EqualTo(ConfidenceLevel.HIGH));
Assert.That(combined.DataSecurity, Is.EqualTo(DataSourceSecurity.SELF_HOSTED), "Data for self-hosted providers only keeps them as the only choice, whichever side brings it.");
Assert.That(combined.OutboundData.Restriction, Is.EqualTo(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES));
Assert.That(combined.OutboundData.Reference, Is.EqualTo(MAILBOX_ID), "The source which demands the strictest restriction is the one to name.");
Assert.That(combined.WebAddressesWrittenByModels, Is.EquivalentTo(new[] { RequestKey("https://example.org/report"), RequestKey("https://example.org/minutes") }), "What models wrote on either side never counts as given.");
});
}
}
}
@@ -72,11 +72,11 @@ public sealed class ChatRestrictionsTests
{
var restrictions = ChatRestrictions.NONE.WithWebAddressesWrittenByModelIn("The report is on https://EXAMPLE.org/report#summary, and the minutes are on http://10.20.30.40:8000/minutes/. Write to ftp://example.org/upload.");
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(restrictions.WebAddressesWrittenByModels, Is.EquivalentTo(new[] { RequestKey("https://example.org/report"), RequestKey("http://10.20.30.40:8000/minutes/") }), "Only addresses a web page could be read from count, compared as the server gets asked for them.");
Assert.That(ChatRestrictions.NONE.WebAddressesWrittenByModels, Is.Empty, "Adding addresses leaves NONE as it is.");
});
}
}
[Test]
@@ -84,11 +84,11 @@ public sealed class ChatRestrictionsTests
{
var restrictions = new ChatRestrictions(ConfidenceLevel.MEDIUM, DataSourceSecurity.ALLOW_ANY, new(OutboundDataRestriction.ONLY_LINKS_FROM_CHAT, OutboundDataOrigin.DATA_SOURCE, DATA_SOURCE_ID), []).WithWebAddressesWrittenByModelIn("See https://example.org/report.");
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
AssertSame(restrictions.StricterOf(ChatRestrictions.NONE), restrictions);
AssertSame(ChatRestrictions.NONE.StricterOf(restrictions), restrictions);
});
}
}
[Test]
@@ -131,11 +131,11 @@ public sealed class ChatRestrictionsTests
chatToStart.RequireRestrictions(new(ConfidenceLevel.HIGH, DataSourceSecurity.SELF_HOSTED, new(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES, OutboundDataOrigin.MAILBOX, MAILBOX_ID), []));
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
AssertSame(chatToStart.CaptureRestrictions(), new(ConfidenceLevel.HIGH, DataSourceSecurity.SELF_HOSTED, new(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES, OutboundDataOrigin.MAILBOX, MAILBOX_ID), []));
AssertSame(assistantThread.CaptureRestrictions(), ChatRestrictions.NONE);
});
}
}
[Test]
@@ -148,11 +148,11 @@ public sealed class ChatRestrictionsTests
chat.RequireProviderConfidence(ConfidenceLevel.HIGH);
chat.RequireDataSecurity(DataSourceSecurity.SELF_HOSTED);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(snapshot.ProviderConfidence, Is.EqualTo(ConfidenceLevel.LOW), "Content which left the chat does not hold what the chat read afterward.");
Assert.That(snapshot.DataSecurity, Is.EqualTo(DataSourceSecurity.NOT_SPECIFIED));
});
}
}
[Test]
@@ -162,12 +162,12 @@ public sealed class ChatRestrictionsTests
var selfHostedProvider = this.AddProvider(SELF_HOSTED_PROVIDER_ID, LLMProviders.SELF_HOSTED, isSelfHosted: true);
var restrictions = ChatRestrictions.NONE with { DataSecurity = DataSourceSecurity.SELF_HOSTED };
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(restrictions.IsLLMProviderAllowed(cloudProvider), Is.False);
Assert.That(restrictions.IsLLMProviderAllowed(selfHostedProvider), Is.True);
Assert.That(ChatRestrictions.NONE.IsLLMProviderAllowed(cloudProvider), Is.True, "Content which demands nothing may go to any provider.");
});
}
}
[Test]
@@ -178,11 +178,11 @@ public sealed class ChatRestrictionsTests
var higherLevels = Enum.GetValues<ConfidenceLevel>().Where(candidate => candidate > level).ToList();
Assume.That(higherLevels, Is.Not.Empty, "The provider has the highest level there is, so no content can demand more of it.");
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That((ChatRestrictions.NONE with { ProviderConfidence = level }).IsLLMProviderAllowed(provider), Is.True);
Assert.That((ChatRestrictions.NONE with { ProviderConfidence = higherLevels.Min() }).IsLLMProviderAllowed(provider), Is.False);
});
}
}
[Test]
@@ -194,21 +194,24 @@ public sealed class ChatRestrictionsTests
chat.RequireDataSecurity(DataSourceSecurity.SELF_HOSTED);
ChatThread? newChat = null;
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(chat.IsLLMProviderAllowed(cloudProvider), Is.EqualTo(chat.CaptureRestrictions().IsLLMProviderAllowed(cloudProvider)));
Assert.That(chat.IsLLMProviderAllowed(selfHostedProvider), Is.EqualTo(chat.CaptureRestrictions().IsLLMProviderAllowed(selfHostedProvider)));
Assert.That(newChat.IsLLMProviderAllowed(cloudProvider), Is.True, "A new chat demands nothing yet.");
});
}
}
private static void AssertSame(ChatRestrictions actual, ChatRestrictions expected) => Assert.Multiple(() =>
private static void AssertSame(ChatRestrictions actual, ChatRestrictions expected)
{
Assert.That(actual.ProviderConfidence, Is.EqualTo(expected.ProviderConfidence));
Assert.That(actual.DataSecurity, Is.EqualTo(expected.DataSecurity));
Assert.That(actual.OutboundData, Is.EqualTo(expected.OutboundData));
Assert.That(actual.WebAddressesWrittenByModels, Is.EquivalentTo(expected.WebAddressesWrittenByModels));
});
using (Assert.EnterMultipleScope())
{
Assert.That(actual.ProviderConfidence, Is.EqualTo(expected.ProviderConfidence));
Assert.That(actual.DataSecurity, Is.EqualTo(expected.DataSecurity));
Assert.That(actual.OutboundData, Is.EqualTo(expected.OutboundData));
Assert.That(actual.WebAddressesWrittenByModels, Is.EquivalentTo(expected.WebAddressesWrittenByModels));
}
}
private static string RequestKey(string address) => WebAddresses.CreateRequestKey(new Uri(address));
@@ -41,11 +41,11 @@ public sealed class ChatThreadAugmentedDataTests
var loaded = JsonSerializer.Deserialize<ChatThread>(CHAT_WITH_AUGMENTED_DATA, WorkspaceBehaviour.JSON_OPTIONS);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(loaded, Is.Not.Null, "An older chat file must still load.");
Assert.That(loaded!.AugmentedData, Is.Empty, "It belonged to a request long over; the next message retrieves anew.");
Assert.That(loaded.Name, Is.EqualTo("An older chat"));
});
}
}
}
+6 -6
View File
@@ -67,14 +67,14 @@ public sealed class ChatThreadEditTests
thread.Remove(answer.Content!);
composer.RestoreFromDraft(thread.Draft);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(thread.Blocks, Is.Empty, "Both the question and the stopped answer leave the chat.");
Assert.That(File.Exists(transcript.FilePath), Is.True, "The composer still carries the transcript, so its file has to stay.");
Assert.That(composer.FileAttachments.Single().Exists, Is.True, "The edited message goes out with the transcript, not with a reference to a deleted file.");
Assert.That(thread.Draft.MediaTranscripts.Select(x => x.FilePath), Is.EqualTo(new[] { transcript.FilePath }), "The draft owns the transcript now: opening the chat again brings it back, and removing it from the composer deletes the file.");
Assert.That(thread.Draft.Text, Is.EqualTo("Summarize the meeting"), "The message being edited is the draft now, its text included.");
});
}
}
[Test]
@@ -101,11 +101,11 @@ public sealed class ChatThreadEditTests
thread.Remove(question.Content!);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(File.Exists(transcript.FilePath), Is.False, "Nothing refers to the transcript of a deleted message any longer, so its file has to go.");
Assert.That(thread.Draft.IsEmpty, Is.True, "A deleted message hands nothing over to the draft.");
});
}
}
[Test]
@@ -121,12 +121,12 @@ public sealed class ChatThreadEditTests
thread.RemoveForEditing(new ContentText { Text = "Question of another chat", FileAttachments = [transcript] });
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(thread.Blocks, Has.Count.EqualTo(2), "The message to edit is not part of this chat, so no block goes.");
Assert.That(thread.Draft.IsEmpty, Is.True, "A message this chat does not hold never becomes its draft.");
Assert.That(File.Exists(transcript.FilePath), Is.True);
});
}
}
/// <summary>
+6 -6
View File
@@ -38,12 +38,12 @@ public sealed class ChatThreadFilePathTests
chat.RewriteFilePaths(Move);
var message = (ContentText)chat.Blocks[0].Content!;
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(message.FileAttachments.Select(attachment => attachment.FilePath), Is.EqualTo(new[] { New("report.pdf"), New("meeting.md"), New("chart.png") }));
Assert.That(photo.Source, Is.EqualTo(New("photo.jpg")), "The model gets such an image from its path.");
Assert.That(chat.Draft.FileAttachments.Select(attachment => attachment.FilePath), Is.EqualTo(new[] { New("call.md") }), "The draft is the one place every path rewrite once forgot.");
});
}
}
[Test]
@@ -60,11 +60,11 @@ public sealed class ChatThreadFilePathTests
chat.RewriteFilePaths(Move);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(((ContentText)chat.Blocks[0].Content!).FileAttachments.Single(), Is.SameAs(elsewhere));
Assert.That(linked.Source, Is.EqualTo("https://example.org/chart.png"), "Only an image held by a local path has a path to rewrite.");
});
}
}
[Test]
@@ -78,14 +78,14 @@ public sealed class ChatThreadFilePathTests
var movedPicture = picture.WithFilePath(New("chart.png"));
var movedDocument = document.WithFilePath(New("report.pdf"));
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(movedTranscript, Is.EqualTo(new ManagedTranscriptAttachment("meeting.md", New("meeting.md"), 512, "meeting.mp3", true)), "A transcript has to stay a transcript, or deleting the chat leaves its file behind.");
Assert.That(movedPicture, Is.TypeOf<FileAttachmentImage>());
Assert.That(((FileAttachmentImage)movedPicture).Source, Is.EqualTo(New("chart.png")), "The model gets the image from its source, not from its path.");
Assert.That(movedPicture.FilePathAsUrl, Is.EqualTo(new FileAttachmentImage("chart.png", New("chart.png"), 2048).FilePathAsUrl), "The preview of the image loads it from there.");
Assert.That(movedDocument, Is.EqualTo(new FileAttachment(FileAttachmentType.DOCUMENT, "report.pdf", New("report.pdf"), 1024)));
});
}
}
private static string Old(string fileName) => Path.Combine(OLD_DIRECTORY, fileName);
@@ -100,12 +100,12 @@ public sealed class ChatThreadOutboundDataRestrictionTests
[TestCase(OutboundDataRestriction.UNRESTRICTED)]
public void ARestrictionThisVersionDoesNotKnowCountsAsTheStrictest(OutboundDataRestriction known)
{
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(UNKNOWN_RESTRICTION.StricterOf(known), Is.EqualTo(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES));
Assert.That(known.StricterOf(UNKNOWN_RESTRICTION), Is.EqualTo(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES));
Assert.That(UNKNOWN_RESTRICTION.StricterOf(UNKNOWN_RESTRICTION), Is.EqualTo(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES));
});
}
}
[Test]
@@ -28,7 +28,7 @@ public sealed class ChatThreadReportedHistoryTests
var history = thread.ReportedHistoryFor(MODEL);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(history.IsKnown, Is.True);
Assert.That(history.PromptTokens, Is.EqualTo(1200));
@@ -38,7 +38,7 @@ public sealed class ChatThreadReportedHistoryTests
// would include the reasoning the next request never carries:
//
Assert.That(history.LastAnswer, Is.EqualTo("Answer 2"));
});
}
}
[Test]
@@ -54,12 +54,12 @@ public sealed class ChatThreadReportedHistoryTests
var thread = Thread(Question(1), Block(ChatRole.AI, reasoningOnly, 2));
var history = thread.ReportedHistoryFor(MODEL);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(history.IsKnown, Is.True);
Assert.That(history.PromptTokens, Is.EqualTo(1200));
Assert.That(history.LastAnswer, Is.Empty);
});
}
}
[Test]
@@ -135,12 +135,12 @@ public sealed class ChatThreadReportedHistoryTests
thread.Remove(lastAnswer.Content!);
var history = thread.ReportedHistoryFor(MODEL);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(history.IsKnown, Is.True, "The first answer is the last block again, with exactly the blocks it was reported for.");
Assert.That(history.PromptTokens, Is.EqualTo(1200));
Assert.That(history.LastAnswer, Is.EqualTo("Answer 2"));
});
}
}
[Test]
@@ -152,12 +152,12 @@ public sealed class ChatThreadReportedHistoryTests
thread.RollBackTo(firstAnswer.Content!);
var history = thread.ReportedHistoryFor(MODEL);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(history.IsKnown, Is.True);
Assert.That(history.PromptTokens, Is.EqualTo(1200));
Assert.That(history.LastAnswer, Is.EqualTo("Answer 2"));
});
}
}
private static ChatThread Thread(params ContentBlock[] blocks) => new()
@@ -24,13 +24,13 @@ public sealed class ChatThreadRestrictionsOfTests
assistantThread.RequireRestrictionsOf(researchThread);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(assistantThread.RequiredProviderConfidence, Is.EqualTo(ConfidenceLevel.HIGH));
Assert.That(assistantThread.DataSecurity, Is.EqualTo(DataSourceSecurity.SELF_HOSTED));
Assert.That(assistantThread.RequiredOutboundDataRestriction.Restriction, Is.EqualTo(OutboundDataRestriction.ONLY_LINKS_FROM_CHAT));
Assert.That(assistantThread.RequiredOutboundDataRestriction.Reference, Is.EqualTo(DATA_SOURCE_ID));
});
}
}
[Test]
@@ -60,12 +60,12 @@ public sealed class ChatThreadRestrictionsOfTests
assistantThread.RequireRestrictionsOf(researchThread);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(assistantThread.RequiredProviderConfidence, Is.EqualTo(ConfidenceLevel.HIGH));
Assert.That(assistantThread.DataSecurity, Is.EqualTo(DataSourceSecurity.SELF_HOSTED));
Assert.That(assistantThread.RequiredOutboundDataRestriction.Restriction, Is.EqualTo(OutboundDataRestriction.ONLY_CONFIGURED_SERVICES));
});
}
}
[Test]
@@ -75,12 +75,12 @@ public sealed class ChatThreadRestrictionsOfTests
assistantThread.RequireRestrictionsOf(new ChatThread());
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(assistantThread.RequiredProviderConfidence, Is.EqualTo(ConfidenceLevel.NONE));
Assert.That(assistantThread.DataSecurity, Is.EqualTo(DataSourceSecurity.NOT_SPECIFIED));
Assert.That(assistantThread.RequiredOutboundDataRestriction, Is.EqualTo(OutboundDataRequirement.NONE));
Assert.That(assistantThread.WebAddressesWrittenByModels, Is.Empty);
});
}
}
}
+8 -8
View File
@@ -41,11 +41,11 @@ public sealed class ChatThreadRollbackTests
var rolledBack = thread.RollBackTo(answer.Content!);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(rolledBack, Is.True, "Two blocks came after the answer, so the rollback removed something.");
Assert.That(Texts(thread), Is.EqualTo(new[] { "First question", "First answer" }), "The hidden prompt goes with the answer to it, although the user never saw it.");
});
}
}
[Test]
@@ -117,7 +117,7 @@ public sealed class ChatThreadRollbackTests
thread.RollBackTo(answer.Content!);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(thread.AugmentedData, Is.Empty, "Nobody knows whether the retrieved data belongs to a kept or to a removed message, so it must not reach the next system prompt.");
Assert.That(thread.AISelectedDataSources, Is.Empty, "The data sources an agent picked belong to the same retrieval as the data.");
@@ -125,7 +125,7 @@ public sealed class ChatThreadRollbackTests
Assert.That(options.DisableDataSources, Is.False);
Assert.That(options.AutomaticDataSourceSelection, Is.True);
Assert.That(options.PreselectedDataSourceIds, Is.EqualTo(new[] { "handbook" }));
});
}
}
[Test]
@@ -150,12 +150,12 @@ public sealed class ChatThreadRollbackTests
thread.RollBackTo(answer.Content!);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(thread.DataSecurity, Is.EqualTo(DataSourceSecurity.SELF_HOSTED), "The confidential data was seen by this chat, so no cloud provider may continue it.");
Assert.That(thread.RequiredProviderConfidence, Is.EqualTo(ConfidenceLevel.HIGH), "The confidence the data demanded stays, although the message which brought it in is gone.");
Assert.That(thread.RequiredOutboundDataRestriction, Is.EqualTo(restriction), "The mail content was read by this chat, so it may still carry it out no further than before.");
});
}
}
[Test]
@@ -194,13 +194,13 @@ public sealed class ChatThreadRollbackTests
private static void AssertUnchanged(ChatThread thread, bool rolledBack, params string[] expectedTexts)
{
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(rolledBack, Is.False, "Nothing came after the content, so there was nothing to roll back.");
Assert.That(Texts(thread), Is.EqualTo(expectedTexts), "A rollback without anything to remove leaves the blocks alone.");
Assert.That(thread.AugmentedData, Is.EqualTo("A chunk from the handbook"), "A rollback without anything to remove keeps the last retrieval, because it still belongs to the last message.");
Assert.That(thread.AISelectedDataSources, Has.Count.EqualTo(1));
});
}
}
private static ContentBlock Block(ChatRole role, string text, int minute, bool hidden = false) => new()
+12 -12
View File
@@ -29,12 +29,12 @@ public sealed class ChatThreadWebAddressTests
{
var thread = Thread(Block(ChatRole.USER, "Please summarize https://example.org/report for me.", 1));
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/report")), Is.True);
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://EXAMPLE.org/report#summary")), Is.True, "The server gets the same request.");
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/report?budget=2026")), Is.False, "The query would carry something the user never wrote.");
});
}
}
[Test]
@@ -43,12 +43,12 @@ public sealed class ChatThreadWebAddressTests
// An internal service such as a simple Python web server for the results of a data science run:
var thread = Thread(Block(ChatRole.USER, "The results of run 17 are on http://10.20.30.40:8000/run-17/, please compare them with run 16.", 1));
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("http://10.20.30.40:8000/run-17/")), Is.True);
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("http://10.20.30.40:8000/run-16/")), Is.False, "The user named run 16, but not its address.");
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://10.20.30.40:8000/run-17/")), Is.False, "Another scheme is another request.");
});
}
}
[Test]
@@ -89,11 +89,11 @@ public sealed class ChatThreadWebAddressTests
using var rustService = new RustService("1", "unused");
thread.PrepareSystemPrompt(new SettingsManager(NullLogger<SettingsManager>.Instance, rustService));
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://handbook.example.org/")), Is.True);
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://intranet.example.org/travel")), Is.True, "The RAG process found it in a data source, the model did not choose it.");
});
}
}
[Test]
@@ -131,11 +131,11 @@ public sealed class ChatThreadWebAddressTests
var thread = Thread(Block(ChatRole.USER, "Make slides of this: the details are on https://example.org/chosen, and our handbook is on https://example.org/handbook.", 1));
thread.RequireRestrictions(ChatRestrictions.NONE.WithWebAddressesWrittenByModelIn("The details are on https://example.org/chosen."));
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/chosen")), Is.False, "A model wrote it, so it could carry anything of the chat it came from.");
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/handbook")), Is.True, "Another address in the same message still counts.");
});
}
}
[Test]
@@ -151,11 +151,11 @@ public sealed class ChatThreadWebAddressTests
thread.RequireRestrictions(ChatRestrictions.NONE.WithWebAddressesWrittenByModelIn("See https://example.org/chosen and https://example.org/sources/2026."));
thread.RuntimeWebAddressesFromTools.Add(WebAddresses.CreateRequestKey(new Uri("https://example.org/chosen")));
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/chosen")), Is.True, "A tool returned it in this chat, so it was not only a model which wrote it.");
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/sources/2026")), Is.True, "A document the user attached holds it.");
});
}
}
[Test]
@@ -177,11 +177,11 @@ public sealed class ChatThreadWebAddressTests
copy.RequireRestrictions(ChatRestrictions.NONE.WithWebAddressesWrittenByModelIn("See https://example.org/chosen."));
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(copy.IsWebAddressGivenToTheModel(new Uri("https://example.org/chosen")), Is.False);
Assert.That(thread.IsWebAddressGivenToTheModel(new Uri("https://example.org/chosen")), Is.True, "What the copy took over must not reach the chat it was copied from.");
});
}
}
private static ChatThread Thread(params ContentBlock[] blocks) => new() { Blocks = [..blocks] };
@@ -21,13 +21,13 @@ public sealed class ContentBlockApartFromChatTests
var template = new ChatThread { ChatId = Guid.NewGuid(), Blocks = chat.Blocks.Select(block => block.CloneApartFromChat()).ToList() };
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(template.CollectProvenanceIds(), Is.Empty, "The entries belong to the chat and are deleted with it.");
Assert.That(template.Blocks.Select(block => ((ContentText)block.Content!).Text), Is.EqualTo(new[] { "Summarize the minutes.", "The board met on Monday." }));
Assert.That(template.Blocks[0].Content!.FileAttachments.Select(attachment => attachment.FilePath), Is.EqualTo(new[] { "/tmp/minutes.txt" }), "The file is read anew by every chat it reaches.");
Assert.That(chat.CollectProvenanceIds(), Has.Count.EqualTo(2), "The chat itself keeps what it received.");
});
}
}
[Test]
+2 -2
View File
@@ -15,11 +15,11 @@ public sealed class ContentPresentationTests
presentation.Data = JsonSerializer.SerializeToElement(new { heading = "Second" });
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(firstHashCode, Is.Not.Zero);
Assert.That(presentation.GetDataHashCode(), Is.Not.EqualTo(firstHashCode));
});
}
}
[Test]
+24 -24
View File
@@ -53,12 +53,12 @@ public sealed class ConversationPartsTests
var parts = ConversationParts.Of(thread, "You are helpful.", "And of Italy?", null, imagesAreSent: true, toolDefinitions: null);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(parts.Texts, Is.EqualTo(new[] { "You are helpful.", "What is the capital of France?", "Paris." }));
Assert.That(parts.DraftText, Is.EqualTo("And of Italy?"));
Assert.That(parts.GrowingTexts, Is.Empty, "The draft is a part of its own, and it stands nowhere else.");
});
}
}
[Test]
@@ -75,12 +75,12 @@ public sealed class ConversationPartsTests
var parts = ConversationParts.Of(thread, string.Empty, "a draft", null, imagesAreSent: true, toolDefinitions: null);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(parts.Texts, Is.EqualTo(new[] { "A question." }));
Assert.That(parts.GrowingTexts, Is.EqualTo(new[] { "The answer so far" }));
Assert.That(parts.DraftText, Is.EqualTo("a draft"));
});
}
}
[Test]
@@ -91,11 +91,11 @@ public sealed class ConversationPartsTests
var parts = ConversationParts.Of(new() { Blocks = [finished] }, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions: null);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(parts.Texts, Is.EqualTo(new[] { "The whole answer." }));
Assert.That(parts.GrowingTexts, Is.Empty);
});
}
}
[Test]
@@ -134,11 +134,11 @@ public sealed class ConversationPartsTests
{
var parts = ConversationParts.Of(null, string.Empty, "Hello", null, imagesAreSent: true, toolDefinitions: null);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(parts.Texts, Is.Empty);
Assert.That(parts.DraftText, Is.EqualTo("Hello"));
});
}
}
[TestCase("")]
@@ -147,12 +147,12 @@ public sealed class ConversationPartsTests
{
var parts = ConversationParts.Of(null, string.Empty, draft, null, imagesAreSent: true, toolDefinitions: null);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(parts.Texts, Is.Empty);
Assert.That(parts.GrowingTexts, Is.Empty);
Assert.That(parts.DraftText, Is.Empty);
});
}
}
[Test]
@@ -169,11 +169,11 @@ public sealed class ConversationPartsTests
var parts = ConversationParts.Of(new() { Blocks = [empty] }, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions: null);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(parts.Texts, Is.Empty);
Assert.That(parts.Documents, Is.Empty);
});
}
}
[Test]
@@ -191,11 +191,11 @@ public sealed class ConversationPartsTests
var parts = ConversationParts.Of(new() { Blocks = [block] }, string.Empty, "And this one.", [FileAttachment.FromPath(draft)], imagesAreSent: true, toolDefinitions: null);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(parts.Documents.Select(document => document.FileName), Is.EqualTo(new[] { "older.txt" }));
Assert.That(parts.DraftDocuments.Select(document => document.FileName), Is.EqualTo(new[] { "draft.txt" }), "Both count, each in the part it belongs to.");
});
}
}
[Test]
@@ -219,11 +219,11 @@ public sealed class ConversationPartsTests
var parts = ConversationParts.Of(null, string.Empty, "Look", [FileAttachment.FromPath(document), FileAttachment.FromPath(image)], imagesAreSent: true, toolDefinitions: null);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(parts.DraftDocuments.Select(entry => entry.FileName), Is.EqualTo(new[] { "notes.txt" }));
Assert.That(parts.DraftImages, Is.EqualTo(1));
});
}
}
[Test]
@@ -240,11 +240,11 @@ public sealed class ConversationPartsTests
var parts = ConversationParts.Of(new() { Blocks = [block] }, string.Empty, "And at this.", [FileAttachment.FromPath(waiting)], imagesAreSent: true, toolDefinitions: null);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(parts.Images, Is.EqualTo(1));
Assert.That(parts.DraftImages, Is.EqualTo(1));
});
}
}
[Test]
@@ -275,12 +275,12 @@ public sealed class ConversationPartsTests
var parts = ConversationParts.Of(new() { Blocks = [running] }, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions: null);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(parts.Texts, Is.Empty);
Assert.That(parts.ToolConversation, Is.EqualTo(new[] { "What the web search found.", "What the page said." }));
Assert.That(parts.GrowingTexts, Is.Empty, "The tool conversation is a part of its own, so that its share can be named.");
});
}
}
[Test]
@@ -314,11 +314,11 @@ public sealed class ConversationPartsTests
var parts = ConversationParts.Of(new() { Blocks = [answered] }, string.Empty, string.Empty, null, imagesAreSent: true, toolDefinitions: null);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(parts.Texts, Is.EqualTo(new[] { "Here is what I found." }));
Assert.That(parts.ToolConversation, Is.Empty);
});
}
}
[Test]
@@ -335,7 +335,7 @@ public sealed class ConversationPartsTests
Tool("read_web_page", "Reads one page.", """{"type":"string"}"""),
]);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(parts.Texts, Is.EqualTo(new[]
{
@@ -344,7 +344,7 @@ public sealed class ConversationPartsTests
}));
Assert.That(parts.GrowingTexts, Is.Empty);
});
}
}
[Test]
@@ -131,11 +131,11 @@ public sealed class ConversationTokenTrackerTests
tracker.Start();
await Task.Delay(1_000);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(runs, Is.GreaterThanOrEqualTo(2));
Assert.That(asked, Is.EqualTo(runs));
});
}
}
[Test]
@@ -155,11 +155,11 @@ public sealed class ConversationTokenTrackerTests
var afterDisposal = runs;
await Task.Delay(400);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(afterDisposal, Is.GreaterThan(0), "The tracker never ran, so this proves nothing about stopping it.");
Assert.That(runs, Is.EqualTo(afterDisposal));
});
}
}
[Test]
+6 -6
View File
@@ -64,11 +64,11 @@ public sealed class ConversationTokensTests
ImageLimits = new ImageLimits(8, 100),
};
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(counted.TooManyImages, Is.True);
Assert.That(counted.ImageLimits.MaxInOneMessage, Is.EqualTo(8));
});
}
}
[Test]
@@ -111,11 +111,11 @@ public sealed class ConversationTokensTests
var estimated = reported with { HistoryIsReported = false };
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(reported.UncountedImages, Is.EqualTo(1), "The provider counted the two which were sent.");
Assert.That(estimated.UncountedImages, Is.EqualTo(3), "Without a report, nobody counted any of them.");
});
}
}
[Test]
@@ -133,11 +133,11 @@ public sealed class ConversationTokensTests
ImageLimits = new ImageLimits(8, null),
};
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(counted.UncountedImages, Is.Zero);
Assert.That(counted.TooManyImages, Is.True);
});
}
}
[Test]
@@ -43,12 +43,12 @@ public sealed class FileAttachmentSerializationTests
var json = JsonSerializer.Serialize(template, SettingsManager.JSON_OPTIONS);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(json, Does.Contain(FILE_PATH.Replace(@"\", @"\\")), "The attachment has to reach the JSON, or this test checks nothing.");
Assert.That(json, Does.Contain("chart.png"), "The attachment of the example conversation has to reach the JSON, or this test checks nothing.");
Assert.That(json, Does.Not.Contain("\"Exists\""));
});
}
}
[Test]
@@ -67,11 +67,11 @@ public sealed class FileAttachmentSerializationTests
var read = JsonSerializer.Deserialize<FileAttachment>(LEGACY_JSON, SettingsManager.JSON_OPTIONS)!;
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(read.FileName, Is.EqualTo("report.pdf"));
Assert.That(read.FilePath, Is.EqualTo("/tmp/report.pdf"));
Assert.That(read.FileSizeBytes, Is.EqualTo(1024));
});
}
}
}
+22 -22
View File
@@ -27,12 +27,12 @@ public sealed class IContentExtensionsTests
var found = content.TryGetExportMarkdown(out var markdown);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(found, Is.True);
Assert.That(markdown, Does.EndWith(content.Sources.ToExportMarkdown()), "What the chat shows below the answer is what the file holds below it.");
Assert.That(TopLevelBlocksOf(markdown), Is.EqualTo(new[] { "ParagraphBlock", "h1", "h2", "ListBlock" }), "The answer stays a paragraph of its own; the source list starts under its own heading.");
});
}
}
[Test]
@@ -47,11 +47,11 @@ public sealed class IContentExtensionsTests
var found = content.TryGetExportMarkdown(out var markdown);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(found, Is.True);
Assert.That(TopLevelBlocksOf(markdown), Is.EqualTo(new[] { "ParagraphBlock", "Table", "h1", "h2", "ListBlock" }), "The table ends where it ended; the headings below it are not two more rows.");
});
}
}
[Test]
@@ -66,11 +66,11 @@ public sealed class IContentExtensionsTests
var found = content.TryGetExportMarkdown(out var markdown);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(found, Is.True);
Assert.That(TopLevelBlocksOf(markdown), Is.EqualTo(new[] { "ParagraphBlock", "ListBlock", "h1", "h2", "ListBlock" }), "Two lists, not one: the sources do not become the fourth point of the answer.");
});
}
}
[Test]
@@ -86,11 +86,11 @@ public sealed class IContentExtensionsTests
var found = content.TryGetExportMarkdown(out var markdown);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(found, Is.True);
Assert.That(TopLevelBlocksOf(markdown), Is.EqualTo(new[] { "ParagraphBlock", "FencedCodeBlock", "h1", "h2", "ListBlock" }), "The code block is closed for the model, so the sources stand below it instead of inside it.");
});
}
}
[Test]
@@ -101,11 +101,11 @@ public sealed class IContentExtensionsTests
var found = content.TryGetExportMarkdown(out var markdown);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(found, Is.True);
Assert.That(markdown, Is.EqualTo(ANSWER.Trim()), "The everyday case: no heading, no empty line, nothing anybody has to explain.");
});
}
}
[Test]
@@ -115,11 +115,11 @@ public sealed class IContentExtensionsTests
var found = content.TryGetExportMarkdown(out var markdown);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(found, Is.True);
Assert.That(markdown, Is.EqualTo(content.Sources.ToExportMarkdown()), "Nothing above the heading means no empty line above it either.");
});
}
}
[Test]
@@ -132,11 +132,11 @@ public sealed class IContentExtensionsTests
Sources = [TOOL_SOURCE],
};
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(picture.TryGetExportMarkdown(out var markdown), Is.False, "There is no text document for a picture, so the caller hears no and says so.");
Assert.That(markdown, Is.Empty, "A file writer must not put an excuse into the file it writes.");
});
}
}
[Test]
@@ -147,11 +147,11 @@ public sealed class IContentExtensionsTests
var found = content.TryGetMarkdownText(out var markdown);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(found, Is.True);
Assert.That(markdown, Is.EqualTo(ANSWER), "Neither trimmed nor extended: whoever reads a table out of a message wants what the model wrote and nothing else.");
});
}
}
[Test]
@@ -164,11 +164,11 @@ public sealed class IContentExtensionsTests
var tables = FilesOf(content);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(tables, Has.Count.EqualTo(1), "One table in the message, one table offered for it.");
Assert.That(content.ToExportContent(tables[0]), Is.EqualTo(tables[0].Content), "A data table has no column a link list would fit into.");
});
}
}
[Test]
@@ -201,12 +201,12 @@ public sealed class IContentExtensionsTests
var exported = content.ToExportContent(file);
var appended = exported[file.Content.Length..].Trim();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(exported, Does.StartWith(file.Content), "The page stays as the model wrote it.");
Assert.That(appended, Does.StartWith("<!--").And.EndWith("-->"), "Below the page stands one comment and nothing a browser would show.");
Assert.That(appended, Does.Contain(TOOL_SOURCE.URL));
});
}
}
[Test]
@@ -221,12 +221,12 @@ public sealed class IContentExtensionsTests
var exported = content.ToExportContent(file);
var appended = exported[file.Content.Length..].Trim();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(exported, Does.StartWith(file.Content), "The document stays as the model wrote it.");
Assert.That(appended.Split(Environment.NewLine), Has.All.StartWith("%"), "A line LaTeX would read could stop the whole run.");
Assert.That(appended, Does.Contain(TOOL_SOURCE.URL));
});
}
}
[Test]
@@ -92,11 +92,11 @@ public sealed class ListContentBlockExtensionsTests
var messages = await BlocksWithAPicture(this.picturePath).BuildMessagesUsingDirectImageUrlAsync(PictureReadingProvider());
var picture = (messages.Single() as MultimodalMessage)!.Content.OfType<SubContentImageUrl>().Single();
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(picture.ImageUrl, Is.EqualTo($"data:image/jpeg;base64,{FakeImageRuntime.IMAGE_BASE64}"));
Assert.That(this.runtime.RequestedPaths, Is.EqualTo(new[] { new FileInfo(this.picturePath).FullName }));
});
}
}
[Test]
@@ -49,11 +49,11 @@ public sealed class ManagedTranscriptAttachmentTests
var chatDirectory = Path.Combine(this.testDirectory, "workspaces", Guid.NewGuid().ToString(), Guid.NewGuid().ToString());
var transcript = WriteTranscript(ManagedTranscriptAttachment.GetTranscriptDirectory(chatDirectory));
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ManagedTranscriptAttachment.TryDeleteOwnedFile(transcript), Is.True);
Assert.That(File.Exists(transcript.FilePath), Is.False);
});
}
}
[Test]
@@ -62,11 +62,11 @@ public sealed class ManagedTranscriptAttachmentTests
var chatDirectory = Path.Combine(this.testDirectory, "workspaces", Guid.NewGuid().ToString(), Guid.NewGuid().ToString());
var file = WriteTranscript(Path.Combine(chatDirectory, "attachments", "images"));
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ManagedTranscriptAttachment.TryDeleteOwnedFile(file), Is.False, "Only the transcript folder belongs to the transcripts.");
Assert.That(File.Exists(file.FilePath), Is.True);
});
}
}
[Test]
@@ -77,11 +77,11 @@ public sealed class ManagedTranscriptAttachmentTests
{
var file = WriteTranscript(ManagedTranscriptAttachment.GetTranscriptDirectory(Path.Combine(outsideDirectory, "tempChats", Guid.NewGuid().ToString())));
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(ManagedTranscriptAttachment.TryDeleteOwnedFile(file), Is.False, "The same folders elsewhere belong to somebody else.");
Assert.That(File.Exists(file.FilePath), Is.True);
});
}
}
finally
{
+14 -14
View File
@@ -50,12 +50,12 @@ public sealed class ProvenanceCleanupTests
var result = await CleanUpAsync(store, Locations(keptChatId), LoaderOf(chat), DateTimeOffset.UtcNow.AddMinutes(1));
var entries = await store.Client.GetEntriesAsync([referenced.Value, ofRemovedMessage!.Value, ofDeletedChat!.Value], TOKEN);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(entries.Keys, Is.EquivalentTo(new[] { referenced.Value }));
Assert.That(result.Orphaned, Is.EqualTo(1));
Assert.That(result.Unreferenced, Is.EqualTo(1));
});
}
}
[Test]
@@ -83,11 +83,11 @@ public sealed class ProvenanceCleanupTests
var result = await CleanUpAsync(store, Locations(Guid.NewGuid()), UNREADABLE, DateTimeOffset.UtcNow.AddMinutes(1));
var entries = await store.Client.GetEntriesAsync([entry!.Value], TOKEN);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(entries, Is.Empty, "No chat on the disk has no id, so nothing would ever remove such an entry.");
Assert.That(result.Orphaned, Is.EqualTo(1));
});
}
}
[Test]
@@ -100,11 +100,11 @@ public sealed class ProvenanceCleanupTests
var result = await CleanUpAsync(store, Locations(chatId), UNREADABLE, DateTimeOffset.UtcNow.AddMinutes(1));
var entries = await store.Client.GetEntriesAsync([entry!.Value], TOKEN);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(entries.Keys, Is.EquivalentTo(new[] { entry.Value }), "Nothing tells what an unreadable chat refers to, e.g., one written by a newer version.");
Assert.That(result.Unreadable, Is.EqualTo(1), "The next start has to read it again.");
});
}
}
[Test]
@@ -119,12 +119,12 @@ public sealed class ProvenanceCleanupTests
var result = await CleanUpAsync(store, Locations(chatId), LoaderOf(ChatWith(passageId: null, attachmentId: null, chatId), reads), lastCleanup.AddMinutes(1), lastCleanup, writtenAt: lastCleanup.AddHours(-1));
var entries = await store.Client.GetEntriesAsync([entry!.Value], TOKEN);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(reads, Is.Empty, "Reading every chat at every start costs much on a small computer.");
Assert.That(result.Skipped, Is.EqualTo(1));
Assert.That(entries.Keys, Is.EquivalentTo(new[] { entry.Value }), "Skipping a chat never removes anything.");
});
}
}
[Test]
@@ -139,11 +139,11 @@ public sealed class ProvenanceCleanupTests
await CleanUpAsync(store, Locations(chatId), LoaderOf(ChatWith(passageId: null, attachmentId: null, chatId), reads), DateTimeOffset.UtcNow.AddMinutes(1), lastCleanup, writtenAt: lastCleanup.AddHours(-1));
var entries = await store.Client.GetEntriesAsync([previewed!.Value], TOKEN);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(reads, Is.EquivalentTo(new[] { chatId }), "The last cleanup had to leave alone what was stored after it began.");
Assert.That(entries, Is.Empty);
});
}
}
[TestCase(60, TestName = "AChatWrittenSinceTheLastCleanupIsRead")]
@@ -160,11 +160,11 @@ public sealed class ProvenanceCleanupTests
await CleanUpAsync(store, Locations(chatId), LoaderOf(ChatWith(passageId: null, attachmentId: null, chatId), reads), lastCleanup.AddMinutes(1), lastCleanup, writtenAt: lastCleanup.AddSeconds(secondsAfterTheLastCleanup));
var entries = await store.Client.GetEntriesAsync([ofRemovedMessage!.Value], TOKEN);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(reads, Is.EquivalentTo(new[] { chatId }));
Assert.That(entries, Is.Empty);
});
}
}
[Test]
@@ -192,12 +192,12 @@ public sealed class ProvenanceCleanupTests
var result = await CleanUpAsync(store, Locations(), LoaderOf(ChatWith(passageId: null, attachmentId: null), reads), lastCleanup.AddMinutes(1), lastCleanup, writtenAt: lastCleanup.AddHours(-1));
var entries = await store.Client.GetEntriesAsync([entry!.Value], TOKEN);
Assert.Multiple(() =>
using (Assert.EnterMultipleScope())
{
Assert.That(entries, Is.Empty, "Telling that a chat no longer exists costs no reading.");
Assert.That(result.Orphaned, Is.EqualTo(1));
Assert.That(reads, Is.Empty);
});
}
}
private static ChatThread ChatWith(Guid? passageId, Guid? attachmentId, Guid? chatId = null) => new()