mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-10-13 17:13:48 +00:00
194 lines
9.5 KiB
C#
194 lines
9.5 KiB
C#
using System.Text.Json;
|
|
|
|
using AIStudio.Chat;
|
|
using AIStudio.Tools;
|
|
|
|
namespace AIStudio.Tests.Chat;
|
|
|
|
/// <summary>
|
|
/// Checks the draft of a chat, the message the user has not sent yet.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The draft is stored with its chat, so the message is still there when the user comes back to it.
|
|
/// Two parties change the draft: the user in the composer, and the media transcription service,
|
|
/// which adds a finished transcript before the composer shows it. That is why a transcript only
|
|
/// leaves the draft when the user removes it, or when the message is sent.
|
|
/// </remarks>
|
|
[TestFixture]
|
|
public sealed class ChatDraftTests
|
|
{
|
|
private static readonly FileAttachment AGENDA = new(FileAttachmentType.DOCUMENT, "agenda.pdf", "/documents/agenda.pdf", 1024);
|
|
private static readonly FileAttachment MINUTES = new(FileAttachmentType.DOCUMENT, "minutes.pdf", "/documents/minutes.pdf", 2048);
|
|
private static readonly FileAttachmentImage CHART = new("chart.png", "/documents/chart.png", 4096);
|
|
private static readonly ManagedTranscriptAttachment TRANSCRIPT = new("meeting-transcript-0001.md", "/data/tempChats/meeting-transcript-0001.md", 512, "meeting.mp4", false);
|
|
private static readonly ManagedTranscriptAttachment STAGED_TRANSCRIPT = new("4f1c.md", "/data/media-staging/4f1c.md", 256, "kickoff.mp4", true);
|
|
|
|
[Test]
|
|
public void MirroringTheComposerKeepsATranscriptItDoesNotShowYet()
|
|
{
|
|
var draft = new ChatDraft();
|
|
draft.AddAttachments([AGENDA]);
|
|
|
|
// The transcription service finished, and the composer has not shown the transcript yet:
|
|
draft.AddAttachments([TRANSCRIPT]);
|
|
|
|
// Meanwhile, the user replaced the agenda by the minutes:
|
|
draft.MirrorAttachments([MINUTES]);
|
|
|
|
Assert.That(draft.FileAttachments, Is.EqualTo(new[] { MINUTES, TRANSCRIPT }), "The composer decides on the other attachments, but a transcript it does not show yet has to stay, or its file is left without an owner.");
|
|
}
|
|
|
|
[Test]
|
|
public void AnAttachmentIsAddedOnlyOnce()
|
|
{
|
|
var draft = new ChatDraft();
|
|
|
|
var added = draft.AddAttachments([TRANSCRIPT]);
|
|
var addedAgain = draft.AddAttachments([TRANSCRIPT with { FileSizeBytes = 1024 }]);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
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]
|
|
public void RemovingAnAttachmentFindsItByItsPath()
|
|
{
|
|
var draft = new ChatDraft();
|
|
draft.AddAttachments([AGENDA, TRANSCRIPT]);
|
|
|
|
// The composer holds the transcript as it normalized it, with the current size of its file:
|
|
draft.RemoveAttachments([TRANSCRIPT with { FileSizeBytes = 1024 }]);
|
|
|
|
Assert.That(draft.FileAttachments, Is.EqualTo(new[] { AGENDA }), "The transcript the user removed must not come back when the chat is opened again.");
|
|
}
|
|
|
|
[Test]
|
|
public void ReplacingTheAttachmentsKeepsTheirKind()
|
|
{
|
|
var draft = new ChatDraft();
|
|
draft.AddAttachments([AGENDA, TRANSCRIPT]);
|
|
|
|
draft.ReplaceAttachments(attachment => attachment with { FilePath = $"/moved{attachment.FilePath}" });
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
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]
|
|
public void EditingAMessageKeepsTheTranscriptsOfTheDraft()
|
|
{
|
|
var draft = new ChatDraft();
|
|
draft.SetText("An older draft");
|
|
draft.AddAttachments([AGENDA, TRANSCRIPT]);
|
|
|
|
draft.RestoreFromMessage(new ContentText { Text = "Summarize the minutes", FileAttachments = [MINUTES] });
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
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]
|
|
public void SendingEmptiesTheDraft()
|
|
{
|
|
var draft = new ChatDraft();
|
|
draft.SetText("Summarize the meeting");
|
|
draft.AddAttachments([TRANSCRIPT]);
|
|
|
|
draft.Clear();
|
|
|
|
Assert.That(draft.IsEmpty, Is.True, "The attachments belong to the sent message now, and a transcript has only one owner.");
|
|
}
|
|
|
|
[Test]
|
|
public void TheDraftKeepsTheKindOfEveryAttachmentWhenStored()
|
|
{
|
|
var thread = new ChatThread { ChatId = Guid.NewGuid(), Name = "Meeting notes" };
|
|
thread.Draft.SetText("Summarize the meeting");
|
|
thread.Draft.AddAttachments([AGENDA, CHART, STAGED_TRANSCRIPT]);
|
|
|
|
var loaded = JsonSerializer.Deserialize<ChatThread>(JsonSerializer.Serialize(thread, WorkspaceBehaviour.JSON_OPTIONS), WorkspaceBehaviour.JSON_OPTIONS)!;
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
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]
|
|
public void AChatOfTheFormerVersionBringsItsPendingTranscriptsIntoTheDraft()
|
|
{
|
|
// How v26.9.1 stored a chat with a transcript which was not sent yet, without a type discriminator:
|
|
const string CHAT_WITH_PENDING_TRANSCRIPTS = """
|
|
{
|
|
"chat_id": "2b7c9e1f-4a6d-4c8b-9e3f-1d5a7c9b2e4f",
|
|
"pending_media_transcripts": [
|
|
{
|
|
"original_file_name": "meeting.mp4",
|
|
"is_staged": false,
|
|
"type": "DOCUMENT",
|
|
"file_name": "meeting-transcript-0001.md",
|
|
"file_path": "/data/tempChats/meeting-transcript-0001.md",
|
|
"file_size_bytes": 512,
|
|
"is_forbidden": false,
|
|
"is_valid": true,
|
|
"is_image": false,
|
|
"file_path_as_url": "/local/file?path=%2Fdata%2FtempChats%2Fmeeting-transcript-0001.md",
|
|
"exists": true
|
|
}
|
|
],
|
|
"name": "Meeting notes",
|
|
"blocks": []
|
|
}
|
|
""";
|
|
|
|
var loaded = JsonSerializer.Deserialize<ChatThread>(CHAT_WITH_PENDING_TRANSCRIPTS, WorkspaceBehaviour.JSON_OPTIONS)!;
|
|
var stored = JsonSerializer.Serialize(loaded, WorkspaceBehaviour.JSON_OPTIONS);
|
|
|
|
Assert.Multiple(() =>
|
|
{
|
|
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]
|
|
public void AChatWithoutADraftOpensWithAnEmptyOne()
|
|
{
|
|
const string CHAT_WITHOUT_DRAFT = """
|
|
{
|
|
"chat_id": "2b7c9e1f-4a6d-4c8b-9e3f-1d5a7c9b2e4f",
|
|
"name": "An older chat",
|
|
"blocks": []
|
|
}
|
|
""";
|
|
|
|
var loaded = JsonSerializer.Deserialize<ChatThread>(CHAT_WITHOUT_DRAFT, WorkspaceBehaviour.JSON_OPTIONS);
|
|
|
|
Assert.That(loaded?.Draft.IsEmpty, Is.True);
|
|
}
|
|
|
|
[Test]
|
|
public void ACopyOfTheSameChatSharesTheDraft()
|
|
{
|
|
var thread = new ChatThread { ChatId = Guid.NewGuid() };
|
|
|
|
// What the chat does when the user picks another profile:
|
|
var withOtherProfile = thread with { SelectedProfile = Guid.NewGuid().ToString() };
|
|
withOtherProfile.Draft.SetText("Summarize the meeting");
|
|
|
|
Assert.That(thread.Draft.Text, Is.EqualTo("Summarize the meeting"), "Both are the same chat, and the transcription service may still hold the former one.");
|
|
}
|
|
} |