mirror of
https://github.com/sasjs/adapter.git
synced 2025-12-11 09:24:35 +00:00
Compare commits
272 Commits
v2.0.1
...
snyk-upgra
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e908ea8373 | ||
|
|
76d0b82b4c | ||
|
|
95d65d270d | ||
|
|
4e5c9c1ccd | ||
|
|
3267af0724 | ||
|
|
75120424d0 | ||
|
|
f13c7e5cf1 | ||
|
|
53a7b1c9e6 | ||
|
|
8c30cbff13 | ||
|
|
8f3a7f33f8 | ||
|
|
67ec27bab7 | ||
|
|
c1b200b0d8 | ||
|
|
e03ec996d6 | ||
|
|
ad8dbfd4ec | ||
|
|
15a774ff81 | ||
|
|
98114c5591 | ||
|
|
dffcb66d54 | ||
|
|
67c7147e62 | ||
|
|
50d1b4d824 | ||
|
|
dc98ce3b0b | ||
|
|
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 | ||
|
|
1a0d62d8f3 | ||
|
|
8f4d1c7aea | ||
|
|
2a4735c6f2 | ||
|
|
9b6ba3548f | ||
|
|
5a2ee88cbc | ||
|
|
efa4c71b8a | ||
|
|
ecfc1a4bf8 | ||
|
|
246b855c76 | ||
|
|
b23f199334 | ||
|
|
ed5dabee9f | ||
|
|
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:
|
||||
GITHUB_TOKEN: ${{ secrets.GH_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
|
||||
|
||||
/coverage
|
||||
/coverage
|
||||
|
||||
.DS_Store
|
||||
@@ -2,75 +2,127 @@
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, religion, or sexual identity
|
||||
and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the
|
||||
overall community
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* The use of sexualized language or imagery, and sexual attention or
|
||||
advances of any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Publishing others' private information, such as a physical or email
|
||||
address, without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project team at support@macropeople.com. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
reported to the community leaders responsible for enforcement at
|
||||
https://sasapps.io/contact-us.
|
||||
All complaints will be reviewed and investigated promptly and fairly.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series
|
||||
of actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or
|
||||
permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within
|
||||
the community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.0, available at
|
||||
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
|
||||
|
||||
Community Impact Guidelines were inspired by [Mozilla's code of conduct
|
||||
enforcement ladder](https://github.com/mozilla/diversity).
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
https://www.contributor-covenant.org/faq. Translations are available at
|
||||
https://www.contributor-covenant.org/translations.
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
@@ -41,8 +41,165 @@ parmcards4;
|
||||
|
||||
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
|
||||
|
||||
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.
|
||||
|
||||
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 = {
|
||||
roots: ["<rootDir>/src"],
|
||||
testMatch: [
|
||||
"**/__tests__/**/*.+(ts|tsx|js)",
|
||||
"**/?(*.)+(spec|test).+(ts|tsx|js)"
|
||||
],
|
||||
testTimeout: 90000,
|
||||
// All imported modules in your tests should be mocked automatically
|
||||
// automock: false,
|
||||
|
||||
// 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: {
|
||||
"^.+\\.(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,
|
||||
}
|
||||
|
||||
9608
package-lock.json
generated
9608
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",
|
||||
"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",
|
||||
"lint:fix": "npx prettier --write '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}'",
|
||||
"test": "jest --coverage",
|
||||
"lint:fix": "npx prettier --write 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}' && npx prettier --write 'sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
|
||||
"lint": "npx prettier --check 'src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}' && npx prettier --check 'sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}'",
|
||||
"test": "jest --silent --coverage",
|
||||
"prepublishOnly": "cp -r ./build/* . && rm -rf ./build",
|
||||
"postpublish": "git clean -fd",
|
||||
"semantic-release": "semantic-release",
|
||||
@@ -36,31 +36,31 @@
|
||||
},
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/isomorphic-fetch": "0.0.35",
|
||||
"@types/jest": "^26.0.15",
|
||||
"@types/jest": "^26.0.22",
|
||||
"cp": "^0.2.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"jest": "^25.5.4",
|
||||
"jest": "^26.6.3",
|
||||
"jest-extended": "^0.11.5",
|
||||
"path": "^0.12.7",
|
||||
"rimraf": "^3.0.2",
|
||||
"semantic-release": "^17.3.0",
|
||||
"semantic-release": "^17.4.2",
|
||||
"terser-webpack-plugin": "^4.2.3",
|
||||
"ts-jest": "^25.5.1",
|
||||
"ts-loader": "^8.0.11",
|
||||
"ts-loader": "^9.1.2",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typedoc": "^0.17.8",
|
||||
"typedoc-neo-theme": "^1.0.10",
|
||||
"typedoc-plugin-external-module-name": "^4.0.3",
|
||||
"typescript": "^3.9.7",
|
||||
"webpack": "^4.44.2",
|
||||
"webpack-cli": "^4.2.0"
|
||||
"typedoc": "^0.20.35",
|
||||
"typedoc-neo-theme": "^1.1.0",
|
||||
"typedoc-plugin-external-module-name": "^4.0.6",
|
||||
"typescript": "^3.9.9",
|
||||
"webpack": "^5.33.2",
|
||||
"webpack-cli": "^4.7.0"
|
||||
},
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"@sasjs/utils": "^1.5.0",
|
||||
"es6-promise": "^4.2.8",
|
||||
"form-data": "^3.0.0",
|
||||
"isomorphic-fetch": "^2.2.1"
|
||||
"@sasjs/utils": "^2.10.2",
|
||||
"axios": "^0.21.1",
|
||||
"form-data": "^4.0.0",
|
||||
"https": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"trailingComma": "none",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": false
|
||||
"semi": 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": ".",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@sasjs/adapter": "^1.18.2",
|
||||
"@sasjs/adapter": "file:../build/sasjs-adapter-5.0.0.tgz",
|
||||
"@sasjs/test-framework": "^1.4.0",
|
||||
"@testing-library/jest-dom": "^4.2.4",
|
||||
"@testing-library/react": "^9.5.0",
|
||||
"@testing-library/user-event": "^7.2.1",
|
||||
"@types/jest": "^26.0.3",
|
||||
"@types/node": "^14.0.14",
|
||||
"@types/react": "^16.9.41",
|
||||
"@types/react-dom": "^16.9.8",
|
||||
"@types/react-router-dom": "^5.1.5",
|
||||
"react": "^16.13.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"@types/jest": "^26.0.22",
|
||||
"@types/node": "^14.14.25",
|
||||
"@types/react": "^17.0.1",
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"@types/react-router-dom": "^5.1.7",
|
||||
"react": "^17.0.1",
|
||||
"react-dom": "^17.0.1",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"react-scripts": "3.4.1",
|
||||
"typescript": "^3.9.6"
|
||||
"react-scripts": "^4.0.2",
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
@@ -26,8 +23,8 @@
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"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": "npm run update:adapter && npm run deploy:tests"
|
||||
"deploy:tests": "rsync -avhe ssh ./build/* --delete $SSH_ACCOUNT:$DEPLOY_PATH",
|
||||
"deploy": "npm run update:adapter && npm run build && npm run deploy:tests"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "react-app"
|
||||
@@ -45,6 +42,6 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"node-sass": "^4.14.1"
|
||||
"node-sass": "^5.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"appLoc": "/Public/app",
|
||||
"serverType": "SASVIYA",
|
||||
"debug": false,
|
||||
"contextName": "SharedCompute",
|
||||
"contextName": "sasjs adapter compute context",
|
||||
"useComputeApi": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
import React, { ReactElement, useState, useContext, useEffect } from "react";
|
||||
import { TestSuiteRunner, TestSuite, AppContext } from "@sasjs/test-framework";
|
||||
import { basicTests } from "./testSuites/Basic";
|
||||
import { sendArrTests, sendObjTests } from "./testSuites/RequestData";
|
||||
import { specialCaseTests } from "./testSuites/SpecialCases";
|
||||
import { sasjsRequestTests } from "./testSuites/SasjsRequests";
|
||||
import "@sasjs/test-framework/dist/index.css";
|
||||
import { computeTests } from "./testSuites/Compute";
|
||||
import React, { ReactElement, useState, useContext, useEffect } from 'react'
|
||||
import { TestSuiteRunner, TestSuite, AppContext } from '@sasjs/test-framework'
|
||||
import { basicTests } from './testSuites/Basic'
|
||||
import { sendArrTests, sendObjTests } from './testSuites/RequestData'
|
||||
import { specialCaseTests } from './testSuites/SpecialCases'
|
||||
import { sasjsRequestTests } from './testSuites/SasjsRequests'
|
||||
import '@sasjs/test-framework/dist/index.css'
|
||||
import { computeTests } from './testSuites/Compute'
|
||||
|
||||
const App = (): ReactElement<{}> => {
|
||||
const { adapter, config } = useContext(AppContext);
|
||||
const [testSuites, setTestSuites] = useState<TestSuite[]>([]);
|
||||
const { adapter, config } = useContext(AppContext)
|
||||
const [testSuites, setTestSuites] = useState<TestSuite[]>([])
|
||||
|
||||
useEffect(() => {
|
||||
if (adapter) {
|
||||
@@ -20,15 +20,15 @@ const App = (): ReactElement<{}> => {
|
||||
specialCaseTests(adapter),
|
||||
sasjsRequestTests(adapter),
|
||||
computeTests(adapter)
|
||||
]);
|
||||
])
|
||||
}
|
||||
}, [adapter, config]);
|
||||
}, [adapter, config])
|
||||
|
||||
return (
|
||||
<div className="app">
|
||||
{adapter && testSuites && <TestSuiteRunner testSuites={testSuites} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default App;
|
||||
export default App
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
import React, { ReactElement, useState, useCallback, useContext } from "react";
|
||||
import "./Login.scss";
|
||||
import { AppContext } from "@sasjs/test-framework";
|
||||
import { Redirect } from "react-router-dom";
|
||||
import React, { ReactElement, useState, useCallback, useContext } from 'react'
|
||||
import './Login.scss'
|
||||
import { AppContext } from '@sasjs/test-framework'
|
||||
import { Redirect } from 'react-router-dom'
|
||||
|
||||
const Login = (): ReactElement<{}> => {
|
||||
const [username, setUsername] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const appContext = useContext(AppContext);
|
||||
const [username, setUsername] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
const appContext = useContext(AppContext)
|
||||
|
||||
const handleSubmit = useCallback(
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
e.preventDefault()
|
||||
appContext.adapter.logIn(username, password).then((res) => {
|
||||
appContext.setIsLoggedIn(res.isLoggedIn);
|
||||
});
|
||||
appContext.setIsLoggedIn(res.isLoggedIn)
|
||||
})
|
||||
},
|
||||
[username, password, appContext]
|
||||
);
|
||||
)
|
||||
|
||||
return !appContext.isLoggedIn ? (
|
||||
<div className="login-container">
|
||||
@@ -48,7 +48,7 @@ const Login = (): ReactElement<{}> => {
|
||||
</div>
|
||||
) : (
|
||||
<Redirect to="/" />
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default Login;
|
||||
export default Login
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
import React, { ReactElement, useContext, FunctionComponent } from "react";
|
||||
import { Redirect, Route } from "react-router-dom";
|
||||
import { AppContext } from "@sasjs/test-framework";
|
||||
import React, { ReactElement, useContext, FunctionComponent } from 'react'
|
||||
import { Redirect, Route } from 'react-router-dom'
|
||||
import { AppContext } from '@sasjs/test-framework'
|
||||
|
||||
interface PrivateRouteProps {
|
||||
component: FunctionComponent;
|
||||
exact?: boolean;
|
||||
path: string;
|
||||
component: FunctionComponent
|
||||
exact?: boolean
|
||||
path: string
|
||||
}
|
||||
|
||||
const PrivateRoute = (
|
||||
props: PrivateRouteProps
|
||||
): ReactElement<PrivateRouteProps> => {
|
||||
const { component, path, exact } = props;
|
||||
const appContext = useContext(AppContext);
|
||||
const { component, path, exact } = props
|
||||
const appContext = useContext(AppContext)
|
||||
return appContext.isLoggedIn ? (
|
||||
<Route component={component} path={path} exact={exact} />
|
||||
) : (
|
||||
<Redirect to="/login" />
|
||||
);
|
||||
};
|
||||
)
|
||||
}
|
||||
|
||||
export default PrivateRoute;
|
||||
export default PrivateRoute
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import React from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
import { Route, HashRouter, Switch } from "react-router-dom";
|
||||
import "./index.scss";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
import { AppProvider } from "@sasjs/test-framework";
|
||||
import PrivateRoute from "./PrivateRoute";
|
||||
import Login from "./Login";
|
||||
import App from "./App";
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom'
|
||||
import { Route, HashRouter, Switch } from 'react-router-dom'
|
||||
import './index.scss'
|
||||
import * as serviceWorker from './serviceWorker'
|
||||
import { AppProvider } from '@sasjs/test-framework'
|
||||
import PrivateRoute from './PrivateRoute'
|
||||
import Login from './Login'
|
||||
import App from './App'
|
||||
|
||||
ReactDOM.render(
|
||||
<AppProvider>
|
||||
@@ -17,10 +17,10 @@ ReactDOM.render(
|
||||
</Switch>
|
||||
</HashRouter>
|
||||
</AppProvider>,
|
||||
document.getElementById("root")
|
||||
);
|
||||
document.getElementById('root')
|
||||
)
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
// 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
|
||||
|
||||
const isLocalhost = Boolean(
|
||||
window.location.hostname === "localhost" ||
|
||||
window.location.hostname === 'localhost' ||
|
||||
// [::1] is the IPv6 localhost address.
|
||||
window.location.hostname === "[::1]" ||
|
||||
window.location.hostname === '[::1]' ||
|
||||
// 127.0.0.0/8 are considered localhost for IPv4.
|
||||
window.location.hostname.match(
|
||||
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
|
||||
)
|
||||
);
|
||||
)
|
||||
|
||||
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.
|
||||
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) {
|
||||
// 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
|
||||
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
|
||||
return;
|
||||
return
|
||||
}
|
||||
|
||||
window.addEventListener("load", () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
|
||||
window.addEventListener('load', () => {
|
||||
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
|
||||
|
||||
if (isLocalhost) {
|
||||
// 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
|
||||
// service worker/PWA documentation.
|
||||
navigator.serviceWorker.ready.then(() => {
|
||||
console.log(
|
||||
"This web app is being served cache-first by a service " +
|
||||
"worker. To learn more, visit https://bit.ly/CRA-PWA"
|
||||
);
|
||||
});
|
||||
'This web app is being served cache-first by a service ' +
|
||||
'worker. To learn more, visit https://bit.ly/CRA-PWA'
|
||||
)
|
||||
})
|
||||
} else {
|
||||
// Is not localhost. Just register service worker
|
||||
registerValidSW(swUrl, config);
|
||||
registerValidSW(swUrl, config)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,83 +59,83 @@ function registerValidSW(swUrl, config) {
|
||||
.register(swUrl)
|
||||
.then((registration) => {
|
||||
registration.onupdatefound = () => {
|
||||
const installingWorker = registration.installing;
|
||||
const installingWorker = registration.installing
|
||||
if (installingWorker == null) {
|
||||
return;
|
||||
return
|
||||
}
|
||||
installingWorker.onstatechange = () => {
|
||||
if (installingWorker.state === "installed") {
|
||||
if (installingWorker.state === 'installed') {
|
||||
if (navigator.serviceWorker.controller) {
|
||||
// At this point, the updated precached content has been fetched,
|
||||
// but the previous service worker will still serve the older
|
||||
// content until all client tabs are closed.
|
||||
console.log(
|
||||
"New content is available and will be used when all " +
|
||||
"tabs for this page are closed. See https://bit.ly/CRA-PWA."
|
||||
);
|
||||
'New content is available and will be used when all ' +
|
||||
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
|
||||
)
|
||||
|
||||
// Execute callback
|
||||
if (config && config.onUpdate) {
|
||||
config.onUpdate(registration);
|
||||
config.onUpdate(registration)
|
||||
}
|
||||
} else {
|
||||
// At this point, everything has been precached.
|
||||
// It's the perfect time to display a
|
||||
// "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
|
||||
if (config && config.onSuccess) {
|
||||
config.onSuccess(registration);
|
||||
config.onSuccess(registration)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error during service worker registration:", error);
|
||||
});
|
||||
console.error('Error during service worker registration:', error)
|
||||
})
|
||||
}
|
||||
|
||||
function checkValidServiceWorker(swUrl, config) {
|
||||
// Check if the service worker can be found. If it can't reload the page.
|
||||
fetch(swUrl, {
|
||||
headers: { "Service-Worker": "script" }
|
||||
headers: { 'Service-Worker': 'script' }
|
||||
})
|
||||
.then((response) => {
|
||||
// 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 (
|
||||
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.
|
||||
navigator.serviceWorker.ready.then((registration) => {
|
||||
registration.unregister().then(() => {
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
window.location.reload()
|
||||
})
|
||||
})
|
||||
} else {
|
||||
// Service worker found. Proceed as normal.
|
||||
registerValidSW(swUrl, config);
|
||||
registerValidSW(swUrl, config)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
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() {
|
||||
if ("serviceWorker" in navigator) {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.ready
|
||||
.then((registration) => {
|
||||
registration.unregister();
|
||||
registration.unregister()
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error(error.message);
|
||||
});
|
||||
console.error(error.message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// 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 { TestSuite } from "@sasjs/test-framework";
|
||||
import SASjs, { SASjsConfig } from '@sasjs/adapter'
|
||||
import { TestSuite } from '@sasjs/test-framework'
|
||||
import { ServerType } from '@sasjs/utils/types'
|
||||
|
||||
const stringData: any = { table1: [{ col1: 'first col value' }] }
|
||||
|
||||
const defaultConfig: SASjsConfig = {
|
||||
serverUrl: window.location.origin,
|
||||
pathSAS9: '/SASStoredProcess/do',
|
||||
pathSASViya: '/SASJobExecution',
|
||||
appLoc: '/Public/seedapp',
|
||||
serverType: ServerType.SASViya,
|
||||
serverType: ServerType.SasViya,
|
||||
debug: false,
|
||||
contextName: 'SAS Job Execution compute context',
|
||||
useComputeApi: false
|
||||
};
|
||||
useComputeApi: false,
|
||||
allowInsecureRequests: false
|
||||
}
|
||||
|
||||
const customConfig = {
|
||||
serverUrl: "http://url.com",
|
||||
pathSAS9: "sas9",
|
||||
pathSASViya: "viya",
|
||||
appLoc: "/Public/seedapp",
|
||||
serverType: ServerType.SAS9,
|
||||
serverUrl: 'http://url.com',
|
||||
pathSAS9: 'sas9',
|
||||
pathSASViya: 'viya',
|
||||
appLoc: '/Public/seedapp',
|
||||
serverType: ServerType.Sas9,
|
||||
debug: false
|
||||
};
|
||||
}
|
||||
|
||||
export const basicTests = (
|
||||
adapter: SASjs,
|
||||
userName: string,
|
||||
password: string
|
||||
): TestSuite => ({
|
||||
name: "Basic Tests",
|
||||
name: 'Basic Tests',
|
||||
tests: [
|
||||
{
|
||||
title: "Log in",
|
||||
description: "Should log the user in",
|
||||
title: 'Log in',
|
||||
description: 'Should log the user in',
|
||||
test: async () => {
|
||||
return adapter.logIn(userName, password);
|
||||
return adapter.logIn(userName, password)
|
||||
},
|
||||
assertion: (response: any) =>
|
||||
response && response.isLoggedIn && response.userName === userName
|
||||
},
|
||||
{
|
||||
title: "Multiple Log in attempts",
|
||||
description: "Should fail on first attempt and should log the user in on second attempt",
|
||||
title: 'Multiple Log in attempts',
|
||||
description:
|
||||
'Should fail on first attempt and should log the user in on second attempt',
|
||||
test: async () => {
|
||||
await adapter.logOut()
|
||||
await adapter.logIn('invalid', 'invalid')
|
||||
@@ -49,14 +54,49 @@ export const basicTests = (
|
||||
response && response.isLoggedIn && response.userName === userName
|
||||
},
|
||||
{
|
||||
title: "Default config",
|
||||
title: 'Trigger login callback',
|
||||
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 () => {
|
||||
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) => {
|
||||
const sasjsConfig = sasjsInstance.getSasjsConfig();
|
||||
const sasjsConfig = sasjsInstance.getSasjsConfig()
|
||||
|
||||
return (
|
||||
sasjsConfig.serverUrl === defaultConfig.serverUrl &&
|
||||
@@ -65,17 +105,17 @@ export const basicTests = (
|
||||
sasjsConfig.appLoc === defaultConfig.appLoc &&
|
||||
sasjsConfig.serverType === defaultConfig.serverType &&
|
||||
sasjsConfig.debug === defaultConfig.debug
|
||||
);
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Custom config",
|
||||
description: "Should use fully custom config whenever supplied",
|
||||
title: 'Custom config',
|
||||
description: 'Should use fully custom config whenever supplied',
|
||||
test: async () => {
|
||||
return Promise.resolve(new SASjs(customConfig));
|
||||
return Promise.resolve(new SASjs(customConfig))
|
||||
},
|
||||
assertion: (sasjsInstance: SASjs) => {
|
||||
const sasjsConfig = sasjsInstance.getSasjsConfig();
|
||||
const sasjsConfig = sasjsInstance.getSasjsConfig()
|
||||
return (
|
||||
sasjsConfig.serverUrl === customConfig.serverUrl &&
|
||||
sasjsConfig.pathSAS9 === customConfig.pathSAS9 &&
|
||||
@@ -83,28 +123,28 @@ export const basicTests = (
|
||||
sasjsConfig.appLoc === customConfig.appLoc &&
|
||||
sasjsConfig.serverType === customConfig.serverType &&
|
||||
sasjsConfig.debug === customConfig.debug
|
||||
);
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Config overrides",
|
||||
description: "Should override default config with supplied properties",
|
||||
title: 'Config overrides',
|
||||
description: 'Should override default config with supplied properties',
|
||||
test: async () => {
|
||||
return Promise.resolve(
|
||||
new SASjs({ serverUrl: "http://test.com", debug: false })
|
||||
);
|
||||
new SASjs({ serverUrl: 'http://test.com', debug: false })
|
||||
)
|
||||
},
|
||||
assertion: (sasjsInstance: SASjs) => {
|
||||
const sasjsConfig = sasjsInstance.getSasjsConfig();
|
||||
const sasjsConfig = sasjsInstance.getSasjsConfig()
|
||||
return (
|
||||
sasjsConfig.serverUrl === "http://test.com" &&
|
||||
sasjsConfig.serverUrl === 'http://test.com' &&
|
||||
sasjsConfig.pathSAS9 === defaultConfig.pathSAS9 &&
|
||||
sasjsConfig.pathSASViya === defaultConfig.pathSASViya &&
|
||||
sasjsConfig.appLoc === defaultConfig.appLoc &&
|
||||
sasjsConfig.serverType === defaultConfig.serverType &&
|
||||
sasjsConfig.debug === false
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,45 +1,50 @@
|
||||
import SASjs from "@sasjs/adapter";
|
||||
import { TestSuite } from "@sasjs/test-framework";
|
||||
import SASjs from '@sasjs/adapter'
|
||||
import { TestSuite } from '@sasjs/test-framework'
|
||||
|
||||
export const computeTests = (adapter: SASjs): TestSuite => ({
|
||||
name: "Compute",
|
||||
name: 'Compute',
|
||||
tests: [
|
||||
{
|
||||
title: "Start Compute Job - not waiting for result",
|
||||
description: "Should start a compute job and return the session",
|
||||
title: 'Start Compute Job - not waiting for result',
|
||||
description: 'Should start a compute job and return the session',
|
||||
test: () => {
|
||||
const data: any = { table1: [{ col1: "first col value" }] };
|
||||
return adapter.startComputeJob("/Public/app/common/sendArr", data);
|
||||
const data: any = { table1: [{ col1: 'first col value' }] }
|
||||
return adapter.startComputeJob('/Public/app/common/sendArr', data)
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
const expectedProperties = ["id", "applicationName", "attributes"]
|
||||
return validate(expectedProperties, res);
|
||||
const expectedProperties = ['id', 'applicationName', 'attributes']
|
||||
return validate(expectedProperties, res)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Start Compute Job - waiting for result",
|
||||
description: "Should start a compute job and return the job",
|
||||
title: 'Start Compute Job - waiting for result',
|
||||
description: 'Should start a compute job and return the job',
|
||||
test: () => {
|
||||
const data: any = { table1: [{ col1: "first col value" }] };
|
||||
return adapter.startComputeJob("/Public/app/common/sendArr", data, {}, "", true);
|
||||
const data: any = { table1: [{ col1: 'first col value' }] }
|
||||
return adapter.startComputeJob(
|
||||
'/Public/app/common/sendArr',
|
||||
data,
|
||||
{},
|
||||
'',
|
||||
true
|
||||
)
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
const expectedProperties = ["id", "state", "creationTimeStamp", "jobConditionCode"]
|
||||
return validate(expectedProperties, res.result);
|
||||
}
|
||||
},
|
||||
{
|
||||
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;`
|
||||
const expectedProperties = [
|
||||
'id',
|
||||
'state',
|
||||
'creationTimeStamp',
|
||||
'jobConditionCode'
|
||||
]
|
||||
|
||||
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(
|
||||
'sasCode.sas',
|
||||
fileLines,
|
||||
@@ -50,34 +55,34 @@ export const computeTests = (adapter: SASjs): TestSuite => ({
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
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",
|
||||
description: "Should execute sas file and return log",
|
||||
title: 'Execute Script Viya - failed job',
|
||||
description: 'Should execute sas file and return log',
|
||||
test: () => {
|
||||
const fileLines = [
|
||||
`%abort;`
|
||||
]
|
||||
|
||||
return adapter.executeScriptSASViya(
|
||||
'sasCode.sas',
|
||||
fileLines,
|
||||
'SAS Studio compute context',
|
||||
undefined,
|
||||
true
|
||||
).catch((err: any) => err )
|
||||
const fileLines = [`%abort;`]
|
||||
|
||||
return adapter
|
||||
.executeScriptSASViya(
|
||||
'sasCode.sas',
|
||||
fileLines,
|
||||
'SAS Studio compute context',
|
||||
undefined,
|
||||
true
|
||||
)
|
||||
.catch((err: any) => err)
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
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 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 actualProperties = Object.keys(data);
|
||||
const actualProperties = Object.keys(data)
|
||||
|
||||
const isValid = expectedProperties.every(
|
||||
(property) => actualProperties.includes(property)
|
||||
);
|
||||
const isValid = expectedProperties.every((property) =>
|
||||
actualProperties.includes(property)
|
||||
)
|
||||
return isValid
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,111 +1,112 @@
|
||||
import SASjs from "@sasjs/adapter";
|
||||
import { TestSuite } from "@sasjs/test-framework";
|
||||
import SASjs from '@sasjs/adapter'
|
||||
import { TestSuite } from '@sasjs/test-framework'
|
||||
|
||||
const stringData: any = { table1: [{ col1: "first col value" }] };
|
||||
const numericData: any = { table1: [{ col1: 3.14159265 }] };
|
||||
const stringData: any = { table1: [{ col1: 'first col value' }] }
|
||||
const numericData: any = { table1: [{ col1: 3.14159265 }] }
|
||||
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 = {
|
||||
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: 1.62, col3: "x", col4: "x" },
|
||||
{ col1: 42, col2: 1.62, col3: "x", col4: "x" }
|
||||
{ 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' }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
const multipleColumnsWithNulls: any = {
|
||||
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: "" },
|
||||
{ col1: 42, col2: null, col3: "x", col4: "" }
|
||||
{ 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: '' }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
const getLongStringData = (length = 32764) => {
|
||||
let x = "X";
|
||||
let x = 'X'
|
||||
for (let i = 1; i <= length; i++) {
|
||||
x = x + "X";
|
||||
x = x + 'X'
|
||||
}
|
||||
const data: any = { table1: [{ col1: x }] };
|
||||
return data;
|
||||
};
|
||||
const data: any = { table1: [{ col1: x }] }
|
||||
return data
|
||||
}
|
||||
|
||||
const getLargeObjectData = () => {
|
||||
const data = { table1: [{ big: "data" }] };
|
||||
const data = { table1: [{ big: 'data' }] }
|
||||
|
||||
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 => ({
|
||||
name: "sendArr",
|
||||
name: 'sendArr',
|
||||
tests: [
|
||||
{
|
||||
title: "Absolute paths",
|
||||
description: "Should work with absolute paths to SAS jobs",
|
||||
title: 'Absolute paths',
|
||||
description: 'Should work with absolute paths to SAS jobs',
|
||||
test: () => {
|
||||
return adapter.request("/Public/app/common/sendArr", stringData);
|
||||
return adapter.request('/Public/app/common/sendArr', stringData)
|
||||
},
|
||||
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",
|
||||
description: "Should send an array with a single string value",
|
||||
title: 'Single string value',
|
||||
description: 'Should send an array with a single string value',
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", stringData);
|
||||
return adapter.request('common/sendArr', stringData)
|
||||
},
|
||||
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:
|
||||
"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: () => {
|
||||
return adapter.request("common/sendArr", getLongStringData());
|
||||
return adapter.request('common/sendArr', getLongStringData())
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
const longStringData = getLongStringData();
|
||||
return res.table1[0][0] === longStringData.table1[0].col1;
|
||||
const longStringData = getLongStringData()
|
||||
return res.table1[0][0] === longStringData.table1[0].col1
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Overly long string value",
|
||||
title: 'Overly long string value',
|
||||
description:
|
||||
"Should error out with long string values over 32765 characters",
|
||||
'Should error out with long string values over 32765 characters',
|
||||
test: () => {
|
||||
const data = getLongStringData(32767);
|
||||
return adapter.request("common/sendArr", data).catch((e) => e);
|
||||
const data = getLongStringData(32767)
|
||||
return adapter.request('common/sendArr', data).catch((e) => e)
|
||||
},
|
||||
assertion: (error: any) => {
|
||||
return !!error && !!error.error && !!error.error.message;
|
||||
return !!error && !!error.error && !!error.error.message
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Single numeric value",
|
||||
description: "Should send an array with a single numeric value",
|
||||
title: 'Single numeric value',
|
||||
description: 'Should send an array with a single numeric value',
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", numericData);
|
||||
return adapter.request('common/sendArr', numericData)
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
return res.table1[0][0] === numericData.table1[0].col1;
|
||||
return res.table1[0][0] === numericData.table1[0].col1
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Multiple columns",
|
||||
description: "Should handle data with multiple columns",
|
||||
title: 'Multiple columns',
|
||||
description: 'Should handle data with multiple columns',
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", multiColumnData);
|
||||
return adapter.request('common/sendArr', multiColumnData)
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
return (
|
||||
@@ -113,142 +114,141 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
|
||||
res.table1[0][1] === multiColumnData.table1[0].col2 &&
|
||||
res.table1[0][2] === multiColumnData.table1[0].col3 &&
|
||||
res.table1[0][3] === multiColumnData.table1[0].col4
|
||||
);
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Multiple rows with nulls",
|
||||
description: "Should handle data with multiple rows with null values",
|
||||
title: 'Multiple rows with nulls',
|
||||
description: 'Should handle data with multiple rows with null values',
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", multipleRowsWithNulls);
|
||||
return adapter.request('common/sendArr', multipleRowsWithNulls)
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
let result = true;
|
||||
let result = true
|
||||
multipleRowsWithNulls.table1.forEach((_: any, index: number) => {
|
||||
result =
|
||||
result &&
|
||||
res.table1[index][0] === multipleRowsWithNulls.table1[index].col1;
|
||||
res.table1[index][0] === multipleRowsWithNulls.table1[index].col1
|
||||
result =
|
||||
result &&
|
||||
res.table1[index][1] === multipleRowsWithNulls.table1[index].col2;
|
||||
res.table1[index][1] === multipleRowsWithNulls.table1[index].col2
|
||||
result =
|
||||
result &&
|
||||
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;
|
||||
res.table1[index][2] === multipleRowsWithNulls.table1[index].col3
|
||||
result =
|
||||
result &&
|
||||
res.table1[index][3] ===
|
||||
(multipleColumnsWithNulls.table1[index].col4 || "");
|
||||
});
|
||||
return result;
|
||||
(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 &&
|
||||
res.table1[index][3] ===
|
||||
(multipleColumnsWithNulls.table1[index].col4 || ' ')
|
||||
})
|
||||
return result
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
|
||||
export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
||||
name: "sendObj",
|
||||
name: 'sendObj',
|
||||
tests: [
|
||||
{
|
||||
title: "Invalid column name",
|
||||
description: "Should throw an error",
|
||||
title: 'Invalid column name',
|
||||
description: 'Should throw an error',
|
||||
test: async () => {
|
||||
const invalidData: any = {
|
||||
"1 invalid table": [{ col1: 42 }]
|
||||
};
|
||||
return adapter.request("common/sendObj", invalidData).catch((e) => e);
|
||||
'1 invalid table': [{ col1: 42 }]
|
||||
}
|
||||
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",
|
||||
description: "Should send an object with a single string value",
|
||||
title: 'Single string value',
|
||||
description: 'Should send an object with a single string value',
|
||||
test: () => {
|
||||
return adapter.request("common/sendObj", stringData);
|
||||
return adapter.request('common/sendObj', stringData)
|
||||
},
|
||||
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:
|
||||
"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: () => {
|
||||
return adapter.request("common/sendObj", getLongStringData());
|
||||
return adapter.request('common/sendObj', getLongStringData())
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
const longStringData = getLongStringData();
|
||||
return res.table1[0].COL1 === longStringData.table1[0].col1;
|
||||
const longStringData = getLongStringData()
|
||||
return res.table1[0].COL1 === longStringData.table1[0].col1
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Overly long string value",
|
||||
title: 'Overly long string value',
|
||||
description:
|
||||
"Should error out with long string values over 32765 characters",
|
||||
'Should error out with long string values over 32765 characters',
|
||||
test: () => {
|
||||
return adapter
|
||||
.request("common/sendObj", getLongStringData(32767))
|
||||
.catch((e) => e);
|
||||
.request('common/sendObj', getLongStringData(32767))
|
||||
.catch((e) => e)
|
||||
},
|
||||
assertion: (error: any) => {
|
||||
return !!error && !!error.error && !!error.error.message;
|
||||
return !!error && !!error.error && !!error.error.message
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Single numeric value",
|
||||
description: "Should send an object with a single numeric value",
|
||||
title: 'Single numeric value',
|
||||
description: 'Should send an object with a single numeric value',
|
||||
test: () => {
|
||||
return adapter.request("common/sendObj", numericData);
|
||||
return adapter.request('common/sendObj', numericData)
|
||||
},
|
||||
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",
|
||||
description: "Should send an object with a large amount of data",
|
||||
title: 'Large data volume',
|
||||
description: 'Should send an object with a large amount of data',
|
||||
test: () => {
|
||||
return adapter.request("common/sendObj", getLargeObjectData());
|
||||
return adapter.request('common/sendObj', getLargeObjectData())
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
const data = getLargeObjectData();
|
||||
return res.table1[9000].BIG === data.table1[9000].big;
|
||||
const data = getLargeObjectData()
|
||||
return res.table1[9000].BIG === data.table1[9000].big
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Multiple columns",
|
||||
description: "Should handle data with multiple columns",
|
||||
title: 'Multiple columns',
|
||||
description: 'Should handle data with multiple columns',
|
||||
test: () => {
|
||||
return adapter.request("common/sendObj", multiColumnData);
|
||||
return adapter.request('common/sendObj', multiColumnData)
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
return (
|
||||
@@ -256,62 +256,63 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
|
||||
res.table1[0].COL2 === multiColumnData.table1[0].col2 &&
|
||||
res.table1[0].COL3 === multiColumnData.table1[0].col3 &&
|
||||
res.table1[0].COL4 === multiColumnData.table1[0].col4
|
||||
);
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Multiple rows with nulls",
|
||||
description: "Should handle data with multiple rows with null values",
|
||||
title: 'Multiple rows with nulls',
|
||||
description: 'Should handle data with multiple rows with null values',
|
||||
test: () => {
|
||||
return adapter.request("common/sendObj", multipleRowsWithNulls);
|
||||
return adapter.request('common/sendObj', multipleRowsWithNulls)
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
let result = true;
|
||||
let result = true
|
||||
multipleRowsWithNulls.table1.forEach((_: any, index: number) => {
|
||||
result =
|
||||
result &&
|
||||
res.table1[index].COL1 === multipleRowsWithNulls.table1[index].col1;
|
||||
res.table1[index].COL1 === multipleRowsWithNulls.table1[index].col1
|
||||
result =
|
||||
result &&
|
||||
res.table1[index].COL2 === multipleRowsWithNulls.table1[index].col2;
|
||||
res.table1[index].COL2 === multipleRowsWithNulls.table1[index].col2
|
||||
result =
|
||||
result &&
|
||||
res.table1[index].COL3 === multipleRowsWithNulls.table1[index].col3;
|
||||
res.table1[index].COL3 === multipleRowsWithNulls.table1[index].col3
|
||||
result =
|
||||
result &&
|
||||
res.table1[index].COL4 === multipleRowsWithNulls.table1[index].col4;
|
||||
});
|
||||
return result;
|
||||
res.table1[index].COL4 ===
|
||||
(multipleRowsWithNulls.table1[index].col4 || ' ')
|
||||
})
|
||||
return result
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Multiple columns with nulls",
|
||||
description: "Should handle data with multiple columns with null values",
|
||||
title: 'Multiple columns with nulls',
|
||||
description: 'Should handle data with multiple columns with null values',
|
||||
test: () => {
|
||||
return adapter.request("common/sendObj", multipleColumnsWithNulls);
|
||||
return adapter.request('common/sendObj', multipleColumnsWithNulls)
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
let result = true;
|
||||
let result = true
|
||||
multipleColumnsWithNulls.table1.forEach((_: any, index: number) => {
|
||||
result =
|
||||
result &&
|
||||
res.table1[index].COL1 ===
|
||||
multipleColumnsWithNulls.table1[index].col1;
|
||||
multipleColumnsWithNulls.table1[index].col1
|
||||
result =
|
||||
result &&
|
||||
res.table1[index].COL2 ===
|
||||
multipleColumnsWithNulls.table1[index].col2;
|
||||
multipleColumnsWithNulls.table1[index].col2
|
||||
result =
|
||||
result &&
|
||||
res.table1[index].COL3 ===
|
||||
multipleColumnsWithNulls.table1[index].col3;
|
||||
multipleColumnsWithNulls.table1[index].col3
|
||||
result =
|
||||
result &&
|
||||
res.table1[index].COL4 ===
|
||||
(multipleColumnsWithNulls.table1[index].col4 || "");
|
||||
});
|
||||
return result;
|
||||
(multipleColumnsWithNulls.table1[index].col4 || ' ')
|
||||
})
|
||||
return result
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,52 +1,49 @@
|
||||
import SASjs from "@sasjs/adapter";
|
||||
import { TestSuite } from "@sasjs/test-framework";
|
||||
import SASjs from '@sasjs/adapter'
|
||||
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 => ({
|
||||
name: "SASjs Requests",
|
||||
name: 'SASjs Requests',
|
||||
tests: [
|
||||
{
|
||||
title: "WORK tables",
|
||||
description: "Should get WORK tables after request",
|
||||
title: 'WORK tables',
|
||||
description: 'Should get WORK tables after request',
|
||||
test: async () => {
|
||||
return adapter.request("common/sendArr", data);
|
||||
return adapter.request('common/sendArr', data)
|
||||
},
|
||||
assertion: () => {
|
||||
const requests = adapter.getSasRequests();
|
||||
const requests = adapter.getSasRequests()
|
||||
if (adapter.getSasjsConfig().debug) {
|
||||
return requests[0].SASWORK !== null;
|
||||
return requests[0].SASWORK !== null
|
||||
} else {
|
||||
return requests[0].SASWORK === null;
|
||||
return requests[0].SASWORK === null
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
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",
|
||||
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',
|
||||
test: async () => {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
adapter
|
||||
.request("common/makeErr", data, {debug: true})
|
||||
.then((res) => {
|
||||
//no action here, this request must throw error
|
||||
})
|
||||
.catch((err) => {
|
||||
let sasRequests = adapter.getSasRequests();
|
||||
let makeErrRequest: any =
|
||||
sasRequests.find((req) =>
|
||||
req.serviceLink.includes("makeErr")
|
||||
) || null;
|
||||
return adapter
|
||||
.request('common/makeErr', data, { debug: true })
|
||||
.catch(() => {
|
||||
const sasRequests = adapter.getSasRequests()
|
||||
const 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) => {
|
||||
return response;
|
||||
return response
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,91 +1,92 @@
|
||||
import SASjs from "@sasjs/adapter";
|
||||
import { TestSuite } from "@sasjs/test-framework";
|
||||
import SASjs from '@sasjs/adapter'
|
||||
import { TestSuite } from '@sasjs/test-framework'
|
||||
|
||||
const specialCharData: any = {
|
||||
table1: [
|
||||
{
|
||||
tab: "\t",
|
||||
lf: "\n",
|
||||
cr: "\r",
|
||||
semicolon: ";semi",
|
||||
percent: "%",
|
||||
tab: '\t',
|
||||
lf: '\n',
|
||||
cr: '\r',
|
||||
semicolon: ';semi',
|
||||
percent: '%',
|
||||
singleQuote: "'",
|
||||
doubleQuote: '"',
|
||||
crlf: "\r\n",
|
||||
euro: "€euro",
|
||||
banghash: "!#banghash"
|
||||
crlf: '\r\n',
|
||||
euro: '€euro',
|
||||
banghash: '!#banghash',
|
||||
dot: '.'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
const moreSpecialCharData: any = {
|
||||
table1: [
|
||||
{
|
||||
speech0: '"speech',
|
||||
pct: "%percent",
|
||||
pct: '%percent',
|
||||
speech: '"speech',
|
||||
slash: "\\slash",
|
||||
slashWithSpecial: "\\\tslash",
|
||||
macvar: "&sysuserid",
|
||||
chinese: "传/傳chinese",
|
||||
sigma: "Σsigma",
|
||||
at: "@at",
|
||||
serbian: "Српски",
|
||||
dollar: "$"
|
||||
slash: '\\slash',
|
||||
slashWithSpecial: '\\\tslash',
|
||||
macvar: '&sysuserid',
|
||||
chinese: '传/傳chinese',
|
||||
sigma: 'Σsigma',
|
||||
at: '@at',
|
||||
serbian: 'Српски',
|
||||
dollar: '$'
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
const getWideData = () => {
|
||||
const cols: any = {};
|
||||
const cols: any = {}
|
||||
for (let i = 1; i <= 10000; i++) {
|
||||
cols["col" + i] = "test" + i;
|
||||
cols['col' + i] = 'test' + i
|
||||
}
|
||||
|
||||
const data: any = {
|
||||
table1: [cols]
|
||||
};
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
return data
|
||||
}
|
||||
|
||||
const getTables = () => {
|
||||
const tables: any = {};
|
||||
const tables: any = {}
|
||||
|
||||
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 rows: any = [];
|
||||
const rows: any = []
|
||||
const colData: string =
|
||||
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
|
||||
'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
|
||||
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 = {
|
||||
table1: rows
|
||||
};
|
||||
}
|
||||
|
||||
return data;
|
||||
};
|
||||
return data
|
||||
}
|
||||
|
||||
const errorAndCsrfData: any = {
|
||||
error: [{ col1: "q", col2: "w", col3: "e", col4: "r" }],
|
||||
_csrf: [{ 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' }]
|
||||
}
|
||||
|
||||
export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
||||
name: "Special Cases",
|
||||
name: 'Special Cases',
|
||||
tests: [
|
||||
{
|
||||
title: "Common special characters",
|
||||
description: "Should handle common special characters",
|
||||
title: 'Common special characters',
|
||||
description: 'Should handle common special characters',
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", specialCharData);
|
||||
return adapter.request('common/sendArr', specialCharData)
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
return (
|
||||
@@ -96,17 +97,18 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
||||
res.table1[0][4] === specialCharData.table1[0].percent &&
|
||||
res.table1[0][5] === specialCharData.table1[0].singleQuote &&
|
||||
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][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",
|
||||
description: "Should handle other special characters",
|
||||
title: 'Other special characters',
|
||||
description: 'Should handle other special characters',
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", moreSpecialCharData);
|
||||
return adapter.request('common/sendArr', moreSpecialCharData)
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
return (
|
||||
@@ -121,50 +123,50 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
||||
res.table1[0][8] === moreSpecialCharData.table1[0].at &&
|
||||
res.table1[0][9] === moreSpecialCharData.table1[0].serbian &&
|
||||
res.table1[0][10] === moreSpecialCharData.table1[0].dollar
|
||||
);
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Wide table with sendArr",
|
||||
description: "Should handle data with 10000 columns",
|
||||
title: 'Wide table with sendArr',
|
||||
description: 'Should handle data with 10000 columns',
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", getWideData());
|
||||
return adapter.request('common/sendArr', getWideData())
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
const data = getWideData();
|
||||
let result = true;
|
||||
const data = getWideData()
|
||||
let result = true
|
||||
for (let i = 0; i <= 10; i++) {
|
||||
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",
|
||||
description: "Should handle data with 10000 columns",
|
||||
title: 'Wide table with sendObj',
|
||||
description: 'Should handle data with 10000 columns',
|
||||
test: () => {
|
||||
return adapter.request("common/sendObj", getWideData());
|
||||
return adapter.request('common/sendObj', getWideData())
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
const data = getWideData();
|
||||
let result = true;
|
||||
const data = getWideData()
|
||||
let result = true
|
||||
for (let i = 0; i <= 10; i++) {
|
||||
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",
|
||||
description: "Should handle data with 100 tables",
|
||||
title: 'Multiple tables',
|
||||
description: 'Should handle data with 100 tables',
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", getTables());
|
||||
return adapter.request('common/sendArr', getTables())
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
const data = getTables();
|
||||
const data = getTables()
|
||||
return (
|
||||
res.table1[0][0] === data.table1[0].col1 &&
|
||||
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][2] === data.table50[0].col3 &&
|
||||
res.table50[0][3] === data.table50[0].col4
|
||||
);
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Large dataset with sendObj",
|
||||
description: "Should handle 5mb of data",
|
||||
title: 'Large dataset with sendObj',
|
||||
description: 'Should handle 5mb of data',
|
||||
test: () => {
|
||||
return adapter.request("common/sendObj", getLargeDataset());
|
||||
return adapter.request('common/sendObj', getLargeDataset())
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
const data = getLargeDataset();
|
||||
let result = true;
|
||||
const data = getLargeDataset()
|
||||
let result = true
|
||||
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",
|
||||
description: "Should handle 5mb of data",
|
||||
title: 'Large dataset with sendArr',
|
||||
description: 'Should handle 5mb of data',
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", getLargeDataset());
|
||||
return adapter.request('common/sendArr', getLargeDataset())
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
const data = getLargeDataset();
|
||||
let result = true;
|
||||
const data = getLargeDataset()
|
||||
let result = true
|
||||
for (let i = 0; i <= 10; i++) {
|
||||
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",
|
||||
description: "Should handle error and _csrf tables",
|
||||
title: 'Error and _csrf tables with sendArr',
|
||||
description: 'Should handle error and _csrf tables',
|
||||
test: () => {
|
||||
return adapter.request("common/sendArr", errorAndCsrfData);
|
||||
return adapter.request('common/sendArr', errorAndCsrfData)
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
return (
|
||||
@@ -224,14 +226,14 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
||||
res._csrf[0][1] === errorAndCsrfData._csrf[0].col2 &&
|
||||
res._csrf[0][2] === errorAndCsrfData._csrf[0].col3 &&
|
||||
res._csrf[0][3] === errorAndCsrfData._csrf[0].col4
|
||||
);
|
||||
)
|
||||
}
|
||||
},
|
||||
{
|
||||
title: "Error and _csrf tables with sendObj",
|
||||
description: "Should handle error and _csrf tables",
|
||||
title: 'Error and _csrf tables with sendObj',
|
||||
description: 'Should handle error and _csrf tables',
|
||||
test: () => {
|
||||
return adapter.request("common/sendObj", errorAndCsrfData);
|
||||
return adapter.request('common/sendObj', errorAndCsrfData)
|
||||
},
|
||||
assertion: (res: any) => {
|
||||
return (
|
||||
@@ -243,8 +245,8 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
|
||||
res._csrf[0].COL2 === errorAndCsrfData._csrf[0].col2 &&
|
||||
res._csrf[0].COL3 === errorAndCsrfData._csrf[0].col3 &&
|
||||
res._csrf[0].COL4 === errorAndCsrfData._csrf[0].col4
|
||||
);
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
export const assert = (
|
||||
expression: boolean | (() => boolean),
|
||||
message = "Assertion failed"
|
||||
message = 'Assertion failed'
|
||||
) => {
|
||||
let result;
|
||||
let result
|
||||
try {
|
||||
if (typeof expression === "boolean") {
|
||||
result = expression;
|
||||
if (typeof expression === 'boolean') {
|
||||
result = expression
|
||||
} else {
|
||||
result = expression();
|
||||
result = expression()
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(message);
|
||||
throw new Error(message);
|
||||
console.error(message)
|
||||
throw new Error(message)
|
||||
}
|
||||
if (!!result) {
|
||||
return;
|
||||
return
|
||||
} else {
|
||||
console.error(message);
|
||||
throw new Error(message);
|
||||
console.error(message)
|
||||
throw new Error(message)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react"
|
||||
"jsx": "react-jsx",
|
||||
"noFallthroughCasesInSwitch": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user