using MeetingAssistant.MeetingNotes; using Microsoft.Extensions.Logging.Abstractions; namespace MeetingAssistant.Tests; public sealed class MeetingArtifactStoreTests { [Fact] public async Task StoreCreatesAssistantContextNoteLinkedToMeetingArtifacts() { var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N")); var contextPath = Path.Combine(root, "Meetings", "Assistant Context", "20260519-context.md"); var artifactStore = new MarkdownMeetingArtifactStore(NullLogger.Instance); var artifacts = new MeetingSessionArtifacts( MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "20260519-meeting.md"), TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "20260519-transcript.md"), AssistantContextPath: contextPath, SummaryPath: Path.Combine(root, "Meetings", "Summaries", "20260519-summary.md")); var meetingNote = MeetingNoteTemplate.Create( "Planning Sync", DateTimeOffset.Parse("2026-05-19T10:00:00+02:00")); await artifactStore.CreateAssistantContextAsync( artifacts, meetingNote, "Review previous decisions\nAgree next steps", DateTimeOffset.Parse("2026-05-19T11:00:00+02:00"), CancellationToken.None); var content = await File.ReadAllTextAsync(contextPath); Assert.Contains("# Assistant Context", content); Assert.StartsWith("---", content, StringComparison.Ordinal); Assert.Contains("title: Planning Sync", content); Assert.Contains("start_time: \"2026-05-19T10:00:00.0000000+02:00\"", content); Assert.Contains("state: collecting metadata", content); Assert.Contains("agenda: |-", content); Assert.Contains(" Review previous decisions", content); Assert.Contains(" Agree next steps", content); Assert.Contains("scheduled_end: \"2026-05-19T11:00:00.0000000+02:00\"", content); Assert.Contains("meeting: \"[[../Notes/20260519-meeting|Meeting Note]]\"", content); Assert.Contains("transcript: \"[[../Transcripts/20260519-transcript|Transcript]]\"", content); Assert.Contains("summary: \"[[../Summaries/20260519-summary|Summary]]\"", content); Assert.DoesNotContain("assistant_context:", content); Assert.Contains("[[../Notes/20260519-meeting|Meeting Note]]", content); Assert.Contains("[[../Transcripts/20260519-transcript|Transcript]]", content); Assert.Contains("[[../Summaries/20260519-summary|Summary]]", content); } [Fact] public async Task StoreUpdatesAssistantContextStateAndPreservesBody() { var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N")); var contextPath = Path.Combine(root, "Meetings", "Assistant Context", "20260519-context.md"); var artifactStore = new MarkdownMeetingArtifactStore(NullLogger.Instance); var artifacts = new MeetingSessionArtifacts( MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "20260519-meeting.md"), TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "20260519-transcript.md"), AssistantContextPath: contextPath, SummaryPath: Path.Combine(root, "Meetings", "Summaries", "20260519-summary.md")); var meetingNote = MeetingNoteTemplate.Create( "Planning Sync", DateTimeOffset.Parse("2026-05-19T10:00:00+02:00")); await artifactStore.CreateAssistantContextAsync( artifacts, meetingNote, "Initial agenda", DateTimeOffset.Parse("2026-05-19T11:00:00+02:00"), CancellationToken.None); await File.AppendAllTextAsync(contextPath, "Observed context line."); await artifactStore.UpdateAssistantContextStateAsync( artifacts, AssistantContextState.Summarizing, CancellationToken.None); var content = await File.ReadAllTextAsync(contextPath); Assert.Contains("state: summarizing", content); Assert.Contains("title: Planning Sync", content); Assert.Contains("start_time: \"2026-05-19T10:00:00.0000000+02:00\"", content); Assert.Contains("agenda: |-", content); Assert.Contains(" Initial agenda", content); Assert.Contains("scheduled_end: \"2026-05-19T11:00:00.0000000+02:00\"", content); Assert.Contains("Observed context line.", content); } [Fact] public async Task StoreOmitsScheduledEndWhenNoTeamsMeetingWasDetected() { var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N")); var contextPath = Path.Combine(root, "Meetings", "Assistant Context", "20260519-context.md"); var artifactStore = new MarkdownMeetingArtifactStore(NullLogger.Instance); var artifacts = new MeetingSessionArtifacts( MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "20260519-meeting.md"), TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "20260519-transcript.md"), AssistantContextPath: contextPath, SummaryPath: Path.Combine(root, "Meetings", "Summaries", "20260519-summary.md")); var meetingNote = MeetingNoteTemplate.Create( "Planning Sync", DateTimeOffset.Parse("2026-05-19T10:00:00+02:00")); await artifactStore.CreateAssistantContextAsync(artifacts, meetingNote, "", null, CancellationToken.None); var content = await File.ReadAllTextAsync(contextPath); Assert.DoesNotContain("scheduled_end:", content); } [Fact] public async Task StoreUpdatesAssistantContextMetadataAndMeetingFrontmatter() { var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N")); var contextPath = Path.Combine(root, "Meetings", "Assistant Context", "20260519-context.md"); var artifactStore = new MarkdownMeetingArtifactStore(NullLogger.Instance); var artifacts = new MeetingSessionArtifacts( MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "20260519-meeting.md"), TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "20260519-transcript.md"), AssistantContextPath: contextPath, SummaryPath: Path.Combine(root, "Meetings", "Summaries", "20260519-summary.md")); await artifactStore.CreateAssistantContextAsync( artifacts, MeetingNoteTemplate.Create("Meeting 2026-05-19 10:00", DateTimeOffset.Parse("2026-05-19T10:00:00+02:00")), "", null, CancellationToken.None); await artifactStore.UpdateAssistantContextMetadataAsync( artifacts, MeetingNoteTemplate.Create("Architecture Sync", DateTimeOffset.Parse("2026-05-19T10:00:00+02:00")), "Calendar agenda", DateTimeOffset.Parse("2026-05-19T11:00:00+02:00"), CancellationToken.None); var content = await File.ReadAllTextAsync(contextPath); Assert.Contains("title: Architecture Sync", content); Assert.Contains("start_time: \"2026-05-19T10:00:00.0000000+02:00\"", content); Assert.Contains("state: collecting metadata", content); Assert.Contains("agenda: |-", content); Assert.Contains(" Calendar agenda", content); Assert.Contains("scheduled_end: \"2026-05-19T11:00:00.0000000+02:00\"", content); } [Fact] public async Task StoreUpdatesAssistantContextMeetingEndAndPreservesCalendarMetadata() { var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N")); var contextPath = Path.Combine(root, "Meetings", "Assistant Context", "20260519-context.md"); var artifactStore = new MarkdownMeetingArtifactStore(NullLogger.Instance); var artifacts = new MeetingSessionArtifacts( MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "20260519-meeting.md"), TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "20260519-transcript.md"), AssistantContextPath: contextPath, SummaryPath: Path.Combine(root, "Meetings", "Summaries", "20260519-summary.md")); await artifactStore.CreateAssistantContextAsync( artifacts, MeetingNoteTemplate.Create("Architecture Sync", DateTimeOffset.Parse("2026-05-19T10:00:00+02:00")), "Calendar agenda", DateTimeOffset.Parse("2026-05-19T11:00:00+02:00"), CancellationToken.None); await artifactStore.UpdateAssistantContextMeetingAsync( artifacts, MeetingNoteTemplate.Create( "Architecture Sync", DateTimeOffset.Parse("2026-05-19T10:00:00+02:00"), DateTimeOffset.Parse("2026-05-19T10:30:00+02:00")), CancellationToken.None); var content = await File.ReadAllTextAsync(contextPath); Assert.Contains("title: Architecture Sync", 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("agenda: |-", content); Assert.Contains(" Calendar agenda", content); Assert.Contains("scheduled_end: \"2026-05-19T11:00:00.0000000+02:00\"", content); } }