Files
codex b22754ce5d
PR and Push Build/Test / build-and-test (push) Successful in 8m49s
Normalize scalar frontmatter lists
2026-06-11 16:00:05 +02:00

196 lines
8.5 KiB
C#

using MeetingAssistant.MeetingNotes;
using MeetingAssistant.Summary;
namespace MeetingAssistant.Tests;
public sealed class ProjectKnowledgeToolTests
{
[Fact]
public async Task ToolsOperateOnProjectsBoundInMeetingFrontmatter()
{
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
var projectsRoot = Path.Combine(root, "Projects");
var meetingAssistantRoot = Path.Combine(projectsRoot, "MeetingAssistant");
var ignoredRoot = Path.Combine(projectsRoot, "IgnoredProject");
Directory.CreateDirectory(Path.Combine(meetingAssistantRoot, "notes"));
Directory.CreateDirectory(ignoredRoot);
await File.WriteAllTextAsync(
Path.Combine(meetingAssistantRoot, "README.md"),
"First line\nSecond alpha line\nThird beta line");
await File.WriteAllTextAsync(Path.Combine(meetingAssistantRoot, "notes", "context.md"), "Project context");
await File.WriteAllTextAsync(Path.Combine(ignoredRoot, "ignored.md"), "alpha should not be searched");
var artifacts = CreateArtifacts(root);
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.MeetingNotePath)!);
await File.WriteAllTextAsync(
artifacts.MeetingNotePath,
"""
---
title: Project Meeting
attendees: []
projects:
- MeetingAssistant
- MissingProject
transcript: ""
assistant_context: ""
summary: ""
---
User notes.
""");
var tools = new MeetingSummaryTools(
artifacts,
new MeetingAssistantOptions
{
Vault =
{
ProjectsFolder = projectsRoot
}
});
Assert.Equal("MeetingAssistant", await tools.ListProjects());
Assert.Equal("README.md\nnotes/context.md", await tools.ListProjectFiles("MeetingAssistant"));
Assert.Equal("Second alpha line\nThird beta line", await tools.ReadProjectFile("MeetingAssistant", "README.md", 2, 99));
var writeResult = await tools.WriteProjectFile("MeetingAssistant", "notes/summary.md", "# Project Update");
Assert.Equal("MeetingAssistant/notes/summary.md", writeResult);
Assert.Equal("# Project Update", await File.ReadAllTextAsync(Path.Combine(meetingAssistantRoot, "notes", "summary.md")));
Assert.Equal("IgnoredProject/ignored.md", await tools.WriteProjectFile("IgnoredProject", "ignored.md", "changed"));
Assert.Equal("alpha should not be searched\nchanged", await File.ReadAllTextAsync(Path.Combine(ignoredRoot, "ignored.md")));
Assert.Equal(
"README.md:2 Second alpha line",
await tools.Search("alpha"));
}
[Fact]
public async Task ListProjectsAcceptsScalarProjectFrontmatter()
{
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
var projectsRoot = Path.Combine(root, "Projects");
Directory.CreateDirectory(Path.Combine(projectsRoot, "MeetingAssistant"));
var artifacts = CreateArtifacts(root);
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.MeetingNotePath)!);
await File.WriteAllTextAsync(
artifacts.MeetingNotePath,
"""
---
projects: MeetingAssistant
---
""");
var tools = new MeetingSummaryTools(
artifacts,
new MeetingAssistantOptions
{
Vault =
{
ProjectsFolder = projectsRoot
}
});
Assert.Equal("MeetingAssistant", await tools.ListProjects());
}
[Fact]
public async Task WriteProjectFileSupportsAppendReplaceInsertOverwriteAndCreate()
{
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
var projectsRoot = Path.Combine(root, "Projects");
var projectRoot = Path.Combine(projectsRoot, "MeetingAssistant");
Directory.CreateDirectory(projectRoot);
var projectFile = Path.Combine(projectRoot, "notes.md");
await File.WriteAllTextAsync(projectFile, "one\ntwo\nthree\nfour");
var tools = new MeetingSummaryTools(
CreateArtifacts(root),
new MeetingAssistantOptions
{
Vault =
{
ProjectsFolder = projectsRoot
}
});
await tools.WriteProjectFile("MeetingAssistant", "notes.md", "TWO\nTHREE", from: 2, to: 3);
Assert.Equal("one\nTWO\nTHREE\nfour", await File.ReadAllTextAsync(projectFile));
await tools.WriteProjectFile("MeetingAssistant", "notes.md", "inserted", insert: 2);
Assert.Equal("one\ninserted\nTWO\nTHREE\nfour", await File.ReadAllTextAsync(projectFile));
await tools.WriteProjectFile("MeetingAssistant", "notes.md", "appended");
Assert.Equal("one\ninserted\nTWO\nTHREE\nfour\nappended", await File.ReadAllTextAsync(projectFile));
await tools.WriteProjectFile("MeetingAssistant", "notes.md", "replacement", replace_file: true);
Assert.Equal("replacement", await File.ReadAllTextAsync(projectFile));
await tools.WriteProjectFile("MeetingAssistant", "created/new.md", "created content");
Assert.Equal("created content", await File.ReadAllTextAsync(Path.Combine(projectRoot, "created", "new.md")));
}
[Fact]
public async Task WriteProjectFileRefusesMissingProjectsEscapingPathsAndAmbiguousLineArguments()
{
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
var projectsRoot = Path.Combine(root, "Projects");
Directory.CreateDirectory(Path.Combine(projectsRoot, "MeetingAssistant"));
var tools = new MeetingSummaryTools(
CreateArtifacts(root),
new MeetingAssistantOptions
{
Vault =
{
ProjectsFolder = projectsRoot
}
});
Assert.StartsWith("Refused:", await tools.WriteProjectFile("MissingProject", "notes.md", "content"));
Assert.StartsWith("Refused:", await tools.WriteProjectFile("MeetingAssistant", "../outside.md", "content"));
Assert.StartsWith("Refused:", await tools.WriteProjectFile("MeetingAssistant", "notes.md", "content", from: 1));
Assert.StartsWith("Refused:", await tools.WriteProjectFile("MeetingAssistant", "notes.md", "content", from: 1, to: 1, insert: 1));
Assert.StartsWith("Refused:", await tools.WriteProjectFile("MeetingAssistant", "notes.md", "content", from: 1, to: 1, replace_file: true));
}
[Fact]
public async Task SearchCanBeLimitedByProjectFilePattern()
{
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
var projectsRoot = Path.Combine(root, "Projects");
var projectRoot = Path.Combine(projectsRoot, "MeetingAssistant");
Directory.CreateDirectory(projectRoot);
await File.WriteAllTextAsync(Path.Combine(projectRoot, "PROJECT.md"), "durable needle");
await File.WriteAllTextAsync(Path.Combine(projectRoot, "scratch.txt"), "scratch needle");
var artifacts = CreateArtifacts(root);
Directory.CreateDirectory(Path.GetDirectoryName(artifacts.MeetingNotePath)!);
await File.WriteAllTextAsync(
artifacts.MeetingNotePath,
"""
---
projects:
- MeetingAssistant
---
""");
var tools = new MeetingSummaryTools(
artifacts,
new MeetingAssistantOptions
{
Vault =
{
ProjectsFolder = projectsRoot
}
});
var search = await tools.Search("needle", file_pattern: "*.md");
Assert.Contains("PROJECT.md:1 durable needle", search);
Assert.DoesNotContain("scratch.txt", search);
}
private static MeetingSessionArtifacts CreateArtifacts(string root)
{
return 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"));
}
}