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

Compare commits

...

140 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
sabhas a3cc274ef1 chore: no need to appendRequest from then block when there is jobExecutionError 2022-06-10 15:38:03 +05:00
sabhas 451d0906fa chore: update error message 2022-06-10 15:31:09 +05:00
sabhas 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
medjedovic 076ed1cc7a chore: added special missing test 2022-05-23 12:59:48 +02:00
medjedovic 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
medjedovic 6d47174a5e fix: csrf token fetch and empty webout promise finish 2022-05-20 16:43:46 +02:00
Allan Bowe ae13ca523c Merge pull request #474 from sasjs/auto-tests
sasjs-tests automation
2022-05-17 12:17:08 +03:00
medjedovic 843cee4dbe chore: fixed test err handling 2022-05-16 15:42:12 +02:00
Yury Shkoda 1ac88ae102 chore: changed let to const 2022-05-12 10:04:38 +03:00
Yury Shkoda a79766c00c chore(cypress): removed comment 2022-05-12 09:54:27 +03:00
Yury Shkoda 3bc70b91b8 fix(npm): pined dep versions 2022-05-12 09:49:04 +03:00
Yury Shkoda b0b1c32180 feat(npm): ignored files not needed for npm package 2022-05-12 09:35:43 +03:00
Yury Shkoda 5b81e0bf4a chore: add blank lines at the end of the files 2022-05-12 09:14:24 +03:00
Yury Shkoda 54a33ac98a chore(lint): fixed lint issues in cypress folder 2022-05-12 09:06:52 +03:00
Yury Shkoda dd237ceeec feat(lint): add cypress folder to lint scripts 2022-05-12 09:06:05 +03:00
medjedovic 552540fb88 fix: sasjs log not append to requests 2022-05-11 23:00:32 +02:00
medjedovic 6d50d4030e chore(git): Merge branch 'master' into auto-tests 2022-05-11 18:00:03 +02:00
Allan Bowe 8856c3b6ec Merge pull request #708 from sasjs/client-deprecate
ClientID when login to SASJS server deprecation
2022-05-11 17:27:57 +03:00
Saad Jutt b8ea618f1e fix: removed getAuthCode function 2022-05-11 19:22:01 +05:00
Saad Jutt 10c72e6483 fix(login): making login requet with CSRF for SASJS server 2022-05-11 19:07:30 +05:00
Saad Jutt 9d03b54fba chore: Merge branch 'master' into client-deprecate 2022-05-11 19:02:37 +05:00
Allan Bowe 38ad9abfbc Merge pull request #709 from sasjs/lint-precommit
chore: running lint as pre-commit hook
2022-05-11 15:32:02 +03:00
medjedovic 42d9a85cfa chore: pull request template update 2022-05-11 14:24:09 +02:00
medjedovic 39f14cc5a0 chore: formatting 2022-05-11 12:04:44 +02:00
medjedovic 29a65052dc chore: pre commit hook 2022-05-10 14:54:34 +02:00
medjedovic 63328163ab chore: pre-push run lint 2022-05-10 14:09:41 +02:00
Allan Bowe aebf4ea8d8 Update pre-commit 2022-05-10 12:22:40 +01:00
Allan Bowe 59e3edf4b3 Update pre-commit 2022-05-10 11:42:35 +01:00
medjedovic 8f309143e9 chore: comment 2022-05-10 12:37:50 +02:00
medjedovic a65d4257a5 chore: running lint as pre-commit hook 2022-05-10 12:32:05 +02:00
medjedovic 5d2f1d306a style: lint 2022-05-09 16:08:49 +02:00
medjedovic f7bd63ee7f chore: removing clientid 2022-05-09 16:05:50 +02:00
medjedovic a4cd320272 chore: utils import fix 2022-05-09 15:47:24 +02:00
medjedovic c243f25477 fix: deprecating sasjs client id 2022-05-09 13:26:17 +02:00
medjedovic 382a19ccfc chore: ci 2022-05-04 13:19:26 +02:00
medjedovic acc56e3a53 chore: ci 2022-05-04 13:10:11 +02:00
medjedovic 1d0fb1774a ci: fix 2022-05-04 12:56:56 +02:00
medjedovic b67340cc70 chore: ci 2022-04-29 16:12:51 +02:00
medjedovic 831023fe1c chore: ci 2022-04-29 15:58:12 +02:00
medjedovic 04f7cdf8ff chore: ci 2022-04-29 15:42:33 +02:00
medjedovic d01a6a60ad chore: ci 2022-04-29 15:34:45 +02:00
medjedovic 7c17f0f584 chore: ci 2022-04-29 15:25:39 +02:00
medjedovic 092b995d14 ci: test 2022-04-29 15:23:21 +02:00
medjedovic eb296bf93c chore: ci fix 2022-04-29 14:53:20 +02:00
medjedovic e2e15ce8e1 fix: sasjs login login callback 2022-04-29 14:26:34 +02:00
medjedovic 3c59db91cd chore(git): Merge branch 'master' into auto-tests 2022-04-29 12:40:51 +02:00
medjedovic a97b2f43ca chore: sasjs login fix 2022-04-29 12:40:38 +02:00
medjedovic fdd3a261e5 chore: package-lock refresh 2022-04-29 11:24:24 +02:00
medjedovic f69e5afaf0 style: lint 2022-04-28 20:48:21 +02:00
medjedovic 7b78f65c4a fix: utils import, tests fix, auto testing script 2022-04-28 20:46:36 +02:00
Allan Bowe 72ed5e3fab chore: update test suite README with lrecl option 2022-04-28 11:11:46 +01:00
Muhammad Saad 6bfd7024ce Merge pull request #705 from sasjs/issue-703
sasjs server type - request and job execution auth fix
2022-04-27 19:18:44 -07:00
medjedovic aabe473ef6 style: lint 2022-04-27 16:26:37 +02:00
medjedovic 8a7d08c3b9 chore(git): Merge branch 'master' into auto-tests 2022-04-27 16:26:02 +02:00
medjedovic fdc3e1cce8 style: lint 2022-04-26 17:41:35 +02:00
medjedovic fc47222830 fix: web request method - login callback handling 2022-04-26 17:37:13 +02:00
Yury Shkoda 0a5de21386 Merge pull request #704 from sasjs/chore
chore: removed console.log
2022-04-26 17:34:58 +03:00
Yury Shkoda 1cbe57d512 chore: removed console.log 2022-04-26 17:25:21 +03:00
medjedovic 936e4f8c0a fix: sasjs server type - request and job execution auth fix 2022-04-26 16:18:36 +02:00
Allan Bowe 4ebf949912 Merge pull request #699 from sasjs/special-missing
Special missing function from @sasjs/utils
2022-04-26 13:54:11 +03:00
medjedovic c00c8007e5 chore: utils update 2022-04-26 12:27:55 +02:00
medjedovic 54516665bf chore: string escaping 2022-04-22 11:54:59 +02:00
medjedovic ecec2e77c0 chore: error improved 2022-04-21 15:45:59 +02:00
Allan Bowe 102898ac33 Merge pull request #700 from sasjs/parse-log-in-executeScript
fix: parse log in executeScript method on sasjs server
2022-04-18 21:31:31 +03:00
sabhas 7370a2be4c fix: can not read property map of undefined 2022-04-18 23:28:12 +05:00
sabhas 135d019026 chore: update tsdoc for executeScript method 2022-04-18 22:56:51 +05:00
sabhas e2651344d7 fix: parse log in executeScript method on sasjs server 2022-04-18 22:50:27 +05:00
medjedovic 9bf3885868 chore: test fix 2022-04-18 14:54:50 +02:00
medjedovic caa5aa47dc fix: isSpecialMissing from utils 2022-04-18 14:49:57 +02:00
Allan Bowe 7a42bc1b88 Merge pull request #698 from sasjs/executeScriptSASjs
feat: add method for executing scripts on sasjs server
2022-04-13 21:49:46 +03:00
Allan Bowe 6c02ee4cd6 Update SASjs.ts 2022-04-13 19:49:16 +01:00
sabhas 73ee214b61 feat: add method for executing scripts on sasjs server 2022-04-13 18:22:26 +05:00
Muhammad Saad 77487bfa35 Merge pull request #696 from sasjs/certificate-error
fix(error): throw Certificate error wherever possible
2022-04-08 14:32:02 -07:00
Saad Jutt 9cf0165cf7 chore(error): removed extra prefix of Error: 2022-04-08 14:13:41 +05:00
Saad Jutt e4d4b3142f chore: updated error message 2022-04-08 00:01:15 +05:00
Saad Jutt a87be39b44 fix(error): throw Certificate error wherever possible 2022-04-07 23:57:44 +05:00
Allan Bowe 8ea621ac98 Merge pull request #687 from sasjs/issue-686
fix: file upload on SASJS SERVER
2022-04-04 12:37:29 +03:00
medjedovic ea61119919 chore(git): Merge branch 'master' into issue-686 2022-03-31 15:09:21 +02:00
Allan Bowe 01235616a0 Merge pull request #690 from sasjs/issue-689
fix: update code for extracting JSON URL on viya4 when debug is enabled
2022-03-31 15:54:20 +03:00
sabhas 6cab245cde fix: update code for extracting json url when debug is enabled 2022-03-30 01:22:03 +05:00
medjedovic ed90cd8036 chore: uploadFile test added 2022-03-28 17:32:30 +02:00
Allan Bowe 784bab4522 fix: adding README for new sendMacVars service 2022-03-28 12:30:01 +00:00
medjedovic b0eb8b07a8 style: lint 2022-03-25 18:11:20 +01:00
medjedovic 1d1ef7179e fix: file upload on SASJS SERVER
Also fixed appendRequest when debug on while on SASJS SERVER
2022-03-25 18:05:47 +01:00
medjedovic d2ea67e5d6 chore: docs 2022-03-08 18:32:45 +01:00
medjedovic b0df4cb7ee fix: special missings accept - regular missing . 2022-03-08 18:28:48 +01:00
medjedovic 1e5e803c92 chore(git): Merge branch 'master' into auto-tests 2021-08-16 13:49:38 +02:00
medjedovic ad34a9a4db chore: sleep removed 2021-08-16 12:12:52 +02:00
medjedovic 11e006741f chore: openvpn version 2021-08-16 11:17:41 +02:00
medjedovic 0bf385d1e0 fix: login issues 2021-07-29 14:27:17 +02:00
medjedovic b217499b59 chore: addressing comments 2021-07-28 18:18:26 +02:00
medjedovic d123296359 chore: code comment 2021-07-27 14:55:24 +02:00
medjedovic 50ab866652 style: lint 2021-07-27 14:15:23 +02:00
medjedovic f1252537a6 chore(git): Merge branch 'fixing-sas9-tests' into auto-tests 2021-07-27 14:12:11 +02:00
medjedovic b1682b6f32 ci: script test done 2021-07-27 14:07:16 +02:00
medjedovic ded7990096 fix: viya with web approach adding 2 underscores in front of program param 2021-07-27 14:05:34 +02:00
medjedovic 52e95e3455 ci: script test 2021-07-27 12:38:33 +02:00
medjedovic 1d13252045 ci: script test 2021-07-27 12:19:14 +02:00
medjedovic f4630309de chore: ci script fixing 2021-07-27 12:18:44 +02:00
medjedovic 27b6e46973 chore(git): Merge branch 'master' into auto-tests 2021-07-26 19:45:23 +02:00
medjedovic e5262a18d4 chore: sending message to slack if cypress with sasjs-tests fails 2021-07-26 19:38:59 +02:00
medjedovic cf10e83e91 test: cypress running with debug on and off, testing all request approaches 2021-07-26 19:16:47 +02:00
medjedovic 0bd156141c chore(git): Merge branch 'master' into fixing-sas9-tests 2021-07-26 14:53:52 +02:00
medjedovic 890608a3e8 test: requests for every approach 2021-07-26 14:52:26 +02:00
medjedovic a615c5fdb6 style: lint 2021-07-24 18:07:17 +02:00
medjedovic ca7ee83f7f chore: fixing multiple login attempts by adding pause between calling functions 2021-07-24 18:06:15 +02:00
medjedovic 97a530cc66 style: lint 2021-07-22 14:44:13 +02:00
medjedovic 317c8c81a0 chore: JES test disable on SAS9 2021-07-22 13:48:11 +02:00
medjedovic c87776ca1b chore(git): Merge branch 'master' into fixing-sas9-tests 2021-07-22 13:44:23 +02:00
medjedovic 04032831c3 fix: debug on test & make error and parse log test 2021-07-22 13:43:50 +02:00
medjedovic c8fb141048 chore: run sasjs testing as a part of npm release 2021-07-20 11:19:41 +02:00
medjedovic a429581089 test: fix 2021-07-16 12:32:48 +02:00
medjedovic dd05258dd2 test: fixing 2021-07-16 12:32:35 +02:00
medjedovic c0e5327e49 chore: cypress fix 2021-07-16 12:09:59 +02:00
medjedovic ffba2acad0 chore: script fix 2021-07-16 12:02:03 +02:00
medjedovic 64c624bf2e chore: config.json fix 2021-07-16 11:56:15 +02:00
medjedovic 2506d28dd4 chore: script fdix deploy sas9 2021-07-16 11:41:18 +02:00
medjedovic b6a438d222 chore: script fix 2021-07-16 11:38:17 +02:00
medjedovic 6f24a55a04 chore: script replace in files install timing 2021-07-16 11:35:15 +02:00
medjedovic 55045eb101 chore: script adapter version 2021-07-16 11:33:21 +02:00
medjedovic f5e367a645 chore: cpn added 2021-07-16 11:29:22 +02:00
medjedovic 017dc3a8b5 chore: script fix 2021-07-16 11:27:53 +02:00
medjedovic 7285a3f40b chore: adding known_hosts 2021-07-16 11:23:49 +02:00
medjedovic 438b39ceca chore: script fix 2021-07-16 11:20:26 +02:00
medjedovic a39b9ea38f chore: auto deploying sasjs-tests 2021-07-16 11:14:42 +02:00
medjedovic 836c3ba518 chore(git): Merge branch 'master' into auto-tests 2021-07-16 10:22:50 +02:00
medjedovic 2d2b4660dc chore(git): Merge branch 'tests-fixing' into auto-tests 2021-07-16 10:21:13 +02:00
medjedovic f25035ae1d chore: cypress added and sasjs test written 2021-07-15 14:30:51 +02:00
medjedovic ff32f648da chore: tests fixing 2021-07-15 13:41:39 +02:00
60 changed files with 26261 additions and 22123 deletions
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
# Using `--silent` helps for showing any errs in the first line of the response
# The first line is picked up by the VS Code GIT UI popup when rc is not 0
if npm run --silent lint:silent ; then
exit 0
else
npm run --silent lint:fix
echo "❌ Prettier check failed! We ran lint:fix for you. Please add & commit again."
exit 1
fi
+30
View File
@@ -0,0 +1,30 @@
cipher AES-256-CBC
setenv FORWARD_COMPATIBLE 1
client
server-poll-timeout 4
nobind
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 443 tcp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
dev tun
dev-type tun
ns-cert-type server
setenv opt tls-version-min 1.0 or-highest
reneg-sec 604800
sndbuf 0
rcvbuf 0
# NOTE: LZO commands are pushed by the Access Server at connect time.
# NOTE: The below line doesn't disable LZO.
comp-lzo no
verb 3
setenv PUSH_PEER_INFO
ca ca.crt
cert user.crt
key user.key
tls-auth tls.key 1
+62 -5
View File
@@ -4,7 +4,6 @@
name: SASjs Build
on:
push:
pull_request:
jobs:
@@ -22,19 +21,77 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Check npm audit
run: npm audit --production --audit-level=low
- name: Install Dependencies
run: npm ci
- name: Check code style
run: npm run lint
- name: Run unit tests
run: npm test
- name: Generate coverage report
uses: artiomtr/jest-coverage-report-action@v2.0-rc.2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build Package
run: npm run package:lib
env:
CI: true
- name: Install SSH Key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.DCGITLAB_KEY }}
known_hosts: 'placeholder'
- name: Write VPN Files
run: |
echo "$CA_CRT" > .github/vpn/ca.crt
echo "$USER_CRT" > .github/vpn/user.crt
echo "$USER_KEY" > .github/vpn/user.key
echo "$TLS_KEY" > .github/vpn/tls.key
shell: bash
env:
CA_CRT: ${{ secrets.CA_CRT}}
USER_CRT: ${{ secrets.USER_CRT }}
USER_KEY: ${{ secrets.USER_KEY }}
TLS_KEY: ${{ secrets.TLS_KEY }}
- name: Install Open VPN
run: |
sudo apt install apt-transport-https
sudo wget https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub
sudo apt-key add openvpn-repo-pkg-key.pub
sudo wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-focal.list
sudo apt update
sudo apt install openvpn3=16~beta+focal
- name: Start Open VPN 3
run: openvpn3 session-start --config .github/vpn/config.ovpn
- name: Deploy sasjs-tests
run: |
npm install -g replace-in-files-cli
cd sasjs-tests
replace-in-files --regex='"@sasjs/adapter".*' --replacement='"@sasjs/adapter":"latest",' ./package.json
npm i
replace-in-files --regex='"serverUrl".*' --replacement='"serverUrl":"${{ secrets.SASJS_SERVER_URL }}",' ./public/config.json
replace-in-files --regex='"userName".*' --replacement='"userName":"${{ secrets.SASJS_USERNAME }}",' ./public/config.json
replace-in-files --regex='"password".*' --replacement='"password":"${{ secrets.SASJS_PASSWORD }}",' ./public/config.json
replace-in-files --regex='"serverType".*' --replacement='"serverType":"SASJS",' ./public/config.json
npm run update:adapter && npm run build
scp -o stricthostkeychecking=no -r ./build/* ${{ secrets.DCGITLAB_DEPLOY_PATH_VIYA }}
- name: Run cypress on sasjs
run: |
replace-in-files --regex='"sasjsTestsUrl".*' --replacement='"sasjsTestsUrl":"${{ secrets.SASJS_TEST_URL_VIYA }}",' ./cypress.json
replace-in-files --regex='"username".*' --replacement='"username":"${{ secrets.SASJS_USERNAME }}",' ./cypress.json
replace-in-files --regex='"password".*' --replacement='"password":"${{ secrets.SASJS_PASSWORD }}",' ./cypress.json
sh ./sasjs-cypress-run.sh ${{ secrets.DISCORD_WEBHOOK }} https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}
# For some reason if coverage report action is run before other commands, those commands can't access the directories and files on which they depend on
- name: Generate coverage report
uses: artiomtr/jest-coverage-report-action@v2.0-rc.2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
+15 -2
View File
@@ -11,15 +11,28 @@ on:
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [lts/fermium]
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
run: npm ci
- name: Check code style
run: npm run lint
- name: Build Project
run: npm run build
- name: Semantic Release
uses: cycjimmy/semantic-release-action@v2
env:
+10
View File
@@ -4,3 +4,13 @@ docs/
*.md
*.spec.ts
.all-contributorsrc
cypress/
.gitpod.yml
.prettierrc
cypress.json
jest.config.js
sasjs-cypress-run.sh
tsconfig.json
tslint.json
typedoc.json
webpack.config.js
+1 -1
View File
@@ -16,5 +16,5 @@ No PR (that involves a non-trivial code change) should be merged, unless all ite
- [ ] All `sasjs-cli` unit tests are passing (`npm test`).
- [ ] All `sasjs-tests` are passing (instructions available [here](https://github.com/sasjs/adapter/blob/master/sasjs-tests/README.md)).
- (CI Runs this) All `sasjs-tests` are passing. If you want to run it manually (instructions available [here](https://github.com/sasjs/adapter/blob/master/sasjs-tests/README.md)).
- [ ] [Data Controller](https://datacontroller.io) builds and is functional on both SAS 9 and Viya
+1 -1
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 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.
The following table illustrates the formats applied to columns under various scenarios:
+11
View File
@@ -0,0 +1,11 @@
{
"chromeWebSecurity": false,
"defaultCommandTimeout": 20000,
"env": {
"sasjsTestsUrl": "",
"username": "",
"password": "",
"screenshotOnRunFailure": false,
"testingFinishTimeout": 600000
}
}
+78
View File
@@ -0,0 +1,78 @@
const sasjsTestsUrl = Cypress.env('sasjsTestsUrl')
const username = Cypress.env('username')
const password = Cypress.env('password')
const testingFinishTimeout = Cypress.env('testingFinishTimeout')
context('sasjs-tests', function () {
this.beforeAll(() => {
cy.visit(sasjsTestsUrl)
})
this.beforeEach(() => {
cy.reload()
})
it('Should have all tests successfull', (done) => {
cy.get('body').then(($body) => {
if ($body.find('input[placeholder="User Name"]').length > 0) {
cy.get('input[placeholder="User Name"]').type(username)
cy.get('input[placeholder="Password"]').type(password)
cy.get('.submit-button').click()
}
cy.get('input[placeholder="User Name"]', { timeout: 40000 })
.should('not.exist')
.then(() => {
cy.get('.ui.massive.icon.primary.left.labeled.button')
.click()
.then(() => {
cy.get('.ui.massive.loading.primary.button', {
timeout: testingFinishTimeout
})
.should('not.exist')
.then(() => {
cy.get('span.icon.failed')
.should('not.exist')
.then(() => {
done()
})
})
})
})
})
})
it('Should have all tests successfull with debug on', (done) => {
cy.get('body').then(($body) => {
if ($body.find('input[placeholder="User Name"]').length > 0) {
cy.get('input[placeholder="User Name"]').type(username)
cy.get('input[placeholder="Password"]').type(password)
cy.get('.submit-button').click()
}
cy.get('.ui.fitted.toggle.checkbox label')
.click()
.then(() => {
cy.get('input[placeholder="User Name"]', { timeout: 40000 })
.should('not.exist')
.then(() => {
cy.get('.ui.massive.icon.primary.left.labeled.button')
.click()
.then(() => {
cy.get('.ui.massive.loading.primary.button', {
timeout: testingFinishTimeout
})
.should('not.exist')
.then(() => {
cy.get('span.icon.failed')
.should('not.exist')
.then(() => {
done()
})
})
})
})
})
})
})
})
+31
View File
@@ -0,0 +1,31 @@
const wp = require('@cypress/webpack-preprocessor')
const webpackOptions = {
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
loaders: ['ts-loader'],
exclude: [/node_modules/]
},
{
test: /\.(html|css)$/,
loader: 'raw-loader',
exclude: /\.async\.(html|css)$/
},
{
test: /\.async\.(html|css)$/,
loaders: ['file?name=[name].[hash].[ext]', 'extract']
}
]
}
}
const options = {
webpackOptions
}
module.exports = wp(options)
+42
View File
@@ -0,0 +1,42 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
const wp = require('@cypress/webpack-preprocessor')
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
const options = {
webpackOptions: require('../webpack.config.js')
}
on('file:preprocessor', wp(options))
on('before:browser:launch', (browser = {}, launchOptions) => {
if (browser.name === 'chrome') {
launchOptions.args.push('--disable-site-isolation-trials')
launchOptions.args.push('--auto-open-devtools-for-tabs')
launchOptions.args.push('--aggressive-cache-discard')
launchOptions.args.push('--disable-cache')
launchOptions.args.push('--disable-application-cache')
launchOptions.args.push('--disable-offline-load-stale-cache')
launchOptions.args.push('--disk-cache-size=0')
return launchOptions
}
})
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"strict": true,
"baseUrl": "../node_modules",
"target": "es6",
"lib": ["es2019", "dom"],
"types": ["cypress"]
},
"include": ["**/*.ts"]
}
+23
View File
@@ -0,0 +1,23 @@
module.exports = {
mode: 'development',
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
exclude: [/node_modules/],
use: [
{
loader: 'ts-loader',
options: {
// skip typechecking for speed
transpileOnly: true
}
}
]
}
]
}
}
+12034 -4951
View File
File diff suppressed because it is too large Load Diff
+13 -5
View File
@@ -8,14 +8,17 @@
"build": "rimraf build && rimraf node && mkdir node && copyfiles -u 1 \"./src/**/*\" ./node && webpack && rimraf build/src && rimraf node",
"package:lib": "npm run build && copyfiles ./package.json ./checkNodeVersion.js build && cd build && npm version \"5.0.0\" && npm pack",
"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": "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:fix": "npx prettier --loglevel silent --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --loglevel silent --write \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --loglevel silent --write \"cypress/**/*.{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}\" && npx prettier --check \"cypress/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
"lint:silent": "npx prettier --loglevel silent --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --loglevel silent --check \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --loglevel silent --check \"cypress/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
"test": "jest --silent --coverage",
"prepublishOnly": "cp -r ./build/* . && rm -rf ./build",
"postpublish": "git clean -fd",
"semantic-release": "semantic-release",
"typedoc": "node createTSDocs",
"prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks && git config core.autocrlf false || true"
"prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks && git config core.autocrlf false || true",
"cypress": "cypress open",
"cy:run": "cypress run"
},
"publishConfig": {
"access": "public"
@@ -40,6 +43,7 @@
},
"license": "ISC",
"devDependencies": {
"@cypress/webpack-preprocessor": "5.9.1",
"@types/axios": "0.14.0",
"@types/express": "4.17.13",
"@types/form-data": "2.5.0",
@@ -49,6 +53,7 @@
"@types/tough-cookie": "4.0.1",
"copyfiles": "2.4.1",
"cp": "0.2.0",
"cypress": "7.7.0",
"dotenv": "16.0.0",
"express": "4.17.3",
"jest": "27.4.7",
@@ -56,15 +61,18 @@
"node-polyfill-webpack-plugin": "1.1.4",
"path": "0.12.7",
"pem": "1.14.6",
"prettier": "2.7.1",
"process": "0.11.10",
"rimraf": "3.0.2",
"semantic-release": "18.0.0",
"semantic-release": "19.0.3",
"terser-webpack-plugin": "5.3.1",
"ts-jest": "27.1.3",
"ts-loader": "9.2.6",
"tslint": "6.1.3",
"tslint-config-prettier": "1.18.0",
"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",
"typescript": "4.5.5",
"webpack": "5.69.0",
@@ -72,7 +80,7 @@
},
"main": "index.js",
"dependencies": {
"@sasjs/utils": "2.42.0",
"@sasjs/utils": "2.44.0",
"axios": "0.26.0",
"axios-cookiejar-support": "1.0.1",
"form-data": "4.0.0",
+10
View File
@@ -0,0 +1,10 @@
#!/bin/bash
if npm run cy:run -- --spec "cypress/integration/sasjs.tests.ts" ; then
echo "Cypress sasjs testing passed!"
else
curl -X POST --header "Content-Type:application/json" --data '{"username":"GitHub CI - Adapter SASJS-TESTS (FAIL)", "content":"Automated sasjs-tests failed on the @sasjs/adapter PR on following link.\n'$2'", "avatar_url":"https://i.ibb.co/Lpk7Xvq/error-outline.png"}' $1
echo "Cypress sasjs testing failed!"
exit 1
fi
+25 -53
View File
@@ -60,86 +60,58 @@ 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.
### SAS 9
The code below will work on ALL SAS platforms (Viya, SAS 9 EBI, SASjs Server).
```sas
filename mc url "https://raw.githubusercontent.com/sasjs/core/main/all.sas";
%inc mc;
filename ft15f001 temp;
filename ft15f001 temp lrecl=1000;
parmcards4;
%webout(FETCH)
%webout(OPEN)
%macro x();
%do i=1 %to &_webin_file_count; %webout(OBJ,&&_webin_name&i,missing=STRING,showmeta=YES) %end;
%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;
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;
parmcards4;
%webout(FETCH)
%webout(OPEN)
%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);
%webout(OBJ,&table,missing=STRING,showmeta=YES)
%end;
%mend;
%x()
%else %do i=1 %to &_webin_file_count;
%webout(OBJ,&&_webin_name&i,missing=STRING,showmeta=YES)
%end;
%mend; %x()
%webout(CLOSE)
;;;;
%mp_createwebservice(path=/Public/app/common,name=sendObj)
filename ft15f001 temp;
%mx_createwebservice(path=/Public/app/common,name=sendObj)
parmcards4;
%webout(FETCH)
%webout(OPEN)
%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);
%webout(ARR,&table,missing=STRING,showmeta=YES)
%end;
%mend;
%x()
%else %do i=1 %to &_webin_file_count;
%webout(ARR,&&_webin_name&i,missing=STRING,showmeta=YES)
%end;
%mend; %x()
%webout(CLOSE)
;;;;
%mp_createwebservice(path=/Public/app/common,name=sendArr)
filename ft15f001 temp;
%mx_createwebservice(path=/Public/app/common,name=sendArr)
parmcards4;
data work.macvars;
set sashelp.vmacro;
run;
%webout(OPEN)
%webout(OBJ,macvars)
%webout(CLOSE)
;;;;
%mx_createwebservice(path=/Public/app/common,name=sendMacVars)
parmcards4;
If you can keep your head when all about you
Are losing theirs and blaming it on you,
If you can trust yourself when all men doubt you,
But make allowance for their doubting too;
;;;;
%mp_createwebservice(path=/Public/app/common,name=makeErr)
%mx_createwebservice(path=/Public/app/common,name=makeErr)
parmcards4;
%webout(OPEN)
data _null_;
@@ -148,7 +120,7 @@ data _null_;
run;
%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.
+13451 -16900
View File
File diff suppressed because it is too large Load Diff
+3 -3
View File
@@ -5,7 +5,7 @@
"private": true,
"dependencies": {
"@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/node": "^14.14.41",
"@types/react": "^17.0.1",
@@ -14,7 +14,7 @@
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router-dom": "^5.2.0",
"react-scripts": "^4.0.2",
"react-scripts": "^5.0.1",
"typescript": "^4.1.3"
},
"scripts": {
@@ -43,6 +43,6 @@
]
},
"devDependencies": {
"node-sass": "^6.0.1"
"node-sass": "^7.0.1"
}
}
+3 -1
View File
@@ -6,6 +6,7 @@ import { specialCaseTests } from './testSuites/SpecialCases'
import { sasjsRequestTests } from './testSuites/SasjsRequests'
import '@sasjs/test-framework/dist/index.css'
import { computeTests } from './testSuites/Compute'
import { fileUploadTests } from './testSuites/FileUpload'
const App = (): ReactElement<{}> => {
const { adapter, config } = useContext(AppContext)
@@ -18,7 +19,8 @@ const App = (): ReactElement<{}> => {
sendArrTests(adapter),
sendObjTests(adapter),
specialCaseTests(adapter),
sasjsRequestTests(adapter)
sasjsRequestTests(adapter),
fileUploadTests(adapter)
]
if (adapter.getSasjsConfig().serverType === 'SASVIYA') {
+3 -3
View File
@@ -9,7 +9,7 @@ const Login = (): ReactElement<{}> => {
const appContext = useContext(AppContext)
const handleSubmit = useCallback(
(e) => {
(e: any) => {
e.preventDefault()
appContext.adapter.logIn(username, password).then((res) => {
appContext.setIsLoggedIn(res.isLoggedIn)
@@ -28,7 +28,7 @@ const Login = (): ReactElement<{}> => {
placeholder="User Name"
value={username}
required
onChange={(e) => setUsername(e.target.value)}
onChange={(e: any) => setUsername(e.target.value)}
/>
</div>
<div className="row">
@@ -38,7 +38,7 @@ const Login = (): ReactElement<{}> => {
type="password"
value={password}
required
onChange={(e) => setPassword(e.target.value)}
onChange={(e: any) => setPassword(e.target.value)}
/>
</div>
<button type="submit" className="submit-button">
+5 -14
View File
@@ -61,7 +61,7 @@ export const basicTests = (
'Should fail on first attempt and should log the user in on second attempt',
test: async () => {
await adapter.logOut()
await adapter.logIn('invalid', 'invalid')
await adapter.logIn('invalid', 'invalid').catch((err: any) => {})
return await adapter.logIn(userName, password)
},
assertion: (response: any) =>
@@ -161,26 +161,17 @@ export const basicTests = (
}
},
{
title: 'Request with extra attributes on JES approach',
description:
'Should complete successful request with extra attributes present in response',
title: 'Web request',
description: 'Should run the request with old web approach',
test: async () => {
const config: Partial<SASjsConfig> = {
useComputeApi: false
}
return await adapter.request(
'common/sendArr',
stringData,
config,
undefined,
undefined,
['file', 'data']
)
return await adapter.request('common/sendArr', stringData, config)
},
assertion: (response: any) => {
const responseKeys: any = Object.keys(response)
return responseKeys.includes('file') && responseKeys.includes('data')
return response.table1[0][0] === stringData.table1[0].col1
}
}
]
+26
View File
@@ -1,9 +1,35 @@
import SASjs from '@sasjs/adapter'
import { TestSuite } from '@sasjs/test-framework'
const stringData: any = { table1: [{ col1: 'first col value' }] }
export const computeTests = (adapter: SASjs): TestSuite => ({
name: 'Compute',
tests: [
{
title: 'Compute API request',
description: 'Should run the request with compute API approach',
test: async () => {
return await adapter.request('common/sendArr', stringData)
},
assertion: (response: any) => {
return response.table1[0][0] === stringData.table1[0].col1
}
},
{
title: 'JES API request',
description: 'Should run the request with JES API approach',
test: async () => {
const config = {
useComputeApi: false
}
return await adapter.request('common/sendArr', stringData, config)
},
assertion: (response: any) => {
return response.table1[0][0] === stringData.table1[0].col1
}
},
{
title: 'Start Compute Job - not waiting for result',
description: 'Should start a compute job and return the session',
+35
View File
@@ -0,0 +1,35 @@
import SASjs from '@sasjs/adapter'
import { TestSuite } from '@sasjs/test-framework'
export const fileUploadTests = (adapter: SASjs): TestSuite => ({
name: 'File Upload Tests',
tests: [
{
title: 'Upload File',
description: 'Should upload the file to VIYA',
test: async () => {
let blob: any = new Blob(['test'], { type: 'text/html' })
blob['lastModifiedDate'] = ''
blob['name'] = 'macvars_testfile'
let file = blob
const filesToUpload = [
{
file: file,
fileName: file.name
}
]
return adapter.uploadFile('common/sendMacVars', filesToUpload, null)
},
assertion: (response: any) =>
(response.macvars as any[]).findIndex(
(el: any) => el.NAME === '_WEBIN_FILE_COUNT' && el.VALUE === '1'
) > -1 &&
(response.macvars as any[]).findIndex(
(el: any) =>
el.NAME === '_WEBIN_FILENAME' && el.VALUE === 'macvars_testfile'
) > -1
}
]
})
+22 -11
View File
@@ -86,7 +86,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
'Should error out with long string values over 32765 characters',
test: () => {
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) => {
return !!error && !!error.error && !!error.error.message
@@ -138,7 +138,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
result =
result &&
res.table1[index][3] ===
(multipleRowsWithNulls.table1[index].col4 || ' ')
(multipleRowsWithNulls.table1[index].col4 || '')
})
return result
}
@@ -164,7 +164,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
result =
result &&
res.table1[index][3] ===
(multipleColumnsWithNulls.table1[index].col4 || ' ')
(multipleColumnsWithNulls.table1[index].col4 || '')
})
return result
}
@@ -182,7 +182,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
const invalidData: any = {
'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) =>
!!error && !!error.error && !!error.error.message
@@ -194,7 +196,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
const invalidData: any = {
'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) =>
!!error && !!error.error && !!error.error.message
@@ -206,7 +210,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
const invalidData: any = {
'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) =>
!!error && !!error.error && !!error.error.message
@@ -218,7 +224,10 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
const invalidData: any = {
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) =>
!!error && !!error.error && !!error.error.message
@@ -230,7 +239,9 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
const invalidData: any = {
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) =>
!!error && !!error.error && !!error.error.message
@@ -264,7 +275,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
test: () => {
return adapter
.request('common/sendObj', getLongStringData(32767))
.catch((e) => e)
.catch((e: any) => e)
},
assertion: (error: any) => {
return !!error && !!error.error && !!error.error.message
@@ -328,7 +339,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
result =
result &&
res.table1[index].COL4 ===
(multipleRowsWithNulls.table1[index].col4 || ' ')
(multipleRowsWithNulls.table1[index].col4 || '')
})
return result
}
@@ -357,7 +368,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
result =
result &&
res.table1[index].COL4 ===
(multipleColumnsWithNulls.table1[index].col4 || ' ')
(multipleColumnsWithNulls.table1[index].col4 || '')
})
return result
}
@@ -37,6 +37,8 @@ const moreSpecialCharData: any = {
]
}
const stringData: any = { table1: [{ col1: 'first col value' }] }
const getWideData = () => {
const cols: any = {}
for (let i = 1; i <= 10000; i++) {
@@ -269,6 +271,34 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
)
}
},
{
title: 'Request with extra attributes on JES approach',
description:
'Should complete successful request with extra attributes present in response',
test: async () => {
if (adapter.getSasjsConfig().serverType !== 'SASVIYA')
return Promise.resolve('skip')
const config = {
useComputeApi: false
}
return await adapter.request(
'common/sendArr',
stringData,
config,
undefined,
undefined,
['file', 'data']
)
},
assertion: (response: any) => {
if (response === 'skip') return true
const responseKeys: any = Object.keys(response)
return responseKeys.includes('file') && responseKeys.includes('data')
}
},
{
title: 'Special missing values',
description: 'Should support special missing values',
+1 -1
View File
@@ -9,7 +9,7 @@ export const assert = (
} else {
result = expression()
}
} catch (e) {
} catch (e: any) {
console.error(message)
throw new Error(message)
}
+1 -1
View File
@@ -31,7 +31,7 @@ describe('SASViyaApiClient', () => {
.mockImplementation(() => Promise.reject('Not Found'))
const error = await sasViyaApiClient
.createFolder('test', '/foo')
.catch((e) => e)
.catch((e: any) => e)
expect(error).toBeInstanceOf(RootFolderNotFoundError)
})
})
+9 -3
View File
@@ -11,7 +11,11 @@ import {
JobDefinition,
PollOptions
} from './types'
import { JobExecutionError, RootFolderNotFoundError } from './types/errors'
import {
CertificateError,
JobExecutionError,
RootFolderNotFoundError
} from './types/errors'
import { SessionManager } from './SessionManager'
import { ContextManager } from './ContextManager'
import { SasAuthResponse, MacroVar, AuthConfig } from '@sasjs/utils/types'
@@ -878,7 +882,8 @@ export class SASViyaApiClient {
const { result: folder } = await this.requestClient
.get<Folder>(`${this.serverUrl}${url}`, accessToken)
.catch(() => {
.catch((err) => {
if (err instanceof CertificateError) throw err
return { result: null }
})
@@ -899,7 +904,8 @@ export class SASViyaApiClient {
const { result: folder } = await this.requestClient
.get<Folder>(`${this.serverUrl}${url}`, accessToken)
.catch(() => {
.catch((err) => {
if (err instanceof CertificateError) throw err
return { result: null }
})
+14 -12
View File
@@ -77,7 +77,7 @@ export default class SASjs {
}
/**
* Executes code against a SAS 9 server. Requires a runner to be present in
* Executes SAS code on a SAS 9 server. Requires a runner to be present in
* the users home directory in metadata.
* @param linesOfCode - lines of sas code from the file to run.
* @param username - a string representing the username.
@@ -97,6 +97,17 @@ export default class SASjs {
)
}
/**
* Executes SAS code on a SASJS server
* @param code - a string of code from the file to run.
* @param authConfig - (optional) a valid client, secret, refresh and access tokens that are authorised to execute scripts.
*/
public async executeScriptSASjs(code: string, authConfig?: AuthConfig) {
this.isMethodSupported('executeScriptSASJS', [ServerType.Sasjs])
return await this.sasJSApiClient?.executeScript(code, authConfig)
}
/**
* Executes sas code in a SAS Viya compute session.
* @param fileName - name of the file to run. It will be converted to path to the file being submitted for execution.
@@ -581,15 +592,6 @@ export default class SASjs {
'A username and password are required when using the default login mechanism.'
)
if (this.sasjsConfig.serverType === ServerType.Sasjs) {
if (!clientId)
throw new Error(
'A username, password and clientId are required when using the default login mechanism with server type SASJS.'
)
return this.authManager!.logInSasjs(username, password, clientId)
}
return this.authManager!.logIn(username, password)
}
@@ -905,8 +907,8 @@ export default class SASjs {
return await this.sasJSApiClient?.deploy(dataJson, appLoc, authConfig)
}
public async executeJobSASjs(query: ExecutionQuery) {
return await this.sasJSApiClient?.executeJob(query)
public async executeJobSASjs(query: ExecutionQuery, authConfig?: AuthConfig) {
return await this.sasJSApiClient?.executeJob(query, authConfig)
}
/**
+37 -17
View File
@@ -3,7 +3,6 @@ import { ExecutionQuery } from './types'
import { RequestClient } from './request/RequestClient'
import { getAccessTokenForSasjs } from './auth/getAccessTokenForSasjs'
import { refreshTokensForSasjs } from './auth/refreshTokensForSasjs'
import { getAuthCodeForSasjs } from './auth/getAuthCodeForSasjs'
import { parseWeboutResponse } from './utils'
import { getTokens } from './auth/getTokens'
@@ -43,7 +42,9 @@ export class SASjsApiClient {
return Promise.resolve(result)
}
public async executeJob(query: ExecutionQuery) {
public async executeJob(query: ExecutionQuery, authConfig?: AuthConfig) {
const access_token = authConfig ? authConfig.access_token : undefined
const { result } = await this.requestClient.post<{
status: string
message: string
@@ -51,7 +52,7 @@ export class SASjsApiClient {
logPath?: string
error?: {}
_webout?: string
}>('SASjsApi/stp/execute', query, undefined)
}>('SASjsApi/stp/execute', query, access_token)
if (Object.keys(result).includes('_webout')) {
result._webout = parseWeboutResponse(result._webout!)
@@ -60,6 +61,39 @@ export class SASjsApiClient {
return Promise.resolve(result)
}
/**
* Executes code on a SASJS server.
* @param code - a string of code to execute.
* @param authConfig - an object for authentication.
*/
public async executeScript(code: string, authConfig?: AuthConfig) {
let access_token = (authConfig || {}).access_token
if (authConfig) {
;({ access_token } = await getTokens(
this.requestClient,
authConfig,
ServerType.Sasjs
))
}
let parsedSasjsServerLog = ''
await this.requestClient
.post('SASjsApi/code/execute', { code }, access_token)
.then((res: any) => {
if (res.result?.log) {
parsedSasjsServerLog = res.result.log
.map((logLine: any) => logLine.line)
.join('\n')
}
})
.catch((err) => {
parsedSasjsServerLog = err
})
return parsedSasjsServerLog
}
/**
* Exchanges the auth code for an access token for the given client.
* @param clientId - the client ID to authenticate with.
@@ -79,20 +113,6 @@ export class SASjsApiClient {
public async refreshTokens(refreshToken: string): Promise<SASjsAuthResponse> {
return refreshTokensForSasjs(this.requestClient, refreshToken)
}
/**
* Performs a login authenticate and returns an auth code for the given client.
* @param username - a string representing the username.
* @param password - a string representing the password.
* @param clientId - the client ID to authenticate with.
*/
public async getAuthCode(
username: string,
password: string,
clientId: string
) {
return getAuthCodeForSasjs(this.requestClient, username, password, clientId)
}
}
// todo move to sasjs/utils
+2 -2
View File
@@ -240,7 +240,7 @@ export async function executeScript(
jobResult = await requestClient
.get<any>(resultLink, access_token, 'text/plain')
.catch(async (e) => {
.catch(async (e: any) => {
if (e instanceof NotFoundError) {
if (logLink) {
const logUrl = `${logLink.href}/content`
@@ -271,7 +271,7 @@ export async function executeScript(
})
return { result: jobResult?.result, log }
} catch (e) {
} catch (e: any) {
interface HttpError {
status: number
}
+9 -9
View File
@@ -80,7 +80,7 @@ describe('executeScript', () => {
'test',
['%put hello'],
'test context'
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while getting session.')
})
@@ -128,7 +128,7 @@ describe('executeScript', () => {
false,
defaultPollOptions,
true
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while getting session variable.')
})
@@ -297,7 +297,7 @@ describe('executeScript', () => {
false,
defaultPollOptions,
true
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while posting job')
})
@@ -367,7 +367,7 @@ describe('executeScript', () => {
true,
defaultPollOptions,
true
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while polling job status.')
})
@@ -393,7 +393,7 @@ describe('executeScript', () => {
true,
defaultPollOptions,
true
).catch((e) => e)
).catch((e: any) => e)
expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith(
requestClient,
@@ -468,7 +468,7 @@ describe('executeScript', () => {
true,
defaultPollOptions,
true
).catch((e) => e)
).catch((e: any) => e)
expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith(
requestClient,
@@ -501,7 +501,7 @@ describe('executeScript', () => {
true,
defaultPollOptions,
true
).catch((e) => e)
).catch((e: any) => e)
expect(fetchLogsModule.fetchLogByChunks).toHaveBeenCalledWith(
requestClient,
@@ -561,7 +561,7 @@ describe('executeScript', () => {
true,
defaultPollOptions,
true
).catch((e) => e)
).catch((e: any) => e)
expect(requestClient.get).toHaveBeenCalledWith(
`/compute/sessions/${mockSession.id}/filerefs/_webout/content`,
@@ -622,7 +622,7 @@ describe('executeScript', () => {
true,
defaultPollOptions,
true
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while clearing session.')
})
+2 -2
View File
@@ -59,7 +59,7 @@ describe('pollJobState', () => {
false,
undefined,
defaultPollOptions
).catch((e) => e)
).catch((e: any) => e)
expect((error as Error).message).toContain('Job state link was not found.')
})
@@ -238,7 +238,7 @@ describe('pollJobState', () => {
false,
undefined,
defaultPollOptions
).catch((e) => e)
).catch((e: any) => e)
expect(error.message).toEqual(
'Error while polling job state for job j0b: Status Error'
+2 -2
View File
@@ -17,7 +17,7 @@ describe('saveLog', () => {
it('should throw an error when a valid access token is not provided', async () => {
const error = await saveLog(mockJob, requestClient, 0, 100, stream).catch(
(e) => e
(e: any) => e
)
expect(error.message).toContain(
@@ -33,7 +33,7 @@ describe('saveLog', () => {
100,
stream,
't0k3n'
).catch((e) => e)
).catch((e: any) => e)
expect(error.message).toContain(
`Log URL for job ${mockJob.id} was not found.`
+2 -2
View File
@@ -30,7 +30,7 @@ describe('uploadTables', () => {
.mockImplementation(() => 'ERROR: LARGE STRING LENGTH')
const error = await uploadTables(requestClient, data, 't0k3n').catch(
(e) => e
(e: any) => e
)
expect(requestClient.uploadFile).not.toHaveBeenCalled()
@@ -46,7 +46,7 @@ describe('uploadTables', () => {
.mockImplementation(() => Promise.reject('Upload Error'))
const error = await uploadTables(requestClient, data, 't0k3n').catch(
(e) => e
(e: any) => e
)
expect(error).toContain('Error while uploading file.')
+1 -1
View File
@@ -28,7 +28,7 @@ describe('writeStream', () => {
jest
.spyOn(stream, 'write')
.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')
})
+1 -1
View File
@@ -4,7 +4,7 @@ export const writeStream = async (
stream: WriteStream,
content: string
): Promise<void> =>
stream.write(content + '\n', (e) => {
stream.write(content + '\n', (e: any) => {
if (e) return Promise.reject(e)
return Promise.resolve()
+30 -53
View File
@@ -2,8 +2,6 @@ import { ServerType } from '@sasjs/utils/types'
import { RequestClient } from '../request/RequestClient'
import { LoginOptions, LoginResult } from '../types/Login'
import { serialize } from '../utils'
import { getAccessTokenForSasjs } from './getAccessTokenForSasjs'
import { getAuthCodeForSasjs } from './getAuthCodeForSasjs'
import { openWebPage } from './openWebPage'
import { verifySas9Login } from './verifySas9Login'
import { verifySasViyaLogin } from './verifySasViyaLogin'
@@ -83,39 +81,6 @@ export class AuthManager {
return { isLoggedIn: false, userName: '' }
}
/**
* Logs into the SAS server with the supplied credentials.
* @param userName - a string representing the username.
* @param password - a string representing the password.
* @param clientId - a string representing the client ID.
* @returns - a boolean `isLoggedin` and a string `username`
*/
public async logInSasjs(
username: string,
password: string,
clientId: string
): Promise<LoginResult> {
const isLoggedIn = await this.sendLoginRequestSasjs(
username,
password,
clientId
)
.then((res) => {
this.userName = username
this.requestClient.saveLocalStorageToken(
res.access_token,
res.refresh_token
)
return true
})
.catch(() => false)
return {
isLoggedIn,
userName: this.userName
}
}
/**
* Logs into the SAS server with the supplied credentials.
* @param username - a string representing the username.
@@ -152,7 +117,7 @@ export class AuthManager {
let loginResponse = await this.sendLoginRequest(loginForm, loginParams)
let isLoggedIn = isLogInSuccess(loginResponse)
let isLoggedIn = isLogInSuccess(this.serverType, loginResponse)
if (!isLoggedIn) {
if (isCredentialsVerifyError(loginResponse)) {
@@ -196,6 +161,17 @@ export class AuthManager {
loginForm: { [key: string]: any },
loginParams: { [key: string]: any }
) {
if (this.serverType === ServerType.Sasjs) {
const { username, password } = loginParams
const { result: loginResponse } = await this.requestClient.post<string>(
this.loginUrl,
{ username, password },
undefined
)
return loginResponse
}
for (const key in loginForm) {
loginParams[key] = loginForm[key]
}
@@ -215,19 +191,6 @@ export class AuthManager {
return loginResponse
}
private async sendLoginRequestSasjs(
username: string,
password: string,
clientId: string
) {
const authCode = await getAuthCodeForSasjs(
this.requestClient,
username,
password,
clientId
)
return getAccessTokenForSasjs(this.requestClient, clientId, authCode)
}
/**
* Checks whether a session is active, or login is required.
* @returns - a promise which resolves with an object containing three values
@@ -248,8 +211,7 @@ export class AuthManager {
//Residue can happen in case of session expiration
await this.logOut()
if (this.serverType !== ServerType.Sasjs)
loginForm = await this.getNewLoginForm()
loginForm = await this.getNewLoginForm()
}
return Promise.resolve({
@@ -260,6 +222,12 @@ export class AuthManager {
}
private async getNewLoginForm() {
if (this.serverType === ServerType.Sasjs) {
// server will be sending CSRF cookie,
// http client will use it automatically
return this.requestClient.get('/', undefined)
}
const { result: formResponse } = await this.requestClient.get<string>(
this.loginUrl.replace('.do', ''),
undefined,
@@ -289,6 +257,12 @@ export class AuthManager {
const isLoggedIn = loginResponse !== 'authErr'
const userName = isLoggedIn ? this.extractUserName(loginResponse) : ''
if (!isLoggedIn) {
//We will logout to make sure cookies are removed and login form is presented
//Residue can happen in case of session expiration
await this.logOut()
}
return { isLoggedIn, userName }
}
@@ -384,5 +358,8 @@ const isCredentialsVerifyError = (response: string): boolean =>
response
)
const isLogInSuccess = (response: string): boolean =>
/You have signed in/gm.test(response)
const isLogInSuccess = (serverType: ServerType, response: any): boolean => {
if (serverType === ServerType.Sasjs) return response?.loggedin
return /You have signed in/gm.test(response)
}
+2
View File
@@ -1,6 +1,7 @@
import { SasAuthResponse } from '@sasjs/utils/types'
import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient } from '../request/RequestClient'
import { CertificateError } from '../types/errors'
/**
* Exchanges the auth code for an access token for the given client.
@@ -36,6 +37,7 @@ export async function getAccessTokenForViya(
.post(url, data, undefined, 'application/x-www-form-urlencoded', headers)
.then((res) => res.result as SasAuthResponse)
.catch((err) => {
if (err instanceof CertificateError) throw err
throw prefixMessage(err, 'Error while getting access token. ')
})
-31
View File
@@ -1,31 +0,0 @@
import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient } from '../request/RequestClient'
/**
* Performs a login authenticate and returns an auth code for the given client.
* @param requestClient - the pre-configured HTTP request client
* @param username - a string representing the username.
* @param password - a string representing the password.
* @param clientId - the client ID to authenticate with.
*/
export const getAuthCodeForSasjs = async (
requestClient: RequestClient,
username: string,
password: string,
clientId: string
) => {
const url = '/SASjsApi/auth/authorize'
const data = { username, password, clientId }
const { code: authCode } = await requestClient
.post<{ code: string }>(url, data, undefined)
.then((res) => res.result)
.catch((err) => {
throw prefixMessage(
err,
'Error while authenticating with provided username, password and clientId. '
)
})
return authCode
}
+1 -1
View File
@@ -53,7 +53,7 @@ describe('getAccessTokenForSasjs', () => {
requestClient,
authConfig.client,
authConfig.refresh_token
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while getting access token')
})
+1 -1
View File
@@ -64,7 +64,7 @@ describe('getAccessTokenForViya', () => {
authConfig.client,
authConfig.secret,
authConfig.refresh_token
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while getting access token')
})
+3 -1
View File
@@ -62,7 +62,9 @@ describe('getTokens', () => {
const expectedError =
'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)
})
+1 -1
View File
@@ -35,7 +35,7 @@ describe('refreshTokensForSasjs', () => {
const error = await refreshTokensForSasjs(
requestClient,
refresh_token
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while refreshing tokens')
})
+1 -1
View File
@@ -63,7 +63,7 @@ describe('refreshTokensForViya', () => {
authConfig.client,
authConfig.secret,
authConfig.refresh_token
).catch((e) => e)
).catch((e: any) => e)
expect(error).toContain('Error while refreshing tokens')
})
@@ -7,7 +7,6 @@ describe('generateFileUploadForm', () => {
}
const BlobMock = jest.fn()
;(global as any).FormData = FormDataMock
;(global as any).Blob = BlobMock
})
+13
View File
@@ -99,7 +99,20 @@ export class FileUploader extends BaseJobExecutor {
? parseWeboutResponse(res.result, uploadUrl)
: res.result
break
case ServerType.Sasjs:
if (typeof res.result._webout === 'object') {
jsonResponse = res.result._webout
} else {
const webout = parseWeboutResponse(
res.result._webout,
uploadUrl
)
jsonResponse = getValidJson(webout)
}
break
}
} else if (this.serverType === ServerType.Sasjs) {
jsonResponse = getValidJson(res.result._webout)
} else {
jsonResponse =
typeof res.result === 'string'
+26 -4
View File
@@ -21,6 +21,7 @@ import {
} from '../utils'
import { BaseJobExecutor } from './JobExecutor'
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
import { Server } from 'https'
export interface WaitingRequstPromise {
promise: Promise<any> | null
@@ -46,7 +47,7 @@ export class WebJobExecutor extends BaseJobExecutor {
authConfig?: AuthConfig,
extraResponseAttributes: ExtraResponseAttributes[] = []
) {
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
const loginCallback = loginRequiredCallback
const program = isRelativePath(sasJob)
? config.appLoc
? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
@@ -79,7 +80,7 @@ export class WebJobExecutor extends BaseJobExecutor {
)
})
await loginCallback()
if (loginCallback) await loginCallback()
} else {
reject(new ErrorResponse(e?.message, e))
}
@@ -91,7 +92,7 @@ export class WebJobExecutor extends BaseJobExecutor {
if (jobUri.length > 0) {
apiUrl += '&_job=' + jobUri
/**
* Using both _job and _program parameters will cause a conflict in the JES web app, as its not clear whether or not the server should make the extra fetch for the job uri.
* Using both _job and _program parameters will cause a conflict in the JES web app, as it's not clear whether or not the server should make the extra fetch for the job uri.
* To handle this, we add the extra underscore and recreate the _program variable in the SAS side of the SASjs adapter so it remains available for backend developers.
*/
apiUrl = apiUrl.replace('_program=', '__program=')
@@ -175,6 +176,18 @@ export class WebJobExecutor extends BaseJobExecutor {
log: parsedSasjsServerLog
}
: res
if (
this.serverType === ServerType.Sasjs &&
res.result._webout.length < 1
) {
throw new JobExecutionError(
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)
let jsonResponse = res.result
@@ -220,6 +233,15 @@ export class WebJobExecutor extends BaseJobExecutor {
}
if (e instanceof LoginRequiredError) {
if (!loginRequiredCallback) {
reject(
new ErrorResponse(
'Request is not authenticated. Make sure .env file exists with valid credentials.',
e
)
)
}
this.appendWaitingRequest(() => {
return this.execute(
sasJob,
@@ -238,7 +260,7 @@ export class WebJobExecutor extends BaseJobExecutor {
)
})
await loginCallback()
if (loginCallback) await loginCallback()
} else {
reject(new ErrorResponse(e?.message, e))
}
+59 -7
View File
@@ -7,7 +7,8 @@ import {
LoginRequiredError,
NotFoundError,
InternalServerError,
JobExecutionError
JobExecutionError,
CertificateError
} from '../types/errors'
import { SASjsRequest } from '../types'
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
@@ -18,6 +19,7 @@ import {
parseSourceCode,
createAxiosInstance
} from '../utils'
import { InvalidCsrfError } from '../types/errors/InvalidCsrfError'
export interface HttpClient {
get<T>(
@@ -131,6 +133,26 @@ export class RequestClient implements HttpClient {
} else {
sasWork = response.log
}
} else if (response?.result?.log) {
//In this scenario we know we got the response from SASJS server
//Log is array of `{ line: '' }` so we need to convert it back to text
//To be able to parse it with current functions.
let log: string = ''
if (typeof log !== 'string') {
log = response.result.log
.map((logLine: any) => logLine.line)
.join('\n')
}
sourceCode = parseSourceCode(log)
generatedCode = parseGeneratedCode(log)
if (response?.result?._webout) {
sasWork = response.result._webout.WORK
} else {
sasWork = log
}
} else if (response?.result) {
sourceCode = parseSourceCode(response.result)
generatedCode = parseGeneratedCode(response.result)
@@ -185,7 +207,7 @@ export class RequestClient implements HttpClient {
return this.parseResponse<T>(response)
})
.catch(async (e) => {
.catch(async (e: any) => {
return await this.handleError(
e,
() =>
@@ -226,7 +248,7 @@ export class RequestClient implements HttpClient {
return this.parseResponse<T>(response)
})
.catch(async (e) => {
.catch(async (e: any) => {
return await this.handleError(e, () =>
this.post<T>(url, data, accessToken, contentType, overrideHeaders)
)
@@ -250,7 +272,7 @@ export class RequestClient implements HttpClient {
throwIfError(response)
return this.parseResponse<T>(response)
})
.catch(async (e) => {
.catch(async (e: any) => {
return await this.handleError(e, () =>
this.put<T>(url, data, accessToken, overrideHeaders)
)
@@ -269,7 +291,7 @@ export class RequestClient implements HttpClient {
throwIfError(response)
return this.parseResponse<T>(response)
})
.catch(async (e) => {
.catch(async (e: any) => {
return await this.handleError(e, () => this.delete<T>(url, accessToken))
})
}
@@ -287,7 +309,7 @@ export class RequestClient implements HttpClient {
throwIfError(response)
return this.parseResponse<T>(response)
})
.catch(async (e) => {
.catch(async (e: any) => {
return await this.handleError(e, () =>
this.patch<T>(url, data, accessToken)
)
@@ -477,6 +499,24 @@ export class RequestClient implements HttpClient {
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) {
this.parseAndSetCsrfToken(response)
@@ -497,6 +537,10 @@ export class RequestClient implements HttpClient {
else return
}
if (e.isAxiosError && e.code === 'UNABLE_TO_VERIFY_LEAF_SIGNATURE') {
throw new CertificateError(e.message)
}
if (e.message) throw e
else throw prefixMessage(e, 'Error while handling error. ')
}
@@ -559,9 +603,17 @@ export class RequestClient implements HttpClient {
export const throwIfError = (response: AxiosResponse) => {
switch (response.status) {
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)
}
if (response.data.toLowerCase() === 'invalid csrf token!') {
throw new InvalidCsrfError()
}
break
case 401:
if (typeof response.data === 'object') {
+2 -2
View File
@@ -68,7 +68,7 @@ export class Sas9RequestClient extends RequestClient {
throwIfError(response)
return this.parseResponse<T>(response)
})
.catch(async (e) => {
.catch(async (e: any) => {
return await this.handleError(
e,
() =>
@@ -113,7 +113,7 @@ export class Sas9RequestClient extends RequestClient {
throwIfError(response)
return this.parseResponse<T>(response)
})
.catch(async (e) => {
.catch(async (e: any) => {
return await this.handleError(e, () =>
this.post<T>(url, data, accessToken, contentType, overrideHeaders)
)
+12 -1
View File
@@ -54,6 +54,17 @@ describe('formatDataForRequest', () => {
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', () => {
let tableWithMissingValues = {
[testTable]: [{ var: 'AA' }, { var: 0 }],
@@ -62,7 +73,7 @@ describe('formatDataForRequest', () => {
expect(() => formatDataForRequest(tableWithMissingValues)).toThrow(
new Error(
'Special missing value can only be a single character from A to Z or _'
`A Special missing value can only be a single character from 'A' to 'Z', '_', '.[a-z]', '._'`
)
)
})
+12
View File
@@ -0,0 +1,12 @@
const instructionsToFix =
'https://github.com/sasjs/cli/issues/1181#issuecomment-1090638584'
export class CertificateError extends Error {
constructor(message: string) {
super(
`${message}\nPlease visit the link below for further information on this issue:\n- ${instructionsToFix}\n`
)
this.name = 'CertificateError'
Object.setPrototypeOf(this, CertificateError.prototype)
}
}
+9
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)
}
}
+7 -6
View File
@@ -1,13 +1,14 @@
export * from './AuthorizeError'
export * from './CertificateError'
export * from './ComputeJobExecutionError'
export * from './ErrorResponse'
export * from './InternalServerError'
export * from './InvalidJsonError'
export * from './JobExecutionError'
export * from './JobStatePollError'
export * from './LoginRequiredError'
export * from './NotFoundError'
export * from './ErrorResponse'
export * from './NoSessionStateError'
export * from './RootFolderNotFoundError'
export * from './JsonParseArrayError'
export * from './LoginRequiredError'
export * from './NoSessionStateError'
export * from './NotFoundError'
export * from './RootFolderNotFoundError'
export * from './WeboutResponseError'
export * from './InvalidJsonError'
+5 -5
View File
@@ -1,3 +1,5 @@
import { isSpecialMissing } from '@sasjs/utils/input/validators'
/**
* Converts the given JSON object array to a CSV string.
* @param data - the array of JSON objects to convert.
@@ -18,7 +20,6 @@ export const convertToCSV = (
let headers: string[] = []
let csvTest
let invalidString = false
const specialMissingValueRegExp = /^[a-z_]{1}$/i
if (formats) {
headers = Object.keys(formats).map((key) => `${key}:${formats![key]}`)
@@ -36,7 +37,7 @@ export const convertToCSV = (
hasNullOrNumber = true
} else if (
typeof row[field] === 'string' &&
specialMissingValueRegExp.test(row[field])
isSpecialMissing(row[field])
) {
hasSpecialMissingString = true
}
@@ -130,10 +131,9 @@ export const convertToCSV = (
value = currentCell === null ? '' : currentCell
if (formats && formats[fieldName] === 'best.') {
if (value && !specialMissingValueRegExp.test(value)) {
console.log(`🤖[value]🤖`, value)
if (value && !isSpecialMissing(value)) {
throw new Error(
'Special missing value can only be a single character from A to Z or _'
`A Special missing value can only be a single character from 'A' to 'Z', '_', '.[a-z]', '._'`
)
}
+1 -1
View File
@@ -15,7 +15,7 @@ export const getValidJson = (str: string | object): object => {
if (str === '') return {}
return JSON.parse(str)
} catch (e) {
} catch (e: any) {
if (e instanceof JsonParseArrayError) throw e
throw new InvalidJsonError()
}
+1 -1
View File
@@ -4,7 +4,7 @@ export const parseSasViyaLog = (logResponse: { items: any[] }) => {
log = logResponse.items
? logResponse.items.map((i) => i.line).join('\n')
: JSON.stringify(logResponse)
} catch (e) {
} catch (e: any) {
console.error('An error has occurred while parsing the log response', e)
log = logResponse
}
+7 -2
View File
@@ -16,10 +16,15 @@ export const parseSasViyaDebugResponse = async (
requestClient: RequestClient,
serverUrl: string
) => {
// On viya 3.5, iframe is like <iframe style="width: 99%; height: 500px" src="..."></iframe>
// On viya 4, iframe is like <iframe style="width: 99%; height: 500px; background-color:Canvas;" src=...></iframe>
const iframeStart = response.split(
'<iframe style="width: 99%; height: 500px" src="'
/<iframe style="width: 99%; height: 500px" src="|<iframe style="width: 99%; height: 500px; background-color:Canvas;" src=/
)[1]
const jsonUrl = iframeStart ? iframeStart.split('"></iframe>')[0] : null
const jsonUrl = iframeStart
? iframeStart.split(/"><\/iframe>|><\/iframe>/)[0]
: null
if (!jsonUrl) {
throw new Error('Unable to find webout file URL.')
}
+1 -1
View File
@@ -8,7 +8,7 @@ export const parseWeboutResponse = (response: string, url?: string): string => {
sasResponse = response
.split('>>weboutBEGIN<<')[1]
.split('>>weboutEND<<')[0]
} catch (e) {
} catch (e: any) {
if (url) throw new WeboutResponseError(url)
sasResponse = ''