Public Access
Generalize settings and logs assistant
PR and Push Build/Test / build-and-test (push) Successful in 16m43s
PR and Push Build/Test / build-and-test (push) Successful in 16m43s
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
using MeetingAssistant.Workflow;
|
||||
using MeetingAssistant.Logging;
|
||||
using MeetingAssistant.Speakers;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
using System.Text.Json;
|
||||
|
||||
@@ -82,6 +84,239 @@ public sealed class WorkflowRulesEditorTests
|
||||
Assert.DoesNotContain("other.yaml", searchResult);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SettingsToolsReadWriteAndValidateConfiguredAppsettingsFile()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(root);
|
||||
var configPath = Path.Combine(root, "appsettings.json");
|
||||
await File.WriteAllTextAsync(
|
||||
configPath,
|
||||
"""
|
||||
{
|
||||
"MeetingAssistant": {
|
||||
"Agent": {
|
||||
"Model": "old-model"
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
var tools = new WorkflowRulesEditorTools(
|
||||
new MeetingAssistantOptions(),
|
||||
configPath: configPath);
|
||||
|
||||
var readResult = await tools.ReadConfig(from: 3, to: 5);
|
||||
var writeResult = await tools.WriteConfig("""
|
||||
{
|
||||
"MeetingAssistant": {
|
||||
"Agent": {
|
||||
"Model": "new-model"
|
||||
}
|
||||
}
|
||||
}
|
||||
""");
|
||||
var invalidResult = await tools.WriteConfig("{ invalid json");
|
||||
|
||||
Assert.Contains("\"Agent\"", readResult);
|
||||
Assert.Equal(configPath, writeResult);
|
||||
Assert.StartsWith("Refused: appsettings JSON is invalid.", invalidResult);
|
||||
Assert.Contains("new-model", await File.ReadAllTextAsync(configPath));
|
||||
Assert.DoesNotContain("invalid json", await File.ReadAllTextAsync(configPath));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SettingsToolsReadConfigurationDocumentation()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(root);
|
||||
var docsPath = Path.Combine(root, "meeting-assistant-configuration.md");
|
||||
await File.WriteAllTextAsync(docsPath, "# Config Docs\n\nAgent model settings.");
|
||||
var tools = new WorkflowRulesEditorTools(
|
||||
new MeetingAssistantOptions(),
|
||||
configDocsPath: docsPath);
|
||||
|
||||
var docs = await tools.ReadConfigDocs();
|
||||
|
||||
Assert.Contains("Agent model settings", docs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SettingsToolsReadAndSearchApplicationLogs()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(root);
|
||||
await File.WriteAllTextAsync(
|
||||
Path.Combine(root, MeetingAssistantLogFiles.CurrentLogFileName),
|
||||
"first line\nsecond needle\nthird line\n");
|
||||
await File.WriteAllTextAsync(
|
||||
Path.Combine(root, MeetingAssistantLogFiles.GetRotatedLogFileName(1)),
|
||||
"rotated needle\n");
|
||||
var tools = new WorkflowRulesEditorTools(
|
||||
new MeetingAssistantOptions(),
|
||||
logDirectory: root);
|
||||
|
||||
var tail = await tools.ReadLogs(tail: 2);
|
||||
var range = await tools.ReadLogs(from: 2, to: 2);
|
||||
var search = await tools.SearchLogs("needle");
|
||||
|
||||
Assert.DoesNotContain("first line", tail);
|
||||
Assert.Contains("second needle", tail);
|
||||
Assert.Equal("second needle", range.Trim());
|
||||
Assert.Contains("meeting-assistant.log:2 second needle", search);
|
||||
Assert.Contains("meeting-assistant.log.1:1 rotated needle", search);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SettingsToolsReadAndSearchOnlyCopiedSpecFiles()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||
var sessionSpec = Path.Combine(root, "meeting-session", "spec.md");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(sessionSpec)!);
|
||||
await File.WriteAllTextAsync(
|
||||
sessionSpec,
|
||||
"""
|
||||
# meeting-session Specification
|
||||
|
||||
Meeting Assistant creates notes.
|
||||
""");
|
||||
await File.WriteAllTextAsync(
|
||||
Path.Combine(root, "root-spec.md"),
|
||||
"Root spec mentions Assistant.");
|
||||
var outside = Path.Combine(Path.GetDirectoryName(root)!, "outside.md");
|
||||
await File.WriteAllTextAsync(outside, "outside Assistant");
|
||||
var tools = new WorkflowRulesEditorTools(
|
||||
new MeetingAssistantOptions(),
|
||||
specRootPath: root);
|
||||
|
||||
var read = await tools.ReadSpecFile("meeting-session/spec.md", from: 3, to: 3);
|
||||
var escaped = await tools.ReadSpecFile("../outside.md");
|
||||
var search = await tools.SearchSpec("Assistant");
|
||||
|
||||
Assert.Equal("Meeting Assistant creates notes.", read.Trim());
|
||||
Assert.StartsWith("Refused: spec path must stay inside", escaped);
|
||||
Assert.Contains("meeting-session/spec.md:3 Meeting Assistant creates notes.", search);
|
||||
Assert.Contains("root-spec.md:1 Root spec mentions Assistant.", search);
|
||||
Assert.DoesNotContain("outside.md", search);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SettingsToolsReadTailAndSearchSpecsWithFilePattern()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||
var sessionSpec = Path.Combine(root, "meeting-session", "spec.md");
|
||||
var otherSpec = Path.Combine(root, "other", "notes.md");
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(sessionSpec)!);
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(otherSpec)!);
|
||||
await File.WriteAllTextAsync(sessionSpec, "one\nsession needle\nthree\nfour");
|
||||
await File.WriteAllTextAsync(otherSpec, "other needle");
|
||||
var tools = new WorkflowRulesEditorTools(
|
||||
new MeetingAssistantOptions(),
|
||||
specRootPath: root);
|
||||
|
||||
var tail = await tools.ReadSpecFile("meeting-session/spec.md", tail: 2);
|
||||
var search = await tools.SearchSpec("needle", file_pattern: "meeting-session/*.md");
|
||||
|
||||
Assert.Equal("three\nfour", tail);
|
||||
Assert.Contains("meeting-session/spec.md:2 session needle", search);
|
||||
Assert.DoesNotContain("other/notes.md", search);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SettingsToolsCreateProjectWithRecommendedSeedOrDirectAgentsFile()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||
var projectsRoot = Path.Combine(root, "Projects");
|
||||
var agentsTemplatePath = Path.Combine(root, "Project-AGENTS.md");
|
||||
Directory.CreateDirectory(root);
|
||||
await File.WriteAllTextAsync(agentsTemplatePath, "Recommended project instructions.");
|
||||
var tools = new WorkflowRulesEditorTools(
|
||||
new MeetingAssistantOptions
|
||||
{
|
||||
Vault =
|
||||
{
|
||||
ProjectsFolder = projectsRoot
|
||||
}
|
||||
},
|
||||
projectAgentsTemplatePath: agentsTemplatePath);
|
||||
|
||||
var recommended = await tools.CreateProject("Alpha Project");
|
||||
var direct = await tools.CreateProject(
|
||||
"Beta Project",
|
||||
seed: "agents_md",
|
||||
agents_md: "Direct project instructions.");
|
||||
|
||||
var alphaRoot = Path.Combine(projectsRoot, "Alpha Project");
|
||||
var betaRoot = Path.Combine(projectsRoot, "Beta Project");
|
||||
Assert.Equal("Alpha Project", recommended);
|
||||
Assert.Contains("Alpha Project", await tools.ListProjects());
|
||||
Assert.Equal("Recommended project instructions.", await File.ReadAllTextAsync(Path.Combine(alphaRoot, "AGENTS.md")));
|
||||
Assert.Contains("# Executive Summary", await File.ReadAllTextAsync(Path.Combine(alphaRoot, "PROJECT.md")));
|
||||
Assert.Contains("project folder was created", await File.ReadAllTextAsync(Path.Combine(alphaRoot, "JOURNAL.md")));
|
||||
Assert.Contains("Important decisions", await File.ReadAllTextAsync(Path.Combine(alphaRoot, "DECISIONS.md")));
|
||||
Assert.Equal("Direct project instructions.", await File.ReadAllTextAsync(Path.Combine(betaRoot, "AGENTS.md")));
|
||||
Assert.False(File.Exists(Path.Combine(betaRoot, "PROJECT.md")));
|
||||
Assert.StartsWith("Refused:", await tools.CreateProject("../Escape"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task SettingsToolsReadWriteAndSearchExistingProjectFiles()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||
var projectsRoot = Path.Combine(root, "Projects");
|
||||
var alphaRoot = Path.Combine(projectsRoot, "Alpha");
|
||||
var betaRoot = Path.Combine(projectsRoot, "Beta");
|
||||
Directory.CreateDirectory(alphaRoot);
|
||||
Directory.CreateDirectory(betaRoot);
|
||||
await File.WriteAllTextAsync(Path.Combine(alphaRoot, "PROJECT.md"), "alpha one\nalpha needle\nalpha three");
|
||||
await File.WriteAllTextAsync(Path.Combine(alphaRoot, "notes.txt"), "alpha needle hidden from md search");
|
||||
await File.WriteAllTextAsync(Path.Combine(betaRoot, "PROJECT.md"), "beta needle");
|
||||
var tools = new WorkflowRulesEditorTools(
|
||||
new MeetingAssistantOptions
|
||||
{
|
||||
Vault =
|
||||
{
|
||||
ProjectsFolder = projectsRoot
|
||||
}
|
||||
});
|
||||
|
||||
var files = await tools.ListProjectFiles("Alpha");
|
||||
var tail = await tools.ReadProjectFile("Alpha", "PROJECT.md", tail: 2);
|
||||
var write = await tools.WriteProjectFile("Alpha", "JOURNAL.md", "created");
|
||||
var search = await tools.SearchProjects("needle", file_pattern: "*.md");
|
||||
|
||||
Assert.Equal("notes.txt\nPROJECT.md", files);
|
||||
Assert.Equal("alpha needle\nalpha three", tail);
|
||||
Assert.Equal("Alpha/JOURNAL.md", write);
|
||||
Assert.Equal("created", await File.ReadAllTextAsync(Path.Combine(alphaRoot, "JOURNAL.md")));
|
||||
Assert.Contains("Alpha/PROJECT.md:2 alpha needle", search);
|
||||
Assert.Contains("Beta/PROJECT.md:1 beta needle", search);
|
||||
Assert.DoesNotContain("notes.txt", search);
|
||||
Assert.StartsWith("Refused:", await tools.ReadProjectFile("Missing", "PROJECT.md"));
|
||||
Assert.StartsWith("Refused:", await tools.WriteProjectFile("Alpha", "../escape.md", "bad"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FileLoggerRotatesCurrentLogAndKeepsFourOlderFiles()
|
||||
{
|
||||
var root = Path.Combine(Path.GetTempPath(), "meeting-assistant-tests", Guid.NewGuid().ToString("N"));
|
||||
Directory.CreateDirectory(root);
|
||||
File.WriteAllText(Path.Combine(root, MeetingAssistantLogFiles.CurrentLogFileName), "current");
|
||||
File.WriteAllText(Path.Combine(root, MeetingAssistantLogFiles.GetRotatedLogFileName(1)), "old1");
|
||||
File.WriteAllText(Path.Combine(root, MeetingAssistantLogFiles.GetRotatedLogFileName(2)), "old2");
|
||||
File.WriteAllText(Path.Combine(root, MeetingAssistantLogFiles.GetRotatedLogFileName(3)), "old3");
|
||||
File.WriteAllText(Path.Combine(root, MeetingAssistantLogFiles.GetRotatedLogFileName(4)), "old4");
|
||||
|
||||
using (var provider = new MeetingAssistantFileLoggerProvider(root))
|
||||
{
|
||||
provider.CreateLogger("Test").LogInformation("fresh");
|
||||
}
|
||||
|
||||
Assert.Contains("fresh", File.ReadAllText(Path.Combine(root, MeetingAssistantLogFiles.CurrentLogFileName)));
|
||||
Assert.Equal("current", File.ReadAllText(Path.Combine(root, MeetingAssistantLogFiles.GetRotatedLogFileName(1))));
|
||||
Assert.Equal("old3", File.ReadAllText(Path.Combine(root, MeetingAssistantLogFiles.GetRotatedLogFileName(4))));
|
||||
Assert.False(File.Exists(Path.Combine(root, "meeting-assistant.log.5")));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RulesEditorToolsAppendByDefaultAndReplaceOnlyWhenRequested()
|
||||
{
|
||||
@@ -157,6 +392,9 @@ public sealed class WorkflowRulesEditorTests
|
||||
Assert.Contains("Meeting Workflow Engine", instructions);
|
||||
Assert.Contains("Workflow rules reference documentation", instructions);
|
||||
Assert.Contains("speaker identity database", instructions);
|
||||
Assert.Contains("read_config", instructions);
|
||||
Assert.Contains("read_logs", instructions);
|
||||
Assert.Contains("read_spec_file", instructions);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user