Skip to content

Commit

Permalink
chore(endomoat): CLI tests
Browse files Browse the repository at this point in the history
  • Loading branch information
boneskull committed Apr 1, 2024
1 parent 87447c2 commit b452d58
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 1 deletion.
194 changes: 194 additions & 0 deletions packages/endomoat/test/cli.spec.js
@@ -0,0 +1,194 @@
import test from 'ava'
import { execFile } from 'node:child_process'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { promisify } from 'node:util'
import { readJsonFile } from '../src/util.js'

const execFileAsync = promisify(execFile)

/**
* RegExp to match ANSI escape codes
*
* @see {@link https://github.com/chalk/ansi-regex}
*/
const ANSI_REGEX = new RegExp(
// eslint-disable-next-line no-control-regex
'[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))',
'g'
)

/**
* Path to the CLI entry point
*/
const CLI_PATH = fileURLToPath(new URL('../src/cli.js', import.meta.url))

const BASIC_ENTRYPOINT = fileURLToPath(
new URL('./fixture/basic/app.js', import.meta.url)
)
const BASIC_ENTRYPOINT_CWD = path.dirname(BASIC_ENTRYPOINT)

/**
* @typedef {import('node:child_process').ExecFileException['code']} ExitCode
*/

/**
* Possible types for the `expected` argument of the {@link testCLI} macro.
*
* Execution output will be automatically trimmed and stripped of ANSI escape
* codes (colors).
*
* If a `string`, it is compared to `stdout`.
*
* @template [Ctx=unknown] Default is `unknown`
* @typedef {string
* | { code: ExitCode }
* | {
* stdout: string
* code?: ExitCode
* }
* | {
* stderr: string
* code?: ExitCode
* }
* | {
* stdout: string
* stderr: string
* code?: ExitCode
* }
* | ((
* t: import('ava').ExecutionContext<Ctx>,
* result: {
* stdout: string
* stderr: string
* code?: ExitCode
* }
* ) => void | Promise<void>)} ExecEndomoatExpectation
*/

/**
* Run the `endomoat` CLI with the provided arguments
*
* @param {string[]} args CLI arguments
* @returns {Promise<{ stdout: string; stderr: string; code: ExitCode }>}
*/
async function runCli(args) {
await Promise.resolve()

/** @type {string} */
let stdout
/** @type {string} */
let stderr
/** @type {import('node:child_process').ExecFileException['code']} */
let code

try {
;({ stdout, stderr } = await execFileAsync(
process.execPath,
[CLI_PATH, ...args],
{ encoding: 'utf8' }
))
} catch (err) {
;({ stdout, stderr, code } = /**
* @type {import('node:child_process').ExecFileException & {
* stdout: string
* stderr: string
* }}
*/ (err))
}
return { stdout, stderr, code }
}

/**
* Macro to run the `endomoat` CLI with the provided arguments, and optionally
* perform assertions on the output.
*
* @todo Could create a factory function so it could be generic for any CLI
*/
const testCLI = test.macro(
/**
* @template [Ctx=unknown] Default is `unknown`
* @param {import('ava').ExecutionContext<Ctx>} t Exec context
* @param {string[]} args CLI arguments
* @param {ExecEndomoatExpectation<Ctx>} [expected] Expected output or
* callback
* @returns {Promise<void>}
*/
async (t, args, expected) => {
await Promise.resolve()

t.log(`executing: ${process.execPath} ${CLI_PATH} ${args.join(' ')}`)

const { stdout, stderr, code } = await runCli(args)

const trimmedStdout = stdout.trim().replace(ANSI_REGEX, '')
const trimmedStderr = stderr.trim().replace(ANSI_REGEX, '')

switch (typeof expected) {
case 'string':
t.is(trimmedStdout, expected, 'stdout does not match expected value')
break
case 'function':
await expected(t, {
stdout: trimmedStdout,
stderr: trimmedStderr,
code,
})
break
case 'object':
if ('stdout' in expected) {
t.is(
trimmedStdout,
expected.stdout,
'stdout does not match expected value'
)
}
if ('stderr' in expected) {
t.is(
trimmedStderr,
expected.stderr,
'stderr does not match expected value'
)
}
if ('code' in expected) {
t.is(code, expected.code, 'exit code does not match expected value')
}
break
default:
t.snapshot(
{ trimmedStderr, trimmedStdout, code },
'execution output does not match expected snapshot'
)
}
}
)

test('--help prints help', testCLI, ['--help'])

test('--version', testCLI, ['--version'], async (t, { stdout }) => {
const { version } = /** @type {import('type-fest').PackageJson} */ (
await readJsonFile(new URL('../package.json', import.meta.url))
)
t.is(stdout, `${version}`)
})

test('run - --help prints help', testCLI, ['run', '--help'])

test(
'run - missing entrypoint',
testCLI,
['run'],
async (t, { code, stderr, stdout }) => {
t.plan(3)
t.is(code, 1)
t.is(stdout, '')
t.regex(stderr, /Not enough non-option arguments: got 0, need at least 1/)
}
)

test(
'run - basic execution',
testCLI,
['run', BASIC_ENTRYPOINT, '--cwd', BASIC_ENTRYPOINT_CWD],
'hello world'
)
3 changes: 3 additions & 0 deletions packages/endomoat/test/fixture/basic/app.js
@@ -0,0 +1,3 @@
export const hello = 'world'

console.log(`hello ${hello}`)
@@ -0,0 +1 @@
{"resources": {}}
8 changes: 8 additions & 0 deletions packages/endomoat/test/fixture/basic/package.json
@@ -0,0 +1,8 @@
{
"name": "basic",
"version": "1.0.0",
"type": "module",
"license": "ISC",
"private": true,
"main": "app.js"
}
1 change: 0 additions & 1 deletion packages/endomoat/test/fixture/no-deps/.npmrc

This file was deleted.

59 changes: 59 additions & 0 deletions packages/endomoat/test/snapshots/cli.spec.js.md
@@ -0,0 +1,59 @@
# Snapshot report for `test/cli.spec.js`

The actual snapshot is saved in `cli.spec.js.snap`.

Generated by [AVA](https://avajs.dev).

## --help prints help

> execution output does not match expected snapshot
{
code: undefined,
trimmedStderr: '',
trimmedStdout: `endomoat <entrypoint>␊
Run an application␊
Positionals:␊
entrypoint Path to the application entry point [string] [required]␊
Options:␊
--help Show help [boolean]␊
--version Show version number [boolean]␊
-p, --policy Filepath to a policy file␊
[string] [default: "lavamoat/node/policy.json"]␊
-o, --policy-override, --override Filepath to a policy override file␊
[string] [default: "lavamoat/node/policy-override.json"]␊
--policy-debug, --pd Filepath to a policy debug file␊
[string] [default: "lavamoat/node/policy-debug.json"]␊
--cwd Path to application root directory␊
[string] [default: (current directory)]`,
}

## run - --help prints help

> execution output does not match expected snapshot
{
code: undefined,
trimmedStderr: '',
trimmedStdout: `endomoat run <entrypoint>␊
Run an application␊
Positionals:␊
entrypoint Path to the application entry point [string] [required]␊
Options:␊
--help Show help [boolean]␊
--version Show version number [boolean]␊
-p, --policy Filepath to a policy file␊
[string] [default: "lavamoat/node/policy.json"]␊
-o, --policy-override, --override Filepath to a policy override file␊
[string] [default: "lavamoat/node/policy-override.json"]␊
--policy-debug, --pd Filepath to a policy debug file␊
[string] [default: "lavamoat/node/policy-debug.json"]␊
--cwd Path to application root directory␊
[string] [default: (current directory)]`,
}
Binary file added packages/endomoat/test/snapshots/cli.spec.js.snap
Binary file not shown.

0 comments on commit b452d58

Please sign in to comment.