1
0
mirror of https://github.com/sasjs/adapter.git synced 2025-12-11 09:24:35 +00:00

Merge pull request #474 from sasjs/auto-tests

sasjs-tests automation
This commit is contained in:
Allan Bowe
2022-05-17 12:17:08 +03:00
committed by GitHub
21 changed files with 13489 additions and 4154 deletions

30
.github/vpn/config.ovpn vendored Normal file
View File

@@ -0,0 +1,30 @@
cipher AES-256-CBC
setenv FORWARD_COMPATIBLE 1
client
server-poll-timeout 4
nobind
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 443 tcp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
remote vpn.analytium.co.uk 1194 udp
dev tun
dev-type tun
ns-cert-type server
setenv opt tls-version-min 1.0 or-highest
reneg-sec 604800
sndbuf 0
rcvbuf 0
# NOTE: LZO commands are pushed by the Access Server at connect time.
# NOTE: The below line doesn't disable LZO.
comp-lzo no
verb 3
setenv PUSH_PEER_INFO
ca ca.crt
cert user.crt
key user.key
tls-auth tls.key 1

View File

@@ -4,7 +4,6 @@
name: SASjs Build
on:
push:
pull_request:
jobs:
@@ -22,19 +21,77 @@ jobs:
with:
node-version: ${{ matrix.node-version }}
cache: npm
- name: Check npm audit
run: npm audit --production --audit-level=low
- name: Install Dependencies
run: npm ci
- name: Check code style
run: npm run lint
- name: Run unit tests
run: npm test
- name: Generate coverage report
uses: artiomtr/jest-coverage-report-action@v2.0-rc.2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
- name: Build Package
run: npm run package:lib
env:
CI: true
- name: Install SSH Key
uses: shimataro/ssh-key-action@v2
with:
key: ${{ secrets.DCGITLAB_KEY }}
known_hosts: 'placeholder'
- name: Write VPN Files
run: |
echo "$CA_CRT" > .github/vpn/ca.crt
echo "$USER_CRT" > .github/vpn/user.crt
echo "$USER_KEY" > .github/vpn/user.key
echo "$TLS_KEY" > .github/vpn/tls.key
shell: bash
env:
CA_CRT: ${{ secrets.CA_CRT}}
USER_CRT: ${{ secrets.USER_CRT }}
USER_KEY: ${{ secrets.USER_KEY }}
TLS_KEY: ${{ secrets.TLS_KEY }}
- name: Install Open VPN
run: |
sudo apt install apt-transport-https
sudo wget https://swupdate.openvpn.net/repos/openvpn-repo-pkg-key.pub
sudo apt-key add openvpn-repo-pkg-key.pub
sudo wget -O /etc/apt/sources.list.d/openvpn3.list https://swupdate.openvpn.net/community/openvpn3/repos/openvpn3-focal.list
sudo apt update
sudo apt install openvpn3=16~beta+focal
- name: Start Open VPN 3
run: openvpn3 session-start --config .github/vpn/config.ovpn
- name: Deploy sasjs-tests
run: |
npm install -g replace-in-files-cli
cd sasjs-tests
replace-in-files --regex='"@sasjs/adapter".*' --replacement='"@sasjs/adapter":"latest",' ./package.json
npm i
replace-in-files --regex='"serverUrl".*' --replacement='"serverUrl":"${{ secrets.SASJS_SERVER_URL }}",' ./public/config.json
replace-in-files --regex='"userName".*' --replacement='"userName":"${{ secrets.SASJS_USERNAME }}",' ./public/config.json
replace-in-files --regex='"password".*' --replacement='"password":"${{ secrets.SASJS_PASSWORD }}",' ./public/config.json
replace-in-files --regex='"serverType".*' --replacement='"serverType":"SASJS",' ./public/config.json
npm run update:adapter && npm run build
scp -o stricthostkeychecking=no -r ./build/* ${{ secrets.DCGITLAB_DEPLOY_PATH_VIYA }}
- name: Run cypress on sasjs
run: |
replace-in-files --regex='"sasjsTestsUrl".*' --replacement='"sasjsTestsUrl":"${{ secrets.SASJS_TEST_URL_VIYA }}",' ./cypress.json
replace-in-files --regex='"username".*' --replacement='"username":"${{ secrets.SASJS_USERNAME }}",' ./cypress.json
replace-in-files --regex='"password".*' --replacement='"password":"${{ secrets.SASJS_PASSWORD }}",' ./cypress.json
sh ./sasjs-cypress-run.sh ${{ secrets.DISCORD_WEBHOOK }} https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}
# For some reason if coverage report action is run before other commands, those commands can't access the directories and files on which they depend on
- name: Generate coverage report
uses: artiomtr/jest-coverage-report-action@v2.0-rc.2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -14,12 +14,16 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Install Dependencies
run: npm ci
- name: Check code style
run: npm run lint
- name: Build Project
run: npm run build
- name: Semantic Release
uses: cycjimmy/semantic-release-action@v2
env:

View File

@@ -4,3 +4,13 @@ docs/
*.md
*.spec.ts
.all-contributorsrc
cypress/
.gitpod.yml
.prettierrc
cypress.json
jest.config.js
sasjs-cypress-run.sh
tsconfig.json
tslint.json
typedoc.json
webpack.config.js

View File

@@ -16,5 +16,5 @@ No PR (that involves a non-trivial code change) should be merged, unless all ite
- [ ] All `sasjs-cli` unit tests are passing (`npm test`).
- [ ] All `sasjs-tests` are passing (instructions available [here](https://github.com/sasjs/adapter/blob/master/sasjs-tests/README.md)).
- (CI Runs this) All `sasjs-tests` are passing. If you want to run it manually (instructions available [here](https://github.com/sasjs/adapter/blob/master/sasjs-tests/README.md)).
- [ ] [Data Controller](https://datacontroller.io) builds and is functional on both SAS 9 and Viya

11
cypress.json Normal file
View File

@@ -0,0 +1,11 @@
{
"chromeWebSecurity": false,
"defaultCommandTimeout": 20000,
"env": {
"sasjsTestsUrl": "",
"username": "",
"password": "",
"screenshotOnRunFailure": false,
"testingFinishTimeout": 600000
}
}

View File

@@ -0,0 +1,78 @@
const sasjsTestsUrl = Cypress.env('sasjsTestsUrl')
const username = Cypress.env('username')
const password = Cypress.env('password')
const testingFinishTimeout = Cypress.env('testingFinishTimeout')
context('sasjs-tests', function () {
this.beforeAll(() => {
cy.visit(sasjsTestsUrl)
})
this.beforeEach(() => {
cy.reload()
})
it('Should have all tests successfull', (done) => {
cy.get('body').then(($body) => {
if ($body.find('input[placeholder="User Name"]').length > 0) {
cy.get('input[placeholder="User Name"]').type(username)
cy.get('input[placeholder="Password"]').type(password)
cy.get('.submit-button').click()
}
cy.get('input[placeholder="User Name"]', { timeout: 40000 })
.should('not.exist')
.then(() => {
cy.get('.ui.massive.icon.primary.left.labeled.button')
.click()
.then(() => {
cy.get('.ui.massive.loading.primary.button', {
timeout: testingFinishTimeout
})
.should('not.exist')
.then(() => {
cy.get('span.icon.failed')
.should('not.exist')
.then(() => {
done()
})
})
})
})
})
})
it('Should have all tests successfull with debug on', (done) => {
cy.get('body').then(($body) => {
if ($body.find('input[placeholder="User Name"]').length > 0) {
cy.get('input[placeholder="User Name"]').type(username)
cy.get('input[placeholder="Password"]').type(password)
cy.get('.submit-button').click()
}
cy.get('.ui.fitted.toggle.checkbox label')
.click()
.then(() => {
cy.get('input[placeholder="User Name"]', { timeout: 40000 })
.should('not.exist')
.then(() => {
cy.get('.ui.massive.icon.primary.left.labeled.button')
.click()
.then(() => {
cy.get('.ui.massive.loading.primary.button', {
timeout: testingFinishTimeout
})
.should('not.exist')
.then(() => {
cy.get('span.icon.failed')
.should('not.exist')
.then(() => {
done()
})
})
})
})
})
})
})
})

View File

@@ -0,0 +1,31 @@
const wp = require('@cypress/webpack-preprocessor')
const webpackOptions = {
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
loaders: ['ts-loader'],
exclude: [/node_modules/]
},
{
test: /\.(html|css)$/,
loader: 'raw-loader',
exclude: /\.async\.(html|css)$/
},
{
test: /\.async\.(html|css)$/,
loaders: ['file?name=[name].[hash].[ext]', 'extract']
}
]
}
}
const options = {
webpackOptions
}
module.exports = wp(options)

42
cypress/plugins/index.js Normal file
View File

@@ -0,0 +1,42 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
const wp = require('@cypress/webpack-preprocessor')
/**
* @type {Cypress.PluginConfig}
*/
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
const options = {
webpackOptions: require('../webpack.config.js')
}
on('file:preprocessor', wp(options))
on('before:browser:launch', (browser = {}, launchOptions) => {
if (browser.name === 'chrome') {
launchOptions.args.push('--disable-site-isolation-trials')
launchOptions.args.push('--auto-open-devtools-for-tabs')
launchOptions.args.push('--aggressive-cache-discard')
launchOptions.args.push('--disable-cache')
launchOptions.args.push('--disable-application-cache')
launchOptions.args.push('--disable-offline-load-stale-cache')
launchOptions.args.push('--disk-cache-size=0')
return launchOptions
}
})
}

10
cypress/tsconfig.json Normal file
View File

@@ -0,0 +1,10 @@
{
"compilerOptions": {
"strict": true,
"baseUrl": "../node_modules",
"target": "es6",
"lib": ["es2019", "dom"],
"types": ["cypress"]
},
"include": ["**/*.ts"]
}

23
cypress/webpack.config.js Normal file
View File

@@ -0,0 +1,23 @@
module.exports = {
mode: 'development',
resolve: {
extensions: ['.ts', '.js']
},
module: {
rules: [
{
test: /\.ts$/,
exclude: [/node_modules/],
use: [
{
loader: 'ts-loader',
options: {
// skip typechecking for speed
transpileOnly: true
}
}
]
}
]
}
}

9534
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,15 +8,17 @@
"build": "rimraf build && rimraf node && mkdir node && copyfiles -u 1 \"./src/**/*\" ./node && webpack && rimraf build/src && rimraf node",
"package:lib": "npm run build && copyfiles ./package.json ./checkNodeVersion.js build && cd build && npm version \"5.0.0\" && npm pack",
"publish:lib": "npm run build && cd build && npm publish",
"lint:fix": "npx prettier --loglevel silent --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --loglevel silent --write \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
"lint": "npx prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --check \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
"lint:silent": "npx prettier --loglevel silent --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --loglevel silent --check \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
"lint:fix": "npx prettier --loglevel silent --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --loglevel silent --write \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --loglevel silent --write \"cypress/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
"lint": "npx prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --check \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --check \"cypress/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
"lint:silent": "npx prettier --loglevel silent --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --loglevel silent --check \"sasjs-tests/src/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\" && npx prettier --loglevel silent --check \"cypress/**/*.{ts,tsx,js,jsx,html,css,sass,less,json,yml,md,graphql}\"",
"test": "jest --silent --coverage",
"prepublishOnly": "cp -r ./build/* . && rm -rf ./build",
"postpublish": "git clean -fd",
"semantic-release": "semantic-release",
"typedoc": "node createTSDocs",
"prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks && git config core.autocrlf false || true"
"prepare": "git rev-parse --git-dir && git config core.hooksPath ./.git-hooks && git config core.autocrlf false || true",
"cypress": "cypress open",
"cy:run": "cypress run"
},
"publishConfig": {
"access": "public"
@@ -41,9 +43,13 @@
},
"license": "ISC",
"devDependencies": {
"@cypress/webpack-preprocessor": "5.9.1",
"@types/form-data": "2.5.0",
"cypress": "7.7.0",
"typedoc-neo-theme": "1.1.1",
"typedoc-plugin-external-module-name": "4.0.6",
"@types/axios": "0.14.0",
"@types/express": "4.17.13",
"@types/form-data": "2.5.0",
"@types/jest": "27.4.0",
"@types/mime": "2.0.3",
"@types/pem": "1.9.6",

10
sasjs-cypress-run.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/bash
if npm run cy:run -- --spec "cypress/integration/sasjs.tests.ts" ; then
echo "Cypress sasjs testing passed!"
else
curl -X POST --header "Content-Type:application/json" --data '{"username":"GitHub CI - Adapter SASJS-TESTS (FAIL)", "content":"Automated sasjs-tests failed on the @sasjs/adapter PR on following link.\n'$2'", "avatar_url":"https://i.ibb.co/Lpk7Xvq/error-outline.png"}' $1
echo "Cypress sasjs testing failed!"
exit 1
fi

File diff suppressed because it is too large Load Diff

View File

@@ -61,7 +61,7 @@ export const basicTests = (
'Should fail on first attempt and should log the user in on second attempt',
test: async () => {
await adapter.logOut()
await adapter.logIn('invalid', 'invalid')
await adapter.logIn('invalid', 'invalid').catch((err: any) => {})
return await adapter.logIn(userName, password)
},
assertion: (response: any) =>
@@ -161,26 +161,17 @@ export const basicTests = (
}
},
{
title: 'Request with extra attributes on JES approach',
description:
'Should complete successful request with extra attributes present in response',
title: 'Web request',
description: 'Should run the request with old web approach',
test: async () => {
const config: Partial<SASjsConfig> = {
useComputeApi: false
}
return await adapter.request(
'common/sendArr',
stringData,
config,
undefined,
undefined,
['file', 'data']
)
return await adapter.request('common/sendArr', stringData, config)
},
assertion: (response: any) => {
const responseKeys: any = Object.keys(response)
return responseKeys.includes('file') && responseKeys.includes('data')
return response.table1[0][0] === stringData.table1[0].col1
}
}
]

View File

@@ -1,9 +1,35 @@
import SASjs from '@sasjs/adapter'
import { TestSuite } from '@sasjs/test-framework'
const stringData: any = { table1: [{ col1: 'first col value' }] }
export const computeTests = (adapter: SASjs): TestSuite => ({
name: 'Compute',
tests: [
{
title: 'Compute API request',
description: 'Should run the request with compute API approach',
test: async () => {
return await adapter.request('common/sendArr', stringData)
},
assertion: (response: any) => {
return response.table1[0][0] === stringData.table1[0].col1
}
},
{
title: 'JES API request',
description: 'Should run the request with JES API approach',
test: async () => {
const config = {
useComputeApi: false
}
return await adapter.request('common/sendArr', stringData, config)
},
assertion: (response: any) => {
return response.table1[0][0] === stringData.table1[0].col1
}
},
{
title: 'Start Compute Job - not waiting for result',
description: 'Should start a compute job and return the session',

View File

@@ -138,7 +138,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
result =
result &&
res.table1[index][3] ===
(multipleRowsWithNulls.table1[index].col4 || ' ')
(multipleRowsWithNulls.table1[index].col4 || '')
})
return result
}
@@ -164,7 +164,7 @@ export const sendArrTests = (adapter: SASjs): TestSuite => ({
result =
result &&
res.table1[index][3] ===
(multipleColumnsWithNulls.table1[index].col4 || ' ')
(multipleColumnsWithNulls.table1[index].col4 || '')
})
return result
}
@@ -329,7 +329,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
result =
result &&
res.table1[index].COL4 ===
(multipleRowsWithNulls.table1[index].col4 || ' ')
(multipleRowsWithNulls.table1[index].col4 || '')
})
return result
}
@@ -358,7 +358,7 @@ export const sendObjTests = (adapter: SASjs): TestSuite => ({
result =
result &&
res.table1[index].COL4 ===
(multipleColumnsWithNulls.table1[index].col4 || ' ')
(multipleColumnsWithNulls.table1[index].col4 || '')
})
return result
}

View File

@@ -37,6 +37,8 @@ const moreSpecialCharData: any = {
]
}
const stringData: any = { table1: [{ col1: 'first col value' }] }
const getWideData = () => {
const cols: any = {}
for (let i = 1; i <= 10000; i++) {
@@ -269,6 +271,34 @@ export const specialCaseTests = (adapter: SASjs): TestSuite => ({
)
}
},
{
title: 'Request with extra attributes on JES approach',
description:
'Should complete successful request with extra attributes present in response',
test: async () => {
if (adapter.getSasjsConfig().serverType !== 'SASVIYA')
return Promise.resolve('skip')
const config = {
useComputeApi: false
}
return await adapter.request(
'common/sendArr',
stringData,
config,
undefined,
undefined,
['file', 'data']
)
},
assertion: (response: any) => {
if (response === 'skip') return true
const responseKeys: any = Object.keys(response)
return responseKeys.includes('file') && responseKeys.includes('data')
}
},
{
title: 'Special missing values',
description: 'Should support special missing values',

View File

@@ -257,6 +257,12 @@ export class AuthManager {
const isLoggedIn = loginResponse !== 'authErr'
const userName = isLoggedIn ? this.extractUserName(loginResponse) : ''
if (!isLoggedIn) {
//We will logout to make sure cookies are removed and login form is presented
//Residue can happen in case of session expiration
await this.logOut()
}
return { isLoggedIn, userName }
}

View File

@@ -92,7 +92,7 @@ export class WebJobExecutor extends BaseJobExecutor {
if (jobUri.length > 0) {
apiUrl += '&_job=' + jobUri
/**
* Using both _job and _program parameters will cause a conflict in the JES web app, as its not clear whether or not the server should make the extra fetch for the job uri.
* Using both _job and _program parameters will cause a conflict in the JES web app, as it's not clear whether or not the server should make the extra fetch for the job uri.
* To handle this, we add the extra underscore and recreate the _program variable in the SAS side of the SASjs adapter so it remains available for backend developers.
*/
apiUrl = apiUrl.replace('_program=', '__program=')
@@ -169,13 +169,17 @@ export class WebJobExecutor extends BaseJobExecutor {
? res.result.log.map((logLine: any) => logLine.line).join('\n')
: res.result.log
const resObj =
this.serverType === ServerType.Sasjs
? {
result: res.result._webout,
log: parsedSasjsServerLog
}
: res
const resObj = res
if (this.serverType === ServerType.Sasjs) {
if (res.result._webout < 1)
throw new JobExecutionError(
0,
'Job execution failed',
parsedSasjsServerLog
)
}
this.requestClient!.appendRequest(resObj, sasJob, config.debug)
let jsonResponse = res.result