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

105 lines
4.4 KiB
Go

package store_sqlite
import (
"database/sql"
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
"github.com/zzet/gortex/internal/graph"
)
// hasNodeColumn reports whether the nodes table currently has the named column.
func hasNodeColumn(t *testing.T, db *sql.DB, col string) bool {
t.Helper()
rows, err := db.Query(`PRAGMA table_info(nodes)`)
require.NoError(t, err)
defer rows.Close()
for rows.Next() {
var (
cid, notnull, pk int
name, ctype string
dflt sql.NullString
)
require.NoError(t, rows.Scan(&cid, &name, &ctype, &notnull, &dflt, &pk))
if name == col {
return true
}
}
require.NoError(t, rows.Err())
return false
}
// TestOpenUpgradesPreDataClassStore is the backward-compatibility proof for the
// promoted data_class column: an existing v1 store written before the column
// existed must Open cleanly (ensureNodeColumns ALTERs the column in before the
// node statements are prepared), keep its rows, and immediately get the working
// SQL-level content filter — all WITHOUT a schema_version bump or a reindex.
//
// The simulated old store has data_class dropped while staying at the v1
// baseline, the exact shape of every Gortex sqlite DB already on disk before
// this change. Without the data_class entry in promotedMetaColumns, the reopen
// fails with "no such column: data_class" when prepare() builds the node
// INSERT/SELECT — so this test fails-closed if that wiring regresses.
func TestOpenUpgradesPreDataClassStore(t *testing.T) {
path := filepath.Join(t.TempDir(), "store.sqlite")
// 1. Create a current store and seed a row, then close. A fresh store is
// stamped at the current schema version; step 2 knocks it back to the v1
// baseline to simulate a store written before data_class existed.
s, err := Open(path)
require.NoError(t, err)
s.AddNode(&graph.Node{ID: "old1", Kind: graph.KindFunction, Name: "Legacy", FilePath: "f.go", RepoPrefix: "r"})
require.NoError(t, s.Close())
// 2. Simulate a store written before data_class existed: drop the column
// while leaving user_version at the v1 baseline.
withRawDB(t, path, func(db *sql.DB) {
_, err := db.Exec(`ALTER TABLE nodes DROP COLUMN data_class`)
require.NoError(t, err, "simulate a pre-data_class store")
require.False(t, hasNodeColumn(t, db, "data_class"), "data_class must be absent before the upgrade")
// A fresh Open stamps the current schema version; knock it back to the v1
// baseline so this genuinely simulates a pre-data_class store and the
// reopen exercises the in-place upgrade arm rather than a no-op.
_, err = db.Exec(`PRAGMA user_version = 1`)
require.NoError(t, err, "reset to the v1 baseline")
var v int
require.NoError(t, db.QueryRow(`PRAGMA user_version`).Scan(&v))
require.Equal(t, 1, v, "the simulated old store must sit at the v1 baseline")
})
// 3. Reopen with the current binary. ensureNodeColumns must re-add the
// column before prepare() references it, so Open succeeds without a wipe.
s2, err := Open(path)
require.NoError(t, err, "Open must upgrade a pre-data_class store in place, not fail on the missing column")
t.Cleanup(func() { _ = s2.Close() })
require.True(t, hasNodeColumn(t, s2.db, "data_class"), "ensureNodeColumns must re-add data_class on Open")
require.False(t, s2.NeedsRebuild(), "an additive-column upgrade must not signal a wipe/reindex")
// 4. Existing rows survived (the upgrade is in place, not a rebuild).
require.NotNil(t, s2.GetNode("old1"), "existing rows must survive the in-place upgrade")
// 5. A new content node persists, round-trips through the promoted column,
// and is correctly dropped by the SQL-level content filter.
s2.AddNode(&graph.Node{ID: "content1", Kind: graph.KindDoc, Name: "doc.txt::0", RepoPrefix: "r",
Meta: map[string]any{"data_class": "content", "section_text": "x"}})
s2.AddNode(&graph.Node{ID: "code1", Kind: graph.KindFunction, Name: "Foo", RepoPrefix: "r"})
content := s2.GetNode("content1")
require.NotNil(t, content)
require.Equal(t, "content", content.Meta["data_class"], "data_class round-trips via the promoted column after upgrade")
ids := map[string]bool{}
for _, n := range s2.GetRepoNonContentNodes("r") {
ids[n.ID] = true
}
require.True(t, ids["old1"], "legacy non-content node kept")
require.True(t, ids["code1"], "code node kept")
require.False(t, ids["content1"], "content node filtered at the SQL level after the upgrade")
require.Len(t, ids, 2)
}