Skip to content

Commit 0b99c2b

Browse files
fix(qa): signup-organization-census waits for the verification mail sent after the sign-up answer (#29992)
Refs #29991 ## 维护者速读 - 改了什么:两个注册人口普查的 dogfood 测试(单组织版与有墙版)在注册接口作答之后,先有界地轮询等待验证邮件写入 `sys_identity_email`,再做后续读取;所有断言原样保留。只改测试,不改产品代码。 - 为什么改:PR #29975 让注册接口「先作答、后发信」,验证邮件的记录在响应之后才落库;单组织版测试在作答后立刻读取,读到 0 条,`main` 上 `Dogfood Regression Gate (2/3)` 因此变红。有墙版测试中间隔着其它异步步骤,目前碰巧能过,属于依赖时序的潜在抖动,同 PR 一并修好。 - 风险与回滚:风险低,仅测试文件;等待上限 15 秒,超时会带着明确信息失败,不会静默放行。回滚即 revert 本 PR。 - 先看:`packages/qa/dogfood/test/signup-organization-census.dogfood.test.ts` 的 `beforeAll` 末尾新增的 `vi.waitFor`;其次 `signup-organization-census-walled.dogfood.test.ts` 的 `selfRegister`。 - 两分钟验证:先 build dogfood 依赖闭包,再跑 `pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/signup-organization-census.dogfood.test.ts`,预期 3 个用例全绿(`main` 上同一文件 2 红 1 绿)。 - 需要你做的:无,席位落地。 ## What changes - `signup-organization-census.dogfood.test.ts`: `POST /auth/sign-up/email` now answers before its verification mail is sent (PR #29975, `sendAfterResponseOnSignUp`), so the row in `sys_identity_email` lands after the response. The `beforeAll` now ends with a bounded `vi.waitFor` (15 s, the bound `password-reset-ends-sessions.dogfood.test.ts` already uses for the after-response reset mail) that polls `sys_identity_email` for the registrant's mail row. The census is then taken of the whole sign-up, as it was before the send moved. Every assertion in the three cases is unchanged. - `signup-organization-census-walled.dogfood.test.ts`: the same read (`:240`, the verification mail for both the registrant and the invitee) sits behind the same after-response send. It passes today only because a sign-in and other journey steps run in between: a timing-dependent pass. `selfRegister` now waits, with the same bound, for the mail row before it goes on, so every later read sees the whole sign-up. Assertions unchanged. - No product code, no shared helper: the two waits are five lines each and read the same table with different keys. ## Sweep of `packages/qa/dogfood/test` Other readers of `sys_identity_email` or of a mail transport, checked against "reads right after a `/sign-up/email` answer": - `password-reset-ends-sessions.dogfood.test.ts` and `identity-mail-read-gate.dogfood.test.ts` (`:310`): read the after-response password-reset mail, already behind `vi.waitFor`. No sign-up mail read. - `identity-mail-read-gate.dogfood.test.ts` `beforeAll` (`:123`), `upgrade-18x-harness.dogfood.test.ts`, `audit-mail-bodies.dogfood.test.ts`: read fixture rows from an 18.x database, not a fresh sign-up. - `self-registration-grant-first-membership-walled.dogfood.test.ts`: signs up through the door but never reads the mail log or a transport. - `email-template-*.test.ts`: register `EmailServicePlugin` but do not sign up. - `upgrade-harness/cli-prerelease-fixture.ts`: signs up and later reads `sys_email`, but it drives a pinned prerelease CLI to build a fixture; it is not a CI test and runs no HEAD sign-up code. ## Tests Head `ce36ea885f`, dogfood dependency closure built first (`pnpm --workspace-concurrency=2 --filter '@objectstack/dogfood^...' build`, lock VERDICT command-exit 0). - Reproduce on `main`: the `origin/main` copy of the census file, run as a temporary file: 2 failed, 1 passed, the same two failures as CI. Temporary file removed, `git status` clean. - After: both census files, run three times in a row: 2 files, 15 tests passed, each run. - `pnpm --filter @objectstack/dogfood run typecheck`: exit 0; `tsc --listFilesOnly` lists both changed files. The `Dogfood Regression Gate` jobs do not run on pull requests or in the merge queue (their `if:` excludes both events), so the local runs above are the evidence for this file before merge; `main`'s push battery is the first CI run on it. <details> <summary>Reproduce on main (origin/main copy of the file)</summary> ```text × guard: the boot is single, and the sign-up reached all three writers 7ms × sys_identity_email carries no organization column, and the sign-up's mail row names none 6ms AssertionError: the verification mail persisted, in the identity-mail log: expected 0 to be greater than 0 AssertionError: expected undefined to match object { related_object: 'sys_user', …(1) } Test Files 1 failed (1) Tests 2 failed | 1 passed (3) os-verify-lock: VERDICT command-exit 1 · held the lock 26s · waited 0s ``` </details> <details> <summary>After, three runs (both census files)</summary> ```text run1 Test Files 2 passed (2) Tests 15 passed (15) VERDICT command-exit 0 run2 Test Files 2 passed (2) Tests 15 passed (15) VERDICT command-exit 0 run3 Test Files 2 passed (2) Tests 15 passed (15) VERDICT command-exit 0 ``` </details> ## Acceptance notes - Test-only, in a private package (`@objectstack/dogfood`): nothing published changes, so this PR carries `skip-changeset` and no changeset. - No ablation: the change adds a wait and pins no new behaviour; the red-on-`main` run above is the before leg. ## Verification `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` on head `ce36ea885f` derived 51 commands; all 51 were run with each exit code captured before any pipe. 50 exit 0. `pnpm check:dual-build-cjs-loads` exited 3 (PREREQUISITE NOT MET: packages outside the dogfood closure have no `dist/`), so it is NOT MEASURED locally; CI's build measures it. `--ran` reconciliation: 51 derived, 50 run, 1 NOT-MEASURED, 0 UNRUN. --- _Generated by [Claude Code](https://claude.ai/code/session_01QSeKYn5CmmdShCJifm6WYj)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent bc9eaf6 commit 0b99c2b

2 files changed

Lines changed: 21 additions & 2 deletions

File tree

‎packages/qa/dogfood/test/signup-organization-census-walled.dogfood.test.ts‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
// derivable), never on row isolation, so the stand-in's lack of a row wall
4141
// measures nothing away. `OS_TENANCY_POSTURE` selects `group`.
4242

43-
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
43+
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
4444
import crmStack from '@objectstack/example-crm';
4545
import { bootStack, type VerifyStack } from '@objectstack/verify';
4646
import { EmailServicePlugin } from '@objectstack/plugin-email';
@@ -112,6 +112,13 @@ for (const posture of ['group', 'isolated'] as const) {
112112
});
113113
expect(signedUp.status, `sign-up ${email}: ${await signedUp.clone().text()}`).toBe(200);
114114
const id = await userIdOf(email);
115+
// The sign-up answers before its verification mail is sent; the journey
116+
// goes on once the mail row that send persists has landed — bounded,
117+
// never assumed — so every later read sees the whole sign-up.
118+
await vi.waitFor(async () => {
119+
const mails = await ql.find('sys_identity_email', { where: { related_object: 'sys_user', related_id: id }, ...SYS });
120+
expect(mails.length, `${email}: the verification mail sent after the sign-up answer landed`).toBeGreaterThan(0);
121+
}, { timeout: 15_000 });
115122
await ql.update('sys_user', { id, email_verified: true }, SYS);
116123
return { id, token: await stack.signIn(email, PASSWORD) };
117124
};

‎packages/qa/dogfood/test/signup-organization-census.dogfood.test.ts‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
// Under a wall the same sign-up has no organization to derive (the registrant
1818
// belongs to none yet); those rows' fate is not settled by this file.
1919

20-
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
20+
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
2121
import crmStack from '@objectstack/example-crm';
2222
import { bootStack, type VerifyStack } from '@objectstack/verify';
2323
import { EmailServicePlugin } from '@objectstack/plugin-email';
@@ -79,6 +79,18 @@ describe('[ADR-0131 D9] a self-registration on a single boot leaves no organizat
7979
body: JSON.stringify({ email: REGISTRANT, password: 'Census-Passw0rd!', name: 'Census Registrant' }),
8080
});
8181
expect(signedUp.status, `sign-up: ${await signedUp.clone().text()}`).toBe(200);
82+
83+
// The sign-up answers before its verification mail is sent: the send, and
84+
// the mail row it persists, follow the response. The census is of what the
85+
// whole sign-up writes, so it is taken once that row has landed — bounded,
86+
// never assumed.
87+
await vi.waitFor(async () => {
88+
const registrant = await ql.findOne('sys_user', { where: { email: REGISTRANT }, ...SYS });
89+
const mails = registrant?.id
90+
? await ql.find('sys_identity_email', { where: { related_object: 'sys_user', related_id: registrant.id }, ...SYS })
91+
: [];
92+
expect(mails.length, 'the verification mail sent after the sign-up answer landed').toBeGreaterThan(0);
93+
}, { timeout: 15_000 });
8294
}, 180_000);
8395

8496
afterAll(async () => {

0 commit comments

Comments
 (0)