using ClaudeDo.Worker.Config; using ClaudeDo.Worker.Runner; using Microsoft.Extensions.Logging.Abstractions; namespace ClaudeDo.Worker.Tests.Runner; // Real-CLI smoke test (plan-verification §1.0 step 3). Spawns the actual `claude` // binary and needs an authenticated session, so it is inert unless the developer // opts in with CLAUDE_AUTHENTICATED=1. Run locally with: // CLAUDE_AUTHENTICATED=1 dotnet test tests/ClaudeDo.Worker.Tests \ // --filter FullyQualifiedName~ClaudeProcessSmokeTest // Override the binary with CLAUDEDO_CLAUDE_BIN if `claude` is not on PATH. public class ClaudeProcessSmokeTest { private static bool Enabled => Environment.GetEnvironmentVariable("CLAUDE_AUTHENTICATED") == "1"; [Fact] public async Task RunAsync_PingPrompt_ReturnsResultSessionAndTokens() { if (!Enabled) { Assert.True(true, "CLAUDE_AUTHENTICATED != 1 — skipping real-CLI smoke test"); return; } var cfg = new WorkerConfig { ClaudeBin = Environment.GetEnvironmentVariable("CLAUDEDO_CLAUDE_BIN") ?? "claude", }; var sut = new ClaudeProcess(cfg, NullLogger.Instance); var args = new ClaudeArgsBuilder().Build(new ClaudeRunConfig( Model: null, SystemPrompt: null, AgentPath: null, ResumeSessionId: null, MaxTurns: 3, PermissionMode: "auto")); var workDir = Path.Combine(Path.GetTempPath(), $"claudedo_smoke_{Guid.NewGuid():N}"); Directory.CreateDirectory(workDir); var lines = new List(); try { using var cts = new CancellationTokenSource(TimeSpan.FromMinutes(2)); var result = await sut.RunAsync( args, "Reply with exactly the word: pong", workDir, line => { lock (lines) { lines.Add(line); } return Task.CompletedTask; }, cts.Token); Assert.Equal(0, result.ExitCode); Assert.False(string.IsNullOrWhiteSpace(result.SessionId), "session_id should be populated"); Assert.False(string.IsNullOrWhiteSpace(result.ResultMarkdown), "result should be non-empty"); Assert.True(result.TokensOut > 0, "output_tokens should be > 0"); } finally { try { Directory.Delete(workDir, true); } catch { } } } }