// SPDX-License-Identifier: EUPL-1.2 package brain import ( "context" "net/http" "net/http/httptest" "testing" core "dappco.re/go/core" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // localDirect returns a DirectSubsystem that never hits the network. // Suitable for tests that validate input before making API calls. func localDirect() *DirectSubsystem { return &DirectSubsystem{apiURL: "http://localhost", apiKey: "test-key"} } // --- sendMessage --- func TestMessaging_SendMessage_Good(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "POST", r.Method) assert.Equal(t, "/v1/messages/send", r.URL.Path) var body map[string]any core.JSONUnmarshalString(core.ReadAll(r.Body).Value.(string), &body) assert.Equal(t, "charon", body["to"]) assert.Equal(t, "deploy complete", body["content"]) assert.Equal(t, "status update", body["subject"]) w.Header().Set("Content-Type", "application/json") w.Write([]byte(core.JSONMarshalString(map[string]any{ "data": map[string]any{"id": float64(42)}, }))) })) defer srv.Close() _, out, err := newTestDirect(srv).sendMessage(context.Background(), nil, SendInput{ To: "charon", Content: "deploy complete", Subject: "status update", }) require.NoError(t, err) assert.True(t, out.Success) assert.Equal(t, 42, out.ID) assert.Equal(t, "charon", out.To) } func TestMessaging_SendMessage_Bad_EmptyTo(t *testing.T) { _, _, err := localDirect().sendMessage(context.Background(), nil, SendInput{ To: "", Content: "hello", }) require.Error(t, err) assert.Contains(t, err.Error(), "to and content are required") } func TestMessaging_SendMessage_Bad_EmptyContent(t *testing.T) { _, _, err := localDirect().sendMessage(context.Background(), nil, SendInput{ To: "charon", Content: "", }) require.Error(t, err) assert.Contains(t, err.Error(), "to and content are required") } func TestMessaging_SendMessage_Bad_APIError(t *testing.T) { srv := httptest.NewServer(errorHandler(http.StatusInternalServerError, `{"error":"queue full"}`)) defer srv.Close() _, out, err := newTestDirect(srv).sendMessage(context.Background(), nil, SendInput{ To: "charon", Content: "hello", }) require.Error(t, err) assert.False(t, out.Success) } // --- inbox --- func TestMessaging_Inbox_Good_WithMessages(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "GET", r.Method) assert.Contains(t, r.URL.Path, "/v1/messages/inbox") w.Header().Set("Content-Type", "application/json") w.Write([]byte(core.JSONMarshalString(map[string]any{ "data": []any{ map[string]any{ "workspace_id": float64(3), "id": float64(1), "from_agent": "charon", "to_agent": "cladius", "subject": "status", "content": "deploy done", "read": true, "read_at": "2026-03-10T12:05:00Z", "created_at": "2026-03-10T12:00:00Z", }, map[string]any{ "id": float64(2), "from": "clotho", "to": "cladius", "subject": "review", "content": "PR ready", "read": false, "created_at": "2026-03-10T13:00:00Z", }, }, }))) })) defer srv.Close() _, out, err := newTestDirect(srv).inbox(context.Background(), nil, InboxInput{Agent: "cladius"}) require.NoError(t, err) assert.True(t, out.Success) require.Len(t, out.Messages, 2) assert.Equal(t, 1, out.Messages[0].ID) assert.Equal(t, 3, out.Messages[0].WorkspaceID) assert.Equal(t, "charon", out.Messages[0].From) assert.Equal(t, "charon", out.Messages[0].FromAgent) assert.Equal(t, "cladius", out.Messages[0].ToAgent) assert.Equal(t, "deploy done", out.Messages[0].Content) assert.True(t, out.Messages[0].Read) assert.Equal(t, "2026-03-10T12:05:00Z", out.Messages[0].ReadAt) assert.Equal(t, 2, out.Messages[1].ID) assert.False(t, out.Messages[1].Read) } func TestMessaging_Inbox_Good_EmptyInbox(t *testing.T) { srv := httptest.NewServer(jsonHandler(map[string]any{"data": []any{}})) defer srv.Close() _, out, err := newTestDirect(srv).inbox(context.Background(), nil, InboxInput{Agent: "cladius"}) require.NoError(t, err) assert.True(t, out.Success) assert.Empty(t, out.Messages) } func TestMessaging_Inbox_Bad_APIError(t *testing.T) { srv := httptest.NewServer(errorHandler(http.StatusInternalServerError, `{"error":"db down"}`)) defer srv.Close() _, out, err := newTestDirect(srv).inbox(context.Background(), nil, InboxInput{}) require.Error(t, err) assert.False(t, out.Success) } // --- conversation --- func TestMessaging_Conversation_Good(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "GET", r.Method) assert.Contains(t, r.URL.Path, "/v1/messages/conversation/charon") w.Header().Set("Content-Type", "application/json") w.Write([]byte(core.JSONMarshalString(map[string]any{ "data": []any{ map[string]any{ "id": float64(10), "from": "cladius", "to": "charon", "content": "how is the deploy?", "created_at": "2026-03-10T12:00:00Z", }, map[string]any{ "id": float64(11), "from": "charon", "to": "cladius", "content": "all green", "created_at": "2026-03-10T12:01:00Z", }, }, }))) })) defer srv.Close() _, out, err := newTestDirect(srv).conversation(context.Background(), nil, ConversationInput{Agent: "charon"}) require.NoError(t, err) assert.True(t, out.Success) require.Len(t, out.Messages, 2) assert.Equal(t, "how is the deploy?", out.Messages[0].Content) assert.Equal(t, "all green", out.Messages[1].Content) } func TestMessaging_Conversation_Bad_EmptyAgent(t *testing.T) { _, _, err := localDirect().conversation(context.Background(), nil, ConversationInput{Agent: ""}) require.Error(t, err) assert.Contains(t, err.Error(), "agent is required") } func TestMessaging_Conversation_Bad_APIError(t *testing.T) { srv := httptest.NewServer(errorHandler(http.StatusNotFound, `{"error":"agent not found"}`)) defer srv.Close() _, out, err := newTestDirect(srv).conversation(context.Background(), nil, ConversationInput{Agent: "nonexistent"}) require.Error(t, err) assert.False(t, out.Success) } // --- parseMessages --- func TestMessaging_ParseMessages_Good(t *testing.T) { result := map[string]any{ "data": []any{ map[string]any{ "id": float64(5), "workspace_id": float64(8), "from": "alice", "to": "bob", "subject": "hello", "content": "hi there", "read": true, "read_at": "2026-03-10T10:01:00Z", "created_at": "2026-03-10T10:00:00Z", }, }, } msgs := parseMessages(result) require.Len(t, msgs, 1) assert.Equal(t, 5, msgs[0].ID) assert.Equal(t, 8, msgs[0].WorkspaceID) assert.Equal(t, "alice", msgs[0].From) assert.Equal(t, "bob", msgs[0].To) assert.Equal(t, "hello", msgs[0].Subject) assert.Equal(t, "hi there", msgs[0].Content) assert.True(t, msgs[0].Read) assert.Equal(t, "2026-03-10T10:01:00Z", msgs[0].ReadAt) assert.Equal(t, "2026-03-10T10:00:00Z", msgs[0].CreatedAt) } func TestMessaging_ParseMessages_Good_EmptyData(t *testing.T) { msgs := parseMessages(map[string]any{"data": []any{}}) assert.Empty(t, msgs) } func TestMessaging_ParseMessages_Good_NoDataKey(t *testing.T) { msgs := parseMessages(map[string]any{"other": "value"}) assert.Empty(t, msgs) } func TestMessaging_ParseMessages_Good_NilResult(t *testing.T) { assert.Empty(t, parseMessages(nil)) } // --- toInt --- func TestMessaging_ToInt_Good_Float64(t *testing.T) { assert.Equal(t, 42, toInt(float64(42))) } func TestMessaging_ToInt_Good_Zero(t *testing.T) { assert.Equal(t, 0, toInt(float64(0))) } func TestMessaging_ToInt_Bad_String(t *testing.T) { assert.Equal(t, 0, toInt("not a number")) } func TestMessaging_ToInt_Bad_Nil(t *testing.T) { assert.Equal(t, 0, toInt(nil)) } func TestMessaging_ToInt_Bad_Int(t *testing.T) { // Go JSON decode always uses float64, so int returns 0. assert.Equal(t, 0, toInt(42)) } // --- Messaging struct round-trips --- func TestMessaging_SendInput_Good_RoundTrip(t *testing.T) { in := SendInput{To: "charon", Content: "hello", Subject: "test"} var out SendInput roundTrip(t, in, &out) assert.Equal(t, in, out) } func TestMessaging_SendOutput_Good_RoundTrip(t *testing.T) { in := SendOutput{Success: true, ID: 42, To: "charon"} var out SendOutput roundTrip(t, in, &out) assert.Equal(t, in, out) } func TestMessaging_InboxOutput_Good_RoundTrip(t *testing.T) { in := InboxOutput{ Success: true, Messages: []MessageItem{ {ID: 1, WorkspaceID: 7, FromAgent: "a", ToAgent: "b", Content: "hi", Read: false, CreatedAt: "2026-03-10T12:00:00Z"}, }, } var out InboxOutput roundTrip(t, in, &out) assert.Equal(t, in.Success, out.Success) require.Len(t, out.Messages, 1) assert.Equal(t, 7, out.Messages[0].WorkspaceID) assert.Equal(t, "a", out.Messages[0].FromAgent) } func TestMessaging_ConversationOutput_Good_RoundTrip(t *testing.T) { in := ConversationOutput{ Success: true, Messages: []MessageItem{ {ID: 10, From: "x", To: "y", Content: "thread", Read: true, ReadAt: "2026-03-10T14:01:00Z", CreatedAt: "2026-03-10T14:00:00Z"}, }, } var out ConversationOutput roundTrip(t, in, &out) assert.True(t, out.Success) require.Len(t, out.Messages, 1) }