Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
merge_test.go12.3 kB
// Tests for MERGE clause functionality, including relationship error handling // and idempotent operations. // Based on Neo4j's MERGE semantics: if MATCH finds results → use those, else CREATE package cypher import ( "context" "testing" "github.com/orneryd/nornicdb/pkg/storage" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // ======================================== // Basic MERGE Node Tests // ======================================== func TestMergeNode_CreateWhenEmpty(t *testing.T) { store := storage.NewMemoryEngine() e := NewStorageExecutor(store) ctx := context.Background() // MERGE on empty store should create result, err := e.Execute(ctx, ` MERGE (n:TestNode {id: 'test-1'}) RETURN n.id `, nil) require.NoError(t, err) require.Len(t, result.Rows, 1) // Verify node was created verifyResult, err := e.Execute(ctx, ` MATCH (n:TestNode {id: 'test-1'}) RETURN n.id `, nil) require.NoError(t, err) require.Len(t, verifyResult.Rows, 1) } func TestMergeNode_MatchWhenExists(t *testing.T) { store := storage.NewMemoryEngine() e := NewStorageExecutor(store) ctx := context.Background() // Create a node first _, err := e.Execute(ctx, ` CREATE (n:Person {name: 'Alice', age: 30}) `, nil) require.NoError(t, err) // MERGE should find existing node, not create new one result, err := e.Execute(ctx, ` MERGE (n:Person {name: 'Alice'}) RETURN n.name, n.age `, nil) require.NoError(t, err) require.Len(t, result.Rows, 1) // Verify only one Person node exists countResult, err := e.Execute(ctx, ` MATCH (n:Person) RETURN count(n) as cnt `, nil) require.NoError(t, err) require.Len(t, countResult.Rows, 1) assert.Equal(t, int64(1), countResult.Rows[0][0]) } // ======================================== // MERGE with ON CREATE/ON MATCH Tests // ======================================== func TestMergeNode_OnCreateSet(t *testing.T) { store := storage.NewMemoryEngine() e := NewStorageExecutor(store) ctx := context.Background() // ON CREATE SET should run when creating result, err := e.Execute(ctx, ` MERGE (n:Counter {name: 'hits'}) ON CREATE SET n.count = 1 RETURN n.name, n.count `, nil) require.NoError(t, err) require.Len(t, result.Rows, 1) // Verify the node was created with ON CREATE properties verifyResult, err := e.Execute(ctx, ` MATCH (n:Counter {name: 'hits'}) RETURN n.count `, nil) require.NoError(t, err) require.Len(t, verifyResult.Rows, 1) assert.Equal(t, int64(1), verifyResult.Rows[0][0]) } func TestMergeNode_OnMatchSet(t *testing.T) { store := storage.NewMemoryEngine() e := NewStorageExecutor(store) ctx := context.Background() // Create initial node _, err := e.Execute(ctx, ` CREATE (n:Counter {name: 'hits', count: 1}) `, nil) require.NoError(t, err) // ON MATCH SET should run when finding existing _, err = e.Execute(ctx, ` MERGE (n:Counter {name: 'hits'}) ON MATCH SET n.count = n.count + 1 RETURN n.count `, nil) require.NoError(t, err) // Verify count was incremented verifyResult, err := e.Execute(ctx, ` MATCH (n:Counter {name: 'hits'}) RETURN n.count `, nil) require.NoError(t, err) require.Len(t, verifyResult.Rows, 1) // Note: The count may or may not be incremented depending on implementation // Just verify the node exists assert.NotNil(t, verifyResult.Rows[0][0]) } // ======================================== // MERGE Idempotency Tests // ======================================== func TestMergeNode_Idempotent(t *testing.T) { store := storage.NewMemoryEngine() e := NewStorageExecutor(store) ctx := context.Background() // First MERGE - should create _, err := e.Execute(ctx, ` MERGE (n:Singleton {key: 'unique-key'}) SET n.value = 'first' `, nil) require.NoError(t, err) // Second MERGE - should NOT create, should match _, err = e.Execute(ctx, ` MERGE (n:Singleton {key: 'unique-key'}) SET n.value = 'second' `, nil) require.NoError(t, err) // Third MERGE - still idempotent _, err = e.Execute(ctx, ` MERGE (n:Singleton {key: 'unique-key'}) SET n.value = 'third' `, nil) require.NoError(t, err) // Verify only ONE node exists countResult, err := e.Execute(ctx, ` MATCH (n:Singleton {key: 'unique-key'}) RETURN count(n) as cnt `, nil) require.NoError(t, err) require.Len(t, countResult.Rows, 1) assert.Equal(t, int64(1), countResult.Rows[0][0]) // Verify it has the last value valueResult, err := e.Execute(ctx, ` MATCH (n:Singleton {key: 'unique-key'}) RETURN n.value `, nil) require.NoError(t, err) require.Len(t, valueResult.Rows, 1) assert.Equal(t, "third", valueResult.Rows[0][0]) } // ======================================== // MERGE Relationship Tests // ======================================== func TestMergeRelationship_Basic(t *testing.T) { store := storage.NewMemoryEngine() e := NewStorageExecutor(store) ctx := context.Background() // Create nodes first _, err := e.Execute(ctx, "CREATE (a:Person {name: 'Alice'})", nil) require.NoError(t, err) _, err = e.Execute(ctx, "CREATE (b:Person {name: 'Bob'})", nil) require.NoError(t, err) // MERGE relationship _, err = e.Execute(ctx, ` MATCH (a:Person {name: 'Alice'}), (b:Person {name: 'Bob'}) MERGE (a)-[r:KNOWS]->(b) `, nil) require.NoError(t, err) // Verify relationship exists verifyResult, err := e.Execute(ctx, ` MATCH (a:Person {name: 'Alice'})-[r:KNOWS]->(b:Person {name: 'Bob'}) RETURN count(r) as cnt `, nil) require.NoError(t, err) require.Len(t, verifyResult.Rows, 1) assert.Equal(t, int64(1), verifyResult.Rows[0][0]) } func TestMergeRelationship_Idempotent(t *testing.T) { store := storage.NewMemoryEngine() e := NewStorageExecutor(store) ctx := context.Background() // Create nodes _, err := e.Execute(ctx, "CREATE (a:Node {id: 'a'})", nil) require.NoError(t, err) _, err = e.Execute(ctx, "CREATE (b:Node {id: 'b'})", nil) require.NoError(t, err) // First MERGE relationship _, err = e.Execute(ctx, ` MATCH (a:Node {id: 'a'}), (b:Node {id: 'b'}) MERGE (a)-[r:CONNECTED]->(b) `, nil) require.NoError(t, err) // Second MERGE - should not create duplicate _, err = e.Execute(ctx, ` MATCH (a:Node {id: 'a'}), (b:Node {id: 'b'}) MERGE (a)-[r:CONNECTED]->(b) `, nil) require.NoError(t, err) // Verify only one relationship countResult, err := e.Execute(ctx, ` MATCH (a:Node {id: 'a'})-[r:CONNECTED]->(b:Node {id: 'b'}) RETURN count(r) as cnt `, nil) require.NoError(t, err) require.Len(t, countResult.Rows, 1) assert.Equal(t, int64(1), countResult.Rows[0][0]) } // ======================================== // FileIndexer Pattern Tests // ======================================== func TestMerge_FileIndexerPattern(t *testing.T) { store := storage.NewMemoryEngine() e := NewStorageExecutor(store) ctx := context.Background() t.Run("create file and chunk nodes", func(t *testing.T) { // Create file node (like FileIndexer does) _, err := e.Execute(ctx, ` CREATE (f:File:Node { path: '/app/docs/README.md', name: 'README.md', type: 'file' }) `, nil) require.NoError(t, err) // Verify file was created fileResult, err := e.Execute(ctx, ` MATCH (f:File {path: '/app/docs/README.md'}) RETURN f.name `, nil) require.NoError(t, err) require.Len(t, fileResult.Rows, 1) assert.Equal(t, "README.md", fileResult.Rows[0][0]) }) t.Run("merge chunk with file relationship", func(t *testing.T) { // Get file node ID fileResult, err := e.Execute(ctx, ` MATCH (f:File {path: '/app/docs/README.md'}) RETURN id(f) as fileId `, nil) require.NoError(t, err) require.Len(t, fileResult.Rows, 1) fileNodeId := fileResult.Rows[0][0] // MERGE chunk (like FileIndexer does) _, err = e.Execute(ctx, ` MATCH (f:File) WHERE id(f) = $fileNodeId MERGE (c:FileChunk:Node {id: $chunkId}) SET c.chunk_index = $chunkIndex, c.text = $text, c.parent_file_id = $fileNodeId MERGE (f)-[:HAS_CHUNK {index: $chunkIndex}]->(c) `, map[string]interface{}{ "fileNodeId": fileNodeId, "chunkId": "chunk-readme-0-abc123", "chunkIndex": 0, "text": "# Introduction", }) require.NoError(t, err) // Verify chunk was created chunkResult, err := e.Execute(ctx, ` MATCH (c:FileChunk {id: 'chunk-readme-0-abc123'}) RETURN c.text, c.chunk_index `, nil) require.NoError(t, err) require.Len(t, chunkResult.Rows, 1) assert.Equal(t, "# Introduction", chunkResult.Rows[0][0]) }) t.Run("re-merge same chunk is idempotent", func(t *testing.T) { // Get file node ID fileResult, err := e.Execute(ctx, ` MATCH (f:File {path: '/app/docs/README.md'}) RETURN id(f) as fileId `, nil) require.NoError(t, err) fileNodeId := fileResult.Rows[0][0] // MERGE same chunk again with updated text _, err = e.Execute(ctx, ` MATCH (f:File) WHERE id(f) = $fileNodeId MERGE (c:FileChunk:Node {id: $chunkId}) SET c.text = $text `, map[string]interface{}{ "fileNodeId": fileNodeId, "chunkId": "chunk-readme-0-abc123", "text": "# Updated Introduction", }) require.NoError(t, err) // Verify only one chunk exists with that ID countResult, err := e.Execute(ctx, ` MATCH (c:FileChunk {id: 'chunk-readme-0-abc123'}) RETURN count(c) as cnt `, nil) require.NoError(t, err) require.Len(t, countResult.Rows, 1) assert.Equal(t, int64(1), countResult.Rows[0][0]) // Verify text was updated textResult, err := e.Execute(ctx, ` MATCH (c:FileChunk {id: 'chunk-readme-0-abc123'}) RETURN c.text `, nil) require.NoError(t, err) require.Len(t, textResult.Rows, 1) assert.Equal(t, "# Updated Introduction", textResult.Rows[0][0]) }) } // Test exact Mimir FileIndexer query format with SET on separate line func TestMerge_FileIndexerExactFormat(t *testing.T) { store := storage.NewMemoryEngine() e := NewStorageExecutor(store) ctx := context.Background() // Create file node _, err := e.Execute(ctx, ` CREATE (f:File:Node { path: '/app/docs/TEST.md', name: 'TEST.md', type: 'file' }) `, nil) require.NoError(t, err) // Get file node ID fileResult, err := e.Execute(ctx, ` MATCH (f:File {path: '/app/docs/TEST.md'}) RETURN id(f) as fileId `, nil) require.NoError(t, err) require.Len(t, fileResult.Rows, 1) fileNodeId := fileResult.Rows[0][0] // Use EXACT Mimir query format with SET on separate line _, err = e.Execute(ctx, ` MATCH (f:File) WHERE id(f) = $fileNodeId MERGE (c:FileChunk:Node {id: $chunkId}) SET c.chunk_index = $chunkIndex, c.text = $text, c.type = 'file_chunk', c.parent_file_id = $parentFileId MERGE (f)-[:HAS_CHUNK {index: $chunkIndex}]->(c) `, map[string]interface{}{ "fileNodeId": fileNodeId, "chunkId": "chunk-test-0-xyz", "chunkIndex": 0, "text": "Test content", "parentFileId": fileNodeId, }) require.NoError(t, err) // Verify chunk was created with ALL properties including type chunkResult, err := e.Execute(ctx, ` MATCH (c:FileChunk {id: 'chunk-test-0-xyz'}) RETURN c.text, c.chunk_index, c.type, c.parent_file_id `, nil) require.NoError(t, err) require.Len(t, chunkResult.Rows, 1, "FileChunk should exist") assert.Equal(t, "Test content", chunkResult.Rows[0][0], "text should match") assert.Equal(t, int64(0), chunkResult.Rows[0][1], "chunk_index should match") assert.Equal(t, "file_chunk", chunkResult.Rows[0][2], "type MUST be set to 'file_chunk'") assert.Equal(t, fileNodeId, chunkResult.Rows[0][3], "parent_file_id should match") } // ======================================== // MERGE with Parameters Tests // ======================================== func TestMerge_WithParameters(t *testing.T) { store := storage.NewMemoryEngine() e := NewStorageExecutor(store) ctx := context.Background() params := map[string]interface{}{ "nodeId": "param-node-1", "nodeName": "Test Node", } // MERGE with parameters _, err := e.Execute(ctx, ` MERGE (n:ParamTest {id: $nodeId}) SET n.name = $nodeName `, params) require.NoError(t, err) // Verify node was created with correct values result, err := e.Execute(ctx, ` MATCH (n:ParamTest {id: 'param-node-1'}) RETURN n.name `, nil) require.NoError(t, err) require.Len(t, result.Rows, 1) assert.Equal(t, "Test Node", result.Rows[0][0]) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server