Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
- [x] Severity filtering (`--min-severity`)
- [x] File/path exclusion patterns
- [x] Performance optimization for large repositories
- [ ] More comprehensive test fixtures
- [x] More comprehensive test fixtures

## Future (not committed)

Expand Down
73 changes: 73 additions & 0 deletions tests/cli/cli.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import test from 'node:test';
import assert from 'node:assert';
import { execSync } from 'child_process';
import * as path from 'path';

const CLI_PATH = path.resolve(__dirname, '..', '..', '..', 'bin', 'opk');
const FIXTURES_DIR = path.resolve(__dirname, '..', '..', '..', 'tests', 'fixtures');

function runCli(args: string[]): { stdout: string; stderr: string; status: number | null } {
try {
const stdout = execSync(`node ${CLI_PATH} ${args.join(' ')}`, { encoding: 'utf-8' });
return { stdout, stderr: '', status: 0 };
} catch (error: any) {
return {
stdout: error.stdout ? error.stdout.toString() : '',
stderr: error.stderr ? error.stderr.toString() : '',
status: error.status,
};
}
}

test('CLI Integration', async (t) => {
await t.test('should exit with 0 when scanning a clean directory', () => {
const targetDir = path.join(FIXTURES_DIR, 'scanner-config');
const result = runCli(['scan', targetDir]);
if (result.status !== 0) {
console.log('CLEAN DIRECTORY DEBUG:', result);
}
assert.strictEqual(result.status, 0);
assert.ok(result.stdout.includes('scanned') || result.stdout.includes('No findings'));
});

await t.test('should exit with 1 when findings are found', () => {
const targetDir = path.join(FIXTURES_DIR, 'ai-credentials');
const result = runCli(['scan', targetDir]);
if (result.status !== 1) {
console.log('FINDINGS DEBUG:', result);
}
assert.strictEqual(result.status, 1);
assert.ok(result.stdout.includes('OPK-001'));
});

await t.test('should output JSON when --json flag is used', () => {
const targetDir = path.join(FIXTURES_DIR, 'prompt-artifacts');
const result = runCli(['scan', targetDir, '--json']);

// Attempt to parse stdout as JSON
const parsed = JSON.parse(result.stdout);
assert.ok(Array.isArray(parsed.findings));
assert.ok(parsed.findings.length > 0);
assert.strictEqual(parsed.findings[0].ruleId, 'OPK-002');
});

await t.test('should exit with 2 when an invalid --min-severity is provided', () => {
const result = runCli(['scan', '--min-severity', 'invalid_level']);
assert.strictEqual(result.status, 2);
assert.ok(result.stderr.includes('Invalid --min-severity value'));
});

await t.test('should exit with 2 when --min-severity is missing value', () => {
const result = runCli(['scan', '--min-severity']);
assert.strictEqual(result.status, 2);
assert.ok(result.stderr.includes('requires a value'));
});

await t.test('should filter out warnings when --min-severity error is provided', () => {
const targetDir = path.join(FIXTURES_DIR, 'prompt-artifacts');
// prompt-artifacts only contains OPK-002 which is a warning.
const result = runCli(['scan', targetDir, '--min-severity', 'error']);
// Since only warnings exist, they are filtered out, so exit code should be 0.
assert.strictEqual(result.status, 0);
});
});
2 changes: 2 additions & 0 deletions tests/fixtures/placeholder-code/python-pass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
def my_function():
pass # TODO: implement this
7 changes: 7 additions & 0 deletions tests/rules/opk-001-ai-credentials.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ describe('OPK-001: Hardcoded AI Credentials', () => {
assert.strictEqual(findings.length, 0, 'Expected no findings for env var reference');
});

it('should NOT flag credentials in .env files', async () => {
const context = makeContext(['.env', '.env.local', '.env.example']);
const findings = await rule.check(context);

assert.strictEqual(findings.length, 0, 'Expected no findings for skipped .env files');
});

it('should NOT flag clean files without credentials', async () => {
const context = makeContext(['clean-file.ts']);
const findings = await rule.check(context);
Expand Down
10 changes: 10 additions & 0 deletions tests/rules/opk-003-placeholder-code.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ describe('OPK-003: Placeholder Implementation', () => {
assert.strictEqual(findings.length, 0, 'Expected no findings for clean file');
});

it('should detect Python pass with TODO', async () => {
const context = makeContext(['python-pass.py']);
const findings = await rule.check(context);

assert.ok(findings.length > 0, 'Expected finding for Python pass with TODO');
assert.strictEqual(findings[0].ruleId, 'OPK-003');
// It may match the "TODO implement" pattern before the Python-specific pattern
assert.ok(findings[0].message.includes('TODO'));
});

it('should have correct rule metadata', () => {
assert.strictEqual(rule.id, 'OPK-003');
assert.strictEqual(rule.name, 'Placeholder Implementation');
Expand Down
Loading