a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
162 lines
5.2 KiB
Go
162 lines
5.2 KiB
Go
package exporter
|
|
|
|
import (
|
|
"bytes"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/zzet/gortex/internal/graph"
|
|
)
|
|
|
|
func TestCypher_NodeShape(t *testing.T) {
|
|
g := buildSampleGraph(t)
|
|
|
|
var buf bytes.Buffer
|
|
_, err := WriteCypher(&buf, g, Options{})
|
|
require.NoError(t, err)
|
|
|
|
out := buf.String()
|
|
// Each node should appear as a labeled CREATE.
|
|
assert.Contains(t, out, "CREATE (:Function:GortexNode")
|
|
assert.Contains(t, out, "CREATE (:Method:GortexNode")
|
|
assert.Contains(t, out, "CREATE (:Field:GortexNode")
|
|
|
|
// Required props on every node.
|
|
assert.Contains(t, out, "id: 'main.go::F'")
|
|
assert.Contains(t, out, "name: 'F'")
|
|
assert.Contains(t, out, "qual_name: 'example.com/m.F'")
|
|
assert.Contains(t, out, "file_path: 'main.go'")
|
|
assert.Contains(t, out, "start_line: 3")
|
|
assert.Contains(t, out, "language: 'go'")
|
|
assert.Contains(t, out, "repo_prefix: 'example'")
|
|
|
|
// Meta keys flattened onto the node, JSON for nested values.
|
|
assert.Contains(t, out, "visibility: 'public'")
|
|
assert.Contains(t, out, "complexity: 4")
|
|
assert.Contains(t, out, `tags: '["hot","core"]'`)
|
|
|
|
// Pure-statement format: no comment lines, no DDL. The first line of
|
|
// the output should be a CREATE statement, not a `//` header.
|
|
assert.NotContains(t, out, "//", "output must not contain comments — Memgraph's .cypherl loader rejects them")
|
|
assert.NotContains(t, out, "CREATE INDEX", "DDL syntax differs between Neo4j and Memgraph; keep the file portable")
|
|
assert.True(t, strings.HasPrefix(out, "CREATE (:"),
|
|
"first line should be a CREATE statement; got: %q", out[:min(60, len(out))])
|
|
}
|
|
|
|
func TestCypher_EdgeShape(t *testing.T) {
|
|
g := buildSampleGraph(t)
|
|
|
|
var buf bytes.Buffer
|
|
_, err := WriteCypher(&buf, g, Options{})
|
|
require.NoError(t, err)
|
|
|
|
out := buf.String()
|
|
assert.Contains(t, out, "MATCH (a:GortexNode {id: 'main.go::F'}), (b:GortexNode {id: 'lib.go::Logger.Print'}) CREATE (a)-[:CALLS")
|
|
assert.Contains(t, out, "confidence: 1")
|
|
assert.Contains(t, out, "confidence_label: 'EXTRACTED'")
|
|
assert.Contains(t, out, "origin: 'ast_resolved'")
|
|
assert.Contains(t, out, "line: 4")
|
|
|
|
// Writes edge with meta.
|
|
assert.Contains(t, out, ":WRITES")
|
|
assert.Contains(t, out, "receiver_type: 'Logger'")
|
|
assert.Contains(t, out, "origin: 'ast_inferred'")
|
|
}
|
|
|
|
func TestCypher_StringEscaping(t *testing.T) {
|
|
g := graph.New()
|
|
g.AddNode(&graph.Node{
|
|
ID: "x.go::Quote", Kind: graph.KindFunction, Name: "Quote",
|
|
FilePath: "x.go", StartLine: 1, EndLine: 1, Language: "go",
|
|
Meta: map[string]any{
|
|
"doc": "It's a \"thing\"\n\twith escapes.",
|
|
},
|
|
})
|
|
var buf bytes.Buffer
|
|
_, err := WriteCypher(&buf, g, Options{})
|
|
require.NoError(t, err)
|
|
|
|
out := buf.String()
|
|
// Single-quote should be backslash-escaped.
|
|
assert.Contains(t, out, `\'`)
|
|
// Newline should be \n literal in the source, not an actual newline inside the string.
|
|
assert.Contains(t, out, `\n`)
|
|
assert.Contains(t, out, `\t`)
|
|
}
|
|
|
|
func TestCypher_StatsCount(t *testing.T) {
|
|
g := buildSampleGraph(t)
|
|
|
|
var buf bytes.Buffer
|
|
stats, err := WriteCypher(&buf, g, Options{})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 3, stats.NodesWritten)
|
|
assert.Equal(t, 2, stats.EdgesWritten)
|
|
assert.Equal(t, int64(buf.Len()), stats.BytesWritten)
|
|
}
|
|
|
|
func TestCypher_EmptyGraph(t *testing.T) {
|
|
g := graph.New()
|
|
var buf bytes.Buffer
|
|
stats, err := WriteCypher(&buf, g, Options{})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 0, stats.NodesWritten)
|
|
assert.Equal(t, 0, stats.EdgesWritten)
|
|
// Empty graph produces an empty file. .cypherl loaders accept this;
|
|
// any header / DDL is the CLI's responsibility (printed to stderr).
|
|
assert.Empty(t, buf.String())
|
|
}
|
|
|
|
func TestCypher_FilterRemovesDanglingEdges(t *testing.T) {
|
|
g := buildSampleGraph(t)
|
|
|
|
var buf bytes.Buffer
|
|
_, err := WriteCypher(&buf, g, Options{Kinds: []graph.NodeKind{graph.KindFunction}})
|
|
require.NoError(t, err)
|
|
|
|
out := buf.String()
|
|
assert.Contains(t, out, "CREATE (:Function:GortexNode")
|
|
assert.NotContains(t, out, "CREATE (:Method:GortexNode")
|
|
assert.NotContains(t, out, ":CALLS",
|
|
"edges with excluded endpoints must be dropped, not produce dangling MATCH")
|
|
}
|
|
|
|
func TestCypher_PropertyNameSanitization(t *testing.T) {
|
|
g := graph.New()
|
|
g.AddNode(&graph.Node{
|
|
ID: "x.go::F", Kind: graph.KindFunction, Name: "F",
|
|
FilePath: "x.go", StartLine: 1, EndLine: 1, Language: "go",
|
|
Meta: map[string]any{
|
|
"with-dash": 1,
|
|
"with space": 2,
|
|
"normal": 3,
|
|
"@@@invalid": 4, // will sanitize to "___invalid"
|
|
"meta.dotted": 5,
|
|
},
|
|
})
|
|
var buf bytes.Buffer
|
|
_, err := WriteCypher(&buf, g, Options{})
|
|
require.NoError(t, err)
|
|
|
|
out := buf.String()
|
|
assert.Contains(t, out, "with_dash: 1")
|
|
assert.Contains(t, out, "with_space: 2")
|
|
assert.Contains(t, out, "normal: 3")
|
|
assert.Contains(t, out, "meta_dotted: 5")
|
|
// Hyphen / space / dot should never appear inside property keys.
|
|
for _, line := range strings.Split(out, "\n") {
|
|
// We only care about the property-map portion of CREATE statements.
|
|
if !strings.Contains(line, "CREATE (:") {
|
|
continue
|
|
}
|
|
// A key always looks like ` <ident>:`. None of our sanitized keys
|
|
// should contain a non-identifier character.
|
|
assert.NotContains(t, line, " with-dash:")
|
|
assert.NotContains(t, line, " with space:")
|
|
assert.NotContains(t, line, " meta.dotted:")
|
|
}
|
|
}
|