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:
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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. '
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user