Public Access
Add meeting inactivity safeguards
PR and Push Build/Test / build-and-test (push) Failing after 14m53s
PR and Push Build/Test / build-and-test (push) Failing after 14m53s
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
using MeetingAssistant.MeetingNotes;
|
||||
using MeetingAssistant.Recording;
|
||||
|
||||
namespace MeetingAssistant.Tests;
|
||||
|
||||
public sealed class MeetingArtifactContentDetectorTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task IsDefaultOnlyReturnsFalseWhenTranscriptContainsSegment()
|
||||
{
|
||||
using var fixture = await DefaultArtifactFixture.CreateAsync();
|
||||
await File.AppendAllTextAsync(
|
||||
fixture.Artifacts.TranscriptPath,
|
||||
"[00:00:01] Guest-1: hello" + Environment.NewLine);
|
||||
|
||||
var result = await MeetingArtifactContentDetector.IsDefaultOnlyAsync(
|
||||
fixture.Artifacts,
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsDefaultOnlyReturnsFalseWhenMeetingNoteContainsUserNotes()
|
||||
{
|
||||
using var fixture = await DefaultArtifactFixture.CreateAsync();
|
||||
await File.AppendAllTextAsync(
|
||||
fixture.Artifacts.MeetingNotePath,
|
||||
"Manual note" + Environment.NewLine);
|
||||
|
||||
var result = await MeetingArtifactContentDetector.IsDefaultOnlyAsync(
|
||||
fixture.Artifacts,
|
||||
CancellationToken.None);
|
||||
|
||||
Assert.False(result);
|
||||
}
|
||||
|
||||
private sealed class DefaultArtifactFixture : IDisposable
|
||||
{
|
||||
private DefaultArtifactFixture(string root, MeetingSessionArtifacts artifacts)
|
||||
{
|
||||
Root = root;
|
||||
Artifacts = artifacts;
|
||||
}
|
||||
|
||||
public string Root { get; }
|
||||
|
||||
public MeetingSessionArtifacts Artifacts { get; }
|
||||
|
||||
public static async Task<DefaultArtifactFixture> CreateAsync()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(root);
|
||||
var artifacts = new MeetingSessionArtifacts(
|
||||
Path.Combine(root, "meeting.md"),
|
||||
Path.Combine(root, "transcript.md"),
|
||||
Path.Combine(root, "assistant-context.md"),
|
||||
Path.Combine(root, "summary.md"));
|
||||
await File.WriteAllTextAsync(
|
||||
artifacts.MeetingNotePath,
|
||||
"""
|
||||
---
|
||||
title: Test
|
||||
---
|
||||
|
||||
""");
|
||||
await File.WriteAllTextAsync(
|
||||
artifacts.TranscriptPath,
|
||||
"# Meeting Transcript" + Environment.NewLine + Environment.NewLine);
|
||||
await File.WriteAllTextAsync(
|
||||
artifacts.AssistantContextPath,
|
||||
"# Assistant Context" + Environment.NewLine + Environment.NewLine + "## Live Context" + Environment.NewLine);
|
||||
|
||||
return new DefaultArtifactFixture(root, artifacts);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Directory.Exists(Root))
|
||||
{
|
||||
Directory.Delete(Root, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,6 +34,9 @@ public sealed class MeetingSummaryInstructionBuilderTests
|
||||
Assert.Contains("oneliner", instructions);
|
||||
Assert.Contains("very short", instructions);
|
||||
Assert.Contains("conciseness is more important", instructions);
|
||||
Assert.Contains("generated default title", instructions);
|
||||
Assert.Contains("purpose of the meeting is clear", instructions);
|
||||
Assert.Contains("title parameter", instructions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
@@ -68,6 +68,70 @@ public sealed class MeetingSummaryToolTests
|
||||
Assert.Contains("# Summary\n\n- Done.", summary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteSummaryRenamesMeetingWhenTitleIsProvided()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||
var artifacts = new MeetingSessionArtifacts(
|
||||
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
||||
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
||||
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
||||
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.MeetingNotePath)!);
|
||||
await File.WriteAllTextAsync(
|
||||
artifacts.MeetingNotePath,
|
||||
"""
|
||||
---
|
||||
title: Meeting 2026-06-03 07:26
|
||||
start_time: "2026-06-03T07:26:00.0000000+02:00"
|
||||
attendees: []
|
||||
projects: []
|
||||
---
|
||||
|
||||
User note.
|
||||
""");
|
||||
var tools = new MeetingSummaryTools(artifacts);
|
||||
|
||||
await tools.WriteSummary("# Summary", "Renamed summary", "Notification Safeguard Test");
|
||||
|
||||
var meetingNote = await File.ReadAllTextAsync(artifacts.MeetingNotePath);
|
||||
var summary = await File.ReadAllTextAsync(artifacts.SummaryPath);
|
||||
Assert.Contains("title: Notification Safeguard Test", meetingNote);
|
||||
Assert.Contains("title: Notification Safeguard Test", summary);
|
||||
Assert.Contains("User note.", meetingNote);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteSummaryKeepsMeetingTitleWhenTitleIsOmittedOrBlank()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||
var artifacts = new MeetingSessionArtifacts(
|
||||
MeetingNotePath: Path.Combine(root, "Meetings", "Notes", "meeting.md"),
|
||||
TranscriptPath: Path.Combine(root, "Meetings", "Transcripts", "transcript.md"),
|
||||
AssistantContextPath: Path.Combine(root, "Meetings", "Assistant Context", "context.md"),
|
||||
SummaryPath: Path.Combine(root, "Meetings", "Summaries", "summary.md"));
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.MeetingNotePath)!);
|
||||
await File.WriteAllTextAsync(
|
||||
artifacts.MeetingNotePath,
|
||||
"""
|
||||
---
|
||||
title: Architecture Review
|
||||
attendees: []
|
||||
projects: []
|
||||
---
|
||||
""");
|
||||
var tools = new MeetingSummaryTools(artifacts);
|
||||
|
||||
await tools.WriteSummary("# Summary", "Original title summary");
|
||||
await tools.WriteSummary("# Summary", "Blank title summary", " ");
|
||||
|
||||
var meetingNote = await File.ReadAllTextAsync(artifacts.MeetingNotePath);
|
||||
var summary = await File.ReadAllTextAsync(artifacts.SummaryPath);
|
||||
Assert.Contains("title: Architecture Review", meetingNote);
|
||||
Assert.Contains("title: Architecture Review", summary);
|
||||
Assert.DoesNotContain("Meeting Summary", summary);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task WriteSummaryPreservesExistingSummaryAttendees()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
using MeetingAssistant.Recording;
|
||||
|
||||
namespace MeetingAssistant.Tests;
|
||||
|
||||
public sealed class NotificationActivationArgumentsTests
|
||||
{
|
||||
[Fact]
|
||||
public void ParseAcceptsSemicolonDelimitedToastActivationArguments()
|
||||
{
|
||||
var arguments = NotificationActivationArguments.Parse(
|
||||
"source=meeting-inactivity-safeguard;promptId=abc123;response=stop");
|
||||
|
||||
Assert.Equal("meeting-inactivity-safeguard", arguments["source"]);
|
||||
Assert.Equal("abc123", arguments["promptId"]);
|
||||
Assert.Equal("stop", arguments["response"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ParseAcceptsAmpersandDelimitedActivationArguments()
|
||||
{
|
||||
var arguments = NotificationActivationArguments.Parse(
|
||||
"source=meeting-inactivity-safeguard&promptId=abc123&response=continue");
|
||||
|
||||
Assert.Equal("meeting-inactivity-safeguard", arguments["source"]);
|
||||
Assert.Equal("abc123", arguments["promptId"]);
|
||||
Assert.Equal("continue", arguments["response"]);
|
||||
}
|
||||
}
|
||||
@@ -179,6 +179,63 @@ public sealed class RecordingCoordinatorTests
|
||||
Assert.Equal(started.SummaryPath, artifactCleaner.DeletedArtifacts?.SummaryPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StopDeletesDefaultOnlyArtifactsInsteadOfSummarizing()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||
try
|
||||
{
|
||||
var options = new MeetingAssistantOptions
|
||||
{
|
||||
Vault =
|
||||
{
|
||||
BaseFolder = root,
|
||||
MeetingNotesFolder = "Notes",
|
||||
TranscriptsFolder = "Transcripts",
|
||||
AssistantContextFolder = "Assistant Context",
|
||||
SummariesFolder = "Summaries"
|
||||
}
|
||||
};
|
||||
var audioSource = new ControlledAudioSource();
|
||||
var artifactCleaner = new CapturingMeetingRunArtifactCleaner();
|
||||
var summaryPipeline = new CapturingMeetingSummaryPipeline();
|
||||
var coordinator = new MeetingRecordingCoordinator(
|
||||
audioSource,
|
||||
new TestSpeechRecognitionPipelineFactory(new EchoStreamingTranscriptionProvider()),
|
||||
new VaultTranscriptStore(
|
||||
Options.Create(options),
|
||||
NullLogger<VaultTranscriptStore>.Instance),
|
||||
new MarkdownMeetingNoteStore(
|
||||
Options.Create(options),
|
||||
NullLogger<MarkdownMeetingNoteStore>.Instance),
|
||||
new CapturingMeetingNoteOpener(),
|
||||
new MarkdownMeetingArtifactStore(
|
||||
NullLogger<MarkdownMeetingArtifactStore>.Instance),
|
||||
new InMemoryRecordedAudioStore(),
|
||||
summaryPipeline,
|
||||
Options.Create(options),
|
||||
NullLogger<MeetingRecordingCoordinator>.Instance,
|
||||
artifactCleaner: artifactCleaner);
|
||||
|
||||
var started = await coordinator.StartAsync(CancellationToken.None);
|
||||
var stopped = await coordinator.StopAsync(CancellationToken.None);
|
||||
|
||||
Assert.False(stopped.IsRecording);
|
||||
Assert.Null(stopped.MeetingNotePath);
|
||||
Assert.False(summaryPipeline.WasRun);
|
||||
Assert.Equal(started.MeetingNotePath, artifactCleaner.DeletedArtifacts?.MeetingNotePath);
|
||||
Assert.Equal(started.TranscriptPath, artifactCleaner.DeletedArtifacts?.TranscriptPath);
|
||||
Assert.Equal(started.AssistantContextPath, artifactCleaner.DeletedArtifacts?.AssistantContextPath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (Directory.Exists(root))
|
||||
{
|
||||
Directory.Delete(root, recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InactivitySafeguardStopsNormallyWhenPromptIsAcceptedAndUsesTranscriptEndTime()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user