mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-03 18:50:05 +00:00
676 lines
17 KiB
TypeScript
676 lines
17 KiB
TypeScript
import { RequestClient } from '../../../request/RequestClient'
|
|
import { SessionManager } from '../../../SessionManager'
|
|
import { executeScript } from '../executeScript'
|
|
import { mockSession, mockAuthConfig, mockJob } from './mockResponses'
|
|
import * as pollJobStateModule from '../pollJobState'
|
|
import * as uploadTablesModule from '../uploadTables'
|
|
import * as getTokensModule from '../../../auth/getTokens'
|
|
import * as formatDataModule from '../../../utils/formatDataForRequest'
|
|
import * as fetchLogsModule from '../../../utils/fetchLogByChunks'
|
|
import { PollOptions } from '../../../types'
|
|
import { ComputeJobExecutionError, NotFoundError } from '../../../types/errors'
|
|
import { Logger, LogLevel } from '@sasjs/utils'
|
|
|
|
const sessionManager = new (<jest.Mock<SessionManager>>SessionManager)()
|
|
const requestClient = new (<jest.Mock<RequestClient>>RequestClient)()
|
|
const defaultPollOptions: PollOptions = {
|
|
maxPollCount: 100,
|
|
pollInterval: 500,
|
|
streamLog: false
|
|
}
|
|
|
|
describe('executeScript', () => {
|
|
beforeEach(() => {
|
|
;(process as any).logger = new Logger(LogLevel.Off)
|
|
setupMocks()
|
|
})
|
|
|
|
it('should not try to get fresh tokens if an authConfig is not provided', async () => {
|
|
await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put hello'],
|
|
'test context'
|
|
)
|
|
|
|
expect(getTokensModule.getTokens).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should try to get fresh tokens if an authConfig is provided', async () => {
|
|
await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put hello'],
|
|
'test context',
|
|
mockAuthConfig
|
|
)
|
|
|
|
expect(getTokensModule.getTokens).toHaveBeenCalledWith(
|
|
requestClient,
|
|
mockAuthConfig
|
|
)
|
|
})
|
|
|
|
it('should get a session from the session manager before executing', async () => {
|
|
await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put hello'],
|
|
'test context'
|
|
)
|
|
|
|
expect(sessionManager.getSession).toHaveBeenCalledWith(undefined)
|
|
})
|
|
|
|
it('should handle errors while getting a session', async () => {
|
|
jest
|
|
.spyOn(sessionManager, 'getSession')
|
|
.mockImplementation(() => Promise.reject('Test Error'))
|
|
|
|
const error = await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put hello'],
|
|
'test context'
|
|
).catch((e: any) => e)
|
|
|
|
expect(error).toContain('Error while getting session.')
|
|
})
|
|
|
|
it('should fetch the PID when printPid is true', async () => {
|
|
await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put hello'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
null,
|
|
false,
|
|
false,
|
|
false,
|
|
defaultPollOptions,
|
|
true
|
|
)
|
|
|
|
expect(sessionManager.getVariable).toHaveBeenCalledWith(
|
|
mockSession.id,
|
|
'SYSJOBID',
|
|
mockAuthConfig.access_token
|
|
)
|
|
})
|
|
|
|
it('should handle errors while getting the job PID', async () => {
|
|
jest
|
|
.spyOn(sessionManager, 'getVariable')
|
|
.mockImplementation(() => Promise.reject('Test Error'))
|
|
|
|
const error = await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put hello'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
null,
|
|
false,
|
|
false,
|
|
false,
|
|
defaultPollOptions,
|
|
true
|
|
).catch((e: any) => e)
|
|
|
|
expect(error).toContain('Error while getting session variable.')
|
|
})
|
|
|
|
it('should use the file upload approach when data contains semicolons', async () => {
|
|
jest
|
|
.spyOn(uploadTablesModule, 'uploadTables')
|
|
.mockImplementation(() =>
|
|
Promise.resolve([{ tableName: 'test', file: { id: 1 } }])
|
|
)
|
|
|
|
await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put hello'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar;' },
|
|
false,
|
|
false,
|
|
false,
|
|
defaultPollOptions,
|
|
true
|
|
)
|
|
|
|
expect(uploadTablesModule.uploadTables).toHaveBeenCalledWith(
|
|
requestClient,
|
|
{ foo: 'bar;' },
|
|
mockAuthConfig.access_token
|
|
)
|
|
})
|
|
|
|
it('should format data as CSV when it does not contain semicolons', async () => {
|
|
await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put hello'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar' },
|
|
false,
|
|
false,
|
|
false,
|
|
defaultPollOptions,
|
|
true
|
|
)
|
|
|
|
expect(formatDataModule.formatDataForRequest).toHaveBeenCalledWith({
|
|
foo: 'bar'
|
|
})
|
|
})
|
|
|
|
it('should submit a job for execution via the compute API', async () => {
|
|
jest
|
|
.spyOn(formatDataModule, 'formatDataForRequest')
|
|
.mockImplementation(() => ({ sasjs_tables: 'foo', sasjs0data: 'bar' }))
|
|
|
|
await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put "hello";'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar' },
|
|
false,
|
|
false,
|
|
false,
|
|
defaultPollOptions,
|
|
true
|
|
)
|
|
|
|
expect(requestClient.post).toHaveBeenCalledWith(
|
|
`/compute/sessions/${mockSession.id}/jobs`,
|
|
{
|
|
name: 'exec-test',
|
|
description: 'Powered by SASjs',
|
|
code: ['%put "hello";'],
|
|
variables: {
|
|
SYS_JES_JOB_URI: '',
|
|
_program: 'test/test',
|
|
sasjs_tables: 'foo',
|
|
sasjs0data: 'bar'
|
|
},
|
|
arguments: {
|
|
_contextName: 'test context',
|
|
_OMITJSONLISTING: true,
|
|
_OMITJSONLOG: true,
|
|
_OMITSESSIONRESULTS: true,
|
|
_OMITTEXTLISTING: true,
|
|
_OMITTEXTLOG: true
|
|
}
|
|
},
|
|
mockAuthConfig.access_token
|
|
)
|
|
})
|
|
|
|
it('should set the correct variables when debug is true', async () => {
|
|
jest
|
|
.spyOn(formatDataModule, 'formatDataForRequest')
|
|
.mockImplementation(() => ({ sasjs_tables: 'foo', sasjs0data: 'bar' }))
|
|
|
|
await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put "hello";'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar' },
|
|
true,
|
|
false,
|
|
false,
|
|
defaultPollOptions,
|
|
true
|
|
)
|
|
|
|
expect(requestClient.post).toHaveBeenCalledWith(
|
|
`/compute/sessions/${mockSession.id}/jobs`,
|
|
{
|
|
name: 'exec-test',
|
|
description: 'Powered by SASjs',
|
|
code: ['%put "hello";'],
|
|
variables: {
|
|
SYS_JES_JOB_URI: '',
|
|
_program: 'test/test',
|
|
sasjs_tables: 'foo',
|
|
sasjs0data: 'bar',
|
|
_DEBUG: 131
|
|
},
|
|
arguments: {
|
|
_contextName: 'test context',
|
|
_OMITJSONLISTING: true,
|
|
_OMITJSONLOG: true,
|
|
_OMITSESSIONRESULTS: false,
|
|
_OMITTEXTLISTING: true,
|
|
_OMITTEXTLOG: false
|
|
}
|
|
},
|
|
mockAuthConfig.access_token
|
|
)
|
|
})
|
|
|
|
it('should handle errors during job submission', async () => {
|
|
jest
|
|
.spyOn(requestClient, 'post')
|
|
.mockImplementation(() => Promise.reject('Test Error'))
|
|
|
|
const error = await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put "hello";'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar' },
|
|
true,
|
|
false,
|
|
false,
|
|
defaultPollOptions,
|
|
true
|
|
).catch((e: any) => e)
|
|
|
|
expect(error).toContain('Error while posting job')
|
|
})
|
|
|
|
it('should immediately return the session when waitForResult is false', async () => {
|
|
const result = await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put "hello";'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar' },
|
|
true,
|
|
false,
|
|
false,
|
|
defaultPollOptions,
|
|
true
|
|
)
|
|
|
|
expect(result).toEqual(mockSession)
|
|
})
|
|
|
|
it('should poll for job completion when waitForResult is true', async () => {
|
|
await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put "hello";'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar' },
|
|
false,
|
|
false,
|
|
true,
|
|
defaultPollOptions,
|
|
true
|
|
)
|
|
|
|
expect(pollJobStateModule.pollJobState).toHaveBeenCalledWith(
|
|
requestClient,
|
|
mockJob,
|
|
false,
|
|
mockAuthConfig,
|
|
defaultPollOptions
|
|
)
|
|
})
|
|
|
|
it('should handle general errors when polling for job status', async () => {
|
|
jest
|
|
.spyOn(pollJobStateModule, 'pollJobState')
|
|
.mockImplementation(() => Promise.reject('Poll Error'))
|
|
|
|
const error = await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put "hello";'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar' },
|
|
false,
|
|
false,
|
|
true,
|
|
defaultPollOptions,
|
|
true
|
|
).catch((e: any) => e)
|
|
|
|
expect(error).toContain('Error while polling job status.')
|
|
})
|
|
|
|
it('should fetch the log and append it to the error in case of a 5113 error code', async () => {
|
|
jest
|
|
.spyOn(pollJobStateModule, 'pollJobState')
|
|
.mockImplementation(() =>
|
|
Promise.reject({ response: { data: 'err=5113,' } })
|
|
)
|
|
|
|
const error = await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put "hello";'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar' },
|
|
false,
|
|
false,
|
|
true,
|
|
defaultPollOptions,
|
|
true
|
|
).catch((e: any) => e)
|
|
|
|
expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith(
|
|
requestClient,
|
|
mockAuthConfig.access_token,
|
|
mockJob.links.find((l) => l.rel === 'up')!.href + '/log',
|
|
1000000
|
|
)
|
|
expect(error.log).toEqual('Test Log')
|
|
})
|
|
|
|
it('should fetch the logs for the job if debug is true and a log URL is available', async () => {
|
|
await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put "hello";'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar' },
|
|
true,
|
|
false,
|
|
true,
|
|
defaultPollOptions,
|
|
true
|
|
)
|
|
|
|
expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith(
|
|
requestClient,
|
|
mockAuthConfig.access_token,
|
|
mockJob.links.find((l) => l.rel === 'log')!.href + '/content',
|
|
mockJob.logStatistics.lineCount
|
|
)
|
|
})
|
|
|
|
it('should not fetch the logs for the job if debug is false', async () => {
|
|
await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put "hello";'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar' },
|
|
false,
|
|
false,
|
|
true,
|
|
defaultPollOptions,
|
|
true
|
|
)
|
|
|
|
expect(fetchLogsModule.fetchLogByChunks).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('should throw a ComputeJobExecutionError if the job has failed', async () => {
|
|
jest
|
|
.spyOn(pollJobStateModule, 'pollJobState')
|
|
.mockImplementation(() => Promise.resolve('failed'))
|
|
|
|
const error: ComputeJobExecutionError = await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put "hello";'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar' },
|
|
true,
|
|
false,
|
|
true,
|
|
defaultPollOptions,
|
|
true
|
|
).catch((e: any) => e)
|
|
|
|
expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith(
|
|
requestClient,
|
|
mockAuthConfig.access_token,
|
|
mockJob.links.find((l) => l.rel === 'log')!.href + '/content',
|
|
mockJob.logStatistics.lineCount
|
|
)
|
|
|
|
expect(error).toBeInstanceOf(ComputeJobExecutionError)
|
|
expect(error.log).toEqual('Test Log')
|
|
expect(error.job).toEqual(mockJob)
|
|
})
|
|
|
|
it('should throw a ComputeJobExecutionError if the job has errored out', async () => {
|
|
jest
|
|
.spyOn(pollJobStateModule, 'pollJobState')
|
|
.mockImplementation(() => Promise.resolve('error'))
|
|
|
|
const error: ComputeJobExecutionError = await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put "hello";'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar' },
|
|
true,
|
|
false,
|
|
true,
|
|
defaultPollOptions,
|
|
true
|
|
).catch((e: any) => e)
|
|
|
|
expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith(
|
|
requestClient,
|
|
mockAuthConfig.access_token,
|
|
mockJob.links.find((l) => l.rel === 'log')!.href + '/content',
|
|
mockJob.logStatistics.lineCount
|
|
)
|
|
|
|
expect(error).toBeInstanceOf(ComputeJobExecutionError)
|
|
expect(error.log).toEqual('Test Log')
|
|
expect(error.job).toEqual(mockJob)
|
|
})
|
|
|
|
it('should fetch the result if expectWebout is true', async () => {
|
|
await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put "hello";'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar' },
|
|
false,
|
|
true,
|
|
true,
|
|
defaultPollOptions,
|
|
true
|
|
)
|
|
|
|
expect(requestClient.get).toHaveBeenCalledWith(
|
|
`/compute/sessions/${mockSession.id}/filerefs/_webout/content`,
|
|
mockAuthConfig.access_token,
|
|
'text/plain'
|
|
)
|
|
})
|
|
|
|
it('should fetch the logs if the webout file was not found', async () => {
|
|
jest.spyOn(requestClient, 'get').mockImplementation((url, ...rest) => {
|
|
if (url.includes('_webout')) {
|
|
return Promise.reject(new NotFoundError(url))
|
|
}
|
|
return Promise.resolve({ result: mockJob, etag: '', status: 200 })
|
|
})
|
|
|
|
const error = await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put "hello";'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar' },
|
|
false,
|
|
true,
|
|
true,
|
|
defaultPollOptions,
|
|
true
|
|
).catch((e: any) => e)
|
|
|
|
expect(requestClient.get).toHaveBeenCalledWith(
|
|
`/compute/sessions/${mockSession.id}/filerefs/_webout/content`,
|
|
mockAuthConfig.access_token,
|
|
'text/plain'
|
|
)
|
|
|
|
expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith(
|
|
requestClient,
|
|
mockAuthConfig.access_token,
|
|
mockJob.links.find((l) => l.rel === 'log')!.href + '/content',
|
|
mockJob.logStatistics.lineCount
|
|
)
|
|
|
|
expect(error.status).toEqual(500)
|
|
expect(error.log).toEqual('Test Log')
|
|
})
|
|
|
|
it('should clear the session after execution is complete', async () => {
|
|
await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put "hello";'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar' },
|
|
false,
|
|
true,
|
|
true,
|
|
defaultPollOptions,
|
|
true
|
|
)
|
|
|
|
expect(sessionManager.clearSession).toHaveBeenCalledWith(
|
|
mockSession.id,
|
|
mockAuthConfig.access_token
|
|
)
|
|
})
|
|
|
|
it('should handle errors while clearing a session', async () => {
|
|
jest
|
|
.spyOn(sessionManager, 'clearSession')
|
|
.mockImplementation(() => Promise.reject('Clear Session Error'))
|
|
|
|
const error = await executeScript(
|
|
requestClient,
|
|
sessionManager,
|
|
'test',
|
|
'test',
|
|
['%put "hello";'],
|
|
'test context',
|
|
mockAuthConfig,
|
|
{ foo: 'bar' },
|
|
false,
|
|
true,
|
|
true,
|
|
defaultPollOptions,
|
|
true
|
|
).catch((e: any) => e)
|
|
|
|
expect(error).toContain('Error while clearing session.')
|
|
})
|
|
})
|
|
|
|
const setupMocks = () => {
|
|
jest.restoreAllMocks()
|
|
jest.mock('../../../request/RequestClient')
|
|
jest.mock('../../../SessionManager')
|
|
jest.mock('../../../auth/getTokens')
|
|
jest.mock('../pollJobState')
|
|
jest.mock('../uploadTables')
|
|
jest.mock('../../../utils/formatDataForRequest')
|
|
jest.mock('../../../utils/fetchLogByChunks')
|
|
|
|
jest
|
|
.spyOn(requestClient, 'post')
|
|
.mockImplementation(() => Promise.resolve({ result: mockJob, etag: '' }))
|
|
jest
|
|
.spyOn(requestClient, 'get')
|
|
.mockImplementation(() =>
|
|
Promise.resolve({ result: mockJob, etag: '', status: 200 })
|
|
)
|
|
jest
|
|
.spyOn(requestClient, 'delete')
|
|
.mockImplementation(() => Promise.resolve({ result: {}, etag: '' }))
|
|
jest
|
|
.spyOn(getTokensModule, 'getTokens')
|
|
.mockImplementation(() => Promise.resolve(mockAuthConfig))
|
|
jest
|
|
.spyOn(pollJobStateModule, 'pollJobState')
|
|
.mockImplementation(() => Promise.resolve('completed'))
|
|
jest
|
|
.spyOn(sessionManager, 'getVariable')
|
|
.mockImplementation(() =>
|
|
Promise.resolve({ result: { value: 'test' }, etag: 'test', status: 200 })
|
|
)
|
|
jest
|
|
.spyOn(sessionManager, 'getSession')
|
|
.mockImplementation(() => Promise.resolve(mockSession))
|
|
jest
|
|
.spyOn(sessionManager, 'clearSession')
|
|
.mockImplementation(() => Promise.resolve())
|
|
jest
|
|
.spyOn(formatDataModule, 'formatDataForRequest')
|
|
.mockImplementation(() => ({ sasjs_tables: 'test', sasjs0data: 'test' }))
|
|
jest
|
|
.spyOn(fetchLogsModule, 'fetchLogByChunks')
|
|
.mockImplementation(() => Promise.resolve('Test Log'))
|
|
}
|