package acp_test import ( "bytes" "encoding/json" "io" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/yan1h/agent-coding-workflow/internal/acp" ) func TestJSONRPC_RoundtripRequestNumberID(t *testing.T) { t.Parallel() req, err := acp.NewRequestInt(42, "session/prompt", map[string]any{"text": "hi"}) require.NoError(t, err) var buf bytes.Buffer require.NoError(t, acp.NewEncoder(&buf).Encode(req)) dec := acp.NewDecoder(&buf, 0) msg, err := dec.DecodeMessage() require.NoError(t, err) assert.True(t, msg.IsRequest()) assert.Equal(t, "session/prompt", msg.Method) id, ok := msg.IDInt64() require.True(t, ok) assert.Equal(t, int64(42), id) } func TestJSONRPC_RoundtripStringID(t *testing.T) { t.Parallel() idJSON, _ := json.Marshal("client-abc") req := &acp.Message{JSONRPC: "2.0", ID: idJSON, Method: "session/prompt"} var buf bytes.Buffer require.NoError(t, acp.NewEncoder(&buf).Encode(req)) dec := acp.NewDecoder(&buf, 0) msg, _ := dec.DecodeMessage() s, ok := msg.IDString() require.True(t, ok) assert.Equal(t, "client-abc", s) _, ok2 := msg.IDInt64() assert.False(t, ok2) } func TestJSONRPC_Notification_NoID(t *testing.T) { t.Parallel() notif, _ := acp.NewNotification("session/update", map[string]string{"x": "y"}) var buf bytes.Buffer require.NoError(t, acp.NewEncoder(&buf).Encode(notif)) msg, _ := acp.NewDecoder(&buf, 0).DecodeMessage() assert.True(t, msg.IsNotification()) assert.False(t, msg.IsRequest()) assert.False(t, msg.IsResponse()) } func TestJSONRPC_Response(t *testing.T) { t.Parallel() idJSON, _ := json.Marshal(int64(7)) resp, _ := acp.NewResponseOK(idJSON, map[string]string{"sessionId": "abc"}) var buf bytes.Buffer require.NoError(t, acp.NewEncoder(&buf).Encode(resp)) msg, _ := acp.NewDecoder(&buf, 0).DecodeMessage() assert.True(t, msg.IsResponse()) var result struct { SessionID string `json:"sessionId"` } require.NoError(t, json.Unmarshal(msg.Result, &result)) assert.Equal(t, "abc", result.SessionID) } func TestJSONRPC_ErrorResponse(t *testing.T) { t.Parallel() idJSON, _ := json.Marshal("client-1") resp := acp.NewResponseErr(idJSON, acp.JSONRPCMethodNotFound, "no such method") var buf bytes.Buffer require.NoError(t, acp.NewEncoder(&buf).Encode(resp)) msg, _ := acp.NewDecoder(&buf, 0).DecodeMessage() require.NotNil(t, msg.Error) assert.Equal(t, acp.JSONRPCMethodNotFound, msg.Error.Code) assert.Equal(t, "no such method", msg.Error.Message) } func TestJSONRPC_DecoderEOF(t *testing.T) { t.Parallel() dec := acp.NewDecoder(bytes.NewReader(nil), 0) _, err := dec.DecodeMessage() assert.ErrorIs(t, err, io.EOF) } func TestJSONRPC_DecoderMaxSize(t *testing.T) { t.Parallel() huge := make([]byte, 200) for i := range huge { huge[i] = 'a' } body := []byte(`{"jsonrpc":"2.0","method":"x","params":"`) body = append(body, huge...) body = append(body, []byte(`"}`+"\n")...) dec := acp.NewDecoder(bytes.NewReader(body), 100) _, err := dec.DecodeMessage() require.Error(t, err) assert.Contains(t, err.Error(), "exceeds max size") } func TestJSONRPC_DecoderBadJSON(t *testing.T) { t.Parallel() dec := acp.NewDecoder(bytes.NewReader([]byte("not-json\n")), 0) _, err := dec.DecodeMessage() require.Error(t, err) } func TestJSONRPC_EncoderConcurrentSafe(t *testing.T) { t.Parallel() var buf bytes.Buffer enc := acp.NewEncoder(&buf) const N = 100 done := make(chan struct{}, N) for i := 0; i < N; i++ { go func(id int64) { req, _ := acp.NewRequestInt(id, "m", nil) _ = enc.Encode(req) done <- struct{}{} }(int64(i)) } for i := 0; i < N; i++ { <-done } dec := acp.NewDecoder(&buf, 0) count := 0 for { _, err := dec.DecodeMessage() if err == io.EOF { break } require.NoError(t, err) count++ } assert.Equal(t, N, count) }