mirror of
https://github.com/sasjs/server.git
synced 2025-12-11 03:34:35 +00:00
feat: mocking sas9 responses with JS STP
This commit is contained in:
@@ -103,8 +103,9 @@ PORT=
|
|||||||
# If not present, mocking function is disabled
|
# If not present, mocking function is disabled
|
||||||
MOCK_SERVERTYPE=
|
MOCK_SERVERTYPE=
|
||||||
|
|
||||||
# Path to mocking folder, it's sub directories should be: sas9, viya, sasjs
|
# default: /api/mocks
|
||||||
# Server will automatically use sub directory accordingly
|
# Path to mocking folder, for generic responses, it's sub directories should be: sas9, viya, sasjs
|
||||||
|
# Server will automatically use subdirectory accordingly
|
||||||
STATIC_MOCK_LOCATION=
|
STATIC_MOCK_LOCATION=
|
||||||
|
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ interface ExecuteFileParams {
|
|||||||
returnJson?: boolean
|
returnJson?: boolean
|
||||||
session?: Session
|
session?: Session
|
||||||
runTime: RunTimeType
|
runTime: RunTimeType
|
||||||
|
forceStringResult?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ExecuteProgramParams extends Omit<ExecuteFileParams, 'programPath'> {
|
interface ExecuteProgramParams extends Omit<ExecuteFileParams, 'programPath'> {
|
||||||
@@ -42,7 +43,8 @@ export class ExecutionController {
|
|||||||
otherArgs,
|
otherArgs,
|
||||||
returnJson,
|
returnJson,
|
||||||
session,
|
session,
|
||||||
runTime
|
runTime,
|
||||||
|
forceStringResult
|
||||||
}: ExecuteFileParams) {
|
}: ExecuteFileParams) {
|
||||||
const program = await readFile(programPath)
|
const program = await readFile(programPath)
|
||||||
|
|
||||||
@@ -53,7 +55,8 @@ export class ExecutionController {
|
|||||||
otherArgs,
|
otherArgs,
|
||||||
returnJson,
|
returnJson,
|
||||||
session,
|
session,
|
||||||
runTime
|
runTime,
|
||||||
|
forceStringResult
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +66,8 @@ export class ExecutionController {
|
|||||||
vars,
|
vars,
|
||||||
otherArgs,
|
otherArgs,
|
||||||
session: sessionByFileUpload,
|
session: sessionByFileUpload,
|
||||||
runTime
|
runTime,
|
||||||
|
forceStringResult
|
||||||
}: ExecuteProgramParams): Promise<ExecuteReturnRaw> {
|
}: ExecuteProgramParams): Promise<ExecuteReturnRaw> {
|
||||||
const sessionController = getSessionController(runTime)
|
const sessionController = getSessionController(runTime)
|
||||||
|
|
||||||
@@ -104,7 +108,7 @@ export class ExecutionController {
|
|||||||
const fileResponse: boolean = httpHeaders.hasOwnProperty('content-type')
|
const fileResponse: boolean = httpHeaders.hasOwnProperty('content-type')
|
||||||
|
|
||||||
const webout = (await fileExists(weboutPath))
|
const webout = (await fileExists(weboutPath))
|
||||||
? fileResponse
|
? fileResponse && !forceStringResult
|
||||||
? await readFileBinary(weboutPath)
|
? await readFileBinary(weboutPath)
|
||||||
: await readFile(weboutPath)
|
: await readFile(weboutPath)
|
||||||
: ''
|
: ''
|
||||||
|
|||||||
@@ -110,17 +110,13 @@ export const processProgram = async (
|
|||||||
|
|
||||||
// create a stream that will write to console outputs to log file
|
// create a stream that will write to console outputs to log file
|
||||||
const writeStream = fs.createWriteStream(logPath)
|
const writeStream = fs.createWriteStream(logPath)
|
||||||
|
|
||||||
// waiting for the open event so that we can have underlying file descriptor
|
// waiting for the open event so that we can have underlying file descriptor
|
||||||
await once(writeStream, 'open')
|
await once(writeStream, 'open')
|
||||||
|
|
||||||
execFileSync(executablePath, [codePath], {
|
execFileSync(executablePath, [codePath], {
|
||||||
stdio: ['ignore', writeStream, writeStream]
|
stdio: ['ignore', writeStream, writeStream]
|
||||||
})
|
})
|
||||||
|
|
||||||
// copy the code file to log and end write stream
|
// copy the code file to log and end write stream
|
||||||
writeStream.end(program)
|
writeStream.end(program)
|
||||||
|
|
||||||
session.completed = true
|
session.completed = true
|
||||||
console.log('session completed', session)
|
console.log('session completed', session)
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
|
|||||||
@@ -2,9 +2,14 @@ import { readFile } from '@sasjs/utils'
|
|||||||
import express from 'express'
|
import express from 'express'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { Request, Post, Get } from 'tsoa'
|
import { Request, Post, Get } from 'tsoa'
|
||||||
import fs from 'fs'
|
|
||||||
import fse from 'fs-extra'
|
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
|
import { ExecutionController } from './internal'
|
||||||
|
import {
|
||||||
|
getPreProgramVariables,
|
||||||
|
getRunTimeAndFilePath,
|
||||||
|
makeFilesNamesMap
|
||||||
|
} from '../utils'
|
||||||
|
import { MulterFile } from '../types/Upload'
|
||||||
|
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
|
|
||||||
@@ -76,17 +81,37 @@ export class MockSas9Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let program = req.query._program?.toString() || undefined
|
const program = req.query._program ?? req.body?._program
|
||||||
let filePath: string[] = ['generic', 'sas-stored-process']
|
let filePath: string[] = ['generic', 'sas-stored-process']
|
||||||
|
|
||||||
if (program) {
|
if (program) {
|
||||||
filePath = `${program}`.replace('/', '').split('/')
|
const vars = { ...req.query, ...req.body, _requestMethod: req.method }
|
||||||
return await getMockResponseFromFile([
|
const otherArgs = {}
|
||||||
process.cwd(),
|
|
||||||
this.mocksPath,
|
try {
|
||||||
'sas9',
|
const { codePath, runTime } = await getRunTimeAndFilePath(
|
||||||
...filePath
|
program + '.js'
|
||||||
])
|
)
|
||||||
|
|
||||||
|
const result = await new ExecutionController().executeFile({
|
||||||
|
programPath: codePath,
|
||||||
|
preProgramVariables: getPreProgramVariables(req),
|
||||||
|
vars: vars,
|
||||||
|
otherArgs: otherArgs,
|
||||||
|
runTime,
|
||||||
|
forceStringResult: true
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: result.result as string
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log('err', err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: 'No webout returned.'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await getMockResponseFromFile([
|
return await getMockResponseFromFile([
|
||||||
@@ -115,66 +140,38 @@ export class MockSas9Controller {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let program = req.query._program?.toString() || ''
|
const program = req.query._program ?? req.body?._program
|
||||||
program = program.replace('/', '')
|
const vars = {
|
||||||
let debug = req.query._debug?.toString()
|
...req.query,
|
||||||
|
...req.body,
|
||||||
|
_requestMethod: req.method,
|
||||||
|
_driveLoc: process.driveLoc
|
||||||
|
}
|
||||||
|
const filesNamesMap = req.files?.length
|
||||||
|
? makeFilesNamesMap(req.files as MulterFile[])
|
||||||
|
: null
|
||||||
|
const otherArgs = { filesNamesMap: filesNamesMap }
|
||||||
|
const { codePath, runTime } = await getRunTimeAndFilePath(program + '.js')
|
||||||
|
try {
|
||||||
|
const result = await new ExecutionController().executeFile({
|
||||||
|
programPath: codePath,
|
||||||
|
preProgramVariables: getPreProgramVariables(req),
|
||||||
|
vars: vars,
|
||||||
|
otherArgs: otherArgs,
|
||||||
|
runTime,
|
||||||
|
session: req.sasjsSession,
|
||||||
|
forceStringResult: true
|
||||||
|
})
|
||||||
|
|
||||||
let fileContents = ''
|
return {
|
||||||
|
content: result.result as string
|
||||||
if (program.includes('runner') && debug === 'log') {
|
|
||||||
if (req.files && req.files.length > 0) {
|
|
||||||
const regexRequest = /cli-tests-request-sas9-.*?\d*/g
|
|
||||||
const uploadFilePath = (req.files as any)[0].path
|
|
||||||
|
|
||||||
fileContents = fs.readFileSync(uploadFilePath, 'utf8')
|
|
||||||
|
|
||||||
let matched = fileContents.match(regexRequest)?.[0]
|
|
||||||
|
|
||||||
if (matched) {
|
|
||||||
const testsFolderPath = path.join(
|
|
||||||
process.cwd(),
|
|
||||||
this.mocksPath,
|
|
||||||
'sas9',
|
|
||||||
'User Folders',
|
|
||||||
'cli-tests',
|
|
||||||
'sasdemo',
|
|
||||||
matched
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!fs.existsSync(testsFolderPath)) fs.mkdirSync(testsFolderPath)
|
|
||||||
|
|
||||||
fse.copySync(
|
|
||||||
path.join(
|
|
||||||
process.cwd(),
|
|
||||||
this.mocksPath,
|
|
||||||
'sas9',
|
|
||||||
'User Folders',
|
|
||||||
'sasdemo',
|
|
||||||
'services'
|
|
||||||
),
|
|
||||||
path.join(testsFolderPath, 'services')
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log('err', err)
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = await getMockResponseFromFile([
|
|
||||||
process.cwd(),
|
|
||||||
this.mocksPath,
|
|
||||||
'sas9',
|
|
||||||
...program.split('/')
|
|
||||||
])
|
|
||||||
|
|
||||||
content.content += fileContents
|
|
||||||
|
|
||||||
if (content.error) {
|
|
||||||
return content
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedContent = parseJsonIfValid(content.content)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
content: parsedContent
|
content: 'No webout returned.'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -263,23 +260,6 @@ export class MockSas9Controller {
|
|||||||
private isPublicAccount = () => this.loggedIn?.toLowerCase() === 'public'
|
private isPublicAccount = () => this.loggedIn?.toLowerCase() === 'public'
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* If JSON is valid it will be parsed otherwise will return text unaltered
|
|
||||||
* @param content string to be parsed
|
|
||||||
* @returns JSON or string
|
|
||||||
*/
|
|
||||||
const parseJsonIfValid = (content: string) => {
|
|
||||||
let fileContent = ''
|
|
||||||
|
|
||||||
try {
|
|
||||||
fileContent = JSON.parse(content)
|
|
||||||
} catch (err: any) {
|
|
||||||
fileContent = content
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileContent
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMockResponseFromFile = async (
|
const getMockResponseFromFile = async (
|
||||||
filePath: string[]
|
filePath: string[]
|
||||||
): Promise<MockFileRead> => {
|
): Promise<MockFileRead> => {
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import express from 'express'
|
|||||||
import { generateCSRFToken } from '../../middlewares'
|
import { generateCSRFToken } from '../../middlewares'
|
||||||
import { WebController } from '../../controllers'
|
import { WebController } from '../../controllers'
|
||||||
import { MockSas9Controller } from '../../controllers/mock-sas9'
|
import { MockSas9Controller } from '../../controllers/mock-sas9'
|
||||||
import fs from 'fs'
|
|
||||||
import multer from 'multer'
|
import multer from 'multer'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import dotenv from 'dotenv'
|
import dotenv from 'dotenv'
|
||||||
|
import { FileUploadController } from '../../controllers/internal'
|
||||||
|
|
||||||
dotenv.config()
|
dotenv.config()
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ const webController = new WebController()
|
|||||||
// Mock controller must be singleton because it keeps the states
|
// Mock controller must be singleton because it keeps the states
|
||||||
// for example `isLoggedIn` and potentially more in future mocks
|
// for example `isLoggedIn` and potentially more in future mocks
|
||||||
const controller = new MockSas9Controller()
|
const controller = new MockSas9Controller()
|
||||||
|
const fileUploadController = new FileUploadController()
|
||||||
|
|
||||||
const mockPath = process.env.STATIC_MOCK_LOCATION || 'mocks'
|
const mockPath = process.env.STATIC_MOCK_LOCATION || 'mocks'
|
||||||
|
|
||||||
@@ -68,26 +69,25 @@ sas9WebRouter.get('/SASStoredProcess/do/', async (req, res) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
sas9WebRouter.post('/SASStoredProcess/do/', upload.any(), async (req, res) => {
|
sas9WebRouter.post(
|
||||||
const response = await controller.sasStoredProcessDoPost(req)
|
'/SASStoredProcess/do/',
|
||||||
|
fileUploadController.preUploadMiddleware,
|
||||||
|
fileUploadController.getMulterUploadObject().any(),
|
||||||
|
async (req, res) => {
|
||||||
|
const response = await controller.sasStoredProcessDoPost(req)
|
||||||
|
|
||||||
if (req.files) {
|
if (response.redirect) {
|
||||||
;(req.files as any).forEach((file: any) => {
|
res.redirect(response.redirect)
|
||||||
fs.renameSync(file.path, file.destination + '/' + file.fieldname)
|
return
|
||||||
})
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (response.redirect) {
|
try {
|
||||||
res.redirect(response.redirect)
|
res.send(response.content)
|
||||||
return
|
} catch (err: any) {
|
||||||
|
res.status(403).send(err.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
)
|
||||||
try {
|
|
||||||
res.send(response.content)
|
|
||||||
} catch (err: any) {
|
|
||||||
res.status(403).send(err.toString())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
sas9WebRouter.get('/SASLogon/login', async (req, res) => {
|
sas9WebRouter.get('/SASLogon/login', async (req, res) => {
|
||||||
const response = await controller.loginGet()
|
const response = await controller.loginGet()
|
||||||
|
|||||||
@@ -18,10 +18,12 @@ export const getPreProgramVariables = (req: Request): PreProgramVars => {
|
|||||||
|
|
||||||
if (cookies.length) httpHeaders.push(`cookie: ${cookies.join('; ')}`)
|
if (cookies.length) httpHeaders.push(`cookie: ${cookies.join('; ')}`)
|
||||||
|
|
||||||
|
//In desktop mode when mocking mode is enabled, user was undefined.
|
||||||
|
//So this is workaround.
|
||||||
return {
|
return {
|
||||||
username: user!.username,
|
username: user ? user.username : 'demo',
|
||||||
userId: user!.userId,
|
userId: user ? user.userId : 0,
|
||||||
displayName: user!.displayName,
|
displayName: user ? user.displayName : 'demo',
|
||||||
serverUrl: protocol + host,
|
serverUrl: protocol + host,
|
||||||
httpHeaders
|
httpHeaders
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ export const setProcessVariables = async () => {
|
|||||||
process.rLoc = process.env.R_PATH
|
process.rLoc = process.env.R_PATH
|
||||||
} else {
|
} else {
|
||||||
const { sasLoc, nodeLoc, pythonLoc, rLoc } = await getDesktopFields()
|
const { sasLoc, nodeLoc, pythonLoc, rLoc } = await getDesktopFields()
|
||||||
|
|
||||||
process.sasLoc = sasLoc
|
process.sasLoc = sasLoc
|
||||||
process.nodeLoc = nodeLoc
|
process.nodeLoc = nodeLoc
|
||||||
process.pythonLoc = pythonLoc
|
process.pythonLoc = pythonLoc
|
||||||
|
|||||||
Reference in New Issue
Block a user