diff --git a/README.md b/README.md index 174bd05..4a1d1a6 100644 --- a/README.md +++ b/README.md @@ -103,8 +103,9 @@ PORT= # If not present, mocking function is disabled MOCK_SERVERTYPE= -# Path to mocking folder, it's sub directories should be: sas9, viya, sasjs -# Server will automatically use sub directory accordingly +# default: /api/mocks +# 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= # diff --git a/api/src/controllers/internal/Execution.ts b/api/src/controllers/internal/Execution.ts index e96e7c4..b7df0ab 100644 --- a/api/src/controllers/internal/Execution.ts +++ b/api/src/controllers/internal/Execution.ts @@ -28,6 +28,7 @@ interface ExecuteFileParams { returnJson?: boolean session?: Session runTime: RunTimeType + forceStringResult?: boolean } interface ExecuteProgramParams extends Omit { @@ -42,7 +43,8 @@ export class ExecutionController { otherArgs, returnJson, session, - runTime + runTime, + forceStringResult }: ExecuteFileParams) { const program = await readFile(programPath) @@ -53,7 +55,8 @@ export class ExecutionController { otherArgs, returnJson, session, - runTime + runTime, + forceStringResult }) } @@ -63,7 +66,8 @@ export class ExecutionController { vars, otherArgs, session: sessionByFileUpload, - runTime + runTime, + forceStringResult }: ExecuteProgramParams): Promise { const sessionController = getSessionController(runTime) @@ -104,7 +108,7 @@ export class ExecutionController { const fileResponse: boolean = httpHeaders.hasOwnProperty('content-type') const webout = (await fileExists(weboutPath)) - ? fileResponse + ? fileResponse && !forceStringResult ? await readFileBinary(weboutPath) : await readFile(weboutPath) : '' diff --git a/api/src/controllers/internal/processProgram.ts b/api/src/controllers/internal/processProgram.ts index ca4a9d4..ff52c9d 100644 --- a/api/src/controllers/internal/processProgram.ts +++ b/api/src/controllers/internal/processProgram.ts @@ -110,17 +110,13 @@ export const processProgram = async ( // create a stream that will write to console outputs to log file const writeStream = fs.createWriteStream(logPath) - // waiting for the open event so that we can have underlying file descriptor await once(writeStream, 'open') - execFileSync(executablePath, [codePath], { stdio: ['ignore', writeStream, writeStream] }) - // copy the code file to log and end write stream writeStream.end(program) - session.completed = true console.log('session completed', session) } catch (err: any) { diff --git a/api/src/controllers/mock-sas9.ts b/api/src/controllers/mock-sas9.ts index 166daff..cff63c1 100644 --- a/api/src/controllers/mock-sas9.ts +++ b/api/src/controllers/mock-sas9.ts @@ -2,9 +2,14 @@ import { readFile } from '@sasjs/utils' import express from 'express' import path from 'path' import { Request, Post, Get } from 'tsoa' -import fs from 'fs' -import fse from 'fs-extra' import dotenv from 'dotenv' +import { ExecutionController } from './internal' +import { + getPreProgramVariables, + getRunTimeAndFilePath, + makeFilesNamesMap +} from '../utils' +import { MulterFile } from '../types/Upload' 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'] if (program) { - filePath = `${program}`.replace('/', '').split('/') - return await getMockResponseFromFile([ - process.cwd(), - this.mocksPath, - 'sas9', - ...filePath - ]) + const vars = { ...req.query, ...req.body, _requestMethod: req.method } + const otherArgs = {} + + try { + const { codePath, runTime } = await getRunTimeAndFilePath( + 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([ @@ -115,66 +140,38 @@ export class MockSas9Controller { } } - let program = req.query._program?.toString() || '' - program = program.replace('/', '') - let debug = req.query._debug?.toString() + const program = req.query._program ?? req.body?._program + const vars = { + ...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 = '' - - 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') - ) - } + return { + content: result.result as string } + } 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 { - content: parsedContent + content: 'No webout returned.' } } @@ -263,23 +260,6 @@ export class MockSas9Controller { 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 ( filePath: string[] ): Promise => { diff --git a/api/src/routes/web/sas9-web.ts b/api/src/routes/web/sas9-web.ts index 797cedc..7294a94 100644 --- a/api/src/routes/web/sas9-web.ts +++ b/api/src/routes/web/sas9-web.ts @@ -2,10 +2,10 @@ import express from 'express' import { generateCSRFToken } from '../../middlewares' import { WebController } from '../../controllers' import { MockSas9Controller } from '../../controllers/mock-sas9' -import fs from 'fs' import multer from 'multer' import path from 'path' import dotenv from 'dotenv' +import { FileUploadController } from '../../controllers/internal' dotenv.config() @@ -14,6 +14,7 @@ const webController = new WebController() // Mock controller must be singleton because it keeps the states // for example `isLoggedIn` and potentially more in future mocks const controller = new MockSas9Controller() +const fileUploadController = new FileUploadController() 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) => { - const response = await controller.sasStoredProcessDoPost(req) +sas9WebRouter.post( + '/SASStoredProcess/do/', + fileUploadController.preUploadMiddleware, + fileUploadController.getMulterUploadObject().any(), + async (req, res) => { + const response = await controller.sasStoredProcessDoPost(req) - if (req.files) { - ;(req.files as any).forEach((file: any) => { - fs.renameSync(file.path, file.destination + '/' + file.fieldname) - }) - } + if (response.redirect) { + res.redirect(response.redirect) + return + } - if (response.redirect) { - res.redirect(response.redirect) - return + try { + res.send(response.content) + } 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) => { const response = await controller.loginGet() diff --git a/api/src/utils/getPreProgramVariables.ts b/api/src/utils/getPreProgramVariables.ts index 396fa9f..5e2d26a 100644 --- a/api/src/utils/getPreProgramVariables.ts +++ b/api/src/utils/getPreProgramVariables.ts @@ -18,10 +18,12 @@ export const getPreProgramVariables = (req: Request): PreProgramVars => { if (cookies.length) httpHeaders.push(`cookie: ${cookies.join('; ')}`) + //In desktop mode when mocking mode is enabled, user was undefined. + //So this is workaround. return { - username: user!.username, - userId: user!.userId, - displayName: user!.displayName, + username: user ? user.username : 'demo', + userId: user ? user.userId : 0, + displayName: user ? user.displayName : 'demo', serverUrl: protocol + host, httpHeaders } diff --git a/api/src/utils/setProcessVariables.ts b/api/src/utils/setProcessVariables.ts index 395e5fc..574508b 100644 --- a/api/src/utils/setProcessVariables.ts +++ b/api/src/utils/setProcessVariables.ts @@ -32,7 +32,6 @@ export const setProcessVariables = async () => { process.rLoc = process.env.R_PATH } else { const { sasLoc, nodeLoc, pythonLoc, rLoc } = await getDesktopFields() - process.sasLoc = sasLoc process.nodeLoc = nodeLoc process.pythonLoc = pythonLoc