journal/Journal.SmokeTests/Program.FragmentTests.cs
Jacob Schmidt 0d77300c22 feat: Project Journal backend monorepo
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>
2026-03-02 20:56:26 -06:00

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;
}
}