From 613cc6b9ef13ea735aafa66ac274f9f778f82a09 Mon Sep 17 00:00:00 2001 From: Krishna Acondy Date: Thu, 4 Feb 2021 09:42:39 +0000 Subject: [PATCH] chore(*): clean up, handle debug responses --- src/auth/spec/AuthManager.spec.ts | 5 +- src/job-execution/ComputeJobExecutor.ts | 11 +- src/job-execution/JesJobExecutor.ts | 15 +- src/job-execution/JobExecutor.ts | 7 +- src/job-execution/WebJobExecutor.ts | 21 +++ src/request/RequestClient.ts | 235 ++++++++++-------------- 6 files changed, 125 insertions(+), 169 deletions(-) diff --git a/src/auth/spec/AuthManager.spec.ts b/src/auth/spec/AuthManager.spec.ts index 71d5c2d..8af9cae 100644 --- a/src/auth/spec/AuthManager.spec.ts +++ b/src/auth/spec/AuthManager.spec.ts @@ -142,7 +142,10 @@ describe('AuthManager', () => { }) ) mockedAxios.post.mockImplementation(() => - Promise.resolve({ data: mockLoginAuthoriseRequiredResponse }) + Promise.resolve({ + data: mockLoginAuthoriseRequiredResponse, + config: { url: 'https://test.com/SASLogon/login' } + }) ) await authManager.logIn(userName, password) diff --git a/src/job-execution/ComputeJobExecutor.ts b/src/job-execution/ComputeJobExecutor.ts index e6f5003..fa7a6f0 100644 --- a/src/job-execution/ComputeJobExecutor.ts +++ b/src/job-execution/ComputeJobExecutor.ts @@ -2,7 +2,6 @@ import { ServerType } from '@sasjs/utils/types' import { ErrorResponse } from '..' import { SASViyaApiClient } from '../SASViyaApiClient' import { ComputeJobExecutionError, LoginRequiredError } from '../types' -import { parseWeboutResponse } from '../utils' import { BaseJobExecutor } from './JobExecutor' export class ComputeJobExecutor extends BaseJobExecutor { @@ -35,15 +34,7 @@ export class ComputeJobExecutor extends BaseJobExecutor { this.appendRequest(response, sasJob, config.debug) let responseJson - try { - if (typeof response!.result === 'string') { - responseJson = JSON.parse(response!.result) - } else { - responseJson = response!.result - } - } catch { - responseJson = JSON.parse(parseWeboutResponse(response!.result)) - } + return response.result return responseJson }) diff --git a/src/job-execution/JesJobExecutor.ts b/src/job-execution/JesJobExecutor.ts index 0ce1d08..dad7b9e 100644 --- a/src/job-execution/JesJobExecutor.ts +++ b/src/job-execution/JesJobExecutor.ts @@ -2,7 +2,6 @@ import { ServerType } from '@sasjs/utils/types' import { ErrorResponse } from '..' import { SASViyaApiClient } from '../SASViyaApiClient' import { JobExecutionError, LoginRequiredError } from '../types' -import { parseWeboutResponse } from '../utils' import { BaseJobExecutor } from './JobExecutor' export class JesJobExecutor extends BaseJobExecutor { @@ -23,19 +22,7 @@ export class JesJobExecutor extends BaseJobExecutor { .then((response) => { this.appendRequest(response, sasJob, config.debug) - let responseJson - - try { - if (typeof response!.result === 'string') { - responseJson = JSON.parse(response!.result) - } else { - responseJson = response!.result - } - } catch { - responseJson = JSON.parse(parseWeboutResponse(response!.result)) - } - - return responseJson + return response.result }) .catch(async (e: Error) => { if (e instanceof JobExecutionError) { diff --git a/src/job-execution/JobExecutor.ts b/src/job-execution/JobExecutor.ts index c195dcf..e8a77cf 100644 --- a/src/job-execution/JobExecutor.ts +++ b/src/job-execution/JobExecutor.ts @@ -90,8 +90,13 @@ export abstract class BaseJobExecutor implements JobExecutor { } } + const stringifiedResult = + typeof response?.result === 'string' + ? response?.result + : JSON.stringify(response?.result, null, 2) + this.requests.push({ - logFile: response?.log || response?.result || response, + logFile: response?.log || stringifiedResult || response, serviceLink: program, timestamp: new Date(), sourceCode, diff --git a/src/job-execution/WebJobExecutor.ts b/src/job-execution/WebJobExecutor.ts index c20c29b..ede976c 100644 --- a/src/job-execution/WebJobExecutor.ts +++ b/src/job-execution/WebJobExecutor.ts @@ -82,6 +82,13 @@ export class WebJobExecutor extends BaseJobExecutor { return this.requestClient!.post(apiUrl, formData, undefined) .then(async (res) => { + if (this.serverType === ServerType.SasViya && config.debug) { + const jsonResponse = await this.parseSasViyaDebugResponse( + res.result as string + ) + this.appendRequest(res, sasJob, config.debug) + return jsonResponse + } this.appendRequest(res, sasJob, config.debug) return res.result }) @@ -99,6 +106,20 @@ export class WebJobExecutor extends BaseJobExecutor { }) } + private parseSasViyaDebugResponse = async (response: string) => { + const iframeStart = response.split( + '')[0] : null + if (!jsonUrl) { + throw new Error('Unable to find webout file URL.') + } + + return this.requestClient + .get(this.serverUrl + jsonUrl, undefined) + .then((res) => res.result) + } + private async getJobUri(sasJob: string) { if (!this.sasViyaApiClient) return '' let uri = '' diff --git a/src/request/RequestClient.ts b/src/request/RequestClient.ts index 053b20c..acbc910 100644 --- a/src/request/RequestClient.ts +++ b/src/request/RequestClient.ts @@ -4,6 +4,7 @@ import { isAuthorizeFormRequired, isLogInRequired } from '../auth' import { LoginRequiredError } from '../types' import { AuthorizeError } from '../types/AuthorizeError' import { NotFoundError } from '../types/NotFoundError' +import { parseWeboutResponse } from '../utils/parseWeboutResponse' export interface HttpClient { get( @@ -42,7 +43,7 @@ export class RequestClient implements HttpClient { private fileUploadCsrfToken: CsrfToken | undefined private httpClient: AxiosInstance - constructor(baseUrl: string) { + constructor(private baseUrl: string) { this.httpClient = axios.create({ baseURL: baseUrl }) } @@ -79,35 +80,12 @@ export class RequestClient implements HttpClient { .get(url, requestConfig) .then((response) => { throwIfError(response) - const etag = response?.headers ? response.headers['etag'] : '' - - return { - result: response.data as T, - etag - } + return this.parseResponse(response) }) .catch(async (e) => { - const response = e.response as AxiosResponse - if (e instanceof AuthorizeError) { - const res = await this.get(e.confirmUrl, undefined, 'text/plain') - if (isAuthorizeFormRequired(res.result as string)) { - await this.authorize(res.result as string) - } - return this.get(url, accessToken, contentType, overrideHeaders) - } - if (e instanceof LoginRequiredError) { - this.clearCsrfTokens() - } - if (response?.status === 403 || response?.status === 449) { - this.parseAndSetCsrfToken(response) - if (this.csrfToken.headerName && this.csrfToken.value) { - return this.get(url, accessToken, contentType, overrideHeaders) - } - throw e - } else if (response?.status === 404) { - throw new NotFoundError(url) - } - throw e + return await this.handleError(e, () => + this.get(url, accessToken, contentType, overrideHeaders) + ) }) } @@ -127,39 +105,12 @@ export class RequestClient implements HttpClient { .post(url, data, { headers, withCredentials: true }) .then((response) => { throwIfError(response) - const etag = response?.headers ? response.headers['etag'] : '' - return { - result: response.data as T, - etag - } + return this.parseResponse(response) }) .catch(async (e) => { - const response = e.response as AxiosResponse - if (e instanceof AuthorizeError) { - const res = await this.get(e.confirmUrl, undefined, 'text/plain') - if (isAuthorizeFormRequired(res.result as string)) { - await this.authorize(res.result as string) - } - return this.post( - url, - data, - accessToken, - contentType, - overrideHeaders - ) - } - if (e instanceof LoginRequiredError) { - this.clearCsrfTokens() - } - if (response?.status === 403 || response?.status === 449) { - this.parseAndSetCsrfToken(response) - - if (this.csrfToken.headerName && this.csrfToken.value) { - return this.post(url, data, accessToken) - } - throw e - } - throw e + return await this.handleError(e, () => + this.post(url, data, accessToken, contentType, overrideHeaders) + ) }) } @@ -174,36 +125,17 @@ export class RequestClient implements HttpClient { ...overrideHeaders } - try { - const response = await this.httpClient.put(url, data, { - headers, - withCredentials: true + return this.httpClient + .put(url, data, { headers, withCredentials: true }) + .then((response) => { + throwIfError(response) + return this.parseResponse(response) + }) + .catch(async (e) => { + return await this.handleError(e, () => + this.put(url, data, accessToken, overrideHeaders) + ) }) - const etag = response?.headers ? response.headers['etag'] : '' - return { - result: response.data as T, - etag - } - } catch (e) { - const response = e.response as AxiosResponse - if (e instanceof LoginRequiredError) { - this.clearCsrfTokens() - } - - if (response?.status === 403 || response?.status === 449) { - this.parseAndSetCsrfToken(response) - - if (this.csrfToken.headerName && this.csrfToken.value) { - return this.put(url, data, accessToken) - } - throw e - } - - if (response?.status === 401) { - throw new LoginRequiredError() - } - throw e - } } public async delete( @@ -212,33 +144,15 @@ export class RequestClient implements HttpClient { ): Promise<{ result: T; etag: string }> { const headers = this.getHeaders(accessToken, 'application/json') - const requestConfig: AxiosRequestConfig = { - headers - } - - try { - const response = await this.httpClient.delete(url, requestConfig) - const etag = response?.headers ? response.headers['etag'] : '' - return { - result: response.data as T, - etag - } - } catch (e) { - const response = e.response as AxiosResponse - if (e instanceof LoginRequiredError) { - this.clearCsrfTokens() - } - - if (response?.status === 403 || response?.status === 449) { - this.parseAndSetCsrfToken(response) - - if (this.csrfToken.headerName && this.csrfToken.value) { - return this.delete(url, accessToken) - } - throw e - } - throw e - } + return this.httpClient + .delete(url, { headers, withCredentials: true }) + .then((response) => { + throwIfError(response) + return this.parseResponse(response) + }) + .catch(async (e) => { + return await this.handleError(e, () => this.delete(url, accessToken)) + }) } public async patch( @@ -248,31 +162,17 @@ export class RequestClient implements HttpClient { ): Promise<{ result: T; etag: string }> { const headers = this.getHeaders(accessToken, 'application/json') - try { - const response = await this.httpClient.patch(url, data, { - headers, - withCredentials: true + return this.httpClient + .patch(url, data, { headers, withCredentials: true }) + .then((response) => { + throwIfError(response) + return this.parseResponse(response) + }) + .catch(async (e) => { + return await this.handleError(e, () => + this.patch(url, data, accessToken) + ) }) - return { - result: response.data as T, - etag: response.headers['etag'] as string - } - } catch (e) { - const response = e.response as AxiosResponse - if (e instanceof LoginRequiredError) { - this.clearCsrfTokens() - } - - if (response?.status === 403 || response?.status === 449) { - this.parseAndSetCsrfToken(response) - - if (this.csrfToken.headerName && this.csrfToken.value) { - return this.patch(url, accessToken) - } - throw e - } - throw e - } } public async uploadFile( @@ -317,9 +217,7 @@ export class RequestClient implements HttpClient { bodyElement.innerHTML = responseBody const form = bodyElement.querySelector('#application_authorization') - authUrl = form - ? this.httpClient.defaults.baseURL! + form.getAttribute('action') - : null + authUrl = form ? this.baseUrl + form.getAttribute('action') : null const inputs: any = form?.querySelectorAll('input') @@ -417,9 +315,60 @@ export class RequestClient implements HttpClient { return csrfToken } } + + private handleError = async (e: any, callback: any) => { + const response = e.response as AxiosResponse + if (e instanceof AuthorizeError) { + const res = await this.get(e.confirmUrl, undefined, 'text/plain') + if (isAuthorizeFormRequired(res.result as string)) { + await this.authorize(res.result as string) + } + return await callback() + } + if (e instanceof LoginRequiredError) { + this.clearCsrfTokens() + } + if (response?.status === 403 || response?.status === 449) { + this.parseAndSetCsrfToken(response) + if (this.csrfToken.headerName && this.csrfToken.value) { + return await callback() + } + throw e + } else if (response?.status === 404) { + throw new NotFoundError(response.config.url!) + } + throw e + } + + private async parseResponse(response: AxiosResponse) { + const etag = response?.headers ? response.headers['etag'] : '' + let parsedResponse + + try { + if (typeof response.data === 'string') { + parsedResponse = JSON.parse(response.data) + } else { + parsedResponse = response.data + } + } catch { + try { + parsedResponse = JSON.parse(parseWeboutResponse(response.data)) + } catch { + parsedResponse = response.data + } + } + return { + result: parsedResponse as T, + etag + } + } } const throwIfError = (response: AxiosResponse) => { + if (response.status === 401) { + throw new LoginRequiredError() + } + if (response.data?.entityID?.includes('login')) { throw new LoginRequiredError() } @@ -427,7 +376,7 @@ const throwIfError = (response: AxiosResponse) => { if ( typeof response.data === 'string' && isLogInRequired(response.data) && - !response.config.url?.includes('/SASLogon/login') + !response.config?.url?.includes('/SASLogon/login') ) { throw new LoginRequiredError() }