package db import ( "context" "errors" "io" "log/slog" "path/filepath" "testing" ) // testLogger returns a logger that writes nowhere, keeping test output clean. func testLogger() *slog.Logger { return slog.New(slog.NewTextHandler(io.Discard, nil)) } func TestOpen_InMemory_正常(t *testing.T) { t.Parallel() ctx := context.Background() db, err := Open(ctx, ":memory:", testLogger()) if err != nil { t.Fatalf("Open(:memory:) error: %v", err) } defer db.Close() if err := db.PingContext(ctx); err != nil { t.Fatalf("ping in-memory db: %v", err) } var n int if err := db.QueryRowContext(ctx, "SELECT count(*) FROM sqlite_master WHERE type='table'").Scan(&n); err != nil { t.Fatalf("query sqlite_master: %v", err) } if n != 0 { t.Fatalf("expected no tables before migrations, got %d", n) } } func TestOpen_InMemory_NilLogger_正常(t *testing.T) { t.Parallel() ctx := context.Background() // A nil logger must not panic and must produce a usable connection. db, err := Open(ctx, ":memory:", nil) if err != nil { t.Fatalf("Open with nil logger error: %v", err) } defer db.Close() if err := db.PingContext(ctx); err != nil { t.Fatalf("ping with nil logger: %v", err) } } func TestOpen_InMemory_Isolation_并发(t *testing.T) { t.Parallel() ctx := context.Background() db1, err := Open(ctx, ":memory:", testLogger()) if err != nil { t.Fatalf("open db1: %v", err) } defer db1.Close() db2, err := Open(ctx, ":memory:", testLogger()) if err != nil { t.Fatalf("open db2: %v", err) } defer db2.Close() if _, err := db1.ExecContext(ctx, "CREATE TABLE t1(x)"); err != nil { t.Fatalf("create table in db1: %v", err) } var n int if err := db2.QueryRowContext(ctx, "SELECT count(*) FROM sqlite_master WHERE name='t1'").Scan(&n); err != nil { t.Fatalf("query db2: %v", err) } if n != 0 { t.Fatalf("db2 must not see db1's table (isolation broken), count=%d", n) } } func TestOpen_File_正常(t *testing.T) { t.Parallel() ctx := context.Background() path := filepath.Join(t.TempDir(), "cyrene.db") db, err := Open(ctx, path, testLogger()) if err != nil { t.Fatalf("Open(file) error: %v", err) } defer db.Close() if err := db.PingContext(ctx); err != nil { t.Fatalf("ping file db: %v", err) } // Persistence across a reopen with the same DSN. db.ExecContext(ctx, "CREATE TABLE p(x)") db.Close() db2, err := Open(ctx, path, testLogger()) if err != nil { t.Fatalf("reopen(file) error: %v", err) } defer db2.Close() var n int if err := db2.QueryRowContext(ctx, "SELECT count(*) FROM sqlite_master WHERE name='p'").Scan(&n); err != nil { t.Fatalf("query reopened db: %v", err) } if n != 1 { t.Fatalf("expected table p to persist across reopen, count=%d", n) } } func TestOpen_BlankPath_异常(t *testing.T) { t.Parallel() ctx := context.Background() for _, path := range []string{"", " "} { if _, err := Open(ctx, path, testLogger()); !errors.Is(err, ErrInvalidPath) { t.Fatalf("Open(%q) err = %v, want wrap of ErrInvalidPath", path, err) } } } func TestOpen_MissingParentDir_异常(t *testing.T) { t.Parallel() ctx := context.Background() // Parent directory does not exist, so the connection cannot be established. path := filepath.Join(t.TempDir(), "does-not-exist", "cyrene.db") if _, err := Open(ctx, path, testLogger()); err == nil { t.Fatalf("Open(%q) expected error for missing parent dir, got nil", path) } }