package console import ( "context" "fmt" "net/http" "net/url" "strings" "testing" "time" "github.com/carlosframework/platform/internal/objstore" "github.com/carlosframework/platform/internal/usage" ) func TestStoreAccountPanelAndPrivateAudit(t *testing.T) { fx := newConsoleFixture(t) c := newBrowser(t) signUp(t, fx, c, "store-panel") putEntitlement(t, fx, fx.sqid, &objstore.Entitlement{Applied: &objstore.Applied{By: "paul@keymail.dev", At: time.Now(), Note: "PRIVATE-NOTE"}, Decision: &objstore.Decision{By: "PRIVATE-OPERATOR", Reason: "PRIVATE-REASON"}}) for _, e := range []struct{ action, detail string }{{"store-apply", "PRIVATE-NOTE"}, {"store-decline", "PRIVATE-REASON"}, {"store-approve", "approved"}} { if err := fx.st.RecordEvent(OpsAccountID, "", "PRIVATE-OPERATOR", e.action, e.detail, time.Now()); err != nil { t.Fatal(err) } } status, _, body := get(t, c, fx.u("/settings")) if status != 200 || !strings.Contains(body, "Object storage") || !strings.Contains(body, "Declined") || !strings.Contains(body, "Applied for object storage") || !strings.Contains(body, "Object storage declined") || !strings.Contains(body, "Object storage approved") { t.Fatalf("panel = %d %s", status, body) } if strings.Contains(body, "PRIVATE-") { t.Fatal("tenant settings leaked private entitlement/audit fields") } if !strings.Contains(body, `/store/apply`) { t.Fatal("owner has no apply control") } if err := fx.st.AddMember(OpsAccountID, "member@keymail.dev", RoleMember, time.Now()); err != nil { t.Fatal(err) } fx.setUser("member@keymail.dev") member := newBrowser(t) signUp(t, fx, member, "store-member") _, _, body = get(t, member, fx.u("/settings")) if strings.Contains(body, `/store/apply`) || strings.Contains(body, "PRIVATE-") || !strings.Contains(body, "Declined") { t.Fatal("member panel is not private and read-only") } status, _, _ = postForm(t, member, fx.u("/store/apply"), url.Values{"csrf": {sessionToken(t, fx, member)}}) if status != http.StatusNotFound { t.Fatalf("member apply=%d", status) } status, _, _ = postForm(t, c, fx.u("/store/apply"), url.Values{"csrf": {"wrong"}}) if status != http.StatusForbidden { t.Fatalf("bad csrf=%d", status) } status, _, body = postForm(t, c, fx.u("/store/apply"), url.Values{"csrf": {sessionToken(t, fx, c)}, "note": {"replacement"}}) if status != http.StatusSeeOther { t.Fatalf("apply=%d %s", status, body) } if e := storedEntitlement(t, fx, fx.sqid); !e.Pending() || e.Applied.Note != "replacement" { t.Fatalf("apply=%+v", e) } } func TestStoreQueueGateAndNonmemberDecision(t *testing.T) { fx := newConsoleFixture(t) putEntitlement(t, fx, fx.sqid, &objstore.Entitlement{Applied: &objstore.Applied{By: "paul@keymail.dev", At: time.Now(), Note: "operator-only-note"}}) c := newBrowser(t) fx.setUser("reviewer@keymail.dev") signUp(t, fx, c, "store-reviewer") for _, auth := range []struct{ fleet, listed bool }{{false, false}, {false, true}, {true, false}, {true, true}} { if err := fx.st.SetOperator("reviewer@keymail.dev", auth.fleet); err != nil { t.Fatal(err) } fx.srv.cfg.StoreOperators = nil if auth.listed { fx.srv.cfg.StoreOperators = []string{" REVIEWER@KEYMAIL.DEV "} } status, _, body := get(t, c, fx.ts.URL+"/fleet/store") if !auth.fleet || !auth.listed { if status != 404 || body != "404 page not found\n" { t.Fatalf("gate=%d %q", status, body) } continue } if status != 200 || !strings.Contains(body, "operator-only-note") { t.Fatalf("queue=%d %s", status, body) } _, _, body = get(t, c, fx.ts.URL+"/fleet") if !strings.Contains(body, "1 pending") { t.Fatal("fleet has no pending count") } } target := fx.ts.URL + "/fleet/store/" + fx.sqid + "/approve" status, _, _ := postForm(t, c, target, url.Values{"csrf": {"wrong"}}) if status != 403 { t.Fatalf("decision csrf=%d", status) } status, loc, _ := postForm(t, c, target, url.Values{"csrf": {sessionToken(t, fx, c)}}) if status != 303 || !strings.Contains(loc, "/oauth/") || storedEntitlement(t, fx, fx.sqid).Approved() { t.Fatalf("approval did not wait for fresh auth: %d %s", status, loc) } status, _, body := completeStepUp(t, fx, c, loc) if status != 303 { t.Fatalf("decision=%d %s", status, body) } if e := storedEntitlement(t, fx, fx.sqid); !e.Approved() || e.Decision.By != "reviewer@keymail.dev" { t.Fatalf("decision=%+v", e) } } func TestStoreBucketCountIsDatedAndNeverInventsZero(t *testing.T) { for _, tc := range []struct { name, body string count int known bool }{ {"empty", `{"v":1,"date":"2026-09-08","apps":{}}`, 0, true}, {"provisioned including empty and unswept", `{"v":1,"date":"2026-09-08","apps":{"bab/a":{"store_provisioned":true},"bab/b":{"store_provisioned":true,"store_unswept":true},"bab/c":{"store_provisioned":false}}}`, 2, true}, {"legacy", `{"v":1,"date":"2026-09-08","apps":{"bab/a":{"store_bytes":20}}}`, 0, false}, {"record unreadable", `{"v":1,"date":"2026-09-08","apps":{"bab/a":{"store_provisioned":false,"store_unswept":true}}}`, 0, false}, {"malformed", `{`, 0, false}, } { t.Run(tc.name, func(t *testing.T) { fx := newConsoleFixture(t) if _, err := fx.rel.Put(context.Background(), "usage/storage/2026-09-08.json", []byte(tc.body), ""); err != nil { t.Fatal(err) } n, date, known, _ := fx.srv.storeBucketCount(context.Background()) if n != tc.count || known != tc.known || date != "2026-09-08" { t.Fatalf("count=%d %s %v", n, date, known) } }) } } func TestStoreAccountPanelStatesAndIsolation(t *testing.T) { for _, state := range []string{"Not applied", "Pending", "Approved"} { t.Run(state, func(t *testing.T) { fx := newConsoleFixture(t) c := newBrowser(t) signUp(t, fx, c, "store-states") if state != "Not applied" { e := &objstore.Entitlement{Applied: &objstore.Applied{By: "private-owner", Note: "private-note", At: time.Now()}} if state == "Approved" { e.Decision = &objstore.Decision{Approved: true, By: "private-operator"} } putEntitlement(t, fx, fx.sqid, e) } if err := fx.st.RecordEvent(OpsAccountID+999, "", "other-account", "store-apply", "other-account-note", time.Now()); err != nil { t.Fatal(err) } status, _, body := get(t, c, fx.u("/settings")) if status != 200 || !strings.Contains(body, state) || strings.Contains(body, "private-") || strings.Contains(body, "other-account") { t.Fatalf("state=%d %s", status, state) } }) } } func TestStoreApprovalReplayRechecksOperatorAuthority(t *testing.T) { for _, revoke := range []string{"fleet", "allowlist"} { t.Run(revoke, func(t *testing.T) { fx := newConsoleFixture(t) c := newBrowser(t) fx.setUser("reviewer@keymail.dev") signUp(t, fx, c, "replay") if err := fx.st.SetOperator("reviewer@keymail.dev", true); err != nil { t.Fatal(err) } fx.srv.cfg.StoreOperators = []string{"reviewer@keymail.dev"} putEntitlement(t, fx, fx.sqid, &objstore.Entitlement{Applied: &objstore.Applied{By: "owner@keymail.dev", At: time.Now()}}) status, loc, _ := postForm(t, c, fx.ts.URL+"/fleet/store/"+fx.sqid+"/approve", url.Values{"csrf": {sessionToken(t, fx, c)}}) if status != 303 { t.Fatalf("gate=%d", status) } if revoke == "fleet" { if err := fx.st.SetOperator("reviewer@keymail.dev", false); err != nil { t.Fatal(err) } } else { fx.srv.cfg.StoreOperators = nil } status, _, body := completeStepUp(t, fx, c, loc) if status != 404 || body != "404 page not found\n" || storedEntitlement(t, fx, fx.sqid).Approved() { t.Fatalf("revoked approval=%d %q", status, body) } }) } } func TestStoreQueueRendersLatestCountWithoutCappingApproval(t *testing.T) { fx := newConsoleFixture(t) c := newBrowser(t) signUp(t, fx, c, "queue-count") if err := fx.st.SetOperator("paul@keymail.dev", true); err != nil { t.Fatal(err) } fx.srv.cfg.StoreOperators = []string{"paul@keymail.dev"} day := usage.StorageDay{V: 1, Date: "2026-09-08", Apps: map[string]usage.AppStorage{}} for i := 0; i < 101; i++ { day.Apps[fmt.Sprintf("bab/app%d", i)] = usage.AppStorage{StoreProvisioned: true} } putStoreJSON(t, fx.rel, usage.StorageDayKey(day.Date), day) putStoreJSON(t, fx.rel, usage.StorageDayKey("2026-09-07"), usage.StorageDay{V: 1, Date: "2026-09-07", Apps: map[string]usage.AppStorage{}}) putEntitlement(t, fx, fx.sqid, &objstore.Entitlement{Applied: &objstore.Applied{By: "paul@keymail.dev", At: time.Now()}}) status, _, body := get(t, c, fx.ts.URL+"/fleet/store") for _, want := range []string{"Provisioned stores: 101", "2026-09-08", "incomplete provisions", ">Approve"} { if status != 200 || !strings.Contains(body, want) { t.Fatalf("queue missing %q (status %d)", want, status) } } status, _, body = postGated(t, fx, c, fx.ts.URL+"/fleet/store/"+fx.sqid+"/approve", url.Values{"csrf": {sessionToken(t, fx, c)}}) if status != 303 || !storedEntitlement(t, fx, fx.sqid).Approved() { t.Fatalf("count capped approval: %d %s", status, body) } }