Skip to content

Commit 2f1e16e

Browse files
committed
test(source/falkordb): migrate integration tests to MCP endpoint
1 parent 6d46c5f commit 2f1e16e

2 files changed

Lines changed: 306 additions & 102 deletions

File tree

‎tests/falkordb/falkordb_integration_test.go‎

Lines changed: 126 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -125,31 +125,36 @@ func newFalkorDBClient(t *testing.T) *falkordb.FalkorDB {
125125
return client
126126
}
127127

128-
// TestFalkorDBToolEndpoints sets up an integration test server and tests the
129-
// API endpoints of the FalkorDB tools: parameterized cypher, ad-hoc cypher
130-
// execution (including read-only enforcement, dry runs, and graph override),
131-
// schema extraction, and graph listing.
132-
func TestFalkorDBToolEndpoints(t *testing.T) {
133-
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
134-
defer cancel()
128+
// setupFalkorDBInstance points the suite at a FalkorDB instance. It defaults to
129+
// an ephemeral container; the FALKORDB_* environment variables take precedence
130+
// when pointing the suite at an external instance. The package-level
131+
// connection variables are restored on cleanup so each test gets its own
132+
// container.
133+
func setupFalkorDBInstance(ctx context.Context, t *testing.T) {
134+
t.Helper()
135+
origHost, origPort, origGraph := FalkorDBHost, FalkorDBPort, FalkorDBGraph
136+
t.Cleanup(func() {
137+
FalkorDBHost, FalkorDBPort, FalkorDBGraph = origHost, origPort, origGraph
138+
})
135139

136-
// Default to an ephemeral container; the FALKORDB_* environment variables
137-
// take precedence when pointing the suite at an external instance.
138140
if FalkorDBHost == "" {
139141
host, port, cleanup := setupFalkorDBContainer(ctx, t)
140142
t.Cleanup(cleanup)
141143
FalkorDBHost, FalkorDBPort = host, port
142144
FalkorDBGraph = "toolbox_test_graph"
143145
}
146+
}
144147

148+
// getFalkorDBToolsConfig returns the tools file shared by the FalkorDB
149+
// integration tests.
150+
func getFalkorDBToolsConfig(t *testing.T) map[string]any {
151+
t.Helper()
145152
sourceConfig := getFalkorDBVars(t)
146153

147-
args := []string{"--enable-api"}
148-
149154
emptyGraphSourceConfig := getFalkorDBVars(t)
150155
emptyGraphSourceConfig["graph"] = "toolbox_nonexistent_graph"
151156

152-
toolsFile := map[string]any{
157+
return map[string]any{
153158
"sources": map[string]any{
154159
"my-instance": sourceConfig,
155160
"my-empty-instance": emptyGraphSourceConfig,
@@ -208,6 +213,20 @@ func TestFalkorDBToolEndpoints(t *testing.T) {
208213
},
209214
},
210215
}
216+
}
217+
218+
// TestFalkorDBToolEndpoints sets up an integration test server and tests the
219+
// API endpoints of the FalkorDB tools: parameterized cypher, ad-hoc cypher
220+
// execution (including read-only enforcement, dry runs, and graph override),
221+
// schema extraction, and graph listing.
222+
func TestFalkorDBToolEndpoints(t *testing.T) {
223+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
224+
defer cancel()
225+
226+
setupFalkorDBInstance(ctx, t)
227+
228+
args := []string{"--enable-api"}
229+
toolsFile := getFalkorDBToolsConfig(t)
211230

212231
cmd, cleanup, err := tests.StartCmd(ctx, toolsFile, args...)
213232
if err != nil {
@@ -318,41 +337,89 @@ func TestFalkorDBToolEndpoints(t *testing.T) {
318337
}
319338

320339
// Test tool `invoke` endpoints to verify their functionality.
321-
invokeTcs := []struct {
322-
name string
323-
api string
324-
requestBody io.Reader
325-
want string
326-
wantStatus int
327-
prepareData func(t *testing.T)
328-
validateFunc func(t *testing.T, body string)
329-
}{
340+
for _, tc := range getFalkorDBInvokeTestCases() {
341+
t.Run(tc.name, func(t *testing.T) {
342+
if tc.prepareData != nil {
343+
tc.prepareData(t)
344+
}
345+
346+
api := fmt.Sprintf("http://127.0.0.1:5000/api/tool/%s/invoke", tc.toolName)
347+
reqBytes, err := json.Marshal(tc.args)
348+
if err != nil {
349+
t.Fatalf("error marshaling request body: %s", err)
350+
}
351+
resp, err := http.Post(api, "application/json", bytes.NewBuffer(reqBytes))
352+
if err != nil {
353+
t.Fatalf("error when sending a request: %s", err)
354+
}
355+
defer resp.Body.Close()
356+
if resp.StatusCode != http.StatusOK {
357+
bodyBytes, _ := io.ReadAll(resp.Body)
358+
t.Fatalf("response status code: got %d, want %d: %s", resp.StatusCode, http.StatusOK, string(bodyBytes))
359+
}
360+
361+
var body map[string]any
362+
err = json.NewDecoder(resp.Body).Decode(&body)
363+
if err != nil {
364+
t.Fatalf("error parsing response body")
365+
}
366+
got, ok := body["result"].(string)
367+
if !ok {
368+
t.Fatalf("unable to find result in response body")
369+
}
370+
371+
switch {
372+
case tc.wantContentErr != "":
373+
if !strings.Contains(got, tc.wantContentErr) {
374+
t.Errorf("expected %q in body: %s", tc.wantContentErr, got)
375+
}
376+
case tc.validateFunc != nil:
377+
tc.validateFunc(t, got)
378+
case got != tc.want:
379+
t.Fatalf("unexpected value: got %q, want %q", got, tc.want)
380+
}
381+
})
382+
}
383+
}
384+
385+
// falkorDBInvokeTestCase describes one tool invocation shared by the REST and
386+
// MCP FalkorDB integration tests.
387+
type falkorDBInvokeTestCase struct {
388+
name string
389+
toolName string
390+
args map[string]any
391+
want string
392+
wantContentErr string
393+
prepareData func(t *testing.T)
394+
validateFunc func(t *testing.T, body string)
395+
}
396+
397+
// getFalkorDBInvokeTestCases returns the tool invocations exercised by both the
398+
// REST and MCP FalkorDB integration tests.
399+
func getFalkorDBInvokeTestCases() []falkorDBInvokeTestCase {
400+
return []falkorDBInvokeTestCase{
330401
{
331-
name: "invoke my-simple-cypher-tool",
332-
api: "http://127.0.0.1:5000/api/tool/my-simple-cypher-tool/invoke",
333-
requestBody: bytes.NewBuffer([]byte(`{}`)),
334-
want: "[{\"a\":1}]",
335-
wantStatus: http.StatusOK,
402+
name: "invoke my-simple-cypher-tool",
403+
toolName: "my-simple-cypher-tool",
404+
args: map[string]any{},
405+
want: "[{\"a\":1}]",
336406
},
337407
{
338-
name: "invoke my-param-cypher-tool",
339-
api: "http://127.0.0.1:5000/api/tool/my-param-cypher-tool/invoke",
340-
requestBody: bytes.NewBuffer([]byte(`{"value": "hello"}`)),
341-
want: "[{\"echoed\":\"hello\"}]",
342-
wantStatus: http.StatusOK,
408+
name: "invoke my-param-cypher-tool",
409+
toolName: "my-param-cypher-tool",
410+
args: map[string]any{"value": "hello"},
411+
want: "[{\"echoed\":\"hello\"}]",
343412
},
344413
{
345-
name: "invoke my-simple-execute-cypher-tool",
346-
api: "http://127.0.0.1:5000/api/tool/my-simple-execute-cypher-tool/invoke",
347-
requestBody: bytes.NewBuffer([]byte(`{"cypher": "RETURN 1 as a"}`)),
348-
want: "[{\"a\":1}]",
349-
wantStatus: http.StatusOK,
414+
name: "invoke my-simple-execute-cypher-tool",
415+
toolName: "my-simple-execute-cypher-tool",
416+
args: map[string]any{"cypher": "RETURN 1 as a"},
417+
want: "[{\"a\":1}]",
350418
},
351419
{
352-
name: "invoke my-simple-execute-cypher-tool with write query",
353-
api: "http://127.0.0.1:5000/api/tool/my-simple-execute-cypher-tool/invoke",
354-
requestBody: bytes.NewBuffer([]byte(`{"cypher": "CREATE (:WriteTest {x: 1})"}`)),
355-
wantStatus: http.StatusOK,
420+
name: "invoke my-simple-execute-cypher-tool with write query",
421+
toolName: "my-simple-execute-cypher-tool",
422+
args: map[string]any{"cypher": "CREATE (:WriteTest {x: 1})"},
356423
prepareData: func(t *testing.T) {
357424
client := newFalkorDBClient(t)
358425
t.Cleanup(func() {
@@ -374,10 +441,9 @@ func TestFalkorDBToolEndpoints(t *testing.T) {
374441
},
375442
},
376443
{
377-
name: "invoke my-simple-execute-cypher-tool with dry_run",
378-
api: "http://127.0.0.1:5000/api/tool/my-simple-execute-cypher-tool/invoke",
379-
requestBody: bytes.NewBuffer([]byte(`{"cypher": "MATCH (n) RETURN n", "dry_run": true}`)),
380-
wantStatus: http.StatusOK,
444+
name: "invoke my-simple-execute-cypher-tool with dry_run",
445+
toolName: "my-simple-execute-cypher-tool",
446+
args: map[string]any{"cypher": "MATCH (n) RETURN n", "dry_run": true},
381447
validateFunc: func(t *testing.T, body string) {
382448
var result map[string]any
383449
if err := json.Unmarshal([]byte(body), &result); err != nil {
@@ -393,21 +459,15 @@ func TestFalkorDBToolEndpoints(t *testing.T) {
393459
},
394460
},
395461
{
396-
name: "invoke readonly tool with write query",
397-
api: "http://127.0.0.1:5000/api/tool/my-readonly-execute-cypher-tool/invoke",
398-
requestBody: bytes.NewBuffer([]byte(`{"cypher": "CREATE (:ReadOnlyViolation)"}`)),
399-
wantStatus: http.StatusOK,
400-
validateFunc: func(t *testing.T, body string) {
401-
if !strings.Contains(body, "read-only") {
402-
t.Errorf("expected read-only rejection in body: %s", body)
403-
}
404-
},
462+
name: "invoke readonly tool with write query",
463+
toolName: "my-readonly-execute-cypher-tool",
464+
args: map[string]any{"cypher": "CREATE (:ReadOnlyViolation)"},
465+
wantContentErr: "read-only",
405466
},
406467
{
407-
name: "invoke graph override tool against another graph",
408-
api: "http://127.0.0.1:5000/api/tool/my-graph-override-execute-cypher-tool/invoke",
409-
requestBody: bytes.NewBuffer([]byte(fmt.Sprintf(`{"cypher": "CREATE (:OverrideNode {x: 1}) RETURN 1 as ok", "graph": %q}`, "toolbox_override_test"))),
410-
wantStatus: http.StatusOK,
468+
name: "invoke graph override tool against another graph",
469+
toolName: "my-graph-override-execute-cypher-tool",
470+
args: map[string]any{"cypher": "CREATE (:OverrideNode {x: 1}) RETURN 1 as ok", "graph": "toolbox_override_test"},
411471
prepareData: func(t *testing.T) {
412472
client := newFalkorDBClient(t)
413473
t.Cleanup(func() {
@@ -432,10 +492,9 @@ func TestFalkorDBToolEndpoints(t *testing.T) {
432492
},
433493
},
434494
{
435-
name: "invoke my-list-graphs-tool",
436-
api: "http://127.0.0.1:5000/api/tool/my-list-graphs-tool/invoke",
437-
requestBody: bytes.NewBuffer([]byte(`{}`)),
438-
wantStatus: http.StatusOK,
495+
name: "invoke my-list-graphs-tool",
496+
toolName: "my-list-graphs-tool",
497+
args: map[string]any{},
439498
prepareData: func(t *testing.T) {
440499
client := newFalkorDBClient(t)
441500
if _, err := client.SelectGraph(FalkorDBGraph).Query("RETURN 1", nil, nil); err != nil {
@@ -464,10 +523,9 @@ func TestFalkorDBToolEndpoints(t *testing.T) {
464523
},
465524
},
466525
{
467-
name: "invoke schema tool on nonexistent graph",
468-
api: "http://127.0.0.1:5000/api/tool/my-empty-graph-schema-tool/invoke",
469-
requestBody: bytes.NewBuffer([]byte(`{}`)),
470-
wantStatus: http.StatusOK,
526+
name: "invoke schema tool on nonexistent graph",
527+
toolName: "my-empty-graph-schema-tool",
528+
args: map[string]any{},
471529
validateFunc: func(t *testing.T, body string) {
472530
var schema map[string]any
473531
if err := json.Unmarshal([]byte(body), &schema); err != nil {
@@ -486,10 +544,9 @@ func TestFalkorDBToolEndpoints(t *testing.T) {
486544
},
487545
},
488546
{
489-
name: "invoke my-schema-tool with populated data",
490-
api: "http://127.0.0.1:5000/api/tool/my-schema-tool/invoke",
491-
requestBody: bytes.NewBuffer([]byte(`{}`)),
492-
wantStatus: http.StatusOK,
547+
name: "invoke my-schema-tool with populated data",
548+
toolName: "my-schema-tool",
549+
args: map[string]any{},
493550
prepareData: func(t *testing.T) {
494551
client := newFalkorDBClient(t)
495552
graph := client.SelectGraph(FalkorDBGraph)
@@ -581,37 +638,4 @@ func TestFalkorDBToolEndpoints(t *testing.T) {
581638
},
582639
},
583640
}
584-
for _, tc := range invokeTcs {
585-
t.Run(tc.name, func(t *testing.T) {
586-
if tc.prepareData != nil {
587-
tc.prepareData(t)
588-
}
589-
590-
resp, err := http.Post(tc.api, "application/json", tc.requestBody)
591-
if err != nil {
592-
t.Fatalf("error when sending a request: %s", err)
593-
}
594-
defer resp.Body.Close()
595-
if resp.StatusCode != tc.wantStatus {
596-
bodyBytes, _ := io.ReadAll(resp.Body)
597-
t.Fatalf("response status code: got %d, want %d: %s", resp.StatusCode, tc.wantStatus, string(bodyBytes))
598-
}
599-
600-
var body map[string]any
601-
err = json.NewDecoder(resp.Body).Decode(&body)
602-
if err != nil {
603-
t.Fatalf("error parsing response body")
604-
}
605-
got, ok := body["result"].(string)
606-
if !ok {
607-
t.Fatalf("unable to find result in response body")
608-
}
609-
610-
if tc.validateFunc != nil {
611-
tc.validateFunc(t, got)
612-
} else if got != tc.want {
613-
t.Fatalf("unexpected value: got %q, want %q", got, tc.want)
614-
}
615-
})
616-
}
617641
}

0 commit comments

Comments
 (0)