Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

253 lines
8.1 KiB
Go

package store_sqlite
import (
"database/sql"
"path/filepath"
"strings"
"testing"
"github.com/zzet/gortex/internal/graph"
)
// TestPromotedColumns_RoundTrip verifies the promoted keys land in their
// columns, are stripped from the JSON blob, and restore into Meta with
// exact types — while non-promoted keys stay in the blob.
func TestPromotedColumns_RoundTrip(t *testing.T) {
s, err := Open(filepath.Join(t.TempDir(), "p.sqlite"))
if err != nil {
t.Fatal(err)
}
defer s.Close()
s.AddNode(&graph.Node{
ID: "f.go::F", Kind: graph.KindFunction, Name: "F", FilePath: "f.go",
Meta: map[string]any{
"signature": "func F()",
"visibility": "public",
"doc": "F docs",
"external": true,
"complexity": 5, // non-promoted — must stay in the blob
},
})
n := s.GetNode("f.go::F")
if n == nil {
t.Fatal("GetNode returned nil")
}
assertType[string](t, n.Meta, "signature", "func F()")
assertType[string](t, n.Meta, "visibility", "public")
assertType[string](t, n.Meta, "doc", "F docs")
assertType[bool](t, n.Meta, "external", true)
assertType[int](t, n.Meta, "complexity", 5)
var sig, vis, doc sql.NullString
var ext sql.NullBool
var blob []byte
row := s.db.QueryRow(`SELECT signature, visibility, doc, external, meta FROM nodes WHERE id=?`, "f.go::F")
if err := row.Scan(&sig, &vis, &doc, &ext, &blob); err != nil {
t.Fatal(err)
}
if !sig.Valid || sig.String != "func F()" {
t.Errorf("signature column = %+v", sig)
}
if !ext.Valid || !ext.Bool {
t.Errorf("external column = %+v", ext)
}
blobStr := string(blob)
for _, k := range []string{"signature", "visibility", "external"} {
if strings.Contains(blobStr, k) {
t.Errorf("blob still contains promoted key %q: %s", k, blobStr)
}
}
if !strings.Contains(blobStr, "complexity") {
t.Errorf("blob missing non-promoted key complexity: %s", blobStr)
}
}
// TestPromotedColumns_NewColumns verifies the added promoted meta columns
// (is_async / is_static / is_abstract / is_exported / return_type / updated_at)
// and the struct-field columns (start_column / end_column) round-trip through
// their typed columns, and that a SQL filter on is_async resolves WITHOUT
// decoding the meta blob — the indexable-column acceptance.
func TestPromotedColumns_NewColumns(t *testing.T) {
s, err := Open(filepath.Join(t.TempDir(), "p.sqlite"))
if err != nil {
t.Fatal(err)
}
defer s.Close()
s.AddNode(&graph.Node{
ID: "f.go::Async", Kind: graph.KindFunction, Name: "Async", FilePath: "f.go",
StartLine: 10, EndLine: 20, StartColumn: 4, EndColumn: 1,
Meta: map[string]any{
"is_async": true,
"is_static": false,
"is_abstract": true,
"is_exported": true,
"return_type": "error",
"updated_at": int64(1700000000),
"complexity": 3, // non-promoted — stays in the blob
},
})
// A second, non-async node to prove the filter is selective.
s.AddNode(&graph.Node{
ID: "f.go::Sync", Kind: graph.KindFunction, Name: "Sync", FilePath: "f.go",
Meta: map[string]any{"is_async": false},
})
// Read-back restores every promoted key into Meta with its exact type,
// and the struct columns into the Node fields.
n := s.GetNode("f.go::Async")
if n == nil {
t.Fatal("GetNode returned nil")
}
assertType[bool](t, n.Meta, "is_async", true)
assertType[bool](t, n.Meta, "is_static", false)
assertType[bool](t, n.Meta, "is_abstract", true)
assertType[bool](t, n.Meta, "is_exported", true)
assertType[string](t, n.Meta, "return_type", "error")
assertType[int64](t, n.Meta, "updated_at", int64(1700000000))
assertType[int](t, n.Meta, "complexity", 3)
if n.StartColumn != 4 || n.EndColumn != 1 {
t.Errorf("column offsets = (%d,%d), want (4,1)", n.StartColumn, n.EndColumn)
}
// The promoted keys are stripped from the JSON blob; complexity is not.
var blob []byte
if err := s.db.QueryRow(`SELECT meta FROM nodes WHERE id=?`, "f.go::Async").Scan(&blob); err != nil {
t.Fatal(err)
}
for _, k := range []string{"is_async", "is_abstract", "return_type", "updated_at"} {
if strings.Contains(string(blob), k) {
t.Errorf("blob still contains promoted key %q: %s", k, blob)
}
}
if !strings.Contains(string(blob), "complexity") {
t.Errorf("blob missing non-promoted key complexity: %s", blob)
}
// Acceptance: a SQL filter on the typed column resolves the node without
// touching the meta blob (only id is selected).
var id string
var startCol, endCol int
if err := s.db.QueryRow(
`SELECT id, start_column, end_column FROM nodes WHERE is_async = 1`,
).Scan(&id, &startCol, &endCol); err != nil {
t.Fatalf("is_async column filter failed: %v", err)
}
if id != "f.go::Async" || startCol != 4 || endCol != 1 {
t.Errorf("filter result = (%q,%d,%d), want (f.go::Async,4,1)", id, startCol, endCol)
}
}
// TestPromotedColumns_SemanticType verifies semantic_type/semantic_source —
// promoted alongside signature/visibility/etc. so enrichment can query the
// unstamped subset by column instead of decoding every node's meta blob —
// round-trip through their columns and out of the JSON blob.
func TestPromotedColumns_SemanticType(t *testing.T) {
s, err := Open(filepath.Join(t.TempDir(), "p.sqlite"))
if err != nil {
t.Fatal(err)
}
defer s.Close()
s.AddNode(&graph.Node{
ID: "f.go::T", Kind: graph.KindFunction, Name: "T", FilePath: "f.go",
Meta: map[string]any{
"semantic_type": "string",
"semantic_source": "lsp-gopls",
"complexity": 5, // non-promoted — must stay in the blob
},
})
n := s.GetNode("f.go::T")
if n == nil {
t.Fatal("GetNode returned nil")
}
assertType[string](t, n.Meta, "semantic_type", "string")
assertType[string](t, n.Meta, "semantic_source", "lsp-gopls")
assertType[int](t, n.Meta, "complexity", 5)
var st, ss sql.NullString
var blob []byte
row := s.db.QueryRow(`SELECT semantic_type, semantic_source, meta FROM nodes WHERE id=?`, "f.go::T")
if err := row.Scan(&st, &ss, &blob); err != nil {
t.Fatal(err)
}
if !st.Valid || st.String != "string" {
t.Errorf("semantic_type column = %+v", st)
}
if !ss.Valid || ss.String != "lsp-gopls" {
t.Errorf("semantic_source column = %+v", ss)
}
blobStr := string(blob)
for _, k := range []string{"semantic_type", "semantic_source"} {
if strings.Contains(blobStr, k) {
t.Errorf("blob still contains promoted key %q: %s", k, blobStr)
}
}
if !strings.Contains(blobStr, "complexity") {
t.Errorf("blob missing non-promoted key complexity: %s", blobStr)
}
}
// TestPromotedColumns_ExternalFalse guards the NULL-vs-false distinction:
// a stored false must round-trip as false, not vanish.
func TestPromotedColumns_ExternalFalse(t *testing.T) {
s, err := Open(filepath.Join(t.TempDir(), "p.sqlite"))
if err != nil {
t.Fatal(err)
}
defer s.Close()
s.AddNode(&graph.Node{
ID: "x", Kind: graph.KindFunction, Name: "x", FilePath: "x.go",
Meta: map[string]any{"external": false},
})
n := s.GetNode("x")
if n == nil {
t.Fatal("nil")
}
v, ok := n.Meta["external"].(bool)
if !ok || v != false {
t.Errorf("external false: got %v (%T)", n.Meta["external"], n.Meta["external"])
}
}
// TestPromotedColumns_Migration verifies ensureNodeColumns adds the
// promoted columns to a database created with the pre-promotion schema.
func TestPromotedColumns_Migration(t *testing.T) {
path := filepath.Join(t.TempDir(), "old.sqlite")
raw, err := sql.Open("sqlite", path)
if err != nil {
t.Fatal(err)
}
_, err = raw.Exec(`CREATE TABLE nodes (
id TEXT PRIMARY KEY, kind TEXT NOT NULL, name TEXT NOT NULL,
qual_name TEXT NOT NULL DEFAULT '', file_path TEXT NOT NULL,
start_line INTEGER NOT NULL DEFAULT 0, end_line INTEGER NOT NULL DEFAULT 0,
language TEXT NOT NULL DEFAULT '', repo_prefix TEXT NOT NULL DEFAULT '',
workspace_id TEXT NOT NULL DEFAULT '', project_id TEXT NOT NULL DEFAULT '',
meta BLOB
) WITHOUT ROWID`)
if err != nil {
t.Fatal(err)
}
_ = raw.Close()
s, err := Open(path)
if err != nil {
t.Fatalf("Open old-schema db: %v", err)
}
defer s.Close()
s.AddNode(&graph.Node{
ID: "m", Kind: graph.KindFunction, Name: "m", FilePath: "m.go",
Meta: map[string]any{"signature": "sig", "external": true},
})
n := s.GetNode("m")
if n == nil {
t.Fatal("nil after migration")
}
assertType[string](t, n.Meta, "signature", "sig")
assertType[bool](t, n.Meta, "external", true)
}