n8n/packages/testing/janitor/src/cli.test.ts

207 lines
5.7 KiB
TypeScript

import { execSync } from 'node:child_process';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
const CLI_PATH = path.join(__dirname, '..', 'dist', 'cli.js');
describe('CLI', () => {
describe('--help', () => {
it('shows help text', () => {
const output = execSync(`node ${CLI_PATH} --help`, { encoding: 'utf-8' });
expect(output).toContain('Playwright Janitor');
expect(output).toContain('Commands:');
expect(output).toContain('baseline');
expect(output).toContain('inventory');
expect(output).toContain('impact');
expect(output).toContain('tcr');
expect(output).toContain('--ignore-baseline');
});
});
describe('--list', () => {
it('lists available rules', () => {
const output = execSync(`node ${CLI_PATH} --list`, { encoding: 'utf-8' });
expect(output).toContain('Available rules:');
expect(output).toContain('boundary-protection');
expect(output).toContain('selector-purity');
expect(output).toContain('dead-code');
expect(output).toContain('scope-lockdown');
});
});
describe('baseline --help', () => {
it('shows baseline help', () => {
const output = execSync(`node ${CLI_PATH} baseline --help`, { encoding: 'utf-8' });
expect(output).toContain('Baseline');
expect(output).toContain('.janitor-baseline.json');
expect(output).toContain('incremental cleanup');
});
});
describe('inventory --help', () => {
it('shows inventory help', () => {
const output = execSync(`node ${CLI_PATH} inventory --help`, { encoding: 'utf-8' });
expect(output).toContain('Inventory');
expect(output).toContain('JSON');
});
});
describe('impact --help', () => {
it('shows impact help', () => {
const output = execSync(`node ${CLI_PATH} impact --help`, { encoding: 'utf-8' });
expect(output).toContain('Impact');
expect(output).toContain('--file');
});
});
describe('method-impact --help', () => {
it('shows method-impact help', () => {
const output = execSync(`node ${CLI_PATH} method-impact --help`, { encoding: 'utf-8' });
expect(output).toContain('Method Impact');
expect(output).toContain('--method');
});
});
describe('tcr --help', () => {
it('shows tcr help', () => {
const output = execSync(`node ${CLI_PATH} tcr --help`, { encoding: 'utf-8' });
expect(output).toContain('TCR');
expect(output).toContain('--execute');
expect(output).toContain('--message');
expect(output).toContain('--max-diff-lines');
});
});
describe('argument parsing', () => {
let tempDir: string;
beforeAll(() => {
// Use realpathSync to avoid macOS /var -> /private/var symlink issues
tempDir = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), 'cli-test-')));
// Create tsconfig.json (required by ts-morph)
fs.writeFileSync(
path.join(tempDir, 'tsconfig.json'),
JSON.stringify({
compilerOptions: {
target: 'ES2020',
module: 'ESNext',
moduleResolution: 'node',
strict: true,
},
include: ['**/*.ts'],
}),
);
// Create minimal config as .js (Node.js can't dynamically import .ts files)
fs.writeFileSync(
path.join(tempDir, 'janitor.config.js'),
`module.exports = {
rootDir: '${tempDir}',
patterns: {
pages: ['pages/**/*.ts'],
components: [],
flows: [],
tests: ['tests/**/*.spec.ts'],
services: [],
fixtures: [],
helpers: [],
factories: [],
testData: [],
},
excludeFromPages: [],
facade: { file: 'pages/AppPage.ts', className: 'AppPage', excludeTypes: [] },
fixtureObjectName: 'app',
apiFixtureName: 'api',
rawApiPatterns: [],
flowLayerName: 'Composable',
architectureLayers: [],
rules: {},
};`,
);
fs.mkdirSync(path.join(tempDir, 'pages'), { recursive: true });
fs.mkdirSync(path.join(tempDir, 'tests'), { recursive: true });
});
afterAll(() => {
fs.rmSync(tempDir, { recursive: true, force: true });
});
it('accepts --json flag', () => {
// Create a clean page file
fs.writeFileSync(path.join(tempDir, 'pages', 'CleanPage.ts'), 'export class CleanPage {}');
try {
const output = execSync(
`node ${CLI_PATH} --json --config=${path.join(tempDir, 'janitor.config.js')}`,
{
encoding: 'utf-8',
cwd: tempDir,
},
);
// Should be valid JSON
expect(() => JSON.parse(output) as unknown).not.toThrow();
} catch (error) {
// Even if there are violations, output should be JSON
const stderr = (error as { stdout?: string }).stdout ?? '';
if (stderr) {
expect(() => JSON.parse(stderr) as unknown).not.toThrow();
}
}
});
it('accepts --verbose flag', () => {
fs.writeFileSync(
path.join(tempDir, 'pages', 'VerbosePage.ts'),
'export class VerbosePage {}',
);
try {
const output = execSync(
`node ${CLI_PATH} --verbose --config=${path.join(tempDir, 'janitor.config.js')}`,
{
encoding: 'utf-8',
cwd: tempDir,
},
);
// Verbose output should include more detail
expect(output).toBeDefined();
} catch {
// May fail with violations, that's OK for this test
}
});
it('accepts --ignore-baseline flag', () => {
fs.writeFileSync(
path.join(tempDir, 'pages', 'BaselinePage.ts'),
'export class BaselinePage {}',
);
try {
const output = execSync(
`node ${CLI_PATH} --ignore-baseline --config=${path.join(tempDir, 'janitor.config.js')}`,
{
encoding: 'utf-8',
cwd: tempDir,
},
);
// Should not show "Using baseline" message when ignoring
expect(output).not.toContain('Using baseline');
} catch {
// May fail with violations, that's OK for this test
}
});
});
});