package console import ( "bytes" "encoding/json" "html" "log" "net/http" "net/url" "os" "regexp" "strings" "testing" "github.com/carlosframework/platform/internal/geo" ) func has(body, plain string) bool { return strings.Contains(body, html.EscapeString(plain)) } // newAppFormGolden is the base tree's own rendering of the create-app form // (the branch base, switch off), with the session's CSRF value blanked. const newAppFormGolden = `
` // newAppForm is the rendered , CSRF value blanked. func newAppForm(t *testing.T, body string) string { t.Helper() i := strings.Index(body, `") return regexp.MustCompile(`name="csrf" value="[^"]*"`).ReplaceAllString(body[i:j], `name="csrf" value="CSRF"`) } // codex plan-R4 3, spec §10 and §16: whenever there is no picker, the form // is today's form byte for byte: with the switch off, and with the switch // on but only one eligible region. func TestNewAppPageWithoutPickerIsUnchanged(t *testing.T) { off := newConsoleFixture(t) one, _ := regionFixture(t) setEdges(t, one, map[string]string{"uk-box": ukNoKey}) // the home alone is eligible for name, fx := range map[string]*consoleFixture{"switch off": off, "one eligible region": one} { _, _, body := get(t, signInPaul(t, fx), fx.u("/apps/new")) if got := newAppForm(t, body); got != newAppFormGolden { t.Errorf("%s: form =\n%q\nwant\n%q", name, got, newAppFormGolden) } } } func TestNewAppPickerAndAutoChoice(t *testing.T) { fx, _ := regionFixture(t) c, csrf := signInAs(t, fx, "paul@keymail.dev", "code-picker") _, _, body := get(t, c, fx.u("/apps/new")) for _, want := range []string{`name="region"`, `value="@auto"`, `value="uk"`, `value="ie"`} { if !strings.Contains(body, want) { t.Fatalf("picker missing %q", want) } } if !has(body, ukPlace) || !has(body, regionCopy.PickerLabel) || !has(body, regionCopy.PickerHelp) { t.Fatal("picker missing its copy") } if status, _, _ := postForm(t, c, fx.u("/apps/new"), url.Values{"csrf": {csrf}, "name": {"auto1"}, "region": {"@auto"}, "tz": {"Europe/London"}}); status != http.StatusSeeOther { t.Fatalf("@auto post = %d", status) } if r, src, _ := fx.st.AppRegion(OpsAccountID, "auto1"); r != "uk" || src != "timezone" { t.Fatalf("@auto = %q %q", r, src) } postForm(t, c, fx.u("/apps/new"), url.Values{"csrf": {csrf}, "name": {"chose"}, "region": {"ie"}, "tz": {"Europe/London"}}) if r, src, _ := fx.st.AppRegion(OpsAccountID, "chose"); r != "" || src != "chosen" { t.Fatalf("explicit ie = %q %q", r, src) } } // codex R2-3: a home NAMED "auto" is a region, not the sentinel. func TestRegionNamedAutoIsNotTheSentinel(t *testing.T) { fx, _ := regionFixture(t) fx.srv.cfg.HomeRegion = "auto" c, csrf := signInAs(t, fx, "paul@keymail.dev", "code-auto-name") postForm(t, c, fx.u("/apps/new"), url.Values{"csrf": {csrf}, "name": {"homeauto"}, "region": {"auto"}}) if r, src, _ := fx.st.AppRegion(OpsAccountID, "homeauto"); r != "" || src != "chosen" { t.Fatalf("explicit home named auto = %q %q", r, src) } } // Review Focus 3: uk's box loses the secrets key between render and POST. func TestNewAppPostWithANoLongerEligibleRegion(t *testing.T) { fx, _ := regionFixture(t) c, csrf := signInAs(t, fx, "paul@keymail.dev", "code-stale") if _, _, body := get(t, c, fx.u("/apps/new")); !strings.Contains(body, `value="uk"`) { t.Fatal("precondition: uk offered") } setEdges(t, fx, map[string]string{"uk-box": ukNoKey}) status, loc, _ := postForm(t, c, fx.u("/apps/new"), url.Values{"csrf": {csrf}, "name": {"stale"}, "region": {"uk"}}) if status != http.StatusSeeOther || loc != fx.path("/apps/new") { t.Fatalf("stale region = %d %q, want back to the form", status, loc) } if _, _, body := get(t, c, fx.u("/apps/new")); !has(body, regionCopy.Unavailable) { t.Fatal("no C5 flash") } if exists, _ := fx.st.HasAppIn(OpsAccountID, "stale"); exists { t.Fatal("a refused region still created the app") } } func TestNewAppRegionRouteNeedsSessionAndCSRF(t *testing.T) { fx, _ := regionFixture(t) c, csrf := signInAs(t, fx, "paul@keymail.dev", "code-route") status, _, body := postForm(t, c, fx.u("/apps/new/region"), url.Values{"csrf": {csrf}, "tz": {"Europe/London"}}) var res map[string]string if err := json.Unmarshal([]byte(body), &res); err != nil { t.Fatalf("guess route body %q: %v", body, err) } if status != http.StatusOK || res["code"] != "uk" || res["source"] != "timezone" || res["name"] != ukPlace { t.Fatalf("guess route = %d %v", status, res) } if status, _, _ := postForm(t, c, fx.u("/apps/new/region"), url.Values{"csrf": {"wrong"}, "tz": {"Europe/London"}}); status != http.StatusForbidden { t.Fatalf("bad csrf = %d, want 403", status) } if status, _, _ := postForm(t, newBrowser(t), fx.u("/apps/new/region"), url.Values{"tz": {"Europe/London"}}); status == http.StatusOK { t.Fatal("no session got a guess") } } // §9.6 as 69a84224 made it: with the switch off the browser door neither // validates nor refuses an explicit region (resolveNewAppRegion stores the // home as "default"), exactly as the API door does. func TestNewAppExplicitRegionSkippedWithSwitchOff(t *testing.T) { fx, _ := regionFixture(t) fx.srv.cfg.AppRegions = false c, csrf := signInAs(t, fx, "paul@keymail.dev", "code-off") for _, region := range []string{"uk", "mars"} { name := "off-" + region status, loc, _ := postForm(t, c, fx.u("/apps/new"), url.Values{"csrf": {csrf}, "name": {name}, "region": {region}}) if status != http.StatusSeeOther || loc == fx.path("/apps/new") { t.Fatalf("switch off, region %q: %d %q, want the app created", region, status, loc) } if r, src, _ := fx.st.AppRegion(OpsAccountID, name); r != "" || src != "default" { t.Fatalf("switch off, region %q stored %q %q, want \"\" default", region, r, src) } } } // With the switch on, a code that is not a region at all is refused with // C5 too, and nothing is created. func TestNewAppUnknownRegionIsRefused(t *testing.T) { fx, _ := regionFixture(t) c, csrf := signInAs(t, fx, "paul@keymail.dev", "code-unknown") status, loc, _ := postForm(t, c, fx.u("/apps/new"), url.Values{"csrf": {csrf}, "name": {"nowhere"}, "region": {"mars!"}}) if status != http.StatusSeeOther || loc != fx.path("/apps/new") { t.Fatalf("unknown region = %d %q", status, loc) } if _, _, body := get(t, c, fx.u("/apps/new")); !has(body, regionCopy.Unavailable) { t.Fatal("no C5 flash") } if exists, _ := fx.st.HasAppIn(OpsAccountID, "nowhere"); exists { t.Fatal("an unknown region still created the app") } } // The picker's preselected option is "@auto", never a region code, so an // untouched form is not an explicit choice (adoption must never move). func TestNewAppPickerPreselectsAuto(t *testing.T) { fx, _ := regionFixture(t) status, _, body := get(t, signInPaul(t, fx), fx.u("/apps/new")) if status != http.StatusOK { t.Fatalf("form = %d", status) } if !strings.Contains(body, `