Files
agentic-coding-workflow/internal/acp/jsonrpc_test.go
T

157 lines
3.8 KiB
Go

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)
}