diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-02-24 02:01:17 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-02-24 02:01:17 +0000 |
| commit | f27d4f7ef3949627c9cb1077f90135a5268b7631 (patch) | |
| tree | 3f6c2dcca7ba33d0a1d377b50e43eb08834bf412 /internal/cli/logs_test.go | |
| parent | 0377c06310cf92cfa477917f35f5e0755c09f063 (diff) | |
Add logs CLI subcommand to tail execution output
Adds `claudomator logs <execution-id>` to stream or display stdout logs
from a past or running execution. Includes unit tests for the command.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/cli/logs_test.go')
| -rw-r--r-- | internal/cli/logs_test.go | 165 |
1 files changed, 165 insertions, 0 deletions
diff --git a/internal/cli/logs_test.go b/internal/cli/logs_test.go new file mode 100644 index 0000000..d1447c3 --- /dev/null +++ b/internal/cli/logs_test.go @@ -0,0 +1,165 @@ +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) + } +} |
