Fix flaky webhook delivery integration test (fixed sleep -> poll) (#22699)
# Context Part of a CI flakiness sweep. `webhooks.integration-spec.ts` › "should deliver webhook successfully when safe mode is disabled" intermittently fails with `expect(receiver.receivedPayloads.length).toBe(1) ... Received: 0` on unrelated PRs (example: run 28959576750, shard 3). # Root cause The test asserted delivery after a fixed 100ms sleep. Delivery actually crosses: a fire-and-forget `EventEmitter2.emit` (the GraphQL response returns before the job is even enqueued) → BullMQ hop 1 (`CallWebhookJobsJob`, which also recomputes the just-invalidated `flatWebhookMaps` cache) → BullMQ hop 2 (`CallWebhookJob`) → HTTP POST to the in-test receiver. Two Redis round trips plus a cache rebuild routinely exceed 100ms on loaded CI runners. The global `waitForAllJobsToFinish` only runs in `afterEach`, after the assertion. # Fix - Poll the receiver with the existing `expectEventually` helper (30s deadline, 100ms interval) instead of sleeping, and give the test an explicit 60s timeout (suite default is 20s). Worst case the test fails slower; it can no longer fail while delivery is merely in flight. - Bonus bug found during adversarial review of this fix: the `finally` cleanup deleted config key `HTTP_TOOL_SAFE_MODE_ENABLED` while the test creates `OUTBOUND_HTTP_SAFE_MODE_ENABLED`, silently leaving outbound safe mode disabled in the DB for every suite that runs after this one. Fixed the key. Duplicate-delivery risk was checked: `CallWebhookJob.handle` never throws (errors swallowed), so `retryLimit: 3` can't produce a second payload that would break `toBe(1)`. Test-only change, 1 file, +15/-9. --- _Generated by [Claude Code](https://claude.ai/code/session_01AtD2wWm3EthV6t3Hs31QyB)_ <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22699?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
+15
-9
@@ -9,6 +9,7 @@ import {
|
||||
updateWebhook,
|
||||
} from 'test/integration/metadata/suites/utils/webhook-test.util';
|
||||
import { makeAdminPanelAPIRequest } from 'test/integration/twenty-config/utils/make-admin-panel-api-request.util';
|
||||
import { expectEventually } from 'test/integration/utils/expect-eventually.util';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
import { type UpdateWebhookInput } from 'src/engine/metadata-modules/webhook/dtos/update-webhook.input';
|
||||
@@ -329,21 +330,26 @@ describe('webhooksResolver (e2e)', () => {
|
||||
expect(createPersonResponse.body.errors).toBeUndefined();
|
||||
createdPersonId = createPersonResponse.body.data.createPerson.id;
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
expect(receiver.receivedPayloads.length).toBe(1);
|
||||
expect(receiver.receivedPayloads[0]).toMatchObject({
|
||||
targetUrl: `http://127.0.0.1:${WEBHOOK_RECEIVER_PORT}/webhook`,
|
||||
eventName: 'person.created',
|
||||
});
|
||||
// Delivery crosses two BullMQ hops behind a fire-and-forget event
|
||||
// emit, so poll the receiver instead of guessing at the latency.
|
||||
await expectEventually(
|
||||
() => {
|
||||
expect(receiver.receivedPayloads.length).toBe(1);
|
||||
expect(receiver.receivedPayloads[0]).toMatchObject({
|
||||
targetUrl: `http://127.0.0.1:${WEBHOOK_RECEIVER_PORT}/webhook`,
|
||||
eventName: 'person.created',
|
||||
});
|
||||
},
|
||||
{ timeoutMs: 30_000, intervalMs: 100 },
|
||||
);
|
||||
} finally {
|
||||
await receiver.close();
|
||||
await makeAdminPanelAPIRequest({
|
||||
query: DELETE_CONFIG_VARIABLE_MUTATION,
|
||||
variables: { key: 'HTTP_TOOL_SAFE_MODE_ENABLED' },
|
||||
variables: { key: 'OUTBOUND_HTTP_SAFE_MODE_ENABLED' },
|
||||
}).catch(() => {});
|
||||
jest.useFakeTimers();
|
||||
}
|
||||
});
|
||||
}, 60_000);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user