test(maintenance): add policy executor and handler tests
Табличные тесты PolicyExecutor, ConfigProvider reload, memory CRUD политик и 503 для /v1/maintenance/* на memory-бэкенде без PostgreSQL. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
package maintenance
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"evobgp/internal/store"
|
||||
)
|
||||
|
||||
func TestConfigProviderReloadAndSnapshot(t *testing.T) {
|
||||
mem := store.NewMemory()
|
||||
ret := 3600
|
||||
if _, err := mem.CreateMaintenancePolicy(&store.MaintenancePolicy{
|
||||
Name: "p1",
|
||||
TableName: "job_audit",
|
||||
Schedule: "0 3 * * *",
|
||||
RetentionPeriodSec: &ret,
|
||||
Enabled: true,
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cp := NewConfigProvider(mem)
|
||||
if err := cp.Reload(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
snap := cp.Snapshot()
|
||||
if len(snap) != 1 || snap[0].Name != "p1" {
|
||||
t.Fatalf("snapshot: %+v", snap)
|
||||
}
|
||||
|
||||
newName := "p1-updated"
|
||||
if _, err := mem.UpdateMaintenancePolicy(snap[0].ID, &store.MaintenancePolicyPatch{Name: &newName}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := cp.Reload(context.Background()); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
snap2 := cp.Snapshot()
|
||||
if len(snap2) != 1 || snap2[0].Name != newName {
|
||||
t.Fatalf("after reload: %+v", snap2)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user