using MeetingAssistant.MeetingNotes; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; namespace MeetingAssistant.Tests; public sealed class MeetingNoteStoreTests { [Fact] public async Task StoreWritesTemplateFrontmatterAndReadsUserNotesBody() { var (vaultRoot, store) = CreateStore(); var note = MeetingNoteTemplate.Create( title: "Leadership Sync", startTime: DateTimeOffset.Parse("2026-05-19T10:00:00+02:00"), endTime: DateTimeOffset.Parse("2026-05-19T10:30:00+02:00"), attendees: [ "Mike ", "Ada" ], projects: [ "Meeting Assistant", "Alaric" ], transcriptPath: Path.Combine(vaultRoot, "Meetings", "Transcripts", "20260519-transcript.md"), assistantContextPath: Path.Combine(vaultRoot, "Meetings", "Assistant Context", "20260519-context.md"), summaryPath: Path.Combine(vaultRoot, "Meetings", "Summaries", "20260519-summary.md"), userNotes: "Discuss rollout risks."); var saved = await store.SaveAsync(note, CancellationToken.None); var content = await File.ReadAllTextAsync(saved.Path); var loaded = await store.ReadAsync(saved.Path, CancellationToken.None); Assert.EndsWith(".md", saved.Path, StringComparison.Ordinal); Assert.Contains("attendees:", content); Assert.Contains("- Mike ", content); Assert.Contains("- Ada", content); Assert.Contains("projects:", content); Assert.Contains("- Meeting Assistant", content); Assert.DoesNotContain("purpose:", content); Assert.Contains("start_time: \"2026-05-19T10:00:00.0000000+02:00\"", content); Assert.Contains("end_time: \"2026-05-19T10:30:00.0000000+02:00\"", content); Assert.Contains("transcript: \"[[../Transcripts/20260519-transcript|Transcript]]\"", content); Assert.Contains("assistant_context: \"[[../Assistant Context/20260519-context|Assistant Context]]\"", content); Assert.Contains("summary: \"[[../Summaries/20260519-summary|Summary]]\"", content); Assert.DoesNotContain("decisions:", content); Assert.DoesNotContain("next_steps:", content); Assert.Equal("Discuss rollout risks.", loaded.UserNotes); Assert.Equal(["Mike ", "Ada"], loaded.Frontmatter.Attendees); Assert.Equal(["Meeting Assistant", "Alaric"], loaded.Frontmatter.Projects); Assert.Equal(DateTimeOffset.Parse("2026-05-19T10:00:00+02:00"), loaded.Frontmatter.StartTime); Assert.Equal(DateTimeOffset.Parse("2026-05-19T10:30:00+02:00"), loaded.Frontmatter.EndTime); Assert.Equal("[[../Transcripts/20260519-transcript|Transcript]]", loaded.Frontmatter.Transcript); Assert.Equal("[[../Assistant Context/20260519-context|Assistant Context]]", loaded.Frontmatter.AssistantContext); Assert.Equal("[[../Summaries/20260519-summary|Summary]]", loaded.Frontmatter.Summary); } [Fact] public async Task FrontmatterUpdatePreservesExistingUserNotesBody() { var (vaultRoot, store) = CreateStore(); var userNotes = """ ## User notes - First point - Second point ```text Keep this block exactly. ``` """; var saved = await store.SaveAsync( MeetingNoteTemplate.Create( title: "Body Preservation", attendees: [], projects: [], transcriptPath: Path.Combine(vaultRoot, "Meetings", "Transcripts", "20260519-transcript.md"), assistantContextPath: Path.Combine(vaultRoot, "Meetings", "Assistant Context", "20260519-context.md"), summaryPath: Path.Combine(vaultRoot, "Meetings", "Summaries", "20260519-summary.md"), userNotes: userNotes), CancellationToken.None); var loaded = await store.ReadAsync(saved.Path, CancellationToken.None); loaded.Frontmatter.Title = "Updated title"; await store.SaveAsync(loaded, CancellationToken.None); var reloaded = await store.ReadAsync(saved.Path, CancellationToken.None); Assert.Equal("Updated title", reloaded.Frontmatter.Title); Assert.Equal(userNotes, reloaded.UserNotes); } [Fact] public void ActionLinkEscapesSummaryFileName() { var link = MeetingNoteActionLinks.CreateSummaryRetryLink( "http://localhost:5090/", "C:\\Vault\\Meetings\\Summaries\\summary with spaces.md"); Assert.Equal( "[Retry summary generation](http://localhost:5090/meetings/summary/retry?summaryPath=summary%20with%20spaces.md)", link); } private static (string VaultRoot, MarkdownMeetingNoteStore Store) CreateStore() { var vaultRoot = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N")); var store = new MarkdownMeetingNoteStore( Options.Create(new MeetingAssistantOptions { Vault = new VaultOptions { MeetingNotesFolder = Path.Combine(vaultRoot, "Meetings", "Notes"), TranscriptsFolder = Path.Combine(vaultRoot, "Meetings", "Transcripts"), AssistantContextFolder = Path.Combine(vaultRoot, "Meetings", "Assistant Context"), SummariesFolder = Path.Combine(vaultRoot, "Meetings", "Summaries"), ProjectsFolder = Path.Combine(vaultRoot, "Projects"), DictationWordsPath = Path.Combine(vaultRoot, "Meetings", "dictation-words.md") } }), NullLogger.Instance); return (vaultRoot, store); } }