1
0
mirror of https://github.com/sasjs/server.git synced 2025-12-10 19:34:34 +00:00

Merge pull request #264 from sasjs/mocker

Mocker
This commit is contained in:
Allan Bowe
2022-09-05 13:16:10 +01:00
committed by GitHub
13 changed files with 2200 additions and 1972 deletions

2
.gitignore vendored
View File

@@ -5,6 +5,8 @@ node_modules/
.env*
sas/
sasjs_root/
api/mocks/custom/*
!api/mocks/custom/.keep
tmp/
build/
sasjsbuild/

View File

@@ -96,6 +96,9 @@ PROTOCOL=
# default: 5000
PORT=
# options: [sas9|sasviya]
# If not present, mocking function is disabled
MOCK_SERVERTYPE=
#
## Additional SAS Options

0
api/mocks/custom/.keep Normal file
View File

View File

@@ -0,0 +1 @@
You have signed in.

View File

@@ -0,0 +1 @@
You have signed out.

View File

@@ -0,0 +1,30 @@
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" dir="ltr" class="bg">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="initial-scale=1" />
</head>
<div class="content">
<form id="credentials" class="minimal" action="/SASLogon/login?service=http%3A%2F%2Flocalhost:5004%2FSASStoredProcess%2Fj_spring_cas_security_check" method="post">
<!--form container-->
<input type="hidden" name="lt" value="LT-8-WGkt9EXwICBihaVbxGc92opjufTK1D" aria-hidden="true" />
<input type="hidden" name="execution" value="e2s1" aria-hidden="true" />
<input type="hidden" name="_eventId" value="submit" aria-hidden="true" />
<span class="userid">
<input id="username" name="username" tabindex="3" aria-labelledby="username1 message1 message2 message3" name="username" placeholder="User ID" type="text" autofocus="true" value="" maxlength="500" autocomplete="off" />
</span>
<span class="password">
<input id="password" name="password" tabindex="4" name="password" placeholder="Password" type="password" value="" maxlength="500" autocomplete="off" />
</span>
<button type="submit" class="btn-submit" title="Sign In" tabindex="5" onClick="this.disabled=true;setSubmitUrl(this.form);this.form.submit();return false;">Sign In</button>
</form>
</div>
</html>

View File

@@ -0,0 +1 @@
"title": "Log Off SAS Demo User"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,143 @@
import { readFile } from '@sasjs/utils'
import express from 'express'
import path from 'path'
import { Request, Post, Get } from 'tsoa'
export interface Sas9Response {
content: string
redirect?: string
error?: boolean
}
export interface MockFileRead {
content: string
error?: boolean
}
export class MockSas9Controller {
private loggedIn: boolean = false
@Get('/SASStoredProcess')
public async sasStoredProcess(): Promise<Sas9Response> {
if (!this.loggedIn) {
return {
content: '',
redirect: '/SASLogon/login'
}
}
return await getMockResponseFromFile([
process.cwd(),
'mocks',
'generic',
'sas9',
'sas-stored-process'
])
}
@Post('/SASStoredProcess/do/')
public async sasStoredProcessDo(
@Request() req: express.Request
): Promise<Sas9Response> {
if (!this.loggedIn) {
return {
content: '',
redirect: '/SASLogon/login'
}
}
let program = req.query._program?.toString() || ''
program = program.replace('/', '')
const content = await getMockResponseFromFile([
process.cwd(),
'mocks',
...program.split('/')
])
if (content.error) {
return content
}
const parsedContent = parseJsonIfValid(content.content)
return {
content: parsedContent
}
}
@Get('/SASLogon/login')
public async loginGet(): Promise<Sas9Response> {
return await getMockResponseFromFile([
process.cwd(),
'mocks',
'generic',
'sas9',
'login'
])
}
@Post('/SASLogon/login')
public async loginPost(): Promise<Sas9Response> {
this.loggedIn = true
return await getMockResponseFromFile([
process.cwd(),
'mocks',
'generic',
'sas9',
'logged-in'
])
}
@Get('/SASLogon/logout')
public async logout(): Promise<Sas9Response> {
this.loggedIn = false
return await getMockResponseFromFile([
process.cwd(),
'mocks',
'generic',
'sas9',
'logged-out'
])
}
}
/**
* 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<MockFileRead> => {
const filePathParsed = path.join(...filePath)
let error: boolean = false
let file = await readFile(filePathParsed).catch((err: any) => {
const errMsg = `Error reading mocked file on path: ${filePathParsed}\nError: ${err}`
console.error(errMsg)
error = true
return errMsg
})
return {
content: file,
error: error
}
}

View File

@@ -1,8 +1,25 @@
import express from 'express'
import sas9WebRouter from './sas9-web'
import sasViyaWebRouter from './sasviya-web'
import webRouter from './web'
import { MOCK_SERVERTYPEType } from '../../utils'
const router = express.Router()
router.use('/', webRouter)
const { MOCK_SERVERTYPE } = process.env
switch (MOCK_SERVERTYPE) {
case MOCK_SERVERTYPEType.SAS9: {
router.use('/', sas9WebRouter)
break
}
case MOCK_SERVERTYPEType.SASVIYA: {
router.use('/', sasViyaWebRouter)
break
}
default: {
router.use('/', webRouter)
}
}
export default router

View File

@@ -0,0 +1,88 @@
import express from 'express'
import { WebController } from '../../controllers'
import { MockSas9Controller } from '../../controllers/mock-sas9'
const sas9WebRouter = express.Router()
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()
sas9WebRouter.get('/', async (req, res) => {
let response
try {
response = await webController.home()
} catch (_) {
response = '<html><head></head><body>Web Build is not present</body></html>'
} finally {
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${req.csrfToken()}; Max-Age=86400; SameSite=Strict; Path=/;'</script>`
const injectedContent = response?.replace(
'</head>',
`${codeToInject}</head>`
)
return res.send(injectedContent)
}
})
sas9WebRouter.get('/SASStoredProcess', async (req, res) => {
const response = await controller.sasStoredProcess()
if (response.redirect) {
res.redirect(response.redirect)
return
}
try {
res.send(response.content)
} catch (err: any) {
res.status(403).send(err.toString())
}
})
sas9WebRouter.post('/SASStoredProcess/do/', async (req, res) => {
const response = await controller.sasStoredProcessDo(req)
if (response.redirect) {
res.redirect(response.redirect)
return
}
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()
try {
res.send(response.content)
} catch (err: any) {
res.status(403).send(err.toString())
}
})
sas9WebRouter.post('/SASLogon/login', async (req, res) => {
const response = await controller.loginPost()
try {
res.send(response.content)
} catch (err: any) {
res.status(403).send(err.toString())
}
})
sas9WebRouter.get('/SASLogon/logout', async (req, res) => {
const response = await controller.logout()
try {
res.send(response.content)
} catch (err: any) {
res.status(403).send(err.toString())
}
})
export default sas9WebRouter

View File

@@ -0,0 +1,32 @@
import express from 'express'
import { WebController } from '../../controllers/web'
const sasViyaWebRouter = express.Router()
const controller = new WebController()
sasViyaWebRouter.get('/', async (req, res) => {
let response
try {
response = await controller.home()
} catch (_) {
response = '<html><head></head><body>Web Build is not present</body></html>'
} finally {
const codeToInject = `<script>document.cookie = 'XSRF-TOKEN=${req.csrfToken()}; Max-Age=86400; SameSite=Strict; Path=/;'</script>`
const injectedContent = response?.replace(
'</head>',
`${codeToInject}</head>`
)
return res.send(injectedContent)
}
})
sasViyaWebRouter.post('/SASJobExecution/', async (req, res) => {
try {
res.send({ test: 'test' })
} catch (err: any) {
res.status(403).send(err.toString())
}
})
export default sasViyaWebRouter

View File

@@ -1,3 +1,8 @@
export enum MOCK_SERVERTYPEType {
SAS9 = 'sas9',
SASVIYA = 'sasviya'
}
export enum ModeType {
Server = 'server',
Desktop = 'desktop'
@@ -40,6 +45,8 @@ export enum ReturnCode {
export const verifyEnvVariables = (): ReturnCode => {
const errors: string[] = []
errors.push(...verifyMOCK_SERVERTYPE())
errors.push(...verifyMODE())
errors.push(...verifyPROTOCOL())
@@ -66,6 +73,23 @@ export const verifyEnvVariables = (): ReturnCode => {
return ReturnCode.Success
}
const verifyMOCK_SERVERTYPE = (): string[] => {
const errors: string[] = []
const { MOCK_SERVERTYPE } = process.env
if (MOCK_SERVERTYPE) {
const modeTypes = Object.values(MOCK_SERVERTYPEType)
if (!modeTypes.includes(MOCK_SERVERTYPE as MOCK_SERVERTYPEType))
errors.push(
`- MOCK_SERVERTYPE '${MOCK_SERVERTYPE}'\n - valid options ${modeTypes}`
)
} else {
process.env.MOCK_SERVERTYPE = undefined
}
return errors
}
const verifyMODE = (): string[] => {
const errors: string[] = []
const { MODE } = process.env