mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-05-16 17:32:13 +08:00
259 lines
7.7 KiB
JavaScript
259 lines
7.7 KiB
JavaScript
/**
|
|
* Tests for scripts/discussion-audit.js
|
|
*/
|
|
|
|
const assert = require('assert');
|
|
const fs = require('fs');
|
|
const os = require('os');
|
|
const path = require('path');
|
|
const { execFileSync, spawnSync } = require('child_process');
|
|
|
|
const SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'discussion-audit.js');
|
|
const { DISCUSSION_QUERY } = require(path.join(__dirname, '..', '..', 'scripts', 'lib', 'github-discussions'));
|
|
|
|
function createTempDir(prefix) {
|
|
return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
|
}
|
|
|
|
function cleanup(dirPath) {
|
|
fs.rmSync(dirPath, { recursive: true, force: true });
|
|
}
|
|
|
|
function discussionGhKey(owner, name, first = 100) {
|
|
return `api graphql -f owner=${owner} -f name=${name} -F first=${first} -f query=${DISCUSSION_QUERY}`;
|
|
}
|
|
|
|
function writeGhShim(rootDir, responses) {
|
|
const shimPath = path.join(rootDir, 'gh-shim.js');
|
|
fs.writeFileSync(shimPath, `
|
|
const responses = ${JSON.stringify(responses)};
|
|
const args = process.argv.slice(2);
|
|
const key = args.join(' ');
|
|
if (process.env.GITHUB_TOKEN) {
|
|
console.error('GITHUB_TOKEN should be unset by default');
|
|
process.exit(42);
|
|
}
|
|
if (!Object.prototype.hasOwnProperty.call(responses, key)) {
|
|
console.error('Unexpected gh args: ' + key);
|
|
process.exit(3);
|
|
}
|
|
process.stdout.write(JSON.stringify(responses[key]));
|
|
`);
|
|
return shimPath;
|
|
}
|
|
|
|
function run(args = [], options = {}) {
|
|
const env = {
|
|
...process.env,
|
|
...(options.env || {})
|
|
};
|
|
|
|
return execFileSync('node', [SCRIPT, ...args], {
|
|
cwd: options.cwd || path.join(__dirname, '..', '..'),
|
|
env,
|
|
encoding: 'utf8',
|
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
timeout: 10000
|
|
});
|
|
}
|
|
|
|
function runProcess(args = [], options = {}) {
|
|
const env = {
|
|
...process.env,
|
|
...(options.env || {})
|
|
};
|
|
|
|
return spawnSync('node', [SCRIPT, ...args], {
|
|
cwd: options.cwd || path.join(__dirname, '..', '..'),
|
|
env,
|
|
encoding: 'utf8',
|
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
timeout: 10000
|
|
});
|
|
}
|
|
|
|
function test(name, fn) {
|
|
try {
|
|
fn();
|
|
console.log(` PASS ${name}`);
|
|
return true;
|
|
} catch (error) {
|
|
console.log(` FAIL ${name}`);
|
|
console.log(` Error: ${error.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
function runTests() {
|
|
console.log('\n=== Testing discussion-audit.js ===\n');
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
if (test('passes when discussions have maintainer touch and accepted answers', () => {
|
|
const rootDir = createTempDir('discussion-audit-pass-');
|
|
|
|
try {
|
|
const shimPath = writeGhShim(rootDir, {
|
|
[discussionGhKey('affaan-m', 'everything-claude-code')]: {
|
|
data: {
|
|
repository: {
|
|
hasDiscussionsEnabled: true,
|
|
discussions: {
|
|
totalCount: 2,
|
|
nodes: [
|
|
{
|
|
number: 1923,
|
|
title: 'Does Continuous Learning v2 work with VS Code Claude Code?',
|
|
url: 'https://github.com/example/discussions/1923',
|
|
updatedAt: '2026-05-15T19:08:52Z',
|
|
authorAssociation: 'NONE',
|
|
category: { name: 'Q&A', isAnswerable: true },
|
|
answer: { url: 'https://github.com/example/discussions/1923#discussioncomment-1', authorAssociation: 'OWNER' },
|
|
comments: { nodes: [] }
|
|
},
|
|
{
|
|
number: 73,
|
|
title: 'Compacting during workflow',
|
|
url: 'https://github.com/example/discussions/73',
|
|
updatedAt: '2026-05-15T00:00:00Z',
|
|
authorAssociation: 'NONE',
|
|
category: { name: 'General', isAnswerable: false },
|
|
answer: null,
|
|
comments: { nodes: [{ authorAssociation: 'MEMBER' }] }
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
const parsed = JSON.parse(run([
|
|
'--json',
|
|
'--repo',
|
|
'affaan-m/everything-claude-code'
|
|
], {
|
|
cwd: rootDir,
|
|
env: {
|
|
ECC_GH_SHIM: shimPath,
|
|
GITHUB_TOKEN: 'must-be-removed'
|
|
}
|
|
}));
|
|
|
|
assert.strictEqual(parsed.ready, true);
|
|
assert.strictEqual(parsed.totals.needingMaintainerTouch, 0);
|
|
assert.strictEqual(parsed.totals.missingAcceptedAnswer, 0);
|
|
assert.ok(parsed.checks.some(check => check.id === 'discussion-accepted-answers' && check.status === 'pass'));
|
|
} finally {
|
|
cleanup(rootDir);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('fails when Q&A lacks accepted answer and maintainer touch', () => {
|
|
const rootDir = createTempDir('discussion-audit-fail-');
|
|
|
|
try {
|
|
const shimPath = writeGhShim(rootDir, {
|
|
[discussionGhKey('affaan-m', 'everything-claude-code')]: {
|
|
data: {
|
|
repository: {
|
|
hasDiscussionsEnabled: true,
|
|
discussions: {
|
|
totalCount: 1,
|
|
nodes: [
|
|
{
|
|
number: 1239,
|
|
title: 'Losing context',
|
|
url: 'https://github.com/example/discussions/1239',
|
|
updatedAt: '2026-05-15T00:00:00Z',
|
|
authorAssociation: 'NONE',
|
|
category: { name: 'Q&A', isAnswerable: true },
|
|
answer: null,
|
|
comments: { nodes: [] }
|
|
}
|
|
]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
const result = runProcess([
|
|
'--json',
|
|
'--repo',
|
|
'affaan-m/everything-claude-code',
|
|
'--exit-code'
|
|
], {
|
|
cwd: rootDir,
|
|
env: { ECC_GH_SHIM: shimPath }
|
|
});
|
|
const parsed = JSON.parse(result.stdout);
|
|
|
|
assert.strictEqual(result.status, 2);
|
|
assert.strictEqual(parsed.ready, false);
|
|
assert.strictEqual(parsed.totals.needingMaintainerTouch, 1);
|
|
assert.strictEqual(parsed.totals.missingAcceptedAnswer, 1);
|
|
assert.ok(parsed.top_actions.some(action => action.id === 'discussion-maintainer-touch'));
|
|
assert.ok(parsed.top_actions.some(action => action.id === 'discussion-accepted-answers'));
|
|
} finally {
|
|
cleanup(rootDir);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('writes markdown output as a durable operator artifact', () => {
|
|
const rootDir = createTempDir('discussion-audit-markdown-');
|
|
const outputPath = path.join(rootDir, 'artifacts', 'discussion-audit.md');
|
|
|
|
try {
|
|
const shimPath = writeGhShim(rootDir, {
|
|
[discussionGhKey('affaan-m', 'everything-claude-code')]: {
|
|
data: {
|
|
repository: {
|
|
hasDiscussionsEnabled: true,
|
|
discussions: { totalCount: 0, nodes: [] }
|
|
}
|
|
}
|
|
}
|
|
});
|
|
const stdout = run([
|
|
'--markdown',
|
|
'--write',
|
|
outputPath,
|
|
'--repo',
|
|
'affaan-m/everything-claude-code'
|
|
], {
|
|
cwd: rootDir,
|
|
env: { ECC_GH_SHIM: shimPath }
|
|
});
|
|
const written = fs.readFileSync(outputPath, 'utf8');
|
|
|
|
assert.strictEqual(stdout, written);
|
|
assert.ok(written.includes('# ECC Discussion Audit'));
|
|
assert.ok(written.includes('Answerable discussions missing accepted answer'));
|
|
assert.ok(written.includes('- none'));
|
|
} finally {
|
|
cleanup(rootDir);
|
|
}
|
|
})) passed++; else failed++;
|
|
|
|
if (test('cli help and invalid args exit cleanly', () => {
|
|
const help = runProcess(['--help']);
|
|
assert.strictEqual(help.status, 0);
|
|
assert.ok(help.stdout.includes('Usage: node scripts/discussion-audit.js'));
|
|
|
|
const invalid = runProcess(['--format', 'xml']);
|
|
assert.strictEqual(invalid.status, 1);
|
|
assert.ok(invalid.stderr.includes('Invalid format'));
|
|
})) passed++; else failed++;
|
|
|
|
console.log(`\nPassed: ${passed}`);
|
|
console.log(`Failed: ${failed}`);
|
|
|
|
if (failed > 0) {
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
runTests();
|