Files
Warp/server/internal/storage/storage_test.go
T
tajniak81andClaude Opus 5 b4fb60a8ab
CI / Test (ubuntu-latest) (push) Canceled after 0s
CI / Test (windows-latest) (push) Canceled after 0s
CI / Storage tests on PostgreSQL (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
CI / Smoke test the binary (push) Canceled after 0s
Phase 0: bootstrap the Warp homeserver
Scaffolds the server module and everything Phase 0 of PLAN.md calls for:
a binary that boots, serves its discovery endpoints and nothing else yet.

- config: one YAML file plus per-field WARP_* environment overrides,
  unknown fields rejected, validation that reports every problem at once
- logging and metrics: slog, a Prometheus registry, per-route HTTP
  instrumentation labelled by pattern rather than path
- httputil: the Matrix error envelope, error-returning handlers, CORS,
  request logging, panic recovery, and a router over http.ServeMux
- endpoints: /_matrix/client/versions, /.well-known/matrix/client,
  /.well-known/matrix/server (gated), /healthz, /metrics, and
  M_UNRECOGNIZED for everything else
- storage: driver registry, shared database/sql base, checksummed
  migration runner, sqlite and postgres drivers, pocketbase stub
- settings: server_settings table behind a cached service, so the
  federation and registration switches change without a restart
- federation: the gate that makes federation a runtime mode
- cmd/warp: serve, migrate, generate-keys, version
- Makefile, golangci-lint v2, GitHub Actions CI (Linux and Windows tests,
  race on Linux, storage tests against PostgreSQL, lint, boot smoke test),
  Dockerfile, example config and docs

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-15 17:01:13 +02:00

150 lines
4.3 KiB
Go

package storage_test
import (
"errors"
"os"
"path/filepath"
"slices"
"testing"
"github.com/dariuszjaniak/warp/server/internal/storage"
_ "github.com/dariuszjaniak/warp/server/internal/storage/drivers"
)
// openTestDB opens each driver that can run without external services. The
// postgres driver joins in when WARP_TEST_POSTGRES_DSN is set, which is how
// CI runs the same tests against both engines.
func openTestDB(t *testing.T, driver string) storage.Database {
t.Helper()
cfg := storage.Config{Driver: driver, MaxOpenConns: 4, MaxIdleConns: 2}
switch driver {
case storage.DriverSQLite:
path := filepath.ToSlash(filepath.Join(t.TempDir(), "warp.db"))
cfg.ConnectionString = "file:" + path + "?_pragma=journal_mode(WAL)&_pragma=busy_timeout(5000)"
case storage.DriverPostgres:
dsn := os.Getenv("WARP_TEST_POSTGRES_DSN")
if dsn == "" {
t.Skip("set WARP_TEST_POSTGRES_DSN to test the postgres driver")
}
cfg.ConnectionString = dsn
default:
t.Fatalf("no test setup for driver %q", driver)
}
db, err := storage.Open(t.Context(), cfg, nil)
if err != nil {
t.Fatalf("Open(%s): %v", driver, err)
}
t.Cleanup(func() { _ = db.Close() })
return db
}
// testDrivers are run by every storage test.
var testDrivers = []string{storage.DriverSQLite, storage.DriverPostgres}
func TestDriversRegistered(t *testing.T) {
got := storage.Drivers()
for _, want := range []string{storage.DriverSQLite, storage.DriverPostgres, storage.DriverPocketBase} {
if !slices.Contains(got, want) {
t.Errorf("driver %q is not registered (have %v)", want, got)
}
}
}
func TestOpenUnknownDriver(t *testing.T) {
_, err := storage.Open(t.Context(), storage.Config{Driver: "mysql"}, nil)
if err == nil {
t.Fatal("expected an error for an unknown driver")
}
}
func TestPocketBaseIsNotImplemented(t *testing.T) {
_, err := storage.Open(t.Context(), storage.Config{
Driver: storage.DriverPocketBase,
ConnectionString: "./pb_data",
}, nil)
if !errors.Is(err, storage.ErrNotImplemented) {
t.Fatalf("expected ErrNotImplemented, got %v", err)
}
}
func TestMigrateAndSettings(t *testing.T) {
for _, driver := range testDrivers {
t.Run(driver, func(t *testing.T) {
ctx := t.Context()
db := openTestDB(t, driver)
pending, err := db.PendingMigrations(ctx)
if err != nil {
t.Fatalf("PendingMigrations: %v", err)
}
if len(pending) == 0 {
t.Skip("database already migrated by a previous run")
}
if err := db.Migrate(ctx); err != nil {
t.Fatalf("Migrate: %v", err)
}
// Migrating twice must be a no-op, not an error: it runs on
// every boot.
if err := db.Migrate(ctx); err != nil {
t.Fatalf("second Migrate: %v", err)
}
pending, err = db.PendingMigrations(ctx)
if err != nil {
t.Fatalf("PendingMigrations: %v", err)
}
if len(pending) != 0 {
t.Errorf("after Migrate, still pending: %v", pending)
}
settings := db.Settings()
if _, found, err := settings.Get(ctx, "federation.enabled"); err != nil {
t.Fatalf("Get: %v", err)
} else if found {
t.Fatal("expected no stored value on a fresh database")
}
if err := settings.Set(ctx, "federation.enabled", "true"); err != nil {
t.Fatalf("Set: %v", err)
}
if err := settings.Set(ctx, "federation.enabled", "false"); err != nil {
t.Fatalf("Set (overwrite): %v", err)
}
value, found, err := settings.Get(ctx, "federation.enabled")
if err != nil || !found {
t.Fatalf("Get after Set: value=%q found=%v err=%v", value, found, err)
}
if value != "false" {
t.Errorf("value = %q, want %q (the upsert must overwrite)", value, "false")
}
all, err := settings.GetAll(ctx)
if err != nil {
t.Fatalf("GetAll: %v", err)
}
if all["federation.enabled"] != "false" {
t.Errorf("GetAll = %v", all)
}
if err := settings.Delete(ctx, "federation.enabled"); err != nil {
t.Fatalf("Delete: %v", err)
}
if err := settings.Delete(ctx, "federation.enabled"); err != nil {
t.Errorf("deleting an absent key must not fail: %v", err)
}
if _, found, _ := settings.Get(ctx, "federation.enabled"); found {
t.Error("value survived Delete")
}
if err := db.Ping(ctx); err != nil {
t.Errorf("Ping: %v", err)
}
if db.Driver() != driver {
t.Errorf("Driver() = %q, want %q", db.Driver(), driver)
}
})
}
}