Skip to content

Commit 9bd9e00

Browse files
committed
Reduce test flakeyness
1 parent 7c7e97d commit 9bd9e00

16 files changed

Lines changed: 83 additions & 245 deletions

.github/workflows/e2e-api-tests.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ jobs:
1919
NODE_ENV: test
2020
STACK_ENABLE_HARDCODED_PASSKEY_CHALLENGE_FOR_TESTING: yes
2121
STACK_DATABASE_CONNECTION_STRING: "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:8128/stackframe"
22-
STACK_FORCE_EXTERNAL_DB_SYNC: "true"
2322
STACK_EXTERNAL_DB_SYNC_MAX_DURATION_MS: "20000"
2423
STACK_EXTERNAL_DB_SYNC_DIRECT: "false"
2524

@@ -160,7 +159,7 @@ jobs:
160159
run: sleep 10
161160

162161
- name: Run tests
163-
run: pnpm test run ${{ matrix.freestyle-mode == 'prod' && '--min-workers=1 --max-workers=1' || '' }}
162+
run: pnpm test run ${{ matrix.freestyle-mode == 'prod' && '--min-workers=1 --max-workers=1' || '' }} ${{ matrix.freestyle-mode == 'prod' && github.ref != 'refs/heads/main' && github.ref != 'refs/heads/dev' && 'mail' || '' }}
164163

165164
- name: Run tests again (attempt 1)
166165
if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev'

.github/workflows/e2e-custom-base-port-api-tests.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ jobs:
1919
STACK_ENABLE_HARDCODED_PASSKEY_CHALLENGE_FOR_TESTING: yes
2020
STACK_DATABASE_CONNECTION_STRING: "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:6728/stackframe"
2121
NEXT_PUBLIC_STACK_PORT_PREFIX: "67"
22-
STACK_FORCE_EXTERNAL_DB_SYNC: "true"
2322
STACK_EXTERNAL_DB_SYNC_MAX_DURATION_MS: "20000"
2423
STACK_EXTERNAL_DB_SYNC_DIRECT: "false"
2524

.github/workflows/e2e-source-of-truth-api-tests.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ jobs:
2121
STACK_OVERRIDE_SOURCE_OF_TRUTH: '{"type": "postgres", "connectionString": "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:8128/source-of-truth-db?schema=sot-schema"}'
2222
STACK_TEST_SOURCE_OF_TRUTH: true
2323
STACK_DATABASE_CONNECTION_STRING: "postgres://postgres:PASSWORD-PLACEHOLDER--uqfEC1hmmv@localhost:8128/stackframe"
24-
STACK_FORCE_EXTERNAL_DB_SYNC: "true"
2524
STACK_EXTERNAL_DB_SYNC_MAX_DURATION_MS: "20000"
2625
STACK_EXTERNAL_DB_SYNC_DIRECT: "false"
2726

.github/workflows/restart-dev-and-test-with-custom-base-port.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ jobs:
1919
runs-on: ubicloud-standard-16
2020
env:
2121
NEXT_PUBLIC_STACK_PORT_PREFIX: "69"
22-
STACK_FORCE_EXTERNAL_DB_SYNC: "true"
2322
STACK_EXTERNAL_DB_SYNC_MAX_DURATION_MS: "20000"
2423
STACK_EXTERNAL_DB_SYNC_DIRECT: "false"
2524

.github/workflows/restart-dev-and-test.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ jobs:
1818
restart-dev-and-test:
1919
runs-on: ubicloud-standard-16
2020
env:
21-
STACK_FORCE_EXTERNAL_DB_SYNC: "true"
2221
STACK_EXTERNAL_DB_SYNC_MAX_DURATION_MS: "20000"
2322
STACK_EXTERNAL_DB_SYNC_DIRECT: "false"
2423

.github/workflows/setup-tests-with-custom-base-port.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ jobs:
1919
runs-on: ubicloud-standard-16
2020
env:
2121
NEXT_PUBLIC_STACK_PORT_PREFIX: "69"
22-
STACK_FORCE_EXTERNAL_DB_SYNC: "true"
2322
STACK_EXTERNAL_DB_SYNC_MAX_DURATION_MS: "20000"
2423
STACK_EXTERNAL_DB_SYNC_DIRECT: "false"
2524

.github/workflows/setup-tests.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ jobs:
1818
setup-tests:
1919
runs-on: ubicloud-standard-16
2020
env:
21-
STACK_FORCE_EXTERNAL_DB_SYNC: "true"
2221
STACK_EXTERNAL_DB_SYNC_MAX_DURATION_MS: "20000"
2322
STACK_EXTERNAL_DB_SYNC_DIRECT: "false"
2423
steps:

apps/backend/src/app/api/latest/internal/external-db-sync/poller/route.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,6 @@ function parseMaxDurationMs(value: string | undefined): number {
3030
return parsed;
3131
}
3232

33-
function parseStopWhenIdle(value: string | undefined): boolean {
34-
if (!value) return false;
35-
if (value === "true") return true;
36-
if (value === "false") return false;
37-
throw new StatusError(400, "stopWhenIdle must be 'true' or 'false'");
38-
}
39-
4033
function directSyncEnabled(): boolean {
4134
return getEnvVariable(DIRECT_SYNC_ENV, "") === "true";
4235
}
@@ -94,13 +87,11 @@ export const GET = createSmartRouteHandler({
9487
return await traceSpan("external-db-sync.poller", async (span) => {
9588
const startTime = performance.now();
9689
const maxDurationMs = parseMaxDurationMs(query.maxDurationMs);
97-
const stopWhenIdle = parseStopWhenIdle(query.stopWhenIdle);
9890
const pollIntervalMs = 50;
9991
const staleClaimIntervalMinutes = 5;
10092
const pollerClaimLimit = getPollerClaimLimit();
10193

10294
span.setAttribute("stack.external-db-sync.max-duration-ms", maxDurationMs);
103-
span.setAttribute("stack.external-db-sync.stop-when-idle", stopWhenIdle);
10495
span.setAttribute("stack.external-db-sync.poll-interval-ms", pollIntervalMs);
10596
span.setAttribute("stack.external-db-sync.poller-claim-limit", pollerClaimLimit);
10697
span.setAttribute("stack.external-db-sync.direct-sync", directSyncEnabled());
@@ -258,8 +249,8 @@ export const GET = createSmartRouteHandler({
258249
const pendingRequests = await claimPendingRequests();
259250
iterationSpan.setAttribute("stack.external-db-sync.pending-count", pendingRequests.length);
260251

261-
if (stopWhenIdle && pendingRequests.length === 0) {
262-
return { stopReason: "idle", processed: 0 };
252+
if (pendingRequests.length > 0) {
253+
console.log(`[Poller] Processing ${pendingRequests.length} pending requests`);
263254
}
264255

265256
const processed = await processRequests(pendingRequests);

apps/backend/src/app/api/latest/internal/external-db-sync/sequencer/route.ts

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,6 @@ function parseMaxDurationMs(value: string | undefined): number {
2727
return parsed;
2828
}
2929

30-
function parseStopWhenIdle(value: string | undefined): boolean {
31-
if (!value) return false;
32-
if (value === "true") return true;
33-
if (value === "false") return false;
34-
throw new StatusError(400, "stopWhenIdle must be 'true' or 'false'");
35-
}
36-
3730
function getSequencerBatchSize(): number {
3831
const rawValue = getEnvVariable(SEQUENCER_BATCH_SIZE_ENV, "");
3932
if (!rawValue) return DEFAULT_BATCH_SIZE;
@@ -145,6 +138,10 @@ async function backfillSequenceIds(batchSize: number): Promise<boolean> {
145138

146139
span.setAttribute("stack.external-db-sync.did-update", didUpdate);
147140

141+
if (didUpdate) {
142+
console.log(`[Sequencer] Backfilled USR=${projectUserTenants.length} CC=${contactChannelTenants.length} DEL=${deletedRowTenants.length}`);
143+
}
144+
148145
return didUpdate;
149146
});
150147
}
@@ -169,7 +166,6 @@ export const GET = createSmartRouteHandler({
169166
}).defined(),
170167
query: yupObject({
171168
maxDurationMs: yupString().optional(),
172-
stopWhenIdle: yupString().optional(),
173169
}).defined(),
174170
}),
175171
response: yupObject({
@@ -189,12 +185,10 @@ export const GET = createSmartRouteHandler({
189185
return await traceSpan("external-db-sync.sequencer", async (span) => {
190186
const startTime = performance.now();
191187
const maxDurationMs = parseMaxDurationMs(query.maxDurationMs);
192-
const stopWhenIdle = parseStopWhenIdle(query.stopWhenIdle);
193188
const pollIntervalMs = 50;
194189
const batchSize = getSequencerBatchSize();
195190

196191
span.setAttribute("stack.external-db-sync.max-duration-ms", maxDurationMs);
197-
span.setAttribute("stack.external-db-sync.stop-when-idle", stopWhenIdle);
198192
span.setAttribute("stack.external-db-sync.poll-interval-ms", pollIntervalMs);
199193
span.setAttribute("stack.external-db-sync.batch-size", batchSize);
200194

@@ -220,9 +214,6 @@ export const GET = createSmartRouteHandler({
220214
try {
221215
const didUpdate = await backfillSequenceIds(batchSize);
222216
iterationSpan.setAttribute("stack.external-db-sync.did-update", didUpdate);
223-
if (stopWhenIdle && !didUpdate) {
224-
return { stopReason: "idle" };
225-
}
226217
} catch (error) {
227218
iterationSpan.setAttribute("stack.external-db-sync.iteration-error", true);
228219
captureError(

apps/e2e/tests/backend/backend-helpers.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,36 @@ export async function bumpEmailAddress(options: { unindexed?: boolean } = {}) {
186186
return mailbox;
187187
}
188188

189+
// Helper to get emails from the outbox, filtered by subject if provided
190+
export async function getOutboxEmails(options?: { subject?: string }) {
191+
const listResponse = await niceBackendFetch("/api/v1/emails/outbox", {
192+
method: "GET",
193+
accessType: "server",
194+
});
195+
if (options?.subject) {
196+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
197+
return listResponse.body.items.filter((e: any) => e.subject === options.subject);
198+
}
199+
return listResponse.body.items;
200+
}
201+
202+
// Helper to poll the outbox until an email with the expected subject and status appears
203+
export async function waitForOutboxEmailWithStatus(subject: string, status: string) {
204+
const maxRetries = 24;
205+
let emails: any[] = [];
206+
for (let i = 0; i < maxRetries; i++) {
207+
emails = await getOutboxEmails({ subject });
208+
if (emails.length > 0 && emails[0].status === status) {
209+
return emails;
210+
}
211+
await wait(500);
212+
}
213+
throw new StackAssertionError(
214+
`Timeout waiting for outbox email with subject "${subject}" and status "${status}"`,
215+
{ foundEmails: emails }
216+
);
217+
}
218+
189219
export namespace Auth {
190220
export async function fastSignUp(body: any = {}) {
191221
const { userId } = await User.create(body);
@@ -405,7 +435,11 @@ export namespace Auth {
405435
}
406436
await wait(100 + i * 20);
407437
if (i >= 30) {
408-
throw new StackAssertionError(`Sign-in code message not found after ${i} attempts`, { response, messages: messages.map(m => ({ ...m, body: m.body && omit(m.body, ["html"]) })) });
438+
throw new StackAssertionError(`Sign-in code message not found after ${i} attempts`, {
439+
response,
440+
messages: messages.map(m => ({ ...m, body: m.body && omit(m.body, ["html"]) })),
441+
outboxEmails: await getOutboxEmails(),
442+
});
409443
}
410444
}
411445
return {

0 commit comments

Comments
 (0)