Phase 1: data layer (SQLite, migrations, models, seed)
This commit is contained in:
77
internal/db/db_test.go
Normal file
77
internal/db/db_test.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestOpenMigratesAndSeeds(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "test.db")
|
||||
|
||||
d, err := Open(path)
|
||||
if err != nil {
|
||||
t.Fatalf("first open: %v", err)
|
||||
}
|
||||
|
||||
// Local user is seeded.
|
||||
var email string
|
||||
if err := d.QueryRow(`SELECT email FROM users WHERE id = ?`, LocalUserID).Scan(&email); err != nil {
|
||||
t.Fatalf("local user not seeded: %v", err)
|
||||
}
|
||||
|
||||
// All expected tables exist.
|
||||
for _, table := range []string{"users", "documents", "suggestions", "plagiarism_reports", "schema_migrations"} {
|
||||
var name string
|
||||
err := d.QueryRow(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`, table).Scan(&name)
|
||||
if err != nil {
|
||||
t.Errorf("table %q missing: %v", table, err)
|
||||
}
|
||||
}
|
||||
|
||||
// The voice suggestion type is permitted by the CHECK constraint.
|
||||
if _, err := d.Exec(`INSERT INTO documents (id, user_id) VALUES ('d1', ?)`, LocalUserID); err != nil {
|
||||
t.Fatalf("insert document: %v", err)
|
||||
}
|
||||
if _, err := d.Exec(
|
||||
`INSERT INTO suggestions (doc_id, from_pos, to_pos, original, replacement, explanation, type)
|
||||
VALUES ('d1', 0, 4, 'teh', '', 'voice flag', 'voice')`,
|
||||
); err != nil {
|
||||
t.Fatalf("insert voice suggestion: %v", err)
|
||||
}
|
||||
|
||||
// An invalid type is rejected.
|
||||
if _, err := d.Exec(
|
||||
`INSERT INTO suggestions (doc_id, from_pos, to_pos, original, replacement, explanation, type)
|
||||
VALUES ('d1', 0, 4, 'teh', 'the', 'x', 'nonsense')`,
|
||||
); err == nil {
|
||||
t.Error("expected CHECK constraint to reject invalid suggestion type")
|
||||
}
|
||||
|
||||
// Cascade delete removes child suggestions (foreign keys enabled).
|
||||
if _, err := d.Exec(`DELETE FROM documents WHERE id = 'd1'`); err != nil {
|
||||
t.Fatalf("delete document: %v", err)
|
||||
}
|
||||
var n int
|
||||
if err := d.QueryRow(`SELECT COUNT(*) FROM suggestions WHERE doc_id = 'd1'`).Scan(&n); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 0 {
|
||||
t.Errorf("expected cascade delete, got %d orphan suggestions", n)
|
||||
}
|
||||
d.Close()
|
||||
|
||||
// Re-opening is idempotent: migrations and seed don't double-apply or error.
|
||||
d2, err := Open(path)
|
||||
if err != nil {
|
||||
t.Fatalf("second open: %v", err)
|
||||
}
|
||||
defer d2.Close()
|
||||
|
||||
var users int
|
||||
if err := d2.QueryRow(`SELECT COUNT(*) FROM users WHERE id = ?`, LocalUserID).Scan(&users); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if users != 1 {
|
||||
t.Errorf("expected exactly 1 local user after reopen, got %d", users)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user