mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-03 18:50:05 +00:00
232 lines
6.8 KiB
TypeScript
232 lines
6.8 KiB
TypeScript
import NodeFormData from 'form-data'
|
|
import {
|
|
SASjsApiClient,
|
|
SASjsAuthResponse,
|
|
ScriptExecutionResult
|
|
} from '../SASjsApiClient'
|
|
import { AuthConfig, ServicePackSASjs } from '@sasjs/utils/types'
|
|
import { ExecutionQuery } from '../types'
|
|
|
|
// Create a mock request client with a post method.
|
|
const mockPost = jest.fn()
|
|
const mockRequestClient = {
|
|
post: mockPost
|
|
}
|
|
|
|
// Instead of referencing external variables, inline the dummy values in the mock factories.
|
|
jest.mock('../auth/getTokens', () => ({
|
|
getTokens: jest.fn().mockResolvedValue({ access_token: 'dummyAccessToken' })
|
|
}))
|
|
|
|
jest.mock('../auth/getAccessTokenForSasjs', () => ({
|
|
getAccessTokenForSasjs: jest.fn().mockResolvedValue({
|
|
access_token: 'newAccessToken',
|
|
refresh_token: 'newRefreshToken'
|
|
} as any)
|
|
}))
|
|
|
|
jest.mock('../auth/refreshTokensForSasjs', () => ({
|
|
refreshTokensForSasjs: jest.fn().mockResolvedValue({
|
|
access_token: 'newAccessToken',
|
|
refresh_token: 'newRefreshToken'
|
|
} as any)
|
|
}))
|
|
|
|
// For deployZipFile, mock the file reading function.
|
|
jest.mock('@sasjs/utils/file', () => ({
|
|
createReadStream: jest.fn().mockResolvedValue('readStreamDummy')
|
|
}))
|
|
|
|
// Dummy result to compare against.
|
|
const dummyResult = {
|
|
status: 'OK',
|
|
message: 'Success',
|
|
streamServiceName: 'service',
|
|
example: {}
|
|
}
|
|
|
|
describe('SASjsApiClient', () => {
|
|
let client: SASjsApiClient
|
|
|
|
beforeEach(() => {
|
|
client = new SASjsApiClient(mockRequestClient as any)
|
|
mockPost.mockReset()
|
|
})
|
|
|
|
describe('deploy', () => {
|
|
it('should deploy service pack using JSON', async () => {
|
|
// Arrange: Simulate a successful response.
|
|
mockPost.mockResolvedValue({ result: dummyResult })
|
|
|
|
const dataJson: ServicePackSASjs = {
|
|
appLoc: '',
|
|
someOtherProp: 'value'
|
|
} as any
|
|
const appLoc = '/base/appLoc'
|
|
const authConfig: AuthConfig = {
|
|
client: 'clientId',
|
|
secret: 'secret',
|
|
access_token: 'token',
|
|
refresh_token: 'refresh'
|
|
}
|
|
|
|
// Act
|
|
const result = await client.deploy(dataJson, appLoc, authConfig)
|
|
|
|
// Assert: Ensure that the JSON gets the appLoc set if not defined.
|
|
expect(dataJson.appLoc).toBe(appLoc)
|
|
expect(mockPost).toHaveBeenCalledWith(
|
|
'SASjsApi/drive/deploy',
|
|
dataJson,
|
|
'dummyAccessToken',
|
|
undefined,
|
|
{},
|
|
{ maxContentLength: Infinity, maxBodyLength: Infinity }
|
|
)
|
|
expect(result).toEqual(dummyResult)
|
|
})
|
|
})
|
|
|
|
describe('deployZipFile', () => {
|
|
it('should deploy zip file and return the result', async () => {
|
|
// Arrange: Simulate a successful response.
|
|
mockPost.mockResolvedValue({ result: dummyResult })
|
|
const zipFilePath = 'path/to/deploy.zip'
|
|
const authConfig: AuthConfig = {
|
|
client: 'clientId',
|
|
secret: 'secret',
|
|
access_token: 'token',
|
|
refresh_token: 'refresh'
|
|
}
|
|
|
|
// Act
|
|
const result = await client.deployZipFile(zipFilePath, authConfig)
|
|
|
|
// Assert: Verify that POST is called with multipart form-data.
|
|
expect(mockPost).toHaveBeenCalled()
|
|
const callArgs = mockPost.mock.calls[0]
|
|
expect(callArgs[0]).toBe('SASjsApi/drive/deploy/upload')
|
|
expect(result).toEqual(dummyResult)
|
|
})
|
|
})
|
|
|
|
describe('executeJob', () => {
|
|
it('should execute a job with absolute program path', async () => {
|
|
// Arrange
|
|
const query: ExecutionQuery = { _program: '/absolute/path' } as any
|
|
const appLoc = '/base/appLoc'
|
|
const authConfig: AuthConfig = { access_token: 'anyToken' } as any
|
|
mockPost.mockResolvedValue({
|
|
result: { jobId: 123 },
|
|
log: 'execution log'
|
|
})
|
|
|
|
// Act
|
|
const { result, log } = await client.executeJob(query, appLoc, authConfig)
|
|
|
|
// Assert: The program path should not be prefixed.
|
|
expect(mockPost).toHaveBeenCalledWith(
|
|
'SASjsApi/stp/execute',
|
|
{ _debug: 131, ...query, _program: '/absolute/path' },
|
|
'anyToken'
|
|
)
|
|
expect(result).toEqual({ jobId: 123 })
|
|
expect(log).toBe('execution log')
|
|
})
|
|
|
|
it('should execute a job with relative program path', async () => {
|
|
// Arrange
|
|
const query: ExecutionQuery = { _program: 'relative/path' } as any
|
|
const appLoc = '/base/appLoc'
|
|
mockPost.mockResolvedValue({ result: { jobId: 456 }, log: 'another log' })
|
|
|
|
// Act
|
|
const { result, log } = await client.executeJob(query, appLoc)
|
|
|
|
// Assert: The program path should be prefixed with appLoc.
|
|
expect(mockPost).toHaveBeenCalledWith(
|
|
'SASjsApi/stp/execute',
|
|
{ _debug: 131, ...query, _program: '/base/appLoc/relative/path' },
|
|
undefined
|
|
)
|
|
expect(result).toEqual({ jobId: 456 })
|
|
expect(log).toBe('another log')
|
|
})
|
|
})
|
|
|
|
describe('executeScript', () => {
|
|
it('should execute a script and return the execution result', async () => {
|
|
// Arrange
|
|
const code = 'data _null_; run;'
|
|
const runTime = 'sas'
|
|
const authConfig: AuthConfig = {
|
|
client: 'clientId',
|
|
secret: 'secret',
|
|
access_token: 'token',
|
|
refresh_token: 'refresh'
|
|
}
|
|
const responsePayload = {
|
|
log: 'log output',
|
|
printOutput: 'print output',
|
|
result: 'web output'
|
|
}
|
|
mockPost.mockResolvedValue(responsePayload)
|
|
|
|
// Act
|
|
const result: ScriptExecutionResult = await client.executeScript(
|
|
code,
|
|
runTime,
|
|
authConfig
|
|
)
|
|
|
|
// Assert
|
|
expect(mockPost).toHaveBeenCalledWith(
|
|
'SASjsApi/code/execute',
|
|
{ code, runTime },
|
|
'dummyAccessToken'
|
|
)
|
|
expect(result.log).toBe('log output')
|
|
expect(result.printOutput).toBe('print output')
|
|
expect(result.webout).toBe('web output')
|
|
})
|
|
|
|
it('should throw an error with a prefixed message when POST fails', async () => {
|
|
// Arrange
|
|
const code = 'data _null_; run;'
|
|
const errorMessage = 'Network Error'
|
|
mockPost.mockRejectedValue(new Error(errorMessage))
|
|
|
|
// Act & Assert
|
|
await expect(client.executeScript(code)).rejects.toThrow(
|
|
/Error while sending POST request to execute code/
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('getAccessToken', () => {
|
|
it('should exchange auth code for access token', async () => {
|
|
// Act
|
|
const result = await client.getAccessToken('clientId', 'authCode123')
|
|
|
|
// Assert: The result should match the dummy auth response.
|
|
expect(result).toEqual({
|
|
access_token: 'newAccessToken',
|
|
refresh_token: 'newRefreshToken'
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('refreshTokens', () => {
|
|
it('should exchange refresh token for new tokens', async () => {
|
|
// Act
|
|
const result = await client.refreshTokens('refreshToken123')
|
|
|
|
// Assert: The result should match the dummy auth response.
|
|
expect(result).toEqual({
|
|
access_token: 'newAccessToken',
|
|
refresh_token: 'newRefreshToken'
|
|
})
|
|
})
|
|
})
|
|
})
|