Implemented Unix socket RPC foundation to enable daemon-based concurrent access: New files: - internal/rpc/protocol.go: Request/Response types with 13 operations - internal/rpc/server.go: Unix socket server with storage adapter - internal/rpc/client.go: Client with auto-detection and typed methods - internal/rpc/rpc_test.go: Integration tests Features: - JSON-based protocol over Unix sockets - Adapter pattern for context/actor propagation to storage API - Ping/health checks for daemon detection - All core operations: create, update, close, list, show, ready, stats, deps, labels - Graceful socket cleanup and signal handling - Concurrent request support Tests: 49.3% coverage, all passing Related issues: - bd-110: Daemon architecture epic - bd-111: Phase 1 (completed) - bd-112: Phase 2 (client auto-detection) - bd-113: Phase 3 (daemon command) - bd-114: Phase 4 (atomic operations) Amp-Thread-ID: https://ampcode.com/threads/T-796c62e6-93b6-41c7-9cb5-8acc4a35ba9a Co-authored-by: Amp <amp@ampcode.com>
171 lines
3.8 KiB
Go
171 lines
3.8 KiB
Go
package rpc
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
)
|
|
|
|
func TestRequestSerialization(t *testing.T) {
|
|
createArgs := CreateArgs{
|
|
Title: "Test Issue",
|
|
Description: "Test description",
|
|
IssueType: "task",
|
|
Priority: 2,
|
|
}
|
|
|
|
argsJSON, err := json.Marshal(createArgs)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal args: %v", err)
|
|
}
|
|
|
|
req := Request{
|
|
Operation: OpCreate,
|
|
Args: argsJSON,
|
|
}
|
|
|
|
reqJSON, err := json.Marshal(req)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal request: %v", err)
|
|
}
|
|
|
|
var decodedReq Request
|
|
if err := json.Unmarshal(reqJSON, &decodedReq); err != nil {
|
|
t.Fatalf("Failed to unmarshal request: %v", err)
|
|
}
|
|
|
|
if decodedReq.Operation != OpCreate {
|
|
t.Errorf("Expected operation %s, got %s", OpCreate, decodedReq.Operation)
|
|
}
|
|
|
|
var decodedArgs CreateArgs
|
|
if err := json.Unmarshal(decodedReq.Args, &decodedArgs); err != nil {
|
|
t.Fatalf("Failed to unmarshal args: %v", err)
|
|
}
|
|
|
|
if decodedArgs.Title != createArgs.Title {
|
|
t.Errorf("Expected title %s, got %s", createArgs.Title, decodedArgs.Title)
|
|
}
|
|
if decodedArgs.Priority != createArgs.Priority {
|
|
t.Errorf("Expected priority %d, got %d", createArgs.Priority, decodedArgs.Priority)
|
|
}
|
|
}
|
|
|
|
func TestResponseSerialization(t *testing.T) {
|
|
resp := Response{
|
|
Success: true,
|
|
Data: json.RawMessage(`{"id":"bd-1","title":"Test"}`),
|
|
}
|
|
|
|
respJSON, err := json.Marshal(resp)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal response: %v", err)
|
|
}
|
|
|
|
var decodedResp Response
|
|
if err := json.Unmarshal(respJSON, &decodedResp); err != nil {
|
|
t.Fatalf("Failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if decodedResp.Success != resp.Success {
|
|
t.Errorf("Expected success %v, got %v", resp.Success, decodedResp.Success)
|
|
}
|
|
|
|
if string(decodedResp.Data) != string(resp.Data) {
|
|
t.Errorf("Expected data %s, got %s", string(resp.Data), string(decodedResp.Data))
|
|
}
|
|
}
|
|
|
|
func TestErrorResponse(t *testing.T) {
|
|
resp := Response{
|
|
Success: false,
|
|
Error: "something went wrong",
|
|
}
|
|
|
|
respJSON, err := json.Marshal(resp)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal response: %v", err)
|
|
}
|
|
|
|
var decodedResp Response
|
|
if err := json.Unmarshal(respJSON, &decodedResp); err != nil {
|
|
t.Fatalf("Failed to unmarshal response: %v", err)
|
|
}
|
|
|
|
if decodedResp.Success {
|
|
t.Errorf("Expected success false, got true")
|
|
}
|
|
|
|
if decodedResp.Error != resp.Error {
|
|
t.Errorf("Expected error %s, got %s", resp.Error, decodedResp.Error)
|
|
}
|
|
}
|
|
|
|
func TestAllOperations(t *testing.T) {
|
|
operations := []string{
|
|
OpPing,
|
|
OpCreate,
|
|
OpUpdate,
|
|
OpClose,
|
|
OpList,
|
|
OpShow,
|
|
OpReady,
|
|
OpStats,
|
|
OpDepAdd,
|
|
OpDepRemove,
|
|
OpDepTree,
|
|
OpLabelAdd,
|
|
OpLabelRemove,
|
|
}
|
|
|
|
for _, op := range operations {
|
|
req := Request{
|
|
Operation: op,
|
|
Args: json.RawMessage(`{}`),
|
|
}
|
|
|
|
reqJSON, err := json.Marshal(req)
|
|
if err != nil {
|
|
t.Errorf("Failed to marshal request for op %s: %v", op, err)
|
|
continue
|
|
}
|
|
|
|
var decodedReq Request
|
|
if err := json.Unmarshal(reqJSON, &decodedReq); err != nil {
|
|
t.Errorf("Failed to unmarshal request for op %s: %v", op, err)
|
|
continue
|
|
}
|
|
|
|
if decodedReq.Operation != op {
|
|
t.Errorf("Expected operation %s, got %s", op, decodedReq.Operation)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestUpdateArgsWithNilValues(t *testing.T) {
|
|
title := "New Title"
|
|
args := UpdateArgs{
|
|
ID: "bd-1",
|
|
Title: &title,
|
|
}
|
|
|
|
argsJSON, err := json.Marshal(args)
|
|
if err != nil {
|
|
t.Fatalf("Failed to marshal args: %v", err)
|
|
}
|
|
|
|
var decodedArgs UpdateArgs
|
|
if err := json.Unmarshal(argsJSON, &decodedArgs); err != nil {
|
|
t.Fatalf("Failed to unmarshal args: %v", err)
|
|
}
|
|
|
|
if decodedArgs.Title == nil {
|
|
t.Errorf("Expected title to be non-nil")
|
|
} else if *decodedArgs.Title != title {
|
|
t.Errorf("Expected title %s, got %s", title, *decodedArgs.Title)
|
|
}
|
|
|
|
if decodedArgs.Status != nil {
|
|
t.Errorf("Expected status to be nil, got %v", *decodedArgs.Status)
|
|
}
|
|
}
|