UNPKG

@sap/cds-dk

Version:

Command line client and development toolkit for the SAP Cloud Application Programming Model

153 lines (132 loc) 5.33 kB
const cds = require('../../../cds') const { fs: { promises: { rename } }, path: { dirname, join }, exists } = cds.utils const { readProject } = require('../../projectReader') const { merge } = require('../../merge') const { renderAndCopy } = require('../../template') const genDataAsJson = require('../data/as-json') const { mockUsersForService } = require('../http') const { dim } = require('../../../util/term') const { filterStringAsRegex } = require('../../add') module.exports = class Test extends require('../../plugin') { static help() { return 'tests for services' } options() { return { 'filter': { type: 'string', short: 'f', help: `Filter for services or entities or actions matching the given pattern. If it contains meta characters like '^' or '*', it is treated as a regular expression, otherwise as an include pattern, i.e /.*pattern.*/i` }, 'out': { type: 'string', short: 'o', help: `Custom output directory. For Node.js, the default is 'test'.` } } } async canRun() { const { isJava } = readProject() if (isJava) { throw `Test generation is not supported in Java projects yet.` } return true } async run() { const proj = readProject() await createNodeTest(proj) } } /** * @param {ReturnType<readProject} proj */ async function createNodeTest(proj) { const { filter, force, out='test' } = cds.cli.options const csn = await loadModel() if (!csn) return console.log(dim(`> skipping, no model found`)) const nameFilter = filterStringAsRegex(filter) const serviceInfo = cds.compile.to.serviceinfo(csn) for (const service of csn.services) { if (!service.$location.file) continue const sInfo = serviceInfo.find(s => s.name === service.name) proj.serviceName = service.name proj.serviceClass = service.name.split('.').pop() proj.servicePath = proj.serviceName ? (proj.serviceName.replace(/\./g, '/')) : '' const entities = Object.entries(service.entities) .filter(([name]) => name.match(nameFilter)) .filter(([name]) => !name.match(/[._]texts$/)) .filter(([, e]) => !e['@cds.autoexposed']) // only consider non-autoexposed entities to reduce clutter .map(([name, e]) => {e._nameUnqualified = name; return e}) let entity // use one entity only (don't want to make users test all entities) if (entities.length) entity = entities[0] const sampleData = {} if (entity) { // see if we have csv data for the entity const refData = await cds.deploy.resources(csn) const data = genDataAsJson.randomFromReferenceData(entity, csn, refData) sampleData[entity.name] = data ?? [] } // mark action data to be generated for (const action of Object.keys(service.actions).filter(name => name.match(nameFilter))) { sampleData[`${service.name}.${action}`] = [] } await genDataAsJson(sampleData, csn, 1, {referenceData: {}}) if (entity) { const elements = Object.values(entity.elements ?? {}).sort(genDataAsJson.elementsSorter(csn)) const keys = elements.filter(el => el.key).map(el => el.name) const selectElements = [ keys[0], // select a non-key, non-association element from the entity itself elements.filter(el => !keys.includes(el.name)).find(el => !(el instanceof cds.Association))?.name ].filter(Boolean) proj.entities = [{ name: entity.name, urlPath: `/${sInfo.endpoints[0].path}${entity._nameUnqualified}`, selectString: selectElements.join(','), elementsString: JSON.stringify( Object.entries(sampleData[entity.name][0]) .filter(([name]) => selectElements.includes(name)) .reduce((acc, [name, value]) => { acc[name] = value; return acc }, {}) ) }] } proj.actions = Object.entries(service.actions) .filter(([name]) => name.match(nameFilter)) .map(([name]) => { return { name, paramsString: JSON.stringify(sampleData[`${service.name}.${name}`][0]), urlPath: `/${sInfo.endpoints[0].path}${name}`, }}) const users = await mockUsersForService(service.name, false, csn) if (Object.keys(users).length) { proj.user = Object.keys(users)[0] proj.password = users[proj.user].password } const destFileType = proj.isTypescript ? '.test.ts' : '.test.js' const outPath = join(out, service.name + destFileType) const destFile = join(cds.root, outPath) if (!force && exists(destFile)) { console.log(dim(`> skipping ${outPath}`)) continue } console.log(dim(`> writing ${outPath}`)) const destPath = dirname(destFile) await renderAndCopy(join(__dirname, 'files/test'), destPath, proj) await rename(join(destPath, 'test.xs'), destFile) await merge(join(__dirname, 'files/package.json')).into('package.json') } } /** * @returns { Promise<import('@cap-js/cds-types').linked.LinkedCSN | null> } */ async function loadModel() { try { return cds.linked(cds.minify(await cds.load(cds.env.roots))) } catch (err) { if (err.code === 'MODEL_NOT_FOUND') return null throw new Error(`Error compiling CDS files. Run 'npm install' and try again.`, {cause:err}) } }