Files
pawsql/internal/postgres/resolver_test.go
Shaun Campbell 3a631f18f6
All checks were successful
Build and Push Image / docker-build-and-push (push) Successful in 6m12s
Test and Release PawSQL / test (push) Successful in 49s
Test and Release PawSQL / release (push) Successful in 9s
feat: read managed database passwords from secrets
2026-09-16 15:04:37 -04:00

199 lines
7.0 KiB
Go

package postgres
import (
"context"
"sync"
"testing"
"time"
config "cloud.campbellwireless.net/git/barkstack/barkfile-parser/v2"
"github.com/barkstack/pawsql/internal/router"
)
func TestResolverDefersManagedDatabaseStartup(t *testing.T) {
routes, err := router.NewStaticResolver([]config.DatabaseConfig{
{Name: "external", Upstream: "192.0.2.1:5432"},
{Name: "managed", Postgres: &config.PostgresConfig{Image: "postgres:18", Volume: "managed-data", PasswordSecret: "managed_password"}},
})
if err != nil {
t.Fatal(err)
}
ensurer := &fakeEnsurer{addresses: map[string]string{"managed": "127.0.0.1:55432"}}
resolver := NewResolver(routes, []config.DatabaseConfig{
{Name: "external", Upstream: "192.0.2.1:5432"},
{Name: "managed", Postgres: &config.PostgresConfig{Image: "postgres:18", Volume: "managed-data", PasswordSecret: "managed_password"}},
}, ensurer)
backend, err := resolver.ResolveDatabase(context.Background(), "external")
if err != nil {
t.Fatal(err)
}
if backend.Address != "192.0.2.1:5432" || ensurer.calls != 0 {
t.Errorf("external backend = %#v, calls = %d", backend, ensurer.calls)
}
backend, err = resolver.ResolveDatabase(context.Background(), "managed")
if err != nil {
t.Fatal(err)
}
if backend.Address != "127.0.0.1:55432" || ensurer.calls != 1 {
t.Errorf("managed backend = %#v, calls = %d", backend, ensurer.calls)
}
}
func TestResolverSerializesManagedStartup(t *testing.T) {
routes, err := router.NewStaticResolver([]config.DatabaseConfig{{Name: "managed", Postgres: &config.PostgresConfig{Image: "postgres:18", Volume: "managed-data", PasswordSecret: "managed_password"}}})
if err != nil {
t.Fatal(err)
}
ensurer := &fakeEnsurer{addresses: map[string]string{"managed": "127.0.0.1:55432"}, gate: make(chan struct{}), started: make(chan struct{})}
resolver := NewResolver(routes, []config.DatabaseConfig{{Name: "managed", Postgres: &config.PostgresConfig{Image: "postgres:18", Volume: "managed-data", PasswordSecret: "managed_password"}}}, ensurer)
var wg sync.WaitGroup
for range 2 {
wg.Add(1)
go func() {
defer wg.Done()
_, _ = resolver.ResolveDatabase(context.Background(), "managed")
}()
}
<-ensurer.started
close(ensurer.gate)
wg.Wait()
if ensurer.calls != 2 {
t.Errorf("EnsureDatabase calls = %d, want 2 route resolutions", ensurer.calls)
}
if ensurer.maxActive != 1 {
t.Errorf("concurrent EnsureDatabase calls = %d, want 1", ensurer.maxActive)
}
}
func TestResolverStopsDatabaseAfterIdleTimeout(t *testing.T) {
routes, err := router.NewStaticResolver([]config.DatabaseConfig{{Name: "managed", Postgres: &config.PostgresConfig{Image: "postgres:18", Volume: "managed-data", PasswordSecret: "managed_password", IdleTimeout: 20 * time.Millisecond}}})
if err != nil {
t.Fatal(err)
}
controller := &fakeEnsurer{addresses: map[string]string{"managed": "127.0.0.1:55432"}, stopped: make(chan string, 1)}
resolver := NewResolver(routes, []config.DatabaseConfig{{Name: "managed", Postgres: &config.PostgresConfig{Image: "postgres:18", Volume: "managed-data", PasswordSecret: "managed_password", IdleTimeout: 20 * time.Millisecond}}}, controller)
if _, err := resolver.ResolveDatabase(context.Background(), "managed"); err != nil {
t.Fatal(err)
}
resolver.ReleaseConnection("managed")
select {
case database := <-controller.stopped:
if database != "managed" {
t.Errorf("stopped database = %q", database)
}
case <-time.After(time.Second):
t.Fatal("managed database was not stopped after idle timeout")
}
}
func TestResolverCancelsIdleStopForNewConnection(t *testing.T) {
routes, err := router.NewStaticResolver([]config.DatabaseConfig{{Name: "managed", Postgres: &config.PostgresConfig{Image: "postgres:18", Volume: "managed-data", PasswordSecret: "managed_password", IdleTimeout: 40 * time.Millisecond}}})
if err != nil {
t.Fatal(err)
}
controller := &fakeEnsurer{addresses: map[string]string{"managed": "127.0.0.1:55432"}, stopped: make(chan string, 1)}
resolver := NewResolver(routes, []config.DatabaseConfig{{Name: "managed", Postgres: &config.PostgresConfig{Image: "postgres:18", Volume: "managed-data", PasswordSecret: "managed_password", IdleTimeout: 40 * time.Millisecond}}}, controller)
if _, err := resolver.ResolveDatabase(context.Background(), "managed"); err != nil {
t.Fatal(err)
}
resolver.ReleaseConnection("managed")
if _, err := resolver.ResolveDatabase(context.Background(), "managed"); err != nil {
t.Fatal(err)
}
select {
case database := <-controller.stopped:
t.Fatalf("stopped active database %q", database)
case <-time.After(80 * time.Millisecond):
}
resolver.ReleaseConnection("managed")
select {
case <-controller.stopped:
case <-time.After(time.Second):
t.Fatal("managed database was not stopped after final lease release")
}
}
func TestResolverMetersTrafficAndStopsTrafficIdleSession(t *testing.T) {
postgres := &config.PostgresConfig{
Image: "postgres:18",
Volume: "managed-data",
PasswordSecret: "managed_password",
TrafficIdleTimeout: 100 * time.Millisecond,
}
routes, err := router.NewStaticResolver([]config.DatabaseConfig{{Name: "managed", Postgres: postgres}})
if err != nil {
t.Fatal(err)
}
controller := &fakeEnsurer{addresses: map[string]string{"managed": "127.0.0.1:55432"}, stopped: make(chan string, 1)}
resolver := NewResolver(routes, []config.DatabaseConfig{{Name: "managed", Postgres: postgres}}, controller)
if _, err := resolver.ResolveDatabase(context.Background(), "managed"); err != nil {
t.Fatal(err)
}
time.Sleep(70 * time.Millisecond)
resolver.RecordTraffic("managed", true, 7)
resolver.RecordTraffic("managed", false, 11)
stats, ok := resolver.TrafficStats("managed")
if !ok || stats.ActiveConnections != 1 || stats.ClientBytes != 7 || stats.BackendBytes != 11 || stats.LastActivity.IsZero() {
t.Errorf("TrafficStats() = %#v, %t", stats, ok)
}
select {
case database := <-controller.stopped:
t.Fatalf("stopped database %q despite recent traffic", database)
case <-time.After(50 * time.Millisecond):
}
select {
case database := <-controller.stopped:
if database != "managed" {
t.Errorf("stopped database = %q", database)
}
case <-time.After(time.Second):
t.Fatal("managed database was not stopped after traffic idle timeout")
}
}
type fakeEnsurer struct {
mu sync.Mutex
addresses map[string]string
calls int
active int
maxActive int
gate chan struct{}
started chan struct{}
startedOnce sync.Once
stopped chan string
}
func (f *fakeEnsurer) EnsureDatabase(_ context.Context, database string, _ config.PostgresConfig) (string, error) {
f.mu.Lock()
f.calls++
f.active++
if f.active > f.maxActive {
f.maxActive = f.active
}
if f.started != nil {
f.startedOnce.Do(func() { close(f.started) })
}
f.mu.Unlock()
if f.gate != nil {
<-f.gate
}
f.mu.Lock()
f.active--
address := f.addresses[database]
f.mu.Unlock()
return address, nil
}
func (f *fakeEnsurer) StopDatabase(_ context.Context, database string) error {
if f.stopped != nil {
f.stopped <- database
}
return nil
}