Skip to content

Commit cc5a9b7

Browse files
authored
tests: Use pollFor logic instead of static wait times in integration tests (#6803)
1 parent 3552957 commit cc5a9b7

File tree

10 files changed

+355
-171
lines changed

10 files changed

+355
-171
lines changed

integration-tests/testkit/flow.ts

Lines changed: 45 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,27 +39,59 @@ export function waitFor(ms: number) {
3939

4040
function pollInternal(
4141
check: () => Promise<boolean>,
42+
43+
/** In milliseconds */
4244
pollFrequency: number,
45+
46+
/** In milliseconds */
47+
maxWait: number,
48+
49+
/** In milliseconds. A random number between 0 and Jitter is added to the pollFrequency to add some
50+
* noise and prevent test cases where exact durations are required.
51+
* */
52+
jitter: number,
53+
4354
resolve: (value: void | PromiseLike<void>) => void,
4455
reject: (reason?: any) => void,
56+
57+
/** In milliseconds */
58+
startTime: number = Date.now(),
4559
) {
46-
setTimeout(async () => {
47-
try {
48-
const passes = await check();
49-
if (passes) {
50-
resolve();
51-
} else {
52-
pollInternal(check, pollFrequency, resolve, reject);
60+
setTimeout(
61+
async () => {
62+
try {
63+
const passes = await check();
64+
if (passes) {
65+
resolve();
66+
} else {
67+
const waited = Date.now() - startTime;
68+
if (waited > maxWait) {
69+
reject(new Error(`Polling failed. Condition was not satisfied within ${maxWait}ms`));
70+
} else {
71+
pollInternal(check, pollFrequency, maxWait, jitter, resolve, reject, startTime);
72+
}
73+
}
74+
} catch (e) {
75+
reject(e);
5376
}
54-
} catch (e) {
55-
reject(e);
56-
}
57-
}, pollFrequency);
77+
},
78+
Math.round(pollFrequency + Math.random() * jitter),
79+
);
5880
}
5981

60-
export function pollFor(check: () => Promise<boolean>, pollFrequency = 500): Promise<void> {
82+
export function pollFor(
83+
check: () => Promise<boolean>,
84+
opts?: { pollFrequency?: number; maxWait?: number; jitter?: number },
85+
): Promise<void> {
6186
return new Promise((resolve, reject) => {
62-
pollInternal(check, pollFrequency, resolve, reject);
87+
pollInternal(
88+
check,
89+
opts?.pollFrequency ?? 500,
90+
opts?.maxWait ?? 10_000,
91+
opts?.jitter ?? 100,
92+
resolve,
93+
reject,
94+
);
6395
});
6496
}
6597

integration-tests/testkit/graphql.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export async function execute<TResult, TVariables>(
6666
rawBody: body,
6767
status: response.status,
6868
expectGraphQLErrors() {
69-
if (!body.errors?.length) {
69+
if (!body?.errors?.length) {
7070
throw new Error(
7171
`Expected GraphQL response to have errors, but no errors were found!${detailsDump}`,
7272
);
@@ -75,7 +75,7 @@ export async function execute<TResult, TVariables>(
7575
return body.errors!;
7676
},
7777
expectNoGraphQLErrors: async () => {
78-
if (body.errors?.length) {
78+
if (body?.errors?.length) {
7979
throw new Error(
8080
`Expected GraphQL response to have no errors, but got ${
8181
body.errors.length

integration-tests/testkit/seed.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,6 +766,38 @@ export function initSeed() {
766766

767767
return pollFor(check);
768768
},
769+
async waitForRequestsCollected(
770+
n: number,
771+
opts?: {
772+
from?: number;
773+
to?: number;
774+
target?: TargetOverwrite;
775+
},
776+
) {
777+
const from = formatISO(opts?.from ?? subHours(Date.now(), 1));
778+
const to = formatISO(opts?.to ?? Date.now());
779+
const check = async () => {
780+
const statsResult = await readOperationsStats(
781+
{
782+
organizationSlug: organization.slug,
783+
projectSlug: project.slug,
784+
targetSlug: (opts?.target ?? target).slug,
785+
period: {
786+
from,
787+
to,
788+
},
789+
},
790+
ownerToken,
791+
).then(r => r.expectNoGraphQLErrors());
792+
const totalRequests = statsResult.operationsStats.operations.nodes.reduce(
793+
(total, node) => total + node.count,
794+
0,
795+
);
796+
return totalRequests == n;
797+
};
798+
799+
return pollFor(check);
800+
},
769801
async readOperationsStats(
770802
from: string,
771803
to: string,

integration-tests/tests/api/app-deployments.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { buildASTSchema, parse } from 'graphql';
22
import { createLogger } from 'graphql-yoga';
3-
import { waitFor } from 'testkit/flow';
43
import { initSeed } from 'testkit/seed';
54
import { getServiceHost } from 'testkit/utils';
65
import { createHive } from '@graphql-hive/core';
@@ -1697,7 +1696,8 @@ test('app deployment usage reporting', async () => {
16971696
const { createOrg, ownerToken } = await initSeed().createOwner();
16981697
const { createProject, setFeatureFlag, organization } = await createOrg();
16991698
await setFeatureFlag('appDeployments', true);
1700-
const { createTargetAccessToken, project, target } = await createProject();
1699+
const { createTargetAccessToken, project, target, waitForOperationsCollected } =
1700+
await createProject();
17011701
const token = await createTargetAccessToken({});
17021702

17031703
const { createAppDeployment } = await execute({
@@ -1806,7 +1806,7 @@ test('app deployment usage reporting', async () => {
18061806
'app-name~app-version~aaa',
18071807
);
18081808

1809-
await waitFor(5000);
1809+
await waitForOperationsCollected(1);
18101810

18111811
data = await execute({
18121812
document: GetAppDeployment,

integration-tests/tests/api/legacy-auth.spec.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ test.concurrent(
1010
const { createOrg } = await initSeed().createOwner();
1111
const { inviteAndJoinMember, createProject } = await createOrg();
1212
await inviteAndJoinMember();
13-
const { createTargetAccessToken, readOperationsStats, readOperationBody } = await createProject(
14-
ProjectType.Single,
15-
);
13+
const {
14+
createTargetAccessToken,
15+
readOperationsStats,
16+
readOperationBody,
17+
waitForOperationsCollected,
18+
} = await createProject(ProjectType.Single);
1619
const { publishSchema, collectLegacyOperations: collectOperations } =
1720
await createTargetAccessToken({});
1821

@@ -40,7 +43,7 @@ test.concurrent(
4043

4144
expect(collectResult.status).toEqual(200);
4245

43-
await waitFor(5000);
46+
await waitForOperationsCollected(1);
4447

4548
const from = formatISO(subHours(Date.now(), 6));
4649
const to = formatISO(Date.now());

integration-tests/tests/api/organization/get-started.spec.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import { ProjectType } from 'testkit/gql/graphql';
2-
import { waitFor } from '../../../testkit/flow';
32
import { initSeed } from '../../../testkit/seed';
43

54
test.concurrent(
@@ -23,8 +22,13 @@ test.concurrent(
2322
async ({ expect }) => {
2423
const { createOrg } = await initSeed().createOwner();
2524
const { inviteAndJoinMember, fetchOrganizationInfo, createProject } = await createOrg();
26-
const { target, project, createTargetAccessToken, toggleTargetValidation } =
27-
await createProject(ProjectType.Single);
25+
const {
26+
target,
27+
project,
28+
createTargetAccessToken,
29+
toggleTargetValidation,
30+
waitForOperationsCollected,
31+
} = await createProject(ProjectType.Single);
2832

2933
const { getStarted: steps } = await fetchOrganizationInfo();
3034
expect(steps?.creatingProject).toBe(true); // modified
@@ -87,7 +91,7 @@ test.concurrent(
8791
},
8892
},
8993
]);
90-
await waitFor(8000);
94+
await waitForOperationsCollected(1);
9195
const { getStarted: steps5 } = await fetchOrganizationInfo();
9296
expect(steps5?.creatingProject).toBe(true);
9397
expect(steps5?.publishingSchema).toBe(true);

integration-tests/tests/api/rate-limit/emails.spec.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ProjectType } from 'testkit/gql/graphql';
22
import * as emails from '../../../testkit/emails';
3-
import { updateOrgRateLimit, waitFor } from '../../../testkit/flow';
3+
import { pollFor, updateOrgRateLimit, waitFor } from '../../../testkit/flow';
44
import { initSeed } from '../../../testkit/seed';
55

66
function filterEmailsByOrg(orgSlug: string, emails: emails.Email[]) {
@@ -15,7 +15,9 @@ function filterEmailsByOrg(orgSlug: string, emails: emails.Email[]) {
1515
test('rate limit approaching and reached for organization', async () => {
1616
const { createOrg, ownerToken, ownerEmail } = await initSeed().createOwner();
1717
const { createProject, organization } = await createOrg();
18-
const { createTargetAccessToken } = await createProject(ProjectType.Single);
18+
const { createTargetAccessToken, waitForRequestsCollected } = await createProject(
19+
ProjectType.Single,
20+
);
1921

2022
await updateOrgRateLimit(
2123
{
@@ -44,7 +46,13 @@ test('rate limit approaching and reached for organization', async () => {
4446
const collectResult = await collectOperations(new Array(10).fill(op));
4547
expect(collectResult.status).toEqual(200);
4648

47-
await waitFor(8000);
49+
await waitForRequestsCollected(10);
50+
51+
// wait for the rate limit email to send...
52+
await pollFor(async () => {
53+
let sent = await emails.history();
54+
return filterEmailsByOrg(organization.slug, sent)?.length === 1;
55+
});
4856

4957
let sent = await emails.history();
5058
expect(sent).toContainEqual({
@@ -58,7 +66,13 @@ test('rate limit approaching and reached for organization', async () => {
5866
const collectMoreResult = await collectOperations([op, op]);
5967
expect(collectMoreResult.status).toEqual(200);
6068

61-
await waitFor(7000);
69+
await waitForRequestsCollected(12);
70+
71+
// wait for the quota email to send...
72+
await pollFor(async () => {
73+
let sent = await emails.history();
74+
return filterEmailsByOrg(organization.slug, sent)?.length === 2;
75+
});
6276

6377
sent = await emails.history();
6478

@@ -73,7 +87,8 @@ test('rate limit approaching and reached for organization', async () => {
7387
const collectEvenMoreResult = await collectOperations([op, op]);
7488
expect(collectEvenMoreResult.status).toEqual(429);
7589

76-
await waitFor(5000);
90+
// @note we can't poll for any state here because nothing should change. Must wait unfortunately...
91+
await waitFor(4_000);
7792

7893
// Nothing new
7994
sent = await emails.history();

integration-tests/tests/api/schema/unused.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,9 @@ test.concurrent(
3535
async ({ expect }) => {
3636
const { createOrg } = await initSeed().createOwner();
3737
const { createProject } = await createOrg();
38-
const { createTargetAccessToken, target } = await createProject(ProjectType.Single);
38+
const { createTargetAccessToken, target, waitForOperationsCollected } = await createProject(
39+
ProjectType.Single,
40+
);
3941

4042
// Create a token with write rights
4143
const writeToken = await createTargetAccessToken({});
@@ -123,7 +125,7 @@ test.concurrent(
123125
],
124126
});
125127
expect(collectResult.status).toEqual(200);
126-
await waitFor(8000);
128+
await waitForOperationsCollected(1);
127129

128130
const secondQuery = await execute({
129131
document: IntegrationTestsUnusedSchemaQuery,

0 commit comments

Comments
 (0)