mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-13 23:20:05 +00:00
Compare commits
7 Commits
v2.10.5
...
fixing-sas
| Author | SHA1 | Date | |
|---|---|---|---|
| 0bd156141c | |||
| a615c5fdb6 | |||
| ca7ee83f7f | |||
| 97a530cc66 | |||
| 317c8c81a0 | |||
| c87776ca1b | |||
| 04032831c3 |
@@ -1,103 +1,24 @@
|
|||||||
{
|
{
|
||||||
"projectName": "adapter",
|
|
||||||
"projectOwner": "sasjs",
|
|
||||||
"repoType": "github",
|
|
||||||
"repoHost": "https://github.com",
|
|
||||||
"files": [
|
"files": [
|
||||||
"README.md"
|
"README.md"
|
||||||
],
|
],
|
||||||
"imageSize": 100,
|
"imageSize": 100,
|
||||||
"commit": false,
|
"commit": false,
|
||||||
"commitConvention": "angular",
|
|
||||||
"contributors": [
|
"contributors": [
|
||||||
{
|
|
||||||
"login": "krishna-acondy",
|
|
||||||
"name": "Krishna Acondy",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/2980428?v=4",
|
|
||||||
"profile": "https://krishna-acondy.io/",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"infra",
|
|
||||||
"blog",
|
|
||||||
"content",
|
|
||||||
"ideas",
|
|
||||||
"video"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "YuryShkoda",
|
|
||||||
"name": "Yury Shkoda",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/25773492?v=4",
|
|
||||||
"profile": "https://www.erudicat.com/",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"infra",
|
|
||||||
"ideas",
|
|
||||||
"test",
|
|
||||||
"video"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"login": "medjedovicm",
|
"login": "medjedovicm",
|
||||||
"name": "Mihajlo Medjedovic",
|
"name": "Mihajlo Medjedovic",
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/18329105?v=4",
|
"avatar_url": "https://avatars.githubusercontent.com/u/18329105?v=4",
|
||||||
"profile": "https://github.com/medjedovicm",
|
"profile": "https://github.com/medjedovicm",
|
||||||
"contributions": [
|
"contributions": [
|
||||||
"code",
|
"code"
|
||||||
"infra",
|
|
||||||
"test",
|
|
||||||
"review"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "allanbowe",
|
|
||||||
"name": "Allan Bowe",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/4420615?v=4",
|
|
||||||
"profile": "https://github.com/allanbowe",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"review",
|
|
||||||
"test",
|
|
||||||
"mentoring",
|
|
||||||
"maintenance"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "saadjutt01",
|
|
||||||
"name": "Muhammad Saad ",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/8914650?v=4",
|
|
||||||
"profile": "https://github.com/saadjutt01",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"review",
|
|
||||||
"test",
|
|
||||||
"mentoring",
|
|
||||||
"infra"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "sabhas",
|
|
||||||
"name": "Sabir Hassan",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/82647447?v=4",
|
|
||||||
"profile": "https://github.com/sabhas",
|
|
||||||
"contributions": [
|
|
||||||
"code",
|
|
||||||
"review",
|
|
||||||
"test",
|
|
||||||
"ideas"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"login": "VladislavParhomchik",
|
|
||||||
"name": "VladislavParhomchik",
|
|
||||||
"avatar_url": "https://avatars.githubusercontent.com/u/83717836?v=4",
|
|
||||||
"profile": "https://github.com/VladislavParhomchik",
|
|
||||||
"contributions": [
|
|
||||||
"test",
|
|
||||||
"review"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
"projectName": "adapter",
|
||||||
|
"projectOwner": "sasjs",
|
||||||
|
"repoType": "github",
|
||||||
|
"repoHost": "https://github.com",
|
||||||
"skipCi": true
|
"skipCi": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ GREEN="\033[1;32m"
|
|||||||
# temporary file which holds the message).
|
# temporary file which holds the message).
|
||||||
commit_message=$(cat "$1")
|
commit_message=$(cat "$1")
|
||||||
|
|
||||||
if (echo "$commit_message" | grep -Eq "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9 -\*]+\))?!?: .+$") then
|
if (echo "$commit_message" | grep -Eq "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9 \-\*]+\))?!?: .+$") then
|
||||||
echo "${GREEN} ✔ Commit message meets Conventional Commit standards"
|
echo "${GREEN} ✔ Commit message meets Conventional Commit standards"
|
||||||
exit 0
|
exit 0
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
sasjs-tests/
|
sasjs-tests/
|
||||||
docs/
|
docs/
|
||||||
.github/
|
.github/
|
||||||
*.md
|
CONTRIBUTING.md
|
||||||
*.spec.ts
|
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -172,7 +172,7 @@ Configuration on the client side involves passing an object on startup, which ca
|
|||||||
* `serverType` - either `SAS9` or `SASVIYA`.
|
* `serverType` - either `SAS9` or `SASVIYA`.
|
||||||
* `serverUrl` - the location (including http protocol and port) of the SAS Server. Can be omitted, eg if serving directly from the SAS Web Server, or in streaming mode.
|
* `serverUrl` - the location (including http protocol and port) of the SAS Server. Can be omitted, eg if serving directly from the SAS Web Server, or in streaming mode.
|
||||||
* `debug` - if `true` then SAS Logs and extra debug information is returned.
|
* `debug` - if `true` then SAS Logs and extra debug information is returned.
|
||||||
* `useComputeApi` - Only relevant when the serverType is `SASVIYA`. If `true` the [Compute API](#using-the-compute-api) is used. If `false` the [JES API](#using-the-jes-api) is used. If `null` or `undefined` the [Web](#using-jes-web-app) approach is used.
|
* `useComputeApi` - Only relevant when the serverType is `SASVIYA`. If `true` the [Compute API](#using-the-compute-api) is used. If `false` the [JES API](#using-the-jes-api) is used. If `null` or `undefined` the [Web](#using-jes-web-app) approach is used.
|
||||||
* `contextName` - Compute context on which the requests will be called. If missing or not provided, defaults to `Job Execution Compute context`.
|
* `contextName` - Compute context on which the requests will be called. If missing or not provided, defaults to `Job Execution Compute context`.
|
||||||
|
|
||||||
The adapter supports a number of approaches for interfacing with Viya (`serverType` is `SASVIYA`). For maximum performance, be sure to [configure your compute context](https://sasjs.io/guide-viya/#shared-account-and-server-re-use) with `reuseServerProcesses` as `true` and a system account in `runServerAs`. This functionality is available since Viya 3.5. This configuration is supported when [creating contexts using the CLI](https://sasjs.io/sasjs-cli-context/#sasjs-context-create).
|
The adapter supports a number of approaches for interfacing with Viya (`serverType` is `SASVIYA`). For maximum performance, be sure to [configure your compute context](https://sasjs.io/guide-viya/#shared-account-and-server-re-use) with `reuseServerProcesses` as `true` and a system account in `runServerAs`. This functionality is available since Viya 3.5. This configuration is supported when [creating contexts using the CLI](https://sasjs.io/sasjs-cli-context/#sasjs-context-create).
|
||||||
@@ -236,9 +236,6 @@ If you find this library useful, help us grow our star graph!
|
|||||||

|

|
||||||
|
|
||||||
## Contributors ✨
|
## Contributors ✨
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
|
|
||||||
[](#contributors-)
|
|
||||||
<!-- ALL-CONTRIBUTORS-BADGE:END -->
|
|
||||||
|
|
||||||
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
|
||||||
|
|
||||||
@@ -247,13 +244,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
<!-- markdownlint-disable -->
|
<!-- markdownlint-disable -->
|
||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center"><a href="https://krishna-acondy.io/"><img src="https://avatars.githubusercontent.com/u/2980428?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Krishna Acondy</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=krishna-acondy" title="Code">💻</a> <a href="#infra-krishna-acondy" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#blog-krishna-acondy" title="Blogposts">📝</a> <a href="#content-krishna-acondy" title="Content">🖋</a> <a href="#ideas-krishna-acondy" title="Ideas, Planning, & Feedback">🤔</a> <a href="#video-krishna-acondy" title="Videos">📹</a></td>
|
<td align="center"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mihajlo Medjedovic</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=medjedovicm" title="Code">💻</a></td>
|
||||||
<td align="center"><a href="https://www.erudicat.com/"><img src="https://avatars.githubusercontent.com/u/25773492?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yury Shkoda</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=YuryShkoda" title="Code">💻</a> <a href="#infra-YuryShkoda" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#ideas-YuryShkoda" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/sasjs/adapter/commits?author=YuryShkoda" title="Tests">⚠️</a> <a href="#video-YuryShkoda" title="Videos">📹</a></td>
|
|
||||||
<td align="center"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mihajlo Medjedovic</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=medjedovicm" title="Code">💻</a> <a href="#infra-medjedovicm" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/sasjs/adapter/commits?author=medjedovicm" title="Tests">⚠️</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Amedjedovicm" title="Reviewed Pull Requests">👀</a></td>
|
|
||||||
<td align="center"><a href="https://github.com/allanbowe"><img src="https://avatars.githubusercontent.com/u/4420615?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Allan Bowe</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=allanbowe" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Aallanbowe" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=allanbowe" title="Tests">⚠️</a> <a href="#mentoring-allanbowe" title="Mentoring">🧑🏫</a> <a href="#maintenance-allanbowe" title="Maintenance">🚧</a></td>
|
|
||||||
<td align="center"><a href="https://github.com/saadjutt01"><img src="https://avatars.githubusercontent.com/u/8914650?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Muhammad Saad </b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=saadjutt01" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Asaadjutt01" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=saadjutt01" title="Tests">⚠️</a> <a href="#mentoring-saadjutt01" title="Mentoring">🧑🏫</a> <a href="#infra-saadjutt01" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
|
|
||||||
<td align="center"><a href="https://github.com/sabhas"><img src="https://avatars.githubusercontent.com/u/82647447?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sabir Hassan</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=sabhas" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Asabhas" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=sabhas" title="Tests">⚠️</a> <a href="#ideas-sabhas" title="Ideas, Planning, & Feedback">🤔</a></td>
|
|
||||||
<td align="center"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>VladislavParhomchik</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
const result = process.versions
|
|
||||||
if (result && result.node) {
|
|
||||||
if (parseInt(result.node) < 14) {
|
|
||||||
console.log(
|
|
||||||
'\x1b[31m%s\x1b[0m',
|
|
||||||
`❌ Process failed due to Node Version,\nPlease install and use Node Version >= 14\nYour current Node Version is: ${result.node}`
|
|
||||||
)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log(
|
|
||||||
'\x1b[31m%s\x1b[0m',
|
|
||||||
'Something went wrong while checking Node version'
|
|
||||||
)
|
|
||||||
process.exit(1)
|
|
||||||
}
|
|
||||||
18422
package-lock.json
generated
18422
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
21
package.json
21
package.json
@@ -3,10 +3,8 @@
|
|||||||
"description": "JavaScript adapter for SAS",
|
"description": "JavaScript adapter for SAS",
|
||||||
"homepage": "https://adapter.sasjs.io",
|
"homepage": "https://adapter.sasjs.io",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"preinstall": "node checkNodeVersion",
|
|
||||||
"prebuild": "node checkNodeVersion",
|
|
||||||
"build": "rimraf build && rimraf node && mkdir node && copyfiles -u 1 \"./src/**/*\" ./node && webpack && rimraf build/src && rimraf node",
|
"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",
|
"package:lib": "npm run build && copyfiles ./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}\"",
|
||||||
@@ -42,38 +40,41 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/axios": "^0.14.0",
|
"@types/axios": "^0.14.0",
|
||||||
"@types/form-data": "^2.5.0",
|
"@types/form-data": "^2.5.0",
|
||||||
"@types/jest": "^27.0.1",
|
"@types/jest": "^26.0.24",
|
||||||
"@types/mime": "^2.0.3",
|
"@types/mime": "^2.0.3",
|
||||||
"@types/tough-cookie": "^4.0.1",
|
"@types/tough-cookie": "^4.0.1",
|
||||||
"copyfiles": "^2.4.1",
|
"copyfiles": "^2.4.1",
|
||||||
"cp": "^0.2.0",
|
"cp": "^0.2.0",
|
||||||
"dotenv": "^10.0.0",
|
"dotenv": "^10.0.0",
|
||||||
"jest": "^27.1.0",
|
"jest": "^27.0.6",
|
||||||
"jest-extended": "^0.11.5",
|
"jest-extended": "^0.11.5",
|
||||||
"node-polyfill-webpack-plugin": "^1.1.4",
|
"node-polyfill-webpack-plugin": "^1.1.4",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"semantic-release": "^17.4.7",
|
"semantic-release": "^17.4.4",
|
||||||
"terser-webpack-plugin": "^5.2.0",
|
"terser-webpack-plugin": "^5.1.4",
|
||||||
"ts-jest": "^27.0.3",
|
"ts-jest": "^27.0.3",
|
||||||
"ts-loader": "^9.2.2",
|
"ts-loader": "^9.2.2",
|
||||||
"tslint": "^6.1.3",
|
"tslint": "^6.1.3",
|
||||||
"tslint-config-prettier": "^1.18.0",
|
"tslint-config-prettier": "^1.18.0",
|
||||||
"typedoc": "^0.21.9",
|
"typedoc": "^0.21.4",
|
||||||
"typedoc-neo-theme": "^1.1.1",
|
"typedoc-neo-theme": "^1.1.1",
|
||||||
"typedoc-plugin-external-module-name": "^4.0.6",
|
"typedoc-plugin-external-module-name": "^4.0.6",
|
||||||
"typescript": "4.3.5",
|
"typescript": "^4.3.5",
|
||||||
"webpack": "^5.44.0",
|
"webpack": "^5.44.0",
|
||||||
"webpack-cli": "^4.7.2"
|
"webpack-cli": "^4.7.2"
|
||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/utils": "^2.30.0",
|
"@sasjs/utils": "^2.27.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",
|
||||||
"https": "^1.0.0",
|
"https": "^1.0.0",
|
||||||
"tough-cookie": "^4.0.0"
|
"tough-cookie": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=15"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
22129
sasjs-tests/package-lock.json
generated
22129
sasjs-tests/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -41,27 +41,16 @@ export const basicTests = (
|
|||||||
assertion: (response: any) =>
|
assertion: (response: any) =>
|
||||||
response && response.isLoggedIn && response.userName === userName
|
response && response.isLoggedIn && response.userName === userName
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: 'Fetch username for already logged in user',
|
|
||||||
description: 'Should log the user in',
|
|
||||||
test: async () => {
|
|
||||||
await adapter.logIn(userName, password)
|
|
||||||
|
|
||||||
const newAdapterIns = new SASjs(adapter.getSasjsConfig())
|
|
||||||
|
|
||||||
return await newAdapterIns.checkSession()
|
|
||||||
},
|
|
||||||
assertion: (response: any) =>
|
|
||||||
response?.isLoggedIn && response?.userName === userName
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: 'Multiple Log in attempts',
|
title: 'Multiple Log in attempts',
|
||||||
description:
|
description:
|
||||||
'Should fail on first attempt and should log the user in on second attempt',
|
'Should fail on first attempt and should log the user in on second attempt',
|
||||||
test: async () => {
|
test: async () => {
|
||||||
await adapter.logOut()
|
await adapter.logOut()
|
||||||
|
await sleep(1000)
|
||||||
await adapter.logIn('invalid', 'invalid')
|
await adapter.logIn('invalid', 'invalid')
|
||||||
return await adapter.logIn(userName, password)
|
await sleep(1000)
|
||||||
|
return adapter.logIn(userName, password)
|
||||||
},
|
},
|
||||||
assertion: (response: any) =>
|
assertion: (response: any) =>
|
||||||
response && response.isLoggedIn && response.userName === userName
|
response && response.isLoggedIn && response.userName === userName
|
||||||
@@ -164,7 +153,10 @@ export const basicTests = (
|
|||||||
description:
|
description:
|
||||||
'Should complete successful request with extra attributes present in response',
|
'Should complete successful request with extra attributes present in response',
|
||||||
test: async () => {
|
test: async () => {
|
||||||
const config: Partial<SASjsConfig> = {
|
if (adapter.getSasjsConfig().serverType !== 'SASVIYA')
|
||||||
|
return Promise.resolve('skip')
|
||||||
|
|
||||||
|
const config = {
|
||||||
useComputeApi: false
|
useComputeApi: false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,9 +170,15 @@ export const basicTests = (
|
|||||||
)
|
)
|
||||||
},
|
},
|
||||||
assertion: (response: any) => {
|
assertion: (response: any) => {
|
||||||
|
if (response === 'skip') return true
|
||||||
|
|
||||||
const responseKeys: any = Object.keys(response)
|
const responseKeys: any = Object.keys(response)
|
||||||
return responseKeys.includes('file') && responseKeys.includes('data')
|
return responseKeys.includes('file') && responseKeys.includes('data')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const sleep = (ms: number) => {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,21 +61,24 @@ export class FileUploader {
|
|||||||
'Content-Type': 'text/plain'
|
'Content-Type': 'text/plain'
|
||||||
}
|
}
|
||||||
|
|
||||||
// currently only web approach is supported for file upload
|
|
||||||
// therefore log is part of response with debug enabled and must be parsed
|
|
||||||
return this.requestClient
|
return this.requestClient
|
||||||
.post(uploadUrl, formData, undefined, 'application/json', headers)
|
.post(uploadUrl, formData, undefined, 'application/json', headers)
|
||||||
.then(async (res) => {
|
.then(async (res) => {
|
||||||
|
// for web approach on Viya
|
||||||
if (
|
if (
|
||||||
this.sasjsConfig.serverType === ServerType.SasViya &&
|
this.sasjsConfig.debug &&
|
||||||
this.sasjsConfig.debug
|
(this.sasjsConfig.useComputeApi === null ||
|
||||||
|
this.sasjsConfig.useComputeApi === undefined) &&
|
||||||
|
this.sasjsConfig.serverType === ServerType.SasViya
|
||||||
) {
|
) {
|
||||||
const jsonResponse = await parseSasViyaDebugResponse(
|
const jsonResponse = await parseSasViyaDebugResponse(
|
||||||
res.result as string,
|
res.result as string,
|
||||||
this.requestClient,
|
this.requestClient,
|
||||||
this.sasjsConfig.serverUrl
|
this.sasjsConfig.serverUrl
|
||||||
)
|
)
|
||||||
return jsonResponse
|
return typeof jsonResponse === 'string'
|
||||||
|
? getValidJson(jsonResponse)
|
||||||
|
: jsonResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
return typeof res.result === 'string'
|
return typeof res.result === 'string'
|
||||||
|
|||||||
@@ -10,13 +10,9 @@ import { isUrl } from './utils'
|
|||||||
export class SAS9ApiClient {
|
export class SAS9ApiClient {
|
||||||
private requestClient: Sas9RequestClient
|
private requestClient: Sas9RequestClient
|
||||||
|
|
||||||
constructor(
|
constructor(private serverUrl: string, private jobsPath: string) {
|
||||||
private serverUrl: string,
|
|
||||||
private jobsPath: string,
|
|
||||||
allowInsecureRequests: boolean
|
|
||||||
) {
|
|
||||||
if (serverUrl) isUrl(serverUrl)
|
if (serverUrl) isUrl(serverUrl)
|
||||||
this.requestClient = new Sas9RequestClient(serverUrl, allowInsecureRequests)
|
this.requestClient = new Sas9RequestClient(serverUrl, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,51 +0,0 @@
|
|||||||
import { Logger, LogLevel } from '@sasjs/utils/logger'
|
|
||||||
import { RequestClient } from './request/RequestClient'
|
|
||||||
import { SASViyaApiClient } from './SASViyaApiClient'
|
|
||||||
import { Folder } from './types'
|
|
||||||
import { RootFolderNotFoundError } from './types/errors'
|
|
||||||
|
|
||||||
const mockFolder: Folder = {
|
|
||||||
id: '1',
|
|
||||||
uri: '/folder',
|
|
||||||
links: [],
|
|
||||||
memberCount: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
const requestClient = new (<jest.Mock<RequestClient>>RequestClient)()
|
|
||||||
const sasViyaApiClient = new SASViyaApiClient(
|
|
||||||
'https://test.com',
|
|
||||||
'/test',
|
|
||||||
'test context',
|
|
||||||
requestClient
|
|
||||||
)
|
|
||||||
|
|
||||||
describe('SASViyaApiClient', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
;(process as any).logger = new Logger(LogLevel.Off)
|
|
||||||
setupMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should throw an error when the root folder is not found on the server', async () => {
|
|
||||||
jest
|
|
||||||
.spyOn(requestClient, 'get')
|
|
||||||
.mockImplementation(() => Promise.reject('Not Found'))
|
|
||||||
const error = await sasViyaApiClient
|
|
||||||
.createFolder('test', '/foo')
|
|
||||||
.catch((e) => e)
|
|
||||||
expect(error).toBeInstanceOf(RootFolderNotFoundError)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const setupMocks = () => {
|
|
||||||
jest
|
|
||||||
.spyOn(requestClient, 'get')
|
|
||||||
.mockImplementation(() =>
|
|
||||||
Promise.resolve({ result: mockFolder, etag: '', status: 200 })
|
|
||||||
)
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(requestClient, 'post')
|
|
||||||
.mockImplementation(() =>
|
|
||||||
Promise.resolve({ result: mockFolder, etag: '', status: 200 })
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
JobDefinition,
|
JobDefinition,
|
||||||
PollOptions
|
PollOptions
|
||||||
} from './types'
|
} from './types'
|
||||||
import { JobExecutionError, RootFolderNotFoundError } from './types/errors'
|
import { JobExecutionError } from './types/errors'
|
||||||
import { SessionManager } from './SessionManager'
|
import { SessionManager } from './SessionManager'
|
||||||
import { ContextManager } from './ContextManager'
|
import { ContextManager } from './ContextManager'
|
||||||
import { SasAuthResponse, MacroVar, AuthConfig } from '@sasjs/utils/types'
|
import { SasAuthResponse, MacroVar, AuthConfig } from '@sasjs/utils/types'
|
||||||
@@ -381,11 +381,7 @@ export class SASViyaApiClient {
|
|||||||
)
|
)
|
||||||
const newFolderName = `${parentFolderPath.split('/').pop()}`
|
const newFolderName = `${parentFolderPath.split('/').pop()}`
|
||||||
if (newParentFolderPath === '') {
|
if (newParentFolderPath === '') {
|
||||||
throw new RootFolderNotFoundError(
|
throw new Error('Root folder has to be present on the server.')
|
||||||
parentFolderPath,
|
|
||||||
this.serverUrl,
|
|
||||||
accessToken
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
logger.info(
|
logger.info(
|
||||||
`Creating parent folder:\n'${newFolderName}' in '${newParentFolderPath}'`
|
`Creating parent folder:\n'${newFolderName}' in '${newParentFolderPath}'`
|
||||||
|
|||||||
17
src/SASjs.ts
17
src/SASjs.ts
@@ -619,11 +619,6 @@ export default class SASjs {
|
|||||||
authConfig
|
authConfig
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
if (!config.contextName)
|
|
||||||
config = {
|
|
||||||
...config,
|
|
||||||
contextName: 'SAS Job Execution compute context'
|
|
||||||
}
|
|
||||||
return await this.jesJobExecutor!.execute(
|
return await this.jesJobExecutor!.execute(
|
||||||
sasJob,
|
sasJob,
|
||||||
data,
|
data,
|
||||||
@@ -754,11 +749,7 @@ export default class SASjs {
|
|||||||
)
|
)
|
||||||
sasApiClient.debug = this.sasjsConfig.debug
|
sasApiClient.debug = this.sasjsConfig.debug
|
||||||
} else if (this.sasjsConfig.serverType === ServerType.Sas9) {
|
} else if (this.sasjsConfig.serverType === ServerType.Sas9) {
|
||||||
sasApiClient = new SAS9ApiClient(
|
sasApiClient = new SAS9ApiClient(serverUrl, this.jobsPath)
|
||||||
serverUrl,
|
|
||||||
this.jobsPath,
|
|
||||||
this.sasjsConfig.allowInsecureRequests
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let sasClientConfig: any = null
|
let sasClientConfig: any = null
|
||||||
@@ -953,8 +944,7 @@ export default class SASjs {
|
|||||||
else
|
else
|
||||||
this.sas9ApiClient = new SAS9ApiClient(
|
this.sas9ApiClient = new SAS9ApiClient(
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
this.jobsPath,
|
this.jobsPath
|
||||||
this.sasjsConfig.allowInsecureRequests
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -975,8 +965,7 @@ export default class SASjs {
|
|||||||
this.sas9JobExecutor = new Sas9JobExecutor(
|
this.sas9JobExecutor = new Sas9JobExecutor(
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.serverUrl,
|
||||||
this.sasjsConfig.serverType!,
|
this.sasjsConfig.serverType!,
|
||||||
this.jobsPath,
|
this.jobsPath
|
||||||
this.sasjsConfig.allowInsecureRequests
|
|
||||||
)
|
)
|
||||||
|
|
||||||
this.computeJobExecutor = new ComputeJobExecutor(
|
this.computeJobExecutor = new ComputeJobExecutor(
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import { prefixMessage } from '@sasjs/utils/error'
|
|||||||
import { RequestClient } from './request/RequestClient'
|
import { RequestClient } from './request/RequestClient'
|
||||||
|
|
||||||
const MAX_SESSION_COUNT = 1
|
const MAX_SESSION_COUNT = 1
|
||||||
|
const RETRY_LIMIT: number = 3
|
||||||
|
let RETRY_COUNT: number = 0
|
||||||
|
|
||||||
export class SessionManager {
|
export class SessionManager {
|
||||||
private loggedErrors: NoSessionStateError[] = []
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private serverUrl: string,
|
private serverUrl: string,
|
||||||
private contextName: string,
|
private contextName: string,
|
||||||
@@ -154,75 +154,69 @@ export class SessionManager {
|
|||||||
session: Session,
|
session: Session,
|
||||||
etag: string | null,
|
etag: string | null,
|
||||||
accessToken?: string
|
accessToken?: string
|
||||||
): Promise<string> {
|
) {
|
||||||
const logger = process.logger || console
|
const logger = process.logger || console
|
||||||
|
|
||||||
let sessionState = session.state
|
let sessionState = session.state
|
||||||
|
|
||||||
const stateLink = session.links.find((l: any) => l.rel === 'state')
|
const stateLink = session.links.find((l: any) => l.rel === 'state')
|
||||||
|
|
||||||
if (
|
return new Promise(async (resolve, reject) => {
|
||||||
sessionState === 'pending' ||
|
if (
|
||||||
sessionState === 'running' ||
|
sessionState === 'pending' ||
|
||||||
sessionState === ''
|
sessionState === 'running' ||
|
||||||
) {
|
sessionState === ''
|
||||||
if (stateLink) {
|
) {
|
||||||
if (this.debug && !this.printedSessionState.printed) {
|
if (stateLink) {
|
||||||
logger.info('Polling session status...')
|
if (this.debug && !this.printedSessionState.printed) {
|
||||||
|
logger.info('Polling session status...')
|
||||||
|
|
||||||
this.printedSessionState.printed = true
|
this.printedSessionState.printed = true
|
||||||
}
|
|
||||||
|
|
||||||
const { result: state, responseStatus: responseStatus } =
|
|
||||||
await this.getSessionState(
|
|
||||||
`${this.serverUrl}${stateLink.href}?wait=30`,
|
|
||||||
etag!,
|
|
||||||
accessToken
|
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(err, 'Error while getting session state.')
|
|
||||||
})
|
|
||||||
|
|
||||||
sessionState = state.trim()
|
|
||||||
|
|
||||||
if (this.debug && this.printedSessionState.state !== sessionState) {
|
|
||||||
logger.info(`Current session state is '${sessionState}'`)
|
|
||||||
|
|
||||||
this.printedSessionState.state = sessionState
|
|
||||||
this.printedSessionState.printed = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!sessionState) {
|
|
||||||
const stateError = new NoSessionStateError(
|
|
||||||
responseStatus,
|
|
||||||
this.serverUrl + stateLink.href,
|
|
||||||
session.links.find((l: any) => l.rel === 'log')?.href as string
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
!this.loggedErrors.find(
|
|
||||||
(err: NoSessionStateError) =>
|
|
||||||
err.serverResponseStatus === stateError.serverResponseStatus
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
this.loggedErrors.push(stateError)
|
|
||||||
|
|
||||||
logger.info(stateError.message)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return await this.waitForSession(session, etag, accessToken)
|
const { result: state, responseStatus: responseStatus } =
|
||||||
|
await this.getSessionState(
|
||||||
|
`${this.serverUrl}${stateLink.href}?wait=30`,
|
||||||
|
etag!,
|
||||||
|
accessToken
|
||||||
|
).catch((err) => {
|
||||||
|
throw prefixMessage(err, 'Error while getting session state.')
|
||||||
|
})
|
||||||
|
|
||||||
|
sessionState = state.trim()
|
||||||
|
|
||||||
|
if (this.debug && this.printedSessionState.state !== sessionState) {
|
||||||
|
logger.info(`Current session state is '${sessionState}'`)
|
||||||
|
|
||||||
|
this.printedSessionState.state = sessionState
|
||||||
|
this.printedSessionState.printed = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is an internal error present in SAS Viya 3.5
|
||||||
|
// Retry to wait for a session status in such case of SAS internal error
|
||||||
|
if (!sessionState) {
|
||||||
|
if (RETRY_COUNT < RETRY_LIMIT) {
|
||||||
|
RETRY_COUNT++
|
||||||
|
|
||||||
|
resolve(this.waitForSession(session, etag, accessToken))
|
||||||
|
} else {
|
||||||
|
reject(
|
||||||
|
new NoSessionStateError(
|
||||||
|
responseStatus,
|
||||||
|
this.serverUrl + stateLink.href,
|
||||||
|
session.links.find((l: any) => l.rel === 'log')
|
||||||
|
?.href as string
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve(sessionState)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loggedErrors = []
|
|
||||||
|
|
||||||
return sessionState
|
|
||||||
} else {
|
} else {
|
||||||
throw 'Error while getting session state link.'
|
resolve(sessionState)
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
this.loggedErrors = []
|
|
||||||
|
|
||||||
return sessionState
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getSessionState(
|
private async getSessionState(
|
||||||
|
|||||||
@@ -23,61 +23,43 @@ export class AuthManager {
|
|||||||
* Logs into the SAS server with the supplied credentials.
|
* Logs into the SAS server with the supplied credentials.
|
||||||
* @param username - a string representing the username.
|
* @param username - a string representing the username.
|
||||||
* @param password - a string representing the password.
|
* @param password - a string representing the password.
|
||||||
* @returns - a boolean `isLoggedin` and a string `username`
|
|
||||||
*/
|
*/
|
||||||
public async logIn(
|
public async logIn(username: string, password: string) {
|
||||||
username: string,
|
const loginParams: any = {
|
||||||
password: string
|
|
||||||
): Promise<{
|
|
||||||
isLoggedIn: boolean
|
|
||||||
userName: string
|
|
||||||
}> {
|
|
||||||
const loginParams = {
|
|
||||||
_service: 'default',
|
_service: 'default',
|
||||||
username,
|
username,
|
||||||
password
|
password
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
this.userName = loginParams.username
|
||||||
isLoggedIn: isLoggedInAlready,
|
|
||||||
loginForm,
|
|
||||||
userName: currentSessionUsername
|
|
||||||
} = await this.checkSession()
|
|
||||||
|
|
||||||
if (isLoggedInAlready) {
|
const { isLoggedIn, loginForm } = await this.checkSession()
|
||||||
if (currentSessionUsername === loginParams.username) {
|
|
||||||
await this.loginCallback()
|
|
||||||
|
|
||||||
this.userName = currentSessionUsername!
|
if (isLoggedIn) {
|
||||||
return {
|
await this.loginCallback()
|
||||||
isLoggedIn: true,
|
|
||||||
userName: this.userName
|
return {
|
||||||
}
|
isLoggedIn,
|
||||||
} else {
|
userName: this.userName
|
||||||
this.logOut()
|
|
||||||
}
|
}
|
||||||
} else this.userName = ''
|
}
|
||||||
|
|
||||||
let loginResponse = await this.sendLoginRequest(loginForm, loginParams)
|
let loginResponse = await this.sendLoginRequest(loginForm, loginParams)
|
||||||
|
|
||||||
let isLoggedIn = isLogInSuccess(loginResponse)
|
let loggedIn = isLogInSuccess(loginResponse)
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!loggedIn) {
|
||||||
if (isCredentialsVerifyError(loginResponse)) {
|
if (isCredentialsVerifyError(loginResponse)) {
|
||||||
const newLoginForm = await this.getLoginForm(loginResponse)
|
const newLoginForm = await this.getLoginForm(loginResponse)
|
||||||
|
|
||||||
loginResponse = await this.sendLoginRequest(newLoginForm, loginParams)
|
loginResponse = await this.sendLoginRequest(newLoginForm, loginParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await this.checkSession()
|
const currentSession = await this.checkSession()
|
||||||
isLoggedIn = res.isLoggedIn
|
loggedIn = currentSession.isLoggedIn
|
||||||
|
|
||||||
if (isLoggedIn) this.userName = res.userName!
|
|
||||||
} else {
|
|
||||||
this.userName = loginParams.username
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoggedIn) {
|
if (loggedIn) {
|
||||||
if (this.serverType === ServerType.Sas9) {
|
if (this.serverType === ServerType.Sas9) {
|
||||||
const casAuthenticationUrl = `${this.serverUrl}/SASStoredProcess/j_spring_cas_security_check`
|
const casAuthenticationUrl = `${this.serverUrl}/SASStoredProcess/j_spring_cas_security_check`
|
||||||
|
|
||||||
@@ -88,10 +70,10 @@ export class AuthManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.loginCallback()
|
this.loginCallback()
|
||||||
} else this.userName = ''
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isLoggedIn,
|
isLoggedIn: !!loggedIn,
|
||||||
userName: this.userName
|
userName: this.userName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,21 +103,14 @@ export class AuthManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a session is active, or login is required.
|
* Checks whether a session is active, or login is required.
|
||||||
* @returns - a promise which resolves with an object containing three values
|
* @returns - a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`.
|
||||||
* - a boolean `isLoggedIn`
|
|
||||||
* - a string `userName` and
|
|
||||||
* - a form `loginForm` if not loggedin.
|
|
||||||
*/
|
*/
|
||||||
public async checkSession(): Promise<{
|
public async checkSession() {
|
||||||
isLoggedIn: boolean
|
|
||||||
userName?: string
|
|
||||||
loginForm?: any
|
|
||||||
}> {
|
|
||||||
//For VIYA we will send request on API endpoint. Which is faster then pinging SASJobExecution.
|
//For VIYA we will send request on API endpoint. Which is faster then pinging SASJobExecution.
|
||||||
//For SAS9 we will send request on SASStoredProcess
|
//For SAS9 we will send request on SASStoredProcess
|
||||||
const url =
|
const url =
|
||||||
this.serverType === 'SASVIYA'
|
this.serverType === 'SASVIYA'
|
||||||
? `${this.serverUrl}/identities/users/@currentUser`
|
? `${this.serverUrl}/identities`
|
||||||
: `${this.serverUrl}/SASStoredProcess`
|
: `${this.serverUrl}/SASStoredProcess`
|
||||||
|
|
||||||
const { result: loginResponse } = await this.requestClient
|
const { result: loginResponse } = await this.requestClient
|
||||||
@@ -145,16 +120,11 @@ export class AuthManager {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const isLoggedIn = loginResponse !== 'authErr'
|
const isLoggedIn = loginResponse !== 'authErr'
|
||||||
const userName = isLoggedIn
|
|
||||||
? this.extractUserName(loginResponse)
|
|
||||||
: undefined
|
|
||||||
|
|
||||||
let loginForm = null
|
let loginForm = null
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!isLoggedIn) {
|
||||||
//We will logout to make sure cookies are removed and login form is presented
|
//We will logout to make sure cookies are removed and login form is presented
|
||||||
//Residue can happen in case of session expiration
|
this.logOut()
|
||||||
await this.logOut()
|
|
||||||
|
|
||||||
const { result: formResponse } = await this.requestClient.get<string>(
|
const { result: formResponse } = await this.requestClient.get<string>(
|
||||||
this.loginUrl.replace('.do', ''),
|
this.loginUrl.replace('.do', ''),
|
||||||
@@ -167,29 +137,11 @@ export class AuthManager {
|
|||||||
|
|
||||||
return Promise.resolve({
|
return Promise.resolve({
|
||||||
isLoggedIn,
|
isLoggedIn,
|
||||||
userName: userName?.toLowerCase(),
|
userName: this.userName,
|
||||||
loginForm
|
loginForm
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private extractUserName = (response: any): string => {
|
|
||||||
switch (this.serverType) {
|
|
||||||
case ServerType.SasViya:
|
|
||||||
return response?.id
|
|
||||||
|
|
||||||
case ServerType.Sas9:
|
|
||||||
const matched = response?.match(/"title":"Log Off [0-1a-zA-Z ]*"/)
|
|
||||||
const username = matched?.[0].slice(17, -1)
|
|
||||||
|
|
||||||
if (!username.includes(' ')) return username
|
|
||||||
|
|
||||||
return username
|
|
||||||
.split(' ')
|
|
||||||
.map((name: string) => name.slice(0, 3).toLowerCase())
|
|
||||||
.join('')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private getLoginForm(response: any) {
|
private getLoginForm(response: any) {
|
||||||
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/
|
const pattern: RegExp = /<form.+action="(.*Logon[^"]*).*>/
|
||||||
const matches = pattern.exec(response)
|
const matches = pattern.exec(response)
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ describe('AuthManager', () => {
|
|||||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
isLoggedIn: true,
|
isLoggedIn: true,
|
||||||
userName,
|
userName: 'test',
|
||||||
loginForm: 'test'
|
loginForm: 'test'
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -89,6 +89,7 @@ describe('AuthManager', () => {
|
|||||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
||||||
Promise.resolve({
|
Promise.resolve({
|
||||||
isLoggedIn: false,
|
isLoggedIn: false,
|
||||||
|
userName: 'test',
|
||||||
loginForm: { name: 'test' }
|
loginForm: { name: 'test' }
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -174,7 +175,7 @@ describe('AuthManager', () => {
|
|||||||
expect(response.isLoggedIn).toBeTruthy()
|
expect(response.isLoggedIn).toBeTruthy()
|
||||||
expect(mockedAxios.get).toHaveBeenNthCalledWith(
|
expect(mockedAxios.get).toHaveBeenNthCalledWith(
|
||||||
1,
|
1,
|
||||||
`http://test-server.com/identities/users/@currentUser`,
|
`http://test-server.com/identities`,
|
||||||
{
|
{
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
responseType: 'text',
|
responseType: 'text',
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
} from '../types/errors'
|
} from '../types/errors'
|
||||||
import { ExtraResponseAttributes } from '@sasjs/utils/types'
|
import { ExtraResponseAttributes } from '@sasjs/utils/types'
|
||||||
import { BaseJobExecutor } from './JobExecutor'
|
import { BaseJobExecutor } from './JobExecutor'
|
||||||
import { appendExtraResponseAttributes } from '../utils'
|
|
||||||
|
|
||||||
export class JesJobExecutor extends BaseJobExecutor {
|
export class JesJobExecutor extends BaseJobExecutor {
|
||||||
constructor(serverUrl: string, private sasViyaApiClient: SASViyaApiClient) {
|
constructor(serverUrl: string, private sasViyaApiClient: SASViyaApiClient) {
|
||||||
@@ -30,10 +29,21 @@ export class JesJobExecutor extends BaseJobExecutor {
|
|||||||
.then((response: any) => {
|
.then((response: any) => {
|
||||||
this.appendRequest(response, sasJob, config.debug)
|
this.appendRequest(response, sasJob, config.debug)
|
||||||
|
|
||||||
const responseObject = appendExtraResponseAttributes(
|
let responseObject = {}
|
||||||
response,
|
|
||||||
extraResponseAttributes
|
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)
|
resolve(responseObject)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,11 +16,10 @@ export class Sas9JobExecutor extends BaseJobExecutor {
|
|||||||
constructor(
|
constructor(
|
||||||
serverUrl: string,
|
serverUrl: string,
|
||||||
serverType: ServerType,
|
serverType: ServerType,
|
||||||
private jobsPath: string,
|
private jobsPath: string
|
||||||
allowInsecureRequests: boolean
|
|
||||||
) {
|
) {
|
||||||
super(serverUrl, serverType)
|
super(serverUrl, serverType)
|
||||||
this.requestClient = new Sas9RequestClient(serverUrl, allowInsecureRequests)
|
this.requestClient = new Sas9RequestClient(serverUrl, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute(sasJob: string, data: any, config: any) {
|
async execute(sasJob: string, data: any, config: any) {
|
||||||
|
|||||||
@@ -1,13 +1,8 @@
|
|||||||
import {
|
import { ServerType } from '@sasjs/utils/types'
|
||||||
AuthConfig,
|
|
||||||
ExtraResponseAttributes,
|
|
||||||
ServerType
|
|
||||||
} from '@sasjs/utils/types'
|
|
||||||
import {
|
import {
|
||||||
ErrorResponse,
|
ErrorResponse,
|
||||||
JobExecutionError,
|
JobExecutionError,
|
||||||
LoginRequiredError,
|
LoginRequiredError
|
||||||
WeboutResponseError
|
|
||||||
} from '../types/errors'
|
} from '../types/errors'
|
||||||
import { generateFileUploadForm } from '../file/generateFileUploadForm'
|
import { generateFileUploadForm } from '../file/generateFileUploadForm'
|
||||||
import { generateTableUploadForm } from '../file/generateTableUploadForm'
|
import { generateTableUploadForm } from '../file/generateTableUploadForm'
|
||||||
@@ -16,8 +11,7 @@ import { SASViyaApiClient } from '../SASViyaApiClient'
|
|||||||
import {
|
import {
|
||||||
isRelativePath,
|
isRelativePath,
|
||||||
getValidJson,
|
getValidJson,
|
||||||
parseSasViyaDebugResponse,
|
parseSasViyaDebugResponse
|
||||||
appendExtraResponseAttributes
|
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { BaseJobExecutor } from './JobExecutor'
|
import { BaseJobExecutor } from './JobExecutor'
|
||||||
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||||
@@ -42,9 +36,7 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
sasJob: string,
|
sasJob: string,
|
||||||
data: any,
|
data: any,
|
||||||
config: any,
|
config: any,
|
||||||
loginRequiredCallback?: any,
|
loginRequiredCallback?: any
|
||||||
authConfig?: AuthConfig,
|
|
||||||
extraResponseAttributes: ExtraResponseAttributes[] = []
|
|
||||||
) {
|
) {
|
||||||
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
||||||
const program = isRelativePath(sasJob)
|
const program = isRelativePath(sasJob)
|
||||||
@@ -62,21 +54,7 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
|
|
||||||
apiUrl += jobUri.length > 0 ? '&_job=' + jobUri : ''
|
apiUrl += jobUri.length > 0 ? '&_job=' + jobUri : ''
|
||||||
|
|
||||||
if (jobUri.length > 0) {
|
apiUrl += config.contextName ? `&_contextname=${config.contextName}` : ''
|
||||||
apiUrl += '&_job=' + jobUri
|
|
||||||
/**
|
|
||||||
* 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=')
|
|
||||||
}
|
|
||||||
|
|
||||||
// if context name exists and is not blank string
|
|
||||||
// then add _contextname variable in apiUrl
|
|
||||||
apiUrl +=
|
|
||||||
config.contextName && !/\s/.test(config.contextName)
|
|
||||||
? `&_contextname=${config.contextName}`
|
|
||||||
: ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let requestParams = {
|
let requestParams = {
|
||||||
@@ -119,34 +97,20 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
|
|
||||||
const requestPromise = new Promise((resolve, reject) => {
|
const requestPromise = new Promise((resolve, reject) => {
|
||||||
this.requestClient!.post(apiUrl, formData, undefined)
|
this.requestClient!.post(apiUrl, formData, undefined)
|
||||||
.then(async (res: any) => {
|
.then(async (res) => {
|
||||||
let jsonResponse = res.result
|
if (this.serverType === ServerType.SasViya && config.debug) {
|
||||||
|
const jsonResponse = await parseSasViyaDebugResponse(
|
||||||
if (config.debug) {
|
res.result as string,
|
||||||
switch (this.serverType) {
|
this.requestClient,
|
||||||
case ServerType.SasViya:
|
this.serverUrl
|
||||||
jsonResponse = await parseSasViyaDebugResponse(
|
)
|
||||||
res.result,
|
this.appendRequest(res, sasJob, config.debug)
|
||||||
this.requestClient,
|
resolve(jsonResponse)
|
||||||
this.serverUrl
|
|
||||||
)
|
|
||||||
break
|
|
||||||
case ServerType.Sas9:
|
|
||||||
jsonResponse =
|
|
||||||
typeof res.result === 'string'
|
|
||||||
? parseWeboutResponse(res.result, apiUrl)
|
|
||||||
: res.result
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.appendRequest(res, sasJob, config.debug)
|
this.appendRequest(res, sasJob, config.debug)
|
||||||
|
getValidJson(res.result as string)
|
||||||
const responseObject = appendExtraResponseAttributes(
|
resolve(res.result)
|
||||||
{ result: jsonResponse },
|
|
||||||
extraResponseAttributes
|
|
||||||
)
|
|
||||||
resolve(responseObject)
|
|
||||||
})
|
})
|
||||||
.catch(async (e: Error) => {
|
.catch(async (e: Error) => {
|
||||||
if (e instanceof JobExecutionError) {
|
if (e instanceof JobExecutionError) {
|
||||||
@@ -163,9 +127,7 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
sasJob,
|
sasJob,
|
||||||
data,
|
data,
|
||||||
config,
|
config,
|
||||||
loginRequiredCallback,
|
loginRequiredCallback
|
||||||
authConfig,
|
|
||||||
extraResponseAttributes
|
|
||||||
).then(
|
).then(
|
||||||
(res: any) => {
|
(res: any) => {
|
||||||
resolve(res)
|
resolve(res)
|
||||||
|
|||||||
@@ -429,7 +429,13 @@ export class RequestClient implements HttpClient {
|
|||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
try {
|
try {
|
||||||
parsedResponse = JSON.parse(parseWeboutResponse(response.data))
|
const weboutResponse = parseWeboutResponse(response.data)
|
||||||
|
if (weboutResponse === '') {
|
||||||
|
throw new Error('Valid JSON could not be extracted from response.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const jsonResponse = getValidJson(weboutResponse)
|
||||||
|
parsedResponse = jsonResponse
|
||||||
} catch {
|
} catch {
|
||||||
parsedResponse = response.data
|
parsedResponse = response.data
|
||||||
}
|
}
|
||||||
@@ -499,60 +505,46 @@ export const throwIfError = (response: AxiosResponse) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parseError = (data: string) => {
|
const parseError = (data: string) => {
|
||||||
if (!data) return null
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const responseJson = JSON.parse(data?.replace(/[\n\r]/g, ' '))
|
const responseJson = JSON.parse(data?.replace(/[\n\r]/g, ' '))
|
||||||
if (responseJson.errorCode && responseJson.message) {
|
return responseJson.errorCode && responseJson.message
|
||||||
return new JobExecutionError(
|
? new JobExecutionError(
|
||||||
responseJson.errorCode,
|
responseJson.errorCode,
|
||||||
responseJson.message,
|
responseJson.message,
|
||||||
data?.replace(/[\n\r]/g, ' ')
|
data?.replace(/[\n\r]/g, ' ')
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const hasError = data?.includes('{"errorCode')
|
|
||||||
if (hasError) {
|
|
||||||
const parts = data.split('{"errorCode')
|
|
||||||
if (parts.length > 1) {
|
|
||||||
const error = '{"errorCode' + parts[1].split('"}')[0] + '"}'
|
|
||||||
const errorJson = JSON.parse(error.replace(/[\n\r]/g, ' '))
|
|
||||||
return new JobExecutionError(
|
|
||||||
errorJson.errorCode,
|
|
||||||
errorJson.message,
|
|
||||||
data?.replace(/[\n\r]/g, '\n')
|
|
||||||
)
|
)
|
||||||
|
: null
|
||||||
|
} catch (_) {
|
||||||
|
try {
|
||||||
|
const hasError = data?.includes('{"errorCode')
|
||||||
|
if (hasError) {
|
||||||
|
const parts = data.split('{"errorCode')
|
||||||
|
if (parts.length > 1) {
|
||||||
|
const error = '{"errorCode' + parts[1].split('"}')[0] + '"}'
|
||||||
|
const errorJson = JSON.parse(error.replace(/[\n\r]/g, ' '))
|
||||||
|
return new JobExecutionError(
|
||||||
|
errorJson.errorCode,
|
||||||
|
errorJson.message,
|
||||||
|
data?.replace(/[\n\r]/g, '\n')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
}
|
try {
|
||||||
} catch (_) {}
|
const hasError = !!data?.match(/stored process not found: /i)
|
||||||
|
if (hasError) {
|
||||||
try {
|
const parts = data.split(/stored process not found: /i)
|
||||||
const hasError = !!data?.match(/stored process not found: /i)
|
if (parts.length > 1) {
|
||||||
if (hasError) {
|
const storedProcessPath = parts[1].split('<i>')[1].split('</i>')[0]
|
||||||
const parts = data.split(/stored process not found: /i)
|
const message = `Stored process not found: ${storedProcessPath}`
|
||||||
if (parts.length > 1) {
|
return new JobExecutionError(404, message, '')
|
||||||
const storedProcessPath = parts[1].split('<i>')[1].split('</i>')[0]
|
}
|
||||||
const message = `Stored process not found: ${storedProcessPath}`
|
}
|
||||||
return new JobExecutionError(404, message, '')
|
} catch (_) {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
} catch (_) {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const hasError =
|
|
||||||
!!data?.match(/Stored Process Error/i) &&
|
|
||||||
!!data?.match(/This request completed with errors./i)
|
|
||||||
if (hasError) {
|
|
||||||
const parts = data.split('<h2>SAS Log</h2>')
|
|
||||||
if (parts.length > 1) {
|
|
||||||
const log = parts[1].split('<pre>')[1].split('</pre>')[0]
|
|
||||||
const message = `This request completed with errors.`
|
|
||||||
return new JobExecutionError(404, message, log)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,6 @@ import { RequestClient } from '../request/RequestClient'
|
|||||||
import { NoSessionStateError } from '../types/errors'
|
import { NoSessionStateError } from '../types/errors'
|
||||||
import * as dotenv from 'dotenv'
|
import * as dotenv from 'dotenv'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { Logger, LogLevel } from '@sasjs/utils'
|
|
||||||
import { Session } from '../types'
|
|
||||||
|
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
const mockedAxios = axios as jest.Mocked<typeof axios>
|
const mockedAxios = axios as jest.Mocked<typeof axios>
|
||||||
@@ -49,91 +47,36 @@ describe('SessionManager', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('waitForSession', () => {
|
describe('waitForSession', () => {
|
||||||
const session: Session = {
|
|
||||||
id: 'id',
|
|
||||||
state: '',
|
|
||||||
links: [{ rel: 'state', href: '', uri: '', type: '', method: 'GET' }],
|
|
||||||
attributes: {
|
|
||||||
sessionInactiveTimeout: 0
|
|
||||||
},
|
|
||||||
creationTimeStamp: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
;(process as any).logger = new Logger(LogLevel.Off)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should reject with NoSessionStateError if SAS server did not provide session state', async () => {
|
it('should reject with NoSessionStateError if SAS server did not provide session state', async () => {
|
||||||
let requestAttempt = 0
|
const responseStatus = 304
|
||||||
const requestAttemptLimit = 10
|
|
||||||
const sessionState = 'idle'
|
|
||||||
|
|
||||||
mockedAxios.get.mockImplementation(() => {
|
|
||||||
requestAttempt += 1
|
|
||||||
|
|
||||||
if (requestAttempt >= requestAttemptLimit) {
|
|
||||||
return Promise.resolve({ data: sessionState, status: 200 })
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve({ data: '', status: 304 })
|
|
||||||
})
|
|
||||||
|
|
||||||
jest.spyOn((process as any).logger, 'info')
|
|
||||||
|
|
||||||
sessionManager.debug = true
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
sessionManager['waitForSession'](session, null, 'access_token')
|
|
||||||
).resolves.toEqual(sessionState)
|
|
||||||
|
|
||||||
expect(mockedAxios.get).toHaveBeenCalledTimes(requestAttemptLimit)
|
|
||||||
expect((process as any).logger.info).toHaveBeenCalledTimes(3)
|
|
||||||
expect((process as any).logger.info).toHaveBeenNthCalledWith(
|
|
||||||
1,
|
|
||||||
'Polling session status...'
|
|
||||||
)
|
|
||||||
expect((process as any).logger.info).toHaveBeenNthCalledWith(
|
|
||||||
2,
|
|
||||||
`Could not get session state. Server responded with 304 whilst checking state: ${process.env.SERVER_URL}`
|
|
||||||
)
|
|
||||||
expect((process as any).logger.info).toHaveBeenNthCalledWith(
|
|
||||||
3,
|
|
||||||
`Current session state is '${sessionState}'`
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should throw an error if there is no session link', async () => {
|
|
||||||
const customSession = JSON.parse(JSON.stringify(session))
|
|
||||||
customSession.links = []
|
|
||||||
|
|
||||||
mockedAxios.get.mockImplementation(() =>
|
mockedAxios.get.mockImplementation(() =>
|
||||||
Promise.resolve({ data: customSession.state, status: 200 })
|
Promise.resolve({ data: '', status: responseStatus })
|
||||||
)
|
)
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sessionManager['waitForSession'](customSession, null, 'access_token')
|
sessionManager['waitForSession'](
|
||||||
).rejects.toContain('Error while getting session state link.')
|
{
|
||||||
})
|
id: 'id',
|
||||||
|
state: '',
|
||||||
it('should throw an error if could not get session state', async () => {
|
links: [
|
||||||
mockedAxios.get.mockImplementation(() => Promise.reject('Mocked error'))
|
{ rel: 'state', href: '', uri: '', type: '', method: 'GET' }
|
||||||
|
],
|
||||||
await expect(
|
attributes: {
|
||||||
sessionManager['waitForSession'](session, null, 'access_token')
|
sessionInactiveTimeout: 0
|
||||||
).rejects.toContain('Error while getting session state.')
|
},
|
||||||
})
|
creationTimeStamp: ''
|
||||||
|
},
|
||||||
it('should return session state', async () => {
|
null,
|
||||||
const customSession = JSON.parse(JSON.stringify(session))
|
'access_token'
|
||||||
customSession.state = 'completed'
|
)
|
||||||
|
).rejects.toEqual(
|
||||||
mockedAxios.get.mockImplementation(() =>
|
new NoSessionStateError(
|
||||||
Promise.resolve({ data: customSession.state, status: 200 })
|
responseStatus,
|
||||||
|
process.env.SERVER_URL as string,
|
||||||
|
'logUrl'
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
await expect(
|
|
||||||
sessionManager['waitForSession'](customSession, null, 'access_token')
|
|
||||||
).resolves.toEqual(customSession.state)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { getValidJson } from '../../utils'
|
import { getValidJson } from '../../utils'
|
||||||
import { JsonParseArrayError, InvalidJsonError } from '../../types/errors'
|
|
||||||
|
|
||||||
describe('jsonValidator', () => {
|
describe('jsonValidator', () => {
|
||||||
it('should not throw an error with a valid json', () => {
|
it('should not throw an error with a valid json', () => {
|
||||||
@@ -20,17 +19,23 @@ describe('jsonValidator', () => {
|
|||||||
|
|
||||||
it('should throw an error with an invalid json', () => {
|
it('should throw an error with an invalid json', () => {
|
||||||
const json = `{\"test\":\"test\"\"test2\":\"test\"}`
|
const json = `{\"test\":\"test\"\"test2\":\"test\"}`
|
||||||
const test = () => {
|
let errorThrown = false
|
||||||
|
try {
|
||||||
getValidJson(json)
|
getValidJson(json)
|
||||||
|
} catch (error) {
|
||||||
|
errorThrown = true
|
||||||
}
|
}
|
||||||
expect(test).toThrowError(InvalidJsonError)
|
expect(errorThrown).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw an error when an array is passed', () => {
|
it('should throw an error when an array is passed', () => {
|
||||||
const array = ['hello', 'world']
|
const array = ['hello', 'world']
|
||||||
const test = () => {
|
let errorThrown = false
|
||||||
|
try {
|
||||||
getValidJson(array)
|
getValidJson(array)
|
||||||
|
} catch (error) {
|
||||||
|
errorThrown = true
|
||||||
}
|
}
|
||||||
expect(test).toThrow(JsonParseArrayError)
|
expect(errorThrown).toBe(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
export class InvalidJsonError extends Error {
|
|
||||||
constructor() {
|
|
||||||
super('Error: invalid Json string')
|
|
||||||
this.name = 'InvalidJsonError'
|
|
||||||
Object.setPrototypeOf(this, InvalidJsonError.prototype)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export class JsonParseArrayError extends Error {
|
|
||||||
constructor() {
|
|
||||||
super('Can not parse array object to json.')
|
|
||||||
this.name = 'JsonParseArrayError'
|
|
||||||
Object.setPrototypeOf(this, JsonParseArrayError.prototype)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { RootFolderNotFoundError } from './RootFolderNotFoundError'
|
|
||||||
|
|
||||||
describe('RootFolderNotFoundError', () => {
|
|
||||||
it('when access token is provided, error message should contain the scopes in the token', () => {
|
|
||||||
const token =
|
|
||||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJzY29wZS0xIiwic2NvcGUtMiJdfQ.ktqPL2ulln-8Asa2jSV9QCfDYmQuNk4tNKopxJR5xZs'
|
|
||||||
|
|
||||||
const error = new RootFolderNotFoundError(
|
|
||||||
'/myProject',
|
|
||||||
'https://analytium.co.uk',
|
|
||||||
token
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(error).toBeInstanceOf(RootFolderNotFoundError)
|
|
||||||
expect(error.message).toContain('scope-1')
|
|
||||||
expect(error.message).toContain('scope-2')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('when access token is not provided, error message should not contain scopes', () => {
|
|
||||||
const error = new RootFolderNotFoundError(
|
|
||||||
'/myProject',
|
|
||||||
'https://analytium.co.uk'
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(error).toBeInstanceOf(RootFolderNotFoundError)
|
|
||||||
expect(error.message).not.toContain(
|
|
||||||
'Your access token contains the following scopes'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should include the folder path and SASDrive URL in the message', () => {
|
|
||||||
const folderPath = '/myProject'
|
|
||||||
const serverUrl = 'https://analytium.co.uk'
|
|
||||||
const error = new RootFolderNotFoundError(folderPath, serverUrl)
|
|
||||||
|
|
||||||
expect(error).toBeInstanceOf(RootFolderNotFoundError)
|
|
||||||
expect(error.message).toContain(folderPath)
|
|
||||||
expect(error.message).toContain(`${serverUrl}/SASDrive`)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { decodeToken } from '@sasjs/utils/auth'
|
|
||||||
|
|
||||||
export class RootFolderNotFoundError extends Error {
|
|
||||||
constructor(
|
|
||||||
parentFolderPath: string,
|
|
||||||
serverUrl: string,
|
|
||||||
accessToken?: string
|
|
||||||
) {
|
|
||||||
let message: string =
|
|
||||||
`Root folder ${parentFolderPath} was not found.` +
|
|
||||||
`\nPlease check ${serverUrl}/SASDrive.` +
|
|
||||||
`\nIf the folder DOES exist then it is likely a permission problem.\n`
|
|
||||||
if (accessToken) {
|
|
||||||
const decodedToken = decodeToken(accessToken)
|
|
||||||
let scope = decodedToken.scope
|
|
||||||
scope = scope.map((element) => '* ' + element)
|
|
||||||
message +=
|
|
||||||
`Your access token contains the following scopes:\n` + scope.join('\n')
|
|
||||||
}
|
|
||||||
super(message)
|
|
||||||
this.name = 'RootFolderNotFoundError'
|
|
||||||
Object.setPrototypeOf(this, RootFolderNotFoundError.prototype)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
export class WeboutResponseError extends Error {
|
|
||||||
constructor(public url: string) {
|
|
||||||
super(`Error: error while parsing response from ${url}`)
|
|
||||||
this.name = 'WeboutResponseError'
|
|
||||||
Object.setPrototypeOf(this, WeboutResponseError.prototype)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,3 @@ export * from './LoginRequiredError'
|
|||||||
export * from './NotFoundError'
|
export * from './NotFoundError'
|
||||||
export * from './ErrorResponse'
|
export * from './ErrorResponse'
|
||||||
export * from './NoSessionStateError'
|
export * from './NoSessionStateError'
|
||||||
export * from './RootFolderNotFoundError'
|
|
||||||
export * from './JsonParseArrayError'
|
|
||||||
export * from './WeboutResponseError'
|
|
||||||
export * from './InvalidJsonError'
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { ExtraResponseAttributes } from '@sasjs/utils/types'
|
|
||||||
|
|
||||||
export async function appendExtraResponseAttributes(
|
|
||||||
response: any,
|
|
||||||
extraResponseAttributes: ExtraResponseAttributes[]
|
|
||||||
) {
|
|
||||||
let responseObject = {}
|
|
||||||
|
|
||||||
if (extraResponseAttributes?.length) {
|
|
||||||
const extraAttributes = extraResponseAttributes.reduce(
|
|
||||||
(map: any, obj: any) => ((map[obj] = response[obj]), map),
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
responseObject = {
|
|
||||||
result: response.result,
|
|
||||||
...extraAttributes
|
|
||||||
}
|
|
||||||
} else responseObject = response.result
|
|
||||||
|
|
||||||
return responseObject
|
|
||||||
}
|
|
||||||
@@ -1,18 +1,16 @@
|
|||||||
import { JsonParseArrayError, InvalidJsonError } from '../types/errors'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* if string passed then parse the string to json else if throw error for all other types unless it is not a valid json object.
|
* if string passed then parse the string to json else if throw error for all other types unless it is not a valid json object.
|
||||||
* @param str - string to check.
|
* @param str - string to check.
|
||||||
*/
|
*/
|
||||||
export const getValidJson = (str: string | object) => {
|
export const getValidJson = (str: string | object) => {
|
||||||
try {
|
try {
|
||||||
if (Array.isArray(str)) throw new JsonParseArrayError()
|
if (Array.isArray(str)) {
|
||||||
|
throw new Error('Can not parse array object to json.')
|
||||||
|
}
|
||||||
if (typeof str === 'object') return str
|
if (typeof str === 'object') return str
|
||||||
|
|
||||||
return JSON.parse(str)
|
return JSON.parse(str)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof JsonParseArrayError) throw e
|
throw new Error('Invalid JSON response.')
|
||||||
throw new InvalidJsonError()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,4 +15,3 @@ export * from './parseWeboutResponse'
|
|||||||
export * from './fetchLogByChunks'
|
export * from './fetchLogByChunks'
|
||||||
export * from './getValidJson'
|
export * from './getValidJson'
|
||||||
export * from './parseViyaDebugResponse'
|
export * from './parseViyaDebugResponse'
|
||||||
export * from './appendExtraResponseAttributes'
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { RequestClient } from '../request/RequestClient'
|
import { RequestClient } from '../request/RequestClient'
|
||||||
import { getValidJson } from '../utils'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When querying a Viya job using the Web approach (as opposed to using the APIs) with _DEBUG enabled,
|
* When querying a Viya job using the Web approach (as opposed to using the APIs) with _DEBUG enabled,
|
||||||
@@ -26,5 +25,5 @@ export const parseSasViyaDebugResponse = async (
|
|||||||
|
|
||||||
return requestClient
|
return requestClient
|
||||||
.get(serverUrl + jsonUrl, undefined)
|
.get(serverUrl + jsonUrl, undefined)
|
||||||
.then((res: any) => getValidJson(res.result))
|
.then((res) => res.result)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { WeboutResponseError } from '../types/errors'
|
export const parseWeboutResponse = (response: string) => {
|
||||||
|
|
||||||
export const parseWeboutResponse = (response: string, url?: string) => {
|
|
||||||
let sasResponse = ''
|
let sasResponse = ''
|
||||||
|
|
||||||
if (response.includes('>>weboutBEGIN<<')) {
|
if (response.includes('>>weboutBEGIN<<')) {
|
||||||
@@ -9,7 +7,6 @@ export const parseWeboutResponse = (response: string, url?: string) => {
|
|||||||
.split('>>weboutBEGIN<<')[1]
|
.split('>>weboutBEGIN<<')[1]
|
||||||
.split('>>weboutEND<<')[0]
|
.split('>>weboutEND<<')[0]
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (url) throw new WeboutResponseError(url)
|
|
||||||
sasResponse = ''
|
sasResponse = ''
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user