claude-web-chat/claude_cli_test.go
djuka 9d0e507689
All checks were successful
Tests / unit-tests (push) Successful in 41s
Popravljen CLI mod: stream-json input, concurrent write fix, lepsi tool prikaz
- Koristi --input-format stream-json za multi-turn razgovor
- Koristi --include-partial-messages za streaming chunk-ove
- Filtrira CLAUDECODE i CLAUDE_CODE_ENTRYPOINT env varijable
- Svi WS write-ovi idu kroz jedan kanal (nema concurrent write panic)
- Tool call prikaz: Read prikazuje putanju, Bash prikazuje komandu, itd
- result polje moze biti string ili objekat (oba obradjena)
- Subscriber/broadcast model za real-time push

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 05:27:34 +00:00

234 lines
6.2 KiB
Go

package main
import (
"encoding/json"
"testing"
)
func TestFilterEnv(t *testing.T) {
env := []string{
"PATH=/usr/bin",
"HOME=/root",
"CLAUDECODE=1",
"OTHER=value",
}
filtered := filterEnv(env, "CLAUDECODE")
if len(filtered) != 3 {
t.Fatalf("got %d entries, want 3", len(filtered))
}
for _, e := range filtered {
if e == "CLAUDECODE=1" {
t.Error("CLAUDECODE should be filtered out")
}
}
}
func TestFilterEnvMulti(t *testing.T) {
env := []string{
"PATH=/usr/bin",
"HOME=/root",
"CLAUDECODE=1",
"CLAUDE_CODE_ENTRYPOINT=cli",
"OTHER=value",
}
filtered := filterEnvMulti(env, []string{"CLAUDECODE", "CLAUDE_CODE_ENTRYPOINT"})
if len(filtered) != 3 {
t.Fatalf("got %d entries, want 3", len(filtered))
}
for _, e := range filtered {
if e == "CLAUDECODE=1" || e == "CLAUDE_CODE_ENTRYPOINT=cli" {
t.Errorf("should be filtered: %s", e)
}
}
}
func TestFilterEnvNotPresent(t *testing.T) {
env := []string{"PATH=/usr/bin", "HOME=/root"}
filtered := filterEnv(env, "CLAUDECODE")
if len(filtered) != 2 {
t.Fatalf("got %d entries, want 2", len(filtered))
}
}
func TestTruncate(t *testing.T) {
tests := []struct {
input string
n int
want string
}{
{"hello", 10, "hello"},
{"hello world", 5, "hello..."},
{"", 5, ""},
}
for _, tt := range tests {
got := truncate(tt.input, tt.n)
if got != tt.want {
t.Errorf("truncate(%q, %d) = %q, want %q", tt.input, tt.n, got, tt.want)
}
}
}
func TestCLIEventParsing(t *testing.T) {
t.Run("system init", func(t *testing.T) {
raw := `{"type":"system","subtype":"init","session_id":"abc-123","cwd":"/"}`
var event CLIEvent
if err := json.Unmarshal([]byte(raw), &event); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if event.Type != "system" {
t.Errorf("type = %q", event.Type)
}
if event.Subtype != "init" {
t.Errorf("subtype = %q", event.Subtype)
}
if event.SessionID != "abc-123" {
t.Errorf("session_id = %q", event.SessionID)
}
})
t.Run("assistant message", func(t *testing.T) {
raw := `{"type":"assistant","message":{"role":"assistant","content":[{"type":"text","text":"Hello!"}]}}`
var event CLIEvent
if err := json.Unmarshal([]byte(raw), &event); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if event.Type != "assistant" {
t.Errorf("type = %q", event.Type)
}
if event.Message == nil {
t.Fatal("message is nil")
}
if len(event.Message.Content) != 1 {
t.Fatalf("content length = %d", len(event.Message.Content))
}
if event.Message.Content[0].Text != "Hello!" {
t.Errorf("text = %q", event.Message.Content[0].Text)
}
})
t.Run("stream_event content_block_delta", func(t *testing.T) {
raw := `{"type":"stream_event","event":{"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"chunk"}}}`
var event CLIEvent
if err := json.Unmarshal([]byte(raw), &event); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if event.Type != "stream_event" {
t.Errorf("type = %q", event.Type)
}
if event.Event == nil {
t.Fatal("event is nil")
}
if event.Event.Type != "content_block_delta" {
t.Errorf("event.type = %q", event.Event.Type)
}
if event.Event.Delta == nil {
t.Fatal("delta is nil")
}
if event.Event.Delta.Text != "chunk" {
t.Errorf("text = %q", event.Event.Delta.Text)
}
})
t.Run("stream_event content_block_start", func(t *testing.T) {
raw := `{"type":"stream_event","event":{"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}}`
var event CLIEvent
if err := json.Unmarshal([]byte(raw), &event); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if event.Event.Type != "content_block_start" {
t.Errorf("event.type = %q", event.Event.Type)
}
})
t.Run("result event object", func(t *testing.T) {
raw := `{"type":"result","subtype":"success","result":{"duration_ms":1234.5,"num_turns":3,"total_cost_usd":0.05,"session_id":"abc123"}}`
var event CLIEvent
if err := json.Unmarshal([]byte(raw), &event); err != nil {
t.Fatalf("unmarshal: %v", err)
}
// Simulate readOutput parsing of RawResult
if len(event.RawResult) > 0 {
var result CLIResult
if err := json.Unmarshal(event.RawResult, &result); err == nil {
event.Result = &result
}
}
if event.Type != "result" {
t.Errorf("type = %q", event.Type)
}
if event.Subtype != "success" {
t.Errorf("subtype = %q", event.Subtype)
}
if event.Result == nil {
t.Fatal("result is nil")
}
if event.Result.SessionID != "abc123" {
t.Errorf("session_id = %q", event.Result.SessionID)
}
})
t.Run("result event string", func(t *testing.T) {
raw := `{"type":"result","subtype":"success","total_cost_usd":0.046,"result":"hello world"}`
var event CLIEvent
if err := json.Unmarshal([]byte(raw), &event); err != nil {
t.Fatalf("unmarshal: %v", err)
}
// Simulate readOutput parsing — string result won't parse as CLIResult
if len(event.RawResult) > 0 {
var result CLIResult
if err := json.Unmarshal(event.RawResult, &result); err == nil {
event.Result = &result
}
if event.Result == nil && event.TotalCostUSD > 0 {
event.Result = &CLIResult{CostUSD: event.TotalCostUSD}
}
}
if event.Result == nil {
t.Fatal("result should not be nil for string result with cost")
}
if event.Result.CostUSD != 0.046 {
t.Errorf("cost = %f, want 0.046", event.Result.CostUSD)
}
})
t.Run("tool_use content", func(t *testing.T) {
raw := `{"type":"assistant","message":{"role":"assistant","content":[{"type":"tool_use","name":"Read","id":"tool1","input":{"file_path":"/tmp/test.go"}}]}}`
var event CLIEvent
if err := json.Unmarshal([]byte(raw), &event); err != nil {
t.Fatalf("unmarshal: %v", err)
}
if event.Message.Content[0].Type != "tool_use" {
t.Errorf("content type = %q", event.Message.Content[0].Type)
}
if event.Message.Content[0].Name != "Read" {
t.Errorf("name = %q", event.Message.Content[0].Name)
}
})
}
func TestCLIInputMessage(t *testing.T) {
msg := CLIInputMessage{
Type: "user",
Message: CLIInputContent{
Role: "user",
Content: "hello world",
},
}
data, err := json.Marshal(msg)
if err != nil {
t.Fatalf("marshal: %v", err)
}
expected := `{"type":"user","message":{"role":"user","content":"hello world"}}`
if string(data) != expected {
t.Errorf("got %s, want %s", string(data), expected)
}
}