fix: CI failures — Node 22, test timeouts, QEMU transaction timeout (#1479)

This commit is contained in:
Konsti Wohlwend 2026-05-22 20:24:13 -07:00 committed by GitHub
parent b48902fcf2
commit 6a8b45e6c5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 24 additions and 23 deletions

View File

@ -26,10 +26,10 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup Node.js v20
- name: Setup Node.js v22
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 20
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4

View File

@ -24,10 +24,10 @@ jobs:
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup Node.js v20
- name: Setup Node.js v22
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6
with:
node-version: 20
node-version: 22
- name: Setup pnpm
uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4

View File

@ -681,7 +681,7 @@ async function seedDummyUsers(options: SeedDummyUsersOptions): Promise<Map<strin
if (directPermissionRows.length > 0) {
await tx.projectUserDirectPermission.createMany({ data: directPermissionRows });
}
});
}, { timeout: 90_000 });
}
// Team memberships for the named seed users — bulk-inserted the same way.
@ -725,7 +725,7 @@ async function seedDummyUsers(options: SeedDummyUsersOptions): Promise<Map<strin
if (teamMemberPermissionRows.length > 0) {
await tx.teamMemberDirectPermission.createMany({ data: teamMemberPermissionRows });
}
});
}, { timeout: 90_000 });
}
return userEmailToId;

View File

@ -480,7 +480,7 @@ class TransactionErrorThatShouldNotBeRetried extends Error {
/**
* @deprecated Prisma transactions are slow and lock the database. Use rawQuery with CTEs instead. Ask Konsti if you're confused or think you need transactions.
*/
export async function retryTransaction<T>(client: Omit<PrismaClient, "$on">, fn: (tx: PrismaClientTransaction) => Promise<T>, options: { level?: "default" | "serializable" } = {}): Promise<T> {
export async function retryTransaction<T>(client: Omit<PrismaClient, "$on">, fn: (tx: PrismaClientTransaction) => Promise<T>, options: { level?: "default" | "serializable", timeout?: number } = {}): Promise<T> {
// serializable transactions are currently off by default, later we may turn them on
const enableSerializable = options.level === "serializable";
@ -524,6 +524,7 @@ export async function retryTransaction<T>(client: Omit<PrismaClient, "$on">, fn:
return res;
}, {
isolationLevel: enableSerializable ? Prisma.TransactionIsolationLevel.Serializable : undefined,
...(options.timeout != null ? { timeout: options.timeout } : {}),
}));
} catch (e) {
// we don't want to retry too aggressively here, because the error may have been thrown after the transaction was already committed

View File

@ -634,7 +634,7 @@ it("rejects batch when analytics event quota is exhausted", async ({ expect }) =
expect(res.body.code).toBe("ITEM_QUANTITY_INSUFFICIENT_AMOUNT");
});
it("accepts batch and debits event quota correctly", async ({ expect }) => {
it("accepts batch and debits event quota correctly", { timeout: 120_000 }, async ({ expect }) => {
const { ownerTeamId } = await setupProjectWithPlan("free");
await Auth.Otp.signIn();
@ -673,7 +673,7 @@ it("accepts batch and debits event quota correctly", async ({ expect }) => {
// We don't support metered pricing or partial batches for now, so the entire
// batch is rejected when remaining quota is less than the batch size, and
// the quota must remain unchanged (no partial debit).
it("rejects batch when remaining quota is less than batch size and does not debit", async ({ expect }) => {
it("rejects batch when remaining quota is less than batch size and does not debit", { timeout: 120_000 }, async ({ expect }) => {
const { ownerTeamId } = await setupProjectWithPlan("free");
await Auth.Otp.signIn();

View File

@ -214,7 +214,7 @@ it("cannot read events from other projects", async ({ expect }) => {
`);
});
it("filters analytics events by user within a project", async ({ expect }) => {
it("filters analytics events by user within a project", { timeout: 120_000 }, async ({ expect }) => {
await Project.createAndSwitch({ config: { magic_link_enabled: true } });
const { userId: userA } = await Auth.Otp.signIn();
await bumpEmailAddress();

View File

@ -43,7 +43,7 @@ async function ensureAnonymousUsersAreStillExcluded(metricsResponse: NiceRespons
// ClickHouse ingestion is async; poll until anonymous users are excluded again.
let response!: NiceResponse;
for (let i = 0; i < 10; i++) {
for (let i = 0; i < 30; i++) {
await wait(2_000);
response = await niceBackendFetch("/api/v1/internal/metrics", { accessType: 'admin' });
const noAnonymousInRecentlyRegistered = (response.body.recently_registered as MetricsUser[]).every((user) => !user.is_anonymous);
@ -72,7 +72,7 @@ async function ensureAnonymousUsersAreStillExcluded(metricsResponse: NiceRespons
async function waitForMetricsToIncludeUsersByCountry(options: { countryCode: string, expectedCount: number }): Promise<NiceResponse> {
let response!: NiceResponse;
for (let i = 0; i < 15; i++) {
for (let i = 0; i < 30; i++) {
response = await niceBackendFetch("/api/v1/internal/metrics", { accessType: 'admin' });
if (response.body?.users_by_country?.[options.countryCode] === options.expectedCount) {
return response;
@ -88,7 +88,7 @@ async function waitForMetricsMatch(
): Promise<NiceResponse> {
let response!: NiceResponse;
const suffix = includeAnonymous ? "?include_anonymous=true" : "";
for (let i = 0; i < 20; i++) {
for (let i = 0; i < 60; i++) {
response = await niceBackendFetch(`/api/v1/internal/metrics${suffix}`, { accessType: 'admin' });
if (predicate(response)) {
return response;
@ -123,7 +123,7 @@ async function waitForAnalyticsRowsForSessionReplaySegment(
throw new Error(`Timed out waiting for ${expectedCount} analytics rows for session replay segment ${sessionReplaySegmentId}`);
}
it("should return metrics data", async ({ expect }) => {
it("should return metrics data", { timeout: 120_000 }, async ({ expect }) => {
await Project.createAndSwitch({
config: {
magic_link_enabled: true,

View File

@ -189,7 +189,7 @@ it("anonymous signup creates exactly one $token-refresh event", async ({ expect
});
});
it("OAuth signup creates exactly one $token-refresh event", async ({ expect }) => {
it("OAuth signup creates exactly one $token-refresh event", { timeout: 120_000 }, async ({ expect }) => {
const { projectId } = await Project.createAndSwitch({
config: {
oauth_providers: [{
@ -223,7 +223,7 @@ it("OAuth signup creates exactly one $token-refresh event", async ({ expect }) =
// Signin Tests
// ============================================================================
it("password signin (existing user) creates exactly one additional $token-refresh event", async ({ expect }) => {
it("password signin (existing user) creates exactly one additional $token-refresh event", { timeout: 120_000 }, async ({ expect }) => {
const { projectId } = await Project.createAndSwitch({
config: { credential_enabled: true },
});
@ -246,7 +246,7 @@ it("password signin (existing user) creates exactly one additional $token-refres
expect(events.every((e: AnalyticsEvent) => e.user_id === userId)).toBe(true);
});
it("OTP signin (existing user) creates exactly one additional $token-refresh event", async ({ expect }) => {
it("OTP signin (existing user) creates exactly one additional $token-refresh event", { timeout: 120_000 }, async ({ expect }) => {
const { projectId } = await Project.createAndSwitch({
config: { magic_link_enabled: true },
});
@ -267,7 +267,7 @@ it("OTP signin (existing user) creates exactly one additional $token-refresh eve
expect(events.every((e: AnalyticsEvent) => e.user_id === userId)).toBe(true);
});
it("OAuth signin (existing user) creates exactly one additional $token-refresh event", async ({ expect }) => {
it("OAuth signin (existing user) creates exactly one additional $token-refresh event", { timeout: 120_000 }, async ({ expect }) => {
const { projectId } = await Project.createAndSwitch({
config: {
oauth_providers: [{
@ -299,7 +299,7 @@ it("OAuth signin (existing user) creates exactly one additional $token-refresh e
// Session Refresh Tests
// ============================================================================
it("session refresh endpoint creates exactly one additional $token-refresh event", async ({ expect }) => {
it("session refresh endpoint creates exactly one additional $token-refresh event", { timeout: 120_000 }, async ({ expect }) => {
const { projectId } = await Project.createAndSwitch({
config: { magic_link_enabled: true },
});
@ -317,7 +317,7 @@ it("session refresh endpoint creates exactly one additional $token-refresh event
expect(events.every((e: AnalyticsEvent) => e.user_id === userId)).toBe(true);
});
it("multiple session refreshes create one event each", async ({ expect }) => {
it("multiple session refreshes create one event each", { timeout: 180_000 }, async ({ expect }) => {
const { projectId } = await Project.createAndSwitch({
config: { magic_link_enabled: true },
});
@ -344,7 +344,7 @@ it("multiple session refreshes create one event each", async ({ expect }) => {
// OAuth Refresh Token Grant Tests
// ============================================================================
it("OAuth refresh token grant creates exactly one additional $token-refresh event", async ({ expect }) => {
it("OAuth refresh token grant creates exactly one additional $token-refresh event", { timeout: 120_000 }, async ({ expect }) => {
const { projectId } = await Project.createAndSwitch({
config: {
oauth_providers: [{
@ -391,7 +391,7 @@ it("OAuth refresh token grant creates exactly one additional $token-refresh even
expect(events.every((e: AnalyticsEvent) => e.user_id === userId)).toBe(true);
});
it("multiple OAuth refresh token grants create one event each", async ({ expect }) => {
it("multiple OAuth refresh token grants create one event each", { timeout: 180_000 }, async ({ expect }) => {
const { projectId } = await Project.createAndSwitch({
config: {
oauth_providers: [{

View File

@ -198,7 +198,7 @@ it("should list invitations from multiple teams", async ({ expect }) => {
});
it("should accept a team invitation via the client SDK", async ({ expect }) => {
it("should accept a team invitation via the client SDK", { timeout: 120_000 }, async ({ expect }) => {
const { clientApp, serverApp } = await createApp({ config: { clientTeamCreationEnabled: true } });
// Create a team