Skip to main content
Glama
orneryd

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

by orneryd
content_escaping_test.go8.25 kB
package cypher import ( "context" "strings" "testing" "github.com/orneryd/nornicdb/pkg/storage" ) // TestContentWithCypherLikeSyntax tests that content containing Cypher-like patterns // is properly handled as string data, not parsed as Cypher commands. // // These test cases come from real-world data that failed migration. func TestContentWithCypherLikeSyntax(t *testing.T) { engine := storage.NewMemoryEngine() executor := NewStorageExecutor(engine) ctx := context.Background() testCases := []struct { name string content string }{ { name: "JSON with arrows", content: `{"overview": {"goal": "Build a blog API"}, "tasks": [{"id": "task-1", "deps": ["task-0"]}]}`, }, { name: "Markdown with arrows", content: `# Guide ## Flow Data -> Processing -> Output User <- Response <- Server`, }, { name: "Cypher query in string", content: `Example query: MATCH (n:User)-[:KNOWS]->(m) RETURN n, m`, }, { name: "CREATE statement in content", content: `To create a node, use: CREATE (n:Person {name: "Alice"})`, }, { name: "MATCH and SET in content", content: `Update pattern: MATCH (n) WHERE n.id = 1 SET n.updated = true`, }, { name: "REMOVE in content", content: `To remove property: MATCH (n) REMOVE n.deprecated`, }, { name: "DELETE in content", content: `Cleanup: MATCH (n:Temp) DELETE n`, }, { name: "Relationship patterns in markdown", content: `## Relationships - User-[:FOLLOWS]->User - Post<-[:WROTE]-User - (a)-[r]->(b)`, }, { name: "Curly braces in JSON", content: `{"props": {"nested": {"key": "value"}}, "array": [1, 2, 3]}`, }, { name: "Parentheses in content", content: `Function call: processData(input) returns (output, error)`, }, { name: "Mixed special chars", content: `Pattern: (a)->[:REL]->(b)<-[:REL2]-(c) with props {x: 1}`, }, { name: "Real orchestration plan excerpt", content: `{ "overview": { "goal": "Build a blog REST API with full CRUD, mock DB, and deploy via Docker/Kubernetes.", "complexity": "Complex", "totalTasks": 10 }, "tasks": [ { "id": "task-0", "title": "Environment Validation", "prompt": "Verify all required dependencies..." } ] }`, }, { name: "Agent preamble with arrows", content: `--- description: Video Game Engineer Agent tools: ['run_terminal_cmd', 'read_file'] --- # Agent Preamble **Flow:** Input -> Process -> Output **Pattern:** User <- Response <- Server`, }, { name: "Code with Cypher examples", content: `// Example Cypher queries: // 1. MATCH (n:Person)-[:KNOWS]->(m:Person) RETURN n.name, m.name // 2. CREATE (n:Node {id: $id}) RETURN n // 3. MATCH (a)-[r:REL]->(b) DELETE r`, }, { name: "SQL-like content", content: `SELECT * FROM users WHERE id = 1; INSERT INTO logs VALUES (1, 'test');`, }, { name: "Escaped quotes", content: `He said "Hello \"World\"" and then 'it\'s fine'`, }, { name: "Newlines and tabs", content: "Line1\nLine2\tTabbed\rCarriage\r\nWindows", }, { name: "Unicode content", content: `Unicode: 你好世界 🚀 émojis →�ñ`, }, { name: "Backticks", content: "Code: `MATCH (n) RETURN n` in markdown", }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Test CREATE with this content createQuery := `CREATE (n:TestNode {content: $content, title: $title}) RETURN n` params := map[string]interface{}{ "content": tc.content, "title": tc.name, } result, err := executor.Execute(ctx, createQuery, params) if err != nil { t.Fatalf("CREATE failed for %q: %v\nContent: %s", tc.name, err, tc.content[:min(100, len(tc.content))]) } if len(result.Rows) == 0 { t.Fatalf("CREATE returned no rows for %q", tc.name) } // Verify we can read it back matchQuery := `MATCH (n:TestNode {title: $title}) RETURN n.content` result, err = executor.Execute(ctx, matchQuery, map[string]interface{}{"title": tc.name}) if err != nil { t.Fatalf("MATCH failed for %q: %v", tc.name, err) } if len(result.Rows) == 0 { t.Fatalf("MATCH returned no rows for %q", tc.name) } // Verify content is intact retrievedContent, ok := result.Rows[0][0].(string) if !ok { t.Fatalf("Retrieved content is not a string for %q", tc.name) } if retrievedContent != tc.content { t.Errorf("Content mismatch for %q:\nExpected: %s\nGot: %s", tc.name, tc.content[:min(50, len(tc.content))], retrievedContent[:min(50, len(retrievedContent))]) } t.Logf("✓ %s: stored and retrieved %d chars", tc.name, len(tc.content)) }) } } // TestBatchInsertWithProblematicContent tests batch INSERT with content that previously failed func TestBatchInsertWithProblematicContent(t *testing.T) { engine := storage.NewMemoryEngine() executor := NewStorageExecutor(engine) ctx := context.Background() // Content samples that caused migration failures problemContent := []string{ `{"tasks": [{"id": "task-1", "deps": ["task-0"]}]}`, `MATCH (n)-[:REL]->(m) RETURN n`, `Pattern: (a)->[:X]->(b)`, `Remove: MATCH (n) REMOVE n.prop`, `Set: MATCH (n) SET n.x = 1`, } for i, content := range problemContent { query := `CREATE (n:BatchTest {id: $id, content: $content}) RETURN n` params := map[string]interface{}{ "id": i, "content": content, } _, err := executor.Execute(ctx, query, params) if err != nil { t.Errorf("Batch item %d failed: %v\nContent: %s", i, err, content) } } // Verify all were inserted countQuery := `MATCH (n:BatchTest) RETURN count(n)` result, _ := executor.Execute(ctx, countQuery, nil) if len(result.Rows) > 0 { count := result.Rows[0][0] t.Logf("✓ Batch inserted %v nodes with problematic content", count) } } // TestStringLiteralParsing verifies string literals are not parsed as Cypher func TestStringLiteralParsing(t *testing.T) { engine := storage.NewMemoryEngine() executor := NewStorageExecutor(engine) ctx := context.Background() // This should create a node with the literal string, not execute it as Cypher dangerousContent := `MATCH (n) DETACH DELETE n` query := `CREATE (n:Safe {content: $content}) RETURN n.content` result, err := executor.Execute(ctx, query, map[string]interface{}{ "content": dangerousContent, }) if err != nil { t.Fatalf("Failed to store dangerous-looking content: %v", err) } // Verify the content was stored, not executed if len(result.Rows) == 0 { t.Fatal("No result returned") } stored := result.Rows[0][0].(string) if stored != dangerousContent { t.Errorf("Content was modified: got %q, want %q", stored, dangerousContent) } // Verify no nodes were deleted (the dangerous content should be data, not executed) countQuery := `MATCH (n:Safe) RETURN count(n)` countResult, _ := executor.Execute(ctx, countQuery, nil) if len(countResult.Rows) > 0 && countResult.Rows[0][0].(int64) == 0 { t.Error("Node was deleted! Dangerous content was executed as Cypher!") } t.Log("✓ Dangerous-looking content stored safely as string literal") } // min is built-in in Go 1.21+ // TestInlinePropertySyntax tests that inline property syntax handles special content func TestInlinePropertySyntax(t *testing.T) { engine := storage.NewMemoryEngine() executor := NewStorageExecutor(engine) ctx := context.Background() // Test inline property assignment with tricky content testCases := []struct { name string query string }{ { name: "Simple inline", query: `CREATE (n:Test {name: "test->value"}) RETURN n.name`, }, { name: "Inline with curly braces", query: `CREATE (n:Test {data: "{\"key\": \"value\"}"}) RETURN n.data`, }, { name: "Inline with parentheses", query: `CREATE (n:Test {expr: "(a + b) * c"}) RETURN n.expr`, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { _, err := executor.Execute(ctx, tc.query, nil) if err != nil { // Check if it's a parsing error vs execution error if strings.Contains(err.Error(), "invalid") || strings.Contains(err.Error(), "parse") { t.Errorf("Parse error for %q: %v", tc.name, err) } else { t.Logf("Execution note for %q: %v", tc.name, err) } } }) } }

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