1
0
mirror of https://github.com/sasjs/adapter.git synced 2025-12-13 02:04:36 +00:00

Compare commits

..

8 Commits

Author SHA1 Message Date
Yury Shkoda
0f881fba72 chore(deps): regenerate package-lock.json 2021-12-23 11:49:42 +03:00
Yury Shkoda
4e125ce38f Merge pull request #605 from sasjs/issue-604
Improve error handling and job/session state polling
2021-12-22 19:11:37 +03:00
Yury Shkoda
4a963ffbf5 feat(polling-state): improve logging 2021-12-22 18:46:06 +03:00
Yury Shkoda
f25d9ec09d test(RequestClient): cover handleError method 2021-12-21 16:41:08 +03:00
Yury Shkoda
4197ad5aa8 test(RequestClient): fix error handling 2021-12-21 11:40:59 +03:00
Yury Shkoda
2ebd6e11ba test(RequestClient): fix error handling 2021-12-21 11:40:27 +03:00
Yury Shkoda
098e7f8590 fix(errors): fixed error handling function 2021-12-20 10:57:53 +03:00
Yury Shkoda
42aec96410 feat(pollJobState): improved loggging 2021-12-20 10:56:52 +03:00
8 changed files with 22570 additions and 8809 deletions

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,6 +5,14 @@ import { app, mockedAuthResponse } from './SAS_server_app'
import { ServerType } from '@sasjs/utils' import { ServerType } from '@sasjs/utils'
import SASjs from '../SASjs' import SASjs from '../SASjs'
import * as axiosModules from '../utils/createAxiosInstance' 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') const axiosActual = jest.requireActual('axios')
@@ -55,10 +63,114 @@ describe('RequestClient', () => {
it('should response the POST method with Unauthorized', async () => { it('should response the POST method with Unauthorized', async () => {
await expect( await expect(
adapter.getAccessToken('clientId', 'clientSecret', 'incorrect') adapter.getAccessToken('clientId', 'clientSecret', 'incorrect')
).rejects.toThrow( ).rejects.toEqual(
'Error while getting access token. Request failed with status code 401' 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', () => { describe('RequestClient - Self Signed Server', () => {
@@ -132,8 +244,11 @@ describe('RequestClient - Self Signed Server', () => {
it('should response the POST method with Unauthorized', async () => { it('should response the POST method with Unauthorized', async () => {
await expect( await expect(
adapter.getAccessToken('clientId', 'clientSecret', 'incorrect') adapter.getAccessToken('clientId', 'clientSecret', 'incorrect')
).rejects.toThrow( ).rejects.toEqual(
'Error while getting access token. Request failed with status code 401' 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) { app.post('/SASLogon/oauth/token', function (req: any, res: any) {
let valid = true let valid = true
// capture the encoded form data // capture the encoded form data
req.on('data', (data: any) => { req.on('data', (data: any) => {
const resData = data.toString() 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).toHaveBeenCalledTimes(3)
expect((process as any).logger.info).toHaveBeenNthCalledWith( expect((process as any).logger.info).toHaveBeenNthCalledWith(
1, 1,
'Polling session status...' `Polling: ${process.env.SERVER_URL}`
) )
expect((process as any).logger.info).toHaveBeenNthCalledWith( expect((process as any).logger.info).toHaveBeenNthCalledWith(
2, 2,