Monorepo with centralized build props, npm workspaces, LlamaSharp AI, SQLite/SQLCipher storage, Svelte frontend, and unified smoke tests. Co-Authored-By: Oz <oz-agent@warp.dev>
232 lines
9.5 KiB
C#
232 lines
9.5 KiB
C#
internal static partial class Program
|
|
{
|
|
static Task TestCreateTrimsAsync()
|
|
{
|
|
var service = NewService();
|
|
var created = service.Create(new CreateFragmentDto(" !TRIGGER ", " stomach drop ", [" stress ", "", " body "]));
|
|
|
|
Assert(created.Type == "!TRIGGER", "Type should be trimmed.");
|
|
Assert(created.Description == "stomach drop", "Description should be trimmed.");
|
|
Assert(created.Tags.Count == 2, "Expected two normalized tags.");
|
|
Assert(created.Tags[0] == "stress" && created.Tags[1] == "body", "Tags should be trimmed and preserved.");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task TestUpdateAcceptsTypeAsync()
|
|
{
|
|
var service = NewService();
|
|
var created = service.Create(new CreateFragmentDto("!TRIGGER", "one"));
|
|
var ok = service.Update(created.Id, new UpdateFragmentDto(Type: " !FLASHBACK ", Description: " two ", Tags: [" memory "]));
|
|
|
|
Assert(ok, "Expected update to succeed.");
|
|
var updated = service.GetById(created.Id);
|
|
Assert(updated is not null, "Updated fragment should exist.");
|
|
Assert(updated!.Type == "!FLASHBACK", "Updated type should be trimmed and stored.");
|
|
Assert(updated.Description == "two", "Updated description should be trimmed and stored.");
|
|
Assert(updated.Tags.Count == 1 && updated.Tags[0] == "memory", "Updated tags should be normalized.");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task TestUpdateRejectsWhitespaceTypeAsync()
|
|
{
|
|
var service = NewService();
|
|
var created = service.Create(new CreateFragmentDto("!TRIGGER", "desc"));
|
|
|
|
try
|
|
{
|
|
_ = service.Update(created.Id, new UpdateFragmentDto(Type: " "));
|
|
}
|
|
catch (ValidationException)
|
|
{
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
throw new InvalidOperationException("Expected ValidationException for whitespace type update.");
|
|
}
|
|
|
|
static Task TestFileRepositoryPersistsAsync()
|
|
{
|
|
var tempRoot = Path.Combine(Path.GetTempPath(), "journal-smoke", Guid.NewGuid().ToString("N"));
|
|
var dataDir = Path.Combine(tempRoot, "data");
|
|
Directory.CreateDirectory(dataDir);
|
|
const string password = "smoke-test-password";
|
|
|
|
try
|
|
{
|
|
var configService = NewConfigService(tempRoot);
|
|
var dbService = new JournalDatabaseService(configService);
|
|
|
|
// First session: create a fragment
|
|
using var session1 = new DatabaseSessionService(dbService);
|
|
session1.SetPassword(password);
|
|
var repo1 = new SqliteFragmentRepository(session1);
|
|
var service1 = new FragmentService(repo1);
|
|
var created = service1.Create(new CreateFragmentDto("!TRIGGER", "persist me", ["tag1"]));
|
|
|
|
// Second session: verify persistence
|
|
using var session2 = new DatabaseSessionService(dbService);
|
|
session2.SetPassword(password);
|
|
var repo2 = new SqliteFragmentRepository(session2);
|
|
var service2 = new FragmentService(repo2);
|
|
var loaded = service2.GetById(created.Id);
|
|
|
|
Assert(loaded is not null, "Expected fragment to persist across repository instances.");
|
|
Assert(loaded!.Description == "persist me", "Persisted fragment description mismatch.");
|
|
Assert(loaded.Tags.Count == 1 && loaded.Tags[0] == "tag1", "Persisted tags mismatch.");
|
|
}
|
|
finally
|
|
{
|
|
if (Directory.Exists(tempRoot))
|
|
Directory.Delete(tempRoot, recursive: true);
|
|
}
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task TestJournalEntryModelAsync()
|
|
{
|
|
var fragment = new Fragment("!TRIGGER", "test fragment");
|
|
var section = new ParsedSection(
|
|
"Summary",
|
|
content: ["line one", "- [x] completed thing"],
|
|
checkboxes: new Dictionary<string, bool> { ["completed thing"] = true });
|
|
|
|
var entry = new JournalEntry(
|
|
date: "2026-02-22",
|
|
fragments: [fragment],
|
|
rawContent: "raw markdown content",
|
|
sections: new Dictionary<string, ParsedSection> { ["Summary"] = section });
|
|
|
|
Assert(entry.Date == "2026-02-22", "JournalEntry date mismatch.");
|
|
Assert(entry.RawContent == "raw markdown content", "JournalEntry raw content mismatch.");
|
|
Assert(entry.Fragments.Count == 1, "JournalEntry fragment count mismatch.");
|
|
Assert(entry.Sections.Count == 1, "JournalEntry section count mismatch.");
|
|
Assert(entry.GetSection("Summary").Contains("line one"), "JournalEntry section content mismatch.");
|
|
Assert(entry.GetCheckboxState("Summary", "completed thing") is true, "JournalEntry checkbox state mismatch.");
|
|
Assert(entry.GetCheckboxState("Summary", "missing") is null, "JournalEntry checkbox should return null when missing.");
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task TestMergeOverwritesMeaningfulSectionAsync()
|
|
{
|
|
var current = new JournalEntry(
|
|
date: "2026-02-22",
|
|
sections: new Dictionary<string, ParsedSection>
|
|
{
|
|
["Summary"] = new ParsedSection("Summary", ["old content"])
|
|
});
|
|
|
|
var incoming = new JournalEntry(
|
|
date: "2026-02-22",
|
|
sections: new Dictionary<string, ParsedSection>
|
|
{
|
|
["Summary"] = new ParsedSection(
|
|
"Summary",
|
|
[" ", "new content line"],
|
|
new Dictionary<string, bool> { ["new check"] = true }),
|
|
["Reflection"] = new ParsedSection("Reflection", ["reflective note"])
|
|
});
|
|
|
|
current.MergeWith(incoming);
|
|
|
|
Assert(current.GetSection("Summary").Contains("new content line"), "Meaningful section update should overwrite existing section.");
|
|
Assert(!current.GetSection("Summary").Contains("old content"), "Old section content should be replaced.");
|
|
Assert(current.GetCheckboxState("Summary", "new check") is true, "Overwritten section checkbox state should come from incoming section.");
|
|
Assert(current.GetSection("Reflection").Contains("reflective note"), "Meaningful new section should be added.");
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task TestMergeIgnoresWhitespaceOnlySectionAsync()
|
|
{
|
|
var current = new JournalEntry(
|
|
date: "2026-02-22",
|
|
sections: new Dictionary<string, ParsedSection>
|
|
{
|
|
["Summary"] = new ParsedSection("Summary", ["keep existing"])
|
|
});
|
|
|
|
var incoming = new JournalEntry(
|
|
date: "2026-02-22",
|
|
sections: new Dictionary<string, ParsedSection>
|
|
{
|
|
["Summary"] = new ParsedSection("Summary", [" ", "\t", ""])
|
|
});
|
|
|
|
current.MergeWith(incoming);
|
|
|
|
Assert(current.GetSection("Summary").Contains("keep existing"), "Whitespace-only section update should be ignored.");
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task TestMergeAppendsNonDuplicateFragmentsAsync()
|
|
{
|
|
var current = new JournalEntry(
|
|
date: "2026-02-22",
|
|
fragments:
|
|
[
|
|
new Fragment("!TRIGGER", "duplicate description")
|
|
]);
|
|
|
|
var incoming = new JournalEntry(
|
|
date: "2026-02-22",
|
|
fragments:
|
|
[
|
|
new Fragment("!NOTE", "duplicate description"),
|
|
new Fragment("!NOTE", "new description")
|
|
]);
|
|
|
|
current.MergeWith(incoming);
|
|
|
|
Assert(current.Fragments.Count == 2, "Expected only one new fragment to be appended.");
|
|
Assert(current.Fragments.Count(fragment => fragment.Description == "duplicate description") == 1, "Duplicate description should not be appended.");
|
|
Assert(current.Fragments.Any(fragment => fragment.Description == "new description"), "New fragment description should be appended.");
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task TestToMarkdownCanonicalSectionOrderAsync()
|
|
{
|
|
var entry = new JournalEntry(
|
|
date: "2026-02-22",
|
|
sections: new Dictionary<string, ParsedSection>
|
|
{
|
|
["Reflection"] = new ParsedSection("Reflection", ["reflection body"]),
|
|
["Summary"] = new ParsedSection("Summary", ["summary body"])
|
|
});
|
|
|
|
var markdown = entry.ToMarkdown();
|
|
var summaryIdx = markdown.IndexOf("## Summary", StringComparison.Ordinal);
|
|
var reflectionIdx = markdown.IndexOf("## Reflection", StringComparison.Ordinal);
|
|
|
|
Assert(summaryIdx >= 0, "Summary header should be emitted.");
|
|
Assert(reflectionIdx >= 0, "Reflection header should be emitted.");
|
|
Assert(summaryIdx < reflectionIdx, "Sections should be emitted in canonical order.");
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task TestToMarkdownFragmentFormattingAsync()
|
|
{
|
|
var fragment = new Fragment("!TRIGGER", "fragment body")
|
|
{
|
|
Time = default,
|
|
Tags = ["stress", "body"]
|
|
};
|
|
var entry = new JournalEntry(
|
|
date: "2026-02-22",
|
|
fragments: [fragment]);
|
|
|
|
var markdown = entry.ToMarkdown();
|
|
|
|
Assert(markdown.Contains("# Fragments\n", StringComparison.Ordinal), "Fragments header should be present.");
|
|
Assert(markdown.Contains("!TRIGGER #stress #body\nfragment body\n", StringComparison.Ordinal), "Fragment block format should match parity shape.");
|
|
Assert(markdown.Contains("**Date:** 2026-02-22", StringComparison.Ordinal), "Date frontmatter line should be present.");
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|
|
|