//go:build integration package inttest import ( "context" "testing" "github.com/google/uuid" "scrabble/backend/internal/payments" ) // TestPaymentAvailabilityKillSwitch exercises the per-rail kill switch + the per-account override // end-to-end against Postgres: fail-open, a disabled rail with a localized message, per-rail // granularity, and the allow/deny/default overrides. func TestPaymentAvailabilityKillSwitch(t *testing.T) { ctx := context.Background() svc := newPaymentsService() acc := uuid.New() // Fail-open: an untouched rail is enabled. if ok, _, err := svc.CanPurchase(ctx, acc, payments.RailDirectWeb, "en"); err != nil || !ok { t.Fatalf("fail-open: CanPurchase = %v/%v, want true", ok, err) } // Disable the web rail with a per-language message → blocked with that message, localized. if err := svc.SetRailStatus(ctx, payments.RailDirectWeb, payments.RailAvailability{MessageRU: "Чиним", MessageEN: "Fixing"}); err != nil { t.Fatalf("set rail status: %v", err) } if ok, reason, err := svc.CanPurchase(ctx, acc, payments.RailDirectWeb, "ru"); err != nil || ok || reason != "Чиним" { t.Fatalf("disabled rail (ru): CanPurchase = %v/%q/%v, want false/Чиним", ok, reason, err) } if ok, reason, _ := svc.CanPurchase(ctx, acc, payments.RailDirectWeb, "en"); ok || reason != "Fixing" { t.Fatalf("disabled rail (en): = %v/%q, want false/Fixing", ok, reason) } // Per-rail granularity: another rail stays enabled. if ok, _, _ := svc.CanPurchase(ctx, acc, payments.RailDirectAndroid, "en"); !ok { t.Fatalf("android rail should stay enabled") } // A per-account "allow" override bypasses the disabled rail. if err := svc.SetPurchaseOverride(ctx, acc, payments.OverrideAllow); err != nil { t.Fatalf("set override allow: %v", err) } if ok, _, _ := svc.CanPurchase(ctx, acc, payments.RailDirectWeb, "en"); !ok { t.Fatalf("allow override should bypass the disabled rail") } if ov, err := svc.PurchaseOverrideFor(ctx, acc); err != nil || ov != payments.OverrideAllow { t.Fatalf("PurchaseOverrideFor = %v/%v, want allow", ov, err) } // A "deny" override blocks even an enabled rail. if err := svc.SetPurchaseOverride(ctx, acc, payments.OverrideDeny); err != nil { t.Fatalf("set override deny: %v", err) } if ok, reason, _ := svc.CanPurchase(ctx, acc, payments.RailDirectAndroid, "en"); ok || reason == "" { t.Fatalf("deny override should block the enabled android rail with a reason, got %v/%q", ok, reason) } // Clearing the override (default) restores rail-driven behaviour. if err := svc.SetPurchaseOverride(ctx, acc, payments.OverrideDefault); err != nil { t.Fatalf("clear override: %v", err) } if ov, _ := svc.PurchaseOverrideFor(ctx, acc); ov != payments.OverrideDefault { t.Fatalf("after clear, override = %v, want default", ov) } if ok, _, _ := svc.CanPurchase(ctx, acc, payments.RailDirectAndroid, "en"); !ok { t.Fatalf("after clearing override, android rail should be enabled again") } // RailStatuses reflects the stored web-rail change and fills the rest fail-open. all, err := svc.RailStatuses(ctx) if err != nil { t.Fatalf("rail statuses: %v", err) } if all[payments.RailDirectWeb].Enabled { t.Fatalf("web rail should read disabled") } if !all[payments.RailVK].Enabled { t.Fatalf("untouched vk rail should read enabled") } }