fix(stats): enforce cleanup mode exclusivity and JSON requests

- Reject conflicting explicit cleanup modes
- Require application/json for duplicate-line maintenance requests
This commit is contained in:
2026-08-12 00:49:32 -07:00
parent 9fd2dfd7a0
commit 4a7c9b04f1
6 changed files with 54 additions and 7 deletions
+8 -2
View File
@@ -316,14 +316,20 @@ export function parseCliPrograms(
'Stats --vocab, --lifetime and --duplicate-lines flags require the cleanup action.',
);
}
if (options.duplicateLines !== true && (options.dryRun === true || options.lookbackDays)) {
if (
options.duplicateLines !== true &&
(options.dryRun === true || options.lookbackDays !== undefined)
) {
throw new Error('Stats --dry-run and --lookback-days require --duplicate-lines.');
}
if (normalizedAction === 'cleanup') {
statsCleanup = true;
statsCleanupLifetime = options.lifetime === true;
statsCleanupDuplicateLines = options.duplicateLines === true;
if (statsCleanupLifetime && statsCleanupDuplicateLines) {
const explicitModeCount = [options.vocab, options.lifetime, options.duplicateLines].filter(
(value) => value === true,
).length;
if (explicitModeCount > 1) {
throw new Error('Stats cleanup runs one mode at a time.');
}
// Vocabulary cleanup stays the default so `stats cleanup` keeps its old meaning.
+18 -3
View File
@@ -262,13 +262,28 @@ test('parseArgs rejects duplicate-line flags without the duplicate-lines mode',
assert.match(error.stderr, /--dry-run and --lookback-days require --duplicate-lines/);
});
test('parseArgs rejects combining lifetime and duplicate-line cleanup modes', () => {
test('parseArgs rejects an empty lookback value outside duplicate-line cleanup', () => {
const error = withProcessExitIntercept(() => {
parseArgs(['stats', 'cleanup', '--lifetime', '--duplicate-lines'], 'subminer', {});
parseArgs(['stats', '--lookback-days', ''], 'subminer', {});
});
assert.equal(error.code, 1);
assert.match(error.stderr, /Stats cleanup runs one mode at a time/);
assert.match(error.stderr, /--dry-run and --lookback-days require --duplicate-lines/);
});
test('parseArgs rejects combining explicit cleanup modes', () => {
for (const modes of [
['--lifetime', '--duplicate-lines'],
['--vocab', '--duplicate-lines'],
['--vocab', '--lifetime'],
]) {
const error = withProcessExitIntercept(() => {
parseArgs(['stats', 'cleanup', ...modes], 'subminer', {});
});
assert.equal(error.code, 1);
assert.match(error.stderr, /Stats cleanup runs one mode at a time/);
}
});
test('parseArgs rejects unusable lookback windows', () => {