Skip to main content
Glama
orneryd

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

by orneryd
parser_test.go11.2 kB
// Package cypher tests for the Cypher parser and executor. package cypher import ( "context" "testing" ) func TestNewParser(t *testing.T) { parser := NewParser() if parser == nil { t.Error("NewParser() returned nil") } } func TestParseEmptyQuery(t *testing.T) { parser := NewParser() _, err := parser.Parse("") if err == nil { t.Error("expected error for empty query") } } func TestParseMatch(t *testing.T) { parser := NewParser() tests := []struct { name string cypher string wantErr bool }{ { name: "simple match", cypher: "MATCH (n) RETURN n", wantErr: false, }, { name: "match with label", cypher: "MATCH (n:Person) RETURN n", wantErr: false, }, { name: "match with relationship", cypher: "MATCH (a)-[r]->(b) RETURN a, b", wantErr: false, }, { name: "optional match", cypher: "OPTIONAL MATCH (n) RETURN n", wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { query, err := parser.Parse(tt.cypher) if (err != nil) != tt.wantErr { t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr && query == nil { t.Error("expected query result") } }) } } func TestParseCreate(t *testing.T) { parser := NewParser() tests := []struct { name string cypher string wantErr bool }{ { name: "simple create", cypher: "CREATE (n)", wantErr: false, }, { name: "create with label", cypher: "CREATE (n:Person)", wantErr: false, }, { name: "create with properties", cypher: "CREATE (n:Person {name: 'Alice'})", wantErr: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { query, err := parser.Parse(tt.cypher) if (err != nil) != tt.wantErr { t.Errorf("Parse() error = %v, wantErr %v", err, tt.wantErr) return } if !tt.wantErr && query.Type != QueryCreate { t.Errorf("expected QueryCreate, got %v", query.Type) } }) } } func TestParseReturn(t *testing.T) { parser := NewParser() query, err := parser.Parse("MATCH (n) RETURN n") if err != nil { t.Fatalf("Parse() error = %v", err) } hasReturn := false for _, clause := range query.Clauses { if _, ok := clause.(*ReturnClause); ok { hasReturn = true break } } if !hasReturn { t.Error("expected RETURN clause") } } func TestParseWhere(t *testing.T) { parser := NewParser() query, err := parser.Parse("MATCH (n) WHERE n.name = 'Alice' RETURN n") if err != nil { t.Fatalf("Parse() error = %v", err) } hasWhere := false for _, clause := range query.Clauses { if _, ok := clause.(*WhereClause); ok { hasWhere = true break } } if !hasWhere { t.Error("expected WHERE clause") } } func TestParseSet(t *testing.T) { parser := NewParser() query, err := parser.Parse("MATCH (n) SET n.name = 'Bob' RETURN n") if err != nil { t.Fatalf("Parse() error = %v", err) } if query.Type != QuerySet { t.Errorf("expected QuerySet, got %v", query.Type) } } func TestParseDelete(t *testing.T) { parser := NewParser() t.Run("simple delete", func(t *testing.T) { query, err := parser.Parse("MATCH (n) DELETE n") if err != nil { t.Fatalf("Parse() error = %v", err) } if query.Type != QueryDelete { t.Errorf("expected QueryDelete, got %v", query.Type) } }) t.Run("detach delete", func(t *testing.T) { query, err := parser.Parse("MATCH (n) DETACH DELETE n") if err != nil { t.Fatalf("Parse() error = %v", err) } if query.Type != QueryDelete { t.Errorf("expected QueryDelete, got %v", query.Type) } hasDetachDelete := false for _, clause := range query.Clauses { if dc, ok := clause.(*DeleteClause); ok && dc.Detach { hasDetachDelete = true break } } if !hasDetachDelete { t.Error("expected DETACH DELETE clause") } }) } func TestTokenize(t *testing.T) { tests := []struct { name string input string expected []string }{ { name: "simple query", input: "MATCH (n)", expected: []string{"MATCH", "(", "n", ")"}, }, { name: "with label", input: "MATCH (n:Person)", expected: []string{"MATCH", "(", "n", ":", "Person", ")"}, }, { name: "with string", input: "CREATE (n {name: 'Alice'})", expected: []string{"CREATE", "(", "n", "{", "name", ":", "'Alice'", "}", ")"}, }, { name: "with relationship", input: "MATCH (a)-[r]->(b)", expected: []string{"MATCH", "(", "a", ")", "-", "[", "r", "]", "-", ">", "(", "b", ")"}, }, { name: "whitespace handling", input: "MATCH (n) RETURN n", expected: []string{"MATCH", "(", "n", ")", "RETURN", "n"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tokens := tokenize(tt.input) if len(tokens) != len(tt.expected) { t.Errorf("expected %d tokens, got %d: %v", len(tt.expected), len(tokens), tokens) return } for i, tok := range tokens { if tok != tt.expected[i] { t.Errorf("token %d: expected %q, got %q", i, tt.expected[i], tok) } } }) } } func TestQueryTypes(t *testing.T) { types := []QueryType{ QueryMatch, QueryCreate, QueryMerge, QueryDelete, QuerySet, QueryReturn, QueryWith, } for i, qt := range types { if int(qt) != i { t.Errorf("QueryType %d has unexpected value %d", i, qt) } } } func TestEdgeDirection(t *testing.T) { tests := []struct { dir EdgeDirection expected int }{ {EdgeBoth, 0}, {EdgeOutgoing, 1}, {EdgeIncoming, 2}, } for _, tt := range tests { if int(tt.dir) != tt.expected { t.Errorf("expected %d, got %d", tt.expected, tt.dir) } } } func TestClauseMarkers(t *testing.T) { // Test that all clause types implement Clause interface clauses := []Clause{ &MatchClause{}, &CreateClause{}, &ReturnClause{}, &WhereClause{}, &SetClause{}, &DeleteClause{}, } for _, c := range clauses { c.clauseMarker() // Should not panic } } func TestExpressionMarkers(t *testing.T) { // Test that all expression types implement Expression interface exprs := []Expression{ &PropertyAccess{}, &Comparison{}, &Literal{}, &Parameter{}, &FunctionCall{}, } for _, e := range exprs { e.exprMarker() // Should not panic } } func TestNodePattern(t *testing.T) { np := NodePattern{ Variable: "n", Labels: []string{"Person", "Employee"}, Properties: map[string]any{"name": "Alice"}, } if np.Variable != "n" { t.Error("wrong variable") } if len(np.Labels) != 2 { t.Error("expected 2 labels") } if np.Properties["name"] != "Alice" { t.Error("wrong property value") } } func TestEdgePattern(t *testing.T) { minHops := 1 maxHops := 3 ep := EdgePattern{ Variable: "r", Type: "KNOWS", Direction: EdgeOutgoing, Properties: map[string]any{"since": 2020}, MinHops: &minHops, MaxHops: &maxHops, } if ep.Variable != "r" { t.Error("wrong variable") } if ep.Direction != EdgeOutgoing { t.Error("wrong direction") } if *ep.MinHops != 1 || *ep.MaxHops != 3 { t.Error("wrong hops") } } func TestReturnItem(t *testing.T) { ri := ReturnItem{ Expression: &PropertyAccess{Variable: "n", Property: "name"}, Alias: "personName", } if ri.Alias != "personName" { t.Error("wrong alias") } } func TestOrderItem(t *testing.T) { oi := OrderItem{ Expression: &PropertyAccess{Variable: "n", Property: "age"}, Descending: true, } if !oi.Descending { t.Error("expected descending") } } func TestSetItem(t *testing.T) { si := SetItem{ Variable: "n", Property: "name", Value: &Literal{Value: "Bob"}, } if si.Variable != "n" || si.Property != "name" { t.Error("wrong set item") } } func TestNewExecutor(t *testing.T) { executor := NewExecutor() if executor == nil { t.Error("NewExecutor() returned nil") } if executor.parser == nil { t.Error("executor should have parser") } } func TestExecutorExecute(t *testing.T) { executor := NewExecutor() ctx := context.Background() t.Run("simple match", func(t *testing.T) { result, err := executor.Execute(ctx, "MATCH (n) RETURN n", nil) if err != nil { t.Fatalf("Execute() error = %v", err) } if result == nil { t.Error("expected result") } }) t.Run("with parameters", func(t *testing.T) { params := map[string]any{ "name": "Alice", } result, err := executor.Execute(ctx, "MATCH (n {name: $name}) RETURN n", params) if err != nil { t.Fatalf("Execute() error = %v", err) } if result == nil { t.Error("expected result") } }) t.Run("invalid query", func(t *testing.T) { _, err := executor.Execute(ctx, "", nil) if err == nil { t.Error("expected error for empty query") } }) } func TestResult(t *testing.T) { result := &Result{ Columns: []string{"name", "age"}, Rows: []map[string]any{ {"name": "Alice", "age": 30}, {"name": "Bob", "age": 25}, }, } if result.RowCount() != 2 { t.Errorf("expected 2 rows, got %d", result.RowCount()) } if len(result.Columns) != 2 { t.Errorf("expected 2 columns, got %d", len(result.Columns)) } } func TestPropertyAccess(t *testing.T) { pa := &PropertyAccess{ Variable: "n", Property: "name", } if pa.Variable != "n" { t.Error("wrong variable") } if pa.Property != "name" { t.Error("wrong property") } } func TestComparison(t *testing.T) { comp := &Comparison{ Left: &PropertyAccess{Variable: "n", Property: "age"}, Operator: ">=", Right: &Literal{Value: 18}, } if comp.Operator != ">=" { t.Error("wrong operator") } } func TestLiteral(t *testing.T) { tests := []struct { name string value any }{ {"string", "hello"}, {"int", 42}, {"float", 3.14}, {"bool", true}, {"nil", nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { lit := &Literal{Value: tt.value} if lit.Value != tt.value { t.Errorf("expected %v, got %v", tt.value, lit.Value) } }) } } func TestParameter(t *testing.T) { param := &Parameter{Name: "userId"} if param.Name != "userId" { t.Error("wrong parameter name") } } func TestFunctionCall(t *testing.T) { fc := &FunctionCall{ Name: "count", Args: []Expression{ &PropertyAccess{Variable: "n", Property: "id"}, }, } if fc.Name != "count" { t.Error("wrong function name") } if len(fc.Args) != 1 { t.Error("expected 1 argument") } } func TestPattern(t *testing.T) { pattern := Pattern{ Nodes: []NodePattern{ {Variable: "a", Labels: []string{"Person"}}, {Variable: "b", Labels: []string{"Person"}}, }, Edges: []EdgePattern{ {Variable: "r", Type: "KNOWS", Direction: EdgeOutgoing}, }, } if len(pattern.Nodes) != 2 { t.Error("expected 2 nodes") } if len(pattern.Edges) != 1 { t.Error("expected 1 edge") } } func TestQuery(t *testing.T) { query := &Query{ Type: QueryMatch, Clauses: []Clause{ &MatchClause{}, &ReturnClause{}, }, Parameters: map[string]any{ "name": "Alice", }, } if query.Type != QueryMatch { t.Error("wrong query type") } if len(query.Clauses) != 2 { t.Error("expected 2 clauses") } if query.Parameters["name"] != "Alice" { t.Error("wrong parameter") } }

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