1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-01-05 03:30:05 +00:00

Compare commits

..

11 Commits

Author SHA1 Message Date
Yury Shkoda
0f7f3e0a11 Merge pull request #614 from sasjs/hot-fix-sasjs-server
fix(sasjs): restrict usage of localstorage for node env
2022-01-05 14:51:33 +03:00
Saad Jutt
437bbe114b fix(sasjs): restrict usage of localstorage for node env 2022-01-05 16:39:59 +05:00
Yury Shkoda
9f7870b804 Merge pull request #608 from sasjs/readme_update
chore(readme): support for special missings
2021-12-30 14:32:08 +03:00
munja
9f00cd646e chore(readme): support for special missings 2021-12-30 09:56:08 +00: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
9 changed files with 146 additions and 23 deletions

View File

@@ -47,7 +47,7 @@ npm i -g copyfiles
``` ```
and then run to build: and then run to build:
```bash ```bash
npm run update:adapter && npm run build npm run update:adapter && npm run build
``` ```
when it finishes run to deploy: when it finishes run to deploy:
```bash ```bash
@@ -70,7 +70,7 @@ parmcards4;
%webout(FETCH) %webout(FETCH)
%webout(OPEN) %webout(OPEN)
%macro x(); %macro x();
%do i=1 %to &_webin_file_count; %webout(OBJ,&&_webin_name&i) %end; %do i=1 %to &_webin_file_count; %webout(OBJ,&&_webin_name&i,missing=STRING) %end;
%mend; %x() %mend; %x()
%webout(CLOSE) %webout(CLOSE)
;;;; ;;;;
@@ -79,7 +79,7 @@ parmcards4;
%webout(FETCH) %webout(FETCH)
%webout(OPEN) %webout(OPEN)
%macro x(); %macro x();
%do i=1 %to &_webin_file_count; %webout(ARR,&&_webin_name&i) %end; %do i=1 %to &_webin_file_count; %webout(ARR,&&_webin_name&i,missing=STRING) %end;
%mend; %x() %mend; %x()
%webout(CLOSE) %webout(CLOSE)
;;;; ;;;;
@@ -111,7 +111,7 @@ parmcards4;
%macro x(); %macro x();
%do i=1 %to %sysfunc(countw(&sasjs_tables)); %do i=1 %to %sysfunc(countw(&sasjs_tables));
%let table=%scan(&sasjs_tables,&i); %let table=%scan(&sasjs_tables,&i);
%webout(OBJ,&table) %webout(OBJ,&table,missing=STRING)
%end; %end;
%mend; %mend;
%x() %x()
@@ -125,7 +125,7 @@ parmcards4;
%macro x(); %macro x();
%do i=1 %to %sysfunc(countw(&sasjs_tables)); %do i=1 %to %sysfunc(countw(&sasjs_tables));
%let table=%scan(&sasjs_tables,&i); %let table=%scan(&sasjs_tables,&i);
%webout(ARR,&table) %webout(ARR,&table,missing=STRING)
%end; %end;
%mend; %mend;
%x() %x()

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

@@ -13,7 +13,7 @@ export class SasjsRequestClient extends RequestClient {
headers.Accept = contentType === 'application/json' ? contentType : '*/*' headers.Accept = contentType === 'application/json' ? contentType : '*/*'
if (!accessToken) if (!accessToken && typeof window !== 'undefined')
accessToken = localStorage.getItem('accessToken') ?? undefined accessToken = localStorage.getItem('accessToken') ?? undefined
if (accessToken) headers.Authorization = `Bearer ${accessToken}` if (accessToken) headers.Authorization = `Bearer ${accessToken}`

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,