mirror of
https://github.com/sasjs/adapter.git
synced 2025-12-11 01:14:36 +00:00
Merge pull request #827 from sasjs/cli-issue-1367
Bleached Verbose Mode and unit testing pollJobState
This commit is contained in:
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -80,6 +80,10 @@ jobs:
|
||||
npm run update:adapter
|
||||
pm2 start --name sasjs-test npm -- start
|
||||
|
||||
- name: Sleep for 10 seconds
|
||||
run: sleep 10s
|
||||
shell: bash
|
||||
|
||||
- name: Run cypress on sasjs
|
||||
run: |
|
||||
replace-in-files --regex='"sasjsTestsUrl".*' --replacement='"sasjsTestsUrl":"http://localhost:3000",' ./cypress.json
|
||||
|
||||
@@ -155,7 +155,7 @@ The adapter will also cache the logs (if debug enabled) and even the work tables
|
||||
|
||||
### Verbose Mode
|
||||
|
||||
Set `verbose` to `true` to enable verbose mode that logs a summary of every HTTP response. Verbose mode can be disabled by calling `disableVerboseMode` method or enabled by `enableVerboseMode` method. Verbose mode can also be enabled/disabled by `startComputeJob` method.
|
||||
Set `verbose` to `true` to enable verbose mode that logs a summary of every HTTP response. Verbose mode can be disabled by calling `disableVerboseMode` method or enabled by `enableVerboseMode` method. Verbose mode also supports `bleached` mode that disables extra colors in req/res summary. To enable `bleached` verbose mode, pass `verbose` equal to `bleached` while instantiating an instance of `RequestClient` or to `setVerboseMode` method. Verbose mode can also be enabled/disabled by `startComputeJob` method.
|
||||
|
||||
### Session Manager
|
||||
|
||||
|
||||
44
src/SASjs.ts
44
src/SASjs.ts
@@ -4,7 +4,12 @@ import {
|
||||
UploadFile,
|
||||
EditContextInput,
|
||||
PollOptions,
|
||||
LoginMechanism
|
||||
LoginMechanism,
|
||||
VerboseMode,
|
||||
ErrorResponse,
|
||||
LoginOptions,
|
||||
LoginResult,
|
||||
ExecutionQuery
|
||||
} from './types'
|
||||
import { SASViyaApiClient } from './SASViyaApiClient'
|
||||
import { SAS9ApiClient } from './SAS9ApiClient'
|
||||
@@ -29,8 +34,6 @@ import {
|
||||
Sas9JobExecutor,
|
||||
FileUploader
|
||||
} from './job-execution'
|
||||
import { ErrorResponse } from './types/errors'
|
||||
import { LoginOptions, LoginResult } from './types/Login'
|
||||
import { AxiosResponse } from 'axios'
|
||||
|
||||
interface ExecuteScriptParams {
|
||||
@@ -158,6 +161,23 @@ export default class SASjs {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes job on SASJS server.
|
||||
* @param query - an object containing job path and debug level.
|
||||
* @param appLoc - an application path.
|
||||
* @param authConfig - an object for authentication.
|
||||
* @returns a promise that resolves into job execution result and log.
|
||||
*/
|
||||
public async executeJob(
|
||||
query: ExecutionQuery,
|
||||
appLoc: string,
|
||||
authConfig?: AuthConfig
|
||||
) {
|
||||
this.isMethodSupported('executeScript', [ServerType.Sasjs])
|
||||
|
||||
return await this.sasJSApiClient?.executeJob(query, appLoc, authConfig)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets compute contexts.
|
||||
* @param accessToken - an access token for an authorised user.
|
||||
@@ -855,7 +875,7 @@ export default class SASjs {
|
||||
* @param pollOptions - an object that represents poll interval(milliseconds) and maximum amount of attempts. Object example: { maxPollCount: 24 * 60 * 60, pollInterval: 1000 }. More information available at src/api/viya/pollJobState.ts.
|
||||
* @param printPid - a boolean that indicates whether the function should print (PID) of the started job.
|
||||
* @param variables - an object that represents macro variables.
|
||||
* @param verboseMode - boolean to enable verbose mode (log every HTTP response).
|
||||
* @param verboseMode - boolean or a string equal to 'bleached' to enable verbose mode (log every HTTP response).
|
||||
*/
|
||||
public async startComputeJob(
|
||||
sasJob: string,
|
||||
@@ -866,7 +886,7 @@ export default class SASjs {
|
||||
pollOptions?: PollOptions,
|
||||
printPid = false,
|
||||
variables?: MacroVar,
|
||||
verboseMode?: boolean
|
||||
verboseMode?: VerboseMode
|
||||
) {
|
||||
config = {
|
||||
...this.sasjsConfig,
|
||||
@@ -880,8 +900,10 @@ export default class SASjs {
|
||||
)
|
||||
}
|
||||
|
||||
if (verboseMode) this.requestClient?.enableVerboseMode()
|
||||
else if (verboseMode === false) this.requestClient?.disableVerboseMode()
|
||||
if (verboseMode) {
|
||||
this.requestClient?.setVerboseMode(verboseMode)
|
||||
this.requestClient?.enableVerboseMode()
|
||||
} else if (verboseMode === false) this.requestClient?.disableVerboseMode()
|
||||
|
||||
return this.sasViyaApiClient?.executeComputeJob(
|
||||
sasJob,
|
||||
@@ -1160,4 +1182,12 @@ export default class SASjs {
|
||||
public disableVerboseMode() {
|
||||
this.requestClient?.disableVerboseMode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets verbose mode.
|
||||
* @param verboseMode - value of the verbose mode, can be true, false or bleached(without extra colors).
|
||||
*/
|
||||
public setVerboseMode = (verboseMode: VerboseMode) => {
|
||||
this.requestClient?.setVerboseMode(verboseMode)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,6 +276,76 @@ describe('pollJobState', () => {
|
||||
expect(delays).toEqual([pollIntervals[0], ...pollIntervals])
|
||||
})
|
||||
|
||||
it('should change default poll strategies after completing provided poll options', async () => {
|
||||
const delays: number[] = []
|
||||
|
||||
jest.spyOn(delayModule, 'delay').mockImplementation((ms: number) => {
|
||||
delays.push(ms)
|
||||
|
||||
return Promise.resolve()
|
||||
})
|
||||
|
||||
const customPollOptions: PollOptions = {
|
||||
maxPollCount: 0,
|
||||
pollInterval: 0
|
||||
}
|
||||
|
||||
const requests = [
|
||||
{ maxPollCount: 202, pollInterval: 300 },
|
||||
{ maxPollCount: 300, pollInterval: 3000 },
|
||||
{ maxPollCount: 500, pollInterval: 30000 },
|
||||
{ maxPollCount: 3400, pollInterval: 60000 }
|
||||
]
|
||||
|
||||
// ~200 requests with delay 300ms
|
||||
let request = requests.splice(0, 1)[0]
|
||||
let { maxPollCount, pollInterval } = request
|
||||
|
||||
// should be only one interval because maxPollCount is equal to 0
|
||||
const pollIntervals = [customPollOptions.pollInterval]
|
||||
|
||||
pollIntervals.push(...Array(maxPollCount - 2).fill(pollInterval))
|
||||
|
||||
// ~300 requests with delay 3000
|
||||
request = requests.splice(0, 1)[0]
|
||||
let newAmount = request.maxPollCount
|
||||
pollInterval = request.pollInterval
|
||||
|
||||
pollIntervals.push(...Array(newAmount - maxPollCount).fill(pollInterval))
|
||||
pollIntervals.push(...Array(2).fill(pollInterval))
|
||||
|
||||
// ~500 requests with delay 30000
|
||||
request = requests.splice(0, 1)[0]
|
||||
|
||||
let oldAmount = newAmount
|
||||
newAmount = request.maxPollCount
|
||||
pollInterval = request.pollInterval
|
||||
|
||||
pollIntervals.push(...Array(newAmount - oldAmount - 2).fill(pollInterval))
|
||||
pollIntervals.push(...Array(2).fill(pollInterval))
|
||||
|
||||
// ~3400 requests with delay 60000
|
||||
request = requests.splice(0, 1)[0]
|
||||
|
||||
oldAmount = newAmount
|
||||
newAmount = request.maxPollCount
|
||||
pollInterval = request.pollInterval
|
||||
|
||||
mockSimplePoll(newAmount)
|
||||
|
||||
pollIntervals.push(...Array(newAmount - oldAmount - 2).fill(pollInterval))
|
||||
|
||||
await pollJobState(
|
||||
requestClient,
|
||||
mockJob,
|
||||
false,
|
||||
undefined,
|
||||
customPollOptions
|
||||
)
|
||||
|
||||
expect(delays).toEqual(pollIntervals)
|
||||
})
|
||||
|
||||
it('should throw an error if not valid poll strategies provided', async () => {
|
||||
// INFO: 'maxPollCount' has to be > 0
|
||||
let invalidPollStrategy = {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
JobExecutionError,
|
||||
CertificateError
|
||||
} from '../types/errors'
|
||||
import { SASjsRequest } from '../types'
|
||||
import { SASjsRequest, HttpClient, VerboseMode } from '../types'
|
||||
import { parseWeboutResponse } from '../utils/parseWeboutResponse'
|
||||
import { prefixMessage } from '@sasjs/utils/error'
|
||||
import { SAS9AuthError } from '../types/errors/SAS9AuthError'
|
||||
@@ -22,45 +22,11 @@ import {
|
||||
import { InvalidSASjsCsrfError } from '../types/errors/InvalidSASjsCsrfError'
|
||||
import { inspect } from 'util'
|
||||
|
||||
export interface HttpClient {
|
||||
get<T>(
|
||||
url: string,
|
||||
accessToken: string | undefined,
|
||||
contentType: string,
|
||||
overrideHeaders: { [key: string]: string | number }
|
||||
): Promise<{ result: T; etag: string }>
|
||||
|
||||
post<T>(
|
||||
url: string,
|
||||
data: any,
|
||||
accessToken: string | undefined,
|
||||
contentType: string,
|
||||
overrideHeaders: { [key: string]: string | number }
|
||||
): Promise<{ result: T; etag: string }>
|
||||
|
||||
put<T>(
|
||||
url: string,
|
||||
data: any,
|
||||
accessToken: string | undefined,
|
||||
overrideHeaders: { [key: string]: string | number }
|
||||
): Promise<{ result: T; etag: string }>
|
||||
|
||||
delete<T>(
|
||||
url: string,
|
||||
accessToken: string | undefined
|
||||
): Promise<{ result: T; etag: string }>
|
||||
|
||||
getCsrfToken(type: 'general' | 'file'): CsrfToken | undefined
|
||||
saveLocalStorageToken(accessToken: string, refreshToken: string): void
|
||||
clearCsrfTokens(): void
|
||||
clearLocalStorageTokens(): void
|
||||
getBaseUrl(): string
|
||||
}
|
||||
|
||||
export class RequestClient implements HttpClient {
|
||||
private requests: SASjsRequest[] = []
|
||||
private requestsLimit: number = 10
|
||||
private httpInterceptor?: number
|
||||
private verboseMode: VerboseMode = false
|
||||
|
||||
protected csrfToken: CsrfToken = { headerName: '', value: '' }
|
||||
protected fileUploadCsrfToken: CsrfToken | undefined
|
||||
@@ -70,13 +36,16 @@ export class RequestClient implements HttpClient {
|
||||
protected baseUrl: string,
|
||||
httpsAgentOptions?: https.AgentOptions,
|
||||
requestsLimit?: number,
|
||||
verboseMode?: boolean
|
||||
verboseMode?: VerboseMode
|
||||
) {
|
||||
this.createHttpClient(baseUrl, httpsAgentOptions)
|
||||
|
||||
if (requestsLimit) this.requestsLimit = requestsLimit
|
||||
|
||||
if (verboseMode) this.enableVerboseMode()
|
||||
if (verboseMode) {
|
||||
this.setVerboseMode(verboseMode)
|
||||
this.enableVerboseMode()
|
||||
}
|
||||
}
|
||||
|
||||
public setConfig(baseUrl: string, httpsAgentOptions?: https.AgentOptions) {
|
||||
@@ -398,10 +367,12 @@ export class RequestClient implements HttpClient {
|
||||
|
||||
/**
|
||||
* Adds colors to the string.
|
||||
* If verboseMode is set to 'bleached', colors should be disabled
|
||||
* @param str - string to be prettified.
|
||||
* @returns - prettified string
|
||||
*/
|
||||
private prettifyString = (str: any) => inspect(str, { colors: true })
|
||||
private prettifyString = (str: any) =>
|
||||
inspect(str, { colors: this.verboseMode !== 'bleached' })
|
||||
|
||||
/**
|
||||
* Formats HTTP request/response body.
|
||||
@@ -471,6 +442,17 @@ ${resHeaders}${parsedResBody ? `\n\n${parsedResBody}` : ''}
|
||||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets verbose mode.
|
||||
* @param verboseMode - value of the verbose mode, can be true, false or bleached(without extra colors).
|
||||
*/
|
||||
public setVerboseMode = (verboseMode: VerboseMode) => {
|
||||
this.verboseMode = verboseMode
|
||||
|
||||
if (this.verboseMode) this.enableVerboseMode()
|
||||
else this.disableVerboseMode()
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on verbose mode to log every HTTP response.
|
||||
* @param successCallBack - function that should be triggered on every HTTP response with the status 2**.
|
||||
|
||||
@@ -1,19 +1,11 @@
|
||||
import { RequestClient } from './RequestClient'
|
||||
import { AxiosResponse } from 'axios'
|
||||
|
||||
export interface SasjsParsedResponse<T> {
|
||||
result: T
|
||||
log: string
|
||||
etag: string
|
||||
status: number
|
||||
printOutput?: string
|
||||
}
|
||||
import { SasjsParsedResponse } from '../types'
|
||||
|
||||
/**
|
||||
* Specific request client for SASJS.
|
||||
* Append tokens in headers.
|
||||
*/
|
||||
|
||||
export class SasjsRequestClient extends RequestClient {
|
||||
getHeaders = (accessToken: string | undefined, contentType: string) => {
|
||||
const headers: any = {}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import {
|
||||
SASJS_LOGS_SEPARATOR,
|
||||
SasjsRequestClient,
|
||||
SasjsParsedResponse
|
||||
} from '../SasjsRequestClient'
|
||||
import { SASJS_LOGS_SEPARATOR, SasjsRequestClient } from '../SasjsRequestClient'
|
||||
import { SasjsParsedResponse } from '../../types'
|
||||
import { AxiosResponse } from 'axios'
|
||||
|
||||
describe('SasjsRequestClient', () => {
|
||||
|
||||
@@ -9,12 +9,14 @@ import {
|
||||
LoginRequiredError,
|
||||
AuthorizeError,
|
||||
NotFoundError,
|
||||
InternalServerError
|
||||
} from '../types/errors'
|
||||
InternalServerError,
|
||||
VerboseMode
|
||||
} from '../types'
|
||||
import { RequestClient } from '../request/RequestClient'
|
||||
import { getTokenRequestErrorPrefixResponse } from '../auth/getTokenRequestErrorPrefix'
|
||||
import { AxiosResponse } from 'axios'
|
||||
import { Logger, LogLevel } from '@sasjs/utils/logger'
|
||||
import * as UtilsModule from 'util'
|
||||
|
||||
const axiosActual = jest.requireActual('axios')
|
||||
|
||||
@@ -235,6 +237,60 @@ ${resHeaders[0]}: ${resHeaders[1]}${
|
||||
})
|
||||
})
|
||||
|
||||
describe('setVerboseMode', () => {
|
||||
it(`should set verbose mode`, () => {
|
||||
const requestClient = new RequestClient('')
|
||||
let verbose: VerboseMode = false
|
||||
requestClient.setVerboseMode(verbose)
|
||||
|
||||
expect(requestClient['verboseMode']).toEqual(verbose)
|
||||
|
||||
verbose = true
|
||||
requestClient.setVerboseMode(verbose)
|
||||
|
||||
expect(requestClient['verboseMode']).toEqual(verbose)
|
||||
|
||||
verbose = 'bleached'
|
||||
requestClient.setVerboseMode(verbose)
|
||||
|
||||
expect(requestClient['verboseMode']).toEqual(verbose)
|
||||
})
|
||||
})
|
||||
|
||||
describe('prettifyString', () => {
|
||||
it(`should call inspect without colors when verbose mode is set to 'bleached'`, () => {
|
||||
const requestClient = new RequestClient('')
|
||||
let verbose: VerboseMode = 'bleached'
|
||||
requestClient.setVerboseMode(verbose)
|
||||
|
||||
jest.spyOn(UtilsModule, 'inspect')
|
||||
|
||||
const testStr = JSON.stringify({ test: 'test' })
|
||||
|
||||
requestClient['prettifyString'](testStr)
|
||||
|
||||
expect(UtilsModule.inspect).toHaveBeenCalledWith(testStr, {
|
||||
colors: false
|
||||
})
|
||||
})
|
||||
|
||||
it(`should call inspect with colors when verbose mode is set to 'true'`, () => {
|
||||
const requestClient = new RequestClient('')
|
||||
let verbose: VerboseMode = true
|
||||
requestClient.setVerboseMode(verbose)
|
||||
|
||||
jest.spyOn(UtilsModule, 'inspect')
|
||||
|
||||
const testStr = JSON.stringify({ test: 'test' })
|
||||
|
||||
requestClient['prettifyString'](testStr)
|
||||
|
||||
expect(UtilsModule.inspect).toHaveBeenCalledWith(testStr, {
|
||||
colors: true
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('disableVerboseMode', () => {
|
||||
it('should eject interceptor', () => {
|
||||
const requestClient = new RequestClient('')
|
||||
|
||||
55
src/types/RequestClient.ts
Normal file
55
src/types/RequestClient.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { CsrfToken } from '..'
|
||||
|
||||
export interface HttpClient {
|
||||
get<T>(
|
||||
url: string,
|
||||
accessToken: string | undefined,
|
||||
contentType: string,
|
||||
overrideHeaders: { [key: string]: string | number }
|
||||
): Promise<{ result: T; etag: string }>
|
||||
|
||||
post<T>(
|
||||
url: string,
|
||||
data: any,
|
||||
accessToken: string | undefined,
|
||||
contentType: string,
|
||||
overrideHeaders: { [key: string]: string | number }
|
||||
): Promise<{ result: T; etag: string }>
|
||||
|
||||
put<T>(
|
||||
url: string,
|
||||
data: any,
|
||||
accessToken: string | undefined,
|
||||
overrideHeaders: { [key: string]: string | number }
|
||||
): Promise<{ result: T; etag: string }>
|
||||
|
||||
delete<T>(
|
||||
url: string,
|
||||
accessToken: string | undefined
|
||||
): Promise<{ result: T; etag: string }>
|
||||
|
||||
getCsrfToken(type: 'general' | 'file'): CsrfToken | undefined
|
||||
saveLocalStorageToken(accessToken: string, refreshToken: string): void
|
||||
clearCsrfTokens(): void
|
||||
clearLocalStorageTokens(): void
|
||||
getBaseUrl(): string
|
||||
}
|
||||
|
||||
export interface SASjsRequest {
|
||||
serviceLink: string
|
||||
timestamp: Date
|
||||
sourceCode: string
|
||||
generatedCode: string
|
||||
logFile: string
|
||||
SASWORK: any
|
||||
}
|
||||
|
||||
export interface SasjsParsedResponse<T> {
|
||||
result: T
|
||||
log: string
|
||||
etag: string
|
||||
status: number
|
||||
printOutput?: string
|
||||
}
|
||||
|
||||
export type VerboseMode = boolean | 'bleached'
|
||||
@@ -1,5 +1,6 @@
|
||||
import * as https from 'https'
|
||||
import { ServerType } from '@sasjs/utils/types'
|
||||
import { VerboseMode } from '../types'
|
||||
|
||||
/**
|
||||
* Specifies the configuration for the SASjs instance - eg where and how to
|
||||
@@ -48,7 +49,7 @@ export class SASjsConfig {
|
||||
/**
|
||||
* Set to `true` to enable verbose mode that will log a summary of every HTTP response.
|
||||
*/
|
||||
verbose?: boolean = true
|
||||
verbose?: VerboseMode = true
|
||||
/**
|
||||
* The name of the compute context to use when calling the Viya services directly.
|
||||
* Example value: 'SAS Job Execution compute context'
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
/**
|
||||
* Represents a SASjs request, its response and logs.
|
||||
*
|
||||
*/
|
||||
export interface SASjsRequest {
|
||||
serviceLink: string
|
||||
timestamp: Date
|
||||
sourceCode: string
|
||||
generatedCode: string
|
||||
logFile: string
|
||||
SASWORK: any
|
||||
}
|
||||
@@ -6,10 +6,12 @@ export * from './Job'
|
||||
export * from './JobDefinition'
|
||||
export * from './JobResult'
|
||||
export * from './Link'
|
||||
export * from './Login'
|
||||
export * from './SASjsConfig'
|
||||
export * from './SASjsRequest'
|
||||
export * from './RequestClient'
|
||||
export * from './Session'
|
||||
export * from './UploadFile'
|
||||
export * from './PollOptions'
|
||||
export * from './WriteStream'
|
||||
export * from './ExecuteScript'
|
||||
export * from './errors'
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SASjsRequest } from '../types/SASjsRequest'
|
||||
import { SASjsRequest } from '../types'
|
||||
|
||||
/**
|
||||
* Comparator for SASjs request timestamps.
|
||||
|
||||
Reference in New Issue
Block a user