Skip to content

Commit

Permalink
test: setup tests for the CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
EmileRolley committed Oct 1, 2024
1 parent f81ac0c commit e398310
Show file tree
Hide file tree
Showing 11 changed files with 549 additions and 1,517 deletions.
1 change: 0 additions & 1 deletion bin/dev.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#!/usr/bin/env bun
// #!/usr/bin/env -S node --loader ts-node/esm --disable-warning=ExperimentalWarning

// eslint-disable-next-line n/shebang
import { execute } from '@oclif/core'
Expand Down
1 change: 0 additions & 1 deletion bin/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@

import { execute } from '@oclif/core'

console.log('Running the CLI:', import.meta.url)
await execute({ dir: import.meta.url })
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "tsup",
"watch": "tsup --watch",
"clean": "rm -rf dist docs",
"test": "jest",
"test": "vitest --globals",
"docs": "typedoc",
"format": "prettier --write .",
"format:check": "prettier --check ."
Expand Down Expand Up @@ -39,7 +39,8 @@
}
},
"files": [
"dist"
"dist",
"bin"
],
"repository": {
"type": "git",
Expand Down Expand Up @@ -69,16 +70,17 @@
"yaml": "^2.4.5"
},
"devDependencies": {
"@oclif/test": "^4.0.9",
"@types/jest": "^29.2.5",
"docdash": "^2.0.1",
"jest": "^29.4.1",
"prettier": "^3.0.0",
"ts-jest": "^29.0.4",
"ts-node": "^10.9.2",
"tsup": "^8.0.2",
"typedoc": "^0.24.8",
"typedoc-plugin-export-functions": "^1.0.0",
"typescript": "^4.9.4"
"typescript": "^4.9.4",
"vitest": "^2.1.1"
},
"tsup": {
"entry": [
Expand Down
98 changes: 98 additions & 0 deletions test/cli-utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import {
ExecException,
execSync,
ExecSyncOptionsWithBufferEncoding,
} from 'child_process'
import path, { join } from 'path'
import fs, { mkdtempSync } from 'fs'
import os from 'os'

export type ExecError = ExecException & { stderr: string; stdout: string }

export type ExecOptions = ExecSyncOptionsWithBufferEncoding & {
silent?: boolean
}

export type ExecResult = {
code: number
stdout?: string
stderr?: string
error?: ExecError
}

const TEST_DATA_DIR = path.join(process.cwd(), 'test', 'commands-data')

/**
* Utility test class to execute the CLI command
*/
export class CLIExecutor {
private binPath: string

constructor() {
const curentDir = process.cwd()
this.binPath =
process.platform === 'win32'
? path.join(curentDir, 'bin', 'dev.cmd')
: path.join(curentDir, 'bin', 'dev.js')
}

public execCommand(cmd: string, options?: ExecOptions): Promise<ExecResult> {
const silent = options?.silent ?? true
const command = `${this.binPath} ${cmd}`
const cwd = process.cwd()

return new Promise((resolve) => {
if (silent) {
try {
const r = execSync(command, { ...options, stdio: 'pipe', cwd })

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium test

This shell command depends on an uncontrolled
absolute path
.
const stdout = r.toString()

resolve({ code: 0, stdout })
} catch (error) {
const err = error as ExecError
console.log(error)

resolve({
code: 1,
error: err,
stdout: err.stdout?.toString() ?? '',
stderr: err.stderr?.toString() ?? '',
})
}
} else {
execSync(command, { ...options, stdio: 'inherit', cwd })

Check warning

Code scanning / CodeQL

Shell command built from environment values Medium test

This shell command depends on an uncontrolled
absolute path
.
resolve({ code: 0 })
}
})
}
}

// On macOS, os.tmpdir() is not a real path:
// https://github.com/nodejs/node/issues/11422
const TMP_DIR = fs.realpathSync(os.tmpdir())

export async function runInDir(
dir: 'tmp' | string,
fn: () => Promise<void>,
): Promise<void> {
const baseCwd = process.cwd()
const ctd =
dir === 'tmp'
? mkdtempSync(path.join(TMP_DIR, 'publicodes-cli-test-'))
: path.join(TEST_DATA_DIR, dir)

if (!fs.existsSync(ctd)) {
fs.mkdirSync(ctd, { recursive: true })
}

process.chdir(ctd)

try {
await fn()
} finally {
process.chdir(baseCwd)
if (dir === 'tmp' && fs.existsSync(ctd)) {
fs.rmSync(ctd, { recursive: true })
}
}
}
16 changes: 16 additions & 0 deletions test/commands-data/yarn-init/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "yarn-init",
"version": "1.0.0",
"description": "",
"author": "",
"type": "module",
"main": "index.js",
"types": "dist/index.d.ts",
"license": "MIT",
"files": [
"dist"
],
"peerDependencies": {
"publicodes": "^1.5.1"
}
}
13 changes: 13 additions & 0 deletions test/commands/base.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { CLIExecutor, runInDir } from '../cli-utils'

describe('publicodes --help', () => {
it('should list all available commands', async () => {
const cli = new CLIExecutor()

runInDir('tmp', async () => {
const { stdout } = await cli.execCommand('--help')

expect(stdout).toContain('init initialize a new project')

Check failure on line 10 in test/commands/base.test.ts

View workflow job for this annotation

GitHub Actions / main

Unhandled error

AssertionError: expected '' to contain 'init initialize a new project' - Expected + Received - init initialize a new project ❯ test/commands/base.test.ts:10:22 ❯ processTicksAndRejections node:internal/process/task_queues:95:5 ❯ Module.runInDir test/cli-utils.ts:91:5 This error originated in "test/commands/base.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "test/commands/base.test.ts". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.
})
})
})
31 changes: 31 additions & 0 deletions test/commands/init.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { execSync } from 'child_process'
import { CLIExecutor, runInDir } from '../cli-utils'
import fs from 'fs'

describe('publicodes init', () => {
it('should update existing package.json', async () => {
const cli = new CLIExecutor()

runInDir('tmp', async () => {
execSync('yarn init -y')
const { stdout } = await cli.execCommand('init')

expect(stdout).toContain('existing package.json file')

Check failure on line 13 in test/commands/init.test.ts

View workflow job for this annotation

GitHub Actions / main

Unhandled error

AssertionError: expected '' to contain 'existing package.json file' - Expected + Received - existing package.json file ❯ test/commands/init.test.ts:13:22 ❯ processTicksAndRejections node:internal/process/task_queues:95:5 ❯ Module.runInDir test/cli-utils.ts:91:5 This error originated in "test/commands/init.test.ts" test file. It doesn't mean the error was thrown inside the file itself, but while it was running. The latest test that might've caused the error is "test/commands/init.test.ts". It might mean one of the following: - The error was thrown, while Vitest was running this test. - If the error occurred after the test had been completed, this was the last documented test before it was thrown.
expect(stdout).toContain('package.json file written')
expect(stdout).toContain('🚀 publicodes is ready to use!')
const packageJson = JSON.parse(fs.readFileSync('package.json', 'utf-8'))
expect(packageJson).toMatchObject({
type: 'module',
main: 'dist/index.js',
types: 'dist/index.d.ts',
files: ['dist'],
peerDependencies: {
publicodes: '^1.5.1',
},
devDependencies: {
'@publicodes/tools': '^1.5.1',
},
})
})
})
})
2 changes: 1 addition & 1 deletion test/optims/constantFolding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Engine, { RuleNode } from 'publicodes'
import { serializeParsedRules } from '../../src'
import { RuleName, RawRules, disabledLogger } from '../../src/commons'
import { constantFolding } from '../../src/optims/'
import { callWithEngine } from '../utils.test'
import { callWithEngine } from '../utils'

function constantFoldingWith(rawRules: any, targets?: RuleName[]): RawRules {
const res = callWithEngine(
Expand Down
3 changes: 3 additions & 0 deletions test/utils.test.ts → test/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { RuleName, disabledLogger } from '../src/commons'
import Engine from 'publicodes'
import type { ParsedRules } from 'publicodes'
import { ChildProcess, exec } from 'child_process'
import fs from 'fs'
import path from 'path'

export function callWithEngine<R>(fn: (engine: Engine) => R, rawRules: any): R {
const engine = new Engine(rawRules, {
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"resolveJsonModule": true,
"allowJs": true,
"sourceMap": true,
"types": ["jest", "node"],
"types": ["vitest/globals", "node"],
"declaration": true,
"forceConsistentCasingInFileNames": true,
"allowSyntheticDefaultImports": true
Expand Down
Loading

0 comments on commit e398310

Please sign in to comment.