Files
summercms/lagoon/connection_test.go
Jakub Zych 8d9109a771 feat(05-03): implement lagoon.Encrypted, key:generate, Laravel decrypt helper
- AES-256-GCM with stdlib HKDF column keys and previous_keys fallback
- Fail-loud LoadAppKey, redacting marshal paths, key:generate via crypto/rand
- Standalone DecryptLaravelPayload unwired from Scan/Value
2026-09-18 19:35:24 +02:00

166 lines
4.1 KiB
Go

package lagoon
import (
"bytes"
"database/sql"
"encoding/base64"
"os"
"path/filepath"
"strings"
"testing"
"git.golem15.com/golem15/summercms/backpack"
"git.golem15.com/golem15/summercms/compass"
"gorm.io/gorm"
)
func TestUseRejectsNilSQL(t *testing.T) {
_, err := Use(t.Context(), nil)
if err == nil || !strings.Contains(err.Error(), "sql db is nil") {
t.Fatalf("got %v", err)
}
}
func TestCheckLocaleRejectsNilSQL(t *testing.T) {
err := CheckLocale(t.Context(), nil)
if err == nil || !strings.Contains(err.Error(), "sql db is nil") {
t.Fatalf("got %v", err)
}
}
func TestPublishRejectsNilHandles(t *testing.T) {
app := backpack.New(nil)
if err := Publish(nil, nil, nil); err == nil {
t.Fatal("want nil app error")
}
if err := Publish(app, nil, &gorm.DB{}); err == nil || !strings.Contains(err.Error(), "nil") {
t.Fatalf("got %v", err)
}
}
func TestDSNEmptyWithoutConfig(t *testing.T) {
if DSN(nil) != "" {
t.Fatal("nil config must yield empty DSN")
}
}
func TestOpenFromAppMissingConfig(t *testing.T) {
_, _, err := OpenFromApp(t.Context(), nil)
if err == nil || !strings.Contains(err.Error(), "app config is missing") {
t.Fatalf("got %v", err)
}
_, _, err = OpenFromApp(t.Context(), backpack.New(nil))
if err == nil || !strings.Contains(err.Error(), "app config is missing") {
t.Fatalf("got %v", err)
}
}
func TestSharedSQLPoolUsedByGORMAndClosed(t *testing.T) {
_, dsn := dedicatedDB(t, "lagoon_shared")
sqlDB, gdb, err := Open(t.Context(), dsn)
if err != nil {
t.Fatal(err)
}
raw, err := gdb.DB()
if err != nil {
t.Fatal(err)
}
if raw != sqlDB {
t.Fatal("GORM is not using the shared *sql.DB")
}
app := backpack.New(nil)
if err := Publish(app, sqlDB, gdb); err != nil {
t.Fatal(err)
}
gotSQL, ok := app.Lookup[*sql.DB]()
if !ok || gotSQL != sqlDB {
t.Fatal("published *sql.DB is not the shared pool")
}
gotGORM, ok := app.Lookup[*gorm.DB]()
if !ok || gotGORM != gdb {
t.Fatal("published *gorm.DB is missing")
}
viaUse, err := Use(t.Context(), sqlDB)
if err != nil {
t.Fatal(err)
}
useRaw, err := viaUse.DB()
if err != nil {
t.Fatal(err)
}
if useRaw != sqlDB {
t.Fatal("Use must open GORM on the caller pool, not a second connection")
}
if err := sqlDB.Close(); err != nil {
t.Fatal(err)
}
if err := sqlDB.PingContext(t.Context()); err == nil {
t.Fatal("closed pool must reject Ping")
}
if _, err := Use(t.Context(), sqlDB); err == nil {
t.Fatal("Use must fail after the shared pool is closed")
}
}
func TestWrongICULocaleFailsOpen(t *testing.T) {
admin := lagoonDB(t)
ctx := t.Context()
if _, err := admin.ExecContext(ctx, `CREATE DATABASE lagoon_locale_fail TEMPLATE template0 ENCODING 'UTF8' LOCALE_PROVIDER libc LOCALE 'C'`); err != nil && !strings.Contains(err.Error(), "already exists") {
t.Fatalf("create libc database: %v", err)
}
t.Cleanup(func() {
_, _ = admin.ExecContext(ctx, `DROP DATABASE IF EXISTS lagoon_locale_fail WITH (FORCE)`)
})
failDSN, err := dsnWithDB(lagoonDSN, "lagoon_locale_fail")
if err != nil {
t.Fatal(err)
}
_, _, err = Open(ctx, failDSN)
if err == nil {
t.Fatal("wrong locale must fail boot")
}
msg := err.Error()
for _, want := range []string{"ICU", "pl-PL", "CREATE DATABASE", "LOCALE_PROVIDER icu"} {
if !strings.Contains(msg, want) {
t.Fatalf("missing %q in %s", want, msg)
}
}
}
func TestOpenFromAppReadsDSN(t *testing.T) {
_, dsn := dedicatedDB(t, "lagoon_from_app")
dir := t.TempDir()
if err := os.WriteFile(filepath.Join(dir, "app.yaml"), []byte("name: lagoon-test\n"), 0o644); err != nil {
t.Fatal(err)
}
cfg, err := compass.Open(compass.Options{
Dir: dir,
Environ: []string{
"SUMMER_ENV=development",
"SUMMER_DATABASE__DSN=" + dsn,
"SUMMER_APP__KEY=" + base64.StdEncoding.EncodeToString(bytes.Repeat([]byte("T"), 32)),
},
})
if err != nil {
t.Fatal(err)
}
if got := DSN(cfg); got != dsn {
t.Fatalf("DSN = %q want %q", got, dsn)
}
sqlDB, gdb, err := OpenFromApp(t.Context(), backpack.New(cfg))
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = sqlDB.Close() })
raw, err := gdb.DB()
if err != nil {
t.Fatal(err)
}
if raw != sqlDB {
t.Fatal("OpenFromApp must share the same *sql.DB with GORM")
}
}