From 7b39f18be6dfdb0a218bf7f626b58b619b7fbf15 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Sun, 5 Jul 2026 11:01:08 +0100 Subject: [PATCH 1/8] add integration tests for node 18 --- .circleci/config.yml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index b0a6f7e48..31f1b6ba5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -134,3 +134,19 @@ workflows: node_version: ['22.20', '24.14.0'] requires: - build + node_18_back_compat: + jobs: + - install: + filters: + branches: + ignore: + - main + - build: + requires: + - install + - integration_test: + matrix: + parameters: + node_version: ['18.20'] + requires: + - build From e762b6f4c035101292d891dcf916d381b93d2247 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Sun, 5 Jul 2026 11:23:51 +0100 Subject: [PATCH 2/8] build in a new legacy test suite --- .circleci/config.yml | 52 ++++++++++++------- integration-tests/legacy-deploy/ava.config.js | 3 ++ integration-tests/legacy-deploy/package.json | 29 +++++++++++ .../legacy-deploy/test/deploy.test.js | 6 +++ package.json | 1 + pnpm-lock.yaml | 29 ++++++++++- 6 files changed, 98 insertions(+), 22 deletions(-) create mode 100644 integration-tests/legacy-deploy/ava.config.js create mode 100644 integration-tests/legacy-deploy/package.json create mode 100644 integration-tests/legacy-deploy/test/deploy.test.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 31f1b6ba5..329151250 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -102,9 +102,34 @@ jobs: - run: name: Check install command: openfn test --log info + # - run: + # name: Run integration test suite + # command: pnpm test:integration + - persist_to_workspace: + root: ~/project + paths: + - . + + legacy_test: + docker: + - image: cimg/node:18.20 + resource_class: medium + parallelism: 1 + steps: + - attach_workspace: + at: ~/project + - run: + name: Build local tarballs + command: pnpm pack:local dist --no-version - run: - name: Run integration test suite - command: pnpm test:integration + name: install CLI globally + command: sudo npm install -g file:dist/openfn-cli.tgz + - run: + name: Check install + command: openfn test --log info + - run: + name: Run legacy test suite + command: pnpm test:legacy # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows @@ -119,9 +144,9 @@ workflows: - build: requires: - install - - unit_test: - requires: - - build + # - unit_test: + # requires: + # - build - type_check: requires: - build @@ -134,19 +159,6 @@ workflows: node_version: ['22.20', '24.14.0'] requires: - build - node_18_back_compat: - jobs: - - install: - filters: - branches: - ignore: - - main - - build: - requires: - - install - - integration_test: - matrix: - parameters: - node_version: ['18.20'] + - legacy_test: requires: - - build + - integration_test diff --git a/integration-tests/legacy-deploy/ava.config.js b/integration-tests/legacy-deploy/ava.config.js new file mode 100644 index 000000000..fd66585ce --- /dev/null +++ b/integration-tests/legacy-deploy/ava.config.js @@ -0,0 +1,3 @@ +export default { + files: ['test/**/*test.js'], +}; diff --git a/integration-tests/legacy-deploy/package.json b/integration-tests/legacy-deploy/package.json new file mode 100644 index 000000000..634d788ae --- /dev/null +++ b/integration-tests/legacy-deploy/package.json @@ -0,0 +1,29 @@ +{ + "name": "@openfn/integration-tests-legacy-deploy", + "private": true, + "version": "1.0.0", + "description": "CLI integration tests", + "author": "Open Function Group ", + "license": "ISC", + "type": "module", + "scripts": { + "clean": "rimraf dist repo", + "start": "docker run cli-integration-tests", + "test": "npx ava -s" + }, + "dependencies": { + "@openfn/lightning-mock": "workspace:^", + "@types/node": "^18.19.130", + "ava": "5.3.1", + "date-fns": "^2.30.0", + "rimraf": "^6.1.3", + "tslib": "^2.8.1" + }, + "files": [ + "dist", + "README.md" + ], + "devDependencies": { + "@types/rimraf": "^3.0.2" + } +} diff --git a/integration-tests/legacy-deploy/test/deploy.test.js b/integration-tests/legacy-deploy/test/deploy.test.js new file mode 100644 index 000000000..b82e243a4 --- /dev/null +++ b/integration-tests/legacy-deploy/test/deploy.test.js @@ -0,0 +1,6 @@ +import test from 'ava'; + +test('should deploy a project', (t) => { + t.log(process.version); + t.pass(); +}); diff --git a/package.json b/package.json index 72ffb6f04..cbb9c7766 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "pack": "pnpm -r run pack", "test:format": "prettier --check packages/*/src", "test:integration": "pnpm -r --filter=./integration-tests/* run test", + "test:legacy": "pnpm -C integration-tests/legacy-deploy run test", "test:types": "pnpm -r --filter=./packages/* run test:types", "test": "pnpm -r --filter=./packages/* run test", "_typesync": "pnpm exec typesync && pnpm -r exec typesync && pnpm install" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6c50d0ee..b6458dc2d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -113,6 +113,31 @@ importers: specifier: ^3.0.2 version: 3.0.2 + integration-tests/legacy-deploy: + dependencies: + '@openfn/lightning-mock': + specifier: workspace:^ + version: link:../../packages/lightning-mock + '@types/node': + specifier: ^18.19.130 + version: 18.19.130 + ava: + specifier: 5.3.1 + version: 5.3.1 + date-fns: + specifier: ^2.30.0 + version: 2.30.0 + rimraf: + specifier: ^6.1.3 + version: 6.1.3 + tslib: + specifier: ^2.8.1 + version: 2.8.1 + devDependencies: + '@types/rimraf': + specifier: ^3.0.2 + version: 3.0.2 + integration-tests/worker: dependencies: '@openfn/engine-multi': @@ -5433,7 +5458,7 @@ snapshots: '@slack/logger@4.0.1': dependencies: - '@types/node': 25.6.0 + '@types/node': 18.19.130 '@slack/types@2.21.1': {} @@ -5703,7 +5728,7 @@ snapshots: '@types/rimraf@3.0.2': dependencies: '@types/glob': 9.0.0 - '@types/node': 25.5.0 + '@types/node': 18.19.130 '@types/semver@7.5.8': {} From 057f4dcb5715c9c725f97f4156eb7051738601c3 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Sun, 5 Jul 2026 11:46:25 +0100 Subject: [PATCH 3/8] update build config --- .circleci/config.yml | 52 ++++++++++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 21 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 329151250..f38642350 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,5 +1,14 @@ version: 2.1 +# Reusable command definitions. +# See: https://circleci.com/docs/2.0/configuration-reference/#commands +commands: + install_cli_global: + steps: + - run: + name: install CLI globally + command: sudo npm install -g file:dist/openfn-cli.tgz + # Define a job to be invoked later in a workflow. # See: https://circleci.com/docs/2.0/configuration-reference/#jobs jobs: @@ -82,6 +91,21 @@ jobs: name: Type check command: pnpm test:types + build_openfnx: + docker: + - image: cimg/node:24.14 + resource_class: medium + steps: + - attach_workspace: + at: ~/project + - run: + name: Build local tarballs + command: pnpm pack:local dist --no-version + - persist_to_workspace: + root: ~/project + paths: + - dist + integration_test: docker: - image: cimg/node:<< parameters.node_version >> @@ -93,22 +117,13 @@ jobs: steps: - attach_workspace: at: ~/project - - run: - name: Build local tarballs - command: pnpm pack:local dist --no-version - - run: - name: install CLI globally - command: sudo npm install -g file:dist/openfn-cli.tgz + - install_cli_global - run: name: Check install command: openfn test --log info # - run: # name: Run integration test suite # command: pnpm test:integration - - persist_to_workspace: - root: ~/project - paths: - - . legacy_test: docker: @@ -118,15 +133,7 @@ jobs: steps: - attach_workspace: at: ~/project - - run: - name: Build local tarballs - command: pnpm pack:local dist --no-version - - run: - name: install CLI globally - command: sudo npm install -g file:dist/openfn-cli.tgz - - run: - name: Check install - command: openfn test --log info + - install_cli_global - run: name: Run legacy test suite command: pnpm test:legacy @@ -153,12 +160,15 @@ workflows: - format: requires: - build + - build_openfnx: + requires: + - build - integration_test: matrix: parameters: node_version: ['22.20', '24.14.0'] requires: - - build + - build_openfnx - legacy_test: requires: - - integration_test + - build_openfnx From e8bc10ab0c56e46d66a4cbaaca364f630f620ef9 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Sun, 5 Jul 2026 12:03:44 +0100 Subject: [PATCH 4/8] implmenet test --- .../legacy-deploy/test/deploy.test.js | 78 ++++++++++++++++++- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/integration-tests/legacy-deploy/test/deploy.test.js b/integration-tests/legacy-deploy/test/deploy.test.js index b82e243a4..7c013cca2 100644 --- a/integration-tests/legacy-deploy/test/deploy.test.js +++ b/integration-tests/legacy-deploy/test/deploy.test.js @@ -1,6 +1,80 @@ import test from 'ava'; +import path from 'node:path'; +import fs from 'node:fs/promises'; +import { exec } from 'node:child_process'; +import { rimraf } from 'rimraf'; +import createLightningServer from '@openfn/lightning-mock'; -test('should deploy a project', (t) => { +const port = 8977; +const endpoint = `http://localhost:${port}`; +const tmpDir = path.resolve('tmp/deploy'); + +let server; + +// Run the globally-installed openfn CLI (the tarball under test) +const run = (cmd) => + new Promise((resolve) => { + exec(cmd, { env: process.env }, (err, stdout, stderr) => { + resolve({ err, stdout, stderr }); + }); + }); + +const testProject = ` +name: test-project +workflows: + My-Workflow: + name: My Workflow + jobs: + my-job: + name: My Job + adaptor: '@openfn/language-common@latest' + body: 'fn(s => s)' + triggers: + webhook: + type: webhook + enabled: true + edges: + webhook->my-job: + condition_type: always + source_trigger: webhook + target_job: my-job +`.trim(); + +test.before(async () => { + server = await createLightningServer({ port }); + + process.env.IGNORE_DOT_ENV = 'true'; + process.env.OPENFN_ENDPOINT = endpoint; + process.env.OPENFN_API_KEY = 'test-key'; +}); + +test.beforeEach(async () => { + await rimraf(tmpDir); + await fs.mkdir(tmpDir, { recursive: true }); + server.reset(); +}); + +test.serial('deploy a local project', async (t) => { + // log the node version so CI confirms we're on the legacy runtime t.log(process.version); - t.pass(); + + await fs.writeFile(path.join(tmpDir, 'project.yaml'), testProject); + + t.is(Object.keys(server.state.projects).length, 0); + + const { stdout, stderr } = await run( + `openfn deploy \ + --project-path ${tmpDir}/project.yaml \ + --state-path ${tmpDir}/.state.json \ + --no-confirm \ + --log-json \ + -l debug` + ); + + t.falsy(stderr); + t.regex(stdout, /"message"\:\["Deployed"\]/); + + t.is(Object.keys(server.state.projects).length, 1); + const [project] = Object.values(server.state.projects); + t.is(project.name, 'test-project'); }); From 16677d7768ff1e59f1e2fde00993e9c23079fe53 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Sun, 5 Jul 2026 12:04:04 +0100 Subject: [PATCH 5/8] restore action --- .circleci/config.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f38642350..a21f49199 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -121,9 +121,9 @@ jobs: - run: name: Check install command: openfn test --log info - # - run: - # name: Run integration test suite - # command: pnpm test:integration + - run: + name: Run integration test suite + command: pnpm test:integration legacy_test: docker: @@ -151,9 +151,9 @@ workflows: - build: requires: - install - # - unit_test: - # requires: - # - build + - unit_test: + requires: + - build - type_check: requires: - build From 258eb14ac673394d30bd135c9409ac2df476096f Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Sun, 5 Jul 2026 12:06:16 +0100 Subject: [PATCH 6/8] comments --- .circleci/config.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index a21f49199..1a38d4297 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -125,6 +125,8 @@ jobs: name: Run integration test suite command: pnpm test:integration + # This ensures that we can run a v1 CLI deploy in node 18, + # which is important for GH sync legacy_test: docker: - image: cimg/node:18.20 From 690e2d10c0b1bfb88f84065750689426e136d8a2 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Sun, 5 Jul 2026 12:14:25 +0100 Subject: [PATCH 7/8] polyfill --- integration-tests/legacy-deploy/ava.config.js | 2 ++ integration-tests/legacy-deploy/test/_setup.cjs | 7 +++++++ 2 files changed, 9 insertions(+) create mode 100644 integration-tests/legacy-deploy/test/_setup.cjs diff --git a/integration-tests/legacy-deploy/ava.config.js b/integration-tests/legacy-deploy/ava.config.js index fd66585ce..37054e7cd 100644 --- a/integration-tests/legacy-deploy/ava.config.js +++ b/integration-tests/legacy-deploy/ava.config.js @@ -1,3 +1,5 @@ export default { files: ['test/**/*test.js'], + // Polyfill the File global for Node 18 before undici 7 loads (see _setup.cjs) + nodeArguments: ['--require=./test/_setup.cjs'], }; diff --git a/integration-tests/legacy-deploy/test/_setup.cjs b/integration-tests/legacy-deploy/test/_setup.cjs new file mode 100644 index 000000000..fb4caec22 --- /dev/null +++ b/integration-tests/legacy-deploy/test/_setup.cjs @@ -0,0 +1,7 @@ +// Node 18 does not expose File as a global (it became global in Node 20) +// undici 7 (pulled in transitively via @openfn/lightning-mock) references +// globalThis.File at load time, so define it from node:buffer before any +// module graph is evaluated +if (typeof globalThis.File === 'undefined') { + globalThis.File = require('node:buffer').File; +} From 40ec017a1da7adf098eb48dd266461a0602b1ee3 Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Mon, 6 Jul 2026 11:35:57 +0100 Subject: [PATCH 8/8] rewrite test --- .../legacy-deploy/.tool-versions | 1 + integration-tests/legacy-deploy/ava.config.js | 2 - integration-tests/legacy-deploy/package.json | 1 - .../legacy-deploy/test/_setup.cjs | 7 --- .../legacy-deploy/test/deploy.test.js | 53 ++++++++++++++----- pnpm-lock.yaml | 3 -- 6 files changed, 42 insertions(+), 25 deletions(-) create mode 100644 integration-tests/legacy-deploy/.tool-versions delete mode 100644 integration-tests/legacy-deploy/test/_setup.cjs diff --git a/integration-tests/legacy-deploy/.tool-versions b/integration-tests/legacy-deploy/.tool-versions new file mode 100644 index 000000000..f6efb75ce --- /dev/null +++ b/integration-tests/legacy-deploy/.tool-versions @@ -0,0 +1 @@ +nodejs 18.17.1 diff --git a/integration-tests/legacy-deploy/ava.config.js b/integration-tests/legacy-deploy/ava.config.js index 37054e7cd..fd66585ce 100644 --- a/integration-tests/legacy-deploy/ava.config.js +++ b/integration-tests/legacy-deploy/ava.config.js @@ -1,5 +1,3 @@ export default { files: ['test/**/*test.js'], - // Polyfill the File global for Node 18 before undici 7 loads (see _setup.cjs) - nodeArguments: ['--require=./test/_setup.cjs'], }; diff --git a/integration-tests/legacy-deploy/package.json b/integration-tests/legacy-deploy/package.json index 634d788ae..7eb35b66c 100644 --- a/integration-tests/legacy-deploy/package.json +++ b/integration-tests/legacy-deploy/package.json @@ -12,7 +12,6 @@ "test": "npx ava -s" }, "dependencies": { - "@openfn/lightning-mock": "workspace:^", "@types/node": "^18.19.130", "ava": "5.3.1", "date-fns": "^2.30.0", diff --git a/integration-tests/legacy-deploy/test/_setup.cjs b/integration-tests/legacy-deploy/test/_setup.cjs deleted file mode 100644 index fb4caec22..000000000 --- a/integration-tests/legacy-deploy/test/_setup.cjs +++ /dev/null @@ -1,7 +0,0 @@ -// Node 18 does not expose File as a global (it became global in Node 20) -// undici 7 (pulled in transitively via @openfn/lightning-mock) references -// globalThis.File at load time, so define it from node:buffer before any -// module graph is evaluated -if (typeof globalThis.File === 'undefined') { - globalThis.File = require('node:buffer').File; -} diff --git a/integration-tests/legacy-deploy/test/deploy.test.js b/integration-tests/legacy-deploy/test/deploy.test.js index 7c013cca2..514139885 100644 --- a/integration-tests/legacy-deploy/test/deploy.test.js +++ b/integration-tests/legacy-deploy/test/deploy.test.js @@ -1,20 +1,46 @@ import test from 'ava'; import path from 'node:path'; import fs from 'node:fs/promises'; +import http from 'node:http'; import { exec } from 'node:child_process'; import { rimraf } from 'rimraf'; -import createLightningServer from '@openfn/lightning-mock'; -const port = 8977; -const endpoint = `http://localhost:${port}`; const tmpDir = path.resolve('tmp/deploy'); let server; +let endpoint; +let lastDeploy; // the payload the CLI POSTs to the provisioning endpoint + +// A minimal stand-in for Lightning's provisioning API +const createMockServer = () => + new Promise((resolve) => { + const srv = http.createServer((req, res) => { + if (!req.url.startsWith('/api/provision')) { + res.statusCode = 404; + return res.end(); + } + + if (req.method === 'GET') { + // pretend no project exists yet, so everything is a "new" change + res.statusCode = 404; + return res.end(); + } + + let body = ''; + req.on('data', (chunk) => (body += chunk)); + req.on('end', () => { + lastDeploy = JSON.parse(body); + res.statusCode = 200; + res.setHeader('content-type', 'application/json'); + res.end(JSON.stringify({ data: lastDeploy })); + }); + }); + srv.listen(0, () => resolve(srv)); + }); -// Run the globally-installed openfn CLI (the tarball under test) const run = (cmd) => new Promise((resolve) => { - exec(cmd, { env: process.env }, (err, stdout, stderr) => { + exec(cmd, (err, stdout, stderr) => { resolve({ err, stdout, stderr }); }); }); @@ -41,17 +67,22 @@ workflows: `.trim(); test.before(async () => { - server = await createLightningServer({ port }); + server = await createMockServer(); + endpoint = `http://localhost:${server.address().port}`; process.env.IGNORE_DOT_ENV = 'true'; process.env.OPENFN_ENDPOINT = endpoint; process.env.OPENFN_API_KEY = 'test-key'; }); +test.after.always(() => { + server?.close(); +}); + test.beforeEach(async () => { await rimraf(tmpDir); await fs.mkdir(tmpDir, { recursive: true }); - server.reset(); + lastDeploy = undefined; }); test.serial('deploy a local project', async (t) => { @@ -60,8 +91,6 @@ test.serial('deploy a local project', async (t) => { await fs.writeFile(path.join(tmpDir, 'project.yaml'), testProject); - t.is(Object.keys(server.state.projects).length, 0); - const { stdout, stderr } = await run( `openfn deploy \ --project-path ${tmpDir}/project.yaml \ @@ -74,7 +103,7 @@ test.serial('deploy a local project', async (t) => { t.falsy(stderr); t.regex(stdout, /"message"\:\["Deployed"\]/); - t.is(Object.keys(server.state.projects).length, 1); - const [project] = Object.values(server.state.projects); - t.is(project.name, 'test-project'); + // the CLI should have posted our project to the mock endpoint + t.truthy(lastDeploy); + t.is(lastDeploy.name, 'test-project'); }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b6458dc2d..64b8abe06 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -115,9 +115,6 @@ importers: integration-tests/legacy-deploy: dependencies: - '@openfn/lightning-mock': - specifier: workspace:^ - version: link:../../packages/lightning-mock '@types/node': specifier: ^18.19.130 version: 18.19.130