From e70ec8b685be42335cf523dc04fc40fa268b3410 Mon Sep 17 00:00:00 2001 From: mac Date: Thu, 26 Mar 2026 02:37:54 +0100 Subject: [PATCH 1/3] fix(e2e): use unique SSM paths per CLI test run to prevent race conditions Concurrent CI runs shared hardcoded SSM paths (/Test/Token, /Test/SingleVariable) against real AWS SSM, causing ParameterNotFound errors when one run's cleanup deleted parameters another run had just created. Each test run now generates a unique runId and creates temporary map files with isolated SSM paths (/Test//Token). Temp files are cleaned up in afterAll. --- e2e/cli.test.ts | 47 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/e2e/cli.test.ts b/e2e/cli.test.ts index d181c579..b0445f68 100644 --- a/e2e/cli.test.ts +++ b/e2e/cli.test.ts @@ -40,25 +40,45 @@ const LOWKEY_VAULT_IMAGE = 'nagyesta/lowkey-vault:7.1.32'; const LOWKEY_VAULT_PORT = 8443; describe('Envilder (E2E)', () => { - beforeAll(async () => { - await cleanUpSystem(); - execSync('pnpm build', { cwd: rootDir, stdio: 'inherit' }); - execSync('node --loader ts-node/esm scripts/pack-and-install.ts', { - cwd: rootDir, - stdio: 'inherit', - }); - }, 60_000); + // Unique ID per test run prevents race conditions between concurrent CI runs + const runId = randomUUID().slice(0, 8); + const ssmPrefix = `/Test/${runId}`; const envilder = 'envilder'; const envFilePath = join(rootDir, 'e2e', 'sample', 'cli-validation.env'); - const mapFilePath = join(rootDir, 'e2e', 'sample', 'param-map.json'); + const mapFilePath = join(rootDir, 'e2e', 'sample', `param-map-${runId}.json`); const mapFileWithConfigPath = join( rootDir, 'e2e', 'sample', - 'param-map-with-aws-config.json', + `param-map-with-aws-config-${runId}.json`, ); - const singleSsmPath = '/Test/SingleVariable'; + const singleSsmPath = `${ssmPrefix}/SingleVariable`; + + beforeAll(async () => { + writeFileSync( + mapFilePath, + JSON.stringify({ TOKEN_SECRET: `${ssmPrefix}/Token` }, null, 2), + ); + writeFileSync( + mapFileWithConfigPath, + JSON.stringify( + { + $config: { provider: 'aws' }, + TOKEN_SECRET: `${ssmPrefix}/Token`, + }, + null, + 2, + ), + ); + + await cleanUpSystem(); + execSync('pnpm build', { cwd: rootDir, stdio: 'inherit' }); + execSync('node --loader ts-node/esm scripts/pack-and-install.ts', { + cwd: rootDir, + stdio: 'inherit', + }); + }, 60_000); beforeEach(async () => { await cleanUpSsm(mapFilePath, singleSsmPath); @@ -72,6 +92,11 @@ describe('Envilder (E2E)', () => { afterAll(async () => { await cleanUpSystem(); + for (const f of [mapFilePath, mapFileWithConfigPath]) { + if (existsSync(f)) { + await unlink(f); + } + } }, 60_000); it('Should_PrintCorrectVersion_When_VersionFlagIsProvided', async () => { From f811b16d1960443b455a193fc33928f914ef1645 Mon Sep 17 00:00:00 2001 From: mac Date: Sun, 29 Mar 2026 20:07:51 +0200 Subject: [PATCH 2/3] test(e2e): use async map file io in cli race-condition test Replace sync map-file writes with async Promise.all in beforeAll and use forced rm cleanup in afterAll. This avoids blocking file IO and simplifies teardown for ephemeral test artifacts. --- e2e/cli.test.ts | 41 +++++++++++++++++++++-------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/e2e/cli.test.ts b/e2e/cli.test.ts index 8e770b36..e321b3dd 100644 --- a/e2e/cli.test.ts +++ b/e2e/cli.test.ts @@ -2,7 +2,7 @@ import 'reflect-metadata'; import { execSync, spawn } from 'node:child_process'; import { randomUUID } from 'node:crypto'; import { existsSync, readFileSync, writeFileSync } from 'node:fs'; -import { rm, unlink } from 'node:fs/promises'; +import { rm, unlink, writeFile } from 'node:fs/promises'; import { dirname, join } from 'node:path'; import { fileURLToPath } from 'node:url'; import { @@ -56,21 +56,23 @@ describe('Envilder (E2E)', () => { const singleSsmPath = `${ssmPrefix}/SingleVariable`; beforeAll(async () => { - writeFileSync( - mapFilePath, - JSON.stringify({ TOKEN_SECRET: `${ssmPrefix}/Token` }, null, 2), - ); - writeFileSync( - mapFileWithConfigPath, - JSON.stringify( - { - $config: { provider: 'aws' }, - TOKEN_SECRET: `${ssmPrefix}/Token`, - }, - null, - 2, + await Promise.all([ + writeFile( + mapFilePath, + JSON.stringify({ TOKEN_SECRET: `${ssmPrefix}/Token` }, null, 2), ), - ); + writeFile( + mapFileWithConfigPath, + JSON.stringify( + { + $config: { provider: 'aws' }, + TOKEN_SECRET: `${ssmPrefix}/Token`, + }, + null, + 2, + ), + ), + ]); await cleanUpSystem(); execSync('pnpm build', { cwd: rootDir, stdio: 'inherit' }); @@ -92,11 +94,10 @@ describe('Envilder (E2E)', () => { afterAll(async () => { await cleanUpSystem(); - for (const f of [mapFilePath, mapFileWithConfigPath]) { - if (existsSync(f)) { - await unlink(f); - } - } + await Promise.all([ + rm(mapFilePath, { force: true }), + rm(mapFileWithConfigPath, { force: true }), + ]); }, 60_000); it('Should_PrintCorrectVersion_When_VersionFlagIsProvided', async () => { From 5fad31ba8da4738fb793a695ab79b7599d4bf390 Mon Sep 17 00:00:00 2001 From: mac Date: Sun, 29 Mar 2026 20:24:54 +0200 Subject: [PATCH 3/3] chore(ci): Add e2e path to workflow triggers --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ce2e44aa..c7555f78 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,6 +22,7 @@ on: - "github-action/action.yml" - "src/**" - "tests/**" + - "e2e/**" - "package.json" - "pnpm-lock.yaml" - "tsconfig.json"