mirror of
https://github.com/sasjs/adapter.git
synced 2026-04-21 21:21:31 +00:00
chore(refactor): split up and add tests for core functionality
This commit is contained in:
@@ -4,7 +4,7 @@ import { executeScript } from '../executeScript'
|
||||
import { mockSession, mockAuthConfig, mockJob } from './mockResponses'
|
||||
import * as pollJobStateModule from '../pollJobState'
|
||||
import * as uploadTablesModule from '../uploadTables'
|
||||
import * as tokensModule from '../../../auth/tokens'
|
||||
import * as getTokensModule from '../../../auth/getTokens'
|
||||
import * as formatDataModule from '../../../utils/formatDataForRequest'
|
||||
import * as fetchLogsModule from '../../../utils/fetchLogByChunks'
|
||||
import { PollOptions } from '../../../types'
|
||||
@@ -35,7 +35,7 @@ describe('executeScript', () => {
|
||||
'test context'
|
||||
)
|
||||
|
||||
expect(tokensModule.getTokens).not.toHaveBeenCalled()
|
||||
expect(getTokensModule.getTokens).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should try to get fresh tokens if an authConfig is provided', async () => {
|
||||
@@ -49,7 +49,7 @@ describe('executeScript', () => {
|
||||
mockAuthConfig
|
||||
)
|
||||
|
||||
expect(tokensModule.getTokens).toHaveBeenCalledWith(
|
||||
expect(getTokensModule.getTokens).toHaveBeenCalledWith(
|
||||
requestClient,
|
||||
mockAuthConfig
|
||||
)
|
||||
@@ -82,7 +82,7 @@ describe('executeScript', () => {
|
||||
'test context'
|
||||
).catch((e) => e)
|
||||
|
||||
expect(error.includes('Error while getting session.')).toBeTruthy()
|
||||
expect(error).toContain('Error while getting session.')
|
||||
})
|
||||
|
||||
it('should fetch the PID when printPid is true', async () => {
|
||||
@@ -130,7 +130,7 @@ describe('executeScript', () => {
|
||||
true
|
||||
).catch((e) => e)
|
||||
|
||||
expect(error.includes('Error while getting session variable.')).toBeTruthy()
|
||||
expect(error).toContain('Error while getting session variable.')
|
||||
})
|
||||
|
||||
it('should use the file upload approach when data contains semicolons', async () => {
|
||||
@@ -300,7 +300,7 @@ describe('executeScript', () => {
|
||||
).catch((e) => e)
|
||||
|
||||
console.log(error)
|
||||
expect(error.includes('Error while posting job')).toBeTruthy()
|
||||
expect(error).toContain('Error while posting job')
|
||||
})
|
||||
|
||||
it('should immediately return the session when waitForResult is false', async () => {
|
||||
@@ -371,7 +371,7 @@ describe('executeScript', () => {
|
||||
true
|
||||
).catch((e) => e)
|
||||
|
||||
expect(error.includes('Error while polling job status.')).toBeTruthy()
|
||||
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 () => {
|
||||
@@ -626,7 +626,7 @@ describe('executeScript', () => {
|
||||
true
|
||||
).catch((e) => e)
|
||||
|
||||
expect(error.includes('Error while clearing session.')).toBeTruthy()
|
||||
expect(error).toContain('Error while clearing session.')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -634,7 +634,7 @@ const setupMocks = () => {
|
||||
jest.restoreAllMocks()
|
||||
jest.mock('../../../request/RequestClient')
|
||||
jest.mock('../../../SessionManager')
|
||||
jest.mock('../../../auth/tokens')
|
||||
jest.mock('../../../auth/getTokens')
|
||||
jest.mock('../pollJobState')
|
||||
jest.mock('../uploadTables')
|
||||
jest.mock('../../../utils/formatDataForRequest')
|
||||
@@ -650,7 +650,7 @@ const setupMocks = () => {
|
||||
.spyOn(requestClient, 'delete')
|
||||
.mockImplementation(() => Promise.resolve({ result: {}, etag: '' }))
|
||||
jest
|
||||
.spyOn(tokensModule, 'getTokens')
|
||||
.spyOn(getTokensModule, 'getTokens')
|
||||
.mockImplementation(() => Promise.resolve(mockAuthConfig))
|
||||
jest
|
||||
.spyOn(pollJobStateModule, 'pollJobState')
|
||||
|
||||
@@ -0,0 +1,266 @@
|
||||
import { RequestClient } from '../../../request/RequestClient'
|
||||
import { mockAuthConfig, mockJob } from './mockResponses'
|
||||
import { pollJobState } from '../pollJobState'
|
||||
import * as getTokensModule from '../../../auth/getTokens'
|
||||
import * as saveLogModule from '../saveLog'
|
||||
import { PollOptions } from '../../../types'
|
||||
import { Logger, LogLevel } from '@sasjs/utils'
|
||||
|
||||
const requestClient = new (<jest.Mock<RequestClient>>RequestClient)()
|
||||
const defaultPollOptions: PollOptions = {
|
||||
maxPollCount: 100,
|
||||
pollInterval: 500,
|
||||
streamLog: false
|
||||
}
|
||||
|
||||
describe('pollJobState', () => {
|
||||
beforeEach(() => {
|
||||
;(process as any).logger = new Logger(LogLevel.Off)
|
||||
setupMocks()
|
||||
})
|
||||
|
||||
it('should get valid tokens if the authConfig has been provided', async () => {
|
||||
await pollJobState(
|
||||
requestClient,
|
||||
mockJob,
|
||||
false,
|
||||
'test',
|
||||
mockAuthConfig,
|
||||
defaultPollOptions
|
||||
)
|
||||
|
||||
expect(getTokensModule.getTokens).toHaveBeenCalledWith(
|
||||
requestClient,
|
||||
mockAuthConfig
|
||||
)
|
||||
})
|
||||
|
||||
it('should not attempt to get tokens if the authConfig has not been provided', async () => {
|
||||
await pollJobState(
|
||||
requestClient,
|
||||
mockJob,
|
||||
false,
|
||||
'test',
|
||||
undefined,
|
||||
defaultPollOptions
|
||||
)
|
||||
|
||||
expect(getTokensModule.getTokens).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should throw an error if the job does not have a state link', async () => {
|
||||
const error = await pollJobState(
|
||||
requestClient,
|
||||
{ ...mockJob, links: mockJob.links.filter((l) => l.rel !== 'state') },
|
||||
false,
|
||||
'test',
|
||||
undefined,
|
||||
defaultPollOptions
|
||||
).catch((e) => e)
|
||||
|
||||
expect((error as Error).message).toContain('Job state link was not found.')
|
||||
})
|
||||
|
||||
it('should attempt to refresh tokens before each poll', async () => {
|
||||
jest
|
||||
.spyOn(requestClient, 'get')
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({ result: 'pending', etag: '' })
|
||||
)
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({ result: 'running', etag: '' })
|
||||
)
|
||||
.mockImplementation(() =>
|
||||
Promise.resolve({ result: 'completed', etag: '' })
|
||||
)
|
||||
|
||||
await pollJobState(
|
||||
requestClient,
|
||||
mockJob,
|
||||
false,
|
||||
'test',
|
||||
mockAuthConfig,
|
||||
defaultPollOptions
|
||||
)
|
||||
|
||||
expect(getTokensModule.getTokens).toHaveBeenCalledTimes(3)
|
||||
})
|
||||
|
||||
it('should attempt to fetch and save the log after each poll', async () => {
|
||||
jest
|
||||
.spyOn(requestClient, 'get')
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({ result: 'pending', etag: '' })
|
||||
)
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({ result: 'running', etag: '' })
|
||||
)
|
||||
.mockImplementation(() =>
|
||||
Promise.resolve({ result: 'completed', etag: '' })
|
||||
)
|
||||
|
||||
await pollJobState(
|
||||
requestClient,
|
||||
mockJob,
|
||||
false,
|
||||
'test',
|
||||
mockAuthConfig,
|
||||
defaultPollOptions
|
||||
)
|
||||
|
||||
expect(saveLogModule.saveLog).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
it('should return the current status when the max poll count is reached', async () => {
|
||||
jest
|
||||
.spyOn(requestClient, 'get')
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({ result: 'pending', etag: '' })
|
||||
)
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({ result: 'running', etag: '' })
|
||||
)
|
||||
|
||||
const state = await pollJobState(
|
||||
requestClient,
|
||||
mockJob,
|
||||
false,
|
||||
'test',
|
||||
mockAuthConfig,
|
||||
{
|
||||
...defaultPollOptions,
|
||||
maxPollCount: 1
|
||||
}
|
||||
)
|
||||
|
||||
expect(state).toEqual('running')
|
||||
})
|
||||
|
||||
it('should continue polling until the job completes or errors', async () => {
|
||||
jest
|
||||
.spyOn(requestClient, 'get')
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({ result: 'pending', etag: '' })
|
||||
)
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({ result: 'running', etag: '' })
|
||||
)
|
||||
.mockImplementation(() =>
|
||||
Promise.resolve({ result: 'completed', etag: '' })
|
||||
)
|
||||
|
||||
const state = await pollJobState(
|
||||
requestClient,
|
||||
mockJob,
|
||||
false,
|
||||
'test',
|
||||
undefined,
|
||||
defaultPollOptions
|
||||
)
|
||||
|
||||
expect(requestClient.get).toHaveBeenCalledTimes(4)
|
||||
expect(state).toEqual('completed')
|
||||
})
|
||||
|
||||
it('should print the state to the console when debug is on', async () => {
|
||||
jest.spyOn((process as any).logger, 'info')
|
||||
jest
|
||||
.spyOn(requestClient, 'get')
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({ result: 'pending', etag: '' })
|
||||
)
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({ result: 'running', etag: '' })
|
||||
)
|
||||
.mockImplementation(() =>
|
||||
Promise.resolve({ result: 'completed', etag: '' })
|
||||
)
|
||||
|
||||
await pollJobState(
|
||||
requestClient,
|
||||
mockJob,
|
||||
true,
|
||||
'test',
|
||||
undefined,
|
||||
defaultPollOptions
|
||||
)
|
||||
|
||||
expect((process as any).logger.info).toHaveBeenCalledTimes(4)
|
||||
expect((process as any).logger.info).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
'Polling job status...'
|
||||
)
|
||||
expect((process as any).logger.info).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
'Current job state: running'
|
||||
)
|
||||
expect((process as any).logger.info).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
'Polling job status...'
|
||||
)
|
||||
expect((process as any).logger.info).toHaveBeenNthCalledWith(
|
||||
4,
|
||||
'Current job state: completed'
|
||||
)
|
||||
})
|
||||
|
||||
it('should continue polling when there is a single error in between', async () => {
|
||||
jest
|
||||
.spyOn(requestClient, 'get')
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({ result: 'pending', etag: '' })
|
||||
)
|
||||
.mockImplementationOnce(() => Promise.reject('Status Error'))
|
||||
.mockImplementationOnce(() =>
|
||||
Promise.resolve({ result: 'completed', etag: '' })
|
||||
)
|
||||
|
||||
const state = await pollJobState(
|
||||
requestClient,
|
||||
mockJob,
|
||||
false,
|
||||
'test',
|
||||
undefined,
|
||||
defaultPollOptions
|
||||
)
|
||||
|
||||
expect(requestClient.get).toHaveBeenCalledTimes(3)
|
||||
expect(state).toEqual('completed')
|
||||
})
|
||||
|
||||
it('should throw an error when the error count exceeds the set value of 5', async () => {
|
||||
jest
|
||||
.spyOn(requestClient, 'get')
|
||||
.mockImplementation(() => Promise.reject('Status Error'))
|
||||
|
||||
const error = await pollJobState(
|
||||
requestClient,
|
||||
mockJob,
|
||||
false,
|
||||
'test',
|
||||
undefined,
|
||||
defaultPollOptions
|
||||
).catch((e) => e)
|
||||
|
||||
expect(error).toContain('Error while getting job state after interval.')
|
||||
})
|
||||
})
|
||||
|
||||
const setupMocks = () => {
|
||||
jest.restoreAllMocks()
|
||||
jest.mock('../../../request/RequestClient')
|
||||
jest.mock('../../../auth/getTokens')
|
||||
jest.mock('../saveLog')
|
||||
|
||||
jest
|
||||
.spyOn(requestClient, 'get')
|
||||
.mockImplementation(() =>
|
||||
Promise.resolve({ result: 'completed', etag: '' })
|
||||
)
|
||||
jest
|
||||
.spyOn(getTokensModule, 'getTokens')
|
||||
.mockImplementation(() => Promise.resolve(mockAuthConfig))
|
||||
jest
|
||||
.spyOn(saveLogModule, 'saveLog')
|
||||
.mockImplementation(() => Promise.resolve())
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import { Logger, LogLevel } from '@sasjs/utils'
|
||||
import * as fileModule from '@sasjs/utils/file'
|
||||
import { RequestClient } from '../../../request/RequestClient'
|
||||
import * as fetchLogsModule from '../../../utils/fetchLogByChunks'
|
||||
import { saveLog } from '../saveLog'
|
||||
import { mockJob } from './mockResponses'
|
||||
|
||||
const requestClient = new (<jest.Mock<RequestClient>>RequestClient)()
|
||||
|
||||
describe('saveLog', () => {
|
||||
beforeEach(() => {
|
||||
;(process as any).logger = new Logger(LogLevel.Off)
|
||||
setupMocks()
|
||||
})
|
||||
|
||||
it('should return immediately if shouldSaveLog is false', async () => {
|
||||
await saveLog(mockJob, requestClient, false, '/test', 't0k3n')
|
||||
|
||||
expect(fetchLogsModule.fetchLogByChunks).not.toHaveBeenCalled()
|
||||
expect(fileModule.createFile).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('should throw an error when a valid access token is not provided', async () => {
|
||||
const error = await saveLog(mockJob, requestClient, true, '/test').catch(
|
||||
(e) => e
|
||||
)
|
||||
|
||||
expect(error.message).toContain(
|
||||
`Logs for job ${mockJob.id} cannot be fetched without a valid access token.`
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw an error when the log URL is not available', async () => {
|
||||
const error = await saveLog(
|
||||
{ ...mockJob, links: mockJob.links.filter((l) => l.rel !== 'log') },
|
||||
requestClient,
|
||||
true,
|
||||
'/test',
|
||||
't0k3n'
|
||||
).catch((e) => e)
|
||||
|
||||
expect(error.message).toContain(
|
||||
`Log URL for job ${mockJob.id} was not found.`
|
||||
)
|
||||
})
|
||||
|
||||
it('should fetch and save logs to the given path', async () => {
|
||||
await saveLog(mockJob, requestClient, true, '/test', 't0k3n')
|
||||
|
||||
expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith(
|
||||
requestClient,
|
||||
't0k3n',
|
||||
'/log/content',
|
||||
100
|
||||
)
|
||||
expect(fileModule.createFile).toHaveBeenCalledWith('/test', 'Test Log')
|
||||
})
|
||||
})
|
||||
|
||||
const setupMocks = () => {
|
||||
jest.restoreAllMocks()
|
||||
jest.mock('../../../request/RequestClient')
|
||||
jest.mock('../../../utils/fetchLogByChunks')
|
||||
jest.mock('@sasjs/utils')
|
||||
|
||||
jest
|
||||
.spyOn(fetchLogsModule, 'fetchLogByChunks')
|
||||
.mockImplementation(() => Promise.resolve('Test Log'))
|
||||
jest
|
||||
.spyOn(fileModule, 'createFile')
|
||||
.mockImplementation(() => Promise.resolve())
|
||||
}
|
||||
Reference in New Issue
Block a user