internal static partial class Program { static async Task TestEntryUnknownActionAsync() { var entry = NewEntry(); var response = await entry.HandleCommandAsync("""{"action":"unknown.action"}"""); using var doc = JsonDocument.Parse(response); Assert(!doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=false."); Assert(doc.RootElement.GetProperty("error").GetString()!.Contains("Unknown action"), "Expected unknown action error."); } static async Task TestEntryInvalidJsonAsync() { var entry = NewEntry(); var response = await entry.HandleCommandAsync("{\"action\":\"fragments.list\""); using var doc = JsonDocument.Parse(response); Assert(!doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=false."); Assert(doc.RootElement.GetProperty("error").GetString()!.Contains("Invalid command JSON"), "Expected invalid JSON error."); } static async Task TestEntryGetMissingReturnsNullDataAsync() { var entry = NewEntry(); var request = JsonSerializer.Serialize(new { action = "fragments.get", id = Guid.NewGuid().ToString(), }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true."); Assert(doc.RootElement.GetProperty("data").ValueKind == JsonValueKind.Null, "Expected data=null for missing fragment."); } static async Task TestEntryCreateMissingPayloadAsync() { var entry = NewEntry(); var response = await entry.HandleCommandAsync("""{"action":"fragments.create"}"""); using var doc = JsonDocument.Parse(response); Assert(!doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=false."); Assert(doc.RootElement.GetProperty("error").GetString()!.Contains("payload", StringComparison.OrdinalIgnoreCase), "Expected payload validation error."); } static async Task TestEntryEntriesSaveMergeAsync() { var entry = NewEntry(); var firstPath = await SaveEntryForTestAsync(entry, "2026-02-22", """ Date: 2026-02-22 ## Summary old summary text """); var mergeRequest = JsonSerializer.Serialize(new { action = "entries.save", payload = new { filePath = firstPath, mode = "Daily", content = """ Date: 2026-02-22 ## Summary new summary text ## Reflection new reflection text """ } }); var mergeResponse = await entry.HandleCommandAsync(mergeRequest); using var mergeDoc = JsonDocument.Parse(mergeResponse); Assert(mergeDoc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for entries.save daily merge."); var loadedAfterMerge = await LoadEntryForTestAsync(entry, firstPath); Assert(loadedAfterMerge.Contains("new summary text", StringComparison.Ordinal), "Expected merged entry to contain new summary text."); Assert(!loadedAfterMerge.Contains("old summary text", StringComparison.Ordinal), "Expected merged entry to replace old summary section."); Assert(loadedAfterMerge.Contains("new reflection text", StringComparison.Ordinal), "Expected merged entry to contain new reflection section."); var fragmentSaveRequest = JsonSerializer.Serialize(new { action = "entries.save", payload = new { filePath = firstPath, mode = "Fragment", content = "!NOTE\nfragment append text" } }); var fragmentResponse = await entry.HandleCommandAsync(fragmentSaveRequest); using var fragmentDoc = JsonDocument.Parse(fragmentResponse); Assert(fragmentDoc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for entries.save fragment mode."); var loadedAfterFragment = await LoadEntryForTestAsync(entry, firstPath); Assert(loadedAfterFragment.Contains("fragment append text", StringComparison.Ordinal), "Expected fragment append text in saved entry."); } static async Task TestEntryEntriesLoadAsync() { var entry = NewEntry(); var content = """ Date: 2026-02-22 ## Summary hello world """; var filePath = await SaveEntryForTestAsync(entry, "2026-02-22", content); var request = JsonSerializer.Serialize(new { action = "entries.load", payload = new { filePath } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for entries.load."); var data = doc.RootElement.GetProperty("data"); var entryDto = data.GetProperty("Entry"); Assert(entryDto.GetProperty("Date").GetString() == "2026-02-22", "Expected parsed date from entries.load."); Assert(entryDto.GetProperty("RawContent").GetString() == content, "Expected raw content from entries.load."); Assert(data.GetProperty("FileName").GetString() == "2026-02-22.md", "Expected file name from entries.load."); } static async Task TestEntryEntriesListAsync() { var entry = NewEntry(); await SaveEntryForTestAsync(entry, "2026-02-03", "c"); await SaveEntryForTestAsync(entry, "2026-02-01", "a"); var request = JsonSerializer.Serialize(new { action = "entries.list", payload = new { } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for entries.list."); var data = doc.RootElement.GetProperty("data"); Assert(data.ValueKind == JsonValueKind.Array, "Expected entries.list data array."); Assert(data.GetArrayLength() == 2, "Expected entries.list to return seeded markdown entries."); Assert(data[0].GetProperty("FileName").GetString() == "2026-02-01.md", "Expected entries.list sort order by file name."); Assert(data[1].GetProperty("FileName").GetString() == "2026-02-03.md", "Expected entries.list sort order by file name."); } static async Task TestEntryTemplatesCrudExcludesFromEntriesListAsync() { var entry = NewEntry(); await SaveEntryForTestAsync(entry, "2026-02-03", "daily entry"); var saveRequest = JsonSerializer.Serialize(new { action = "templates.save", payload = new { name = "Weekly Review", content = "# Weekly Review\n\n## Wins\n- one" } }); var saveResponse = await entry.HandleCommandAsync(saveRequest); using var saveDoc = JsonDocument.Parse(saveResponse); Assert(saveDoc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for templates.save."); var templatePath = saveDoc.RootElement.GetProperty("data").GetProperty("FilePath").GetString() ?? ""; Assert(templatePath.EndsWith("Weekly%20Review.template.md", StringComparison.OrdinalIgnoreCase), "Template path should be canonical db://template path."); var listTemplatesRequest = JsonSerializer.Serialize(new { action = "templates.list", payload = new { } }); var listTemplatesResponse = await entry.HandleCommandAsync(listTemplatesRequest); using var listTemplatesDoc = JsonDocument.Parse(listTemplatesResponse); Assert(listTemplatesDoc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for templates.list."); var templateItems = listTemplatesDoc.RootElement.GetProperty("data"); Assert(templateItems.GetArrayLength() == 1, "Expected one template in templates.list."); Assert(templateItems[0].GetProperty("FileName").GetString() == "Weekly Review.template.md", "Template file name mismatch."); var listEntriesRequest = JsonSerializer.Serialize(new { action = "entries.list", payload = new { } }); var listEntriesResponse = await entry.HandleCommandAsync(listEntriesRequest); using var listEntriesDoc = JsonDocument.Parse(listEntriesResponse); Assert(listEntriesDoc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for entries.list."); var entryItems = listEntriesDoc.RootElement.GetProperty("data"); Assert(entryItems.GetArrayLength() == 1, "Expected entries.list to exclude template files."); Assert(entryItems[0].GetProperty("FileName").GetString() == "2026-02-03.md", "Expected only daily entry file in entries.list."); var loadTemplateRequest = JsonSerializer.Serialize(new { action = "templates.load", payload = new { filePath = templatePath } }); var loadTemplateResponse = await entry.HandleCommandAsync(loadTemplateRequest); using var loadTemplateDoc = JsonDocument.Parse(loadTemplateResponse); Assert(loadTemplateDoc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for templates.load."); var content = loadTemplateDoc.RootElement.GetProperty("data").GetProperty("Content").GetString() ?? ""; Assert(content.Contains("## Wins", StringComparison.Ordinal), "Expected template content in templates.load result."); var deleteTemplateRequest = JsonSerializer.Serialize(new { action = "templates.delete", payload = new { filePath = templatePath } }); var deleteTemplateResponse = await entry.HandleCommandAsync(deleteTemplateRequest); using var deleteTemplateDoc = JsonDocument.Parse(deleteTemplateResponse); Assert(deleteTemplateDoc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for templates.delete."); Assert(deleteTemplateDoc.RootElement.GetProperty("data").GetBoolean(), "Expected templates.delete to return true."); } static async Task TestEntrySearchEntriesMatchesRawContentAsync() { var entry = NewEntry(); await SeedSearchFixtureEntriesAsync(entry); var request = JsonSerializer.Serialize(new { action = "search.entries", payload = new { query = "common token", } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for search.entries."); var data = doc.RootElement.GetProperty("data"); Assert(data.ValueKind == JsonValueKind.Array, "Expected data array from search.entries."); Assert(data.GetArrayLength() == 2, "Expected two entries matching query across raw content."); } static async Task TestEntrySearchEntriesWithoutQueryReturnsAllAsync() { var entry = NewEntry(); await SeedSearchFixtureEntriesAsync(entry); var request = JsonSerializer.Serialize(new { action = "search.entries", payload = new { } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for search.entries without query."); var data = doc.RootElement.GetProperty("data"); Assert(data.ValueKind == JsonValueKind.Array, "Expected data array from search.entries."); Assert(data.GetArrayLength() == 3, "Expected all seeded markdown entries to be returned when query is omitted."); } static async Task TestEntrySearchEntriesDateRangeFilterAsync() { var entry = NewEntry(); await SeedSearchFixtureEntriesAsync(entry); var request = JsonSerializer.Serialize(new { action = "search.entries", payload = new { startDate = "2026-02-02", endDate = "2026-02-28", } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for date-range filtered search."); var data = doc.RootElement.GetProperty("data"); Assert(data.GetArrayLength() == 1, "Expected one result for filtered date range."); Assert(data[0].GetProperty("FileName").GetString() == "2026-02-05.md", "Date-range result mismatch."); } static async Task TestEntrySearchEntriesSectionFilterAsync() { var entry = NewEntry(); await SeedSearchFixtureEntriesAsync(entry); var request = JsonSerializer.Serialize(new { action = "search.entries", payload = new { query = "focus area", section = "Reflection", } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for section-scoped search."); var data = doc.RootElement.GetProperty("data"); Assert(data.GetArrayLength() == 1, "Expected one section-scoped result."); Assert(data[0].GetProperty("FileName").GetString() == "2026-02-01.md", "Section filter result mismatch."); } static async Task TestEntrySearchEntriesTagTypeFilterAsync() { var entry = NewEntry(); await SeedSearchFixtureEntriesAsync(entry); var request = JsonSerializer.Serialize(new { action = "search.entries", payload = new { tags = new[] { "stress" }, types = new[] { "!TRIGGER" }, } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for fragment tag/type filtered search."); var data = doc.RootElement.GetProperty("data"); Assert(data.GetArrayLength() == 1, "Expected one result for fragment tag/type filters."); Assert(data[0].GetProperty("FileName").GetString() == "2026-02-01.md", "Tag/type filter result mismatch."); } static async Task TestEntrySearchEntriesCheckboxFilterAsync() { var entry = NewEntry(); await SeedSearchFixtureEntriesAsync(entry); var request = JsonSerializer.Serialize(new { action = "search.entries", payload = new { @checked = new[] { "med taken" }, @unchecked = new[] { "drink water" }, } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for checkbox filtered search."); var data = doc.RootElement.GetProperty("data"); Assert(data.GetArrayLength() == 2, "Expected OR-style checkbox match across checked/unchecked filters."); } static async Task TestEntrySearchEntriesRejectsInvalidDateAsync() { var entry = NewEntry(); await SeedSearchFixtureEntriesAsync(entry); var request = JsonSerializer.Serialize(new { action = "search.entries", payload = new { startDate = "2026/02/01", } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(!doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=false for invalid date format."); var error = doc.RootElement.GetProperty("error").GetString() ?? ""; Assert(error.Contains("invalid startdate value", StringComparison.OrdinalIgnoreCase), "Expected invalid startDate error."); } static Task TestSidecarSearchCliFilteredAsync() { var root = Path.Combine(Path.GetTempPath(), "journal-sidecar-cli-smoke", Guid.NewGuid().ToString("N")); var config = NewConfigService(root); var dbService = new JournalDatabaseService(config); using var session = new DatabaseSessionService(dbService); session.SetPassword("vault-pass-123"); var repo = new SqliteEntryFileRepository(session); var entryFiles = new EntryFileService(repo); var searchService = new EntrySearchService(repo); var cli = new SidecarCli(new VaultStorageService(new VaultCryptoService(), dbService), searchService, config, entryFiles); try { entryFiles.SaveEntry(new EntrySavePayload(""" Date: 2026-02-01 ## Summary common - [x] med taken !TRIGGER #stress matched body """, Mode: "Overwrite", FileName: "2026-02-01")); entryFiles.SaveEntry(new EntrySavePayload(""" Date: 2026-02-05 ## Summary common - [ ] drink water !NOTE #daily non match """, Mode: "Overwrite", FileName: "2026-02-05")); var (exitCode, stdout, stderr) = CaptureConsole(() => cli.RunSearchCommand( [ "common", "--start-date", "2026-02-01", "--end-date", "2026-02-28", "--tag", "stress", "--type", "!TRIGGER", "--checked", "med taken", "--section", "Summary" ])); Assert(exitCode == 0, "Expected search CLI command to succeed."); Assert(string.IsNullOrWhiteSpace(stderr), "Expected no stderr output for successful search CLI command."); Assert(stdout.Contains("--- 2026-02-01 ---", StringComparison.Ordinal), "Expected matching entry header in search CLI output."); Assert(!stdout.Contains("--- 2026-02-05 ---", StringComparison.Ordinal), "Unexpected non-matching entry in filtered search CLI output."); } finally { session.Dispose(); if (Directory.Exists(root)) Directory.Delete(root, recursive: true); } return Task.CompletedTask; } static Task TestSidecarSearchCliEmptyDataAsync() { var root = Path.Combine(Path.GetTempPath(), "journal-sidecar-cli-smoke", Guid.NewGuid().ToString("N")); var config = NewConfigService(root); var dbService = new JournalDatabaseService(config); using var session = new DatabaseSessionService(dbService); session.SetPassword("vault-pass-123"); var repo = new SqliteEntryFileRepository(session); var entryFiles = new EntryFileService(repo); var searchService = new EntrySearchService(repo); var cli = new SidecarCli(new VaultStorageService(new VaultCryptoService(), dbService), searchService, config, entryFiles); try { var (exitCode, stdout, _) = CaptureConsole(() => cli.RunSearchCommand([])); Assert(exitCode == 0, "Expected search CLI command to return success for empty data store."); Assert(stdout.Contains("No journal entries found", StringComparison.OrdinalIgnoreCase), "Expected empty-data guidance message."); } finally { session.Dispose(); if (Directory.Exists(root)) Directory.Delete(root, recursive: true); } return Task.CompletedTask; } static Task TestEntrySavePayloadFileNameDeserializationAsync() { var json = """{"content":"hello","mode":"Overwrite","fileName":"My Custom Name"}"""; var options = new JsonSerializerOptions { PropertyNameCaseInsensitive = true }; var element = JsonSerializer.Deserialize(json); var payload = element.Deserialize(options); Assert(payload is not null, "Payload should not be null."); Assert(payload!.Content == "hello", "Content should be deserialized."); Assert(payload.Mode == "Overwrite", "Mode should be deserialized."); Assert(payload.FileName == "My Custom Name", "FileName should be deserialized from camelCase JSON via JsonElement."); Assert(payload.FilePath is null, "FilePath should be null when not provided."); return Task.CompletedTask; } static async Task TestEntrySaveWithFileNameAsync() { var root = Path.Combine(Path.GetTempPath(), "journal-entry-smoke", Guid.NewGuid().ToString("N")); var config = NewConfigService(root); var dbService = new JournalDatabaseService(config); using var session = new DatabaseSessionService(dbService); session.SetPassword("vault-pass-123"); var repo = new SqliteEntryFileRepository(session); var service = new EntryFileService(repo); try { var payload = new EntrySavePayload( Content: "# Custom Entry\n\nHello world", FilePath: null, Mode: "Overwrite", FileName: "My Custom Name"); var result = service.SaveEntry(payload); Assert(result.FilePath.StartsWith("db://entry/", StringComparison.OrdinalIgnoreCase), "Expected canonical db://entry path."); Assert(result.FilePath.Contains("My%20Custom%20Name.md", StringComparison.OrdinalIgnoreCase), "Expected escaped custom name in db path."); var loaded = service.LoadEntry(result.FilePath); Assert(loaded.Entry.RawContent.Contains("Hello world", StringComparison.Ordinal), "Stored content should match."); var entry = NewEntry(); var request = JsonSerializer.Serialize(new { action = "entries.save", payload = new { content = "# Second Entry", mode = "Overwrite", fileName = "Another Custom Name" } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for entries.save with fileName."); var savedFilePath = doc.RootElement.GetProperty("data").GetProperty("FilePath").GetString() ?? ""; Assert(savedFilePath.Contains("Another%20Custom%20Name.md", StringComparison.OrdinalIgnoreCase), $"Expected file path to contain 'Another%20Custom%20Name.md' but got '{savedFilePath}'."); } finally { session.Dispose(); if (Directory.Exists(root)) Directory.Delete(root, recursive: true); } } private static async Task SaveEntryForTestAsync(Entry entry, string fileStem, string content) { var request = JsonSerializer.Serialize(new { action = "entries.save", payload = new { content, mode = "Overwrite", fileName = fileStem } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for test seed entries.save."); return doc.RootElement.GetProperty("data").GetProperty("FilePath").GetString() ?? ""; } private static async Task LoadEntryForTestAsync(Entry entry, string filePath) { var request = JsonSerializer.Serialize(new { action = "entries.load", payload = new { filePath } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for test entries.load."); return doc.RootElement.GetProperty("data").GetProperty("Entry").GetProperty("RawContent").GetString() ?? ""; } private static async Task SeedSearchFixtureEntriesAsync(Entry entry) { await SaveEntryForTestAsync(entry, "2026-02-01", """ Date: 2026-02-01 ## Summary Alpha line common token ## Reflection focus area - [x] med taken !TRIGGER #stress fragment one """); await SaveEntryForTestAsync(entry, "2026-02-05", """ Date: 2026-02-05 ## Summary beta line COMMON token ## Reflection other notes - [ ] drink water !NOTE #daily fragment two """); await SaveEntryForTestAsync(entry, "2026-03-01", """ Date: 2026-03-01 ## Summary gamma only ## Reflection nothing related !NOTE #other fragment three """); } }