From eb6b123dba7a84f93be30219d022c16bf0dcb1f1 Mon Sep 17 00:00:00 2001 From: mulahasanovic Date: Tue, 12 May 2026 20:10:58 +0200 Subject: [PATCH] test(viya): migrate execution-tasks tests to runAsTask config --- sasjs-tests/src/testSuites/executionTasks.ts | 109 ++++++++++++++---- src/job-execution/spec/executionTasks.spec.ts | 106 +++++++++++------ 2 files changed, 159 insertions(+), 56 deletions(-) diff --git a/sasjs-tests/src/testSuites/executionTasks.ts b/sasjs-tests/src/testSuites/executionTasks.ts index 78e7866..1ab689d 100644 --- a/sasjs-tests/src/testSuites/executionTasks.ts +++ b/sasjs-tests/src/testSuites/executionTasks.ts @@ -4,55 +4,120 @@ import type { TestSuite } from '../types' const tableData: any = { table1: [{ col1: 'first col value' }] } const fileData: any = { table1: [{ col1: 'value with ; semicolon' }] } +const multiTableData: any = { + table1: [{ col1: 'first col value' }], + table2: [{ col2: 'second table value' }] +} +const multiFileData: any = { + table1: [{ col1: 'value with ; semicolon' }], + table2: [{ col2: 'another; value' }] +} + +const taskConfig: any = { useComputeApi: null, runAsTask: true } +const noTaskConfig: any = { useComputeApi: null, runAsTask: false } export const executionTasksTests = (adapter: SASjs): TestSuite => ({ - name: '_executionTasks=true behaviour', + name: 'runAsTask behaviour', tests: [ { - title: 'sends table data in body', - description: 'table payload, no _executionTasks flag', + title: 'no inputs (runAsTask=false)', + description: 'no payload, runAsTask explicitly disabled', test: () => adapter - .request('services/common/sendArr', tableData, { - useComputeApi: null - }) + .request('services/common/sendArr', null, noTaskConfig) .then((res: any) => ({ ok: true, res })) .catch((e: any) => ({ ok: false, error: e })), assertion: (res: any) => res?.ok === true }, { - title: 'sends table data when _executionTasks=true', - description: 'table payload with _executionTasks=true', + title: 'no inputs (runAsTask=true)', + description: 'no payload, runAsTask=true via config', test: () => adapter - .request('services/common/sendArr&_executionTasks=true', tableData, { - useComputeApi: null - }) + .request('services/common/sendArr', null, taskConfig) .then((res: any) => ({ ok: true, res })) .catch((e: any) => ({ ok: false, error: e })), assertion: (res: any) => res?.ok === true }, { - title: 'uploads as file when payload has semicolons', - description: 'semicolon payload, no _executionTasks flag', + title: 'one input table (runAsTask=false)', + description: 'single table payload, runAsTask explicitly disabled', test: () => adapter - .request('services/common/sendArr', fileData, { - useComputeApi: null - }) + .request('services/common/sendArr', tableData, noTaskConfig) .then((res: any) => ({ ok: true, res })) .catch((e: any) => ({ ok: false, error: e })), assertion: (res: any) => res?.ok === true }, { - title: - 'uploads as file when _executionTasks=true and payload has semicolons', - description: 'semicolon payload with _executionTasks=true', + title: 'one input table (runAsTask=true)', + description: 'single table payload, runAsTask=true via config', test: () => adapter - .request('services/common/sendArr&_executionTasks=true', fileData, { - useComputeApi: null - }) + .request('services/common/sendArr', tableData, taskConfig) + .then((res: any) => ({ ok: true, res })) + .catch((e: any) => ({ ok: false, error: e })), + assertion: (res: any) => res?.ok === true + }, + { + title: 'multiple input tables (runAsTask=false)', + description: 'multi-table payload, runAsTask explicitly disabled', + test: () => + adapter + .request('services/common/sendArr', multiTableData, noTaskConfig) + .then((res: any) => ({ ok: true, res })) + .catch((e: any) => ({ ok: false, error: e })), + assertion: (res: any) => res?.ok === true + }, + { + title: 'multiple input tables (runAsTask=true)', + description: 'multi-table payload, runAsTask=true via config', + test: () => + adapter + .request('services/common/sendArr', multiTableData, taskConfig) + .then((res: any) => ({ ok: true, res })) + .catch((e: any) => ({ ok: false, error: e })), + assertion: (res: any) => res?.ok === true + }, + { + title: 'semicolon payload, single table, blob path (runAsTask=false)', + description: 'semicolon payload routes through blob path, runAsTask off', + test: () => + adapter + .request('services/common/sendArr', fileData, noTaskConfig) + .then((res: any) => ({ ok: true, res })) + .catch((e: any) => ({ ok: false, error: e })), + assertion: (res: any) => res?.ok === true + }, + { + title: 'semicolon payload, single table, blob path (runAsTask=true)', + description: + 'semicolon payload (single table) routes through blob path, runAsTask=true', + test: () => + adapter + .request('services/common/sendArr', fileData, taskConfig) + .then((res: any) => ({ ok: true, res })) + .catch((e: any) => ({ ok: false, error: e })), + assertion: (res: any) => res?.ok === true + }, + { + title: 'semicolon payload, multi-table, blob path (runAsTask=false)', + description: + 'semicolon payload (multi-table) routes through blob path, runAsTask off', + test: () => + adapter + .request('services/common/sendArr', multiFileData, noTaskConfig) + .then((res: any) => ({ ok: true, res })) + .catch((e: any) => ({ ok: false, error: e })), + assertion: (res: any) => res?.ok === true + }, + { + title: 'semicolon payload, multi-table, blob path (runAsTask=true)', + description: + 'semicolon payload (multi-table) routes through blob path, runAsTask=true', + test: () => + adapter + .request('services/common/sendArr', multiFileData, taskConfig) .then((res: any) => ({ ok: true, res })) .catch((e: any) => ({ ok: false, error: e })), assertion: (res: any) => res?.ok === true diff --git a/src/job-execution/spec/executionTasks.spec.ts b/src/job-execution/spec/executionTasks.spec.ts index 877c0f6..3851700 100644 --- a/src/job-execution/spec/executionTasks.spec.ts +++ b/src/job-execution/spec/executionTasks.spec.ts @@ -4,7 +4,7 @@ import { WebJobExecutor } from '../WebJobExecutor' import { RequestClient } from '../../request/RequestClient' import { SASViyaApiClient } from '../../SASViyaApiClient' -describe('WebJobExecutor _executionTasks=true behaviour', () => { +describe('WebJobExecutor runAsTask behaviour', () => { const serverUrl = 'https://sample.server.com' const jobsPath = '/SASJobExecution' @@ -31,35 +31,63 @@ describe('WebJobExecutor _executionTasks=true behaviour', () => { serverUrl, serverType: ServerType.SasViya, appLoc: '/Public/app', + useComputeApi: false, debug: false } - it('sends table data in body', async () => { + it('sends table data in body (runAsTask=false)', async () => { const { executor, postSpy } = makeExecutor() await executor.execute( 'services/common/sendArr', { table1: [{ col1: 'v' }] }, - baseConfig + { ...baseConfig, runAsTask: false } ) - const [, body, , contentType] = postSpy.mock.calls[0] + const [apiUrl, body, , contentType] = postSpy.mock.calls[0] + expect(apiUrl).not.toContain('_executionTasks=true') expect(body).toBeInstanceOf(NodeFormData) expect(contentType).toMatch(/^multipart\/form-data/) }) - it('sends table data when _executionTasks=true', async () => { + it('uploads as file when payload has semicolons (runAsTask=false)', async () => { const { executor, postSpy } = makeExecutor() await executor.execute( - 'services/common/sendArr&_executionTasks=true', - { table1: [{ col1: 'v' }] }, - baseConfig + 'services/common/sendArr', + { table1: [{ col1: 'has; semicolon' }] }, + { ...baseConfig, runAsTask: false } ) const [apiUrl, body, , contentType] = postSpy.mock.calls[0] - expect(apiUrl).toContain('_program=/Public/app/services/common/sendArr') - expect(apiUrl).toContain('_executionTasks=true') + expect(apiUrl).not.toContain('_executionTasks=') + expect(body).toBeInstanceOf(NodeFormData) + expect(contentType).toMatch(/^multipart\/form-data/) + }) + + it('appends &_executionTasks=true to URL when runAsTask=true and no data', async () => { + const { executor, postSpy } = makeExecutor() + + await executor.execute('services/common/sendArr', null, { + ...baseConfig, + runAsTask: true + }) + + const [apiUrl] = postSpy.mock.calls[0] + expect(apiUrl).toContain('&_executionTasks=true') + }) + + it('appends &_executionTasks=true and sends table data when runAsTask=true with one input table', async () => { + const { executor, postSpy } = makeExecutor() + + await executor.execute( + 'services/common/sendArr', + { table1: [{ col1: 'v' }] }, + { ...baseConfig, runAsTask: true } + ) + + const [apiUrl, body, , contentType] = postSpy.mock.calls[0] + expect(apiUrl).toContain('&_executionTasks=true') expect(body).toBeInstanceOf(NodeFormData) expect(contentType).toMatch(/^multipart\/form-data/) const dump = (body as NodeFormData).getBuffer().toString() @@ -67,42 +95,52 @@ describe('WebJobExecutor _executionTasks=true behaviour', () => { expect(dump).toContain('name="sasjs1data"') }) - it('uploads as file when payload has semicolons', async () => { + it('appends &_executionTasks=true to URL when runAsTask=true with multiple input tables', async () => { + const { executor, postSpy } = makeExecutor() + + await executor.execute( + 'services/common/sendArr', + { table1: [{ col1: 'v' }], table2: [{ col2: 'w' }] }, + { ...baseConfig, runAsTask: true } + ) + + const [apiUrl, body, , contentType] = postSpy.mock.calls[0] + expect(apiUrl).toContain('&_executionTasks=true') + expect(body).toBeInstanceOf(NodeFormData) + expect(contentType).toMatch(/^multipart\/form-data/) + const dump = (body as NodeFormData).getBuffer().toString() + expect(dump).toContain('name="sasjs_tables"') + expect(dump).toMatch(/table1\s+table2/) + }) + + it('uploads as file when runAsTask=true and payload has semicolons', async () => { const { executor, postSpy } = makeExecutor() await executor.execute( 'services/common/sendArr', { table1: [{ col1: 'has; semicolon' }] }, - baseConfig + { ...baseConfig, runAsTask: true } ) const [apiUrl, body, , contentType] = postSpy.mock.calls[0] - expect(apiUrl).toContain('_program=') - expect(apiUrl).not.toContain('_executionTasks=') + expect(apiUrl).toContain('&_executionTasks=true') expect(body).toBeInstanceOf(NodeFormData) - expect(body).not.toBeInstanceOf(URLSearchParams) expect(contentType).toMatch(/^multipart\/form-data/) - expect(contentType).not.toBe('application/x-www-form-urlencoded') - }) - - it('uploads as file when _executionTasks=true and payload has semicolons', async () => { - const { executor, postSpy } = makeExecutor() - - await executor.execute( - 'services/common/sendArr&_executionTasks=true', - { table1: [{ col1: 'has; semicolon' }] }, - baseConfig - ) - - const [apiUrl, body, , contentType] = postSpy.mock.calls[0] - expect(apiUrl).toContain('_program=') - expect(apiUrl).toContain('_executionTasks=true') - expect(body).toBeInstanceOf(NodeFormData) - expect(body).not.toBeInstanceOf(URLSearchParams) - expect(contentType).toMatch(/^multipart\/form-data/) - expect(contentType).not.toBe('application/x-www-form-urlencoded') const dump = (body as NodeFormData).getBuffer().toString() expect(dump).toContain('filename="table1.csv"') expect(dump).toContain('Content-Type: application/csv') }) + + it('does NOT append _executionTasks=true to URL when runAsTask=false', async () => { + const { executor, postSpy } = makeExecutor() + + await executor.execute( + 'services/common/sendArr', + { table1: [{ col1: 'v' }] }, + { ...baseConfig, runAsTask: false } + ) + + const [apiUrl] = postSpy.mock.calls[0] + expect(apiUrl).not.toContain('_executionTasks=true') + }) })