package console import ( "context" "errors" "testing" "github.com/carlosframework/platform/internal/plans" "github.com/carlosframework/platform/internal/provision" ) func capsApp(t *testing.T, fx *consoleFixture, acct *Account, app string) { t.Helper() if _, err := fx.st.CreateAppIn(acct.ID, app, "owner@example.com", planNow); err != nil { t.Fatal(err) } } func capsInstance(t *testing.T, fx *consoleFixture, acct *Account, app, host string) { t.Helper() if _, err := fx.rel.Put(context.Background(), provision.RecordKey(acct.Sqid, app, host), []byte(`{}`), ""); err != nil { t.Fatal(err) } } func capsSidecar(t *testing.T, fx *consoleFixture, acct *Account, app, name string) { t.Helper() if _, err := fx.rel.Put(context.Background(), provision.SidecarRecordKey(acct.Sqid, app, name), []byte(`{}`), ""); err != nil { t.Fatal(err) } } func isCap(err error) bool { var c *planCapError return errors.As(err, &c) } func TestPlanChecksPassWithoutACatalog(t *testing.T) { fx := newConsoleFixture(t) acct := planAccount(t, fx, 501) for i := range 5 { capsApp(t, fx, acct, "app"+string(rune('a'+i))) } capsSidecar(t, fx, acct, "appa", "w1") capsSidecar(t, fx, acct, "appa", "w2") ctx := context.Background() for name, err := range map[string]error{ "running things": fx.srv.checkRunningThings(ctx, acct, 1), "sidecars": fx.srv.checkSidecars(ctx, acct, "appa"), "free account": fx.srv.checkNewFreeAccount("owner" + acct.Sqid + "@example.com"), "fleets": fx.srv.checkFleets(acct), "custom domains": fx.srv.checkCustomDomains(acct), } { if err != nil { t.Errorf("%s with no catalog: %v", name, err) } } } func TestLiveAppCountCountsRestoringNotTrashed(t *testing.T) { fx := newConsoleFixture(t) acct := planAccount(t, fx, 501) for _, app := range []string{"live", "gone", "coming-back"} { capsApp(t, fx, acct, app) } if _, err := fx.st.db.Exec(`UPDATE apps SET deleted_at = ? WHERE account_id = ? AND app IN ('gone', 'coming-back')`, planNow.Unix(), acct.ID); err != nil { t.Fatal(err) } if _, err := fx.st.db.Exec(`UPDATE apps SET restoring = 1 WHERE account_id = ? AND app = 'coming-back'`, acct.ID); err != nil { t.Fatal(err) } if n, err := fx.st.LiveAppCount(acct.ID); err != nil || n != 2 { t.Fatalf("LiveAppCount = %d, %v; want 2 (live + restoring)", n, err) } } func TestRunningThingsCountsAppsAndInstances(t *testing.T) { fx := newConsoleFixture(t) acct := planAccount(t, fx, 501) capsApp(t, fx, acct, "shop") capsApp(t, fx, acct, "blog") capsApp(t, fx, acct, "old") capsInstance(t, fx, acct, "shop", "a.shop.example.com") capsInstance(t, fx, acct, "shop", "b.shop.example.com") capsInstance(t, fx, acct, "blog", "c.blog.example.com") capsInstance(t, fx, acct, "old", "d.old.example.com") if err := fx.st.TrashAppRow(acct.ID, "old", planNow); err != nil { t.Fatal(err) } if n, err := fx.srv.runningThings(context.Background(), acct); err != nil || n != 5 { t.Fatalf("runningThings = %d, %v; want 5 (2 apps + 3 instances; the trashed app and its instance do not count)", n, err) } } func TestRunningThingsCapAtAndBelow(t *testing.T) { fx := newConsoleFixture(t) fx.srv.cfg.PlanCatalog = examplePlanCatalog(t) acct := planAccount(t, fx, 501) capsApp(t, fx, acct, "one") capsApp(t, fx, acct, "two") if err := fx.srv.checkRunningThings(context.Background(), acct, 1); err != nil { t.Fatalf("at 2 of 3: %v", err) } capsApp(t, fx, acct, "three") if err := fx.srv.checkRunningThings(context.Background(), acct, 1); !isCap(err) { t.Fatalf("at 3 of 3: %v, want a plan cap", err) } } func TestRunningThingsCapUsesEffectiveLimits(t *testing.T) { fx := newConsoleFixture(t) fx.srv.cfg.PlanCatalog = examplePlanCatalog(t) acct := planAccount(t, fx, 501) if _, err := fx.st.SetAccountPlan(acct.ID, "free", nil, PlanSourceSponsored, &plans.Limits{RunningThings: 300}, "existing account", "ops@example.com", planNow); err != nil { t.Fatal(err) } for i := range 4 { capsApp(t, fx, acct, "app"+string(rune('a'+i))) } if err := fx.srv.checkRunningThings(context.Background(), acct, 1); err != nil { t.Fatalf("sponsored 300, running 4: %v", err) } } func TestSidecarCap(t *testing.T) { fx := newConsoleFixture(t) fx.srv.cfg.PlanCatalog = examplePlanCatalog(t) acct := planAccount(t, fx, 501) capsApp(t, fx, acct, "shop") if err := fx.srv.checkSidecars(context.Background(), acct, "shop"); err != nil { t.Fatalf("free, no sidecars: %v", err) } capsSidecar(t, fx, acct, "shop", "w1") if err := fx.srv.checkSidecars(context.Background(), acct, "shop"); !isCap(err) { t.Fatalf("free, one sidecar: %v, want a plan cap", err) } if _, err := fx.st.SetAccountPlan(acct.ID, "personal", nil, PlanSourceOperator, nil, "r", "ops@example.com", planNow); err != nil { t.Fatal(err) } for _, n := range []string{"w2", "w3", "w4"} { capsSidecar(t, fx, acct, "shop", n) } if err := fx.srv.checkSidecars(context.Background(), acct, "shop"); err != nil { t.Fatalf("personal, four sidecars: %v", err) } capsSidecar(t, fx, acct, "shop", "w5") if err := fx.srv.checkSidecars(context.Background(), acct, "shop"); !isCap(err) { t.Fatalf("personal, five sidecars: %v, want a plan cap", err) } } func TestNewFreeAccountCap(t *testing.T) { fx := newConsoleFixture(t) fx.srv.cfg.PlanCatalog = examplePlanCatalog(t) acct := planAccount(t, fx, 501) owner := "owner" + acct.Sqid + "@example.com" if err := fx.srv.checkNewFreeAccount(owner); !isCap(err) { t.Fatalf("owner of one free account: %v, want a plan cap", err) } if _, err := fx.st.SetAccountPlan(acct.ID, "personal", nil, PlanSourceOperator, nil, "r", "ops@example.com", planNow); err != nil { t.Fatal(err) } if err := fx.srv.checkNewFreeAccount(owner); err != nil { t.Fatalf("owner of one paid account: %v", err) } member := "member@example.com" other := planAccount(t, fx, 502) if err := fx.st.AddMember(other.ID, member, RoleMember, planNow); err != nil { t.Fatal(err) } if err := fx.srv.checkNewFreeAccount(member); err != nil { t.Fatalf("member, not owner, of a free account: %v", err) } } func TestFleetsAndCustomDomainsFollowThePlan(t *testing.T) { fx := newConsoleFixture(t) fx.srv.cfg.PlanCatalog = examplePlanCatalog(t) acct := planAccount(t, fx, 501) if !isCap(fx.srv.checkFleets(acct)) || !isCap(fx.srv.checkCustomDomains(acct)) { t.Fatal("free plan allowed fleets or custom domains") } if _, err := fx.st.SetAccountPlan(acct.ID, "personal", nil, PlanSourceOperator, nil, "r", "ops@example.com", planNow); err != nil { t.Fatal(err) } if fx.srv.checkFleets(acct) != nil || fx.srv.checkCustomDomains(acct) != nil { t.Fatal("personal plan refused fleets or custom domains") } } // Astra plans-3a finding 1: restoring an app brings back its retained // instance records too, so the check must count the whole footprint. func TestRestoreCountsTheAppsInstances(t *testing.T) { fx := newConsoleFixture(t) fx.srv.cfg.PlanCatalog = examplePlanCatalog(t) acct := planAccount(t, fx, 501) capsApp(t, fx, acct, "live") capsApp(t, fx, acct, "old") capsInstance(t, fx, acct, "old", "a.old.example.com") capsInstance(t, fx, acct, "old", "b.old.example.com") if err := fx.st.TrashAppRow(acct.ID, "old", planNow); err != nil { t.Fatal(err) } // One running thing of three: restoring "old" would make four. if err := fx.srv.checkRestore(context.Background(), acct, "old"); !isCap(err) { t.Fatalf("restore bringing back an app and two instances at 1 of 3: %v, want a plan cap", err) } } // Astra plans-3a finding 3: adopting an app whose objects already exist in // the bucket brings its instance records with it. func TestCreateCountsAnAdoptedAppsInstances(t *testing.T) { fx := newConsoleFixture(t) fx.srv.cfg.PlanCatalog = examplePlanCatalog(t) acct := planAccount(t, fx, 501) capsApp(t, fx, acct, "live") capsInstance(t, fx, acct, "legacy", "a.legacy.example.com") capsInstance(t, fx, acct, "legacy", "b.legacy.example.com") if err := fx.srv.checkCreateApp(context.Background(), acct, "legacy"); !isCap(err) { t.Fatalf("adopting an app with two instances at 1 of 3: %v, want a plan cap", err) } if err := fx.srv.checkCreateApp(context.Background(), acct, "fresh"); err != nil { t.Fatalf("a new app at 1 of 3: %v", err) } }