@@ -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