mirror of
https://github.com/affaan-m/ECC.git
synced 2026-10-14 12:15:29 +02:00
Merge remote-tracking branch 'origin/pr-3523' into steward/late-arrivals-20261011
This commit is contained in:
@@ -12,8 +12,9 @@ function normalizeRepo(repo) {
|
||||
}
|
||||
|
||||
function normalizeIssueNumber(value) {
|
||||
const parsed = Number.parseInt(String(value), 10);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||
const text = String(value).trim();
|
||||
const parsed = Number(text);
|
||||
if (!/^\+?\d+$/.test(text) || !Number.isSafeInteger(parsed) || parsed <= 0) {
|
||||
throw new Error(`Invalid issue number: ${value}`);
|
||||
}
|
||||
return parsed;
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* Tests for scripts/lib/github-coordination/gh-api.js — normalizeIssueNumber
|
||||
*
|
||||
* Guards against silent truncation of malformed issue numbers
|
||||
* (https://github.com/affaan-m/ECC/issues/3514): parseInt-style prefix
|
||||
* parsing accepted "42oops" as 42, "42.9" as 42, and "4e2" as 4, while
|
||||
* integers beyond 2^53 lost precision (9007199254740993 became ...992).
|
||||
* The same helper also parses --limit, so malformed values there must be
|
||||
* rejected too. Whitespace, leading plus, and leading zero tolerance is
|
||||
* preserved.
|
||||
*
|
||||
* Run with: node tests/lib/github-coordination-gh-api.test.js
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const {
|
||||
normalizeIssueNumber,
|
||||
} = require('../../scripts/lib/github-coordination/gh-api');
|
||||
const { test, banner, section, summary } = require('./helpers/mini-test-runner');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
banner('normalizeIssueNumber — accepted forms:');
|
||||
|
||||
if (test('accepts a plain positive integer', () => {
|
||||
assert.strictEqual(normalizeIssueNumber('42'), 42);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('preserves surrounding whitespace tolerance', () => {
|
||||
assert.strictEqual(normalizeIssueNumber(' 7 '), 7);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('preserves leading plus tolerance', () => {
|
||||
assert.strictEqual(normalizeIssueNumber('+7'), 7);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('preserves leading zero tolerance', () => {
|
||||
assert.strictEqual(normalizeIssueNumber('007'), 7);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('accepts MAX_SAFE_INTEGER exactly', () => {
|
||||
assert.strictEqual(normalizeIssueNumber('9007199254740991'), 9007199254740991);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('accepts numeric (non-string) input', () => {
|
||||
assert.strictEqual(normalizeIssueNumber(42), 42);
|
||||
})) passed++; else failed++;
|
||||
|
||||
section('normalizeIssueNumber — malformed tokens are rejected, not truncated:');
|
||||
|
||||
for (const [value, label] of [
|
||||
['42oops', 'trailing garbage after digits'],
|
||||
['42.9', 'decimal fraction'],
|
||||
['4e2', 'exponent notation'],
|
||||
['0x10', 'hex notation'],
|
||||
['1e999', 'exponent beyond Number range'],
|
||||
['12 34', 'internal whitespace'],
|
||||
['-.', 'punctuation only'],
|
||||
]) {
|
||||
if (test(`rejects ${JSON.stringify(value)} (${label})`, () => {
|
||||
assert.throws(() => normalizeIssueNumber(value), /Invalid issue number/);
|
||||
})) passed++; else failed++;
|
||||
}
|
||||
|
||||
section('normalizeIssueNumber — unsafe integers are rejected:');
|
||||
|
||||
for (const value of [
|
||||
'9007199254740992', // 2^53
|
||||
'9007199254740993', // 2^53 + 1 (rounds to 2^53 as a Number)
|
||||
'999999999999999999999999',
|
||||
]) {
|
||||
if (test(`rejects ${value}`, () => {
|
||||
assert.throws(() => normalizeIssueNumber(value), /Invalid issue number/);
|
||||
})) passed++; else failed++;
|
||||
}
|
||||
|
||||
section('normalizeIssueNumber — non-positive and empty inputs:');
|
||||
|
||||
for (const value of ['0', '-3', '', ' ', 'abc']) {
|
||||
if (test(`rejects ${JSON.stringify(value)}`, () => {
|
||||
assert.throws(() => normalizeIssueNumber(value), /Invalid issue number/);
|
||||
})) passed++; else failed++;
|
||||
}
|
||||
|
||||
if (test('error message preserves the original input', () => {
|
||||
assert.throws(
|
||||
() => normalizeIssueNumber('42oops'),
|
||||
(error) => error.message === 'Invalid issue number: 42oops'
|
||||
);
|
||||
})) passed++; else failed++;
|
||||
|
||||
summary(passed, failed);
|
||||
@@ -93,6 +93,28 @@ async function runTests() {
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
if (
|
||||
await test('rejects malformed issue numbers during argument parsing', async () => {
|
||||
const { parseArgs } = require(SCRIPT);
|
||||
|
||||
for (const argv of [
|
||||
['node', 'github-coordination.js', 'claim', '42oops', '--repo', 'example/project'],
|
||||
['node', 'github-coordination.js', 'claim', '--issue', '42.9', '--repo', 'example/project'],
|
||||
['node', 'github-coordination.js', 'claim', '--issue', '4e2', '--repo', 'example/project'],
|
||||
['node', 'github-coordination.js', 'claim', '--issue', '9007199254740993', '--repo', 'example/project'],
|
||||
['node', 'github-coordination.js', 'claim', '--limit', '42.9', '--repo', 'example/project'],
|
||||
]) {
|
||||
assert.throws(() => parseArgs(argv), /Invalid issue number/);
|
||||
}
|
||||
|
||||
const valid = parseArgs(['node', 'github-coordination.js', 'claim', '7', '--repo', 'example/project']);
|
||||
assert.strictEqual(valid.issueNumber, 7);
|
||||
assert.strictEqual(valid.limit, 100);
|
||||
})
|
||||
)
|
||||
passed++;
|
||||
else failed++;
|
||||
|
||||
if (
|
||||
await test('claims an epic issue, updates GitHub state, and caches a work item', async () => {
|
||||
const rootDir = createTempDir('github-coordination-claim-');
|
||||
|
||||
Reference in New Issue
Block a user