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

Compare commits

..

7 Commits

16 changed files with 112 additions and 403 deletions

View File

@@ -1,103 +1,24 @@
{
"projectName": "adapter",
"projectOwner": "sasjs",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"imageSize": 100,
"commit": false,
"commitConvention": "angular",
"contributors": [
{
"login": "krishna-acondy",
"name": "Krishna Acondy",
"avatar_url": "https://avatars.githubusercontent.com/u/2980428?v=4",
"profile": "https://krishna-acondy.io/",
"contributions": [
"code",
"infra",
"blog",
"content",
"ideas",
"video"
]
},
{
"login": "YuryShkoda",
"name": "Yury Shkoda",
"avatar_url": "https://avatars.githubusercontent.com/u/25773492?v=4",
"profile": "https://www.erudicat.com/",
"contributions": [
"code",
"infra",
"ideas",
"test",
"video"
]
},
{
"login": "medjedovicm",
"name": "Mihajlo Medjedovic",
"avatar_url": "https://avatars.githubusercontent.com/u/18329105?v=4",
"profile": "https://github.com/medjedovicm",
"contributions": [
"code",
"infra",
"test",
"review"
]
},
{
"login": "allanbowe",
"name": "Allan Bowe",
"avatar_url": "https://avatars.githubusercontent.com/u/4420615?v=4",
"profile": "https://github.com/allanbowe",
"contributions": [
"code",
"review",
"test",
"mentoring",
"maintenance"
]
},
{
"login": "saadjutt01",
"name": "Muhammad Saad ",
"avatar_url": "https://avatars.githubusercontent.com/u/8914650?v=4",
"profile": "https://github.com/saadjutt01",
"contributions": [
"code",
"review",
"test",
"mentoring",
"infra"
]
},
{
"login": "sabhas",
"name": "Sabir Hassan",
"avatar_url": "https://avatars.githubusercontent.com/u/82647447?v=4",
"profile": "https://github.com/sabhas",
"contributions": [
"code",
"review",
"test",
"ideas"
]
},
{
"login": "VladislavParhomchik",
"name": "VladislavParhomchik",
"avatar_url": "https://avatars.githubusercontent.com/u/83717836?v=4",
"profile": "https://github.com/VladislavParhomchik",
"contributions": [
"test",
"review"
"code"
]
}
],
"contributorsPerLine": 7,
"projectName": "adapter",
"projectOwner": "sasjs",
"repoType": "github",
"repoHost": "https://github.com",
"skipCi": true
}

View File

@@ -6,7 +6,7 @@ GREEN="\033[1;32m"
# temporary file which holds the message).
commit_message=$(cat "$1")
if (echo "$commit_message" | grep -Eq "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9 -\*]+\))?!?: .+$") then
if (echo "$commit_message" | grep -Eq "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9 \-\*]+\))?!?: .+$") then
echo "${GREEN} ✔ Commit message meets Conventional Commit standards"
exit 0
fi

View File

@@ -172,7 +172,7 @@ Configuration on the client side involves passing an object on startup, which ca
* `serverType` - either `SAS9` or `SASVIYA`.
* `serverUrl` - the location (including http protocol and port) of the SAS Server. Can be omitted, eg if serving directly from the SAS Web Server, or in streaming mode.
* `debug` - if `true` then SAS Logs and extra debug information is returned.
* `useComputeApi` - Only relevant when the serverType is `SASVIYA`. If `true` the [Compute API](#using-the-compute-api) is used. If `false` the [JES API](#using-the-jes-api) is used. If `null` or `undefined` the [Web](#using-jes-web-app) approach is used.
* `useComputeApi` - Only relevant when the serverType is `SASVIYA`. If `true` the [Compute API](#using-the-compute-api) is used. If `false` the [JES API](#using-the-jes-api) is used. If `null` or `undefined` the [Web](#using-jes-web-app) approach is used.
* `contextName` - Compute context on which the requests will be called. If missing or not provided, defaults to `Job Execution Compute context`.
The adapter supports a number of approaches for interfacing with Viya (`serverType` is `SASVIYA`). For maximum performance, be sure to [configure your compute context](https://sasjs.io/guide-viya/#shared-account-and-server-re-use) with `reuseServerProcesses` as `true` and a system account in `runServerAs`. This functionality is available since Viya 3.5. This configuration is supported when [creating contexts using the CLI](https://sasjs.io/sasjs-cli-context/#sasjs-context-create).
@@ -236,9 +236,6 @@ If you find this library useful, help us grow our star graph!
![](https://starchart.cc/sasjs/adapter.svg)
## Contributors ✨
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors-)
<!-- ALL-CONTRIBUTORS-BADGE:END -->
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@@ -247,13 +244,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<!-- markdownlint-disable -->
<table>
<tr>
<td align="center"><a href="https://krishna-acondy.io/"><img src="https://avatars.githubusercontent.com/u/2980428?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Krishna Acondy</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=krishna-acondy" title="Code">💻</a> <a href="#infra-krishna-acondy" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#blog-krishna-acondy" title="Blogposts">📝</a> <a href="#content-krishna-acondy" title="Content">🖋</a> <a href="#ideas-krishna-acondy" title="Ideas, Planning, & Feedback">🤔</a> <a href="#video-krishna-acondy" title="Videos">📹</a></td>
<td align="center"><a href="https://www.erudicat.com/"><img src="https://avatars.githubusercontent.com/u/25773492?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Yury Shkoda</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=YuryShkoda" title="Code">💻</a> <a href="#infra-YuryShkoda" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="#ideas-YuryShkoda" title="Ideas, Planning, & Feedback">🤔</a> <a href="https://github.com/sasjs/adapter/commits?author=YuryShkoda" title="Tests">⚠️</a> <a href="#video-YuryShkoda" title="Videos">📹</a></td>
<td align="center"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mihajlo Medjedovic</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=medjedovicm" title="Code">💻</a> <a href="#infra-medjedovicm" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a> <a href="https://github.com/sasjs/adapter/commits?author=medjedovicm" title="Tests">⚠️</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Amedjedovicm" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/allanbowe"><img src="https://avatars.githubusercontent.com/u/4420615?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Allan Bowe</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=allanbowe" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Aallanbowe" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=allanbowe" title="Tests">⚠️</a> <a href="#mentoring-allanbowe" title="Mentoring">🧑‍🏫</a> <a href="#maintenance-allanbowe" title="Maintenance">🚧</a></td>
<td align="center"><a href="https://github.com/saadjutt01"><img src="https://avatars.githubusercontent.com/u/8914650?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Muhammad Saad </b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=saadjutt01" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Asaadjutt01" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=saadjutt01" title="Tests">⚠️</a> <a href="#mentoring-saadjutt01" title="Mentoring">🧑‍🏫</a> <a href="#infra-saadjutt01" title="Infrastructure (Hosting, Build-Tools, etc)">🚇</a></td>
<td align="center"><a href="https://github.com/sabhas"><img src="https://avatars.githubusercontent.com/u/82647447?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Sabir Hassan</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=sabhas" title="Code">💻</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3Asabhas" title="Reviewed Pull Requests">👀</a> <a href="https://github.com/sasjs/adapter/commits?author=sabhas" title="Tests">⚠️</a> <a href="#ideas-sabhas" title="Ideas, Planning, & Feedback">🤔</a></td>
<td align="center"><a href="https://github.com/VladislavParhomchik"><img src="https://avatars.githubusercontent.com/u/83717836?v=4?s=100" width="100px;" alt=""/><br /><sub><b>VladislavParhomchik</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=VladislavParhomchik" title="Tests">⚠️</a> <a href="https://github.com/sasjs/adapter/pulls?q=is%3Apr+reviewed-by%3AVladislavParhomchik" title="Reviewed Pull Requests">👀</a></td>
<td align="center"><a href="https://github.com/medjedovicm"><img src="https://avatars.githubusercontent.com/u/18329105?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Mihajlo Medjedovic</b></sub></a><br /><a href="https://github.com/sasjs/adapter/commits?author=medjedovicm" title="Code">💻</a></td>
</tr>
</table>

View File

@@ -47,7 +47,9 @@ export const basicTests = (
'Should fail on first attempt and should log the user in on second attempt',
test: async () => {
await adapter.logOut()
await sleep(1000)
await adapter.logIn('invalid', 'invalid')
await sleep(1000)
return adapter.logIn(userName, password)
},
assertion: (response: any) =>
@@ -151,6 +153,9 @@ export const basicTests = (
description:
'Should complete successful request with extra attributes present in response',
test: async () => {
if (adapter.getSasjsConfig().serverType !== 'SASVIYA')
return Promise.resolve('skip')
const config = {
useComputeApi: false
}
@@ -165,9 +170,15 @@ export const basicTests = (
)
},
assertion: (response: any) => {
if (response === 'skip') return true
const responseKeys: any = Object.keys(response)
return responseKeys.includes('file') && responseKeys.includes('data')
}
}
]
})
const sleep = (ms: number) => {
return new Promise((resolve) => setTimeout(resolve, ms))
}

View File

@@ -61,14 +61,15 @@ export class FileUploader {
'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)
.then(async (res) => {
// for web approach on Viya
if (
this.sasjsConfig.serverType === ServerType.SasViya &&
this.sasjsConfig.debug
this.sasjsConfig.debug &&
(this.sasjsConfig.useComputeApi === null ||
this.sasjsConfig.useComputeApi === undefined) &&
this.sasjsConfig.serverType === ServerType.SasViya
) {
const jsonResponse = await parseSasViyaDebugResponse(
res.result as string,

View File

@@ -10,13 +10,9 @@ import { isUrl } from './utils'
export class SAS9ApiClient {
private requestClient: Sas9RequestClient
constructor(
private serverUrl: string,
private jobsPath: string,
allowInsecureRequests: boolean
) {
constructor(private serverUrl: string, private jobsPath: string) {
if (serverUrl) isUrl(serverUrl)
this.requestClient = new Sas9RequestClient(serverUrl, allowInsecureRequests)
this.requestClient = new Sas9RequestClient(serverUrl, false)
}
/**

View File

@@ -1,51 +0,0 @@
import { Logger, LogLevel } from '@sasjs/utils/logger'
import { RequestClient } from './request/RequestClient'
import { SASViyaApiClient } from './SASViyaApiClient'
import { Folder } from './types'
import { RootFolderNotFoundError } from './types/errors'
const mockFolder: Folder = {
id: '1',
uri: '/folder',
links: [],
memberCount: 1
}
const requestClient = new (<jest.Mock<RequestClient>>RequestClient)()
const sasViyaApiClient = new SASViyaApiClient(
'https://test.com',
'/test',
'test context',
requestClient
)
describe('SASViyaApiClient', () => {
beforeEach(() => {
;(process as any).logger = new Logger(LogLevel.Off)
setupMocks()
})
it('should throw an error when the root folder is not found on the server', async () => {
jest
.spyOn(requestClient, 'get')
.mockImplementation(() => Promise.reject('Not Found'))
const error = await sasViyaApiClient
.createFolder('test', '/foo')
.catch((e) => e)
expect(error).toBeInstanceOf(RootFolderNotFoundError)
})
})
const setupMocks = () => {
jest
.spyOn(requestClient, 'get')
.mockImplementation(() =>
Promise.resolve({ result: mockFolder, etag: '', status: 200 })
)
jest
.spyOn(requestClient, 'post')
.mockImplementation(() =>
Promise.resolve({ result: mockFolder, etag: '', status: 200 })
)
}

View File

@@ -11,7 +11,7 @@ import {
JobDefinition,
PollOptions
} from './types'
import { JobExecutionError, RootFolderNotFoundError } from './types/errors'
import { JobExecutionError } from './types/errors'
import { SessionManager } from './SessionManager'
import { ContextManager } from './ContextManager'
import { SasAuthResponse, MacroVar, AuthConfig } from '@sasjs/utils/types'
@@ -381,11 +381,7 @@ export class SASViyaApiClient {
)
const newFolderName = `${parentFolderPath.split('/').pop()}`
if (newParentFolderPath === '') {
throw new RootFolderNotFoundError(
parentFolderPath,
this.serverUrl,
accessToken
)
throw new Error('Root folder has to be present on the server.')
}
logger.info(
`Creating parent folder:\n'${newFolderName}' in '${newParentFolderPath}'`

View File

@@ -619,11 +619,6 @@ export default class SASjs {
authConfig
)
} else {
if (!config.contextName)
config = {
...config,
contextName: 'SAS Job Execution compute context'
}
return await this.jesJobExecutor!.execute(
sasJob,
data,
@@ -754,11 +749,7 @@ export default class SASjs {
)
sasApiClient.debug = this.sasjsConfig.debug
} else if (this.sasjsConfig.serverType === ServerType.Sas9) {
sasApiClient = new SAS9ApiClient(
serverUrl,
this.jobsPath,
this.sasjsConfig.allowInsecureRequests
)
sasApiClient = new SAS9ApiClient(serverUrl, this.jobsPath)
}
} else {
let sasClientConfig: any = null
@@ -953,8 +944,7 @@ export default class SASjs {
else
this.sas9ApiClient = new SAS9ApiClient(
this.sasjsConfig.serverUrl,
this.jobsPath,
this.sasjsConfig.allowInsecureRequests
this.jobsPath
)
}
@@ -975,8 +965,7 @@ export default class SASjs {
this.sas9JobExecutor = new Sas9JobExecutor(
this.sasjsConfig.serverUrl,
this.sasjsConfig.serverType!,
this.jobsPath,
this.sasjsConfig.allowInsecureRequests
this.jobsPath
)
this.computeJobExecutor = new ComputeJobExecutor(

View File

@@ -5,10 +5,10 @@ import { prefixMessage } from '@sasjs/utils/error'
import { RequestClient } from './request/RequestClient'
const MAX_SESSION_COUNT = 1
const RETRY_LIMIT: number = 3
let RETRY_COUNT: number = 0
export class SessionManager {
private loggedErrors: NoSessionStateError[] = []
constructor(
private serverUrl: string,
private contextName: string,
@@ -154,75 +154,69 @@ export class SessionManager {
session: Session,
etag: string | null,
accessToken?: string
): Promise<string> {
) {
const logger = process.logger || console
let sessionState = session.state
const stateLink = session.links.find((l: any) => l.rel === 'state')
if (
sessionState === 'pending' ||
sessionState === 'running' ||
sessionState === ''
) {
if (stateLink) {
if (this.debug && !this.printedSessionState.printed) {
logger.info('Polling session status...')
return new Promise(async (resolve, reject) => {
if (
sessionState === 'pending' ||
sessionState === 'running' ||
sessionState === ''
) {
if (stateLink) {
if (this.debug && !this.printedSessionState.printed) {
logger.info('Polling session status...')
this.printedSessionState.printed = true
}
const { result: state, responseStatus: responseStatus } =
await this.getSessionState(
`${this.serverUrl}${stateLink.href}?wait=30`,
etag!,
accessToken
).catch((err) => {
throw prefixMessage(err, 'Error while getting session state.')
})
sessionState = state.trim()
if (this.debug && this.printedSessionState.state !== sessionState) {
logger.info(`Current session state is '${sessionState}'`)
this.printedSessionState.state = sessionState
this.printedSessionState.printed = false
}
if (!sessionState) {
const stateError = new NoSessionStateError(
responseStatus,
this.serverUrl + stateLink.href,
session.links.find((l: any) => l.rel === 'log')?.href as string
)
if (
!this.loggedErrors.find(
(err: NoSessionStateError) =>
err.serverResponseStatus === stateError.serverResponseStatus
)
) {
this.loggedErrors.push(stateError)
logger.info(stateError.message)
this.printedSessionState.printed = true
}
return await this.waitForSession(session, etag, accessToken)
const { result: state, responseStatus: responseStatus } =
await this.getSessionState(
`${this.serverUrl}${stateLink.href}?wait=30`,
etag!,
accessToken
).catch((err) => {
throw prefixMessage(err, 'Error while getting session state.')
})
sessionState = state.trim()
if (this.debug && this.printedSessionState.state !== sessionState) {
logger.info(`Current session state is '${sessionState}'`)
this.printedSessionState.state = sessionState
this.printedSessionState.printed = false
}
// There is an internal error present in SAS Viya 3.5
// Retry to wait for a session status in such case of SAS internal error
if (!sessionState) {
if (RETRY_COUNT < RETRY_LIMIT) {
RETRY_COUNT++
resolve(this.waitForSession(session, etag, accessToken))
} else {
reject(
new NoSessionStateError(
responseStatus,
this.serverUrl + stateLink.href,
session.links.find((l: any) => l.rel === 'log')
?.href as string
)
)
}
}
resolve(sessionState)
}
this.loggedErrors = []
return sessionState
} else {
throw 'Error while getting session state link.'
resolve(sessionState)
}
} else {
this.loggedErrors = []
return sessionState
}
})
}
private async getSessionState(

View File

@@ -16,11 +16,10 @@ export class Sas9JobExecutor extends BaseJobExecutor {
constructor(
serverUrl: string,
serverType: ServerType,
private jobsPath: string,
allowInsecureRequests: boolean
private jobsPath: string
) {
super(serverUrl, serverType)
this.requestClient = new Sas9RequestClient(serverUrl, allowInsecureRequests)
this.requestClient = new Sas9RequestClient(serverUrl, false)
}
async execute(sasJob: string, data: any, config: any) {

View File

@@ -54,12 +54,7 @@ export class WebJobExecutor extends BaseJobExecutor {
apiUrl += jobUri.length > 0 ? '&_job=' + jobUri : ''
// if context name exists and is not blank string
// then add _contextname variable in apiUrl
apiUrl +=
config.contextName && !/\s/.test(config.contextName)
? `&_contextname=${config.contextName}`
: ''
apiUrl += config.contextName ? `&_contextname=${config.contextName}` : ''
}
let requestParams = {
@@ -112,20 +107,9 @@ export class WebJobExecutor extends BaseJobExecutor {
this.appendRequest(res, sasJob, config.debug)
resolve(jsonResponse)
}
if (this.serverType === ServerType.Sas9 && config.debug) {
const jsonResponse = parseWeboutResponse(res.result as string)
if (jsonResponse === '') {
throw new Error(
'Valid JSON could not be extracted from response.'
)
}
getValidJson(jsonResponse)
this.appendRequest(res, sasJob, config.debug)
resolve(res.result)
}
getValidJson(res.result as string)
this.appendRequest(res, sasJob, config.debug)
getValidJson(res.result as string)
resolve(res.result)
})
.catch(async (e: Error) => {

View File

@@ -3,8 +3,6 @@ import { RequestClient } from '../request/RequestClient'
import { NoSessionStateError } from '../types/errors'
import * as dotenv from 'dotenv'
import axios from 'axios'
import { Logger, LogLevel } from '@sasjs/utils'
import { Session } from '../types'
jest.mock('axios')
const mockedAxios = axios as jest.Mocked<typeof axios>
@@ -49,91 +47,36 @@ describe('SessionManager', () => {
})
describe('waitForSession', () => {
const session: Session = {
id: 'id',
state: '',
links: [{ rel: 'state', href: '', uri: '', type: '', method: 'GET' }],
attributes: {
sessionInactiveTimeout: 0
},
creationTimeStamp: ''
}
beforeEach(() => {
;(process as any).logger = new Logger(LogLevel.Off)
})
it('should reject with NoSessionStateError if SAS server did not provide session state', async () => {
let requestAttempt = 0
const requestAttemptLimit = 10
const sessionState = 'idle'
mockedAxios.get.mockImplementation(() => {
requestAttempt += 1
if (requestAttempt >= requestAttemptLimit) {
return Promise.resolve({ data: sessionState, status: 200 })
}
return Promise.resolve({ data: '', status: 304 })
})
jest.spyOn((process as any).logger, 'info')
sessionManager.debug = true
await expect(
sessionManager['waitForSession'](session, null, 'access_token')
).resolves.toEqual(sessionState)
expect(mockedAxios.get).toHaveBeenCalledTimes(requestAttemptLimit)
expect((process as any).logger.info).toHaveBeenCalledTimes(3)
expect((process as any).logger.info).toHaveBeenNthCalledWith(
1,
'Polling session status...'
)
expect((process as any).logger.info).toHaveBeenNthCalledWith(
2,
`Could not get session state. Server responded with 304 whilst checking state: ${process.env.SERVER_URL}`
)
expect((process as any).logger.info).toHaveBeenNthCalledWith(
3,
`Current session state is '${sessionState}'`
)
})
it('should throw an error if there is no session link', async () => {
const customSession = JSON.parse(JSON.stringify(session))
customSession.links = []
const responseStatus = 304
mockedAxios.get.mockImplementation(() =>
Promise.resolve({ data: customSession.state, status: 200 })
Promise.resolve({ data: '', status: responseStatus })
)
await expect(
sessionManager['waitForSession'](customSession, null, 'access_token')
).rejects.toContain('Error while getting session state link.')
})
it('should throw an error if could not get session state', async () => {
mockedAxios.get.mockImplementation(() => Promise.reject('Mocked error'))
await expect(
sessionManager['waitForSession'](session, null, 'access_token')
).rejects.toContain('Error while getting session state.')
})
it('should return session state', async () => {
const customSession = JSON.parse(JSON.stringify(session))
customSession.state = 'completed'
mockedAxios.get.mockImplementation(() =>
Promise.resolve({ data: customSession.state, status: 200 })
sessionManager['waitForSession'](
{
id: 'id',
state: '',
links: [
{ rel: 'state', href: '', uri: '', type: '', method: 'GET' }
],
attributes: {
sessionInactiveTimeout: 0
},
creationTimeStamp: ''
},
null,
'access_token'
)
).rejects.toEqual(
new NoSessionStateError(
responseStatus,
process.env.SERVER_URL as string,
'logUrl'
)
)
await expect(
sessionManager['waitForSession'](customSession, null, 'access_token')
).resolves.toEqual(customSession.state)
})
})
})

View File

@@ -1,40 +0,0 @@
import { RootFolderNotFoundError } from './RootFolderNotFoundError'
describe('RootFolderNotFoundError', () => {
it('when access token is provided, error message should contain the scopes in the token', () => {
const token =
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY29wZSI6WyJzY29wZS0xIiwic2NvcGUtMiJdfQ.ktqPL2ulln-8Asa2jSV9QCfDYmQuNk4tNKopxJR5xZs'
const error = new RootFolderNotFoundError(
'/myProject',
'https://analytium.co.uk',
token
)
expect(error).toBeInstanceOf(RootFolderNotFoundError)
expect(error.message).toContain('scope-1')
expect(error.message).toContain('scope-2')
})
it('when access token is not provided, error message should not contain scopes', () => {
const error = new RootFolderNotFoundError(
'/myProject',
'https://analytium.co.uk'
)
expect(error).toBeInstanceOf(RootFolderNotFoundError)
expect(error.message).not.toContain(
'Your access token contains the following scopes'
)
})
it('should include the folder path and SASDrive URL in the message', () => {
const folderPath = '/myProject'
const serverUrl = 'https://analytium.co.uk'
const error = new RootFolderNotFoundError(folderPath, serverUrl)
expect(error).toBeInstanceOf(RootFolderNotFoundError)
expect(error.message).toContain(folderPath)
expect(error.message).toContain(`${serverUrl}/SASDrive`)
})
})

View File

@@ -1,24 +0,0 @@
import { decodeToken } from '@sasjs/utils/auth'
export class RootFolderNotFoundError extends Error {
constructor(
parentFolderPath: string,
serverUrl: string,
accessToken?: string
) {
let message: string =
`Root folder ${parentFolderPath} was not found.` +
`\nPlease check ${serverUrl}/SASDrive.` +
`\nIf the folder DOES exist then it is likely a permission problem.\n`
if (accessToken) {
const decodedToken = decodeToken(accessToken)
let scope = decodedToken.scope
scope = scope.map((element) => '* ' + element)
message +=
`Your access token contains the following scopes:\n` + scope.join('\n')
}
super(message)
this.name = 'RootFolderNotFoundError'
Object.setPrototypeOf(this, RootFolderNotFoundError.prototype)
}
}

View File

@@ -7,4 +7,3 @@ export * from './LoginRequiredError'
export * from './NotFoundError'
export * from './ErrorResponse'
export * from './NoSessionStateError'
export * from './RootFolderNotFoundError'