a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
242 lines
7.2 KiB
Go
242 lines
7.2 KiB
Go
package eval
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/mark3labs/mcp-go/mcp"
|
|
mcpserver "github.com/mark3labs/mcp-go/server"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/zzet/gortex/internal/graph"
|
|
"github.com/zzet/gortex/internal/server"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// TestEvalServerLifecycle is an integration test that exercises the full
|
|
// eval-server HTTP lifecycle: start → health check → tool call → stats → shutdown.
|
|
func TestEvalServerLifecycle(t *testing.T) {
|
|
// --- Setup: build a handler with a graph and a registered tool ---
|
|
g := graph.New()
|
|
g.AddNode(&graph.Node{
|
|
ID: "main.go::Main",
|
|
Kind: graph.KindFunction,
|
|
Name: "Main",
|
|
FilePath: "main.go",
|
|
Language: "go",
|
|
})
|
|
g.AddNode(&graph.Node{
|
|
ID: "main.go::Helper",
|
|
Kind: graph.KindFunction,
|
|
Name: "Helper",
|
|
FilePath: "main.go",
|
|
Language: "go",
|
|
})
|
|
g.AddEdge(&graph.Edge{
|
|
From: "main.go::Main",
|
|
To: "main.go::Helper",
|
|
Kind: graph.EdgeCalls,
|
|
})
|
|
|
|
srv := mcpserver.NewMCPServer("gortex-integration", "0.1.0-test",
|
|
mcpserver.WithToolCapabilities(false),
|
|
mcpserver.WithRecovery(),
|
|
)
|
|
srv.AddTool(
|
|
mcp.NewTool("echo",
|
|
mcp.WithDescription("Echo tool for integration testing"),
|
|
mcp.WithString("message", mcp.Description("Message to echo")),
|
|
),
|
|
func(_ context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
|
args := req.GetArguments()
|
|
msg, _ := args["message"].(string)
|
|
if msg == "" {
|
|
msg = "empty"
|
|
}
|
|
return mcp.NewToolResultText("echo: " + msg), nil
|
|
},
|
|
)
|
|
|
|
logger := zap.NewNop()
|
|
handler := NewHandler(srv, g, "0.1.0-test", logger)
|
|
|
|
// --- Start: use httptest.NewServer for a real HTTP server ---
|
|
ts := httptest.NewServer(handler)
|
|
defer ts.Close()
|
|
|
|
client := ts.Client()
|
|
|
|
// --- Step 1: Health check ---
|
|
t.Run("health_check", func(t *testing.T) {
|
|
resp, err := client.Get(ts.URL + "/v1/health")
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
assert.Equal(t, "application/json", resp.Header.Get("Content-Type"))
|
|
|
|
var health server.HealthResponse
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&health))
|
|
|
|
assert.Equal(t, "ok", health.Status)
|
|
assert.True(t, health.Indexed, "graph has nodes so indexed should be true")
|
|
assert.Equal(t, 2, health.Nodes)
|
|
assert.Equal(t, 1, health.Edges)
|
|
assert.Equal(t, "0.1.0-test", health.Version)
|
|
assert.Greater(t, health.UptimeSeconds, float64(0))
|
|
})
|
|
|
|
// --- Step 2: Tool call (echo) ---
|
|
t.Run("tool_call_echo", func(t *testing.T) {
|
|
body := `{"arguments":{"message":"integration test"}}`
|
|
resp, err := client.Post(
|
|
ts.URL+"/v1/tools/echo",
|
|
"application/json",
|
|
strings.NewReader(body),
|
|
)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
var toolResp server.ToolResponse
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&toolResp))
|
|
|
|
assert.False(t, toolResp.IsError)
|
|
require.Len(t, toolResp.Content, 1)
|
|
assert.Equal(t, "text", toolResp.Content[0].Type)
|
|
assert.Contains(t, toolResp.Content[0].Text, "integration test")
|
|
})
|
|
|
|
// --- Step 3: Stats endpoint ---
|
|
t.Run("stats", func(t *testing.T) {
|
|
resp, err := client.Get(ts.URL + "/v1/stats")
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
var stats server.StatsResponse
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&stats))
|
|
|
|
assert.Equal(t, 2, stats.TotalNodes)
|
|
assert.Equal(t, 1, stats.TotalEdges)
|
|
assert.NotNil(t, stats.ByKind)
|
|
assert.NotNil(t, stats.ByLanguage)
|
|
})
|
|
|
|
// --- Step 4: Unknown tool returns 404 ---
|
|
t.Run("unknown_tool_404", func(t *testing.T) {
|
|
body := `{"arguments":{}}`
|
|
resp, err := client.Post(
|
|
ts.URL+"/v1/tools/nonexistent",
|
|
"application/json",
|
|
strings.NewReader(body),
|
|
)
|
|
require.NoError(t, err)
|
|
defer resp.Body.Close()
|
|
|
|
assert.Equal(t, http.StatusNotFound, resp.StatusCode)
|
|
})
|
|
|
|
// --- Shutdown is implicit: ts.Close() in defer ---
|
|
}
|
|
|
|
// TestIndexCacheRoundTrip is an integration test that exercises the full
|
|
// cache lifecycle: create files → store → load → verify integrity → evict.
|
|
func TestIndexCacheRoundTrip(t *testing.T) {
|
|
cacheDir := t.TempDir()
|
|
version := "0.2.0-test"
|
|
|
|
cache, err := NewCache(cacheDir, version)
|
|
require.NoError(t, err)
|
|
|
|
// --- Step 1: Create a realistic index directory with multiple files ---
|
|
indexDir := t.TempDir()
|
|
require.NoError(t, os.WriteFile(
|
|
filepath.Join(indexDir, "graph.bin"),
|
|
[]byte("serialized-graph-data-with-nodes-and-edges"),
|
|
0o644,
|
|
))
|
|
// Create a nested directory simulating search.bleve/
|
|
bleveDir := filepath.Join(indexDir, "search.bleve")
|
|
require.NoError(t, os.MkdirAll(bleveDir, 0o755))
|
|
require.NoError(t, os.WriteFile(
|
|
filepath.Join(bleveDir, "store"),
|
|
[]byte("bleve-store-data"),
|
|
0o644,
|
|
))
|
|
require.NoError(t, os.WriteFile(
|
|
filepath.Join(bleveDir, "index_meta.json"),
|
|
[]byte(`{"version":1}`),
|
|
0o644,
|
|
))
|
|
|
|
repo := "django/django"
|
|
commit := "a1b2c3d4e5f6"
|
|
|
|
// --- Step 2: Verify cache is empty initially ---
|
|
assert.False(t, cache.Check(repo, commit), "cache should be empty initially")
|
|
|
|
// --- Step 3: Store the index ---
|
|
require.NoError(t, cache.Store(repo, commit, indexDir))
|
|
|
|
// --- Step 4: Verify cache entry exists ---
|
|
assert.True(t, cache.Check(repo, commit), "cache should have entry after store")
|
|
|
|
// --- Step 5: Load and verify path ---
|
|
loadedPath, err := cache.Load(repo, commit)
|
|
require.NoError(t, err)
|
|
expectedPath := filepath.Join(cacheDir, CacheKey(repo, commit))
|
|
assert.Equal(t, expectedPath, loadedPath)
|
|
|
|
// --- Step 6: Verify all files are intact ---
|
|
graphData, err := os.ReadFile(filepath.Join(loadedPath, "graph.bin"))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "serialized-graph-data-with-nodes-and-edges", string(graphData))
|
|
|
|
bleveStore, err := os.ReadFile(filepath.Join(loadedPath, "search.bleve", "store"))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "bleve-store-data", string(bleveStore))
|
|
|
|
bleveMeta, err := os.ReadFile(filepath.Join(loadedPath, "search.bleve", "index_meta.json"))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, `{"version":1}`, string(bleveMeta))
|
|
|
|
// --- Step 7: Validate version compatibility ---
|
|
assert.True(t, cache.Validate(repo, commit), "version should match")
|
|
|
|
// --- Step 8: Version mismatch detection ---
|
|
cacheV2, err := NewCache(cacheDir, "0.3.0-different")
|
|
require.NoError(t, err)
|
|
assert.False(t, cacheV2.Validate(repo, commit), "different version should fail validation")
|
|
|
|
// --- Step 9: Re-store with updated content ---
|
|
indexDir2 := t.TempDir()
|
|
require.NoError(t, os.WriteFile(
|
|
filepath.Join(indexDir2, "graph.bin"),
|
|
[]byte("updated-graph-data"),
|
|
0o644,
|
|
))
|
|
require.NoError(t, cache.Store(repo, commit, indexDir2))
|
|
|
|
loadedPath2, err := cache.Load(repo, commit)
|
|
require.NoError(t, err)
|
|
updatedData, err := os.ReadFile(filepath.Join(loadedPath2, "graph.bin"))
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "updated-graph-data", string(updatedData))
|
|
|
|
// --- Step 10: Evict and verify removal ---
|
|
require.NoError(t, cache.Evict(repo, commit))
|
|
assert.False(t, cache.Check(repo, commit), "cache should be empty after eviction")
|
|
|
|
_, err = cache.Load(repo, commit)
|
|
assert.Error(t, err, "load after eviction should fail")
|
|
}
|