mirror of
https://github.com/sasjs/adapter.git
synced 2025-12-11 01:14:36 +00:00
490 lines
15 KiB
TypeScript
490 lines
15 KiB
TypeScript
import { SessionManager } from '../SessionManager'
|
|
import { RequestClient } from '../request/RequestClient'
|
|
import * as dotenv from 'dotenv'
|
|
import axios from 'axios'
|
|
import { Logger, LogLevel } from '@sasjs/utils/logger'
|
|
import { Session, SessionState, Context } from '../types'
|
|
|
|
jest.mock('axios')
|
|
const mockedAxios = axios as jest.Mocked<typeof axios>
|
|
const requestClient = new (<jest.Mock<RequestClient>>RequestClient)()
|
|
|
|
describe('SessionManager', () => {
|
|
dotenv.config()
|
|
process.env.SERVER_URL = 'https://server.com'
|
|
|
|
const sessionManager = new SessionManager(
|
|
process.env.SERVER_URL as string,
|
|
process.env.DEFAULT_COMPUTE_CONTEXT as string,
|
|
requestClient
|
|
)
|
|
const sessionStateLink = '/compute/sessions/session-id-ses0000/state'
|
|
const sessionEtag = 'etag-string'
|
|
|
|
const getMockSession = (): Session => ({
|
|
id: ['id', new Date().getTime(), Math.random()].join('-'),
|
|
state: SessionState.NoState,
|
|
links: [
|
|
{
|
|
href: sessionStateLink,
|
|
method: 'GET',
|
|
rel: 'state',
|
|
type: 'text/plain',
|
|
uri: sessionStateLink
|
|
}
|
|
],
|
|
attributes: {
|
|
sessionInactiveTimeout: 900
|
|
},
|
|
creationTimeStamp: `${new Date(new Date().getTime()).toISOString()}`,
|
|
stateUrl: sessionStateLink,
|
|
etag: sessionEtag
|
|
})
|
|
|
|
afterEach(() => {
|
|
jest.restoreAllMocks()
|
|
})
|
|
|
|
describe('getVariable', () => {
|
|
it('should fetch session variable', async () => {
|
|
const sampleResponse = {
|
|
ok: true,
|
|
links: [],
|
|
name: 'SYSJOBID',
|
|
scope: 'GLOBAL',
|
|
value: '25218',
|
|
version: 1
|
|
}
|
|
|
|
mockedAxios.get.mockImplementation(() =>
|
|
Promise.resolve({ data: sampleResponse })
|
|
)
|
|
|
|
const expectedResponse = {
|
|
etag: '',
|
|
result: sampleResponse
|
|
}
|
|
|
|
await expect(
|
|
sessionManager.getVariable(
|
|
'fakeSessionId',
|
|
'SYSJOBID',
|
|
'fakeAccessToken'
|
|
)
|
|
).resolves.toEqual(expectedResponse)
|
|
})
|
|
|
|
it('should throw an error if GET request failed', async () => {
|
|
const responseStatus = 500
|
|
const responseErrorMessage = `The process timed out after 60 seconds. Request failed with status code ${responseStatus}`
|
|
const response = {
|
|
status: responseStatus,
|
|
data: {
|
|
message: responseErrorMessage
|
|
}
|
|
}
|
|
const testVariable = 'testVariable'
|
|
|
|
jest.spyOn(requestClient, 'get').mockImplementation(() =>
|
|
Promise.reject({
|
|
response
|
|
})
|
|
)
|
|
|
|
const expectedError = `Error while fetching session variable '${testVariable}'. GET request to ${process.env.SERVER_URL}/compute/sessions/testId/variables/${testVariable} failed with status code ${responseStatus}. ${responseErrorMessage}`
|
|
|
|
await expect(
|
|
sessionManager.getVariable('testId', testVariable)
|
|
).rejects.toEqual(expectedError)
|
|
})
|
|
})
|
|
|
|
describe('waitForSession', () => {
|
|
const session: Session = {
|
|
id: 'id',
|
|
state: SessionState.NoState,
|
|
links: [{ rel: 'state', href: '', uri: '', type: '', method: 'GET' }],
|
|
attributes: {
|
|
sessionInactiveTimeout: 0
|
|
},
|
|
creationTimeStamp: '',
|
|
stateUrl: sessionStateLink,
|
|
etag: sessionEtag
|
|
}
|
|
|
|
beforeEach(() => {
|
|
;(process as any).logger = new Logger(LogLevel.Off)
|
|
})
|
|
|
|
it('should log http response code and session state if SAS server did not provide session state', async () => {
|
|
let requestAttempt = 0
|
|
const requestAttemptLimit = 10
|
|
const sessionState = 'idle'
|
|
|
|
mockedAxios.get.mockImplementation(() => {
|
|
requestAttempt += 1
|
|
|
|
if (requestAttempt >= requestAttemptLimit) {
|
|
return Promise.resolve({ data: sessionState, status: 200 })
|
|
}
|
|
|
|
return Promise.resolve({ data: '', status: 304 })
|
|
})
|
|
|
|
jest.spyOn((process as any).logger, 'info')
|
|
|
|
sessionManager.debug = true
|
|
|
|
await expect(
|
|
sessionManager['waitForSession'](session, null, 'access_token')
|
|
).resolves.toEqual(sessionState)
|
|
|
|
const sessionStateUrl = process.env.SERVER_URL + session.stateUrl
|
|
|
|
expect(mockedAxios.get).toHaveBeenCalledTimes(requestAttemptLimit)
|
|
expect((process as any).logger.info).toHaveBeenCalledTimes(3)
|
|
expect((process as any).logger.info).toHaveBeenNthCalledWith(
|
|
1,
|
|
`Polling: ${sessionStateUrl}`
|
|
)
|
|
expect((process as any).logger.info).toHaveBeenNthCalledWith(
|
|
2,
|
|
`Could not get session state. Server responded with 304 whilst checking state: ${sessionStateUrl}`
|
|
)
|
|
expect((process as any).logger.info).toHaveBeenNthCalledWith(
|
|
3,
|
|
`Current session state is '${sessionState}'`
|
|
)
|
|
})
|
|
|
|
it('should throw an error if there is no session link', async () => {
|
|
const customSession = JSON.parse(JSON.stringify(session))
|
|
customSession.stateUrl = ''
|
|
|
|
mockedAxios.get.mockImplementation(() =>
|
|
Promise.resolve({ data: customSession.state, status: 200 })
|
|
)
|
|
|
|
await expect(
|
|
sessionManager['waitForSession'](customSession, null, 'access_token')
|
|
).rejects.toContain('Error while getting session state link.')
|
|
})
|
|
|
|
it('should throw an error if could not get session state', async () => {
|
|
const gettingSessionStatus = 500
|
|
const sessionStatusError = `Getting session status timed out after 60 seconds. Request failed with status code ${gettingSessionStatus}`
|
|
const sessionStateUrl = process.env.SERVER_URL + session.stateUrl
|
|
|
|
mockedAxios.get.mockImplementation(() =>
|
|
Promise.reject({
|
|
response: {
|
|
status: gettingSessionStatus,
|
|
data: {
|
|
message: sessionStatusError
|
|
}
|
|
}
|
|
})
|
|
)
|
|
|
|
const expectedError = `Error while waiting for session. Error while getting session state. GET request to ${sessionStateUrl}?wait=30 failed with status code ${gettingSessionStatus}. ${sessionStatusError}`
|
|
|
|
await expect(
|
|
sessionManager['waitForSession'](session, null, 'access_token')
|
|
).rejects.toEqual(expectedError)
|
|
})
|
|
|
|
it('should return session state', async () => {
|
|
const customSession = JSON.parse(JSON.stringify(session))
|
|
customSession.state = 'completed'
|
|
|
|
mockedAxios.get.mockImplementation(() =>
|
|
Promise.resolve({ data: customSession.state, status: 200 })
|
|
)
|
|
|
|
await expect(
|
|
sessionManager['waitForSession'](customSession, null, 'access_token')
|
|
).resolves.toEqual(customSession.state)
|
|
})
|
|
})
|
|
|
|
describe('isSessionValid', () => {
|
|
const session: Session = getMockSession()
|
|
|
|
it('should return false if not a session provided', () => {
|
|
expect(sessionManager['isSessionValid'](undefined as any)).toEqual(false)
|
|
})
|
|
|
|
it('should return true if session is not expired', () => {
|
|
expect(sessionManager['isSessionValid'](session)).toEqual(true)
|
|
})
|
|
|
|
it('should return false if session is expired', () => {
|
|
session.creationTimeStamp = `${new Date(
|
|
new Date().getTime() -
|
|
(session.attributes.sessionInactiveTimeout * 1000 + 1000)
|
|
).toISOString()}`
|
|
expect(sessionManager['isSessionValid'](session)).toEqual(false)
|
|
})
|
|
})
|
|
|
|
describe('removeSessionFromPool', () => {
|
|
it('should remove session from the pool of sessions', () => {
|
|
const session: Session = getMockSession()
|
|
const sessions: Session[] = [getMockSession(), session]
|
|
|
|
sessionManager['sessions'] = sessions
|
|
sessionManager['removeSessionFromPool'](session)
|
|
|
|
expect(sessionManager['sessions'].length).toEqual(1)
|
|
})
|
|
})
|
|
|
|
describe('getSession', () => {
|
|
it('should return session if there is a valid session and create new session', async () => {
|
|
jest
|
|
.spyOn(sessionManager as any, 'createAndWaitForSession')
|
|
.mockImplementation(async () => Promise.resolve(getMockSession()))
|
|
|
|
const session = getMockSession()
|
|
sessionManager['sessions'] = [session]
|
|
|
|
await expect(sessionManager.getSession()).resolves.toEqual(session)
|
|
expect(sessionManager['createAndWaitForSession']).toHaveBeenCalled()
|
|
})
|
|
|
|
it('should return a session and keep one session if there is no sessions available', async () => {
|
|
jest
|
|
.spyOn(sessionManager as any, 'createAndWaitForSession')
|
|
.mockImplementation(async () => {
|
|
const session = getMockSession()
|
|
sessionManager['sessions'].push(session)
|
|
|
|
return Promise.resolve(session)
|
|
})
|
|
|
|
const session = await sessionManager.getSession()
|
|
|
|
expect(Object.keys(session)).toEqual(Object.keys(getMockSession()))
|
|
expect(sessionManager['createAndWaitForSession']).toHaveBeenCalledTimes(2)
|
|
expect(sessionManager['sessions'].length).toEqual(1)
|
|
})
|
|
|
|
it.concurrent(
|
|
'should throw an error if session creation request returned 500',
|
|
async () => {
|
|
const sessionCreationStatus = 500
|
|
const sessionCreationError = `The process initialization for the Compute server with the ID 'ed40398a-ec8a-422b-867a-61493ee8a57f' timed out after 60 seconds. Request failed with status code ${sessionCreationStatus}`
|
|
|
|
jest.spyOn(requestClient, 'post').mockImplementation(() =>
|
|
Promise.reject({
|
|
response: {
|
|
status: sessionCreationStatus,
|
|
data: {
|
|
message: sessionCreationError
|
|
}
|
|
}
|
|
})
|
|
)
|
|
|
|
const contextId = 'testContextId'
|
|
const context: Context = {
|
|
name: 'testContext',
|
|
id: contextId,
|
|
createdBy: 'createdBy',
|
|
version: 1
|
|
}
|
|
|
|
sessionManager['currentContext'] = context
|
|
|
|
const expectedError = new Error(
|
|
`Error while creating session. POST request to ${process.env.SERVER_URL}/compute/contexts/${contextId}/sessions failed with status code ${sessionCreationStatus}. ${sessionCreationError}`
|
|
)
|
|
|
|
await expect(sessionManager.getSession()).rejects.toEqual(expectedError)
|
|
}
|
|
)
|
|
})
|
|
|
|
describe('clearSession', () => {
|
|
it('should clear session', async () => {
|
|
jest
|
|
.spyOn(requestClient, 'delete')
|
|
.mockImplementation(() =>
|
|
Promise.resolve({ result: '', etag: '', status: 200 })
|
|
)
|
|
|
|
const sessionToBeCleared = getMockSession()
|
|
const sessionToStay = getMockSession()
|
|
|
|
sessionManager['sessions'] = [sessionToBeCleared, sessionToStay]
|
|
|
|
await sessionManager.clearSession(sessionToBeCleared.id)
|
|
|
|
expect(sessionManager['sessions']).toEqual([sessionToStay])
|
|
})
|
|
|
|
it('should throw error if DELETE request failed', async () => {
|
|
const sessionCreationStatus = 500
|
|
const sessionDeleteError = `The process timed out after 60 seconds. Request failed with status code ${sessionCreationStatus}`
|
|
|
|
jest.spyOn(requestClient, 'delete').mockImplementation(() =>
|
|
Promise.reject({
|
|
response: {
|
|
status: sessionCreationStatus,
|
|
data: {
|
|
message: sessionDeleteError
|
|
}
|
|
}
|
|
})
|
|
)
|
|
|
|
const session = getMockSession()
|
|
|
|
sessionManager['sessions'] = [session]
|
|
|
|
const expectedError = `Error while deleting session. DELETE request to /compute/sessions/${session.id} failed with status code ${sessionCreationStatus}. ${sessionDeleteError}`
|
|
|
|
await expect(sessionManager.clearSession(session.id)).rejects.toEqual(
|
|
expectedError
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('waitForCurrentContext', () => {
|
|
it('should resolve when current context is ready', async () => {
|
|
sessionManager['settingContext'] = true
|
|
sessionManager['contextName'] = 'test context'
|
|
|
|
await expect(sessionManager['waitForCurrentContext']()).toResolve()
|
|
expect(sessionManager['settingContext']).toEqual(false)
|
|
})
|
|
})
|
|
|
|
describe('setCurrentContext', () => {
|
|
it('should set current context', async () => {
|
|
const contextName = 'test context'
|
|
const testContext: Context = {
|
|
name: contextName,
|
|
id: 'string',
|
|
createdBy: 'string',
|
|
version: 1
|
|
}
|
|
|
|
jest.spyOn(requestClient, 'get').mockImplementation(() => {
|
|
return Promise.resolve({
|
|
result: {
|
|
items: [testContext]
|
|
},
|
|
etag: '',
|
|
status: 200
|
|
})
|
|
})
|
|
|
|
sessionManager['currentContext'] = null
|
|
sessionManager['contextName'] = contextName
|
|
sessionManager['settingContext'] = false
|
|
|
|
await expect(sessionManager['setCurrentContext']()).toResolve()
|
|
expect(sessionManager['currentContext']).toEqual(testContext)
|
|
})
|
|
|
|
it('should throw error if GET request failed', async () => {
|
|
const responseStatus = 500
|
|
const responseErrorMessage = `The process timed out after 60 seconds. Request failed with status code ${responseStatus}`
|
|
const response = {
|
|
status: responseStatus,
|
|
data: {
|
|
message: responseErrorMessage
|
|
}
|
|
}
|
|
|
|
jest.spyOn(requestClient, 'get').mockImplementation(() =>
|
|
Promise.reject({
|
|
response
|
|
})
|
|
)
|
|
|
|
const expectedError = `Error while getting list of contexts. GET request to ${process.env.SERVER_URL}/compute/contexts?limit=10000 failed with status code ${responseStatus}. ${responseErrorMessage}`
|
|
|
|
sessionManager['currentContext'] = null
|
|
|
|
await expect(sessionManager['setCurrentContext']()).rejects.toEqual(
|
|
expectedError
|
|
)
|
|
})
|
|
|
|
it('should throw an error if current context is not in the list of contexts', async () => {
|
|
const contextName = 'test context'
|
|
const testContext: Context = {
|
|
name: `${contextName} does not exist`,
|
|
id: 'string',
|
|
createdBy: 'string',
|
|
version: 1
|
|
}
|
|
|
|
jest.spyOn(requestClient, 'get').mockImplementation(() => {
|
|
return Promise.resolve({
|
|
result: {
|
|
items: [testContext]
|
|
},
|
|
etag: '',
|
|
status: 200
|
|
})
|
|
})
|
|
|
|
sessionManager['currentContext'] = null
|
|
sessionManager['contextName'] = contextName
|
|
sessionManager['settingContext'] = false
|
|
|
|
const expectedError = new Error(
|
|
`The context '${contextName}' was not found on the server ${process.env.SERVER_URL}.`
|
|
)
|
|
|
|
await expect(sessionManager['setCurrentContext']()).rejects.toEqual(
|
|
expectedError
|
|
)
|
|
})
|
|
})
|
|
|
|
describe('createAndWaitForSession', () => {
|
|
it('should create session with etag and stateUrl', async () => {
|
|
const etag = sessionEtag
|
|
const customSession: any = getMockSession()
|
|
delete customSession.etag
|
|
delete customSession.stateUrl
|
|
|
|
jest.spyOn(requestClient, 'post').mockImplementation(() =>
|
|
Promise.resolve({
|
|
result: customSession,
|
|
etag
|
|
})
|
|
)
|
|
|
|
jest
|
|
.spyOn(sessionManager as any, 'setCurrentContext')
|
|
.mockImplementation(() => Promise.resolve())
|
|
|
|
sessionManager['currentContext'] = {
|
|
name: 'context name',
|
|
id: 'string',
|
|
createdBy: 'string',
|
|
version: 1
|
|
}
|
|
|
|
jest
|
|
.spyOn(sessionManager as any, 'getSessionState')
|
|
.mockImplementation(() =>
|
|
Promise.resolve({ result: SessionState.Idle, responseStatus: 200 })
|
|
)
|
|
|
|
const expectedSession = await sessionManager['createAndWaitForSession']()
|
|
|
|
expect(customSession.id).toEqual(expectedSession.id)
|
|
expect(
|
|
customSession.links.find((l: any) => l.rel === 'state').href
|
|
).toEqual(expectedSession.stateUrl)
|
|
expect(expectedSession.etag).toEqual(etag)
|
|
})
|
|
})
|
|
})
|