1
0
mirror of https://github.com/sasjs/adapter.git synced 2026-04-10 07:53:14 +00:00

Compare commits

..

7 Commits

22 changed files with 3517 additions and 5280 deletions

1
package-lock.json generated
View File

@@ -6351,7 +6351,6 @@
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"semver": "^7.3.5", "semver": "^7.3.5",
"ssri": "^8.0.1", "ssri": "^8.0.1",
"tar": "^6.1.0",
"treeverse": "^1.0.4", "treeverse": "^1.0.4",
"walk-up-path": "^1.0.0" "walk-up-path": "^1.0.0"
} }

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,7 @@
"test": "react-scripts test", "test": "react-scripts test",
"eject": "react-scripts eject", "eject": "react-scripts eject",
"update:adapter": "cd .. && npm run package:lib && cd sasjs-tests && npm i ../build/sasjs-adapter-5.0.0.tgz", "update:adapter": "cd .. && npm run package:lib && cd sasjs-tests && npm i ../build/sasjs-adapter-5.0.0.tgz",
"deploy:tests": "rsync -avhe ssh ./build/* --delete $SSH_ACCOUNT:$DEPLOY_PATH || npm run deploy:tests-win", "deploy:tests": "rsync -avhe ssh ./build/* --delete sabhas@sas.analytium.co.uk:/var/www/html/sabhas/sasjs-test || npm run deploy:tests-win",
"deploy:tests-win": "scp %DEPLOY_PATH% ./build/*", "deploy:tests-win": "scp %DEPLOY_PATH% ./build/*",
"deploy": "npm run update:adapter && npm run build && npm run deploy:tests" "deploy": "npm run update:adapter && npm run build && npm run deploy:tests"
}, },

View File

@@ -2,7 +2,7 @@
"userName": "", "userName": "",
"password": "", "password": "",
"sasJsConfig": { "sasJsConfig": {
"serverUrl": "", "serverUrl": "https://sas.analytium.co.uk/",
"appLoc": "/Public/app", "appLoc": "/Public/app",
"serverType": "SASVIYA", "serverType": "SASVIYA",
"debug": false, "debug": false,

View File

@@ -14,16 +14,16 @@ const App = (): ReactElement<{}> => {
useEffect(() => { useEffect(() => {
if (adapter) { if (adapter) {
const testSuites = [ const testSuites = [
basicTests(adapter, config.userName, config.password), // basicTests(adapter, config.userName, config.password),
sendArrTests(adapter), // sendArrTests(adapter),
sendObjTests(adapter), // sendObjTests(adapter),
specialCaseTests(adapter), // specialCaseTests(adapter),
sasjsRequestTests(adapter) sasjsRequestTests(adapter)
] ]
if (adapter.getSasjsConfig().serverType === 'SASVIYA') { // if (adapter.getSasjsConfig().serverType === 'SASVIYA') {
testSuites.push(computeTests(adapter)) // testSuites.push(computeTests(adapter))
} // }
setTestSuites(testSuites) setTestSuites(testSuites)
} }

View File

@@ -41,19 +41,6 @@ export const basicTests = (
assertion: (response: any) => assertion: (response: any) =>
response && response.isLoggedIn && response.userName === userName response && response.isLoggedIn && response.userName === userName
}, },
{
title: 'Fetch username for already logged in user',
description: 'Should log the user in',
test: async () => {
await adapter.logIn(userName, password)
const newAdapterIns = new SASjs(adapter.getSasjsConfig())
return await newAdapterIns.checkSession()
},
assertion: (response: any) =>
response?.isLoggedIn && response?.userName === userName
},
{ {
title: 'Multiple Log in attempts', title: 'Multiple Log in attempts',
description: description:
@@ -61,7 +48,7 @@ export const basicTests = (
test: async () => { test: async () => {
await adapter.logOut() await adapter.logOut()
await adapter.logIn('invalid', 'invalid') await adapter.logIn('invalid', 'invalid')
return await adapter.logIn(userName, password) return adapter.logIn(userName, password)
}, },
assertion: (response: any) => assertion: (response: any) =>
response && response.isLoggedIn && response.userName === userName response && response.isLoggedIn && response.userName === userName
@@ -164,7 +151,7 @@ export const basicTests = (
description: description:
'Should complete successful request with extra attributes present in response', 'Should complete successful request with extra attributes present in response',
test: async () => { test: async () => {
const config: Partial<SASjsConfig> = { const config = {
useComputeApi: false useComputeApi: false
} }

View File

@@ -64,7 +64,16 @@ export class FileUploader {
// currently only web approach is supported for file upload // currently only web approach is supported for file upload
// therefore log is part of response with debug enabled and must be parsed // therefore log is part of response with debug enabled and must be parsed
return this.requestClient return this.requestClient
.post(uploadUrl, formData, undefined, 'application/json', headers) .post(
uploadUrl,
formData,
undefined,
'application/json',
headers,
this.sasjsConfig.debug,
true,
sasJob
)
.then(async (res) => { .then(async (res) => {
if ( if (
this.sasjsConfig.serverType === ServerType.SasViya && this.sasjsConfig.serverType === ServerType.SasViya &&

View File

@@ -782,12 +782,24 @@ export class SASViyaApiClient {
jobResult = await this.requestClient.get<any>( jobResult = await this.requestClient.get<any>(
`${this.serverUrl}${resultLink}/content`, `${this.serverUrl}${resultLink}/content`,
access_token, access_token,
'text/plain' 'text/plain',
{},
debug,
true,
sasJob
) )
} }
if (debug && logLink) { if (debug && logLink) {
log = await this.requestClient log = await this.requestClient
.get<any>(`${this.serverUrl}${logLink.href}/content`, access_token) .get<any>(
`${this.serverUrl}${logLink.href}/content`,
access_token,
'application/json',
{},
debug,
true,
sasJob
)
.then((res: any) => res.result.items.map((i: any) => i.line).join('\n')) .then((res: any) => res.result.items.map((i: any) => i.line).join('\n'))
} }
if (jobStatus === 'failed') { if (jobStatus === 'failed') {

View File

@@ -50,6 +50,7 @@ export default class SASjs {
private sas9JobExecutor: JobExecutor | null = null private sas9JobExecutor: JobExecutor | null = null
constructor(config?: any) { constructor(config?: any) {
console.log('from SASjs constructor')
this.sasjsConfig = { this.sasjsConfig = {
...defaultConfig, ...defaultConfig,
...config ...config
@@ -611,6 +612,7 @@ export default class SASjs {
config.useComputeApi !== null config.useComputeApi !== null
) { ) {
if (config.useComputeApi) { if (config.useComputeApi) {
console.log(615)
return await this.computeJobExecutor!.execute( return await this.computeJobExecutor!.execute(
sasJob, sasJob,
data, data,
@@ -878,20 +880,20 @@ export default class SASjs {
}) })
} }
/**
* this method returns an array of SASjsRequest
* @returns SASjsRequest[]
*/
public getSasRequests() { public getSasRequests() {
const requests = [ console.log('from getSASRequests')
...this.webJobExecutor!.getRequests(), const requests = this.requestClient!.getRequests()
...this.computeJobExecutor!.getRequests(),
...this.jesJobExecutor!.getRequests()
]
const sortedRequests = requests.sort(compareTimestamps) const sortedRequests = requests.sort(compareTimestamps)
console.log('sortedRequests', sortedRequests)
return sortedRequests return sortedRequests
} }
public clearSasRequests() { public clearSasRequests() {
this.webJobExecutor!.clearRequests() this.requestClient!.clearRequests()
this.computeJobExecutor!.clearRequests()
this.jesJobExecutor!.clearRequests()
} }
private setupConfiguration() { private setupConfiguration() {

View File

@@ -239,7 +239,15 @@ export async function executeScript(
const resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content` const resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content`
jobResult = await requestClient jobResult = await requestClient
.get<any>(resultLink, access_token, 'text/plain') .get<any>(
resultLink,
access_token,
'text/plain',
{},
debug,
true,
jobPath
)
.catch(async (e) => { .catch(async (e) => {
if (e instanceof NotFoundError) { if (e instanceof NotFoundError) {
if (logLink) { if (logLink) {

View File

@@ -23,61 +23,43 @@ export class AuthManager {
* Logs into the SAS server with the supplied credentials. * Logs into the SAS server with the supplied credentials.
* @param username - a string representing the username. * @param username - a string representing the username.
* @param password - a string representing the password. * @param password - a string representing the password.
* @returns - a boolean `isLoggedin` and a string `username`
*/ */
public async logIn( public async logIn(username: string, password: string) {
username: string, const loginParams: any = {
password: string
): Promise<{
isLoggedIn: boolean
userName: string
}> {
const loginParams = {
_service: 'default', _service: 'default',
username, username,
password password
} }
let { this.userName = loginParams.username
isLoggedIn: isLoggedInAlready,
loginForm,
userName: currentSessionUsername
} = await this.checkSession()
if (isLoggedInAlready) { const { isLoggedIn, loginForm } = await this.checkSession()
if (currentSessionUsername === loginParams.username) {
await this.loginCallback()
this.userName = currentSessionUsername! if (isLoggedIn) {
return { await this.loginCallback()
isLoggedIn: true,
userName: this.userName return {
} isLoggedIn,
} else { userName: this.userName
this.logOut()
} }
} else this.userName = '' }
let loginResponse = await this.sendLoginRequest(loginForm, loginParams) let loginResponse = await this.sendLoginRequest(loginForm, loginParams)
let isLoggedIn = isLogInSuccess(loginResponse) let loggedIn = isLogInSuccess(loginResponse)
if (!isLoggedIn) { if (!loggedIn) {
if (isCredentialsVerifyError(loginResponse)) { if (isCredentialsVerifyError(loginResponse)) {
const newLoginForm = await this.getLoginForm(loginResponse) const newLoginForm = await this.getLoginForm(loginResponse)
loginResponse = await this.sendLoginRequest(newLoginForm, loginParams) loginResponse = await this.sendLoginRequest(newLoginForm, loginParams)
} }
const res = await this.checkSession() const currentSession = await this.checkSession()
isLoggedIn = res.isLoggedIn loggedIn = currentSession.isLoggedIn
if (isLoggedIn) this.userName = res.userName!
} else {
this.userName = loginParams.username
} }
if (isLoggedIn) { if (loggedIn) {
if (this.serverType === ServerType.Sas9) { if (this.serverType === ServerType.Sas9) {
const casAuthenticationUrl = `${this.serverUrl}/SASStoredProcess/j_spring_cas_security_check` const casAuthenticationUrl = `${this.serverUrl}/SASStoredProcess/j_spring_cas_security_check`
@@ -88,10 +70,10 @@ export class AuthManager {
} }
this.loginCallback() this.loginCallback()
} else this.userName = '' }
return { return {
isLoggedIn, isLoggedIn: !!loggedIn,
userName: this.userName userName: this.userName
} }
} }
@@ -121,21 +103,14 @@ export class AuthManager {
/** /**
* Checks whether a session is active, or login is required. * Checks whether a session is active, or login is required.
* @returns - a promise which resolves with an object containing three values * @returns - a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`.
* - a boolean `isLoggedIn`
* - a string `userName` and
* - a form `loginForm` if not loggedin.
*/ */
public async checkSession(): Promise<{ public async checkSession() {
isLoggedIn: boolean
userName?: string
loginForm?: any
}> {
//For VIYA we will send request on API endpoint. Which is faster then pinging SASJobExecution. //For VIYA we will send request on API endpoint. Which is faster then pinging SASJobExecution.
//For SAS9 we will send request on SASStoredProcess //For SAS9 we will send request on SASStoredProcess
const url = const url =
this.serverType === 'SASVIYA' this.serverType === 'SASVIYA'
? `${this.serverUrl}/identities/users/@currentUser` ? `${this.serverUrl}/identities`
: `${this.serverUrl}/SASStoredProcess` : `${this.serverUrl}/SASStoredProcess`
const { result: loginResponse } = await this.requestClient const { result: loginResponse } = await this.requestClient
@@ -145,10 +120,6 @@ export class AuthManager {
}) })
const isLoggedIn = loginResponse !== 'authErr' const isLoggedIn = loginResponse !== 'authErr'
const userName = isLoggedIn
? this.extractUserName(loginResponse)
: undefined
let loginForm = null let loginForm = null
if (!isLoggedIn) { if (!isLoggedIn) {
@@ -167,29 +138,11 @@ export class AuthManager {
return Promise.resolve({ return Promise.resolve({
isLoggedIn, isLoggedIn,
userName: userName?.toLowerCase(), userName: this.userName,
loginForm loginForm
}) })
} }
private extractUserName = (response: any): string => {
switch (this.serverType) {
case ServerType.SasViya:
return response?.id
case ServerType.Sas9:
const matched = response?.match(/"title":"Log Off [0-1a-zA-Z ]*"/)
const username = matched?.[0].slice(17, -1)
if (!username.includes(' ')) return username
return username
.split(' ')
.map((name: string) => name.slice(0, 3).toLowerCase())
.join('')
}
}
private getLoginForm(response: any) { private getLoginForm(response: any) {
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/ const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/
const matches = pattern.exec(response) const matches = pattern.exec(response)

View File

@@ -67,7 +67,7 @@ describe('AuthManager', () => {
jest.spyOn(authManager, 'checkSession').mockImplementation(() => jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
Promise.resolve({ Promise.resolve({
isLoggedIn: true, isLoggedIn: true,
userName, userName: 'test',
loginForm: 'test' loginForm: 'test'
}) })
) )
@@ -89,6 +89,7 @@ describe('AuthManager', () => {
jest.spyOn(authManager, 'checkSession').mockImplementation(() => jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
Promise.resolve({ Promise.resolve({
isLoggedIn: false, isLoggedIn: false,
userName: 'test',
loginForm: { name: 'test' } loginForm: { name: 'test' }
}) })
) )
@@ -174,7 +175,7 @@ describe('AuthManager', () => {
expect(response.isLoggedIn).toBeTruthy() expect(response.isLoggedIn).toBeTruthy()
expect(mockedAxios.get).toHaveBeenNthCalledWith( expect(mockedAxios.get).toHaveBeenNthCalledWith(
1, 1,
`http://test-server.com/identities/users/@currentUser`, `http://test-server.com/identities`,
{ {
withCredentials: true, withCredentials: true,
responseType: 'text', responseType: 'text',

View File

@@ -35,14 +35,11 @@ export class ComputeJobExecutor extends BaseJobExecutor {
expectWebout expectWebout
) )
.then((response) => { .then((response) => {
this.appendRequest(response, sasJob, config.debug) console.log('then block of compute job executor')
resolve(response.result) resolve(response.result)
}) })
.catch(async (e: Error) => { .catch(async (e: Error) => {
if (e instanceof ComputeJobExecutionError) { if (e instanceof ComputeJobExecutionError) {
this.appendRequest(e, sasJob, config.debug)
reject(new ErrorResponse(e?.message, e)) reject(new ErrorResponse(e?.message, e))
} }

View File

@@ -7,7 +7,6 @@ import {
} from '../types/errors' } from '../types/errors'
import { ExtraResponseAttributes } from '@sasjs/utils/types' import { ExtraResponseAttributes } from '@sasjs/utils/types'
import { BaseJobExecutor } from './JobExecutor' import { BaseJobExecutor } from './JobExecutor'
import { appendExtraResponseAttributes } from '../utils'
export class JesJobExecutor extends BaseJobExecutor { export class JesJobExecutor extends BaseJobExecutor {
constructor(serverUrl: string, private sasViyaApiClient: SASViyaApiClient) { constructor(serverUrl: string, private sasViyaApiClient: SASViyaApiClient) {
@@ -28,19 +27,26 @@ export class JesJobExecutor extends BaseJobExecutor {
this.sasViyaApiClient this.sasViyaApiClient
?.executeJob(sasJob, config.contextName, config.debug, data, authConfig) ?.executeJob(sasJob, config.contextName, config.debug, data, authConfig)
.then((response: any) => { .then((response: any) => {
this.appendRequest(response, sasJob, config.debug) let responseObject = {}
const responseObject = appendExtraResponseAttributes( if (extraResponseAttributes && extraResponseAttributes.length > 0) {
response, const extraAttributes = extraResponseAttributes.reduce(
extraResponseAttributes (map: any, obj: any) => ((map[obj] = response[obj]), map),
) {}
)
responseObject = {
result: response.result,
...extraAttributes
}
} else {
responseObject = response.result
}
resolve(responseObject) resolve(responseObject)
}) })
.catch(async (e: Error) => { .catch(async (e: Error) => {
if (e instanceof JobExecutionError) { if (e instanceof JobExecutionError) {
this.appendRequest(e, sasJob, config.debug)
reject(new ErrorResponse(e?.message, e)) reject(new ErrorResponse(e?.message, e))
} }

View File

@@ -15,8 +15,6 @@ export interface JobExecutor {
extraResponseAttributes?: ExtraResponseAttributes[] extraResponseAttributes?: ExtraResponseAttributes[]
) => Promise<any> ) => Promise<any>
resendWaitingRequests: () => Promise<void> resendWaitingRequests: () => Promise<void>
getRequests: () => SASjsRequest[]
clearRequests: () => void
} }
export abstract class BaseJobExecutor implements JobExecutor { export abstract class BaseJobExecutor implements JobExecutor {
@@ -46,54 +44,7 @@ export abstract class BaseJobExecutor implements JobExecutor {
return return
} }
getRequests = () => this.requests
clearRequests = () => {
this.requests = []
}
protected appendWaitingRequest(request: ExecuteFunction) { protected appendWaitingRequest(request: ExecuteFunction) {
this.waitingRequests.push(request) this.waitingRequests.push(request)
} }
protected appendRequest(response: any, program: string, debug: boolean) {
let sourceCode = ''
let generatedCode = ''
let sasWork = null
if (debug) {
if (response?.log) {
sourceCode = parseSourceCode(response.log)
generatedCode = parseGeneratedCode(response.log)
if (response?.result) {
sasWork = response.result.WORK
} else {
sasWork = response.log
}
} else if (response?.result) {
sourceCode = parseSourceCode(response.result)
generatedCode = parseGeneratedCode(response.result)
sasWork = response.result.WORK
}
}
const stringifiedResult =
typeof response?.result === 'string'
? response?.result
: JSON.stringify(response?.result, null, 2)
this.requests.push({
logFile: response?.log || stringifiedResult || response,
serviceLink: program,
timestamp: new Date(),
sourceCode,
generatedCode,
SASWORK: sasWork
})
if (this.requests.length > 20) {
this.requests.splice(0, 1)
}
}
} }

View File

@@ -1,8 +1,4 @@
import { import { ServerType } from '@sasjs/utils/types'
AuthConfig,
ExtraResponseAttributes,
ServerType
} from '@sasjs/utils/types'
import { import {
ErrorResponse, ErrorResponse,
JobExecutionError, JobExecutionError,
@@ -16,8 +12,7 @@ import { SASViyaApiClient } from '../SASViyaApiClient'
import { import {
isRelativePath, isRelativePath,
getValidJson, getValidJson,
parseSasViyaDebugResponse, parseSasViyaDebugResponse
appendExtraResponseAttributes
} from '../utils' } from '../utils'
import { BaseJobExecutor } from './JobExecutor' import { BaseJobExecutor } from './JobExecutor'
import { parseWeboutResponse } from '../utils/parseWeboutResponse' import { parseWeboutResponse } from '../utils/parseWeboutResponse'
@@ -42,9 +37,7 @@ export class WebJobExecutor extends BaseJobExecutor {
sasJob: string, sasJob: string,
data: any, data: any,
config: any, config: any,
loginRequiredCallback?: any, loginRequiredCallback?: any
authConfig?: AuthConfig,
extraResponseAttributes: ExtraResponseAttributes[] = []
) { ) {
const loginCallback = loginRequiredCallback || (() => Promise.resolve()) const loginCallback = loginRequiredCallback || (() => Promise.resolve())
const program = isRelativePath(sasJob) const program = isRelativePath(sasJob)
@@ -118,40 +111,38 @@ export class WebJobExecutor extends BaseJobExecutor {
} }
const requestPromise = new Promise((resolve, reject) => { const requestPromise = new Promise((resolve, reject) => {
this.requestClient!.post(apiUrl, formData, undefined) this.requestClient!.post(
apiUrl,
formData,
undefined,
'application/json',
{},
config.debug,
true,
sasJob
)
.then(async (res: any) => { .then(async (res: any) => {
let jsonResponse = res.result if (this.serverType === ServerType.SasViya && config.debug) {
const jsonResponse = await parseSasViyaDebugResponse(
if (config.debug) { res.result,
switch (this.serverType) { this.requestClient,
case ServerType.SasViya: this.serverUrl
jsonResponse = await parseSasViyaDebugResponse( )
res.result, resolve(jsonResponse)
this.requestClient,
this.serverUrl
)
break
case ServerType.Sas9:
jsonResponse =
typeof res.result === 'string'
? parseWeboutResponse(res.result, apiUrl)
: res.result
break
}
} }
if (this.serverType === ServerType.Sas9 && config.debug) {
let jsonResponse = res.result
if (typeof res.result === 'string')
jsonResponse = parseWeboutResponse(res.result, apiUrl)
this.appendRequest(res, sasJob, config.debug) getValidJson(jsonResponse)
resolve(res.result)
const responseObject = appendExtraResponseAttributes( }
{ result: jsonResponse }, getValidJson(res.result as string)
extraResponseAttributes resolve(res.result)
)
resolve(responseObject)
}) })
.catch(async (e: Error) => { .catch(async (e: Error) => {
if (e instanceof JobExecutionError) { if (e instanceof JobExecutionError) {
this.appendRequest(e, sasJob, config.debug)
reject(new ErrorResponse(e?.message, e)) reject(new ErrorResponse(e?.message, e))
} }
@@ -163,9 +154,7 @@ export class WebJobExecutor extends BaseJobExecutor {
sasJob, sasJob,
data, data,
config, config,
loginRequiredCallback, loginRequiredCallback
authConfig,
extraResponseAttributes
).then( ).then(
(res: any) => { (res: any) => {
resolve(res) resolve(res)

View File

@@ -8,10 +8,11 @@ import {
InternalServerError, InternalServerError,
JobExecutionError JobExecutionError
} from '../types/errors' } from '../types/errors'
import { SASjsRequest } from '../types'
import { parseWeboutResponse } from '../utils/parseWeboutResponse' import { parseWeboutResponse } from '../utils/parseWeboutResponse'
import { prefixMessage } from '@sasjs/utils/error' import { prefixMessage } from '@sasjs/utils/error'
import { SAS9AuthError } from '../types/errors/SAS9AuthError' import { SAS9AuthError } from '../types/errors/SAS9AuthError'
import { getValidJson } from '../utils' import { parseGeneratedCode, parseSourceCode } from '../utils'
export interface HttpClient { export interface HttpClient {
get<T>( get<T>(
@@ -47,6 +48,8 @@ export interface HttpClient {
} }
export class RequestClient implements HttpClient { export class RequestClient implements HttpClient {
private requests: SASjsRequest[] = []
protected csrfToken: CsrfToken = { headerName: '', value: '' } protected csrfToken: CsrfToken = { headerName: '', value: '' }
protected fileUploadCsrfToken: CsrfToken | undefined protected fileUploadCsrfToken: CsrfToken | undefined
protected httpClient: AxiosInstance protected httpClient: AxiosInstance
@@ -83,12 +86,75 @@ export class RequestClient implements HttpClient {
return this.httpClient.defaults.baseURL || '' return this.httpClient.defaults.baseURL || ''
} }
/**
* this method returns all requests, an array of SASjsRequest type
* @returns SASjsRequest[]
*/
public getRequests = () => this.requests
/**
* this method clears the requests array, i.e set to empty
*/
public clearRequests = () => {
this.requests = []
}
/**
* this method appends the response from sasjs request to requests array
* @param response - response from sasjs request
* @param program - name of program
* @param debug - a boolean that indicates whether debug was enabled or not
*/
public appendRequest = (response: any, program: string, debug: boolean) => {
console.log('from appendRequest')
let sourceCode = ''
let generatedCode = ''
let sasWork = null
if (debug) {
if (response?.log) {
sourceCode = parseSourceCode(response.log)
generatedCode = parseGeneratedCode(response.log)
if (response?.result) {
sasWork = response.result.WORK
} else {
sasWork = response.log
}
} else if (response?.result) {
sourceCode = parseSourceCode(response.result)
generatedCode = parseGeneratedCode(response.result)
sasWork = response.result.WORK
}
}
const stringifiedResult =
typeof response?.result === 'string'
? response?.result
: JSON.stringify(response?.result, null, 2)
this.requests.push({
logFile: response?.log || stringifiedResult || response,
serviceLink: program,
timestamp: new Date(),
sourceCode,
generatedCode,
SASWORK: sasWork
})
if (this.requests.length > 20) {
this.requests.splice(0, 1)
}
}
public async get<T>( public async get<T>(
url: string, url: string,
accessToken: string | undefined, accessToken: string | undefined,
contentType: string = 'application/json', contentType: string = 'application/json',
overrideHeaders: { [key: string]: string | number } = {}, overrideHeaders: { [key: string]: string | number } = {},
debug: boolean = false debug: boolean = false,
captureRequest: boolean = false,
sasJob: string = ''
): Promise<{ result: T; etag: string; status: number }> { ): Promise<{ result: T; etag: string; status: number }> {
const headers = { const headers = {
...this.getHeaders(accessToken, contentType), ...this.getHeaders(accessToken, contentType),
@@ -108,8 +174,11 @@ export class RequestClient implements HttpClient {
.get<T>(url, requestConfig) .get<T>(url, requestConfig)
.then((response) => { .then((response) => {
throwIfError(response) throwIfError(response)
const responseToReturn = this.parseResponse<T>(response)
return this.parseResponse<T>(response) if (captureRequest) {
this.appendRequest(responseToReturn, sasJob, debug)
}
return responseToReturn
}) })
.catch(async (e) => { .catch(async (e) => {
return await this.handleError( return await this.handleError(
@@ -135,7 +204,10 @@ export class RequestClient implements HttpClient {
data: any, data: any,
accessToken: string | undefined, accessToken: string | undefined,
contentType = 'application/json', contentType = 'application/json',
overrideHeaders: { [key: string]: string | number } = {} overrideHeaders: { [key: string]: string | number } = {},
debug: boolean = false,
captureRequest: boolean = false,
sasJob: string = ''
): Promise<{ result: T; etag: string }> { ): Promise<{ result: T; etag: string }> {
const headers = { const headers = {
...this.getHeaders(accessToken, contentType), ...this.getHeaders(accessToken, contentType),
@@ -146,7 +218,11 @@ 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) const responseToReturn = this.parseResponse<T>(response)
if (captureRequest) {
this.appendRequest(responseToReturn, sasJob, debug)
}
return responseToReturn
}) })
.catch(async (e) => { .catch(async (e) => {
return await this.handleError(e, () => return await this.handleError(e, () =>
@@ -499,60 +575,46 @@ export const throwIfError = (response: AxiosResponse) => {
} }
const parseError = (data: string) => { const parseError = (data: string) => {
if (!data) return null
try { try {
const responseJson = JSON.parse(data?.replace(/[\n\r]/g, ' ')) const responseJson = JSON.parse(data?.replace(/[\n\r]/g, ' '))
if (responseJson.errorCode && responseJson.message) { return responseJson.errorCode && responseJson.message
return new JobExecutionError( ? new JobExecutionError(
responseJson.errorCode, responseJson.errorCode,
responseJson.message, responseJson.message,
data?.replace(/[\n\r]/g, ' ') data?.replace(/[\n\r]/g, ' ')
)
}
} catch (_) {}
try {
const hasError = data?.includes('{"errorCode')
if (hasError) {
const parts = data.split('{"errorCode')
if (parts.length > 1) {
const error = '{"errorCode' + parts[1].split('"}')[0] + '"}'
const errorJson = JSON.parse(error.replace(/[\n\r]/g, ' '))
return new JobExecutionError(
errorJson.errorCode,
errorJson.message,
data?.replace(/[\n\r]/g, '\n')
) )
: null
} catch (_) {
try {
const hasError = data?.includes('{"errorCode')
if (hasError) {
const parts = data.split('{"errorCode')
if (parts.length > 1) {
const error = '{"errorCode' + parts[1].split('"}')[0] + '"}'
const errorJson = JSON.parse(error.replace(/[\n\r]/g, ' '))
return new JobExecutionError(
errorJson.errorCode,
errorJson.message,
data?.replace(/[\n\r]/g, '\n')
)
}
return null
} }
} try {
} catch (_) {} const hasError = !!data?.match(/stored process not found: /i)
if (hasError) {
try { const parts = data.split(/stored process not found: /i)
const hasError = !!data?.match(/stored process not found: /i) if (parts.length > 1) {
if (hasError) { const storedProcessPath = parts[1].split('<i>')[1].split('</i>')[0]
const parts = data.split(/stored process not found: /i) const message = `Stored process not found: ${storedProcessPath}`
if (parts.length > 1) { return new JobExecutionError(404, message, '')
const storedProcessPath = parts[1].split('<i>')[1].split('</i>')[0] }
const message = `Stored process not found: ${storedProcessPath}` }
return new JobExecutionError(404, message, '') } catch (_) {
return null
} }
} catch (_) {
return null
} }
} catch (_) {} }
try {
const hasError =
!!data?.match(/Stored Process Error/i) &&
!!data?.match(/This request completed with errors./i)
if (hasError) {
const parts = data.split('<h2>SAS Log</h2>')
if (parts.length > 1) {
const log = parts[1].split('<pre>')[1].split('</pre>')[0]
const message = `This request completed with errors.`
return new JobExecutionError(404, message, log)
}
}
} catch (_) {}
return null
} }

View File

@@ -34,7 +34,8 @@ const prepareFilesAndParams = () => {
describe('FileUploader', () => { describe('FileUploader', () => {
const config: SASjsConfig = { const config: SASjsConfig = {
...new SASjsConfig(), ...new SASjsConfig(),
appLoc: '/sample/apploc' appLoc: '/sample/apploc',
debug: false
} }
const fileUploader = new FileUploader( const fileUploader = new FileUploader(

View File

@@ -33,4 +33,18 @@ describe('jsonValidator', () => {
} }
expect(test).toThrow(JsonParseArrayError) expect(test).toThrow(JsonParseArrayError)
}) })
it('should throw an error when null is passed', () => {
const test = () => {
getValidJson(null as any)
}
expect(test).toThrow(InvalidJsonError)
})
it('should throw an error when undefined is passed', () => {
const test = () => {
getValidJson(undefined as any)
}
expect(test).toThrow(InvalidJsonError)
})
}) })

View File

@@ -1,22 +0,0 @@
import { ExtraResponseAttributes } from '@sasjs/utils/types'
export async function appendExtraResponseAttributes(
response: any,
extraResponseAttributes: ExtraResponseAttributes[]
) {
let responseObject = {}
if (extraResponseAttributes?.length) {
const extraAttributes = extraResponseAttributes.reduce(
(map: any, obj: any) => ((map[obj] = response[obj]), map),
{}
)
responseObject = {
result: response.result,
...extraAttributes
}
} else responseObject = response.result
return responseObject
}

View File

@@ -6,6 +6,8 @@ import { JsonParseArrayError, InvalidJsonError } from '../types/errors'
*/ */
export const getValidJson = (str: string | object) => { export const getValidJson = (str: string | object) => {
try { try {
if (str === null || str === undefined) throw new InvalidJsonError()
if (Array.isArray(str)) throw new JsonParseArrayError() if (Array.isArray(str)) throw new JsonParseArrayError()
if (typeof str === 'object') return str if (typeof str === 'object') return str

View File

@@ -15,4 +15,3 @@ export * from './parseWeboutResponse'
export * from './fetchLogByChunks' export * from './fetchLogByChunks'
export * from './getValidJson' export * from './getValidJson'
export * from './parseViyaDebugResponse' export * from './parseViyaDebugResponse'
export * from './appendExtraResponseAttributes'