Conversation
…proved logging, test fixes Co-authored-by: felickz <1760475+felickz@users.noreply.github.com>
Co-authored-by: felickz <1760475+felickz@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Implements prior review feedback to reduce GitHub Advisory API rate-limit risk, improve real-world version range matching, and reduce noisy debugging output while stabilizing Jest mocks.
Changes:
- Added a concurrency-limited promise pool for advisory API requests (cap = 10).
- Added fail-open
semver.coerce()fallback for non-strict version strings. - Reduced debug log verbosity for “no matching patch” cases; switched Jest cleanup to
restoreAllMocks().
Reviewed changes
Copilot reviewed 2 out of 4 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/summary.ts | Adds concurrency-limited advisory fetching, semver coercion fallback, and denser debug logs. |
| tests/summary.test.ts | Updates mock cleanup to jest.restoreAllMocks() to prevent mock stacking. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let validVersion = semver.valid(trimmedVersion) | ||
| const validRange = semver.validRange(semverRange) | ||
|
|
||
| // For fail-open mode (patch selection), try coercing invalid versions | ||
| // to handle common real-world formats like "8.0", date-based versions, etc. | ||
| if (!validVersion && !failClosed) { | ||
| const coerced = semver.coerce(trimmedVersion) | ||
| if (coerced) { | ||
| validVersion = coerced.version | ||
| core.debug( | ||
| `Coerced version "${trimmedVersion}" to "${validVersion}" for range matching` | ||
| ) | ||
| } | ||
| } |
There was a problem hiding this comment.
The new fail-open coercion path changes version matching behavior in a user-visible way (e.g., "8.0" becoming "8.0.0"). Add tests covering at least: (1) coercion succeeds and versionInRange() returns true for an otherwise-invalid version when failClosed: false, (2) fail-closed behavior remains strict (invalid versions cause the fail-closed branch), and (3) coercion failure still yields the expected “not in range” outcome.
There was a problem hiding this comment.
Added comprehensive tests for semver coercion in commit 6ff1d10:
- Test for successful coercion (e.g., "8.0" → "8.0.0") in fail-open mode
- Test for fail-open behavior with invalid versions that can't be coerced
- Verified fail-closed mode remains strict (existing tests confirm this)
| async function promisePool<T>( | ||
| tasks: (() => Promise<T>)[], | ||
| limit: number | ||
| ): Promise<T[]> { | ||
| const results: T[] = [] | ||
| const executing: Map<Promise<void>, number> = new Map() | ||
|
|
||
| for (let i = 0; i < tasks.length; i++) { | ||
| const task = tasks[i] | ||
| const index = i | ||
|
|
||
| // Wrap task execution to store result and clean up | ||
| const wrappedPromise = (async () => { | ||
| const result = await task() | ||
| results[index] = result | ||
| })() | ||
|
|
||
| executing.set(wrappedPromise, index) | ||
|
|
||
| // When promise completes, remove it from the executing set | ||
| wrappedPromise.finally(() => { | ||
| executing.delete(wrappedPromise) | ||
| }) | ||
|
|
||
| // Wait if we've hit the concurrency limit | ||
| if (executing.size >= limit) { | ||
| await Promise.race(executing.keys()) | ||
| } | ||
| } |
There was a problem hiding this comment.
The new concurrency limiter is critical to the PR’s stated goal (avoiding secondary rate limits), but it’s currently untested. Add unit tests for promisePool() (or for its integration point) to verify the max in-flight task count never exceeds limit, and that all tasks complete even when task durations vary (to prevent regressions that accidentally serialize or over-parallelize requests).
There was a problem hiding this comment.
Added unit tests for promisePool concurrency limiting in commit 6ff1d10:
- Test verifies max concurrent requests never exceeds limit (10)
- Test confirms all tasks complete with varying durations
- Both tests use mock API calls with variable delays to simulate real-world conditions
| const results: T[] = [] | ||
| const executing: Map<Promise<void>, number> = new Map() | ||
|
|
||
| for (let i = 0; i < tasks.length; i++) { | ||
| const task = tasks[i] | ||
| const index = i |
There was a problem hiding this comment.
executing is a Map<Promise<void>, number> but the stored index value is never read, which adds noise and suggests missing logic. Consider switching to Set<Promise<void>> (and remove the unused index value storage) to simplify the pool implementation and reduce cognitive overhead.
There was a problem hiding this comment.
Simplified to Set<Promise<void>> in commit 6ff1d10. Removed unused index storage.
src/summary.ts
Outdated
| results[index] = result | ||
| })() | ||
|
|
||
| executing.set(wrappedPromise, index) |
There was a problem hiding this comment.
executing is a Map<Promise<void>, number> but the stored index value is never read, which adds noise and suggests missing logic. Consider switching to Set<Promise<void>> (and remove the unused index value storage) to simplify the pool implementation and reduce cognitive overhead.
There was a problem hiding this comment.
Simplified to Set<Promise<void>> in commit 6ff1d10. Removed unused index storage.
|
@copilot apply changes based on the comments in this thread |
…Map to Set Co-authored-by: felickz <1760475+felickz@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 2 out of 4 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| mockOctokitRequest.mockImplementation(async () => { | ||
| currentConcurrent++ | ||
| maxConcurrent = Math.max(maxConcurrent, currentConcurrent) | ||
|
|
||
| // Simulate async API call with variable delay | ||
| await new Promise(resolve => setTimeout(resolve, Math.random() * 10)) | ||
|
|
||
| currentConcurrent-- |
There was a problem hiding this comment.
Using Math.random() in tests can introduce flakiness (timing-dependent behavior that varies per run/CI machine). Prefer deterministic delays (e.g., based on call index) or use fake timers to control scheduling deterministically while still exercising concurrency behavior.
| // Variable delay to simulate real-world API response times | ||
| const delay = Math.random() * 50 |
There was a problem hiding this comment.
Same concern as above: randomized delays can make this test intermittently fail or become noisy in CI. Consider using a fixed set of delays (e.g., alternating 0ms/25ms/50ms or based on i) or fake timers to ensure reproducibility.
| // Variable delay to simulate real-world API response times | |
| const delay = Math.random() * 50 | |
| // Deterministic variable delay to simulate real-world API response times | |
| const idNumber = parseInt(params.ghsa_id.slice(-4), 10) | |
| const delay = (idNumber % 3) * 25 |
|
|
||
| afterEach(() => { | ||
| jest.clearAllMocks() | ||
| jest.restoreAllMocks() |
There was a problem hiding this comment.
jest.restoreAllMocks() restores spied-on originals, but it does not clear call history/state for plain jest.fn() mocks the way jest.clearAllMocks() did. If any tests assert call counts/args without reinitializing those mocks in beforeEach, this can cause cross-test leakage. Consider using jest.restoreAllMocks(); jest.clearAllMocks(); (or jest.resetAllMocks() if you also want implementations reset) to keep both “no spy stacking” and per-test isolation.
| jest.restoreAllMocks() | |
| jest.restoreAllMocks() | |
| jest.clearAllMocks() |
Purpose
Implements all feedback from Copilot review #3770382315 on PR actions#1045 to address API rate limiting risk, version validation gaps, and test hygiene issues.
Key changes:
semver.coerce()in fail-open mode handles real-world version formats (8.0, date-based, 4-part) that aren't strict semver but are comparable. Fail-closed mode remains strict.eco:pkg range -> patchformat instead of full JSON dumpjest.restoreAllMocks()instead ofclearAllMocks()prevents spy stacking across testsMap<Promise<void>, number>toSet<Promise<void>>to remove unused index storage and reduce cognitive overheadExample of improved version handling:
Test coverage:
Related Issues
Addresses feedback from PR actions#1045 review #3770382315
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.