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

Compare commits

..

19 Commits

Author SHA1 Message Date
Yury Shkoda
8e9cf98985 fix(deps): semantic-release, @sasjs/test-framework 2022-06-24 15:47:34 +03:00
Yury Shkoda
c3a0ad1f41 fix(workflow): added actions/setup-node@v2 2022-06-20 20:43:11 +03:00
Yury Shkoda
9b6a42e412 fix(workflows): fixed npmpublish workflow 2022-06-20 20:33:44 +03:00
Yury Shkoda
dd788ae423 chore(lint): fixed linting 2022-06-20 19:48:42 +03:00
Yury Shkoda
a113c95441 chore(deps): added prettier dev dependency 2022-06-20 19:36:12 +03:00
Yury Shkoda
489947bcae chore(lint): fixed linting issues 2022-06-20 19:26:29 +03:00
Yury Shkoda
1596173dda fix(deps): regenerated package-lock 2022-06-20 19:24:09 +03:00
Allan Bowe
bb1b2ddcb2 Merge pull request #719 from sasjs/issue-718
fix: parse the logs before appending the request to request array whe…
2022-06-20 17:53:44 +02:00
a3cc274ef1 chore: no need to appendRequest from then block when there is jobExecutionError 2022-06-10 15:38:03 +05:00
451d0906fa chore: update error message 2022-06-10 15:31:09 +05:00
dd6b89b0d0 fix: parse the logs before appending the request to request array when server type is sasjs 2022-06-09 23:11:47 +05:00
Allan Bowe
f0525c5796 Merge pull request #674 from sasjs/numeric-missing
fix: special missings accept - regular missing .
2022-05-23 14:11:30 +03:00
Allan Bowe
01bcfe176a Merge pull request #712 from sasjs/issue-711
When expires csrf token, re-fetch and empty webout fix
2022-05-23 14:03:42 +03:00
076ed1cc7a chore: added special missing test 2022-05-23 12:59:48 +02:00
0a3289b577 chore(git): Merge branch 'master' into numeric-missing 2022-05-23 12:47:13 +02:00
Allan Bowe
cbb55ff426 chore: updating README in server tests to deploy backend 2022-05-20 17:43:49 +00:00
6d47174a5e fix: csrf token fetch and empty webout promise finish 2022-05-20 16:43:46 +02:00
d2ea67e5d6 chore: docs 2022-03-08 18:32:45 +01:00
b0df4cb7ee fix: special missings accept - regular missing . 2022-03-08 18:28:48 +01:00
32 changed files with 17170 additions and 22470 deletions

View File

@@ -11,10 +11,19 @@ on:
jobs: jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy:
matrix:
node-version: [lts/fermium]
steps: steps:
- name: Checkout - uses: actions/checkout@v2
uses: actions/checkout@v2 - name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Install Dependencies - name: Install Dependencies
run: npm ci run: npm ci
@@ -23,7 +32,7 @@ jobs:
- name: Build Project - name: Build Project
run: npm run build run: npm run build
- name: Semantic Release - name: Semantic Release
uses: cycjimmy/semantic-release-action@v2 uses: cycjimmy/semantic-release-action@v2
env: env:

View File

@@ -155,7 +155,7 @@ The SAS type (char/numeric) of the values is determined according to a set of ru
* If the values are numeric, the SAS type is numeric * If the values are numeric, the SAS type is numeric
* If the values are all string, the SAS type is character * If the values are all string, the SAS type is character
* If the values contain a single character (a-Z + underscore) AND a numeric, then the SAS type is numeric (with special missing values). * If the values contain a single character (a-Z + underscore + .) AND a numeric, then the SAS type is numeric (with special missing values).
* `null` is set to either '.' or '' depending on the assigned or derived type per the above rules. If entire column is `null` then the type will be numeric. * `null` is set to either '.' or '' depending on the assigned or derived type per the above rules. If entire column is `null` then the type will be numeric.
The following table illustrates the formats applied to columns under various scenarios: The following table illustrates the formats applied to columns under various scenarios:

10253
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -44,18 +44,16 @@
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@cypress/webpack-preprocessor": "5.9.1", "@cypress/webpack-preprocessor": "5.9.1",
"@types/form-data": "2.5.0",
"cypress": "7.7.0",
"typedoc-neo-theme": "1.1.1",
"typedoc-plugin-external-module-name": "4.0.6",
"@types/axios": "0.14.0", "@types/axios": "0.14.0",
"@types/express": "4.17.13", "@types/express": "4.17.13",
"@types/form-data": "2.5.0",
"@types/jest": "27.4.0", "@types/jest": "27.4.0",
"@types/mime": "2.0.3", "@types/mime": "2.0.3",
"@types/pem": "1.9.6", "@types/pem": "1.9.6",
"@types/tough-cookie": "4.0.1", "@types/tough-cookie": "4.0.1",
"copyfiles": "2.4.1", "copyfiles": "2.4.1",
"cp": "0.2.0", "cp": "0.2.0",
"cypress": "7.7.0",
"dotenv": "16.0.0", "dotenv": "16.0.0",
"express": "4.17.3", "express": "4.17.3",
"jest": "27.4.7", "jest": "27.4.7",
@@ -63,15 +61,18 @@
"node-polyfill-webpack-plugin": "1.1.4", "node-polyfill-webpack-plugin": "1.1.4",
"path": "0.12.7", "path": "0.12.7",
"pem": "1.14.6", "pem": "1.14.6",
"prettier": "2.7.1",
"process": "0.11.10", "process": "0.11.10",
"rimraf": "3.0.2", "rimraf": "3.0.2",
"semantic-release": "18.0.0", "semantic-release": "19.0.3",
"terser-webpack-plugin": "5.3.1", "terser-webpack-plugin": "5.3.1",
"ts-jest": "27.1.3", "ts-jest": "27.1.3",
"ts-loader": "9.2.6", "ts-loader": "9.2.6",
"tslint": "6.1.3", "tslint": "6.1.3",
"tslint-config-prettier": "1.18.0", "tslint-config-prettier": "1.18.0",
"typedoc": "0.22.11", "typedoc": "0.22.11",
"typedoc-neo-theme": "1.1.1",
"typedoc-plugin-external-module-name": "4.0.6",
"typedoc-plugin-rename-defaults": "0.4.0", "typedoc-plugin-rename-defaults": "0.4.0",
"typescript": "4.5.5", "typescript": "4.5.5",
"webpack": "5.69.0", "webpack": "5.69.0",

View File

@@ -60,7 +60,7 @@ If you'd like to deploy just `sasjs-tests` without changing the adapter version,
The below services need to be created on your SAS server, at the location specified as the `appLoc` in the SASjs configuration. The below services need to be created on your SAS server, at the location specified as the `appLoc` in the SASjs configuration.
### SAS 9 The code below will work on ALL SAS platforms (Viya, SAS 9 EBI, SASjs Server).
```sas ```sas
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas"; filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
@@ -70,76 +70,32 @@ 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,missing=STRING,showmeta=YES) %end; %if %symexist(sasjs_tables) %then %do i=1 %to %sysfunc(countw(&sasjs_tables));
%mend; %x()
%webout(CLOSE)
;;;;
%mm_createwebservice(path=/Public/app/common,name=sendObj)
parmcards4;
%webout(FETCH)
%webout(OPEN)
%macro x();
%do i=1 %to &_webin_file_count; %webout(ARR,&&_webin_name&i,missing=STRING,showmeta=YES) %end;
%mend; %x()
%webout(CLOSE)
;;;;
%mm_createwebservice(path=/Public/app/common,name=sendArr)
parmcards4;
data work.macvars;
set sashelp.vmacro;
run;
%webout(OPEN)
%webout(OBJ,macvars)
%webout(CLOSE)
;;;;
%mm_createwebservice(path=/Public/app/common,name=sendMacVars)
parmcards4;
let he who hath understanding, reckon the number of the beast
;;;;
%mm_createwebservice(path=/Public/app/common,name=makeErr)
parmcards4;
%webout(OPEN)
data _null_;
file _webout;
put ' the discovery channel ';
run;
%webout(CLOSE)
;;;;
%mm_createwebservice(path=/Public/app/common,name=invalidJSON)
```
### SAS Viya
```sas
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
filename ft15f001 temp lrecl=1000;
parmcards4;
%webout(FETCH)
%webout(OPEN)
%macro x();
%do i=1 %to %sysfunc(countw(&sasjs_tables));
%let table=%scan(&sasjs_tables,&i); %let table=%scan(&sasjs_tables,&i);
%webout(OBJ,&table,missing=STRING,showmeta=YES) %webout(OBJ,&table,missing=STRING,showmeta=YES)
%end; %end;
%mend; %else %do i=1 %to &_webin_file_count;
%x() %webout(OBJ,&&_webin_name&i,missing=STRING,showmeta=YES)
%end;
%mend; %x()
%webout(CLOSE) %webout(CLOSE)
;;;; ;;;;
%mp_createwebservice(path=/Public/app/common,name=sendObj) %mx_createwebservice(path=/Public/app/common,name=sendObj)
parmcards4; parmcards4;
%webout(FETCH) %webout(FETCH)
%webout(OPEN) %webout(OPEN)
%macro x(); %macro x();
%do i=1 %to %sysfunc(countw(&sasjs_tables)); %if %symexist(sasjs_tables) %then %do i=1 %to %sysfunc(countw(&sasjs_tables));
%let table=%scan(&sasjs_tables,&i); %let table=%scan(&sasjs_tables,&i);
%webout(ARR,&table,missing=STRING,showmeta=YES) %webout(ARR,&table,missing=STRING,showmeta=YES)
%end; %end;
%mend; %else %do i=1 %to &_webin_file_count;
%x() %webout(ARR,&&_webin_name&i,missing=STRING,showmeta=YES)
%end;
%mend; %x()
%webout(CLOSE) %webout(CLOSE)
;;;; ;;;;
%mp_createwebservice(path=/Public/app/common,name=sendArr) %mx_createwebservice(path=/Public/app/common,name=sendArr)
parmcards4; parmcards4;
data work.macvars; data work.macvars;
set sashelp.vmacro; set sashelp.vmacro;
@@ -148,14 +104,14 @@ parmcards4;
%webout(OBJ,macvars) %webout(OBJ,macvars)
%webout(CLOSE) %webout(CLOSE)
;;;; ;;;;
%mp_createwebservice(path=/Public/app/common,name=sendMacVars) %mx_createwebservice(path=/Public/app/common,name=sendMacVars)
parmcards4; parmcards4;
If you can keep your head when all about you If you can keep your head when all about you
Are losing theirs and blaming it on you, Are losing theirs and blaming it on you,
If you can trust yourself when all men doubt you, If you can trust yourself when all men doubt you,
But make allowance for their doubting too; But make allowance for their doubting too;
;;;; ;;;;
%mp_createwebservice(path=/Public/app/common,name=makeErr) %mx_createwebservice(path=/Public/app/common,name=makeErr)
parmcards4; parmcards4;
%webout(OPEN) %webout(OPEN)
data _null_; data _null_;
@@ -164,7 +120,7 @@ data _null_;
run; run;
%webout(CLOSE) %webout(CLOSE)
;;;; ;;;;
%mp_createwebservice(path=/Public/app/common,name=invalidJSON) %mx_createwebservice(path=/Public/app/common,name=invalidJSON)
``` ```
You should now be able to access the tests in your browser at the deployed path on your server. You should now be able to access the tests in your browser at the deployed path on your server.

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@sasjs/adapter": "file:../build/sasjs-adapter-5.0.0.tgz", "@sasjs/adapter": "file:../build/sasjs-adapter-5.0.0.tgz",
"@sasjs/test-framework": "^1.4.3", "@sasjs/test-framework": "^1.5.6",
"@types/jest": "^26.0.20", "@types/jest": "^26.0.20",
"@types/node": "^14.14.41", "@types/node": "^14.14.41",
"@types/react": "^17.0.1", "@types/react": "^17.0.1",
@@ -14,7 +14,7 @@
"react": "^17.0.1", "react": "^17.0.1",
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "^4.0.2", "react-scripts": "^5.0.1",
"typescript": "^4.1.3" "typescript": "^4.1.3"
}, },
"scripts": { "scripts": {
@@ -43,6 +43,6 @@
] ]
}, },
"devDependencies": { "devDependencies": {
"node-sass": "^6.0.1" "node-sass": "^7.0.1"
} }
} }

View File

@@ -9,7 +9,7 @@ const Login = (): ReactElement<{}> => {
const appContext = useContext(AppContext) const appContext = useContext(AppContext)
const handleSubmit = useCallback( const handleSubmit = useCallback(
(e) => { (e: any) => {
e.preventDefault() e.preventDefault()
appContext.adapter.logIn(username, password).then((res) => { appContext.adapter.logIn(username, password).then((res) => {
appContext.setIsLoggedIn(res.isLoggedIn) appContext.setIsLoggedIn(res.isLoggedIn)
@@ -28,7 +28,7 @@ const Login = (): ReactElement<{}> => {
placeholder="User Name" placeholder="User Name"
value={username} value={username}
required required
onChange={(e) => setUsername(e.target.value)} onChange={(e: any) => setUsername(e.target.value)}
/> />
</div> </div>
<div className="row"> <div className="row">
@@ -38,7 +38,7 @@ const Login = (): ReactElement<{}> => {
type="password" type="password"
value={password} value={password}
required required
onChange={(e) => setPassword(e.target.value)} onChange={(e: any) => setPassword(e.target.value)}
/> />
</div> </div>
<button type="submit" className="submit-button"> <button type="submit" className="submit-button">

View File

@@ -86,7 +86,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
'Should error out with long string values over 32765 characters', 'Should error out with long string values over 32765 characters',
test: () => { test: () => {
const data = getLongStringData(32767) const data = getLongStringData(32767)
return adapter.request('common/sendArr', data).catch((e) => e) return adapter.request('common/sendArr', data).catch((e: any) => e)
}, },
assertion: (error: any) => { assertion: (error: any) => {
return !!error && !!error.error && !!error.error.message return !!error && !!error.error && !!error.error.message
@@ -182,7 +182,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
const invalidData: any = { const invalidData: any = {
'1InvalidTable': [{ col1: 42 }] '1InvalidTable': [{ col1: 42 }]
} }
return adapter.request('common/sendObj', invalidData).catch((e) => e) return adapter
.request('common/sendObj', invalidData)
.catch((e: any) => e)
}, },
assertion: (error: any) => assertion: (error: any) =>
!!error && !!error.error && !!error.error.message !!error && !!error.error && !!error.error.message
@@ -194,7 +196,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
const invalidData: any = { const invalidData: any = {
'an invalidTable': [{ col1: 42 }] 'an invalidTable': [{ col1: 42 }]
} }
return adapter.request('common/sendObj', invalidData).catch((e) => e) return adapter
.request('common/sendObj', invalidData)
.catch((e: any) => e)
}, },
assertion: (error: any) => assertion: (error: any) =>
!!error && !!error.error && !!error.error.message !!error && !!error.error && !!error.error.message
@@ -206,7 +210,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
const invalidData: any = { const invalidData: any = {
'anInvalidTable#': [{ col1: 42 }] 'anInvalidTable#': [{ col1: 42 }]
} }
return adapter.request('common/sendObj', invalidData).catch((e) => e) return adapter
.request('common/sendObj', invalidData)
.catch((e: any) => e)
}, },
assertion: (error: any) => assertion: (error: any) =>
!!error && !!error.error && !!error.error.message !!error && !!error.error && !!error.error.message
@@ -219,7 +225,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: [{ col1: 42 }] xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: [{ col1: 42 }]
} }
return adapter.request('common/sendObj', invalidData).catch((e) => e) return adapter
.request('common/sendObj', invalidData)
.catch((e: any) => e)
}, },
assertion: (error: any) => assertion: (error: any) =>
!!error && !!error.error && !!error.error.message !!error && !!error.error && !!error.error.message
@@ -231,7 +239,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
const invalidData: any = { const invalidData: any = {
inData: [[{ data: 'value' }]] inData: [[{ data: 'value' }]]
} }
return adapter.request('common/sendObj', invalidData).catch((e) => e) return adapter
.request('common/sendObj', invalidData)
.catch((e: any) => e)
}, },
assertion: (error: any) => assertion: (error: any) =>
!!error && !!error.error && !!error.error.message !!error && !!error.error && !!error.error.message
@@ -265,7 +275,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
test: () => { test: () => {
return adapter return adapter
.request('common/sendObj', getLongStringData(32767)) .request('common/sendObj', getLongStringData(32767))
.catch((e) => e) .catch((e: any) => e)
}, },
assertion: (error: any) => { assertion: (error: any) => {
return !!error && !!error.error && !!error.error.message return !!error && !!error.error && !!error.error.message

View File

@@ -9,7 +9,7 @@ export const assert = (
} else { } else {
result = expression() result = expression()
} }
} catch (e) { } catch (e: any) {
console.error(message) console.error(message)
throw new Error(message) throw new Error(message)
} }

View File

@@ -31,7 +31,7 @@ describe('SASViyaApiClient', () => {
.mockImplementation(() => Promise.reject('Not Found')) .mockImplementation(() => Promise.reject('Not Found'))
const error = await sasViyaApiClient const error = await sasViyaApiClient
.createFolder('test', '/foo') .createFolder('test', '/foo')
.catch((e) => e) .catch((e: any) => e)
expect(error).toBeInstanceOf(RootFolderNotFoundError) expect(error).toBeInstanceOf(RootFolderNotFoundError)
}) })
}) })

View File

@@ -240,7 +240,7 @@ export async function executeScript(
jobResult = await requestClient jobResult = await requestClient
.get<any>(resultLink, access_token, 'text/plain') .get<any>(resultLink, access_token, 'text/plain')
.catch(async (e) => { .catch(async (e: any) => {
if (e instanceof NotFoundError) { if (e instanceof NotFoundError) {
if (logLink) { if (logLink) {
const logUrl = `${logLink.href}/content` const logUrl = `${logLink.href}/content`
@@ -271,7 +271,7 @@ export async function executeScript(
}) })
return { result: jobResult?.result, log } return { result: jobResult?.result, log }
} catch (e) { } catch (e: any) {
interface HttpError { interface HttpError {
status: number status: number
} }

View File

@@ -80,7 +80,7 @@ describe('executeScript', () => {
'test', 'test',
['%put hello'], ['%put hello'],
'test context' 'test context'
).catch((e) => e) ).catch((e: any) => e)
expect(error).toContain('Error while getting session.') expect(error).toContain('Error while getting session.')
}) })
@@ -128,7 +128,7 @@ describe('executeScript', () => {
false, false,
defaultPollOptions, defaultPollOptions,
true true
).catch((e) => e) ).catch((e: any) => e)
expect(error).toContain('Error while getting session variable.') expect(error).toContain('Error while getting session variable.')
}) })
@@ -297,7 +297,7 @@ describe('executeScript', () => {
false, false,
defaultPollOptions, defaultPollOptions,
true true
).catch((e) => e) ).catch((e: any) => e)
expect(error).toContain('Error while posting job') expect(error).toContain('Error while posting job')
}) })
@@ -367,7 +367,7 @@ describe('executeScript', () => {
true, true,
defaultPollOptions, defaultPollOptions,
true true
).catch((e) => e) ).catch((e: any) => e)
expect(error).toContain('Error while polling job status.') expect(error).toContain('Error while polling job status.')
}) })
@@ -393,7 +393,7 @@ describe('executeScript', () => {
true, true,
defaultPollOptions, defaultPollOptions,
true true
).catch((e) => e) ).catch((e: any) => e)
expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith( expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith(
requestClient, requestClient,
@@ -468,7 +468,7 @@ describe('executeScript', () => {
true, true,
defaultPollOptions, defaultPollOptions,
true true
).catch((e) => e) ).catch((e: any) => e)
expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith( expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith(
requestClient, requestClient,
@@ -501,7 +501,7 @@ describe('executeScript', () => {
true, true,
defaultPollOptions, defaultPollOptions,
true true
).catch((e) => e) ).catch((e: any) => e)
expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith( expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith(
requestClient, requestClient,
@@ -561,7 +561,7 @@ describe('executeScript', () => {
true, true,
defaultPollOptions, defaultPollOptions,
true true
).catch((e) => e) ).catch((e: any) => e)
expect(requestClient.get).toHaveBeenCalledWith( expect(requestClient.get).toHaveBeenCalledWith(
`/compute/sessions/${mockSession.id}/filerefs/_webout/content`, `/compute/sessions/${mockSession.id}/filerefs/_webout/content`,
@@ -622,7 +622,7 @@ describe('executeScript', () => {
true, true,
defaultPollOptions, defaultPollOptions,
true true
).catch((e) => e) ).catch((e: any) => e)
expect(error).toContain('Error while clearing session.') expect(error).toContain('Error while clearing session.')
}) })

View File

@@ -59,7 +59,7 @@ describe('pollJobState', () => {
false, false,
undefined, undefined,
defaultPollOptions defaultPollOptions
).catch((e) => e) ).catch((e: any) => e)
expect((error as Error).message).toContain('Job state link was not found.') expect((error as Error).message).toContain('Job state link was not found.')
}) })
@@ -238,7 +238,7 @@ describe('pollJobState', () => {
false, false,
undefined, undefined,
defaultPollOptions defaultPollOptions
).catch((e) => e) ).catch((e: any) => e)
expect(error.message).toEqual( expect(error.message).toEqual(
'Error while polling job state for job j0b: Status Error' 'Error while polling job state for job j0b: Status Error'

View File

@@ -17,7 +17,7 @@ describe('saveLog', () => {
it('should throw an error when a valid access token is not provided', async () => { it('should throw an error when a valid access token is not provided', async () => {
const error = await saveLog(mockJob, requestClient, 0, 100, stream).catch( const error = await saveLog(mockJob, requestClient, 0, 100, stream).catch(
(e) => e (e: any) => e
) )
expect(error.message).toContain( expect(error.message).toContain(
@@ -33,7 +33,7 @@ describe('saveLog', () => {
100, 100,
stream, stream,
't0k3n' 't0k3n'
).catch((e) => e) ).catch((e: any) => e)
expect(error.message).toContain( expect(error.message).toContain(
`Log URL for job ${mockJob.id} was not found.` `Log URL for job ${mockJob.id} was not found.`

View File

@@ -30,7 +30,7 @@ describe('uploadTables', () => {
.mockImplementation(() => 'ERROR: LARGE STRING LENGTH') .mockImplementation(() => 'ERROR: LARGE STRING LENGTH')
const error = await uploadTables(requestClient, data, 't0k3n').catch( const error = await uploadTables(requestClient, data, 't0k3n').catch(
(e) => e (e: any) => e
) )
expect(requestClient.uploadFile).not.toHaveBeenCalled() expect(requestClient.uploadFile).not.toHaveBeenCalled()
@@ -46,7 +46,7 @@ describe('uploadTables', () => {
.mockImplementation(() => Promise.reject('Upload Error')) .mockImplementation(() => Promise.reject('Upload Error'))
const error = await uploadTables(requestClient, data, 't0k3n').catch( const error = await uploadTables(requestClient, data, 't0k3n').catch(
(e) => e (e: any) => e
) )
expect(error).toContain('Error while uploading file.') expect(error).toContain('Error while uploading file.')

View File

@@ -28,7 +28,7 @@ describe('writeStream', () => {
jest jest
.spyOn(stream, 'write') .spyOn(stream, 'write')
.mockImplementation((_, callback) => callback(new Error('Test Error'))) .mockImplementation((_, callback) => callback(new Error('Test Error')))
const error = await writeStream(stream, content).catch((e) => e) const error = await writeStream(stream, content).catch((e: any) => e)
expect(error.message).toEqual('Test Error') expect(error.message).toEqual('Test Error')
}) })

View File

@@ -4,7 +4,7 @@ export const writeStream = async (
stream: WriteStream, stream: WriteStream,
content: string content: string
): Promise<void> => ): Promise<void> =>
stream.write(content + '\n', (e) => { stream.write(content + '\n', (e: any) => {
if (e) return Promise.reject(e) if (e) return Promise.reject(e)
return Promise.resolve() return Promise.resolve()

View File

@@ -53,7 +53,7 @@ describe('getAccessTokenForSasjs', () => {
requestClient, requestClient,
authConfig.client, authConfig.client,
authConfig.refresh_token authConfig.refresh_token
).catch((e) => e) ).catch((e: any) => e)
expect(error).toContain('Error while getting access token') expect(error).toContain('Error while getting access token')
}) })

View File

@@ -64,7 +64,7 @@ describe('getAccessTokenForViya', () => {
authConfig.client, authConfig.client,
authConfig.secret, authConfig.secret,
authConfig.refresh_token authConfig.refresh_token
).catch((e) => e) ).catch((e: any) => e)
expect(error).toContain('Error while getting access token') expect(error).toContain('Error while getting access token')
}) })

View File

@@ -62,7 +62,9 @@ describe('getTokens', () => {
const expectedError = const expectedError =
'Unable to obtain new access token. Your refresh token has expired.' 'Unable to obtain new access token. Your refresh token has expired.'
const error = await getTokens(requestClient, authConfig).catch((e) => e) const error = await getTokens(requestClient, authConfig).catch(
(e: any) => e
)
expect(error.message).toEqual(expectedError) expect(error.message).toEqual(expectedError)
}) })

View File

@@ -35,7 +35,7 @@ describe('refreshTokensForSasjs', () => {
const error = await refreshTokensForSasjs( const error = await refreshTokensForSasjs(
requestClient, requestClient,
refresh_token refresh_token
).catch((e) => e) ).catch((e: any) => e)
expect(error).toContain('Error while refreshing tokens') expect(error).toContain('Error while refreshing tokens')
}) })

View File

@@ -63,7 +63,7 @@ describe('refreshTokensForViya', () => {
authConfig.client, authConfig.client,
authConfig.secret, authConfig.secret,
authConfig.refresh_token authConfig.refresh_token
).catch((e) => e) ).catch((e: any) => e)
expect(error).toContain('Error while refreshing tokens') expect(error).toContain('Error while refreshing tokens')
}) })

View File

@@ -7,7 +7,6 @@ describe('generateFileUploadForm', () => {
} }
const BlobMock = jest.fn() const BlobMock = jest.fn()
;(global as any).FormData = FormDataMock ;(global as any).FormData = FormDataMock
;(global as any).Blob = BlobMock ;(global as any).Blob = BlobMock
}) })

View File

@@ -169,15 +169,23 @@ export class WebJobExecutor extends BaseJobExecutor {
? res.result.log.map((logLine: any) => logLine.line).join('\n') ? res.result.log.map((logLine: any) => logLine.line).join('\n')
: res.result.log : res.result.log
const resObj = res const resObj =
this.serverType === ServerType.Sasjs
? {
result: res.result._webout,
log: parsedSasjsServerLog
}
: res
if (this.serverType === ServerType.Sasjs) { if (
if (res.result._webout < 1) this.serverType === ServerType.Sasjs &&
throw new JobExecutionError( res.result._webout.length < 1
0, ) {
'Job execution failed', throw new JobExecutionError(
parsedSasjsServerLog 0,
) `No webout was returned by job ${program}. Server type is SASJS and the calling function is WebJobExecutor. Please check the SAS log for more info.`,
parsedSasjsServerLog
)
} }
this.requestClient!.appendRequest(resObj, sasJob, config.debug) this.requestClient!.appendRequest(resObj, sasJob, config.debug)

View File

@@ -19,6 +19,7 @@ import {
parseSourceCode, parseSourceCode,
createAxiosInstance createAxiosInstance
} from '../utils' } from '../utils'
import { InvalidCsrfError } from '../types/errors/InvalidCsrfError'
export interface HttpClient { export interface HttpClient {
get<T>( get<T>(
@@ -206,7 +207,7 @@ export class RequestClient implements HttpClient {
return this.parseResponse<T>(response) return this.parseResponse<T>(response)
}) })
.catch(async (e) => { .catch(async (e: any) => {
return await this.handleError( return await this.handleError(
e, e,
() => () =>
@@ -247,7 +248,7 @@ export class RequestClient implements HttpClient {
return this.parseResponse<T>(response) return this.parseResponse<T>(response)
}) })
.catch(async (e) => { .catch(async (e: any) => {
return await this.handleError(e, () => return await this.handleError(e, () =>
this.post<T>(url, data, accessToken, contentType, overrideHeaders) this.post<T>(url, data, accessToken, contentType, overrideHeaders)
) )
@@ -271,7 +272,7 @@ export class RequestClient implements HttpClient {
throwIfError(response) throwIfError(response)
return this.parseResponse<T>(response) return this.parseResponse<T>(response)
}) })
.catch(async (e) => { .catch(async (e: any) => {
return await this.handleError(e, () => return await this.handleError(e, () =>
this.put<T>(url, data, accessToken, overrideHeaders) this.put<T>(url, data, accessToken, overrideHeaders)
) )
@@ -290,7 +291,7 @@ export class RequestClient implements HttpClient {
throwIfError(response) throwIfError(response)
return this.parseResponse<T>(response) return this.parseResponse<T>(response)
}) })
.catch(async (e) => { .catch(async (e: any) => {
return await this.handleError(e, () => this.delete<T>(url, accessToken)) return await this.handleError(e, () => this.delete<T>(url, accessToken))
}) })
} }
@@ -308,7 +309,7 @@ export class RequestClient implements HttpClient {
throwIfError(response) throwIfError(response)
return this.parseResponse<T>(response) return this.parseResponse<T>(response)
}) })
.catch(async (e) => { .catch(async (e: any) => {
return await this.handleError(e, () => return await this.handleError(e, () =>
this.patch<T>(url, data, accessToken) this.patch<T>(url, data, accessToken)
) )
@@ -498,6 +499,24 @@ export class RequestClient implements HttpClient {
throw e throw e
} }
if (e instanceof InvalidCsrfError) {
// Fetching root will inject CSRF token in cookie
await this.httpClient
.get('/', {
withCredentials: true
})
.catch((err) => {
throw prefixMessage(err, 'Error while re-fetching CSRF token.')
})
return await callback().catch((err: any) => {
throw prefixMessage(
err,
'Error while executing callback in handleError. '
)
})
}
if (response?.status === 403 || response?.status === 449) { if (response?.status === 403 || response?.status === 449) {
this.parseAndSetCsrfToken(response) this.parseAndSetCsrfToken(response)
@@ -584,9 +603,17 @@ export class RequestClient implements HttpClient {
export const throwIfError = (response: AxiosResponse) => { export const throwIfError = (response: AxiosResponse) => {
switch (response.status) { switch (response.status) {
case 400: case 400:
if (typeof response.data === 'object') { if (
typeof response.data === 'object' &&
response.data.error === 'invalid_grant'
) {
// In SASVIYA when trying to get access token, if auth code is wrong status code will be 400 so in such case we return login required error.
throw new LoginRequiredError(response.data) throw new LoginRequiredError(response.data)
} }
if (response.data.toLowerCase() === 'invalid csrf token!') {
throw new InvalidCsrfError()
}
break break
case 401: case 401:
if (typeof response.data === 'object') { if (typeof response.data === 'object') {

View File

@@ -68,7 +68,7 @@ export class Sas9RequestClient extends RequestClient {
throwIfError(response) throwIfError(response)
return this.parseResponse<T>(response) return this.parseResponse<T>(response)
}) })
.catch(async (e) => { .catch(async (e: any) => {
return await this.handleError( return await this.handleError(
e, e,
() => () =>
@@ -113,7 +113,7 @@ export class Sas9RequestClient extends RequestClient {
throwIfError(response) throwIfError(response)
return this.parseResponse<T>(response) return this.parseResponse<T>(response)
}) })
.catch(async (e) => { .catch(async (e: any) => {
return await this.handleError(e, () => return await this.handleError(e, () =>
this.post<T>(url, data, accessToken, contentType, overrideHeaders) this.post<T>(url, data, accessToken, contentType, overrideHeaders)
) )

View File

@@ -54,6 +54,17 @@ describe('formatDataForRequest', () => {
expect(formatDataForRequest(data)).toEqual(expectedOutput) expect(formatDataForRequest(data)).toEqual(expectedOutput)
}) })
it('should accept . as special missing value', () => {
let tableWithMissingValues = {
[testTable]: [{ var: '.' }, { var: 0 }],
[`$${testTable}`]: { formats: { var: 'best.' } }
}
expect(() =>
formatDataForRequest(tableWithMissingValues)
).not.toThrowError()
})
it('should throw an error if special missing values is not valid', () => { it('should throw an error if special missing values is not valid', () => {
let tableWithMissingValues = { let tableWithMissingValues = {
[testTable]: [{ var: 'AA' }, { var: 0 }], [testTable]: [{ var: 'AA' }, { var: 0 }],

View File

@@ -0,0 +1,9 @@
export class InvalidCsrfError extends Error {
constructor() {
const message = 'Invalid CSRF token!'
super(`Auth error: ${message}`)
this.name = 'InvalidCsrfError'
Object.setPrototypeOf(this, InvalidCsrfError.prototype)
}
}

View File

@@ -15,7 +15,7 @@ export const getValidJson = (str: string | object): object => {
if (str === '') return {} if (str === '') return {}
return JSON.parse(str) return JSON.parse(str)
} catch (e) { } catch (e: any) {
if (e instanceof JsonParseArrayError) throw e if (e instanceof JsonParseArrayError) throw e
throw new InvalidJsonError() throw new InvalidJsonError()
} }

View File

@@ -4,7 +4,7 @@ export const parseSasViyaLog = (logResponse: { items: any[] }) => {
log = logResponse.items log = logResponse.items
? logResponse.items.map((i) => i.line).join('\n') ? logResponse.items.map((i) => i.line).join('\n')
: JSON.stringify(logResponse) : JSON.stringify(logResponse)
} catch (e) { } catch (e: any) {
console.error('An error has occurred while parsing the log response', e) console.error('An error has occurred while parsing the log response', e)
log = logResponse log = logResponse
} }

View File

@@ -8,7 +8,7 @@ export const parseWeboutResponse = (response: string, url?: string): string => {
sasResponse = response sasResponse = response
.split('>>weboutBEGIN<<')[1] .split('>>weboutBEGIN<<')[1]
.split('>>weboutEND<<')[0] .split('>>weboutEND<<')[0]
} catch (e) { } catch (e: any) {
if (url) throw new WeboutResponseError(url) if (url) throw new WeboutResponseError(url)
sasResponse = '' sasResponse = ''