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

Compare commits

..

65 Commits

Author SHA1 Message Date
VladislavParhomchik
9b976d48ca Merge pull request #423 from sasjs/issue-394
fix: SAS9 performs CAS Authentication after login
2021-06-18 11:41:13 +03:00
Saad Jutt
00b19de497 chore: sasjs-tests package-lock.json updated 2021-06-17 19:35:24 +05:00
Saad Jutt
f4cdd2d607 fix: CAS Authentication upon SAS9 login 2021-06-17 19:33:58 +05:00
Yury Shkoda
cdc0c12ec4 chore(lint): fixed lint scriptes on Windows 2021-06-17 15:35:36 +03:00
Saad Jutt
bc6f109c48 fix: make duplicate request only if payload is present 2021-06-17 14:37:46 +05:00
Saad Jutt
cfab64cfa0 fix: first request after login redirects from server 2021-06-17 08:31:40 +05:00
Krishna Acondy
d4c8c58552 Merge pull request #422 from sasjs/assign-qa-reviewer
chore(reviewers): add Sabir and Vlad as reviewers
2021-06-16 07:50:58 +01:00
Krishna Acondy
2b8cb51a50 chore(reviewers): add Sabir to list of devs, create separate QA list with Vlad 2021-06-16 07:42:23 +01:00
Krishna Acondy
e068d3263c Merge pull request #419 from sasjs/issue-327
fix(*): SASWORK is not being parsed correctly
2021-06-15 08:41:58 +01:00
630f2e9c37 fix: test regarding Request with extra attributes on JES approach fixed 2021-06-15 11:29:21 +05:00
51ac6b052b fix: test case which check extra attributes on JES approach fixed 2021-06-14 23:21:17 +05:00
c32258eb3c fix: code modified in appendRequest method fixes #327 2021-06-14 23:18:26 +05:00
Allan Bowe
88f50e3c74 Update README.md 2021-06-14 21:11:18 +03:00
Krishna Acondy
bfe5ac0ff7 Merge pull request #417 from sasjs/force-sas9-webout
fix(sas9): force webout output when executing arbitrary code on SAS9
2021-06-14 09:17:32 +01:00
Krishna Acondy
d50f5a030a chore(lint): fix formatting 2021-06-14 09:12:11 +01:00
Krishna Acondy
c320caec99 fix(sas9): force webout output when executing arbitrary code on SAS9 2021-06-14 09:10:26 +01:00
Allan Bowe
16a5b2b012 Merge pull request #414 from sasjs/issue-276
fix: Issue 276
2021-06-13 21:20:18 +03:00
Allan Bowe
2951e0cc2d Merge branch 'master' into issue-276 2021-06-13 21:04:56 +03:00
Allan Bowe
6bb4a7ea18 Update SASjs.ts
fix grammar
2021-06-13 21:01:15 +03:00
Allan Bowe
2827978fe5 Merge pull request #390 from sasjs/service-pack-with-file-resource
feat: create file resource while deploying service pack for viya
2021-06-13 14:52:09 +03:00
Saad Jutt
541c19c1a4 chore(merge): Merge branch 'service-pack-with-file-resource' of github.com:sasjs/adapter into service-pack-with-file-resource 2021-06-13 16:26:27 +05:00
Saad Jutt
c5e995f8d6 chore: TSDoc comments updated 2021-06-13 16:25:04 +05:00
Allan Bowe
8bf36da566 Merge branch 'master' into service-pack-with-file-resource 2021-06-13 11:56:54 +03:00
ccb4ec6e03 chore: code refactored for better readability 2021-06-11 22:53:06 +05:00
06ebb52bc9 chore(merge): merge master into issue-276 2021-06-10 22:12:36 +05:00
Yury Shkoda
6e23a0362f Merge pull request #411 from sasjs/issue-408
feat: select extra attributes in JES response
2021-06-10 19:38:16 +03:00
a59d78bcf7 chore(git): Merge branch 'master' into issue-408 2021-06-10 15:06:10 +02:00
33d4ee92a7 chore: updated utils and comment 2021-06-10 15:03:51 +02:00
dadce3d4c9 chore: added extra attributes type from @sasjs/utils 2021-06-10 14:22:31 +02:00
Saad Jutt
b61cf34723 chore(merge): Merge branch 'master' into service-pack-with-file-resource 2021-06-10 16:55:35 +05:00
Saad Jutt
22445d1268 fix: uploading file Buffer with FormData 2021-06-10 16:49:20 +05:00
Allan Bowe
cba9dacb37 Merge branch 'master' into issue-276 2021-06-10 14:03:14 +03:00
Yury Shkoda
a055b36c5c Merge pull request #389 from sasjs/issue-381
fix: sas fails with verifying credentials
2021-06-10 13:42:21 +03:00
06895cc9f8 style: lint 2021-06-10 12:08:56 +02:00
24496a997a chore: addressing comments 2021-06-10 12:08:16 +02:00
6419686269 chore: lint fixes 2021-06-09 17:28:27 +00:00
Sabir Hassan
4554c9100c Merge branch 'master' into issue-276 2021-06-09 16:51:49 +05:00
919c83c143 chore: lint fixes 2021-06-09 16:40:29 +05:00
00ba2957fb Merge branch 'master' into issue-381 2021-06-09 13:10:06 +02:00
5beda6547a Merge branch 'master' into issue-408 2021-06-09 13:09:59 +02:00
bd49b3757a chore(git): Merge branch 'master' into issue-408 2021-06-09 13:05:48 +02:00
Yury Shkoda
b32352a369 Merge pull request #413 from sasjs/webpack-fix
fix(webpack): removed process plugin from nodeConfig
2021-06-09 14:04:47 +03:00
b306f11148 chore(git): Merge branch 'master' into issue-381 2021-06-09 13:04:47 +02:00
Yury Shkoda
8c4955cb65 chore(git): merge branch 'master' of https://github.com/sasjs/adapter into webpack-fix 2021-06-09 13:58:59 +03:00
Yury Shkoda
155f2bb0e8 fix(webpack): removed process plugin from nodeConfig 2021-06-09 13:53:27 +03:00
488d8b9316 chore(git): Merge branch 'master' into issue-381 2021-06-09 10:38:25 +02:00
88f70a7966 chore: merge 2021-06-08 17:01:41 +02:00
89ff323206 style: lint 2021-06-08 16:55:10 +02:00
d4357d939e test: extra attributes on JES 2021-06-08 16:54:46 +02:00
Allan Bowe
6cb76f0b5c chore: merge fix 2021-06-08 13:18:16 +00:00
Allan Bowe
ba2baa36c0 chore: updating merge conflicts 2021-06-08 13:14:29 +00:00
2fa3a353fa feat: select extra attributes in JES response 2021-06-08 13:25:08 +02:00
Allan Bowe
b2c135ae61 Merge branch 'master' into issue-381 2021-06-07 10:34:16 +03:00
sabir_hassan
1867658cde fix: add validations for table name and table structure #276 2021-06-03 15:08:48 +05:00
0b18fddc3e chore: merge 2021-06-02 11:06:34 +02:00
19503e0b31 style: lint 2021-06-02 11:01:19 +02:00
d8bdc02f09 chore: sasjs-tests compute only on viya, login order fix 2021-06-02 11:00:08 +02:00
2d0833061f chore: merge branch 'master' into issue-381 2021-06-01 11:52:52 +02:00
Yury Shkoda
5dfc4e4086 Merge branch 'master' into issue-381 2021-05-31 08:03:44 +03:00
Saad Jutt
c5824a8a8d fix: using mime package to determine content-type 2021-05-30 23:47:31 +05:00
Saad Jutt
56a1960fff feat: create file resource while deploying service pack for viya 2021-05-30 05:58:17 +05:00
b8c9522a55 chore: packages 2021-05-28 16:58:56 +02:00
b461cff731 Merge branch 'master' into issue-381 2021-05-28 15:24:01 +02:00
728167fd71 test: fix 2021-05-28 15:22:57 +02:00
460575b462 fix: when sas fails with verifying credentials, resend request with new csrf token 2021-05-28 15:05:44 +02:00
17 changed files with 732 additions and 676 deletions

View File

@@ -7,3 +7,8 @@ groups:
- saadjutt01 - saadjutt01
- medjedovicm - medjedovicm
- allanbowe - allanbowe
- sabhas
- name: SASjs QA
reviewers: 1
usernames:
- VladislavParhomchik

806
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,8 @@
"build": "rimraf build && rimraf node && mkdir node && cp -r src/* node && webpack && rimraf build/src && rimraf node", "build": "rimraf build && rimraf node && mkdir node && cp -r src/* node && webpack && rimraf build/src && rimraf node",
"package:lib": "npm run build && cp ./package.json build && cd build && npm version \"5.0.0\" && npm pack", "package:lib": "npm run build && cp ./package.json build && cd build && npm version \"5.0.0\" && npm pack",
"publish:lib": "npm run build && cd build && npm publish", "publish:lib": "npm run build && cd build && npm publish",
"lint:fix": "npx prettier --write 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}' && npx prettier --write 'sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'", "lint:fix": "npx prettier --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --write \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
"lint": "npx prettier --check 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}' && npx prettier --check 'sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'", "lint": "npx prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --check \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
"test": "jest --silent --coverage", "test": "jest --silent --coverage",
"prepublishOnly": "cp -r ./build/* . && rm -rf ./build", "prepublishOnly": "cp -r ./build/* . && rm -rf ./build",
"postpublish": "git clean -fd", "postpublish": "git clean -fd",
@@ -39,11 +39,13 @@
"license": "ISC", "license": "ISC",
"devDependencies": { "devDependencies": {
"@types/jest": "^26.0.23", "@types/jest": "^26.0.23",
"@types/mime": "^2.0.3",
"@types/tough-cookie": "^4.0.0", "@types/tough-cookie": "^4.0.0",
"cp": "^0.2.0", "cp": "^0.2.0",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"jest": "^27.0.4", "jest": "^27.0.4",
"jest-extended": "^0.11.5", "jest-extended": "^0.11.5",
"mime": "^2.5.2",
"path": "^0.12.7", "path": "^0.12.7",
"process": "^0.11.10", "process": "^0.11.10",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
@@ -62,7 +64,7 @@
}, },
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"@sasjs/utils": "^2.18.0", "@sasjs/utils": "^2.20.1",
"axios": "^0.21.1", "axios": "^0.21.1",
"axios-cookiejar-support": "^1.0.1", "axios-cookiejar-support": "^1.0.1",
"form-data": "^4.0.0", "form-data": "^4.0.0",

View File

@@ -6,7 +6,7 @@ When developing on `@sasjs/adapter`, it's good practice to run the test suite ag
You can use the provided `update:adapter` NPM script for this. You can use the provided `update:adapter` NPM script for this.
``` ```bash
npm run update:adapter npm run update:adapter
``` ```
@@ -37,7 +37,7 @@ To be able to run the `deploy` script, two environment variables need to be set:
So you can run the script like so: So you can run the script like so:
``` ```bash
SSH_ACCOUNT=me@my-sas-server.com DEPLOY_PATH=/var/www/html/my-folder/sasjs-tests npm run deploy SSH_ACCOUNT=me@my-sas-server.com DEPLOY_PATH=/var/www/html/my-folder/sasjs-tests npm run deploy
``` ```
@@ -49,8 +49,7 @@ The below services need to be created on your SAS server, at the location specif
### SAS 9 ### SAS 9
``` ```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";
%inc mc; %inc mc;
filename ft15f001 temp; filename ft15f001 temp;
@@ -76,11 +75,20 @@ parmcards4;
let he who hath understanding, reckon the number of the beast let he who hath understanding, reckon the number of the beast
;;;; ;;;;
%mm_createwebservice(path=/Public/app/common,name=makeErr) %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 Viya
``` ```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";
%inc mc; %inc mc;
filename ft15f001 temp; filename ft15f001 temp;
@@ -119,6 +127,15 @@ 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) %mp_createwebservice(path=/Public/app/common,name=makeErr)
parmcards4;
%webout(OPEN)
data _null_;
file _webout;
put ' the discovery channel ';
run;
%webout(CLOSE)
;;;;
%mp_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.

View File

@@ -2005,12 +2005,15 @@
}, },
"@sasjs/adapter": { "@sasjs/adapter": {
"version": "file:../build/sasjs-adapter-5.0.0.tgz", "version": "file:../build/sasjs-adapter-5.0.0.tgz",
"integrity": "sha512-DxoQbdJqzqOTIuT7qwSfAbmNTWdpOx5zGkiMuZBSwoi9lSsRNoARiWnJq5Vl6h4RXJlc/FVdBFt35RZm4Mc0ZQ==", "integrity": "sha512-QV4fy09Cp5FvweEULkPev60EJNyylDr2T5SN0mkp7j6wr7i08pMwyAHi8jKboTfpn3pCFrBz/DtOzylbVmttrA==",
"requires": { "requires": {
"@sasjs/utils": "^2.10.2", "@sasjs/utils": "^2.20.1",
"axios": "^0.21.1", "axios": "^0.21.1",
"axios-cookiejar-support": "^1.0.1",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"https": "^1.0.0" "https": "^1.0.0",
"tough-cookie": "^4.0.0",
"url": "^0.11.0"
}, },
"dependencies": { "dependencies": {
"form-data": { "form-data": {
@@ -2022,6 +2025,21 @@
"combined-stream": "^1.0.8", "combined-stream": "^1.0.8",
"mime-types": "^2.1.12" "mime-types": "^2.1.12"
} }
},
"tough-cookie": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
"integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==",
"requires": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.1.2"
}
},
"universalify": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
} }
} }
}, },
@@ -2046,14 +2064,15 @@
} }
}, },
"@sasjs/utils": { "@sasjs/utils": {
"version": "2.12.1", "version": "2.20.1",
"resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.12.1.tgz", "resolved": "https://registry.npmjs.org/@sasjs/utils/-/utils-2.20.1.tgz",
"integrity": "sha512-6gZS5zW0J70P7XaVuEczyfHVaVa8Ks/aWr4PIlpJcxWD0enJtCEmos2DdnezdSoNvODkPq/8rzMPqko5jaXK1Q==", "integrity": "sha512-Wer6RrGPowBgvgJ2Hdk2nrdA9mIsG4AKI50s/cEWKfzMnQRQVrCNmVUyZlM5I8/pZRzsMzwq7PLaxjAADYUCuQ==",
"requires": { "requires": {
"@types/prompts": "^2.0.11", "@types/prompts": "^2.0.13",
"chalk": "^4.1.1", "chalk": "^4.1.1",
"cli-table": "^0.3.6", "cli-table": "^0.3.6",
"consola": "^2.15.0", "consola": "^2.15.0",
"fs-extra": "^10.0.0",
"prompts": "^2.4.1", "prompts": "^2.4.1",
"valid-url": "^1.0.9" "valid-url": "^1.0.9"
}, },
@@ -2088,6 +2107,16 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
}, },
"fs-extra": {
"version": "10.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz",
"integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==",
"requires": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
"universalify": "^2.0.0"
}
},
"has-flag": { "has-flag": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -2422,9 +2451,9 @@
"integrity": "sha512-6gOkRe7OIioWAXfnO/2lFiv+SJichKVSys1mSsgyrYHSEjk8Ctv4tSR/Odvnu+HWlH2C8j53dahU03XmQdd5fA==" "integrity": "sha512-6gOkRe7OIioWAXfnO/2lFiv+SJichKVSys1mSsgyrYHSEjk8Ctv4tSR/Odvnu+HWlH2C8j53dahU03XmQdd5fA=="
}, },
"@types/prompts": { "@types/prompts": {
"version": "2.0.11", "version": "2.0.13",
"resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.0.11.tgz", "resolved": "https://registry.npmjs.org/@types/prompts/-/prompts-2.0.13.tgz",
"integrity": "sha512-dcF5L3rU9VfpLEJIV++FEyhGhuIpJllNEwllVuJ5g8eoVqjf048tW9+spivIwjzgPbtaGAl7mIZW3cmhDAq2UQ==", "integrity": "sha512-jwMOIGy49VruR/gYehhJYgpVzB+EVpEE7t7j9m1oTo4HMpOe7KmsyqdBuoxAzA5B4caUgx0cKrWr7wUEqMXJ7Q==",
"requires": { "requires": {
"@types/node": "*" "@types/node": "*"
} }
@@ -3467,6 +3496,22 @@
"follow-redirects": "^1.10.0" "follow-redirects": "^1.10.0"
} }
}, },
"axios-cookiejar-support": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-1.0.1.tgz",
"integrity": "sha512-IZJxnAJ99XxiLqNeMOqrPbfR7fRyIfaoSLdPUf4AMQEGkH8URs0ghJK/xtqBsD+KsSr3pKl4DEQjCn834pHMig==",
"requires": {
"is-redirect": "^1.0.0",
"pify": "^5.0.0"
},
"dependencies": {
"pify": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz",
"integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA=="
}
}
},
"axobject-query": { "axobject-query": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz",
@@ -5691,11 +5736,6 @@
"is-obj": "^2.0.0" "is-obj": "^2.0.0"
} }
}, },
"dotenv": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
},
"dotenv-expand": { "dotenv-expand": {
"version": "5.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
@@ -8557,6 +8597,11 @@
"resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz",
"integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=" "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c="
}, },
"is-redirect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
"integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ="
},
"is-regex": { "is-regex": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.0.tgz",
@@ -14242,6 +14287,11 @@
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz",
"integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==" "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg=="
}, },
"dotenv": {
"version": "8.2.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
"integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw=="
},
"resolve": { "resolve": {
"version": "1.18.1", "version": "1.18.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz",

View File

@@ -13,14 +13,19 @@ const App = (): ReactElement<{}> => {
useEffect(() => { useEffect(() => {
if (adapter) { if (adapter) {
setTestSuites([ 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)
computeTests(adapter) ]
])
if (adapter.getSasjsConfig().serverType === 'SASVIYA') {
testSuites.push(computeTests(adapter))
}
setTestSuites(testSuites)
} }
}, [adapter, config]) }, [adapter, config])

View File

@@ -145,6 +145,29 @@ export const basicTests = (
sasjsConfig.debug === false sasjsConfig.debug === false
) )
} }
},
{
title: 'Request with extra attributes on JES approach',
description:
'Should complete successful request with extra attributes present in response',
test: async () => {
const config = {
useComputeApi: false
}
return await adapter.request(
'common/sendArr',
stringData,
config,
undefined,
undefined,
['file', 'data']
)
},
assertion: (response: any) => {
const responseKeys: any = Object.keys(response)
return responseKeys.includes('file') && responseKeys.includes('data')
}
} }
] ]
}) })

View File

@@ -176,11 +176,59 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
name: 'sendObj', name: 'sendObj',
tests: [ tests: [
{ {
title: 'Invalid column name', title: 'Table name starts with numeric',
description: 'Should throw an error', description: 'Should throw an error',
test: async () => { test: async () => {
const invalidData: any = { const invalidData: any = {
'1 invalid table': [{ col1: 42 }] '1InvalidTable': [{ col1: 42 }]
}
return adapter.request('common/sendObj', invalidData).catch((e) => e)
},
assertion: (error: any) =>
!!error && !!error.error && !!error.error.message
},
{
title: 'Table name contains a space',
description: 'Should throw an error',
test: async () => {
const invalidData: any = {
'an invalidTable': [{ col1: 42 }]
}
return adapter.request('common/sendObj', invalidData).catch((e) => e)
},
assertion: (error: any) =>
!!error && !!error.error && !!error.error.message
},
{
title: 'Table name contains a special character',
description: 'Should throw an error',
test: async () => {
const invalidData: any = {
'anInvalidTable#': [{ col1: 42 }]
}
return adapter.request('common/sendObj', invalidData).catch((e) => e)
},
assertion: (error: any) =>
!!error && !!error.error && !!error.error.message
},
{
title: 'Table name exceeds max length of 32 characters',
description: 'Should throw an error',
test: async () => {
const invalidData: any = {
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx: [{ col1: 42 }]
}
return adapter.request('common/sendObj', invalidData).catch((e) => e)
},
assertion: (error: any) =>
!!error && !!error.error && !!error.error.message
},
{
title: "Invalid data object's structure",
description: 'Should throw an error',
test: async () => {
const invalidData: any = {
inData: [[{ data: 'value' }]]
} }
return adapter.request('common/sendObj', invalidData).catch((e) => e) return adapter.request('common/sendObj', invalidData).catch((e) => e)
}, },

View File

@@ -45,7 +45,16 @@ export class SAS9ApiClient {
) { ) {
await this.requestClient.login(userName, password, this.jobsPath) await this.requestClient.login(userName, password, this.jobsPath)
const formData = generateFileUploadForm(linesOfCode.join('\n')) // This piece of code forces a webout to prevent Stored Process Errors.
const forceOutputCode = [
'data _null_;',
'file _webout;',
`put 'Executed sasjs run';`,
'run;'
]
const formData = generateFileUploadForm(
[...linesOfCode, ...forceOutputCode].join('\n')
)
const codeInjectorPath = `/User Folders/${userName}/My Folder/sasjs/runner` const codeInjectorPath = `/User Folders/${userName}/My Folder/sasjs/runner`
const contentType = const contentType =

View File

@@ -12,6 +12,7 @@ import {
Context, Context,
ContextAllAttributes, ContextAllAttributes,
Folder, Folder,
File,
EditContextInput, EditContextInput,
JobDefinition, JobDefinition,
PollOptions PollOptions
@@ -30,6 +31,7 @@ import { isAuthorizeFormRequired } from './auth/isAuthorizeFormRequired'
import { RequestClient } from './request/RequestClient' import { RequestClient } from './request/RequestClient'
import { SasAuthResponse, MacroVar } from '@sasjs/utils/types' import { SasAuthResponse, MacroVar } from '@sasjs/utils/types'
import { prefixMessage } from '@sasjs/utils/error' import { prefixMessage } from '@sasjs/utils/error'
import * as mime from 'mime'
/** /**
* A client for interfacing with the SAS Viya REST API. * A client for interfacing with the SAS Viya REST API.
@@ -536,6 +538,53 @@ export class SASViyaApiClient {
.then((res) => res.result) .then((res) => res.result)
} }
/**
* Creates a file. Path to or URI of the parent folder is required.
* @param fileName - the name of the new file.
* @param contentBuffer - the content of the new file in Buffer.
* @param parentFolderPath - the full path to the parent folder. If not
* provided, the parentFolderUri must be provided.
* @param parentFolderUri - the URI (eg /folders/folders/UUID) of the parent
* folder. If not provided, the parentFolderPath must be provided.
* @param accessToken - an access token for authorizing the request.
*/
public async createFile(
fileName: string,
contentBuffer: Buffer,
parentFolderPath?: string,
parentFolderUri?: string,
accessToken?: string
): Promise<File> {
if (!parentFolderPath && !parentFolderUri) {
throw new Error('Path or URI of the parent folder is required.')
}
if (!parentFolderUri && parentFolderPath) {
parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken)
}
const headers = {
Accept: 'application/vnd.sas.file+json',
'Content-Disposition': `filename="${fileName}";`
}
const formData = new NodeFormData()
formData.append('file', contentBuffer, fileName)
const mimeType =
mime.getType(fileName.match(/\.[0-9a-z]+$/i)?.[0] || '') ?? 'text/plain'
return (
await this.requestClient.post<File>(
`/files/files?parentFolderUri=${parentFolderUri}&typeDefName=file#rawUpload`,
formData,
accessToken,
'multipart/form-data; boundary=' + (formData as any)._boundary,
headers
)
).result
}
/** /**
* Creates a folder. Path to or URI of the parent folder is required. * Creates a folder. Path to or URI of the parent folder is required.
* @param folderName - the name of the new folder. * @param folderName - the name of the new folder.

View File

@@ -14,6 +14,7 @@ import {
Sas9JobExecutor Sas9JobExecutor
} from './job-execution' } from './job-execution'
import { ErrorResponse } from './types/errors' import { ErrorResponse } from './types/errors'
import { ExtraResponseAttributes } from '@sasjs/utils/types'
const defaultConfig: SASjsConfig = { const defaultConfig: SASjsConfig = {
serverUrl: '', serverUrl: '',
@@ -267,7 +268,7 @@ export default class SASjs {
} }
/** /**
* Creates a folder at SAS file system. * Creates a folder in the logical SAS folder tree
* @param folderName - name of the folder to be created. * @param folderName - name of the folder to be created.
* @param parentFolderPath - the full path (eg `/Public/example/myFolder`) of the parent folder. * @param parentFolderPath - the full path (eg `/Public/example/myFolder`) of the parent folder.
* @param parentFolderUri - the URI of the parent folder. * @param parentFolderUri - the URI of the parent folder.
@@ -299,6 +300,40 @@ export default class SASjs {
) )
} }
/**
* Creates a file in the logical SAS folder tree
* @param fileName - name of the file to be created.
* @param content - content of the file to be created.
* @param parentFolderPath - the full path (eg `/Public/example/myFolder`) of the parent folder.
* @param parentFolderUri - the URI of the parent folder.
* @param accessToken - the access token to authorizing the request.
* @param sasApiClient - a client for interfacing with SAS API.
*/
public async createFile(
fileName: string,
content: Buffer,
parentFolderPath: string,
parentFolderUri?: string,
accessToken?: string,
sasApiClient?: SASViyaApiClient
) {
if (sasApiClient)
return await sasApiClient.createFile(
fileName,
content,
parentFolderPath,
parentFolderUri,
accessToken
)
return await this.sasViyaApiClient!.createFile(
fileName,
content,
parentFolderPath,
parentFolderUri,
accessToken
)
}
/** /**
* Fetches a folder from the SAS file system. * Fetches a folder from the SAS file system.
* @param folderPath - path of the folder to be fetched. * @param folderPath - path of the folder to be fetched.
@@ -540,50 +575,126 @@ export default class SASjs {
* `await request(sasJobPath, data, config, () => setIsLoggedIn(false))` * `await request(sasJobPath, data, config, () => setIsLoggedIn(false))`
* If you are not passing in any data and configuration, it will look like so: * If you are not passing in any data and configuration, it will look like so:
* `await request(sasJobPath, {}, {}, () => setIsLoggedIn(false))` * `await request(sasJobPath, {}, {}, () => setIsLoggedIn(false))`
* @param extraResponseAttributes - a array of predefined values that are used
* to provide extra attributes (same names as those values) to be added in response
* Supported values are declared in ExtraResponseAttributes type.
*/ */
public async request( public async request(
sasJob: string, sasJob: string,
data: { [key: string]: any }, data: { [key: string]: any } | null,
config: { [key: string]: any } = {}, config: { [key: string]: any } = {},
loginRequiredCallback?: () => any, loginRequiredCallback?: () => any,
accessToken?: string accessToken?: string,
extraResponseAttributes: ExtraResponseAttributes[] = []
) { ) {
config = { config = {
...this.sasjsConfig, ...this.sasjsConfig,
...config ...config
} }
if (config.serverType === ServerType.SasViya && config.contextName) { const validationResult = this.validateInput(data)
if (config.useComputeApi) {
return await this.computeJobExecutor!.execute( if (validationResult.status) {
sasJob, if (config.serverType === ServerType.SasViya && config.contextName) {
data, if (config.useComputeApi) {
config, return await this.computeJobExecutor!.execute(
loginRequiredCallback, sasJob,
accessToken data,
) config,
loginRequiredCallback,
accessToken
)
} else {
return await this.jesJobExecutor!.execute(
sasJob,
data,
config,
loginRequiredCallback,
accessToken,
extraResponseAttributes
)
}
} else if (
config.serverType === ServerType.Sas9 &&
config.username &&
config.password
) {
return await this.sas9JobExecutor!.execute(sasJob, data, config)
} else { } else {
return await this.jesJobExecutor!.execute( return await this.webJobExecutor!.execute(
sasJob, sasJob,
data, data,
config, config,
loginRequiredCallback, loginRequiredCallback,
accessToken accessToken,
extraResponseAttributes
) )
} }
} else if (
config.serverType === ServerType.Sas9 &&
config.username &&
config.password
) {
return await this.sas9JobExecutor!.execute(sasJob, data, config)
} else { } else {
return await this.webJobExecutor!.execute( return Promise.reject(new ErrorResponse(validationResult.msg))
sasJob, }
data, }
config,
loginRequiredCallback /**
) * This function validates the input data structure and table naming convention
*
* @param data A json object that contains one or more tables, it can also be null
* @returns An object which contains two attributes: 1) status: boolean, 2) msg: string
*/
private validateInput(data: { [key: string]: any } | null): {
status: boolean
msg: string
} {
if (data === null) return { status: true, msg: '' }
for (const key in data) {
if (!key.match(/^[a-zA-Z_]/)) {
return {
status: false,
msg: 'First letter of table should be alphabet or underscore.'
}
}
if (!key.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/)) {
return { status: false, msg: 'Table name should be alphanumeric.' }
}
if (key.length > 32) {
return {
status: false,
msg: 'Maximum length for table name could be 32 characters.'
}
}
if (this.getType(data[key]) !== 'Array') {
return {
status: false,
msg: 'Parameter data contains invalid table structure.'
}
}
for (let i = 0; i < data[key].length; i++) {
if (this.getType(data[key][i]) !== 'object') {
return {
status: false,
msg: `Table ${key} contains invalid structure.`
}
}
}
}
return { status: true, msg: '' }
}
/**
* this function returns the type of variable
*
* @param data it could be anything, like string, array, object etc.
* @returns a string which tells the type of input parameter
*/
private getType(data: any): string {
if (Array.isArray(data)) {
return 'Array'
} else {
return typeof data
} }
} }
@@ -874,6 +985,16 @@ export default class SASjs {
isForced isForced
) )
break break
case 'file':
await this.createFile(
member.name,
member.code,
parentFolder,
undefined,
accessToken,
sasApiClient
)
break
case 'service': case 'service':
await this.createJobDefinition( await this.createJobDefinition(
member.name, member.name,

View File

@@ -35,6 +35,7 @@ export class AuthManager {
this.userName = loginParams.username this.userName = loginParams.username
const { isLoggedIn, loginForm } = await this.checkSession() const { isLoggedIn, loginForm } = await this.checkSession()
if (isLoggedIn) { if (isLoggedIn) {
await this.loginCallback() await this.loginCallback()
@@ -44,6 +45,44 @@ export class AuthManager {
} }
} }
let loginResponse = await this.sendLoginRequest(loginForm, loginParams)
let loggedIn = isLogInSuccess(loginResponse)
if (!loggedIn) {
if (isCredentialsVerifyError(loginResponse)) {
const newLoginForm = await this.getLoginForm(loginResponse)
loginResponse = await this.sendLoginRequest(newLoginForm, loginParams)
}
const currentSession = await this.checkSession()
loggedIn = currentSession.isLoggedIn
}
if (loggedIn) {
if (this.serverType === ServerType.Sas9) {
const casAuthenticationUrl = `${this.serverUrl}/SASStoredProcess/j_spring_cas_security_check`
await this.requestClient.get<string>(
`/SASLogon/login?service=${casAuthenticationUrl}`,
undefined
)
}
this.loginCallback()
}
return {
isLoggedIn: !!loggedIn,
userName: this.userName
}
}
private async sendLoginRequest(
loginForm: { [key: string]: any },
loginParams: { [key: string]: any }
) {
for (const key in loginForm) { for (const key in loginForm) {
loginParams[key] = loginForm[key] loginParams[key] = loginForm[key]
} }
@@ -60,21 +99,7 @@ export class AuthManager {
} }
) )
let loggedIn = isLogInSuccess(loginResponse) return loginResponse
if (!loggedIn) {
const currentSession = await this.checkSession()
loggedIn = currentSession.isLoggedIn
}
if (loggedIn) {
this.loginCallback()
}
return {
isLoggedIn: !!loggedIn,
userName: this.userName
}
} }
/** /**
@@ -168,5 +193,10 @@ export class AuthManager {
} }
} }
const isCredentialsVerifyError = (response: string): boolean =>
/An error occurred while the system was verifying your credentials. Please enter your credentials again./gm.test(
response
)
const isLogInSuccess = (response: string): boolean => const isLogInSuccess = (response: string): boolean =>
/You have signed in/gm.test(response) /You have signed in/gm.test(response)

View File

@@ -5,6 +5,7 @@ import {
JobExecutionError, JobExecutionError,
LoginRequiredError LoginRequiredError
} from '../types/errors' } from '../types/errors'
import { ExtraResponseAttributes } from '@sasjs/utils/types'
import { BaseJobExecutor } from './JobExecutor' import { BaseJobExecutor } from './JobExecutor'
export class JesJobExecutor extends BaseJobExecutor { export class JesJobExecutor extends BaseJobExecutor {
@@ -17,7 +18,8 @@ export class JesJobExecutor extends BaseJobExecutor {
data: any, data: any,
config: any, config: any,
loginRequiredCallback?: any, loginRequiredCallback?: any,
accessToken?: string accessToken?: string,
extraResponseAttributes: ExtraResponseAttributes[] = []
) { ) {
const loginCallback = loginRequiredCallback || (() => Promise.resolve()) const loginCallback = loginRequiredCallback || (() => Promise.resolve())
@@ -30,10 +32,26 @@ export class JesJobExecutor extends BaseJobExecutor {
data, data,
accessToken accessToken
) )
.then((response) => { .then((response: any) => {
this.appendRequest(response, sasJob, config.debug) this.appendRequest(response, sasJob, config.debug)
resolve(response) let responseObject = {}
if (extraResponseAttributes && extraResponseAttributes.length > 0) {
const extraAttributes = extraResponseAttributes.reduce(
(map: any, obj: any) => ((map[obj] = response[obj]), map),
{}
)
responseObject = {
result: response.result,
...extraAttributes
}
} else {
responseObject = response.result
}
resolve(responseObject)
}) })
.catch(async (e: Error) => { .catch(async (e: Error) => {
if (e instanceof JobExecutionError) { if (e instanceof JobExecutionError) {
@@ -50,7 +68,9 @@ export class JesJobExecutor extends BaseJobExecutor {
sasJob, sasJob,
data, data,
config, config,
loginRequiredCallback loginRequiredCallback,
accessToken,
extraResponseAttributes
).then( ).then(
(res: any) => { (res: any) => {
resolve(res) resolve(res)

View File

@@ -1,5 +1,6 @@
import { ServerType } from '@sasjs/utils/types' import { ServerType } from '@sasjs/utils/types'
import { SASjsRequest } from '../types' import { SASjsRequest } from '../types'
import { ExtraResponseAttributes } from '@sasjs/utils/types'
import { asyncForEach, parseGeneratedCode, parseSourceCode } from '../utils' import { asyncForEach, parseGeneratedCode, parseSourceCode } from '../utils'
export type ExecuteFunction = () => Promise<any> export type ExecuteFunction = () => Promise<any>
@@ -10,7 +11,8 @@ export interface JobExecutor {
data: any, data: any,
config: any, config: any,
loginRequiredCallback?: any, loginRequiredCallback?: any,
accessToken?: string accessToken?: string,
extraResponseAttributes?: ExtraResponseAttributes[]
) => Promise<any> ) => Promise<any>
resendWaitingRequests: () => Promise<void> resendWaitingRequests: () => Promise<void>
getRequests: () => SASjsRequest[] getRequests: () => SASjsRequest[]
@@ -28,7 +30,8 @@ export abstract class BaseJobExecutor implements JobExecutor {
data: any, data: any,
config: any, config: any,
loginRequiredCallback?: any, loginRequiredCallback?: any,
accessToken?: string | undefined accessToken?: string | undefined,
extraResponseAttributes?: ExtraResponseAttributes[]
): Promise<any> ): Promise<any>
resendWaitingRequests = async () => { resendWaitingRequests = async () => {
@@ -59,14 +62,14 @@ export abstract class BaseJobExecutor implements JobExecutor {
let sasWork = null let sasWork = null
if (debug) { if (debug) {
if (response?.result && response?.log) { if (response?.log) {
sourceCode = parseSourceCode(response.log) sourceCode = parseSourceCode(response.log)
generatedCode = parseGeneratedCode(response.log) generatedCode = parseGeneratedCode(response.log)
if (response.log) { if (response?.result) {
sasWork = response.log
} else {
sasWork = response.result.WORK sasWork = response.result.WORK
} else {
sasWork = response.log
} }
} else if (response?.result) { } else if (response?.result) {
sourceCode = parseSourceCode(response.result) sourceCode = parseSourceCode(response.result)

8
src/types/File.ts Normal file
View File

@@ -0,0 +1,8 @@
import { Link } from './Link'
export interface File {
id: string
name: string
parentUri: string
links: Link[]
}

View File

@@ -1,6 +1,7 @@
export * from './Context' export * from './Context'
export * from './CsrfToken' export * from './CsrfToken'
export * from './Folder' export * from './Folder'
export * from './File'
export * from './Job' export * from './Job'
export * from './JobDefinition' export * from './JobDefinition'
export * from './JobResult' export * from './JobResult'

View File

@@ -2,19 +2,30 @@ const path = require('path')
const webpack = require('webpack') const webpack = require('webpack')
const terserPlugin = require('terser-webpack-plugin') const terserPlugin = require('terser-webpack-plugin')
const defaultPlugins = [
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/),
new webpack.SourceMapDevToolPlugin({
filename: null,
exclude: [/node_modules/],
test: /\.ts($|\?)/i
})
]
const optimization = {
minimize: true,
minimizer: [
new terserPlugin({
parallel: true,
terserOptions: {}
})
]
}
const browserConfig = { const browserConfig = {
entry: './src/index.ts', entry: './src/index.ts',
devtool: 'inline-source-map', devtool: 'inline-source-map',
mode: 'production', mode: 'production',
optimization: { optimization: optimization,
minimize: true,
minimizer: [
new terserPlugin({
parallel: true,
terserOptions: {}
})
]
},
module: { module: {
rules: [ rules: [
{ {
@@ -35,20 +46,26 @@ const browserConfig = {
library: 'SASjs' library: 'SASjs'
}, },
plugins: [ plugins: [
new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /en/), ...defaultPlugins,
new webpack.SourceMapDevToolPlugin({
filename: null,
exclude: [/node_modules/],
test: /\.ts($|\?)/i
}),
new webpack.ProvidePlugin({ new webpack.ProvidePlugin({
process: 'process/browser' process: 'process/browser'
}) })
] ]
} }
const browserConfigWithoutProcessPlugin = {
entry: browserConfig.entry,
devtool: browserConfig.devtool,
mode: browserConfig.mode,
optimization: optimization,
module: browserConfig.module,
resolve: browserConfig.resolve,
output: browserConfig.output,
plugins: defaultPlugins
}
const nodeConfig = { const nodeConfig = {
...browserConfig, ...browserConfigWithoutProcessPlugin,
target: 'node', target: 'node',
entry: './node/index.ts', entry: './node/index.ts',
output: { output: {