1
0
mirror of https://github.com/sasjs/server.git synced 2025-12-10 19:34:34 +00:00
Files
server/api/src/routes/api/spec/stp.spec.ts

507 lines
15 KiB
TypeScript

import path from 'path'
import { Express } from 'express'
import mongoose, { Mongoose } from 'mongoose'
import { MongoMemoryServer } from 'mongodb-memory-server'
import request from 'supertest'
import appPromise from '../../../app'
import {
UserController,
PermissionController,
PermissionType,
PermissionSettingForRoute,
PrincipalType
} from '../../../controllers/'
import {
generateAccessToken,
saveTokensInDB,
getFilesFolder,
RunTimeType,
generateUniqueFileName,
getSessionsFolder
} from '../../../utils'
import { createFile, generateTimestamp, deleteFolder } from '@sasjs/utils'
import {
SessionController,
SASSessionController
} from '../../../controllers/internal'
import * as ProcessProgramModule from '../../../controllers/internal/processProgram'
import { Session } from '../../../types'
const clientId = 'someclientID'
const user = {
displayName: 'Test User',
username: 'testUsername',
password: '87654321',
isAdmin: false,
isActive: true
}
const sampleSasProgram = '%put hello world!;'
const sampleJsProgram = `console.log('hello world!/')`
const samplePyProgram = `print('hello world!/')`
const filesFolder = getFilesFolder()
const testFilesFolder = `test-stp-${generateTimestamp()}`
let app: Express
let accessToken: string
describe('stp', () => {
let con: Mongoose
let mongoServer: MongoMemoryServer
const userController = new UserController()
const permissionController = new PermissionController()
beforeAll(async () => {
app = await appPromise
mongoServer = await MongoMemoryServer.create()
con = await mongoose.connect(mongoServer.getUri())
const dbUser = await userController.createUser(user)
accessToken = await generateAndSaveToken(dbUser.id)
await permissionController.createPermission({
path: '/SASjsApi/stp/execute',
type: PermissionType.route,
principalType: PrincipalType.user,
principalId: dbUser.id,
setting: PermissionSettingForRoute.grant
})
})
afterAll(async () => {
await con.connection.dropDatabase()
await con.connection.close()
await mongoServer.stop()
})
describe('execute', () => {
describe('get', () => {
describe('with runtime js', () => {
const testFilesFolder = `test-stp-${generateTimestamp()}`
beforeAll(() => {
process.runTimes = [RunTimeType.JS]
})
beforeEach(() => {
jest.resetModules() // it clears the cache
setupMocks()
})
afterEach(async () => {
jest.resetAllMocks()
await deleteFolder(path.join(filesFolder, testFilesFolder))
})
it('should execute js program when both js and sas program are present', async () => {
await makeRequestAndAssert(
[RunTimeType.JS, RunTimeType.SAS],
200,
RunTimeType.JS
)
})
it('should throw error when js program is not present but sas program exists', async () => {
await makeRequestAndAssert([], 400)
})
})
describe('with runtime py', () => {
const testFilesFolder = `test-stp-${generateTimestamp()}`
beforeAll(() => {
process.runTimes = [RunTimeType.PY]
})
beforeEach(() => {
jest.resetModules() // it clears the cache
setupMocks()
})
afterEach(async () => {
jest.resetAllMocks()
await deleteFolder(path.join(filesFolder, testFilesFolder))
})
it('should execute python program when python, js and sas programs are present', async () => {
await makeRequestAndAssert(
[RunTimeType.PY, RunTimeType.SAS, RunTimeType.JS],
200,
RunTimeType.PY
)
})
it('should throw error when py program is not present but js or sas program exists', async () => {
await makeRequestAndAssert([], 400)
})
})
describe('with runtime sas', () => {
beforeAll(() => {
process.runTimes = [RunTimeType.SAS]
})
beforeEach(() => {
jest.resetModules() // it clears the cache
setupMocks()
})
afterEach(async () => {
jest.resetAllMocks()
await deleteFolder(path.join(filesFolder, testFilesFolder))
})
it('should execute sas program when both sas and js programs are present', async () => {
await makeRequestAndAssert([RunTimeType.SAS], 200, RunTimeType.SAS)
})
it('should throw error when sas program do not exit but js exists', async () => {
await makeRequestAndAssert([], 400)
})
})
describe('with runtime js and sas', () => {
beforeAll(() => {
process.runTimes = [RunTimeType.JS, RunTimeType.SAS]
})
beforeEach(() => {
jest.resetModules() // it clears the cache
setupMocks()
})
afterEach(async () => {
jest.resetAllMocks()
await deleteFolder(path.join(filesFolder, testFilesFolder))
})
it('should execute js program when both js and sas program are present', async () => {
await makeRequestAndAssert(
[RunTimeType.SAS, RunTimeType.JS],
200,
RunTimeType.JS
)
})
it('should execute sas program when js program is not present but sas program exists', async () => {
await makeRequestAndAssert([RunTimeType.SAS], 200, RunTimeType.SAS)
})
it('should throw error when both sas and js programs do not exist', async () => {
await makeRequestAndAssert([], 400)
})
})
describe('with runtime py and sas', () => {
beforeAll(() => {
process.runTimes = [RunTimeType.PY, RunTimeType.SAS]
})
beforeEach(() => {
jest.resetModules() // it clears the cache
setupMocks()
})
afterEach(async () => {
jest.resetAllMocks()
await deleteFolder(path.join(filesFolder, testFilesFolder))
})
it('should execute python program when both python and sas program are present', async () => {
await makeRequestAndAssert(
[RunTimeType.PY, RunTimeType.SAS],
200,
RunTimeType.PY
)
})
it('should execute sas program when python program is not present but sas program exists', async () => {
await makeRequestAndAssert([RunTimeType.SAS], 200, RunTimeType.SAS)
})
it('should throw error when both sas and js programs do not exist', async () => {
await makeRequestAndAssert([], 400)
})
})
describe('with runtime sas and js', () => {
beforeAll(() => {
process.runTimes = [RunTimeType.SAS, RunTimeType.JS]
})
beforeEach(() => {
jest.resetModules() // it clears the cache
setupMocks()
})
afterEach(async () => {
jest.resetAllMocks()
await deleteFolder(path.join(filesFolder, testFilesFolder))
})
it('should execute sas program when both sas and js programs exist', async () => {
await makeRequestAndAssert(
[RunTimeType.SAS, RunTimeType.JS],
200,
RunTimeType.SAS
)
})
it('should execute js program when sas program is not present but js program exists', async () => {
await makeRequestAndAssert([RunTimeType.JS], 200, RunTimeType.JS)
})
it('should throw error when both sas and js programs do not exist', async () => {
await makeRequestAndAssert([], 400)
})
})
describe('with runtime sas and py', () => {
beforeAll(() => {
process.runTimes = [RunTimeType.SAS, RunTimeType.PY]
})
beforeEach(() => {
jest.resetModules() // it clears the cache
setupMocks()
})
afterEach(async () => {
jest.resetAllMocks()
await deleteFolder(path.join(filesFolder, testFilesFolder))
})
it('should execute sas program when both sas and python programs exist', async () => {
await makeRequestAndAssert(
[RunTimeType.SAS, RunTimeType.PY],
200,
RunTimeType.SAS
)
})
it('should execute python program when sas program is not present but python program exists', async () => {
await makeRequestAndAssert([RunTimeType.PY], 200, RunTimeType.PY)
})
it('should throw error when both sas and python programs do not exist', async () => {
await makeRequestAndAssert([], 400)
})
})
describe('with runtime sas, js and py', () => {
beforeAll(() => {
process.runTimes = [RunTimeType.SAS, RunTimeType.JS, RunTimeType.PY]
})
beforeEach(() => {
jest.resetModules() // it clears the cache
setupMocks()
})
afterEach(async () => {
jest.resetAllMocks()
await deleteFolder(path.join(filesFolder, testFilesFolder))
})
it('should execute sas program when it exists, no matter js and python programs exist or not', async () => {
await makeRequestAndAssert(
[RunTimeType.SAS, RunTimeType.PY, RunTimeType.JS],
200,
RunTimeType.SAS
)
})
it('should execute js program when sas program is absent but js and python programs are present', async () => {
await makeRequestAndAssert(
[RunTimeType.JS, RunTimeType.PY],
200,
RunTimeType.JS
)
})
it('should execute python program when both sas and js programs are not present', async () => {
await makeRequestAndAssert([RunTimeType.PY], 200, RunTimeType.PY)
})
it('should throw error when no program exists', async () => {
await makeRequestAndAssert([], 400)
})
})
describe('with runtime js, sas and py', () => {
beforeAll(() => {
process.runTimes = [RunTimeType.JS, RunTimeType.SAS, RunTimeType.PY]
})
beforeEach(() => {
jest.resetModules() // it clears the cache
setupMocks()
})
afterEach(async () => {
jest.resetAllMocks()
await deleteFolder(path.join(filesFolder, testFilesFolder))
})
it('should execute js program when it exists, no matter sas and python programs exist or not', async () => {
await makeRequestAndAssert(
[RunTimeType.JS, RunTimeType.SAS, RunTimeType.PY],
200,
RunTimeType.JS
)
})
it('should execute sas program when js program is absent but sas and python programs are present', async () => {
await makeRequestAndAssert(
[RunTimeType.SAS, RunTimeType.PY],
200,
RunTimeType.SAS
)
})
it('should execute python program when both sas and js programs are not present', async () => {
await makeRequestAndAssert([RunTimeType.PY], 200, RunTimeType.PY)
})
it('should throw error when no program exists', async () => {
await makeRequestAndAssert([], 400)
})
})
describe('with runtime py, sas and js', () => {
beforeAll(() => {
process.runTimes = [RunTimeType.PY, RunTimeType.SAS, RunTimeType.JS]
})
beforeEach(() => {
jest.resetModules() // it clears the cache
setupMocks()
})
afterEach(async () => {
jest.resetAllMocks()
await deleteFolder(path.join(filesFolder, testFilesFolder))
})
it('should execute python program when it exists, no matter sas and js programs exist or not', async () => {
await makeRequestAndAssert(
[RunTimeType.PY, RunTimeType.SAS, RunTimeType.JS],
200,
RunTimeType.PY
)
})
it('should execute sas program when python program is absent but sas and js programs are present', async () => {
await makeRequestAndAssert(
[RunTimeType.SAS, RunTimeType.JS],
200,
RunTimeType.SAS
)
})
it('should execute js program when both sas and python programs are not present', async () => {
await makeRequestAndAssert([RunTimeType.JS], 200, RunTimeType.JS)
})
it('should throw error when no program exists', async () => {
await makeRequestAndAssert([], 400)
})
})
})
})
})
const makeRequestAndAssert = async (
programTypes: RunTimeType[],
expectedStatusCode: number,
expectedRuntime?: RunTimeType
) => {
const programPath = path.join(testFilesFolder, 'program')
for (const programType of programTypes) {
if (programType === RunTimeType.JS)
await createFile(
path.join(filesFolder, `${programPath}.js`),
sampleJsProgram
)
else if (programType === RunTimeType.PY)
await createFile(
path.join(filesFolder, `${programPath}.py`),
samplePyProgram
)
else if (programType === RunTimeType.SAS)
await createFile(
path.join(filesFolder, `${programPath}.sas`),
sampleSasProgram
)
}
await request(app)
.get(`/SASjsApi/stp/execute?_program=${programPath}`)
.auth(accessToken, { type: 'bearer' })
.send()
.expect(expectedStatusCode)
if (expectedRuntime)
expect(ProcessProgramModule.processProgram).toHaveBeenCalledWith(
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expect.anything(),
expectedRuntime,
expect.anything(),
undefined
)
}
const generateAndSaveToken = async (userId: number) => {
const accessToken = generateAccessToken({
clientId,
userId
})
await saveTokensInDB(userId, clientId, accessToken, 'refreshToken')
return accessToken
}
const setupMocks = async () => {
jest
.spyOn(SASSessionController.prototype, 'getSession')
.mockImplementation(mockedGetSession)
jest
.spyOn(SASSessionController.prototype, 'getSession')
.mockImplementation(mockedGetSession)
jest
.spyOn(ProcessProgramModule, 'processProgram')
.mockImplementation(() => Promise.resolve())
}
const mockedGetSession = async () => {
const sessionId = generateUniqueFileName(generateTimestamp())
const sessionFolder = path.join(getSessionsFolder(), sessionId)
const creationTimeStamp = sessionId.split('-').pop() as string
// death time of session is 15 mins from creation
const deathTimeStamp = (
parseInt(creationTimeStamp) +
15 * 60 * 1000 -
1000
).toString()
const session: Session = {
id: sessionId,
ready: true,
inUse: true,
consumed: false,
completed: false,
creationTimeStamp,
deathTimeStamp,
path: sessionFolder
}
return session
}