mirror of
https://github.com/sasjs/adapter.git
synced 2025-12-14 02:24:35 +00:00
Compare commits
258 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c30cbff13 | ||
|
|
8f3a7f33f8 | ||
|
|
67ec27bab7 | ||
|
|
c1b200b0d8 | ||
|
|
e03ec996d6 | ||
|
|
ad8dbfd4ec | ||
|
|
15a774ff81 | ||
|
|
98114c5591 | ||
|
|
dffcb66d54 | ||
|
|
67c7147e62 | ||
|
|
50d1b4d824 | ||
|
|
cf1e3f3835 | ||
|
|
2f913e9363 | ||
|
|
05a9864df8 | ||
|
|
3a0d764dfa | ||
|
|
310087b895 | ||
|
|
dc39ecd4a8 | ||
|
|
99e192c5de | ||
|
|
b86658ef9b | ||
|
|
88f08e8864 | ||
|
|
80e5de5d65 | ||
|
|
665734b168 | ||
|
|
5543f467e6 | ||
|
|
a32c0879b3 | ||
|
|
bb2ad5bb9a | ||
|
|
6f2f11d112 | ||
|
|
fef65bbfd2 | ||
|
|
efeba71612 | ||
|
|
8f54002b1e | ||
|
|
9d6882799d | ||
|
|
73a3acee68 | ||
|
|
0a88220e04 | ||
|
|
c8e1779272 | ||
|
|
8bd3580e23 | ||
|
|
f732b32873 | ||
|
|
65b18f9148 | ||
|
|
10b1676a35 | ||
|
|
b9bd09d3e8 | ||
|
|
537f687b94 | ||
|
|
bfd532f813 | ||
|
|
4f2b4f46a8 | ||
|
|
077cc9458d | ||
|
|
0a7ab394a4 | ||
|
|
f873febfde | ||
|
|
55e8ce359b | ||
|
|
99d7c8f119 | ||
|
|
b3c90f09d6 | ||
|
|
2401962c53 | ||
|
|
362b4d4db3 | ||
|
|
8aea325139 | ||
|
|
bb370061a2 | ||
|
|
48442f7769 | ||
|
|
e67a8531ce | ||
|
|
ef4f020e2a | ||
|
|
2feceeb2f9 | ||
|
|
eaec922fea | ||
|
|
de94777fff | ||
|
|
0aa0ae65e0 | ||
|
|
4b0d62d59b | ||
|
|
b3ef50e9eb | ||
|
|
d30a1890a1 | ||
|
|
f1c2569de3 | ||
|
|
4826388cdd | ||
| e88736056a | |||
| 9da2a29a72 | |||
| dce8a08a86 | |||
| 1fabb9e610 | |||
| 23db0ac80d | |||
| 28370341d8 | |||
|
|
a023ffe850 | ||
|
|
a4e77ecf6e | ||
|
|
7efc0a1fb2 | ||
|
|
c3e2b2ce70 | ||
|
|
dde1228b1d | ||
|
|
b3474b6dfb | ||
|
|
179a04ae31 | ||
|
|
2bdcbda54c | ||
|
|
a123392c56 | ||
|
|
719135e366 | ||
|
|
ba619554b7 | ||
|
|
6a3ab7032f | ||
|
|
d818d14cb4 | ||
|
|
599c130395 | ||
|
|
9ef2759e27 | ||
|
|
43355c88d4 | ||
|
|
15e1acaf4f | ||
|
|
ec77ffdd88 | ||
|
|
9797c1ca84 | ||
|
|
bbe9633dc8 | ||
|
|
6f60ac5cc7 | ||
|
|
e7ba09793c | ||
|
|
c0c0800e61 | ||
|
|
0bd9d8f93f | ||
|
|
214fc2d5cd | ||
|
|
55b0e2f934 | ||
|
|
609cd4ed6d | ||
|
|
2b20bbdcc8 | ||
|
|
946a95bea1 | ||
|
|
7ec1c152e3 | ||
|
|
0cf1110018 | ||
|
|
9c5ada6fa1 | ||
|
|
0f5702e21b | ||
|
|
f10d6ec80a | ||
|
|
490215df90 | ||
|
|
9d27451813 | ||
|
|
a977f59675 | ||
|
|
51a09d049c | ||
|
|
47be5e77e5 | ||
|
|
d517e79ec0 | ||
|
|
8714ecdde8 | ||
|
|
2cffc57209 | ||
|
|
4e43687de2 | ||
|
|
f8dab83e37 | ||
|
|
655af03cf3 | ||
|
|
0a4dd00edb | ||
|
|
55b4929c54 | ||
|
|
8eb73a6b3c | ||
|
|
0aeb201625 | ||
|
|
e0140a23c2 | ||
|
|
ff6698a9d1 | ||
|
|
843d498b72 | ||
|
|
349612a065 | ||
|
|
e48b22128d | ||
|
|
14dfe4ec51 | ||
|
|
f679b17cbe | ||
|
|
36a0f0e743 | ||
|
|
ad563b9bc8 | ||
|
|
e0051bf276 | ||
|
|
7b72998e1c | ||
|
|
893cce7f21 | ||
|
|
bf7e8fd0e6 | ||
|
|
66d02cf1d1 | ||
|
|
f2c8e40430 | ||
|
|
4b28ee8e73 | ||
|
|
c7e54cfe9f | ||
|
|
50be3acc78 | ||
|
|
66c12a69bb | ||
|
|
e2058f0c71 | ||
|
|
75a74152a5 | ||
|
|
9344c4fd35 | ||
|
|
be0fe00076 | ||
|
|
a2069ee48b | ||
|
|
907885cf85 | ||
|
|
f10ed3236e | ||
|
|
a852a0af7c | ||
|
|
03f3550774 | ||
|
|
2e8d06f9e1 | ||
|
|
1d31ec4dcc | ||
|
|
4407ed68ae | ||
|
|
b3359f2138 | ||
|
|
cff9104adf | ||
|
|
b73f821c19 | ||
|
|
dd3c1a7375 | ||
|
|
c10b5368af | ||
|
|
2df66765c0 | ||
|
|
ed65545571 | ||
|
|
b4ae486520 | ||
|
|
bb7b3d0b84 | ||
|
|
65b34aa015 | ||
|
|
b3c9303def | ||
|
|
19dfda9b6a | ||
|
|
ce2abb448f | ||
|
|
413c3b8098 | ||
|
|
269514a44f | ||
|
|
5fc334dd8b | ||
|
|
1b251f1cea | ||
|
|
70d3e25c7f | ||
|
|
5e9b33e346 | ||
|
|
eb52ec7532 | ||
|
|
f46c4bf3ca | ||
|
|
d8176912cf | ||
|
|
4c54ade2d3 | ||
|
|
52c41dfb3a | ||
|
|
36f0aa7411 | ||
|
|
a10e4ec264 | ||
|
|
851b6bc273 | ||
|
|
23f8d31b1b | ||
|
|
a5683bcd07 | ||
|
|
c7be71c781 | ||
|
|
594f274323 | ||
|
|
ba1ed5e732 | ||
|
|
613cc6b9ef | ||
|
|
8edb00f869 | ||
|
|
60a1f84604 | ||
|
|
36cfaee5db | ||
|
|
cb607c93ca | ||
|
|
03b5e1d824 | ||
|
|
85a530ae5a | ||
|
|
2ea49a425f | ||
|
|
3c894c4147 | ||
|
|
23d151c919 | ||
|
|
6d1c4ff81a | ||
|
|
7a6e6e8333 | ||
|
|
0eba6bdcf4 | ||
|
|
d7ecaf5932 | ||
|
|
1fc6db114d | ||
|
|
8d203b8df4 | ||
|
|
39924ff078 | ||
|
|
de25f106ec | ||
|
|
e0d85f458b | ||
|
|
3a9cd46e6e | ||
|
|
301edab8ad | ||
|
|
aed39c2ec4 | ||
|
|
cc594eca52 | ||
|
|
3fbca2835e | ||
|
|
61ed5c4fa7 | ||
|
|
e31774ae9d | ||
|
|
00f09179a8 | ||
|
|
4196901e01 | ||
|
|
bf35dd072a | ||
|
|
75e3fd018d | ||
|
|
965dfff7c6 | ||
|
|
ff64dd22ad | ||
|
|
e7cceab065 | ||
|
|
f789b8f7a2 | ||
|
|
c0b82c5125 | ||
|
|
1c1b5baefe | ||
|
|
8b17aeaea2 | ||
|
|
cb0d03c965 | ||
|
|
9e77f3d64e | ||
|
|
25f61815dc | ||
|
|
3a2252e69c | ||
|
|
8a08980e6a | ||
|
|
d0f31771ad | ||
|
|
e9e2c9372d | ||
|
|
70c4a095a0 | ||
|
|
82e2fc4445 | ||
|
|
6661d81fdf | ||
|
|
e76abaafa8 | ||
|
|
fbfc1c05d6 | ||
|
|
839c211c64 | ||
|
|
f3ff82143a | ||
|
|
0dd0abae87 | ||
|
|
13781c993e | ||
|
|
7616cacbec | ||
|
|
cab7d3c012 | ||
|
|
dfce676fdf | ||
|
|
1890cab623 | ||
|
|
4307d8fe43 | ||
|
|
8df6fdbee6 | ||
|
|
ac5c2a3088 | ||
|
|
0212b677ae | ||
|
|
2a4735c6f2 | ||
|
|
9b6ba3548f | ||
|
|
efa4c71b8a | ||
|
|
ecfc1a4bf8 | ||
|
|
246b855c76 | ||
|
|
80aa848617 | ||
|
|
acf045965e | ||
|
|
5a7f64dc35 | ||
|
|
de447b0727 | ||
|
|
a2906abf71 | ||
|
|
ace16efd93 | ||
|
|
13ae5ae756 | ||
|
|
f1b035032f | ||
|
|
1555afe771 | ||
|
|
a2832f1e1a | ||
|
|
56f34508fa |
2
.github/workflows/npmpublish.yml
vendored
2
.github/workflows/npmpublish.yml
vendored
@@ -25,3 +25,5 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||||
|
- name: Send Slack message
|
||||||
|
run: curl -X POST --data-urlencode "payload={\"channel\":\"#sasjs\", \"username\":\"GitHub CI\", \"text\":\"New version of @sasjs/adapter has been released! \n Please deploy and run `dctests` with new adapter to make sure everything is still in place.\", \"icon_emoji\":\":rocket:\"}" ${{ secrets.SLACK_WEBHOOK }}
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,4 +3,6 @@ build
|
|||||||
|
|
||||||
.env
|
.env
|
||||||
|
|
||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
161
README.md
161
README.md
@@ -6,7 +6,7 @@ SASjs is a open-source framework for building Web Apps on SAS® platforms. You c
|
|||||||
|
|
||||||
1 - `npm install @sasjs/adapter` - for use in a node project
|
1 - `npm install @sasjs/adapter` - for use in a node project
|
||||||
|
|
||||||
2 - [Download](https://cdn.jsdelivr.net/npm/@sasjs/adapter@1/index.js) and use a copy of the latest JS file
|
2 - [Download](https://cdn.jsdelivr.net/npm/@sasjs/adapter@2/index.js) and use a copy of the latest JS file
|
||||||
|
|
||||||
3 - Reference directly from the CDN - in which case click [here](https://www.jsdelivr.com/package/npm/@sasjs/adapter?tab=collection) and select "SRI" to get the script tag with the integrity hash.
|
3 - Reference directly from the CDN - in which case click [here](https://www.jsdelivr.com/package/npm/@sasjs/adapter?tab=collection) and select "SRI" to get the script tag with the integrity hash.
|
||||||
|
|
||||||
@@ -41,8 +41,165 @@ parmcards4;
|
|||||||
|
|
||||||
You now have a simple web app with a backend service!
|
You now have a simple web app with a backend service!
|
||||||
|
|
||||||
|
## Detailed Overview
|
||||||
|
|
||||||
|
The SASjs adapter is a JS library and a set of SAS Macros that handle the communication between the frontend app and backend SAS services.
|
||||||
|
|
||||||
|
There are three parts to consider:
|
||||||
|
|
||||||
|
1. JS request / response
|
||||||
|
2. SAS inputs / outputs
|
||||||
|
3. Configuration
|
||||||
|
|
||||||
|
### JS Request / Response
|
||||||
|
|
||||||
|
To install the library you can simply run `npm i @sasjs/adapter` or include a `<script>` tag with a reference to our [CDN](https://www.jsdelivr.com/package/npm/@sasjs/adapter).
|
||||||
|
|
||||||
|
Full technical documentation is available [here](https://adapter.sasjs.io). The main parts are:
|
||||||
|
|
||||||
|
### Instantiation
|
||||||
|
The following code will instantiate an instance of the adapter:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
let sasJs = new SASjs.default(
|
||||||
|
{
|
||||||
|
appLoc: "/Your/SAS/Folder",
|
||||||
|
serverType:"SAS9"
|
||||||
|
}
|
||||||
|
);
|
||||||
|
```
|
||||||
|
If you've installed it via NPM, you can import it as a default import like so:
|
||||||
|
```
|
||||||
|
import SASjs from '@sasjs/adapter';
|
||||||
|
```
|
||||||
|
You can then instantiate it with:
|
||||||
|
```
|
||||||
|
const sasJs = new SASjs({your config})
|
||||||
|
```
|
||||||
|
|
||||||
|
More on the config later.
|
||||||
|
|
||||||
|
### SAS Logon
|
||||||
|
The login process can be handled directly, as below, or as a callback function to a SAS request.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
sasJs.logIn('USERNAME','PASSWORD'
|
||||||
|
).then((response) => {
|
||||||
|
if (response.isLoggedIn === true) {
|
||||||
|
console.log('do stuff')
|
||||||
|
} else {
|
||||||
|
console.log('do other stuff')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Request / Response
|
||||||
|
A simple request can be sent to SAS in the following fashion:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
sasJs.request("/path/to/my/service", dataObject)
|
||||||
|
.then((response) => {
|
||||||
|
// all tables are in the response object, eg:
|
||||||
|
console.log(response.tablewith2cols1row[0].COL1.value)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
We supply the path to the SAS service, and a data object. The data object can be null (for services with no input), or can contain one or more tables in the following format:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
let dataObject={
|
||||||
|
"tablewith2cols1row": [{
|
||||||
|
"col1": "val1",
|
||||||
|
"col2": 42
|
||||||
|
}],
|
||||||
|
"tablewith1col2rows": [{
|
||||||
|
"col": "row1"
|
||||||
|
}, {
|
||||||
|
"col": "row2"
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
There are optional parameters such as a config object and a callback login function.
|
||||||
|
|
||||||
|
The response object will contain returned tables and columns. Table names are always lowercase, and column names uppercase.
|
||||||
|
|
||||||
|
The adapter will also cache the logs (if debug enabled) and even the work tables. For performance, it is best to keep debug mode off.
|
||||||
|
|
||||||
|
## SAS Inputs / Outputs
|
||||||
|
|
||||||
|
The SAS side is handled by a number of macros in the [macro core](https://github.com/sasjs/core) library.
|
||||||
|
|
||||||
|
The following snippet shows the process of SAS tables arriving / leaving:
|
||||||
|
```sas
|
||||||
|
/* fetch all input tables sent from frontend - they arrive as work tables */
|
||||||
|
%webout(FETCH)
|
||||||
|
|
||||||
|
/* some sas code */
|
||||||
|
data some sas tables;
|
||||||
|
set from js;
|
||||||
|
run;
|
||||||
|
|
||||||
|
%webout(OPEN) /* open the JSON to be returned */
|
||||||
|
%webout(OBJ,some) /* `some` table is sent in object format */
|
||||||
|
%webout(ARR,sas) /* `sas` table is sent in array format, smaller filesize */
|
||||||
|
%webout(OBJ,tables,fmt=N) /* unformatted (raw) data */
|
||||||
|
%webout(OBJ,tables,label=newtable) /* rename tables on export */
|
||||||
|
%webout(CLOSE) /* close the JSON and send some extra useful variables too */
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Configuration on the client side involves passing an object on startup, which can also be passed with each request. Technical documentation on the SASjsConfig class is available [here](https://adapter.sasjs.io/classes/types.sasjsconfig.html). The main config items are:
|
||||||
|
|
||||||
|
* `appLoc` - this is the folder under which the SAS services will be created.
|
||||||
|
* `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.
|
||||||
|
* `debug` - if `true` then SAS Logs and extra debug information is returned.
|
||||||
|
* `useComputeApi` - if `true` and the serverType is `SASVIYA` then the REST APIs will be called directly (rather than using the JES web service).
|
||||||
|
* `contextName` - if missing or blank, and `useComputeApi` is `true` and `serverType` is `SASVIYA` then the JES API will be used.
|
||||||
|
|
||||||
|
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).
|
||||||
|
|
||||||
|
### Using JES Web App
|
||||||
|
|
||||||
|
In this setup, all requests are routed through the JES web app, at `YOURSERVER/SASJobExecution`. This is the most reliable method, and also the slowest. One request is made to the JES app, and remaining requests (getting job uri, session spawning, passing parameters, running the program, fetching the log) are made on the SAS server by the JES app.
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
appLoc:"/Your/Path",
|
||||||
|
serverType:"SASVIYA"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using the JES API
|
||||||
|
Here we are running Jobs using the Job Execution Service except this time we are making the requests directly using the REST API instead of through the JES Web App. This is helpful when we need to call web services outside of a browser (eg with the SASjs CLI or other commandline tools). To save one network request, the adapter prefetches the JOB URIs and passes them in the `__job` parameter.
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
appLoc:"/Your/Path",
|
||||||
|
serverType:"SASVIYA",
|
||||||
|
useComputeApi: true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using the Compute API
|
||||||
|
This approach is by far the fastest, as a result of the optimisations we have built into the adapter. With this configuration, in the first sasjs request, we take a URI map of the services in the target folder, and create a session manager - which spawns an extra session. The next time a request is made, the adapter will use the 'hot' session. Sessions are deleted after every use, which actually makes this _less_ resource intensive than a typical JES web app, in which all sessions are kept alive by default for 15 minutes.
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
appLoc:"/Your/Path",
|
||||||
|
serverType:"SASVIYA",
|
||||||
|
useComputeApi: true,
|
||||||
|
contextName: 'yourComputeContext'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
# More resources
|
# More resources
|
||||||
|
|
||||||
For more information and examples specific to this adapter you can check out the [user guide](https://sasjs.io/sasjs-adapter/) or the [technical](http://adapter.sasjs.io/) documentation.
|
For more information and examples specific to this adapter you can check out the [user guide](https://sasjs.io/sasjs-adapter/) or the [technical](http://adapter.sasjs.io/) documentation.
|
||||||
|
|
||||||
For more information on building web apps in general, check out these [resources](https://sasjs.io/training/resources/) or contact the [author](https://www.linkedin.com/in/allanbowe/) directly.
|
For more information on building web apps in general, check out these [resources](https://sasjs.io/training/resources/) or contact the [author](https://www.linkedin.com/in/allanbowe/) directly.
|
||||||
|
|
||||||
|
If you are a SAS 9 or SAS Viya customer you can also request a copy of [Data Controller](https://datacontroller.io) - free for up to 5 users, this tool makes use of all parts of the SASjs framework.
|
||||||
|
|||||||
13
cypress.json
13
cypress.json
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"defaultCommandTimeout": 10000,
|
|
||||||
"chromeWebSecurity": false,
|
|
||||||
"screenshotOnRunFailure": false,
|
|
||||||
"env": {
|
|
||||||
"serverUrl": "",
|
|
||||||
"appLoc": "/Public/app",
|
|
||||||
"serverType": "SAS9",
|
|
||||||
"debug": false,
|
|
||||||
"username": "",
|
|
||||||
"password": ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
328
docs/classes/auth.authmanager.html
Normal file
328
docs/classes/auth.authmanager.html
Normal file
File diff suppressed because one or more lines are too long
435
docs/classes/job_execution.basejobexecutor.html
Normal file
435
docs/classes/job_execution.basejobexecutor.html
Normal file
File diff suppressed because one or more lines are too long
438
docs/classes/job_execution.computejobexecutor.html
Normal file
438
docs/classes/job_execution.computejobexecutor.html
Normal file
File diff suppressed because one or more lines are too long
438
docs/classes/job_execution.jesjobexecutor.html
Normal file
438
docs/classes/job_execution.jesjobexecutor.html
Normal file
File diff suppressed because one or more lines are too long
444
docs/classes/job_execution.webjobexecutor.html
Normal file
444
docs/classes/job_execution.webjobexecutor.html
Normal file
File diff suppressed because one or more lines are too long
231
docs/classes/reflection-817.reflection-220.fileuploader.html
Normal file
231
docs/classes/reflection-817.reflection-220.fileuploader.html
Normal file
File diff suppressed because one or more lines are too long
312
docs/classes/reflection-817.reflection-220.sas9apiclient.html
Normal file
312
docs/classes/reflection-817.reflection-220.sas9apiclient.html
Normal file
File diff suppressed because one or more lines are too long
1715
docs/classes/reflection-817.reflection-220.sasjs.html
Normal file
1715
docs/classes/reflection-817.reflection-220.sasjs.html
Normal file
File diff suppressed because one or more lines are too long
1516
docs/classes/reflection-817.reflection-220.sasviyaapiclient.html
Normal file
1516
docs/classes/reflection-817.reflection-220.sasviyaapiclient.html
Normal file
File diff suppressed because one or more lines are too long
323
docs/classes/reflection-817.reflection-220.sessionmanager.html
Normal file
323
docs/classes/reflection-817.reflection-220.sessionmanager.html
Normal file
File diff suppressed because one or more lines are too long
231
docs/classes/reflection-830.reflection-222.fileuploader.html
Normal file
231
docs/classes/reflection-830.reflection-222.fileuploader.html
Normal file
File diff suppressed because one or more lines are too long
312
docs/classes/reflection-830.reflection-222.sas9apiclient.html
Normal file
312
docs/classes/reflection-830.reflection-222.sas9apiclient.html
Normal file
File diff suppressed because one or more lines are too long
1721
docs/classes/reflection-830.reflection-222.sasjs.html
Normal file
1721
docs/classes/reflection-830.reflection-222.sasjs.html
Normal file
File diff suppressed because one or more lines are too long
1528
docs/classes/reflection-830.reflection-222.sasviyaapiclient.html
Normal file
1528
docs/classes/reflection-830.reflection-222.sasviyaapiclient.html
Normal file
File diff suppressed because one or more lines are too long
360
docs/classes/reflection-830.reflection-222.sessionmanager.html
Normal file
360
docs/classes/reflection-830.reflection-222.sessionmanager.html
Normal file
File diff suppressed because one or more lines are too long
551
docs/classes/request.requestclient.html
Normal file
551
docs/classes/request.requestclient.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
328
docs/classes/types.authorizeerror.html
Normal file
328
docs/classes/types.authorizeerror.html
Normal file
File diff suppressed because one or more lines are too long
346
docs/classes/types.computejobexecutionerror.html
Normal file
346
docs/classes/types.computejobexecutionerror.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
367
docs/classes/types.jobexecutionerror.html
Normal file
367
docs/classes/types.jobexecutionerror.html
Normal file
File diff suppressed because one or more lines are too long
301
docs/classes/types.loginrequirederror.html
Normal file
301
docs/classes/types.loginrequirederror.html
Normal file
File diff suppressed because one or more lines are too long
325
docs/classes/types.notfounderror.html
Normal file
325
docs/classes/types.notfounderror.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
286
docs/classes/types_errors.authorizeerror.html
Normal file
286
docs/classes/types_errors.authorizeerror.html
Normal file
File diff suppressed because one or more lines are too long
304
docs/classes/types_errors.computejobexecutionerror.html
Normal file
304
docs/classes/types_errors.computejobexecutionerror.html
Normal file
File diff suppressed because one or more lines are too long
209
docs/classes/types_errors.errorresponse.html
Normal file
209
docs/classes/types_errors.errorresponse.html
Normal file
File diff suppressed because one or more lines are too long
259
docs/classes/types_errors.internalservererror.html
Normal file
259
docs/classes/types_errors.internalservererror.html
Normal file
File diff suppressed because one or more lines are too long
325
docs/classes/types_errors.jobexecutionerror.html
Normal file
325
docs/classes/types_errors.jobexecutionerror.html
Normal file
File diff suppressed because one or more lines are too long
259
docs/classes/types_errors.loginrequirederror.html
Normal file
259
docs/classes/types_errors.loginrequirederror.html
Normal file
File diff suppressed because one or more lines are too long
283
docs/classes/types_errors.notfounderror.html
Normal file
283
docs/classes/types_errors.notfounderror.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
136
docs/index.html
136
docs/index.html
File diff suppressed because one or more lines are too long
305
docs/interfaces/job_execution.jobexecutor.html
Normal file
305
docs/interfaces/job_execution.jobexecutor.html
Normal file
File diff suppressed because one or more lines are too long
200
docs/interfaces/job_execution.waitingrequstpromise.html
Normal file
200
docs/interfaces/job_execution.waitingrequstpromise.html
Normal file
File diff suppressed because one or more lines are too long
392
docs/interfaces/request.httpclient.html
Normal file
392
docs/interfaces/request.httpclient.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
209
docs/interfaces/types.logstatistics.html
Normal file
209
docs/interfaces/types.logstatistics.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
94
docs/modules/__mocks__.html
Normal file
94
docs/modules/__mocks__.html
Normal file
File diff suppressed because one or more lines are too long
179
docs/modules/auth.html
Normal file
179
docs/modules/auth.html
Normal file
File diff suppressed because one or more lines are too long
144
docs/modules/auth_spec.html
Normal file
144
docs/modules/auth_spec.html
Normal file
File diff suppressed because one or more lines are too long
184
docs/modules/file.html
Normal file
184
docs/modules/file.html
Normal file
File diff suppressed because one or more lines are too long
175
docs/modules/job_execution.html
Normal file
175
docs/modules/job_execution.html
Normal file
File diff suppressed because one or more lines are too long
106
docs/modules/reflection-817.html
Normal file
106
docs/modules/reflection-817.html
Normal file
File diff suppressed because one or more lines are too long
128
docs/modules/reflection-817.reflection-220.html
Normal file
128
docs/modules/reflection-817.reflection-220.html
Normal file
File diff suppressed because one or more lines are too long
106
docs/modules/reflection-830.html
Normal file
106
docs/modules/reflection-830.html
Normal file
File diff suppressed because one or more lines are too long
128
docs/modules/reflection-830.reflection-222.html
Normal file
128
docs/modules/reflection-830.reflection-222.html
Normal file
File diff suppressed because one or more lines are too long
118
docs/modules/request.html
Normal file
118
docs/modules/request.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
133
docs/modules/types_errors.html
Normal file
133
docs/modules/types_errors.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
188
jest.config.js
188
jest.config.js
@@ -1,10 +1,184 @@
|
|||||||
|
// For a detailed explanation regarding each configuration property, visit:
|
||||||
|
// https://jestjs.io/docs/en/configuration.html
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
roots: ["<rootDir>/src"],
|
testTimeout: 90000,
|
||||||
testMatch: [
|
// All imported modules in your tests should be mocked automatically
|
||||||
"**/__tests__/**/*.+(ts|tsx|js)",
|
// automock: false,
|
||||||
"**/?(*.)+(spec|test).+(ts|tsx|js)"
|
|
||||||
],
|
// Stop running tests after `n` failures
|
||||||
|
// bail: 1,
|
||||||
|
|
||||||
|
// Respect "browser" field in package.json when resolving modules
|
||||||
|
// browser: false,
|
||||||
|
|
||||||
|
// The directory where Jest should store its cached dependency information
|
||||||
|
// cacheDirectory: "/private/var/folders/7y/nmqg1srj29q6210rs9dfsdzc0000gn/T/jest_dx",
|
||||||
|
|
||||||
|
// Automatically clear mock calls and instances between every test
|
||||||
|
clearMocks: true,
|
||||||
|
|
||||||
|
// Indicates whether the coverage information should be collected while executing the test
|
||||||
|
// collectCoverage: false,
|
||||||
|
|
||||||
|
// An array of glob patterns indicating a set of files for which coverage information should be collected
|
||||||
|
// collectCoverageFrom: undefined,
|
||||||
|
|
||||||
|
// The directory where Jest should output its coverage files
|
||||||
|
coverageDirectory: 'coverage',
|
||||||
|
|
||||||
|
// An array of regexp pattern strings used to skip coverage collection
|
||||||
|
// coveragePathIgnorePatterns: [
|
||||||
|
// "/node_modules/"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// A list of reporter names that Jest uses when writing coverage reports
|
||||||
|
// coverageReporters: [
|
||||||
|
// "json",
|
||||||
|
// "text",
|
||||||
|
// "lcov",
|
||||||
|
// "clover"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// An object that configures minimum threshold enforcement for coverage results
|
||||||
|
// coverageThreshold: undefined,
|
||||||
|
|
||||||
|
// A path to a custom dependency extractor
|
||||||
|
// dependencyExtractor: undefined,
|
||||||
|
|
||||||
|
// Make calling deprecated APIs throw helpful error messages
|
||||||
|
// errorOnDeprecated: false,
|
||||||
|
|
||||||
|
// Force coverage collection from ignored files using an array of glob patterns
|
||||||
|
// forceCoverageMatch: [],
|
||||||
|
|
||||||
|
// A path to a module which exports an async function that is triggered once before all test suites
|
||||||
|
// globalSetup: undefined,
|
||||||
|
|
||||||
|
// A path to a module which exports an async function that is triggered once after all test suites
|
||||||
|
// globalTeardown: undefined,
|
||||||
|
|
||||||
|
// A set of global variables that need to be available in all test environments
|
||||||
|
// globals: {},
|
||||||
|
|
||||||
|
// The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers.
|
||||||
|
// maxWorkers: "50%",
|
||||||
|
|
||||||
|
// An array of directory names to be searched recursively up from the requiring module's location
|
||||||
|
// moduleDirectories: [
|
||||||
|
// "node_modules"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// An array of file extensions your modules use
|
||||||
|
// moduleFileExtensions: [
|
||||||
|
// "js",
|
||||||
|
// "json",
|
||||||
|
// "jsx",
|
||||||
|
// "ts",
|
||||||
|
// "tsx",
|
||||||
|
// "node"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// A map from regular expressions to module names that allow to stub out resources with a single module
|
||||||
|
moduleNameMapper: {},
|
||||||
|
|
||||||
|
// An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader
|
||||||
|
// modulePathIgnorePatterns: [],
|
||||||
|
|
||||||
|
// Activates notifications for test results
|
||||||
|
// notify: false,
|
||||||
|
|
||||||
|
// An enum that specifies notification mode. Requires { notify: true }
|
||||||
|
// notifyMode: "failure-change",
|
||||||
|
|
||||||
|
// A preset that is used as a base for Jest's configuration
|
||||||
|
preset: 'ts-jest/presets/js-with-ts',
|
||||||
|
|
||||||
|
// Run tests from one or more projects
|
||||||
|
// projects: undefined,
|
||||||
|
|
||||||
|
// Use this configuration option to add custom reporters to Jest
|
||||||
|
// reporters: undefined,
|
||||||
|
|
||||||
|
// Automatically reset mock state between every test
|
||||||
|
// resetMocks: false,
|
||||||
|
|
||||||
|
// Reset the module registry before running each individual test
|
||||||
|
// resetModules: false,
|
||||||
|
|
||||||
|
// A path to a custom resolver
|
||||||
|
// resolver: undefined,
|
||||||
|
|
||||||
|
// Automatically restore mock state between every test
|
||||||
|
// restoreMocks: false,
|
||||||
|
|
||||||
|
// The root directory that Jest should scan for tests and modules within
|
||||||
|
// rootDir: undefined,
|
||||||
|
|
||||||
|
// A list of paths to directories that Jest should use to search for files in
|
||||||
|
// roots: [
|
||||||
|
// "<rootDir>"
|
||||||
|
// ],
|
||||||
|
|
||||||
|
// Allows you to use a custom runner instead of Jest's default test runner
|
||||||
|
// runner: "jest-runner",
|
||||||
|
|
||||||
|
// The paths to modules that run some code to configure or set up the testing environment before each test
|
||||||
|
setupFiles: [],
|
||||||
|
|
||||||
|
// A list of paths to modules that run some code to configure or set up the testing framework before each test
|
||||||
|
setupFilesAfterEnv: ['jest-extended'],
|
||||||
|
|
||||||
|
// A list of paths to snapshot serializer modules Jest should use for snapshot testing
|
||||||
|
// snapshotSerializers: [],
|
||||||
|
|
||||||
|
// Options that will be passed to the testEnvironment
|
||||||
|
// testEnvironmentOptions: {},
|
||||||
|
|
||||||
|
// Adds a location field to test results
|
||||||
|
// testLocationInResults: false,
|
||||||
|
|
||||||
|
// The glob patterns Jest uses to detect test files
|
||||||
|
testMatch: ['**/*spec.[j|t]s?(x)'],
|
||||||
|
|
||||||
|
// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
|
||||||
|
testPathIgnorePatterns: ['/node_modules/', '/build'],
|
||||||
|
|
||||||
|
// The regexp pattern or array of patterns that Jest uses to detect test files
|
||||||
|
// testRegex: [],
|
||||||
|
|
||||||
|
// This option allows the use of a custom results processor
|
||||||
|
// testResultsProcessor: undefined,
|
||||||
|
|
||||||
|
// This option allows use of a custom test runner
|
||||||
|
// testRunner: "jasmine2",
|
||||||
|
|
||||||
|
// This option sets the URL for the jsdom environment. It is reflected in properties such as location.href
|
||||||
|
// testURL: "http://localhost",
|
||||||
|
|
||||||
|
// Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout"
|
||||||
|
// timers: "real",
|
||||||
|
|
||||||
|
// A map from regular expressions to paths to transformers
|
||||||
transform: {
|
transform: {
|
||||||
"^.+\\.(ts|tsx)$": "ts-jest"
|
'^.+\\.ts?$': 'ts-jest'
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
|
||||||
|
// transformIgnorePatterns: [
|
||||||
|
// '**/test/**/*.ts?(x)',
|
||||||
|
// '**/?(*.)+(spec|test).ts?(x)'
|
||||||
|
// ]
|
||||||
|
|
||||||
|
// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
|
||||||
|
// unmockedModulePathPatterns: undefined,
|
||||||
|
|
||||||
|
// Indicates whether each individual test should be reported during the run
|
||||||
|
// verbose: undefined,
|
||||||
|
|
||||||
|
// An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode
|
||||||
|
// watchPathIgnorePatterns: [],
|
||||||
|
|
||||||
|
// Whether to use watchman for file crawling
|
||||||
|
// watchman: true,
|
||||||
|
}
|
||||||
|
|||||||
9601
package-lock.json
generated
9601
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
@@ -5,9 +5,9 @@
|
|||||||
"build": "rimraf build && rimraf node && mkdir node && cp -r src/* node && webpack && rimraf build/src && rimraf node",
|
"build": "rimraf build && rimraf node && mkdir node && cp -r src/* node && webpack && rimraf build/src && rimraf node",
|
||||||
"package:lib": "npm run build && cp ./package.json build && cd build && npm version \"5.0.0\" && npm pack",
|
"package:lib": "npm run build && cp ./package.json build && cd build && npm version \"5.0.0\" && npm pack",
|
||||||
"publish:lib": "npm run build && cd build && npm publish",
|
"publish:lib": "npm run build && cd build && npm publish",
|
||||||
"lint:fix": "npx prettier --write 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
|
"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}'",
|
"lint": "npx prettier --check 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}' && npx prettier --check 'sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
|
||||||
"test": "jest --coverage",
|
"test": "jest --silent --coverage",
|
||||||
"prepublishOnly": "cp -r ./build/* . && rm -rf ./build",
|
"prepublishOnly": "cp -r ./build/* . && rm -rf ./build",
|
||||||
"postpublish": "git clean -fd",
|
"postpublish": "git clean -fd",
|
||||||
"semantic-release": "semantic-release",
|
"semantic-release": "semantic-release",
|
||||||
@@ -36,31 +36,31 @@
|
|||||||
},
|
},
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/isomorphic-fetch": "0.0.35",
|
"@types/jest": "^26.0.22",
|
||||||
"@types/jest": "^26.0.15",
|
|
||||||
"cp": "^0.2.0",
|
"cp": "^0.2.0",
|
||||||
"dotenv": "^8.2.0",
|
"dotenv": "^8.2.0",
|
||||||
"jest": "^25.5.4",
|
"jest": "^26.6.3",
|
||||||
|
"jest-extended": "^0.11.5",
|
||||||
"path": "^0.12.7",
|
"path": "^0.12.7",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"semantic-release": "^17.3.0",
|
"semantic-release": "^17.4.2",
|
||||||
"terser-webpack-plugin": "^4.2.3",
|
"terser-webpack-plugin": "^4.2.3",
|
||||||
"ts-jest": "^25.5.1",
|
"ts-jest": "^25.5.1",
|
||||||
"ts-loader": "^8.0.11",
|
"ts-loader": "^9.1.2",
|
||||||
"tslint": "^6.1.3",
|
"tslint": "^6.1.3",
|
||||||
"tslint-config-prettier": "^1.18.0",
|
"tslint-config-prettier": "^1.18.0",
|
||||||
"typedoc": "^0.17.8",
|
"typedoc": "^0.20.35",
|
||||||
"typedoc-neo-theme": "^1.0.10",
|
"typedoc-neo-theme": "^1.1.0",
|
||||||
"typedoc-plugin-external-module-name": "^4.0.3",
|
"typedoc-plugin-external-module-name": "^4.0.6",
|
||||||
"typescript": "^3.9.7",
|
"typescript": "^3.9.9",
|
||||||
"webpack": "^4.44.2",
|
"webpack": "^5.33.2",
|
||||||
"webpack-cli": "^4.2.0"
|
"webpack-cli": "^4.7.0"
|
||||||
},
|
},
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/utils": "^1.5.0",
|
"@sasjs/utils": "^2.10.2",
|
||||||
"es6-promise": "^4.2.8",
|
"axios": "^0.21.1",
|
||||||
"form-data": "^3.0.0",
|
"form-data": "^4.0.0",
|
||||||
"isomorphic-fetch": "^2.2.1"
|
"https": "^1.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"trailingComma": "none",
|
"trailingComma": "none",
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"semi": true,
|
"semi": false,
|
||||||
"singleQuote": false
|
"singleQuote": true
|
||||||
}
|
}
|
||||||
|
|||||||
12479
sasjs-tests/package-lock.json
generated
12479
sasjs-tests/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,21 +4,18 @@
|
|||||||
"homepage": ".",
|
"homepage": ".",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sasjs/adapter": "^1.18.2",
|
"@sasjs/adapter": "file:../build/sasjs-adapter-5.0.0.tgz",
|
||||||
"@sasjs/test-framework": "^1.4.0",
|
"@sasjs/test-framework": "^1.4.0",
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
"@types/jest": "^26.0.20",
|
||||||
"@testing-library/react": "^9.5.0",
|
"@types/node": "^14.14.25",
|
||||||
"@testing-library/user-event": "^7.2.1",
|
"@types/react": "^17.0.1",
|
||||||
"@types/jest": "^26.0.3",
|
"@types/react-dom": "^17.0.0",
|
||||||
"@types/node": "^14.0.14",
|
"@types/react-router-dom": "^5.1.7",
|
||||||
"@types/react": "^16.9.41",
|
"react": "^17.0.1",
|
||||||
"@types/react-dom": "^16.9.8",
|
"react-dom": "^17.0.1",
|
||||||
"@types/react-router-dom": "^5.1.5",
|
|
||||||
"react": "^16.13.1",
|
|
||||||
"react-dom": "^16.13.1",
|
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
"react-scripts": "3.4.1",
|
"react-scripts": "^4.0.2",
|
||||||
"typescript": "^3.9.6"
|
"typescript": "^4.1.3"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
@@ -26,8 +23,8 @@
|
|||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"update:adapter": "cd .. && npm run package:lib && cd sasjs-tests && npm i ../build/sasjs-adapter-5.0.0.tgz",
|
"update:adapter": "cd .. && npm run package:lib && cd sasjs-tests && npm i ../build/sasjs-adapter-5.0.0.tgz",
|
||||||
"deploy:tests": "npm run build && rsync -avhe ssh ./build/* --delete $SSH_ACCOUNT:$DEPLOY_PATH",
|
"deploy:tests": "rsync -avhe ssh ./build/* --delete $SSH_ACCOUNT:$DEPLOY_PATH",
|
||||||
"deploy": "npm run update:adapter && npm run deploy:tests"
|
"deploy": "npm run update:adapter && npm run build && npm run deploy:tests"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": "react-app"
|
"extends": "react-app"
|
||||||
@@ -45,6 +42,6 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"node-sass": "^4.14.1"
|
"node-sass": "^5.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
"appLoc": "/Public/app",
|
"appLoc": "/Public/app",
|
||||||
"serverType": "SASVIYA",
|
"serverType": "SASVIYA",
|
||||||
"debug": false,
|
"debug": false,
|
||||||
"contextName": "SharedCompute",
|
"contextName": "sasjs adapter compute context",
|
||||||
"useComputeApi": true
|
"useComputeApi": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import React, { ReactElement, useState, useContext, useEffect } from "react";
|
import React, { ReactElement, useState, useContext, useEffect } from 'react'
|
||||||
import { TestSuiteRunner, TestSuite, AppContext } from "@sasjs/test-framework";
|
import { TestSuiteRunner, TestSuite, AppContext } from '@sasjs/test-framework'
|
||||||
import { basicTests } from "./testSuites/Basic";
|
import { basicTests } from './testSuites/Basic'
|
||||||
import { sendArrTests, sendObjTests } from "./testSuites/RequestData";
|
import { sendArrTests, sendObjTests } from './testSuites/RequestData'
|
||||||
import { specialCaseTests } from "./testSuites/SpecialCases";
|
import { specialCaseTests } from './testSuites/SpecialCases'
|
||||||
import { sasjsRequestTests } from "./testSuites/SasjsRequests";
|
import { sasjsRequestTests } from './testSuites/SasjsRequests'
|
||||||
import "@sasjs/test-framework/dist/index.css";
|
import '@sasjs/test-framework/dist/index.css'
|
||||||
import { computeTests } from "./testSuites/Compute";
|
import { computeTests } from './testSuites/Compute'
|
||||||
|
|
||||||
const App = (): ReactElement<{}> => {
|
const App = (): ReactElement<{}> => {
|
||||||
const { adapter, config } = useContext(AppContext);
|
const { adapter, config } = useContext(AppContext)
|
||||||
const [testSuites, setTestSuites] = useState<TestSuite[]>([]);
|
const [testSuites, setTestSuites] = useState<TestSuite[]>([])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
@@ -20,15 +20,15 @@ const App = (): ReactElement<{}> => {
|
|||||||
specialCaseTests(adapter),
|
specialCaseTests(adapter),
|
||||||
sasjsRequestTests(adapter),
|
sasjsRequestTests(adapter),
|
||||||
computeTests(adapter)
|
computeTests(adapter)
|
||||||
]);
|
])
|
||||||
}
|
}
|
||||||
}, [adapter, config]);
|
}, [adapter, config])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
{adapter && testSuites && <TestSuiteRunner testSuites={testSuites} />}
|
{adapter && testSuites && <TestSuiteRunner testSuites={testSuites} />}
|
||||||
</div>
|
</div>
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default App;
|
export default App
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
import React, { ReactElement, useState, useCallback, useContext } from "react";
|
import React, { ReactElement, useState, useCallback, useContext } from 'react'
|
||||||
import "./Login.scss";
|
import './Login.scss'
|
||||||
import { AppContext } from "@sasjs/test-framework";
|
import { AppContext } from '@sasjs/test-framework'
|
||||||
import { Redirect } from "react-router-dom";
|
import { Redirect } from 'react-router-dom'
|
||||||
|
|
||||||
const Login = (): ReactElement<{}> => {
|
const Login = (): ReactElement<{}> => {
|
||||||
const [username, setUsername] = useState("");
|
const [username, setUsername] = useState('')
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState('')
|
||||||
const appContext = useContext(AppContext);
|
const appContext = useContext(AppContext)
|
||||||
|
|
||||||
const handleSubmit = useCallback(
|
const handleSubmit = useCallback(
|
||||||
(e) => {
|
(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault()
|
||||||
appContext.adapter.logIn(username, password).then((res) => {
|
appContext.adapter.logIn(username, password).then((res) => {
|
||||||
appContext.setIsLoggedIn(res.isLoggedIn);
|
appContext.setIsLoggedIn(res.isLoggedIn)
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
[username, password, appContext]
|
[username, password, appContext]
|
||||||
);
|
)
|
||||||
|
|
||||||
return !appContext.isLoggedIn ? (
|
return !appContext.isLoggedIn ? (
|
||||||
<div className="login-container">
|
<div className="login-container">
|
||||||
@@ -48,7 +48,7 @@ const Login = (): ReactElement<{}> => {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Redirect to="/" />
|
<Redirect to="/" />
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default Login;
|
export default Login
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
import React, { ReactElement, useContext, FunctionComponent } from "react";
|
import React, { ReactElement, useContext, FunctionComponent } from 'react'
|
||||||
import { Redirect, Route } from "react-router-dom";
|
import { Redirect, Route } from 'react-router-dom'
|
||||||
import { AppContext } from "@sasjs/test-framework";
|
import { AppContext } from '@sasjs/test-framework'
|
||||||
|
|
||||||
interface PrivateRouteProps {
|
interface PrivateRouteProps {
|
||||||
component: FunctionComponent;
|
component: FunctionComponent
|
||||||
exact?: boolean;
|
exact?: boolean
|
||||||
path: string;
|
path: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const PrivateRoute = (
|
const PrivateRoute = (
|
||||||
props: PrivateRouteProps
|
props: PrivateRouteProps
|
||||||
): ReactElement<PrivateRouteProps> => {
|
): ReactElement<PrivateRouteProps> => {
|
||||||
const { component, path, exact } = props;
|
const { component, path, exact } = props
|
||||||
const appContext = useContext(AppContext);
|
const appContext = useContext(AppContext)
|
||||||
return appContext.isLoggedIn ? (
|
return appContext.isLoggedIn ? (
|
||||||
<Route component={component} path={path} exact={exact} />
|
<Route component={component} path={path} exact={exact} />
|
||||||
) : (
|
) : (
|
||||||
<Redirect to="/login" />
|
<Redirect to="/login" />
|
||||||
);
|
)
|
||||||
};
|
}
|
||||||
|
|
||||||
export default PrivateRoute;
|
export default PrivateRoute
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import React from "react";
|
import React from 'react'
|
||||||
import ReactDOM from "react-dom";
|
import ReactDOM from 'react-dom'
|
||||||
import { Route, HashRouter, Switch } from "react-router-dom";
|
import { Route, HashRouter, Switch } from 'react-router-dom'
|
||||||
import "./index.scss";
|
import './index.scss'
|
||||||
import * as serviceWorker from "./serviceWorker";
|
import * as serviceWorker from './serviceWorker'
|
||||||
import { AppProvider } from "@sasjs/test-framework";
|
import { AppProvider } from '@sasjs/test-framework'
|
||||||
import PrivateRoute from "./PrivateRoute";
|
import PrivateRoute from './PrivateRoute'
|
||||||
import Login from "./Login";
|
import Login from './Login'
|
||||||
import App from "./App";
|
import App from './App'
|
||||||
|
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
<AppProvider>
|
<AppProvider>
|
||||||
@@ -17,10 +17,10 @@ ReactDOM.render(
|
|||||||
</Switch>
|
</Switch>
|
||||||
</HashRouter>
|
</HashRouter>
|
||||||
</AppProvider>,
|
</AppProvider>,
|
||||||
document.getElementById("root")
|
document.getElementById('root')
|
||||||
);
|
)
|
||||||
|
|
||||||
// If you want your app to work offline and load faster, you can change
|
// If you want your app to work offline and load faster, you can change
|
||||||
// unregister() to register() below. Note this comes with some pitfalls.
|
// unregister() to register() below. Note this comes with some pitfalls.
|
||||||
// Learn more about service workers: https://bit.ly/CRA-PWA
|
// Learn more about service workers: https://bit.ly/CRA-PWA
|
||||||
serviceWorker.unregister();
|
serviceWorker.unregister()
|
||||||
|
|||||||
@@ -11,46 +11,46 @@
|
|||||||
// opt-in, read https://bit.ly/CRA-PWA
|
// opt-in, read https://bit.ly/CRA-PWA
|
||||||
|
|
||||||
const isLocalhost = Boolean(
|
const isLocalhost = Boolean(
|
||||||
window.location.hostname === "localhost" ||
|
window.location.hostname === 'localhost' ||
|
||||||
// [::1] is the IPv6 localhost address.
|
// [::1] is the IPv6 localhost address.
|
||||||
window.location.hostname === "[::1]" ||
|
window.location.hostname === '[::1]' ||
|
||||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||||
window.location.hostname.match(
|
window.location.hostname.match(
|
||||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||||
)
|
)
|
||||||
);
|
)
|
||||||
|
|
||||||
export function register(config) {
|
export function register(config) {
|
||||||
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
|
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
|
||||||
// The URL constructor is available in all browsers that support SW.
|
// The URL constructor is available in all browsers that support SW.
|
||||||
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
|
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
|
||||||
if (publicUrl.origin !== window.location.origin) {
|
if (publicUrl.origin !== window.location.origin) {
|
||||||
// Our service worker won't work if PUBLIC_URL is on a different origin
|
// Our service worker won't work if PUBLIC_URL is on a different origin
|
||||||
// from what our page is served on. This might happen if a CDN is used to
|
// from what our page is served on. This might happen if a CDN is used to
|
||||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener("load", () => {
|
window.addEventListener('load', () => {
|
||||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
|
||||||
|
|
||||||
if (isLocalhost) {
|
if (isLocalhost) {
|
||||||
// This is running on localhost. Let's check if a service worker still exists or not.
|
// This is running on localhost. Let's check if a service worker still exists or not.
|
||||||
checkValidServiceWorker(swUrl, config);
|
checkValidServiceWorker(swUrl, config)
|
||||||
|
|
||||||
// Add some additional logging to localhost, pointing developers to the
|
// Add some additional logging to localhost, pointing developers to the
|
||||||
// service worker/PWA documentation.
|
// service worker/PWA documentation.
|
||||||
navigator.serviceWorker.ready.then(() => {
|
navigator.serviceWorker.ready.then(() => {
|
||||||
console.log(
|
console.log(
|
||||||
"This web app is being served cache-first by a service " +
|
'This web app is being served cache-first by a service ' +
|
||||||
"worker. To learn more, visit https://bit.ly/CRA-PWA"
|
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
// Is not localhost. Just register service worker
|
// Is not localhost. Just register service worker
|
||||||
registerValidSW(swUrl, config);
|
registerValidSW(swUrl, config)
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,83 +59,83 @@ function registerValidSW(swUrl, config) {
|
|||||||
.register(swUrl)
|
.register(swUrl)
|
||||||
.then((registration) => {
|
.then((registration) => {
|
||||||
registration.onupdatefound = () => {
|
registration.onupdatefound = () => {
|
||||||
const installingWorker = registration.installing;
|
const installingWorker = registration.installing
|
||||||
if (installingWorker == null) {
|
if (installingWorker == null) {
|
||||||
return;
|
return
|
||||||
}
|
}
|
||||||
installingWorker.onstatechange = () => {
|
installingWorker.onstatechange = () => {
|
||||||
if (installingWorker.state === "installed") {
|
if (installingWorker.state === 'installed') {
|
||||||
if (navigator.serviceWorker.controller) {
|
if (navigator.serviceWorker.controller) {
|
||||||
// At this point, the updated precached content has been fetched,
|
// At this point, the updated precached content has been fetched,
|
||||||
// but the previous service worker will still serve the older
|
// but the previous service worker will still serve the older
|
||||||
// content until all client tabs are closed.
|
// content until all client tabs are closed.
|
||||||
console.log(
|
console.log(
|
||||||
"New content is available and will be used when all " +
|
'New content is available and will be used when all ' +
|
||||||
"tabs for this page are closed. See https://bit.ly/CRA-PWA."
|
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||||
);
|
)
|
||||||
|
|
||||||
// Execute callback
|
// Execute callback
|
||||||
if (config && config.onUpdate) {
|
if (config && config.onUpdate) {
|
||||||
config.onUpdate(registration);
|
config.onUpdate(registration)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// At this point, everything has been precached.
|
// At this point, everything has been precached.
|
||||||
// It's the perfect time to display a
|
// It's the perfect time to display a
|
||||||
// "Content is cached for offline use." message.
|
// "Content is cached for offline use." message.
|
||||||
console.log("Content is cached for offline use.");
|
console.log('Content is cached for offline use.')
|
||||||
|
|
||||||
// Execute callback
|
// Execute callback
|
||||||
if (config && config.onSuccess) {
|
if (config && config.onSuccess) {
|
||||||
config.onSuccess(registration);
|
config.onSuccess(registration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error("Error during service worker registration:", error);
|
console.error('Error during service worker registration:', error)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkValidServiceWorker(swUrl, config) {
|
function checkValidServiceWorker(swUrl, config) {
|
||||||
// Check if the service worker can be found. If it can't reload the page.
|
// Check if the service worker can be found. If it can't reload the page.
|
||||||
fetch(swUrl, {
|
fetch(swUrl, {
|
||||||
headers: { "Service-Worker": "script" }
|
headers: { 'Service-Worker': 'script' }
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
// Ensure service worker exists, and that we really are getting a JS file.
|
// Ensure service worker exists, and that we really are getting a JS file.
|
||||||
const contentType = response.headers.get("content-type");
|
const contentType = response.headers.get('content-type')
|
||||||
if (
|
if (
|
||||||
response.status === 404 ||
|
response.status === 404 ||
|
||||||
(contentType != null && contentType.indexOf("javascript") === -1)
|
(contentType != null && contentType.indexOf('javascript') === -1)
|
||||||
) {
|
) {
|
||||||
// No service worker found. Probably a different app. Reload the page.
|
// No service worker found. Probably a different app. Reload the page.
|
||||||
navigator.serviceWorker.ready.then((registration) => {
|
navigator.serviceWorker.ready.then((registration) => {
|
||||||
registration.unregister().then(() => {
|
registration.unregister().then(() => {
|
||||||
window.location.reload();
|
window.location.reload()
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
} else {
|
} else {
|
||||||
// Service worker found. Proceed as normal.
|
// Service worker found. Proceed as normal.
|
||||||
registerValidSW(swUrl, config);
|
registerValidSW(swUrl, config)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
console.log(
|
console.log(
|
||||||
"No internet connection found. App is running in offline mode."
|
'No internet connection found. App is running in offline mode.'
|
||||||
);
|
)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unregister() {
|
export function unregister() {
|
||||||
if ("serviceWorker" in navigator) {
|
if ('serviceWorker' in navigator) {
|
||||||
navigator.serviceWorker.ready
|
navigator.serviceWorker.ready
|
||||||
.then((registration) => {
|
.then((registration) => {
|
||||||
registration.unregister();
|
registration.unregister()
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error(error.message);
|
console.error(error.message)
|
||||||
});
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,4 @@
|
|||||||
// allows you to do things like:
|
// allows you to do things like:
|
||||||
// expect(element).toHaveTextContent(/react/i)
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
import "@testing-library/jest-dom/extend-expect";
|
import '@testing-library/jest-dom/extend-expect'
|
||||||
|
|||||||
@@ -1,45 +1,50 @@
|
|||||||
import SASjs, { ServerType, SASjsConfig } from "@sasjs/adapter";
|
import SASjs, { SASjsConfig } from '@sasjs/adapter'
|
||||||
import { TestSuite } from "@sasjs/test-framework";
|
import { TestSuite } from '@sasjs/test-framework'
|
||||||
|
import { ServerType } from '@sasjs/utils/types'
|
||||||
|
|
||||||
|
const stringData: any = { table1: [{ col1: 'first col value' }] }
|
||||||
|
|
||||||
const defaultConfig: SASjsConfig = {
|
const defaultConfig: SASjsConfig = {
|
||||||
serverUrl: window.location.origin,
|
serverUrl: window.location.origin,
|
||||||
pathSAS9: '/SASStoredProcess/do',
|
pathSAS9: '/SASStoredProcess/do',
|
||||||
pathSASViya: '/SASJobExecution',
|
pathSASViya: '/SASJobExecution',
|
||||||
appLoc: '/Public/seedapp',
|
appLoc: '/Public/seedapp',
|
||||||
serverType: ServerType.SASViya,
|
serverType: ServerType.SasViya,
|
||||||
debug: false,
|
debug: false,
|
||||||
contextName: 'SAS Job Execution compute context',
|
contextName: 'SAS Job Execution compute context',
|
||||||
useComputeApi: false
|
useComputeApi: false,
|
||||||
};
|
allowInsecureRequests: false
|
||||||
|
}
|
||||||
|
|
||||||
const customConfig = {
|
const customConfig = {
|
||||||
serverUrl: "http://url.com",
|
serverUrl: 'http://url.com',
|
||||||
pathSAS9: "sas9",
|
pathSAS9: 'sas9',
|
||||||
pathSASViya: "viya",
|
pathSASViya: 'viya',
|
||||||
appLoc: "/Public/seedapp",
|
appLoc: '/Public/seedapp',
|
||||||
serverType: ServerType.SAS9,
|
serverType: ServerType.Sas9,
|
||||||
debug: false
|
debug: false
|
||||||
};
|
}
|
||||||
|
|
||||||
export const basicTests = (
|
export const basicTests = (
|
||||||
adapter: SASjs,
|
adapter: SASjs,
|
||||||
userName: string,
|
userName: string,
|
||||||
password: string
|
password: string
|
||||||
): TestSuite => ({
|
): TestSuite => ({
|
||||||
name: "Basic Tests",
|
name: 'Basic Tests',
|
||||||
tests: [
|
tests: [
|
||||||
{
|
{
|
||||||
title: "Log in",
|
title: 'Log in',
|
||||||
description: "Should log the user in",
|
description: 'Should log the user in',
|
||||||
test: async () => {
|
test: async () => {
|
||||||
return adapter.logIn(userName, password);
|
return adapter.logIn(userName, password)
|
||||||
},
|
},
|
||||||
assertion: (response: any) =>
|
assertion: (response: any) =>
|
||||||
response && response.isLoggedIn && response.userName === userName
|
response && response.isLoggedIn && response.userName === userName
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Multiple Log in attempts",
|
title: 'Multiple Log in attempts',
|
||||||
description: "Should fail on first attempt and should log the user in on second attempt",
|
description:
|
||||||
|
'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 adapter.logIn('invalid', 'invalid')
|
await adapter.logIn('invalid', 'invalid')
|
||||||
@@ -49,14 +54,49 @@ export const basicTests = (
|
|||||||
response && response.isLoggedIn && response.userName === userName
|
response && response.isLoggedIn && response.userName === userName
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Default config",
|
title: 'Trigger login callback',
|
||||||
description:
|
description:
|
||||||
"Should instantiate with default config when none is provided",
|
'Should trigger required login callback and after successful login, it should finish the request',
|
||||||
test: async () => {
|
test: async () => {
|
||||||
return Promise.resolve(new SASjs());
|
await adapter.logOut()
|
||||||
|
|
||||||
|
return await adapter.request(
|
||||||
|
'common/sendArr',
|
||||||
|
stringData,
|
||||||
|
undefined,
|
||||||
|
() => {
|
||||||
|
adapter.logIn(userName, password)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
assertion: (response: any) => {
|
||||||
|
return response.table1[0][0] === stringData.table1[0].col1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Request with debug on',
|
||||||
|
description:
|
||||||
|
'Should complete successful request with debugging switched on',
|
||||||
|
test: async () => {
|
||||||
|
const config = {
|
||||||
|
debug: true
|
||||||
|
}
|
||||||
|
|
||||||
|
return await adapter.request('common/sendArr', stringData, config)
|
||||||
|
},
|
||||||
|
assertion: (response: any) => {
|
||||||
|
return response.table1[0][0] === stringData.table1[0].col1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Default config',
|
||||||
|
description:
|
||||||
|
'Should instantiate with default config when none is provided',
|
||||||
|
test: async () => {
|
||||||
|
return Promise.resolve(new SASjs())
|
||||||
},
|
},
|
||||||
assertion: (sasjsInstance: SASjs) => {
|
assertion: (sasjsInstance: SASjs) => {
|
||||||
const sasjsConfig = sasjsInstance.getSasjsConfig();
|
const sasjsConfig = sasjsInstance.getSasjsConfig()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
sasjsConfig.serverUrl === defaultConfig.serverUrl &&
|
sasjsConfig.serverUrl === defaultConfig.serverUrl &&
|
||||||
@@ -65,17 +105,17 @@ export const basicTests = (
|
|||||||
sasjsConfig.appLoc === defaultConfig.appLoc &&
|
sasjsConfig.appLoc === defaultConfig.appLoc &&
|
||||||
sasjsConfig.serverType === defaultConfig.serverType &&
|
sasjsConfig.serverType === defaultConfig.serverType &&
|
||||||
sasjsConfig.debug === defaultConfig.debug
|
sasjsConfig.debug === defaultConfig.debug
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Custom config",
|
title: 'Custom config',
|
||||||
description: "Should use fully custom config whenever supplied",
|
description: 'Should use fully custom config whenever supplied',
|
||||||
test: async () => {
|
test: async () => {
|
||||||
return Promise.resolve(new SASjs(customConfig));
|
return Promise.resolve(new SASjs(customConfig))
|
||||||
},
|
},
|
||||||
assertion: (sasjsInstance: SASjs) => {
|
assertion: (sasjsInstance: SASjs) => {
|
||||||
const sasjsConfig = sasjsInstance.getSasjsConfig();
|
const sasjsConfig = sasjsInstance.getSasjsConfig()
|
||||||
return (
|
return (
|
||||||
sasjsConfig.serverUrl === customConfig.serverUrl &&
|
sasjsConfig.serverUrl === customConfig.serverUrl &&
|
||||||
sasjsConfig.pathSAS9 === customConfig.pathSAS9 &&
|
sasjsConfig.pathSAS9 === customConfig.pathSAS9 &&
|
||||||
@@ -83,28 +123,28 @@ export const basicTests = (
|
|||||||
sasjsConfig.appLoc === customConfig.appLoc &&
|
sasjsConfig.appLoc === customConfig.appLoc &&
|
||||||
sasjsConfig.serverType === customConfig.serverType &&
|
sasjsConfig.serverType === customConfig.serverType &&
|
||||||
sasjsConfig.debug === customConfig.debug
|
sasjsConfig.debug === customConfig.debug
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Config overrides",
|
title: 'Config overrides',
|
||||||
description: "Should override default config with supplied properties",
|
description: 'Should override default config with supplied properties',
|
||||||
test: async () => {
|
test: async () => {
|
||||||
return Promise.resolve(
|
return Promise.resolve(
|
||||||
new SASjs({ serverUrl: "http://test.com", debug: false })
|
new SASjs({ serverUrl: 'http://test.com', debug: false })
|
||||||
);
|
)
|
||||||
},
|
},
|
||||||
assertion: (sasjsInstance: SASjs) => {
|
assertion: (sasjsInstance: SASjs) => {
|
||||||
const sasjsConfig = sasjsInstance.getSasjsConfig();
|
const sasjsConfig = sasjsInstance.getSasjsConfig()
|
||||||
return (
|
return (
|
||||||
sasjsConfig.serverUrl === "http://test.com" &&
|
sasjsConfig.serverUrl === 'http://test.com' &&
|
||||||
sasjsConfig.pathSAS9 === defaultConfig.pathSAS9 &&
|
sasjsConfig.pathSAS9 === defaultConfig.pathSAS9 &&
|
||||||
sasjsConfig.pathSASViya === defaultConfig.pathSASViya &&
|
sasjsConfig.pathSASViya === defaultConfig.pathSASViya &&
|
||||||
sasjsConfig.appLoc === defaultConfig.appLoc &&
|
sasjsConfig.appLoc === defaultConfig.appLoc &&
|
||||||
sasjsConfig.serverType === defaultConfig.serverType &&
|
sasjsConfig.serverType === defaultConfig.serverType &&
|
||||||
sasjsConfig.debug === false
|
sasjsConfig.debug === false
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,45 +1,50 @@
|
|||||||
import SASjs from "@sasjs/adapter";
|
import SASjs from '@sasjs/adapter'
|
||||||
import { TestSuite } from "@sasjs/test-framework";
|
import { TestSuite } from '@sasjs/test-framework'
|
||||||
|
|
||||||
export const computeTests = (adapter: SASjs): TestSuite => ({
|
export const computeTests = (adapter: SASjs): TestSuite => ({
|
||||||
name: "Compute",
|
name: 'Compute',
|
||||||
tests: [
|
tests: [
|
||||||
{
|
{
|
||||||
title: "Start Compute Job - not waiting for result",
|
title: 'Start Compute Job - not waiting for result',
|
||||||
description: "Should start a compute job and return the session",
|
description: 'Should start a compute job and return the session',
|
||||||
test: () => {
|
test: () => {
|
||||||
const data: any = { table1: [{ col1: "first col value" }] };
|
const data: any = { table1: [{ col1: 'first col value' }] }
|
||||||
return adapter.startComputeJob("/Public/app/common/sendArr", data);
|
return adapter.startComputeJob('/Public/app/common/sendArr', data)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
const expectedProperties = ["id", "applicationName", "attributes"]
|
const expectedProperties = ['id', 'applicationName', 'attributes']
|
||||||
return validate(expectedProperties, res);
|
return validate(expectedProperties, res)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Start Compute Job - waiting for result",
|
title: 'Start Compute Job - waiting for result',
|
||||||
description: "Should start a compute job and return the job",
|
description: 'Should start a compute job and return the job',
|
||||||
test: () => {
|
test: () => {
|
||||||
const data: any = { table1: [{ col1: "first col value" }] };
|
const data: any = { table1: [{ col1: 'first col value' }] }
|
||||||
return adapter.startComputeJob("/Public/app/common/sendArr", data, {}, "", true);
|
return adapter.startComputeJob(
|
||||||
|
'/Public/app/common/sendArr',
|
||||||
|
data,
|
||||||
|
{},
|
||||||
|
'',
|
||||||
|
true
|
||||||
|
)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
const expectedProperties = ["id", "state", "creationTimeStamp", "jobConditionCode"]
|
const expectedProperties = [
|
||||||
return validate(expectedProperties, res.result);
|
'id',
|
||||||
}
|
'state',
|
||||||
},
|
'creationTimeStamp',
|
||||||
{
|
'jobConditionCode'
|
||||||
title: "Execute Script Viya - complete job",
|
|
||||||
description: "Should execute sas file and return log",
|
|
||||||
test: () => {
|
|
||||||
const fileLines = [
|
|
||||||
`data;`,
|
|
||||||
`do x=1 to 100;`,
|
|
||||||
`output;`,
|
|
||||||
`end;`,
|
|
||||||
`run;`
|
|
||||||
]
|
]
|
||||||
|
return validate(expectedProperties, res.job)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Execute Script Viya - complete job',
|
||||||
|
description: 'Should execute sas file and return log',
|
||||||
|
test: () => {
|
||||||
|
const fileLines = [`data;`, `do x=1 to 100;`, `output;`, `end;`, `run;`]
|
||||||
|
|
||||||
return adapter.executeScriptSASViya(
|
return adapter.executeScriptSASViya(
|
||||||
'sasCode.sas',
|
'sasCode.sas',
|
||||||
fileLines,
|
fileLines,
|
||||||
@@ -50,34 +55,34 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
|
|||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
const expectedLogContent = `1 data;\\n2 do x=1 to 100;\\n3 output;\\n4 end;\\n5 run;\\n\\n`
|
const expectedLogContent = `1 data;\\n2 do x=1 to 100;\\n3 output;\\n4 end;\\n5 run;\\n\\n`
|
||||||
|
|
||||||
return validateLog(expectedLogContent, res.log);
|
return validateLog(expectedLogContent, res.log)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Execute Script Viya - failed job",
|
title: 'Execute Script Viya - failed job',
|
||||||
description: "Should execute sas file and return log",
|
description: 'Should execute sas file and return log',
|
||||||
test: () => {
|
test: () => {
|
||||||
const fileLines = [
|
const fileLines = [`%abort;`]
|
||||||
`%abort;`
|
|
||||||
]
|
return adapter
|
||||||
|
.executeScriptSASViya(
|
||||||
return adapter.executeScriptSASViya(
|
'sasCode.sas',
|
||||||
'sasCode.sas',
|
fileLines,
|
||||||
fileLines,
|
'SAS Studio compute context',
|
||||||
'SAS Studio compute context',
|
undefined,
|
||||||
undefined,
|
true
|
||||||
true
|
)
|
||||||
).catch((err: any) => err )
|
.catch((err: any) => err)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
const expectedLogContent = `1 %abort;\\nERROR: The %ABORT statement is not valid in open code.\\n`
|
const expectedLogContent = `1 %abort;\\nERROR: The %ABORT statement is not valid in open code.\\n`
|
||||||
|
|
||||||
return validateLog(expectedLogContent, res.log);
|
return validateLog(expectedLogContent, res.log)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
})
|
||||||
|
|
||||||
const validateLog = (text: string, log: string): boolean => {
|
const validateLog = (text: string, log: string): boolean => {
|
||||||
const isValid = JSON.stringify(log).includes(text)
|
const isValid = JSON.stringify(log).includes(text)
|
||||||
@@ -86,10 +91,10 @@ const validateLog = (text: string, log: string): boolean => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const validate = (expectedProperties: string[], data: any): boolean => {
|
const validate = (expectedProperties: string[], data: any): boolean => {
|
||||||
const actualProperties = Object.keys(data);
|
const actualProperties = Object.keys(data)
|
||||||
|
|
||||||
const isValid = expectedProperties.every(
|
const isValid = expectedProperties.every((property) =>
|
||||||
(property) => actualProperties.includes(property)
|
actualProperties.includes(property)
|
||||||
);
|
)
|
||||||
return isValid
|
return isValid
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,111 +1,112 @@
|
|||||||
import SASjs from "@sasjs/adapter";
|
import SASjs from '@sasjs/adapter'
|
||||||
import { TestSuite } from "@sasjs/test-framework";
|
import { TestSuite } from '@sasjs/test-framework'
|
||||||
|
|
||||||
const stringData: any = { table1: [{ col1: "first col value" }] };
|
const stringData: any = { table1: [{ col1: 'first col value' }] }
|
||||||
const numericData: any = { table1: [{ col1: 3.14159265 }] };
|
const numericData: any = { table1: [{ col1: 3.14159265 }] }
|
||||||
const multiColumnData: any = {
|
const multiColumnData: any = {
|
||||||
table1: [{ col1: 42, col2: 1.618, col3: "x", col4: "x" }]
|
table1: [{ col1: 42, col2: 1.618, col3: 'x', col4: 'x' }]
|
||||||
};
|
}
|
||||||
const multipleRowsWithNulls: any = {
|
const multipleRowsWithNulls: any = {
|
||||||
table1: [
|
table1: [
|
||||||
{ col1: 42, col2: null, col3: "x", col4: "" },
|
{ col1: 42, col2: null, col3: 'x', col4: '' },
|
||||||
{ col1: 42, col2: null, col3: "x", col4: "" },
|
{ col1: 42, col2: null, col3: 'x', col4: '' },
|
||||||
{ col1: 42, col2: null, col3: "x", col4: "" },
|
{ col1: 42, col2: null, col3: 'x', col4: '' },
|
||||||
{ col1: 42, col2: 1.62, col3: "x", col4: "x" },
|
{ col1: 42, col2: 1.62, col3: 'x', col4: 'x' },
|
||||||
{ col1: 42, col2: 1.62, col3: "x", col4: "x" }
|
{ col1: 42, col2: 1.62, col3: 'x', col4: 'x' }
|
||||||
]
|
]
|
||||||
};
|
}
|
||||||
|
|
||||||
const multipleColumnsWithNulls: any = {
|
const multipleColumnsWithNulls: any = {
|
||||||
table1: [
|
table1: [
|
||||||
{ col1: 42, col2: null, col3: "x", col4: null },
|
{ col1: 42, col2: null, col3: 'x', col4: null },
|
||||||
{ col1: 42, col2: null, col3: "x", col4: null },
|
{ col1: 42, col2: null, col3: 'x', col4: null },
|
||||||
{ col1: 42, col2: null, col3: "x", col4: null },
|
{ col1: 42, col2: null, col3: 'x', col4: null },
|
||||||
{ col1: 42, col2: null, col3: "x", col4: "" },
|
{ col1: 42, col2: null, col3: 'x', col4: '' },
|
||||||
{ col1: 42, col2: null, col3: "x", col4: "" }
|
{ col1: 42, col2: null, col3: 'x', col4: '' }
|
||||||
]
|
]
|
||||||
};
|
}
|
||||||
|
|
||||||
const getLongStringData = (length = 32764) => {
|
const getLongStringData = (length = 32764) => {
|
||||||
let x = "X";
|
let x = 'X'
|
||||||
for (let i = 1; i <= length; i++) {
|
for (let i = 1; i <= length; i++) {
|
||||||
x = x + "X";
|
x = x + 'X'
|
||||||
}
|
}
|
||||||
const data: any = { table1: [{ col1: x }] };
|
const data: any = { table1: [{ col1: x }] }
|
||||||
return data;
|
return data
|
||||||
};
|
}
|
||||||
|
|
||||||
const getLargeObjectData = () => {
|
const getLargeObjectData = () => {
|
||||||
const data = { table1: [{ big: "data" }] };
|
const data = { table1: [{ big: 'data' }] }
|
||||||
|
|
||||||
for (let i = 1; i < 10000; i++) {
|
for (let i = 1; i < 10000; i++) {
|
||||||
data.table1.push(data.table1[0]);
|
data.table1.push(data.table1[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data
|
||||||
};
|
}
|
||||||
|
|
||||||
export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
||||||
name: "sendArr",
|
name: 'sendArr',
|
||||||
tests: [
|
tests: [
|
||||||
{
|
{
|
||||||
title: "Absolute paths",
|
title: 'Absolute paths',
|
||||||
description: "Should work with absolute paths to SAS jobs",
|
description: 'Should work with absolute paths to SAS jobs',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("/Public/app/common/sendArr", stringData);
|
return adapter.request('/Public/app/common/sendArr', stringData)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
return res.table1[0][0] === stringData.table1[0].col1;
|
return res.table1[0][0] === stringData.table1[0].col1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Single string value",
|
title: 'Single string value',
|
||||||
description: "Should send an array with a single string value",
|
description: 'Should send an array with a single string value',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendArr", stringData);
|
return adapter.request('common/sendArr', stringData)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
return res.table1[0][0] === stringData.table1[0].col1;
|
return res.table1[0][0] === stringData.table1[0].col1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Long string value",
|
title: 'Long string value',
|
||||||
description:
|
description:
|
||||||
"Should send an array with a long string value under 32765 characters",
|
'Should send an array with a long string value under 32765 characters',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendArr", getLongStringData());
|
return adapter.request('common/sendArr', getLongStringData())
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
const longStringData = getLongStringData();
|
const longStringData = getLongStringData()
|
||||||
return res.table1[0][0] === longStringData.table1[0].col1;
|
return res.table1[0][0] === longStringData.table1[0].col1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Overly long string value",
|
title: 'Overly long string value',
|
||||||
description:
|
description:
|
||||||
"Should error out with long string values over 32765 characters",
|
'Should error out with long string values over 32765 characters',
|
||||||
test: () => {
|
test: () => {
|
||||||
const data = getLongStringData(32767);
|
const data = getLongStringData(32767)
|
||||||
return adapter.request("common/sendArr", data).catch((e) => e);
|
return adapter.request('common/sendArr', data).catch((e) => e)
|
||||||
},
|
},
|
||||||
assertion: (error: any) => {
|
assertion: (error: any) => {
|
||||||
return !!error && !!error.error && !!error.error.message;
|
return !!error && !!error.error && !!error.error.message
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Single numeric value",
|
title: 'Single numeric value',
|
||||||
description: "Should send an array with a single numeric value",
|
description: 'Should send an array with a single numeric value',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendArr", numericData);
|
return adapter.request('common/sendArr', numericData)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
return res.table1[0][0] === numericData.table1[0].col1;
|
return res.table1[0][0] === numericData.table1[0].col1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Multiple columns",
|
title: 'Multiple columns',
|
||||||
description: "Should handle data with multiple columns",
|
description: 'Should handle data with multiple columns',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendArr", multiColumnData);
|
return adapter.request('common/sendArr', multiColumnData)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
return (
|
return (
|
||||||
@@ -113,142 +114,141 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
|||||||
res.table1[0][1] === multiColumnData.table1[0].col2 &&
|
res.table1[0][1] === multiColumnData.table1[0].col2 &&
|
||||||
res.table1[0][2] === multiColumnData.table1[0].col3 &&
|
res.table1[0][2] === multiColumnData.table1[0].col3 &&
|
||||||
res.table1[0][3] === multiColumnData.table1[0].col4
|
res.table1[0][3] === multiColumnData.table1[0].col4
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Multiple rows with nulls",
|
title: 'Multiple rows with nulls',
|
||||||
description: "Should handle data with multiple rows with null values",
|
description: 'Should handle data with multiple rows with null values',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendArr", multipleRowsWithNulls);
|
return adapter.request('common/sendArr', multipleRowsWithNulls)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
let result = true;
|
let result = true
|
||||||
multipleRowsWithNulls.table1.forEach((_: any, index: number) => {
|
multipleRowsWithNulls.table1.forEach((_: any, index: number) => {
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[index][0] === multipleRowsWithNulls.table1[index].col1;
|
res.table1[index][0] === multipleRowsWithNulls.table1[index].col1
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[index][1] === multipleRowsWithNulls.table1[index].col2;
|
res.table1[index][1] === multipleRowsWithNulls.table1[index].col2
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[index][2] === multipleRowsWithNulls.table1[index].col3;
|
res.table1[index][2] === multipleRowsWithNulls.table1[index].col3
|
||||||
result =
|
|
||||||
result &&
|
|
||||||
res.table1[index][3] === multipleRowsWithNulls.table1[index].col4;
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: "Multiple columns with nulls",
|
|
||||||
description: "Should handle data with multiple columns with null values",
|
|
||||||
test: () => {
|
|
||||||
return adapter.request("common/sendArr", multipleColumnsWithNulls);
|
|
||||||
},
|
|
||||||
assertion: (res: any) => {
|
|
||||||
let result = true;
|
|
||||||
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => {
|
|
||||||
result =
|
|
||||||
result &&
|
|
||||||
res.table1[index][0] ===
|
|
||||||
multipleColumnsWithNulls.table1[index].col1;
|
|
||||||
result =
|
|
||||||
result &&
|
|
||||||
res.table1[index][1] ===
|
|
||||||
multipleColumnsWithNulls.table1[index].col2;
|
|
||||||
result =
|
|
||||||
result &&
|
|
||||||
res.table1[index][2] ===
|
|
||||||
multipleColumnsWithNulls.table1[index].col3;
|
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[index][3] ===
|
res.table1[index][3] ===
|
||||||
(multipleColumnsWithNulls.table1[index].col4 || "");
|
(multipleRowsWithNulls.table1[index].col4 || ' ')
|
||||||
});
|
})
|
||||||
return result;
|
return result
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Multiple columns with nulls',
|
||||||
|
description: 'Should handle data with multiple columns with null values',
|
||||||
|
test: () => {
|
||||||
|
return adapter.request('common/sendArr', multipleColumnsWithNulls)
|
||||||
|
},
|
||||||
|
assertion: (res: any) => {
|
||||||
|
let result = true
|
||||||
|
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => {
|
||||||
|
result =
|
||||||
|
result &&
|
||||||
|
res.table1[index][0] === multipleColumnsWithNulls.table1[index].col1
|
||||||
|
result =
|
||||||
|
result &&
|
||||||
|
res.table1[index][1] === multipleColumnsWithNulls.table1[index].col2
|
||||||
|
result =
|
||||||
|
result &&
|
||||||
|
res.table1[index][2] === multipleColumnsWithNulls.table1[index].col3
|
||||||
|
result =
|
||||||
|
result &&
|
||||||
|
res.table1[index][3] ===
|
||||||
|
(multipleColumnsWithNulls.table1[index].col4 || ' ')
|
||||||
|
})
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
})
|
||||||
|
|
||||||
export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
||||||
name: "sendObj",
|
name: 'sendObj',
|
||||||
tests: [
|
tests: [
|
||||||
{
|
{
|
||||||
title: "Invalid column name",
|
title: 'Invalid column name',
|
||||||
description: "Should throw an error",
|
description: 'Should throw an error',
|
||||||
test: async () => {
|
test: async () => {
|
||||||
const invalidData: any = {
|
const invalidData: any = {
|
||||||
"1 invalid table": [{ col1: 42 }]
|
'1 invalid table': [{ col1: 42 }]
|
||||||
};
|
}
|
||||||
return adapter.request("common/sendObj", invalidData).catch((e) => e);
|
return adapter.request('common/sendObj', invalidData).catch((e) => e)
|
||||||
},
|
},
|
||||||
assertion: (error: any) => !!error && !!error.error && !!error.error.message
|
assertion: (error: any) =>
|
||||||
|
!!error && !!error.error && !!error.error.message
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Single string value",
|
title: 'Single string value',
|
||||||
description: "Should send an object with a single string value",
|
description: 'Should send an object with a single string value',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", stringData);
|
return adapter.request('common/sendObj', stringData)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
return res.table1[0].COL1 === stringData.table1[0].col1;
|
return res.table1[0].COL1 === stringData.table1[0].col1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Long string value",
|
title: 'Long string value',
|
||||||
description:
|
description:
|
||||||
"Should send an object with a long string value under 32765 characters",
|
'Should send an object with a long string value under 32765 characters',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", getLongStringData());
|
return adapter.request('common/sendObj', getLongStringData())
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
const longStringData = getLongStringData();
|
const longStringData = getLongStringData()
|
||||||
return res.table1[0].COL1 === longStringData.table1[0].col1;
|
return res.table1[0].COL1 === longStringData.table1[0].col1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Overly long string value",
|
title: 'Overly long string value',
|
||||||
description:
|
description:
|
||||||
"Should error out with long string values over 32765 characters",
|
'Should error out with long string values over 32765 characters',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter
|
return adapter
|
||||||
.request("common/sendObj", getLongStringData(32767))
|
.request('common/sendObj', getLongStringData(32767))
|
||||||
.catch((e) => e);
|
.catch((e) => e)
|
||||||
},
|
},
|
||||||
assertion: (error: any) => {
|
assertion: (error: any) => {
|
||||||
return !!error && !!error.error && !!error.error.message;
|
return !!error && !!error.error && !!error.error.message
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Single numeric value",
|
title: 'Single numeric value',
|
||||||
description: "Should send an object with a single numeric value",
|
description: 'Should send an object with a single numeric value',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", numericData);
|
return adapter.request('common/sendObj', numericData)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
return res.table1[0].COL1 === numericData.table1[0].col1;
|
return res.table1[0].COL1 === numericData.table1[0].col1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
title: "Large data volume",
|
title: 'Large data volume',
|
||||||
description: "Should send an object with a large amount of data",
|
description: 'Should send an object with a large amount of data',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", getLargeObjectData());
|
return adapter.request('common/sendObj', getLargeObjectData())
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
const data = getLargeObjectData();
|
const data = getLargeObjectData()
|
||||||
return res.table1[9000].BIG === data.table1[9000].big;
|
return res.table1[9000].BIG === data.table1[9000].big
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Multiple columns",
|
title: 'Multiple columns',
|
||||||
description: "Should handle data with multiple columns",
|
description: 'Should handle data with multiple columns',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", multiColumnData);
|
return adapter.request('common/sendObj', multiColumnData)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
return (
|
return (
|
||||||
@@ -256,62 +256,63 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
|||||||
res.table1[0].COL2 === multiColumnData.table1[0].col2 &&
|
res.table1[0].COL2 === multiColumnData.table1[0].col2 &&
|
||||||
res.table1[0].COL3 === multiColumnData.table1[0].col3 &&
|
res.table1[0].COL3 === multiColumnData.table1[0].col3 &&
|
||||||
res.table1[0].COL4 === multiColumnData.table1[0].col4
|
res.table1[0].COL4 === multiColumnData.table1[0].col4
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Multiple rows with nulls",
|
title: 'Multiple rows with nulls',
|
||||||
description: "Should handle data with multiple rows with null values",
|
description: 'Should handle data with multiple rows with null values',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", multipleRowsWithNulls);
|
return adapter.request('common/sendObj', multipleRowsWithNulls)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
let result = true;
|
let result = true
|
||||||
multipleRowsWithNulls.table1.forEach((_: any, index: number) => {
|
multipleRowsWithNulls.table1.forEach((_: any, index: number) => {
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[index].COL1 === multipleRowsWithNulls.table1[index].col1;
|
res.table1[index].COL1 === multipleRowsWithNulls.table1[index].col1
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[index].COL2 === multipleRowsWithNulls.table1[index].col2;
|
res.table1[index].COL2 === multipleRowsWithNulls.table1[index].col2
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[index].COL3 === multipleRowsWithNulls.table1[index].col3;
|
res.table1[index].COL3 === multipleRowsWithNulls.table1[index].col3
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[index].COL4 === multipleRowsWithNulls.table1[index].col4;
|
res.table1[index].COL4 ===
|
||||||
});
|
(multipleRowsWithNulls.table1[index].col4 || ' ')
|
||||||
return result;
|
})
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Multiple columns with nulls",
|
title: 'Multiple columns with nulls',
|
||||||
description: "Should handle data with multiple columns with null values",
|
description: 'Should handle data with multiple columns with null values',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", multipleColumnsWithNulls);
|
return adapter.request('common/sendObj', multipleColumnsWithNulls)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
let result = true;
|
let result = true
|
||||||
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => {
|
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => {
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[index].COL1 ===
|
res.table1[index].COL1 ===
|
||||||
multipleColumnsWithNulls.table1[index].col1;
|
multipleColumnsWithNulls.table1[index].col1
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[index].COL2 ===
|
res.table1[index].COL2 ===
|
||||||
multipleColumnsWithNulls.table1[index].col2;
|
multipleColumnsWithNulls.table1[index].col2
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[index].COL3 ===
|
res.table1[index].COL3 ===
|
||||||
multipleColumnsWithNulls.table1[index].col3;
|
multipleColumnsWithNulls.table1[index].col3
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[index].COL4 ===
|
res.table1[index].COL4 ===
|
||||||
(multipleColumnsWithNulls.table1[index].col4 || "");
|
(multipleColumnsWithNulls.table1[index].col4 || ' ')
|
||||||
});
|
})
|
||||||
return result;
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,52 +1,49 @@
|
|||||||
import SASjs from "@sasjs/adapter";
|
import SASjs from '@sasjs/adapter'
|
||||||
import { TestSuite } from "@sasjs/test-framework";
|
import { TestSuite } from '@sasjs/test-framework'
|
||||||
|
|
||||||
const data: any = { table1: [{ col1: "first col value" }] };
|
const data: any = { table1: [{ col1: 'first col value' }] }
|
||||||
|
|
||||||
export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
|
export const sasjsRequestTests = (adapter: SASjs): TestSuite => ({
|
||||||
name: "SASjs Requests",
|
name: 'SASjs Requests',
|
||||||
tests: [
|
tests: [
|
||||||
{
|
{
|
||||||
title: "WORK tables",
|
title: 'WORK tables',
|
||||||
description: "Should get WORK tables after request",
|
description: 'Should get WORK tables after request',
|
||||||
test: async () => {
|
test: async () => {
|
||||||
return adapter.request("common/sendArr", data);
|
return adapter.request('common/sendArr', data)
|
||||||
},
|
},
|
||||||
assertion: () => {
|
assertion: () => {
|
||||||
const requests = adapter.getSasRequests();
|
const requests = adapter.getSasRequests()
|
||||||
if (adapter.getSasjsConfig().debug) {
|
if (adapter.getSasjsConfig().debug) {
|
||||||
return requests[0].SASWORK !== null;
|
return requests[0].SASWORK !== null
|
||||||
} else {
|
} else {
|
||||||
return requests[0].SASWORK === null;
|
return requests[0].SASWORK === null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Make error and capture log",
|
title: 'Make error and capture log',
|
||||||
description: "Should make an error and capture log, in the same time it is testing if debug override is working",
|
description:
|
||||||
|
'Should make an error and capture log, in the same time it is testing if debug override is working',
|
||||||
test: async () => {
|
test: async () => {
|
||||||
return new Promise(async (resolve, reject) => {
|
return adapter
|
||||||
adapter
|
.request('common/makeErr', data, { debug: true })
|
||||||
.request("common/makeErr", data, {debug: true})
|
.catch(() => {
|
||||||
.then((res) => {
|
const sasRequests = adapter.getSasRequests()
|
||||||
//no action here, this request must throw error
|
const makeErrRequest: any =
|
||||||
})
|
sasRequests.find((req) => req.serviceLink.includes('makeErr')) ||
|
||||||
.catch((err) => {
|
null
|
||||||
let sasRequests = adapter.getSasRequests();
|
|
||||||
let makeErrRequest: any =
|
|
||||||
sasRequests.find((req) =>
|
|
||||||
req.serviceLink.includes("makeErr")
|
|
||||||
) || null;
|
|
||||||
|
|
||||||
if (!makeErrRequest) resolve(false)
|
if (!makeErrRequest) return false
|
||||||
|
|
||||||
resolve(!!(makeErrRequest.logFile && makeErrRequest.logFile.length > 0));
|
return !!(
|
||||||
});
|
makeErrRequest.logFile && makeErrRequest.logFile.length > 0
|
||||||
});
|
)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
assertion: (response) => {
|
assertion: (response) => {
|
||||||
return response;
|
return response
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,91 +1,92 @@
|
|||||||
import SASjs from "@sasjs/adapter";
|
import SASjs from '@sasjs/adapter'
|
||||||
import { TestSuite } from "@sasjs/test-framework";
|
import { TestSuite } from '@sasjs/test-framework'
|
||||||
|
|
||||||
const specialCharData: any = {
|
const specialCharData: any = {
|
||||||
table1: [
|
table1: [
|
||||||
{
|
{
|
||||||
tab: "\t",
|
tab: '\t',
|
||||||
lf: "\n",
|
lf: '\n',
|
||||||
cr: "\r",
|
cr: '\r',
|
||||||
semicolon: ";semi",
|
semicolon: ';semi',
|
||||||
percent: "%",
|
percent: '%',
|
||||||
singleQuote: "'",
|
singleQuote: "'",
|
||||||
doubleQuote: '"',
|
doubleQuote: '"',
|
||||||
crlf: "\r\n",
|
crlf: '\r\n',
|
||||||
euro: "€euro",
|
euro: '€euro',
|
||||||
banghash: "!#banghash"
|
banghash: '!#banghash',
|
||||||
|
dot: '.'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
}
|
||||||
|
|
||||||
const moreSpecialCharData: any = {
|
const moreSpecialCharData: any = {
|
||||||
table1: [
|
table1: [
|
||||||
{
|
{
|
||||||
speech0: '"speech',
|
speech0: '"speech',
|
||||||
pct: "%percent",
|
pct: '%percent',
|
||||||
speech: '"speech',
|
speech: '"speech',
|
||||||
slash: "\\slash",
|
slash: '\\slash',
|
||||||
slashWithSpecial: "\\\tslash",
|
slashWithSpecial: '\\\tslash',
|
||||||
macvar: "&sysuserid",
|
macvar: '&sysuserid',
|
||||||
chinese: "传/傳chinese",
|
chinese: '传/傳chinese',
|
||||||
sigma: "Σsigma",
|
sigma: 'Σsigma',
|
||||||
at: "@at",
|
at: '@at',
|
||||||
serbian: "Српски",
|
serbian: 'Српски',
|
||||||
dollar: "$"
|
dollar: '$'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
}
|
||||||
|
|
||||||
const getWideData = () => {
|
const getWideData = () => {
|
||||||
const cols: any = {};
|
const cols: any = {}
|
||||||
for (let i = 1; i <= 10000; i++) {
|
for (let i = 1; i <= 10000; i++) {
|
||||||
cols["col" + i] = "test" + i;
|
cols['col' + i] = 'test' + i
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: any = {
|
const data: any = {
|
||||||
table1: [cols]
|
table1: [cols]
|
||||||
};
|
}
|
||||||
|
|
||||||
return data;
|
return data
|
||||||
};
|
}
|
||||||
|
|
||||||
const getTables = () => {
|
const getTables = () => {
|
||||||
const tables: any = {};
|
const tables: any = {}
|
||||||
|
|
||||||
for (let i = 1; i <= 100; i++) {
|
for (let i = 1; i <= 100; i++) {
|
||||||
tables["table" + i] = [{ col1: "x", col2: "x", col3: "x", col4: "x" }];
|
tables['table' + i] = [{ col1: 'x', col2: 'x', col3: 'x', col4: 'x' }]
|
||||||
}
|
}
|
||||||
return tables;
|
return tables
|
||||||
};
|
}
|
||||||
|
|
||||||
const getLargeDataset = () => {
|
const getLargeDataset = () => {
|
||||||
const rows: any = [];
|
const rows: any = []
|
||||||
const colData: string =
|
const colData: string =
|
||||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||||
for (let i = 1; i <= 10000; i++) {
|
for (let i = 1; i <= 10000; i++) {
|
||||||
rows.push({ col1: colData, col2: colData, col3: colData, col4: colData });
|
rows.push({ col1: colData, col2: colData, col3: colData, col4: colData })
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: any = {
|
const data: any = {
|
||||||
table1: rows
|
table1: rows
|
||||||
};
|
}
|
||||||
|
|
||||||
return data;
|
return data
|
||||||
};
|
}
|
||||||
|
|
||||||
const errorAndCsrfData: any = {
|
const errorAndCsrfData: any = {
|
||||||
error: [{ col1: "q", col2: "w", col3: "e", col4: "r" }],
|
error: [{ col1: 'q', col2: 'w', col3: 'e', col4: 'r' }],
|
||||||
_csrf: [{ col1: "q", col2: "w", col3: "e", col4: "r" }]
|
_csrf: [{ col1: 'q', col2: 'w', col3: 'e', col4: 'r' }]
|
||||||
};
|
}
|
||||||
|
|
||||||
export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
||||||
name: "Special Cases",
|
name: 'Special Cases',
|
||||||
tests: [
|
tests: [
|
||||||
{
|
{
|
||||||
title: "Common special characters",
|
title: 'Common special characters',
|
||||||
description: "Should handle common special characters",
|
description: 'Should handle common special characters',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendArr", specialCharData);
|
return adapter.request('common/sendArr', specialCharData)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
return (
|
return (
|
||||||
@@ -96,17 +97,18 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
|||||||
res.table1[0][4] === specialCharData.table1[0].percent &&
|
res.table1[0][4] === specialCharData.table1[0].percent &&
|
||||||
res.table1[0][5] === specialCharData.table1[0].singleQuote &&
|
res.table1[0][5] === specialCharData.table1[0].singleQuote &&
|
||||||
res.table1[0][6] === specialCharData.table1[0].doubleQuote &&
|
res.table1[0][6] === specialCharData.table1[0].doubleQuote &&
|
||||||
res.table1[0][7] === "\n" &&
|
res.table1[0][7] === '\n' &&
|
||||||
res.table1[0][8] === specialCharData.table1[0].euro &&
|
res.table1[0][8] === specialCharData.table1[0].euro &&
|
||||||
res.table1[0][9] === specialCharData.table1[0].banghash
|
res.table1[0][9] === specialCharData.table1[0].banghash &&
|
||||||
);
|
res.table1[0][10] === specialCharData.table1[0].dot
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Other special characters",
|
title: 'Other special characters',
|
||||||
description: "Should handle other special characters",
|
description: 'Should handle other special characters',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendArr", moreSpecialCharData);
|
return adapter.request('common/sendArr', moreSpecialCharData)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
return (
|
return (
|
||||||
@@ -121,50 +123,50 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
|||||||
res.table1[0][8] === moreSpecialCharData.table1[0].at &&
|
res.table1[0][8] === moreSpecialCharData.table1[0].at &&
|
||||||
res.table1[0][9] === moreSpecialCharData.table1[0].serbian &&
|
res.table1[0][9] === moreSpecialCharData.table1[0].serbian &&
|
||||||
res.table1[0][10] === moreSpecialCharData.table1[0].dollar
|
res.table1[0][10] === moreSpecialCharData.table1[0].dollar
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Wide table with sendArr",
|
title: 'Wide table with sendArr',
|
||||||
description: "Should handle data with 10000 columns",
|
description: 'Should handle data with 10000 columns',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendArr", getWideData());
|
return adapter.request('common/sendArr', getWideData())
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
const data = getWideData();
|
const data = getWideData()
|
||||||
let result = true;
|
let result = true
|
||||||
for (let i = 0; i <= 10; i++) {
|
for (let i = 0; i <= 10; i++) {
|
||||||
result =
|
result =
|
||||||
result && res.table1[0][i] === data.table1[0]["col" + (i + 1)];
|
result && res.table1[0][i] === data.table1[0]['col' + (i + 1)]
|
||||||
}
|
}
|
||||||
return result;
|
return result
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Wide table with sendObj",
|
title: 'Wide table with sendObj',
|
||||||
description: "Should handle data with 10000 columns",
|
description: 'Should handle data with 10000 columns',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", getWideData());
|
return adapter.request('common/sendObj', getWideData())
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
const data = getWideData();
|
const data = getWideData()
|
||||||
let result = true;
|
let result = true
|
||||||
for (let i = 0; i <= 10; i++) {
|
for (let i = 0; i <= 10; i++) {
|
||||||
result =
|
result =
|
||||||
result &&
|
result &&
|
||||||
res.table1[0]["COL" + (i + 1)] === data.table1[0]["col" + (i + 1)];
|
res.table1[0]['COL' + (i + 1)] === data.table1[0]['col' + (i + 1)]
|
||||||
}
|
}
|
||||||
return result;
|
return result
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Multiple tables",
|
title: 'Multiple tables',
|
||||||
description: "Should handle data with 100 tables",
|
description: 'Should handle data with 100 tables',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendArr", getTables());
|
return adapter.request('common/sendArr', getTables())
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
const data = getTables();
|
const data = getTables()
|
||||||
return (
|
return (
|
||||||
res.table1[0][0] === data.table1[0].col1 &&
|
res.table1[0][0] === data.table1[0].col1 &&
|
||||||
res.table1[0][1] === data.table1[0].col2 &&
|
res.table1[0][1] === data.table1[0].col2 &&
|
||||||
@@ -174,45 +176,45 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
|||||||
res.table50[0][1] === data.table50[0].col2 &&
|
res.table50[0][1] === data.table50[0].col2 &&
|
||||||
res.table50[0][2] === data.table50[0].col3 &&
|
res.table50[0][2] === data.table50[0].col3 &&
|
||||||
res.table50[0][3] === data.table50[0].col4
|
res.table50[0][3] === data.table50[0].col4
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Large dataset with sendObj",
|
title: 'Large dataset with sendObj',
|
||||||
description: "Should handle 5mb of data",
|
description: 'Should handle 5mb of data',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", getLargeDataset());
|
return adapter.request('common/sendObj', getLargeDataset())
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
const data = getLargeDataset();
|
const data = getLargeDataset()
|
||||||
let result = true;
|
let result = true
|
||||||
for (let i = 0; i <= 10; i++) {
|
for (let i = 0; i <= 10; i++) {
|
||||||
result = result && res.table1[i][0] === data.table1[i][0];
|
result = result && res.table1[i][0] === data.table1[i][0]
|
||||||
}
|
}
|
||||||
return result;
|
return result
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Large dataset with sendArr",
|
title: 'Large dataset with sendArr',
|
||||||
description: "Should handle 5mb of data",
|
description: 'Should handle 5mb of data',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendArr", getLargeDataset());
|
return adapter.request('common/sendArr', getLargeDataset())
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
const data = getLargeDataset();
|
const data = getLargeDataset()
|
||||||
let result = true;
|
let result = true
|
||||||
for (let i = 0; i <= 10; i++) {
|
for (let i = 0; i <= 10; i++) {
|
||||||
result =
|
result =
|
||||||
result && res.table1[i][0] === Object.values(data.table1[i])[0];
|
result && res.table1[i][0] === Object.values(data.table1[i])[0]
|
||||||
}
|
}
|
||||||
return result;
|
return result
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Error and _csrf tables with sendArr",
|
title: 'Error and _csrf tables with sendArr',
|
||||||
description: "Should handle error and _csrf tables",
|
description: 'Should handle error and _csrf tables',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendArr", errorAndCsrfData);
|
return adapter.request('common/sendArr', errorAndCsrfData)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
return (
|
return (
|
||||||
@@ -224,14 +226,14 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
|||||||
res._csrf[0][1] === errorAndCsrfData._csrf[0].col2 &&
|
res._csrf[0][1] === errorAndCsrfData._csrf[0].col2 &&
|
||||||
res._csrf[0][2] === errorAndCsrfData._csrf[0].col3 &&
|
res._csrf[0][2] === errorAndCsrfData._csrf[0].col3 &&
|
||||||
res._csrf[0][3] === errorAndCsrfData._csrf[0].col4
|
res._csrf[0][3] === errorAndCsrfData._csrf[0].col4
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Error and _csrf tables with sendObj",
|
title: 'Error and _csrf tables with sendObj',
|
||||||
description: "Should handle error and _csrf tables",
|
description: 'Should handle error and _csrf tables',
|
||||||
test: () => {
|
test: () => {
|
||||||
return adapter.request("common/sendObj", errorAndCsrfData);
|
return adapter.request('common/sendObj', errorAndCsrfData)
|
||||||
},
|
},
|
||||||
assertion: (res: any) => {
|
assertion: (res: any) => {
|
||||||
return (
|
return (
|
||||||
@@ -243,8 +245,8 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
|||||||
res._csrf[0].COL2 === errorAndCsrfData._csrf[0].col2 &&
|
res._csrf[0].COL2 === errorAndCsrfData._csrf[0].col2 &&
|
||||||
res._csrf[0].COL3 === errorAndCsrfData._csrf[0].col3 &&
|
res._csrf[0].COL3 === errorAndCsrfData._csrf[0].col3 &&
|
||||||
res._csrf[0].COL4 === errorAndCsrfData._csrf[0].col4
|
res._csrf[0].COL4 === errorAndCsrfData._csrf[0].col4
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
})
|
||||||
|
|||||||
@@ -1,22 +1,22 @@
|
|||||||
export const assert = (
|
export const assert = (
|
||||||
expression: boolean | (() => boolean),
|
expression: boolean | (() => boolean),
|
||||||
message = "Assertion failed"
|
message = 'Assertion failed'
|
||||||
) => {
|
) => {
|
||||||
let result;
|
let result
|
||||||
try {
|
try {
|
||||||
if (typeof expression === "boolean") {
|
if (typeof expression === 'boolean') {
|
||||||
result = expression;
|
result = expression
|
||||||
} else {
|
} else {
|
||||||
result = expression();
|
result = expression()
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(message);
|
console.error(message)
|
||||||
throw new Error(message);
|
throw new Error(message)
|
||||||
}
|
}
|
||||||
if (!!result) {
|
if (!!result) {
|
||||||
return;
|
return
|
||||||
} else {
|
} else {
|
||||||
console.error(message);
|
console.error(message)
|
||||||
throw new Error(message);
|
throw new Error(message)
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"jsx": "react"
|
"jsx": "react-jsx",
|
||||||
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"src"
|
"src"
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import {
|
import { Context, EditContextInput, ContextAllAttributes } from './types'
|
||||||
Context,
|
import { isUrl } from './utils'
|
||||||
CsrfToken,
|
|
||||||
EditContextInput,
|
|
||||||
ContextAllAttributes
|
|
||||||
} from './types'
|
|
||||||
import { makeRequest, isUrl } from './utils'
|
|
||||||
import { SASViyaApiClient } from './SASViyaApiClient'
|
|
||||||
import { prefixMessage } from '@sasjs/utils/error'
|
import { prefixMessage } from '@sasjs/utils/error'
|
||||||
|
import { RequestClient } from './request/RequestClient'
|
||||||
|
|
||||||
export class ContextManager {
|
export class ContextManager {
|
||||||
private defaultComputeContexts = [
|
private defaultComputeContexts = [
|
||||||
@@ -29,8 +24,6 @@ export class ContextManager {
|
|||||||
'SAS Visual Forecasting launcher context'
|
'SAS Visual Forecasting launcher context'
|
||||||
]
|
]
|
||||||
|
|
||||||
private csrfToken: CsrfToken | null = null
|
|
||||||
|
|
||||||
get getDefaultComputeContexts() {
|
get getDefaultComputeContexts() {
|
||||||
return this.defaultComputeContexts
|
return this.defaultComputeContexts
|
||||||
}
|
}
|
||||||
@@ -38,28 +31,19 @@ export class ContextManager {
|
|||||||
return this.defaultLauncherContexts
|
return this.defaultLauncherContexts
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(
|
constructor(private serverUrl: string, private requestClient: RequestClient) {
|
||||||
private serverUrl: string,
|
|
||||||
private setCsrfToken: (csrfToken: CsrfToken) => void
|
|
||||||
) {
|
|
||||||
if (serverUrl) isUrl(serverUrl)
|
if (serverUrl) isUrl(serverUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getComputeContexts(accessToken?: string) {
|
public async getComputeContexts(accessToken?: string) {
|
||||||
const headers: any = {
|
const { result: contexts } = await this.requestClient
|
||||||
'Content-Type': 'application/json'
|
.get<{ items: Context[] }>(
|
||||||
}
|
`${this.serverUrl}/compute/contexts?limit=10000`,
|
||||||
|
accessToken
|
||||||
if (accessToken) {
|
)
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
.catch((err) => {
|
||||||
}
|
throw prefixMessage(err, 'Error while getting compute contexts. ')
|
||||||
|
})
|
||||||
const { result: contexts } = await this.request<{ items: Context[] }>(
|
|
||||||
`${this.serverUrl}/compute/contexts?limit=10000`,
|
|
||||||
{ headers }
|
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(err, 'Error while getting compute contexts. ')
|
|
||||||
})
|
|
||||||
|
|
||||||
const contextsList = contexts && contexts.items ? contexts.items : []
|
const contextsList = contexts && contexts.items ? contexts.items : []
|
||||||
|
|
||||||
@@ -73,20 +57,14 @@ export class ContextManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async getLauncherContexts(accessToken?: string) {
|
public async getLauncherContexts(accessToken?: string) {
|
||||||
const headers: any = {
|
const { result: contexts } = await this.requestClient
|
||||||
'Content-Type': 'application/json'
|
.get<{ items: Context[] }>(
|
||||||
}
|
`${this.serverUrl}/launcher/contexts?limit=10000`,
|
||||||
|
accessToken
|
||||||
if (accessToken) {
|
)
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
.catch((err) => {
|
||||||
}
|
throw prefixMessage(err, 'Error while getting launcher contexts. ')
|
||||||
|
})
|
||||||
const { result: contexts } = await this.request<{ items: Context[] }>(
|
|
||||||
`${this.serverUrl}/launcher/contexts?limit=10000`,
|
|
||||||
{ headers }
|
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(err, 'Error while getting launcher contexts. ')
|
|
||||||
})
|
|
||||||
|
|
||||||
const contextsList = contexts && contexts.items ? contexts.items : []
|
const contextsList = contexts && contexts.items ? contexts.items : []
|
||||||
|
|
||||||
@@ -184,18 +162,15 @@ export class ContextManager {
|
|||||||
requestBody.environment = { autoExecLines }
|
requestBody.environment = { autoExecLines }
|
||||||
}
|
}
|
||||||
|
|
||||||
const createContextRequest: RequestInit = {
|
const { result: context } = await this.requestClient
|
||||||
method: 'POST',
|
.post<Context>(
|
||||||
headers,
|
`${this.serverUrl}/compute/contexts`,
|
||||||
body: JSON.stringify(requestBody)
|
requestBody,
|
||||||
}
|
accessToken
|
||||||
|
)
|
||||||
const { result: context } = await this.request<Context>(
|
.catch((err) => {
|
||||||
`${this.serverUrl}/compute/contexts`,
|
throw prefixMessage(err, 'Error while creating compute context. ')
|
||||||
createContextRequest
|
})
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(err, 'Error while creating compute context. ')
|
|
||||||
})
|
|
||||||
|
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
@@ -238,18 +213,15 @@ export class ContextManager {
|
|||||||
launchType
|
launchType
|
||||||
}
|
}
|
||||||
|
|
||||||
const createContextRequest: RequestInit = {
|
const { result: context } = await this.requestClient
|
||||||
method: 'POST',
|
.post<Context>(
|
||||||
headers,
|
`${this.serverUrl}/launcher/contexts`,
|
||||||
body: JSON.stringify(requestBody)
|
requestBody,
|
||||||
}
|
accessToken
|
||||||
|
)
|
||||||
const { result: context } = await this.request<Context>(
|
.catch((err) => {
|
||||||
`${this.serverUrl}/launcher/contexts`,
|
throw prefixMessage(err, 'Error while creating launcher context. ')
|
||||||
createContextRequest
|
})
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(err, 'Error while creating launcher context. ')
|
|
||||||
})
|
|
||||||
|
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
@@ -268,14 +240,6 @@ export class ContextManager {
|
|||||||
true
|
true
|
||||||
)
|
)
|
||||||
|
|
||||||
const headers: any = {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (accessToken) {
|
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
|
||||||
}
|
|
||||||
|
|
||||||
let originalContext
|
let originalContext
|
||||||
|
|
||||||
originalContext = await this.getComputeContextByName(
|
originalContext = await this.getComputeContextByName(
|
||||||
@@ -291,39 +255,33 @@ export class ContextManager {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { result: context, etag } = await this.request<Context>(
|
const { result: context, etag } = await this.requestClient
|
||||||
`${this.serverUrl}/compute/contexts/${originalContext.id}`,
|
.get<Context>(
|
||||||
{
|
`${this.serverUrl}/compute/contexts/${originalContext.id}`,
|
||||||
headers
|
accessToken
|
||||||
}
|
)
|
||||||
).catch((err) => {
|
.catch((err) => {
|
||||||
if (err && err.status === 404) {
|
if (err && err.status === 404) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`The context '${contextName}' was not found on this server.`
|
`The context '${contextName}' was not found on this server.`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
throw err
|
throw err
|
||||||
})
|
})
|
||||||
|
|
||||||
// An If-Match header with the value of the last ETag for the context
|
// An If-Match header with the value of the last ETag for the context
|
||||||
// is required to be able to update it
|
// is required to be able to update it
|
||||||
// https://developer.sas.com/apis/rest/Compute/#update-a-context-definition
|
// https://developer.sas.com/apis/rest/Compute/#update-a-context-definition
|
||||||
headers['If-Match'] = etag
|
return await this.requestClient.put<Context>(
|
||||||
|
`/compute/contexts/${context.id}`,
|
||||||
const updateContextRequest: RequestInit = {
|
{
|
||||||
method: 'PUT',
|
|
||||||
headers,
|
|
||||||
body: JSON.stringify({
|
|
||||||
...context,
|
...context,
|
||||||
...editedContext,
|
...editedContext,
|
||||||
attributes: { ...context.attributes, ...editedContext.attributes }
|
attributes: { ...context.attributes, ...editedContext.attributes }
|
||||||
})
|
},
|
||||||
}
|
accessToken,
|
||||||
|
{ 'If-Match': etag }
|
||||||
return await this.request<Context>(
|
|
||||||
`${this.serverUrl}/compute/contexts/${context.id}`,
|
|
||||||
updateContextRequest
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,20 +289,17 @@ export class ContextManager {
|
|||||||
contextName: string,
|
contextName: string,
|
||||||
accessToken?: string
|
accessToken?: string
|
||||||
): Promise<Context> {
|
): Promise<Context> {
|
||||||
const headers: any = {
|
const { result: contexts } = await this.requestClient
|
||||||
'Content-Type': 'application/json'
|
.get<{ items: Context[] }>(
|
||||||
}
|
`${this.serverUrl}/compute/contexts?filter=eq(name, "${contextName}")`,
|
||||||
|
accessToken
|
||||||
if (accessToken) {
|
)
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
.catch((err) => {
|
||||||
}
|
throw prefixMessage(
|
||||||
|
err,
|
||||||
const { result: contexts } = await this.request<{ items: Context[] }>(
|
'Error while getting compute context by name. '
|
||||||
`${this.serverUrl}/compute/contexts?filter=eq(name, "${contextName}")`,
|
)
|
||||||
{ headers }
|
})
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(err, 'Error while getting compute context by name. ')
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!contexts || !(contexts.items && contexts.items.length)) {
|
if (!contexts || !(contexts.items && contexts.items.length)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
@@ -359,20 +314,14 @@ export class ContextManager {
|
|||||||
contextId: string,
|
contextId: string,
|
||||||
accessToken?: string
|
accessToken?: string
|
||||||
): Promise<ContextAllAttributes> {
|
): Promise<ContextAllAttributes> {
|
||||||
const headers: any = {
|
const { result: context } = await this.requestClient
|
||||||
'Content-Type': 'application/json'
|
.get<ContextAllAttributes>(
|
||||||
}
|
`${this.serverUrl}/compute/contexts/${contextId}`,
|
||||||
|
accessToken
|
||||||
if (accessToken) {
|
)
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
.catch((err) => {
|
||||||
}
|
throw prefixMessage(err, 'Error while getting compute context by id. ')
|
||||||
|
})
|
||||||
const { result: context } = await this.request<ContextAllAttributes>(
|
|
||||||
`${this.serverUrl}/compute/contexts/${contextId}`,
|
|
||||||
{ headers }
|
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(err, 'Error while getting compute context by id. ')
|
|
||||||
})
|
|
||||||
|
|
||||||
return context
|
return context
|
||||||
}
|
}
|
||||||
@@ -381,20 +330,14 @@ export class ContextManager {
|
|||||||
executeScript: Function,
|
executeScript: Function,
|
||||||
accessToken?: string
|
accessToken?: string
|
||||||
) {
|
) {
|
||||||
const headers: any = {
|
const { result: contexts } = await this.requestClient
|
||||||
'Content-Type': 'application/json'
|
.get<{ items: Context[] }>(
|
||||||
}
|
`${this.serverUrl}/compute/contexts?limit=10000`,
|
||||||
|
accessToken
|
||||||
if (accessToken) {
|
)
|
||||||
headers.Authorization = `Bearer ${accessToken}`
|
.catch((err) => {
|
||||||
}
|
throw prefixMessage(err, 'Error while fetching compute contexts.')
|
||||||
|
})
|
||||||
const { result: contexts } = await this.request<{ items: Context[] }>(
|
|
||||||
`${this.serverUrl}/compute/contexts?limit=10000`,
|
|
||||||
{ headers }
|
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(err, 'Error while fetching compute contexts.')
|
|
||||||
})
|
|
||||||
|
|
||||||
const contextsList = contexts.items || []
|
const contextsList = contexts.items || []
|
||||||
const executableContexts: any[] = []
|
const executableContexts: any[] = []
|
||||||
@@ -471,14 +414,9 @@ export class ContextManager {
|
|||||||
|
|
||||||
const context = await this.getComputeContextByName(contextName, accessToken)
|
const context = await this.getComputeContextByName(contextName, accessToken)
|
||||||
|
|
||||||
const deleteContextRequest: RequestInit = {
|
return await this.requestClient.delete<Context>(
|
||||||
method: 'DELETE',
|
|
||||||
headers
|
|
||||||
}
|
|
||||||
|
|
||||||
return await this.request<Context>(
|
|
||||||
`${this.serverUrl}/compute/contexts/${context.id}`,
|
`${this.serverUrl}/compute/contexts/${context.id}`,
|
||||||
deleteContextRequest
|
accessToken
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,34 +424,6 @@ export class ContextManager {
|
|||||||
|
|
||||||
// TODO: implement deleteLauncherContext method
|
// TODO: implement deleteLauncherContext method
|
||||||
|
|
||||||
private async request<T>(
|
|
||||||
url: string,
|
|
||||||
options: RequestInit,
|
|
||||||
contentType: 'text' | 'json' = 'json'
|
|
||||||
) {
|
|
||||||
if (this.csrfToken) {
|
|
||||||
options.headers = {
|
|
||||||
...options.headers,
|
|
||||||
[this.csrfToken.headerName]: this.csrfToken.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return await makeRequest<T>(
|
|
||||||
url,
|
|
||||||
options,
|
|
||||||
(token) => {
|
|
||||||
this.csrfToken = token
|
|
||||||
this.setCsrfToken(token)
|
|
||||||
},
|
|
||||||
contentType
|
|
||||||
).catch((err) => {
|
|
||||||
throw prefixMessage(
|
|
||||||
err,
|
|
||||||
'Error while making request in Context Manager. '
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private validateContextName(name: string) {
|
private validateContextName(name: string) {
|
||||||
if (!name) throw new Error('Context name is required.')
|
if (!name) throw new Error('Context name is required.')
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user