Skip to content

Commit 3bc8abf

Browse files
authored
Merge pull request #1339 from supertokens/agent/issue-1335-stress-assertions-dispatch
fix: re-enable stress-test seeding assertions and allow manual dispatch
2 parents 55437a2 + fc595c6 commit 3bc8abf

5 files changed

Lines changed: 80 additions & 5 deletions

File tree

.github/workflows/stress-tests.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ on:
77
description: 'Docker image tag to use'
88
required: true
99
type: string
10+
workflow_dispatch:
11+
inputs:
12+
tag:
13+
description: 'Docker image tag to use'
14+
required: true
15+
type: string
1016

1117
jobs:
1218
stress-tests:

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

88
## [Unreleased]
99

10+
- Test-only: the 1M-user stress-test suite now records non-OK results per seeding step and fails the run at the end if
11+
any step errored, and its workflow can be triggered manually via `workflow_dispatch`
12+
1013
## [12.0.8]
1114

1215
- Security improvements around api-key/ip allow list handling

stress-tests/src/common/utils.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,57 @@ export class StatsCollector {
132132
}
133133
}
134134

135+
/**
136+
* Tracks non-OK results produced by the seeding steps. A seeding step that
137+
* silently errors would otherwise still "pass" and invalidate every
138+
* measurement downstream of it, so we count the non-OK results per step and
139+
* fail the run at the end if any step had failures.
140+
*/
141+
export class FailureTracker {
142+
private static instance: FailureTracker;
143+
private failures: Map<string, { count: number; statuses: Record<string, number> }> = new Map();
144+
145+
private constructor() {}
146+
147+
public static getInstance(): FailureTracker {
148+
if (!FailureTracker.instance) {
149+
FailureTracker.instance = new FailureTracker();
150+
}
151+
return FailureTracker.instance;
152+
}
153+
154+
public recordFailure(step: string, status: string) {
155+
const entry = this.failures.get(step) ?? { count: 0, statuses: {} };
156+
entry.count++;
157+
entry.statuses[status] = (entry.statuses[status] ?? 0) + 1;
158+
this.failures.set(step, entry);
159+
}
160+
161+
public hasFailures(): boolean {
162+
return this.failures.size > 0;
163+
}
164+
165+
/**
166+
* Prints a per-step summary of any non-OK results and throws if there were
167+
* any, so the run fails at the end without aborting the seeding mid-way.
168+
*/
169+
public throwIfAnyFailures() {
170+
if (!this.hasFailures()) {
171+
console.log('\nAll seeding steps completed with no non-OK results.');
172+
return;
173+
}
174+
console.error('\nSeeding step failures detected (non-OK results):');
175+
let total = 0;
176+
for (const [step, info] of this.failures) {
177+
total += info.count;
178+
console.error(` ${step}: ${info.count} non-OK result(s) — ${JSON.stringify(info.statuses)}`);
179+
}
180+
throw new Error(
181+
`${total} non-OK result(s) across ${this.failures.size} seeding step(s); measurements are not trustworthy.`
182+
);
183+
}
184+
}
185+
135186
export const measureTime = async <T>(title: string, fn: () => Promise<T>): Promise<T> => {
136187
const st = Date.now();
137188
const result = await fn();

stress-tests/src/oneMillionUsers/createUsers.ts

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import EmailPassword from 'supertokens-node/recipe/emailpassword';
22
import Passwordless from 'supertokens-node/recipe/passwordless';
33
import ThirdParty from 'supertokens-node/recipe/thirdparty';
44

5-
import { workInBatches, measureTime } from '../common/utils';
5+
import { workInBatches, measureTime, FailureTracker } from '../common/utils';
66

77
const TOTAL_USERS = 10000;
88

@@ -16,13 +16,13 @@ const createEmailPasswordUsers = async () => {
1616
.map(() => String.fromCharCode(97 + Math.floor(Math.random() * 26)))
1717
.join('') + '@example.com';
1818
const createdUser = await EmailPassword.signUp('public', email, 'password');
19-
// expect(createdUser.status).toBe("OK");
2019
if (createdUser.status === 'OK') {
2120
return {
2221
recipeUserId: createdUser.recipeUserId.getAsString(),
2322
email: email,
2423
};
2524
}
25+
FailureTracker.getInstance().recordFailure('EmailPassword users creation', createdUser.status);
2626
});
2727
};
2828

@@ -38,13 +38,16 @@ const createPasswordlessUsersWithEmail = async () => {
3838
tenantId: 'public',
3939
email,
4040
});
41-
// expect(createdUser.status).toBe("OK");
4241
if (createdUser.status === 'OK') {
4342
return {
4443
recipeUserId: createdUser.recipeUserId.getAsString(),
4544
email,
4645
};
4746
}
47+
FailureTracker.getInstance().recordFailure(
48+
'Passwordless users (with email) creation',
49+
createdUser.status
50+
);
4851
});
4952
};
5053

@@ -56,13 +59,16 @@ const createPasswordlessUsersWithPhone = async () => {
5659
tenantId: 'public',
5760
phoneNumber,
5861
});
59-
// expect(createdUser.status).toBe("OK");
6062
if (createdUser.status === 'OK') {
6163
return {
6264
recipeUserId: createdUser.recipeUserId.getAsString(),
6365
phoneNumber,
6466
};
6567
}
68+
FailureTracker.getInstance().recordFailure(
69+
'Passwordless users (with phone) creation',
70+
createdUser.status
71+
);
6672
});
6773
};
6874

@@ -85,13 +91,16 @@ const createThirdPartyUsers = async (thirdPartyId: string) => {
8591
email,
8692
true
8793
);
88-
// expect(createdUser.status).toBe("OK");
8994
if (createdUser.status === 'OK') {
9095
return {
9196
recipeUserId: createdUser.recipeUserId.getAsString(),
9297
email,
9398
};
9499
}
100+
FailureTracker.getInstance().recordFailure(
101+
`ThirdParty users (${thirdPartyId}) creation`,
102+
createdUser.status
103+
);
95104
});
96105
};
97106

stress-tests/src/oneMillionUsers/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
deleteStInstance,
44
setupLicense,
55
StatsCollector,
6+
FailureTracker,
67
} from '../common/utils';
78

89
import SuperTokens from 'supertokens-node';
@@ -147,6 +148,11 @@ async function main() {
147148
// Write stats to file
148149
StatsCollector.getInstance().writeToFile();
149150
console.log('\nStats written to stats.json');
151+
152+
// Fail the run if any seeding step produced non-OK results, so silently
153+
// errored steps don't leave the run looking green with untrustworthy
154+
// measurements.
155+
FailureTracker.getInstance().throwIfAnyFailures();
150156
} catch (error) {
151157
console.error('An error occurred during execution:', error);
152158
throw error;

0 commit comments

Comments
 (0)