mirror of
https://github.com/sasjs/adapter.git
synced 2025-12-16 11:14:36 +00:00
Compare commits
1 Commits
v2.11.2
...
debug-sasj
| Author | SHA1 | Date | |
|---|---|---|---|
| 2c6198ae25 |
1
package-lock.json
generated
1
package-lock.json
generated
@@ -6351,7 +6351,6 @@
|
|||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"semver": "^7.3.5",
|
"semver": "^7.3.5",
|
||||||
"ssri": "^8.0.1",
|
"ssri": "^8.0.1",
|
||||||
"tar": "^6.1.0",
|
|
||||||
"treeverse": "^1.0.4",
|
"treeverse": "^1.0.4",
|
||||||
"walk-up-path": "^1.0.0"
|
"walk-up-path": "^1.0.0"
|
||||||
}
|
}
|
||||||
|
|||||||
8247
sasjs-tests/package-lock.json
generated
8247
sasjs-tests/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@
|
|||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
"eject": "react-scripts eject",
|
"eject": "react-scripts eject",
|
||||||
"update:adapter": "cd .. && npm run package:lib && cd sasjs-tests && npm i ../build/sasjs-adapter-5.0.0.tgz",
|
"update:adapter": "cd .. && npm run package:lib && cd sasjs-tests && npm i ../build/sasjs-adapter-5.0.0.tgz",
|
||||||
"deploy:tests": "rsync -avhe ssh ./build/* --delete $SSH_ACCOUNT:$DEPLOY_PATH || npm run deploy:tests-win",
|
"deploy:tests": "rsync -avhe ssh ./build/* --delete sabhas@sas.analytium.co.uk:/var/www/html/sabhas/sasjs-test || npm run deploy:tests-win",
|
||||||
"deploy:tests-win": "scp %DEPLOY_PATH% ./build/*",
|
"deploy:tests-win": "scp %DEPLOY_PATH% ./build/*",
|
||||||
"deploy": "npm run update:adapter && npm run build && npm run deploy:tests"
|
"deploy": "npm run update:adapter && npm run build && npm run deploy:tests"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
"userName": "",
|
"userName": "",
|
||||||
"password": "",
|
"password": "",
|
||||||
"sasJsConfig": {
|
"sasJsConfig": {
|
||||||
"serverUrl": "",
|
"serverUrl": "https://sas.analytium.co.uk/",
|
||||||
"appLoc": "/Public/app",
|
"appLoc": "/Public/app",
|
||||||
"serverType": "SASVIYA",
|
"serverType": "SASVIYA",
|
||||||
"debug": false,
|
"debug": false,
|
||||||
|
|||||||
@@ -14,16 +14,16 @@ const App = (): ReactElement<{}> => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (adapter) {
|
if (adapter) {
|
||||||
const testSuites = [
|
const testSuites = [
|
||||||
basicTests(adapter, config.userName, config.password),
|
// basicTests(adapter, config.userName, config.password),
|
||||||
sendArrTests(adapter),
|
// sendArrTests(adapter),
|
||||||
sendObjTests(adapter),
|
// sendObjTests(adapter),
|
||||||
specialCaseTests(adapter),
|
// specialCaseTests(adapter),
|
||||||
sasjsRequestTests(adapter)
|
sasjsRequestTests(adapter)
|
||||||
]
|
]
|
||||||
|
|
||||||
if (adapter.getSasjsConfig().serverType === 'SASVIYA') {
|
// if (adapter.getSasjsConfig().serverType === 'SASVIYA') {
|
||||||
testSuites.push(computeTests(adapter))
|
// testSuites.push(computeTests(adapter))
|
||||||
}
|
// }
|
||||||
|
|
||||||
setTestSuites(testSuites)
|
setTestSuites(testSuites)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import SASjs, { LoginMechanism, SASjsConfig } from '@sasjs/adapter'
|
import SASjs, { SASjsConfig } from '@sasjs/adapter'
|
||||||
import { TestSuite } from '@sasjs/test-framework'
|
import { TestSuite } from '@sasjs/test-framework'
|
||||||
import { ServerType } from '@sasjs/utils/types'
|
import { ServerType } from '@sasjs/utils/types'
|
||||||
|
|
||||||
@@ -13,8 +13,7 @@ const defaultConfig: SASjsConfig = {
|
|||||||
debug: false,
|
debug: false,
|
||||||
contextName: 'SAS Job Execution compute context',
|
contextName: 'SAS Job Execution compute context',
|
||||||
useComputeApi: false,
|
useComputeApi: false,
|
||||||
allowInsecureRequests: false,
|
allowInsecureRequests: false
|
||||||
loginMechanism: LoginMechanism.Default
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const customConfig = {
|
const customConfig = {
|
||||||
@@ -42,19 +41,6 @@ export const basicTests = (
|
|||||||
assertion: (response: any) =>
|
assertion: (response: any) =>
|
||||||
response && response.isLoggedIn && response.userName === userName
|
response && response.isLoggedIn && response.userName === userName
|
||||||
},
|
},
|
||||||
{
|
|
||||||
title: 'Fetch username for already logged in user',
|
|
||||||
description: 'Should log the user in',
|
|
||||||
test: async () => {
|
|
||||||
await adapter.logIn(userName, password)
|
|
||||||
|
|
||||||
const newAdapterIns = new SASjs(adapter.getSasjsConfig())
|
|
||||||
|
|
||||||
return await newAdapterIns.checkSession()
|
|
||||||
},
|
|
||||||
assertion: (response: any) =>
|
|
||||||
response?.isLoggedIn && response?.userName === userName
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
title: 'Multiple Log in attempts',
|
title: 'Multiple Log in attempts',
|
||||||
description:
|
description:
|
||||||
@@ -62,7 +48,7 @@ export const basicTests = (
|
|||||||
test: async () => {
|
test: async () => {
|
||||||
await adapter.logOut()
|
await adapter.logOut()
|
||||||
await adapter.logIn('invalid', 'invalid')
|
await adapter.logIn('invalid', 'invalid')
|
||||||
return await adapter.logIn(userName, password)
|
return adapter.logIn(userName, password)
|
||||||
},
|
},
|
||||||
assertion: (response: any) =>
|
assertion: (response: any) =>
|
||||||
response && response.isLoggedIn && response.userName === userName
|
response && response.isLoggedIn && response.userName === userName
|
||||||
@@ -165,7 +151,7 @@ export const basicTests = (
|
|||||||
description:
|
description:
|
||||||
'Should complete successful request with extra attributes present in response',
|
'Should complete successful request with extra attributes present in response',
|
||||||
test: async () => {
|
test: async () => {
|
||||||
const config: Partial<SASjsConfig> = {
|
const config = {
|
||||||
useComputeApi: false
|
useComputeApi: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
107
src/FileUploader.ts
Normal file
107
src/FileUploader.ts
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import { isUrl, getValidJson, parseSasViyaDebugResponse } from './utils'
|
||||||
|
import { UploadFile } from './types/UploadFile'
|
||||||
|
import { ErrorResponse, LoginRequiredError } from './types/errors'
|
||||||
|
import { RequestClient } from './request/RequestClient'
|
||||||
|
import { ServerType } from '@sasjs/utils/types'
|
||||||
|
import SASjs from './SASjs'
|
||||||
|
import { Server } from 'https'
|
||||||
|
import { SASjsConfig } from './types'
|
||||||
|
import { config } from 'process'
|
||||||
|
|
||||||
|
export class FileUploader {
|
||||||
|
constructor(
|
||||||
|
private sasjsConfig: SASjsConfig,
|
||||||
|
private jobsPath: string,
|
||||||
|
private requestClient: RequestClient
|
||||||
|
) {
|
||||||
|
if (this.sasjsConfig.serverUrl) isUrl(this.sasjsConfig.serverUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
public uploadFile(sasJob: string, files: UploadFile[], params: any) {
|
||||||
|
if (files?.length < 1)
|
||||||
|
return Promise.reject(
|
||||||
|
new ErrorResponse('At least one file must be provided.')
|
||||||
|
)
|
||||||
|
if (!sasJob || sasJob === '')
|
||||||
|
return Promise.reject(new ErrorResponse('sasJob must be provided.'))
|
||||||
|
|
||||||
|
let paramsString = ''
|
||||||
|
|
||||||
|
for (let param in params) {
|
||||||
|
if (params.hasOwnProperty(param)) {
|
||||||
|
paramsString += `&${param}=${params[param]}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const program = this.sasjsConfig.appLoc
|
||||||
|
? this.sasjsConfig.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
|
||||||
|
: sasJob
|
||||||
|
const uploadUrl = `${this.jobsPath}/?${
|
||||||
|
'_program=' + program
|
||||||
|
}${paramsString}`
|
||||||
|
|
||||||
|
const formData = new FormData()
|
||||||
|
|
||||||
|
for (let file of files) {
|
||||||
|
formData.append('file', file.file, file.fileName)
|
||||||
|
}
|
||||||
|
|
||||||
|
const csrfToken = this.requestClient.getCsrfToken('file')
|
||||||
|
if (csrfToken) formData.append('_csrf', csrfToken.value)
|
||||||
|
if (this.sasjsConfig.debug) formData.append('_debug', '131')
|
||||||
|
if (
|
||||||
|
this.sasjsConfig.serverType === ServerType.SasViya &&
|
||||||
|
this.sasjsConfig.contextName
|
||||||
|
)
|
||||||
|
formData.append('_contextname', this.sasjsConfig.contextName)
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
'cache-control': 'no-cache',
|
||||||
|
Accept: '*/*',
|
||||||
|
'Content-Type': 'text/plain'
|
||||||
|
}
|
||||||
|
|
||||||
|
// currently only web approach is supported for file upload
|
||||||
|
// therefore log is part of response with debug enabled and must be parsed
|
||||||
|
return this.requestClient
|
||||||
|
.post(
|
||||||
|
uploadUrl,
|
||||||
|
formData,
|
||||||
|
undefined,
|
||||||
|
'application/json',
|
||||||
|
headers,
|
||||||
|
this.sasjsConfig.debug,
|
||||||
|
true,
|
||||||
|
sasJob
|
||||||
|
)
|
||||||
|
.then(async (res) => {
|
||||||
|
if (
|
||||||
|
this.sasjsConfig.serverType === ServerType.SasViya &&
|
||||||
|
this.sasjsConfig.debug
|
||||||
|
) {
|
||||||
|
const jsonResponse = await parseSasViyaDebugResponse(
|
||||||
|
res.result as string,
|
||||||
|
this.requestClient,
|
||||||
|
this.sasjsConfig.serverUrl
|
||||||
|
)
|
||||||
|
return jsonResponse
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof res.result === 'string'
|
||||||
|
? getValidJson(res.result)
|
||||||
|
: res.result
|
||||||
|
|
||||||
|
//TODO: append to SASjs requests
|
||||||
|
})
|
||||||
|
.catch((err: Error) => {
|
||||||
|
if (err instanceof LoginRequiredError) {
|
||||||
|
return Promise.reject(
|
||||||
|
new ErrorResponse('You must be logged in to upload a file.', err)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return Promise.reject(
|
||||||
|
new ErrorResponse('File upload request failed.', err)
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -51,16 +51,6 @@ export class SASViyaApiClient {
|
|||||||
)
|
)
|
||||||
private folderMap = new Map<string, Job[]>()
|
private folderMap = new Map<string, Job[]>()
|
||||||
|
|
||||||
/**
|
|
||||||
* A helper method used to call appendRequest method of RequestClient
|
|
||||||
* @param response - response from sasjs request
|
|
||||||
* @param program - name of program
|
|
||||||
* @param debug - a boolean that indicates whether debug was enabled or not
|
|
||||||
*/
|
|
||||||
public appendRequest(response: any, program: string, debug: boolean) {
|
|
||||||
this.requestClient!.appendRequest(response, program, debug)
|
|
||||||
}
|
|
||||||
|
|
||||||
public get debug() {
|
public get debug() {
|
||||||
return this._debug
|
return this._debug
|
||||||
}
|
}
|
||||||
@@ -792,12 +782,24 @@ export class SASViyaApiClient {
|
|||||||
jobResult = await this.requestClient.get<any>(
|
jobResult = await this.requestClient.get<any>(
|
||||||
`${this.serverUrl}${resultLink}/content`,
|
`${this.serverUrl}${resultLink}/content`,
|
||||||
access_token,
|
access_token,
|
||||||
'text/plain'
|
'text/plain',
|
||||||
|
{},
|
||||||
|
debug,
|
||||||
|
true,
|
||||||
|
sasJob
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (debug && logLink) {
|
if (debug && logLink) {
|
||||||
log = await this.requestClient
|
log = await this.requestClient
|
||||||
.get<any>(`${this.serverUrl}${logLink.href}/content`, access_token)
|
.get<any>(
|
||||||
|
`${this.serverUrl}${logLink.href}/content`,
|
||||||
|
access_token,
|
||||||
|
'application/json',
|
||||||
|
{},
|
||||||
|
debug,
|
||||||
|
true,
|
||||||
|
sasJob
|
||||||
|
)
|
||||||
.then((res: any) => res.result.items.map((i: any) => i.line).join('\n'))
|
.then((res: any) => res.result.items.map((i: any) => i.line).join('\n'))
|
||||||
}
|
}
|
||||||
if (jobStatus === 'failed') {
|
if (jobStatus === 'failed') {
|
||||||
|
|||||||
100
src/SASjs.ts
100
src/SASjs.ts
@@ -1,13 +1,8 @@
|
|||||||
import { compareTimestamps, asyncForEach } from './utils'
|
import { compareTimestamps, asyncForEach } from './utils'
|
||||||
import {
|
import { SASjsConfig, UploadFile, EditContextInput, PollOptions } from './types'
|
||||||
SASjsConfig,
|
|
||||||
UploadFile,
|
|
||||||
EditContextInput,
|
|
||||||
PollOptions,
|
|
||||||
LoginMechanism
|
|
||||||
} from './types'
|
|
||||||
import { SASViyaApiClient } from './SASViyaApiClient'
|
import { SASViyaApiClient } from './SASViyaApiClient'
|
||||||
import { SAS9ApiClient } from './SAS9ApiClient'
|
import { SAS9ApiClient } from './SAS9ApiClient'
|
||||||
|
import { FileUploader } from './FileUploader'
|
||||||
import { AuthManager } from './auth'
|
import { AuthManager } from './auth'
|
||||||
import {
|
import {
|
||||||
ServerType,
|
ServerType,
|
||||||
@@ -21,11 +16,9 @@ import {
|
|||||||
WebJobExecutor,
|
WebJobExecutor,
|
||||||
ComputeJobExecutor,
|
ComputeJobExecutor,
|
||||||
JesJobExecutor,
|
JesJobExecutor,
|
||||||
Sas9JobExecutor,
|
Sas9JobExecutor
|
||||||
FileUploader
|
|
||||||
} from './job-execution'
|
} from './job-execution'
|
||||||
import { ErrorResponse } from './types/errors'
|
import { ErrorResponse } from './types/errors'
|
||||||
import { LoginOptions, LoginResult } from './types/Login'
|
|
||||||
|
|
||||||
const defaultConfig: SASjsConfig = {
|
const defaultConfig: SASjsConfig = {
|
||||||
serverUrl: '',
|
serverUrl: '',
|
||||||
@@ -36,8 +29,7 @@ const defaultConfig: SASjsConfig = {
|
|||||||
debug: false,
|
debug: false,
|
||||||
contextName: 'SAS Job Execution compute context',
|
contextName: 'SAS Job Execution compute context',
|
||||||
useComputeApi: null,
|
useComputeApi: null,
|
||||||
allowInsecureRequests: false,
|
allowInsecureRequests: false
|
||||||
loginMechanism: LoginMechanism.Default
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,6 +50,7 @@ export default class SASjs {
|
|||||||
private sas9JobExecutor: JobExecutor | null = null
|
private sas9JobExecutor: JobExecutor | null = null
|
||||||
|
|
||||||
constructor(config?: any) {
|
constructor(config?: any) {
|
||||||
|
console.log('from SASjs constructor')
|
||||||
this.sasjsConfig = {
|
this.sasjsConfig = {
|
||||||
...defaultConfig,
|
...defaultConfig,
|
||||||
...config
|
...config
|
||||||
@@ -507,7 +500,7 @@ export default class SASjs {
|
|||||||
...this.sasjsConfig,
|
...this.sasjsConfig,
|
||||||
...config
|
...config
|
||||||
}
|
}
|
||||||
this.setupConfiguration()
|
await this.setupConfiguration()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -534,27 +527,8 @@ export default class SASjs {
|
|||||||
* @param username - a string representing the username.
|
* @param username - a string representing the username.
|
||||||
* @param password - a string representing the password.
|
* @param password - a string representing the password.
|
||||||
*/
|
*/
|
||||||
public async logIn(
|
public async logIn(username: string, password: string) {
|
||||||
username?: string,
|
return this.authManager!.logIn(username, password)
|
||||||
password?: string,
|
|
||||||
options: LoginOptions = {}
|
|
||||||
): Promise<LoginResult> {
|
|
||||||
if (this.sasjsConfig.loginMechanism === LoginMechanism.Default) {
|
|
||||||
if (!username || !password) {
|
|
||||||
throw new Error(
|
|
||||||
'A username and password are required when using the default login mechanism.'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return this.authManager!.logIn(username, password)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof window === typeof undefined) {
|
|
||||||
throw new Error(
|
|
||||||
'The redirected login mechanism is only available for use in the browser.'
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.authManager!.redirectedLogIn(options)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -571,32 +545,24 @@ export default class SASjs {
|
|||||||
* Process). Is prepended at runtime with the value of `appLoc`.
|
* Process). Is prepended at runtime with the value of `appLoc`.
|
||||||
* @param files - array of files to be uploaded, including File object and file name.
|
* @param files - array of files to be uploaded, including File object and file name.
|
||||||
* @param params - request URL parameters.
|
* @param params - request URL parameters.
|
||||||
* @param config - provide any changes to the config here, for instance to
|
* @param overrideSasjsConfig - object to override existing config (optional)
|
||||||
* enable/disable `debug`. Any change provided will override the global config,
|
|
||||||
* for that particular function call.
|
|
||||||
* @param loginRequiredCallback - a function that is called if the
|
|
||||||
* user is not logged in (eg to display a login form). The request will be
|
|
||||||
* resubmitted after successful login.
|
|
||||||
*/
|
*/
|
||||||
public async uploadFile(
|
public uploadFile(
|
||||||
sasJob: string,
|
sasJob: string,
|
||||||
files: UploadFile[],
|
files: UploadFile[],
|
||||||
params: { [key: string]: any } | null,
|
params: any,
|
||||||
config: { [key: string]: any } = {},
|
overrideSasjsConfig?: any
|
||||||
loginRequiredCallback?: () => any
|
|
||||||
) {
|
) {
|
||||||
config = {
|
const fileUploader = overrideSasjsConfig
|
||||||
...this.sasjsConfig,
|
? new FileUploader(
|
||||||
...config
|
{ ...this.sasjsConfig, ...overrideSasjsConfig },
|
||||||
}
|
this.jobsPath,
|
||||||
const data = { files, params }
|
this.requestClient!
|
||||||
|
)
|
||||||
|
: this.fileUploader ||
|
||||||
|
new FileUploader(this.sasjsConfig, this.jobsPath, this.requestClient!)
|
||||||
|
|
||||||
return await this.fileUploader!.execute(
|
return fileUploader.uploadFile(sasJob, files, params)
|
||||||
sasJob,
|
|
||||||
data,
|
|
||||||
config,
|
|
||||||
loginRequiredCallback
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -646,6 +612,7 @@ export default class SASjs {
|
|||||||
config.useComputeApi !== null
|
config.useComputeApi !== null
|
||||||
) {
|
) {
|
||||||
if (config.useComputeApi) {
|
if (config.useComputeApi) {
|
||||||
|
console.log(615)
|
||||||
return await this.computeJobExecutor!.execute(
|
return await this.computeJobExecutor!.execute(
|
||||||
sasJob,
|
sasJob,
|
||||||
data,
|
data,
|
||||||
@@ -882,7 +849,6 @@ export default class SASjs {
|
|||||||
await this.webJobExecutor?.resendWaitingRequests()
|
await this.webJobExecutor?.resendWaitingRequests()
|
||||||
await this.computeJobExecutor?.resendWaitingRequests()
|
await this.computeJobExecutor?.resendWaitingRequests()
|
||||||
await this.jesJobExecutor?.resendWaitingRequests()
|
await this.jesJobExecutor?.resendWaitingRequests()
|
||||||
await this.fileUploader?.resendWaitingRequests()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -919,8 +885,10 @@ export default class SASjs {
|
|||||||
* @returns SASjsRequest[]
|
* @returns SASjsRequest[]
|
||||||
*/
|
*/
|
||||||
public getSasRequests() {
|
public getSasRequests() {
|
||||||
const requests = [...this.requestClient!.getRequests()]
|
console.log('from getSASRequests')
|
||||||
|
const requests = this.requestClient!.getRequests()
|
||||||
const sortedRequests = requests.sort(compareTimestamps)
|
const sortedRequests = requests.sort(compareTimestamps)
|
||||||
|
console.log('sortedRequests', sortedRequests)
|
||||||
return sortedRequests
|
return sortedRequests
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -948,17 +916,10 @@ export default class SASjs {
|
|||||||
this.sasjsConfig.serverUrl = this.sasjsConfig.serverUrl.slice(0, -1)
|
this.sasjsConfig.serverUrl = this.sasjsConfig.serverUrl.slice(0, -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.requestClient) {
|
this.requestClient = new RequestClient(
|
||||||
this.requestClient = new RequestClient(
|
this.sasjsConfig.serverUrl,
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig.allowInsecureRequests
|
||||||
this.sasjsConfig.allowInsecureRequests
|
)
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.requestClient.setConfig(
|
|
||||||
this.sasjsConfig.serverUrl,
|
|
||||||
this.sasjsConfig.allowInsecureRequests
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.jobsPath =
|
this.jobsPath =
|
||||||
this.sasjsConfig.serverType === ServerType.SasViya
|
this.sasjsConfig.serverType === ServerType.SasViya
|
||||||
@@ -1000,8 +961,7 @@ export default class SASjs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.fileUploader = new FileUploader(
|
this.fileUploader = new FileUploader(
|
||||||
this.sasjsConfig.serverUrl,
|
this.sasjsConfig,
|
||||||
this.sasjsConfig.serverType!,
|
|
||||||
this.jobsPath,
|
this.jobsPath,
|
||||||
this.requestClient
|
this.requestClient
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -239,7 +239,15 @@ export async function executeScript(
|
|||||||
const resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content`
|
const resultLink = `/compute/sessions/${executionSessionId}/filerefs/_webout/content`
|
||||||
|
|
||||||
jobResult = await requestClient
|
jobResult = await requestClient
|
||||||
.get<any>(resultLink, access_token, 'text/plain')
|
.get<any>(
|
||||||
|
resultLink,
|
||||||
|
access_token,
|
||||||
|
'text/plain',
|
||||||
|
{},
|
||||||
|
debug,
|
||||||
|
true,
|
||||||
|
jobPath
|
||||||
|
)
|
||||||
.catch(async (e) => {
|
.catch(async (e) => {
|
||||||
if (e instanceof NotFoundError) {
|
if (e instanceof NotFoundError) {
|
||||||
if (logLink) {
|
if (logLink) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { getTokens } from '../../auth/getTokens'
|
|||||||
import { RequestClient } from '../../request/RequestClient'
|
import { RequestClient } from '../../request/RequestClient'
|
||||||
import { JobStatePollError } from '../../types/errors'
|
import { JobStatePollError } from '../../types/errors'
|
||||||
import { Link, WriteStream } from '../../types'
|
import { Link, WriteStream } from '../../types'
|
||||||
import { delay, isNode } from '../../utils'
|
import { isNode } from '../../utils'
|
||||||
|
|
||||||
export async function pollJobState(
|
export async function pollJobState(
|
||||||
requestClient: RequestClient,
|
requestClient: RequestClient,
|
||||||
@@ -246,3 +246,5 @@ const doPoll = async (
|
|||||||
|
|
||||||
return { state, pollCount }
|
return { state, pollCount }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import { ServerType } from '@sasjs/utils/types'
|
import { ServerType } from '@sasjs/utils/types'
|
||||||
import { RequestClient } from '../request/RequestClient'
|
import { RequestClient } from '../request/RequestClient'
|
||||||
import { LoginOptions, LoginResult } from '../types/Login'
|
|
||||||
import { serialize } from '../utils'
|
import { serialize } from '../utils'
|
||||||
import { openWebPage } from './openWebPage'
|
|
||||||
import { verifySas9Login } from './verifySas9Login'
|
|
||||||
import { verifySasViyaLogin } from './verifySasViyaLogin'
|
|
||||||
|
|
||||||
export class AuthManager {
|
export class AuthManager {
|
||||||
public userName = ''
|
public userName = ''
|
||||||
private loginUrl: string
|
private loginUrl: string
|
||||||
private logoutUrl: string
|
private logoutUrl: string
|
||||||
private redirectedLoginUrl = `/SASLogon/home`
|
|
||||||
constructor(
|
constructor(
|
||||||
private serverUrl: string,
|
private serverUrl: string,
|
||||||
private serverType: ServerType,
|
private serverType: ServerType,
|
||||||
@@ -24,137 +19,65 @@ export class AuthManager {
|
|||||||
: '/SASLogon/logout.do?'
|
: '/SASLogon/logout.do?'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Opens Pop up window to SAS Login screen.
|
|
||||||
* And checks if user has finished login process.
|
|
||||||
*/
|
|
||||||
public async redirectedLogIn({
|
|
||||||
onLoggedOut
|
|
||||||
}: LoginOptions): Promise<LoginResult> {
|
|
||||||
const { isLoggedIn: isLoggedInAlready, userName: currentSessionUsername } =
|
|
||||||
await this.fetchUserName()
|
|
||||||
|
|
||||||
if (isLoggedInAlready) {
|
|
||||||
await this.loginCallback()
|
|
||||||
|
|
||||||
return {
|
|
||||||
isLoggedIn: true,
|
|
||||||
userName: currentSessionUsername
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loginPopup = await openWebPage(
|
|
||||||
this.redirectedLoginUrl,
|
|
||||||
'SASLogon',
|
|
||||||
{
|
|
||||||
width: 500,
|
|
||||||
height: 600
|
|
||||||
},
|
|
||||||
onLoggedOut
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!loginPopup) {
|
|
||||||
return { isLoggedIn: false, userName: '' }
|
|
||||||
}
|
|
||||||
|
|
||||||
const { isLoggedIn } =
|
|
||||||
this.serverType === ServerType.SasViya
|
|
||||||
? await verifySasViyaLogin(loginPopup)
|
|
||||||
: await verifySas9Login(loginPopup)
|
|
||||||
|
|
||||||
loginPopup.close()
|
|
||||||
|
|
||||||
if (isLoggedIn) {
|
|
||||||
if (this.serverType === ServerType.Sas9) {
|
|
||||||
await this.performCASSecurityCheck()
|
|
||||||
}
|
|
||||||
|
|
||||||
const { userName } = await this.fetchUserName()
|
|
||||||
|
|
||||||
await this.loginCallback()
|
|
||||||
|
|
||||||
return { isLoggedIn: true, userName }
|
|
||||||
}
|
|
||||||
|
|
||||||
return { isLoggedIn: false, userName: '' }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs into the SAS server with the supplied credentials.
|
* Logs into the SAS server with the supplied credentials.
|
||||||
* @param username - a string representing the username.
|
* @param username - a string representing the username.
|
||||||
* @param password - a string representing the password.
|
* @param password - a string representing the password.
|
||||||
* @returns - a boolean `isLoggedin` and a string `username`
|
|
||||||
*/
|
*/
|
||||||
public async logIn(username: string, password: string): Promise<LoginResult> {
|
public async logIn(username: string, password: string) {
|
||||||
const loginParams = {
|
const loginParams: any = {
|
||||||
_service: 'default',
|
_service: 'default',
|
||||||
username,
|
username,
|
||||||
password
|
password
|
||||||
}
|
}
|
||||||
|
|
||||||
let {
|
this.userName = loginParams.username
|
||||||
isLoggedIn: isLoggedInAlready,
|
|
||||||
loginForm,
|
|
||||||
userName: currentSessionUsername
|
|
||||||
} = await this.checkSession()
|
|
||||||
|
|
||||||
if (isLoggedInAlready) {
|
const { isLoggedIn, loginForm } = await this.checkSession()
|
||||||
if (currentSessionUsername === loginParams.username) {
|
|
||||||
await this.loginCallback()
|
|
||||||
|
|
||||||
this.userName = currentSessionUsername!
|
if (isLoggedIn) {
|
||||||
return {
|
await this.loginCallback()
|
||||||
isLoggedIn: true,
|
|
||||||
userName: this.userName
|
return {
|
||||||
}
|
isLoggedIn,
|
||||||
} else {
|
userName: this.userName
|
||||||
await this.logOut()
|
|
||||||
loginForm = await this.getNewLoginForm()
|
|
||||||
}
|
}
|
||||||
} else this.userName = ''
|
}
|
||||||
|
|
||||||
let loginResponse = await this.sendLoginRequest(loginForm, loginParams)
|
let loginResponse = await this.sendLoginRequest(loginForm, loginParams)
|
||||||
|
|
||||||
let isLoggedIn = isLogInSuccess(loginResponse)
|
let loggedIn = isLogInSuccess(loginResponse)
|
||||||
|
|
||||||
if (!isLoggedIn) {
|
if (!loggedIn) {
|
||||||
if (isCredentialsVerifyError(loginResponse)) {
|
if (isCredentialsVerifyError(loginResponse)) {
|
||||||
const newLoginForm = await this.getLoginForm(loginResponse)
|
const newLoginForm = await this.getLoginForm(loginResponse)
|
||||||
|
|
||||||
loginResponse = await this.sendLoginRequest(newLoginForm, loginParams)
|
loginResponse = await this.sendLoginRequest(newLoginForm, loginParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await this.checkSession()
|
const currentSession = await this.checkSession()
|
||||||
isLoggedIn = res.isLoggedIn
|
loggedIn = currentSession.isLoggedIn
|
||||||
|
|
||||||
if (isLoggedIn) this.userName = res.userName
|
|
||||||
} else {
|
|
||||||
this.userName = loginParams.username
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isLoggedIn) {
|
if (loggedIn) {
|
||||||
if (this.serverType === ServerType.Sas9) {
|
if (this.serverType === ServerType.Sas9) {
|
||||||
await this.performCASSecurityCheck()
|
const casAuthenticationUrl = `${this.serverUrl}/SASStoredProcess/j_spring_cas_security_check`
|
||||||
|
|
||||||
|
await this.requestClient.get<string>(
|
||||||
|
`/SASLogon/login?service=${casAuthenticationUrl}`,
|
||||||
|
undefined
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loginCallback()
|
this.loginCallback()
|
||||||
} else this.userName = ''
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isLoggedIn,
|
isLoggedIn: !!loggedIn,
|
||||||
userName: this.userName
|
userName: this.userName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async performCASSecurityCheck() {
|
|
||||||
const casAuthenticationUrl = `${this.serverUrl}/SASStoredProcess/j_spring_cas_security_check`
|
|
||||||
|
|
||||||
await this.requestClient.get<string>(
|
|
||||||
`/SASLogon/login?service=${casAuthenticationUrl}`,
|
|
||||||
undefined
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private async sendLoginRequest(
|
private async sendLoginRequest(
|
||||||
loginForm: { [key: string]: any },
|
loginForm: { [key: string]: any },
|
||||||
loginParams: { [key: string]: any }
|
loginParams: { [key: string]: any }
|
||||||
@@ -180,53 +103,14 @@ export class AuthManager {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks whether a session is active, or login is required.
|
* Checks whether a session is active, or login is required.
|
||||||
* @returns - a promise which resolves with an object containing three values
|
* @returns - a promise which resolves with an object containing two values - a boolean `isLoggedIn`, and a string `userName`.
|
||||||
* - a boolean `isLoggedIn`
|
|
||||||
* - a string `userName` and
|
|
||||||
* - a form `loginForm` if not loggedin.
|
|
||||||
*/
|
*/
|
||||||
public async checkSession(): Promise<{
|
public async checkSession() {
|
||||||
isLoggedIn: boolean
|
|
||||||
userName: string
|
|
||||||
loginForm?: any
|
|
||||||
}> {
|
|
||||||
const { isLoggedIn, userName } = await this.fetchUserName()
|
|
||||||
let loginForm = null
|
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
loginForm = await this.getNewLoginForm()
|
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.resolve({
|
|
||||||
isLoggedIn,
|
|
||||||
userName: userName.toLowerCase(),
|
|
||||||
loginForm
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
private async getNewLoginForm() {
|
|
||||||
const { result: formResponse } = await this.requestClient.get<string>(
|
|
||||||
this.loginUrl.replace('.do', ''),
|
|
||||||
undefined,
|
|
||||||
'text/plain'
|
|
||||||
)
|
|
||||||
|
|
||||||
return await this.getLoginForm(formResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
private async fetchUserName(): Promise<{
|
|
||||||
isLoggedIn: boolean
|
|
||||||
userName: string
|
|
||||||
}> {
|
|
||||||
//For VIYA we will send request on API endpoint. Which is faster then pinging SASJobExecution.
|
//For VIYA we will send request on API endpoint. Which is faster then pinging SASJobExecution.
|
||||||
//For SAS9 we will send request on SASStoredProcess
|
//For SAS9 we will send request on SASStoredProcess
|
||||||
const url =
|
const url =
|
||||||
this.serverType === ServerType.SasViya
|
this.serverType === 'SASVIYA'
|
||||||
? `${this.serverUrl}/identities/users/@currentUser`
|
? `${this.serverUrl}/identities`
|
||||||
: `${this.serverUrl}/SASStoredProcess`
|
: `${this.serverUrl}/SASStoredProcess`
|
||||||
|
|
||||||
const { result: loginResponse } = await this.requestClient
|
const { result: loginResponse } = await this.requestClient
|
||||||
@@ -236,27 +120,27 @@ export class AuthManager {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const isLoggedIn = loginResponse !== 'authErr'
|
const isLoggedIn = loginResponse !== 'authErr'
|
||||||
const userName = isLoggedIn ? this.extractUserName(loginResponse) : ''
|
let loginForm = null
|
||||||
|
|
||||||
return { isLoggedIn, userName }
|
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()
|
||||||
|
|
||||||
private extractUserName = (response: any): string => {
|
const { result: formResponse } = await this.requestClient.get<string>(
|
||||||
switch (this.serverType) {
|
this.loginUrl.replace('.do', ''),
|
||||||
case ServerType.SasViya:
|
undefined,
|
||||||
return response?.id
|
'text/plain'
|
||||||
|
)
|
||||||
|
|
||||||
case ServerType.Sas9:
|
loginForm = await this.getLoginForm(formResponse)
|
||||||
const matched = response?.match(/"title":"Log Off [0-1a-zA-Z ]*"/)
|
|
||||||
const username = matched?.[0].slice(17, -1)
|
|
||||||
|
|
||||||
if (!username.includes(' ')) return username
|
|
||||||
|
|
||||||
return username
|
|
||||||
.split(' ')
|
|
||||||
.map((name: string) => name.slice(0, 3).toLowerCase())
|
|
||||||
.join('')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Promise.resolve({
|
||||||
|
isLoggedIn,
|
||||||
|
userName: this.userName,
|
||||||
|
loginForm
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLoginForm(response: any) {
|
private getLoginForm(response: any) {
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
import { openLoginPrompt } from '../utils/loginPrompt'
|
|
||||||
|
|
||||||
interface WindowFeatures {
|
|
||||||
width: number
|
|
||||||
height: number
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultWindowFeatures: WindowFeatures = { width: 500, height: 600 }
|
|
||||||
|
|
||||||
export async function openWebPage(
|
|
||||||
url: string,
|
|
||||||
windowName: string = '',
|
|
||||||
WindowFeatures: WindowFeatures = defaultWindowFeatures,
|
|
||||||
onLoggedOut?: () => Promise<Boolean>
|
|
||||||
): Promise<Window | null> {
|
|
||||||
const { width, height } = WindowFeatures
|
|
||||||
const left = screen.width / 2 - width / 2
|
|
||||||
const top = screen.height / 2 - height / 2
|
|
||||||
|
|
||||||
const loginPopup = window.open(
|
|
||||||
url,
|
|
||||||
windowName,
|
|
||||||
`toolbar=0,location=0,menubar=0,width=${width},height=${height},left=${left},top=${top}`
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!loginPopup) {
|
|
||||||
const getUserAction: () => Promise<Boolean> = onLoggedOut ?? openLoginPrompt
|
|
||||||
|
|
||||||
const doLogin = await getUserAction()
|
|
||||||
return doLogin
|
|
||||||
? window.open(
|
|
||||||
url,
|
|
||||||
windowName,
|
|
||||||
`toolbar=0,location=0,menubar=0,width=${width},height=${height},left=${left},top=${top}`
|
|
||||||
)
|
|
||||||
: null
|
|
||||||
}
|
|
||||||
|
|
||||||
return loginPopup
|
|
||||||
}
|
|
||||||
@@ -3,14 +3,10 @@ import * as dotenv from 'dotenv'
|
|||||||
import { ServerType } from '@sasjs/utils/types'
|
import { ServerType } from '@sasjs/utils/types'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import {
|
import {
|
||||||
mockedCurrentUserApi,
|
|
||||||
mockLoginAuthoriseRequiredResponse,
|
mockLoginAuthoriseRequiredResponse,
|
||||||
mockLoginSuccessResponse
|
mockLoginSuccessResponse
|
||||||
} from './mockResponses'
|
} from './mockResponses'
|
||||||
import { serialize } from '../../utils'
|
import { serialize } from '../../utils'
|
||||||
import * as openWebPageModule from '../openWebPage'
|
|
||||||
import * as verifySasViyaLoginModule from '../verifySasViyaLogin'
|
|
||||||
import * as verifySas9LoginModule from '../verifySas9Login'
|
|
||||||
import { RequestClient } from '../../request/RequestClient'
|
import { RequestClient } from '../../request/RequestClient'
|
||||||
jest.mock('axios')
|
jest.mock('axios')
|
||||||
const mockedAxios = axios as jest.Mocked<typeof axios>
|
const mockedAxios = axios as jest.Mocked<typeof axios>
|
||||||
@@ -61,614 +57,134 @@ describe('AuthManager', () => {
|
|||||||
expect((authManager as any).logoutUrl).toEqual('/SASLogon/logout?')
|
expect((authManager as any).logoutUrl).toEqual('/SASLogon/logout?')
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('login - default mechanism', () => {
|
it('should call the auth callback and return when already logged in', async () => {
|
||||||
it('should call the auth callback and return when already logged in', async () => {
|
const authManager = new AuthManager(
|
||||||
const authManager = new AuthManager(
|
serverUrl,
|
||||||
serverUrl,
|
serverType,
|
||||||
serverType,
|
requestClient,
|
||||||
requestClient,
|
authCallback
|
||||||
authCallback
|
)
|
||||||
)
|
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
||||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
Promise.resolve({
|
||||||
Promise.resolve({
|
isLoggedIn: true,
|
||||||
isLoggedIn: true,
|
userName: 'test',
|
||||||
userName,
|
loginForm: 'test'
|
||||||
loginForm: 'test'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const loginResponse = await authManager.logIn(userName, password)
|
|
||||||
|
|
||||||
expect(loginResponse.isLoggedIn).toBeTruthy()
|
|
||||||
expect(loginResponse.userName).toEqual(userName)
|
|
||||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should post a login request to the server when already logged in with other username', async () => {
|
|
||||||
const authManager = new AuthManager(
|
|
||||||
serverUrl,
|
|
||||||
serverType,
|
|
||||||
requestClient,
|
|
||||||
authCallback
|
|
||||||
)
|
|
||||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
isLoggedIn: true,
|
|
||||||
userName: 'someOtherUsername',
|
|
||||||
loginForm: null
|
|
||||||
})
|
|
||||||
)
|
|
||||||
jest
|
|
||||||
.spyOn(authManager, 'logOut')
|
|
||||||
.mockImplementation(() => Promise.resolve(true))
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn<any, any>(authManager, 'getNewLoginForm')
|
|
||||||
.mockImplementation(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
name: 'test'
|
|
||||||
})
|
|
||||||
)
|
|
||||||
mockedAxios.post.mockImplementation(() =>
|
|
||||||
Promise.resolve({ data: mockLoginSuccessResponse })
|
|
||||||
)
|
|
||||||
|
|
||||||
const loginResponse = await authManager.logIn(userName, password)
|
|
||||||
|
|
||||||
expect(loginResponse.isLoggedIn).toBeTruthy()
|
|
||||||
expect(loginResponse.userName).toEqual(userName)
|
|
||||||
|
|
||||||
const loginParams = serialize({
|
|
||||||
_service: 'default',
|
|
||||||
username: userName,
|
|
||||||
password,
|
|
||||||
name: 'test'
|
|
||||||
})
|
})
|
||||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
)
|
||||||
expect(authManager.logOut).toHaveBeenCalledTimes(1)
|
|
||||||
expect(authManager['getNewLoginForm']).toHaveBeenCalledTimes(1)
|
|
||||||
expect(mockedAxios.post).toHaveBeenCalledWith(
|
|
||||||
`/SASLogon/login`,
|
|
||||||
loginParams,
|
|
||||||
{
|
|
||||||
withCredentials: true,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
Accept: '*/*'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should post a login request to the server when not logged in', async () => {
|
const loginResponse = await authManager.logIn(userName, password)
|
||||||
const authManager = new AuthManager(
|
|
||||||
serverUrl,
|
|
||||||
serverType,
|
|
||||||
requestClient,
|
|
||||||
authCallback
|
|
||||||
)
|
|
||||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
isLoggedIn: false,
|
|
||||||
userName: '',
|
|
||||||
loginForm: { name: 'test' }
|
|
||||||
})
|
|
||||||
)
|
|
||||||
mockedAxios.post.mockImplementation(() =>
|
|
||||||
Promise.resolve({ data: mockLoginSuccessResponse })
|
|
||||||
)
|
|
||||||
|
|
||||||
const loginResponse = await authManager.logIn(userName, password)
|
expect(loginResponse.isLoggedIn).toBeTruthy()
|
||||||
|
expect(loginResponse.userName).toEqual(userName)
|
||||||
expect(loginResponse.isLoggedIn).toBeTruthy()
|
expect(authCallback).toHaveBeenCalledTimes(1)
|
||||||
expect(loginResponse.userName).toEqual(userName)
|
|
||||||
|
|
||||||
const loginParams = serialize({
|
|
||||||
_service: 'default',
|
|
||||||
username: userName,
|
|
||||||
password,
|
|
||||||
name: 'test'
|
|
||||||
})
|
|
||||||
expect(mockedAxios.post).toHaveBeenCalledWith(
|
|
||||||
`/SASLogon/login`,
|
|
||||||
loginParams,
|
|
||||||
{
|
|
||||||
withCredentials: true,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
Accept: '*/*'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should post a login & a cas_security request to the SAS9 server when not logged in', async () => {
|
|
||||||
const serverType = ServerType.Sas9
|
|
||||||
const authManager = new AuthManager(
|
|
||||||
serverUrl,
|
|
||||||
serverType,
|
|
||||||
requestClient,
|
|
||||||
authCallback
|
|
||||||
)
|
|
||||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
isLoggedIn: false,
|
|
||||||
userName: '',
|
|
||||||
loginForm: { name: 'test' }
|
|
||||||
})
|
|
||||||
)
|
|
||||||
mockedAxios.post.mockImplementation(() =>
|
|
||||||
Promise.resolve({ data: mockLoginSuccessResponse })
|
|
||||||
)
|
|
||||||
mockedAxios.get.mockImplementation(() => Promise.resolve({ status: 200 }))
|
|
||||||
|
|
||||||
const loginResponse = await authManager.logIn(userName, password)
|
|
||||||
|
|
||||||
expect(loginResponse.isLoggedIn).toBeTruthy()
|
|
||||||
expect(loginResponse.userName).toEqual(userName)
|
|
||||||
|
|
||||||
const loginParams = serialize({
|
|
||||||
_service: 'default',
|
|
||||||
username: userName,
|
|
||||||
password,
|
|
||||||
name: 'test'
|
|
||||||
})
|
|
||||||
expect(mockedAxios.post).toHaveBeenCalledWith(
|
|
||||||
`/SASLogon/login`,
|
|
||||||
loginParams,
|
|
||||||
{
|
|
||||||
withCredentials: true,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
Accept: '*/*'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const casAuthenticationUrl = `${serverUrl}/SASStoredProcess/j_spring_cas_security_check`
|
|
||||||
expect(mockedAxios.get).toHaveBeenCalledWith(
|
|
||||||
`/SASLogon/login?service=${casAuthenticationUrl}`,
|
|
||||||
getHeadersJson
|
|
||||||
)
|
|
||||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return empty username if unable to logged in', async () => {
|
|
||||||
const authManager = new AuthManager(
|
|
||||||
serverUrl,
|
|
||||||
serverType,
|
|
||||||
requestClient,
|
|
||||||
authCallback
|
|
||||||
)
|
|
||||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
isLoggedIn: false,
|
|
||||||
userName: '',
|
|
||||||
loginForm: { name: 'test' }
|
|
||||||
})
|
|
||||||
)
|
|
||||||
mockedAxios.post.mockImplementation(() =>
|
|
||||||
Promise.resolve({ data: 'Not Signed in' })
|
|
||||||
)
|
|
||||||
|
|
||||||
const loginResponse = await authManager.logIn(userName, password)
|
|
||||||
|
|
||||||
expect(loginResponse.isLoggedIn).toBeFalsy()
|
|
||||||
expect(loginResponse.userName).toEqual('')
|
|
||||||
|
|
||||||
const loginParams = serialize({
|
|
||||||
_service: 'default',
|
|
||||||
username: userName,
|
|
||||||
password,
|
|
||||||
name: 'test'
|
|
||||||
})
|
|
||||||
expect(mockedAxios.post).toHaveBeenCalledWith(
|
|
||||||
`/SASLogon/login`,
|
|
||||||
loginParams,
|
|
||||||
{
|
|
||||||
withCredentials: true,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
Accept: '*/*'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should parse and submit the authorisation form when necessary', async () => {
|
|
||||||
const authManager = new AuthManager(
|
|
||||||
serverUrl,
|
|
||||||
serverType,
|
|
||||||
requestClient,
|
|
||||||
authCallback
|
|
||||||
)
|
|
||||||
jest
|
|
||||||
.spyOn(requestClient, 'authorize')
|
|
||||||
.mockImplementation(() => Promise.resolve())
|
|
||||||
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
isLoggedIn: false,
|
|
||||||
userName: 'test',
|
|
||||||
loginForm: { name: 'test' }
|
|
||||||
})
|
|
||||||
)
|
|
||||||
mockedAxios.post.mockImplementationOnce(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
data: mockLoginAuthoriseRequiredResponse,
|
|
||||||
config: { url: 'https://test.com/SASLogon/login' },
|
|
||||||
request: { responseURL: 'https://test.com/OAuth/authorize' }
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
mockedAxios.get.mockImplementationOnce(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
data: mockLoginAuthoriseRequiredResponse
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
await authManager.logIn(userName, password)
|
|
||||||
|
|
||||||
expect(requestClient.authorize).toHaveBeenCalledWith(
|
|
||||||
mockLoginAuthoriseRequiredResponse
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('login - redirect mechanism', () => {
|
it('should post a login request to the server if not logged in', async () => {
|
||||||
beforeAll(() => {
|
const authManager = new AuthManager(
|
||||||
jest.mock('../openWebPage')
|
serverUrl,
|
||||||
jest
|
serverType,
|
||||||
.spyOn(openWebPageModule, 'openWebPage')
|
requestClient,
|
||||||
.mockImplementation(() =>
|
authCallback
|
||||||
Promise.resolve({ close: jest.fn() } as unknown as Window)
|
)
|
||||||
)
|
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
||||||
jest.mock('../verifySasViyaLogin')
|
Promise.resolve({
|
||||||
jest
|
isLoggedIn: false,
|
||||||
.spyOn(verifySasViyaLoginModule, 'verifySasViyaLogin')
|
userName: 'test',
|
||||||
.mockImplementation(() => Promise.resolve({ isLoggedIn: true }))
|
loginForm: { name: 'test' }
|
||||||
jest.mock('../verifySas9Login')
|
})
|
||||||
jest
|
)
|
||||||
.spyOn(verifySas9LoginModule, 'verifySas9Login')
|
mockedAxios.post.mockImplementation(() =>
|
||||||
.mockImplementation(() => Promise.resolve({ isLoggedIn: true }))
|
Promise.resolve({ data: mockLoginSuccessResponse })
|
||||||
})
|
)
|
||||||
|
|
||||||
it('should call the auth callback and return when already logged in', async () => {
|
const loginResponse = await authManager.logIn(userName, password)
|
||||||
const authManager = new AuthManager(
|
|
||||||
serverUrl,
|
expect(loginResponse.isLoggedIn).toBeTruthy()
|
||||||
serverType,
|
expect(loginResponse.userName).toEqual(userName)
|
||||||
requestClient,
|
|
||||||
authCallback
|
const loginParams = serialize({
|
||||||
)
|
_service: 'default',
|
||||||
jest
|
username: userName,
|
||||||
.spyOn<any, any>(authManager, 'fetchUserName')
|
password,
|
||||||
.mockImplementation(() =>
|
name: 'test'
|
||||||
Promise.resolve({
|
|
||||||
isLoggedIn: true,
|
|
||||||
userName
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const loginResponse = await authManager.redirectedLogIn({})
|
|
||||||
|
|
||||||
expect(loginResponse.isLoggedIn).toBeTruthy()
|
|
||||||
expect(loginResponse.userName).toEqual(userName)
|
|
||||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should perform login via pop up if not logged in', async () => {
|
|
||||||
const authManager = new AuthManager(
|
|
||||||
serverUrl,
|
|
||||||
serverType,
|
|
||||||
requestClient,
|
|
||||||
authCallback
|
|
||||||
)
|
|
||||||
jest
|
|
||||||
.spyOn<any, any>(authManager, 'fetchUserName')
|
|
||||||
.mockImplementationOnce(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
isLoggedIn: false,
|
|
||||||
userName: ''
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.mockImplementationOnce(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
isLoggedIn: true,
|
|
||||||
userName
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const loginResponse = await authManager.redirectedLogIn({})
|
|
||||||
|
|
||||||
expect(loginResponse.isLoggedIn).toBeTruthy()
|
|
||||||
expect(loginResponse.userName).toEqual(userName)
|
|
||||||
|
|
||||||
expect(openWebPageModule.openWebPage).toHaveBeenCalledWith(
|
|
||||||
`/SASLogon/home`,
|
|
||||||
'SASLogon',
|
|
||||||
{
|
|
||||||
width: 500,
|
|
||||||
height: 600
|
|
||||||
},
|
|
||||||
undefined
|
|
||||||
)
|
|
||||||
expect(authManager['fetchUserName']).toHaveBeenCalledTimes(2)
|
|
||||||
expect(verifySasViyaLoginModule.verifySasViyaLogin).toHaveBeenCalledTimes(
|
|
||||||
1
|
|
||||||
)
|
|
||||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should perform login via pop up if not logged in with server sas9', async () => {
|
|
||||||
const serverType = ServerType.Sas9
|
|
||||||
const authManager = new AuthManager(
|
|
||||||
serverUrl,
|
|
||||||
serverType,
|
|
||||||
requestClient,
|
|
||||||
authCallback
|
|
||||||
)
|
|
||||||
jest
|
|
||||||
.spyOn<any, any>(authManager, 'fetchUserName')
|
|
||||||
.mockImplementationOnce(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
isLoggedIn: false,
|
|
||||||
userName: ''
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.mockImplementationOnce(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
isLoggedIn: true,
|
|
||||||
userName
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const loginResponse = await authManager.redirectedLogIn({})
|
|
||||||
|
|
||||||
expect(loginResponse.isLoggedIn).toBeTruthy()
|
|
||||||
expect(loginResponse.userName).toEqual(userName)
|
|
||||||
|
|
||||||
expect(openWebPageModule.openWebPage).toHaveBeenCalledWith(
|
|
||||||
`/SASLogon/home`,
|
|
||||||
'SASLogon',
|
|
||||||
{
|
|
||||||
width: 500,
|
|
||||||
height: 600
|
|
||||||
},
|
|
||||||
undefined
|
|
||||||
)
|
|
||||||
expect(authManager['fetchUserName']).toHaveBeenCalledTimes(2)
|
|
||||||
expect(verifySas9LoginModule.verifySas9Login).toHaveBeenCalledTimes(1)
|
|
||||||
expect(authCallback).toHaveBeenCalledTimes(1)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return empty username if user unable to re-login via pop up', async () => {
|
|
||||||
const authManager = new AuthManager(
|
|
||||||
serverUrl,
|
|
||||||
serverType,
|
|
||||||
requestClient,
|
|
||||||
authCallback
|
|
||||||
)
|
|
||||||
jest
|
|
||||||
.spyOn<any, any>(authManager, 'fetchUserName')
|
|
||||||
.mockImplementationOnce(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
isLoggedIn: false,
|
|
||||||
userName: ''
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.mockImplementationOnce(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
isLoggedIn: true,
|
|
||||||
userName
|
|
||||||
})
|
|
||||||
)
|
|
||||||
jest
|
|
||||||
.spyOn(verifySasViyaLoginModule, 'verifySasViyaLogin')
|
|
||||||
.mockImplementation(() => Promise.resolve({ isLoggedIn: false }))
|
|
||||||
|
|
||||||
const loginResponse = await authManager.redirectedLogIn({})
|
|
||||||
|
|
||||||
expect(loginResponse.isLoggedIn).toBeFalsy()
|
|
||||||
expect(loginResponse.userName).toEqual('')
|
|
||||||
|
|
||||||
expect(openWebPageModule.openWebPage).toHaveBeenCalledWith(
|
|
||||||
`/SASLogon/home`,
|
|
||||||
'SASLogon',
|
|
||||||
{
|
|
||||||
width: 500,
|
|
||||||
height: 600
|
|
||||||
},
|
|
||||||
undefined
|
|
||||||
)
|
|
||||||
expect(authManager['fetchUserName']).toHaveBeenCalledTimes(1)
|
|
||||||
|
|
||||||
expect(authCallback).toHaveBeenCalledTimes(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return empty username if user rejects to re-login', async () => {
|
|
||||||
const authManager = new AuthManager(
|
|
||||||
serverUrl,
|
|
||||||
serverType,
|
|
||||||
requestClient,
|
|
||||||
authCallback
|
|
||||||
)
|
|
||||||
jest
|
|
||||||
.spyOn<any, any>(authManager, 'fetchUserName')
|
|
||||||
.mockImplementationOnce(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
isLoggedIn: false,
|
|
||||||
userName: ''
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.mockImplementationOnce(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
isLoggedIn: true,
|
|
||||||
userName
|
|
||||||
})
|
|
||||||
)
|
|
||||||
jest
|
|
||||||
.spyOn(openWebPageModule, 'openWebPage')
|
|
||||||
.mockImplementation(() => Promise.resolve(null))
|
|
||||||
|
|
||||||
const loginResponse = await authManager.redirectedLogIn({})
|
|
||||||
|
|
||||||
expect(loginResponse.isLoggedIn).toBeFalsy()
|
|
||||||
expect(loginResponse.userName).toEqual('')
|
|
||||||
|
|
||||||
expect(openWebPageModule.openWebPage).toHaveBeenCalledWith(
|
|
||||||
`/SASLogon/home`,
|
|
||||||
'SASLogon',
|
|
||||||
{
|
|
||||||
width: 500,
|
|
||||||
height: 600
|
|
||||||
},
|
|
||||||
undefined
|
|
||||||
)
|
|
||||||
expect(authManager['fetchUserName']).toHaveBeenCalledTimes(1)
|
|
||||||
|
|
||||||
expect(authCallback).toHaveBeenCalledTimes(0)
|
|
||||||
})
|
})
|
||||||
|
expect(mockedAxios.post).toHaveBeenCalledWith(
|
||||||
|
`/SASLogon/login`,
|
||||||
|
loginParams,
|
||||||
|
{
|
||||||
|
withCredentials: true,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
|
Accept: '*/*'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
expect(authCallback).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('checkSession', () => {
|
it('should parse and submit the authorisation form when necessary', async () => {
|
||||||
it('return session information when logged in', async () => {
|
const authManager = new AuthManager(
|
||||||
const authManager = new AuthManager(
|
serverUrl,
|
||||||
serverUrl,
|
serverType,
|
||||||
serverType,
|
requestClient,
|
||||||
requestClient,
|
authCallback
|
||||||
authCallback
|
)
|
||||||
)
|
jest
|
||||||
mockedAxios.get.mockImplementation(() =>
|
.spyOn(requestClient, 'authorize')
|
||||||
Promise.resolve({ data: mockedCurrentUserApi(userName) })
|
.mockImplementation(() => Promise.resolve())
|
||||||
)
|
jest.spyOn(authManager, 'checkSession').mockImplementation(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
isLoggedIn: false,
|
||||||
|
userName: 'test',
|
||||||
|
loginForm: { name: 'test' }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
mockedAxios.post.mockImplementationOnce(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
data: mockLoginAuthoriseRequiredResponse,
|
||||||
|
config: { url: 'https://test.com/SASLogon/login' },
|
||||||
|
request: { responseURL: 'https://test.com/OAuth/authorize' }
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
const response = await authManager.checkSession()
|
mockedAxios.get.mockImplementationOnce(() =>
|
||||||
expect(response.isLoggedIn).toBeTruthy()
|
Promise.resolve({
|
||||||
expect(response.userName).toEqual(userName)
|
data: mockLoginAuthoriseRequiredResponse
|
||||||
expect(mockedAxios.get).toHaveBeenNthCalledWith(
|
})
|
||||||
1,
|
)
|
||||||
`http://test-server.com/identities/users/@currentUser`,
|
|
||||||
{
|
await authManager.logIn(userName, password)
|
||||||
withCredentials: true,
|
|
||||||
responseType: 'text',
|
expect(requestClient.authorize).toHaveBeenCalledWith(
|
||||||
transformResponse: undefined,
|
mockLoginAuthoriseRequiredResponse
|
||||||
headers: {
|
)
|
||||||
Accept: '*/*',
|
})
|
||||||
'Content-Type': 'text/plain'
|
|
||||||
}
|
it('should check and return session information if logged in', async () => {
|
||||||
|
const authManager = new AuthManager(
|
||||||
|
serverUrl,
|
||||||
|
serverType,
|
||||||
|
requestClient,
|
||||||
|
authCallback
|
||||||
|
)
|
||||||
|
mockedAxios.get.mockImplementation(() =>
|
||||||
|
Promise.resolve({ data: '<button onClick="logout">' })
|
||||||
|
)
|
||||||
|
|
||||||
|
const response = await authManager.checkSession()
|
||||||
|
expect(response.isLoggedIn).toBeTruthy()
|
||||||
|
expect(mockedAxios.get).toHaveBeenNthCalledWith(
|
||||||
|
1,
|
||||||
|
`http://test-server.com/identities`,
|
||||||
|
{
|
||||||
|
withCredentials: true,
|
||||||
|
responseType: 'text',
|
||||||
|
transformResponse: undefined,
|
||||||
|
headers: {
|
||||||
|
Accept: '*/*',
|
||||||
|
'Content-Type': 'text/plain'
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
it('return session information when logged in - SAS9', async () => {
|
|
||||||
// username cannot have `-` and cannot be uppercased
|
|
||||||
const username = 'testusername'
|
|
||||||
const serverType = ServerType.Sas9
|
|
||||||
const authManager = new AuthManager(
|
|
||||||
serverUrl,
|
|
||||||
serverType,
|
|
||||||
requestClient,
|
|
||||||
authCallback
|
|
||||||
)
|
|
||||||
mockedAxios.get.mockImplementation(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
data: `"title":"Log Off ${username}","url":"javascript: clearFrame(\"/SASStoredProcess/do?_action=logoff\")"' })`
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const response = await authManager.checkSession()
|
|
||||||
expect(response.isLoggedIn).toBeTruthy()
|
|
||||||
expect(response.userName).toEqual(username)
|
|
||||||
expect(mockedAxios.get).toHaveBeenNthCalledWith(
|
|
||||||
1,
|
|
||||||
`http://test-server.com/SASStoredProcess`,
|
|
||||||
{
|
|
||||||
withCredentials: true,
|
|
||||||
responseType: 'text',
|
|
||||||
transformResponse: undefined,
|
|
||||||
headers: {
|
|
||||||
Accept: '*/*',
|
|
||||||
'Content-Type': 'text/plain'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('return session information when logged in - SAS9 - having full name in html', async () => {
|
|
||||||
const fullname = 'FirstName LastName'
|
|
||||||
const username = 'firlas'
|
|
||||||
const serverType = ServerType.Sas9
|
|
||||||
const authManager = new AuthManager(
|
|
||||||
serverUrl,
|
|
||||||
serverType,
|
|
||||||
requestClient,
|
|
||||||
authCallback
|
|
||||||
)
|
|
||||||
mockedAxios.get.mockImplementation(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
data: `"title":"Log Off ${fullname}","url":"javascript: clearFrame(\"/SASStoredProcess/do?_action=logoff\")"' })`
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const response = await authManager.checkSession()
|
|
||||||
expect(response.isLoggedIn).toBeTruthy()
|
|
||||||
expect(response.userName).toEqual(username)
|
|
||||||
expect(mockedAxios.get).toHaveBeenNthCalledWith(
|
|
||||||
1,
|
|
||||||
`http://test-server.com/SASStoredProcess`,
|
|
||||||
{
|
|
||||||
withCredentials: true,
|
|
||||||
responseType: 'text',
|
|
||||||
transformResponse: undefined,
|
|
||||||
headers: {
|
|
||||||
Accept: '*/*',
|
|
||||||
'Content-Type': 'text/plain'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('perform logout when not logged in', async () => {
|
|
||||||
const authManager = new AuthManager(
|
|
||||||
serverUrl,
|
|
||||||
serverType,
|
|
||||||
requestClient,
|
|
||||||
authCallback
|
|
||||||
)
|
|
||||||
mockedAxios.get
|
|
||||||
.mockImplementationOnce(() => Promise.resolve({ status: 401 }))
|
|
||||||
.mockImplementation(() => Promise.resolve({}))
|
|
||||||
|
|
||||||
const response = await authManager.checkSession()
|
|
||||||
expect(response.isLoggedIn).toBeFalsy()
|
|
||||||
expect(response.userName).toEqual('')
|
|
||||||
expect(mockedAxios.get).toHaveBeenNthCalledWith(
|
|
||||||
1,
|
|
||||||
`http://test-server.com/identities/users/@currentUser`,
|
|
||||||
{
|
|
||||||
withCredentials: true,
|
|
||||||
responseType: 'text',
|
|
||||||
transformResponse: undefined,
|
|
||||||
headers: {
|
|
||||||
Accept: '*/*',
|
|
||||||
'Content-Type': 'text/plain'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
expect(mockedAxios.get).toHaveBeenNthCalledWith(
|
|
||||||
2,
|
|
||||||
`/SASLogon/logout.do?`,
|
|
||||||
getHeadersJson
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const getHeadersJson = {
|
|
||||||
withCredentials: true,
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
Accept: 'application/json'
|
|
||||||
},
|
|
||||||
responseType: 'json'
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,28 +22,3 @@ export const generateToken = (timeToLiveSeconds: number): string => {
|
|||||||
const token = `${header}.${payload}.${signature}`
|
const token = `${header}.${payload}.${signature}`
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mockedCurrentUserApi = (username: string) => ({
|
|
||||||
creationTimeStamp: '2021-04-17T14:13:14.000Z',
|
|
||||||
modifiedTimeStamp: '2021-08-31T22:08:07.000Z',
|
|
||||||
id: username,
|
|
||||||
type: 'user',
|
|
||||||
name: 'Full User Name',
|
|
||||||
links: [
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
rel: 'self',
|
|
||||||
href: `/identities/users/${username}`,
|
|
||||||
uri: `/identities/users/${username}`,
|
|
||||||
type: 'user'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
method: 'GET',
|
|
||||||
rel: 'alternate',
|
|
||||||
href: `/identities/users/${username}`,
|
|
||||||
uri: `/identities/users/${username}`,
|
|
||||||
type: 'application/vnd.sas.summary'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
version: 2
|
|
||||||
})
|
|
||||||
|
|||||||
@@ -1,64 +0,0 @@
|
|||||||
/**
|
|
||||||
* @jest-environment jsdom
|
|
||||||
*/
|
|
||||||
import { openWebPage } from '../openWebPage'
|
|
||||||
import * as loginPromptModule from '../../utils/loginPrompt'
|
|
||||||
|
|
||||||
describe('openWebPage', () => {
|
|
||||||
const serverUrl = 'http://test-server.com'
|
|
||||||
|
|
||||||
describe('window.open is not blocked', () => {
|
|
||||||
const mockedOpen = jest
|
|
||||||
.fn()
|
|
||||||
.mockImplementation(() => ({} as unknown as Window))
|
|
||||||
const originalOpen = window.open
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
window.open = mockedOpen
|
|
||||||
})
|
|
||||||
afterAll(() => {
|
|
||||||
window.open = originalOpen
|
|
||||||
})
|
|
||||||
|
|
||||||
it(`should return new Window popup - using default adapter's dialog`, async () => {
|
|
||||||
await expect(openWebPage(serverUrl)).resolves.toBeDefined()
|
|
||||||
|
|
||||||
expect(mockedOpen).toBeCalled()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('window.open is blocked', () => {
|
|
||||||
const mockedOpen = jest.fn().mockImplementation(() => null)
|
|
||||||
const originalOpen = window.open
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
window.open = mockedOpen
|
|
||||||
})
|
|
||||||
afterAll(() => {
|
|
||||||
window.open = originalOpen
|
|
||||||
})
|
|
||||||
|
|
||||||
it(`should return new Window popup - using default adapter's dialog`, async () => {
|
|
||||||
jest.mock('../../utils/loginPrompt')
|
|
||||||
jest
|
|
||||||
.spyOn(loginPromptModule, 'openLoginPrompt')
|
|
||||||
.mockImplementation(() => Promise.resolve(true))
|
|
||||||
|
|
||||||
await expect(openWebPage(serverUrl)).resolves.toBeDefined()
|
|
||||||
expect(loginPromptModule.openLoginPrompt).toBeCalled()
|
|
||||||
expect(mockedOpen).toBeCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it(`should return new Window popup - using frontend's provided onloggedOut`, async () => {
|
|
||||||
const onLoggedOut = jest
|
|
||||||
.fn()
|
|
||||||
.mockImplementation(() => Promise.resolve(true))
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
openWebPage(serverUrl, undefined, undefined, onLoggedOut)
|
|
||||||
).resolves.toBeDefined()
|
|
||||||
expect(onLoggedOut).toBeCalled()
|
|
||||||
expect(mockedOpen).toBeCalled()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
/**
|
|
||||||
* @jest-environment jsdom
|
|
||||||
*/
|
|
||||||
import { verifySas9Login } from '../verifySas9Login'
|
|
||||||
import * as delayModule from '../../utils/delay'
|
|
||||||
|
|
||||||
describe('verifySas9Login', () => {
|
|
||||||
const serverUrl = 'http://test-server.com'
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
jest.mock('../../utils')
|
|
||||||
jest
|
|
||||||
.spyOn(delayModule, 'delay')
|
|
||||||
.mockImplementation(() => Promise.resolve({}))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return isLoggedIn true by checking state of popup', async () => {
|
|
||||||
const popup = {
|
|
||||||
window: {
|
|
||||||
location: { href: serverUrl + `/SASLogon/home` },
|
|
||||||
document: { body: { innerText: '<h3>You have signed in.</h3>' } }
|
|
||||||
}
|
|
||||||
} as unknown as Window
|
|
||||||
|
|
||||||
await expect(verifySas9Login(popup)).resolves.toEqual({
|
|
||||||
isLoggedIn: true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return isLoggedIn false if user closed popup, already', async () => {
|
|
||||||
const popup: Window = { closed: true } as unknown as Window
|
|
||||||
|
|
||||||
await expect(verifySas9Login(popup)).resolves.toEqual({
|
|
||||||
isLoggedIn: false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
/**
|
|
||||||
* @jest-environment jsdom
|
|
||||||
*/
|
|
||||||
import { verifySasViyaLogin } from '../verifySasViyaLogin'
|
|
||||||
import * as delayModule from '../../utils/delay'
|
|
||||||
|
|
||||||
describe('verifySasViyaLogin', () => {
|
|
||||||
const serverUrl = 'http://test-server.com'
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
jest.mock('../../utils')
|
|
||||||
jest
|
|
||||||
.spyOn(delayModule, 'delay')
|
|
||||||
.mockImplementation(() => Promise.resolve({}))
|
|
||||||
document.cookie = encodeURIComponent('Current-User={"userId":"user-hash"}')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return isLoggedIn true by checking state of popup', async () => {
|
|
||||||
const popup = {
|
|
||||||
window: {
|
|
||||||
location: { href: serverUrl + `/SASLogon/home` },
|
|
||||||
document: { body: { innerText: '<h3>You have signed in.</h3>' } }
|
|
||||||
}
|
|
||||||
} as unknown as Window
|
|
||||||
|
|
||||||
await expect(verifySasViyaLogin(popup)).resolves.toEqual({
|
|
||||||
isLoggedIn: true
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return isLoggedIn false if user closed popup, already', async () => {
|
|
||||||
const popup: Window = { closed: true } as unknown as Window
|
|
||||||
|
|
||||||
await expect(verifySasViyaLogin(popup)).resolves.toEqual({
|
|
||||||
isLoggedIn: false
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
import { delay } from '../utils'
|
|
||||||
|
|
||||||
export async function verifySas9Login(loginPopup: Window): Promise<{
|
|
||||||
isLoggedIn: boolean
|
|
||||||
}> {
|
|
||||||
let isLoggedIn = false
|
|
||||||
let startTime = new Date()
|
|
||||||
let elapsedSeconds = 0
|
|
||||||
do {
|
|
||||||
await delay(1000)
|
|
||||||
if (loginPopup.closed) break
|
|
||||||
|
|
||||||
isLoggedIn =
|
|
||||||
loginPopup.window.location.href.includes('SASLogon') &&
|
|
||||||
loginPopup.window.document.body.innerText.includes('You have signed in.')
|
|
||||||
elapsedSeconds = (new Date().valueOf() - startTime.valueOf()) / 1000
|
|
||||||
} while (!isLoggedIn && elapsedSeconds < 5 * 60)
|
|
||||||
|
|
||||||
return { isLoggedIn }
|
|
||||||
}
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { delay } from '../utils'
|
|
||||||
|
|
||||||
export async function verifySasViyaLogin(loginPopup: Window): Promise<{
|
|
||||||
isLoggedIn: boolean
|
|
||||||
}> {
|
|
||||||
let isLoggedIn = false
|
|
||||||
let startTime = new Date()
|
|
||||||
let elapsedSeconds = 0
|
|
||||||
do {
|
|
||||||
await delay(1000)
|
|
||||||
if (loginPopup.closed) break
|
|
||||||
isLoggedIn = isLoggedInSASVIYA()
|
|
||||||
elapsedSeconds = (new Date().valueOf() - startTime.valueOf()) / 1000
|
|
||||||
} while (!isLoggedIn && elapsedSeconds < 5 * 60)
|
|
||||||
|
|
||||||
let isAuthorized = false
|
|
||||||
startTime = new Date()
|
|
||||||
do {
|
|
||||||
await delay(1000)
|
|
||||||
if (loginPopup.closed) break
|
|
||||||
isAuthorized =
|
|
||||||
loginPopup.window.location.href.includes('SASLogon') ||
|
|
||||||
loginPopup.window.document.body?.innerText?.includes(
|
|
||||||
'You have signed in.'
|
|
||||||
)
|
|
||||||
elapsedSeconds = (new Date().valueOf() - startTime.valueOf()) / 1000
|
|
||||||
} while (!isAuthorized && elapsedSeconds < 5 * 60)
|
|
||||||
|
|
||||||
return { isLoggedIn: isLoggedIn && isAuthorized }
|
|
||||||
}
|
|
||||||
|
|
||||||
export const isLoggedInSASVIYA = () =>
|
|
||||||
document.cookie.includes('Current-User') && document.cookie.includes('userId')
|
|
||||||
@@ -35,12 +35,11 @@ export class ComputeJobExecutor extends BaseJobExecutor {
|
|||||||
expectWebout
|
expectWebout
|
||||||
)
|
)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
this.sasViyaApiClient.appendRequest(response, sasJob, config.debug)
|
console.log('then block of compute job executor')
|
||||||
resolve(response.result)
|
resolve(response.result)
|
||||||
})
|
})
|
||||||
.catch(async (e: Error) => {
|
.catch(async (e: Error) => {
|
||||||
if (e instanceof ComputeJobExecutionError) {
|
if (e instanceof ComputeJobExecutionError) {
|
||||||
this.sasViyaApiClient.appendRequest(e, sasJob, config.debug)
|
|
||||||
reject(new ErrorResponse(e?.message, e))
|
reject(new ErrorResponse(e?.message, e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,143 +0,0 @@
|
|||||||
import {
|
|
||||||
getValidJson,
|
|
||||||
parseSasViyaDebugResponse,
|
|
||||||
parseWeboutResponse
|
|
||||||
} from '../utils'
|
|
||||||
import { UploadFile } from '../types/UploadFile'
|
|
||||||
import {
|
|
||||||
ErrorResponse,
|
|
||||||
JobExecutionError,
|
|
||||||
LoginRequiredError
|
|
||||||
} from '../types/errors'
|
|
||||||
import { RequestClient } from '../request/RequestClient'
|
|
||||||
import { ServerType } from '@sasjs/utils/types'
|
|
||||||
import { BaseJobExecutor } from './JobExecutor'
|
|
||||||
|
|
||||||
interface dataFileUpload {
|
|
||||||
files: UploadFile[]
|
|
||||||
params: { [key: string]: any } | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export class FileUploader extends BaseJobExecutor {
|
|
||||||
constructor(
|
|
||||||
serverUrl: string,
|
|
||||||
serverType: ServerType,
|
|
||||||
private jobsPath: string,
|
|
||||||
private requestClient: RequestClient
|
|
||||||
) {
|
|
||||||
super(serverUrl, serverType)
|
|
||||||
}
|
|
||||||
|
|
||||||
public async execute(
|
|
||||||
sasJob: string,
|
|
||||||
data: any,
|
|
||||||
config: any,
|
|
||||||
loginRequiredCallback?: any
|
|
||||||
) {
|
|
||||||
const { files, params }: dataFileUpload = data
|
|
||||||
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
|
||||||
|
|
||||||
if (!files?.length)
|
|
||||||
throw new ErrorResponse('At least one file must be provided.')
|
|
||||||
|
|
||||||
if (!sasJob || sasJob === '')
|
|
||||||
throw new ErrorResponse('sasJob must be provided.')
|
|
||||||
|
|
||||||
let paramsString = ''
|
|
||||||
|
|
||||||
for (let param in params)
|
|
||||||
if (params.hasOwnProperty(param))
|
|
||||||
paramsString += `&${param}=${params[param]}`
|
|
||||||
|
|
||||||
const program = config.appLoc
|
|
||||||
? config.appLoc.replace(/\/?$/, '/') + sasJob.replace(/^\//, '')
|
|
||||||
: sasJob
|
|
||||||
const uploadUrl = `${this.jobsPath}/?${
|
|
||||||
'_program=' + program
|
|
||||||
}${paramsString}`
|
|
||||||
|
|
||||||
const formData = new FormData()
|
|
||||||
|
|
||||||
for (let file of files) {
|
|
||||||
formData.append('file', file.file, file.fileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
const csrfToken = this.requestClient.getCsrfToken('file')
|
|
||||||
if (csrfToken) formData.append('_csrf', csrfToken.value)
|
|
||||||
if (config.debug) formData.append('_debug', '131')
|
|
||||||
if (config.serverType === ServerType.SasViya && config.contextName)
|
|
||||||
formData.append('_contextname', config.contextName)
|
|
||||||
|
|
||||||
const headers = {
|
|
||||||
'cache-control': 'no-cache',
|
|
||||||
Accept: '*/*',
|
|
||||||
'Content-Type': 'text/plain'
|
|
||||||
}
|
|
||||||
|
|
||||||
// currently only web approach is supported for file upload
|
|
||||||
// therefore log is part of response with debug enabled and must be parsed
|
|
||||||
const requestPromise = new Promise((resolve, reject) => {
|
|
||||||
this.requestClient
|
|
||||||
.post(uploadUrl, formData, undefined, 'application/json', headers)
|
|
||||||
.then(async (res: any) => {
|
|
||||||
this.requestClient.appendRequest(res, sasJob, config.debug)
|
|
||||||
|
|
||||||
let jsonResponse = res.result
|
|
||||||
|
|
||||||
if (config.debug) {
|
|
||||||
switch (this.serverType) {
|
|
||||||
case ServerType.SasViya:
|
|
||||||
jsonResponse = await parseSasViyaDebugResponse(
|
|
||||||
res.result,
|
|
||||||
this.requestClient,
|
|
||||||
config.serverUrl
|
|
||||||
)
|
|
||||||
break
|
|
||||||
case ServerType.Sas9:
|
|
||||||
jsonResponse =
|
|
||||||
typeof res.result === 'string'
|
|
||||||
? parseWeboutResponse(res.result, uploadUrl)
|
|
||||||
: res.result
|
|
||||||
break
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
jsonResponse =
|
|
||||||
typeof res.result === 'string'
|
|
||||||
? getValidJson(res.result)
|
|
||||||
: res.result
|
|
||||||
}
|
|
||||||
|
|
||||||
resolve(jsonResponse)
|
|
||||||
})
|
|
||||||
.catch(async (e: Error) => {
|
|
||||||
if (e instanceof JobExecutionError) {
|
|
||||||
this.requestClient!.appendRequest(e, sasJob, config.debug)
|
|
||||||
reject(new ErrorResponse(e?.message, e))
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e instanceof LoginRequiredError) {
|
|
||||||
this.appendWaitingRequest(() => {
|
|
||||||
return this.execute(
|
|
||||||
sasJob,
|
|
||||||
data,
|
|
||||||
config,
|
|
||||||
loginRequiredCallback
|
|
||||||
).then(
|
|
||||||
(res: any) => {
|
|
||||||
resolve(res)
|
|
||||||
},
|
|
||||||
(err: any) => {
|
|
||||||
reject(err)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
await loginCallback()
|
|
||||||
} else {
|
|
||||||
reject(new ErrorResponse('File upload request failed.', e))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return requestPromise
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,6 @@ import {
|
|||||||
} from '../types/errors'
|
} from '../types/errors'
|
||||||
import { ExtraResponseAttributes } from '@sasjs/utils/types'
|
import { ExtraResponseAttributes } from '@sasjs/utils/types'
|
||||||
import { BaseJobExecutor } from './JobExecutor'
|
import { BaseJobExecutor } from './JobExecutor'
|
||||||
import { appendExtraResponseAttributes } from '../utils'
|
|
||||||
|
|
||||||
export class JesJobExecutor extends BaseJobExecutor {
|
export class JesJobExecutor extends BaseJobExecutor {
|
||||||
constructor(serverUrl: string, private sasViyaApiClient: SASViyaApiClient) {
|
constructor(serverUrl: string, private sasViyaApiClient: SASViyaApiClient) {
|
||||||
@@ -28,19 +27,26 @@ export class JesJobExecutor extends BaseJobExecutor {
|
|||||||
this.sasViyaApiClient
|
this.sasViyaApiClient
|
||||||
?.executeJob(sasJob, config.contextName, config.debug, data, authConfig)
|
?.executeJob(sasJob, config.contextName, config.debug, data, authConfig)
|
||||||
.then((response: any) => {
|
.then((response: any) => {
|
||||||
this.sasViyaApiClient.appendRequest(response, sasJob, config.debug)
|
let responseObject = {}
|
||||||
|
|
||||||
const responseObject = appendExtraResponseAttributes(
|
if (extraResponseAttributes && extraResponseAttributes.length > 0) {
|
||||||
response,
|
const extraAttributes = extraResponseAttributes.reduce(
|
||||||
extraResponseAttributes
|
(map: any, obj: any) => ((map[obj] = response[obj]), map),
|
||||||
)
|
{}
|
||||||
|
)
|
||||||
|
|
||||||
|
responseObject = {
|
||||||
|
result: response.result,
|
||||||
|
...extraAttributes
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
responseObject = response.result
|
||||||
|
}
|
||||||
|
|
||||||
resolve(responseObject)
|
resolve(responseObject)
|
||||||
})
|
})
|
||||||
.catch(async (e: Error) => {
|
.catch(async (e: Error) => {
|
||||||
if (e instanceof JobExecutionError) {
|
if (e instanceof JobExecutionError) {
|
||||||
this.sasViyaApiClient.appendRequest(e, sasJob, config.debug)
|
|
||||||
|
|
||||||
reject(new ErrorResponse(e?.message, e))
|
reject(new ErrorResponse(e?.message, e))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { AuthConfig, ServerType } from '@sasjs/utils/types'
|
import { AuthConfig, ServerType } from '@sasjs/utils/types'
|
||||||
|
import { SASjsRequest } from '../types'
|
||||||
import { ExtraResponseAttributes } from '@sasjs/utils/types'
|
import { ExtraResponseAttributes } from '@sasjs/utils/types'
|
||||||
import { asyncForEach } from '../utils'
|
import { asyncForEach, parseGeneratedCode, parseSourceCode } from '../utils'
|
||||||
|
|
||||||
export type ExecuteFunction = () => Promise<any>
|
export type ExecuteFunction = () => Promise<any>
|
||||||
|
|
||||||
@@ -20,6 +21,7 @@ export abstract class BaseJobExecutor implements JobExecutor {
|
|||||||
constructor(protected serverUrl: string, protected serverType: ServerType) {}
|
constructor(protected serverUrl: string, protected serverType: ServerType) {}
|
||||||
|
|
||||||
private waitingRequests: ExecuteFunction[] = []
|
private waitingRequests: ExecuteFunction[] = []
|
||||||
|
private requests: SASjsRequest[] = []
|
||||||
|
|
||||||
abstract execute(
|
abstract execute(
|
||||||
sasJob: string,
|
sasJob: string,
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export class Sas9JobExecutor extends BaseJobExecutor {
|
|||||||
if (data) {
|
if (data) {
|
||||||
try {
|
try {
|
||||||
formData = generateFileUploadForm(formData, data)
|
formData = generateFileUploadForm(formData, data)
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Promise.reject(new ErrorResponse(e?.message, e))
|
return Promise.reject(new ErrorResponse(e?.message, e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
import {
|
import { ServerType } from '@sasjs/utils/types'
|
||||||
AuthConfig,
|
|
||||||
ExtraResponseAttributes,
|
|
||||||
ServerType
|
|
||||||
} from '@sasjs/utils/types'
|
|
||||||
import {
|
import {
|
||||||
ErrorResponse,
|
ErrorResponse,
|
||||||
JobExecutionError,
|
JobExecutionError,
|
||||||
LoginRequiredError
|
LoginRequiredError,
|
||||||
|
WeboutResponseError
|
||||||
} from '../types/errors'
|
} from '../types/errors'
|
||||||
import { generateFileUploadForm } from '../file/generateFileUploadForm'
|
import { generateFileUploadForm } from '../file/generateFileUploadForm'
|
||||||
import { generateTableUploadForm } from '../file/generateTableUploadForm'
|
import { generateTableUploadForm } from '../file/generateTableUploadForm'
|
||||||
@@ -14,8 +11,8 @@ import { RequestClient } from '../request/RequestClient'
|
|||||||
import { SASViyaApiClient } from '../SASViyaApiClient'
|
import { SASViyaApiClient } from '../SASViyaApiClient'
|
||||||
import {
|
import {
|
||||||
isRelativePath,
|
isRelativePath,
|
||||||
parseSasViyaDebugResponse,
|
getValidJson,
|
||||||
appendExtraResponseAttributes
|
parseSasViyaDebugResponse
|
||||||
} from '../utils'
|
} from '../utils'
|
||||||
import { BaseJobExecutor } from './JobExecutor'
|
import { BaseJobExecutor } from './JobExecutor'
|
||||||
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||||
@@ -40,9 +37,7 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
sasJob: string,
|
sasJob: string,
|
||||||
data: any,
|
data: any,
|
||||||
config: any,
|
config: any,
|
||||||
loginRequiredCallback?: any,
|
loginRequiredCallback?: any
|
||||||
authConfig?: AuthConfig,
|
|
||||||
extraResponseAttributes: ExtraResponseAttributes[] = []
|
|
||||||
) {
|
) {
|
||||||
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
const loginCallback = loginRequiredCallback || (() => Promise.resolve())
|
||||||
const program = isRelativePath(sasJob)
|
const program = isRelativePath(sasJob)
|
||||||
@@ -53,7 +48,10 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
let apiUrl = `${config.serverUrl}${this.jobsPath}/?${'_program=' + program}`
|
let apiUrl = `${config.serverUrl}${this.jobsPath}/?${'_program=' + program}`
|
||||||
|
|
||||||
if (config.serverType === ServerType.SasViya) {
|
if (config.serverType === ServerType.SasViya) {
|
||||||
const jobUri = await this.getJobUri(sasJob)
|
const jobUri =
|
||||||
|
config.serverType === ServerType.SasViya
|
||||||
|
? await this.getJobUri(sasJob)
|
||||||
|
: ''
|
||||||
|
|
||||||
apiUrl += jobUri.length > 0 ? '&_job=' + jobUri : ''
|
apiUrl += jobUri.length > 0 ? '&_job=' + jobUri : ''
|
||||||
|
|
||||||
@@ -90,7 +88,7 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
// file upload approach
|
// file upload approach
|
||||||
try {
|
try {
|
||||||
formData = generateFileUploadForm(formData, data)
|
formData = generateFileUploadForm(formData, data)
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Promise.reject(new ErrorResponse(e?.message, e))
|
return Promise.reject(new ErrorResponse(e?.message, e))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -100,7 +98,7 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
generateTableUploadForm(formData, data)
|
generateTableUploadForm(formData, data)
|
||||||
formData = newFormData
|
formData = newFormData
|
||||||
requestParams = { ...requestParams, ...params }
|
requestParams = { ...requestParams, ...params }
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
return Promise.reject(new ErrorResponse(e?.message, e))
|
return Promise.reject(new ErrorResponse(e?.message, e))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,51 +111,50 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const requestPromise = new Promise((resolve, reject) => {
|
const requestPromise = new Promise((resolve, reject) => {
|
||||||
this.requestClient!.post(apiUrl, formData, undefined)
|
this.requestClient!.post(
|
||||||
|
apiUrl,
|
||||||
|
formData,
|
||||||
|
undefined,
|
||||||
|
'application/json',
|
||||||
|
{},
|
||||||
|
config.debug,
|
||||||
|
true,
|
||||||
|
sasJob
|
||||||
|
)
|
||||||
.then(async (res: any) => {
|
.then(async (res: any) => {
|
||||||
this.requestClient!.appendRequest(res, sasJob, config.debug)
|
if (this.serverType === ServerType.SasViya && config.debug) {
|
||||||
|
const jsonResponse = await parseSasViyaDebugResponse(
|
||||||
let jsonResponse = res.result
|
res.result,
|
||||||
|
this.requestClient,
|
||||||
if (config.debug) {
|
this.serverUrl
|
||||||
switch (this.serverType) {
|
)
|
||||||
case ServerType.SasViya:
|
resolve(jsonResponse)
|
||||||
jsonResponse = await parseSasViyaDebugResponse(
|
|
||||||
res.result,
|
|
||||||
this.requestClient,
|
|
||||||
this.serverUrl
|
|
||||||
)
|
|
||||||
break
|
|
||||||
case ServerType.Sas9:
|
|
||||||
jsonResponse =
|
|
||||||
typeof res.result === 'string'
|
|
||||||
? parseWeboutResponse(res.result, apiUrl)
|
|
||||||
: res.result
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (this.serverType === ServerType.Sas9 && config.debug) {
|
||||||
|
let jsonResponse = res.result
|
||||||
|
if (typeof res.result === 'string')
|
||||||
|
jsonResponse = parseWeboutResponse(res.result, apiUrl)
|
||||||
|
|
||||||
const responseObject = appendExtraResponseAttributes(
|
getValidJson(jsonResponse)
|
||||||
{ result: jsonResponse },
|
resolve(res.result)
|
||||||
extraResponseAttributes
|
}
|
||||||
)
|
getValidJson(res.result as string)
|
||||||
resolve(responseObject)
|
resolve(res.result)
|
||||||
})
|
})
|
||||||
.catch(async (e: Error) => {
|
.catch(async (e: Error) => {
|
||||||
if (e instanceof JobExecutionError) {
|
if (e instanceof JobExecutionError) {
|
||||||
this.requestClient!.appendRequest(e, sasJob, config.debug)
|
|
||||||
reject(new ErrorResponse(e?.message, e))
|
reject(new ErrorResponse(e?.message, e))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e instanceof LoginRequiredError) {
|
if (e instanceof LoginRequiredError) {
|
||||||
|
await loginCallback()
|
||||||
|
|
||||||
this.appendWaitingRequest(() => {
|
this.appendWaitingRequest(() => {
|
||||||
return this.execute(
|
return this.execute(
|
||||||
sasJob,
|
sasJob,
|
||||||
data,
|
data,
|
||||||
config,
|
config,
|
||||||
loginRequiredCallback,
|
loginRequiredCallback
|
||||||
authConfig,
|
|
||||||
extraResponseAttributes
|
|
||||||
).then(
|
).then(
|
||||||
(res: any) => {
|
(res: any) => {
|
||||||
resolve(res)
|
resolve(res)
|
||||||
@@ -167,8 +164,6 @@ export class WebJobExecutor extends BaseJobExecutor {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
await loginCallback()
|
|
||||||
} else {
|
} else {
|
||||||
reject(new ErrorResponse(e?.message, e))
|
reject(new ErrorResponse(e?.message, e))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,4 +3,3 @@ export * from './JesJobExecutor'
|
|||||||
export * from './JobExecutor'
|
export * from './JobExecutor'
|
||||||
export * from './Sas9JobExecutor'
|
export * from './Sas9JobExecutor'
|
||||||
export * from './WebJobExecutor'
|
export * from './WebJobExecutor'
|
||||||
export * from './FileUploader'
|
|
||||||
|
|||||||
@@ -52,14 +52,25 @@ export class RequestClient implements HttpClient {
|
|||||||
|
|
||||||
protected csrfToken: CsrfToken = { headerName: '', value: '' }
|
protected csrfToken: CsrfToken = { headerName: '', value: '' }
|
||||||
protected fileUploadCsrfToken: CsrfToken | undefined
|
protected fileUploadCsrfToken: CsrfToken | undefined
|
||||||
protected httpClient!: AxiosInstance
|
protected httpClient: AxiosInstance
|
||||||
|
|
||||||
constructor(protected baseUrl: string, allowInsecure = false) {
|
constructor(protected baseUrl: string, allowInsecure = false) {
|
||||||
this.createHttpClient(baseUrl, allowInsecure)
|
const https = require('https')
|
||||||
}
|
if (allowInsecure && https.Agent) {
|
||||||
|
this.httpClient = axios.create({
|
||||||
|
baseURL: baseUrl,
|
||||||
|
httpsAgent: new https.Agent({
|
||||||
|
rejectUnauthorized: !allowInsecure
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
this.httpClient = axios.create({
|
||||||
|
baseURL: baseUrl
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
public setConfig(baseUrl: string, allowInsecure = false) {
|
this.httpClient.defaults.validateStatus = (status) =>
|
||||||
this.createHttpClient(baseUrl, allowInsecure)
|
status >= 200 && status < 305
|
||||||
}
|
}
|
||||||
|
|
||||||
public getCsrfToken(type: 'general' | 'file' = 'general') {
|
public getCsrfToken(type: 'general' | 'file' = 'general') {
|
||||||
@@ -94,7 +105,8 @@ export class RequestClient implements HttpClient {
|
|||||||
* @param program - name of program
|
* @param program - name of program
|
||||||
* @param debug - a boolean that indicates whether debug was enabled or not
|
* @param debug - a boolean that indicates whether debug was enabled or not
|
||||||
*/
|
*/
|
||||||
public appendRequest(response: any, program: string, debug: boolean) {
|
public appendRequest = (response: any, program: string, debug: boolean) => {
|
||||||
|
console.log('from appendRequest')
|
||||||
let sourceCode = ''
|
let sourceCode = ''
|
||||||
let generatedCode = ''
|
let generatedCode = ''
|
||||||
let sasWork = null
|
let sasWork = null
|
||||||
@@ -140,7 +152,9 @@ export class RequestClient implements HttpClient {
|
|||||||
accessToken: string | undefined,
|
accessToken: string | undefined,
|
||||||
contentType: string = 'application/json',
|
contentType: string = 'application/json',
|
||||||
overrideHeaders: { [key: string]: string | number } = {},
|
overrideHeaders: { [key: string]: string | number } = {},
|
||||||
debug: boolean = false
|
debug: boolean = false,
|
||||||
|
captureRequest: boolean = false,
|
||||||
|
sasJob: string = ''
|
||||||
): Promise<{ result: T; etag: string; status: number }> {
|
): Promise<{ result: T; etag: string; status: number }> {
|
||||||
const headers = {
|
const headers = {
|
||||||
...this.getHeaders(accessToken, contentType),
|
...this.getHeaders(accessToken, contentType),
|
||||||
@@ -160,8 +174,11 @@ export class RequestClient implements HttpClient {
|
|||||||
.get<T>(url, requestConfig)
|
.get<T>(url, requestConfig)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
throwIfError(response)
|
throwIfError(response)
|
||||||
|
const responseToReturn = this.parseResponse<T>(response)
|
||||||
return this.parseResponse<T>(response)
|
if (captureRequest) {
|
||||||
|
this.appendRequest(responseToReturn, sasJob, debug)
|
||||||
|
}
|
||||||
|
return responseToReturn
|
||||||
})
|
})
|
||||||
.catch(async (e) => {
|
.catch(async (e) => {
|
||||||
return await this.handleError(
|
return await this.handleError(
|
||||||
@@ -182,12 +199,15 @@ export class RequestClient implements HttpClient {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public async post<T>(
|
public post<T>(
|
||||||
url: string,
|
url: string,
|
||||||
data: any,
|
data: any,
|
||||||
accessToken: string | undefined,
|
accessToken: string | undefined,
|
||||||
contentType = 'application/json',
|
contentType = 'application/json',
|
||||||
overrideHeaders: { [key: string]: string | number } = {}
|
overrideHeaders: { [key: string]: string | number } = {},
|
||||||
|
debug: boolean = false,
|
||||||
|
captureRequest: boolean = false,
|
||||||
|
sasJob: string = ''
|
||||||
): Promise<{ result: T; etag: string }> {
|
): Promise<{ result: T; etag: string }> {
|
||||||
const headers = {
|
const headers = {
|
||||||
...this.getHeaders(accessToken, contentType),
|
...this.getHeaders(accessToken, contentType),
|
||||||
@@ -198,7 +218,11 @@ export class RequestClient implements HttpClient {
|
|||||||
.post<T>(url, data, { headers, withCredentials: true })
|
.post<T>(url, data, { headers, withCredentials: true })
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
throwIfError(response)
|
throwIfError(response)
|
||||||
return this.parseResponse<T>(response)
|
const responseToReturn = this.parseResponse<T>(response)
|
||||||
|
if (captureRequest) {
|
||||||
|
this.appendRequest(responseToReturn, sasJob, debug)
|
||||||
|
}
|
||||||
|
return responseToReturn
|
||||||
})
|
})
|
||||||
.catch(async (e) => {
|
.catch(async (e) => {
|
||||||
return await this.handleError(e, () =>
|
return await this.handleError(e, () =>
|
||||||
@@ -286,7 +310,7 @@ export class RequestClient implements HttpClient {
|
|||||||
result: response.data,
|
result: response.data,
|
||||||
etag: response.headers['etag'] as string
|
etag: response.headers['etag'] as string
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e) {
|
||||||
const response = e.response as AxiosResponse
|
const response = e.response as AxiosResponse
|
||||||
if (response?.status === 403 || response?.status === 449) {
|
if (response?.status === 403 || response?.status === 449) {
|
||||||
this.parseAndSetFileUploadCsrfToken(response)
|
this.parseAndSetFileUploadCsrfToken(response)
|
||||||
@@ -506,25 +530,6 @@ export class RequestClient implements HttpClient {
|
|||||||
|
|
||||||
return responseToReturn
|
return responseToReturn
|
||||||
}
|
}
|
||||||
|
|
||||||
private createHttpClient(baseUrl: string, allowInsecure = false) {
|
|
||||||
const https = require('https')
|
|
||||||
if (allowInsecure && https.Agent) {
|
|
||||||
this.httpClient = axios.create({
|
|
||||||
baseURL: baseUrl,
|
|
||||||
httpsAgent: new https.Agent({
|
|
||||||
rejectUnauthorized: !allowInsecure
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.httpClient = axios.create({
|
|
||||||
baseURL: baseUrl
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.httpClient.defaults.validateStatus = (status) =>
|
|
||||||
status >= 200 && status < 305
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const throwIfError = (response: AxiosResponse) => {
|
export const throwIfError = (response: AxiosResponse) => {
|
||||||
@@ -570,60 +575,46 @@ export const throwIfError = (response: AxiosResponse) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const parseError = (data: string) => {
|
const parseError = (data: string) => {
|
||||||
if (!data) return null
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const responseJson = JSON.parse(data?.replace(/[\n\r]/g, ' '))
|
const responseJson = JSON.parse(data?.replace(/[\n\r]/g, ' '))
|
||||||
if (responseJson.errorCode && responseJson.message) {
|
return responseJson.errorCode && responseJson.message
|
||||||
return new JobExecutionError(
|
? new JobExecutionError(
|
||||||
responseJson.errorCode,
|
responseJson.errorCode,
|
||||||
responseJson.message,
|
responseJson.message,
|
||||||
data?.replace(/[\n\r]/g, ' ')
|
data?.replace(/[\n\r]/g, ' ')
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const hasError = data?.includes('{"errorCode')
|
|
||||||
if (hasError) {
|
|
||||||
const parts = data.split('{"errorCode')
|
|
||||||
if (parts.length > 1) {
|
|
||||||
const error = '{"errorCode' + parts[1].split('"}')[0] + '"}'
|
|
||||||
const errorJson = JSON.parse(error.replace(/[\n\r]/g, ' '))
|
|
||||||
return new JobExecutionError(
|
|
||||||
errorJson.errorCode,
|
|
||||||
errorJson.message,
|
|
||||||
data?.replace(/[\n\r]/g, '\n')
|
|
||||||
)
|
)
|
||||||
|
: null
|
||||||
|
} catch (_) {
|
||||||
|
try {
|
||||||
|
const hasError = data?.includes('{"errorCode')
|
||||||
|
if (hasError) {
|
||||||
|
const parts = data.split('{"errorCode')
|
||||||
|
if (parts.length > 1) {
|
||||||
|
const error = '{"errorCode' + parts[1].split('"}')[0] + '"}'
|
||||||
|
const errorJson = JSON.parse(error.replace(/[\n\r]/g, ' '))
|
||||||
|
return new JobExecutionError(
|
||||||
|
errorJson.errorCode,
|
||||||
|
errorJson.message,
|
||||||
|
data?.replace(/[\n\r]/g, '\n')
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
}
|
try {
|
||||||
} catch (_) {}
|
const hasError = !!data?.match(/stored process not found: /i)
|
||||||
|
if (hasError) {
|
||||||
try {
|
const parts = data.split(/stored process not found: /i)
|
||||||
const hasError = !!data?.match(/stored process not found: /i)
|
if (parts.length > 1) {
|
||||||
if (hasError) {
|
const storedProcessPath = parts[1].split('<i>')[1].split('</i>')[0]
|
||||||
const parts = data.split(/stored process not found: /i)
|
const message = `Stored process not found: ${storedProcessPath}`
|
||||||
if (parts.length > 1) {
|
return new JobExecutionError(404, message, '')
|
||||||
const storedProcessPath = parts[1].split('<i>')[1].split('</i>')[0]
|
}
|
||||||
const message = `Stored process not found: ${storedProcessPath}`
|
}
|
||||||
return new JobExecutionError(404, message, '')
|
} catch (_) {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
|
} catch (_) {
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
} catch (_) {}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const hasError =
|
|
||||||
!!data?.match(/Stored Process Error/i) &&
|
|
||||||
!!data?.match(/This request completed with errors./i)
|
|
||||||
if (hasError) {
|
|
||||||
const parts = data.split('<h2>SAS Log</h2>')
|
|
||||||
if (parts.length > 1) {
|
|
||||||
const log = parts[1].split('<pre>')[1].split('</pre>')[0]
|
|
||||||
const message = `This request completed with errors.`
|
|
||||||
return new JobExecutionError(404, message, log)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @jest-environment jsdom
|
* @jest-environment jsdom
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { FileUploader } from '../job-execution/FileUploader'
|
import { FileUploader } from '../FileUploader'
|
||||||
import { SASjsConfig, UploadFile } from '../types'
|
import { SASjsConfig, UploadFile } from '../types'
|
||||||
import { RequestClient } from '../request/RequestClient'
|
import { RequestClient } from '../request/RequestClient'
|
||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
@@ -39,66 +39,56 @@ describe('FileUploader', () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fileUploader = new FileUploader(
|
const fileUploader = new FileUploader(
|
||||||
config.serverUrl,
|
config,
|
||||||
config.serverType!,
|
|
||||||
'/jobs/path',
|
'/jobs/path',
|
||||||
new RequestClient('https://sample.server.com')
|
new RequestClient('https://sample.server.com')
|
||||||
)
|
)
|
||||||
|
|
||||||
it('should upload successfully', async () => {
|
it('should upload successfully', async () => {
|
||||||
const sasJob = 'test/upload'
|
const sasJob = 'test/upload'
|
||||||
const data = prepareFilesAndParams()
|
const { files, params } = prepareFilesAndParams()
|
||||||
mockedAxios.post.mockImplementation(() =>
|
mockedAxios.post.mockImplementation(() =>
|
||||||
Promise.resolve({ data: sampleResponse })
|
Promise.resolve({ data: sampleResponse })
|
||||||
)
|
)
|
||||||
|
|
||||||
const res = await fileUploader.execute(sasJob, data, config)
|
const res = await fileUploader.uploadFile(sasJob, files, params)
|
||||||
|
|
||||||
expect(res).toEqual(JSON.parse(sampleResponse))
|
expect(res).toEqual(JSON.parse(sampleResponse))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should upload successfully when login is required', async () => {
|
|
||||||
mockedAxios.post
|
|
||||||
.mockImplementationOnce(() =>
|
|
||||||
Promise.resolve({ data: '<form action="Logon">' })
|
|
||||||
)
|
|
||||||
.mockImplementationOnce(() => Promise.resolve({ data: sampleResponse }))
|
|
||||||
|
|
||||||
const loginCallback = jest.fn().mockImplementation(async () => {
|
|
||||||
await fileUploader.resendWaitingRequests()
|
|
||||||
Promise.resolve()
|
|
||||||
})
|
|
||||||
|
|
||||||
const sasJob = 'test'
|
|
||||||
const data = prepareFilesAndParams()
|
|
||||||
|
|
||||||
const res = await fileUploader.execute(sasJob, data, config, loginCallback)
|
|
||||||
|
|
||||||
expect(res).toEqual(JSON.parse(sampleResponse))
|
|
||||||
|
|
||||||
expect(mockedAxios.post).toHaveBeenCalledTimes(2)
|
|
||||||
expect(loginCallback).toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should an error when no files are provided', async () => {
|
it('should an error when no files are provided', async () => {
|
||||||
const sasJob = 'test/upload'
|
const sasJob = 'test/upload'
|
||||||
const files: UploadFile[] = []
|
const files: UploadFile[] = []
|
||||||
const params = { table: 'libtable' }
|
const params = { table: 'libtable' }
|
||||||
|
|
||||||
const res: any = await fileUploader
|
const err = await fileUploader
|
||||||
.execute(sasJob, files, params, config)
|
.uploadFile(sasJob, files, params)
|
||||||
.catch((err: any) => err)
|
.catch((err: any) => err)
|
||||||
expect(res.error.message).toEqual('At least one file must be provided.')
|
expect(err.error.message).toEqual('At least one file must be provided.')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw an error when no sasJob is provided', async () => {
|
it('should throw an error when no sasJob is provided', async () => {
|
||||||
const sasJob = ''
|
const sasJob = ''
|
||||||
const data = prepareFilesAndParams()
|
const { files, params } = prepareFilesAndParams()
|
||||||
|
|
||||||
const res: any = await fileUploader
|
const err = await fileUploader
|
||||||
.execute(sasJob, data, config)
|
.uploadFile(sasJob, files, params)
|
||||||
.catch((err: any) => err)
|
.catch((err: any) => err)
|
||||||
expect(res.error.message).toEqual('sasJob must be provided.')
|
expect(err.error.message).toEqual('sasJob must be provided.')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw an error when login is required', async () => {
|
||||||
|
mockedAxios.post.mockImplementation(() =>
|
||||||
|
Promise.resolve({ data: '<form action="Logon">' })
|
||||||
|
)
|
||||||
|
|
||||||
|
const sasJob = 'test'
|
||||||
|
const { files, params } = prepareFilesAndParams()
|
||||||
|
|
||||||
|
const err = await fileUploader
|
||||||
|
.uploadFile(sasJob, files, params)
|
||||||
|
.catch((err: any) => err)
|
||||||
|
expect(err.error.message).toEqual('You must be logged in to upload a file.')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw an error when invalid JSON is returned by the server', async () => {
|
it('should throw an error when invalid JSON is returned by the server', async () => {
|
||||||
@@ -107,13 +97,12 @@ describe('FileUploader', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const sasJob = 'test'
|
const sasJob = 'test'
|
||||||
const data = prepareFilesAndParams()
|
const { files, params } = prepareFilesAndParams()
|
||||||
|
|
||||||
const res: any = await fileUploader
|
const err = await fileUploader
|
||||||
.execute(sasJob, data, config)
|
.uploadFile(sasJob, files, params)
|
||||||
.catch((err: any) => err)
|
.catch((err: any) => err)
|
||||||
|
expect(err.error.message).toEqual('File upload request failed.')
|
||||||
expect(res.error.message).toEqual('File upload request failed.')
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw an error when the server request fails', async () => {
|
it('should throw an error when the server request fails', async () => {
|
||||||
@@ -122,11 +111,11 @@ describe('FileUploader', () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
const sasJob = 'test'
|
const sasJob = 'test'
|
||||||
const data = prepareFilesAndParams()
|
const { files, params } = prepareFilesAndParams()
|
||||||
|
|
||||||
const res: any = await fileUploader
|
const err = await fileUploader
|
||||||
.execute(sasJob, data, config)
|
.uploadFile(sasJob, files, params)
|
||||||
.catch((err: any) => err)
|
.catch((err: any) => err)
|
||||||
expect(res.error.message).toEqual('File upload request failed.')
|
expect(err.error.message).toEqual('File upload request failed.')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
export interface LoginOptions {
|
|
||||||
onLoggedOut?: () => Promise<boolean>
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LoginResult {
|
|
||||||
isLoggedIn: boolean
|
|
||||||
userName: string
|
|
||||||
}
|
|
||||||
@@ -59,13 +59,4 @@ export class SASjsConfig {
|
|||||||
* Changing this setting is not recommended.
|
* Changing this setting is not recommended.
|
||||||
*/
|
*/
|
||||||
allowInsecureRequests = false
|
allowInsecureRequests = false
|
||||||
/**
|
|
||||||
* Supported login mechanisms are - Redirected and Default
|
|
||||||
*/
|
|
||||||
loginMechanism: LoginMechanism = LoginMechanism.Default
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum LoginMechanism {
|
|
||||||
Default = 'Default',
|
|
||||||
Redirected = 'Redirected'
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
import { ExtraResponseAttributes } from '@sasjs/utils/types'
|
|
||||||
|
|
||||||
export async function appendExtraResponseAttributes(
|
|
||||||
response: any,
|
|
||||||
extraResponseAttributes: ExtraResponseAttributes[]
|
|
||||||
) {
|
|
||||||
let responseObject = {}
|
|
||||||
|
|
||||||
if (extraResponseAttributes?.length) {
|
|
||||||
const extraAttributes = extraResponseAttributes.reduce(
|
|
||||||
(map: any, obj: any) => ((map[obj] = response[obj]), map),
|
|
||||||
{}
|
|
||||||
)
|
|
||||||
|
|
||||||
responseObject = {
|
|
||||||
result: response.result,
|
|
||||||
...extraAttributes
|
|
||||||
}
|
|
||||||
} else responseObject = response.result
|
|
||||||
|
|
||||||
return responseObject
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
export const delay = (ms: number) =>
|
|
||||||
new Promise((resolve) => setTimeout(resolve, ms))
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
export * from './asyncForEach'
|
export * from './asyncForEach'
|
||||||
export * from './compareTimestamps'
|
export * from './compareTimestamps'
|
||||||
export * from './convertToCsv'
|
export * from './convertToCsv'
|
||||||
export * from './delay'
|
|
||||||
export * from './isNode'
|
export * from './isNode'
|
||||||
export * from './isRelativePath'
|
export * from './isRelativePath'
|
||||||
export * from './isUri'
|
export * from './isUri'
|
||||||
@@ -16,4 +15,3 @@ export * from './parseWeboutResponse'
|
|||||||
export * from './fetchLogByChunks'
|
export * from './fetchLogByChunks'
|
||||||
export * from './getValidJson'
|
export * from './getValidJson'
|
||||||
export * from './parseViyaDebugResponse'
|
export * from './parseViyaDebugResponse'
|
||||||
export * from './appendExtraResponseAttributes'
|
|
||||||
|
|||||||
@@ -1,177 +0,0 @@
|
|||||||
enum domIDs {
|
|
||||||
styles = 'sasjsAdapterStyles',
|
|
||||||
overlay = 'sasjsAdapterLoginPromptBG',
|
|
||||||
dialog = 'sasjsAdapterLoginPrompt'
|
|
||||||
}
|
|
||||||
const cssPrefix = 'sasjs-adapter'
|
|
||||||
|
|
||||||
const classes = {
|
|
||||||
popUp: `${cssPrefix}popUp`,
|
|
||||||
popUpBG: `${cssPrefix}popUpBG`
|
|
||||||
}
|
|
||||||
|
|
||||||
export const openLoginPrompt = (): Promise<boolean> => {
|
|
||||||
return new Promise(async (resolve) => {
|
|
||||||
const style = document.createElement('style')
|
|
||||||
style.id = domIDs.styles
|
|
||||||
style.innerText = cssContent
|
|
||||||
|
|
||||||
const loginPromptBG = document.createElement('div')
|
|
||||||
loginPromptBG.id = domIDs.overlay
|
|
||||||
loginPromptBG.classList.add(classes.popUpBG)
|
|
||||||
|
|
||||||
const loginPrompt = document.createElement('div')
|
|
||||||
loginPrompt.id = domIDs.dialog
|
|
||||||
loginPrompt.classList.add(classes.popUp)
|
|
||||||
|
|
||||||
const title = document.createElement('h1')
|
|
||||||
title.innerText = 'Session Expired!'
|
|
||||||
loginPrompt.appendChild(title)
|
|
||||||
|
|
||||||
const descHolder = document.createElement('div')
|
|
||||||
const desc = document.createElement('span')
|
|
||||||
desc.innerText = 'You need to relogin, click OK to login.'
|
|
||||||
descHolder.appendChild(desc)
|
|
||||||
loginPrompt.appendChild(descHolder)
|
|
||||||
|
|
||||||
const buttonCancel = document.createElement('button')
|
|
||||||
buttonCancel.classList.add('cancel')
|
|
||||||
buttonCancel.innerText = 'Cancel'
|
|
||||||
buttonCancel.onclick = () => {
|
|
||||||
closeLoginPrompt()
|
|
||||||
resolve(false)
|
|
||||||
}
|
|
||||||
loginPrompt.appendChild(buttonCancel)
|
|
||||||
|
|
||||||
const buttonOk = document.createElement('button')
|
|
||||||
buttonOk.classList.add('confirm')
|
|
||||||
buttonOk.innerText = 'Ok'
|
|
||||||
buttonOk.onclick = () => {
|
|
||||||
closeLoginPrompt()
|
|
||||||
resolve(true)
|
|
||||||
}
|
|
||||||
loginPrompt.appendChild(buttonOk)
|
|
||||||
|
|
||||||
document.body.style.overflow = 'hidden'
|
|
||||||
|
|
||||||
document.body.appendChild(style)
|
|
||||||
document.body.appendChild(loginPromptBG)
|
|
||||||
document.body.appendChild(loginPrompt)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const closeLoginPrompt = () => {
|
|
||||||
Object.values(domIDs).forEach((id) => {
|
|
||||||
const elem = document.getElementById(id)
|
|
||||||
elem?.parentNode?.removeChild(elem)
|
|
||||||
})
|
|
||||||
|
|
||||||
document.body.style.overflow = 'auto'
|
|
||||||
}
|
|
||||||
|
|
||||||
const cssContent = `
|
|
||||||
.${classes.popUpBG} ,
|
|
||||||
.${classes.popUp} {
|
|
||||||
z-index: 10000;
|
|
||||||
}
|
|
||||||
.${classes.popUp} {
|
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
display: block;
|
|
||||||
position: fixed;
|
|
||||||
top: 40%;
|
|
||||||
left: 50%;
|
|
||||||
padding: 0;
|
|
||||||
font-size: 14px;
|
|
||||||
font-family: 'PT Sans', sans-serif;
|
|
||||||
color: #fff;
|
|
||||||
border-style: none;
|
|
||||||
z-index: 999;
|
|
||||||
overflow: hidden;
|
|
||||||
background: rgba(0, 0, 0, 0.2);
|
|
||||||
margin: 0;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 300px;
|
|
||||||
height: auto;
|
|
||||||
max-height: 300px;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
.${classes.popUp} > h1 {
|
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
padding: 5px;
|
|
||||||
min-height: 40px;
|
|
||||||
font-size: 1.2em;
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
color: #fff;
|
|
||||||
background-color: transparent;
|
|
||||||
border-style: none;
|
|
||||||
border-width: 5px;
|
|
||||||
border-color: black;
|
|
||||||
}
|
|
||||||
.${classes.popUp} > div {
|
|
||||||
width: 100%;
|
|
||||||
height: calc(100% -108px);
|
|
||||||
margin: 0;
|
|
||||||
display: block;
|
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
padding: 5%;
|
|
||||||
text-align: center;
|
|
||||||
border-width: 1px;
|
|
||||||
border-color: #ccc;
|
|
||||||
border-style: none none solid none;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
.${classes.popUp} > div > span {
|
|
||||||
display: table-cell;
|
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
width: 300px;
|
|
||||||
height: 108px;
|
|
||||||
vertical-align: middle;
|
|
||||||
border-style: none;
|
|
||||||
}
|
|
||||||
.${classes.popUp} .cancel {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
.${classes.popUp} .confirm {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
.${classes.popUp} > button {
|
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
-moz-box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
padding: 10px;
|
|
||||||
width: 50%;
|
|
||||||
border: 1px none #ccc;
|
|
||||||
color: #fff;
|
|
||||||
font-family: inherit;
|
|
||||||
cursor: pointer;
|
|
||||||
height: 50px;
|
|
||||||
background: rgba(1, 1, 1, 0.2);
|
|
||||||
}
|
|
||||||
.${classes.popUp} > button:hover {
|
|
||||||
background: rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
.${classes.popUpBG} {
|
|
||||||
display: block;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
opacity: 0.95;
|
|
||||||
z-index: 50;
|
|
||||||
background-image: radial-gradient(#0378cd, #012036);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
Reference in New Issue
Block a user