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>
189 lines
11 KiB
C#
189 lines
11 KiB
C#
internal static partial class Program
|
|
{
|
|
static Task TestDatabaseKeyDerivationMatchesPythonAsync()
|
|
{
|
|
var service = NewDatabaseService();
|
|
var keyHex = Convert.ToHexString(service.DeriveDatabaseKey("vault-pass-123")).ToLowerInvariant();
|
|
var expected = "6a9de08e13357aa8f14e7eb0ccde119e7b4d277c60aaaca6493d9a1e1eaa5b04";
|
|
Assert(keyHex == expected, "Database key derivation should match Python PBKDF2 fixture.");
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task TestDatabaseSchemaParityAsync()
|
|
{
|
|
var service = NewDatabaseService();
|
|
var statements = service.GetSchemaStatements();
|
|
var tableNames = new HashSet<string>(statements.Keys, StringComparer.OrdinalIgnoreCase);
|
|
|
|
Assert(tableNames.Contains("entries"), "Schema should contain entries table.");
|
|
Assert(tableNames.Contains("sections"), "Schema should contain sections table.");
|
|
Assert(tableNames.Contains("fragments"), "Schema should contain fragments table.");
|
|
Assert(tableNames.Contains("tags"), "Schema should contain tags table.");
|
|
Assert(tableNames.Contains("fragment_tags"), "Schema should contain fragment_tags table.");
|
|
Assert(tableNames.Contains("entry_documents"), "Schema should contain entry_documents table.");
|
|
|
|
var fragmentTagsSql = statements["fragment_tags"];
|
|
Assert(fragmentTagsSql.Contains("PRIMARY KEY (fragment_id, tag_id)", StringComparison.OrdinalIgnoreCase), "fragment_tags should enforce composite primary key parity.");
|
|
Assert(fragmentTagsSql.Contains("FOREIGN KEY (fragment_id) REFERENCES fragments (id)", StringComparison.OrdinalIgnoreCase), "fragment_tags should contain fragment foreign key parity.");
|
|
Assert(fragmentTagsSql.Contains("FOREIGN KEY (tag_id) REFERENCES tags (id)", StringComparison.OrdinalIgnoreCase), "fragment_tags should contain tag foreign key parity.");
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static async Task TestEntryDatabaseStatusAsync()
|
|
{
|
|
var entry = NewEntry(unlocked: false);
|
|
var request = JsonSerializer.Serialize(new
|
|
{
|
|
action = "db.status",
|
|
payload = new
|
|
{
|
|
password = "vault-pass-123"
|
|
}
|
|
});
|
|
|
|
var response = await entry.HandleCommandAsync(request);
|
|
using var doc = JsonDocument.Parse(response);
|
|
Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for db.status.");
|
|
var data = doc.RootElement.GetProperty("data");
|
|
Assert(data.TryGetProperty("DatabasePath", out var databasePath), "Expected DatabasePath in db.status payload.");
|
|
Assert(databasePath.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(databasePath.GetString()), "Expected non-empty DatabasePath.");
|
|
Assert(data.TryGetProperty("KeyDerivation", out var keyDerivation), "Expected KeyDerivation in db.status payload.");
|
|
Assert(string.Equals(keyDerivation.GetString(), "PBKDF2-HMAC-SHA256", StringComparison.Ordinal), "Expected PBKDF2-HMAC-SHA256 key derivation.");
|
|
Assert(data.TryGetProperty("SchemaTables", out var schemaTables), "Expected SchemaTables list in db.status payload.");
|
|
Assert(schemaTables.ValueKind == JsonValueKind.Array && schemaTables.GetArrayLength() >= 5, "Expected schema table list in db.status payload.");
|
|
Assert(data.TryGetProperty("RuntimeReady", out var runtimeReady), "Expected RuntimeReady in db.status payload.");
|
|
Assert(runtimeReady.ValueKind == JsonValueKind.True, "Expected SQLCipher runtime-ready status in db.status payload.");
|
|
}
|
|
|
|
static async Task TestEntryDatabaseInitializeSchemaAsync()
|
|
{
|
|
var entry = NewEntry(unlocked: false);
|
|
var request = JsonSerializer.Serialize(new
|
|
{
|
|
action = "db.initialize_schema",
|
|
payload = new
|
|
{
|
|
password = "vault-pass-123"
|
|
}
|
|
});
|
|
|
|
var response = await entry.HandleCommandAsync(request);
|
|
using var doc = JsonDocument.Parse(response);
|
|
Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for db.initialize_schema.");
|
|
|
|
var data = doc.RootElement.GetProperty("data");
|
|
Assert(data.TryGetProperty("initialized", out var initialized), "Expected initialized flag in db.initialize_schema response.");
|
|
Assert(initialized.ValueKind == JsonValueKind.True, "Expected initialized=true from db.initialize_schema.");
|
|
Assert(data.TryGetProperty("databasePath", out var databasePath), "Expected databasePath in db.initialize_schema response.");
|
|
Assert(databasePath.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(databasePath.GetString()), "Expected non-empty databasePath from db.initialize_schema.");
|
|
}
|
|
|
|
static async Task TestEntryDatabaseHydrateWorkspaceAsync()
|
|
{
|
|
var entry = NewEntry(unlocked: false);
|
|
var request = JsonSerializer.Serialize(new
|
|
{
|
|
action = "db.hydrate_workspace",
|
|
payload = new
|
|
{
|
|
password = "vault-pass-123"
|
|
}
|
|
});
|
|
|
|
var response = await entry.HandleCommandAsync(request);
|
|
using var doc = JsonDocument.Parse(response);
|
|
Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for db.hydrate_workspace.");
|
|
var data = doc.RootElement.GetProperty("data");
|
|
|
|
Assert(data.TryGetProperty("DatabasePath", out var databasePath), "Expected DatabasePath in hydrate payload.");
|
|
Assert(databasePath.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(databasePath.GetString()), "Expected non-empty DatabasePath.");
|
|
Assert(data.TryGetProperty("EntryFilesProcessed", out var filesProcessed), "Expected EntryFilesProcessed in hydrate payload.");
|
|
Assert(filesProcessed.ValueKind == JsonValueKind.Number && filesProcessed.GetInt32() >= 0, "Expected non-negative EntryFilesProcessed in hydrate payload.");
|
|
Assert(data.TryGetProperty("RuntimeReady", out var runtimeReady), "Expected RuntimeReady in hydrate payload.");
|
|
Assert(runtimeReady.ValueKind == JsonValueKind.True, "Expected RuntimeReady=true when SQLCipher runtime hydration succeeds.");
|
|
}
|
|
|
|
static Task TestConfigServiceParityKeysAsync()
|
|
{
|
|
IJournalConfigService config = NewConfigService();
|
|
var current = config.Current;
|
|
|
|
Assert(!string.IsNullOrWhiteSpace(current.ProjectRoot), "Config ProjectRoot should not be empty.");
|
|
Assert(!string.IsNullOrWhiteSpace(current.AppDirectory), "Config AppDirectory should not be empty.");
|
|
Assert(!string.IsNullOrWhiteSpace(current.VaultDirectory), "Config VaultDirectory should not be empty.");
|
|
Assert(!string.IsNullOrWhiteSpace(current.DatabaseFilename), "Config DatabaseFilename should not be empty.");
|
|
|
|
Assert(current.LlamaCppUrl == "http://127.0.0.1:8085/v1/completions", "Config LlamaCppUrl default mismatch.");
|
|
Assert(current.LlamaCppModel == "qwen/qwen3-4b", "Config LlamaCppModel default mismatch.");
|
|
Assert(current.EmbeddingApiUrl == "http://127.0.0.1:8086/v1/embeddings", "Config EmbeddingApiUrl default mismatch.");
|
|
Assert(current.SpeechRecognitionEngine == "whisper", "Config SpeechRecognitionEngine default mismatch.");
|
|
Assert(current.WhisperModelSize == "base", "Config WhisperModelSize default mismatch.");
|
|
Assert(current.AiProvider == "llamasharp", "Config AiProvider default mismatch.");
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static async Task TestEntryConfigGetAsync()
|
|
{
|
|
var entry = NewEntry();
|
|
var response = await entry.HandleCommandAsync("""{"action":"config.get"}""");
|
|
using var doc = JsonDocument.Parse(response);
|
|
|
|
Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for config.get.");
|
|
var data = doc.RootElement.GetProperty("data");
|
|
Assert(data.ValueKind == JsonValueKind.Object, "Expected object payload for config.get.");
|
|
|
|
Assert(data.TryGetProperty("VaultDirectory", out var vaultDirectory), "Expected VaultDirectory in config payload.");
|
|
Assert(vaultDirectory.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(vaultDirectory.GetString()), "Expected non-empty VaultDirectory value.");
|
|
Assert(data.TryGetProperty("DatabaseFilename", out var databaseFilename), "Expected DatabaseFilename in config payload.");
|
|
Assert(databaseFilename.ValueKind == JsonValueKind.String && !string.IsNullOrWhiteSpace(databaseFilename.GetString()), "Expected non-empty DatabaseFilename value.");
|
|
Assert(data.TryGetProperty("LlamaCppUrl", out _), "Expected LlamaCppUrl in config payload.");
|
|
Assert(data.TryGetProperty("SpeechRecognitionEngine", out _), "Expected SpeechRecognitionEngine in config payload.");
|
|
}
|
|
|
|
static Task TestLogRedactorScrubsSensitiveFieldsAsync()
|
|
{
|
|
var payload = JsonSerializer.SerializeToElement(new
|
|
{
|
|
password = "vault-pass-123",
|
|
content = "private journal body",
|
|
prompt = "private ai prompt",
|
|
nested = new
|
|
{
|
|
token = "abc123"
|
|
}
|
|
});
|
|
|
|
var redacted = LogRedactor.RedactPayload(payload);
|
|
var serialized = JsonSerializer.Serialize(redacted);
|
|
|
|
Assert(!serialized.Contains("vault-pass-123", StringComparison.Ordinal), "Password should be redacted.");
|
|
Assert(!serialized.Contains("private journal body", StringComparison.Ordinal), "Entry content should be redacted.");
|
|
Assert(!serialized.Contains("private ai prompt", StringComparison.Ordinal), "Prompt should be redacted.");
|
|
Assert(!serialized.Contains("abc123", StringComparison.Ordinal), "Nested token should be redacted.");
|
|
Assert(serialized.Contains("[REDACTED]", StringComparison.Ordinal), "Redacted marker should be present.");
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
|
|
static Task TestLogRedactorPreservesNonSensitiveFieldsAsync()
|
|
{
|
|
var payload = JsonSerializer.SerializeToElement(new
|
|
{
|
|
action = "entries.save",
|
|
mode = "Daily",
|
|
filePath = "db://entry/2026-02-24.md"
|
|
});
|
|
|
|
var redacted = LogRedactor.RedactPayload(payload);
|
|
var serialized = JsonSerializer.Serialize(redacted);
|
|
|
|
Assert(serialized.Contains("entries.save", StringComparison.Ordinal), "Non-sensitive action field should be preserved.");
|
|
Assert(serialized.Contains("Daily", StringComparison.Ordinal), "Non-sensitive mode field should be preserved.");
|
|
Assert(serialized.Contains("2026-02-24.md", StringComparison.Ordinal), "Non-sensitive path field should be preserved.");
|
|
|
|
return Task.CompletedTask;
|
|
}
|
|
}
|