package store_test import ( "context" "testing" "time" "github.com/wangjia/pangolin/server/internal/nodes" ) // TestSQLite_ConnectPathSchema guards the "no such column: p.daily_mb" class of // regression that 500'd /v1/nodes/{id}/connect: the connect handler's store // queries must bind to the schema the migrations actually produce. Each // 000015-dependent query runs against a fully-migrated real SQLite DB — if any // references a column/table no migration creates, the call fails with a SQL // error and this test goes red. (The live outage was operational — binary // deployed ahead of the migration — but this also catches a query that names a // column no migration adds.) func TestSQLite_ConnectPathSchema(t *testing.T) { ctx := context.Background() db := openSQLite(t) ns := nodes.NewSQLNodeStore(db) seedUser(t, db, 1) // EntitlementForUser: the exact query that broke (SELECT ... p.daily_mb ...). // With a PRO subscription, DailyMB must come back from plans.daily_mb (102400, // seeded by migration 000015). var proID int64 if err := db.QueryRow(`SELECT id FROM plans WHERE code='pro'`).Scan(&proID); err != nil { t.Fatalf("lookup pro plan: %v", err) } if _, err := db.Exec( `INSERT INTO subscriptions (user_id, plan_id, expires_at, source) VALUES (1, ?, ?, 'trial')`, proID, time.Now().UTC().Add(24*time.Hour)); err != nil { t.Fatalf("seed subscription: %v", err) } ent, err := ns.EntitlementForUser(ctx, 1) if err != nil { t.Fatalf("EntitlementForUser: %v", err) // a missing daily_mb column lands here } if ent == nil { t.Fatal("EntitlementForUser returned nil entitlement") } if ent.PlanCode != "pro" { t.Errorf("PlanCode = %q, want pro", ent.PlanCode) } if !ent.DailyMB.Valid || ent.DailyMB.Int64 != 102400 { t.Errorf("DailyMB = %+v, want valid 102400", ent.DailyMB) } // EnsureDeviceDpUUID: exercises devices.dp_uuid (000015 ADD COLUMN). Mints a // per-device data-plane credential into the new column. if _, err := db.Exec( `INSERT INTO devices (id, uuid, user_id, name, platform) VALUES (1, 'dev-uuid', 1, 'dev', 'windows')`); err != nil { t.Fatalf("seed device: %v", err) } dp, devID, err := ns.EnsureDeviceDpUUID(ctx, 1, "dev-uuid") if err != nil { t.Fatalf("EnsureDeviceDpUUID: %v", err) // missing devices.dp_uuid lands here } if dp == "" || devID != 1 { t.Errorf("EnsureDeviceDpUUID = (%q, %d), want non-empty dp + devID 1", dp, devID) } // AccountDayBytes: GB-quota query run right after entitlement; must bind cleanly. if _, err := ns.AccountDayBytes(ctx, 1, time.Now().UTC()); err != nil { t.Fatalf("AccountDayBytes: %v", err) } }