internal static partial class Program { static async Task TestEntryAiHealthDefaultAsync() { var entry = NewEntry(); var response = await entry.HandleCommandAsync("""{"action":"ai.health"}"""); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for ai.health."); var data = doc.RootElement.GetProperty("data"); Assert(data.GetProperty("Enabled").GetBoolean() is false, "Expected AI disabled by default."); Assert(string.Equals(data.GetProperty("Provider").GetString(), "none", StringComparison.OrdinalIgnoreCase), "Expected default provider 'none'."); } static async Task TestEntryAiSummarizeEntryDisabledAsync() { var entry = NewEntry(); var request = JsonSerializer.Serialize(new { action = "ai.summarize_entry", payload = new { content = "sample entry" } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for disabled ai.summarize_entry."); var data = doc.RootElement.GetProperty("data").GetString() ?? ""; Assert(data.Contains("disabled", StringComparison.OrdinalIgnoreCase), "Expected disabled provider message for ai.summarize_entry."); } static async Task TestEntryAiSummarizeAllDisabledAsync() { var entry = NewEntry(); var request = JsonSerializer.Serialize(new { action = "ai.summarize_all", payload = new { entries = new[] { "entry one", "entry two" } } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for disabled ai.summarize_all."); var data = doc.RootElement.GetProperty("data").GetString() ?? ""; Assert(data.Contains("disabled", StringComparison.OrdinalIgnoreCase), "Expected disabled provider message for ai.summarize_all."); } static async Task TestEntryAiChatDisabledAsync() { var entry = NewEntry(); var request = JsonSerializer.Serialize(new { action = "ai.chat", payload = new { prompt = "hello cloud" } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for disabled ai.chat."); var data = doc.RootElement.GetProperty("data").GetString() ?? ""; Assert(data.Contains("disabled", StringComparison.OrdinalIgnoreCase), "Expected disabled provider message for ai.chat."); } static async Task TestEntryAiEmbedDisabledAsync() { var entry = NewEntry(); var request = JsonSerializer.Serialize(new { action = "ai.embed", payload = new { content = "embedding source text" } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for disabled ai.embed."); var data = doc.RootElement.GetProperty("data"); Assert(data.ValueKind == JsonValueKind.Array, "Expected ai.embed response to be a JSON array."); Assert(data.GetArrayLength() == 0, "Expected disabled ai.embed to return an empty vector."); } static async Task TestEntrySpeechDevicesListDisabledAsync() { var entry = NewEntry(); var request = JsonSerializer.Serialize(new { action = "speech.devices.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 speech.devices.list when disabled."); var data = doc.RootElement.GetProperty("data"); Assert(data.ValueKind == JsonValueKind.Object, "Expected speech.devices.list data to be an object."); } static async Task TestEntrySpeechTranscribeDisabledAsync() { var entry = NewEntry(); var request = JsonSerializer.Serialize(new { action = "speech.transcribe", payload = new { text = "fixture transcript", engine = "whisper" } }); var response = await entry.HandleCommandAsync(request); using var doc = JsonDocument.Parse(response); Assert(doc.RootElement.GetProperty("ok").GetBoolean(), "Expected ok=true for speech.transcribe when disabled."); var data = doc.RootElement.GetProperty("data"); Assert(data.ValueKind == JsonValueKind.Object, "Expected speech.transcribe data to be an object."); var warning = data.TryGetProperty("Warning", out var warningNode) ? warningNode.GetString() ?? "" : ""; Assert(warning.Contains("disabled", StringComparison.OrdinalIgnoreCase), "Expected disabled speech warning."); } static async Task TestPythonSidecarAiServiceJsonLineAsync() { var root = Path.Combine(Path.GetTempPath(), "journal-ai-smoke", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(root); var scriptPath = Path.Combine(root, "fake_ai_sidecar.py"); File.WriteAllText(scriptPath, """ import json, sys request = json.loads(sys.stdin.readline()) action = request.get("action", "") print("DEBUG prelude") if action == "health": print(json.dumps({"ok": True, "data": {"provider": "python-sidecar", "healthy": True, "message": "ok"}})) elif action == "summarize_entry": payload = request.get("payload") or {} print(json.dumps({"ok": True, "data": "ENTRY::" + str(payload.get("content", ""))})) elif action == "summarize_all": payload = request.get("payload") or {} entries = payload.get("entries") or [] print(json.dumps({"ok": True, "data": "ALL::" + str(len(entries))})) elif action == "chat": payload = request.get("payload") or {} print(json.dumps({"ok": True, "data": "CHAT::" + str(payload.get("prompt", ""))})) elif action == "embed": payload = request.get("payload") or {} text = str(payload.get("content", "")) print(json.dumps({"ok": True, "data": [float(len(text)), 2.5, -1.0]})) else: print(json.dumps({"ok": False, "error": "unknown action"})) """); try { var config = BuildAiConfig(scriptPath, timeoutMs: 4000); IAiService service = new PythonSidecarAiService(config); var health = await service.HealthAsync(); Assert(health.Enabled, "Expected enabled=true for python-sidecar health."); Assert(health.Healthy, "Expected healthy=true from fake sidecar health."); var one = await service.SummarizeEntryAsync("hello"); Assert(one == "ENTRY::hello", "Unexpected summarize_entry response."); var all = await service.SummarizeAllAsync(["a", "b", "c"]); Assert(all == "ALL::3", "Unexpected summarize_all response."); var chat = await service.ChatAsync("hello"); Assert(chat == "CHAT::hello", "Unexpected chat response."); var vector = await service.EmbedAsync("hello"); Assert(vector.Count == 3, "Unexpected embed vector length."); Assert(Math.Abs(vector[0] - 5d) < 0.0001d, "Unexpected embed vector first value."); Assert(Math.Abs(vector[1] - 2.5d) < 0.0001d, "Unexpected embed vector second value."); Assert(Math.Abs(vector[2] + 1.0d) < 0.0001d, "Unexpected embed vector third value."); } finally { if (Directory.Exists(root)) Directory.Delete(root, recursive: true); } } static async Task TestPythonSidecarAiServiceErrorAsync() { var root = Path.Combine(Path.GetTempPath(), "journal-ai-smoke", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(root); var scriptPath = Path.Combine(root, "fake_ai_sidecar_error.py"); File.WriteAllText(scriptPath, """ import json, sys _ = json.loads(sys.stdin.readline()) print(json.dumps({"ok": False, "error": "simulated failure"})) """); try { var config = BuildAiConfig(scriptPath, timeoutMs: 4000); IAiService service = new PythonSidecarAiService(config); try { _ = await service.SummarizeEntryAsync("hello"); } catch (InvalidOperationException ex) when (ex.Message.Contains("simulated failure", StringComparison.OrdinalIgnoreCase)) { return; } throw new InvalidOperationException("Expected summarize_entry to surface sidecar error."); } finally { if (Directory.Exists(root)) Directory.Delete(root, recursive: true); } } static async Task TestPythonSidecarSpeechServiceNoDevicesAsync() { var root = Path.Combine(Path.GetTempPath(), "journal-speech-smoke", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(root); var scriptPath = Path.Combine(root, "fake_speech_sidecar_nodes.py"); File.WriteAllText(scriptPath, """ import json, sys request = json.loads(sys.stdin.readline()) action = request.get("action", "") if action == "speech.devices.list": print(json.dumps({"ok": True, "data": {"devices": [], "warning": "no devices"}})) elif action == "speech.transcribe": payload = request.get("payload") or {} print(json.dumps({"ok": True, "data": {"text": payload.get("text", ""), "engine": payload.get("engine", "whisper")}})) else: print(json.dumps({"ok": False, "error": "unknown action"})) """); try { var config = BuildAiConfig(scriptPath, timeoutMs: 4000); ISpeechBridgeService service = new PythonSidecarSpeechService(config); var devices = await service.ListDevicesAsync(); Assert(devices.Devices.Count == 0, "Expected empty devices list."); Assert((devices.Warning ?? "").Contains("no devices", StringComparison.OrdinalIgnoreCase), "Expected no-devices warning."); var transcript = await service.TranscribeAsync(new SpeechTranscribeRequestDto(Text: "fixture text", Engine: "whisper")); Assert(transcript.Text == "fixture text", "Expected passthrough transcript text."); Assert(transcript.Engine == "whisper", "Expected passthrough transcript engine."); } finally { if (Directory.Exists(root)) Directory.Delete(root, recursive: true); } } static async Task TestPythonSidecarSpeechServiceErrorAsync() { var root = Path.Combine(Path.GetTempPath(), "journal-speech-smoke", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(root); var scriptPath = Path.Combine(root, "fake_speech_sidecar_error.py"); File.WriteAllText(scriptPath, """ import json, sys request = json.loads(sys.stdin.readline()) action = request.get("action", "") if action == "speech.transcribe": print(json.dumps({"ok": False, "error": "engine unavailable"})) else: print(json.dumps({"ok": True, "data": {"devices": []}})) """); try { var config = BuildAiConfig(scriptPath, timeoutMs: 4000); ISpeechBridgeService service = new PythonSidecarSpeechService(config); try { _ = await service.TranscribeAsync(new SpeechTranscribeRequestDto(Text: "fixture", Engine: "faster-whisper")); } catch (InvalidOperationException ex) when (ex.Message.Contains("engine unavailable", StringComparison.OrdinalIgnoreCase)) { return; } throw new InvalidOperationException("Expected speech transcribe to surface sidecar engine error."); } finally { if (Directory.Exists(root)) Directory.Delete(root, recursive: true); } } static async Task TestPythonSidecarSpeechServiceTimeoutAsync() { var root = Path.Combine(Path.GetTempPath(), "journal-speech-smoke", Guid.NewGuid().ToString("N")); Directory.CreateDirectory(root); var scriptPath = Path.Combine(root, "fake_speech_sidecar_timeout.py"); File.WriteAllText(scriptPath, """ import json, sys, time request = json.loads(sys.stdin.readline()) payload = request.get("payload") or {} sleep_ms = int(payload.get("simulate_delay_ms") or 0) time.sleep(max(0, sleep_ms) / 1000.0) print(json.dumps({"ok": True, "data": {"text": "", "engine": "whisper"}})) """); try { var config = BuildAiConfig(scriptPath, timeoutMs: 100); ISpeechBridgeService service = new PythonSidecarSpeechService(config); try { _ = await service.TranscribeAsync(new SpeechTranscribeRequestDto(Text: "fixture", SimulateDelayMs: 500)); } catch (TimeoutException) { return; } throw new InvalidOperationException("Expected speech transcribe timeout path."); } finally { if (Directory.Exists(root)) Directory.Delete(root, recursive: true); } } }