package cli import ( "bytes" "errors" "os" "path/filepath" "strings" "testing" "github.com/thepeterstone/claudomator/internal/storage" ) type fakeExecutionStore struct { exec *storage.Execution err error } func (f *fakeExecutionStore) GetExecution(id string) (*storage.Execution, error) { return f.exec, f.err } func TestLogsCmd_PrintsAssistantText(t *testing.T) { dir := t.TempDir() logFile := filepath.Join(dir, "stdout.log") line := `{"type":"assistant","message":{"content":[{"type":"text","text":"Hello from Claude"}]}}` if err := os.WriteFile(logFile, []byte(line+"\n"), 0644); err != nil { t.Fatal(err) } store := &fakeExecutionStore{ exec: &storage.Execution{ ID: "exec-1", StdoutPath: logFile, CostUSD: 0.0042, ExitCode: 0, }, } var buf bytes.Buffer if err := renderLogs("exec-1", store, &buf); err != nil { t.Fatalf("unexpected error: %v", err) } out := buf.String() if !strings.Contains(out, "Hello from Claude") { t.Errorf("expected text output, got: %q", out) } if !strings.Contains(out, "Cost: $0.0042") { t.Errorf("expected cost in footer, got: %q", out) } if !strings.Contains(out, "Exit: 0") { t.Errorf("expected exit code in footer, got: %q", out) } } func TestLogsCmd_PrintsToolUse(t *testing.T) { dir := t.TempDir() logFile := filepath.Join(dir, "stdout.log") line := `{"type":"assistant","message":{"content":[{"type":"tool_use","name":"Bash","input":{"command":"ls -la"}}]}}` if err := os.WriteFile(logFile, []byte(line+"\n"), 0644); err != nil { t.Fatal(err) } store := &fakeExecutionStore{ exec: &storage.Execution{ ID: "exec-2", StdoutPath: logFile, CostUSD: 0.0001, ExitCode: 0, }, } var buf bytes.Buffer if err := renderLogs("exec-2", store, &buf); err != nil { t.Fatalf("unexpected error: %v", err) } out := buf.String() if !strings.Contains(out, " > Bash") { t.Errorf("expected tool_use prefix, got: %q", out) } if !strings.Contains(out, "ls -la") { t.Errorf("expected tool input summary, got: %q", out) } } func TestLogsCmd_ExecutionNotFound(t *testing.T) { store := &fakeExecutionStore{ err: errors.New("not found"), } var buf bytes.Buffer err := renderLogs("exec-missing", store, &buf) if err == nil { t.Fatal("expected error for missing execution") } out := buf.String() if !strings.Contains(out, "execution exec-missing not found") { t.Errorf("expected not-found message, got: %q", out) } } func TestLogsCmd_EmptyLog(t *testing.T) { dir := t.TempDir() logFile := filepath.Join(dir, "stdout.log") if err := os.WriteFile(logFile, []byte(""), 0644); err != nil { t.Fatal(err) } store := &fakeExecutionStore{ exec: &storage.Execution{ ID: "exec-empty", StdoutPath: logFile, CostUSD: 0, ExitCode: 0, }, } var buf bytes.Buffer if err := renderLogs("exec-empty", store, &buf); err != nil { t.Fatalf("unexpected error: %v", err) } out := buf.String() if !strings.Contains(out, "no output recorded") { t.Errorf("expected no-output message, got: %q", out) } } func TestLogsCmd_SkipsThinkingAndNonAssistant(t *testing.T) { dir := t.TempDir() logFile := filepath.Join(dir, "stdout.log") content := strings.Join([]string{ `{"type":"system","message":"session start"}`, `{"type":"user","message":{"content":[{"type":"text","text":"do something"}]}}`, `{"type":"assistant","message":{"content":[{"type":"thinking","thinking":"let me think"},{"type":"text","text":"Done."}]}}`, }, "\n") + "\n" if err := os.WriteFile(logFile, []byte(content), 0644); err != nil { t.Fatal(err) } store := &fakeExecutionStore{ exec: &storage.Execution{ ID: "exec-3", StdoutPath: logFile, CostUSD: 0.0010, ExitCode: 0, }, } var buf bytes.Buffer if err := renderLogs("exec-3", store, &buf); err != nil { t.Fatalf("unexpected error: %v", err) } out := buf.String() if strings.Contains(out, "let me think") { t.Errorf("thinking block should be skipped, got: %q", out) } if !strings.Contains(out, "Done.") { t.Errorf("expected text output, got: %q", out) } }