diff --git a/tests/integration/use-cache.test.ts b/tests/integration/use-cache.test.ts index 558e165445..671e196656 100644 --- a/tests/integration/use-cache.test.ts +++ b/tests/integration/use-cache.test.ts @@ -165,6 +165,10 @@ describe.skipIf(!nextVersionSatisfies('>=15.3.0-canary.13'))('use cache', () => vi.stubEnv('NETLIFY_PURGE_API_TOKEN', 'fake-token') await startMockBlobStore(ctx as FixtureTestContext) + // we use same fixture for all the tests, which normally cleans up after each test + // but here we want to control cleanup ourselves and only do that after all test suites did run + ctx.skipAutoCleanup = true + await createFixture('use-cache', ctx) await runPlugin(ctx) }) diff --git a/tests/test-setup.ts b/tests/test-setup.ts index 20bb5c1e6f..2d3af50c29 100644 --- a/tests/test-setup.ts +++ b/tests/test-setup.ts @@ -14,5 +14,7 @@ export async function afterTestCleanup({ cleanup }: FixtureTestContext) { // cleanup after each test as a fallback if someone forgot to call it afterEach(async (ctx) => { - await afterTestCleanup(ctx) + if (ctx.skipAutoCleanup !== true) { + await afterTestCleanup(ctx) + } }) diff --git a/tests/utils/contexts.ts b/tests/utils/contexts.ts index bc2bebe99f..14cd85b6ae 100644 --- a/tests/utils/contexts.ts +++ b/tests/utils/contexts.ts @@ -16,4 +16,5 @@ export interface FixtureTestContext extends TestContext { edgeFunctionPort: number edgeFunctionOutput: WriteStream cleanup?: (() => Promise)[] + skipAutoCleanup?: boolean } diff --git a/tests/utils/fixture.ts b/tests/utils/fixture.ts index 327198e6f5..36b1d6bcdf 100644 --- a/tests/utils/fixture.ts +++ b/tests/utils/fixture.ts @@ -9,6 +9,7 @@ import { execaCommand } from 'execa' import getPort from 'get-port' import { spawn } from 'node:child_process' import { createWriteStream, existsSync } from 'node:fs' +import { createRequire } from 'node:module' import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { basename, dirname, join, parse, relative } from 'node:path' @@ -37,6 +38,12 @@ const bootstrapURL = await getBootstrapURL() const actualCwd = await vi.importActual('process').then((p) => p.cwd()) const eszipHelper = join(actualCwd, 'tools/deno/eszip.ts') +const require = createRequire(import.meta.url) +const mod = require('module') + +const originalRequire = mod.prototype.require +const originalResolveFilename = mod._resolveFilename + async function installDependencies(cwd: string) { const NEXT_VERSION = process.env.NEXT_VERSION ?? 'latest' await setNextVersionInFixture(cwd, NEXT_VERSION, { silent: true }) @@ -110,6 +117,30 @@ export const createFixture = async (fixture: string, ctx: FixtureTestContext) => // from any previous function invocations that might have run in the same process delete globalThis[Symbol.for('next.server.manifests')] + // require hook leaves modified "require" and "require.resolve" modified - we restore here to original + // https://github.com/vercel/next.js/blob/812c26ab8741f68fbd6e2fe095510e0f03eac4c5/packages/next/src/server/require-hook.ts + mod.prototype.require = originalRequire + mod._resolveFilename = originalResolveFilename + + // node environment baseline defines global WebSocket getter that requires compiled 'ws' package from first function modules + // we need to reset the getter to not have it attempt to import 'ws' package from unrelated functions that might have already been deleted + // https://github.com/vercel/next.js/blob/812c26ab8741f68fbd6e2fe095510e0f03eac4c5/packages/next/src/server/node-environment-baseline.ts#L11-L27 + // note that some next versions didn't have setter, so we can't just do "globalThis.WebSocket = undefined" as that would throw + // "Cannot set property WebSocket of # which has only a getter" errors + Object.defineProperty(globalThis, 'WebSocket', { + get() { + return undefined + }, + set(value) { + Object.defineProperty(globalThis, 'WebSocket', { + configurable: true, + writable: true, + value, + }) + }, + configurable: true, + }) + ctx.cwd = await mkdtemp(join(tmpdir(), 'opennextjs-netlify-')) vi.spyOn(process, 'cwd').mockReturnValue(ctx.cwd) @@ -147,6 +178,16 @@ export const createFixture = async (fixture: string, ctx: FixtureTestContext) => } catch (error) { console.log(`Fixture '${fixture}' has failed to cleanup at '${ctx.cwd}'`, error) } + if (ctx.functionDist) { + try { + await rm(ctx.functionDist, { recursive: true, force: true }) + } catch (error) { + console.log( + `Fixture's '${fixture}' bundled serverless function has failed to cleanup at '${ctx.cwd}'`, + error, + ) + } + } }) }