package console import ( "bytes" "log" "net/http" "net/url" "os" "strings" "testing" "time" "github.com/carlosframework/platform/internal/domains" "github.com/carlosframework/platform/internal/release" ) // devAccountID is the id whose sqid is "dev" (#285). const devAccountID = int64(1168) // Every console door that creates an account (landing's personal-account // mint, POST /accounts/new, POST /auth/cli/accounts) mints through // domains.MintAccount, so none of them can hand out the reserved "dev". Each // door is driven against a bucket whose highest account is 1167, so the next // plain id would be 1168. func TestConsoleCreationDoorsSkipReservedSqids(t *testing.T) { doors := map[string]func(t *testing.T, fx *consoleFixture) (owner string){ "landing": func(t *testing.T, fx *consoleFixture) string { fx.setUser("newbie@keymail.dev") defer fx.setUser("paul@keymail.dev") c := newBrowser(t) signUp(t, fx, c, "code-new") if st, loc, _ := get(t, c, fx.ts.URL+"/"); st != http.StatusFound || !strings.HasPrefix(loc, "/a/") { t.Fatalf("landing = %d -> %q, want 302 into an account", st, loc) } return "newbie@keymail.dev" }, "POST /accounts/new": func(t *testing.T, fx *consoleFixture) string { c := signInPaul(t, fx) csrf := sessionToken(t, fx, c) if st, loc, _ := postForm(t, c, fx.ts.URL+"/accounts/new", url.Values{"csrf": {csrf}, "name": {"shop"}}); st != http.StatusSeeOther { t.Fatalf("create = %d -> %q, want 303", st, loc) } return "paul@keymail.dev" }, "POST /auth/cli/accounts": func(t *testing.T, fx *consoleFixture) string { token := cliToken(t, fx, "paul@keymail.dev", "code-acct") if st, out := postJSON(t, fx.ts.URL+"/auth/cli/accounts", map[string]string{"name": "shop"}, token); st != http.StatusOK { t.Fatalf("accounts create = %d %v", st, out) } return "paul@keymail.dev" }, } for name, door := range doors { t.Run(name, func(t *testing.T) { fx := newConsoleFixture(t) ctx := t.Context() if err := WriteAccountManifest(ctx, fx.rel, devAccountID-1, domains.Sqid(devAccountID-1), "neighbour", time.Unix(1700000000, 0)); err != nil { t.Fatal(err) } owner := door(t, fx) accts, err := fx.st.AccountsFor(owner) if err != nil { t.Fatal(err) } var made *Account for i := range accts { if accts[i].ID > devAccountID-1 { made = &accts[i] } } if made == nil { t.Fatalf("no new account for %s in %+v", owner, accts) } if made.ID != devAccountID+1 || made.Sqid != "dew" { t.Fatalf("minted (%d, %q), want (%d, \"dew\"): the door skipped the reserved-sqid rule", made.ID, made.Sqid, devAccountID+1) } if _, _, err := fx.rel.Get(ctx, AccountManifestKey("dev")); err == nil { t.Fatal("a manifest landed at accounts/dev/") } if a, err := fx.st.AccountBySqid("dev"); err != nil || a != nil { t.Fatalf("AccountBySqid(dev) = %+v %v, want none", a, err) } }) } } // The ruling leaves existing accounts alone, so an account that already holds // a reserved sqid (the list grew over it, or a manifest was written by hand) // is materialized as usual and named in the log, once per pass. A tombstoned // one is not named: it serves nothing. func TestMaterializeFlagsALiveReservedSqid(t *testing.T) { var logged bytes.Buffer log.SetOutput(&logged) t.Cleanup(func() { log.SetOutput(os.Stderr) }) st := openTestStore(t) rel := release.NewDirStore(t.TempDir()) ctx := t.Context() created := time.Unix(1700000000, 0).UTC() edgeID := int64(49868) // Sqid "edge" for _, a := range []struct { id int64 name string }{{devAccountID, "held"}, {devAccountID + 1, "plain"}, {edgeID, "retired"}} { if err := WriteAccountManifest(ctx, rel, a.id, domains.Sqid(a.id), a.name, created); err != nil { t.Fatal(err) } } if err := domains.TombstoneAccount(ctx, rel, "edge", created); err != nil { t.Fatal(err) } if _, err := MaterializeAccounts(ctx, rel, st); err != nil { t.Fatal(err) } if a, err := st.AccountBySqid("dev"); err != nil || a == nil || a.ID != devAccountID { t.Fatalf("AccountBySqid(dev) = %+v %v: an existing account must be left as it is", a, err) } out := logged.String() if !strings.Contains(out, "account dev (id 1168) has a reserved sqid") { t.Errorf("no reserved-sqid line for the live dev account:\n%s", out) } flagged := 0 for _, line := range strings.Split(out, "\n") { if !strings.Contains(line, "reserved sqid") { continue } flagged++ if strings.Contains(line, "—") { t.Errorf("the reserved-sqid log line carries an em dash: %s", line) } } if flagged != 1 { t.Errorf("%d reserved-sqid lines, want exactly one (dev; not the plain dew, not the retired edge):\n%s", flagged, out) } }