mirror of
https://github.com/sasjs/adapter.git
synced 2026-01-06 12:10:04 +00:00
Merge branch 'master' into issue-186
This commit is contained in:
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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1451,26 +1282,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]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1482,22 +1297,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