1
0
mirror of https://github.com/sasjs/adapter.git synced 2025-12-11 01:14:36 +00:00

Merge pull request #605 from sasjs/issue-604

Improve error handling and job/session state polling
This commit is contained in:
Yury Shkoda
2021-12-22 19:11:37 +03:00
committed by GitHub
7 changed files with 140 additions and 17 deletions

View File

@@ -168,7 +168,7 @@ export class SessionManager {
) {
if (stateLink) {
if (this.debug && !this.printedSessionState.printed) {
logger.info('Polling session status...')
logger.info(`Polling: ${this.serverUrl + stateLink.href}`)
this.printedSessionState.printed = true
}

View File

@@ -206,10 +206,11 @@ const doPoll = async (
pollCount++
const jobHref = postedJob.links.find((l: Link) => l.rel === 'self')!.href
if (pollOptions?.streamLog) {
const jobUrl = postedJob.links.find((l: Link) => l.rel === 'self')
const { result: job } = await requestClient.get<Job>(
jobUrl!.href,
jobHref,
authConfig?.access_token
)
@@ -231,7 +232,7 @@ const doPoll = async (
}
if (debug && printedState !== state) {
logger.info('Polling job status...')
logger.info(`Polling: ${requestClient.getBaseUrl() + jobHref}/state`)
logger.info(`Current job state: ${state}`)
printedState = state

View File

@@ -9,7 +9,10 @@ import * as isNodeModule from '../../../utils/isNode'
import { PollOptions } from '../../../types'
import { WriteStream } from 'fs'
const baseUrl = 'http://localhost'
const requestClient = new (<jest.Mock<RequestClient>>RequestClient)()
requestClient['httpClient'].defaults.baseURL = baseUrl
const defaultPollOptions: PollOptions = {
maxPollCount: 100,
pollInterval: 500,
@@ -195,7 +198,7 @@ describe('pollJobState', () => {
expect((process as any).logger.info).toHaveBeenCalledTimes(4)
expect((process as any).logger.info).toHaveBeenNthCalledWith(
1,
'Polling job status...'
`Polling: ${baseUrl}/job/state`
)
expect((process as any).logger.info).toHaveBeenNthCalledWith(
2,
@@ -203,7 +206,7 @@ describe('pollJobState', () => {
)
expect((process as any).logger.info).toHaveBeenNthCalledWith(
3,
'Polling job status...'
`Polling: ${baseUrl}/job/state`
)
expect((process as any).logger.info).toHaveBeenNthCalledWith(
4,

View File

@@ -195,9 +195,7 @@ export class RequestClient implements HttpClient {
}
),
debug
).catch((err) => {
throw prefixMessage(err, 'Error while handling error. ')
})
)
})
}
@@ -217,6 +215,7 @@ export class RequestClient implements HttpClient {
.post<T>(url, data, { headers, withCredentials: true })
.then((response) => {
throwIfError(response)
return this.parseResponse<T>(response)
})
.catch(async (e) => {
@@ -466,6 +465,8 @@ export class RequestClient implements HttpClient {
if (e instanceof LoginRequiredError) {
this.clearCsrfTokens()
throw e
}
if (response?.status === 403 || response?.status === 449) {
@@ -488,7 +489,8 @@ export class RequestClient implements HttpClient {
else return
}
throw e
if (e.message) throw e
else throw prefixMessage(e, 'Error while handling error. ')
}
protected parseResponse<T>(response: AxiosResponse<any>) {
@@ -540,8 +542,9 @@ export class RequestClient implements HttpClient {
this.httpClient = createAxiosInstance(baseUrl, httpsAgent)
this.httpClient.defaults.validateStatus = (status) =>
status >= 200 && status < 401
this.httpClient.defaults.validateStatus = (status) => {
return status >= 200 && status <= 401
}
}
}

View File

@@ -5,6 +5,14 @@ import { app, mockedAuthResponse } from './SAS_server_app'
import { ServerType } from '@sasjs/utils'
import SASjs from '../SASjs'
import * as axiosModules from '../utils/createAxiosInstance'
import {
LoginRequiredError,
AuthorizeError,
NotFoundError,
InternalServerError
} from '../types/errors'
import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient } from '../request/RequestClient'
const axiosActual = jest.requireActual('axios')
@@ -55,10 +63,114 @@ describe('RequestClient', () => {
it('should response the POST method with Unauthorized', async () => {
await expect(
adapter.getAccessToken('clientId', 'clientSecret', 'incorrect')
).rejects.toThrow(
'Error while getting access token. Request failed with status code 401'
).rejects.toEqual(
prefixMessage(
new LoginRequiredError(),
'Error while getting access token. '
)
)
})
describe('handleError', () => {
const requestClient = new RequestClient('https://localhost:8009')
const randomError = 'some error'
it('should throw an error if could not get confirmUrl', async () => {
const authError = new AuthorizeError('message', 'confirm_url')
jest
.spyOn(requestClient['httpClient'], 'get')
.mockImplementation(() => Promise.reject(randomError))
await expect(
requestClient['handleError'](authError, () => {})
).rejects.toEqual(`Error while getting error confirmUrl. ${randomError}`)
})
it('should throw an error if authorize form is required', async () => {
const authError = new AuthorizeError('message', 'confirm_url')
jest
.spyOn(requestClient['httpClient'], 'get')
.mockImplementation(() =>
Promise.resolve({ data: '<form action="(Logon/oauth/authorize")>' })
)
jest
.spyOn(requestClient, 'authorize')
.mockImplementation(() => Promise.reject(randomError))
await expect(
requestClient['handleError'](authError, () => {})
).rejects.toEqual(`Error while authorizing request. ${randomError}`)
})
it('should throw an error from callback function', async () => {
const authError = new AuthorizeError('message', 'confirm_url')
jest
.spyOn(requestClient['httpClient'], 'get')
.mockImplementation(() => Promise.resolve({ data: '' }))
await expect(
requestClient['handleError'](authError, () =>
Promise.reject(randomError)
)
).rejects.toEqual(
`Error while executing callback in handleError. ${randomError}`
)
})
it('should handle error with 403 response status', async () => {
const error = {
response: {
status: 403,
headers: { 'x-csrf-header': 'x-csrf-header' }
}
}
await expect(
requestClient['handleError'](error, () => Promise.reject(randomError))
).rejects.toEqual(
`Error while executing callback in handleError. ${randomError}`
)
error.response.headers = {} as unknown as { 'x-csrf-header': string }
requestClient['csrfToken'].headerName = ''
await expect(
requestClient['handleError'](error, () => Promise.reject(randomError))
).rejects.toEqual(error)
})
it('should handle error with 404 response status', async () => {
const error = {
response: {
status: 404,
config: { url: 'test url' }
}
}
await expect(
requestClient['handleError'](error, () => {})
).rejects.toEqual(new NotFoundError(error.response.config.url))
})
it('should handle error with 502 response status', async () => {
const error = {
response: {
status: 502
}
}
await expect(
requestClient['handleError'](error, () => {}, true)
).rejects.toEqual(new InternalServerError())
await expect(
requestClient['handleError'](error, () => {}, false)
).resolves.toEqual(undefined)
})
})
})
describe('RequestClient - Self Signed Server', () => {
@@ -132,8 +244,11 @@ describe('RequestClient - Self Signed Server', () => {
it('should response the POST method with Unauthorized', async () => {
await expect(
adapter.getAccessToken('clientId', 'clientSecret', 'incorrect')
).rejects.toThrow(
'Error while getting access token. Request failed with status code 401'
).rejects.toEqual(
prefixMessage(
new LoginRequiredError(),
'Error while getting access token. '
)
)
})
})

View File

@@ -18,6 +18,7 @@ app.get('/', function (req: any, res: any) {
app.post('/SASLogon/oauth/token', function (req: any, res: any) {
let valid = true
// capture the encoded form data
req.on('data', (data: any) => {
const resData = data.toString()

View File

@@ -89,7 +89,7 @@ describe('SessionManager', () => {
expect((process as any).logger.info).toHaveBeenCalledTimes(3)
expect((process as any).logger.info).toHaveBeenNthCalledWith(
1,
'Polling session status...'
`Polling: ${process.env.SERVER_URL}`
)
expect((process as any).logger.info).toHaveBeenNthCalledWith(
2,