package mcp_test import ( "context" "encoding/json" "net/http" "net/http/httptest" "os" "path/filepath" "testing" "github.com/mathiasbq/hyperguild/ingestion/internal/mcp" "github.com/mathiasbq/hyperguild/ingestion/internal/pipeline" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func mockAnswerLLM(response string) pipeline.CompleteFunc { return func(_ context.Context, _, _ string) (string, error) { return response, nil } } func brainDirWithContent(t *testing.T) string { t.Helper() dir := t.TempDir() wikiDir := filepath.Join(dir, "wiki") require.NoError(t, os.MkdirAll(wikiDir, 0o755)) require.NoError(t, os.WriteFile(filepath.Join(wikiDir, "test.md"), []byte( "---\ntitle: Pass-rate Logging\ntype: spec\n---\n\nPass-rate logging tracks skill invocations.", ), 0o644)) return dir } func callTool(t *testing.T, ts *httptest.Server, name string, arguments map[string]any) map[string]any { t.Helper() req := map[string]any{ "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": map[string]any{"name": name, "arguments": arguments}, } resp, err := http.Post(ts.URL, "application/json", body(t, req)) require.NoError(t, err) defer resp.Body.Close() //nolint:errcheck var out map[string]any require.NoError(t, json.NewDecoder(resp.Body).Decode(&out)) return out } func TestBrainAnswer_NoLLM(t *testing.T) { srv := mcp.NewServer(t.TempDir(), nil, nil, nil) ts := httptest.NewServer(srv) defer ts.Close() rpc := callTool(t, ts, "brain_answer", map[string]any{"query": "test"}) assert.NotNil(t, rpc["error"], "expected error when answerLLM is nil") } func TestBrainAnswer_Synthesizes(t *testing.T) { brainDir := brainDirWithContent(t) srv := mcp.NewServer(brainDir, nil, nil, mockAnswerLLM("Pass-rate logging is described in spec.")) ts := httptest.NewServer(srv) defer ts.Close() rpc := callTool(t, ts, "brain_answer", map[string]any{"query": "pass-rate logging"}) require.Nil(t, rpc["error"]) content := rpc["result"].(map[string]any)["content"].([]any)[0].(map[string]any)["text"].(string) var result map[string]any require.NoError(t, json.Unmarshal([]byte(content), &result)) assert.Equal(t, "Pass-rate logging is described in spec.", result["answer"]) assert.NotEmpty(t, result["sources"]) } func TestBrainClassify_ReturnsJSON(t *testing.T) { llmResp := `{"type":"spec","title":"My Spec","tags":["go","mcp"]}` srv := mcp.NewServer(t.TempDir(), nil, nil, mockAnswerLLM(llmResp)) ts := httptest.NewServer(srv) defer ts.Close() rpc := callTool(t, ts, "brain_classify", map[string]any{"text": "# My Spec\n\nThis is a Go MCP spec."}) require.Nil(t, rpc["error"]) content := rpc["result"].(map[string]any)["content"].([]any)[0].(map[string]any)["text"].(string) var result map[string]any require.NoError(t, json.Unmarshal([]byte(content), &result)) assert.Equal(t, "spec", result["type"]) assert.Equal(t, "My Spec", result["title"]) } func TestBrainClassify_StripsFences(t *testing.T) { llmResp := "```json\n{\"type\":\"note\",\"title\":\"T\",\"tags\":[]}\n```" srv := mcp.NewServer(t.TempDir(), nil, nil, mockAnswerLLM(llmResp)) ts := httptest.NewServer(srv) defer ts.Close() rpc := callTool(t, ts, "brain_classify", map[string]any{"text": "some text"}) require.Nil(t, rpc["error"]) content := rpc["result"].(map[string]any)["content"].([]any)[0].(map[string]any)["text"].(string) var result map[string]any require.NoError(t, json.Unmarshal([]byte(content), &result)) assert.Equal(t, "note", result["type"]) }