mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-09 13:30:04 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd872e0e75 | ||
|
|
a14a1663fc | ||
|
|
2482a0c674 | ||
|
|
a19de50e67 | ||
|
|
860c9f907c | ||
|
|
d7126a6878 | ||
|
|
a69ebd0fd3 | ||
|
|
0f47326bb6 | ||
|
|
2d6efa2437 | ||
| bfefdb65a3 | |||
| bbe52562da | |||
| 48917bb4d9 | |||
| 81c3d2a3dc | |||
| 0052d5d340 | |||
| a633bda24d | |||
| e951ea0ab2 | |||
| b75ab13eb7 | |||
| 4de34cc8b0 | |||
| 23a789b383 | |||
| 156f1b1180 | |||
| 491e36d703 | |||
| 06b6e48a16 | |||
| 9bebd356ca | |||
| 171f9bc7b9 |
25
.github/vpn/config.ovpn
vendored
Normal file
25
.github/vpn/config.ovpn
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Client
|
||||||
|
client
|
||||||
|
tls-client
|
||||||
|
dev tun
|
||||||
|
# this will connect with whatever proto DNS tells us (https://community.openvpn.net/openvpn/ticket/934)
|
||||||
|
proto tcp
|
||||||
|
remote vpn.4gl.io 7494
|
||||||
|
resolv-retry infinite
|
||||||
|
cipher AES-256-CBC
|
||||||
|
auth SHA256
|
||||||
|
script-security 2
|
||||||
|
keepalive 10 120
|
||||||
|
remote-cert-tls server
|
||||||
|
|
||||||
|
# Keys
|
||||||
|
ca ca.crt
|
||||||
|
cert user.crt
|
||||||
|
key user.key
|
||||||
|
tls-auth tls.key 1
|
||||||
|
|
||||||
|
# Security
|
||||||
|
nobind
|
||||||
|
persist-key
|
||||||
|
persist-tun
|
||||||
|
verb 3
|
||||||
38
.github/workflows/build.yml
vendored
38
.github/workflows/build.yml
vendored
@@ -39,11 +39,33 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
|
|
||||||
- name: Install SSH Key
|
- name: Write VPN Files
|
||||||
uses: shimataro/ssh-key-action@v2
|
run: |
|
||||||
with:
|
echo "$CA_CRT" > .github/vpn/ca.crt
|
||||||
key: ${{ secrets.DCGITLAB_KEY }}
|
echo "$USER_CRT" > .github/vpn/user.crt
|
||||||
known_hosts: 'placeholder'
|
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-jammy.list
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install openvpn3=17~betaUb22042+jammy
|
||||||
|
|
||||||
|
- name: Start Open VPN 3
|
||||||
|
run: openvpn3 session-start --config .github/vpn/config.ovpn
|
||||||
|
|
||||||
|
- name: install pm2
|
||||||
|
run: npm i -g pm2
|
||||||
|
|
||||||
- name: Deploy sasjs-tests
|
- name: Deploy sasjs-tests
|
||||||
run: |
|
run: |
|
||||||
@@ -55,12 +77,12 @@ jobs:
|
|||||||
replace-in-files --regex='"userName".*' --replacement='"userName":"${{ secrets.SASJS_USERNAME }}",' ./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='"password".*' --replacement='"password":"${{ secrets.SASJS_PASSWORD }}",' ./public/config.json
|
||||||
replace-in-files --regex='"serverType".*' --replacement='"serverType":"SASJS",' ./public/config.json
|
replace-in-files --regex='"serverType".*' --replacement='"serverType":"SASJS",' ./public/config.json
|
||||||
npm run update:adapter && npm run build
|
npm run update:adapter
|
||||||
scp -o stricthostkeychecking=no -r ./build/* ${{ secrets.DCGITLAB_DEPLOY_PATH_VIYA }}
|
pm2 start --name sasjs-test npm -- start
|
||||||
|
|
||||||
- name: Run cypress on sasjs
|
- name: Run cypress on sasjs
|
||||||
run: |
|
run: |
|
||||||
replace-in-files --regex='"sasjsTestsUrl".*' --replacement='"sasjsTestsUrl":"${{ secrets.SASJS_TEST_URL_VIYA }}",' ./cypress.json
|
replace-in-files --regex='"sasjsTestsUrl".*' --replacement='"sasjsTestsUrl":"http://localhost:3000",' ./cypress.json
|
||||||
replace-in-files --regex='"username".*' --replacement='"username":"${{ secrets.SASJS_USERNAME }}",' ./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
|
replace-in-files --regex='"password".*' --replacement='"password":"${{ secrets.SASJS_PASSWORD }}",' ./cypress.json
|
||||||
sh ./sasjs-tests/sasjs-cypress-run.sh ${{ secrets.MATRIX_TOKEN }} https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}
|
sh ./sasjs-tests/sasjs-cypress-run.sh ${{ secrets.MATRIX_TOKEN }} https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}
|
||||||
|
|||||||
@@ -14,65 +14,83 @@ context('sasjs-tests', function () {
|
|||||||
|
|
||||||
it('Should have all tests successfull', (done) => {
|
it('Should have all tests successfull', (done) => {
|
||||||
cy.get('body').then(($body) => {
|
cy.get('body').then(($body) => {
|
||||||
if ($body.find('input[placeholder="User Name"]').length > 0) {
|
cy.wait(1000).then(() => {
|
||||||
cy.get('input[placeholder="User Name"]').type(username)
|
const startButton = $body.find(
|
||||||
cy.get('input[placeholder="Password"]').type(password)
|
'.ui.massive.icon.primary.left.labeled.button'
|
||||||
cy.get('.submit-button').click()
|
)[0]
|
||||||
}
|
|
||||||
|
|
||||||
cy.get('input[placeholder="User Name"]', { timeout: 40000 })
|
if (
|
||||||
.should('not.exist')
|
!startButton ||
|
||||||
.then(() => {
|
(startButton && !Cypress.dom.isVisible(startButton))
|
||||||
cy.get('.ui.massive.icon.primary.left.labeled.button')
|
) {
|
||||||
.click()
|
cy.get('input[placeholder="User Name"]').type(username)
|
||||||
.then(() => {
|
cy.get('input[placeholder="Password"]').type(password)
|
||||||
cy.get('.ui.massive.loading.primary.button', {
|
cy.get('.submit-button').click()
|
||||||
timeout: testingFinishTimeout
|
}
|
||||||
})
|
|
||||||
.should('not.exist')
|
cy.get('input[placeholder="User Name"]', { timeout: 40000 })
|
||||||
.then(() => {
|
.should('not.exist')
|
||||||
cy.get('span.icon.failed')
|
.then(() => {
|
||||||
.should('not.exist')
|
cy.get('.ui.massive.icon.primary.left.labeled.button')
|
||||||
.then(() => {
|
.click()
|
||||||
done()
|
.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) => {
|
it('Should have all tests successfull with debug on', (done) => {
|
||||||
cy.get('body').then(($body) => {
|
cy.get('body').then(($body) => {
|
||||||
if ($body.find('input[placeholder="User Name"]').length > 0) {
|
cy.wait(1000).then(() => {
|
||||||
cy.get('input[placeholder="User Name"]').type(username)
|
const startButton = $body.find(
|
||||||
cy.get('input[placeholder="Password"]').type(password)
|
'.ui.massive.icon.primary.left.labeled.button'
|
||||||
cy.get('.submit-button').click()
|
)[0]
|
||||||
}
|
|
||||||
|
|
||||||
cy.get('.ui.fitted.toggle.checkbox label')
|
if (
|
||||||
.click()
|
!startButton ||
|
||||||
.then(() => {
|
(startButton && !Cypress.dom.isVisible(startButton))
|
||||||
cy.get('input[placeholder="User Name"]', { timeout: 40000 })
|
) {
|
||||||
.should('not.exist')
|
cy.get('input[placeholder="User Name"]').type(username)
|
||||||
.then(() => {
|
cy.get('input[placeholder="Password"]').type(password)
|
||||||
cy.get('.ui.massive.icon.primary.left.labeled.button')
|
cy.get('.submit-button').click()
|
||||||
.click()
|
}
|
||||||
.then(() => {
|
|
||||||
cy.get('.ui.massive.loading.primary.button', {
|
cy.get('.ui.fitted.toggle.checkbox label')
|
||||||
timeout: testingFinishTimeout
|
.click()
|
||||||
})
|
.then(() => {
|
||||||
.should('not.exist')
|
cy.get('input[placeholder="User Name"]', { timeout: 40000 })
|
||||||
.then(() => {
|
.should('not.exist')
|
||||||
cy.get('span.icon.failed')
|
.then(() => {
|
||||||
.should('not.exist')
|
cy.get('.ui.massive.icon.primary.left.labeled.button')
|
||||||
.then(() => {
|
.click()
|
||||||
done()
|
.then(() => {
|
||||||
})
|
cy.get('.ui.massive.loading.primary.button', {
|
||||||
|
timeout: testingFinishTimeout
|
||||||
})
|
})
|
||||||
})
|
.should('not.exist')
|
||||||
})
|
.then(() => {
|
||||||
})
|
cy.get('span.icon.failed')
|
||||||
|
.should('not.exist')
|
||||||
|
.then(() => {
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
25
cypress/support/commands.js
Normal file
25
cypress/support/commands.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
// ***********************************************
|
||||||
|
// This example commands.js shows you how to
|
||||||
|
// create various custom commands and overwrite
|
||||||
|
// existing commands.
|
||||||
|
//
|
||||||
|
// For more comprehensive examples of custom
|
||||||
|
// commands please read more here:
|
||||||
|
// https://on.cypress.io/custom-commands
|
||||||
|
// ***********************************************
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a parent command --
|
||||||
|
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a child command --
|
||||||
|
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a dual command --
|
||||||
|
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This will overwrite an existing command --
|
||||||
|
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||||
20
cypress/support/index.js
Normal file
20
cypress/support/index.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
// ***********************************************************
|
||||||
|
// This example support/index.js is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
import './commands'
|
||||||
|
|
||||||
|
// Alternatively you can use CommonJS syntax:
|
||||||
|
// require('./commands')
|
||||||
14
package-lock.json
generated
14
package-lock.json
generated
@@ -32,7 +32,7 @@
|
|||||||
"node-polyfill-webpack-plugin": "1.1.4",
|
"node-polyfill-webpack-plugin": "1.1.4",
|
||||||
"path": "0.12.7",
|
"path": "0.12.7",
|
||||||
"pem": "1.14.5",
|
"pem": "1.14.5",
|
||||||
"prettier": "2.7.1",
|
"prettier": "2.8.7",
|
||||||
"process": "0.11.10",
|
"process": "0.11.10",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"semantic-release": "19.0.3",
|
"semantic-release": "19.0.3",
|
||||||
@@ -13925,9 +13925,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/prettier": {
|
"node_modules/prettier": {
|
||||||
"version": "2.7.1",
|
"version": "2.8.7",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz",
|
||||||
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
"integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"prettier": "bin-prettier.js"
|
"prettier": "bin-prettier.js"
|
||||||
@@ -27416,9 +27416,9 @@
|
|||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
"version": "2.7.1",
|
"version": "2.8.7",
|
||||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz",
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.7.tgz",
|
||||||
"integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==",
|
"integrity": "sha512-yPngTo3aXUUmyuTjeTUT75txrf+aMh9FiD7q9ZE/i6r0bPb22g4FsE6Y338PQX1bmfy08i9QQCB7/rcUAVntfw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"pretty-bytes": {
|
"pretty-bytes": {
|
||||||
|
|||||||
@@ -60,7 +60,7 @@
|
|||||||
"node-polyfill-webpack-plugin": "1.1.4",
|
"node-polyfill-webpack-plugin": "1.1.4",
|
||||||
"path": "0.12.7",
|
"path": "0.12.7",
|
||||||
"pem": "1.14.5",
|
"pem": "1.14.5",
|
||||||
"prettier": "2.7.1",
|
"prettier": "2.8.7",
|
||||||
"process": "0.11.10",
|
"process": "0.11.10",
|
||||||
"rimraf": "3.0.2",
|
"rimraf": "3.0.2",
|
||||||
"semantic-release": "19.0.3",
|
"semantic-release": "19.0.3",
|
||||||
|
|||||||
5
sasjs-tests/.gitignore
vendored
5
sasjs-tests/.gitignore
vendored
@@ -13,10 +13,7 @@
|
|||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env.local
|
.env.*
|
||||||
.env.development.local
|
|
||||||
.env.test.local
|
|
||||||
.env.production.local
|
|
||||||
|
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
|
|||||||
1357
sasjs-tests/package-lock.json
generated
1357
sasjs-tests/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -3,10 +3,10 @@
|
|||||||
"password": "",
|
"password": "",
|
||||||
"sasJsConfig": {
|
"sasJsConfig": {
|
||||||
"serverUrl": "",
|
"serverUrl": "",
|
||||||
"appLoc": "/Public/app/adapter-tests",
|
"appLoc": "/Public/app/adapter-tests/services",
|
||||||
"serverType": "SASJS",
|
"serverType": "SASJS",
|
||||||
"debug": false,
|
"debug": false,
|
||||||
"contextName": "sasjs adapter compute context",
|
"contextName": "sasjs adapter compute context",
|
||||||
"useComputeApi": true
|
"useComputeApi": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,29 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://cli.sasjs.io/sasjsconfig-schema.json",
|
"$schema": "https://cli.sasjs.io/sasjsconfig-schema.json",
|
||||||
"serviceConfig": {
|
"serviceConfig": {
|
||||||
"serviceFolders": [
|
"serviceFolders": ["sasjs/common"]
|
||||||
"sasjs/common"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
"defaultTarget": "4gl",
|
"defaultTarget": "4gl",
|
||||||
"targets": [
|
"targets": [
|
||||||
{
|
{
|
||||||
"name": "4gl",
|
"name": "4gl",
|
||||||
"serverType": "SASJS",
|
|
||||||
"serverUrl": "https://sas9.4gl.io",
|
"serverUrl": "https://sas9.4gl.io",
|
||||||
"appLoc": "/Public/app/adapter-tests"
|
"serverType": "SASJS",
|
||||||
|
"httpsAgentOptions": {
|
||||||
|
"allowInsecureRequests": false
|
||||||
|
},
|
||||||
|
"appLoc": "/Public/app/adapter-tests",
|
||||||
|
"deployConfig": {
|
||||||
|
"deployServicePack": true,
|
||||||
|
"deployScripts": []
|
||||||
|
},
|
||||||
|
"streamConfig": {
|
||||||
|
"streamWeb": true,
|
||||||
|
"streamWebFolder": "webv",
|
||||||
|
"webSourcePath": "build",
|
||||||
|
"streamServiceName": "adapter-tests",
|
||||||
|
"assetPaths": []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,11 +29,33 @@ describe('SASViyaApiClient', () => {
|
|||||||
jest
|
jest
|
||||||
.spyOn(requestClient, 'get')
|
.spyOn(requestClient, 'get')
|
||||||
.mockImplementation(() => Promise.reject('Not Found'))
|
.mockImplementation(() => Promise.reject('Not Found'))
|
||||||
|
|
||||||
const error = await sasViyaApiClient
|
const error = await sasViyaApiClient
|
||||||
.createFolder('test', '/foo')
|
.createFolder('test', '/foo')
|
||||||
.catch((e: any) => e)
|
.catch((e: any) => e)
|
||||||
|
|
||||||
expect(error).toBeInstanceOf(RootFolderNotFoundError)
|
expect(error).toBeInstanceOf(RootFolderNotFoundError)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should throw an error when ', async () => {
|
||||||
|
const testMessage1 = 'test message 1'
|
||||||
|
const testMessage2 = 'test message 2.'
|
||||||
|
|
||||||
|
jest.spyOn(requestClient, 'post').mockImplementation(() => {
|
||||||
|
return Promise.reject({
|
||||||
|
message: testMessage1,
|
||||||
|
response: { data: { message: testMessage2 }, status: 409 }
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const error = await sasViyaApiClient
|
||||||
|
.createFolder('test', '/foo')
|
||||||
|
.catch((e: any) => e)
|
||||||
|
|
||||||
|
const expectedError = `${testMessage1}. ${testMessage2} To override, please set "isForced" to "true".`
|
||||||
|
|
||||||
|
expect(error).toEqual(expectedError)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const setupMocks = () => {
|
const setupMocks = () => {
|
||||||
|
|||||||
@@ -378,12 +378,14 @@ export class SASViyaApiClient {
|
|||||||
isForced?: boolean
|
isForced?: boolean
|
||||||
): Promise<Folder> {
|
): Promise<Folder> {
|
||||||
const logger = process.logger || console
|
const logger = process.logger || console
|
||||||
|
|
||||||
if (!parentFolderPath && !parentFolderUri) {
|
if (!parentFolderPath && !parentFolderUri) {
|
||||||
throw new Error('Path or URI of the parent folder is required.')
|
throw new Error('Path or URI of the parent folder is required.')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!parentFolderUri && parentFolderPath) {
|
if (!parentFolderUri && parentFolderPath) {
|
||||||
parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken)
|
parentFolderUri = await this.getFolderUri(parentFolderPath, accessToken)
|
||||||
|
|
||||||
if (!parentFolderUri) {
|
if (!parentFolderUri) {
|
||||||
logger.info(
|
logger.info(
|
||||||
`Parent folder at path '${parentFolderPath}' is not present.`
|
`Parent folder at path '${parentFolderPath}' is not present.`
|
||||||
@@ -394,6 +396,7 @@ export class SASViyaApiClient {
|
|||||||
parentFolderPath.lastIndexOf('/')
|
parentFolderPath.lastIndexOf('/')
|
||||||
)
|
)
|
||||||
const newFolderName = `${parentFolderPath.split('/').pop()}`
|
const newFolderName = `${parentFolderPath.split('/').pop()}`
|
||||||
|
|
||||||
if (newParentFolderPath === '') {
|
if (newParentFolderPath === '') {
|
||||||
throw new RootFolderNotFoundError(
|
throw new RootFolderNotFoundError(
|
||||||
parentFolderPath,
|
parentFolderPath,
|
||||||
@@ -401,20 +404,24 @@ export class SASViyaApiClient {
|
|||||||
accessToken
|
accessToken
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`Creating parent folder:\n'${newFolderName}' in '${newParentFolderPath}'`
|
`Creating parent folder:\n'${newFolderName}' in '${newParentFolderPath}'`
|
||||||
)
|
)
|
||||||
|
|
||||||
const parentFolder = await this.createFolder(
|
const parentFolder = await this.createFolder(
|
||||||
newFolderName,
|
newFolderName,
|
||||||
newParentFolderPath,
|
newParentFolderPath,
|
||||||
undefined,
|
undefined,
|
||||||
accessToken
|
accessToken
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
`Parent folder '${newFolderName}' has been successfully created.`
|
`Parent folder '${newFolderName}' has been successfully created.`
|
||||||
)
|
)
|
||||||
|
|
||||||
parentFolderUri = `/folders/folders/${parentFolder.id}`
|
parentFolderUri = `/folders/folders/${parentFolder.id}`
|
||||||
} else if (isForced && accessToken) {
|
} else if (isForced) {
|
||||||
const folderPath = parentFolderPath + '/' + folderName
|
const folderPath = parentFolderPath + '/' + folderName
|
||||||
const folderUri = await this.getFolderUri(folderPath, accessToken)
|
const folderUri = await this.getFolderUri(folderPath, accessToken)
|
||||||
|
|
||||||
@@ -427,8 +434,8 @@ export class SASViyaApiClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const { result: createFolderResponse } =
|
const { result: createFolderResponse } = await this.requestClient
|
||||||
await this.requestClient.post<Folder>(
|
.post<Folder>(
|
||||||
`/folders/folders?parentFolderUri=${parentFolderUri}`,
|
`/folders/folders?parentFolderUri=${parentFolderUri}`,
|
||||||
{
|
{
|
||||||
name: folderName,
|
name: folderName,
|
||||||
@@ -436,12 +443,34 @@ export class SASViyaApiClient {
|
|||||||
},
|
},
|
||||||
accessToken
|
accessToken
|
||||||
)
|
)
|
||||||
|
.catch((err) => {
|
||||||
|
const { message, response } = err
|
||||||
|
|
||||||
|
if (message && response && response.data && response.data.message) {
|
||||||
|
const { status } = response
|
||||||
|
const { message: responseMessage } = response.data
|
||||||
|
const messages = [message, responseMessage].map((mes: string) =>
|
||||||
|
/\.$/.test(mes) ? mes : `${mes}.`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!isForced && status === 409) {
|
||||||
|
messages.push(`To override, please set "isForced" to "true".`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const errMessage = messages.join(' ')
|
||||||
|
|
||||||
|
throw errMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err
|
||||||
|
})
|
||||||
|
|
||||||
// update folder map with newly created folder.
|
// update folder map with newly created folder.
|
||||||
await this.populateFolderMap(
|
await this.populateFolderMap(
|
||||||
`${parentFolderPath}/${folderName}`,
|
`${parentFolderPath}/${folderName}`,
|
||||||
accessToken
|
accessToken
|
||||||
)
|
)
|
||||||
|
|
||||||
return createFolderResponse
|
return createFolderResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -900,7 +929,7 @@ export class SASViyaApiClient {
|
|||||||
return `/folders/folders/${folderDetails.id}`
|
return `/folders/folders/${folderDetails.id}`
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getRecycleBinUri(accessToken: string) {
|
private async getRecycleBinUri(accessToken?: string) {
|
||||||
const url = '/folders/folders/@myRecycleBin'
|
const url = '/folders/folders/@myRecycleBin'
|
||||||
|
|
||||||
const { result: folder } = await this.requestClient
|
const { result: folder } = await this.requestClient
|
||||||
@@ -984,7 +1013,7 @@ export class SASViyaApiClient {
|
|||||||
sourceFolder: string,
|
sourceFolder: string,
|
||||||
targetParentFolder: string,
|
targetParentFolder: string,
|
||||||
targetFolderName: string,
|
targetFolderName: string,
|
||||||
accessToken: string
|
accessToken?: string
|
||||||
) {
|
) {
|
||||||
// If target path is an existing folder, than keep source folder name, othervise rename it with given target folder name
|
// If target path is an existing folder, than keep source folder name, othervise rename it with given target folder name
|
||||||
const sourceFolderName = sourceFolder.split('/').pop() as string
|
const sourceFolderName = sourceFolder.split('/').pop() as string
|
||||||
@@ -1051,7 +1080,7 @@ export class SASViyaApiClient {
|
|||||||
* @param folderPath - the full path (eg `/Public/example/deleteThis`) of the folder to be deleted.
|
* @param folderPath - the full path (eg `/Public/example/deleteThis`) of the folder to be deleted.
|
||||||
* @param accessToken - an access token for authorizing the request.
|
* @param accessToken - an access token for authorizing the request.
|
||||||
*/
|
*/
|
||||||
public async deleteFolder(folderPath: string, accessToken: string) {
|
public async deleteFolder(folderPath: string, accessToken?: string) {
|
||||||
const recycleBinUri = await this.getRecycleBinUri(accessToken)
|
const recycleBinUri = await this.getRecycleBinUri(accessToken)
|
||||||
const folderName = folderPath.split('/').pop() || ''
|
const folderName = folderPath.split('/').pop() || ''
|
||||||
const date = new Date()
|
const date = new Date()
|
||||||
|
|||||||
19
src/SASjs.ts
19
src/SASjs.ts
@@ -337,13 +337,16 @@ export default class SASjs {
|
|||||||
sasApiClient?: SASViyaApiClient,
|
sasApiClient?: SASViyaApiClient,
|
||||||
isForced?: boolean
|
isForced?: boolean
|
||||||
) {
|
) {
|
||||||
if (sasApiClient)
|
if (sasApiClient) {
|
||||||
return await sasApiClient.createFolder(
|
return await sasApiClient.createFolder(
|
||||||
folderName,
|
folderName,
|
||||||
parentFolderPath,
|
parentFolderPath,
|
||||||
parentFolderUri,
|
parentFolderUri,
|
||||||
accessToken
|
accessToken,
|
||||||
|
isForced
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return await this.sasViyaApiClient!.createFolder(
|
return await this.sasViyaApiClient!.createFolder(
|
||||||
folderName,
|
folderName,
|
||||||
parentFolderPath,
|
parentFolderPath,
|
||||||
@@ -783,13 +786,11 @@ export default class SASjs {
|
|||||||
this.isMethodSupported('deployServicePack', [ServerType.SasViya])
|
this.isMethodSupported('deployServicePack', [ServerType.SasViya])
|
||||||
|
|
||||||
let sasApiClient: any = null
|
let sasApiClient: any = null
|
||||||
|
|
||||||
if (serverUrl || appLoc) {
|
if (serverUrl || appLoc) {
|
||||||
if (!serverUrl) {
|
if (!serverUrl) serverUrl = this.sasjsConfig.serverUrl
|
||||||
serverUrl = this.sasjsConfig.serverUrl
|
if (!appLoc) appLoc = this.sasjsConfig.appLoc
|
||||||
}
|
|
||||||
if (!appLoc) {
|
|
||||||
appLoc = this.sasjsConfig.appLoc
|
|
||||||
}
|
|
||||||
if (this.sasjsConfig.serverType === ServerType.SasViya) {
|
if (this.sasjsConfig.serverType === ServerType.SasViya) {
|
||||||
sasApiClient = new SASViyaApiClient(
|
sasApiClient = new SASViyaApiClient(
|
||||||
serverUrl,
|
serverUrl,
|
||||||
@@ -807,11 +808,13 @@ export default class SASjs {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let sasClientConfig: any = null
|
let sasClientConfig: any = null
|
||||||
|
|
||||||
if (this.sasjsConfig.serverType === ServerType.SasViya) {
|
if (this.sasjsConfig.serverType === ServerType.SasViya) {
|
||||||
sasClientConfig = this.sasViyaApiClient!.getConfig()
|
sasClientConfig = this.sasViyaApiClient!.getConfig()
|
||||||
} else if (this.sasjsConfig.serverType === ServerType.Sas9) {
|
} else if (this.sasjsConfig.serverType === ServerType.Sas9) {
|
||||||
sasClientConfig = this.sas9ApiClient!.getConfig()
|
sasClientConfig = this.sas9ApiClient!.getConfig()
|
||||||
}
|
}
|
||||||
|
|
||||||
serverUrl = sasClientConfig.serverUrl
|
serverUrl = sasClientConfig.serverUrl
|
||||||
appLoc = sasClientConfig.rootFolderName as string
|
appLoc = sasClientConfig.rootFolderName as string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,24 @@
|
|||||||
import * as NodeFormData from 'form-data'
|
import * as NodeFormData from 'form-data'
|
||||||
import { AuthConfig, ServerType, ServicePackSASjs } from '@sasjs/utils/types'
|
import { AuthConfig, ServerType, ServicePackSASjs } from '@sasjs/utils/types'
|
||||||
|
import { prefixMessage } from '@sasjs/utils/error'
|
||||||
import { ExecutionQuery } from './types'
|
import { ExecutionQuery } from './types'
|
||||||
import { RequestClient } from './request/RequestClient'
|
import { RequestClient } from './request/RequestClient'
|
||||||
import { getAccessTokenForSasjs } from './auth/getAccessTokenForSasjs'
|
import { getAccessTokenForSasjs } from './auth/getAccessTokenForSasjs'
|
||||||
import { refreshTokensForSasjs } from './auth/refreshTokensForSasjs'
|
import { refreshTokensForSasjs } from './auth/refreshTokensForSasjs'
|
||||||
import { getTokens } from './auth/getTokens'
|
import { getTokens } from './auth/getTokens'
|
||||||
|
|
||||||
|
// TODO: move to sasjs/utils
|
||||||
|
export interface SASjsAuthResponse {
|
||||||
|
access_token: string
|
||||||
|
refresh_token: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ScriptExecutionResult {
|
||||||
|
log: string
|
||||||
|
webout?: string
|
||||||
|
printOutput?: string
|
||||||
|
}
|
||||||
|
|
||||||
export class SASjsApiClient {
|
export class SASjsApiClient {
|
||||||
constructor(private requestClient: RequestClient) {}
|
constructor(private requestClient: RequestClient) {}
|
||||||
|
|
||||||
@@ -118,18 +131,28 @@ export class SASjsApiClient {
|
|||||||
code: string,
|
code: string,
|
||||||
runTime: string = 'sas',
|
runTime: string = 'sas',
|
||||||
authConfig?: AuthConfig
|
authConfig?: AuthConfig
|
||||||
) {
|
): Promise<ScriptExecutionResult> {
|
||||||
const access_token = await this.getAccessTokenForRequest(authConfig)
|
const access_token = await this.getAccessTokenForRequest(authConfig)
|
||||||
|
const executionResult: ScriptExecutionResult = { log: '' }
|
||||||
let parsedSasjsServerLog = ''
|
|
||||||
|
|
||||||
await this.requestClient
|
await this.requestClient
|
||||||
.post('SASjsApi/code/execute', { code, runTime }, access_token)
|
.post('SASjsApi/code/execute', { code, runTime }, access_token)
|
||||||
.then((res: any) => {
|
.then((res: any) => {
|
||||||
if (res.log) parsedSasjsServerLog = res.log
|
const { log, printOutput, result: webout } = res
|
||||||
|
|
||||||
|
executionResult.log = log
|
||||||
|
|
||||||
|
if (printOutput) executionResult.printOutput = printOutput
|
||||||
|
if (webout) executionResult.webout = webout
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
throw prefixMessage(
|
||||||
|
err,
|
||||||
|
'Error while sending POST request to execute code. '
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
return parsedSasjsServerLog
|
return executionResult
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -152,9 +175,3 @@ export class SASjsApiClient {
|
|||||||
return refreshTokensForSasjs(this.requestClient, refreshToken)
|
return refreshTokensForSasjs(this.requestClient, refreshToken)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo move to sasjs/utils
|
|
||||||
export interface SASjsAuthResponse {
|
|
||||||
access_token: string
|
|
||||||
refresh_token: string
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -13,7 +13,11 @@ import { generateFileUploadForm } from '../file/generateFileUploadForm'
|
|||||||
|
|
||||||
import { RequestClient } from '../request/RequestClient'
|
import { RequestClient } from '../request/RequestClient'
|
||||||
|
|
||||||
import { isRelativePath, appendExtraResponseAttributes } from '../utils'
|
import {
|
||||||
|
isRelativePath,
|
||||||
|
appendExtraResponseAttributes,
|
||||||
|
getValidJson
|
||||||
|
} from '../utils'
|
||||||
import { BaseJobExecutor } from './JobExecutor'
|
import { BaseJobExecutor } from './JobExecutor'
|
||||||
|
|
||||||
export class SasjsJobExecutor extends BaseJobExecutor {
|
export class SasjsJobExecutor extends BaseJobExecutor {
|
||||||
@@ -89,12 +93,16 @@ export class SasjsJobExecutor extends BaseJobExecutor {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { result } = res.result
|
||||||
|
if (result && result.trim()) res.result = getValidJson(result)
|
||||||
|
|
||||||
this.requestClient!.appendRequest(res, sasJob, config.debug)
|
this.requestClient!.appendRequest(res, sasJob, config.debug)
|
||||||
|
|
||||||
const responseObject = appendExtraResponseAttributes(
|
const responseObject = appendExtraResponseAttributes(
|
||||||
res,
|
res,
|
||||||
extraResponseAttributes
|
extraResponseAttributes
|
||||||
)
|
)
|
||||||
|
|
||||||
resolve(responseObject)
|
resolve(responseObject)
|
||||||
})
|
})
|
||||||
.catch(async (e: Error) => {
|
.catch(async (e: Error) => {
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
import { RequestClient } from './RequestClient'
|
import { RequestClient } from './RequestClient'
|
||||||
import { AxiosResponse } from 'axios'
|
import { AxiosResponse } from 'axios'
|
||||||
import { SASJS_LOGS_SEPARATOR, getValidJson } from '../utils'
|
import { SASJS_LOGS_SEPARATOR } from '../utils'
|
||||||
|
|
||||||
|
interface SasjsParsedResponse<T> {
|
||||||
|
result: T
|
||||||
|
log: string
|
||||||
|
etag: string
|
||||||
|
status: number
|
||||||
|
printOutput?: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Specific request client for SASJS.
|
* Specific request client for SASJS.
|
||||||
@@ -27,7 +35,7 @@ export class SasjsRequestClient extends RequestClient {
|
|||||||
protected parseResponse<T>(response: AxiosResponse<any>) {
|
protected parseResponse<T>(response: AxiosResponse<any>) {
|
||||||
const etag = response?.headers ? response.headers['etag'] : ''
|
const etag = response?.headers ? response.headers['etag'] : ''
|
||||||
let parsedResponse = {}
|
let parsedResponse = {}
|
||||||
let log
|
let webout, log, printOutput
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (typeof response.data === 'string') {
|
if (typeof response.data === 'string') {
|
||||||
@@ -38,17 +46,26 @@ export class SasjsRequestClient extends RequestClient {
|
|||||||
} catch {
|
} catch {
|
||||||
if (response.data.includes(SASJS_LOGS_SEPARATOR)) {
|
if (response.data.includes(SASJS_LOGS_SEPARATOR)) {
|
||||||
const splittedResponse = response.data.split(SASJS_LOGS_SEPARATOR)
|
const splittedResponse = response.data.split(SASJS_LOGS_SEPARATOR)
|
||||||
|
|
||||||
|
webout = splittedResponse[0]
|
||||||
|
if (webout !== undefined) parsedResponse = webout
|
||||||
|
|
||||||
log = splittedResponse[1]
|
log = splittedResponse[1]
|
||||||
if (splittedResponse[0].trim())
|
printOutput = splittedResponse[2]
|
||||||
parsedResponse = getValidJson(splittedResponse[0])
|
} else {
|
||||||
} else parsedResponse = response.data
|
parsedResponse = response.data
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
const returnResult: SasjsParsedResponse<T> = {
|
||||||
result: parsedResponse as T,
|
result: parsedResponse as T,
|
||||||
log,
|
log,
|
||||||
etag,
|
etag,
|
||||||
status: response.status
|
status: response.status
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (printOutput) returnResult.printOutput = printOutput
|
||||||
|
|
||||||
|
return returnResult
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ export const getValidJson = (str: string | object): object => {
|
|||||||
return JSON.parse(str)
|
return JSON.parse(str)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
if (e instanceof JsonParseArrayError) throw e
|
if (e instanceof JsonParseArrayError) throw e
|
||||||
|
|
||||||
throw new InvalidJsonError()
|
throw new InvalidJsonError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user