proxy-pool/internal/adapters/postgresadmin/adapter_external_test.go
2026-08-02 15:14:20 +08:00

249 lines
6.8 KiB
Go

package postgresadmin_test
import (
"context"
"errors"
"strings"
"sync/atomic"
"testing"
"time"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgconn"
"github.com/jackc/pgx/v5/pgxpool"
"proxy-pool/internal/adapters/postgresadmin"
"proxy-pool/internal/domain/adminstate"
)
func TestNewRejectsNilPool(t *testing.T) {
t.Parallel()
store, err := postgresadmin.New(nil)
if store != nil {
t.Fatalf("New(nil) store = %T, want nil", store)
}
if !errors.Is(err, adminstate.ErrInvalidCommand) {
t.Fatalf("New(nil) error = %v, want ErrInvalidCommand", err)
}
}
func TestNewRejectsTypedNilPool(t *testing.T) {
t.Parallel()
var pool *pgxpool.Pool
store, err := postgresadmin.New(pool)
if store != nil || !errors.Is(err, adminstate.ErrInvalidCommand) {
t.Fatalf("New(typed nil) = %T, %v, want nil, ErrInvalidCommand", store, err)
}
}
func TestOperationsPrioritizeCanceledContext(t *testing.T) {
t.Parallel()
pool := &countingPool{}
store, err := postgresadmin.New(pool)
if err != nil {
t.Fatalf("New(): %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
cancel()
operations := []struct {
name string
run func() error
}{
{name: "commit config", run: func() error {
_, operationErr := store.CommitConfig(ctx, adminstate.CommitConfigCommand{})
return operationErr
}},
{name: "set upstream", run: func() error {
_, operationErr := store.SetUpstreamEnabled(ctx, adminstate.SetUpstreamCommand{})
return operationErr
}},
{name: "switch routing", run: func() error {
_, operationErr := store.SwitchRouting(ctx, adminstate.SwitchRoutingCommand{})
return operationErr
}},
{name: "disable routing", run: func() error {
_, operationErr := store.DisableRouting(ctx, adminstate.DisableRoutingCommand{})
return operationErr
}},
{name: "snapshot", run: func() error {
_, operationErr := store.Snapshot(ctx)
return operationErr
}},
{name: "read audit", run: func() error {
_, operationErr := store.ReadAudit(ctx, adminstate.AuditQuery{})
return operationErr
}},
{name: "claim", run: func() error {
_, operationErr := store.Claim(ctx, adminstate.ClaimCommand{})
return operationErr
}},
{name: "acknowledge", run: func() error {
return store.Acknowledge(ctx, adminstate.AcknowledgeCommand{})
}},
}
for _, operation := range operations {
operation := operation
t.Run(operation.name, func(t *testing.T) {
if operationErr := operation.run(); !errors.Is(operationErr, context.Canceled) {
t.Fatalf("operation error = %v, want context.Canceled", operationErr)
}
})
}
if begins := pool.begins.Load(); begins != 0 {
t.Fatalf("BeginTx calls = %d, want 0", begins)
}
}
func TestInvalidOperationsDoNotBeginTransaction(t *testing.T) {
t.Parallel()
pool := &countingPool{}
store, err := postgresadmin.New(pool)
if err != nil {
t.Fatalf("New(): %v", err)
}
operations := []struct {
name string
run func() error
}{
{name: "commit config", run: func() error {
_, operationErr := store.CommitConfig(context.Background(), adminstate.CommitConfigCommand{})
return operationErr
}},
{name: "set upstream", run: func() error {
_, operationErr := store.SetUpstreamEnabled(context.Background(), adminstate.SetUpstreamCommand{})
return operationErr
}},
{name: "switch routing", run: func() error {
_, operationErr := store.SwitchRouting(context.Background(), adminstate.SwitchRoutingCommand{})
return operationErr
}},
{name: "disable routing", run: func() error {
_, operationErr := store.DisableRouting(context.Background(), adminstate.DisableRoutingCommand{})
return operationErr
}},
{name: "read audit", run: func() error {
_, operationErr := store.ReadAudit(context.Background(), adminstate.AuditQuery{})
return operationErr
}},
{name: "claim", run: func() error {
_, operationErr := store.Claim(context.Background(), adminstate.ClaimCommand{})
return operationErr
}},
{name: "acknowledge", run: func() error {
return store.Acknowledge(context.Background(), adminstate.AcknowledgeCommand{})
}},
}
for _, operation := range operations {
operation := operation
t.Run(operation.name, func(t *testing.T) {
if operationErr := operation.run(); !errors.Is(operationErr, adminstate.ErrInvalidCommand) {
t.Fatalf("operation error = %v, want ErrInvalidCommand", operationErr)
}
})
}
if begins := pool.begins.Load(); begins != 0 {
t.Fatalf("BeginTx calls = %d, want 0", begins)
}
}
func TestDatabaseErrorsAreMappedAndRedacted(t *testing.T) {
t.Parallel()
tests := []struct {
name string
err error
want error
}{
{
name: "connection",
err: errors.New("postgres://admin:secret@database/private SQL payload"),
want: adminstate.ErrUnavailable,
},
{
name: "unique constraint",
err: &pgconn.PgError{
Code: "23505",
Message: "duplicate cfg-sensitive at postgres://admin:secret@database",
},
want: adminstate.ErrConflict,
},
{
name: "foreign key constraint",
err: &pgconn.PgError{
Code: "23503",
Message: "backend revision reference failed with secret details",
},
want: adminstate.ErrUnavailable,
},
{
name: "check constraint",
err: &pgconn.PgError{
Code: "23514",
Message: "backend invariant failed with secret details",
},
want: adminstate.ErrUnavailable,
},
{
name: "value too long",
err: &pgconn.PgError{
Code: "22001",
Message: "backend encoding failed with secret details",
},
want: adminstate.ErrUnavailable,
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
store, err := postgresadmin.New(&errorPool{err: test.err})
if err != nil {
t.Fatalf("New(): %v", err)
}
_, operationErr := store.CommitConfig(context.Background(), validConfigCommand())
if !errors.Is(operationErr, test.want) {
t.Fatalf("CommitConfig() error = %v, want %v", operationErr, test.want)
}
for _, secret := range []string{"secret", "private SQL", "cfg-sensitive", "postgres://"} {
if strings.Contains(operationErr.Error(), secret) {
t.Fatalf("CommitConfig() error leaked %q: %v", secret, operationErr)
}
}
})
}
}
type countingPool struct {
begins atomic.Int64
}
func (pool *countingPool) BeginTx(context.Context, pgx.TxOptions) (pgx.Tx, error) {
pool.begins.Add(1)
return nil, errors.New("unexpected transaction")
}
type errorPool struct {
err error
}
func (pool *errorPool) BeginTx(context.Context, pgx.TxOptions) (pgx.Tx, error) {
return nil, pool.err
}
func validConfigCommand() adminstate.CommitConfigCommand {
return adminstate.CommitConfigCommand{
RequestID: "request-a",
Actor: adminstate.Actor{ID: "admin-a", SourceIP: "192.0.2.10"},
OccurredAt: time.Date(2026, 7, 30, 10, 0, 0, 0, time.UTC),
ConfigVersion: "cfg-sensitive",
Checksum: strings.Repeat("a", adminstate.SHA256HexBytes),
Source: "configs/proxy-pool.yaml",
}
}