Табличные тесты PolicyExecutor, ConfigProvider reload, memory CRUD политик и 503 для /v1/maintenance/* на memory-бэкенде без PostgreSQL. Co-authored-by: Cursor <cursoragent@cursor.com>
162 lines
4.1 KiB
Go
162 lines
4.1 KiB
Go
package maintenance
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
|
|
"evobgp/internal/store"
|
|
)
|
|
|
|
func validPolicy() *store.MaintenancePolicy {
|
|
ret := 86400
|
|
return &store.MaintenancePolicy{
|
|
ID: "11111111-1111-1111-1111-111111111111",
|
|
Name: "job audit",
|
|
TableName: "job_audit",
|
|
Condition: "true",
|
|
RetentionPeriodSec: &ret,
|
|
VacuumStrategy: store.VacuumStrategyNone,
|
|
Schedule: "0 3 * * *",
|
|
Enabled: true,
|
|
}
|
|
}
|
|
|
|
func TestPolicyExecutorExecuteValidation(t *testing.T) {
|
|
ctx := context.Background()
|
|
mem := store.NewMemory()
|
|
base := validPolicy()
|
|
|
|
tests := []struct {
|
|
name string
|
|
exec *PolicyExecutor
|
|
policy *store.MaintenancePolicy
|
|
wantErr error
|
|
contains string
|
|
}{
|
|
{
|
|
name: "nil policy",
|
|
exec: &PolicyExecutor{Store: mem},
|
|
policy: nil,
|
|
wantErr: store.ErrInvalidInput,
|
|
},
|
|
{
|
|
name: "nil pool",
|
|
exec: &PolicyExecutor{Store: mem, Pool: nil},
|
|
policy: base,
|
|
contains: "postgres not configured",
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, err := tc.exec.Execute(ctx, tc.policy, false)
|
|
if tc.wantErr != nil {
|
|
if !errors.Is(err, tc.wantErr) {
|
|
t.Fatalf("Execute() err=%v want %v", err, tc.wantErr)
|
|
}
|
|
return
|
|
}
|
|
if err == nil {
|
|
t.Fatal("Execute() expected error")
|
|
}
|
|
if tc.contains != "" && !strings.Contains(err.Error(), tc.contains) {
|
|
t.Fatalf("Execute() err=%q want substring %q", err, tc.contains)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestPolicyExecutorPreExecuteValidation(t *testing.T) {
|
|
p := validPolicy()
|
|
p.TableName = "tenant"
|
|
if err := ValidateTableName(p.TableName); err == nil {
|
|
t.Fatal("expected blocked table error")
|
|
}
|
|
p = validPolicy()
|
|
p.Condition = "1=1; DROP TABLE job_audit"
|
|
if err := ValidateCondition(p.Condition); err == nil {
|
|
t.Fatal("expected unsafe condition error")
|
|
}
|
|
p = validPolicy()
|
|
p.VacuumStrategy = "invalid"
|
|
if !store.ValidVacuumStrategy(p.VacuumStrategy) {
|
|
return
|
|
}
|
|
t.Fatal("expected invalid vacuum strategy")
|
|
}
|
|
|
|
func TestPolicyAction(t *testing.T) {
|
|
ret := 3600
|
|
tests := []struct {
|
|
name string
|
|
p *store.MaintenancePolicy
|
|
want string
|
|
}{
|
|
{"nil", nil, "run"},
|
|
{"cleanup only", &store.MaintenancePolicy{RetentionPeriodSec: &ret, VacuumStrategy: store.VacuumStrategyNone}, "cleanup"},
|
|
{"vacuum only", &store.MaintenancePolicy{VacuumStrategy: store.VacuumStrategyVacuum}, "vacuum"},
|
|
{"cleanup+vacuum", &store.MaintenancePolicy{MaxRows: &ret, VacuumStrategy: store.VacuumStrategyAnalyze}, "cleanup_vacuum"},
|
|
{"noop run", &store.MaintenancePolicy{VacuumStrategy: store.VacuumStrategyNone}, "run"},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := policyAction(tc.p); got != tc.want {
|
|
t.Fatalf("policyAction()=%q want %q", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestVacuumKind(t *testing.T) {
|
|
tests := []struct {
|
|
strategy string
|
|
want string
|
|
}{
|
|
{store.VacuumStrategyVacuum, "vacuum"},
|
|
{store.VacuumStrategyAnalyze, "analyze"},
|
|
{store.VacuumStrategyVacuumAnalyze, "vacuum_analyze"},
|
|
{store.VacuumStrategyReindex, "reindex"},
|
|
{"unknown", "vacuum"},
|
|
}
|
|
for _, tc := range tests {
|
|
if got := vacuumKind(tc.strategy); got != tc.want {
|
|
t.Fatalf("vacuumKind(%q)=%q want %q", tc.strategy, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRowsDeletedFromDetail(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
detail map[string]any
|
|
want int64
|
|
}{
|
|
{"nil", nil, 0},
|
|
{"int64", map[string]any{"deleted": int64(42)}, 42},
|
|
{"int", map[string]any{"deleted": 7}, 7},
|
|
{"float64", map[string]any{"deleted": float64(3)}, 3},
|
|
{"missing", map[string]any{"other": 1}, 0},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
if got := rowsDeletedFromDetail(tc.detail); got != tc.want {
|
|
t.Fatalf("rowsDeletedFromDetail()=%d want %d", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAdvisoryKeyStable(t *testing.T) {
|
|
a := advisoryKey("policy-a")
|
|
b := advisoryKey("policy-a")
|
|
c := advisoryKey("policy-b")
|
|
if a != b {
|
|
t.Fatal("advisory key not stable for same id")
|
|
}
|
|
if a == c {
|
|
t.Fatal("advisory key collision for different ids")
|
|
}
|
|
}
|