package index
|
|
import (
|
"database/sql"
|
"os"
|
"path/filepath"
|
"testing"
|
)
|
|
// newV1Store 手工构建一个 v1 schema 的老库(模拟迁移前状态)
|
func newV1Store(t *testing.T) (*Store, string) {
|
t.Helper()
|
dbPath := filepath.Join(t.TempDir(), "kb-v1.db")
|
db, err := sql.Open("sqlite3", dbPath+"?_journal_mode=WAL")
|
if err != nil {
|
t.Fatal(err)
|
}
|
schema := `
|
CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT NOT NULL);
|
CREATE TABLE nodes (
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
path TEXT NOT NULL UNIQUE,
|
title TEXT, section TEXT, tags TEXT, entities TEXT, wikilinks TEXT,
|
content_fts TEXT,
|
created_at TEXT DEFAULT (datetime('now')),
|
updated_at TEXT DEFAULT (datetime('now')));
|
CREATE TABLE edges (
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
from_node INTEGER NOT NULL, to_node INTEGER NOT NULL,
|
relation TEXT NOT NULL, label TEXT,
|
UNIQUE(from_node, to_node, relation, label));
|
`
|
if _, err := db.Exec(schema); err != nil {
|
t.Fatal(err)
|
}
|
if _, err := db.Exec(`INSERT INTO nodes (path, title, tags, entities, wikilinks, content_fts)
|
VALUES ('FAQ/001-测试.md', '测试', '["a"]', '[]', '[]', '内容')`); err != nil {
|
t.Fatal(err)
|
}
|
return &Store{db: db, dbPath: dbPath}, dbPath
|
}
|
|
func TestMigrateV1ToV2(t *testing.T) {
|
store, dbPath := newV1Store(t)
|
defer store.Close()
|
|
// 迁移前备份文件必须存在
|
if err := store.migrate(); err != nil {
|
t.Fatalf("migrate: %v", err)
|
}
|
if _, err := os.Stat(dbPath + ".bak"); err != nil {
|
t.Fatalf("备份文件不存在: %v", err)
|
}
|
|
// v2 列必须存在
|
cols := store.nodeColumns(t)
|
for _, want := range []string{"aliases", "status", "size", "mtime", "content_hash"} {
|
if !cols[want] {
|
t.Errorf("nodes 缺列 %s", want)
|
}
|
}
|
if !store.edgeHasProvenance(t) {
|
t.Error("edges 缺 provenance 列")
|
}
|
if !store.tableExists(t, "unresolved_links") {
|
t.Error("缺 unresolved_links 表")
|
}
|
|
// 数据保留
|
var title string
|
if err := store.db.QueryRow(`SELECT title FROM nodes WHERE path='FAQ/001-测试.md'`).Scan(&title); err != nil {
|
t.Fatal(err)
|
}
|
if title != "测试" {
|
t.Errorf("迁移丢数据: %s", title)
|
}
|
}
|
|
func TestMigrateIdempotent(t *testing.T) {
|
store, _ := newV1Store(t)
|
defer store.Close()
|
if err := store.migrate(); err != nil {
|
t.Fatalf("第一次: %v", err)
|
}
|
if err := store.migrate(); err != nil {
|
t.Fatalf("第二次(幂等): %v", err)
|
}
|
}
|
|
func TestNewDBIsV2(t *testing.T) {
|
dbPath := filepath.Join(t.TempDir(), "kb-v2.db")
|
store, err := Open(dbPath)
|
if err != nil {
|
t.Fatal(err)
|
}
|
defer store.Close()
|
if !store.edgeHasProvenance(t) {
|
t.Error("新建库应为 v2 schema")
|
}
|
}
|
|
// 测试辅助:列检查
|
func (s *Store) nodeColumns(t *testing.T) map[string]bool {
|
t.Helper()
|
rows, err := s.db.Query(`SELECT name FROM pragma_table_info('nodes')`)
|
if err != nil {
|
t.Fatal(err)
|
}
|
defer rows.Close()
|
m := map[string]bool{}
|
for rows.Next() {
|
var name string
|
rows.Scan(&name)
|
m[name] = true
|
}
|
return m
|
}
|
|
func (s *Store) edgeHasProvenance(t *testing.T) bool {
|
t.Helper()
|
var n int
|
s.db.QueryRow(`SELECT COUNT(*) FROM pragma_table_info('edges') WHERE name='provenance'`).Scan(&n)
|
return n > 0
|
}
|
|
func (s *Store) tableExists(t *testing.T, name string) bool {
|
t.Helper()
|
var n int
|
s.db.QueryRow(`SELECT COUNT(*) FROM sqlite_master WHERE type='table' AND name=?`, name).Scan(&n)
|
return n > 0
|
}
|