返回 DeepSeek-Reasonix
junction_migration_test.go
根目录 / internal / sessioncatalog / junction_migration_test.go
1 package sessioncatalog
2
3 import (
4 "context"
5 "fmt"
6 "os"
7 "path/filepath"
8 "testing"
9 "time"
10
11 "reasonix/internal/projectiondb"
12 )
13
14 func TestNativeIdentityMigrationRebuildsOnlyProjectionOnce(t *testing.T) {
15 // Version 13 already includes the identity-invalidating migration, so the
16 // stale-row assertion covers only the last pre-invalidation schema. The
17 // migration boundary itself is covered by TestSchemaV13RebuildsFilesystemIdentityProjection.
18 for _, version := range []int{12} {
19 t.Run(fmt.Sprint(version), func(t *testing.T) {
20 ctx := context.Background()
21 root := t.TempDir()
22 transcript := filepath.Join(root, "keep.jsonl")
23 const content = "authoritative transcript must survive projection migration\n"
24 if err := os.WriteFile(transcript, []byte(content), 0o600); err != nil {
25 t.Fatal(err)
26 }
27 path := filepath.Join(root, "catalog.sqlite")
28 old, err := projectiondb.Open(ctx, projectiondb.OpenOptions{Path: path, Migrations: sessionMigrations()[:version], Now: time.Now})
29 if err != nil {
30 t.Fatal(err)
31 }
32 _, err = old.DB.ExecContext(ctx, `INSERT INTO catalog_directories(path,path_key,scope) VALUES('/alias','old-alias-key','global')`)
33 if closeErr := old.DB.Close(); err != nil || closeErr != nil {
34 t.Fatalf("seed legacy projection: %v / %v", err, closeErr)
35 }
36 catalog, err := Open(ctx, Options{Path: path, DisableRepair: true})
37 if err != nil {
38 t.Fatal(err)
39 }
40 var count int
41 if err := catalog.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM catalog_directories`).Scan(&count); err != nil || count != 0 {
42 t.Fatalf("stale identity retained: %d / %v", count, err)
43 }
44 _, err = catalog.db.ExecContext(ctx, `INSERT INTO catalog_directories(path,path_key,scope) VALUES('/physical','new-physical-key','global')`)
45 if closeErr := catalog.Close(ctx); err != nil || closeErr != nil {
46 t.Fatalf("seed rebuilt projection: %v / %v", err, closeErr)
47 }
48 catalog, err = Open(ctx, Options{Path: path, DisableRepair: true})
49 if err != nil {
50 t.Fatal(err)
51 }
52 defer catalog.Close(ctx)
53 if err := catalog.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM catalog_directories WHERE path_key='new-physical-key'`).Scan(&count); err != nil || count != 1 {
54 t.Fatalf("restart discarded new projection: %d / %v", count, err)
55 }
56 oldReader, err := projectiondb.Open(ctx, projectiondb.OpenOptions{Path: path, MemoryName: "old-reader", Migrations: sessionMigrations()[:version], Now: time.Now})
57 if err != nil {
58 t.Fatal(err)
59 }
60 defer oldReader.DB.Close()
61 if oldReader.Status.Mode != projectiondb.ModeMemory || oldReader.Status.QuarantinedPath != "" {
62 t.Fatalf("old reader reused or quarantined new projection: %+v", oldReader.Status)
63 }
64 if inspection := projectiondb.Inspect(ctx, path); inspection.Schema != len(sessionMigrations()) {
65 t.Fatalf("old reader changed new schema: %+v", inspection)
66 }
67 if raw, err := os.ReadFile(transcript); err != nil || string(raw) != content {
68 t.Fatalf("authoritative data changed: %v", err)
69 }
70 })
71 }
72 }
73
73 lines GO