1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-01-19 10:00:06 +00:00

Merge pull request #491 from sasjs/session-state-fix

fix(session): remove retry limit if could not get state
This commit is contained in:
Yury Shkoda
2021-07-29 10:34:50 +03:00
committed by GitHub
2 changed files with 141 additions and 78 deletions

View File

@@ -5,10 +5,10 @@ import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient } from './request/RequestClient' import { RequestClient } from './request/RequestClient'
const MAX_SESSION_COUNT = 1 const MAX_SESSION_COUNT = 1
const RETRY_LIMIT: number = 3
let RETRY_COUNT: number = 0
export class SessionManager { export class SessionManager {
private loggedErrors: NoSessionStateError[] = []
constructor( constructor(
private serverUrl: string, private serverUrl: string,
private contextName: string, private contextName: string,
@@ -154,69 +154,75 @@ export class SessionManager {
session: Session, session: Session,
etag: string | null, etag: string | null,
accessToken?: string accessToken?: string
) { ): Promise<string> {
const logger = process.logger || console const logger = process.logger || console
let sessionState = session.state let sessionState = session.state
const stateLink = session.links.find((l: any) => l.rel === 'state') const stateLink = session.links.find((l: any) => l.rel === 'state')
return new Promise(async (resolve, reject) => { if (
if ( sessionState === 'pending' ||
sessionState === 'pending' || sessionState === 'running' ||
sessionState === 'running' || sessionState === ''
sessionState === '' ) {
) { if (stateLink) {
if (stateLink) { if (this.debug && !this.printedSessionState.printed) {
if (this.debug && !this.printedSessionState.printed) { logger.info('Polling session status...')
logger.info('Polling session status...')
this.printedSessionState.printed = true this.printedSessionState.printed = true
}
const { result: state, responseStatus: responseStatus } =
await this.getSessionState(
`${this.serverUrl}${stateLink.href}?wait=30`,
etag!,
accessToken
).catch((err) => {
throw prefixMessage(err, 'Error while getting session state.')
})
sessionState = state.trim()
if (this.debug && this.printedSessionState.state !== sessionState) {
logger.info(`Current session state is '${sessionState}'`)
this.printedSessionState.state = sessionState
this.printedSessionState.printed = false
}
// There is an internal error present in SAS Viya 3.5
// Retry to wait for a session status in such case of SAS internal error
if (!sessionState) {
if (RETRY_COUNT < RETRY_LIMIT) {
RETRY_COUNT++
resolve(this.waitForSession(session, etag, accessToken))
} else {
reject(
new NoSessionStateError(
responseStatus,
this.serverUrl + stateLink.href,
session.links.find((l: any) => l.rel === 'log')
?.href as string
)
)
}
}
resolve(sessionState)
} }
const { result: state, responseStatus: responseStatus } =
await this.getSessionState(
`${this.serverUrl}${stateLink.href}?wait=30`,
etag!,
accessToken
).catch((err) => {
throw prefixMessage(err, 'Error while getting session state.')
})
sessionState = state.trim()
if (this.debug && this.printedSessionState.state !== sessionState) {
logger.info(`Current session state is '${sessionState}'`)
this.printedSessionState.state = sessionState
this.printedSessionState.printed = false
}
if (!sessionState) {
const stateError = new NoSessionStateError(
responseStatus,
this.serverUrl + stateLink.href,
session.links.find((l: any) => l.rel === 'log')?.href as string
)
if (
!this.loggedErrors.find(
(err: NoSessionStateError) =>
err.serverResponseStatus === stateError.serverResponseStatus
)
) {
this.loggedErrors.push(stateError)
logger.info(stateError.message)
}
return await this.waitForSession(session, etag, accessToken)
}
this.loggedErrors = []
return sessionState
} else { } else {
resolve(sessionState) throw 'Error while getting session state link.'
} }
}) } else {
this.loggedErrors = []
return sessionState
}
} }
private async getSessionState( private async getSessionState(

View File

@@ -3,6 +3,8 @@ import { RequestClient } from '../request/RequestClient'
import { NoSessionStateError } from '../types/errors' import { NoSessionStateError } from '../types/errors'
import * as dotenv from 'dotenv' import * as dotenv from 'dotenv'
import axios from 'axios' import axios from 'axios'
import { Logger, LogLevel } from '@sasjs/utils'
import { Session } from '../types'
jest.mock('axios') jest.mock('axios')
const mockedAxios = axios as jest.Mocked<typeof axios> const mockedAxios = axios as jest.Mocked<typeof axios>
@@ -47,36 +49,91 @@ describe('SessionManager', () => {
}) })
describe('waitForSession', () => { describe('waitForSession', () => {
const session: Session = {
id: 'id',
state: '',
links: [{ rel: 'state', href: '', uri: '', type: '', method: 'GET' }],
attributes: {
sessionInactiveTimeout: 0
},
creationTimeStamp: ''
}
beforeEach(() => {
;(process as any).logger = new Logger(LogLevel.Off)
})
it('should reject with NoSessionStateError if SAS server did not provide session state', async () => { it('should reject with NoSessionStateError if SAS server did not provide session state', async () => {
const responseStatus = 304 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)
expect(mockedAxios.get).toHaveBeenCalledTimes(requestAttemptLimit)
expect((process as any).logger.info).toHaveBeenCalledTimes(3)
expect((process as any).logger.info).toHaveBeenNthCalledWith(
1,
'Polling session status...'
)
expect((process as any).logger.info).toHaveBeenNthCalledWith(
2,
`Could not get session state. Server responded with 304 whilst checking state: ${process.env.SERVER_URL}`
)
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.links = []
mockedAxios.get.mockImplementation(() => mockedAxios.get.mockImplementation(() =>
Promise.resolve({ data: '', status: responseStatus }) Promise.resolve({ data: customSession.state, status: 200 })
) )
await expect( await expect(
sessionManager['waitForSession']( sessionManager['waitForSession'](customSession, null, 'access_token')
{ ).rejects.toContain('Error while getting session state link.')
id: 'id', })
state: '',
links: [ it('should throw an error if could not get session state', async () => {
{ rel: 'state', href: '', uri: '', type: '', method: 'GET' } mockedAxios.get.mockImplementation(() => Promise.reject('Mocked error'))
],
attributes: { await expect(
sessionInactiveTimeout: 0 sessionManager['waitForSession'](session, null, 'access_token')
}, ).rejects.toContain('Error while getting session state.')
creationTimeStamp: '' })
},
null, it('should return session state', async () => {
'access_token' const customSession = JSON.parse(JSON.stringify(session))
) customSession.state = 'completed'
).rejects.toEqual(
new NoSessionStateError( mockedAxios.get.mockImplementation(() =>
responseStatus, Promise.resolve({ data: customSession.state, status: 200 })
process.env.SERVER_URL as string,
'logUrl'
)
) )
await expect(
sessionManager['waitForSession'](customSession, null, 'access_token')
).resolves.toEqual(customSession.state)
}) })
}) })
}) })