mirror of
https://github.com/sasjs/adapter.git
synced 2025-12-11 09:24:35 +00:00
Merge pull request #177 from sasjs/cli-issue-182
feat(context): moved context related logic to ContextManager
This commit is contained in:
File diff suppressed because one or more lines are too long
231
docs/classes/reflection-831.reflection-220.fileuploader.html
Normal file
231
docs/classes/reflection-831.reflection-220.fileuploader.html
Normal file
File diff suppressed because one or more lines are too long
312
docs/classes/reflection-831.reflection-220.sas9apiclient.html
Normal file
312
docs/classes/reflection-831.reflection-220.sas9apiclient.html
Normal file
File diff suppressed because one or more lines are too long
1725
docs/classes/reflection-831.reflection-220.sasjs.html
Normal file
1725
docs/classes/reflection-831.reflection-220.sasjs.html
Normal file
File diff suppressed because one or more lines are too long
1567
docs/classes/reflection-831.reflection-220.sasviyaapiclient.html
Normal file
1567
docs/classes/reflection-831.reflection-220.sasviyaapiclient.html
Normal file
File diff suppressed because one or more lines are too long
323
docs/classes/reflection-831.reflection-220.sessionmanager.html
Normal file
323
docs/classes/reflection-831.reflection-220.sessionmanager.html
Normal file
File diff suppressed because one or more lines are too long
231
docs/classes/reflection-837.reflection-220.fileuploader.html
Normal file
231
docs/classes/reflection-837.reflection-220.fileuploader.html
Normal file
File diff suppressed because one or more lines are too long
312
docs/classes/reflection-837.reflection-220.sas9apiclient.html
Normal file
312
docs/classes/reflection-837.reflection-220.sas9apiclient.html
Normal file
File diff suppressed because one or more lines are too long
1782
docs/classes/reflection-837.reflection-220.sasjs.html
Normal file
1782
docs/classes/reflection-837.reflection-220.sasjs.html
Normal file
File diff suppressed because one or more lines are too long
1567
docs/classes/reflection-837.reflection-220.sasviyaapiclient.html
Normal file
1567
docs/classes/reflection-837.reflection-220.sasviyaapiclient.html
Normal file
File diff suppressed because one or more lines are too long
323
docs/classes/reflection-837.reflection-220.sessionmanager.html
Normal file
323
docs/classes/reflection-837.reflection-220.sessionmanager.html
Normal file
File diff suppressed because one or more lines are too long
622
docs/classes/root.contextmanager.html
Normal file
622
docs/classes/root.contextmanager.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
106
docs/modules/reflection-831.html
Normal file
106
docs/modules/reflection-831.html
Normal file
File diff suppressed because one or more lines are too long
128
docs/modules/reflection-831.reflection-220.html
Normal file
128
docs/modules/reflection-831.reflection-220.html
Normal file
File diff suppressed because one or more lines are too long
106
docs/modules/reflection-837.html
Normal file
106
docs/modules/reflection-837.html
Normal file
File diff suppressed because one or more lines are too long
128
docs/modules/reflection-837.reflection-220.html
Normal file
128
docs/modules/reflection-837.reflection-220.html
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1299
package-lock.json
generated
1299
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
538
src/ContextManager.ts
Normal file
538
src/ContextManager.ts
Normal file
@@ -0,0 +1,538 @@
|
||||
import {
|
||||
Context,
|
||||
CsrfToken,
|
||||
EditContextInput,
|
||||
ContextAllAttributes
|
||||
} from './types'
|
||||
import { makeRequest, isUrl } from './utils'
|
||||
import { SASViyaApiClient } from './SASViyaApiClient'
|
||||
import { prefixMessage } from '@sasjs/utils/error'
|
||||
|
||||
export class ContextManager {
|
||||
private defaultComputeContexts = [
|
||||
'CAS Formats service compute context',
|
||||
'Data Mining compute context',
|
||||
'Import 9 service compute context',
|
||||
'SAS Job Execution compute context',
|
||||
'SAS Model Manager compute context',
|
||||
'SAS Studio compute context',
|
||||
'SAS Visual Forecasting compute context'
|
||||
]
|
||||
private defaultLauncherContexts = [
|
||||
'CAS Formats service launcher context',
|
||||
'Data Mining launcher context',
|
||||
'Import 9 service launcher context',
|
||||
'Job Flow Execution launcher context',
|
||||
'SAS Job Execution launcher context',
|
||||
'SAS Model Manager launcher context',
|
||||
'SAS Studio launcher context',
|
||||
'SAS Visual Forecasting launcher context'
|
||||
]
|
||||
|
||||
private csrfToken: CsrfToken | null = null
|
||||
|
||||
get getDefaultComputeContexts() {
|
||||
return this.defaultComputeContexts
|
||||
}
|
||||
get getDefaultLauncherContexts() {
|
||||
return this.defaultLauncherContexts
|
||||
}
|
||||
|
||||
constructor(
|
||||
private serverUrl: string,
|
||||
private setCsrfToken: (csrfToken: CsrfToken) => void
|
||||
) {
|
||||
if (serverUrl) isUrl(serverUrl)
|
||||
}
|
||||
|
||||
public async getComputeContexts(accessToken?: string) {
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const { result: contexts } = await this.request<{ items: Context[] }>(
|
||||
`${this.serverUrl}/compute/contexts?limit=10000`,
|
||||
{ headers }
|
||||
).catch((err) => {
|
||||
throw prefixMessage(err, 'Error while getting compute contexts. ')
|
||||
})
|
||||
|
||||
const contextsList = contexts && contexts.items ? contexts.items : []
|
||||
|
||||
return contextsList.map((context: any) => ({
|
||||
createdBy: context.createdBy,
|
||||
id: context.id,
|
||||
name: context.name,
|
||||
version: context.version,
|
||||
attributes: {}
|
||||
}))
|
||||
}
|
||||
|
||||
public async getLauncherContexts(accessToken?: string) {
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const { result: contexts } = await this.request<{ items: Context[] }>(
|
||||
`${this.serverUrl}/launcher/contexts?limit=10000`,
|
||||
{ headers }
|
||||
).catch((err) => {
|
||||
throw prefixMessage(err, 'Error while getting launcher contexts. ')
|
||||
})
|
||||
|
||||
const contextsList = contexts && contexts.items ? contexts.items : []
|
||||
|
||||
return contextsList.map((context: any) => ({
|
||||
createdBy: context.createdBy,
|
||||
id: context.id,
|
||||
name: context.name,
|
||||
version: context.version,
|
||||
attributes: {}
|
||||
}))
|
||||
}
|
||||
|
||||
public async createComputeContext(
|
||||
contextName: string,
|
||||
launchContextName: string,
|
||||
sharedAccountId: string,
|
||||
autoExecLines: string[],
|
||||
accessToken?: string,
|
||||
authorizedUsers?: string[]
|
||||
) {
|
||||
this.validateContextName(contextName)
|
||||
|
||||
this.isDefaultContext(
|
||||
contextName,
|
||||
this.defaultComputeContexts,
|
||||
`Compute context '${contextName}' already exists.`
|
||||
)
|
||||
|
||||
const existingComputeContexts = await this.getComputeContexts(accessToken)
|
||||
|
||||
if (
|
||||
existingComputeContexts.find((context) => context.name === contextName)
|
||||
) {
|
||||
throw new Error(`Compute context '${contextName}' already exists.`)
|
||||
}
|
||||
|
||||
if (launchContextName) {
|
||||
if (!this.defaultLauncherContexts.includes(launchContextName)) {
|
||||
const launcherContexts = await this.getLauncherContexts(accessToken)
|
||||
|
||||
if (
|
||||
!launcherContexts.find(
|
||||
(context) => context.name === launchContextName
|
||||
)
|
||||
) {
|
||||
const description = `The launcher context for ${launchContextName}`
|
||||
const launchType = 'direct'
|
||||
|
||||
const newLauncherContext = await this.createLauncherContext(
|
||||
launchContextName,
|
||||
description,
|
||||
launchType,
|
||||
accessToken
|
||||
).catch((err) => {
|
||||
throw new Error(`Error while creating launcher context. ${err}`)
|
||||
})
|
||||
|
||||
if (newLauncherContext && newLauncherContext.name) {
|
||||
launchContextName = newLauncherContext.name
|
||||
} else {
|
||||
throw new Error('Error while creating launcher context.')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
let attributes = { reuseServerProcesses: true } as object
|
||||
|
||||
if (sharedAccountId)
|
||||
attributes = { ...attributes, runServerAs: sharedAccountId }
|
||||
|
||||
const requestBody: any = {
|
||||
name: contextName,
|
||||
launchContext: {
|
||||
contextName: launchContextName || ''
|
||||
},
|
||||
attributes
|
||||
}
|
||||
|
||||
if (authorizedUsers && authorizedUsers.length) {
|
||||
requestBody['authorizedUsers'] = authorizedUsers
|
||||
} else {
|
||||
requestBody['authorizeAllAuthenticatedUsers'] = true
|
||||
}
|
||||
|
||||
if (autoExecLines) {
|
||||
requestBody.environment = { autoExecLines }
|
||||
}
|
||||
|
||||
const createContextRequest: RequestInit = {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(requestBody)
|
||||
}
|
||||
|
||||
const { result: context } = await this.request<Context>(
|
||||
`${this.serverUrl}/compute/contexts`,
|
||||
createContextRequest
|
||||
).catch((err) => {
|
||||
throw prefixMessage(err, 'Error while creating compute context. ')
|
||||
})
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
public async createLauncherContext(
|
||||
contextName: string,
|
||||
description: string,
|
||||
launchType = 'direct',
|
||||
accessToken?: string
|
||||
) {
|
||||
if (!contextName) {
|
||||
throw new Error('Context name is required.')
|
||||
}
|
||||
|
||||
this.isDefaultContext(
|
||||
contextName,
|
||||
this.defaultLauncherContexts,
|
||||
`Launcher context '${contextName}' already exists.`
|
||||
)
|
||||
|
||||
const existingLauncherContexts = await this.getLauncherContexts(accessToken)
|
||||
|
||||
if (
|
||||
existingLauncherContexts.find((context) => context.name === contextName)
|
||||
) {
|
||||
throw new Error(`Launcher context '${contextName}' already exists.`)
|
||||
}
|
||||
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const requestBody: any = {
|
||||
name: contextName,
|
||||
description: description,
|
||||
launchType
|
||||
}
|
||||
|
||||
const createContextRequest: RequestInit = {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(requestBody)
|
||||
}
|
||||
|
||||
const { result: context } = await this.request<Context>(
|
||||
`${this.serverUrl}/launcher/contexts`,
|
||||
createContextRequest
|
||||
).catch((err) => {
|
||||
throw prefixMessage(err, 'Error while creating launcher context. ')
|
||||
})
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
public async editComputeContext(
|
||||
contextName: string,
|
||||
editedContext: EditContextInput,
|
||||
accessToken?: string
|
||||
) {
|
||||
this.validateContextName(contextName)
|
||||
|
||||
this.isDefaultContext(
|
||||
contextName,
|
||||
this.defaultComputeContexts,
|
||||
'Editing default SAS compute contexts is not allowed.',
|
||||
true
|
||||
)
|
||||
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
let originalContext
|
||||
|
||||
originalContext = await this.getComputeContextByName(
|
||||
contextName,
|
||||
accessToken
|
||||
)
|
||||
|
||||
// Try to find context by id, when context name has been changed.
|
||||
if (!originalContext) {
|
||||
originalContext = await this.getComputeContextById(
|
||||
editedContext.id!,
|
||||
accessToken
|
||||
)
|
||||
}
|
||||
|
||||
const { result: context, etag } = await this.request<Context>(
|
||||
`${this.serverUrl}/compute/contexts/${originalContext.id}`,
|
||||
{
|
||||
headers
|
||||
}
|
||||
).catch((err) => {
|
||||
if (err && err.status === 404) {
|
||||
throw new Error(
|
||||
`The context '${contextName}' was not found on this server.`
|
||||
)
|
||||
}
|
||||
|
||||
throw err
|
||||
})
|
||||
|
||||
// An If-Match header with the value of the last ETag for the context
|
||||
// is required to be able to update it
|
||||
// https://developer.sas.com/apis/rest/Compute/#update-a-context-definition
|
||||
headers['If-Match'] = etag
|
||||
|
||||
const updateContextRequest: RequestInit = {
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
...context,
|
||||
...editedContext,
|
||||
attributes: { ...context.attributes, ...editedContext.attributes }
|
||||
})
|
||||
}
|
||||
|
||||
return await this.request<Context>(
|
||||
`${this.serverUrl}/compute/contexts/${context.id}`,
|
||||
updateContextRequest
|
||||
)
|
||||
}
|
||||
|
||||
public async getComputeContextByName(
|
||||
contextName: string,
|
||||
accessToken?: string
|
||||
): Promise<Context> {
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const { result: contexts } = await this.request<{ items: Context[] }>(
|
||||
`${this.serverUrl}/compute/contexts?filter=eq(name, "${contextName}")`,
|
||||
{ headers }
|
||||
).catch((err) => {
|
||||
throw prefixMessage(err, 'Error while getting compute context by name. ')
|
||||
})
|
||||
|
||||
if (!contexts || !(contexts.items && contexts.items.length)) {
|
||||
throw new Error(
|
||||
`The context '${contextName}' was not found at '${this.serverUrl}'.`
|
||||
)
|
||||
}
|
||||
|
||||
return contexts.items[0]
|
||||
}
|
||||
|
||||
public async getComputeContextById(
|
||||
contextId: string,
|
||||
accessToken?: string
|
||||
): Promise<ContextAllAttributes> {
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const { result: context } = await this.request<ContextAllAttributes>(
|
||||
`${this.serverUrl}/compute/contexts/${contextId}`,
|
||||
{ headers }
|
||||
).catch((err) => {
|
||||
throw prefixMessage(err, 'Error while getting compute context by id. ')
|
||||
})
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
public async getExecutableContexts(
|
||||
executeScript: Function,
|
||||
accessToken?: string
|
||||
) {
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const { result: contexts } = await this.request<{ items: Context[] }>(
|
||||
`${this.serverUrl}/compute/contexts?limit=10000`,
|
||||
{ headers }
|
||||
).catch((err) => {
|
||||
throw prefixMessage(err, 'Error while fetching compute contexts.')
|
||||
})
|
||||
|
||||
const contextsList = contexts.items || []
|
||||
const executableContexts: any[] = []
|
||||
|
||||
const promises = contextsList.map((context: any) => {
|
||||
const linesOfCode = ['%put &=sysuserid;']
|
||||
|
||||
return () =>
|
||||
executeScript(
|
||||
`test-${context.name}`,
|
||||
linesOfCode,
|
||||
context.name,
|
||||
accessToken,
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
).catch((err: any) => err)
|
||||
})
|
||||
|
||||
let results: any[] = []
|
||||
|
||||
for (const promise of promises) results.push(await promise())
|
||||
|
||||
results.forEach((result: any, index: number) => {
|
||||
if (result && result.log) {
|
||||
try {
|
||||
const resultParsed = result.log
|
||||
let sysUserId = ''
|
||||
|
||||
const sysUserIdLog = resultParsed
|
||||
.split('\n')
|
||||
.find((line: string) => line.startsWith('SYSUSERID='))
|
||||
|
||||
if (sysUserIdLog) {
|
||||
sysUserId = sysUserIdLog.replace('SYSUSERID=', '')
|
||||
|
||||
executableContexts.push({
|
||||
createdBy: contextsList[index].createdBy,
|
||||
id: contextsList[index].id,
|
||||
name: contextsList[index].name,
|
||||
version: contextsList[index].version,
|
||||
attributes: {
|
||||
sysUserId
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return executableContexts
|
||||
}
|
||||
|
||||
public async deleteComputeContext(contextName: string, accessToken?: string) {
|
||||
this.validateContextName(contextName)
|
||||
|
||||
this.isDefaultContext(
|
||||
contextName,
|
||||
this.defaultComputeContexts,
|
||||
'Deleting default SAS compute contexts is not allowed.',
|
||||
true
|
||||
)
|
||||
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const context = await this.getComputeContextByName(contextName, accessToken)
|
||||
|
||||
const deleteContextRequest: RequestInit = {
|
||||
method: 'DELETE',
|
||||
headers
|
||||
}
|
||||
|
||||
return await this.request<Context>(
|
||||
`${this.serverUrl}/compute/contexts/${context.id}`,
|
||||
deleteContextRequest
|
||||
)
|
||||
}
|
||||
|
||||
// TODO: implement editLauncherContext method
|
||||
|
||||
// TODO: implement deleteLauncherContext method
|
||||
|
||||
private async request<T>(
|
||||
url: string,
|
||||
options: RequestInit,
|
||||
contentType: 'text' | 'json' = 'json'
|
||||
) {
|
||||
if (this.csrfToken) {
|
||||
options.headers = {
|
||||
...options.headers,
|
||||
[this.csrfToken.headerName]: this.csrfToken.value
|
||||
}
|
||||
}
|
||||
|
||||
return await makeRequest<T>(
|
||||
url,
|
||||
options,
|
||||
(token) => {
|
||||
this.csrfToken = token
|
||||
this.setCsrfToken(token)
|
||||
},
|
||||
contentType
|
||||
).catch((err) => {
|
||||
throw prefixMessage(
|
||||
err,
|
||||
'Error while making request in Context Manager. '
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
private validateContextName(name: string) {
|
||||
if (!name) throw new Error('Context name is required.')
|
||||
}
|
||||
|
||||
public isDefaultContext(
|
||||
context: string,
|
||||
defaultContexts: string[] = this.defaultComputeContexts,
|
||||
errorMessage = '',
|
||||
listDefaults = false
|
||||
) {
|
||||
if (defaultContexts.includes(context)) {
|
||||
throw new Error(
|
||||
`${errorMessage}${
|
||||
listDefaults
|
||||
? '\nDefault contexts:' +
|
||||
defaultContexts.map((context, i) => `\n${i + 1}. ${context}`)
|
||||
: ''
|
||||
}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from './types'
|
||||
import { formatDataForRequest } from './utils/formatDataForRequest'
|
||||
import { SessionManager } from './SessionManager'
|
||||
import { ContextManager } from './ContextManager'
|
||||
import { timestampToYYYYMMDDHHMMSS } from '@sasjs/utils/time'
|
||||
import { Logger, LogLevel } from '@sasjs/utils/logger'
|
||||
|
||||
@@ -46,6 +47,7 @@ export class SASViyaApiClient {
|
||||
this.contextName,
|
||||
this.setCsrfToken
|
||||
)
|
||||
private contextManager = new ContextManager(this.serverUrl, this.setCsrfToken)
|
||||
private folderMap = new Map<string, Job[]>()
|
||||
|
||||
public get debug() {
|
||||
@@ -98,29 +100,23 @@ export class SASViyaApiClient {
|
||||
* Returns all available compute contexts on this server.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async getAllContexts(accessToken?: string) {
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
public async getComputeContexts(accessToken?: string) {
|
||||
return await this.contextManager.getComputeContexts(accessToken)
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
/**
|
||||
* Returns default(system) compute contexts.
|
||||
*/
|
||||
public getDefaultComputeContexts() {
|
||||
return this.contextManager.getDefaultComputeContexts
|
||||
}
|
||||
|
||||
const { result: contexts } = await this.request<{ items: Context[] }>(
|
||||
`${this.serverUrl}/compute/contexts?limit=10000`,
|
||||
{ headers }
|
||||
)
|
||||
|
||||
const contextsList = contexts && contexts.items ? contexts.items : []
|
||||
|
||||
return contextsList.map((context: any) => ({
|
||||
createdBy: context.createdBy,
|
||||
id: context.id,
|
||||
name: context.name,
|
||||
version: context.version,
|
||||
attributes: {}
|
||||
}))
|
||||
/**
|
||||
* Returns all available launcher contexts on this server.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async getLauncherContexts(accessToken?: string) {
|
||||
return await this.contextManager.getLauncherContexts(accessToken)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,74 +124,12 @@ export class SASViyaApiClient {
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async getExecutableContexts(accessToken?: string) {
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
const bindedExecuteScript = this.executeScript.bind(this)
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const { result: contexts } = await this.request<{ items: Context[] }>(
|
||||
`${this.serverUrl}/compute/contexts?limit=10000`,
|
||||
{ headers }
|
||||
).catch((err) => {
|
||||
throw err
|
||||
})
|
||||
|
||||
const contextsList = contexts.items || []
|
||||
const executableContexts: any[] = []
|
||||
|
||||
const promises = contextsList.map((context: any) => {
|
||||
const linesOfCode = ['%put &=sysuserid;']
|
||||
|
||||
return () =>
|
||||
this.executeScript(
|
||||
`test-${context.name}`,
|
||||
linesOfCode,
|
||||
context.name,
|
||||
accessToken,
|
||||
null,
|
||||
false,
|
||||
true,
|
||||
true
|
||||
).catch((err) => err)
|
||||
})
|
||||
|
||||
let results: any[] = []
|
||||
|
||||
for (const promise of promises) results.push(await promise())
|
||||
|
||||
results.forEach((result: any, index: number) => {
|
||||
if (result && result.log) {
|
||||
try {
|
||||
const resultParsed = result.log
|
||||
let sysUserId = ''
|
||||
|
||||
const sysUserIdLog = resultParsed
|
||||
.split('\n')
|
||||
.find((line: string) => line.startsWith('SYSUSERID='))
|
||||
|
||||
if (sysUserIdLog) {
|
||||
sysUserId = sysUserIdLog.replace('SYSUSERID=', '')
|
||||
|
||||
executableContexts.push({
|
||||
createdBy: contextsList[index].createdBy,
|
||||
id: contextsList[index].id,
|
||||
name: contextsList[index].name,
|
||||
version: contextsList[index].version,
|
||||
attributes: {
|
||||
sysUserId
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return executableContexts
|
||||
return await this.contextManager.getExecutableContexts(
|
||||
bindedExecuteScript,
|
||||
accessToken
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -248,7 +182,7 @@ export class SASViyaApiClient {
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
* @param authorizedUsers - an optional list of authorized user IDs.
|
||||
*/
|
||||
public async createContext(
|
||||
public async createComputeContext(
|
||||
contextName: string,
|
||||
launchContextName: string,
|
||||
sharedAccountId: string,
|
||||
@@ -256,59 +190,35 @@ export class SASViyaApiClient {
|
||||
accessToken?: string,
|
||||
authorizedUsers?: string[]
|
||||
) {
|
||||
if (!contextName) {
|
||||
throw new Error('Context name is required.')
|
||||
}
|
||||
|
||||
if (!launchContextName) {
|
||||
throw new Error('Launch context name is required.')
|
||||
}
|
||||
|
||||
if (!sharedAccountId) {
|
||||
throw new Error('Shared account ID is required.')
|
||||
}
|
||||
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const requestBody: any = {
|
||||
name: contextName,
|
||||
launchContext: {
|
||||
contextName: launchContextName
|
||||
},
|
||||
attributes: {
|
||||
reuseServerProcesses: true,
|
||||
runServerAs: sharedAccountId
|
||||
}
|
||||
}
|
||||
|
||||
if (authorizedUsers && authorizedUsers.length) {
|
||||
requestBody['authorizedUsers'] = authorizedUsers
|
||||
} else {
|
||||
requestBody['authorizeAllAuthenticatedUsers'] = true
|
||||
}
|
||||
|
||||
if (autoExecLines) {
|
||||
requestBody.environment = { autoExecLines }
|
||||
}
|
||||
|
||||
const createContextRequest: RequestInit = {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(requestBody)
|
||||
}
|
||||
|
||||
const { result: context } = await this.request<Context>(
|
||||
`${this.serverUrl}/compute/contexts`,
|
||||
createContextRequest
|
||||
return await this.contextManager.createComputeContext(
|
||||
contextName,
|
||||
launchContextName,
|
||||
sharedAccountId,
|
||||
autoExecLines,
|
||||
accessToken,
|
||||
authorizedUsers
|
||||
)
|
||||
}
|
||||
|
||||
return context
|
||||
/**
|
||||
* Creates a launcher context on the given server.
|
||||
* @param contextName - the name of the context to be created.
|
||||
* @param description - the description of the context to be created.
|
||||
* @param launchType - launch type of the context to be created.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async createLauncherContext(
|
||||
contextName: string,
|
||||
description: string,
|
||||
launchType = 'direct',
|
||||
accessToken?: string
|
||||
) {
|
||||
return await this.contextManager.createLauncherContext(
|
||||
contextName,
|
||||
description,
|
||||
launchType,
|
||||
accessToken
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -317,75 +227,15 @@ export class SASViyaApiClient {
|
||||
* @param editedContext - an object with the properties to be updated.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async editContext(
|
||||
public async editComputeContext(
|
||||
contextName: string,
|
||||
editedContext: EditContextInput,
|
||||
accessToken?: string
|
||||
) {
|
||||
if (!contextName) {
|
||||
throw new Error('Invalid context name.')
|
||||
}
|
||||
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
let originalContext
|
||||
|
||||
originalContext = await this.getComputeContextByName(
|
||||
return await this.contextManager.editComputeContext(
|
||||
contextName,
|
||||
editedContext,
|
||||
accessToken
|
||||
).catch((err) => {
|
||||
throw err
|
||||
})
|
||||
|
||||
// Try to find context by id, when context name has been changed.
|
||||
if (!originalContext) {
|
||||
originalContext = await this.getComputeContextById(
|
||||
editedContext.id!,
|
||||
accessToken
|
||||
).catch((err) => {
|
||||
throw err
|
||||
})
|
||||
}
|
||||
|
||||
const { result: context, etag } = await this.request<Context>(
|
||||
`${this.serverUrl}/compute/contexts/${originalContext.id}`,
|
||||
{
|
||||
headers
|
||||
}
|
||||
).catch((err) => {
|
||||
if (err && err.status === 404) {
|
||||
throw new Error(
|
||||
`The context '${contextName}' was not found on this server.`
|
||||
)
|
||||
}
|
||||
|
||||
throw err
|
||||
})
|
||||
|
||||
// An If-Match header with the value of the last ETag for the context
|
||||
// is required to be able to update it
|
||||
// https://developer.sas.com/apis/rest/Compute/#update-a-context-definition
|
||||
headers['If-Match'] = etag
|
||||
|
||||
const updateContextRequest: RequestInit = {
|
||||
method: 'PUT',
|
||||
headers,
|
||||
body: JSON.stringify({
|
||||
...context,
|
||||
...editedContext,
|
||||
attributes: { ...context.attributes, ...editedContext.attributes }
|
||||
})
|
||||
}
|
||||
|
||||
return await this.request<Context>(
|
||||
`${this.serverUrl}/compute/contexts/${context.id}`,
|
||||
updateContextRequest
|
||||
)
|
||||
}
|
||||
|
||||
@@ -394,29 +244,10 @@ export class SASViyaApiClient {
|
||||
* @param contextName - the name of the context to be deleted.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async deleteContext(contextName: string, accessToken?: string) {
|
||||
if (!contextName) {
|
||||
throw new Error('Invalid context name.')
|
||||
}
|
||||
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const context = await this.getComputeContextByName(contextName, accessToken)
|
||||
|
||||
const deleteContextRequest: RequestInit = {
|
||||
method: 'DELETE',
|
||||
headers
|
||||
}
|
||||
|
||||
return await this.request<Context>(
|
||||
`${this.serverUrl}/compute/contexts/${context.id}`,
|
||||
deleteContextRequest
|
||||
public async deleteComputeContext(contextName: string, accessToken?: string) {
|
||||
return await this.contextManager.deleteComputeContext(
|
||||
contextName,
|
||||
accessToken
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1438,26 +1269,10 @@ export class SASViyaApiClient {
|
||||
contextName: string,
|
||||
accessToken?: string
|
||||
): Promise<Context> {
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const { result: contexts } = await this.request<{ items: Context[] }>(
|
||||
`${this.serverUrl}/compute/contexts?filter=eq(name, "${contextName}")`,
|
||||
{ headers }
|
||||
return await this.contextManager.getComputeContextByName(
|
||||
contextName,
|
||||
accessToken
|
||||
)
|
||||
|
||||
if (!contexts || !(contexts.items && contexts.items.length)) {
|
||||
throw new Error(
|
||||
`The context '${contextName}' was not found at '${this.serverUrl}'.`
|
||||
)
|
||||
}
|
||||
|
||||
return contexts.items[0]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1469,22 +1284,10 @@ export class SASViyaApiClient {
|
||||
contextId: string,
|
||||
accessToken?: string
|
||||
): Promise<ContextAllAttributes> {
|
||||
const headers: any = {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
|
||||
if (accessToken) {
|
||||
headers.Authorization = `Bearer ${accessToken}`
|
||||
}
|
||||
|
||||
const { result: context } = await this.request<ContextAllAttributes>(
|
||||
`${this.serverUrl}/compute/contexts/${contextId}`,
|
||||
{ headers }
|
||||
).catch((err) => {
|
||||
throw err
|
||||
})
|
||||
|
||||
return context
|
||||
return await this.contextManager.getComputeContextById(
|
||||
contextId,
|
||||
accessToken
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
77
src/SASjs.ts
77
src/SASjs.ts
@@ -96,12 +96,39 @@ export default class SASjs {
|
||||
)
|
||||
}
|
||||
|
||||
public async getAllContexts(accessToken: string) {
|
||||
this.isMethodSupported('getAllContexts', ServerType.SASViya)
|
||||
/**
|
||||
* Gets compute contexts.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async getComputeContexts(accessToken: string) {
|
||||
this.isMethodSupported('getComputeContexts', ServerType.SASViya)
|
||||
|
||||
return await this.sasViyaApiClient!.getAllContexts(accessToken)
|
||||
return await this.sasViyaApiClient!.getComputeContexts(accessToken)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets launcher contexts.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async getLauncherContexts(accessToken: string) {
|
||||
this.isMethodSupported('getLauncherContexts', ServerType.SASViya)
|
||||
|
||||
return await this.sasViyaApiClient!.getLauncherContexts(accessToken)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets default(system) launcher contexts.
|
||||
*/
|
||||
public getDefaultComputeContexts() {
|
||||
this.isMethodSupported('getDefaultComputeContexts', ServerType.SASViya)
|
||||
|
||||
return this.sasViyaApiClient!.getDefaultComputeContexts()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets executable compute contexts.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async getExecutableContexts(accessToken: string) {
|
||||
this.isMethodSupported('getExecutableContexts', ServerType.SASViya)
|
||||
|
||||
@@ -117,7 +144,7 @@ export default class SASjs {
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
* @param authorizedUsers - an optional list of authorized user IDs.
|
||||
*/
|
||||
public async createContext(
|
||||
public async createComputeContext(
|
||||
contextName: string,
|
||||
launchContextName: string,
|
||||
sharedAccountId: string,
|
||||
@@ -125,9 +152,9 @@ export default class SASjs {
|
||||
accessToken: string,
|
||||
authorizedUsers?: string[]
|
||||
) {
|
||||
this.isMethodSupported('createContext', ServerType.SASViya)
|
||||
this.isMethodSupported('createComputeContext', ServerType.SASViya)
|
||||
|
||||
return await this.sasViyaApiClient!.createContext(
|
||||
return await this.sasViyaApiClient!.createComputeContext(
|
||||
contextName,
|
||||
launchContextName,
|
||||
sharedAccountId,
|
||||
@@ -137,20 +164,43 @@ export default class SASjs {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a launcher context on the given server.
|
||||
* @param contextName - the name of the context to be created.
|
||||
* @param description - the description of the context to be created.
|
||||
* @param launchType - launch type of the context to be created.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async createLauncherContext(
|
||||
contextName: string,
|
||||
description: string,
|
||||
launchType: string,
|
||||
accessToken: string
|
||||
) {
|
||||
this.isMethodSupported('createLauncherContext', ServerType.SASViya)
|
||||
|
||||
return await this.sasViyaApiClient!.createLauncherContext(
|
||||
contextName,
|
||||
description,
|
||||
launchType,
|
||||
accessToken
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a compute context on the given server.
|
||||
* @param contextName - the original name of the context to be deleted.
|
||||
* @param editedContext - an object with the properties to be updated.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async editContext(
|
||||
public async editComputeContext(
|
||||
contextName: string,
|
||||
editedContext: EditContextInput,
|
||||
accessToken?: string
|
||||
) {
|
||||
this.isMethodSupported('editContext', ServerType.SASViya)
|
||||
this.isMethodSupported('editComputeContext', ServerType.SASViya)
|
||||
|
||||
return await this.sasViyaApiClient!.editContext(
|
||||
return await this.sasViyaApiClient!.editComputeContext(
|
||||
contextName,
|
||||
editedContext,
|
||||
accessToken
|
||||
@@ -162,10 +212,13 @@ export default class SASjs {
|
||||
* @param contextName - the name of the context to be deleted.
|
||||
* @param accessToken - an access token for an authorized user.
|
||||
*/
|
||||
public async deleteContext(contextName: string, accessToken?: string) {
|
||||
this.isMethodSupported('deleteContext', ServerType.SASViya)
|
||||
public async deleteComputeContext(contextName: string, accessToken?: string) {
|
||||
this.isMethodSupported('deleteComputeContext', ServerType.SASViya)
|
||||
|
||||
return await this.sasViyaApiClient!.deleteContext(contextName, accessToken)
|
||||
return await this.sasViyaApiClient!.deleteComputeContext(
|
||||
contextName,
|
||||
accessToken
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -176,7 +176,7 @@ export class SessionManager {
|
||||
) {
|
||||
if (stateLink) {
|
||||
if (this.debug) {
|
||||
console.log('Polling session status... \n') // ?
|
||||
console.log('Polling session status... \n')
|
||||
}
|
||||
|
||||
const { result: state } = await this.requestSessionStatus<string>(
|
||||
|
||||
585
src/test/ContextManager.spec.ts
Normal file
585
src/test/ContextManager.spec.ts
Normal file
@@ -0,0 +1,585 @@
|
||||
import { ContextManager } from '../ContextManager'
|
||||
|
||||
describe('ContextManager', () => {
|
||||
let originalFetch: any
|
||||
let fetchCallNumber = 0
|
||||
|
||||
const fakeGlobalFetch = (fakeResponses: object[]) => {
|
||||
;(global as any).fetch = jest.fn().mockImplementation(() => {
|
||||
const fakeResponse = fakeResponses[fetchCallNumber]
|
||||
|
||||
if (
|
||||
fetchCallNumber !== fakeResponses.length &&
|
||||
fakeResponses.length > 1
|
||||
) {
|
||||
if (fetchCallNumber + 1 === fakeResponses.length) fetchCallNumber = 0
|
||||
else fetchCallNumber += 1
|
||||
} else {
|
||||
fetchCallNumber = 0
|
||||
}
|
||||
|
||||
return Promise.resolve({
|
||||
ok: true,
|
||||
headers: { get: () => '' },
|
||||
json: () => Promise.resolve(fakeResponse)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const contextManager = new ContextManager(
|
||||
process.env.SERVER_URL as string,
|
||||
() => {}
|
||||
)
|
||||
|
||||
const defaultComputeContexts = contextManager.getDefaultComputeContexts
|
||||
const defaultLauncherContexts = contextManager.getDefaultLauncherContexts
|
||||
|
||||
const getRandomDefaultComputeContext = () =>
|
||||
defaultComputeContexts[
|
||||
Math.floor(Math.random() * defaultComputeContexts.length)
|
||||
]
|
||||
const getRandomDefaultLauncherContext = () =>
|
||||
defaultLauncherContexts[
|
||||
Math.floor(Math.random() * defaultLauncherContexts.length)
|
||||
]
|
||||
|
||||
beforeAll(() => {
|
||||
originalFetch = (global as any).fetch
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
;(global as any).fetch = originalFetch
|
||||
})
|
||||
|
||||
describe('getComputeContexts', () => {
|
||||
it('should fetch compute contexts', async () => {
|
||||
const sampleComputeContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: 'Fake Compute Context',
|
||||
attributes: {}
|
||||
}
|
||||
|
||||
const sampleResponse = {
|
||||
items: [sampleComputeContext]
|
||||
}
|
||||
|
||||
fakeGlobalFetch([sampleResponse])
|
||||
|
||||
await expect(contextManager.getComputeContexts()).resolves.toEqual([
|
||||
sampleComputeContext
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('getLauncherContexts', () => {
|
||||
it('should fetch launcher contexts', async () => {
|
||||
const sampleComputeContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: 'Fake Launcher Context',
|
||||
attributes: {}
|
||||
}
|
||||
|
||||
const sampleResponse = {
|
||||
items: [sampleComputeContext]
|
||||
}
|
||||
|
||||
fakeGlobalFetch([sampleResponse])
|
||||
|
||||
await expect(contextManager.getLauncherContexts()).resolves.toEqual([
|
||||
sampleComputeContext
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('createComputeContext', () => {
|
||||
it('should throw an error if context name was not provided', async () => {
|
||||
await expect(
|
||||
contextManager.createComputeContext(
|
||||
'',
|
||||
'Test Launcher Context',
|
||||
'fakeAccountId',
|
||||
[]
|
||||
)
|
||||
).rejects.toEqual(new Error('Context name is required.'))
|
||||
})
|
||||
|
||||
it('should throw an error when attempt to create context with reserved name', async () => {
|
||||
const contextName = getRandomDefaultComputeContext()
|
||||
|
||||
await expect(
|
||||
contextManager.createComputeContext(
|
||||
contextName,
|
||||
'Test Launcher Context',
|
||||
'fakeAccountId',
|
||||
[]
|
||||
)
|
||||
).rejects.toEqual(
|
||||
new Error(`Compute context '${contextName}' already exists.`)
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw an error if context already exists', async () => {
|
||||
const contextName = 'Existing Compute Context'
|
||||
|
||||
const sampleComputeContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: contextName,
|
||||
attributes: {}
|
||||
}
|
||||
|
||||
const sampleResponse = {
|
||||
items: [sampleComputeContext]
|
||||
}
|
||||
|
||||
fakeGlobalFetch([sampleResponse])
|
||||
|
||||
await expect(
|
||||
contextManager.createComputeContext(
|
||||
contextName,
|
||||
'Test Launcher Context',
|
||||
'fakeAccountId',
|
||||
[]
|
||||
)
|
||||
).rejects.toEqual(
|
||||
new Error(`Compute context '${contextName}' already exists.`)
|
||||
)
|
||||
})
|
||||
|
||||
it('should create compute context without launcher context', async () => {
|
||||
const contextName = 'New Compute Context'
|
||||
|
||||
const sampleExistingComputeContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: 'Existing Compute Context',
|
||||
attributes: {}
|
||||
}
|
||||
const sampleNewComputeContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: contextName,
|
||||
attributes: {}
|
||||
}
|
||||
|
||||
const sampleResponseExistingComputeContexts = {
|
||||
items: [sampleExistingComputeContext]
|
||||
}
|
||||
const sampleResponseCreatedComputeContext = {
|
||||
items: [sampleNewComputeContext]
|
||||
}
|
||||
|
||||
fakeGlobalFetch([
|
||||
sampleResponseExistingComputeContexts,
|
||||
sampleResponseCreatedComputeContext
|
||||
])
|
||||
|
||||
await expect(
|
||||
contextManager.createComputeContext(
|
||||
contextName,
|
||||
'',
|
||||
'fakeAccountId',
|
||||
[]
|
||||
)
|
||||
).resolves.toEqual({
|
||||
items: [
|
||||
{
|
||||
attributes: {},
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
name: contextName,
|
||||
version: 2
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
it('should create compute context with default launcher context', async () => {
|
||||
const contextName = 'New Compute Context'
|
||||
|
||||
const sampleExistingComputeContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: 'Existing Compute Context',
|
||||
attributes: {}
|
||||
}
|
||||
const sampleNewComputeContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: contextName,
|
||||
attributes: {}
|
||||
}
|
||||
|
||||
const sampleResponseExistingComputeContexts = {
|
||||
items: [sampleExistingComputeContext]
|
||||
}
|
||||
const sampleResponseCreatedComputeContext = {
|
||||
items: [sampleNewComputeContext]
|
||||
}
|
||||
|
||||
fakeGlobalFetch([
|
||||
sampleResponseExistingComputeContexts,
|
||||
sampleResponseCreatedComputeContext
|
||||
])
|
||||
|
||||
await expect(
|
||||
contextManager.createComputeContext(
|
||||
contextName,
|
||||
getRandomDefaultLauncherContext(),
|
||||
'fakeAccountId',
|
||||
[]
|
||||
)
|
||||
).resolves.toEqual({
|
||||
items: [
|
||||
{
|
||||
attributes: {},
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
name: contextName,
|
||||
version: 2
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
|
||||
it('should create compute context with not existing launcher context', async () => {
|
||||
const computeContextName = 'New Compute Context'
|
||||
const launcherContextName = 'New Launcher Context'
|
||||
|
||||
const sampleExistingComputeContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: 'Existing Compute Context',
|
||||
attributes: {}
|
||||
}
|
||||
const sampleNewComputeContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: computeContextName,
|
||||
attributes: {}
|
||||
}
|
||||
const sampleNewLauncherContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: launcherContextName,
|
||||
attributes: {}
|
||||
}
|
||||
|
||||
const sampleResponseExistingComputeContexts = {
|
||||
items: [sampleExistingComputeContext]
|
||||
}
|
||||
const sampleResponseCreatedLauncherContext = {
|
||||
items: [sampleNewLauncherContext]
|
||||
}
|
||||
const sampleResponseCreatedComputeContext = {
|
||||
items: [sampleNewComputeContext]
|
||||
}
|
||||
|
||||
fakeGlobalFetch([
|
||||
sampleResponseExistingComputeContexts,
|
||||
sampleResponseCreatedLauncherContext,
|
||||
sampleResponseCreatedComputeContext
|
||||
])
|
||||
|
||||
await expect(
|
||||
contextManager.createComputeContext(
|
||||
computeContextName,
|
||||
launcherContextName,
|
||||
'fakeAccountId',
|
||||
[]
|
||||
)
|
||||
).resolves.toEqual({
|
||||
items: [
|
||||
{
|
||||
attributes: {},
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
name: computeContextName,
|
||||
version: 2
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('createLauncherContext', () => {
|
||||
it('should throw an error if context name was not provided', async () => {
|
||||
await expect(
|
||||
contextManager.createLauncherContext('', 'Test Description')
|
||||
).rejects.toEqual(new Error('Context name is required.'))
|
||||
})
|
||||
|
||||
it('should throw an error when attempt to create context with reserved name', async () => {
|
||||
const contextName = getRandomDefaultLauncherContext()
|
||||
|
||||
await expect(
|
||||
contextManager.createLauncherContext(contextName, 'Test Description')
|
||||
).rejects.toEqual(
|
||||
new Error(`Launcher context '${contextName}' already exists.`)
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw an error if context already exists', async () => {
|
||||
const contextName = 'Existing Launcher Context'
|
||||
|
||||
const sampleLauncherContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: contextName,
|
||||
attributes: {}
|
||||
}
|
||||
|
||||
const sampleResponse = {
|
||||
items: [sampleLauncherContext]
|
||||
}
|
||||
|
||||
fakeGlobalFetch([sampleResponse])
|
||||
|
||||
await expect(
|
||||
contextManager.createLauncherContext(contextName, 'Test Description')
|
||||
).rejects.toEqual(
|
||||
new Error(`Launcher context '${contextName}' already exists.`)
|
||||
)
|
||||
})
|
||||
|
||||
it('should create launcher context', async () => {
|
||||
const contextName = 'New Launcher Context'
|
||||
|
||||
const sampleExistingLauncherContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: 'Existing Launcher Context',
|
||||
attributes: {}
|
||||
}
|
||||
const sampleNewLauncherContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: contextName,
|
||||
attributes: {}
|
||||
}
|
||||
|
||||
const sampleResponseExistingLauncherContext = {
|
||||
items: [sampleExistingLauncherContext]
|
||||
}
|
||||
const sampleResponseCreatedLauncherContext = {
|
||||
items: [sampleNewLauncherContext]
|
||||
}
|
||||
|
||||
fakeGlobalFetch([
|
||||
sampleResponseExistingLauncherContext,
|
||||
sampleResponseCreatedLauncherContext
|
||||
])
|
||||
|
||||
await expect(
|
||||
contextManager.createLauncherContext(contextName, 'Test Description')
|
||||
).resolves.toEqual({
|
||||
items: [
|
||||
{
|
||||
attributes: {},
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
name: contextName,
|
||||
version: 2
|
||||
}
|
||||
]
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('editComputeContext', () => {
|
||||
const editedContext = {
|
||||
name: 'updated name',
|
||||
description: 'updated description',
|
||||
id: 'someId'
|
||||
}
|
||||
|
||||
it('should throw an error if context name was not provided', async () => {
|
||||
await expect(
|
||||
contextManager.editComputeContext('', editedContext)
|
||||
).rejects.toEqual(new Error('Context name is required.'))
|
||||
})
|
||||
|
||||
it('should throw an error when attempt to edit context with reserved name', async () => {
|
||||
const contextName = getRandomDefaultComputeContext()
|
||||
|
||||
let editError: Error = { name: '', message: '' }
|
||||
|
||||
try {
|
||||
contextManager.isDefaultContext(
|
||||
contextName,
|
||||
defaultComputeContexts,
|
||||
'Editing default SAS compute contexts is not allowed.',
|
||||
true
|
||||
)
|
||||
} catch (error) {
|
||||
editError = error
|
||||
}
|
||||
|
||||
await expect(
|
||||
contextManager.editComputeContext(contextName, editedContext)
|
||||
).rejects.toEqual(editError)
|
||||
})
|
||||
|
||||
it('should edit context if founded by name', async () => {
|
||||
const sampleComputeContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: editedContext.name,
|
||||
attributes: {}
|
||||
}
|
||||
|
||||
const sampleResponseGetComputeContextByName = {
|
||||
items: [sampleComputeContext]
|
||||
}
|
||||
|
||||
fakeGlobalFetch([sampleResponseGetComputeContextByName])
|
||||
|
||||
const expectedResponse = {
|
||||
etag: '',
|
||||
result: sampleResponseGetComputeContextByName
|
||||
}
|
||||
|
||||
await expect(
|
||||
contextManager.editComputeContext(editedContext.name, editedContext)
|
||||
).resolves.toEqual(expectedResponse)
|
||||
})
|
||||
})
|
||||
|
||||
describe('getExecutableContexts', () => {
|
||||
it('should return executable contexts', async () => {
|
||||
const sampleComputeContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: 'Executable Compute Context',
|
||||
attributes: {}
|
||||
}
|
||||
|
||||
const sampleResponse = {
|
||||
items: [sampleComputeContext]
|
||||
}
|
||||
|
||||
fakeGlobalFetch([sampleResponse])
|
||||
|
||||
const user = 'testUser'
|
||||
|
||||
const fakedExecuteScript = async () => {
|
||||
return Promise.resolve({ log: `SYSUSERID=${user}` })
|
||||
}
|
||||
|
||||
const expectedResponse = [
|
||||
{
|
||||
...sampleComputeContext,
|
||||
attributes: { sysUserId: user }
|
||||
}
|
||||
]
|
||||
|
||||
await expect(
|
||||
contextManager.getExecutableContexts(fakedExecuteScript)
|
||||
).resolves.toEqual(expectedResponse)
|
||||
})
|
||||
|
||||
it('should not return executable contexts', async () => {
|
||||
const sampleComputeContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: 'Not Executable Compute Context',
|
||||
attributes: {}
|
||||
}
|
||||
|
||||
const sampleResponse = {
|
||||
items: [sampleComputeContext]
|
||||
}
|
||||
|
||||
fakeGlobalFetch([sampleResponse])
|
||||
|
||||
const fakedExecuteScript = async () => {
|
||||
return Promise.resolve({ log: '' })
|
||||
}
|
||||
|
||||
await expect(
|
||||
contextManager.getExecutableContexts(fakedExecuteScript)
|
||||
).resolves.toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('deleteComputeContext', () => {
|
||||
it('should throw an error if context name was not provided', async () => {
|
||||
await expect(contextManager.deleteComputeContext('')).rejects.toEqual(
|
||||
new Error('Context name is required.')
|
||||
)
|
||||
})
|
||||
|
||||
it('should throw an error when attempt to delete context with reserved name', async () => {
|
||||
const contextName = getRandomDefaultComputeContext()
|
||||
|
||||
let deleteError: Error = { name: '', message: '' }
|
||||
|
||||
try {
|
||||
contextManager.isDefaultContext(
|
||||
contextName,
|
||||
defaultComputeContexts,
|
||||
'Deleting default SAS compute contexts is not allowed.',
|
||||
true
|
||||
)
|
||||
} catch (error) {
|
||||
deleteError = error
|
||||
}
|
||||
|
||||
await expect(
|
||||
contextManager.deleteComputeContext(contextName)
|
||||
).rejects.toEqual(deleteError)
|
||||
})
|
||||
|
||||
it('should delete context', async () => {
|
||||
const contextName = 'Compute Context To Delete'
|
||||
|
||||
const sampleComputeContext = {
|
||||
createdBy: 'fake creator',
|
||||
id: 'fakeId',
|
||||
version: 2,
|
||||
name: contextName,
|
||||
attributes: {}
|
||||
}
|
||||
|
||||
const sampleResponseGetComputeContextByName = {
|
||||
items: [sampleComputeContext]
|
||||
}
|
||||
|
||||
const sampleResponseDeletedContext = {
|
||||
items: [sampleComputeContext]
|
||||
}
|
||||
|
||||
fakeGlobalFetch([
|
||||
sampleResponseGetComputeContextByName,
|
||||
sampleResponseDeletedContext
|
||||
])
|
||||
|
||||
const expectedResponse = {
|
||||
etag: '',
|
||||
result: sampleResponseDeletedContext
|
||||
}
|
||||
|
||||
await expect(
|
||||
contextManager.deleteComputeContext(contextName)
|
||||
).resolves.toEqual(expectedResponse)
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user