From 6e2629b92f6faf7a0ffcdaded56a1b8913df674d Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Fri, 17 Apr 2026 14:03:45 +0100 Subject: [PATCH 01/27] Remove implementation-testing log verifications from unit tests Strips log-verification assertions that tested internal implementation details (method-entry tracing, JSON response dumps, mid-flow checkpoints, cross-cutting info-log enumerations, redundant log verifies) while preserving all tests of observable behaviour. - Removes 2,640 lines across 11 spec files (~15.7% reduction) - All 783 tests still pass - 100% coverage preserved (statements, branches, functions, lines) Adds TestPlan.md documenting the broader unit test improvement strategy. --- TestPlan.md | 272 ++++ src/task/tests/git/gitInvoker.spec.ts | 172 --- .../tests/git/octokitGitDiffParser.spec.ts | 82 -- src/task/tests/metrics/codeMetrics.spec.ts | 398 +----- .../metrics/codeMetricsCalculator.spec.ts | 47 - src/task/tests/metrics/inputs.spec.ts | 1124 +---------------- .../tests/pullRequests/pullRequest.spec.ts | 17 +- .../pullRequests/pullRequestComments.spec.ts | 127 +- .../tests/repos/azureReposInvoker.spec.ts | 174 --- .../tests/repos/gitHubReposInvoker.spec.ts | 296 ----- src/task/tests/repos/reposInvoker.spec.ts | 82 -- src/task/tests/repos/tokenManager.spec.ts | 127 -- 12 files changed, 277 insertions(+), 2641 deletions(-) create mode 100644 TestPlan.md diff --git a/TestPlan.md b/TestPlan.md new file mode 100644 index 000000000..3b952f299 --- /dev/null +++ b/TestPlan.md @@ -0,0 +1,272 @@ +# Unit Test Improvement Plan + +This plan proposes concrete improvements to the PR Metrics unit test suite. The +current suite provides strong coverage, which is vital given that end-to-end +production testing before release is infeasible. The goal here is to reduce +maintenance cost _without_ reducing coverage, so that future refactors become +cheaper and the safety net remains intact. + +## Diagnostic Summary + +Quantitative picture of the current suite: + +- 23 spec files, approximately 16,820 lines total. +- Four files over 1,800 lines: `inputs.spec.ts` (2,175), `codeMetrics.spec.ts` + (2,065), `azureReposInvoker.spec.ts` (2,042), and `gitHubReposInvoker.spec.ts` + (1,848). +- 1,118 `verify(logger.log...)` calls across 12 files. +- 302 `process.env` manipulations across 9 files. +- 297 SUT constructor calls across 12 files, roughly one per test case. + +The core problem: tests are coupled to implementation, not behaviour. Every +refactor of a private method, every rename of an internal helper, and every new +log line cascades into dozens of test edits. That is the maintenance cost +currently being felt. + +## Guiding Principles + +1. **Preserve Coverage**: Every change must keep coverage equal or better. + Branches and data cases must all still be exercised. +2. **Test Observable Behaviour, Not Tracing**: Debug log calls of the form + `* Class.method()` are internal tracing. Asserting them locks private method + names into the test suite. +3. **Shift Invariants to Property Tests**: `fast-check` is already in the + dependency set. Many exhaustive `forEach` data tables are invariants in + disguise. +4. **Move Integration Testing Up One Layer**: End-to-end-before-release is + impossible. This argues for a small in-process integration tier that wires + real components with faked external I/O, not for more mock-heavy unit tests. +5. **Factor Shared Test Infrastructure**: The duplication of mock setup, + localisation mocks, environment-variable handling, and SUT construction is + the single biggest source of line count. + +## Concrete Proposals + +### 1. Stop Verifying Debug-Trace Log Calls + +**Observation**: Roughly 800 to 900 of the 1,118 `verify(logger.log...)` calls +assert `* ClassName.methodName()` tracing. For example, a single test in +`inputs.spec.ts` (lines 162 to 202) verifies 30+ log lines including counts for +every private initialiser. + +**Proposal**: + +- Delete all `verify(logger.logDebug("* X.y()"))` assertions. +- Keep `verify(logger.logWarning(...))` and `verify(logger.logError(...))` + assertions; those are observable and meaningful. +- Keep `verify(logger.logInfo(...))` only when the info message is part of the + contract, such as user-facing progress reporting. +- Optionally, verify that tracing exists via a single test per class ("emits + entry trace when called") using `ts-mockito`'s `atLeast(1)` with a regex + matcher, rather than exhaustively per test. + +**Rationale**: Debug tracing is a cross-cutting side effect. It is useful in +production but is not the SUT's contract. Asserting it forces tests to know +about private method names and call ordering. Removing these saves roughly +5,000 lines and unblocks refactoring. + +**Coverage Impact**: None. Coverage measures code execution; these assertions +do not add execution. + +### 2. Replace Localisation Mock `beforeEach` Blocks With a Helper + +**Observation**: `codeMetrics.spec.ts` has a 186-line `beforeEach` that stubs +every `(size, coverage)` combination. `pullRequest.spec.ts` similarly has +roughly 100 lines of `runnerInvoker.loc(...)` stubs. The stubs are largely +identity mappings, such as `"titleSizeXS"` returning `"XS"`. + +**Proposal**: + +- Add `tests/testUtilities/localisationMock.ts` that reads the actual + `resources.resjson` file and wires `runnerInvoker.loc()` to return real + values. +- Each spec file replaces its 100 to 186 lines with one line, for example + `stubLocalisation(runnerInvoker)`. + +**Rationale**: The current code re-implements the resource file in mock stubs, +then tests against those mocked values. It is testing the mock. Reading the +real resource file removes the duplication and makes the tests resilient to +resource file changes. Localisation is a stable, low-risk boundary to treat as +real in tests. + +**Coverage Impact**: None. The localisation wiring is not exercised by those +stubs anyway. + +### 3. Replace `process.env` Juggling With a Scoped Helper + +**Observation**: 302 manual `process.env.X = ...` and +`delete process.env.X` pairs exist across tests. Tests often set up in an +Arrange section, repeat in a Finalisation section, and rely on `beforeEach` +and `afterEach` to clean up. Some files miss cleanups, introducing +cross-test leak risk. + +**Proposal**: + +- Add `tests/testUtilities/envSandbox.ts` exporting `withEnv(overrides, fn)` + and `stubEnv(overrides)`, the latter auto-restoring in `afterEach` via a + global hook. +- Tests become + `stubEnv({ GITHUB_ACTION: "PR-Metrics", GITHUB_REF: "refs/pull/12345/merge" })` + with no cleanup required. + +**Rationale**: Deterministic cleanup, no cross-test leak, and elimination of +the finalisation boilerplate. The existing code in `gitInvoker.spec.ts` that +sets environment variables and then deletes them at the end of each test +(lines 89 to 90, 114 to 115, 142 to 143, 174 to 175, and elsewhere) is pure +noise. + +### 4. Split Oversized Files by Describe Block + +**Proposal**: + +- `codeMetrics.spec.ts` (2,065 lines): split into + `codeMetrics.sizeIndicator.spec.ts`, `codeMetrics.testCoverage.spec.ts`, + `codeMetrics.fileMatching.spec.ts`, and so on, based on the existing + `describe` blocks. +- `inputs.spec.ts` (2,175 lines): split per input, for example + `inputs.baseSize.spec.ts`, `inputs.growthRate.spec.ts`, and + `inputs.testFactor.spec.ts`. +- `azureReposInvoker.spec.ts` and `gitHubReposInvoker.spec.ts`: split per + public method. + +**Rationale**: Files over roughly 800 lines are hard to navigate and lead to +copy-paste rot. Smaller files also parallelise better in Mocha and make test +failures easier to triage. + +**Coverage Impact**: None. Tests just move. + +### 5. Introduce Test Data Builders + +**Proposal**: Add `tests/testUtilities/builders/` with factories such as +`aCodeMetricsData()`, `aPullRequestDetails()`, and `aCommentData()`: + +```typescript +const data = aCodeMetricsData().withProductCode(400).withTestCode(0).build(); +``` + +**Rationale**: Tests currently pass raw constructor positional arguments such +as `new CodeMetricsData(400, 399, 0)`, forcing the reader to count commas. +Builders give each test a one-liner that reads as intent. + +### 6. Extract a SUT Factory Per Spec + +**Proposal**: Each spec file adds a local `createSut(overrides?)` that +constructs the SUT with all its dependency instances. Tests become: + +```typescript +const sut = createSut(); +// or, with a swap: +const sut = createSut({ gitInvoker: customGitInvoker }); +``` + +**Rationale**: 297 SUT `new X(...)` calls across tests. One factory per spec +collapses that to one constructor call site and makes dependency swaps +obvious. + +### 7. Expand Property-Based Testing to Replace Exhaustive Tables + +**Observation**: Many `forEach` tables are property tests in disguise. For +example, the size-indicator boundaries in `codeMetrics.spec.ts` can be stated +as: "for any `productCode` in `[0, baseSize)`, the indicator is `XS`." The +invalid-input tables in `inputs.spec.ts` can be stated as: "for any string +that does not parse to a positive number, the default is returned." + +**Proposal**: + +- Keep example-based tests for boundaries (199 vs 200, 399 vs 400); those + document the contract. +- Replace bulk interior cases with property tests. A single `fc.property` with + 100 runs covers more than 20 hand-picked `forEach` entries. +- Add property files for `inputs.ts`, `codeMetrics.ts` size bands, and file + pattern matching. + +**Rationale**: Property tests are shorter, catch edge cases that hand-picked +tables miss, and are less coupled to specific values. `fast-check` is already +a dependency; deepen that investment. + +### 8. Add a Small In-Process Integration Tier – THIS WON'T WORK, SKIP THIS + +**Observation**: There is a gap between 23 mock-heavy unit spec files and +manual test instructions in `manualTests/`. + +**Proposal**: Add `tests/integration/` with 5 to 10 tests that exercise +`pullRequestMetrics.run()` end-to-end with: + +- Real `Inputs`, `CodeMetrics`, `Logger`, `RunnerInvoker`, and + `PullRequestComments`. +- Faked `RunnerInvoker.exec` (canned Git output) and `ReposInvoker` (in-memory). +- Golden scenarios such as "small PR with tests", "XL PR without tests", and + "PR with ignored files". + +**Rationale**: This is the layer that catches regressions where unit tests +pass but integration is broken. Given that end-to-end production testing +before release is impossible, this is the insurance policy. Unit tests verify +behaviour of pieces; integration tests verify the pieces compose correctly. + +**Coverage Impact**: Strictly additive. + +### 9. Consolidate Common Invalid-Input Fixtures + +**Proposal**: A single `tests/testUtilities/fixtures/invalidInputs.ts` exports +shared sets such as `INVALID_STRINGS = [null, "", " ", "abc", "==="]` and +`NEGATIVE_NUMBERS = ["0", "-1", "-1000"]`. Specs import them. + +**Rationale**: `validator.spec.ts` lines 12 and 50, `inputs.spec.ts` lines +293 to 302 and 487 to 496, and other locations enumerate overlapping "invalid +string" sets. Sharing prevents drift and captures domain knowledge once. + +### 10. Document a Testing Style Guide –DON'T BOTHER + +**Proposal**: Add `docs/testing.md`, or a section in `AGENTS.md`, that +codifies: + +- When to mock a collaborator and when to use the real thing. +- The rule that debug-trace log calls are not asserted. +- The preference for property tests for invariants, and example tests for + boundaries. +- How to use the new helpers (`withEnv`, `stubLocalisation`, builders, and + `createSut`). +- The distinction between the integration tier and the unit tier. + +**Rationale**: Prevents regression to the current style once the helpers +exist. + +## Prioritisation + +Ranked by impact per unit of work: + +1. **Remove Debug-Trace Log Assertions**: Biggest single win on maintenance + burden (roughly 5,000 lines removed, refactors unblocked). Low risk, + preserves coverage. Do first. +2. **`stubLocalisation` Helper**: Eliminates the largest single block of + setup boilerplate. Low risk. +3. **`withEnv` and `stubEnv` Helpers**: Removes roughly 600 lines and a class + of cross-test-leak bugs. +4. **SUT Factory and Builders**: Tidies remaining code and enables later + splits. +5. **Split Oversized Files**: Do after steps 1 to 4 so splits happen on clean + code. +6. **Expand Property Tests**: Higher engineering investment; do last among + the unit-suite items. +7. **Integration Tier**: Separate project; worth doing but orthogonal to the + unit-test cleanup. +8. **Consolidate Invalid-Input Fixtures and Style Guide**: Cleanup sweep. + +Rough estimate: steps 1 to 4 alone cut the test codebase by roughly 30% and +eliminate the "one private method rename equals 50 test edits" problem. + +## Open Questions + +Two items to resolve before executing the plan: + +1. **Intent Behind Debug-Trace Assertions**: The current approach treats + tracing assertions as accidental complexity. If the `* Class.method()` + tracing was added deliberately to verify call paths (for instance, to + confirm that the correct helper was reached in routing logic), proposal 1 + should be narrowed: keep trace verification only where the call path + itself is the thing being tested (probably a handful of cases in + `reposInvoker.spec.ts`) and delete the rest. +2. **Scope Preference**: One big design document covering all 10 proposals, + or a two-phase split where phase A covers the high-impact, low-risk + cleanup (proposals 1 to 4) and phase B covers the deeper changes + (proposals 5 to 10) as a follow-up once the ground is clear. diff --git a/src/task/tests/git/gitInvoker.spec.ts b/src/task/tests/git/gitInvoker.spec.ts index d02f5b846..d99117ca9 100644 --- a/src/task/tests/git/gitInvoker.spec.ts +++ b/src/task/tests/git/gitInvoker.spec.ts @@ -81,9 +81,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, 12345); - verify(logger.logDebug("* GitInvoker.pullRequestId")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdForGitHub")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -106,9 +103,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result1, 12345); assert.equal(result2, 12345); - verify(logger.logDebug("* GitInvoker.pullRequestId")).twice(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdForGitHub")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -134,9 +128,6 @@ describe("gitInvoker.ts", (): void => { ), ); verify(logger.logWarning("'GITHUB_REF' is undefined.")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestId")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdForGitHub")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -166,9 +157,6 @@ describe("gitInvoker.ts", (): void => { "'GITHUB_REF' is in an incorrect format 'refs/pull'.", ), ).once(); - verify(logger.logDebug("* GitInvoker.pullRequestId")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdForGitHub")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -189,9 +177,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, 12345); - verify(logger.logDebug("* GitInvoker.pullRequestId")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdForGitHub")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -219,11 +204,6 @@ describe("gitInvoker.ts", (): void => { verify( logger.logWarning("'BUILD_REPOSITORY_PROVIDER' is undefined."), ).once(); - verify(logger.logDebug("* GitInvoker.pullRequestId")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); }); it("should throw an error when the Azure Pipelines runner is being used and SYSTEM_PULLREQUEST_PULLREQUESTID is undefined", (): void => { @@ -247,11 +227,6 @@ describe("gitInvoker.ts", (): void => { verify( logger.logWarning("'SYSTEM_PULLREQUEST_PULLREQUESTID' is undefined."), ).once(); - verify(logger.logDebug("* GitInvoker.pullRequestId")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); }); it("should throw an error when the Azure Pipelines runner is being used and SYSTEM_PULLREQUEST_PULLREQUESTID is not numeric", (): void => { @@ -277,11 +252,6 @@ describe("gitInvoker.ts", (): void => { "'SYSTEM_PULLREQUEST_PULLREQUESTID' is not numeric 'abc'.", ), ).once(); - verify(logger.logDebug("* GitInvoker.pullRequestId")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); }); { @@ -311,11 +281,6 @@ describe("gitInvoker.ts", (): void => { "'SYSTEM_PULLREQUEST_PULLREQUESTNUMBER' is undefined.", ), ).once(); - verify(logger.logDebug("* GitInvoker.pullRequestId")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -347,9 +312,6 @@ describe("gitInvoker.ts", (): void => { "Pull request ID 'PullRequestID' from 'GITHUB_REF' is not numeric.", ), ).once(); - verify(logger.logDebug("* GitInvoker.pullRequestId")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdForGitHub")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -384,11 +346,6 @@ describe("gitInvoker.ts", (): void => { "'SYSTEM_PULLREQUEST_PULLREQUESTNUMBER' is not numeric 'abc'.", ), ).once(); - verify(logger.logDebug("* GitInvoker.pullRequestId")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -428,8 +385,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, true); - verify(logger.logDebug("* GitInvoker.isGitRepo()")).once(); - verify(logger.logDebug("* GitInvoker.invokeGit()")).once(); }); }); } @@ -459,8 +414,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, false); - verify(logger.logDebug("* GitInvoker.isGitRepo()")).once(); - verify(logger.logDebug("* GitInvoker.invokeGit()")).once(); }); }); @@ -479,9 +432,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, true); - verify(logger.logDebug("* GitInvoker.isPullRequestIdAvailable()")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdForGitHub")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -502,9 +452,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, false); verify(logger.logWarning("'GITHUB_REF' is undefined.")).once(); - verify(logger.logDebug("* GitInvoker.isPullRequestIdAvailable()")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdForGitHub")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -529,9 +476,6 @@ describe("gitInvoker.ts", (): void => { "'GITHUB_REF' is in an incorrect format 'refs/pull'.", ), ).once(); - verify(logger.logDebug("* GitInvoker.isPullRequestIdAvailable()")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdForGitHub")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -552,9 +496,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, true); - verify(logger.logDebug("* GitInvoker.isPullRequestIdAvailable()")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdForGitHub")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -577,11 +518,6 @@ describe("gitInvoker.ts", (): void => { verify( logger.logWarning("'BUILD_REPOSITORY_PROVIDER' is undefined."), ).once(); - verify(logger.logDebug("* GitInvoker.isPullRequestIdAvailable()")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); }); it("should throw an error when the Azure Pipelines runner is being used and SYSTEM_PULLREQUEST_PULLREQUESTID is undefined", (): void => { @@ -600,11 +536,6 @@ describe("gitInvoker.ts", (): void => { verify( logger.logWarning("'SYSTEM_PULLREQUEST_PULLREQUESTID' is undefined."), ).once(); - verify(logger.logDebug("* GitInvoker.isPullRequestIdAvailable()")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); }); { @@ -629,13 +560,6 @@ describe("gitInvoker.ts", (): void => { "'SYSTEM_PULLREQUEST_PULLREQUESTNUMBER' is undefined.", ), ).once(); - verify( - logger.logDebug("* GitInvoker.isPullRequestIdAvailable()"), - ).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -657,9 +581,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, false); - verify(logger.logDebug("* GitInvoker.isPullRequestIdAvailable()")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdForGitHub")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -681,14 +602,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, true); - verify(logger.logDebug("* GitInvoker.isGitHistoryAvailable()")).once(); - verify(logger.logDebug("* GitInvoker.initialize()")).once(); - verify(logger.logDebug("* GitInvoker.targetBranch")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); - verify(logger.logDebug("* GitInvoker.invokeGit()")).once(); }); it("should return true when the Git history is available and the method is called after retrieving the pull request ID", async (): Promise => { @@ -706,15 +619,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result1, 12345); assert.equal(result2, true); - verify(logger.logDebug("* GitInvoker.pullRequestId")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); - verify(logger.logDebug("* GitInvoker.isGitHistoryAvailable()")).once(); - verify(logger.logDebug("* GitInvoker.initialize()")).once(); - verify(logger.logDebug("* GitInvoker.targetBranch")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).twice(); - verify(logger.logDebug("* GitInvoker.invokeGit()")).once(); }); it("should return true when the Git history is available and the PR is using the GitHub runner", async (): Promise => { @@ -732,12 +636,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, true); - verify(logger.logDebug("* GitInvoker.isGitHistoryAvailable()")).once(); - verify(logger.logDebug("* GitInvoker.initialize()")).once(); - verify(logger.logDebug("* GitInvoker.targetBranch")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdForGitHub")).once(); - verify(logger.logDebug("* GitInvoker.invokeGit()")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -762,9 +660,6 @@ describe("gitInvoker.ts", (): void => { func, "'GITHUB_BASE_REF', accessed within 'GitInvoker.targetBranch', is invalid, null, or undefined 'undefined'.", ); - verify(logger.logDebug("* GitInvoker.isGitHistoryAvailable()")).once(); - verify(logger.logDebug("* GitInvoker.initialize()")).once(); - verify(logger.logDebug("* GitInvoker.targetBranch")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -790,16 +685,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, true); - verify( - logger.logDebug("* GitInvoker.isGitHistoryAvailable()"), - ).once(); - verify(logger.logDebug("* GitInvoker.initialize()")).once(); - verify(logger.logDebug("* GitInvoker.targetBranch")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); - verify(logger.logDebug("* GitInvoker.invokeGit()")).once(); // Finalization delete process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER; @@ -837,14 +722,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, false); - verify(logger.logDebug("* GitInvoker.isGitHistoryAvailable()")).once(); - verify(logger.logDebug("* GitInvoker.initialize()")).once(); - verify(logger.logDebug("* GitInvoker.targetBranch")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); - verify(logger.logDebug("* GitInvoker.invokeGit()")).once(); }); it("should return true when the Git history is available and the method is called twice", async (): Promise => { @@ -861,14 +738,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result1, true); assert.equal(result2, true); - verify(logger.logDebug("* GitInvoker.isGitHistoryAvailable()")).twice(); - verify(logger.logDebug("* GitInvoker.initialize()")).twice(); - verify(logger.logDebug("* GitInvoker.targetBranch")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); - verify(logger.logDebug("* GitInvoker.invokeGit()")).twice(); }); it("should throw an error when SYSTEM_PULLREQUEST_TARGETBRANCH is undefined", async (): Promise => { @@ -888,9 +757,6 @@ describe("gitInvoker.ts", (): void => { func, "'SYSTEM_PULLREQUEST_TARGETBRANCH', accessed within 'GitInvoker.targetBranch', is invalid, null, or undefined 'undefined'.", ); - verify(logger.logDebug("* GitInvoker.isGitHistoryAvailable()")).once(); - verify(logger.logDebug("* GitInvoker.initialize()")).once(); - verify(logger.logDebug("* GitInvoker.targetBranch")).once(); }); it("should throw an error when the target branch contains whitespace", async (): Promise => { @@ -910,9 +776,6 @@ describe("gitInvoker.ts", (): void => { func, "Target branch 'main branch' contains whitespace or control characters, which is not allowed in command-line arguments.", ); - verify(logger.logDebug("* GitInvoker.isGitHistoryAvailable()")).once(); - verify(logger.logDebug("* GitInvoker.initialize()")).once(); - verify(logger.logDebug("* GitInvoker.targetBranch")).once(); }); }); @@ -929,14 +792,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, "1\t2\tFile.txt"); - verify(logger.logDebug("* GitInvoker.getDiffSummary()")).once(); - verify(logger.logDebug("* GitInvoker.initialize()")).once(); - verify(logger.logDebug("* GitInvoker.targetBranch")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); - verify(logger.logDebug("* GitInvoker.invokeGit()")).once(); }); it("should return the correct output when no error occurs and the target branch is in the GitHub format", async (): Promise => { @@ -952,14 +807,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, "1\t2\tFile.txt"); - verify(logger.logDebug("* GitInvoker.getDiffSummary()")).once(); - verify(logger.logDebug("* GitInvoker.initialize()")).once(); - verify(logger.logDebug("* GitInvoker.targetBranch")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); - verify(logger.logDebug("* GitInvoker.invokeGit()")).once(); }); it("should return the correct output when no error occurs and the method is called twice", async (): Promise => { @@ -975,14 +822,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, "1\t2\tFile.txt"); - verify(logger.logDebug("* GitInvoker.getDiffSummary()")).twice(); - verify(logger.logDebug("* GitInvoker.initialize()")).twice(); - verify(logger.logDebug("* GitInvoker.targetBranch")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); - verify(logger.logDebug("* GitInvoker.invokeGit()")).twice(); }); it("should throw an error when SYSTEM_PULLREQUEST_TARGETBRANCH is undefined", async (): Promise => { @@ -1002,9 +841,6 @@ describe("gitInvoker.ts", (): void => { func, "'SYSTEM_PULLREQUEST_TARGETBRANCH', accessed within 'GitInvoker.targetBranch', is invalid, null, or undefined 'undefined'.", ); - verify(logger.logDebug("* GitInvoker.getDiffSummary()")).once(); - verify(logger.logDebug("* GitInvoker.initialize()")).once(); - verify(logger.logDebug("* GitInvoker.targetBranch")).once(); }); it("should throw an error when Git invocation fails", async (): Promise => { @@ -1038,14 +874,6 @@ describe("gitInvoker.ts", (): void => { // Assert await AssertExtensions.toThrowAsync(func, "Failure"); - verify(logger.logDebug("* GitInvoker.getDiffSummary()")).once(); - verify(logger.logDebug("* GitInvoker.initialize()")).once(); - verify(logger.logDebug("* GitInvoker.targetBranch")).once(); - verify(logger.logDebug("* GitInvoker.pullRequestIdInternal")).once(); - verify( - logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines"), - ).once(); - verify(logger.logDebug("* GitInvoker.invokeGit()")).once(); }); }); }); diff --git a/src/task/tests/git/octokitGitDiffParser.spec.ts b/src/task/tests/git/octokitGitDiffParser.spec.ts index e66c0cbe2..753588f5b 100644 --- a/src/task/tests/git/octokitGitDiffParser.spec.ts +++ b/src/task/tests/git/octokitGitDiffParser.spec.ts @@ -120,16 +120,6 @@ describe("octokitGitDiffParser.ts", (): void => { // Assert assert.equal(result, lineNumber); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLine()"), - ).once(); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLines()"), - ).once(); - verify(logger.logDebug("* OctokitGitDiffParser.getDiffs()")).once(); - verify( - logger.logDebug("* OctokitGitDiffParser.processDiffs()"), - ).once(); }); }, ); @@ -175,14 +165,6 @@ describe("octokitGitDiffParser.ts", (): void => { // Assert assert.equal(result, 11); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLine()"), - ).once(); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLines()"), - ).once(); - verify(logger.logDebug("* OctokitGitDiffParser.getDiffs()")).once(); - verify(logger.logDebug("* OctokitGitDiffParser.processDiffs()")).once(); }); it("should return the correct line number when considering a renamed file with no changes", async (): Promise => { @@ -219,14 +201,6 @@ describe("octokitGitDiffParser.ts", (): void => { // Assert assert.equal(result, null); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLine()"), - ).once(); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLines()"), - ).once(); - verify(logger.logDebug("* OctokitGitDiffParser.getDiffs()")).once(); - verify(logger.logDebug("* OctokitGitDiffParser.processDiffs()")).once(); }); it("should return the correct line number when considering an added file", async (): Promise => { @@ -267,14 +241,6 @@ describe("octokitGitDiffParser.ts", (): void => { // Assert assert.equal(result, 1); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLine()"), - ).once(); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLines()"), - ).once(); - verify(logger.logDebug("* OctokitGitDiffParser.getDiffs()")).once(); - verify(logger.logDebug("* OctokitGitDiffParser.processDiffs()")).once(); }); it("should return null when considering a deleted file", async (): Promise => { @@ -314,14 +280,6 @@ describe("octokitGitDiffParser.ts", (): void => { // Assert assert.equal(result, null); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLine()"), - ).once(); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLines()"), - ).once(); - verify(logger.logDebug("* OctokitGitDiffParser.getDiffs()")).once(); - verify(logger.logDebug("* OctokitGitDiffParser.processDiffs()")).once(); verify( logger.logDebug( "Skipping file type 'DeletedFile' while performing diff parsing.", @@ -363,14 +321,6 @@ describe("octokitGitDiffParser.ts", (): void => { // Assert assert.equal(result, null); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLine()"), - ).once(); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLines()"), - ).once(); - verify(logger.logDebug("* OctokitGitDiffParser.getDiffs()")).once(); - verify(logger.logDebug("* OctokitGitDiffParser.processDiffs()")).once(); verify( logger.logDebug( "Skipping 'AddedFile' 'file.png' while performing diff parsing.", @@ -411,14 +361,6 @@ describe("octokitGitDiffParser.ts", (): void => { // Assert assert.equal(result, null); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLine()"), - ).once(); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLines()"), - ).once(); - verify(logger.logDebug("* OctokitGitDiffParser.getDiffs()")).once(); - verify(logger.logDebug("* OctokitGitDiffParser.processDiffs()")).once(); verify( logger.logDebug( "Skipping 'ChangedFile' 'file.png' while performing diff parsing.", @@ -462,14 +404,6 @@ describe("octokitGitDiffParser.ts", (): void => { // Assert assert.equal(result, null); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLine()"), - ).once(); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLines()"), - ).once(); - verify(logger.logDebug("* OctokitGitDiffParser.getDiffs()")).once(); - verify(logger.logDebug("* OctokitGitDiffParser.processDiffs()")).once(); verify( logger.logDebug( "Skipping 'RenamedFile' 'file.png' while performing diff parsing.", @@ -523,14 +457,6 @@ describe("octokitGitDiffParser.ts", (): void => { // Assert assert.equal(result1, 11); assert.equal(result2, 11); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLine()"), - ).twice(); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLines()"), - ).twice(); - verify(logger.logDebug("* OctokitGitDiffParser.getDiffs()")).once(); - verify(logger.logDebug("* OctokitGitDiffParser.processDiffs()")).once(); }); it("should return null when an unknown file is specified", async (): Promise => { @@ -570,14 +496,6 @@ describe("octokitGitDiffParser.ts", (): void => { // Assert assert.equal(result, null); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLine()"), - ).once(); - verify( - logger.logDebug("* OctokitGitDiffParser.getFirstChangedLines()"), - ).once(); - verify(logger.logDebug("* OctokitGitDiffParser.getDiffs()")).once(); - verify(logger.logDebug("* OctokitGitDiffParser.processDiffs()")).once(); }); }); }); diff --git a/src/task/tests/metrics/codeMetrics.spec.ts b/src/task/tests/metrics/codeMetrics.spec.ts index accf97166..c885e9691 100644 --- a/src/task/tests/metrics/codeMetrics.spec.ts +++ b/src/task/tests/metrics/codeMetrics.spec.ts @@ -5,7 +5,7 @@ import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import * as InputsDefault from "../../src/metrics/inputsDefault.js"; -import { anyString, instance, mock, verify, when } from "ts-mockito"; +import { anyString, instance, mock, when } from "ts-mockito"; import CodeMetrics from "../../src/metrics/codeMetrics.js"; import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; import GitInvoker from "../../src/git/gitInvoker.js"; @@ -189,7 +189,6 @@ describe("codeMetrics.ts", (): void => { { interface TestCaseType { gitResponse: string; - globChecks: number; metrics: CodeMetricsData; sizeIndicator: string; testCoverageIndicator: boolean; @@ -198,490 +197,420 @@ describe("codeMetrics.ts", (): void => { const testCases: TestCaseType[] = [ { gitResponse: "0\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(0, 0, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(1, 0, 0), sizeIndicator: "XS", testCoverageIndicator: false, }, { gitResponse: "1\t0\tfile.ts\n1\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(1, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "199\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(199, 0, 0), sizeIndicator: "XS", testCoverageIndicator: false, }, { gitResponse: "199\t0\tfile.ts\n198\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(199, 198, 0), sizeIndicator: "XS", testCoverageIndicator: false, }, { gitResponse: "199\t0\tfile.ts\n199\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(199, 199, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "200\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(200, 0, 0), sizeIndicator: "S", testCoverageIndicator: false, }, { gitResponse: "200\t0\tfile.ts\n199\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(200, 199, 0), sizeIndicator: "S", testCoverageIndicator: false, }, { gitResponse: "200\t0\tfile.ts\n200\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(200, 200, 0), sizeIndicator: "S", testCoverageIndicator: true, }, { gitResponse: "399\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(399, 0, 0), sizeIndicator: "S", testCoverageIndicator: false, }, { gitResponse: "399\t0\tfile.ts\n398\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(399, 398, 0), sizeIndicator: "S", testCoverageIndicator: false, }, { gitResponse: "399\t0\tfile.ts\n399\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(399, 399, 0), sizeIndicator: "S", testCoverageIndicator: true, }, { gitResponse: "400\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(400, 0, 0), sizeIndicator: "M", testCoverageIndicator: false, }, { gitResponse: "400\t0\tfile.ts\n399\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(400, 399, 0), sizeIndicator: "M", testCoverageIndicator: false, }, { gitResponse: "400\t0\tfile.ts\n400\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(400, 400, 0), sizeIndicator: "M", testCoverageIndicator: true, }, { gitResponse: "799\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(799, 0, 0), sizeIndicator: "M", testCoverageIndicator: false, }, { gitResponse: "799\t0\tfile.ts\n798\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(799, 798, 0), sizeIndicator: "M", testCoverageIndicator: false, }, { gitResponse: "799\t0\tfile.ts\n799\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(799, 799, 0), sizeIndicator: "M", testCoverageIndicator: true, }, { gitResponse: "800\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(800, 0, 0), sizeIndicator: "L", testCoverageIndicator: false, }, { gitResponse: "800\t0\tfile.ts\n799\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(800, 799, 0), sizeIndicator: "L", testCoverageIndicator: false, }, { gitResponse: "800\t0\tfile.ts\n800\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(800, 800, 0), sizeIndicator: "L", testCoverageIndicator: true, }, { gitResponse: "1599\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(1599, 0, 0), sizeIndicator: "L", testCoverageIndicator: false, }, { gitResponse: "1599\t0\tfile.ts\n1598\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(1599, 1598, 0), sizeIndicator: "L", testCoverageIndicator: false, }, { gitResponse: "1599\t0\tfile.ts\n1599\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(1599, 1599, 0), sizeIndicator: "L", testCoverageIndicator: true, }, { gitResponse: "1600\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(1600, 0, 0), sizeIndicator: "XL", testCoverageIndicator: false, }, { gitResponse: "1600\t0\tfile.ts\n1599\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(1600, 1599, 0), sizeIndicator: "XL", testCoverageIndicator: false, }, { gitResponse: "1600\t0\tfile.ts\n1600\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(1600, 1600, 0), sizeIndicator: "XL", testCoverageIndicator: true, }, { gitResponse: "3199\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(3199, 0, 0), sizeIndicator: "XL", testCoverageIndicator: false, }, { gitResponse: "3199\t0\tfile.ts\n3198\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(3199, 3198, 0), sizeIndicator: "XL", testCoverageIndicator: false, }, { gitResponse: "3199\t0\tfile.ts\n3199\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(3199, 3199, 0), sizeIndicator: "XL", testCoverageIndicator: true, }, { gitResponse: "3200\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(3200, 0, 0), sizeIndicator: "2XL", testCoverageIndicator: false, }, { gitResponse: "3200\t0\tfile.ts\n3199\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(3200, 3199, 0), sizeIndicator: "2XL", testCoverageIndicator: false, }, { gitResponse: "3200\t0\tfile.ts\n3200\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(3200, 3200, 0), sizeIndicator: "2XL", testCoverageIndicator: true, }, { gitResponse: "6399\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(6399, 0, 0), sizeIndicator: "2XL", testCoverageIndicator: false, }, { gitResponse: "6399\t0\tfile.ts\n6398\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(6399, 6398, 0), sizeIndicator: "2XL", testCoverageIndicator: false, }, { gitResponse: "6399\t0\tfile.ts\n6399\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(6399, 6399, 0), sizeIndicator: "2XL", testCoverageIndicator: true, }, { gitResponse: "6400\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(6400, 0, 0), sizeIndicator: "3XL", testCoverageIndicator: false, }, { gitResponse: "6400\t0\tfile.ts\n6399\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(6400, 6399, 0), sizeIndicator: "3XL", testCoverageIndicator: false, }, { gitResponse: "6400\t0\tfile.ts\n6400\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(6400, 6400, 0), sizeIndicator: "3XL", testCoverageIndicator: true, }, { gitResponse: "819200\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(819200, 0, 0), sizeIndicator: "10XL", testCoverageIndicator: false, }, { gitResponse: "819200\t0\tfile.ts\n819199\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(819200, 819199, 0), sizeIndicator: "10XL", testCoverageIndicator: false, }, { gitResponse: "819200\t0\tfile.ts\n819200\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(819200, 819200, 0), sizeIndicator: "10XL", testCoverageIndicator: true, }, { gitResponse: "1\t0\tfile.TS", - globChecks: 6, metrics: new CodeMetricsData(1, 0, 0), sizeIndicator: "XS", testCoverageIndicator: false, }, { gitResponse: "0\t1\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(0, 0, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\tfile.ignored", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 1), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\tfile", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 1), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\tfile.ts.ignored", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 1), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\tfile.ignored.ts", - globChecks: 6, metrics: new CodeMetricsData(1, 0, 0), sizeIndicator: "XS", testCoverageIndicator: false, }, { gitResponse: "1\t0\ttest.ignored", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 1), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\ttasb.cc => test.ts", - globChecks: 3, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\tt{a => e}s{b => t}.t{c => s}", - globChecks: 3, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\tt{a => est.ts}", - globChecks: 3, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\t{a => test.ts}", - globChecks: 3, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\tfolder/test.ts", - globChecks: 3, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\tfolder/Test.ts", - globChecks: 3, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\tfolder/TEST.ts", - globChecks: 3, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\tfolder/DuplicateStorage.ts", - globChecks: 6, metrics: new CodeMetricsData(1, 0, 0), sizeIndicator: "XS", testCoverageIndicator: false, }, { gitResponse: "1\t0\tfolder/file.spec.ts", - globChecks: 5, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\tfolder/file.Spec.ts", - globChecks: 5, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\tfolder.spec.ts/file.ts", - globChecks: 6, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\ttest/file.ts", - globChecks: 4, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\ttests/file.ts", - globChecks: 4, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\ttests/file.spec.ts", - globChecks: 4, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\ttests/file.SPEC.ts", - globChecks: 4, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t0\tfolder/tests/file.ts", - globChecks: 4, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t1\tfa/b => folder/test.ts", - globChecks: 3, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "1\t1\tf{a => older}/{b => test.ts}", - globChecks: 3, metrics: new CodeMetricsData(0, 1, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "0\t0\tfile.ts\n", - globChecks: 6, metrics: new CodeMetricsData(0, 0, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "-\t-\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(0, 0, 0), sizeIndicator: "XS", testCoverageIndicator: true, }, { gitResponse: "0\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(0, 0, 0), sizeIndicator: "XS", testCoverageIndicator: true, @@ -691,7 +620,6 @@ describe("codeMetrics.ts", (): void => { testCases.forEach( ({ gitResponse, - globChecks, metrics, sizeIndicator, testCoverageIndicator, @@ -728,41 +656,6 @@ describe("codeMetrics.ts", (): void => { await codeMetrics.isSufficientlyTested(), testCoverageIndicator, ); - const derivedCount: number = - (gitResponse.replace(/\r\n/gu, "").match(/\n/gu) ?? []).length + - 1 - - (gitResponse.endsWith("\n") ? 1 : 0); - verify( - logger.logDebug("* CodeMetrics.getFilesNotRequiringReview()"), - ).once(); - verify( - logger.logDebug( - "* CodeMetrics.getDeletedFilesNotRequiringReview()", - ), - ).once(); - verify(logger.logDebug("* CodeMetrics.getSize()")).once(); - verify(logger.logDebug("* CodeMetrics.initialize()")).times(7); - verify(logger.logDebug("* CodeMetrics.initializeMetrics()")).once(); - verify( - logger.logDebug("* CodeMetrics.determineIfValidFilePattern()"), - ).times(derivedCount); - verify(logger.logDebug("* CodeMetrics.performGlobCheck()")).times( - globChecks, - ); - verify(logger.logDebug("* CodeMetrics.matchFileExtension()")).times( - derivedCount, - ); - verify(logger.logDebug("* CodeMetrics.constructMetrics()")).once(); - verify( - logger.logDebug("* CodeMetrics.createFileMetricsMap()"), - ).once(); - verify( - logger.logDebug("* CodeMetrics.initializeIsSufficientlyTested()"), - ).once(); - verify( - logger.logDebug("* CodeMetrics.initializeSizeIndicator()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.calculateSize()")).once(); }); }, ); @@ -773,7 +666,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: string[]; filesNotRequiringReview: string[]; gitResponse: string; - globChecks: number; metrics: CodeMetricsData; sizeIndicator: string; testCoverageIndicator: boolean; @@ -784,7 +676,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "0\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(0, 0, 0), sizeIndicator: "XS", testCoverageIndicator: true, @@ -793,7 +684,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "1\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(1, 0, 0), sizeIndicator: "XS", testCoverageIndicator: false, @@ -802,7 +692,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "1\t0\tfile.ts\n1\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(1, 1, 0), sizeIndicator: "XS", testCoverageIndicator: false, @@ -811,7 +700,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "1\t0\tfile.ts\n2\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(1, 2, 0), sizeIndicator: "XS", testCoverageIndicator: true, @@ -820,7 +708,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "99\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(99, 0, 0), sizeIndicator: "XS", testCoverageIndicator: false, @@ -829,7 +716,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "99\t0\tfile.ts\n197\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(99, 197, 0), sizeIndicator: "XS", testCoverageIndicator: false, @@ -838,7 +724,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "99\t0\tfile.ts\n198\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(99, 198, 0), sizeIndicator: "XS", testCoverageIndicator: true, @@ -847,7 +732,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "100\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(100, 0, 0), sizeIndicator: "S", testCoverageIndicator: false, @@ -856,7 +740,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "100\t0\tfile.ts\n199\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(100, 199, 0), sizeIndicator: "S", testCoverageIndicator: false, @@ -865,7 +748,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "100\t0\tfile.ts\n200\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(100, 200, 0), sizeIndicator: "S", testCoverageIndicator: true, @@ -874,7 +756,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "149\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(149, 0, 0), sizeIndicator: "S", testCoverageIndicator: false, @@ -883,7 +764,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "149\t0\tfile.ts\n297\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(149, 297, 0), sizeIndicator: "S", testCoverageIndicator: false, @@ -892,7 +772,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "149\t0\tfile.ts\n298\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(149, 298, 0), sizeIndicator: "S", testCoverageIndicator: true, @@ -901,7 +780,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "150\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(150, 0, 0), sizeIndicator: "M", testCoverageIndicator: false, @@ -910,7 +788,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "150\t0\tfile.ts\n299\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(150, 299, 0), sizeIndicator: "M", testCoverageIndicator: false, @@ -919,7 +796,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "150\t0\tfile.ts\n300\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(150, 300, 0), sizeIndicator: "M", testCoverageIndicator: true, @@ -928,7 +804,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "224\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(224, 0, 0), sizeIndicator: "M", testCoverageIndicator: false, @@ -937,7 +812,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "224\t0\tfile.ts\n447\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(224, 447, 0), sizeIndicator: "M", testCoverageIndicator: false, @@ -946,7 +820,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "224\t0\tfile.ts\n448\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(224, 448, 0), sizeIndicator: "M", testCoverageIndicator: true, @@ -955,7 +828,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "225\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(225, 0, 0), sizeIndicator: "L", testCoverageIndicator: false, @@ -964,7 +836,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "225\t0\tfile.ts\n449\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(225, 449, 0), sizeIndicator: "L", testCoverageIndicator: false, @@ -973,7 +844,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "225\t0\tfile.ts\n450\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(225, 450, 0), sizeIndicator: "L", testCoverageIndicator: true, @@ -982,7 +852,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "337\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(337, 0, 0), sizeIndicator: "L", testCoverageIndicator: false, @@ -991,7 +860,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "337\t0\tfile.ts\n673\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(337, 673, 0), sizeIndicator: "L", testCoverageIndicator: false, @@ -1000,7 +868,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "337\t0\tfile.ts\n674\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(337, 674, 0), sizeIndicator: "L", testCoverageIndicator: true, @@ -1009,7 +876,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "338\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(338, 0, 0), sizeIndicator: "XL", testCoverageIndicator: false, @@ -1018,7 +884,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "338\t0\tfile.ts\n675\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(338, 675, 0), sizeIndicator: "XL", testCoverageIndicator: false, @@ -1027,7 +892,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "338\t0\tfile.ts\n676\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(338, 676, 0), sizeIndicator: "XL", testCoverageIndicator: true, @@ -1036,7 +900,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "506\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(506, 0, 0), sizeIndicator: "XL", testCoverageIndicator: false, @@ -1045,7 +908,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "506\t0\tfile.ts\n1011\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(506, 1011, 0), sizeIndicator: "XL", testCoverageIndicator: false, @@ -1054,7 +916,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "506\t0\tfile.ts\n1012\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(506, 1012, 0), sizeIndicator: "XL", testCoverageIndicator: true, @@ -1063,7 +924,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "507\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(507, 0, 0), sizeIndicator: "2XL", testCoverageIndicator: false, @@ -1072,7 +932,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "507\t0\tfile.ts\n1013\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(507, 1013, 0), sizeIndicator: "2XL", testCoverageIndicator: false, @@ -1081,7 +940,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "507\t0\tfile.ts\n1014\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(507, 1014, 0), sizeIndicator: "2XL", testCoverageIndicator: true, @@ -1090,7 +948,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "759\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(759, 0, 0), sizeIndicator: "2XL", testCoverageIndicator: false, @@ -1099,7 +956,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "759\t0\tfile.ts\n1517\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(759, 1517, 0), sizeIndicator: "2XL", testCoverageIndicator: false, @@ -1108,7 +964,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "759\t0\tfile.ts\n1518\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(759, 1518, 0), sizeIndicator: "2XL", testCoverageIndicator: true, @@ -1117,7 +972,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "760\t0\tfile.ts", - globChecks: 6, metrics: new CodeMetricsData(760, 0, 0), sizeIndicator: "3XL", testCoverageIndicator: false, @@ -1126,7 +980,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "760\t0\tfile.ts\n1519\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(760, 1519, 0), sizeIndicator: "3XL", testCoverageIndicator: false, @@ -1135,7 +988,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "760\t0\tfile.ts\n1520\t0\ttest.ts", - globChecks: 9, metrics: new CodeMetricsData(760, 1520, 0), sizeIndicator: "3XL", testCoverageIndicator: true, @@ -1144,7 +996,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "1\t0\tfile.cs", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 1), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1153,7 +1004,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "1\t0\ttest.cs", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 1), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1162,7 +1012,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "1\t0\tfile.tst", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 1), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1171,7 +1020,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "1\t0\tfile.tts", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 1), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1180,7 +1028,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: [], gitResponse: "1\t0\tfilets", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 1), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1189,7 +1036,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: ["ignored.ts"], gitResponse: "1\t0\tignored.ts", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 1), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1198,7 +1044,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: ["ignored.cs"], gitResponse: "1\t0\tignored.cs", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 1), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1207,7 +1052,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: ["folder/ignored.ts"], gitResponse: "1\t0\tfolder/ignored.ts", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 1), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1216,7 +1060,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: ["folder/ignored.cs"], gitResponse: "1\t0\tfolder/ignored.cs", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 1), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1225,7 +1068,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: ["ignored.ts"], gitResponse: "0\t0\tignored.ts", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 0), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1234,7 +1076,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: ["ignored.cs"], gitResponse: "0\t0\tignored.cs", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 0), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1243,7 +1084,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: ["folder/ignored.ts"], gitResponse: "0\t0\tfolder/ignored.ts", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 0), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1252,7 +1092,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: ["folder/ignored.cs"], gitResponse: "0\t0\tfolder/ignored.cs", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 0), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1261,7 +1100,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: [], filesNotRequiringReview: ["ignored.ts", "folder/ignored.ts"], gitResponse: "1\t0\tignored.ts\n0\t0\tfolder/ignored.ts", - globChecks: 4, metrics: new CodeMetricsData(0, 0, 1), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1270,7 +1108,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: ["ignored.ts"], filesNotRequiringReview: [], gitResponse: "0\t1\tignored.ts", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 0), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1279,7 +1116,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: ["ignored.cs"], filesNotRequiringReview: [], gitResponse: "0\t1\tignored.cs", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 0), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1288,7 +1124,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: ["folder/ignored.ts"], filesNotRequiringReview: [], gitResponse: "0\t1\tfolder/ignored.ts", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 0), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1297,7 +1132,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: ["folder/ignored.cs"], filesNotRequiringReview: [], gitResponse: "0\t1\tfolder/ignored.cs", - globChecks: 2, metrics: new CodeMetricsData(0, 0, 0), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1306,7 +1140,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview: ["folder/ignored.ts"], filesNotRequiringReview: ["ignored.ts"], gitResponse: "1\t0\tignored.ts\n0\t1\tfolder/ignored.ts", - globChecks: 4, metrics: new CodeMetricsData(0, 0, 1), sizeIndicator: "XS", testCoverageIndicator: true, @@ -1318,7 +1151,6 @@ describe("codeMetrics.ts", (): void => { deletedFilesNotRequiringReview, filesNotRequiringReview, gitResponse, - globChecks, metrics, sizeIndicator, testCoverageIndicator, @@ -1367,42 +1199,6 @@ describe("codeMetrics.ts", (): void => { await codeMetrics.isSufficientlyTested(), testCoverageIndicator, ); - const derivedCount: number = - (gitResponse.match(/\n/gu) ?? []).length + 1; - verify( - logger.logDebug("* CodeMetrics.getFilesNotRequiringReview()"), - ).once(); - verify( - logger.logDebug( - "* CodeMetrics.getDeletedFilesNotRequiringReview()", - ), - ).once(); - verify(logger.logDebug("* CodeMetrics.getSize()")).once(); - verify(logger.logDebug("* CodeMetrics.initialize()")).times(7); - verify(logger.logDebug("* CodeMetrics.initializeMetrics()")).once(); - verify( - logger.logDebug("* CodeMetrics.determineIfValidFilePattern()"), - ).times(derivedCount); - verify(logger.logDebug("* CodeMetrics.performGlobCheck()")).times( - globChecks, - ); - verify(logger.logDebug("* CodeMetrics.matchFileExtension()")).times( - derivedCount, - ); - verify(logger.logDebug("* CodeMetrics.matchFileExtension()")).times( - (gitResponse.match(/\n/gu) ?? []).length + 1, - ); - verify(logger.logDebug("* CodeMetrics.constructMetrics()")).once(); - verify( - logger.logDebug("* CodeMetrics.createFileMetricsMap()"), - ).once(); - verify( - logger.logDebug("* CodeMetrics.initializeIsSufficientlyTested()"), - ).once(); - verify( - logger.logDebug("* CodeMetrics.initializeSizeIndicator()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.calculateSize()")).once(); }); }, ); @@ -1411,28 +1207,24 @@ describe("codeMetrics.ts", (): void => { { interface TestCaseType { gitResponse: string; - globChecks: number; } const testCases: TestCaseType[] = [ { gitResponse: "2\t2\tfile.ts\n1\t1\tignored1.ts\n1\t1\tacceptance.ts\n1\t1\tignored2.ts", - globChecks: 13, }, { gitResponse: "1\t1\tfile1.ts\n1\t1\tignored1.ts\n1\t1\tignored2.ts\n1\t1\tacceptance.ts\n1\t1\tfile2.ts", - globChecks: 17, }, { gitResponse: "1\t1\tfile1.ts\n1\t1\tignored1.ts\n1\t1\tfile2.ts\n1\t1\tacceptance.ts\n1\t1\tignored2.ts", - globChecks: 17, }, ]; - testCases.forEach(({ gitResponse, globChecks }: TestCaseType): void => { + testCases.forEach(({ gitResponse }: TestCaseType): void => { it(`with multiple ignore patterns and git diff '${gitResponse.replace(/\n/gu, "\\n").replace(/\r/gu, "\\r")}' ignores the appropriate files`, async (): Promise => { // Arrange when(inputs.baseSize).thenReturn(100); @@ -1472,35 +1264,6 @@ describe("codeMetrics.ts", (): void => { ); assert.equal(await codeMetrics.isSmall(), true); assert.equal(await codeMetrics.isSufficientlyTested(), false); - const derivedCount: number = - (gitResponse.match(/\n/gu) ?? []).length + 1; - verify( - logger.logDebug("* CodeMetrics.getFilesNotRequiringReview()"), - ).once(); - verify( - logger.logDebug("* CodeMetrics.getDeletedFilesNotRequiringReview()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.getSize()")).once(); - verify(logger.logDebug("* CodeMetrics.initialize()")).times(7); - verify(logger.logDebug("* CodeMetrics.initializeMetrics()")).once(); - verify( - logger.logDebug("* CodeMetrics.determineIfValidFilePattern()"), - ).times(derivedCount); - verify(logger.logDebug("* CodeMetrics.performGlobCheck()")).times( - globChecks, - ); - verify(logger.logDebug("* CodeMetrics.matchFileExtension()")).times( - derivedCount, - ); - verify(logger.logDebug("* CodeMetrics.constructMetrics()")).once(); - verify(logger.logDebug("* CodeMetrics.createFileMetricsMap()")).once(); - verify( - logger.logDebug("* CodeMetrics.initializeIsSufficientlyTested()"), - ).once(); - verify( - logger.logDebug("* CodeMetrics.initializeSizeIndicator()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.calculateSize()")).once(); }); }); } @@ -1537,27 +1300,6 @@ describe("codeMetrics.ts", (): void => { ); assert.equal(await codeMetrics.isSmall(), true); assert.equal(await codeMetrics.isSufficientlyTested(), false); - verify( - logger.logDebug("* CodeMetrics.getFilesNotRequiringReview()"), - ).once(); - verify( - logger.logDebug("* CodeMetrics.getDeletedFilesNotRequiringReview()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.getSize()")).once(); - verify(logger.logDebug("* CodeMetrics.initialize()")).times(7); - verify(logger.logDebug("* CodeMetrics.initializeMetrics()")).once(); - verify( - logger.logDebug("* CodeMetrics.determineIfValidFilePattern()"), - ).times(3); - verify(logger.logDebug("* CodeMetrics.performGlobCheck()")).times(10); - verify(logger.logDebug("* CodeMetrics.matchFileExtension()")).times(3); - verify(logger.logDebug("* CodeMetrics.constructMetrics()")).once(); - verify(logger.logDebug("* CodeMetrics.createFileMetricsMap()")).once(); - verify( - logger.logDebug("* CodeMetrics.initializeIsSufficientlyTested()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.initializeSizeIndicator()")).once(); - verify(logger.logDebug("* CodeMetrics.calculateSize()")).once(); }); it("with only negative patterns, treats all files as non-matching", async (): Promise => { @@ -1593,27 +1335,6 @@ describe("codeMetrics.ts", (): void => { ); assert.equal(await codeMetrics.isSmall(), true); assert.equal(await codeMetrics.isSufficientlyTested(), true); - verify( - logger.logDebug("* CodeMetrics.getFilesNotRequiringReview()"), - ).once(); - verify( - logger.logDebug("* CodeMetrics.getDeletedFilesNotRequiringReview()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.getSize()")).once(); - verify(logger.logDebug("* CodeMetrics.initialize()")).times(7); - verify(logger.logDebug("* CodeMetrics.initializeMetrics()")).once(); - verify( - logger.logDebug("* CodeMetrics.determineIfValidFilePattern()"), - ).times(2); - verify(logger.logDebug("* CodeMetrics.performGlobCheck()")).never(); - verify(logger.logDebug("* CodeMetrics.matchFileExtension()")).times(2); - verify(logger.logDebug("* CodeMetrics.constructMetrics()")).once(); - verify(logger.logDebug("* CodeMetrics.createFileMetricsMap()")).once(); - verify( - logger.logDebug("* CodeMetrics.initializeIsSufficientlyTested()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.initializeSizeIndicator()")).once(); - verify(logger.logDebug("* CodeMetrics.calculateSize()")).once(); }); it("with double exclusion ignore patterns ignores the appropriate files", async (): Promise => { @@ -1653,27 +1374,6 @@ describe("codeMetrics.ts", (): void => { ); assert.equal(await codeMetrics.isSmall(), true); assert.equal(await codeMetrics.isSufficientlyTested(), false); - verify( - logger.logDebug("* CodeMetrics.getFilesNotRequiringReview()"), - ).once(); - verify( - logger.logDebug("* CodeMetrics.getDeletedFilesNotRequiringReview()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.getSize()")).once(); - verify(logger.logDebug("* CodeMetrics.initialize()")).times(7); - verify(logger.logDebug("* CodeMetrics.initializeMetrics()")).once(); - verify( - logger.logDebug("* CodeMetrics.determineIfValidFilePattern()"), - ).times(4); - verify(logger.logDebug("* CodeMetrics.performGlobCheck()")).times(19); - verify(logger.logDebug("* CodeMetrics.matchFileExtension()")).times(4); - verify(logger.logDebug("* CodeMetrics.constructMetrics()")).once(); - verify(logger.logDebug("* CodeMetrics.createFileMetricsMap()")).once(); - verify( - logger.logDebug("* CodeMetrics.initializeIsSufficientlyTested()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.initializeSizeIndicator()")).once(); - verify(logger.logDebug("* CodeMetrics.calculateSize()")).once(); }); it("with all files matching test files, returns the appropriate results", async (): Promise => { @@ -1702,27 +1402,6 @@ describe("codeMetrics.ts", (): void => { ); assert.equal(await codeMetrics.isSmall(), true); assert.equal(await codeMetrics.isSufficientlyTested(), true); - verify( - logger.logDebug("* CodeMetrics.getFilesNotRequiringReview()"), - ).once(); - verify( - logger.logDebug("* CodeMetrics.getDeletedFilesNotRequiringReview()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.getSize()")).once(); - verify(logger.logDebug("* CodeMetrics.initialize()")).times(7); - verify(logger.logDebug("* CodeMetrics.initializeMetrics()")).once(); - verify( - logger.logDebug("* CodeMetrics.determineIfValidFilePattern()"), - ).times(3); - verify(logger.logDebug("* CodeMetrics.performGlobCheck()")).times(9); - verify(logger.logDebug("* CodeMetrics.matchFileExtension()")).times(3); - verify(logger.logDebug("* CodeMetrics.constructMetrics()")).once(); - verify(logger.logDebug("* CodeMetrics.createFileMetricsMap()")).once(); - verify( - logger.logDebug("* CodeMetrics.initializeIsSufficientlyTested()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.initializeSizeIndicator()")).once(); - verify(logger.logDebug("* CodeMetrics.calculateSize()")).once(); }); it("should return the expected result with test coverage disabled", async (): Promise => { @@ -1749,27 +1428,6 @@ describe("codeMetrics.ts", (): void => { ); assert.equal(await codeMetrics.isSmall(), true); assert.equal(await codeMetrics.isSufficientlyTested(), null); - verify( - logger.logDebug("* CodeMetrics.getFilesNotRequiringReview()"), - ).once(); - verify( - logger.logDebug("* CodeMetrics.getDeletedFilesNotRequiringReview()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.getSize()")).once(); - verify(logger.logDebug("* CodeMetrics.initialize()")).times(7); - verify(logger.logDebug("* CodeMetrics.initializeMetrics()")).once(); - verify( - logger.logDebug("* CodeMetrics.determineIfValidFilePattern()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.performGlobCheck()")).times(6); - verify(logger.logDebug("* CodeMetrics.matchFileExtension()")).once(); - verify(logger.logDebug("* CodeMetrics.constructMetrics()")).once(); - verify(logger.logDebug("* CodeMetrics.createFileMetricsMap()")).once(); - verify( - logger.logDebug("* CodeMetrics.initializeIsSufficientlyTested()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.initializeSizeIndicator()")).once(); - verify(logger.logDebug("* CodeMetrics.calculateSize()")).once(); }); describe("getFilesNotRequiringReview()", (): void => { @@ -1793,17 +1451,6 @@ describe("codeMetrics.ts", (): void => { // Assert assert.deepEqual(result, []); - verify( - logger.logDebug("* CodeMetrics.getFilesNotRequiringReview()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.initialize()")).once(); - verify( - logger.logDebug("* CodeMetrics.initializeIsSufficientlyTested()"), - ).once(); - verify( - logger.logDebug("* CodeMetrics.initializeSizeIndicator()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.calculateSize()")).once(); }); }); } @@ -1861,13 +1508,6 @@ describe("codeMetrics.ts", (): void => { func, `The number of elements '${String(elements)}' in '${summary.trim()}' in input '${summary.trim()}' did not match the expected 3.`, ); - verify( - logger.logDebug("* CodeMetrics.getFilesNotRequiringReview()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.initialize()")).once(); - verify( - logger.logDebug("* CodeMetrics.createFileMetricsMap()"), - ).once(); }); }); } @@ -1891,11 +1531,6 @@ describe("codeMetrics.ts", (): void => { func, "Could not parse added lines 'A' from line 'A\t0\tfile.ts'.", ); - verify( - logger.logDebug("* CodeMetrics.getFilesNotRequiringReview()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.initialize()")).once(); - verify(logger.logDebug("* CodeMetrics.createFileMetricsMap()")).once(); }); it("should throw when the lines deleted in the Git diff summary cannot be converted", async (): Promise => { @@ -1917,11 +1552,6 @@ describe("codeMetrics.ts", (): void => { func, "Could not parse deleted lines 'A' from line '0\tA\tfile.ts'.", ); - verify( - logger.logDebug("* CodeMetrics.getFilesNotRequiringReview()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.initialize()")).once(); - verify(logger.logDebug("* CodeMetrics.createFileMetricsMap()")).once(); }); }); @@ -1942,15 +1572,6 @@ describe("codeMetrics.ts", (): void => { // Assert assert.deepEqual(result, []); - verify( - logger.logDebug("* CodeMetrics.getDeletedFilesNotRequiringReview()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.initialize()")).once(); - verify( - logger.logDebug("* CodeMetrics.initializeIsSufficientlyTested()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.initializeSizeIndicator()")).once(); - verify(logger.logDebug("* CodeMetrics.calculateSize()")).once(); }); it("should throw when the file name in the Git diff summary '0' cannot be parsed", async (): Promise => { @@ -1972,11 +1593,6 @@ describe("codeMetrics.ts", (): void => { func, "The number of elements '1' in '0' in input '0' did not match the expected 3.", ); - verify( - logger.logDebug("* CodeMetrics.getDeletedFilesNotRequiringReview()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.initialize()")).once(); - verify(logger.logDebug("* CodeMetrics.createFileMetricsMap()")).once(); }); it("should throw when the lines added in the Git diff summary cannot be converted", async (): Promise => { @@ -1998,11 +1614,6 @@ describe("codeMetrics.ts", (): void => { func, "Could not parse added lines 'A' from line 'A\t0\tfile.ts'.", ); - verify( - logger.logDebug("* CodeMetrics.getDeletedFilesNotRequiringReview()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.initialize()")).once(); - verify(logger.logDebug("* CodeMetrics.createFileMetricsMap()")).once(); }); it("should throw when the lines deleted in the Git diff summary cannot be converted", async (): Promise => { @@ -2024,11 +1635,6 @@ describe("codeMetrics.ts", (): void => { func, "Could not parse deleted lines 'A' from line '0\tA\tfile.ts'.", ); - verify( - logger.logDebug("* CodeMetrics.getDeletedFilesNotRequiringReview()"), - ).once(); - verify(logger.logDebug("* CodeMetrics.initialize()")).once(); - verify(logger.logDebug("* CodeMetrics.createFileMetricsMap()")).once(); }); }); diff --git a/src/task/tests/metrics/codeMetricsCalculator.spec.ts b/src/task/tests/metrics/codeMetricsCalculator.spec.ts index e3929cdb4..94f638da6 100644 --- a/src/task/tests/metrics/codeMetricsCalculator.spec.ts +++ b/src/task/tests/metrics/codeMetricsCalculator.spec.ts @@ -104,7 +104,6 @@ describe("codeMetricsCalculator.ts", (): void => { // Assert assert.equal(result, null); - verify(logger.logDebug("* CodeMetricsCalculator.shouldSkip")).once(); }); it("should return the appropriate message when not a supported provider", (): void => { @@ -125,7 +124,6 @@ describe("codeMetricsCalculator.ts", (): void => { // Assert assert.equal(result, "The build is not running against a pull request."); - verify(logger.logDebug("* CodeMetricsCalculator.shouldSkip")).once(); }); it("should return null when the task should not be skipped", (): void => { @@ -149,7 +147,6 @@ describe("codeMetricsCalculator.ts", (): void => { result, "The build is running against a pull request from 'Other', which is not a supported provider.", ); - verify(logger.logDebug("* CodeMetricsCalculator.shouldSkip")).once(); }); }); @@ -171,7 +168,6 @@ describe("codeMetricsCalculator.ts", (): void => { // Assert assert.equal(result, null); - verify(logger.logDebug("* CodeMetricsCalculator.shouldStop()")).once(); }); it("should return the appropriate message when no access token is available", async (): Promise => { @@ -194,7 +190,6 @@ describe("codeMetricsCalculator.ts", (): void => { // Assert assert.equal(result, "No Access Token"); - verify(logger.logDebug("* CodeMetricsCalculator.shouldStop()")).once(); }); it("should return the appropriate message when not called from a Git repo on Azure DevOps", async (): Promise => { @@ -218,7 +213,6 @@ describe("codeMetricsCalculator.ts", (): void => { result, "No Git repo present. Remove 'checkout: none' (YAML) or disable 'Don't sync sources' under the build process phase settings (classic).", ); - verify(logger.logDebug("* CodeMetricsCalculator.shouldStop()")).once(); }); it("should return the appropriate message when not called from a Git repo on GitHub", async (): Promise => { @@ -243,7 +237,6 @@ describe("codeMetricsCalculator.ts", (): void => { result, "No Git repo present. Run the 'actions/checkout' action prior to PR Metrics.", ); - verify(logger.logDebug("* CodeMetricsCalculator.shouldStop()")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -267,7 +260,6 @@ describe("codeMetricsCalculator.ts", (): void => { // Assert assert.equal(result, "Could not determine the Pull Request ID."); - verify(logger.logDebug("* CodeMetricsCalculator.shouldStop()")).once(); }); it("should return the appropriate message when the pull request ID is not available on GitHub", async (): Promise => { @@ -292,7 +284,6 @@ describe("codeMetricsCalculator.ts", (): void => { result, "Could not determine the Pull Request ID. Ensure 'pull_request' is the pipeline trigger.", ); - verify(logger.logDebug("* CodeMetricsCalculator.shouldStop()")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -319,7 +310,6 @@ describe("codeMetricsCalculator.ts", (): void => { result, "Could not access sufficient Git history. Set 'fetchDepth: 0' as a parameter to the 'checkout' task (YAML) or disable 'Shallow fetch' under the build process phase settings (classic).", ); - verify(logger.logDebug("* CodeMetricsCalculator.shouldStop()")).once(); }); it("should return the appropriate message when the Git history is unavailable on GitHub", async (): Promise => { @@ -344,7 +334,6 @@ describe("codeMetricsCalculator.ts", (): void => { result, "Could not access sufficient Git history. Add 'fetch-depth: 0' as a parameter to the 'actions/checkout' action.", ); - verify(logger.logDebug("* CodeMetricsCalculator.shouldStop()")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -376,7 +365,6 @@ describe("codeMetricsCalculator.ts", (): void => { await codeMetricsCalculator.updateDetails(); // Assert - verify(logger.logDebug("* CodeMetricsCalculator.updateDetails()")).once(); verify(pullRequest.getUpdatedTitle("Title")).once(); verify(pullRequest.getUpdatedDescription("Description")).once(); verify( @@ -406,7 +394,6 @@ describe("codeMetricsCalculator.ts", (): void => { await codeMetricsCalculator.updateDetails(); // Assert - verify(logger.logDebug("* CodeMetricsCalculator.updateDetails()")).once(); verify(pullRequest.getUpdatedTitle("Title")).once(); verify(pullRequest.getUpdatedDescription(null)).once(); verify( @@ -438,9 +425,6 @@ describe("codeMetricsCalculator.ts", (): void => { await codeMetricsCalculator.updateComments(); // Assert - verify( - logger.logDebug("* CodeMetricsCalculator.updateComments()"), - ).once(); }); it("should perform the expected actions when the metrics comment is to be updated", async (): Promise => { @@ -469,12 +453,6 @@ describe("codeMetricsCalculator.ts", (): void => { await codeMetricsCalculator.updateComments(); // Assert - verify( - logger.logDebug("* CodeMetricsCalculator.updateComments()"), - ).once(); - verify( - logger.logDebug("* CodeMetricsCalculator.updateMetricsComment()"), - ).once(); verify( reposInvoker.updateComment( 1, @@ -509,12 +487,6 @@ describe("codeMetricsCalculator.ts", (): void => { await codeMetricsCalculator.updateComments(); // Assert - verify( - logger.logDebug("* CodeMetricsCalculator.updateComments()"), - ).once(); - verify( - logger.logDebug("* CodeMetricsCalculator.updateMetricsComment()"), - ).once(); verify( reposInvoker.createComment( "Description", @@ -583,14 +555,6 @@ describe("codeMetricsCalculator.ts", (): void => { await codeMetricsCalculator.updateComments(); // Assert - verify( - logger.logDebug("* CodeMetricsCalculator.updateComments()"), - ).once(); - verify( - logger.logDebug( - "* CodeMetricsCalculator.updateNoReviewRequiredComment()", - ), - ).times(file1Comments + file2Comments); verify( reposInvoker.createComment( "No Review Required", @@ -640,14 +604,6 @@ describe("codeMetricsCalculator.ts", (): void => { await codeMetricsCalculator.updateComments(); // Assert - verify( - logger.logDebug("* CodeMetricsCalculator.updateComments()"), - ).once(); - verify( - logger.logDebug( - "* CodeMetricsCalculator.updateNoReviewRequiredComment()", - ), - ).times(file1Comments + file2Comments); verify( reposInvoker.createComment( "No Review Required", @@ -691,9 +647,6 @@ describe("codeMetricsCalculator.ts", (): void => { await codeMetricsCalculator.updateComments(); // Assert - verify( - logger.logDebug("* CodeMetricsCalculator.updateComments()"), - ).once(); verify(reposInvoker.deleteCommentThread(1)).once(); verify(reposInvoker.deleteCommentThread(2)).once(); }); diff --git a/src/task/tests/metrics/inputs.spec.ts b/src/task/tests/metrics/inputs.spec.ts index f98d9f09b..be878225a 100644 --- a/src/task/tests/metrics/inputs.spec.ts +++ b/src/task/tests/metrics/inputs.spec.ts @@ -159,32 +159,6 @@ describe("inputs.ts", (): void => { inputs.codeFileExtensions, new Set(InputsDefault.codeFileExtensions), ); - verify(logger.logDebug("* Inputs.initialize()")).times(7); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.baseSize")).once(); - verify(logger.logDebug("* Inputs.growthRate")).once(); - verify(logger.logDebug("* Inputs.testFactor")).once(); - verify(logger.logDebug("* Inputs.alwaysCloseComment")).once(); - verify(logger.logDebug("* Inputs.fileMatchingPatterns")).once(); - verify(logger.logDebug("* Inputs.testMatchingPatterns")).once(); - verify(logger.logDebug("* Inputs.codeFileExtensions")).once(); verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); verify(logger.logInfo(adjustingBaseSizeResource)).once(); verify(logger.logInfo(adjustingGrowthRateResource)).once(); @@ -192,14 +166,6 @@ describe("inputs.ts", (): void => { verify(logger.logInfo(adjustingFileMatchingPatternsResource)).once(); verify(logger.logInfo(adjustingTestMatchingPatternsResource)).once(); verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); it("should set all input values when all are specified", (): void => { @@ -243,41 +209,6 @@ describe("inputs.ts", (): void => { inputs.codeFileExtensions, new Set(["js", "ts"]), ); - verify(logger.logDebug("* Inputs.initialize()")).times(7); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.baseSize")).once(); - verify(logger.logDebug("* Inputs.growthRate")).once(); - verify(logger.logDebug("* Inputs.testFactor")).once(); - verify(logger.logDebug("* Inputs.alwaysCloseComment")).once(); - verify(logger.logDebug("* Inputs.fileMatchingPatterns")).once(); - verify(logger.logDebug("* Inputs.testMatchingPatterns")).once(); - verify(logger.logDebug("* Inputs.codeFileExtensions")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).never(); - verify(logger.logInfo(adjustingBaseSizeResource)).never(); - verify(logger.logInfo(adjustingGrowthRateResource)).never(); - verify(logger.logInfo(adjustingTestFactorResource)).never(); - verify(logger.logInfo(adjustingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(adjustingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).never(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).never(); - verify(logger.logInfo(disablingTestFactorResource)).never(); verify(logger.logInfo(settingAlwaysCloseComment)).once(); verify(logger.logInfo(settingBaseSizeResource)).once(); verify(logger.logInfo(settingGrowthRateResource)).once(); @@ -316,45 +247,7 @@ describe("inputs.ts", (): void => { // Assert assert.equal(inputs.baseSize, InputsDefault.baseSize); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.baseSize")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -377,45 +270,7 @@ describe("inputs.ts", (): void => { // Assert assert.equal(inputs.baseSize, InputsDefault.baseSize); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.baseSize")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -438,45 +293,7 @@ describe("inputs.ts", (): void => { // Assert assert.equal(inputs.baseSize, parseInt(baseSize, decimalRadix)); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.baseSize")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).never(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); verify(logger.logInfo(settingBaseSizeResource)).once(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -511,45 +328,7 @@ describe("inputs.ts", (): void => { // Assert assert.equal(inputs.growthRate, InputsDefault.growthRate); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.growthRate")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -580,45 +359,7 @@ describe("inputs.ts", (): void => { // Assert assert.equal(inputs.growthRate, InputsDefault.growthRate); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.growthRate")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -650,45 +391,7 @@ describe("inputs.ts", (): void => { // Assert assert.equal(inputs.growthRate, parseFloat(growthRate)); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.growthRate")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).never(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); verify(logger.logInfo(settingGrowthRateResource)).once(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -723,45 +426,7 @@ describe("inputs.ts", (): void => { // Assert assert.equal(inputs.testFactor, InputsDefault.testFactor); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.testFactor")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -790,45 +455,7 @@ describe("inputs.ts", (): void => { // Assert assert.equal(inputs.testFactor, InputsDefault.testFactor); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.testFactor")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -860,45 +487,7 @@ describe("inputs.ts", (): void => { // Assert assert.equal(inputs.testFactor, parseFloat(testFactor)); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.testFactor")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).never(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); verify(logger.logInfo(settingTestFactorResource)).once(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -921,45 +510,7 @@ describe("inputs.ts", (): void => { // Assert assert.equal(inputs.testFactor, null); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.testFactor")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).never(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); verify(logger.logInfo(disablingTestFactorResource)).once(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -998,45 +549,7 @@ describe("inputs.ts", (): void => { inputs.alwaysCloseComment, InputsDefault.alwaysCloseComment, ); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.alwaysCloseComment")).once(); verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -1059,45 +572,7 @@ describe("inputs.ts", (): void => { // Assert assert.equal(inputs.alwaysCloseComment, true); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.alwaysCloseComment")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).never(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); verify(logger.logInfo(settingAlwaysCloseComment)).once(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -1127,45 +602,9 @@ describe("inputs.ts", (): void => { inputs.fileMatchingPatterns, InputsDefault.fileMatchingPatterns, ); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), + logger.logInfo(adjustingFileMatchingPatternsResource), ).once(); - verify(logger.logDebug("* Inputs.fileMatchingPatterns")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -1196,45 +635,10 @@ describe("inputs.ts", (): void => { assert.deepEqual(inputs.fileMatchingPatterns, [ fileMatchingPatterns, ]); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.fileMatchingPatterns")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); verify( logger.logInfo(adjustingFileMatchingPatternsResource), ).never(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -1263,45 +667,10 @@ describe("inputs.ts", (): void => { // Assert assert.deepEqual(inputs.fileMatchingPatterns, expectedOutput); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.fileMatchingPatterns")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); verify( logger.logInfo(adjustingFileMatchingPatternsResource), ).never(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -1323,41 +692,7 @@ describe("inputs.ts", (): void => { "folder1/file.js", "folder2/*.js", ]); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.fileMatchingPatterns")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify(logger.logInfo(adjustingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(adjustingTestMatchingPatternsResource)).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); it("should remove trailing new lines", (): void => { @@ -1374,41 +709,7 @@ describe("inputs.ts", (): void => { // Assert assert.deepEqual(inputs.fileMatchingPatterns, ["file.js"]); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.fileMatchingPatterns")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify(logger.logInfo(adjustingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(adjustingTestMatchingPatternsResource)).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); it("should trim whitespace and filter empty lines", (): void => { @@ -1483,45 +784,9 @@ describe("inputs.ts", (): void => { inputs.testMatchingPatterns, InputsDefault.testMatchingPatterns, ); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.testMatchingPatterns")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); verify( logger.logInfo(adjustingTestMatchingPatternsResource), ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -1552,45 +817,10 @@ describe("inputs.ts", (): void => { assert.deepEqual(inputs.testMatchingPatterns, [ testMatchingPatterns, ]); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.testMatchingPatterns")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); verify( logger.logInfo(adjustingTestMatchingPatternsResource), ).never(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -1619,45 +849,10 @@ describe("inputs.ts", (): void => { // Assert assert.deepEqual(inputs.testMatchingPatterns, expectedOutput); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.testMatchingPatterns")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); verify( logger.logInfo(adjustingTestMatchingPatternsResource), ).never(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -1679,41 +874,7 @@ describe("inputs.ts", (): void => { "folder1/file.js", "folder2/*.js", ]); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.testMatchingPatterns")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify(logger.logInfo(adjustingFileMatchingPatternsResource)).once(); - verify(logger.logInfo(adjustingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); it("should remove trailing new lines", (): void => { @@ -1730,44 +891,7 @@ describe("inputs.ts", (): void => { // Assert assert.deepEqual(inputs.testMatchingPatterns, ["file.js"]); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.testMatchingPatterns")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify(logger.logInfo(adjustingFileMatchingPatternsResource)).once(); - verify(logger.logInfo(adjustingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); @@ -1793,45 +917,7 @@ describe("inputs.ts", (): void => { inputs.codeFileExtensions, new Set(InputsDefault.codeFileExtensions), ); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.codeFileExtensions")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).never(); }); }); } @@ -1861,44 +947,6 @@ describe("inputs.ts", (): void => { // Assert assert.deepEqual(inputs.codeFileExtensions, expectedResult); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.codeFileExtensions")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).never(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); }); }); @@ -1918,40 +966,6 @@ describe("inputs.ts", (): void => { // Assert assert.deepEqual(inputs.codeFileExtensions, new Set(["ada"])); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.codeFileExtensions")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify(logger.logInfo(adjustingFileMatchingPatternsResource)).once(); - verify(logger.logInfo(adjustingTestMatchingPatternsResource)).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).never(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); }); @@ -1972,40 +986,6 @@ describe("inputs.ts", (): void => { inputs.codeFileExtensions, new Set(["ada", "cs", "txt"]), ); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.codeFileExtensions")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify(logger.logInfo(adjustingFileMatchingPatternsResource)).once(); - verify(logger.logInfo(adjustingTestMatchingPatternsResource)).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).never(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); }); @@ -2026,40 +1006,6 @@ describe("inputs.ts", (): void => { inputs.codeFileExtensions, new Set(["ada", "txt"]), ); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.codeFileExtensions")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify(logger.logInfo(adjustingFileMatchingPatternsResource)).once(); - verify(logger.logInfo(adjustingTestMatchingPatternsResource)).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).never(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); }); @@ -2080,40 +1026,6 @@ describe("inputs.ts", (): void => { inputs.codeFileExtensions, new Set(["ada", "cs", "txt"]), ); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.codeFileExtensions")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify(logger.logInfo(adjustingFileMatchingPatternsResource)).once(); - verify(logger.logInfo(adjustingTestMatchingPatternsResource)).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).never(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); }); @@ -2134,40 +1046,6 @@ describe("inputs.ts", (): void => { inputs.codeFileExtensions, new Set(["ada", "cs", "txt"]), ); - verify(logger.logDebug("* Inputs.initialize()")).once(); - verify(logger.logDebug("* Inputs.initializeBaseSize()")).once(); - verify(logger.logDebug("* Inputs.initializeGrowthRate()")).once(); - verify(logger.logDebug("* Inputs.initializeTestFactor()")).once(); - verify( - logger.logDebug("* Inputs.initializeAlwaysCloseComment()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeFileMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeTestMatchingPatterns()"), - ).once(); - verify( - logger.logDebug("* Inputs.initializeMatchingPatterns()"), - ).twice(); - verify( - logger.logDebug("* Inputs.initializeCodeFileExtensions()"), - ).once(); - verify(logger.logDebug("* Inputs.codeFileExtensions")).once(); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify(logger.logInfo(adjustingFileMatchingPatternsResource)).once(); - verify(logger.logInfo(adjustingTestMatchingPatternsResource)).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).never(); - verify(logger.logInfo(disablingTestFactorResource)).never(); - verify(logger.logInfo(settingAlwaysCloseComment)).never(); - verify(logger.logInfo(settingBaseSizeResource)).never(); - verify(logger.logInfo(settingGrowthRateResource)).never(); - verify(logger.logInfo(settingTestFactorResource)).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).never(); verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); }); }); diff --git a/src/task/tests/pullRequests/pullRequest.spec.ts b/src/task/tests/pullRequests/pullRequest.spec.ts index 4f5bf691a..92b72986d 100644 --- a/src/task/tests/pullRequests/pullRequest.spec.ts +++ b/src/task/tests/pullRequests/pullRequest.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { instance, mock, verify, when } from "ts-mockito"; +import { instance, mock, when } from "ts-mockito"; import CodeMetrics from "../../src/metrics/codeMetrics.js"; import Logger from "../../src/utilities/logger.js"; import PullRequest from "../../src/pullRequests/pullRequest.js"; @@ -126,7 +126,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, true); - verify(logger.logDebug("* PullRequest.isPullRequest")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -148,7 +147,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, false); - verify(logger.logDebug("* PullRequest.isPullRequest")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -169,7 +167,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, true); - verify(logger.logDebug("* PullRequest.isPullRequest")).once(); // Finalization delete process.env.SYSTEM_PULLREQUEST_PULLREQUESTID; @@ -189,7 +186,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, false); - verify(logger.logDebug("* PullRequest.isPullRequest")).once(); }); }); @@ -208,7 +204,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, true); - verify(logger.logDebug("* PullRequest.isSupportedProvider")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -234,7 +229,6 @@ describe("pullRequest.ts", (): void => { "'BUILD_REPOSITORY_PROVIDER', accessed within 'PullRequest.isSupportedProvider', is invalid, null, or undefined 'undefined'.", ), ); - verify(logger.logDebug("* PullRequest.isSupportedProvider")).once(); }); { @@ -255,7 +249,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, true); - verify(logger.logDebug("* PullRequest.isSupportedProvider")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -277,7 +270,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, "Other"); - verify(logger.logDebug("* PullRequest.isSupportedProvider")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -299,7 +291,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, null); - verify(logger.logDebug("* PullRequest.getUpdatedDescription()")).once(); }); { @@ -320,9 +311,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, "❌ **Add a description.**"); - verify( - logger.logDebug("* PullRequest.getUpdatedDescription()"), - ).once(); }); }); } @@ -343,7 +331,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, null); - verify(logger.logDebug("* PullRequest.getUpdatedTitle()")).once(); }); { @@ -372,7 +359,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, `S✔ ◾ ${currentTitle}`); - verify(logger.logDebug("* PullRequest.getUpdatedTitle()")).once(); }); }); } @@ -418,7 +404,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, "PREFIX ◾ Title"); - verify(logger.logDebug("* PullRequest.getUpdatedTitle()")).once(); }); }); } diff --git a/src/task/tests/pullRequests/pullRequestComments.spec.ts b/src/task/tests/pullRequests/pullRequestComments.spec.ts index 488477b61..9ce232f64 100644 --- a/src/task/tests/pullRequests/pullRequestComments.spec.ts +++ b/src/task/tests/pullRequests/pullRequestComments.spec.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import { instance, mock, verify, when } from "ts-mockito"; +import { instance, mock, when } from "ts-mockito"; import CodeMetrics from "../../src/metrics/codeMetrics.js"; import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; import CommentData from "../../src/repos/interfaces/commentData.js"; @@ -154,9 +154,6 @@ describe("pullRequestComments.ts", (): void => { // Assert assert.equal(result, "❗ **This file doesn't require review.**"); - verify( - logger.logDebug("* PullRequestComments.noReviewRequiredComment"), - ).once(); }); }); @@ -184,7 +181,6 @@ describe("pullRequestComments.ts", (): void => { assert.deepEqual(result.filesNotRequiringReview, []); assert.deepEqual(result.deletedFilesNotRequiringReview, []); assert.deepEqual(result.commentThreadsRequiringDeletion, []); - verify(logger.logDebug("* PullRequestComments.getCommentData()")).once(); }); { @@ -225,12 +221,6 @@ describe("pullRequestComments.ts", (): void => { assert.deepEqual(result.filesNotRequiringReview, []); assert.deepEqual(result.deletedFilesNotRequiringReview, []); assert.deepEqual(result.commentThreadsRequiringDeletion, []); - verify( - logger.logDebug("* PullRequestComments.getCommentData()"), - ).once(); - verify( - logger.logDebug("* PullRequestComments.getMetricsCommentData()"), - ).atLeast(1); }); }); } @@ -315,14 +305,6 @@ describe("pullRequestComments.ts", (): void => { ); assert.deepEqual(result.deletedFilesNotRequiringReview, []); assert.deepEqual(result.commentThreadsRequiringDeletion, []); - verify( - logger.logDebug("* PullRequestComments.getCommentData()"), - ).once(); - verify( - logger.logDebug( - "* PullRequestComments.getFilesRequiringCommentUpdates()", - ), - ).atLeast(1); }); }, ); @@ -411,14 +393,6 @@ describe("pullRequestComments.ts", (): void => { deletedFilesNotRequiringReview, ); assert.deepEqual(result.commentThreadsRequiringDeletion, []); - verify( - logger.logDebug("* PullRequestComments.getCommentData()"), - ).once(); - verify( - logger.logDebug( - "* PullRequestComments.getFilesRequiringCommentUpdates()", - ), - ).atLeast(1); }); }, ); @@ -456,15 +430,6 @@ describe("pullRequestComments.ts", (): void => { assert.deepEqual(result.filesNotRequiringReview, ["folder/file1.ts"]); assert.deepEqual(result.deletedFilesNotRequiringReview, []); assert.deepEqual(result.commentThreadsRequiringDeletion, []); - verify(logger.logDebug("* PullRequestComments.getCommentData()")).once(); - verify( - logger.logDebug("* PullRequestComments.getMetricsCommentData()"), - ).once(); - verify( - logger.logDebug( - "* PullRequestComments.getFilesRequiringCommentUpdates()", - ), - ).twice(); }); it("should return the expected result when all comment types are present in deleted files not requiring review", async (): Promise => { @@ -501,15 +466,6 @@ describe("pullRequestComments.ts", (): void => { "folder/file1.ts", ]); assert.deepEqual(result.commentThreadsRequiringDeletion, []); - verify(logger.logDebug("* PullRequestComments.getCommentData()")).once(); - verify( - logger.logDebug("* PullRequestComments.getMetricsCommentData()"), - ).once(); - verify( - logger.logDebug( - "* PullRequestComments.getFilesRequiringCommentUpdates()", - ), - ).twice(); }); it("should return the expected result when all comment types are present in both modified and deleted files not requiring review", async (): Promise => { @@ -547,15 +503,6 @@ describe("pullRequestComments.ts", (): void => { assert.deepEqual(result.filesNotRequiringReview, ["folder/file1.ts"]); assert.deepEqual(result.deletedFilesNotRequiringReview, ["file3.ts"]); assert.deepEqual(result.commentThreadsRequiringDeletion, []); - verify(logger.logDebug("* PullRequestComments.getCommentData()")).once(); - verify( - logger.logDebug("* PullRequestComments.getMetricsCommentData()"), - ).once(); - verify( - logger.logDebug( - "* PullRequestComments.getFilesRequiringCommentUpdates()", - ), - ).twice(); }); it("should return the expected result when all comment types are present in both modified and deleted files not requiring review and comments need to be deleted", async (): Promise => { @@ -592,15 +539,6 @@ describe("pullRequestComments.ts", (): void => { assert.deepEqual(result.filesNotRequiringReview, ["folder/file1.ts"]); assert.deepEqual(result.deletedFilesNotRequiringReview, ["file3.ts"]); assert.deepEqual(result.commentThreadsRequiringDeletion, [40]); - verify(logger.logDebug("* PullRequestComments.getCommentData()")).once(); - verify( - logger.logDebug("* PullRequestComments.getMetricsCommentData()"), - ).once(); - verify( - logger.logDebug( - "* PullRequestComments.getFilesRequiringCommentUpdates()", - ), - ).twice(); }); it("should continue when no comment content is present", async (): Promise => { @@ -635,10 +573,6 @@ describe("pullRequestComments.ts", (): void => { assert.equal(result.metricsCommentContent, null); assert.deepEqual(result.filesNotRequiringReview, ["file.ts"]); assert.deepEqual(result.commentThreadsRequiringDeletion, []); - verify(logger.logDebug("* PullRequestComments.getCommentData()")).once(); - verify( - logger.logDebug("* PullRequestComments.getMetricsCommentData()"), - ).once(); }); }); @@ -686,18 +620,6 @@ describe("pullRequestComments.ts", (): void => { "\n" + "[Metrics computed by PR Metrics. Add it to your Azure DevOps and GitHub PRs!](https://aka.ms/PRMetrics/Comment)", ); - verify( - logger.logDebug("* PullRequestComments.getMetricsComment()"), - ).once(); - verify( - logger.logDebug("* PullRequestComments.addCommentSizeStatus()"), - ).once(); - verify( - logger.logDebug("* PullRequestComments.addCommentTestStatus()"), - ).once(); - verify( - logger.logDebug("* PullRequestComments.addCommentMetrics()"), - ).times(5); }); }); } @@ -739,18 +661,6 @@ describe("pullRequestComments.ts", (): void => { "\n" + "[Metrics computed by PR Metrics. Add it to your Azure DevOps and GitHub PRs!](https://aka.ms/PRMetrics/Comment)", ); - verify( - logger.logDebug("* PullRequestComments.getMetricsComment()"), - ).once(); - verify( - logger.logDebug("* PullRequestComments.addCommentSizeStatus()"), - ).once(); - verify( - logger.logDebug("* PullRequestComments.addCommentTestStatus()"), - ).once(); - verify( - logger.logDebug("* PullRequestComments.addCommentMetrics()"), - ).times(5); }); }); } @@ -785,18 +695,6 @@ describe("pullRequestComments.ts", (): void => { "\n" + "[Metrics computed by PR Metrics. Add it to your Azure DevOps and GitHub PRs!](https://aka.ms/PRMetrics/Comment)", ); - verify( - logger.logDebug("* PullRequestComments.getMetricsComment()"), - ).once(); - verify( - logger.logDebug("* PullRequestComments.addCommentSizeStatus()"), - ).once(); - verify( - logger.logDebug("* PullRequestComments.addCommentTestStatus()"), - ).once(); - verify( - logger.logDebug("* PullRequestComments.addCommentMetrics()"), - ).times(5); }); it("should return the expected result when the pull request does not require a specific level of test coverage", async (): Promise => { @@ -828,18 +726,6 @@ describe("pullRequestComments.ts", (): void => { "\n" + "[Metrics computed by PR Metrics. Add it to your Azure DevOps and GitHub PRs!](https://aka.ms/PRMetrics/Comment)", ); - verify( - logger.logDebug("* PullRequestComments.getMetricsComment()"), - ).once(); - verify( - logger.logDebug("* PullRequestComments.addCommentSizeStatus()"), - ).once(); - verify( - logger.logDebug("* PullRequestComments.addCommentTestStatus()"), - ).once(); - verify( - logger.logDebug("* PullRequestComments.addCommentMetrics()"), - ).times(5); }); }); @@ -861,9 +747,6 @@ describe("pullRequestComments.ts", (): void => { // Assert assert.equal(result, CommentThreadStatus.Closed); - verify( - logger.logDebug("* PullRequestComments.getMetricsCommentStatus()"), - ).once(); }); { @@ -891,9 +774,6 @@ describe("pullRequestComments.ts", (): void => { // Assert assert.equal(result, CommentThreadStatus.Closed); - verify( - logger.logDebug("* PullRequestComments.getMetricsCommentStatus()"), - ).once(); }); }); } @@ -946,11 +826,6 @@ describe("pullRequestComments.ts", (): void => { // Assert assert.equal(result, CommentThreadStatus.Active); - verify( - logger.logDebug( - "* PullRequestComments.getMetricsCommentStatus()", - ), - ).once(); }); }, ); diff --git a/src/task/tests/repos/azureReposInvoker.spec.ts b/src/task/tests/repos/azureReposInvoker.spec.ts index ddd0afd0b..7bd57b742 100644 --- a/src/task/tests/repos/azureReposInvoker.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.spec.ts @@ -110,9 +110,6 @@ describe("azureReposInvoker.ts", (): void => { // Assert assert.equal(result, null); - verify( - logger.logDebug("* AzureReposInvoker.isAccessTokenAvailable()"), - ).once(); }); it("should return a string when the token manager fails", async (): Promise => { @@ -133,9 +130,6 @@ describe("azureReposInvoker.ts", (): void => { // Assert assert.equal(result, "Failure"); - verify( - logger.logDebug("* AzureReposInvoker.isAccessTokenAvailable()"), - ).once(); }); it("should return a string when the token does not exist", async (): Promise => { @@ -158,9 +152,6 @@ describe("azureReposInvoker.ts", (): void => { result, "Could not access the Workload Identity Federation or Personal Access Token (PAT). Add the 'WorkloadIdentityFederation' input or 'PR_Metrics_Access_Token' as a secret environment variable.", ); - verify( - logger.logDebug("* AzureReposInvoker.isAccessTokenAvailable()"), - ).once(); }); }); @@ -194,10 +185,6 @@ describe("azureReposInvoker.ts", (): void => { func, `'SYSTEM_TEAMPROJECT', accessed within 'AzureReposInvoker.getGitApi()', is invalid, null, or undefined '${String(variable)}'.`, ); - verify( - logger.logDebug("* AzureReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); }); }); } @@ -231,10 +218,6 @@ describe("azureReposInvoker.ts", (): void => { func, `'BUILD_REPOSITORY_ID', accessed within 'AzureReposInvoker.getGitApi()', is invalid, null, or undefined '${String(variable)}'.`, ); - verify( - logger.logDebug("* AzureReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); }); }); } @@ -268,10 +251,6 @@ describe("azureReposInvoker.ts", (): void => { func, `'PR_METRICS_ACCESS_TOKEN', accessed within 'AzureReposInvoker.getGitApi()', is invalid, null, or undefined '${String(variable)}'.`, ); - verify( - logger.logDebug("* AzureReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); }); }); } @@ -305,10 +284,6 @@ describe("azureReposInvoker.ts", (): void => { func, `'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI', accessed within 'AzureReposInvoker.getGitApi()', is invalid, null, or undefined '${String(variable)}'.`, ); - verify( - logger.logDebug("* AzureReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); verify( azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), ).once(); @@ -361,10 +336,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.getPullRequestById(10, "Project")).once(); - verify( - logger.logDebug("* AzureReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); }); }); } @@ -398,13 +369,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.getPullRequestById(10, "Project")).once(); - verify( - logger.logDebug("* AzureReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify( - logger.logDebug('{"description":"Description","title":"Title"}'), - ).once(); }); it("should return the title and description when available and called multiple times", async (): Promise => { @@ -437,13 +401,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.getPullRequestById(10, "Project")).twice(); - verify( - logger.logDebug("* AzureReposInvoker.getTitleAndDescription()"), - ).twice(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).twice(); - verify( - logger.logDebug('{"description":"Description","title":"Title"}'), - ).twice(); }); it("should return the title when the description is unavailable", async (): Promise => { @@ -474,11 +431,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.getPullRequestById(10, "Project")).once(); - verify( - logger.logDebug("* AzureReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify(logger.logDebug('{"title":"Title"}')).once(); }); it("should throw when the title is unavailable", async (): Promise => { @@ -509,11 +461,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.getPullRequestById(10, "Project")).once(); - verify( - logger.logDebug("* AzureReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify(logger.logDebug("{}")).once(); }); }); @@ -563,8 +510,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.getThreads("RepoID", 10, "Project")).once(); - verify(logger.logDebug("* AzureReposInvoker.getComments()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); }); }); } @@ -602,13 +547,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.getThreads("RepoID", 10, "Project")).once(); - verify(logger.logDebug("* AzureReposInvoker.getComments()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify( - logger.logDebug( - '[{"comments":[{"content":"Content"}],"id":1,"status":1}]', - ), - ).once(); }); it("should return the result when called with a pull request comment whose thread context is null", async (): Promise => { @@ -649,13 +587,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.getThreads("RepoID", 10, "Project")).once(); - verify(logger.logDebug("* AzureReposInvoker.getComments()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify( - logger.logDebug( - '[{"comments":[{"content":"Content"}],"id":1,"status":1,"threadContext":null}]', - ), - ).once(); }); it("should return the result when called with a file comment", async (): Promise => { @@ -694,13 +625,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.getThreads("RepoID", 10, "Project")).once(); - verify(logger.logDebug("* AzureReposInvoker.getComments()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify( - logger.logDebug( - '[{"comments":[{"content":"Content"}],"id":1,"status":1,"threadContext":{"filePath":"/file.ts"}}]', - ), - ).once(); }); it("should return the result when called with both a pull request and file comment", async (): Promise => { @@ -746,13 +670,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.getThreads("RepoID", 10, "Project")).once(); - verify(logger.logDebug("* AzureReposInvoker.getComments()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify( - logger.logDebug( - '[{"comments":[{"content":"PR Content"}],"id":1,"status":1},{"comments":[{"content":"File Content"}],"id":2,"status":1,"threadContext":{"filePath":"/file.ts"}}]', - ), - ).once(); }); it("should return the result when called multiple times", async (): Promise => { @@ -789,13 +706,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.getThreads("RepoID", 10, "Project")).twice(); - verify(logger.logDebug("* AzureReposInvoker.getComments()")).twice(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).twice(); - verify( - logger.logDebug( - '[{"comments":[{"content":"Content"}],"id":1,"status":1}]', - ), - ).twice(); }); it("should throw when provided with a payload with no ID", async (): Promise => { @@ -828,11 +738,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.getThreads("RepoID", 10, "Project")).once(); - verify(logger.logDebug("* AzureReposInvoker.getComments()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify( - logger.logDebug('[{"comments":[{"content":"Content"}],"status":1}]'), - ).once(); }); it("should continue if the payload has no status", async (): Promise => { @@ -880,9 +785,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.getThreads("RepoID", 10, "Project")).once(); - verify(logger.logDebug("* AzureReposInvoker.getComments()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify(logger.logDebug(JSON.stringify(getThreadsResult))).once(); }); { @@ -964,9 +866,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.getThreads("RepoID", 10, "Project")).once(); - verify(logger.logDebug("* AzureReposInvoker.getComments()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify(logger.logDebug(JSON.stringify(getThreadsResult))).once(); }); }); } @@ -1022,10 +921,6 @@ describe("azureReposInvoker.ts", (): void => { verify( gitApi.updatePullRequest(any(), "RepoID", 10, "Project"), ).once(); - verify( - logger.logDebug("* AzureReposInvoker.setTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); }); }); } @@ -1054,10 +949,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).never(); verify(gitApi.updatePullRequest(any(), "RepoID", 10, "Project")).never(); - verify( - logger.logDebug("* AzureReposInvoker.setTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).never(); }); it("should call the API when the title is valid", async (): Promise => { @@ -1100,11 +991,6 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).once(); - verify( - logger.logDebug("* AzureReposInvoker.setTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify(logger.logDebug("{}")).once(); }); it("should call the API when the description is valid", async (): Promise => { @@ -1147,11 +1033,6 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).once(); - verify( - logger.logDebug("* AzureReposInvoker.setTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify(logger.logDebug("{}")).once(); }); it("should call the API when both the title and description are valid", async (): Promise => { @@ -1195,11 +1076,6 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).once(); - verify( - logger.logDebug("* AzureReposInvoker.setTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify(logger.logDebug("{}")).once(); }); it("should call the API when both the title and description are valid and called multiple times", async (): Promise => { @@ -1244,11 +1120,6 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).twice(); - verify( - logger.logDebug("* AzureReposInvoker.setTitleAndDescription()"), - ).twice(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).twice(); - verify(logger.logDebug("{}")).twice(); }); }); @@ -1304,8 +1175,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.createThread(any(), "RepoID", 10, "Project")).once(); - verify(logger.logDebug("* AzureReposInvoker.createComment()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); }); }); } @@ -1355,9 +1224,6 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).once(); - verify(logger.logDebug("* AzureReposInvoker.createComment()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify(logger.logDebug("{}")).once(); }); it("should call the API for no file when called multiple times", async (): Promise => { @@ -1410,9 +1276,6 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).twice(); - verify(logger.logDebug("* AzureReposInvoker.createComment()")).twice(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).twice(); - verify(logger.logDebug("{}")).twice(); }); it("should call the API for a file", async (): Promise => { @@ -1471,9 +1334,6 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).once(); - verify(logger.logDebug("* AzureReposInvoker.createComment()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify(logger.logDebug("{}")).once(); }); it("should call the API for a deleted file", async (): Promise => { @@ -1533,9 +1393,6 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).once(); - verify(logger.logDebug("* AzureReposInvoker.createComment()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify(logger.logDebug("{}")).once(); }); }); @@ -1593,8 +1450,6 @@ describe("azureReposInvoker.ts", (): void => { verify( gitApi.updateComment(any(), "RepoID", 10, 20, 1, "Project"), ).once(); - verify(logger.logDebug("* AzureReposInvoker.updateComment()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); }); }); } @@ -1658,9 +1513,6 @@ describe("azureReposInvoker.ts", (): void => { verify( gitApi.updateThread(any(), "RepoID", 10, 20, "Project"), ).once(); - verify(logger.logDebug("* AzureReposInvoker.updateComment()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify(logger.logDebug("{}")).once(); }); }); } @@ -1734,9 +1586,6 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).once(); - verify(logger.logDebug("* AzureReposInvoker.updateComment()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify(logger.logDebug("{}")).twice(); }); it("should call the API when the comment content is updated", async (): Promise => { @@ -1783,9 +1632,6 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).once(); - verify(logger.logDebug("* AzureReposInvoker.updateComment()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify(logger.logDebug("{}")).once(); }); it("should call the API when the thread status is updated", async (): Promise => { @@ -1834,9 +1680,6 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).once(); - verify(logger.logDebug("* AzureReposInvoker.updateComment()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); - verify(logger.logDebug("{}")).once(); }); it("should call no APIs when neither the comment content nor the thread status are updated", async (): Promise => { @@ -1866,8 +1709,6 @@ describe("azureReposInvoker.ts", (): void => { gitApi.updateComment(any(), "RepoID", 10, 20, 1, "Project"), ).never(); verify(gitApi.updateThread(any(), "RepoID", 10, 20, "Project")).never(); - verify(logger.logDebug("* AzureReposInvoker.updateComment()")).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).never(); }); it("should call the API when called multiple times", async (): Promise => { @@ -1915,9 +1756,6 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).twice(); - verify(logger.logDebug("* AzureReposInvoker.updateComment()")).twice(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).twice(); - verify(logger.logDebug("{}")).twice(); }); }); @@ -1969,10 +1807,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).once(); - verify( - logger.logDebug("* AzureReposInvoker.deleteCommentThread()"), - ).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); }); }); } @@ -2000,10 +1834,6 @@ describe("azureReposInvoker.ts", (): void => { ), ).once(); verify(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).once(); - verify( - logger.logDebug("* AzureReposInvoker.deleteCommentThread()"), - ).once(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).once(); }); it("should call the API when called multiple times", async (): Promise => { @@ -2033,10 +1863,6 @@ describe("azureReposInvoker.ts", (): void => { ).once(); verify(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).once(); verify(gitApi.deleteComment("RepoID", 10, 30, 1, "Project")).once(); - verify( - logger.logDebug("* AzureReposInvoker.deleteCommentThread()"), - ).twice(); - verify(logger.logDebug("* AzureReposInvoker.getGitApi()")).twice(); }); }); }); diff --git a/src/task/tests/repos/gitHubReposInvoker.spec.ts b/src/task/tests/repos/gitHubReposInvoker.spec.ts index 8bf909ae0..294725278 100644 --- a/src/task/tests/repos/gitHubReposInvoker.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.spec.ts @@ -107,9 +107,6 @@ describe("gitHubReposInvoker.ts", (): void => { // Assert assert.equal(result, null); - verify( - logger.logDebug("* GitHubReposInvoker.isAccessTokenAvailable()"), - ).once(); }); it("should return null when the token exists on GitHub", async (): Promise => { @@ -129,9 +126,6 @@ describe("gitHubReposInvoker.ts", (): void => { // Assert assert.equal(result, null); - verify( - logger.logDebug("* GitHubReposInvoker.isAccessTokenAvailable()"), - ).once(); // Finalization delete process.env.PR_METRICS_ACCESS_TOKEN; @@ -157,9 +151,6 @@ describe("gitHubReposInvoker.ts", (): void => { result, "Could not access the Personal Access Token (PAT). Add 'PR_Metrics_Access_Token' as a secret environment variable with Read and Write access to Pull Requests (or access to 'repos' if using a Classic PAT, or write access to 'pull-requests' and 'statuses' if specified within the workflow YAML).", ); - verify( - logger.logDebug("* GitHubReposInvoker.isAccessTokenAvailable()"), - ).once(); }); }); @@ -192,13 +183,6 @@ describe("gitHubReposInvoker.ts", (): void => { func, `'SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI', accessed within 'GitHubReposInvoker.initializeForAzureDevOps()', is invalid, null, or undefined '${String(variable)}'.`, ); - verify( - logger.logDebug("* GitHubReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); }); }); } @@ -223,13 +207,6 @@ describe("gitHubReposInvoker.ts", (): void => { func, "SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI 'https://github.com/microsoft' is in an unexpected format.", ); - verify( - logger.logDebug("* GitHubReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); }); { @@ -263,13 +240,6 @@ describe("gitHubReposInvoker.ts", (): void => { func, `'GITHUB_API_URL', accessed within 'GitHubReposInvoker.initializeForGitHub()', is invalid, null, or undefined '${String(variable)}'.`, ); - verify( - logger.logDebug("* GitHubReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForGitHub()"), - ).once(); // Finalization delete process.env.PR_METRICS_ACCESS_TOKEN; @@ -311,13 +281,6 @@ describe("gitHubReposInvoker.ts", (): void => { func, `'GITHUB_REPOSITORY_OWNER', accessed within 'GitHubReposInvoker.initializeForGitHub()', is invalid, null, or undefined '${String(variable)}'.`, ); - verify( - logger.logDebug("* GitHubReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForGitHub()"), - ).once(); // Finalization delete process.env.PR_METRICS_ACCESS_TOKEN; @@ -361,13 +324,6 @@ describe("gitHubReposInvoker.ts", (): void => { func, `'GITHUB_REPOSITORY', accessed within 'GitHubReposInvoker.initializeForGitHub()', is invalid, null, or undefined '${String(variable)}'.`, ); - verify( - logger.logDebug("* GitHubReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForGitHub()"), - ).once(); // Finalization delete process.env.PR_METRICS_ACCESS_TOKEN; @@ -403,13 +359,6 @@ describe("gitHubReposInvoker.ts", (): void => { func, "GITHUB_REPOSITORY 'microsoft' is in an unexpected format.", ); - verify( - logger.logDebug("* GitHubReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForGitHub()"), - ).once(); // Finalization delete process.env.PR_METRICS_ACCESS_TOKEN; @@ -448,18 +397,6 @@ describe("gitHubReposInvoker.ts", (): void => { assert.equal(result.description, "Description"); verify(octokitWrapper.initialize(any())).once(); verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); - verify( - logger.logDebug("* GitHubReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify( - logger.logDebug( - JSON.stringify(GitHubReposInvokerConstants.getPullResponse), - ), - ).once(); }); it("should succeed when the inputs are valid and the task is running on GitHub", async (): Promise => { @@ -495,18 +432,6 @@ describe("gitHubReposInvoker.ts", (): void => { assert.equal(result.description, "Description"); verify(octokitWrapper.initialize(any())).once(); verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); - verify( - logger.logDebug("* GitHubReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForGitHub()"), - ).once(); - verify( - logger.logDebug( - JSON.stringify(GitHubReposInvokerConstants.getPullResponse), - ), - ).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -546,18 +471,6 @@ describe("gitHubReposInvoker.ts", (): void => { assert.equal(result.description, "Description"); verify(octokitWrapper.initialize(any())).once(); verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); - verify( - logger.logDebug("* GitHubReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify( - logger.logDebug( - JSON.stringify(GitHubReposInvokerConstants.getPullResponse), - ), - ).once(); }); it("should succeed when the inputs are valid and GitHub Enterprise is in use", async (): Promise => { @@ -595,23 +508,6 @@ describe("gitHubReposInvoker.ts", (): void => { assert.equal(result.description, "Description"); verify(octokitWrapper.initialize(any())).once(); verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); - verify( - logger.logDebug("* GitHubReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify( - logger.logDebug( - "Using Base URL 'https://organization.githubenterprise.com/api/v3'.", - ), - ).once(); - verify( - logger.logDebug( - JSON.stringify(GitHubReposInvokerConstants.getPullResponse), - ), - ).once(); }); it("should succeed when called twice with the inputs valid", async (): Promise => { @@ -644,18 +540,6 @@ describe("gitHubReposInvoker.ts", (): void => { assert.equal(result.description, "Description"); verify(octokitWrapper.initialize(any())).once(); verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).twice(); - verify( - logger.logDebug("* GitHubReposInvoker.getTitleAndDescription()"), - ).twice(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).twice(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify( - logger.logDebug( - JSON.stringify(GitHubReposInvokerConstants.getPullResponse), - ), - ).twice(); }); it("should succeed when the description is null", async (): Promise => { @@ -693,14 +577,6 @@ describe("gitHubReposInvoker.ts", (): void => { assert.equal(result.description, null); verify(octokitWrapper.initialize(any())).once(); verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); - verify( - logger.logDebug("* GitHubReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify(logger.logDebug(JSON.stringify(currentMockPullResponse))).once(); }); { @@ -748,13 +624,6 @@ describe("gitHubReposInvoker.ts", (): void => { await AssertExtensions.toThrowAsync(func, expectedMessage); assert.equal(result.internalMessage, "Test"); verify(octokitWrapper.initialize(any())).once(); - verify( - logger.logDebug("* GitHubReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); }); }); } @@ -789,13 +658,6 @@ describe("gitHubReposInvoker.ts", (): void => { // Assert await AssertExtensions.toThrowAsync(func, "Error"); verify(octokitWrapper.initialize(any())).once(); - verify( - logger.logDebug("* GitHubReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); }); it("should initialize log object correctly", async (): Promise => { @@ -878,15 +740,6 @@ describe("gitHubReposInvoker.ts", (): void => { verify( octokitWrapper.getReviewComments("microsoft", "PR-Metrics", 12345), ).once(); - verify(logger.logDebug("* GitHubReposInvoker.getComments()")).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify( - logger.logDebug("* GitHubReposInvoker.convertPullRequestComments()"), - ).once(); - verify(logger.logDebug(JSON.stringify(response))).once(); }); it("should return the result when called with a file comment", async (): Promise => { @@ -929,19 +782,6 @@ describe("gitHubReposInvoker.ts", (): void => { verify( octokitWrapper.getReviewComments("microsoft", "PR-Metrics", 12345), ).once(); - verify(logger.logDebug("* GitHubReposInvoker.getComments()")).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify( - logger.logDebug("* GitHubReposInvoker.convertPullRequestComments()"), - ).once(); - verify( - logger.logDebug( - JSON.stringify(GitHubReposInvokerConstants.getReviewCommentsResponse), - ), - ).once(); }); it("should return the result when called with both a pull request and file comment", async (): Promise => { @@ -1000,20 +840,6 @@ describe("gitHubReposInvoker.ts", (): void => { verify( octokitWrapper.getReviewComments("microsoft", "PR-Metrics", 12345), ).once(); - verify(logger.logDebug("* GitHubReposInvoker.getComments()")).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify( - logger.logDebug("* GitHubReposInvoker.convertPullRequestComments()"), - ).once(); - verify(logger.logDebug(JSON.stringify(response))).once(); - verify( - logger.logDebug( - JSON.stringify(GitHubReposInvokerConstants.getReviewCommentsResponse), - ), - ).once(); }); it("should skip pull request comments with no body", async (): Promise => { @@ -1059,15 +885,6 @@ describe("gitHubReposInvoker.ts", (): void => { verify( octokitWrapper.getReviewComments("microsoft", "PR-Metrics", 12345), ).once(); - verify(logger.logDebug("* GitHubReposInvoker.getComments()")).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify( - logger.logDebug("* GitHubReposInvoker.convertPullRequestComments()"), - ).once(); - verify(logger.logDebug(JSON.stringify(response))).once(); }); }); @@ -1085,10 +902,6 @@ describe("gitHubReposInvoker.ts", (): void => { await gitHubReposInvoker.setTitleAndDescription(null, null); // Assert - verify( - logger.logDebug("* GitHubReposInvoker.setTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).never(); }); it("should succeed when the title and description are both set", async (): Promise => { @@ -1125,18 +938,6 @@ describe("gitHubReposInvoker.ts", (): void => { "Description", ), ).once(); - verify( - logger.logDebug("* GitHubReposInvoker.setTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify( - logger.logDebug( - JSON.stringify(GitHubReposInvokerConstants.getPullResponse), - ), - ).once(); }); it("should succeed when the title is set", async (): Promise => { @@ -1173,14 +974,6 @@ describe("gitHubReposInvoker.ts", (): void => { null, ), ).once(); - verify( - logger.logDebug("* GitHubReposInvoker.setTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify(logger.logDebug("null")).once(); }); it("should succeed when the description is set", async (): Promise => { @@ -1217,14 +1010,6 @@ describe("gitHubReposInvoker.ts", (): void => { "Description", ), ).once(); - verify( - logger.logDebug("* GitHubReposInvoker.setTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify(logger.logDebug("null")).once(); }); }); @@ -1267,13 +1052,6 @@ describe("gitHubReposInvoker.ts", (): void => { "sha54321", ), ).once(); - verify(logger.logDebug("* GitHubReposInvoker.createComment()")).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.getCommitId()")).once(); - verify(logger.logDebug("null")).once(); }); it("should throw when the commit list is empty", async (): Promise => { @@ -1322,12 +1100,6 @@ describe("gitHubReposInvoker.ts", (): void => { verify( octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), ).once(); - verify(logger.logDebug("* GitHubReposInvoker.createComment()")).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.getCommitId()")).once(); }); it("should succeed when there are multiple pages of commits", async (): Promise => { @@ -1381,13 +1153,6 @@ describe("gitHubReposInvoker.ts", (): void => { "sha54321", ), ).once(); - verify(logger.logDebug("* GitHubReposInvoker.createComment()")).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.getCommitId()")).once(); - verify(logger.logDebug("null")).once(); }); it("should throw when the link header does not match the expected format", async (): Promise => { @@ -1433,12 +1198,6 @@ describe("gitHubReposInvoker.ts", (): void => { verify( octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), ).once(); - verify(logger.logDebug("* GitHubReposInvoker.createComment()")).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.getCommitId()")).once(); }); it("should succeed when a file name is specified and the method is called twice", async (): Promise => { @@ -1480,13 +1239,6 @@ describe("gitHubReposInvoker.ts", (): void => { "sha54321", ), ).twice(); - verify(logger.logDebug("* GitHubReposInvoker.createComment()")).twice(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).twice(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.getCommitId()")).once(); - verify(logger.logDebug("null")).twice(); }); it("should succeed when createReviewComment() returns undefined", async (): Promise => { @@ -1537,13 +1289,6 @@ describe("gitHubReposInvoker.ts", (): void => { "sha54321", ), ).once(); - verify(logger.logDebug("* GitHubReposInvoker.createComment()")).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.getCommitId()")).once(); - verify(logger.logDebug("null")).once(); }); { @@ -1608,14 +1353,6 @@ describe("gitHubReposInvoker.ts", (): void => { "sha54321", ), ).once(); - verify( - logger.logDebug("* GitHubReposInvoker.createComment()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.getCommitId()")).once(); verify( logger.logInfo( "GitHub createReviewComment() threw a 422 error related to a large diff. Ignoring as this is expected.", @@ -1688,14 +1425,6 @@ describe("gitHubReposInvoker.ts", (): void => { "sha54321", ), ).once(); - verify( - logger.logDebug("* GitHubReposInvoker.createComment()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.getCommitId()")).once(); }); }); } @@ -1733,12 +1462,6 @@ describe("gitHubReposInvoker.ts", (): void => { "Content", ), ).once(); - verify(logger.logDebug("* GitHubReposInvoker.createComment()")).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify(logger.logDebug("null")).once(); }); }); @@ -1756,11 +1479,6 @@ describe("gitHubReposInvoker.ts", (): void => { await gitHubReposInvoker.updateComment(54321, null); // Assert - verify(logger.logDebug("* GitHubReposInvoker.updateComment()")).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).never(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).never(); }); it("should succeed when the content is set", async (): Promise => { @@ -1797,12 +1515,6 @@ describe("gitHubReposInvoker.ts", (): void => { "Content", ), ).once(); - verify(logger.logDebug("* GitHubReposInvoker.updateComment()")).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify(logger.logDebug("null")).once(); }); }); @@ -1835,14 +1547,6 @@ describe("gitHubReposInvoker.ts", (): void => { verify( octokitWrapper.deleteReviewComment("microsoft", "PR-Metrics", 54321), ).once(); - verify( - logger.logDebug("* GitHubReposInvoker.deleteCommentThread()"), - ).once(); - verify(logger.logDebug("* GitHubReposInvoker.initialize()")).once(); - verify( - logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()"), - ).once(); - verify(logger.logDebug("null")).once(); }); }); }); diff --git a/src/task/tests/repos/reposInvoker.spec.ts b/src/task/tests/repos/reposInvoker.spec.ts index d06bda47d..56bfecbd5 100644 --- a/src/task/tests/repos/reposInvoker.spec.ts +++ b/src/task/tests/repos/reposInvoker.spec.ts @@ -41,8 +41,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.isAccessTokenAvailable()).once(); verify(gitHubReposInvoker.isAccessTokenAvailable()).never(); - verify(logger.logDebug("* ReposInvoker.isAccessTokenAvailable()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); assert.equal(result, null); // Finalization @@ -67,10 +65,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.isAccessTokenAvailable()).twice(); verify(gitHubReposInvoker.isAccessTokenAvailable()).never(); - verify( - logger.logDebug("* ReposInvoker.isAccessTokenAvailable()"), - ).twice(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).twice(); assert.equal(result1, null); assert.equal(result2, null); @@ -93,8 +87,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.isAccessTokenAvailable()).never(); verify(gitHubReposInvoker.isAccessTokenAvailable()).once(); - verify(logger.logDebug("* ReposInvoker.isAccessTokenAvailable()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); assert.equal(result, null); // Finalization @@ -121,10 +113,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.isAccessTokenAvailable()).never(); verify(gitHubReposInvoker.isAccessTokenAvailable()).once(); - verify( - logger.logDebug("* ReposInvoker.isAccessTokenAvailable()"), - ).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); assert.equal(result, null); // Finalization @@ -153,8 +141,6 @@ describe("reposInvoker.ts", (): void => { ); verify(azureReposInvoker.isAccessTokenAvailable()).never(); verify(gitHubReposInvoker.isAccessTokenAvailable()).never(); - verify(logger.logDebug("* ReposInvoker.isAccessTokenAvailable()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); }); it("should throw when the repo type is set to an invalid value", async (): Promise => { @@ -177,8 +163,6 @@ describe("reposInvoker.ts", (): void => { ); verify(azureReposInvoker.isAccessTokenAvailable()).never(); verify(gitHubReposInvoker.isAccessTokenAvailable()).never(); - verify(logger.logDebug("* ReposInvoker.isAccessTokenAvailable()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -202,8 +186,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.getTitleAndDescription()).once(); verify(gitHubReposInvoker.getTitleAndDescription()).never(); - verify(logger.logDebug("* ReposInvoker.getTitleAndDescription()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); assert.equal(result, null); // Finalization @@ -226,8 +208,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.getTitleAndDescription()).never(); verify(gitHubReposInvoker.getTitleAndDescription()).once(); - verify(logger.logDebug("* ReposInvoker.getTitleAndDescription()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); assert.equal(result, null); // Finalization @@ -254,10 +234,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.getTitleAndDescription()).never(); verify(gitHubReposInvoker.getTitleAndDescription()).once(); - verify( - logger.logDebug("* ReposInvoker.getTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); assert.equal(result, null); // Finalization @@ -286,8 +262,6 @@ describe("reposInvoker.ts", (): void => { ); verify(azureReposInvoker.getTitleAndDescription()).never(); verify(gitHubReposInvoker.getTitleAndDescription()).never(); - verify(logger.logDebug("* ReposInvoker.getTitleAndDescription()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); }); it("should throw when the repo type is set to an invalid value", async (): Promise => { @@ -310,8 +284,6 @@ describe("reposInvoker.ts", (): void => { ); verify(azureReposInvoker.getTitleAndDescription()).never(); verify(gitHubReposInvoker.getTitleAndDescription()).never(); - verify(logger.logDebug("* ReposInvoker.getTitleAndDescription()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -334,8 +306,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.getComments()).once(); verify(gitHubReposInvoker.getComments()).never(); - verify(logger.logDebug("* ReposInvoker.getComments()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); assert.equal(result, null); // Finalization @@ -357,8 +327,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.getComments()).never(); verify(gitHubReposInvoker.getComments()).once(); - verify(logger.logDebug("* ReposInvoker.getComments()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); assert.equal(result, null); // Finalization @@ -384,8 +352,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.getComments()).never(); verify(gitHubReposInvoker.getComments()).once(); - verify(logger.logDebug("* ReposInvoker.getComments()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); assert.equal(result, null); // Finalization @@ -414,8 +380,6 @@ describe("reposInvoker.ts", (): void => { ); verify(azureReposInvoker.getComments()).never(); verify(gitHubReposInvoker.getComments()).never(); - verify(logger.logDebug("* ReposInvoker.getComments()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); }); it("should throw when the repo type is set to an invalid value", async (): Promise => { @@ -438,8 +402,6 @@ describe("reposInvoker.ts", (): void => { ); verify(azureReposInvoker.getComments()).never(); verify(gitHubReposInvoker.getComments()).never(); - verify(logger.logDebug("* ReposInvoker.getComments()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -462,8 +424,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.setTitleAndDescription(null, null)).once(); verify(gitHubReposInvoker.setTitleAndDescription(null, null)).never(); - verify(logger.logDebug("* ReposInvoker.setTitleAndDescription()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -484,8 +444,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.setTitleAndDescription(null, null)).never(); verify(gitHubReposInvoker.setTitleAndDescription(null, null)).once(); - verify(logger.logDebug("* ReposInvoker.setTitleAndDescription()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -510,10 +468,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.setTitleAndDescription(null, null)).never(); verify(gitHubReposInvoker.setTitleAndDescription(null, null)).once(); - verify( - logger.logDebug("* ReposInvoker.setTitleAndDescription()"), - ).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -541,8 +495,6 @@ describe("reposInvoker.ts", (): void => { ); verify(azureReposInvoker.setTitleAndDescription(null, null)).never(); verify(gitHubReposInvoker.setTitleAndDescription(null, null)).never(); - verify(logger.logDebug("* ReposInvoker.setTitleAndDescription()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); }); it("should throw when the repo type is set to an invalid value", async (): Promise => { @@ -565,8 +517,6 @@ describe("reposInvoker.ts", (): void => { ); verify(azureReposInvoker.setTitleAndDescription(null, null)).never(); verify(gitHubReposInvoker.setTitleAndDescription(null, null)).never(); - verify(logger.logDebug("* ReposInvoker.setTitleAndDescription()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -609,8 +559,6 @@ describe("reposInvoker.ts", (): void => { false, ), ).never(); - verify(logger.logDebug("* ReposInvoker.createComment()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -651,8 +599,6 @@ describe("reposInvoker.ts", (): void => { false, ), ).once(); - verify(logger.logDebug("* ReposInvoker.createComment()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -697,8 +643,6 @@ describe("reposInvoker.ts", (): void => { false, ), ).once(); - verify(logger.logDebug("* ReposInvoker.createComment()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -741,8 +685,6 @@ describe("reposInvoker.ts", (): void => { false, ), ).never(); - verify(logger.logDebug("* ReposInvoker.createComment()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); }); it("should throw when the repo type is set to an invalid value", async (): Promise => { @@ -780,8 +722,6 @@ describe("reposInvoker.ts", (): void => { false, ), ).never(); - verify(logger.logDebug("* ReposInvoker.createComment()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -805,8 +745,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.updateComment(0, null, null)).once(); // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubReposInvoker.updateComment(0, null, null)).never(); - verify(logger.logDebug("* ReposInvoker.updateComment()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -828,8 +766,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.updateComment(0, null, null)).never(); // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubReposInvoker.updateComment(0, null, null)).once(); - verify(logger.logDebug("* ReposInvoker.updateComment()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -855,8 +791,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.updateComment(0, null, null)).never(); // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubReposInvoker.updateComment(0, null, null)).once(); - verify(logger.logDebug("* ReposInvoker.updateComment()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -885,8 +819,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.updateComment(0, null, null)).never(); // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubReposInvoker.updateComment(0, null, null)).never(); - verify(logger.logDebug("* ReposInvoker.updateComment()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); }); it("should throw when the repo type is set to an invalid value", async (): Promise => { @@ -910,8 +842,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.updateComment(0, null, null)).never(); // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubReposInvoker.updateComment(0, null, null)).never(); - verify(logger.logDebug("* ReposInvoker.updateComment()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -934,8 +864,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.deleteCommentThread(20)).once(); verify(gitHubReposInvoker.deleteCommentThread(20)).never(); - verify(logger.logDebug("* ReposInvoker.deleteCommentThread()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -956,8 +884,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.deleteCommentThread(20)).never(); verify(gitHubReposInvoker.deleteCommentThread(20)).once(); - verify(logger.logDebug("* ReposInvoker.deleteCommentThread()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.GITHUB_ACTION; @@ -982,10 +908,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.deleteCommentThread(20)).never(); verify(gitHubReposInvoker.deleteCommentThread(20)).once(); - verify( - logger.logDebug("* ReposInvoker.deleteCommentThread()"), - ).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; @@ -1013,8 +935,6 @@ describe("reposInvoker.ts", (): void => { ); verify(azureReposInvoker.deleteCommentThread(20)).never(); verify(gitHubReposInvoker.deleteCommentThread(20)).never(); - verify(logger.logDebug("* ReposInvoker.deleteCommentThread()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); }); it("should throw when the repo type is set to an invalid value", async (): Promise => { @@ -1037,8 +957,6 @@ describe("reposInvoker.ts", (): void => { ); verify(azureReposInvoker.deleteCommentThread(20)).never(); verify(gitHubReposInvoker.deleteCommentThread(20)).never(); - verify(logger.logDebug("* ReposInvoker.deleteCommentThread()")).once(); - verify(logger.logDebug("* ReposInvoker.getReposInvoker()")).once(); // Finalization delete process.env.BUILD_REPOSITORY_PROVIDER; diff --git a/src/task/tests/repos/tokenManager.spec.ts b/src/task/tests/repos/tokenManager.spec.ts index 553ec326b..aab364f36 100644 --- a/src/task/tests/repos/tokenManager.spec.ts +++ b/src/task/tests/repos/tokenManager.spec.ts @@ -156,12 +156,6 @@ describe("tokenManager.ts", (): void => { // Assert assert.equal(result, null); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify( - logger.logDebug( - "No workload identity federation specified. Using Personal Access Token (PAT) for authentication.", - ), - ).once(); }); it("returns a string indicating that the authorization scheme is invalid", async (): Promise => { @@ -189,12 +183,6 @@ describe("tokenManager.ts", (): void => { // Assert assert.equal(result, null); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify( - logger.logDebug( - "Using workload identity federation 'Id' for authentication.", - ), - ).once(); }); it("throws an error when the service principal ID is null", async (): Promise => { @@ -220,8 +208,6 @@ describe("tokenManager.ts", (): void => { func, "'servicePrincipalId', accessed within 'TokenManager.getAccessToken()', is invalid, null, or undefined 'null'.", ); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify(logger.logDebug("* TokenManager.getAccessToken()")).once(); }); it("throws an error when the tenant ID is null", async (): Promise => { @@ -244,8 +230,6 @@ describe("tokenManager.ts", (): void => { func, "'tenantId', accessed within 'TokenManager.getAccessToken()', is invalid, null, or undefined 'null'.", ); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify(logger.logDebug("* TokenManager.getAccessToken()")).once(); }); it("throws an error when the service principal ID is not a valid GUID", async (): Promise => { @@ -271,8 +255,6 @@ describe("tokenManager.ts", (): void => { func, "'servicePrincipalId', accessed within 'TokenManager.getAccessToken()', is not a valid GUID 'NotAGuid'.", ); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify(logger.logDebug("* TokenManager.getAccessToken()")).once(); }); it("throws an error when the tenant ID is not a valid GUID", async (): Promise => { @@ -295,8 +277,6 @@ describe("tokenManager.ts", (): void => { func, "'tenantId', accessed within 'TokenManager.getAccessToken()', is not a valid GUID 'NotAGuid'.", ); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify(logger.logDebug("* TokenManager.getAccessToken()")).once(); }); { @@ -332,14 +312,6 @@ describe("tokenManager.ts", (): void => { func, `Could not acquire authorization token from workload identity federation as the scheme was '${endpointAuthorization?.scheme ?? ""}'.`, ); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify(logger.logDebug("* TokenManager.getAccessToken()")).once(); - verify( - logger.logDebug("* TokenManager.getFederatedToken()"), - ).once(); - verify( - logger.logDebug("* TokenManager.getSystemAccessToken()"), - ).once(); }); }, ); @@ -371,15 +343,6 @@ describe("tokenManager.ts", (): void => { func, "'endpointAuthorization.parameters.AccessToken', accessed within 'TokenManager.getSystemAccessToken()', is invalid, null, or undefined 'undefined'.", ); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify(logger.logDebug("* TokenManager.getAccessToken()")).once(); - verify(logger.logDebug("* TokenManager.getFederatedToken()")).once(); - verify(logger.logDebug("* TokenManager.getSystemAccessToken()")).once(); - verify( - logger.logDebug( - "Acquired authorization token from workload identity federation.", - ), - ).once(); }); it("throws an error when the collection URI is undefined", async (): Promise => { @@ -400,15 +363,6 @@ describe("tokenManager.ts", (): void => { func, "'SYSTEM_COLLECTIONURI', accessed within 'TokenManager.getFederatedToken()', is invalid, null, or undefined 'undefined'.", ); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify(logger.logDebug("* TokenManager.getAccessToken()")).once(); - verify(logger.logDebug("* TokenManager.getFederatedToken()")).once(); - verify(logger.logDebug("* TokenManager.getSystemAccessToken()")).once(); - verify( - logger.logDebug( - "Acquired authorization token from workload identity federation.", - ), - ).once(); }); it("throws an error when the team project URI is undefined", async (): Promise => { @@ -429,15 +383,6 @@ describe("tokenManager.ts", (): void => { func, "'SYSTEM_TEAMPROJECTID', accessed within 'TokenManager.getFederatedToken()', is invalid, null, or undefined 'undefined'.", ); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify(logger.logDebug("* TokenManager.getAccessToken()")).once(); - verify(logger.logDebug("* TokenManager.getFederatedToken()")).once(); - verify(logger.logDebug("* TokenManager.getSystemAccessToken()")).once(); - verify( - logger.logDebug( - "Acquired authorization token from workload identity federation.", - ), - ).once(); }); it("throws an error when the host type is undefined", async (): Promise => { @@ -458,15 +403,6 @@ describe("tokenManager.ts", (): void => { func, "'SYSTEM_HOSTTYPE', accessed within 'TokenManager.getFederatedToken()', is invalid, null, or undefined 'undefined'.", ); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify(logger.logDebug("* TokenManager.getAccessToken()")).once(); - verify(logger.logDebug("* TokenManager.getFederatedToken()")).once(); - verify(logger.logDebug("* TokenManager.getSystemAccessToken()")).once(); - verify( - logger.logDebug( - "Acquired authorization token from workload identity federation.", - ), - ).once(); }); it("throws an error when the plan ID is undefined", async (): Promise => { @@ -487,15 +423,6 @@ describe("tokenManager.ts", (): void => { func, "'SYSTEM_PLANID', accessed within 'TokenManager.getFederatedToken()', is invalid, null, or undefined 'undefined'.", ); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify(logger.logDebug("* TokenManager.getAccessToken()")).once(); - verify(logger.logDebug("* TokenManager.getFederatedToken()")).once(); - verify(logger.logDebug("* TokenManager.getSystemAccessToken()")).once(); - verify( - logger.logDebug( - "Acquired authorization token from workload identity federation.", - ), - ).once(); }); it("throws an error when the job ID is undefined", async (): Promise => { @@ -516,15 +443,6 @@ describe("tokenManager.ts", (): void => { func, "'SYSTEM_JOBID', accessed within 'TokenManager.getFederatedToken()', is invalid, null, or undefined 'undefined'.", ); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify(logger.logDebug("* TokenManager.getAccessToken()")).once(); - verify(logger.logDebug("* TokenManager.getFederatedToken()")).once(); - verify(logger.logDebug("* TokenManager.getSystemAccessToken()")).once(); - verify( - logger.logDebug( - "Acquired authorization token from workload identity federation.", - ), - ).once(); }); it("throws an error when the OIDC token is undefined", async (): Promise => { @@ -554,15 +472,6 @@ describe("tokenManager.ts", (): void => { func, "'response.oidcToken', accessed within 'TokenManager.getFederatedToken()', is invalid, null, or undefined 'undefined'.", ); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify(logger.logDebug("* TokenManager.getAccessToken()")).once(); - verify(logger.logDebug("* TokenManager.getFederatedToken()")).once(); - verify(logger.logDebug("* TokenManager.getSystemAccessToken()")).once(); - verify( - logger.logDebug( - "Acquired authorization token from workload identity federation.", - ), - ).once(); }); it("throws an error when Azure sign in fails", async (): Promise => { @@ -599,15 +508,6 @@ describe("tokenManager.ts", (): void => { // Assert await AssertExtensions.toThrowAsync(func, "Error Message"); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify(logger.logDebug("* TokenManager.getAccessToken()")).once(); - verify(logger.logDebug("* TokenManager.getFederatedToken()")).once(); - verify(logger.logDebug("* TokenManager.getSystemAccessToken()")).once(); - verify( - logger.logDebug( - "Acquired authorization token from workload identity federation.", - ), - ).once(); verify(runnerInvoker.setSecret("OidcToken")).once(); }); @@ -644,15 +544,6 @@ describe("tokenManager.ts", (): void => { // Assert await AssertExtensions.toThrowAsync(func, "Error Message"); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify(logger.logDebug("* TokenManager.getAccessToken()")).once(); - verify(logger.logDebug("* TokenManager.getFederatedToken()")).once(); - verify(logger.logDebug("* TokenManager.getSystemAccessToken()")).once(); - verify( - logger.logDebug( - "Acquired authorization token from workload identity federation.", - ), - ).once(); verify(runnerInvoker.setSecret("OidcToken")).once(); }); @@ -670,15 +561,6 @@ describe("tokenManager.ts", (): void => { // Assert assert.equal(result, null); assert.equal(process.env.PR_METRICS_ACCESS_TOKEN, "AccessToken"); - verify(logger.logDebug("* TokenManager.getToken()")).once(); - verify(logger.logDebug("* TokenManager.getAccessToken()")).once(); - verify(logger.logDebug("* TokenManager.getFederatedToken()")).once(); - verify(logger.logDebug("* TokenManager.getSystemAccessToken()")).once(); - verify( - logger.logDebug( - "Acquired authorization token from workload identity federation.", - ), - ).once(); verify(runnerInvoker.setSecret("OidcToken")).once(); verify(runnerInvoker.setSecret("AccessToken")).once(); }); @@ -699,15 +581,6 @@ describe("tokenManager.ts", (): void => { assert.equal(result1, null); assert.equal(result2, null); assert.equal(process.env.PR_METRICS_ACCESS_TOKEN, "AccessToken"); - verify(logger.logDebug("* TokenManager.getToken()")).twice(); - verify(logger.logDebug("* TokenManager.getAccessToken()")).once(); - verify(logger.logDebug("* TokenManager.getFederatedToken()")).once(); - verify(logger.logDebug("* TokenManager.getSystemAccessToken()")).once(); - verify( - logger.logDebug( - "Acquired authorization token from workload identity federation.", - ), - ).once(); verify(runnerInvoker.setSecret("OidcToken")).once(); verify(runnerInvoker.setSecret("AccessToken")).once(); }); From dc6d5083e67342bec62b17bf8122f5f3b66e70c1 Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Fri, 17 Apr 2026 14:46:35 +0100 Subject: [PATCH 02/27] Replace localisation mock blocks with stubLocalization helper Adds tests/testUtilities/stubLocalization.ts that reads the real resources.resjson file and wires runnerInvoker.loc() to return actual values with util.format parameter substitution. Replaces hand-written loc() stubs in seven spec files with a single helper call, removing ~370 lines of duplicated resource mocks. Side effects caught by the migration: - azureReposInvoker.spec.ts had outdated stub text for the insufficient permissions message ('Code > Read' vs the real 'Code > Read & write'); five assertions updated to match the real resource. - tokenManager.spec.ts "invalid scheme" test was passing accidentally because its manual stub args did not match the actual SUT call, leaving loc() to return null. The test now receives the real formatted error string and asserts it, matching the test's own stated intent. inputs.spec.ts is not migrated in this pass; it embeds the mock return values in verify(logger.logInfo(...)) assertions across dozens of tests, which requires a separate per-test refactor. - 783 tests still pass, 100% coverage preserved. --- src/task/tests/metrics/codeMetrics.spec.ts | 146 +----------------- .../metrics/codeMetricsCalculator.spec.ts | 45 +----- .../tests/pullRequests/pullRequest.spec.ts | 88 +---------- .../pullRequests/pullRequestComments.spec.ts | 72 +-------- .../tests/repos/azureReposInvoker.spec.ts | 33 ++-- .../tests/repos/gitHubReposInvoker.spec.ts | 19 +-- src/task/tests/repos/tokenManager.spec.ts | 16 +- .../tests/testUtilities/stubLocalization.ts | 73 +++++++++ 8 files changed, 98 insertions(+), 394 deletions(-) create mode 100644 src/task/tests/testUtilities/stubLocalization.ts diff --git a/src/task/tests/metrics/codeMetrics.spec.ts b/src/task/tests/metrics/codeMetrics.spec.ts index c885e9691..e60765d15 100644 --- a/src/task/tests/metrics/codeMetrics.spec.ts +++ b/src/task/tests/metrics/codeMetrics.spec.ts @@ -13,6 +13,7 @@ import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; +import { stubLocalization } from "../testUtilities/stubLocalization.js"; describe("codeMetrics.ts", (): void => { let gitInvoker: GitInvoker; @@ -40,150 +41,7 @@ describe("codeMetrics.ts", (): void => { logger = mock(Logger); runnerInvoker = mock(RunnerInvoker); - when(runnerInvoker.loc("metrics.codeMetrics.titleSizeXS")).thenReturn("XS"); - when(runnerInvoker.loc("metrics.codeMetrics.titleSizeS")).thenReturn("S"); - when(runnerInvoker.loc("metrics.codeMetrics.titleSizeM")).thenReturn("M"); - when(runnerInvoker.loc("metrics.codeMetrics.titleSizeL")).thenReturn("L"); - when(runnerInvoker.loc("metrics.codeMetrics.titleSizeXL")).thenReturn("XL"); - when( - runnerInvoker.loc("metrics.codeMetrics.titleTestsSufficient"), - ).thenReturn("✔"); - when( - runnerInvoker.loc("metrics.codeMetrics.titleTestsInsufficient"), - ).thenReturn("⚠️"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "XS", - "✔", - ), - ).thenReturn("XS✔"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "XS", - "⚠️", - ), - ).thenReturn("XS⚠️"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "S", - "✔", - ), - ).thenReturn("S✔"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "S", - "⚠️", - ), - ).thenReturn("S⚠️"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "M", - "✔", - ), - ).thenReturn("M✔"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "M", - "⚠️", - ), - ).thenReturn("M⚠️"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "L", - "✔", - ), - ).thenReturn("L✔"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "L", - "⚠️", - ), - ).thenReturn("L⚠️"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "XL", - "✔", - ), - ).thenReturn("XL✔"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "XL", - "⚠️", - ), - ).thenReturn("XL⚠️"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "2XL", - "✔", - ), - ).thenReturn("2XL✔"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "2XL", - "⚠️", - ), - ).thenReturn("2XL⚠️"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "3XL", - "✔", - ), - ).thenReturn("3XL✔"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "3XL", - "⚠️", - ), - ).thenReturn("3XL⚠️"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "10XL", - "✔", - ), - ).thenReturn("10XL✔"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "10XL", - "⚠️", - ), - ).thenReturn("10XL⚠️"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "XS", - "", - ), - ).thenReturn("XS"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "S", - "", - ), - ).thenReturn("S"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "M", - "", - ), - ).thenReturn("M"); + stubLocalization(runnerInvoker); }); { diff --git a/src/task/tests/metrics/codeMetricsCalculator.spec.ts b/src/task/tests/metrics/codeMetricsCalculator.spec.ts index 94f638da6..731dca36d 100644 --- a/src/task/tests/metrics/codeMetricsCalculator.spec.ts +++ b/src/task/tests/metrics/codeMetricsCalculator.spec.ts @@ -14,6 +14,7 @@ import PullRequestCommentsData from "../../src/pullRequests/pullRequestCommentsD import ReposInvoker from "../../src/repos/reposInvoker.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; +import { stubLocalization } from "../testUtilities/stubLocalization.js"; describe("codeMetricsCalculator.ts", (): void => { let gitInvoker: GitInvoker; @@ -41,49 +42,7 @@ describe("codeMetricsCalculator.ts", (): void => { pullRequestComments = mock(PullRequestComments); runnerInvoker = mock(RunnerInvoker); - when( - runnerInvoker.loc("metrics.codeMetricsCalculator.noGitRepoAzureDevOps"), - ).thenReturn( - "No Git repo present. Remove 'checkout: none' (YAML) or disable 'Don't sync sources' under the build process phase settings (classic).", - ); - when( - runnerInvoker.loc("metrics.codeMetricsCalculator.noGitRepoGitHub"), - ).thenReturn( - "No Git repo present. Run the 'actions/checkout' action prior to PR Metrics.", - ); - when( - runnerInvoker.loc( - "metrics.codeMetricsCalculator.noGitHistoryAzureDevOps", - ), - ).thenReturn( - "Could not access sufficient Git history. Set 'fetchDepth: 0' as a parameter to the 'checkout' task (YAML) or disable 'Shallow fetch' under the build process phase settings (classic).", - ); - when( - runnerInvoker.loc("metrics.codeMetricsCalculator.noGitHistoryGitHub"), - ).thenReturn( - "Could not access sufficient Git history. Add 'fetch-depth: 0' as a parameter to the 'actions/checkout' action.", - ); - when( - runnerInvoker.loc( - "metrics.codeMetricsCalculator.noPullRequestIdAzureDevOps", - ), - ).thenReturn("Could not determine the Pull Request ID."); - when( - runnerInvoker.loc("metrics.codeMetricsCalculator.noPullRequestIdGitHub"), - ).thenReturn( - "Could not determine the Pull Request ID. Ensure 'pull_request' is the pipeline trigger.", - ); - when( - runnerInvoker.loc("metrics.codeMetricsCalculator.noPullRequest"), - ).thenReturn("The build is not running against a pull request."); - when( - runnerInvoker.loc( - "metrics.codeMetricsCalculator.unsupportedProvider", - "Other", - ), - ).thenReturn( - "The build is running against a pull request from 'Other', which is not a supported provider.", - ); + stubLocalization(runnerInvoker); }); describe("shouldSkipWithUnsupportedProvider", (): void => { diff --git a/src/task/tests/pullRequests/pullRequest.spec.ts b/src/task/tests/pullRequests/pullRequest.spec.ts index 92b72986d..daa0c4908 100644 --- a/src/task/tests/pullRequests/pullRequest.spec.ts +++ b/src/task/tests/pullRequests/pullRequest.spec.ts @@ -9,6 +9,7 @@ import Logger from "../../src/utilities/logger.js"; import PullRequest from "../../src/pullRequests/pullRequest.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; +import { stubLocalization } from "../testUtilities/stubLocalization.js"; describe("pullRequest.ts", (): void => { let codeMetrics: CodeMetrics; @@ -22,92 +23,7 @@ describe("pullRequest.ts", (): void => { logger = mock(Logger); runnerInvoker = mock(RunnerInvoker); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - "(XS|S|M|L|\\d*XL)", - "(✔|⚠️)?", - ), - ).thenReturn("(XS|S|M|L|\\d*XL)(✔|⚠️)?"); - when(runnerInvoker.loc("metrics.codeMetrics.titleSizeL")).thenReturn("L"); - when(runnerInvoker.loc("metrics.codeMetrics.titleSizeM")).thenReturn("M"); - when(runnerInvoker.loc("metrics.codeMetrics.titleSizeS")).thenReturn("S"); - when(runnerInvoker.loc("metrics.codeMetrics.titleSizeXL")).thenReturn("XL"); - when(runnerInvoker.loc("metrics.codeMetrics.titleSizeXS")).thenReturn("XS"); - when( - runnerInvoker.loc("metrics.codeMetrics.titleTestsInsufficient"), - ).thenReturn("⚠️"); - when( - runnerInvoker.loc("metrics.codeMetrics.titleTestsSufficient"), - ).thenReturn("✔"); - when( - runnerInvoker.loc("pullRequests.pullRequest.addDescription"), - ).thenReturn("❌ **Add a description.**"); - when( - runnerInvoker.loc("pullRequests.pullRequest.titleFormat", "S✔", ""), - ).thenReturn("S✔ ◾ "); - when( - runnerInvoker.loc("pullRequests.pullRequest.titleFormat", "PREFIX", ""), - ).thenReturn("PREFIX ◾ "); - when( - runnerInvoker.loc("pullRequests.pullRequest.titleFormat", "S✔", "Title"), - ).thenReturn("S✔ ◾ Title"); - when( - runnerInvoker.loc( - "pullRequests.pullRequest.titleFormat", - "PREFIX", - "Title", - ), - ).thenReturn("PREFIX ◾ Title"); - when( - runnerInvoker.loc( - "pullRequests.pullRequest.titleFormat", - "S✔", - "PREFIX ◾ Title", - ), - ).thenReturn("S✔ ◾ PREFIX ◾ Title"); - when( - runnerInvoker.loc( - "pullRequests.pullRequest.titleFormat", - "S✔", - "PREFIX✔ ◾ Title", - ), - ).thenReturn("S✔ ◾ PREFIX✔ ◾ Title"); - when( - runnerInvoker.loc( - "pullRequests.pullRequest.titleFormat", - "S✔", - "PREFIX⚠️ ◾ Title", - ), - ).thenReturn("S✔ ◾ PREFIX⚠️ ◾ Title"); - when( - runnerInvoker.loc( - "pullRequests.pullRequest.titleFormat", - "S✔", - "PS ◾ Title", - ), - ).thenReturn("S✔ ◾ PS ◾ Title"); - when( - runnerInvoker.loc( - "pullRequests.pullRequest.titleFormat", - "S✔", - "PS✔ ◾ Title", - ), - ).thenReturn("S✔ ◾ PS✔ ◾ Title"); - when( - runnerInvoker.loc( - "pullRequests.pullRequest.titleFormat", - "S✔", - "PS⚠️ ◾ Title", - ), - ).thenReturn("S✔ ◾ PS⚠️ ◾ Title"); - when( - runnerInvoker.loc( - "pullRequests.pullRequest.titleFormat", - "(XS|S|M|L|\\d*XL)(✔|⚠️)?", - "(?.*)", - ), - ).thenReturn("(XS|S|M|L|\\d*XL)(✔|⚠️)? ◾ (?.*)"); + stubLocalization(runnerInvoker); }); describe("isPullRequest", (): void => { diff --git a/src/task/tests/pullRequests/pullRequestComments.spec.ts b/src/task/tests/pullRequests/pullRequestComments.spec.ts index 9ce232f64..c9fbdab35 100644 --- a/src/task/tests/pullRequests/pullRequestComments.spec.ts +++ b/src/task/tests/pullRequests/pullRequestComments.spec.ts @@ -18,6 +18,7 @@ import type PullRequestCommentsData from "../../src/pullRequests/pullRequestComm import ReposInvoker from "../../src/repos/reposInvoker.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; +import { stubLocalization } from "../testUtilities/stubLocalization.js"; describe("pullRequestComments.ts", (): void => { let complexGitPullRequestComments: CommentData; @@ -66,76 +67,7 @@ describe("pullRequestComments.ts", (): void => { logger = mock(Logger); runnerInvoker = mock(RunnerInvoker); - when( - runnerInvoker.loc("pullRequests.pullRequestComments.commentFooter"), - ).thenReturn( - "[Metrics computed by PR Metrics. Add it to your Azure DevOps and GitHub PRs!](https://aka.ms/PRMetrics/Comment)", - ); - when( - runnerInvoker.loc("pullRequests.pullRequestComments.commentTitle"), - ).thenReturn("# PR Metrics"); - when( - runnerInvoker.loc( - "pullRequests.pullRequestComments.largePullRequestComment", - (400).toLocaleString(), - ), - ).thenReturn( - `❌ **Try to keep pull requests smaller than ${(400).toLocaleString()} lines of new product code by following the [Single Responsibility Principle (SRP)](https://aka.ms/PRMetrics/SRP).**`, - ); - when( - runnerInvoker.loc( - "pullRequests.pullRequestComments.largePullRequestComment", - (2000).toLocaleString(), - ), - ).thenReturn( - `❌ **Try to keep pull requests smaller than ${(2000).toLocaleString()} lines of new product code by following the [Single Responsibility Principle (SRP)](https://aka.ms/PRMetrics/SRP).**`, - ); - when( - runnerInvoker.loc( - "pullRequests.pullRequestComments.largePullRequestComment", - (2000000).toLocaleString(), - ), - ).thenReturn( - `❌ **Try to keep pull requests smaller than ${(2000000).toLocaleString()} lines of new product code by following the [Single Responsibility Principle (SRP)](https://aka.ms/PRMetrics/SRP).**`, - ); - when( - runnerInvoker.loc( - "pullRequests.pullRequestComments.noReviewRequiredComment", - ), - ).thenReturn("❗ **This file doesn't require review.**"); - when( - runnerInvoker.loc( - "pullRequests.pullRequestComments.smallPullRequestComment", - ), - ).thenReturn("✔ **Thanks for keeping your pull request small.**"); - when( - runnerInvoker.loc("pullRequests.pullRequestComments.tableIgnoredCode"), - ).thenReturn("Ignored Code"); - when( - runnerInvoker.loc("pullRequests.pullRequestComments.tableLines"), - ).thenReturn("Lines"); - when( - runnerInvoker.loc("pullRequests.pullRequestComments.tableProductCode"), - ).thenReturn("Product Code"); - when( - runnerInvoker.loc("pullRequests.pullRequestComments.tableSubtotal"), - ).thenReturn("Subtotal"); - when( - runnerInvoker.loc("pullRequests.pullRequestComments.tableTestCode"), - ).thenReturn("Test Code"); - when( - runnerInvoker.loc("pullRequests.pullRequestComments.tableTotal"), - ).thenReturn("Total"); - when( - runnerInvoker.loc( - "pullRequests.pullRequestComments.testsInsufficientComment", - ), - ).thenReturn("⚠️ **Consider adding additional tests.**"); - when( - runnerInvoker.loc( - "pullRequests.pullRequestComments.testsSufficientComment", - ), - ).thenReturn("✔ **Thanks for adding tests.**"); + stubLocalization(runnerInvoker); }); describe("noReviewRequiredComment", (): void => { diff --git a/src/task/tests/repos/azureReposInvoker.spec.ts b/src/task/tests/repos/azureReposInvoker.spec.ts index 7bd57b742..d0bf410f2 100644 --- a/src/task/tests/repos/azureReposInvoker.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.spec.ts @@ -27,6 +27,7 @@ import TokenManager from "../../src/repos/tokenManager.js"; import { WebApi } from "azure-devops-node-api"; import assert from "node:assert/strict"; import { resolvableInstance } from "../testUtilities/resolvableInstance.js"; +import { stubLocalization } from "../testUtilities/stubLocalization.js"; describe("azureReposInvoker.ts", (): void => { let gitApi: IGitApi; @@ -65,23 +66,7 @@ describe("azureReposInvoker.ts", (): void => { logger = mock(Logger); runnerInvoker = mock(RunnerInvoker); - when( - runnerInvoker.loc( - "repos.azureReposInvoker.insufficientAzureReposAccessTokenPermissions", - ), - ).thenReturn( - "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read' and 'Pull Request Threads' > 'Read & write'.", - ); - when( - runnerInvoker.loc("repos.azureReposInvoker.noAzureReposAccessToken"), - ).thenReturn( - "Could not access the Workload Identity Federation or Personal Access Token (PAT). Add the 'WorkloadIdentityFederation' input or 'PR_Metrics_Access_Token' as a secret environment variable.", - ); - when( - runnerInvoker.loc("repos.baseReposInvoker.resourceNotFound"), - ).thenReturn( - "The resource could not be found. Verify the repository and pull request exist.", - ); + stubLocalization(runnerInvoker); tokenManager = mock(TokenManager); }); @@ -320,7 +305,7 @@ describe("azureReposInvoker.ts", (): void => { const expectedMessage: string = statusCode === StatusCodes.NOT_FOUND ? "The resource could not be found. Verify the repository and pull request exist." - : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read' and 'Pull Request Threads' > 'Read & write'."; + : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( func, expectedMessage, @@ -494,7 +479,7 @@ describe("azureReposInvoker.ts", (): void => { const expectedMessage: string = statusCode === StatusCodes.NOT_FOUND ? "The resource could not be found. Verify the repository and pull request exist." - : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read' and 'Pull Request Threads' > 'Read & write'."; + : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( func, expectedMessage, @@ -903,7 +888,7 @@ describe("azureReposInvoker.ts", (): void => { const expectedMessage: string = statusCode === StatusCodes.NOT_FOUND ? "The resource could not be found. Verify the repository and pull request exist." - : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read' and 'Pull Request Threads' > 'Read & write'."; + : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( func, expectedMessage, @@ -1159,7 +1144,7 @@ describe("azureReposInvoker.ts", (): void => { const expectedMessage: string = statusCode === StatusCodes.NOT_FOUND ? "The resource could not be found. Verify the repository and pull request exist." - : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read' and 'Pull Request Threads' > 'Read & write'."; + : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( func, expectedMessage, @@ -1432,7 +1417,7 @@ describe("azureReposInvoker.ts", (): void => { const expectedMessage: string = statusCode === StatusCodes.NOT_FOUND ? "The resource could not be found. Verify the repository and pull request exist." - : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read' and 'Pull Request Threads' > 'Read & write'."; + : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( func, expectedMessage, @@ -1492,7 +1477,7 @@ describe("azureReposInvoker.ts", (): void => { const expectedMessage: string = status === StatusCodes.NOT_FOUND ? "The resource could not be found. Verify the repository and pull request exist." - : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read' and 'Pull Request Threads' > 'Read & write'."; + : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( func, expectedMessage, @@ -1791,7 +1776,7 @@ describe("azureReposInvoker.ts", (): void => { const expectedMessage: string = statusCode === StatusCodes.NOT_FOUND ? "The resource could not be found. Verify the repository and pull request exist." - : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read' and 'Pull Request Threads' > 'Read & write'."; + : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( func, expectedMessage, diff --git a/src/task/tests/repos/gitHubReposInvoker.spec.ts b/src/task/tests/repos/gitHubReposInvoker.spec.ts index 294725278..9f19c65bc 100644 --- a/src/task/tests/repos/gitHubReposInvoker.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.spec.ts @@ -25,6 +25,7 @@ import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { StatusCodes } from "http-status-codes"; import assert from "node:assert/strict"; import { createRequestError } from "../testUtilities/createRequestError.js"; +import { stubLocalization } from "../testUtilities/stubLocalization.js"; describe("gitHubReposInvoker.ts", (): void => { let gitInvoker: GitInvoker; @@ -67,23 +68,7 @@ describe("gitHubReposInvoker.ts", (): void => { ).thenResolve(GitHubReposInvokerConstants.listCommitsResponse); runnerInvoker = mock(RunnerInvoker); - when( - runnerInvoker.loc( - "repos.gitHubReposInvoker.insufficientGitHubAccessTokenPermissions", - ), - ).thenReturn( - "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has Read and Write access to pull requests (or access to 'repos' if using a Classic PAT).", - ); - when( - runnerInvoker.loc("repos.gitHubReposInvoker.noGitHubAccessToken"), - ).thenReturn( - "Could not access the Personal Access Token (PAT). Add 'PR_Metrics_Access_Token' as a secret environment variable with Read and Write access to Pull Requests (or access to 'repos' if using a Classic PAT, or write access to 'pull-requests' and 'statuses' if specified within the workflow YAML).", - ); - when( - runnerInvoker.loc("repos.baseReposInvoker.resourceNotFound"), - ).thenReturn( - "The resource could not be found. Verify the repository and pull request exist.", - ); + stubLocalization(runnerInvoker); }); afterEach((): void => { diff --git a/src/task/tests/repos/tokenManager.spec.ts b/src/task/tests/repos/tokenManager.spec.ts index aab364f36..b47a291b4 100644 --- a/src/task/tests/repos/tokenManager.spec.ts +++ b/src/task/tests/repos/tokenManager.spec.ts @@ -15,6 +15,7 @@ import TokenManager from "../../src/repos/tokenManager.js"; import { WebApi } from "azure-devops-node-api"; import assert from "node:assert/strict"; import { resolvableInstance } from "../testUtilities/resolvableInstance.js"; +import { stubLocalization } from "../testUtilities/stubLocalization.js"; describe("tokenManager.ts", (): void => { let taskApi: ITaskApi; @@ -64,6 +65,7 @@ describe("tokenManager.ts", (): void => { logger = mock(Logger); runnerInvoker = mock(RunnerInvoker); + stubLocalization(runnerInvoker); when( runnerInvoker.getInput(deepEqual(["Workload", "Identity", "Federation"])), ).thenReturn("Id"); @@ -168,21 +170,15 @@ describe("tokenManager.ts", (): void => { when(runnerInvoker.getEndpointAuthorizationScheme("Id")).thenReturn( "Other", ); - when( - runnerInvoker.loc( - "repos.tokenManager.incorrectAuthorizationScheme", - "WorkloadIdentityFederation", - "Other", - ), - ).thenReturn( - "Authorization scheme of workload identity federation 'Id' must be 'WorkloadIdentityFederation' instead of 'Other'.", - ); // Act const result: string | null = await tokenManager.getToken(); // Assert - assert.equal(result, null); + assert.equal( + result, + "Authorization scheme of workload identity federation 'Id' must be 'WorkloadIdentityFederation' instead of 'Other'.", + ); }); it("throws an error when the service principal ID is null", async (): Promise => { diff --git a/src/task/tests/testUtilities/stubLocalization.ts b/src/task/tests/testUtilities/stubLocalization.ts new file mode 100644 index 000000000..9bf3d83f7 --- /dev/null +++ b/src/task/tests/testUtilities/stubLocalization.ts @@ -0,0 +1,73 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as fs from "node:fs"; +import * as path from "node:path"; +import * as util from "node:util"; +import type ResourcesJsonInterface from "../../src/jsonTypes/resourcesJsonInterface.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import { anyString } from "./mockito.js"; +import { when } from "ts-mockito"; + +const resourcePrefix = "loc.messages."; + +let cachedResources: Map | null = null; + +const loadResources = (): Map => { + if (cachedResources !== null) { + return cachedResources; + } + + const resourcesPath: string = path.join( + import.meta.dirname, + "..", + "..", + "Strings", + "resources.resjson", + "en-US", + "resources.resjson", + ); + const raw: string = fs.readFileSync(resourcesPath, "utf8"); + const json: ResourcesJsonInterface = JSON.parse( + raw, + ) as ResourcesJsonInterface; + + const map: Map = new Map(); + for (const [key, value] of Object.entries(json)) { + if (key.startsWith(resourcePrefix)) { + map.set(key.substring(resourcePrefix.length), value); + } + } + cachedResources = map; + return map; +}; + +/** + * Wires the `loc()` method on a mocked `RunnerInvoker` to return values read + * from the real `resources.resjson` file, with parameter substitution via + * `util.format`. + * @param runnerInvoker The mocked runner invoker. + */ +export const stubLocalization = (runnerInvoker: RunnerInvoker): void => { + const resources: Map = loadResources(); + + const lookup = (key: string, ...params: string[]): string => { + const template: string | undefined = resources.get(key); + if (typeof template === "undefined") { + throw new Error(`Unknown localization key: '${key}'.`); + } + + return params.length > 0 ? util.format(template, ...params) : template; + }; + + when(runnerInvoker.loc(anyString())).thenCall(lookup); + when(runnerInvoker.loc(anyString(), anyString())).thenCall(lookup); + when(runnerInvoker.loc(anyString(), anyString(), anyString())).thenCall( + lookup, + ); + when( + runnerInvoker.loc(anyString(), anyString(), anyString(), anyString()), + ).thenCall(lookup); +}; From 2c6daf9c1e3a2a7cac9402b4441ea39e8be87a4d Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Fri, 17 Apr 2026 15:47:51 +0100 Subject: [PATCH 03/27] Replace process.env juggling with stubEnv helper Adds tests/testUtilities/stubEnv.ts exporting a stubEnv helper that captures the pre-test value of each environment variable and restores it via a global afterEach hook. Replaces hand-written process.env.X = "value" assignments and matching delete process.env.X cleanups across nine spec files. Design notes: - stubEnv takes variadic [name, value] tuples rather than an object literal so that POSIX-style uppercase variable names flow through without needing the camelCase object-key naming convention to be suppressed. - Reflect.deleteProperty is used instead of delete to avoid the dynamic-delete rule trigger; no ESLint suppressions are required anywhere in the helper or its call sites. - Pass undefined as a value to temporarily unset a variable; the original value is restored at afterEach time regardless. - Replaces the existing after() cleanup blocks in tokenManager.spec.ts and azureReposInvoker.spec.ts, and the afterEach cleanup in gitInvoker.spec.ts and gitHubReposInvoker.spec.ts. - 783 tests still pass, 100% coverage preserved, lint clean. --- src/task/tests/git/gitInvoker.spec.ts | 149 ++++++------------ .../metrics/codeMetricsCalculator.spec.ts | 13 +- .../tests/pullRequests/pullRequest.spec.ts | 35 ++-- .../tests/repos/azureReposInvoker.spec.ts | 39 ++--- .../tests/repos/gitHubReposInvoker.spec.ts | 128 ++++++--------- src/task/tests/repos/reposInvoker.spec.ts | 131 +++++---------- src/task/tests/repos/tokenManager.spec.ts | 31 ++-- src/task/tests/runners/runnerInvoker.spec.ts | 65 ++------ src/task/tests/testUtilities/stubEnv.ts | 57 +++++++ src/task/tests/utilities/validator.spec.ts | 15 +- 10 files changed, 262 insertions(+), 401 deletions(-) create mode 100644 src/task/tests/testUtilities/stubEnv.ts diff --git a/src/task/tests/git/gitInvoker.spec.ts b/src/task/tests/git/gitInvoker.spec.ts index d99117ca9..07cd00b13 100644 --- a/src/task/tests/git/gitInvoker.spec.ts +++ b/src/task/tests/git/gitInvoker.spec.ts @@ -10,15 +10,16 @@ import GitInvoker from "../../src/git/gitInvoker.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; +import { stubEnv } from "../testUtilities/stubEnv.js"; describe("gitInvoker.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; beforeEach((): void => { - process.env.BUILD_REPOSITORY_PROVIDER = "TfsGit"; - process.env.SYSTEM_PULLREQUEST_TARGETBRANCH = "refs/heads/develop"; - process.env.SYSTEM_PULLREQUEST_PULLREQUESTID = "12345"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "TfsGit"]); + stubEnv(["SYSTEM_PULLREQUEST_TARGETBRANCH", "refs/heads/develop"]); + stubEnv(["SYSTEM_PULLREQUEST_PULLREQUESTID", "12345"]); logger = mock(Logger); @@ -60,17 +61,11 @@ describe("gitInvoker.ts", (): void => { ); }); - afterEach((): void => { - delete process.env.BUILD_REPOSITORY_PROVIDER; - delete process.env.SYSTEM_PULLREQUEST_TARGETBRANCH; - delete process.env.SYSTEM_PULLREQUEST_PULLREQUESTID; - }); - describe("pullRequestId", (): void => { it("should return the correct output when the GitHub runner is being used", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; - process.env.GITHUB_REF = "refs/pull/12345/merge"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_REF", "refs/pull/12345/merge"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -82,15 +77,12 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, 12345); - // Finalization - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_REF; }); it("should return the correct output when the GitHub runner is being used and it is called multiple times", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; - process.env.GITHUB_REF = "refs/pull/12345/merge"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_REF", "refs/pull/12345/merge"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -104,14 +96,11 @@ describe("gitInvoker.ts", (): void => { assert.equal(result1, 12345); assert.equal(result2, 12345); - // Finalization - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_REF; }); it("should throw an error when the GitHub runner is being used and GITHUB_REF is undefined", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -129,14 +118,12 @@ describe("gitInvoker.ts", (): void => { ); verify(logger.logWarning("'GITHUB_REF' is undefined.")).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); it("should throw an error when the GitHub runner is being used and GITHUB_REF is in the incorrect format", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; - process.env.GITHUB_REF = "refs/pull"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_REF", "refs/pull"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -158,15 +145,12 @@ describe("gitInvoker.ts", (): void => { ), ).once(); - // Finalization - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_REF; }); it("should return the correct output when the Azure Pipelines runner is being used", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; - process.env.GITHUB_REF = "refs/pull/12345/merge"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_REF", "refs/pull/12345/merge"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -178,14 +162,11 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, 12345); - // Finalization - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_REF; }); it("should throw an error when the Azure Pipelines runner is being used and BUILD_REPOSITORY_PROVIDER is undefined", (): void => { // Arrange - delete process.env.BUILD_REPOSITORY_PROVIDER; + stubEnv(["BUILD_REPOSITORY_PROVIDER", undefined]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -208,7 +189,7 @@ describe("gitInvoker.ts", (): void => { it("should throw an error when the Azure Pipelines runner is being used and SYSTEM_PULLREQUEST_PULLREQUESTID is undefined", (): void => { // Arrange - delete process.env.SYSTEM_PULLREQUEST_PULLREQUESTID; + stubEnv(["SYSTEM_PULLREQUEST_PULLREQUESTID", undefined]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -231,7 +212,7 @@ describe("gitInvoker.ts", (): void => { it("should throw an error when the Azure Pipelines runner is being used and SYSTEM_PULLREQUEST_PULLREQUESTID is not numeric", (): void => { // Arrange - process.env.SYSTEM_PULLREQUEST_PULLREQUESTID = "abc"; + stubEnv(["SYSTEM_PULLREQUEST_PULLREQUESTID", "abc"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -260,7 +241,7 @@ describe("gitInvoker.ts", (): void => { testCases.forEach((buildRepositoryProvider: string): void => { it(`should throw an error when the Azure Pipelines runner is being used and the PR is on '${buildRepositoryProvider}' and SYSTEM_PULLREQUEST_PULLREQUESTNUMBER is undefined`, (): void => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = buildRepositoryProvider; + stubEnv(["BUILD_REPOSITORY_PROVIDER", buildRepositoryProvider]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -282,16 +263,14 @@ describe("gitInvoker.ts", (): void => { ), ).once(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); } it("should throw an error when the ID cannot be parsed as an integer", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; - process.env.GITHUB_REF = "refs/pull/PullRequestID/merge"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_REF", "refs/pull/PullRequestID/merge"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -313,9 +292,6 @@ describe("gitInvoker.ts", (): void => { ), ).once(); - // Finalization - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_REF; }); { @@ -324,8 +300,8 @@ describe("gitInvoker.ts", (): void => { testCases.forEach((buildRepositoryProvider: string): void => { it(`should throw an error when the Azure Pipelines runner is being used and the PR is on '${buildRepositoryProvider}' and SYSTEM_PULLREQUEST_PULLREQUESTNUMBER is not numeric`, (): void => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = buildRepositoryProvider; - process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER = "abc"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", buildRepositoryProvider]); + stubEnv(["SYSTEM_PULLREQUEST_PULLREQUESTNUMBER", "abc"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -347,9 +323,6 @@ describe("gitInvoker.ts", (): void => { ), ).once(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; - delete process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER; }); }); } @@ -420,8 +393,8 @@ describe("gitInvoker.ts", (): void => { describe("isPullRequestIdAvailable()", (): void => { it("should return true when the GitHub runner is being used", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; - process.env.GITHUB_REF = "refs/pull/12345/merge"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_REF", "refs/pull/12345/merge"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -433,14 +406,11 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, true); - // Finalization - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_REF; }); it("should return false when the GitHub runner is being used and GITHUB_REF is undefined", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -453,14 +423,12 @@ describe("gitInvoker.ts", (): void => { assert.equal(result, false); verify(logger.logWarning("'GITHUB_REF' is undefined.")).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); it("should return false when the GitHub runner is being used and GITHUB_REF is in the incorrect format", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; - process.env.GITHUB_REF = "refs/pull"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_REF", "refs/pull"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -477,15 +445,12 @@ describe("gitInvoker.ts", (): void => { ), ).once(); - // Finalization - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_REF; }); it("should return true when the Azure Pipelines runner is being used", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; - process.env.GITHUB_REF = "refs/pull/12345/merge"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_REF", "refs/pull/12345/merge"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -497,14 +462,11 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, true); - // Finalization - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_REF; }); it("should throw an error when the Azure Pipelines runner is being used and BUILD_REPOSITORY_PROVIDER is undefined", (): void => { // Arrange - delete process.env.BUILD_REPOSITORY_PROVIDER; + stubEnv(["BUILD_REPOSITORY_PROVIDER", undefined]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -522,7 +484,7 @@ describe("gitInvoker.ts", (): void => { it("should throw an error when the Azure Pipelines runner is being used and SYSTEM_PULLREQUEST_PULLREQUESTID is undefined", (): void => { // Arrange - delete process.env.SYSTEM_PULLREQUEST_PULLREQUESTID; + stubEnv(["SYSTEM_PULLREQUEST_PULLREQUESTID", undefined]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -544,7 +506,7 @@ describe("gitInvoker.ts", (): void => { testCases.forEach((buildRepositoryProvider: string): void => { it(`should throw an error when the Azure Pipelines runner is being used and the PR is on '${buildRepositoryProvider}' and SYSTEM_PULLREQUEST_PULLREQUESTNUMBER is undefined`, (): void => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = buildRepositoryProvider; + stubEnv(["BUILD_REPOSITORY_PROVIDER", buildRepositoryProvider]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -561,16 +523,14 @@ describe("gitInvoker.ts", (): void => { ), ).once(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); } it("should throw an error when the ID cannot be parsed as an integer", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; - process.env.GITHUB_REF = "refs/pull/PullRequestID/merge"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_REF", "refs/pull/PullRequestID/merge"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -582,16 +542,13 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, false); - // Finalization - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_REF; }); }); describe("isGitHistoryAvailable()", (): void => { it("should return true when the Git history is available", async (): Promise => { // Arrange - delete process.env.GITHUB_ACTION; + stubEnv(["GITHUB_ACTION", undefined]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -606,7 +563,7 @@ describe("gitInvoker.ts", (): void => { it("should return true when the Git history is available and the method is called after retrieving the pull request ID", async (): Promise => { // Arrange - delete process.env.GITHUB_ACTION; + stubEnv(["GITHUB_ACTION", undefined]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -623,9 +580,9 @@ describe("gitInvoker.ts", (): void => { it("should return true when the Git history is available and the PR is using the GitHub runner", async (): Promise => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; - process.env.GITHUB_BASE_REF = "develop"; - process.env.GITHUB_REF = "refs/pull/12345/merge"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_BASE_REF", "develop"]); + stubEnv(["GITHUB_REF", "refs/pull/12345/merge"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -637,15 +594,11 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, true); - // Finalization - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_BASE_REF; - delete process.env.GITHUB_REF; }); it("should throw an error when the PR is using the GitHub runner and GITHUB_BASE_REF is undefined", async (): Promise => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -661,8 +614,6 @@ describe("gitInvoker.ts", (): void => { "'GITHUB_BASE_REF', accessed within 'GitInvoker.targetBranch', is invalid, null, or undefined 'undefined'.", ); - // Finalization - delete process.env.GITHUB_ACTION; }); { @@ -671,10 +622,14 @@ describe("gitInvoker.ts", (): void => { testCases.forEach((buildRepositoryProvider: string): void => { it(`should return true when the Git history is available and the PR is on '${buildRepositoryProvider}'`, async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = buildRepositoryProvider; - process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER = - process.env.SYSTEM_PULLREQUEST_PULLREQUESTID; - delete process.env.SYSTEM_PULLREQUEST_PULLREQUESTID; + stubEnv( + ["BUILD_REPOSITORY_PROVIDER", buildRepositoryProvider], + ["SYSTEM_PULLREQUEST_PULLREQUESTID", undefined], + [ + "SYSTEM_PULLREQUEST_PULLREQUESTNUMBER", + process.env.SYSTEM_PULLREQUEST_PULLREQUESTID, + ], + ); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -686,8 +641,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, true); - // Finalization - delete process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER; }); }); } @@ -742,7 +695,7 @@ describe("gitInvoker.ts", (): void => { it("should throw an error when SYSTEM_PULLREQUEST_TARGETBRANCH is undefined", async (): Promise => { // Arrange - delete process.env.SYSTEM_PULLREQUEST_TARGETBRANCH; + stubEnv(["SYSTEM_PULLREQUEST_TARGETBRANCH", undefined]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -761,7 +714,7 @@ describe("gitInvoker.ts", (): void => { it("should throw an error when the target branch contains whitespace", async (): Promise => { // Arrange - process.env.SYSTEM_PULLREQUEST_TARGETBRANCH = "refs/heads/main branch"; + stubEnv(["SYSTEM_PULLREQUEST_TARGETBRANCH", "refs/heads/main branch"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -796,7 +749,7 @@ describe("gitInvoker.ts", (): void => { it("should return the correct output when no error occurs and the target branch is in the GitHub format", async (): Promise => { // Arrange - process.env.SYSTEM_PULLREQUEST_TARGETBRANCH = "develop"; + stubEnv(["SYSTEM_PULLREQUEST_TARGETBRANCH", "develop"]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), @@ -826,7 +779,7 @@ describe("gitInvoker.ts", (): void => { it("should throw an error when SYSTEM_PULLREQUEST_TARGETBRANCH is undefined", async (): Promise => { // Arrange - delete process.env.SYSTEM_PULLREQUEST_TARGETBRANCH; + stubEnv(["SYSTEM_PULLREQUEST_TARGETBRANCH", undefined]); const gitInvoker: GitInvoker = new GitInvoker( instance(logger), instance(runnerInvoker), diff --git a/src/task/tests/metrics/codeMetricsCalculator.spec.ts b/src/task/tests/metrics/codeMetricsCalculator.spec.ts index 731dca36d..680f358ac 100644 --- a/src/task/tests/metrics/codeMetricsCalculator.spec.ts +++ b/src/task/tests/metrics/codeMetricsCalculator.spec.ts @@ -14,6 +14,7 @@ import PullRequestCommentsData from "../../src/pullRequests/pullRequestCommentsD import ReposInvoker from "../../src/repos/reposInvoker.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; +import { stubEnv } from "../testUtilities/stubEnv.js"; import { stubLocalization } from "../testUtilities/stubLocalization.js"; describe("codeMetricsCalculator.ts", (): void => { @@ -176,7 +177,7 @@ describe("codeMetricsCalculator.ts", (): void => { it("should return the appropriate message when not called from a Git repo on GitHub", async (): Promise => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); when(gitInvoker.isGitRepo()).thenResolve(false); const codeMetricsCalculator: CodeMetricsCalculator = new CodeMetricsCalculator( @@ -197,8 +198,6 @@ describe("codeMetricsCalculator.ts", (): void => { "No Git repo present. Run the 'actions/checkout' action prior to PR Metrics.", ); - // Finalization - delete process.env.GITHUB_ACTION; }); it("should return the appropriate message when the pull request ID is not available on Azure DevOps", async (): Promise => { @@ -223,7 +222,7 @@ describe("codeMetricsCalculator.ts", (): void => { it("should return the appropriate message when the pull request ID is not available on GitHub", async (): Promise => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); when(gitInvoker.isPullRequestIdAvailable()).thenReturn(false); const codeMetricsCalculator: CodeMetricsCalculator = new CodeMetricsCalculator( @@ -244,8 +243,6 @@ describe("codeMetricsCalculator.ts", (): void => { "Could not determine the Pull Request ID. Ensure 'pull_request' is the pipeline trigger.", ); - // Finalization - delete process.env.GITHUB_ACTION; }); it("should return the appropriate message when the Git history is unavailable on Azure DevOps", async (): Promise => { @@ -273,7 +270,7 @@ describe("codeMetricsCalculator.ts", (): void => { it("should return the appropriate message when the Git history is unavailable on GitHub", async (): Promise => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); when(gitInvoker.isGitHistoryAvailable()).thenResolve(false); const codeMetricsCalculator: CodeMetricsCalculator = new CodeMetricsCalculator( @@ -294,8 +291,6 @@ describe("codeMetricsCalculator.ts", (): void => { "Could not access sufficient Git history. Add 'fetch-depth: 0' as a parameter to the 'actions/checkout' action.", ); - // Finalization - delete process.env.GITHUB_ACTION; }); }); diff --git a/src/task/tests/pullRequests/pullRequest.spec.ts b/src/task/tests/pullRequests/pullRequest.spec.ts index daa0c4908..ec9a40c1d 100644 --- a/src/task/tests/pullRequests/pullRequest.spec.ts +++ b/src/task/tests/pullRequests/pullRequest.spec.ts @@ -9,6 +9,7 @@ import Logger from "../../src/utilities/logger.js"; import PullRequest from "../../src/pullRequests/pullRequest.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; +import { stubEnv } from "../testUtilities/stubEnv.js"; import { stubLocalization } from "../testUtilities/stubLocalization.js"; describe("pullRequest.ts", (): void => { @@ -29,8 +30,8 @@ describe("pullRequest.ts", (): void => { describe("isPullRequest", (): void => { it("should return true when the GitHub runner is being used and GITHUB_BASE_REF is defined", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; - process.env.GITHUB_BASE_REF = "develop"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_BASE_REF", "develop"]); const pullRequest: PullRequest = new PullRequest( instance(codeMetrics), instance(logger), @@ -43,15 +44,12 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, true); - // Finalization - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_BASE_REF; }); it("should return false when the GitHub runner is being used and GITHUB_BASE_REF is the empty string", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; - process.env.GITHUB_BASE_REF = ""; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_BASE_REF", ""]); const pullRequest: PullRequest = new PullRequest( instance(codeMetrics), instance(logger), @@ -64,14 +62,11 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, false); - // Finalization - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_BASE_REF; }); it("should return true when the Azure Pipelines runner is being used and SYSTEM_PULLREQUEST_PULLREQUESTID is defined", (): void => { // Arrange - process.env.SYSTEM_PULLREQUEST_PULLREQUESTID = "refs/heads/develop"; + stubEnv(["SYSTEM_PULLREQUEST_PULLREQUESTID", "refs/heads/develop"]); const pullRequest: PullRequest = new PullRequest( instance(codeMetrics), instance(logger), @@ -84,13 +79,11 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, true); - // Finalization - delete process.env.SYSTEM_PULLREQUEST_PULLREQUESTID; }); it("should return false when the Azure Pipelines runner is being used and SYSTEM_PULLREQUEST_PULLREQUESTID is not defined", (): void => { // Arrange - delete process.env.SYSTEM_PULLREQUEST_TARGETBRANCH; + stubEnv(["SYSTEM_PULLREQUEST_TARGETBRANCH", undefined]); const pullRequest: PullRequest = new PullRequest( instance(codeMetrics), instance(logger), @@ -108,7 +101,7 @@ describe("pullRequest.ts", (): void => { describe("isSupportedProvider", (): void => { it("should return true when the GitHub runner is being used", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const pullRequest: PullRequest = new PullRequest( instance(codeMetrics), instance(logger), @@ -121,13 +114,11 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, true); - // Finalization - delete process.env.GITHUB_ACTION; }); it("should throw an error when the Azure Pipelines runner is being used and BUILD_REPOSITORY_PROVIDER is undefined", (): void => { // Arrange - delete process.env.BUILD_REPOSITORY_PROVIDER; + stubEnv(["BUILD_REPOSITORY_PROVIDER", undefined]); const pullRequest: PullRequest = new PullRequest( instance(codeMetrics), instance(logger), @@ -153,7 +144,7 @@ describe("pullRequest.ts", (): void => { testCases.forEach((provider: string): void => { it(`should return true when the Azure Pipelines runner is being used and BUILD_REPOSITORY_PROVIDER is set to '${provider}'`, (): void => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = provider; + stubEnv(["BUILD_REPOSITORY_PROVIDER", provider]); const pullRequest: PullRequest = new PullRequest( instance(codeMetrics), instance(logger), @@ -166,15 +157,13 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, true); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); } it("should return the provider when the Azure Pipelines runner is being used and BUILD_REPOSITORY_PROVIDER is not set to TfsGit or GitHub", (): void => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = "Other"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "Other"]); const pullRequest: PullRequest = new PullRequest( instance(codeMetrics), instance(logger), @@ -187,8 +176,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, "Other"); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); diff --git a/src/task/tests/repos/azureReposInvoker.spec.ts b/src/task/tests/repos/azureReposInvoker.spec.ts index d0bf410f2..bcf556d7d 100644 --- a/src/task/tests/repos/azureReposInvoker.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.spec.ts @@ -27,6 +27,7 @@ import TokenManager from "../../src/repos/tokenManager.js"; import { WebApi } from "azure-devops-node-api"; import assert from "node:assert/strict"; import { resolvableInstance } from "../testUtilities/resolvableInstance.js"; +import { stubEnv } from "../testUtilities/stubEnv.js"; import { stubLocalization } from "../testUtilities/stubLocalization.js"; describe("azureReposInvoker.ts", (): void => { @@ -38,11 +39,12 @@ describe("azureReposInvoker.ts", (): void => { let tokenManager: TokenManager; beforeEach((): void => { - process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI = - "https://dev.azure.com/organization"; - process.env.SYSTEM_TEAMPROJECT = "Project"; - process.env.BUILD_REPOSITORY_ID = "RepoID"; - process.env.PR_METRICS_ACCESS_TOKEN = "PAT"; + stubEnv( + ["BUILD_REPOSITORY_ID", "RepoID"], + ["PR_METRICS_ACCESS_TOKEN", "PAT"], + ["SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", "https://dev.azure.com/organization"], + ["SYSTEM_TEAMPROJECT", "Project"], + ); gitApi = mock(); const requestHandler: IRequestHandler = mock(); @@ -71,13 +73,6 @@ describe("azureReposInvoker.ts", (): void => { tokenManager = mock(TokenManager); }); - after(() => { - delete process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI; - delete process.env.SYSTEM_TEAMPROJECT; - delete process.env.BUILD_REPOSITORY_ID; - delete process.env.PR_METRICS_ACCESS_TOKEN; - }); - describe("isAccessTokenAvailable()", (): void => { it("should return null when the token exists", async (): Promise => { // Arrange @@ -99,7 +94,7 @@ describe("azureReposInvoker.ts", (): void => { it("should return a string when the token manager fails", async (): Promise => { // Arrange - delete process.env.PR_METRICS_ACCESS_TOKEN; + stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( instance(azureDevOpsApiWrapper), instance(gitInvoker), @@ -119,7 +114,7 @@ describe("azureReposInvoker.ts", (): void => { it("should return a string when the token does not exist", async (): Promise => { // Arrange - delete process.env.PR_METRICS_ACCESS_TOKEN; + stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( instance(azureDevOpsApiWrapper), instance(gitInvoker), @@ -148,9 +143,9 @@ describe("azureReposInvoker.ts", (): void => { it(`should throw when SYSTEM_TEAMPROJECT is set to the invalid value '${String(variable)}'`, async (): Promise => { // Arrange if (typeof variable === "undefined") { - delete process.env.SYSTEM_TEAMPROJECT; + stubEnv(["SYSTEM_TEAMPROJECT", undefined]); } else { - process.env.SYSTEM_TEAMPROJECT = variable; + stubEnv(["SYSTEM_TEAMPROJECT", variable]); } const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( @@ -181,9 +176,9 @@ describe("azureReposInvoker.ts", (): void => { it(`should throw when BUILD_REPOSITORY_ID is set to the invalid value '${String(variable)}'`, async (): Promise => { // Arrange if (typeof variable === "undefined") { - delete process.env.BUILD_REPOSITORY_ID; + stubEnv(["BUILD_REPOSITORY_ID", undefined]); } else { - process.env.BUILD_REPOSITORY_ID = variable; + stubEnv(["BUILD_REPOSITORY_ID", variable]); } const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( @@ -214,9 +209,9 @@ describe("azureReposInvoker.ts", (): void => { it(`should throw when PR_METRICS_ACCESS_TOKEN is set to the invalid value '${String(variable)}'`, async (): Promise => { // Arrange if (typeof variable === "undefined") { - delete process.env.PR_METRICS_ACCESS_TOKEN; + stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); } else { - process.env.PR_METRICS_ACCESS_TOKEN = variable; + stubEnv(["PR_METRICS_ACCESS_TOKEN", variable]); } const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( @@ -247,9 +242,9 @@ describe("azureReposInvoker.ts", (): void => { it(`should throw when SYSTEM_TEAMFOUNDATIONCOLLECTIONURI is set to the invalid value '${String(variable)}'`, async (): Promise => { // Arrange if (typeof variable === "undefined") { - delete process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI; + stubEnv(["SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", undefined]); } else { - process.env.SYSTEM_TEAMFOUNDATIONCOLLECTIONURI = variable; + stubEnv(["SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", variable]); } const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( diff --git a/src/task/tests/repos/gitHubReposInvoker.spec.ts b/src/task/tests/repos/gitHubReposInvoker.spec.ts index 9f19c65bc..b2b23fa70 100644 --- a/src/task/tests/repos/gitHubReposInvoker.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.spec.ts @@ -25,6 +25,7 @@ import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { StatusCodes } from "http-status-codes"; import assert from "node:assert/strict"; import { createRequestError } from "../testUtilities/createRequestError.js"; +import { stubEnv } from "../testUtilities/stubEnv.js"; import { stubLocalization } from "../testUtilities/stubLocalization.js"; describe("gitHubReposInvoker.ts", (): void => { @@ -36,9 +37,13 @@ describe("gitHubReposInvoker.ts", (): void => { const expectedUserAgent = "PRMetrics/v1.7.13"; beforeEach((): void => { - process.env.PR_METRICS_ACCESS_TOKEN = "PAT"; - process.env.SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI = - "https://github.com/microsoft/PR-Metrics"; + stubEnv( + ["PR_METRICS_ACCESS_TOKEN", "PAT"], + [ + "SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", + "https://github.com/microsoft/PR-Metrics", + ], + ); gitInvoker = mock(GitInvoker); when(gitInvoker.pullRequestId).thenReturn(12345); @@ -71,11 +76,6 @@ describe("gitHubReposInvoker.ts", (): void => { stubLocalization(runnerInvoker); }); - afterEach((): void => { - delete process.env.PR_METRICS_ACCESS_TOKEN; - delete process.env.SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI; - }); - describe("isAccessTokenAvailable()", (): void => { it("should return null when the token exists on Azure DevOps", async (): Promise => { // Arrange @@ -96,8 +96,8 @@ describe("gitHubReposInvoker.ts", (): void => { it("should return null when the token exists on GitHub", async (): Promise => { // Arrange - process.env.PR_METRICS_ACCESS_TOKEN = "PAT"; - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( instance(gitInvoker), instance(logger), @@ -112,14 +112,11 @@ describe("gitHubReposInvoker.ts", (): void => { // Assert assert.equal(result, null); - // Finalization - delete process.env.PR_METRICS_ACCESS_TOKEN; - delete process.env.GITHUB_ACTION; }); it("should return a string when the token does not exist", async (): Promise => { // Arrange - delete process.env.PR_METRICS_ACCESS_TOKEN; + stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( instance(gitInvoker), instance(logger), @@ -147,9 +144,9 @@ describe("gitHubReposInvoker.ts", (): void => { it(`should throw when SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI is set to the invalid value '${String(variable)}' and the task is running on Azure Pipelines`, async (): Promise => { // Arrange if (typeof variable === "undefined") { - delete process.env.SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI; + stubEnv(["SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", undefined]); } else { - process.env.SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI = variable; + stubEnv(["SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", variable]); } const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( @@ -174,8 +171,9 @@ describe("gitHubReposInvoker.ts", (): void => { it("should throw when SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI is set to an invalid URL and the task is running on Azure Pipelines", async (): Promise => { // Arrange - process.env.SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI = - "https://github.com/microsoft"; + stubEnv( + ["SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", "https://github.com/microsoft"], + ); const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( instance(gitInvoker), instance(logger), @@ -200,13 +198,13 @@ describe("gitHubReposInvoker.ts", (): void => { testCases.forEach((variable: string | undefined): void => { it(`should throw when GITHUB_API_URL is set to the invalid value '${String(variable)}' and the task is running on GitHub`, async (): Promise => { // Arrange - delete process.env.PR_METRICS_ACCESS_TOKEN; - process.env.PR_METRICS_ACCESS_TOKEN = "PAT"; - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); + stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); if (typeof variable === "undefined") { - delete process.env.GITHUB_API_URL; + stubEnv(["GITHUB_API_URL", undefined]); } else { - process.env.GITHUB_API_URL = variable; + stubEnv(["GITHUB_API_URL", variable]); } const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( @@ -226,10 +224,6 @@ describe("gitHubReposInvoker.ts", (): void => { `'GITHUB_API_URL', accessed within 'GitHubReposInvoker.initializeForGitHub()', is invalid, null, or undefined '${String(variable)}'.`, ); - // Finalization - delete process.env.PR_METRICS_ACCESS_TOKEN; - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_API_URL; }); }); } @@ -240,14 +234,14 @@ describe("gitHubReposInvoker.ts", (): void => { testCases.forEach((variable: string | undefined): void => { it(`should throw when GITHUB_REPOSITORY_OWNER is set to the invalid value '${String(variable)}' and the task is running on GitHub`, async (): Promise => { // Arrange - delete process.env.PR_METRICS_ACCESS_TOKEN; - process.env.PR_METRICS_ACCESS_TOKEN = "PAT"; - process.env.GITHUB_ACTION = "PR-Metrics"; - process.env.GITHUB_API_URL = "https://api.github.com"; + stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); + stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_API_URL", "https://api.github.com"]); if (typeof variable === "undefined") { - delete process.env.GITHUB_REPOSITORY_OWNER; + stubEnv(["GITHUB_REPOSITORY_OWNER", undefined]); } else { - process.env.GITHUB_REPOSITORY_OWNER = variable; + stubEnv(["GITHUB_REPOSITORY_OWNER", variable]); } const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( @@ -267,11 +261,6 @@ describe("gitHubReposInvoker.ts", (): void => { `'GITHUB_REPOSITORY_OWNER', accessed within 'GitHubReposInvoker.initializeForGitHub()', is invalid, null, or undefined '${String(variable)}'.`, ); - // Finalization - delete process.env.PR_METRICS_ACCESS_TOKEN; - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_API_URL; - delete process.env.GITHUB_REPOSITORY_OWNER; }); }); } @@ -282,15 +271,15 @@ describe("gitHubReposInvoker.ts", (): void => { testCases.forEach((variable: string | undefined): void => { it(`should throw when GITHUB_REPOSITORY is set to the invalid value '${String(variable)}' and the task is running on GitHub`, async (): Promise => { // Arrange - delete process.env.PR_METRICS_ACCESS_TOKEN; - process.env.PR_METRICS_ACCESS_TOKEN = "PAT"; - process.env.GITHUB_ACTION = "PR-Metrics"; - process.env.GITHUB_API_URL = "https://api.github.com"; - process.env.GITHUB_REPOSITORY_OWNER = "microsoft"; + stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); + stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_API_URL", "https://api.github.com"]); + stubEnv(["GITHUB_REPOSITORY_OWNER", "microsoft"]); if (typeof variable === "undefined") { - delete process.env.GITHUB_REPOSITORY; + stubEnv(["GITHUB_REPOSITORY", undefined]); } else { - process.env.GITHUB_REPOSITORY = variable; + stubEnv(["GITHUB_REPOSITORY", variable]); } const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( @@ -310,24 +299,18 @@ describe("gitHubReposInvoker.ts", (): void => { `'GITHUB_REPOSITORY', accessed within 'GitHubReposInvoker.initializeForGitHub()', is invalid, null, or undefined '${String(variable)}'.`, ); - // Finalization - delete process.env.PR_METRICS_ACCESS_TOKEN; - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_API_URL; - delete process.env.GITHUB_REPOSITORY_OWNER; - delete process.env.GITHUB_REPOSITORY; }); }); } it("should throw when GITHUB_REPOSITORY is in an incorrect format and the task is running on GitHub", async (): Promise => { // Arrange - delete process.env.PR_METRICS_ACCESS_TOKEN; - process.env.PR_METRICS_ACCESS_TOKEN = "PAT"; - process.env.GITHUB_ACTION = "PR-Metrics"; - process.env.GITHUB_API_URL = "https://api.github.com"; - process.env.GITHUB_REPOSITORY_OWNER = "microsoft"; - process.env.GITHUB_REPOSITORY = "microsoft"; + stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); + stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_API_URL", "https://api.github.com"]); + stubEnv(["GITHUB_REPOSITORY_OWNER", "microsoft"]); + stubEnv(["GITHUB_REPOSITORY", "microsoft"]); const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( instance(gitInvoker), instance(logger), @@ -345,12 +328,6 @@ describe("gitHubReposInvoker.ts", (): void => { "GITHUB_REPOSITORY 'microsoft' is in an unexpected format.", ); - // Finalization - delete process.env.PR_METRICS_ACCESS_TOKEN; - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_API_URL; - delete process.env.GITHUB_REPOSITORY_OWNER; - delete process.env.GITHUB_REPOSITORY; }); it("should succeed when the inputs are valid and the task is running on Azure Pipelines", async (): Promise => { @@ -386,10 +363,10 @@ describe("gitHubReposInvoker.ts", (): void => { it("should succeed when the inputs are valid and the task is running on GitHub", async (): Promise => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; - process.env.GITHUB_API_URL = "https://api.github.com"; - process.env.GITHUB_REPOSITORY_OWNER = "microsoft"; - process.env.GITHUB_REPOSITORY = "microsoft/PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_API_URL", "https://api.github.com"]); + stubEnv(["GITHUB_REPOSITORY_OWNER", "microsoft"]); + stubEnv(["GITHUB_REPOSITORY", "microsoft/PR-Metrics"]); when(octokitWrapper.initialize(any())).thenCall( (options: OctokitOptions): void => { assert.equal(options.auth, "PAT"); @@ -418,17 +395,14 @@ describe("gitHubReposInvoker.ts", (): void => { verify(octokitWrapper.initialize(any())).once(); verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); - // Finalization - delete process.env.GITHUB_ACTION; - delete process.env.GITHUB_API_URL; - delete process.env.GITHUB_REPOSITORY_OWNER; - delete process.env.GITHUB_REPOSITORY; }); it("should succeed when the inputs are valid and the URL ends with '.git'", async (): Promise => { // Arrange - process.env.SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI = - "https://github.com/microsoft/PR-Metrics.git"; + stubEnv([ + "SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", + "https://github.com/microsoft/PR-Metrics.git", + ]); when(octokitWrapper.initialize(any())).thenCall( (options: OctokitOptions): void => { assert.equal(options.auth, "PAT"); @@ -460,8 +434,10 @@ describe("gitHubReposInvoker.ts", (): void => { it("should succeed when the inputs are valid and GitHub Enterprise is in use", async (): Promise => { // Arrange - process.env.SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI = - "https://organization.githubenterprise.com/microsoft/PR-Metrics"; + stubEnv([ + "SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", + "https://organization.githubenterprise.com/microsoft/PR-Metrics", + ]); when(octokitWrapper.initialize(any())).thenCall( (options: OctokitOptions): void => { assert.equal(options.auth, "PAT"); diff --git a/src/task/tests/repos/reposInvoker.spec.ts b/src/task/tests/repos/reposInvoker.spec.ts index 56bfecbd5..98fe3988e 100644 --- a/src/task/tests/repos/reposInvoker.spec.ts +++ b/src/task/tests/repos/reposInvoker.spec.ts @@ -13,6 +13,7 @@ import Logger from "../../src/utilities/logger.js"; import type PullRequestDetailsInterface from "../../src/repos/interfaces/pullRequestDetailsInterface.js"; import ReposInvoker from "../../src/repos/reposInvoker.js"; import assert from "node:assert/strict"; +import { stubEnv } from "../testUtilities/stubEnv.js"; describe("reposInvoker.ts", (): void => { let azureReposInvoker: AzureReposInvoker; @@ -28,7 +29,7 @@ describe("reposInvoker.ts", (): void => { describe("isAccessTokenAvailable()", (): void => { it("should invoke Azure Repos when called from an appropriate repo", async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = "TfsGit"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "TfsGit"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -43,13 +44,11 @@ describe("reposInvoker.ts", (): void => { verify(gitHubReposInvoker.isAccessTokenAvailable()).never(); assert.equal(result, null); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); it("should invoke Azure Repos when called from an appropriate repo twice", async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = "TfsGit"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "TfsGit"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -68,13 +67,11 @@ describe("reposInvoker.ts", (): void => { assert.equal(result1, null); assert.equal(result2, null); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); it("should invoke GitHub when called from a GitHub runner", async (): Promise => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -89,8 +86,6 @@ describe("reposInvoker.ts", (): void => { verify(gitHubReposInvoker.isAccessTokenAvailable()).once(); assert.equal(result, null); - // Finalization - delete process.env.GITHUB_ACTION; }); { @@ -99,7 +94,7 @@ describe("reposInvoker.ts", (): void => { testCases.forEach((buildRepositoryProvider: string): void => { it(`should invoke GitHub when called from a repo on '${buildRepositoryProvider}'`, async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = buildRepositoryProvider; + stubEnv(["BUILD_REPOSITORY_PROVIDER", buildRepositoryProvider]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -115,15 +110,13 @@ describe("reposInvoker.ts", (): void => { verify(gitHubReposInvoker.isAccessTokenAvailable()).once(); assert.equal(result, null); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); } it("should throw when the repo type is not set", async (): Promise => { // Arrange - delete process.env.BUILD_REPOSITORY_PROVIDER; + stubEnv(["BUILD_REPOSITORY_PROVIDER", undefined]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -145,7 +138,7 @@ describe("reposInvoker.ts", (): void => { it("should throw when the repo type is set to an invalid value", async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = "Other"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "Other"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -164,15 +157,13 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.isAccessTokenAvailable()).never(); verify(gitHubReposInvoker.isAccessTokenAvailable()).never(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); describe("getTitleAndDescription()", (): void => { it("should invoke Azure Repos when called from an appropriate repo", async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = "TfsGit"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "TfsGit"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -188,13 +179,11 @@ describe("reposInvoker.ts", (): void => { verify(gitHubReposInvoker.getTitleAndDescription()).never(); assert.equal(result, null); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); it("should invoke GitHub when called from a GitHub runner", async (): Promise => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -210,8 +199,6 @@ describe("reposInvoker.ts", (): void => { verify(gitHubReposInvoker.getTitleAndDescription()).once(); assert.equal(result, null); - // Finalization - delete process.env.GITHUB_ACTION; }); { @@ -220,7 +207,7 @@ describe("reposInvoker.ts", (): void => { testCases.forEach((buildRepositoryProvider: string): void => { it(`should invoke GitHub when called from a repo on '${buildRepositoryProvider}'`, async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = buildRepositoryProvider; + stubEnv(["BUILD_REPOSITORY_PROVIDER", buildRepositoryProvider]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -236,15 +223,13 @@ describe("reposInvoker.ts", (): void => { verify(gitHubReposInvoker.getTitleAndDescription()).once(); assert.equal(result, null); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); } it("should throw when the repo type is not set", async (): Promise => { // Arrange - delete process.env.BUILD_REPOSITORY_PROVIDER; + stubEnv(["BUILD_REPOSITORY_PROVIDER", undefined]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -266,7 +251,7 @@ describe("reposInvoker.ts", (): void => { it("should throw when the repo type is set to an invalid value", async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = "Other"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "Other"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -285,15 +270,13 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.getTitleAndDescription()).never(); verify(gitHubReposInvoker.getTitleAndDescription()).never(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); describe("getComments()", (): void => { it("should invoke Azure Repos when called from an appropriate repo", async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = "TfsGit"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "TfsGit"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -308,13 +291,11 @@ describe("reposInvoker.ts", (): void => { verify(gitHubReposInvoker.getComments()).never(); assert.equal(result, null); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); it("should invoke GitHub when called from a GitHub runner", async (): Promise => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -329,8 +310,6 @@ describe("reposInvoker.ts", (): void => { verify(gitHubReposInvoker.getComments()).once(); assert.equal(result, null); - // Finalization - delete process.env.GITHUB_ACTION; }); { @@ -339,7 +318,7 @@ describe("reposInvoker.ts", (): void => { testCases.forEach((buildRepositoryProvider: string): void => { it(`should invoke GitHub when called from a repo on '${buildRepositoryProvider}'`, async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = buildRepositoryProvider; + stubEnv(["BUILD_REPOSITORY_PROVIDER", buildRepositoryProvider]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -354,15 +333,13 @@ describe("reposInvoker.ts", (): void => { verify(gitHubReposInvoker.getComments()).once(); assert.equal(result, null); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); } it("should throw when the repo type is not set", async (): Promise => { // Arrange - delete process.env.BUILD_REPOSITORY_PROVIDER; + stubEnv(["BUILD_REPOSITORY_PROVIDER", undefined]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -384,7 +361,7 @@ describe("reposInvoker.ts", (): void => { it("should throw when the repo type is set to an invalid value", async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = "Other"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "Other"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -403,15 +380,13 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.getComments()).never(); verify(gitHubReposInvoker.getComments()).never(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); describe("setTitleAndDescription()", (): void => { it("should invoke Azure Repos when called from an appropriate repo", async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = "TfsGit"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "TfsGit"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -425,13 +400,11 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.setTitleAndDescription(null, null)).once(); verify(gitHubReposInvoker.setTitleAndDescription(null, null)).never(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); it("should invoke GitHub when called from a GitHub runner", async (): Promise => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -445,8 +418,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.setTitleAndDescription(null, null)).never(); verify(gitHubReposInvoker.setTitleAndDescription(null, null)).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); { @@ -455,7 +426,7 @@ describe("reposInvoker.ts", (): void => { testCases.forEach((buildRepositoryProvider: string): void => { it(`should invoke GitHub when called from a repo on '${buildRepositoryProvider}'`, async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = buildRepositoryProvider; + stubEnv(["BUILD_REPOSITORY_PROVIDER", buildRepositoryProvider]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -469,15 +440,13 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.setTitleAndDescription(null, null)).never(); verify(gitHubReposInvoker.setTitleAndDescription(null, null)).once(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); } it("should throw when the repo type is not set", async (): Promise => { // Arrange - delete process.env.BUILD_REPOSITORY_PROVIDER; + stubEnv(["BUILD_REPOSITORY_PROVIDER", undefined]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -499,7 +468,7 @@ describe("reposInvoker.ts", (): void => { it("should throw when the repo type is set to an invalid value", async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = "Other"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "Other"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -518,15 +487,13 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.setTitleAndDescription(null, null)).never(); verify(gitHubReposInvoker.setTitleAndDescription(null, null)).never(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); describe("createComment()", (): void => { it("should invoke Azure Repos when called from an appropriate repo", async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = "TfsGit"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "TfsGit"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -560,13 +527,11 @@ describe("reposInvoker.ts", (): void => { ), ).never(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); it("should invoke GitHub when called from a GitHub runner", async (): Promise => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -600,8 +565,6 @@ describe("reposInvoker.ts", (): void => { ), ).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); { @@ -610,7 +573,7 @@ describe("reposInvoker.ts", (): void => { testCases.forEach((buildRepositoryProvider: string): void => { it(`should invoke GitHub when called from a repo on '${buildRepositoryProvider}'`, async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = buildRepositoryProvider; + stubEnv(["BUILD_REPOSITORY_PROVIDER", buildRepositoryProvider]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -644,15 +607,13 @@ describe("reposInvoker.ts", (): void => { ), ).once(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); } it("should throw when the repo type is not set", async (): Promise => { // Arrange - delete process.env.BUILD_REPOSITORY_PROVIDER; + stubEnv(["BUILD_REPOSITORY_PROVIDER", undefined]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -689,7 +650,7 @@ describe("reposInvoker.ts", (): void => { it("should throw when the repo type is set to an invalid value", async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = "Other"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "Other"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -723,15 +684,13 @@ describe("reposInvoker.ts", (): void => { ), ).never(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); describe("updateComment()", (): void => { it("should invoke Azure Repos when called from an appropriate repo", async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = "TfsGit"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "TfsGit"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -746,13 +705,11 @@ describe("reposInvoker.ts", (): void => { // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubReposInvoker.updateComment(0, null, null)).never(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); it("should invoke GitHub when called from a GitHub runner", async (): Promise => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -767,8 +724,6 @@ describe("reposInvoker.ts", (): void => { // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubReposInvoker.updateComment(0, null, null)).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); { @@ -777,7 +732,7 @@ describe("reposInvoker.ts", (): void => { testCases.forEach((buildRepositoryProvider: string): void => { it(`should invoke GitHub when called from a repo on '${buildRepositoryProvider}'`, async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = buildRepositoryProvider; + stubEnv(["BUILD_REPOSITORY_PROVIDER", buildRepositoryProvider]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -792,15 +747,13 @@ describe("reposInvoker.ts", (): void => { // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubReposInvoker.updateComment(0, null, null)).once(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); } it("should throw when the repo type is not set", async (): Promise => { // Arrange - delete process.env.BUILD_REPOSITORY_PROVIDER; + stubEnv(["BUILD_REPOSITORY_PROVIDER", undefined]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -823,7 +776,7 @@ describe("reposInvoker.ts", (): void => { it("should throw when the repo type is set to an invalid value", async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = "Other"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "Other"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -843,15 +796,13 @@ describe("reposInvoker.ts", (): void => { // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubReposInvoker.updateComment(0, null, null)).never(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); describe("deleteCommentThread()", (): void => { it("should invoke Azure Repos when called from an appropriate repo", async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = "TfsGit"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "TfsGit"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -865,13 +816,11 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.deleteCommentThread(20)).once(); verify(gitHubReposInvoker.deleteCommentThread(20)).never(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); it("should invoke GitHub when called from a GitHub runner", async (): Promise => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -885,8 +834,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.deleteCommentThread(20)).never(); verify(gitHubReposInvoker.deleteCommentThread(20)).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); { @@ -895,7 +842,7 @@ describe("reposInvoker.ts", (): void => { testCases.forEach((buildRepositoryProvider: string): void => { it(`should invoke GitHub when called from a repo on '${buildRepositoryProvider}'`, async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = buildRepositoryProvider; + stubEnv(["BUILD_REPOSITORY_PROVIDER", buildRepositoryProvider]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -909,15 +856,13 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.deleteCommentThread(20)).never(); verify(gitHubReposInvoker.deleteCommentThread(20)).once(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); } it("should throw when the repo type is not set", async (): Promise => { // Arrange - delete process.env.BUILD_REPOSITORY_PROVIDER; + stubEnv(["BUILD_REPOSITORY_PROVIDER", undefined]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -939,7 +884,7 @@ describe("reposInvoker.ts", (): void => { it("should throw when the repo type is set to an invalid value", async (): Promise => { // Arrange - process.env.BUILD_REPOSITORY_PROVIDER = "Other"; + stubEnv(["BUILD_REPOSITORY_PROVIDER", "Other"]); const reposInvoker: ReposInvoker = new ReposInvoker( instance(azureReposInvoker), instance(gitHubReposInvoker), @@ -958,8 +903,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.deleteCommentThread(20)).never(); verify(gitHubReposInvoker.deleteCommentThread(20)).never(); - // Finalization - delete process.env.BUILD_REPOSITORY_PROVIDER; }); }); }); diff --git a/src/task/tests/repos/tokenManager.spec.ts b/src/task/tests/repos/tokenManager.spec.ts index b47a291b4..72cd04e42 100644 --- a/src/task/tests/repos/tokenManager.spec.ts +++ b/src/task/tests/repos/tokenManager.spec.ts @@ -15,6 +15,7 @@ import TokenManager from "../../src/repos/tokenManager.js"; import { WebApi } from "azure-devops-node-api"; import assert from "node:assert/strict"; import { resolvableInstance } from "../testUtilities/resolvableInstance.js"; +import { stubEnv } from "../testUtilities/stubEnv.js"; import { stubLocalization } from "../testUtilities/stubLocalization.js"; describe("tokenManager.ts", (): void => { @@ -28,11 +29,13 @@ describe("tokenManager.ts", (): void => { const tenantId = "98765432-abcd-ef01-2345-678901234567"; beforeEach((): void => { - process.env.SYSTEM_COLLECTIONURI = "https://dev.azure.com/organization"; - process.env.SYSTEM_TEAMPROJECTID = "TeamProjectId"; - process.env.SYSTEM_HOSTTYPE = "HostType"; - process.env.SYSTEM_PLANID = "PlanId"; - process.env.SYSTEM_JOBID = "JobId"; + stubEnv( + ["SYSTEM_COLLECTIONURI", "https://dev.azure.com/organization"], + ["SYSTEM_HOSTTYPE", "HostType"], + ["SYSTEM_JOBID", "JobId"], + ["SYSTEM_PLANID", "PlanId"], + ["SYSTEM_TEAMPROJECTID", "TeamProjectId"], + ); taskApi = mock(); const requestHandler: IRequestHandler = mock(); @@ -131,14 +134,6 @@ describe("tokenManager.ts", (): void => { }); }); - after(() => { - delete process.env.SYSTEM_COLLECTIONURI; - delete process.env.SYSTEM_TEAMPROJECTID; - delete process.env.SYSTEM_HOSTTYPE; - delete process.env.SYSTEM_PLANID; - delete process.env.SYSTEM_JOBID; - }); - describe("getToken()", (): void => { it("returns null when no workload identity federation is specified", async (): Promise => { // Arrange @@ -343,7 +338,7 @@ describe("tokenManager.ts", (): void => { it("throws an error when the collection URI is undefined", async (): Promise => { // Arrange - delete process.env.SYSTEM_COLLECTIONURI; + stubEnv(["SYSTEM_COLLECTIONURI", undefined]); const tokenManager: TokenManager = new TokenManager( instance(azureDevOpsApiWrapper), instance(logger), @@ -363,7 +358,7 @@ describe("tokenManager.ts", (): void => { it("throws an error when the team project URI is undefined", async (): Promise => { // Arrange - delete process.env.SYSTEM_TEAMPROJECTID; + stubEnv(["SYSTEM_TEAMPROJECTID", undefined]); const tokenManager: TokenManager = new TokenManager( instance(azureDevOpsApiWrapper), instance(logger), @@ -383,7 +378,7 @@ describe("tokenManager.ts", (): void => { it("throws an error when the host type is undefined", async (): Promise => { // Arrange - delete process.env.SYSTEM_HOSTTYPE; + stubEnv(["SYSTEM_HOSTTYPE", undefined]); const tokenManager: TokenManager = new TokenManager( instance(azureDevOpsApiWrapper), instance(logger), @@ -403,7 +398,7 @@ describe("tokenManager.ts", (): void => { it("throws an error when the plan ID is undefined", async (): Promise => { // Arrange - delete process.env.SYSTEM_PLANID; + stubEnv(["SYSTEM_PLANID", undefined]); const tokenManager: TokenManager = new TokenManager( instance(azureDevOpsApiWrapper), instance(logger), @@ -423,7 +418,7 @@ describe("tokenManager.ts", (): void => { it("throws an error when the job ID is undefined", async (): Promise => { // Arrange - delete process.env.SYSTEM_JOBID; + stubEnv(["SYSTEM_JOBID", undefined]); const tokenManager: TokenManager = new TokenManager( instance(azureDevOpsApiWrapper), instance(logger), diff --git a/src/task/tests/runners/runnerInvoker.spec.ts b/src/task/tests/runners/runnerInvoker.spec.ts index ba6d30758..f836e96d0 100644 --- a/src/task/tests/runners/runnerInvoker.spec.ts +++ b/src/task/tests/runners/runnerInvoker.spec.ts @@ -10,6 +10,7 @@ import type ExecOutput from "../../src/runners/execOutput.js"; import GitHubRunnerInvoker from "../../src/runners/gitHubRunnerInvoker.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; +import { stubEnv } from "../testUtilities/stubEnv.js"; describe("runnerInvoker.ts", (): void => { let azurePipelinesRunnerInvoker: AzurePipelinesRunnerInvoker; @@ -31,7 +32,7 @@ describe("runnerInvoker.ts", (): void => { it("should return true when running on GitHub", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); // Act const result: boolean = RunnerInvoker.isGitHub; @@ -39,8 +40,6 @@ describe("runnerInvoker.ts", (): void => { // Assert assert.equal(result, true); - // Finalization - delete process.env.GITHUB_ACTION; }); }); @@ -86,7 +85,7 @@ describe("runnerInvoker.ts", (): void => { it("should call the underlying method when running on GitHub", async (): Promise => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const runnerInvoker: RunnerInvoker = new RunnerInvoker( instance(azurePipelinesRunnerInvoker), instance(gitHubRunnerInvoker), @@ -120,13 +119,11 @@ describe("runnerInvoker.ts", (): void => { gitHubRunnerInvoker.exec("TOOL", deepEqual(["Argument1", "Argument2"])), ).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); it("should call the underlying method each time when running on GitHub", async (): Promise => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const runnerInvoker: RunnerInvoker = new RunnerInvoker( instance(azurePipelinesRunnerInvoker), instance(gitHubRunnerInvoker), @@ -167,8 +164,6 @@ describe("runnerInvoker.ts", (): void => { gitHubRunnerInvoker.exec("TOOL", deepEqual(["Argument1", "Argument2"])), ).twice(); - // Finalization - delete process.env.GITHUB_ACTION; }); }); @@ -198,7 +193,7 @@ describe("runnerInvoker.ts", (): void => { it("should call the underlying method when running on GitHub", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const runnerInvoker: RunnerInvoker = new RunnerInvoker( instance(azurePipelinesRunnerInvoker), instance(gitHubRunnerInvoker), @@ -219,8 +214,6 @@ describe("runnerInvoker.ts", (): void => { gitHubRunnerInvoker.getInput(deepEqual(["Test", "Suffix"])), ).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); }); @@ -254,7 +247,7 @@ describe("runnerInvoker.ts", (): void => { it("should call the underlying method when running on GitHub", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const runnerInvoker: RunnerInvoker = new RunnerInvoker( instance(azurePipelinesRunnerInvoker), instance(gitHubRunnerInvoker), @@ -282,8 +275,6 @@ describe("runnerInvoker.ts", (): void => { // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubRunnerInvoker.getEndpointAuthorization("id")).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); }); @@ -313,7 +304,7 @@ describe("runnerInvoker.ts", (): void => { it("should call the underlying method when running on GitHub", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const runnerInvoker: RunnerInvoker = new RunnerInvoker( instance(azurePipelinesRunnerInvoker), instance(gitHubRunnerInvoker), @@ -335,8 +326,6 @@ describe("runnerInvoker.ts", (): void => { // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubRunnerInvoker.getEndpointAuthorizationScheme("id")).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); }); @@ -374,7 +363,7 @@ describe("runnerInvoker.ts", (): void => { it("should call the underlying method when running on GitHub", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const runnerInvoker: RunnerInvoker = new RunnerInvoker( instance(azurePipelinesRunnerInvoker), instance(gitHubRunnerInvoker), @@ -401,8 +390,6 @@ describe("runnerInvoker.ts", (): void => { gitHubRunnerInvoker.getEndpointAuthorizationParameter("id", "key"), ).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); }); @@ -424,7 +411,7 @@ describe("runnerInvoker.ts", (): void => { it("should call the underlying method when running on GitHub", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const runnerInvoker: RunnerInvoker = new RunnerInvoker( instance(azurePipelinesRunnerInvoker), instance(gitHubRunnerInvoker), @@ -437,8 +424,6 @@ describe("runnerInvoker.ts", (): void => { verify(azurePipelinesRunnerInvoker.locInitialize("TEST")).never(); verify(gitHubRunnerInvoker.locInitialize("TEST")).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); it("should throw when locInitialize is called twice", (): void => { @@ -502,7 +487,7 @@ describe("runnerInvoker.ts", (): void => { it("should call the underlying method when running on GitHub", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const runnerInvoker: RunnerInvoker = new RunnerInvoker( instance(azurePipelinesRunnerInvoker), instance(gitHubRunnerInvoker), @@ -518,8 +503,6 @@ describe("runnerInvoker.ts", (): void => { verify(azurePipelinesRunnerInvoker.loc("TEST")).never(); verify(gitHubRunnerInvoker.loc("TEST")).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); }); @@ -541,7 +524,7 @@ describe("runnerInvoker.ts", (): void => { it("should call the underlying method when running on GitHub", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const runnerInvoker: RunnerInvoker = new RunnerInvoker( instance(azurePipelinesRunnerInvoker), instance(gitHubRunnerInvoker), @@ -554,8 +537,6 @@ describe("runnerInvoker.ts", (): void => { verify(azurePipelinesRunnerInvoker.logDebug("TEST")).never(); verify(gitHubRunnerInvoker.logDebug("TEST")).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); }); @@ -577,7 +558,7 @@ describe("runnerInvoker.ts", (): void => { it("should call the underlying method when running on GitHub", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const runnerInvoker: RunnerInvoker = new RunnerInvoker( instance(azurePipelinesRunnerInvoker), instance(gitHubRunnerInvoker), @@ -590,8 +571,6 @@ describe("runnerInvoker.ts", (): void => { verify(azurePipelinesRunnerInvoker.logError("TEST")).never(); verify(gitHubRunnerInvoker.logError("TEST")).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); }); @@ -613,7 +592,7 @@ describe("runnerInvoker.ts", (): void => { it("should call the underlying method when running on GitHub", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const runnerInvoker: RunnerInvoker = new RunnerInvoker( instance(azurePipelinesRunnerInvoker), instance(gitHubRunnerInvoker), @@ -626,8 +605,6 @@ describe("runnerInvoker.ts", (): void => { verify(azurePipelinesRunnerInvoker.logWarning("TEST")).never(); verify(gitHubRunnerInvoker.logWarning("TEST")).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); }); @@ -649,7 +626,7 @@ describe("runnerInvoker.ts", (): void => { it("should call the underlying method when running on GitHub", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const runnerInvoker: RunnerInvoker = new RunnerInvoker( instance(azurePipelinesRunnerInvoker), instance(gitHubRunnerInvoker), @@ -662,8 +639,6 @@ describe("runnerInvoker.ts", (): void => { verify(azurePipelinesRunnerInvoker.setStatusFailed("TEST")).never(); verify(gitHubRunnerInvoker.setStatusFailed("TEST")).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); }); @@ -685,7 +660,7 @@ describe("runnerInvoker.ts", (): void => { it("should call the underlying method when running on GitHub", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const runnerInvoker: RunnerInvoker = new RunnerInvoker( instance(azurePipelinesRunnerInvoker), instance(gitHubRunnerInvoker), @@ -698,8 +673,6 @@ describe("runnerInvoker.ts", (): void => { verify(azurePipelinesRunnerInvoker.setStatusSkipped("TEST")).never(); verify(gitHubRunnerInvoker.setStatusSkipped("TEST")).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); }); @@ -721,7 +694,7 @@ describe("runnerInvoker.ts", (): void => { it("should call the underlying method when running on GitHub", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const runnerInvoker: RunnerInvoker = new RunnerInvoker( instance(azurePipelinesRunnerInvoker), instance(gitHubRunnerInvoker), @@ -734,8 +707,6 @@ describe("runnerInvoker.ts", (): void => { verify(azurePipelinesRunnerInvoker.setStatusSucceeded("TEST")).never(); verify(gitHubRunnerInvoker.setStatusSucceeded("TEST")).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); }); @@ -757,7 +728,7 @@ describe("runnerInvoker.ts", (): void => { it("should call the underlying method when running on GitHub", (): void => { // Arrange - process.env.GITHUB_ACTION = "PR-Metrics"; + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); const runnerInvoker: RunnerInvoker = new RunnerInvoker( instance(azurePipelinesRunnerInvoker), instance(gitHubRunnerInvoker), @@ -770,8 +741,6 @@ describe("runnerInvoker.ts", (): void => { verify(azurePipelinesRunnerInvoker.setSecret("id")).never(); verify(gitHubRunnerInvoker.setSecret("id")).once(); - // Finalization - delete process.env.GITHUB_ACTION; }); }); }); diff --git a/src/task/tests/testUtilities/stubEnv.ts b/src/task/tests/testUtilities/stubEnv.ts new file mode 100644 index 000000000..678f751b4 --- /dev/null +++ b/src/task/tests/testUtilities/stubEnv.ts @@ -0,0 +1,57 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +interface PendingChange { + key: string; + original: string | undefined; +} + +const pending: PendingChange[] = []; + +const unset = (key: string): void => { + Reflect.deleteProperty(process.env, key); +}; + +/** + * Sets one or more environment variables for the duration of the current test. + * Any previous value is captured and restored automatically by a global + * `afterEach` hook once the test completes. + * + * Pass `undefined` as a value to temporarily unset a variable. The helper + * composes correctly with `process.env` assignments from `beforeEach` blocks, + * restoring the state that existed before the `stubEnv` call. + * + * Tuple arguments are used so that POSIX-style uppercase environment variable + * names can be expressed without triggering the camelCase naming convention + * applied to object literal properties. + * @param entries One or more `[name, value]` tuples. + */ +export const stubEnv = ( + ...entries: (readonly [string, string | undefined])[] +): void => { + for (const [key, value] of entries) { + pending.push({ key, original: process.env[key] }); + if (typeof value === "undefined") { + unset(key); + } else { + process.env[key] = value; + } + } +}; + +afterEach((): void => { + while (pending.length > 0) { + const entry: PendingChange | undefined = pending.pop(); + if (typeof entry === "undefined") { + break; + } + + if (typeof entry.original === "undefined") { + unset(entry.key); + } else { + process.env[entry.key] = entry.original; + } + } +}); diff --git a/src/task/tests/utilities/validator.spec.ts b/src/task/tests/utilities/validator.spec.ts index 0c86c8b72..9ef241ece 100644 --- a/src/task/tests/utilities/validator.spec.ts +++ b/src/task/tests/utilities/validator.spec.ts @@ -5,6 +5,7 @@ import * as Validator from "../../src/utilities/validator.js"; import assert from "node:assert/strict"; +import { stubEnv } from "../testUtilities/stubEnv.js"; describe("validator.ts", (): void => { describe("validateString()", (): void => { @@ -52,11 +53,7 @@ describe("validator.ts", (): void => { testCases.forEach((value: string | undefined): void => { it(`should throw an error when passed invalid string value '${String(value)}'`, (): void => { // Arrange - if (typeof value === "undefined") { - delete process.env.TEST_VARIABLE; - } else { - process.env.TEST_VARIABLE = value; - } + stubEnv(["TEST_VARIABLE", value]); // Act const func: () => void = () => @@ -72,16 +69,13 @@ describe("validator.ts", (): void => { `'TEST_VARIABLE', accessed within 'string test method name', is invalid, null, or undefined '${String(value)}'.`, ), ); - - // Finalization - delete process.env.TEST_VARIABLE; }); }); } it("should not throw an error when passed a valid string value", (): void => { // Arrange - process.env.TEST_VARIABLE = "value"; + stubEnv(["TEST_VARIABLE", "value"]); // Act const result: string = Validator.validateVariable( @@ -91,9 +85,6 @@ describe("validator.ts", (): void => { // Assert assert.equal(result, "value"); - - // Finalization - delete process.env.TEST_VARIABLE; }); }); From 2d6cef48cdc6f14c55875a22320fd8dceea01c49 Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Fri, 17 Apr 2026 16:40:04 +0100 Subject: [PATCH 04/27] chore(ci): add skill for updating CI dependencies - Introduce a new skill to refresh pinned CI/CD dependencies. - Enforce consistency of Node.js runtime across workflows and pipelines. - Provide guidelines for when to use this skill and the process involved. - Include verification steps and constraints to ensure proper updates. --- .../skills/update-ci-dependencies/SKILL.md | 149 ++++++++++++++++++ 1 file changed, 149 insertions(+) create mode 100644 .github/skills/update-ci-dependencies/SKILL.md diff --git a/.github/skills/update-ci-dependencies/SKILL.md b/.github/skills/update-ci-dependencies/SKILL.md new file mode 100644 index 000000000..48b3f6f57 --- /dev/null +++ b/.github/skills/update-ci-dependencies/SKILL.md @@ -0,0 +1,149 @@ +--- +name: update-ci-dependencies +description: >- + Refreshes pinned CI/CD dependencies in this repository – SHA-pinned actions in + .github/workflows/, plus task versions and 1ES template refs in + .github/azure-devops/. Also verifies that the pinned Node.js runtime stays + consistent across files without bumping its version. Use when asked to update + CI dependencies, refresh pinned GitHub Actions, bump Azure DevOps task + versions, update 1ES template tags, audit workflow dependencies, or sync + Node.js version pins across workflows, pipelines, and package.json. +--- + +# Update CI Dependencies + +Refreshes pinned versions in GitHub Actions workflows and Azure DevOps +pipelines and enforces Node.js runtime consistency. + +## When to Use + +- Performing a scheduled dependency-refresh sweep before a release. +- Reacting to a CVE that affects a CI dependency. +- Dependabot has stalled or cannot resolve an update. +- A new major version of an Azure DevOps task or 1ES template needs evaluation. +- Verifying the pinned Node.js runtime is consistent across workflows, + pipelines, and `package.json`. + +## Inventory + +Catalog every pinned version before editing. Use `grep` to locate each +pattern. + +### GitHub Workflows (`.github/workflows/*.yml`) + +- **SHA-Pinned Actions**: `uses: owner/repo@<40-char SHA> # vX.Y.Z`. The SHA + and trailing version comment must stay in sync. +- **Other Pinned Tools**: literal `version:` or `ref:` values in step inputs. + +### Azure DevOps Pipelines (`.github/azure-devops/*.yml`) + +- **Task Versions**: `task: TaskName@N` (for example, `Npm@1`, `UseNode@1`, + `EsrpCodeSigning@6`). Only the major version is declared; the latest minor + or patch resolves at runtime. +- **Template Refs**: `ref: refs/tags/` inside `resources.repositories`. + 1ES templates use the moving `release` tag; pin a specific tag only when + compliance requires it. + +## Process + +1. Ensure the working tree is clean: `git status` should show no uncommitted + changes on the target branch. +1. For each SHA-pinned GitHub Action, resolve the latest release and tag SHA: + + ```bash + gh api repos///releases/latest --jq '.tag_name' + gh api repos///git/refs/tags/ --jq '.object.sha' + ``` + + Update the SHA and the trailing `# vX.Y.Z` comment in one edit – never one + without the other. If the tag points to an annotated tag object, dereference + it with a second `gh api` call against the tag object. + +1. For each Azure DevOps task, check the task's latest major version in the + [Azure Pipelines task reference][ado-tasks]. Bump the major version only + after reviewing its release notes and adjusting inputs accordingly. This + includes `UseNode@1` itself (the task version), but never the `version:` + input it receives. +1. For template refs, list available tags and compare with the current pin: + + ```bash + git ls-remote --tags + ``` + + The 1ES templates are hosted in Azure DevOps; authenticate via `az` if the + listing is restricted. + +1. Save changes while preserving formatting: trailing newlines, comment spacing, + and two-space indentation. + +## Node.js Version Consistency + +The pinned Node.js runtime must match everywhere it appears. This skill does +not bump Node.js to a newer version – that is a deliberate, standalone change +– but it flags and resolves inconsistencies. + +Locate every occurrence with `grep`: + +- `.github/workflows/*.yml` – `node-version: X.Y.Z` under `actions/setup-node`. +- `.github/azure-devops/*.yml` – `UseNode@1` with `version: X.Y.Z`. +- `package.json` – `engines.node`. +- `.nvmrc` if present. + +If values diverge, align them on the single intended pin. Prefer the value +already set by the most recent deliberate bump (check `git log` on whichever +file changed most recently). Do not change the value itself. + +## Output Format + +A single commit (or pull request) whose diff touches only CI definition files +under `.github/workflows/` and `.github/azure-devops/`, with: + +- Each SHA-pinned action updated atomically (SHA plus `# vX.Y.Z` comment). +- Each Azure DevOps task major version either left unchanged or bumped + together with any required input adjustments. +- Each 1ES template ref left on its moving tag unless a compliance reason + dictates otherwise. +- Node.js pins aligned across files if they had diverged. + +The commit message should start with `chore:` and reference the dependency +class updated (for example, `chore: refresh GitHub Action pins`). + +## Constraints + +- **Never desynchronize SHA and version comment**: always update both, in the + same edit. +- **Never blindly bump an Azure DevOps task major version**: review release + notes and adjust inputs first. +- **Never bump the Node.js runtime value** from this skill – that is a + separate, deliberate change. +- **Never duplicate a Dependabot PR**: if Dependabot already has an open PR + for an action, rebase or merge it rather than producing a competing change. +- **Never hard-pin a 1ES template ref without justification**: the `release` + tag is intentionally moving. +- **Never modify files outside `.github/workflows/`, + `.github/azure-devops/`, `package.json`, or `.nvmrc`** during this task. + +## Quick Reference + +| Dependency Type | Files | Update Source | +| ----------------- | ---------------------------- | ---------------------- | +| GitHub Action | `.github/workflows/*.yml` | `gh api` latest tag | +| Azure DevOps Task | `.github/azure-devops/*.yml` | ADO task reference | +| 1ES Template Ref | `.github/azure-devops/*.yml` | `git ls-remote --tags` | + +## Verification + +- Lint YAML with the `Build` workflow's `validate-linter` job (Super-Linter) or + locally: `npx yaml-lint .github/workflows/*.yml .github/azure-devops/*.yml`. +- Confirm each GitHub Action SHA still matches its version comment: + + ```bash + gh api "repos///git/refs/tags/v" --jq '.object.sha' + ``` + +- Push to a branch and confirm the `Build` workflow passes on GitHub. For Azure + DevOps, run `pr-test.yml` against the branch. +- Re-run `npm run build:package` if any action-side behavior shifted, so `dist/` + stays consistent. + +[ado-tasks]: https://learn.microsoft.com/azure/devops/pipelines/tasks/reference/ From 2cc21aec4aa505763a63f3482e2444161346516e Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Fri, 17 Apr 2026 18:01:15 +0100 Subject: [PATCH 05/27] Split oversized spec files by topic and per public method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Splits the four largest spec files into 28 smaller siblings to make refactors cheaper and triage easier: - inputs.spec.ts (1,053 lines) → 8 files per nested describe. - azureReposInvoker.spec.ts (1,848 lines) → 7 files per public method. - gitHubReposInvoker.spec.ts (1,513 lines) → 7 files per public method. - codeMetrics.spec.ts (1,529 lines) → 6 files grouped by topic (defaults, nonDefaults, fileMatching, edgeCases, and the existing getFilesNotRequiringReview/getDeletedFilesNotRequiringReview describes). Shared beforeEach setup is extracted into per-file *TestSetup.ts helpers co-located with the splits. All test cases are preserved verbatim and coverage remains at 100% across statements, branches, functions, and lines. Implements step 4 of TestPlan.md. --- .../metrics/codeMetrics.defaults.spec.ts | 505 +++++ .../metrics/codeMetrics.edgeCases.spec.ts | 87 + .../metrics/codeMetrics.fileMatching.spec.ts | 230 ++ ....getDeletedFilesNotRequiringReview.spec.ts | 113 + ...Metrics.getFilesNotRequiringReview.spec.ts | 155 ++ .../metrics/codeMetrics.nonDefaults.spec.ts | 573 +++++ src/task/tests/metrics/codeMetrics.spec.ts | 1529 -------------- .../tests/metrics/codeMetricsTestSetup.ts | 50 + .../tests/metrics/inputs.allInputs.spec.ts | 127 ++ .../metrics/inputs.alwaysCloseComment.spec.ts | 89 + .../tests/metrics/inputs.baseSize.spec.ts | 108 + .../metrics/inputs.codeFileExtensions.spec.ts | 182 ++ .../inputs.fileMatchingPatterns.spec.ts | 209 ++ .../tests/metrics/inputs.growthRate.spec.ts | 125 ++ src/task/tests/metrics/inputs.spec.ts | 1053 ---------- .../tests/metrics/inputs.testFactor.spec.ts | 147 ++ .../inputs.testMatchingPatterns.spec.ts | 162 ++ src/task/tests/metrics/inputsTestSetup.ts | 131 ++ .../azureReposInvoker.createComment.spec.ts | 318 +++ ...reReposInvoker.deleteCommentThread.spec.ts | 146 ++ .../azureReposInvoker.getComments.spec.ts | 448 ++++ ...eposInvoker.getTitleAndDescription.spec.ts | 350 ++++ ...eposInvoker.isAccessTokenAvailable.spec.ts | 95 + ...eposInvoker.setTitleAndDescription.spec.ts | 297 +++ .../tests/repos/azureReposInvoker.spec.ts | 1848 ----------------- .../azureReposInvoker.updateComment.spec.ts | 408 ++++ .../tests/repos/azureReposInvokerTestSetup.ts | 79 + .../gitHubReposInvoker.createComment.spec.ts | 489 +++++ ...ubReposInvoker.deleteCommentThread.spec.ts | 63 + .../gitHubReposInvoker.getComments.spec.ts | 233 +++ ...eposInvoker.getTitleAndDescription.spec.ts | 556 +++++ ...eposInvoker.isAccessTokenAvailable.spec.ts | 90 + ...eposInvoker.setTitleAndDescription.spec.ts | 156 ++ .../tests/repos/gitHubReposInvoker.spec.ts | 1513 -------------- .../gitHubReposInvoker.updateComment.spec.ts | 84 + .../repos/gitHubReposInvokerTestSetup.ts | 72 + 36 files changed, 6877 insertions(+), 5943 deletions(-) create mode 100644 src/task/tests/metrics/codeMetrics.defaults.spec.ts create mode 100644 src/task/tests/metrics/codeMetrics.edgeCases.spec.ts create mode 100644 src/task/tests/metrics/codeMetrics.fileMatching.spec.ts create mode 100644 src/task/tests/metrics/codeMetrics.getDeletedFilesNotRequiringReview.spec.ts create mode 100644 src/task/tests/metrics/codeMetrics.getFilesNotRequiringReview.spec.ts create mode 100644 src/task/tests/metrics/codeMetrics.nonDefaults.spec.ts delete mode 100644 src/task/tests/metrics/codeMetrics.spec.ts create mode 100644 src/task/tests/metrics/codeMetricsTestSetup.ts create mode 100644 src/task/tests/metrics/inputs.allInputs.spec.ts create mode 100644 src/task/tests/metrics/inputs.alwaysCloseComment.spec.ts create mode 100644 src/task/tests/metrics/inputs.baseSize.spec.ts create mode 100644 src/task/tests/metrics/inputs.codeFileExtensions.spec.ts create mode 100644 src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts create mode 100644 src/task/tests/metrics/inputs.growthRate.spec.ts delete mode 100644 src/task/tests/metrics/inputs.spec.ts create mode 100644 src/task/tests/metrics/inputs.testFactor.spec.ts create mode 100644 src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts create mode 100644 src/task/tests/metrics/inputsTestSetup.ts create mode 100644 src/task/tests/repos/azureReposInvoker.createComment.spec.ts create mode 100644 src/task/tests/repos/azureReposInvoker.deleteCommentThread.spec.ts create mode 100644 src/task/tests/repos/azureReposInvoker.getComments.spec.ts create mode 100644 src/task/tests/repos/azureReposInvoker.getTitleAndDescription.spec.ts create mode 100644 src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts create mode 100644 src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts delete mode 100644 src/task/tests/repos/azureReposInvoker.spec.ts create mode 100644 src/task/tests/repos/azureReposInvoker.updateComment.spec.ts create mode 100644 src/task/tests/repos/azureReposInvokerTestSetup.ts create mode 100644 src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts create mode 100644 src/task/tests/repos/gitHubReposInvoker.deleteCommentThread.spec.ts create mode 100644 src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts create mode 100644 src/task/tests/repos/gitHubReposInvoker.getTitleAndDescription.spec.ts create mode 100644 src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts create mode 100644 src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts delete mode 100644 src/task/tests/repos/gitHubReposInvoker.spec.ts create mode 100644 src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts create mode 100644 src/task/tests/repos/gitHubReposInvokerTestSetup.ts diff --git a/src/task/tests/metrics/codeMetrics.defaults.spec.ts b/src/task/tests/metrics/codeMetrics.defaults.spec.ts new file mode 100644 index 000000000..c33fead7c --- /dev/null +++ b/src/task/tests/metrics/codeMetrics.defaults.spec.ts @@ -0,0 +1,505 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import { instance, when } from "ts-mockito"; +import CodeMetrics from "../../src/metrics/codeMetrics.js"; +import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import Inputs from "../../src/metrics/inputs.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import assert from "node:assert/strict"; +import { createCodeMetricsMocks } from "./codeMetricsTestSetup.js"; + +describe("codeMetrics.ts", (): void => { + let gitInvoker: GitInvoker; + let inputs: Inputs; + let logger: Logger; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ + gitInvoker, + inputs, + logger, + runnerInvoker, + } = createCodeMetricsMocks()); + }); + + { + interface TestCaseType { + gitResponse: string; + metrics: CodeMetricsData; + sizeIndicator: string; + testCoverageIndicator: boolean; + } + + const testCases: TestCaseType[] = [ + { + gitResponse: "0\t0\tfile.ts", + metrics: new CodeMetricsData(0, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\tfile.ts", + metrics: new CodeMetricsData(1, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: false, + }, + { + gitResponse: "1\t0\tfile.ts\n1\t0\ttest.ts", + metrics: new CodeMetricsData(1, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "199\t0\tfile.ts", + metrics: new CodeMetricsData(199, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: false, + }, + { + gitResponse: "199\t0\tfile.ts\n198\t0\ttest.ts", + metrics: new CodeMetricsData(199, 198, 0), + sizeIndicator: "XS", + testCoverageIndicator: false, + }, + { + gitResponse: "199\t0\tfile.ts\n199\t0\ttest.ts", + metrics: new CodeMetricsData(199, 199, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "200\t0\tfile.ts", + metrics: new CodeMetricsData(200, 0, 0), + sizeIndicator: "S", + testCoverageIndicator: false, + }, + { + gitResponse: "200\t0\tfile.ts\n199\t0\ttest.ts", + metrics: new CodeMetricsData(200, 199, 0), + sizeIndicator: "S", + testCoverageIndicator: false, + }, + { + gitResponse: "200\t0\tfile.ts\n200\t0\ttest.ts", + metrics: new CodeMetricsData(200, 200, 0), + sizeIndicator: "S", + testCoverageIndicator: true, + }, + { + gitResponse: "399\t0\tfile.ts", + metrics: new CodeMetricsData(399, 0, 0), + sizeIndicator: "S", + testCoverageIndicator: false, + }, + { + gitResponse: "399\t0\tfile.ts\n398\t0\ttest.ts", + metrics: new CodeMetricsData(399, 398, 0), + sizeIndicator: "S", + testCoverageIndicator: false, + }, + { + gitResponse: "399\t0\tfile.ts\n399\t0\ttest.ts", + metrics: new CodeMetricsData(399, 399, 0), + sizeIndicator: "S", + testCoverageIndicator: true, + }, + { + gitResponse: "400\t0\tfile.ts", + metrics: new CodeMetricsData(400, 0, 0), + sizeIndicator: "M", + testCoverageIndicator: false, + }, + { + gitResponse: "400\t0\tfile.ts\n399\t0\ttest.ts", + metrics: new CodeMetricsData(400, 399, 0), + sizeIndicator: "M", + testCoverageIndicator: false, + }, + { + gitResponse: "400\t0\tfile.ts\n400\t0\ttest.ts", + metrics: new CodeMetricsData(400, 400, 0), + sizeIndicator: "M", + testCoverageIndicator: true, + }, + { + gitResponse: "799\t0\tfile.ts", + metrics: new CodeMetricsData(799, 0, 0), + sizeIndicator: "M", + testCoverageIndicator: false, + }, + { + gitResponse: "799\t0\tfile.ts\n798\t0\ttest.ts", + metrics: new CodeMetricsData(799, 798, 0), + sizeIndicator: "M", + testCoverageIndicator: false, + }, + { + gitResponse: "799\t0\tfile.ts\n799\t0\ttest.ts", + metrics: new CodeMetricsData(799, 799, 0), + sizeIndicator: "M", + testCoverageIndicator: true, + }, + { + gitResponse: "800\t0\tfile.ts", + metrics: new CodeMetricsData(800, 0, 0), + sizeIndicator: "L", + testCoverageIndicator: false, + }, + { + gitResponse: "800\t0\tfile.ts\n799\t0\ttest.ts", + metrics: new CodeMetricsData(800, 799, 0), + sizeIndicator: "L", + testCoverageIndicator: false, + }, + { + gitResponse: "800\t0\tfile.ts\n800\t0\ttest.ts", + metrics: new CodeMetricsData(800, 800, 0), + sizeIndicator: "L", + testCoverageIndicator: true, + }, + { + gitResponse: "1599\t0\tfile.ts", + metrics: new CodeMetricsData(1599, 0, 0), + sizeIndicator: "L", + testCoverageIndicator: false, + }, + { + gitResponse: "1599\t0\tfile.ts\n1598\t0\ttest.ts", + metrics: new CodeMetricsData(1599, 1598, 0), + sizeIndicator: "L", + testCoverageIndicator: false, + }, + { + gitResponse: "1599\t0\tfile.ts\n1599\t0\ttest.ts", + metrics: new CodeMetricsData(1599, 1599, 0), + sizeIndicator: "L", + testCoverageIndicator: true, + }, + { + gitResponse: "1600\t0\tfile.ts", + metrics: new CodeMetricsData(1600, 0, 0), + sizeIndicator: "XL", + testCoverageIndicator: false, + }, + { + gitResponse: "1600\t0\tfile.ts\n1599\t0\ttest.ts", + metrics: new CodeMetricsData(1600, 1599, 0), + sizeIndicator: "XL", + testCoverageIndicator: false, + }, + { + gitResponse: "1600\t0\tfile.ts\n1600\t0\ttest.ts", + metrics: new CodeMetricsData(1600, 1600, 0), + sizeIndicator: "XL", + testCoverageIndicator: true, + }, + { + gitResponse: "3199\t0\tfile.ts", + metrics: new CodeMetricsData(3199, 0, 0), + sizeIndicator: "XL", + testCoverageIndicator: false, + }, + { + gitResponse: "3199\t0\tfile.ts\n3198\t0\ttest.ts", + metrics: new CodeMetricsData(3199, 3198, 0), + sizeIndicator: "XL", + testCoverageIndicator: false, + }, + { + gitResponse: "3199\t0\tfile.ts\n3199\t0\ttest.ts", + metrics: new CodeMetricsData(3199, 3199, 0), + sizeIndicator: "XL", + testCoverageIndicator: true, + }, + { + gitResponse: "3200\t0\tfile.ts", + metrics: new CodeMetricsData(3200, 0, 0), + sizeIndicator: "2XL", + testCoverageIndicator: false, + }, + { + gitResponse: "3200\t0\tfile.ts\n3199\t0\ttest.ts", + metrics: new CodeMetricsData(3200, 3199, 0), + sizeIndicator: "2XL", + testCoverageIndicator: false, + }, + { + gitResponse: "3200\t0\tfile.ts\n3200\t0\ttest.ts", + metrics: new CodeMetricsData(3200, 3200, 0), + sizeIndicator: "2XL", + testCoverageIndicator: true, + }, + { + gitResponse: "6399\t0\tfile.ts", + metrics: new CodeMetricsData(6399, 0, 0), + sizeIndicator: "2XL", + testCoverageIndicator: false, + }, + { + gitResponse: "6399\t0\tfile.ts\n6398\t0\ttest.ts", + metrics: new CodeMetricsData(6399, 6398, 0), + sizeIndicator: "2XL", + testCoverageIndicator: false, + }, + { + gitResponse: "6399\t0\tfile.ts\n6399\t0\ttest.ts", + metrics: new CodeMetricsData(6399, 6399, 0), + sizeIndicator: "2XL", + testCoverageIndicator: true, + }, + { + gitResponse: "6400\t0\tfile.ts", + metrics: new CodeMetricsData(6400, 0, 0), + sizeIndicator: "3XL", + testCoverageIndicator: false, + }, + { + gitResponse: "6400\t0\tfile.ts\n6399\t0\ttest.ts", + metrics: new CodeMetricsData(6400, 6399, 0), + sizeIndicator: "3XL", + testCoverageIndicator: false, + }, + { + gitResponse: "6400\t0\tfile.ts\n6400\t0\ttest.ts", + metrics: new CodeMetricsData(6400, 6400, 0), + sizeIndicator: "3XL", + testCoverageIndicator: true, + }, + { + gitResponse: "819200\t0\tfile.ts", + metrics: new CodeMetricsData(819200, 0, 0), + sizeIndicator: "10XL", + testCoverageIndicator: false, + }, + { + gitResponse: "819200\t0\tfile.ts\n819199\t0\ttest.ts", + metrics: new CodeMetricsData(819200, 819199, 0), + sizeIndicator: "10XL", + testCoverageIndicator: false, + }, + { + gitResponse: "819200\t0\tfile.ts\n819200\t0\ttest.ts", + metrics: new CodeMetricsData(819200, 819200, 0), + sizeIndicator: "10XL", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\tfile.TS", + metrics: new CodeMetricsData(1, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: false, + }, + { + gitResponse: "0\t1\tfile.ts", + metrics: new CodeMetricsData(0, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\tfile.ignored", + metrics: new CodeMetricsData(0, 0, 1), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\tfile", + metrics: new CodeMetricsData(0, 0, 1), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\tfile.ts.ignored", + metrics: new CodeMetricsData(0, 0, 1), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\tfile.ignored.ts", + metrics: new CodeMetricsData(1, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: false, + }, + { + gitResponse: "1\t0\ttest.ignored", + metrics: new CodeMetricsData(0, 0, 1), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\ttasb.cc => test.ts", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\tt{a => e}s{b => t}.t{c => s}", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\tt{a => est.ts}", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\t{a => test.ts}", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\tfolder/test.ts", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\tfolder/Test.ts", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\tfolder/TEST.ts", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\tfolder/DuplicateStorage.ts", + metrics: new CodeMetricsData(1, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: false, + }, + { + gitResponse: "1\t0\tfolder/file.spec.ts", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\tfolder/file.Spec.ts", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\tfolder.spec.ts/file.ts", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\ttest/file.ts", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\ttests/file.ts", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\ttests/file.spec.ts", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\ttests/file.SPEC.ts", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t0\tfolder/tests/file.ts", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t1\tfa/b => folder/test.ts", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "1\t1\tf{a => older}/{b => test.ts}", + metrics: new CodeMetricsData(0, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "0\t0\tfile.ts\n", + metrics: new CodeMetricsData(0, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "-\t-\tfile.ts", + metrics: new CodeMetricsData(0, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + gitResponse: "0\t0\tfile.ts", + metrics: new CodeMetricsData(0, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + ]; + + testCases.forEach( + ({ + gitResponse, + metrics, + sizeIndicator, + testCoverageIndicator, + }: TestCaseType): void => { + it(`with default inputs and git diff '${gitResponse.replace(/\n/gu, "\\n").replace(/\r/gu, "\\r")}', returns '${sizeIndicator}' size and '${String(testCoverageIndicator)}' test coverage`, async (): Promise => { + // Arrange + when(gitInvoker.getDiffSummary()).thenResolve(gitResponse); + + // Act + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), []); + assert.deepEqual( + await codeMetrics.getDeletedFilesNotRequiringReview(), + [], + ); + assert.equal(await codeMetrics.getSize(), sizeIndicator); + assert.equal( + await codeMetrics.getSizeIndicator(), + `${sizeIndicator}${testCoverageIndicator ? "✔" : "⚠️"}`, + ); + assert.deepEqual(await codeMetrics.getMetrics(), metrics); + assert.equal( + await codeMetrics.isSmall(), + sizeIndicator === "XS" || sizeIndicator === "S", + ); + assert.equal( + await codeMetrics.isSufficientlyTested(), + testCoverageIndicator, + ); + }); + }, + ); + } +}); diff --git a/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts b/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts new file mode 100644 index 000000000..d520ff558 --- /dev/null +++ b/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts @@ -0,0 +1,87 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import { anyString, instance, when } from "ts-mockito"; +import CodeMetrics from "../../src/metrics/codeMetrics.js"; +import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import Inputs from "../../src/metrics/inputs.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import assert from "node:assert/strict"; +import { createCodeMetricsMocks } from "./codeMetricsTestSetup.js"; + +describe("codeMetrics.ts", (): void => { + let gitInvoker: GitInvoker; + let inputs: Inputs; + let logger: Logger; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ + gitInvoker, + inputs, + logger, + runnerInvoker, + } = createCodeMetricsMocks()); + }); + + it("should return the expected result with test coverage disabled", async (): Promise => { + // Arrange + when(inputs.testFactor).thenReturn(null); + when(gitInvoker.getDiffSummary()).thenResolve("1\t0\tfile.ts"); + + // Act + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), []); + assert.deepEqual(await codeMetrics.getDeletedFilesNotRequiringReview(), []); + assert.equal(await codeMetrics.getSize(), "XS"); + assert.equal(await codeMetrics.getSizeIndicator(), "XS"); + assert.deepEqual( + await codeMetrics.getMetrics(), + new CodeMetricsData(1, 0, 0), + ); + assert.equal(await codeMetrics.isSmall(), true); + assert.equal(await codeMetrics.isSufficientlyTested(), null); + }); + + it("with a size multiplier exceeding 1000, returns a size without thousands separators", async (): Promise => { + // ARRANGE + when(inputs.baseSize).thenReturn(1); + when(inputs.growthRate).thenReturn(1.001); + when(inputs.testFactor).thenReturn(1.0); + when(inputs.fileMatchingPatterns).thenReturn(["**/*"]); + when(inputs.codeFileExtensions).thenReturn(new Set(["ts"])); + when(gitInvoker.getDiffSummary()).thenResolve("3\t0\tfile.ts"); + when( + runnerInvoker.loc( + "metrics.codeMetrics.titleSizeIndicatorFormat", + anyString() as string, + anyString() as string, + ), + ).thenReturn(""); + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // ACT + const size: string = await codeMetrics.getSize(); + + // ASSERT + assert.match(size, /^\d+XL$/u); + const multiplier: number = Number.parseInt(size.replace("XL", ""), 10); + assert.ok(multiplier >= 1000); + }); +}); diff --git a/src/task/tests/metrics/codeMetrics.fileMatching.spec.ts b/src/task/tests/metrics/codeMetrics.fileMatching.spec.ts new file mode 100644 index 000000000..7e8d38c6b --- /dev/null +++ b/src/task/tests/metrics/codeMetrics.fileMatching.spec.ts @@ -0,0 +1,230 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import { instance, when } from "ts-mockito"; +import CodeMetrics from "../../src/metrics/codeMetrics.js"; +import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import Inputs from "../../src/metrics/inputs.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import assert from "node:assert/strict"; +import { createCodeMetricsMocks } from "./codeMetricsTestSetup.js"; + +describe("codeMetrics.ts", (): void => { + let gitInvoker: GitInvoker; + let inputs: Inputs; + let logger: Logger; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ + gitInvoker, + inputs, + logger, + runnerInvoker, + } = createCodeMetricsMocks()); + }); + + { + interface TestCaseType { + gitResponse: string; + } + + const testCases: TestCaseType[] = [ + { + gitResponse: + "2\t2\tfile.ts\n1\t1\tignored1.ts\n1\t1\tacceptance.ts\n1\t1\tignored2.ts", + }, + { + gitResponse: + "1\t1\tfile1.ts\n1\t1\tignored1.ts\n1\t1\tignored2.ts\n1\t1\tacceptance.ts\n1\t1\tfile2.ts", + }, + { + gitResponse: + "1\t1\tfile1.ts\n1\t1\tignored1.ts\n1\t1\tfile2.ts\n1\t1\tacceptance.ts\n1\t1\tignored2.ts", + }, + ]; + + testCases.forEach(({ gitResponse }: TestCaseType): void => { + it(`with multiple ignore patterns and git diff '${gitResponse.replace(/\n/gu, "\\n").replace(/\r/gu, "\\r")}' ignores the appropriate files`, async (): Promise => { + // Arrange + when(inputs.baseSize).thenReturn(100); + when(inputs.growthRate).thenReturn(1.5); + when(inputs.testFactor).thenReturn(2.0); + when(inputs.fileMatchingPatterns).thenReturn([ + "**/*", + "!**/ignored1.ts", + "!**/ignored2.ts", + ]); + when(inputs.testMatchingPatterns).thenReturn(["**/acceptance.ts"]); + when(inputs.codeFileExtensions).thenReturn(new Set(["ts"])); + when(gitInvoker.getDiffSummary()).thenResolve(gitResponse); + + // Act + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), [ + "ignored1.ts", + "ignored2.ts", + ]); + assert.deepEqual( + await codeMetrics.getDeletedFilesNotRequiringReview(), + [], + ); + assert.equal(await codeMetrics.getSize(), "XS"); + assert.equal(await codeMetrics.getSizeIndicator(), "XS⚠️"); + assert.deepEqual( + await codeMetrics.getMetrics(), + new CodeMetricsData(2, 1, 2), + ); + assert.equal(await codeMetrics.isSmall(), true); + assert.equal(await codeMetrics.isSufficientlyTested(), false); + }); + }); + } + + it("with custom include pattern, includes the relevant files", async (): Promise => { + // Arrange + when(inputs.baseSize).thenReturn(100); + when(inputs.growthRate).thenReturn(1.5); + when(inputs.testFactor).thenReturn(2.0); + when(inputs.fileMatchingPatterns).thenReturn(["src/*.ts", "__test__/*.ts"]); + when(inputs.codeFileExtensions).thenReturn(new Set(["ts"])); + when(gitInvoker.getDiffSummary()).thenResolve( + "1\t1\tfile.ts\n1\t1\tsrc/file.ts\n1\t1\t__test__/file.test.ts", + ); + + // Act + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), [ + "file.ts", + ]); + assert.deepEqual(await codeMetrics.getDeletedFilesNotRequiringReview(), []); + assert.equal(await codeMetrics.getSize(), "XS"); + assert.equal(await codeMetrics.getSizeIndicator(), "XS⚠️"); + assert.deepEqual( + await codeMetrics.getMetrics(), + new CodeMetricsData(1, 1, 1), + ); + assert.equal(await codeMetrics.isSmall(), true); + assert.equal(await codeMetrics.isSufficientlyTested(), false); + }); + + it("with only negative patterns, treats all files as non-matching", async (): Promise => { + // Arrange + when(inputs.baseSize).thenReturn(100); + when(inputs.growthRate).thenReturn(1.5); + when(inputs.testFactor).thenReturn(2.0); + when(inputs.fileMatchingPatterns).thenReturn(["!**/ignored.ts"]); + when(inputs.codeFileExtensions).thenReturn(new Set(["ts"])); + when(gitInvoker.getDiffSummary()).thenResolve( + "1\t1\tfile.ts\n1\t1\tignored.ts", + ); + + // Act + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), [ + "file.ts", + "ignored.ts", + ]); + assert.deepEqual(await codeMetrics.getDeletedFilesNotRequiringReview(), []); + assert.equal(await codeMetrics.getSize(), "XS"); + assert.equal(await codeMetrics.getSizeIndicator(), "XS✔"); + assert.deepEqual( + await codeMetrics.getMetrics(), + new CodeMetricsData(0, 0, 2), + ); + assert.equal(await codeMetrics.isSmall(), true); + assert.equal(await codeMetrics.isSufficientlyTested(), true); + }); + + it("with double exclusion ignore patterns ignores the appropriate files", async (): Promise => { + // Arrange + when(inputs.baseSize).thenReturn(100); + when(inputs.growthRate).thenReturn(1.5); + when(inputs.testFactor).thenReturn(2.0); + when(inputs.fileMatchingPatterns).thenReturn([ + "**/*", + "!**/ignored*.ts", + "!!**/ignored2.ts", + ]); + when(inputs.codeFileExtensions).thenReturn(new Set(["ts"])); + when(gitInvoker.getDiffSummary()).thenResolve( + "1\t1\tfile.ts\n1\t1\tignored1.ts\n1\t1\tignored2.ts\n1\t1\tignored3.ts", + ); + + // Act + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), [ + "ignored1.ts", + "ignored3.ts", + ]); + assert.deepEqual(await codeMetrics.getDeletedFilesNotRequiringReview(), []); + assert.equal(await codeMetrics.getSize(), "XS"); + assert.equal(await codeMetrics.getSizeIndicator(), "XS⚠️"); + assert.deepEqual( + await codeMetrics.getMetrics(), + new CodeMetricsData(2, 0, 2), + ); + assert.equal(await codeMetrics.isSmall(), true); + assert.equal(await codeMetrics.isSufficientlyTested(), false); + }); + + it("with all files matching test files, returns the appropriate results", async (): Promise => { + // Arrange + when(inputs.testMatchingPatterns).thenReturn(["**", "*/**"]); + when(gitInvoker.getDiffSummary()).thenResolve( + "1\t0\tfile.ts\n1\t0\ttest.ts\n1\t0\tfolder/file.ts", + ); + + // Act + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), []); + assert.deepEqual(await codeMetrics.getDeletedFilesNotRequiringReview(), []); + assert.equal(await codeMetrics.getSize(), "XS"); + assert.equal(await codeMetrics.getSizeIndicator(), "XS✔"); + assert.deepEqual( + await codeMetrics.getMetrics(), + new CodeMetricsData(0, 3, 0), + ); + assert.equal(await codeMetrics.isSmall(), true); + assert.equal(await codeMetrics.isSufficientlyTested(), true); + }); +}); diff --git a/src/task/tests/metrics/codeMetrics.getDeletedFilesNotRequiringReview.spec.ts b/src/task/tests/metrics/codeMetrics.getDeletedFilesNotRequiringReview.spec.ts new file mode 100644 index 000000000..24b44d0e6 --- /dev/null +++ b/src/task/tests/metrics/codeMetrics.getDeletedFilesNotRequiringReview.spec.ts @@ -0,0 +1,113 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as AssertExtensions from "../testUtilities/assertExtensions.js"; +import { instance, when } from "ts-mockito"; +import CodeMetrics from "../../src/metrics/codeMetrics.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import Inputs from "../../src/metrics/inputs.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import assert from "node:assert/strict"; +import { createCodeMetricsMocks } from "./codeMetricsTestSetup.js"; + +describe("codeMetrics.ts", (): void => { + let gitInvoker: GitInvoker; + let inputs: Inputs; + let logger: Logger; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ + gitInvoker, + inputs, + logger, + runnerInvoker, + } = createCodeMetricsMocks()); + }); + + describe("getDeletedFilesNotRequiringReview()", (): void => { + it("should return an empty array when the Git diff summary '' is empty", async (): Promise => { + // Arrange + when(gitInvoker.getDiffSummary()).thenResolve(""); + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // Act + const result: string[] = + await codeMetrics.getDeletedFilesNotRequiringReview(); + + // Assert + assert.deepEqual(result, []); + }); + + it("should throw when the file name in the Git diff summary '0' cannot be parsed", async (): Promise => { + // Arrange + when(gitInvoker.getDiffSummary()).thenResolve("0"); + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + codeMetrics.getDeletedFilesNotRequiringReview(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + "The number of elements '1' in '0' in input '0' did not match the expected 3.", + ); + }); + + it("should throw when the lines added in the Git diff summary cannot be converted", async (): Promise => { + // Arrange + when(gitInvoker.getDiffSummary()).thenResolve("A\t0\tfile.ts"); + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + codeMetrics.getDeletedFilesNotRequiringReview(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + "Could not parse added lines 'A' from line 'A\t0\tfile.ts'.", + ); + }); + + it("should throw when the lines deleted in the Git diff summary cannot be converted", async (): Promise => { + // Arrange + when(gitInvoker.getDiffSummary()).thenResolve("0\tA\tfile.ts"); + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + codeMetrics.getDeletedFilesNotRequiringReview(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + "Could not parse deleted lines 'A' from line '0\tA\tfile.ts'.", + ); + }); + }); +}); diff --git a/src/task/tests/metrics/codeMetrics.getFilesNotRequiringReview.spec.ts b/src/task/tests/metrics/codeMetrics.getFilesNotRequiringReview.spec.ts new file mode 100644 index 000000000..b3b72e3b7 --- /dev/null +++ b/src/task/tests/metrics/codeMetrics.getFilesNotRequiringReview.spec.ts @@ -0,0 +1,155 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as AssertExtensions from "../testUtilities/assertExtensions.js"; +import { instance, when } from "ts-mockito"; +import CodeMetrics from "../../src/metrics/codeMetrics.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import Inputs from "../../src/metrics/inputs.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import assert from "node:assert/strict"; +import { createCodeMetricsMocks } from "./codeMetricsTestSetup.js"; + +describe("codeMetrics.ts", (): void => { + let gitInvoker: GitInvoker; + let inputs: Inputs; + let logger: Logger; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ + gitInvoker, + inputs, + logger, + runnerInvoker, + } = createCodeMetricsMocks()); + }); + + describe("getFilesNotRequiringReview()", (): void => { + { + const testCases: string[] = ["", " ", "\t", "\n", "\t\n"]; + + testCases.forEach((gitDiffSummary: string): void => { + it(`should return an empty array when the Git diff summary '${gitDiffSummary}' is empty`, async (): Promise => { + // Arrange + when(gitInvoker.getDiffSummary()).thenResolve(gitDiffSummary); + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // Act + const result: string[] = + await codeMetrics.getFilesNotRequiringReview(); + + // Assert + assert.deepEqual(result, []); + }); + }); + } + + { + interface TestCaseType { + elements: number; + summary: string; + } + + const testCases: TestCaseType[] = [ + { + elements: 1, + summary: "0", + }, + { + elements: 1, + summary: "0\t", + }, + { + elements: 2, + summary: "0\t0", + }, + { + elements: 2, + summary: "0\t0\t", + }, + { + elements: 2, + summary: "0\tfile.ts", + }, + { + elements: 2, + summary: "0\tfile.ts\t", + }, + ]; + + testCases.forEach(({ elements, summary }: TestCaseType): void => { + it(`should throw when the file name in the Git diff summary '${summary}' cannot be parsed`, async (): Promise => { + // Arrange + when(gitInvoker.getDiffSummary()).thenResolve(summary); + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + codeMetrics.getFilesNotRequiringReview(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + `The number of elements '${String(elements)}' in '${summary.trim()}' in input '${summary.trim()}' did not match the expected 3.`, + ); + }); + }); + } + + it("should throw when the lines added in the Git diff summary cannot be converted", async (): Promise => { + // Arrange + when(gitInvoker.getDiffSummary()).thenResolve("A\t0\tfile.ts"); + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + codeMetrics.getFilesNotRequiringReview(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + "Could not parse added lines 'A' from line 'A\t0\tfile.ts'.", + ); + }); + + it("should throw when the lines deleted in the Git diff summary cannot be converted", async (): Promise => { + // Arrange + when(gitInvoker.getDiffSummary()).thenResolve("0\tA\tfile.ts"); + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + codeMetrics.getFilesNotRequiringReview(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + "Could not parse deleted lines 'A' from line '0\tA\tfile.ts'.", + ); + }); + }); +}); diff --git a/src/task/tests/metrics/codeMetrics.nonDefaults.spec.ts b/src/task/tests/metrics/codeMetrics.nonDefaults.spec.ts new file mode 100644 index 000000000..de121786f --- /dev/null +++ b/src/task/tests/metrics/codeMetrics.nonDefaults.spec.ts @@ -0,0 +1,573 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import { instance, when } from "ts-mockito"; +import CodeMetrics from "../../src/metrics/codeMetrics.js"; +import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import Inputs from "../../src/metrics/inputs.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import assert from "node:assert/strict"; +import { createCodeMetricsMocks } from "./codeMetricsTestSetup.js"; + +describe("codeMetrics.ts", (): void => { + let gitInvoker: GitInvoker; + let inputs: Inputs; + let logger: Logger; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ + gitInvoker, + inputs, + logger, + runnerInvoker, + } = createCodeMetricsMocks()); + }); + + { + interface TestCaseType { + deletedFilesNotRequiringReview: string[]; + filesNotRequiringReview: string[]; + gitResponse: string; + metrics: CodeMetricsData; + sizeIndicator: string; + testCoverageIndicator: boolean; + } + + const testCases: TestCaseType[] = [ + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "0\t0\tfile.ts", + metrics: new CodeMetricsData(0, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "1\t0\tfile.ts", + metrics: new CodeMetricsData(1, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "1\t0\tfile.ts\n1\t0\ttest.ts", + metrics: new CodeMetricsData(1, 1, 0), + sizeIndicator: "XS", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "1\t0\tfile.ts\n2\t0\ttest.ts", + metrics: new CodeMetricsData(1, 2, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "99\t0\tfile.ts", + metrics: new CodeMetricsData(99, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "99\t0\tfile.ts\n197\t0\ttest.ts", + metrics: new CodeMetricsData(99, 197, 0), + sizeIndicator: "XS", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "99\t0\tfile.ts\n198\t0\ttest.ts", + metrics: new CodeMetricsData(99, 198, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "100\t0\tfile.ts", + metrics: new CodeMetricsData(100, 0, 0), + sizeIndicator: "S", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "100\t0\tfile.ts\n199\t0\ttest.ts", + metrics: new CodeMetricsData(100, 199, 0), + sizeIndicator: "S", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "100\t0\tfile.ts\n200\t0\ttest.ts", + metrics: new CodeMetricsData(100, 200, 0), + sizeIndicator: "S", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "149\t0\tfile.ts", + metrics: new CodeMetricsData(149, 0, 0), + sizeIndicator: "S", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "149\t0\tfile.ts\n297\t0\ttest.ts", + metrics: new CodeMetricsData(149, 297, 0), + sizeIndicator: "S", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "149\t0\tfile.ts\n298\t0\ttest.ts", + metrics: new CodeMetricsData(149, 298, 0), + sizeIndicator: "S", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "150\t0\tfile.ts", + metrics: new CodeMetricsData(150, 0, 0), + sizeIndicator: "M", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "150\t0\tfile.ts\n299\t0\ttest.ts", + metrics: new CodeMetricsData(150, 299, 0), + sizeIndicator: "M", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "150\t0\tfile.ts\n300\t0\ttest.ts", + metrics: new CodeMetricsData(150, 300, 0), + sizeIndicator: "M", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "224\t0\tfile.ts", + metrics: new CodeMetricsData(224, 0, 0), + sizeIndicator: "M", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "224\t0\tfile.ts\n447\t0\ttest.ts", + metrics: new CodeMetricsData(224, 447, 0), + sizeIndicator: "M", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "224\t0\tfile.ts\n448\t0\ttest.ts", + metrics: new CodeMetricsData(224, 448, 0), + sizeIndicator: "M", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "225\t0\tfile.ts", + metrics: new CodeMetricsData(225, 0, 0), + sizeIndicator: "L", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "225\t0\tfile.ts\n449\t0\ttest.ts", + metrics: new CodeMetricsData(225, 449, 0), + sizeIndicator: "L", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "225\t0\tfile.ts\n450\t0\ttest.ts", + metrics: new CodeMetricsData(225, 450, 0), + sizeIndicator: "L", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "337\t0\tfile.ts", + metrics: new CodeMetricsData(337, 0, 0), + sizeIndicator: "L", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "337\t0\tfile.ts\n673\t0\ttest.ts", + metrics: new CodeMetricsData(337, 673, 0), + sizeIndicator: "L", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "337\t0\tfile.ts\n674\t0\ttest.ts", + metrics: new CodeMetricsData(337, 674, 0), + sizeIndicator: "L", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "338\t0\tfile.ts", + metrics: new CodeMetricsData(338, 0, 0), + sizeIndicator: "XL", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "338\t0\tfile.ts\n675\t0\ttest.ts", + metrics: new CodeMetricsData(338, 675, 0), + sizeIndicator: "XL", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "338\t0\tfile.ts\n676\t0\ttest.ts", + metrics: new CodeMetricsData(338, 676, 0), + sizeIndicator: "XL", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "506\t0\tfile.ts", + metrics: new CodeMetricsData(506, 0, 0), + sizeIndicator: "XL", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "506\t0\tfile.ts\n1011\t0\ttest.ts", + metrics: new CodeMetricsData(506, 1011, 0), + sizeIndicator: "XL", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "506\t0\tfile.ts\n1012\t0\ttest.ts", + metrics: new CodeMetricsData(506, 1012, 0), + sizeIndicator: "XL", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "507\t0\tfile.ts", + metrics: new CodeMetricsData(507, 0, 0), + sizeIndicator: "2XL", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "507\t0\tfile.ts\n1013\t0\ttest.ts", + metrics: new CodeMetricsData(507, 1013, 0), + sizeIndicator: "2XL", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "507\t0\tfile.ts\n1014\t0\ttest.ts", + metrics: new CodeMetricsData(507, 1014, 0), + sizeIndicator: "2XL", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "759\t0\tfile.ts", + metrics: new CodeMetricsData(759, 0, 0), + sizeIndicator: "2XL", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "759\t0\tfile.ts\n1517\t0\ttest.ts", + metrics: new CodeMetricsData(759, 1517, 0), + sizeIndicator: "2XL", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "759\t0\tfile.ts\n1518\t0\ttest.ts", + metrics: new CodeMetricsData(759, 1518, 0), + sizeIndicator: "2XL", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "760\t0\tfile.ts", + metrics: new CodeMetricsData(760, 0, 0), + sizeIndicator: "3XL", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "760\t0\tfile.ts\n1519\t0\ttest.ts", + metrics: new CodeMetricsData(760, 1519, 0), + sizeIndicator: "3XL", + testCoverageIndicator: false, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "760\t0\tfile.ts\n1520\t0\ttest.ts", + metrics: new CodeMetricsData(760, 1520, 0), + sizeIndicator: "3XL", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "1\t0\tfile.cs", + metrics: new CodeMetricsData(0, 0, 1), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "1\t0\ttest.cs", + metrics: new CodeMetricsData(0, 0, 1), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "1\t0\tfile.tst", + metrics: new CodeMetricsData(0, 0, 1), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "1\t0\tfile.tts", + metrics: new CodeMetricsData(0, 0, 1), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: [], + gitResponse: "1\t0\tfilets", + metrics: new CodeMetricsData(0, 0, 1), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: ["ignored.ts"], + gitResponse: "1\t0\tignored.ts", + metrics: new CodeMetricsData(0, 0, 1), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: ["ignored.cs"], + gitResponse: "1\t0\tignored.cs", + metrics: new CodeMetricsData(0, 0, 1), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: ["folder/ignored.ts"], + gitResponse: "1\t0\tfolder/ignored.ts", + metrics: new CodeMetricsData(0, 0, 1), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: ["folder/ignored.cs"], + gitResponse: "1\t0\tfolder/ignored.cs", + metrics: new CodeMetricsData(0, 0, 1), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: ["ignored.ts"], + gitResponse: "0\t0\tignored.ts", + metrics: new CodeMetricsData(0, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: ["ignored.cs"], + gitResponse: "0\t0\tignored.cs", + metrics: new CodeMetricsData(0, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: ["folder/ignored.ts"], + gitResponse: "0\t0\tfolder/ignored.ts", + metrics: new CodeMetricsData(0, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: ["folder/ignored.cs"], + gitResponse: "0\t0\tfolder/ignored.cs", + metrics: new CodeMetricsData(0, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: [], + filesNotRequiringReview: ["ignored.ts", "folder/ignored.ts"], + gitResponse: "1\t0\tignored.ts\n0\t0\tfolder/ignored.ts", + metrics: new CodeMetricsData(0, 0, 1), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: ["ignored.ts"], + filesNotRequiringReview: [], + gitResponse: "0\t1\tignored.ts", + metrics: new CodeMetricsData(0, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: ["ignored.cs"], + filesNotRequiringReview: [], + gitResponse: "0\t1\tignored.cs", + metrics: new CodeMetricsData(0, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: ["folder/ignored.ts"], + filesNotRequiringReview: [], + gitResponse: "0\t1\tfolder/ignored.ts", + metrics: new CodeMetricsData(0, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: ["folder/ignored.cs"], + filesNotRequiringReview: [], + gitResponse: "0\t1\tfolder/ignored.cs", + metrics: new CodeMetricsData(0, 0, 0), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + { + deletedFilesNotRequiringReview: ["folder/ignored.ts"], + filesNotRequiringReview: ["ignored.ts"], + gitResponse: "1\t0\tignored.ts\n0\t1\tfolder/ignored.ts", + metrics: new CodeMetricsData(0, 0, 1), + sizeIndicator: "XS", + testCoverageIndicator: true, + }, + ]; + + testCases.forEach( + ({ + deletedFilesNotRequiringReview, + filesNotRequiringReview, + gitResponse, + metrics, + sizeIndicator, + testCoverageIndicator, + }: TestCaseType): void => { + it(`with non-default inputs and git diff '${gitResponse.replace(/\n/gu, "\\n")}', returns '${sizeIndicator}' size and '${String(testCoverageIndicator)}' test coverage`, async (): Promise => { + // Arrange + when(inputs.baseSize).thenReturn(100); + when(inputs.growthRate).thenReturn(1.5); + when(inputs.testFactor).thenReturn(2.0); + when(inputs.fileMatchingPatterns).thenReturn([ + "**/*", + "other.ts", + "!**/ignored.*", + ]); + when(inputs.codeFileExtensions).thenReturn(new Set(["ts"])); + when(gitInvoker.getDiffSummary()).thenResolve(gitResponse); + + // Act + const codeMetrics: CodeMetrics = new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual( + await codeMetrics.getFilesNotRequiringReview(), + filesNotRequiringReview, + ); + assert.deepEqual( + await codeMetrics.getDeletedFilesNotRequiringReview(), + deletedFilesNotRequiringReview, + ); + assert.equal(await codeMetrics.getSize(), sizeIndicator); + assert.equal( + await codeMetrics.getSizeIndicator(), + `${sizeIndicator}${testCoverageIndicator ? "✔" : "⚠️"}`, + ); + assert.deepEqual(await codeMetrics.getMetrics(), metrics); + assert.equal( + await codeMetrics.isSmall(), + sizeIndicator === "XS" || sizeIndicator === "S", + ); + assert.equal( + await codeMetrics.isSufficientlyTested(), + testCoverageIndicator, + ); + }); + }, + ); + } +}); diff --git a/src/task/tests/metrics/codeMetrics.spec.ts b/src/task/tests/metrics/codeMetrics.spec.ts deleted file mode 100644 index e60765d15..000000000 --- a/src/task/tests/metrics/codeMetrics.spec.ts +++ /dev/null @@ -1,1529 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT License. - */ - -import * as AssertExtensions from "../testUtilities/assertExtensions.js"; -import * as InputsDefault from "../../src/metrics/inputsDefault.js"; -import { anyString, instance, mock, when } from "ts-mockito"; -import CodeMetrics from "../../src/metrics/codeMetrics.js"; -import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; -import Inputs from "../../src/metrics/inputs.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; -import assert from "node:assert/strict"; -import { stubLocalization } from "../testUtilities/stubLocalization.js"; - -describe("codeMetrics.ts", (): void => { - let gitInvoker: GitInvoker; - let inputs: Inputs; - let logger: Logger; - let runnerInvoker: RunnerInvoker; - - beforeEach((): void => { - gitInvoker = mock(GitInvoker); - - inputs = mock(Inputs); - when(inputs.baseSize).thenReturn(InputsDefault.baseSize); - when(inputs.growthRate).thenReturn(InputsDefault.growthRate); - when(inputs.testFactor).thenReturn(InputsDefault.testFactor); - when(inputs.fileMatchingPatterns).thenReturn( - InputsDefault.fileMatchingPatterns, - ); - when(inputs.testMatchingPatterns).thenReturn( - InputsDefault.testMatchingPatterns, - ); - when(inputs.codeFileExtensions).thenReturn( - new Set(InputsDefault.codeFileExtensions), - ); - - logger = mock(Logger); - - runnerInvoker = mock(RunnerInvoker); - stubLocalization(runnerInvoker); - }); - - { - interface TestCaseType { - gitResponse: string; - metrics: CodeMetricsData; - sizeIndicator: string; - testCoverageIndicator: boolean; - } - - const testCases: TestCaseType[] = [ - { - gitResponse: "0\t0\tfile.ts", - metrics: new CodeMetricsData(0, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\tfile.ts", - metrics: new CodeMetricsData(1, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: false, - }, - { - gitResponse: "1\t0\tfile.ts\n1\t0\ttest.ts", - metrics: new CodeMetricsData(1, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "199\t0\tfile.ts", - metrics: new CodeMetricsData(199, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: false, - }, - { - gitResponse: "199\t0\tfile.ts\n198\t0\ttest.ts", - metrics: new CodeMetricsData(199, 198, 0), - sizeIndicator: "XS", - testCoverageIndicator: false, - }, - { - gitResponse: "199\t0\tfile.ts\n199\t0\ttest.ts", - metrics: new CodeMetricsData(199, 199, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "200\t0\tfile.ts", - metrics: new CodeMetricsData(200, 0, 0), - sizeIndicator: "S", - testCoverageIndicator: false, - }, - { - gitResponse: "200\t0\tfile.ts\n199\t0\ttest.ts", - metrics: new CodeMetricsData(200, 199, 0), - sizeIndicator: "S", - testCoverageIndicator: false, - }, - { - gitResponse: "200\t0\tfile.ts\n200\t0\ttest.ts", - metrics: new CodeMetricsData(200, 200, 0), - sizeIndicator: "S", - testCoverageIndicator: true, - }, - { - gitResponse: "399\t0\tfile.ts", - metrics: new CodeMetricsData(399, 0, 0), - sizeIndicator: "S", - testCoverageIndicator: false, - }, - { - gitResponse: "399\t0\tfile.ts\n398\t0\ttest.ts", - metrics: new CodeMetricsData(399, 398, 0), - sizeIndicator: "S", - testCoverageIndicator: false, - }, - { - gitResponse: "399\t0\tfile.ts\n399\t0\ttest.ts", - metrics: new CodeMetricsData(399, 399, 0), - sizeIndicator: "S", - testCoverageIndicator: true, - }, - { - gitResponse: "400\t0\tfile.ts", - metrics: new CodeMetricsData(400, 0, 0), - sizeIndicator: "M", - testCoverageIndicator: false, - }, - { - gitResponse: "400\t0\tfile.ts\n399\t0\ttest.ts", - metrics: new CodeMetricsData(400, 399, 0), - sizeIndicator: "M", - testCoverageIndicator: false, - }, - { - gitResponse: "400\t0\tfile.ts\n400\t0\ttest.ts", - metrics: new CodeMetricsData(400, 400, 0), - sizeIndicator: "M", - testCoverageIndicator: true, - }, - { - gitResponse: "799\t0\tfile.ts", - metrics: new CodeMetricsData(799, 0, 0), - sizeIndicator: "M", - testCoverageIndicator: false, - }, - { - gitResponse: "799\t0\tfile.ts\n798\t0\ttest.ts", - metrics: new CodeMetricsData(799, 798, 0), - sizeIndicator: "M", - testCoverageIndicator: false, - }, - { - gitResponse: "799\t0\tfile.ts\n799\t0\ttest.ts", - metrics: new CodeMetricsData(799, 799, 0), - sizeIndicator: "M", - testCoverageIndicator: true, - }, - { - gitResponse: "800\t0\tfile.ts", - metrics: new CodeMetricsData(800, 0, 0), - sizeIndicator: "L", - testCoverageIndicator: false, - }, - { - gitResponse: "800\t0\tfile.ts\n799\t0\ttest.ts", - metrics: new CodeMetricsData(800, 799, 0), - sizeIndicator: "L", - testCoverageIndicator: false, - }, - { - gitResponse: "800\t0\tfile.ts\n800\t0\ttest.ts", - metrics: new CodeMetricsData(800, 800, 0), - sizeIndicator: "L", - testCoverageIndicator: true, - }, - { - gitResponse: "1599\t0\tfile.ts", - metrics: new CodeMetricsData(1599, 0, 0), - sizeIndicator: "L", - testCoverageIndicator: false, - }, - { - gitResponse: "1599\t0\tfile.ts\n1598\t0\ttest.ts", - metrics: new CodeMetricsData(1599, 1598, 0), - sizeIndicator: "L", - testCoverageIndicator: false, - }, - { - gitResponse: "1599\t0\tfile.ts\n1599\t0\ttest.ts", - metrics: new CodeMetricsData(1599, 1599, 0), - sizeIndicator: "L", - testCoverageIndicator: true, - }, - { - gitResponse: "1600\t0\tfile.ts", - metrics: new CodeMetricsData(1600, 0, 0), - sizeIndicator: "XL", - testCoverageIndicator: false, - }, - { - gitResponse: "1600\t0\tfile.ts\n1599\t0\ttest.ts", - metrics: new CodeMetricsData(1600, 1599, 0), - sizeIndicator: "XL", - testCoverageIndicator: false, - }, - { - gitResponse: "1600\t0\tfile.ts\n1600\t0\ttest.ts", - metrics: new CodeMetricsData(1600, 1600, 0), - sizeIndicator: "XL", - testCoverageIndicator: true, - }, - { - gitResponse: "3199\t0\tfile.ts", - metrics: new CodeMetricsData(3199, 0, 0), - sizeIndicator: "XL", - testCoverageIndicator: false, - }, - { - gitResponse: "3199\t0\tfile.ts\n3198\t0\ttest.ts", - metrics: new CodeMetricsData(3199, 3198, 0), - sizeIndicator: "XL", - testCoverageIndicator: false, - }, - { - gitResponse: "3199\t0\tfile.ts\n3199\t0\ttest.ts", - metrics: new CodeMetricsData(3199, 3199, 0), - sizeIndicator: "XL", - testCoverageIndicator: true, - }, - { - gitResponse: "3200\t0\tfile.ts", - metrics: new CodeMetricsData(3200, 0, 0), - sizeIndicator: "2XL", - testCoverageIndicator: false, - }, - { - gitResponse: "3200\t0\tfile.ts\n3199\t0\ttest.ts", - metrics: new CodeMetricsData(3200, 3199, 0), - sizeIndicator: "2XL", - testCoverageIndicator: false, - }, - { - gitResponse: "3200\t0\tfile.ts\n3200\t0\ttest.ts", - metrics: new CodeMetricsData(3200, 3200, 0), - sizeIndicator: "2XL", - testCoverageIndicator: true, - }, - { - gitResponse: "6399\t0\tfile.ts", - metrics: new CodeMetricsData(6399, 0, 0), - sizeIndicator: "2XL", - testCoverageIndicator: false, - }, - { - gitResponse: "6399\t0\tfile.ts\n6398\t0\ttest.ts", - metrics: new CodeMetricsData(6399, 6398, 0), - sizeIndicator: "2XL", - testCoverageIndicator: false, - }, - { - gitResponse: "6399\t0\tfile.ts\n6399\t0\ttest.ts", - metrics: new CodeMetricsData(6399, 6399, 0), - sizeIndicator: "2XL", - testCoverageIndicator: true, - }, - { - gitResponse: "6400\t0\tfile.ts", - metrics: new CodeMetricsData(6400, 0, 0), - sizeIndicator: "3XL", - testCoverageIndicator: false, - }, - { - gitResponse: "6400\t0\tfile.ts\n6399\t0\ttest.ts", - metrics: new CodeMetricsData(6400, 6399, 0), - sizeIndicator: "3XL", - testCoverageIndicator: false, - }, - { - gitResponse: "6400\t0\tfile.ts\n6400\t0\ttest.ts", - metrics: new CodeMetricsData(6400, 6400, 0), - sizeIndicator: "3XL", - testCoverageIndicator: true, - }, - { - gitResponse: "819200\t0\tfile.ts", - metrics: new CodeMetricsData(819200, 0, 0), - sizeIndicator: "10XL", - testCoverageIndicator: false, - }, - { - gitResponse: "819200\t0\tfile.ts\n819199\t0\ttest.ts", - metrics: new CodeMetricsData(819200, 819199, 0), - sizeIndicator: "10XL", - testCoverageIndicator: false, - }, - { - gitResponse: "819200\t0\tfile.ts\n819200\t0\ttest.ts", - metrics: new CodeMetricsData(819200, 819200, 0), - sizeIndicator: "10XL", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\tfile.TS", - metrics: new CodeMetricsData(1, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: false, - }, - { - gitResponse: "0\t1\tfile.ts", - metrics: new CodeMetricsData(0, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\tfile.ignored", - metrics: new CodeMetricsData(0, 0, 1), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\tfile", - metrics: new CodeMetricsData(0, 0, 1), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\tfile.ts.ignored", - metrics: new CodeMetricsData(0, 0, 1), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\tfile.ignored.ts", - metrics: new CodeMetricsData(1, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: false, - }, - { - gitResponse: "1\t0\ttest.ignored", - metrics: new CodeMetricsData(0, 0, 1), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\ttasb.cc => test.ts", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\tt{a => e}s{b => t}.t{c => s}", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\tt{a => est.ts}", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\t{a => test.ts}", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\tfolder/test.ts", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\tfolder/Test.ts", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\tfolder/TEST.ts", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\tfolder/DuplicateStorage.ts", - metrics: new CodeMetricsData(1, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: false, - }, - { - gitResponse: "1\t0\tfolder/file.spec.ts", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\tfolder/file.Spec.ts", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\tfolder.spec.ts/file.ts", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\ttest/file.ts", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\ttests/file.ts", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\ttests/file.spec.ts", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\ttests/file.SPEC.ts", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t0\tfolder/tests/file.ts", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t1\tfa/b => folder/test.ts", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "1\t1\tf{a => older}/{b => test.ts}", - metrics: new CodeMetricsData(0, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "0\t0\tfile.ts\n", - metrics: new CodeMetricsData(0, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "-\t-\tfile.ts", - metrics: new CodeMetricsData(0, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - gitResponse: "0\t0\tfile.ts", - metrics: new CodeMetricsData(0, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - ]; - - testCases.forEach( - ({ - gitResponse, - metrics, - sizeIndicator, - testCoverageIndicator, - }: TestCaseType): void => { - it(`with default inputs and git diff '${gitResponse.replace(/\n/gu, "\\n").replace(/\r/gu, "\\r")}', returns '${sizeIndicator}' size and '${String(testCoverageIndicator)}' test coverage`, async (): Promise => { - // Arrange - when(gitInvoker.getDiffSummary()).thenResolve(gitResponse); - - // Act - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), []); - assert.deepEqual( - await codeMetrics.getDeletedFilesNotRequiringReview(), - [], - ); - assert.equal(await codeMetrics.getSize(), sizeIndicator); - assert.equal( - await codeMetrics.getSizeIndicator(), - `${sizeIndicator}${testCoverageIndicator ? "✔" : "⚠️"}`, - ); - assert.deepEqual(await codeMetrics.getMetrics(), metrics); - assert.equal( - await codeMetrics.isSmall(), - sizeIndicator === "XS" || sizeIndicator === "S", - ); - assert.equal( - await codeMetrics.isSufficientlyTested(), - testCoverageIndicator, - ); - }); - }, - ); - } - - { - interface TestCaseType { - deletedFilesNotRequiringReview: string[]; - filesNotRequiringReview: string[]; - gitResponse: string; - metrics: CodeMetricsData; - sizeIndicator: string; - testCoverageIndicator: boolean; - } - - const testCases: TestCaseType[] = [ - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "0\t0\tfile.ts", - metrics: new CodeMetricsData(0, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "1\t0\tfile.ts", - metrics: new CodeMetricsData(1, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "1\t0\tfile.ts\n1\t0\ttest.ts", - metrics: new CodeMetricsData(1, 1, 0), - sizeIndicator: "XS", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "1\t0\tfile.ts\n2\t0\ttest.ts", - metrics: new CodeMetricsData(1, 2, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "99\t0\tfile.ts", - metrics: new CodeMetricsData(99, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "99\t0\tfile.ts\n197\t0\ttest.ts", - metrics: new CodeMetricsData(99, 197, 0), - sizeIndicator: "XS", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "99\t0\tfile.ts\n198\t0\ttest.ts", - metrics: new CodeMetricsData(99, 198, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "100\t0\tfile.ts", - metrics: new CodeMetricsData(100, 0, 0), - sizeIndicator: "S", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "100\t0\tfile.ts\n199\t0\ttest.ts", - metrics: new CodeMetricsData(100, 199, 0), - sizeIndicator: "S", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "100\t0\tfile.ts\n200\t0\ttest.ts", - metrics: new CodeMetricsData(100, 200, 0), - sizeIndicator: "S", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "149\t0\tfile.ts", - metrics: new CodeMetricsData(149, 0, 0), - sizeIndicator: "S", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "149\t0\tfile.ts\n297\t0\ttest.ts", - metrics: new CodeMetricsData(149, 297, 0), - sizeIndicator: "S", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "149\t0\tfile.ts\n298\t0\ttest.ts", - metrics: new CodeMetricsData(149, 298, 0), - sizeIndicator: "S", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "150\t0\tfile.ts", - metrics: new CodeMetricsData(150, 0, 0), - sizeIndicator: "M", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "150\t0\tfile.ts\n299\t0\ttest.ts", - metrics: new CodeMetricsData(150, 299, 0), - sizeIndicator: "M", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "150\t0\tfile.ts\n300\t0\ttest.ts", - metrics: new CodeMetricsData(150, 300, 0), - sizeIndicator: "M", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "224\t0\tfile.ts", - metrics: new CodeMetricsData(224, 0, 0), - sizeIndicator: "M", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "224\t0\tfile.ts\n447\t0\ttest.ts", - metrics: new CodeMetricsData(224, 447, 0), - sizeIndicator: "M", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "224\t0\tfile.ts\n448\t0\ttest.ts", - metrics: new CodeMetricsData(224, 448, 0), - sizeIndicator: "M", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "225\t0\tfile.ts", - metrics: new CodeMetricsData(225, 0, 0), - sizeIndicator: "L", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "225\t0\tfile.ts\n449\t0\ttest.ts", - metrics: new CodeMetricsData(225, 449, 0), - sizeIndicator: "L", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "225\t0\tfile.ts\n450\t0\ttest.ts", - metrics: new CodeMetricsData(225, 450, 0), - sizeIndicator: "L", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "337\t0\tfile.ts", - metrics: new CodeMetricsData(337, 0, 0), - sizeIndicator: "L", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "337\t0\tfile.ts\n673\t0\ttest.ts", - metrics: new CodeMetricsData(337, 673, 0), - sizeIndicator: "L", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "337\t0\tfile.ts\n674\t0\ttest.ts", - metrics: new CodeMetricsData(337, 674, 0), - sizeIndicator: "L", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "338\t0\tfile.ts", - metrics: new CodeMetricsData(338, 0, 0), - sizeIndicator: "XL", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "338\t0\tfile.ts\n675\t0\ttest.ts", - metrics: new CodeMetricsData(338, 675, 0), - sizeIndicator: "XL", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "338\t0\tfile.ts\n676\t0\ttest.ts", - metrics: new CodeMetricsData(338, 676, 0), - sizeIndicator: "XL", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "506\t0\tfile.ts", - metrics: new CodeMetricsData(506, 0, 0), - sizeIndicator: "XL", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "506\t0\tfile.ts\n1011\t0\ttest.ts", - metrics: new CodeMetricsData(506, 1011, 0), - sizeIndicator: "XL", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "506\t0\tfile.ts\n1012\t0\ttest.ts", - metrics: new CodeMetricsData(506, 1012, 0), - sizeIndicator: "XL", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "507\t0\tfile.ts", - metrics: new CodeMetricsData(507, 0, 0), - sizeIndicator: "2XL", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "507\t0\tfile.ts\n1013\t0\ttest.ts", - metrics: new CodeMetricsData(507, 1013, 0), - sizeIndicator: "2XL", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "507\t0\tfile.ts\n1014\t0\ttest.ts", - metrics: new CodeMetricsData(507, 1014, 0), - sizeIndicator: "2XL", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "759\t0\tfile.ts", - metrics: new CodeMetricsData(759, 0, 0), - sizeIndicator: "2XL", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "759\t0\tfile.ts\n1517\t0\ttest.ts", - metrics: new CodeMetricsData(759, 1517, 0), - sizeIndicator: "2XL", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "759\t0\tfile.ts\n1518\t0\ttest.ts", - metrics: new CodeMetricsData(759, 1518, 0), - sizeIndicator: "2XL", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "760\t0\tfile.ts", - metrics: new CodeMetricsData(760, 0, 0), - sizeIndicator: "3XL", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "760\t0\tfile.ts\n1519\t0\ttest.ts", - metrics: new CodeMetricsData(760, 1519, 0), - sizeIndicator: "3XL", - testCoverageIndicator: false, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "760\t0\tfile.ts\n1520\t0\ttest.ts", - metrics: new CodeMetricsData(760, 1520, 0), - sizeIndicator: "3XL", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "1\t0\tfile.cs", - metrics: new CodeMetricsData(0, 0, 1), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "1\t0\ttest.cs", - metrics: new CodeMetricsData(0, 0, 1), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "1\t0\tfile.tst", - metrics: new CodeMetricsData(0, 0, 1), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "1\t0\tfile.tts", - metrics: new CodeMetricsData(0, 0, 1), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: [], - gitResponse: "1\t0\tfilets", - metrics: new CodeMetricsData(0, 0, 1), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: ["ignored.ts"], - gitResponse: "1\t0\tignored.ts", - metrics: new CodeMetricsData(0, 0, 1), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: ["ignored.cs"], - gitResponse: "1\t0\tignored.cs", - metrics: new CodeMetricsData(0, 0, 1), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: ["folder/ignored.ts"], - gitResponse: "1\t0\tfolder/ignored.ts", - metrics: new CodeMetricsData(0, 0, 1), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: ["folder/ignored.cs"], - gitResponse: "1\t0\tfolder/ignored.cs", - metrics: new CodeMetricsData(0, 0, 1), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: ["ignored.ts"], - gitResponse: "0\t0\tignored.ts", - metrics: new CodeMetricsData(0, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: ["ignored.cs"], - gitResponse: "0\t0\tignored.cs", - metrics: new CodeMetricsData(0, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: ["folder/ignored.ts"], - gitResponse: "0\t0\tfolder/ignored.ts", - metrics: new CodeMetricsData(0, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: ["folder/ignored.cs"], - gitResponse: "0\t0\tfolder/ignored.cs", - metrics: new CodeMetricsData(0, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: [], - filesNotRequiringReview: ["ignored.ts", "folder/ignored.ts"], - gitResponse: "1\t0\tignored.ts\n0\t0\tfolder/ignored.ts", - metrics: new CodeMetricsData(0, 0, 1), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: ["ignored.ts"], - filesNotRequiringReview: [], - gitResponse: "0\t1\tignored.ts", - metrics: new CodeMetricsData(0, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: ["ignored.cs"], - filesNotRequiringReview: [], - gitResponse: "0\t1\tignored.cs", - metrics: new CodeMetricsData(0, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: ["folder/ignored.ts"], - filesNotRequiringReview: [], - gitResponse: "0\t1\tfolder/ignored.ts", - metrics: new CodeMetricsData(0, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: ["folder/ignored.cs"], - filesNotRequiringReview: [], - gitResponse: "0\t1\tfolder/ignored.cs", - metrics: new CodeMetricsData(0, 0, 0), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - { - deletedFilesNotRequiringReview: ["folder/ignored.ts"], - filesNotRequiringReview: ["ignored.ts"], - gitResponse: "1\t0\tignored.ts\n0\t1\tfolder/ignored.ts", - metrics: new CodeMetricsData(0, 0, 1), - sizeIndicator: "XS", - testCoverageIndicator: true, - }, - ]; - - testCases.forEach( - ({ - deletedFilesNotRequiringReview, - filesNotRequiringReview, - gitResponse, - metrics, - sizeIndicator, - testCoverageIndicator, - }: TestCaseType): void => { - it(`with non-default inputs and git diff '${gitResponse.replace(/\n/gu, "\\n")}', returns '${sizeIndicator}' size and '${String(testCoverageIndicator)}' test coverage`, async (): Promise => { - // Arrange - when(inputs.baseSize).thenReturn(100); - when(inputs.growthRate).thenReturn(1.5); - when(inputs.testFactor).thenReturn(2.0); - when(inputs.fileMatchingPatterns).thenReturn([ - "**/*", - "other.ts", - "!**/ignored.*", - ]); - when(inputs.codeFileExtensions).thenReturn(new Set(["ts"])); - when(gitInvoker.getDiffSummary()).thenResolve(gitResponse); - - // Act - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual( - await codeMetrics.getFilesNotRequiringReview(), - filesNotRequiringReview, - ); - assert.deepEqual( - await codeMetrics.getDeletedFilesNotRequiringReview(), - deletedFilesNotRequiringReview, - ); - assert.equal(await codeMetrics.getSize(), sizeIndicator); - assert.equal( - await codeMetrics.getSizeIndicator(), - `${sizeIndicator}${testCoverageIndicator ? "✔" : "⚠️"}`, - ); - assert.deepEqual(await codeMetrics.getMetrics(), metrics); - assert.equal( - await codeMetrics.isSmall(), - sizeIndicator === "XS" || sizeIndicator === "S", - ); - assert.equal( - await codeMetrics.isSufficientlyTested(), - testCoverageIndicator, - ); - }); - }, - ); - } - - { - interface TestCaseType { - gitResponse: string; - } - - const testCases: TestCaseType[] = [ - { - gitResponse: - "2\t2\tfile.ts\n1\t1\tignored1.ts\n1\t1\tacceptance.ts\n1\t1\tignored2.ts", - }, - { - gitResponse: - "1\t1\tfile1.ts\n1\t1\tignored1.ts\n1\t1\tignored2.ts\n1\t1\tacceptance.ts\n1\t1\tfile2.ts", - }, - { - gitResponse: - "1\t1\tfile1.ts\n1\t1\tignored1.ts\n1\t1\tfile2.ts\n1\t1\tacceptance.ts\n1\t1\tignored2.ts", - }, - ]; - - testCases.forEach(({ gitResponse }: TestCaseType): void => { - it(`with multiple ignore patterns and git diff '${gitResponse.replace(/\n/gu, "\\n").replace(/\r/gu, "\\r")}' ignores the appropriate files`, async (): Promise => { - // Arrange - when(inputs.baseSize).thenReturn(100); - when(inputs.growthRate).thenReturn(1.5); - when(inputs.testFactor).thenReturn(2.0); - when(inputs.fileMatchingPatterns).thenReturn([ - "**/*", - "!**/ignored1.ts", - "!**/ignored2.ts", - ]); - when(inputs.testMatchingPatterns).thenReturn(["**/acceptance.ts"]); - when(inputs.codeFileExtensions).thenReturn(new Set(["ts"])); - when(gitInvoker.getDiffSummary()).thenResolve(gitResponse); - - // Act - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), [ - "ignored1.ts", - "ignored2.ts", - ]); - assert.deepEqual( - await codeMetrics.getDeletedFilesNotRequiringReview(), - [], - ); - assert.equal(await codeMetrics.getSize(), "XS"); - assert.equal(await codeMetrics.getSizeIndicator(), "XS⚠️"); - assert.deepEqual( - await codeMetrics.getMetrics(), - new CodeMetricsData(2, 1, 2), - ); - assert.equal(await codeMetrics.isSmall(), true); - assert.equal(await codeMetrics.isSufficientlyTested(), false); - }); - }); - } - - it("with custom include pattern, includes the relevant files", async (): Promise => { - // Arrange - when(inputs.baseSize).thenReturn(100); - when(inputs.growthRate).thenReturn(1.5); - when(inputs.testFactor).thenReturn(2.0); - when(inputs.fileMatchingPatterns).thenReturn(["src/*.ts", "__test__/*.ts"]); - when(inputs.codeFileExtensions).thenReturn(new Set(["ts"])); - when(gitInvoker.getDiffSummary()).thenResolve( - "1\t1\tfile.ts\n1\t1\tsrc/file.ts\n1\t1\t__test__/file.test.ts", - ); - - // Act - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), [ - "file.ts", - ]); - assert.deepEqual(await codeMetrics.getDeletedFilesNotRequiringReview(), []); - assert.equal(await codeMetrics.getSize(), "XS"); - assert.equal(await codeMetrics.getSizeIndicator(), "XS⚠️"); - assert.deepEqual( - await codeMetrics.getMetrics(), - new CodeMetricsData(1, 1, 1), - ); - assert.equal(await codeMetrics.isSmall(), true); - assert.equal(await codeMetrics.isSufficientlyTested(), false); - }); - - it("with only negative patterns, treats all files as non-matching", async (): Promise => { - // Arrange - when(inputs.baseSize).thenReturn(100); - when(inputs.growthRate).thenReturn(1.5); - when(inputs.testFactor).thenReturn(2.0); - when(inputs.fileMatchingPatterns).thenReturn(["!**/ignored.ts"]); - when(inputs.codeFileExtensions).thenReturn(new Set(["ts"])); - when(gitInvoker.getDiffSummary()).thenResolve( - "1\t1\tfile.ts\n1\t1\tignored.ts", - ); - - // Act - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), [ - "file.ts", - "ignored.ts", - ]); - assert.deepEqual(await codeMetrics.getDeletedFilesNotRequiringReview(), []); - assert.equal(await codeMetrics.getSize(), "XS"); - assert.equal(await codeMetrics.getSizeIndicator(), "XS✔"); - assert.deepEqual( - await codeMetrics.getMetrics(), - new CodeMetricsData(0, 0, 2), - ); - assert.equal(await codeMetrics.isSmall(), true); - assert.equal(await codeMetrics.isSufficientlyTested(), true); - }); - - it("with double exclusion ignore patterns ignores the appropriate files", async (): Promise => { - // Arrange - when(inputs.baseSize).thenReturn(100); - when(inputs.growthRate).thenReturn(1.5); - when(inputs.testFactor).thenReturn(2.0); - when(inputs.fileMatchingPatterns).thenReturn([ - "**/*", - "!**/ignored*.ts", - "!!**/ignored2.ts", - ]); - when(inputs.codeFileExtensions).thenReturn(new Set(["ts"])); - when(gitInvoker.getDiffSummary()).thenResolve( - "1\t1\tfile.ts\n1\t1\tignored1.ts\n1\t1\tignored2.ts\n1\t1\tignored3.ts", - ); - - // Act - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), [ - "ignored1.ts", - "ignored3.ts", - ]); - assert.deepEqual(await codeMetrics.getDeletedFilesNotRequiringReview(), []); - assert.equal(await codeMetrics.getSize(), "XS"); - assert.equal(await codeMetrics.getSizeIndicator(), "XS⚠️"); - assert.deepEqual( - await codeMetrics.getMetrics(), - new CodeMetricsData(2, 0, 2), - ); - assert.equal(await codeMetrics.isSmall(), true); - assert.equal(await codeMetrics.isSufficientlyTested(), false); - }); - - it("with all files matching test files, returns the appropriate results", async (): Promise => { - // Arrange - when(inputs.testMatchingPatterns).thenReturn(["**", "*/**"]); - when(gitInvoker.getDiffSummary()).thenResolve( - "1\t0\tfile.ts\n1\t0\ttest.ts\n1\t0\tfolder/file.ts", - ); - - // Act - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), []); - assert.deepEqual(await codeMetrics.getDeletedFilesNotRequiringReview(), []); - assert.equal(await codeMetrics.getSize(), "XS"); - assert.equal(await codeMetrics.getSizeIndicator(), "XS✔"); - assert.deepEqual( - await codeMetrics.getMetrics(), - new CodeMetricsData(0, 3, 0), - ); - assert.equal(await codeMetrics.isSmall(), true); - assert.equal(await codeMetrics.isSufficientlyTested(), true); - }); - - it("should return the expected result with test coverage disabled", async (): Promise => { - // Arrange - when(inputs.testFactor).thenReturn(null); - when(gitInvoker.getDiffSummary()).thenResolve("1\t0\tfile.ts"); - - // Act - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), []); - assert.deepEqual(await codeMetrics.getDeletedFilesNotRequiringReview(), []); - assert.equal(await codeMetrics.getSize(), "XS"); - assert.equal(await codeMetrics.getSizeIndicator(), "XS"); - assert.deepEqual( - await codeMetrics.getMetrics(), - new CodeMetricsData(1, 0, 0), - ); - assert.equal(await codeMetrics.isSmall(), true); - assert.equal(await codeMetrics.isSufficientlyTested(), null); - }); - - describe("getFilesNotRequiringReview()", (): void => { - { - const testCases: string[] = ["", " ", "\t", "\n", "\t\n"]; - - testCases.forEach((gitDiffSummary: string): void => { - it(`should return an empty array when the Git diff summary '${gitDiffSummary}' is empty`, async (): Promise => { - // Arrange - when(gitInvoker.getDiffSummary()).thenResolve(gitDiffSummary); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // Act - const result: string[] = - await codeMetrics.getFilesNotRequiringReview(); - - // Assert - assert.deepEqual(result, []); - }); - }); - } - - { - interface TestCaseType { - elements: number; - summary: string; - } - - const testCases: TestCaseType[] = [ - { - elements: 1, - summary: "0", - }, - { - elements: 1, - summary: "0\t", - }, - { - elements: 2, - summary: "0\t0", - }, - { - elements: 2, - summary: "0\t0\t", - }, - { - elements: 2, - summary: "0\tfile.ts", - }, - { - elements: 2, - summary: "0\tfile.ts\t", - }, - ]; - - testCases.forEach(({ elements, summary }: TestCaseType): void => { - it(`should throw when the file name in the Git diff summary '${summary}' cannot be parsed`, async (): Promise => { - // Arrange - when(gitInvoker.getDiffSummary()).thenResolve(summary); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - codeMetrics.getFilesNotRequiringReview(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - `The number of elements '${String(elements)}' in '${summary.trim()}' in input '${summary.trim()}' did not match the expected 3.`, - ); - }); - }); - } - - it("should throw when the lines added in the Git diff summary cannot be converted", async (): Promise => { - // Arrange - when(gitInvoker.getDiffSummary()).thenResolve("A\t0\tfile.ts"); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - codeMetrics.getFilesNotRequiringReview(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - "Could not parse added lines 'A' from line 'A\t0\tfile.ts'.", - ); - }); - - it("should throw when the lines deleted in the Git diff summary cannot be converted", async (): Promise => { - // Arrange - when(gitInvoker.getDiffSummary()).thenResolve("0\tA\tfile.ts"); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - codeMetrics.getFilesNotRequiringReview(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - "Could not parse deleted lines 'A' from line '0\tA\tfile.ts'.", - ); - }); - }); - - describe("getDeletedFilesNotRequiringReview()", (): void => { - it("should return an empty array when the Git diff summary '' is empty", async (): Promise => { - // Arrange - when(gitInvoker.getDiffSummary()).thenResolve(""); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // Act - const result: string[] = - await codeMetrics.getDeletedFilesNotRequiringReview(); - - // Assert - assert.deepEqual(result, []); - }); - - it("should throw when the file name in the Git diff summary '0' cannot be parsed", async (): Promise => { - // Arrange - when(gitInvoker.getDiffSummary()).thenResolve("0"); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - codeMetrics.getDeletedFilesNotRequiringReview(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - "The number of elements '1' in '0' in input '0' did not match the expected 3.", - ); - }); - - it("should throw when the lines added in the Git diff summary cannot be converted", async (): Promise => { - // Arrange - when(gitInvoker.getDiffSummary()).thenResolve("A\t0\tfile.ts"); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - codeMetrics.getDeletedFilesNotRequiringReview(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - "Could not parse added lines 'A' from line 'A\t0\tfile.ts'.", - ); - }); - - it("should throw when the lines deleted in the Git diff summary cannot be converted", async (): Promise => { - // Arrange - when(gitInvoker.getDiffSummary()).thenResolve("0\tA\tfile.ts"); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - codeMetrics.getDeletedFilesNotRequiringReview(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - "Could not parse deleted lines 'A' from line '0\tA\tfile.ts'.", - ); - }); - }); - - it("with a size multiplier exceeding 1000, returns a size without thousands separators", async (): Promise => { - // ARRANGE - when(inputs.baseSize).thenReturn(1); - when(inputs.growthRate).thenReturn(1.001); - when(inputs.testFactor).thenReturn(1.0); - when(inputs.fileMatchingPatterns).thenReturn(["**/*"]); - when(inputs.codeFileExtensions).thenReturn(new Set(["ts"])); - when(gitInvoker.getDiffSummary()).thenResolve("3\t0\tfile.ts"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - anyString() as string, - anyString() as string, - ), - ).thenReturn(""); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); - - // ACT - const size: string = await codeMetrics.getSize(); - - // ASSERT - assert.match(size, /^\d+XL$/u); - const multiplier: number = Number.parseInt(size.replace("XL", ""), 10); - assert.ok(multiplier >= 1000); - }); -}); diff --git a/src/task/tests/metrics/codeMetricsTestSetup.ts b/src/task/tests/metrics/codeMetricsTestSetup.ts new file mode 100644 index 000000000..8f989ec3f --- /dev/null +++ b/src/task/tests/metrics/codeMetricsTestSetup.ts @@ -0,0 +1,50 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as InputsDefault from "../../src/metrics/inputsDefault.js"; +import { mock, when } from "ts-mockito"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import Inputs from "../../src/metrics/inputs.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import { stubLocalization } from "../testUtilities/stubLocalization.js"; + +export interface CodeMetricsMocks { + gitInvoker: GitInvoker; + inputs: Inputs; + logger: Logger; + runnerInvoker: RunnerInvoker; +} + +/** + * Creates the mocks required by `codeMetrics.ts` tests, pre-wired with the + * default `Inputs` values so that `CodeMetrics` construction succeeds. + * Individual tests can override any stub after calling this helper. + * @returns The paired mocks. + */ +export const createCodeMetricsMocks = (): CodeMetricsMocks => { + const gitInvoker: GitInvoker = mock(GitInvoker); + + const inputs: Inputs = mock(Inputs); + when(inputs.baseSize).thenReturn(InputsDefault.baseSize); + when(inputs.growthRate).thenReturn(InputsDefault.growthRate); + when(inputs.testFactor).thenReturn(InputsDefault.testFactor); + when(inputs.fileMatchingPatterns).thenReturn( + InputsDefault.fileMatchingPatterns, + ); + when(inputs.testMatchingPatterns).thenReturn( + InputsDefault.testMatchingPatterns, + ); + when(inputs.codeFileExtensions).thenReturn( + new Set(InputsDefault.codeFileExtensions), + ); + + const logger: Logger = mock(Logger); + + const runnerInvoker: RunnerInvoker = mock(RunnerInvoker); + stubLocalization(runnerInvoker); + + return { gitInvoker, inputs, logger, runnerInvoker }; +}; diff --git a/src/task/tests/metrics/inputs.allInputs.spec.ts b/src/task/tests/metrics/inputs.allInputs.spec.ts new file mode 100644 index 000000000..ba722550d --- /dev/null +++ b/src/task/tests/metrics/inputs.allInputs.spec.ts @@ -0,0 +1,127 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as InputsDefault from "../../src/metrics/inputsDefault.js"; +import { + adjustingAlwaysCloseComment, + adjustingBaseSizeResource, + adjustingCodeFileExtensionsResource, + adjustingFileMatchingPatternsResource, + adjustingGrowthRateResource, + adjustingTestFactorResource, + adjustingTestMatchingPatternsResource, + createInputsMocks, + settingAlwaysCloseComment, + settingBaseSizeResource, + settingCodeFileExtensionsResource, + settingFileMatchingPatternsResource, + settingGrowthRateResource, + settingTestFactorResource, + settingTestMatchingPatternsResource, +} from "./inputsTestSetup.js"; +import { deepEqual, instance, verify, when } from "ts-mockito"; +import Inputs from "../../src/metrics/inputs.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import assert from "node:assert/strict"; + +describe("inputs.ts", (): void => { + let logger: Logger; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ logger, runnerInvoker } = createInputsMocks()); + }); + + describe("initialize()", (): void => { + describe("all inputs", (): void => { + it("should set all default values when nothing is specified", (): void => { + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.equal(inputs.baseSize, InputsDefault.baseSize); + assert.equal(inputs.growthRate, InputsDefault.growthRate); + assert.equal(inputs.testFactor, InputsDefault.testFactor); + assert.equal( + inputs.alwaysCloseComment, + InputsDefault.alwaysCloseComment, + ); + assert.deepEqual( + inputs.fileMatchingPatterns, + InputsDefault.fileMatchingPatterns, + ); + assert.deepEqual( + inputs.testMatchingPatterns, + InputsDefault.testMatchingPatterns, + ); + assert.deepEqual( + inputs.codeFileExtensions, + new Set(InputsDefault.codeFileExtensions), + ); + verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); + verify(logger.logInfo(adjustingBaseSizeResource)).once(); + verify(logger.logInfo(adjustingGrowthRateResource)).once(); + verify(logger.logInfo(adjustingTestFactorResource)).once(); + verify(logger.logInfo(adjustingFileMatchingPatternsResource)).once(); + verify(logger.logInfo(adjustingTestMatchingPatternsResource)).once(); + verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); + }); + + it("should set all input values when all are specified", (): void => { + // Arrange + when(runnerInvoker.getInput(deepEqual(["Base", "Size"]))).thenReturn( + "5.0", + ); + when(runnerInvoker.getInput(deepEqual(["Growth", "Rate"]))).thenReturn( + "4.4", + ); + when(runnerInvoker.getInput(deepEqual(["Test", "Factor"]))).thenReturn( + "2.7", + ); + when( + runnerInvoker.getInput(deepEqual(["Always", "Close", "Comment"])), + ).thenReturn("true"); + when( + runnerInvoker.getInput(deepEqual(["File", "Matching", "Patterns"])), + ).thenReturn("aa\nbb"); + when( + runnerInvoker.getInput(deepEqual(["Test", "Matching", "Patterns"])), + ).thenReturn("cc\ndd"); + when( + runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), + ).thenReturn("js\nts"); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.equal(inputs.baseSize, 5.0); + assert.equal(inputs.growthRate, 4.4); + assert.equal(inputs.testFactor, 2.7); + assert.deepEqual(inputs.alwaysCloseComment, true); + assert.deepEqual(inputs.fileMatchingPatterns, ["aa", "bb"]); + assert.deepEqual(inputs.testMatchingPatterns, ["cc", "dd"]); + assert.deepEqual( + inputs.codeFileExtensions, + new Set(["js", "ts"]), + ); + verify(logger.logInfo(settingAlwaysCloseComment)).once(); + verify(logger.logInfo(settingBaseSizeResource)).once(); + verify(logger.logInfo(settingGrowthRateResource)).once(); + verify(logger.logInfo(settingTestFactorResource)).once(); + verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); + verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); + verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); + }); + }); + }); +}); diff --git a/src/task/tests/metrics/inputs.alwaysCloseComment.spec.ts b/src/task/tests/metrics/inputs.alwaysCloseComment.spec.ts new file mode 100644 index 000000000..894ebd969 --- /dev/null +++ b/src/task/tests/metrics/inputs.alwaysCloseComment.spec.ts @@ -0,0 +1,89 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as InputsDefault from "../../src/metrics/inputsDefault.js"; +import { + adjustingAlwaysCloseComment, + createInputsMocks, + settingAlwaysCloseComment, +} from "./inputsTestSetup.js"; +import { deepEqual, instance, verify, when } from "ts-mockito"; +import Inputs from "../../src/metrics/inputs.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import assert from "node:assert/strict"; + +describe("inputs.ts", (): void => { + let logger: Logger; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ logger, runnerInvoker } = createInputsMocks()); + }); + + describe("initialize()", (): void => { + describe("alwaysCloseComment", (): void => { + { + const testCases: (string | null)[] = [ + null, + "", + " ", + "abc", + "false", + "False", + "FALSE", + "fALSE", + "null", + "undefined", + ]; + + testCases.forEach((alwaysCloseComment: string | null): void => { + it(`should set the default when the input is '${String(alwaysCloseComment)}'`, (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Always", "Close", "Comment"])), + ).thenReturn(alwaysCloseComment); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.equal( + inputs.alwaysCloseComment, + InputsDefault.alwaysCloseComment, + ); + verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); + }); + }); + } + + { + const testCases: string[] = ["true", "True", "TRUE", "tRUE"]; + + testCases.forEach((alwaysCloseComment: string): void => { + it(`should set to true when the input is '${alwaysCloseComment}'`, (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Always", "Close", "Comment"])), + ).thenReturn(alwaysCloseComment); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.equal(inputs.alwaysCloseComment, true); + verify(logger.logInfo(settingAlwaysCloseComment)).once(); + }); + }); + } + }); + }); +}); diff --git a/src/task/tests/metrics/inputs.baseSize.spec.ts b/src/task/tests/metrics/inputs.baseSize.spec.ts new file mode 100644 index 000000000..15db17a39 --- /dev/null +++ b/src/task/tests/metrics/inputs.baseSize.spec.ts @@ -0,0 +1,108 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as InputsDefault from "../../src/metrics/inputsDefault.js"; +import { + adjustingBaseSizeResource, + createInputsMocks, + settingBaseSizeResource, +} from "./inputsTestSetup.js"; +import { deepEqual, instance, verify, when } from "ts-mockito"; +import Inputs from "../../src/metrics/inputs.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import assert from "node:assert/strict"; +import { decimalRadix } from "../../src/utilities/constants.js"; + +describe("inputs.ts", (): void => { + let logger: Logger; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ logger, runnerInvoker } = createInputsMocks()); + }); + + describe("initialize()", (): void => { + describe("baseSize", (): void => { + { + const testCases: (string | null)[] = [ + null, + "", + " ", + "abc", + "===", + "!2", + "null", + "undefined", + ]; + + testCases.forEach((baseSize: string | null): void => { + it(`should set the default when the input '${String(baseSize)}' is invalid`, (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Base", "Size"])), + ).thenReturn(baseSize); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.equal(inputs.baseSize, InputsDefault.baseSize); + verify(logger.logInfo(adjustingBaseSizeResource)).once(); + }); + }); + } + + { + const testCases: string[] = ["0", "-1", "-1000", "-5"]; + + testCases.forEach((baseSize: string): void => { + it(`should set the default when the input '${baseSize}' is less than or equal to 0`, (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Base", "Size"])), + ).thenReturn(baseSize); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.equal(inputs.baseSize, InputsDefault.baseSize); + verify(logger.logInfo(adjustingBaseSizeResource)).once(); + }); + }); + } + + { + const testCases: string[] = ["1", "5", "1000", "5.5"]; + + testCases.forEach((baseSize: string): void => { + it(`should set the converted value when the input '${baseSize}' is greater than 0`, (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Base", "Size"])), + ).thenReturn(baseSize); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.equal(inputs.baseSize, parseInt(baseSize, decimalRadix)); + verify(logger.logInfo(settingBaseSizeResource)).once(); + }); + }); + } + }); + }); +}); diff --git a/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts b/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts new file mode 100644 index 000000000..3943777d7 --- /dev/null +++ b/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts @@ -0,0 +1,182 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as InputsDefault from "../../src/metrics/inputsDefault.js"; +import { + adjustingCodeFileExtensionsResource, + createInputsMocks, + settingCodeFileExtensionsResource, +} from "./inputsTestSetup.js"; +import { deepEqual, instance, verify, when } from "ts-mockito"; +import Inputs from "../../src/metrics/inputs.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import assert from "node:assert/strict"; + +describe("inputs.ts", (): void => { + let logger: Logger; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ logger, runnerInvoker } = createInputsMocks()); + }); + + describe("initialize()", (): void => { + describe("codeFileExtensions", (): void => { + { + const testCases: (string | null)[] = [null, "", " ", " ", "\n"]; + + testCases.forEach((codeFileExtensions: string | null): void => { + it(`should set the default when the input '${String(codeFileExtensions?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), + ).thenReturn(codeFileExtensions); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual( + inputs.codeFileExtensions, + new Set(InputsDefault.codeFileExtensions), + ); + verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); + }); + }); + } + + { + const testCases: string[] = [ + "ada\njs\nts\nbb\ntxt", + "abc\ndef\nhij", + "ts", + ]; + + testCases.forEach((codeFileExtensions: string): void => { + it(`should split '${codeFileExtensions.replace(/\n/gu, "\\n")}' at the newline character`, (): void => { + // Arrange + const expectedResult: Set = new Set( + codeFileExtensions.split("\n"), + ); + when( + runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), + ).thenReturn(codeFileExtensions); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(inputs.codeFileExtensions, expectedResult); + verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); + }); + }); + } + + it("should handle repeated insertion of identical items", (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), + ).thenReturn("ada\nada"); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(inputs.codeFileExtensions, new Set(["ada"])); + verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); + }); + + it("should convert extensions to lower case", (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), + ).thenReturn("ADA\ncS\nTxT"); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual( + inputs.codeFileExtensions, + new Set(["ada", "cs", "txt"]), + ); + verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); + }); + + it("should remove . and * from extension names", (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), + ).thenReturn("*.ada\n.txt"); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual( + inputs.codeFileExtensions, + new Set(["ada", "txt"]), + ); + verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); + }); + + it("should convert extensions to lower case", (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), + ).thenReturn("ADA\ncS\nTxT"); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual( + inputs.codeFileExtensions, + new Set(["ada", "cs", "txt"]), + ); + verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); + }); + + it("should remove trailing new lines", (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), + ).thenReturn("ada\ncs\ntxt\n"); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual( + inputs.codeFileExtensions, + new Set(["ada", "cs", "txt"]), + ); + verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); + }); + }); + }); +}); diff --git a/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts b/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts new file mode 100644 index 000000000..d4514b9a9 --- /dev/null +++ b/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts @@ -0,0 +1,209 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as InputsDefault from "../../src/metrics/inputsDefault.js"; +import { + adjustingFileMatchingPatternsResource, + createInputsMocks, + settingFileMatchingPatternsResource, +} from "./inputsTestSetup.js"; +import { deepEqual, instance, verify, when } from "ts-mockito"; +import Inputs from "../../src/metrics/inputs.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import assert from "node:assert/strict"; + +describe("inputs.ts", (): void => { + let logger: Logger; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ logger, runnerInvoker } = createInputsMocks()); + }); + + describe("initialize()", (): void => { + describe("fileMatchingPatterns", (): void => { + { + const testCases: (string | null)[] = [null, "", " ", " ", "\n"]; + + testCases.forEach((fileMatchingPatterns: string | null): void => { + it(`should set the default when the input '${String(fileMatchingPatterns?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { + // Arrange + when( + runnerInvoker.getInput( + deepEqual(["File", "Matching", "Patterns"]), + ), + ).thenReturn(fileMatchingPatterns); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual( + inputs.fileMatchingPatterns, + InputsDefault.fileMatchingPatterns, + ); + verify( + logger.logInfo(adjustingFileMatchingPatternsResource), + ).once(); + }); + }); + } + + { + const testCases: string[] = [ + "abc", + "abc def hik", + "*.ada *.js *ts *.bb *txt", + ]; + + testCases.forEach((fileMatchingPatterns: string): void => { + it(`should not split '${fileMatchingPatterns}'`, (): void => { + // Arrange + when( + runnerInvoker.getInput( + deepEqual(["File", "Matching", "Patterns"]), + ), + ).thenReturn(fileMatchingPatterns); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(inputs.fileMatchingPatterns, [ + fileMatchingPatterns, + ]); + verify( + logger.logInfo(adjustingFileMatchingPatternsResource), + ).never(); + verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); + }); + }); + } + + { + const testCases: string[] = [ + "*.ada\n*.js\n*.ts\n*.bb\n*.txt", + "abc\ndef\nhij", + ]; + + testCases.forEach((fileMatchingPatterns: string): void => { + it(`should split '${fileMatchingPatterns.replace(/\n/gu, "\\n")}' at the newline character`, (): void => { + // Arrange + const expectedOutput: string[] = fileMatchingPatterns.split("\n"); + when( + runnerInvoker.getInput( + deepEqual(["File", "Matching", "Patterns"]), + ), + ).thenReturn(fileMatchingPatterns); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(inputs.fileMatchingPatterns, expectedOutput); + verify( + logger.logInfo(adjustingFileMatchingPatternsResource), + ).never(); + verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); + }); + }); + } + + it("should replace all '\\' with '/'", (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["File", "Matching", "Patterns"])), + ).thenReturn("folder1\\file.js\nfolder2\\*.js"); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(inputs.fileMatchingPatterns, [ + "folder1/file.js", + "folder2/*.js", + ]); + verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); + }); + + it("should remove trailing new lines", (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["File", "Matching", "Patterns"])), + ).thenReturn("file.js\n"); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(inputs.fileMatchingPatterns, ["file.js"]); + verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); + }); + + it("should trim whitespace and filter empty lines", (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["File", "Matching", "Patterns"])), + ).thenReturn(" pattern1 \n\n pattern2 \n \npattern3"); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(inputs.fileMatchingPatterns, [ + "pattern1", + "pattern2", + "pattern3", + ]); + }); + + it("should truncate patterns exceeding the maximum count", (): void => { + // Arrange + const patterns: string[] = Array.from( + { length: 250 }, + (_value: string, index: number) => `pattern${String(index)}`, + ); + when( + runnerInvoker.getInput(deepEqual(["File", "Matching", "Patterns"])), + ).thenReturn(patterns.join("\n")); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.equal(inputs.fileMatchingPatterns.length, 200); + assert.equal(inputs.fileMatchingPatterns[0], "pattern0"); + assert.equal(inputs.fileMatchingPatterns[199], "pattern199"); + verify( + logger.logWarning( + "The matching pattern count '250' exceeds the maximum '200'. Using only the first '200'.", + ), + ).once(); + }); + }); + }); +}); diff --git a/src/task/tests/metrics/inputs.growthRate.spec.ts b/src/task/tests/metrics/inputs.growthRate.spec.ts new file mode 100644 index 000000000..14e9f2c58 --- /dev/null +++ b/src/task/tests/metrics/inputs.growthRate.spec.ts @@ -0,0 +1,125 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as InputsDefault from "../../src/metrics/inputsDefault.js"; +import { + adjustingGrowthRateResource, + createInputsMocks, + settingGrowthRateResource, +} from "./inputsTestSetup.js"; +import { deepEqual, instance, verify, when } from "ts-mockito"; +import Inputs from "../../src/metrics/inputs.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import assert from "node:assert/strict"; + +describe("inputs.ts", (): void => { + let logger: Logger; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ logger, runnerInvoker } = createInputsMocks()); + }); + + describe("initialize()", (): void => { + describe("growthRate", (): void => { + { + const testCases: (string | null)[] = [ + null, + "", + " ", + "abc", + "===", + "!2", + "null", + "undefined", + "Infinity", + ]; + + testCases.forEach((growthRate: string | null): void => { + it(`should set the default when the input '${String(growthRate)}' is invalid`, (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Growth", "Rate"])), + ).thenReturn(growthRate); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.equal(inputs.growthRate, InputsDefault.growthRate); + verify(logger.logInfo(adjustingGrowthRateResource)).once(); + }); + }); + } + + { + const testCases: string[] = [ + "0", + "0.5", + "1", + "-2", + "-1.2", + "-5", + "0.9999999999", + ]; + + testCases.forEach((growthRate: string): void => { + it(`should set the default when the input '${growthRate}' is less than or equal to 1.0`, (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Growth", "Rate"])), + ).thenReturn(growthRate); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.equal(inputs.growthRate, InputsDefault.growthRate); + verify(logger.logInfo(adjustingGrowthRateResource)).once(); + }); + }); + } + + { + const testCases: string[] = [ + "5", + "2.0", + "1000", + "1.001", + "1.2", + "1.0000000001", + "1.09", + "7", + ]; + + testCases.forEach((growthRate: string): void => { + it(`should set the converted value when the input '${growthRate}' is greater than 1.0`, (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Growth", "Rate"])), + ).thenReturn(growthRate); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.equal(inputs.growthRate, parseFloat(growthRate)); + verify(logger.logInfo(settingGrowthRateResource)).once(); + }); + }); + } + }); + }); +}); diff --git a/src/task/tests/metrics/inputs.spec.ts b/src/task/tests/metrics/inputs.spec.ts deleted file mode 100644 index be878225a..000000000 --- a/src/task/tests/metrics/inputs.spec.ts +++ /dev/null @@ -1,1053 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT License. - */ - -import * as InputsDefault from "../../src/metrics/inputsDefault.js"; -import { deepEqual, instance, mock, verify, when } from "ts-mockito"; -import Inputs from "../../src/metrics/inputs.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; -import { anyString } from "../testUtilities/mockito.js"; -import assert from "node:assert/strict"; -import { decimalRadix } from "../../src/utilities/constants.js"; - -describe("inputs.ts", (): void => { - const adjustingAlwaysCloseComment = - "Adjusting the always-close-comment mode input to 'false'."; - const adjustingBaseSizeResource = `Adjusting the base size input to '${String(InputsDefault.baseSize)}'.`; - const adjustingGrowthRateResource = `Adjusting the growth rate input to '${String(InputsDefault.growthRate)}'.`; - const adjustingTestFactorResource = `Adjusting the test factor input to '${String(InputsDefault.testFactor)}'.`; - const adjustingFileMatchingPatternsResource = `Adjusting the file matching patterns input to '${JSON.stringify(InputsDefault.fileMatchingPatterns)}'.`; - const adjustingTestMatchingPatternsResource = `Adjusting the test matching patterns input to '${JSON.stringify(InputsDefault.testMatchingPatterns)}'.`; - const adjustingCodeFileExtensionsResource = `Adjusting the code file extensions input to '${JSON.stringify(InputsDefault.codeFileExtensions)}'.`; - const disablingTestFactorResource = "Disabling the test factor validation."; - const settingAlwaysCloseComment = - "Setting the always-close-comment mode input to 'true'."; - const settingBaseSizeResource = "Setting the base size input to 'VALUE'."; - const settingGrowthRateResource = "Setting the growth rate input to 'VALUE'."; - const settingTestFactorResource = "Setting the test factor input to 'VALUE'."; - const settingFileMatchingPatternsResource = - "Setting the file matching patterns input to 'VALUE'."; - const settingTestMatchingPatternsResource = - "Setting the test matching patterns input to 'VALUE'."; - const settingCodeFileExtensionsResource = - "Setting the code file extensions input to 'VALUE'."; - - let logger: Logger; - let runnerInvoker: RunnerInvoker; - - beforeEach((): void => { - logger = mock(Logger); - - runnerInvoker = mock(RunnerInvoker); - when(runnerInvoker.getInput(deepEqual(["Base", "Size"]))).thenReturn(""); - when(runnerInvoker.getInput(deepEqual(["Growth", "Rate"]))).thenReturn(""); - when(runnerInvoker.getInput(deepEqual(["Test", "Factor"]))).thenReturn(""); - when( - runnerInvoker.getInput(deepEqual(["Always", "Close", "Comment"])), - ).thenReturn(""); - when( - runnerInvoker.getInput(deepEqual(["File", "Matching", "Patterns"])), - ).thenReturn(""); - when( - runnerInvoker.getInput(deepEqual(["Test", "Matching", "Patterns"])), - ).thenReturn(""); - when( - runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), - ).thenReturn(""); - when( - runnerInvoker.loc("metrics.inputs.adjustingAlwaysCloseComment"), - ).thenReturn(adjustingAlwaysCloseComment); - when( - runnerInvoker.loc( - "metrics.inputs.adjustingBaseSize", - InputsDefault.baseSize.toLocaleString(), - ), - ).thenReturn(adjustingBaseSizeResource); - when( - runnerInvoker.loc( - "metrics.inputs.adjustingGrowthRate", - InputsDefault.growthRate.toLocaleString(), - ), - ).thenReturn(adjustingGrowthRateResource); - when( - runnerInvoker.loc( - "metrics.inputs.adjustingTestFactor", - InputsDefault.testFactor.toLocaleString(), - ), - ).thenReturn(adjustingTestFactorResource); - when( - runnerInvoker.loc( - "metrics.inputs.adjustingFileMatchingPatterns", - JSON.stringify(InputsDefault.fileMatchingPatterns), - ), - ).thenReturn(adjustingFileMatchingPatternsResource); - when( - runnerInvoker.loc( - "metrics.inputs.adjustingTestMatchingPatterns", - JSON.stringify(InputsDefault.testMatchingPatterns), - ), - ).thenReturn(adjustingTestMatchingPatternsResource); - when( - runnerInvoker.loc( - "metrics.inputs.adjustingCodeFileExtensions", - JSON.stringify(InputsDefault.codeFileExtensions), - ), - ).thenReturn(adjustingCodeFileExtensionsResource); - when(runnerInvoker.loc("metrics.inputs.disablingTestFactor")).thenReturn( - disablingTestFactorResource, - ); - when( - runnerInvoker.loc("metrics.inputs.settingAlwaysCloseComment"), - ).thenReturn(settingAlwaysCloseComment); - when( - runnerInvoker.loc("metrics.inputs.settingBaseSize", anyString()), - ).thenReturn(settingBaseSizeResource); - when( - runnerInvoker.loc("metrics.inputs.settingGrowthRate", anyString()), - ).thenReturn(settingGrowthRateResource); - when( - runnerInvoker.loc("metrics.inputs.settingTestFactor", anyString()), - ).thenReturn(settingTestFactorResource); - when( - runnerInvoker.loc( - "metrics.inputs.settingFileMatchingPatterns", - anyString(), - ), - ).thenReturn(settingFileMatchingPatternsResource); - when( - runnerInvoker.loc( - "metrics.inputs.settingTestMatchingPatterns", - anyString(), - ), - ).thenReturn(settingTestMatchingPatternsResource); - when( - runnerInvoker.loc( - "metrics.inputs.settingCodeFileExtensions", - anyString(), - ), - ).thenReturn(settingCodeFileExtensionsResource); - }); - - describe("initialize()", (): void => { - describe("all inputs", (): void => { - it("should set all default values when nothing is specified", (): void => { - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.equal(inputs.baseSize, InputsDefault.baseSize); - assert.equal(inputs.growthRate, InputsDefault.growthRate); - assert.equal(inputs.testFactor, InputsDefault.testFactor); - assert.equal( - inputs.alwaysCloseComment, - InputsDefault.alwaysCloseComment, - ); - assert.deepEqual( - inputs.fileMatchingPatterns, - InputsDefault.fileMatchingPatterns, - ); - assert.deepEqual( - inputs.testMatchingPatterns, - InputsDefault.testMatchingPatterns, - ); - assert.deepEqual( - inputs.codeFileExtensions, - new Set(InputsDefault.codeFileExtensions), - ); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - verify(logger.logInfo(adjustingFileMatchingPatternsResource)).once(); - verify(logger.logInfo(adjustingTestMatchingPatternsResource)).once(); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - }); - - it("should set all input values when all are specified", (): void => { - // Arrange - when(runnerInvoker.getInput(deepEqual(["Base", "Size"]))).thenReturn( - "5.0", - ); - when(runnerInvoker.getInput(deepEqual(["Growth", "Rate"]))).thenReturn( - "4.4", - ); - when(runnerInvoker.getInput(deepEqual(["Test", "Factor"]))).thenReturn( - "2.7", - ); - when( - runnerInvoker.getInput(deepEqual(["Always", "Close", "Comment"])), - ).thenReturn("true"); - when( - runnerInvoker.getInput(deepEqual(["File", "Matching", "Patterns"])), - ).thenReturn("aa\nbb"); - when( - runnerInvoker.getInput(deepEqual(["Test", "Matching", "Patterns"])), - ).thenReturn("cc\ndd"); - when( - runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), - ).thenReturn("js\nts"); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.equal(inputs.baseSize, 5.0); - assert.equal(inputs.growthRate, 4.4); - assert.equal(inputs.testFactor, 2.7); - assert.deepEqual(inputs.alwaysCloseComment, true); - assert.deepEqual(inputs.fileMatchingPatterns, ["aa", "bb"]); - assert.deepEqual(inputs.testMatchingPatterns, ["cc", "dd"]); - assert.deepEqual( - inputs.codeFileExtensions, - new Set(["js", "ts"]), - ); - verify(logger.logInfo(settingAlwaysCloseComment)).once(); - verify(logger.logInfo(settingBaseSizeResource)).once(); - verify(logger.logInfo(settingGrowthRateResource)).once(); - verify(logger.logInfo(settingTestFactorResource)).once(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); - }); - }); - - describe("baseSize", (): void => { - { - const testCases: (string | null)[] = [ - null, - "", - " ", - "abc", - "===", - "!2", - "null", - "undefined", - ]; - - testCases.forEach((baseSize: string | null): void => { - it(`should set the default when the input '${String(baseSize)}' is invalid`, (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Base", "Size"])), - ).thenReturn(baseSize); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.equal(inputs.baseSize, InputsDefault.baseSize); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - }); - }); - } - - { - const testCases: string[] = ["0", "-1", "-1000", "-5"]; - - testCases.forEach((baseSize: string): void => { - it(`should set the default when the input '${baseSize}' is less than or equal to 0`, (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Base", "Size"])), - ).thenReturn(baseSize); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.equal(inputs.baseSize, InputsDefault.baseSize); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - }); - }); - } - - { - const testCases: string[] = ["1", "5", "1000", "5.5"]; - - testCases.forEach((baseSize: string): void => { - it(`should set the converted value when the input '${baseSize}' is greater than 0`, (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Base", "Size"])), - ).thenReturn(baseSize); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.equal(inputs.baseSize, parseInt(baseSize, decimalRadix)); - verify(logger.logInfo(settingBaseSizeResource)).once(); - }); - }); - } - }); - - describe("growthRate", (): void => { - { - const testCases: (string | null)[] = [ - null, - "", - " ", - "abc", - "===", - "!2", - "null", - "undefined", - "Infinity", - ]; - - testCases.forEach((growthRate: string | null): void => { - it(`should set the default when the input '${String(growthRate)}' is invalid`, (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Growth", "Rate"])), - ).thenReturn(growthRate); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.equal(inputs.growthRate, InputsDefault.growthRate); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - }); - }); - } - - { - const testCases: string[] = [ - "0", - "0.5", - "1", - "-2", - "-1.2", - "-5", - "0.9999999999", - ]; - - testCases.forEach((growthRate: string): void => { - it(`should set the default when the input '${growthRate}' is less than or equal to 1.0`, (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Growth", "Rate"])), - ).thenReturn(growthRate); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.equal(inputs.growthRate, InputsDefault.growthRate); - verify(logger.logInfo(adjustingGrowthRateResource)).once(); - }); - }); - } - - { - const testCases: string[] = [ - "5", - "2.0", - "1000", - "1.001", - "1.2", - "1.0000000001", - "1.09", - "7", - ]; - - testCases.forEach((growthRate: string): void => { - it(`should set the converted value when the input '${growthRate}' is greater than 1.0`, (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Growth", "Rate"])), - ).thenReturn(growthRate); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.equal(inputs.growthRate, parseFloat(growthRate)); - verify(logger.logInfo(settingGrowthRateResource)).once(); - }); - }); - } - }); - - describe("testFactor", (): void => { - { - const testCases: (string | null)[] = [ - null, - "", - " ", - "abc", - "===", - "!2", - "null", - "undefined", - "Infinity", - ]; - - testCases.forEach((testFactor: string | null): void => { - it(`should set the default when the input '${String(testFactor)}' is invalid`, (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Test", "Factor"])), - ).thenReturn(testFactor); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.equal(inputs.testFactor, InputsDefault.testFactor); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - }); - }); - } - - { - const testCases: string[] = [ - "-0.0000009", - "-2", - "-1.2", - "-5", - "-0.9999999999", - ]; - - testCases.forEach((testFactor: string): void => { - it(`should set the default when the input '${testFactor}' is less than 0.0`, (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Test", "Factor"])), - ).thenReturn(testFactor); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.equal(inputs.testFactor, InputsDefault.testFactor); - verify(logger.logInfo(adjustingTestFactorResource)).once(); - }); - }); - } - - { - const testCases: string[] = [ - "5", - "2.0", - "1000", - "1.001", - "1.2", - "0.000000000000009", - "0.09", - "7", - ]; - - testCases.forEach((testFactor: string): void => { - it(`should set the converted value when the input '${testFactor}' is greater than 0.0`, (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Test", "Factor"])), - ).thenReturn(testFactor); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.equal(inputs.testFactor, parseFloat(testFactor)); - verify(logger.logInfo(settingTestFactorResource)).once(); - }); - }); - } - - { - const testCases: string[] = ["0", "0.0"]; - - testCases.forEach((testFactor: string): void => { - it(`should set null when the input '${testFactor}' is equal to 0.0`, (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Test", "Factor"])), - ).thenReturn(testFactor); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.equal(inputs.testFactor, null); - verify(logger.logInfo(disablingTestFactorResource)).once(); - }); - }); - } - }); - - describe("alwaysCloseComment", (): void => { - { - const testCases: (string | null)[] = [ - null, - "", - " ", - "abc", - "false", - "False", - "FALSE", - "fALSE", - "null", - "undefined", - ]; - - testCases.forEach((alwaysCloseComment: string | null): void => { - it(`should set the default when the input is '${String(alwaysCloseComment)}'`, (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Always", "Close", "Comment"])), - ).thenReturn(alwaysCloseComment); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.equal( - inputs.alwaysCloseComment, - InputsDefault.alwaysCloseComment, - ); - verify(logger.logInfo(adjustingAlwaysCloseComment)).once(); - }); - }); - } - - { - const testCases: string[] = ["true", "True", "TRUE", "tRUE"]; - - testCases.forEach((alwaysCloseComment: string): void => { - it(`should set to true when the input is '${alwaysCloseComment}'`, (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Always", "Close", "Comment"])), - ).thenReturn(alwaysCloseComment); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.equal(inputs.alwaysCloseComment, true); - verify(logger.logInfo(settingAlwaysCloseComment)).once(); - }); - }); - } - }); - - describe("fileMatchingPatterns", (): void => { - { - const testCases: (string | null)[] = [null, "", " ", " ", "\n"]; - - testCases.forEach((fileMatchingPatterns: string | null): void => { - it(`should set the default when the input '${String(fileMatchingPatterns?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { - // Arrange - when( - runnerInvoker.getInput( - deepEqual(["File", "Matching", "Patterns"]), - ), - ).thenReturn(fileMatchingPatterns); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual( - inputs.fileMatchingPatterns, - InputsDefault.fileMatchingPatterns, - ); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - }); - }); - } - - { - const testCases: string[] = [ - "abc", - "abc def hik", - "*.ada *.js *ts *.bb *txt", - ]; - - testCases.forEach((fileMatchingPatterns: string): void => { - it(`should not split '${fileMatchingPatterns}'`, (): void => { - // Arrange - when( - runnerInvoker.getInput( - deepEqual(["File", "Matching", "Patterns"]), - ), - ).thenReturn(fileMatchingPatterns); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(inputs.fileMatchingPatterns, [ - fileMatchingPatterns, - ]); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); - }); - }); - } - - { - const testCases: string[] = [ - "*.ada\n*.js\n*.ts\n*.bb\n*.txt", - "abc\ndef\nhij", - ]; - - testCases.forEach((fileMatchingPatterns: string): void => { - it(`should split '${fileMatchingPatterns.replace(/\n/gu, "\\n")}' at the newline character`, (): void => { - // Arrange - const expectedOutput: string[] = fileMatchingPatterns.split("\n"); - when( - runnerInvoker.getInput( - deepEqual(["File", "Matching", "Patterns"]), - ), - ).thenReturn(fileMatchingPatterns); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(inputs.fileMatchingPatterns, expectedOutput); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); - }); - }); - } - - it("should replace all '\\' with '/'", (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["File", "Matching", "Patterns"])), - ).thenReturn("folder1\\file.js\nfolder2\\*.js"); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(inputs.fileMatchingPatterns, [ - "folder1/file.js", - "folder2/*.js", - ]); - verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); - }); - - it("should remove trailing new lines", (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["File", "Matching", "Patterns"])), - ).thenReturn("file.js\n"); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(inputs.fileMatchingPatterns, ["file.js"]); - verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); - }); - - it("should trim whitespace and filter empty lines", (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["File", "Matching", "Patterns"])), - ).thenReturn(" pattern1 \n\n pattern2 \n \npattern3"); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(inputs.fileMatchingPatterns, [ - "pattern1", - "pattern2", - "pattern3", - ]); - }); - - it("should truncate patterns exceeding the maximum count", (): void => { - // Arrange - const patterns: string[] = Array.from( - { length: 250 }, - (_value: string, index: number) => `pattern${String(index)}`, - ); - when( - runnerInvoker.getInput(deepEqual(["File", "Matching", "Patterns"])), - ).thenReturn(patterns.join("\n")); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.equal(inputs.fileMatchingPatterns.length, 200); - assert.equal(inputs.fileMatchingPatterns[0], "pattern0"); - assert.equal(inputs.fileMatchingPatterns[199], "pattern199"); - verify( - logger.logWarning( - "The matching pattern count '250' exceeds the maximum '200'. Using only the first '200'.", - ), - ).once(); - }); - }); - - describe("testMatchingPatterns", (): void => { - { - const testCases: (string | null)[] = [null, "", " ", " ", "\n"]; - - testCases.forEach((testMatchingPatterns: string | null): void => { - it(`should set the default when the input '${String(testMatchingPatterns?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { - // Arrange - when( - runnerInvoker.getInput( - deepEqual(["Test", "Matching", "Patterns"]), - ), - ).thenReturn(testMatchingPatterns); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual( - inputs.testMatchingPatterns, - InputsDefault.testMatchingPatterns, - ); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - }); - }); - } - - { - const testCases: string[] = [ - "abc", - "abc def hik", - "*.ada *.js *ts *.bb *txt", - ]; - - testCases.forEach((testMatchingPatterns: string): void => { - it(`should not split '${testMatchingPatterns}'`, (): void => { - // Arrange - when( - runnerInvoker.getInput( - deepEqual(["Test", "Matching", "Patterns"]), - ), - ).thenReturn(testMatchingPatterns); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(inputs.testMatchingPatterns, [ - testMatchingPatterns, - ]); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); - }); - }); - } - - { - const testCases: string[] = [ - "*.ada\n*.js\n*.ts\n*.bb\n*.txt", - "abc\ndef\nhij", - ]; - - testCases.forEach((testMatchingPatterns: string): void => { - it(`should split '${testMatchingPatterns.replace(/\n/gu, "\\n")}' at the newline character`, (): void => { - // Arrange - const expectedOutput: string[] = testMatchingPatterns.split("\n"); - when( - runnerInvoker.getInput( - deepEqual(["Test", "Matching", "Patterns"]), - ), - ).thenReturn(testMatchingPatterns); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(inputs.testMatchingPatterns, expectedOutput); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); - }); - }); - } - - it("should replace all '\\' with '/'", (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Test", "Matching", "Patterns"])), - ).thenReturn("folder1\\file.js\nfolder2\\*.js"); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(inputs.testMatchingPatterns, [ - "folder1/file.js", - "folder2/*.js", - ]); - verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); - }); - - it("should remove trailing new lines", (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Test", "Matching", "Patterns"])), - ).thenReturn("file.js\n"); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(inputs.testMatchingPatterns, ["file.js"]); - verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); - }); - }); - - describe("codeFileExtensions", (): void => { - { - const testCases: (string | null)[] = [null, "", " ", " ", "\n"]; - - testCases.forEach((codeFileExtensions: string | null): void => { - it(`should set the default when the input '${String(codeFileExtensions?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), - ).thenReturn(codeFileExtensions); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual( - inputs.codeFileExtensions, - new Set(InputsDefault.codeFileExtensions), - ); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - }); - }); - } - - { - const testCases: string[] = [ - "ada\njs\nts\nbb\ntxt", - "abc\ndef\nhij", - "ts", - ]; - - testCases.forEach((codeFileExtensions: string): void => { - it(`should split '${codeFileExtensions.replace(/\n/gu, "\\n")}' at the newline character`, (): void => { - // Arrange - const expectedResult: Set = new Set( - codeFileExtensions.split("\n"), - ); - when( - runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), - ).thenReturn(codeFileExtensions); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(inputs.codeFileExtensions, expectedResult); - verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); - }); - }); - } - - it("should handle repeated insertion of identical items", (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), - ).thenReturn("ada\nada"); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual(inputs.codeFileExtensions, new Set(["ada"])); - verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); - }); - - it("should convert extensions to lower case", (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), - ).thenReturn("ADA\ncS\nTxT"); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual( - inputs.codeFileExtensions, - new Set(["ada", "cs", "txt"]), - ); - verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); - }); - - it("should remove . and * from extension names", (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), - ).thenReturn("*.ada\n.txt"); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual( - inputs.codeFileExtensions, - new Set(["ada", "txt"]), - ); - verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); - }); - - it("should convert extensions to lower case", (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), - ).thenReturn("ADA\ncS\nTxT"); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual( - inputs.codeFileExtensions, - new Set(["ada", "cs", "txt"]), - ); - verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); - }); - - it("should remove trailing new lines", (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), - ).thenReturn("ada\ncs\ntxt\n"); - - // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); - - // Assert - assert.deepEqual( - inputs.codeFileExtensions, - new Set(["ada", "cs", "txt"]), - ); - verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); - }); - }); - }); -}); diff --git a/src/task/tests/metrics/inputs.testFactor.spec.ts b/src/task/tests/metrics/inputs.testFactor.spec.ts new file mode 100644 index 000000000..ceae78310 --- /dev/null +++ b/src/task/tests/metrics/inputs.testFactor.spec.ts @@ -0,0 +1,147 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as InputsDefault from "../../src/metrics/inputsDefault.js"; +import { + adjustingTestFactorResource, + createInputsMocks, + disablingTestFactorResource, + settingTestFactorResource, +} from "./inputsTestSetup.js"; +import { deepEqual, instance, verify, when } from "ts-mockito"; +import Inputs from "../../src/metrics/inputs.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import assert from "node:assert/strict"; + +describe("inputs.ts", (): void => { + let logger: Logger; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ logger, runnerInvoker } = createInputsMocks()); + }); + + describe("initialize()", (): void => { + describe("testFactor", (): void => { + { + const testCases: (string | null)[] = [ + null, + "", + " ", + "abc", + "===", + "!2", + "null", + "undefined", + "Infinity", + ]; + + testCases.forEach((testFactor: string | null): void => { + it(`should set the default when the input '${String(testFactor)}' is invalid`, (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Test", "Factor"])), + ).thenReturn(testFactor); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.equal(inputs.testFactor, InputsDefault.testFactor); + verify(logger.logInfo(adjustingTestFactorResource)).once(); + }); + }); + } + + { + const testCases: string[] = [ + "-0.0000009", + "-2", + "-1.2", + "-5", + "-0.9999999999", + ]; + + testCases.forEach((testFactor: string): void => { + it(`should set the default when the input '${testFactor}' is less than 0.0`, (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Test", "Factor"])), + ).thenReturn(testFactor); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.equal(inputs.testFactor, InputsDefault.testFactor); + verify(logger.logInfo(adjustingTestFactorResource)).once(); + }); + }); + } + + { + const testCases: string[] = [ + "5", + "2.0", + "1000", + "1.001", + "1.2", + "0.000000000000009", + "0.09", + "7", + ]; + + testCases.forEach((testFactor: string): void => { + it(`should set the converted value when the input '${testFactor}' is greater than 0.0`, (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Test", "Factor"])), + ).thenReturn(testFactor); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.equal(inputs.testFactor, parseFloat(testFactor)); + verify(logger.logInfo(settingTestFactorResource)).once(); + }); + }); + } + + { + const testCases: string[] = ["0", "0.0"]; + + testCases.forEach((testFactor: string): void => { + it(`should set null when the input '${testFactor}' is equal to 0.0`, (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Test", "Factor"])), + ).thenReturn(testFactor); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.equal(inputs.testFactor, null); + verify(logger.logInfo(disablingTestFactorResource)).once(); + }); + }); + } + }); + }); +}); diff --git a/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts b/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts new file mode 100644 index 000000000..b1041364b --- /dev/null +++ b/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts @@ -0,0 +1,162 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as InputsDefault from "../../src/metrics/inputsDefault.js"; +import { + adjustingTestMatchingPatternsResource, + createInputsMocks, + settingTestMatchingPatternsResource, +} from "./inputsTestSetup.js"; +import { deepEqual, instance, verify, when } from "ts-mockito"; +import Inputs from "../../src/metrics/inputs.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import assert from "node:assert/strict"; + +describe("inputs.ts", (): void => { + let logger: Logger; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ logger, runnerInvoker } = createInputsMocks()); + }); + + describe("initialize()", (): void => { + describe("testMatchingPatterns", (): void => { + { + const testCases: (string | null)[] = [null, "", " ", " ", "\n"]; + + testCases.forEach((testMatchingPatterns: string | null): void => { + it(`should set the default when the input '${String(testMatchingPatterns?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { + // Arrange + when( + runnerInvoker.getInput( + deepEqual(["Test", "Matching", "Patterns"]), + ), + ).thenReturn(testMatchingPatterns); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual( + inputs.testMatchingPatterns, + InputsDefault.testMatchingPatterns, + ); + verify( + logger.logInfo(adjustingTestMatchingPatternsResource), + ).once(); + }); + }); + } + + { + const testCases: string[] = [ + "abc", + "abc def hik", + "*.ada *.js *ts *.bb *txt", + ]; + + testCases.forEach((testMatchingPatterns: string): void => { + it(`should not split '${testMatchingPatterns}'`, (): void => { + // Arrange + when( + runnerInvoker.getInput( + deepEqual(["Test", "Matching", "Patterns"]), + ), + ).thenReturn(testMatchingPatterns); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(inputs.testMatchingPatterns, [ + testMatchingPatterns, + ]); + verify( + logger.logInfo(adjustingTestMatchingPatternsResource), + ).never(); + verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); + }); + }); + } + + { + const testCases: string[] = [ + "*.ada\n*.js\n*.ts\n*.bb\n*.txt", + "abc\ndef\nhij", + ]; + + testCases.forEach((testMatchingPatterns: string): void => { + it(`should split '${testMatchingPatterns.replace(/\n/gu, "\\n")}' at the newline character`, (): void => { + // Arrange + const expectedOutput: string[] = testMatchingPatterns.split("\n"); + when( + runnerInvoker.getInput( + deepEqual(["Test", "Matching", "Patterns"]), + ), + ).thenReturn(testMatchingPatterns); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(inputs.testMatchingPatterns, expectedOutput); + verify( + logger.logInfo(adjustingTestMatchingPatternsResource), + ).never(); + verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); + }); + }); + } + + it("should replace all '\\' with '/'", (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Test", "Matching", "Patterns"])), + ).thenReturn("folder1\\file.js\nfolder2\\*.js"); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(inputs.testMatchingPatterns, [ + "folder1/file.js", + "folder2/*.js", + ]); + verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); + }); + + it("should remove trailing new lines", (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Test", "Matching", "Patterns"])), + ).thenReturn("file.js\n"); + + // Act + const inputs: Inputs = new Inputs( + instance(logger), + instance(runnerInvoker), + ); + + // Assert + assert.deepEqual(inputs.testMatchingPatterns, ["file.js"]); + verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); + }); + }); + }); +}); diff --git a/src/task/tests/metrics/inputsTestSetup.ts b/src/task/tests/metrics/inputsTestSetup.ts new file mode 100644 index 000000000..df99023bf --- /dev/null +++ b/src/task/tests/metrics/inputsTestSetup.ts @@ -0,0 +1,131 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as InputsDefault from "../../src/metrics/inputsDefault.js"; +import { deepEqual, mock, when } from "ts-mockito"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import { anyString } from "../testUtilities/mockito.js"; + +export const adjustingAlwaysCloseComment = + "Adjusting the always-close-comment mode input to 'false'."; +export const adjustingBaseSizeResource = `Adjusting the base size input to '${String(InputsDefault.baseSize)}'.`; +export const adjustingGrowthRateResource = `Adjusting the growth rate input to '${String(InputsDefault.growthRate)}'.`; +export const adjustingTestFactorResource = `Adjusting the test factor input to '${String(InputsDefault.testFactor)}'.`; +export const adjustingFileMatchingPatternsResource = `Adjusting the file matching patterns input to '${JSON.stringify(InputsDefault.fileMatchingPatterns)}'.`; +export const adjustingTestMatchingPatternsResource = `Adjusting the test matching patterns input to '${JSON.stringify(InputsDefault.testMatchingPatterns)}'.`; +export const adjustingCodeFileExtensionsResource = `Adjusting the code file extensions input to '${JSON.stringify(InputsDefault.codeFileExtensions)}'.`; +export const disablingTestFactorResource = + "Disabling the test factor validation."; +export const settingAlwaysCloseComment = + "Setting the always-close-comment mode input to 'true'."; +export const settingBaseSizeResource = "Setting the base size input to 'VALUE'."; +export const settingGrowthRateResource = + "Setting the growth rate input to 'VALUE'."; +export const settingTestFactorResource = + "Setting the test factor input to 'VALUE'."; +export const settingFileMatchingPatternsResource = + "Setting the file matching patterns input to 'VALUE'."; +export const settingTestMatchingPatternsResource = + "Setting the test matching patterns input to 'VALUE'."; +export const settingCodeFileExtensionsResource = + "Setting the code file extensions input to 'VALUE'."; + +export interface InputsMocks { + logger: Logger; + runnerInvoker: RunnerInvoker; +} + +/** + * Creates mocked `Logger` and `RunnerInvoker` instances pre-configured with the + * default stubs required for `Inputs.initialize()` to run without throwing. + * Tests can override individual stubs as needed. + * @returns The paired mocks. + */ +export const createInputsMocks = (): InputsMocks => { + const logger: Logger = mock(Logger); + const runnerInvoker: RunnerInvoker = mock(RunnerInvoker); + + when(runnerInvoker.getInput(deepEqual(["Base", "Size"]))).thenReturn(""); + when(runnerInvoker.getInput(deepEqual(["Growth", "Rate"]))).thenReturn(""); + when(runnerInvoker.getInput(deepEqual(["Test", "Factor"]))).thenReturn(""); + when( + runnerInvoker.getInput(deepEqual(["Always", "Close", "Comment"])), + ).thenReturn(""); + when( + runnerInvoker.getInput(deepEqual(["File", "Matching", "Patterns"])), + ).thenReturn(""); + when( + runnerInvoker.getInput(deepEqual(["Test", "Matching", "Patterns"])), + ).thenReturn(""); + when( + runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), + ).thenReturn(""); + when( + runnerInvoker.loc("metrics.inputs.adjustingAlwaysCloseComment"), + ).thenReturn(adjustingAlwaysCloseComment); + when( + runnerInvoker.loc( + "metrics.inputs.adjustingBaseSize", + InputsDefault.baseSize.toLocaleString(), + ), + ).thenReturn(adjustingBaseSizeResource); + when( + runnerInvoker.loc( + "metrics.inputs.adjustingGrowthRate", + InputsDefault.growthRate.toLocaleString(), + ), + ).thenReturn(adjustingGrowthRateResource); + when( + runnerInvoker.loc( + "metrics.inputs.adjustingTestFactor", + InputsDefault.testFactor.toLocaleString(), + ), + ).thenReturn(adjustingTestFactorResource); + when( + runnerInvoker.loc( + "metrics.inputs.adjustingFileMatchingPatterns", + JSON.stringify(InputsDefault.fileMatchingPatterns), + ), + ).thenReturn(adjustingFileMatchingPatternsResource); + when( + runnerInvoker.loc( + "metrics.inputs.adjustingTestMatchingPatterns", + JSON.stringify(InputsDefault.testMatchingPatterns), + ), + ).thenReturn(adjustingTestMatchingPatternsResource); + when( + runnerInvoker.loc( + "metrics.inputs.adjustingCodeFileExtensions", + JSON.stringify(InputsDefault.codeFileExtensions), + ), + ).thenReturn(adjustingCodeFileExtensionsResource); + when(runnerInvoker.loc("metrics.inputs.disablingTestFactor")).thenReturn( + disablingTestFactorResource, + ); + when( + runnerInvoker.loc("metrics.inputs.settingAlwaysCloseComment"), + ).thenReturn(settingAlwaysCloseComment); + when( + runnerInvoker.loc("metrics.inputs.settingBaseSize", anyString()), + ).thenReturn(settingBaseSizeResource); + when( + runnerInvoker.loc("metrics.inputs.settingGrowthRate", anyString()), + ).thenReturn(settingGrowthRateResource); + when( + runnerInvoker.loc("metrics.inputs.settingTestFactor", anyString()), + ).thenReturn(settingTestFactorResource); + when( + runnerInvoker.loc("metrics.inputs.settingFileMatchingPatterns", anyString()), + ).thenReturn(settingFileMatchingPatternsResource); + when( + runnerInvoker.loc("metrics.inputs.settingTestMatchingPatterns", anyString()), + ).thenReturn(settingTestMatchingPatternsResource); + when( + runnerInvoker.loc("metrics.inputs.settingCodeFileExtensions", anyString()), + ).thenReturn(settingCodeFileExtensionsResource); + + return { logger, runnerInvoker }; +}; diff --git a/src/task/tests/repos/azureReposInvoker.createComment.spec.ts b/src/task/tests/repos/azureReposInvoker.createComment.spec.ts new file mode 100644 index 000000000..4f7032042 --- /dev/null +++ b/src/task/tests/repos/azureReposInvoker.createComment.spec.ts @@ -0,0 +1,318 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as AssertExtensions from "../testUtilities/assertExtensions.js"; +import { CommentThreadStatus, type GitPullRequestCommentThread } from "azure-devops-node-api/interfaces/GitInterfaces.js"; +import { + deepEqual, + instance, + verify, + when, +} from "ts-mockito"; +import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; +import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; +import ErrorWithStatus from "../wrappers/errorWithStatus.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import type { IGitApi } from "azure-devops-node-api/GitApi.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import { StatusCodes } from "http-status-codes"; +import TokenManager from "../../src/repos/tokenManager.js"; +import { any } from "../testUtilities/mockito.js"; +import assert from "node:assert/strict"; +import { createAzureReposInvokerMocks } from "./azureReposInvokerTestSetup.js"; + +describe("azureReposInvoker.ts", (): void => { + let gitApi: IGitApi; + let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; + let gitInvoker: GitInvoker; + let logger: Logger; + let runnerInvoker: RunnerInvoker; + let tokenManager: TokenManager; + + beforeEach((): void => { + ({ + gitApi, + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + } = createAzureReposInvokerMocks()); + }); + + describe("createComment()", (): void => { + { + const testCases: StatusCodes[] = [ + StatusCodes.UNAUTHORIZED, + StatusCodes.FORBIDDEN, + StatusCodes.NOT_FOUND, + ]; + + testCases.forEach((statusCode: StatusCodes): void => { + it(`should throw when the access token has insufficient access and the API call returns status code '${String(statusCode)}'`, async (): Promise => { + // Arrange + const error: ErrorWithStatus = new ErrorWithStatus("Test"); + error.statusCode = statusCode; + when(gitApi.createThread(any(), "RepoID", 10, "Project")).thenThrow( + error, + ); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const func: () => Promise = async () => + azureReposInvoker.createComment( + "Comment Content", + "file.ts", + CommentThreadStatus.Active, + ); + + // Assert + const expectedMessage: string = + statusCode === StatusCodes.NOT_FOUND + ? "The resource could not be found. Verify the repository and pull request exist." + : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; + const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( + func, + expectedMessage, + ); + assert.equal(result.internalMessage, "Test"); + verify( + azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), + ).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.createThread(any(), "RepoID", 10, "Project")).once(); + }); + }); + } + + it("should call the API for no file", async (): Promise => { + // Arrange + const expectedComment: GitPullRequestCommentThread = { + comments: [{ content: "Comment Content" }], + status: CommentThreadStatus.Active, + }; + when( + gitApi.createThread( + deepEqual(expectedComment), + "RepoID", + 10, + "Project", + ), + ).thenResolve({}); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.createComment( + "Comment Content", + null, + CommentThreadStatus.Active, + ); + + // Assert + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify( + gitApi.createThread( + deepEqual(expectedComment), + "RepoID", + 10, + "Project", + ), + ).once(); + }); + + it("should call the API for no file when called multiple times", async (): Promise => { + // Arrange + const expectedComment: GitPullRequestCommentThread = { + comments: [{ content: "Comment Content" }], + status: CommentThreadStatus.Active, + }; + when( + gitApi.createThread( + deepEqual(expectedComment), + "RepoID", + 10, + "Project", + ), + ).thenResolve({}); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.createComment( + "Comment Content", + null, + CommentThreadStatus.Active, + ); + await azureReposInvoker.createComment( + "Comment Content", + null, + CommentThreadStatus.Active, + ); + + // Assert + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify( + gitApi.createThread( + deepEqual(expectedComment), + "RepoID", + 10, + "Project", + ), + ).twice(); + }); + + it("should call the API for a file", async (): Promise => { + // Arrange + const expectedComment: GitPullRequestCommentThread = { + comments: [{ content: "Comment Content" }], + status: CommentThreadStatus.Active, + threadContext: { + filePath: "/file.ts", + rightFileEnd: { + line: 1, + offset: 2, + }, + rightFileStart: { + line: 1, + offset: 1, + }, + }, + }; + when( + gitApi.createThread( + deepEqual(expectedComment), + "RepoID", + 10, + "Project", + ), + ).thenResolve({}); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.createComment( + "Comment Content", + "file.ts", + CommentThreadStatus.Active, + ); + + // Assert + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify( + gitApi.createThread( + deepEqual(expectedComment), + "RepoID", + 10, + "Project", + ), + ).once(); + }); + + it("should call the API for a deleted file", async (): Promise => { + // Arrange + const expectedComment: GitPullRequestCommentThread = { + comments: [{ content: "Comment Content" }], + status: CommentThreadStatus.Active, + threadContext: { + filePath: "/file.ts", + leftFileEnd: { + line: 1, + offset: 2, + }, + leftFileStart: { + line: 1, + offset: 1, + }, + }, + }; + when( + gitApi.createThread( + deepEqual(expectedComment), + "RepoID", + 10, + "Project", + ), + ).thenResolve({}); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.createComment( + "Comment Content", + "file.ts", + CommentThreadStatus.Active, + true, + ); + + // Assert + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify( + gitApi.createThread( + deepEqual(expectedComment), + "RepoID", + 10, + "Project", + ), + ).once(); + }); + }); +}); diff --git a/src/task/tests/repos/azureReposInvoker.deleteCommentThread.spec.ts b/src/task/tests/repos/azureReposInvoker.deleteCommentThread.spec.ts new file mode 100644 index 000000000..b59115fb6 --- /dev/null +++ b/src/task/tests/repos/azureReposInvoker.deleteCommentThread.spec.ts @@ -0,0 +1,146 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as AssertExtensions from "../testUtilities/assertExtensions.js"; +import { any, anyNumber } from "../testUtilities/mockito.js"; +import { instance, verify, when } from "ts-mockito"; +import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; +import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; +import ErrorWithStatus from "../wrappers/errorWithStatus.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import type { IGitApi } from "azure-devops-node-api/GitApi.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import { StatusCodes } from "http-status-codes"; +import TokenManager from "../../src/repos/tokenManager.js"; +import assert from "node:assert/strict"; +import { createAzureReposInvokerMocks } from "./azureReposInvokerTestSetup.js"; + +describe("azureReposInvoker.ts", (): void => { + let gitApi: IGitApi; + let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; + let gitInvoker: GitInvoker; + let logger: Logger; + let runnerInvoker: RunnerInvoker; + let tokenManager: TokenManager; + + beforeEach((): void => { + ({ + gitApi, + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + } = createAzureReposInvokerMocks()); + }); + + describe("deleteCommentThread()", (): void => { + { + const testCases: StatusCodes[] = [ + StatusCodes.UNAUTHORIZED, + StatusCodes.FORBIDDEN, + StatusCodes.NOT_FOUND, + ]; + + testCases.forEach((statusCode: StatusCodes): void => { + it(`should throw when the access token has insufficient access and the API call returns status code '${String(statusCode)}'`, async (): Promise => { + // Arrange + const error: ErrorWithStatus = new ErrorWithStatus("Test"); + error.statusCode = statusCode; + when(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).thenThrow( + error, + ); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const func: () => Promise = async () => + azureReposInvoker.deleteCommentThread(20); + + // Assert + const expectedMessage: string = + statusCode === StatusCodes.NOT_FOUND + ? "The resource could not be found. Verify the repository and pull request exist." + : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; + const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( + func, + expectedMessage, + ); + assert.equal(result.internalMessage, "Test"); + verify( + azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), + ).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).once(); + }); + }); + } + + it("should call the API for a single comment", async (): Promise => { + // Arrange + when(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).thenResolve(); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.deleteCommentThread(20); + + // Assert + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).once(); + }); + + it("should call the API when called multiple times", async (): Promise => { + // Arrange + when( + gitApi.deleteComment("RepoID", 10, anyNumber(), 1, "Project"), + ).thenResolve(); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.deleteCommentThread(20); + await azureReposInvoker.deleteCommentThread(30); + + // Assert + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).once(); + verify(gitApi.deleteComment("RepoID", 10, 30, 1, "Project")).once(); + }); + }); +}); diff --git a/src/task/tests/repos/azureReposInvoker.getComments.spec.ts b/src/task/tests/repos/azureReposInvoker.getComments.spec.ts new file mode 100644 index 000000000..98c618c7d --- /dev/null +++ b/src/task/tests/repos/azureReposInvoker.getComments.spec.ts @@ -0,0 +1,448 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as AssertExtensions from "../testUtilities/assertExtensions.js"; +import { CommentThreadStatus, type GitPullRequestCommentThread } from "azure-devops-node-api/interfaces/GitInterfaces.js"; +import { instance, verify, when } from "ts-mockito"; +import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; +import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; +import type CommentData from "../../src/repos/interfaces/commentData.js"; +import ErrorWithStatus from "../wrappers/errorWithStatus.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import type { IGitApi } from "azure-devops-node-api/GitApi.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import { StatusCodes } from "http-status-codes"; +import TokenManager from "../../src/repos/tokenManager.js"; +import { any } from "../testUtilities/mockito.js"; +import assert from "node:assert/strict"; +import { createAzureReposInvokerMocks } from "./azureReposInvokerTestSetup.js"; + +describe("azureReposInvoker.ts", (): void => { + let gitApi: IGitApi; + let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; + let gitInvoker: GitInvoker; + let logger: Logger; + let runnerInvoker: RunnerInvoker; + let tokenManager: TokenManager; + + beforeEach((): void => { + ({ + gitApi, + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + } = createAzureReposInvokerMocks()); + }); + + describe("getComments()", (): void => { + { + const testCases: StatusCodes[] = [ + StatusCodes.UNAUTHORIZED, + StatusCodes.FORBIDDEN, + StatusCodes.NOT_FOUND, + ]; + + testCases.forEach((statusCode: StatusCodes): void => { + it(`should throw when the access token has insufficient access and the API call returns status code '${String(statusCode)}'`, async (): Promise => { + // Arrange + const error: ErrorWithStatus = new ErrorWithStatus("Test"); + error.statusCode = statusCode; + when(gitApi.getThreads("RepoID", 10, "Project")).thenThrow(error); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const func: () => Promise = async () => + azureReposInvoker.getComments(); + + // Assert + const expectedMessage: string = + statusCode === StatusCodes.NOT_FOUND + ? "The resource could not be found. Verify the repository and pull request exist." + : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; + const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( + func, + expectedMessage, + ); + assert.equal(result.internalMessage, "Test"); + verify( + azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), + ).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.getThreads("RepoID", 10, "Project")).once(); + }); + }); + } + + it("should return the result when called with a pull request comment whose thread context is undefined", async (): Promise => { + // Arrange + when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ + { comments: [{ content: "Content" }], id: 1, status: 1 }, + ]); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const result: CommentData = await azureReposInvoker.getComments(); + + // Assert + assert.equal(result.pullRequestComments.length, 1); + assert.equal(result.pullRequestComments[0]?.id, 1); + assert.equal(result.pullRequestComments[0].content, "Content"); + assert.equal( + result.pullRequestComments[0].status, + CommentThreadStatus.Active, + ); + assert.equal(result.fileComments.length, 0); + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.getThreads("RepoID", 10, "Project")).once(); + }); + + it("should return the result when called with a pull request comment whose thread context is null", async (): Promise => { + // Arrange + when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ + { + comments: [{ content: "Content" }], + id: 1, + status: 1, + threadContext: null as unknown as undefined, + }, + ]); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const result: CommentData = await azureReposInvoker.getComments(); + + // Assert + assert.equal(result.pullRequestComments.length, 1); + assert.equal(result.pullRequestComments[0]?.id, 1); + assert.equal(result.pullRequestComments[0].content, "Content"); + assert.equal( + result.pullRequestComments[0].status, + CommentThreadStatus.Active, + ); + assert.equal(result.fileComments.length, 0); + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.getThreads("RepoID", 10, "Project")).once(); + }); + + it("should return the result when called with a file comment", async (): Promise => { + // Arrange + when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ + { + comments: [{ content: "Content" }], + id: 1, + status: 1, + threadContext: { filePath: "/file.ts" }, + }, + ]); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const result: CommentData = await azureReposInvoker.getComments(); + + // Assert + assert.equal(result.pullRequestComments.length, 0); + assert.equal(result.fileComments.length, 1); + assert.equal(result.fileComments[0]?.id, 1); + assert.equal(result.fileComments[0].content, "Content"); + assert.equal(result.fileComments[0].status, CommentThreadStatus.Active); + assert.equal(result.fileComments[0].fileName, "file.ts"); + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.getThreads("RepoID", 10, "Project")).once(); + }); + + it("should return the result when called with both a pull request and file comment", async (): Promise => { + // Arrange + when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ + { comments: [{ content: "PR Content" }], id: 1, status: 1 }, + { + comments: [{ content: "File Content" }], + id: 2, + status: 1, + threadContext: { filePath: "/file.ts" }, + }, + ]); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const result: CommentData = await azureReposInvoker.getComments(); + + // Assert + assert.equal(result.pullRequestComments.length, 1); + assert.equal(result.pullRequestComments[0]?.id, 1); + assert.equal(result.pullRequestComments[0].content, "PR Content"); + assert.equal( + result.pullRequestComments[0].status, + CommentThreadStatus.Active, + ); + assert.equal(result.fileComments.length, 1); + assert.equal(result.fileComments[0]?.id, 2); + assert.equal(result.fileComments[0].content, "File Content"); + assert.equal(result.fileComments[0].status, CommentThreadStatus.Active); + assert.equal(result.fileComments[0].fileName, "file.ts"); + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.getThreads("RepoID", 10, "Project")).once(); + }); + + it("should return the result when called multiple times", async (): Promise => { + // Arrange + when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ + { comments: [{ content: "Content" }], id: 1, status: 1 }, + ]); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.getComments(); + const result: CommentData = await azureReposInvoker.getComments(); + + // Assert + assert.equal(result.pullRequestComments.length, 1); + assert.equal(result.pullRequestComments[0]?.id, 1); + assert.equal(result.pullRequestComments[0].content, "Content"); + assert.equal( + result.pullRequestComments[0].status, + CommentThreadStatus.Active, + ); + assert.equal(result.fileComments.length, 0); + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.getThreads("RepoID", 10, "Project")).twice(); + }); + + it("should throw when provided with a payload with no ID", async (): Promise => { + // Arrange + when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ + { comments: [{ content: "Content" }], status: 1 }, + ]); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const func: () => Promise = async () => + azureReposInvoker.getComments(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + "'commentThread[0].id', accessed within 'AzureReposInvoker.convertPullRequestComments()', is invalid, null, or undefined 'undefined'.", + ); + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.getThreads("RepoID", 10, "Project")).once(); + }); + + it("should continue if the payload has no status", async (): Promise => { + // Arrange + const getThreadsResult: GitPullRequestCommentThread[] = [ + { comments: [{ content: "PR Content" }], id: 1 }, + { + comments: [{ content: "File Content" }], + id: 2, + threadContext: { filePath: "/file.ts" }, + }, + ]; + when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve( + getThreadsResult, + ); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const result: CommentData = await azureReposInvoker.getComments(); + + // Assert + assert.equal(result.pullRequestComments.length, 1); + assert.equal(result.pullRequestComments[0]?.id, 1); + assert.equal(result.pullRequestComments[0].content, "PR Content"); + assert.equal( + result.pullRequestComments[0].status, + CommentThreadStatus.Unknown, + ); + assert.equal(result.fileComments.length, 1); + assert.equal(result.fileComments[0]?.id, 2); + assert.equal(result.fileComments[0].content, "File Content"); + assert.equal(result.fileComments[0].status, CommentThreadStatus.Unknown); + assert.equal(result.fileComments[0].fileName, "file.ts"); + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.getThreads("RepoID", 10, "Project")).once(); + }); + + { + const testCases: GitPullRequestCommentThread[] = [ + { id: 1, status: 1 }, + { comments: [], id: 1, status: 1 }, + { comments: [{}], id: 1, status: 1 }, + { comments: [{ content: "" }], id: 1, status: 1 }, + { + comments: [{ content: "Content" }], + id: 1, + status: 1, + threadContext: {}, + }, + { + comments: [{ content: "Content" }], + id: 1, + status: 1, + threadContext: { filePath: "" }, + }, + { + comments: [{ content: "Content" }], + id: 1, + status: 1, + threadContext: { filePath: "/" }, + }, + ]; + + testCases.forEach((commentThread: GitPullRequestCommentThread): void => { + it(`should skip the comment with the malformed payload '${JSON.stringify(commentThread)}'`, async (): Promise => { + // Arrange + const getThreadsResult: GitPullRequestCommentThread[] = [ + commentThread, + { comments: [{ content: "PR Content" }], id: 2, status: 1 }, + { + comments: [{ content: "File Content" }], + id: 3, + status: 1, + threadContext: { filePath: "/file.ts" }, + }, + ]; + when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve( + getThreadsResult, + ); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const result: CommentData = await azureReposInvoker.getComments(); + + // Assert + assert.equal(result.pullRequestComments.length, 1); + assert.equal(result.pullRequestComments[0]?.id, 2); + assert.equal(result.pullRequestComments[0].content, "PR Content"); + assert.equal( + result.pullRequestComments[0].status, + CommentThreadStatus.Active, + ); + assert.equal(result.fileComments.length, 1); + assert.equal(result.fileComments[0]?.id, 3); + assert.equal(result.fileComments[0].content, "File Content"); + assert.equal( + result.fileComments[0].status, + CommentThreadStatus.Active, + ); + assert.equal(result.fileComments[0].fileName, "file.ts"); + verify( + azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), + ).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.getThreads("RepoID", 10, "Project")).once(); + }); + }); + } + }); +}); diff --git a/src/task/tests/repos/azureReposInvoker.getTitleAndDescription.spec.ts b/src/task/tests/repos/azureReposInvoker.getTitleAndDescription.spec.ts new file mode 100644 index 000000000..bbe9293c0 --- /dev/null +++ b/src/task/tests/repos/azureReposInvoker.getTitleAndDescription.spec.ts @@ -0,0 +1,350 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as AssertExtensions from "../testUtilities/assertExtensions.js"; +import { instance, verify, when } from "ts-mockito"; +import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; +import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; +import ErrorWithStatus from "../wrappers/errorWithStatus.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import type { IGitApi } from "azure-devops-node-api/GitApi.js"; +import Logger from "../../src/utilities/logger.js"; +import type PullRequestDetailsInterface from "../../src/repos/interfaces/pullRequestDetailsInterface.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import { StatusCodes } from "http-status-codes"; +import TokenManager from "../../src/repos/tokenManager.js"; +import { any } from "../testUtilities/mockito.js"; +import assert from "node:assert/strict"; +import { createAzureReposInvokerMocks } from "./azureReposInvokerTestSetup.js"; +import { stubEnv } from "../testUtilities/stubEnv.js"; + +describe("azureReposInvoker.ts", (): void => { + let gitApi: IGitApi; + let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; + let gitInvoker: GitInvoker; + let logger: Logger; + let runnerInvoker: RunnerInvoker; + let tokenManager: TokenManager; + + beforeEach((): void => { + ({ + gitApi, + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + } = createAzureReposInvokerMocks()); + }); + + describe("getTitleAndDescription()", (): void => { + { + const testCases: (string | undefined)[] = [undefined, ""]; + + testCases.forEach((variable: string | undefined): void => { + it(`should throw when SYSTEM_TEAMPROJECT is set to the invalid value '${String(variable)}'`, async (): Promise => { + // Arrange + if (typeof variable === "undefined") { + stubEnv(["SYSTEM_TEAMPROJECT", undefined]); + } else { + stubEnv(["SYSTEM_TEAMPROJECT", variable]); + } + + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const func: () => Promise = async () => + azureReposInvoker.getTitleAndDescription(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + `'SYSTEM_TEAMPROJECT', accessed within 'AzureReposInvoker.getGitApi()', is invalid, null, or undefined '${String(variable)}'.`, + ); + }); + }); + } + + { + const testCases: (string | undefined)[] = [undefined, ""]; + + testCases.forEach((variable: string | undefined): void => { + it(`should throw when BUILD_REPOSITORY_ID is set to the invalid value '${String(variable)}'`, async (): Promise => { + // Arrange + if (typeof variable === "undefined") { + stubEnv(["BUILD_REPOSITORY_ID", undefined]); + } else { + stubEnv(["BUILD_REPOSITORY_ID", variable]); + } + + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const func: () => Promise = async () => + azureReposInvoker.getTitleAndDescription(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + `'BUILD_REPOSITORY_ID', accessed within 'AzureReposInvoker.getGitApi()', is invalid, null, or undefined '${String(variable)}'.`, + ); + }); + }); + } + + { + const testCases: (string | undefined)[] = [undefined, ""]; + + testCases.forEach((variable: string | undefined): void => { + it(`should throw when PR_METRICS_ACCESS_TOKEN is set to the invalid value '${String(variable)}'`, async (): Promise => { + // Arrange + if (typeof variable === "undefined") { + stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); + } else { + stubEnv(["PR_METRICS_ACCESS_TOKEN", variable]); + } + + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const func: () => Promise = async () => + azureReposInvoker.getTitleAndDescription(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + `'PR_METRICS_ACCESS_TOKEN', accessed within 'AzureReposInvoker.getGitApi()', is invalid, null, or undefined '${String(variable)}'.`, + ); + }); + }); + } + + { + const testCases: (string | undefined)[] = [undefined, ""]; + + testCases.forEach((variable: string | undefined): void => { + it(`should throw when SYSTEM_TEAMFOUNDATIONCOLLECTIONURI is set to the invalid value '${String(variable)}'`, async (): Promise => { + // Arrange + if (typeof variable === "undefined") { + stubEnv(["SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", undefined]); + } else { + stubEnv(["SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", variable]); + } + + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const func: () => Promise = async () => + azureReposInvoker.getTitleAndDescription(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + `'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI', accessed within 'AzureReposInvoker.getGitApi()', is invalid, null, or undefined '${String(variable)}'.`, + ); + verify( + azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), + ).once(); + }); + }); + } + + { + const testCases: StatusCodes[] = [ + StatusCodes.UNAUTHORIZED, + StatusCodes.FORBIDDEN, + StatusCodes.NOT_FOUND, + ]; + + testCases.forEach((statusCode: StatusCodes): void => { + it(`should throw when the access token has insufficient access and the API call returns status code '${String(statusCode)}'`, async (): Promise => { + // Arrange + const error: ErrorWithStatus = new ErrorWithStatus("Test"); + error.statusCode = statusCode; + when(gitApi.getPullRequestById(10, "Project")).thenThrow(error); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const func: () => Promise = async () => + azureReposInvoker.getTitleAndDescription(); + + // Assert + const expectedMessage: string = + statusCode === StatusCodes.NOT_FOUND + ? "The resource could not be found. Verify the repository and pull request exist." + : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; + const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( + func, + expectedMessage, + ); + assert.equal(result.internalMessage, "Test"); + verify( + azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), + ).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.getPullRequestById(10, "Project")).once(); + }); + }); + } + + it("should return the title and description when available", async (): Promise => { + // Arrange + when(gitApi.getPullRequestById(10, "Project")).thenResolve({ + description: "Description", + title: "Title", + }); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const result: PullRequestDetailsInterface = + await azureReposInvoker.getTitleAndDescription(); + + // Assert + assert.equal(result.title, "Title"); + assert.equal(result.description, "Description"); + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.getPullRequestById(10, "Project")).once(); + }); + + it("should return the title and description when available and called multiple times", async (): Promise => { + // Arrange + when(gitApi.getPullRequestById(10, "Project")).thenResolve({ + description: "Description", + title: "Title", + }); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.getTitleAndDescription(); + const result: PullRequestDetailsInterface = + await azureReposInvoker.getTitleAndDescription(); + + // Assert + assert.equal(result.title, "Title"); + assert.equal(result.description, "Description"); + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.getPullRequestById(10, "Project")).twice(); + }); + + it("should return the title when the description is unavailable", async (): Promise => { + // Arrange + when(gitApi.getPullRequestById(10, "Project")).thenResolve({ + title: "Title", + }); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const result: PullRequestDetailsInterface = + await azureReposInvoker.getTitleAndDescription(); + + // Assert + assert.equal(result.title, "Title"); + assert.equal(result.description, null); + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.getPullRequestById(10, "Project")).once(); + }); + + it("should throw when the title is unavailable", async (): Promise => { + // Arrange + when(gitApi.getPullRequestById(10, "Project")).thenResolve({}); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const func: () => Promise = async () => + azureReposInvoker.getTitleAndDescription(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + "'title', accessed within 'AzureReposInvoker.getTitleAndDescription()', is invalid, null, or undefined 'undefined'.", + ); + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify(gitApi.getPullRequestById(10, "Project")).once(); + }); + }); +}); diff --git a/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts b/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts new file mode 100644 index 000000000..32056f63b --- /dev/null +++ b/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts @@ -0,0 +1,95 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import { instance, when } from "ts-mockito"; +import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; +import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import TokenManager from "../../src/repos/tokenManager.js"; +import assert from "node:assert/strict"; +import { createAzureReposInvokerMocks } from "./azureReposInvokerTestSetup.js"; +import { stubEnv } from "../testUtilities/stubEnv.js"; + +describe("azureReposInvoker.ts", (): void => { + let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; + let gitInvoker: GitInvoker; + let logger: Logger; + let runnerInvoker: RunnerInvoker; + let tokenManager: TokenManager; + + beforeEach((): void => { + ({ + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + } = createAzureReposInvokerMocks()); + }); + + describe("isAccessTokenAvailable()", (): void => { + it("should return null when the token exists", async (): Promise => { + // Arrange + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const result: string | null = + await azureReposInvoker.isAccessTokenAvailable(); + + // Assert + assert.equal(result, null); + }); + + it("should return a string when the token manager fails", async (): Promise => { + // Arrange + stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + when(tokenManager.getToken()).thenResolve("Failure"); + + // Act + const result: string | null = + await azureReposInvoker.isAccessTokenAvailable(); + + // Assert + assert.equal(result, "Failure"); + }); + + it("should return a string when the token does not exist", async (): Promise => { + // Arrange + stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const result: string | null = + await azureReposInvoker.isAccessTokenAvailable(); + + // Assert + assert.equal( + result, + "Could not access the Workload Identity Federation or Personal Access Token (PAT). Add the 'WorkloadIdentityFederation' input or 'PR_Metrics_Access_Token' as a secret environment variable.", + ); + }); + }); +}); diff --git a/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts b/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts new file mode 100644 index 000000000..286a90648 --- /dev/null +++ b/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts @@ -0,0 +1,297 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as AssertExtensions from "../testUtilities/assertExtensions.js"; +import { + deepEqual, + instance, + verify, + when, +} from "ts-mockito"; +import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; +import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; +import ErrorWithStatus from "../wrappers/errorWithStatus.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import { type GitPullRequest } from "azure-devops-node-api/interfaces/GitInterfaces.js"; +import type { IGitApi } from "azure-devops-node-api/GitApi.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import { StatusCodes } from "http-status-codes"; +import TokenManager from "../../src/repos/tokenManager.js"; +import { any } from "../testUtilities/mockito.js"; +import assert from "node:assert/strict"; +import { createAzureReposInvokerMocks } from "./azureReposInvokerTestSetup.js"; + +describe("azureReposInvoker.ts", (): void => { + let gitApi: IGitApi; + let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; + let gitInvoker: GitInvoker; + let logger: Logger; + let runnerInvoker: RunnerInvoker; + let tokenManager: TokenManager; + + beforeEach((): void => { + ({ + gitApi, + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + } = createAzureReposInvokerMocks()); + }); + + describe("setTitleAndDescription()", (): void => { + { + const testCases: StatusCodes[] = [ + StatusCodes.UNAUTHORIZED, + StatusCodes.FORBIDDEN, + StatusCodes.NOT_FOUND, + ]; + + testCases.forEach((statusCode: StatusCodes): void => { + it(`should throw when the access token has insufficient access and the API call returns status code '${String(statusCode)}'`, async (): Promise => { + // Arrange + const error: ErrorWithStatus = new ErrorWithStatus("Test"); + error.statusCode = statusCode; + when( + gitApi.updatePullRequest(any(), "RepoID", 10, "Project"), + ).thenThrow(error); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const func: () => Promise = async () => + azureReposInvoker.setTitleAndDescription("Title", "Description"); + + // Assert + const expectedMessage: string = + statusCode === StatusCodes.NOT_FOUND + ? "The resource could not be found. Verify the repository and pull request exist." + : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; + const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( + func, + expectedMessage, + ); + assert.equal(result.internalMessage, "Test"); + verify( + azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), + ).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify( + gitApi.updatePullRequest(any(), "RepoID", 10, "Project"), + ).once(); + }); + }); + } + + it("should not call the API when the title and description are null", async (): Promise => { + // Arrange + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.setTitleAndDescription(null, null); + + // Assert + verify( + azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), + ).never(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).never(); + verify(gitApi.updatePullRequest(any(), "RepoID", 10, "Project")).never(); + }); + + it("should call the API when the title is valid", async (): Promise => { + // Arrange + const expectedDetails: GitPullRequest = { + title: "Title", + }; + when( + gitApi.updatePullRequest( + deepEqual(expectedDetails), + "RepoID", + 10, + "Project", + ), + ).thenResolve({}); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.setTitleAndDescription("Title", null); + + // Assert + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify( + gitApi.updatePullRequest( + deepEqual(expectedDetails), + "RepoID", + 10, + "Project", + ), + ).once(); + }); + + it("should call the API when the description is valid", async (): Promise => { + // Arrange + const expectedDetails: GitPullRequest = { + description: "Description", + }; + when( + gitApi.updatePullRequest( + deepEqual(expectedDetails), + "RepoID", + 10, + "Project", + ), + ).thenResolve({}); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.setTitleAndDescription(null, "Description"); + + // Assert + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify( + gitApi.updatePullRequest( + deepEqual(expectedDetails), + "RepoID", + 10, + "Project", + ), + ).once(); + }); + + it("should call the API when both the title and description are valid", async (): Promise => { + // Arrange + const expectedDetails: GitPullRequest = { + description: "Description", + title: "Title", + }; + when( + gitApi.updatePullRequest( + deepEqual(expectedDetails), + "RepoID", + 10, + "Project", + ), + ).thenResolve({}); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.setTitleAndDescription("Title", "Description"); + + // Assert + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify( + gitApi.updatePullRequest( + deepEqual(expectedDetails), + "RepoID", + 10, + "Project", + ), + ).once(); + }); + + it("should call the API when both the title and description are valid and called multiple times", async (): Promise => { + // Arrange + const expectedDetails: GitPullRequest = { + description: "Description", + title: "Title", + }; + when( + gitApi.updatePullRequest( + deepEqual(expectedDetails), + "RepoID", + 10, + "Project", + ), + ).thenResolve({}); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.setTitleAndDescription("Title", "Description"); + await azureReposInvoker.setTitleAndDescription("Title", "Description"); + + // Assert + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify( + gitApi.updatePullRequest( + deepEqual(expectedDetails), + "RepoID", + 10, + "Project", + ), + ).twice(); + }); + }); +}); diff --git a/src/task/tests/repos/azureReposInvoker.spec.ts b/src/task/tests/repos/azureReposInvoker.spec.ts deleted file mode 100644 index bcf556d7d..000000000 --- a/src/task/tests/repos/azureReposInvoker.spec.ts +++ /dev/null @@ -1,1848 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT License. - */ - -import * as AssertExtensions from "../testUtilities/assertExtensions.js"; -import { - type Comment, - CommentThreadStatus, - type GitPullRequest, - type GitPullRequestCommentThread, -} from "azure-devops-node-api/interfaces/GitInterfaces.js"; -import { any, anyNumber } from "../testUtilities/mockito.js"; -import { deepEqual, instance, mock, verify, when } from "ts-mockito"; -import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; -import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; -import type CommentData from "../../src/repos/interfaces/commentData.js"; -import ErrorWithStatus from "../wrappers/errorWithStatus.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; -import type { IGitApi } from "azure-devops-node-api/GitApi.js"; -import type { IRequestHandler } from "azure-devops-node-api/interfaces/common/VsoBaseInterfaces.js"; -import Logger from "../../src/utilities/logger.js"; -import type PullRequestDetailsInterface from "../../src/repos/interfaces/pullRequestDetailsInterface.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; -import { StatusCodes } from "http-status-codes"; -import TokenManager from "../../src/repos/tokenManager.js"; -import { WebApi } from "azure-devops-node-api"; -import assert from "node:assert/strict"; -import { resolvableInstance } from "../testUtilities/resolvableInstance.js"; -import { stubEnv } from "../testUtilities/stubEnv.js"; -import { stubLocalization } from "../testUtilities/stubLocalization.js"; - -describe("azureReposInvoker.ts", (): void => { - let gitApi: IGitApi; - let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; - let gitInvoker: GitInvoker; - let logger: Logger; - let runnerInvoker: RunnerInvoker; - let tokenManager: TokenManager; - - beforeEach((): void => { - stubEnv( - ["BUILD_REPOSITORY_ID", "RepoID"], - ["PR_METRICS_ACCESS_TOKEN", "PAT"], - ["SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", "https://dev.azure.com/organization"], - ["SYSTEM_TEAMPROJECT", "Project"], - ); - - gitApi = mock(); - const requestHandler: IRequestHandler = mock(); - const webApi: WebApi = mock(WebApi); - when(webApi.getGitApi()).thenResolve(resolvableInstance(gitApi)); - - azureDevOpsApiWrapper = mock(AzureDevOpsApiWrapper); - when(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).thenReturn( - instance(requestHandler), - ); - when( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - deepEqual(instance(requestHandler)), - ), - ).thenReturn(instance(webApi)); - - gitInvoker = mock(GitInvoker); - when(gitInvoker.pullRequestId).thenReturn(10); - - logger = mock(Logger); - - runnerInvoker = mock(RunnerInvoker); - stubLocalization(runnerInvoker); - - tokenManager = mock(TokenManager); - }); - - describe("isAccessTokenAvailable()", (): void => { - it("should return null when the token exists", async (): Promise => { - // Arrange - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const result: string | null = - await azureReposInvoker.isAccessTokenAvailable(); - - // Assert - assert.equal(result, null); - }); - - it("should return a string when the token manager fails", async (): Promise => { - // Arrange - stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - when(tokenManager.getToken()).thenResolve("Failure"); - - // Act - const result: string | null = - await azureReposInvoker.isAccessTokenAvailable(); - - // Assert - assert.equal(result, "Failure"); - }); - - it("should return a string when the token does not exist", async (): Promise => { - // Arrange - stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const result: string | null = - await azureReposInvoker.isAccessTokenAvailable(); - - // Assert - assert.equal( - result, - "Could not access the Workload Identity Federation or Personal Access Token (PAT). Add the 'WorkloadIdentityFederation' input or 'PR_Metrics_Access_Token' as a secret environment variable.", - ); - }); - }); - - describe("getTitleAndDescription()", (): void => { - { - const testCases: (string | undefined)[] = [undefined, ""]; - - testCases.forEach((variable: string | undefined): void => { - it(`should throw when SYSTEM_TEAMPROJECT is set to the invalid value '${String(variable)}'`, async (): Promise => { - // Arrange - if (typeof variable === "undefined") { - stubEnv(["SYSTEM_TEAMPROJECT", undefined]); - } else { - stubEnv(["SYSTEM_TEAMPROJECT", variable]); - } - - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const func: () => Promise = async () => - azureReposInvoker.getTitleAndDescription(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - `'SYSTEM_TEAMPROJECT', accessed within 'AzureReposInvoker.getGitApi()', is invalid, null, or undefined '${String(variable)}'.`, - ); - }); - }); - } - - { - const testCases: (string | undefined)[] = [undefined, ""]; - - testCases.forEach((variable: string | undefined): void => { - it(`should throw when BUILD_REPOSITORY_ID is set to the invalid value '${String(variable)}'`, async (): Promise => { - // Arrange - if (typeof variable === "undefined") { - stubEnv(["BUILD_REPOSITORY_ID", undefined]); - } else { - stubEnv(["BUILD_REPOSITORY_ID", variable]); - } - - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const func: () => Promise = async () => - azureReposInvoker.getTitleAndDescription(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - `'BUILD_REPOSITORY_ID', accessed within 'AzureReposInvoker.getGitApi()', is invalid, null, or undefined '${String(variable)}'.`, - ); - }); - }); - } - - { - const testCases: (string | undefined)[] = [undefined, ""]; - - testCases.forEach((variable: string | undefined): void => { - it(`should throw when PR_METRICS_ACCESS_TOKEN is set to the invalid value '${String(variable)}'`, async (): Promise => { - // Arrange - if (typeof variable === "undefined") { - stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); - } else { - stubEnv(["PR_METRICS_ACCESS_TOKEN", variable]); - } - - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const func: () => Promise = async () => - azureReposInvoker.getTitleAndDescription(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - `'PR_METRICS_ACCESS_TOKEN', accessed within 'AzureReposInvoker.getGitApi()', is invalid, null, or undefined '${String(variable)}'.`, - ); - }); - }); - } - - { - const testCases: (string | undefined)[] = [undefined, ""]; - - testCases.forEach((variable: string | undefined): void => { - it(`should throw when SYSTEM_TEAMFOUNDATIONCOLLECTIONURI is set to the invalid value '${String(variable)}'`, async (): Promise => { - // Arrange - if (typeof variable === "undefined") { - stubEnv(["SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", undefined]); - } else { - stubEnv(["SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", variable]); - } - - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const func: () => Promise = async () => - azureReposInvoker.getTitleAndDescription(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - `'SYSTEM_TEAMFOUNDATIONCOLLECTIONURI', accessed within 'AzureReposInvoker.getGitApi()', is invalid, null, or undefined '${String(variable)}'.`, - ); - verify( - azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), - ).once(); - }); - }); - } - - { - const testCases: StatusCodes[] = [ - StatusCodes.UNAUTHORIZED, - StatusCodes.FORBIDDEN, - StatusCodes.NOT_FOUND, - ]; - - testCases.forEach((statusCode: StatusCodes): void => { - it(`should throw when the access token has insufficient access and the API call returns status code '${String(statusCode)}'`, async (): Promise => { - // Arrange - const error: ErrorWithStatus = new ErrorWithStatus("Test"); - error.statusCode = statusCode; - when(gitApi.getPullRequestById(10, "Project")).thenThrow(error); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const func: () => Promise = async () => - azureReposInvoker.getTitleAndDescription(); - - // Assert - const expectedMessage: string = - statusCode === StatusCodes.NOT_FOUND - ? "The resource could not be found. Verify the repository and pull request exist." - : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; - const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( - func, - expectedMessage, - ); - assert.equal(result.internalMessage, "Test"); - verify( - azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), - ).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.getPullRequestById(10, "Project")).once(); - }); - }); - } - - it("should return the title and description when available", async (): Promise => { - // Arrange - when(gitApi.getPullRequestById(10, "Project")).thenResolve({ - description: "Description", - title: "Title", - }); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const result: PullRequestDetailsInterface = - await azureReposInvoker.getTitleAndDescription(); - - // Assert - assert.equal(result.title, "Title"); - assert.equal(result.description, "Description"); - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.getPullRequestById(10, "Project")).once(); - }); - - it("should return the title and description when available and called multiple times", async (): Promise => { - // Arrange - when(gitApi.getPullRequestById(10, "Project")).thenResolve({ - description: "Description", - title: "Title", - }); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.getTitleAndDescription(); - const result: PullRequestDetailsInterface = - await azureReposInvoker.getTitleAndDescription(); - - // Assert - assert.equal(result.title, "Title"); - assert.equal(result.description, "Description"); - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.getPullRequestById(10, "Project")).twice(); - }); - - it("should return the title when the description is unavailable", async (): Promise => { - // Arrange - when(gitApi.getPullRequestById(10, "Project")).thenResolve({ - title: "Title", - }); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const result: PullRequestDetailsInterface = - await azureReposInvoker.getTitleAndDescription(); - - // Assert - assert.equal(result.title, "Title"); - assert.equal(result.description, null); - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.getPullRequestById(10, "Project")).once(); - }); - - it("should throw when the title is unavailable", async (): Promise => { - // Arrange - when(gitApi.getPullRequestById(10, "Project")).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const func: () => Promise = async () => - azureReposInvoker.getTitleAndDescription(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - "'title', accessed within 'AzureReposInvoker.getTitleAndDescription()', is invalid, null, or undefined 'undefined'.", - ); - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.getPullRequestById(10, "Project")).once(); - }); - }); - - describe("getComments()", (): void => { - { - const testCases: StatusCodes[] = [ - StatusCodes.UNAUTHORIZED, - StatusCodes.FORBIDDEN, - StatusCodes.NOT_FOUND, - ]; - - testCases.forEach((statusCode: StatusCodes): void => { - it(`should throw when the access token has insufficient access and the API call returns status code '${String(statusCode)}'`, async (): Promise => { - // Arrange - const error: ErrorWithStatus = new ErrorWithStatus("Test"); - error.statusCode = statusCode; - when(gitApi.getThreads("RepoID", 10, "Project")).thenThrow(error); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const func: () => Promise = async () => - azureReposInvoker.getComments(); - - // Assert - const expectedMessage: string = - statusCode === StatusCodes.NOT_FOUND - ? "The resource could not be found. Verify the repository and pull request exist." - : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; - const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( - func, - expectedMessage, - ); - assert.equal(result.internalMessage, "Test"); - verify( - azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), - ).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.getThreads("RepoID", 10, "Project")).once(); - }); - }); - } - - it("should return the result when called with a pull request comment whose thread context is undefined", async (): Promise => { - // Arrange - when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ - { comments: [{ content: "Content" }], id: 1, status: 1 }, - ]); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const result: CommentData = await azureReposInvoker.getComments(); - - // Assert - assert.equal(result.pullRequestComments.length, 1); - assert.equal(result.pullRequestComments[0]?.id, 1); - assert.equal(result.pullRequestComments[0].content, "Content"); - assert.equal( - result.pullRequestComments[0].status, - CommentThreadStatus.Active, - ); - assert.equal(result.fileComments.length, 0); - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.getThreads("RepoID", 10, "Project")).once(); - }); - - it("should return the result when called with a pull request comment whose thread context is null", async (): Promise => { - // Arrange - when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ - { - comments: [{ content: "Content" }], - id: 1, - status: 1, - threadContext: null as unknown as undefined, - }, - ]); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const result: CommentData = await azureReposInvoker.getComments(); - - // Assert - assert.equal(result.pullRequestComments.length, 1); - assert.equal(result.pullRequestComments[0]?.id, 1); - assert.equal(result.pullRequestComments[0].content, "Content"); - assert.equal( - result.pullRequestComments[0].status, - CommentThreadStatus.Active, - ); - assert.equal(result.fileComments.length, 0); - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.getThreads("RepoID", 10, "Project")).once(); - }); - - it("should return the result when called with a file comment", async (): Promise => { - // Arrange - when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ - { - comments: [{ content: "Content" }], - id: 1, - status: 1, - threadContext: { filePath: "/file.ts" }, - }, - ]); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const result: CommentData = await azureReposInvoker.getComments(); - - // Assert - assert.equal(result.pullRequestComments.length, 0); - assert.equal(result.fileComments.length, 1); - assert.equal(result.fileComments[0]?.id, 1); - assert.equal(result.fileComments[0].content, "Content"); - assert.equal(result.fileComments[0].status, CommentThreadStatus.Active); - assert.equal(result.fileComments[0].fileName, "file.ts"); - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.getThreads("RepoID", 10, "Project")).once(); - }); - - it("should return the result when called with both a pull request and file comment", async (): Promise => { - // Arrange - when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ - { comments: [{ content: "PR Content" }], id: 1, status: 1 }, - { - comments: [{ content: "File Content" }], - id: 2, - status: 1, - threadContext: { filePath: "/file.ts" }, - }, - ]); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const result: CommentData = await azureReposInvoker.getComments(); - - // Assert - assert.equal(result.pullRequestComments.length, 1); - assert.equal(result.pullRequestComments[0]?.id, 1); - assert.equal(result.pullRequestComments[0].content, "PR Content"); - assert.equal( - result.pullRequestComments[0].status, - CommentThreadStatus.Active, - ); - assert.equal(result.fileComments.length, 1); - assert.equal(result.fileComments[0]?.id, 2); - assert.equal(result.fileComments[0].content, "File Content"); - assert.equal(result.fileComments[0].status, CommentThreadStatus.Active); - assert.equal(result.fileComments[0].fileName, "file.ts"); - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.getThreads("RepoID", 10, "Project")).once(); - }); - - it("should return the result when called multiple times", async (): Promise => { - // Arrange - when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ - { comments: [{ content: "Content" }], id: 1, status: 1 }, - ]); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.getComments(); - const result: CommentData = await azureReposInvoker.getComments(); - - // Assert - assert.equal(result.pullRequestComments.length, 1); - assert.equal(result.pullRequestComments[0]?.id, 1); - assert.equal(result.pullRequestComments[0].content, "Content"); - assert.equal( - result.pullRequestComments[0].status, - CommentThreadStatus.Active, - ); - assert.equal(result.fileComments.length, 0); - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.getThreads("RepoID", 10, "Project")).twice(); - }); - - it("should throw when provided with a payload with no ID", async (): Promise => { - // Arrange - when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ - { comments: [{ content: "Content" }], status: 1 }, - ]); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const func: () => Promise = async () => - azureReposInvoker.getComments(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - "'commentThread[0].id', accessed within 'AzureReposInvoker.convertPullRequestComments()', is invalid, null, or undefined 'undefined'.", - ); - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.getThreads("RepoID", 10, "Project")).once(); - }); - - it("should continue if the payload has no status", async (): Promise => { - // Arrange - const getThreadsResult: GitPullRequestCommentThread[] = [ - { comments: [{ content: "PR Content" }], id: 1 }, - { - comments: [{ content: "File Content" }], - id: 2, - threadContext: { filePath: "/file.ts" }, - }, - ]; - when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve( - getThreadsResult, - ); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const result: CommentData = await azureReposInvoker.getComments(); - - // Assert - assert.equal(result.pullRequestComments.length, 1); - assert.equal(result.pullRequestComments[0]?.id, 1); - assert.equal(result.pullRequestComments[0].content, "PR Content"); - assert.equal( - result.pullRequestComments[0].status, - CommentThreadStatus.Unknown, - ); - assert.equal(result.fileComments.length, 1); - assert.equal(result.fileComments[0]?.id, 2); - assert.equal(result.fileComments[0].content, "File Content"); - assert.equal(result.fileComments[0].status, CommentThreadStatus.Unknown); - assert.equal(result.fileComments[0].fileName, "file.ts"); - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.getThreads("RepoID", 10, "Project")).once(); - }); - - { - const testCases: GitPullRequestCommentThread[] = [ - { id: 1, status: 1 }, - { comments: [], id: 1, status: 1 }, - { comments: [{}], id: 1, status: 1 }, - { comments: [{ content: "" }], id: 1, status: 1 }, - { - comments: [{ content: "Content" }], - id: 1, - status: 1, - threadContext: {}, - }, - { - comments: [{ content: "Content" }], - id: 1, - status: 1, - threadContext: { filePath: "" }, - }, - { - comments: [{ content: "Content" }], - id: 1, - status: 1, - threadContext: { filePath: "/" }, - }, - ]; - - testCases.forEach((commentThread: GitPullRequestCommentThread): void => { - it(`should skip the comment with the malformed payload '${JSON.stringify(commentThread)}'`, async (): Promise => { - // Arrange - const getThreadsResult: GitPullRequestCommentThread[] = [ - commentThread, - { comments: [{ content: "PR Content" }], id: 2, status: 1 }, - { - comments: [{ content: "File Content" }], - id: 3, - status: 1, - threadContext: { filePath: "/file.ts" }, - }, - ]; - when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve( - getThreadsResult, - ); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const result: CommentData = await azureReposInvoker.getComments(); - - // Assert - assert.equal(result.pullRequestComments.length, 1); - assert.equal(result.pullRequestComments[0]?.id, 2); - assert.equal(result.pullRequestComments[0].content, "PR Content"); - assert.equal( - result.pullRequestComments[0].status, - CommentThreadStatus.Active, - ); - assert.equal(result.fileComments.length, 1); - assert.equal(result.fileComments[0]?.id, 3); - assert.equal(result.fileComments[0].content, "File Content"); - assert.equal( - result.fileComments[0].status, - CommentThreadStatus.Active, - ); - assert.equal(result.fileComments[0].fileName, "file.ts"); - verify( - azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), - ).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.getThreads("RepoID", 10, "Project")).once(); - }); - }); - } - }); - - describe("setTitleAndDescription()", (): void => { - { - const testCases: StatusCodes[] = [ - StatusCodes.UNAUTHORIZED, - StatusCodes.FORBIDDEN, - StatusCodes.NOT_FOUND, - ]; - - testCases.forEach((statusCode: StatusCodes): void => { - it(`should throw when the access token has insufficient access and the API call returns status code '${String(statusCode)}'`, async (): Promise => { - // Arrange - const error: ErrorWithStatus = new ErrorWithStatus("Test"); - error.statusCode = statusCode; - when( - gitApi.updatePullRequest(any(), "RepoID", 10, "Project"), - ).thenThrow(error); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const func: () => Promise = async () => - azureReposInvoker.setTitleAndDescription("Title", "Description"); - - // Assert - const expectedMessage: string = - statusCode === StatusCodes.NOT_FOUND - ? "The resource could not be found. Verify the repository and pull request exist." - : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; - const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( - func, - expectedMessage, - ); - assert.equal(result.internalMessage, "Test"); - verify( - azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), - ).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify( - gitApi.updatePullRequest(any(), "RepoID", 10, "Project"), - ).once(); - }); - }); - } - - it("should not call the API when the title and description are null", async (): Promise => { - // Arrange - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.setTitleAndDescription(null, null); - - // Assert - verify( - azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), - ).never(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).never(); - verify(gitApi.updatePullRequest(any(), "RepoID", 10, "Project")).never(); - }); - - it("should call the API when the title is valid", async (): Promise => { - // Arrange - const expectedDetails: GitPullRequest = { - title: "Title", - }; - when( - gitApi.updatePullRequest( - deepEqual(expectedDetails), - "RepoID", - 10, - "Project", - ), - ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.setTitleAndDescription("Title", null); - - // Assert - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify( - gitApi.updatePullRequest( - deepEqual(expectedDetails), - "RepoID", - 10, - "Project", - ), - ).once(); - }); - - it("should call the API when the description is valid", async (): Promise => { - // Arrange - const expectedDetails: GitPullRequest = { - description: "Description", - }; - when( - gitApi.updatePullRequest( - deepEqual(expectedDetails), - "RepoID", - 10, - "Project", - ), - ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.setTitleAndDescription(null, "Description"); - - // Assert - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify( - gitApi.updatePullRequest( - deepEqual(expectedDetails), - "RepoID", - 10, - "Project", - ), - ).once(); - }); - - it("should call the API when both the title and description are valid", async (): Promise => { - // Arrange - const expectedDetails: GitPullRequest = { - description: "Description", - title: "Title", - }; - when( - gitApi.updatePullRequest( - deepEqual(expectedDetails), - "RepoID", - 10, - "Project", - ), - ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.setTitleAndDescription("Title", "Description"); - - // Assert - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify( - gitApi.updatePullRequest( - deepEqual(expectedDetails), - "RepoID", - 10, - "Project", - ), - ).once(); - }); - - it("should call the API when both the title and description are valid and called multiple times", async (): Promise => { - // Arrange - const expectedDetails: GitPullRequest = { - description: "Description", - title: "Title", - }; - when( - gitApi.updatePullRequest( - deepEqual(expectedDetails), - "RepoID", - 10, - "Project", - ), - ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.setTitleAndDescription("Title", "Description"); - await azureReposInvoker.setTitleAndDescription("Title", "Description"); - - // Assert - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify( - gitApi.updatePullRequest( - deepEqual(expectedDetails), - "RepoID", - 10, - "Project", - ), - ).twice(); - }); - }); - - describe("createComment()", (): void => { - { - const testCases: StatusCodes[] = [ - StatusCodes.UNAUTHORIZED, - StatusCodes.FORBIDDEN, - StatusCodes.NOT_FOUND, - ]; - - testCases.forEach((statusCode: StatusCodes): void => { - it(`should throw when the access token has insufficient access and the API call returns status code '${String(statusCode)}'`, async (): Promise => { - // Arrange - const error: ErrorWithStatus = new ErrorWithStatus("Test"); - error.statusCode = statusCode; - when(gitApi.createThread(any(), "RepoID", 10, "Project")).thenThrow( - error, - ); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const func: () => Promise = async () => - azureReposInvoker.createComment( - "Comment Content", - "file.ts", - CommentThreadStatus.Active, - ); - - // Assert - const expectedMessage: string = - statusCode === StatusCodes.NOT_FOUND - ? "The resource could not be found. Verify the repository and pull request exist." - : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; - const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( - func, - expectedMessage, - ); - assert.equal(result.internalMessage, "Test"); - verify( - azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), - ).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.createThread(any(), "RepoID", 10, "Project")).once(); - }); - }); - } - - it("should call the API for no file", async (): Promise => { - // Arrange - const expectedComment: GitPullRequestCommentThread = { - comments: [{ content: "Comment Content" }], - status: CommentThreadStatus.Active, - }; - when( - gitApi.createThread( - deepEqual(expectedComment), - "RepoID", - 10, - "Project", - ), - ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.createComment( - "Comment Content", - null, - CommentThreadStatus.Active, - ); - - // Assert - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify( - gitApi.createThread( - deepEqual(expectedComment), - "RepoID", - 10, - "Project", - ), - ).once(); - }); - - it("should call the API for no file when called multiple times", async (): Promise => { - // Arrange - const expectedComment: GitPullRequestCommentThread = { - comments: [{ content: "Comment Content" }], - status: CommentThreadStatus.Active, - }; - when( - gitApi.createThread( - deepEqual(expectedComment), - "RepoID", - 10, - "Project", - ), - ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.createComment( - "Comment Content", - null, - CommentThreadStatus.Active, - ); - await azureReposInvoker.createComment( - "Comment Content", - null, - CommentThreadStatus.Active, - ); - - // Assert - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify( - gitApi.createThread( - deepEqual(expectedComment), - "RepoID", - 10, - "Project", - ), - ).twice(); - }); - - it("should call the API for a file", async (): Promise => { - // Arrange - const expectedComment: GitPullRequestCommentThread = { - comments: [{ content: "Comment Content" }], - status: CommentThreadStatus.Active, - threadContext: { - filePath: "/file.ts", - rightFileEnd: { - line: 1, - offset: 2, - }, - rightFileStart: { - line: 1, - offset: 1, - }, - }, - }; - when( - gitApi.createThread( - deepEqual(expectedComment), - "RepoID", - 10, - "Project", - ), - ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.createComment( - "Comment Content", - "file.ts", - CommentThreadStatus.Active, - ); - - // Assert - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify( - gitApi.createThread( - deepEqual(expectedComment), - "RepoID", - 10, - "Project", - ), - ).once(); - }); - - it("should call the API for a deleted file", async (): Promise => { - // Arrange - const expectedComment: GitPullRequestCommentThread = { - comments: [{ content: "Comment Content" }], - status: CommentThreadStatus.Active, - threadContext: { - filePath: "/file.ts", - leftFileEnd: { - line: 1, - offset: 2, - }, - leftFileStart: { - line: 1, - offset: 1, - }, - }, - }; - when( - gitApi.createThread( - deepEqual(expectedComment), - "RepoID", - 10, - "Project", - ), - ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.createComment( - "Comment Content", - "file.ts", - CommentThreadStatus.Active, - true, - ); - - // Assert - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify( - gitApi.createThread( - deepEqual(expectedComment), - "RepoID", - 10, - "Project", - ), - ).once(); - }); - }); - - describe("updateComment()", (): void => { - { - const testCases: StatusCodes[] = [ - StatusCodes.UNAUTHORIZED, - StatusCodes.FORBIDDEN, - StatusCodes.NOT_FOUND, - ]; - - testCases.forEach((statusCode: StatusCodes): void => { - it(`should throw when the access token has insufficient access for the updateComment API and the API call returns status code '${String(statusCode)}'`, async (): Promise => { - // Arrange - const error: ErrorWithStatus = new ErrorWithStatus("Test"); - error.statusCode = statusCode; - when( - gitApi.updateComment(any(), "RepoID", 10, 20, 1, "Project"), - ).thenThrow(error); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const func: () => Promise = async () => - azureReposInvoker.updateComment( - 20, - "Content", - CommentThreadStatus.Active, - ); - - // Assert - const expectedMessage: string = - statusCode === StatusCodes.NOT_FOUND - ? "The resource could not be found. Verify the repository and pull request exist." - : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; - const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( - func, - expectedMessage, - ); - assert.equal(result.internalMessage, "Test"); - verify( - azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), - ).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify( - gitApi.updateComment(any(), "RepoID", 10, 20, 1, "Project"), - ).once(); - }); - }); - } - - { - const testCases: StatusCodes[] = [ - StatusCodes.UNAUTHORIZED, - StatusCodes.FORBIDDEN, - StatusCodes.NOT_FOUND, - ]; - - testCases.forEach((status: StatusCodes): void => { - it(`should throw when the access token has insufficient access for the updateComment API and the API call returns status '${String(status)}'`, async (): Promise => { - // Arrange - const error: ErrorWithStatus = new ErrorWithStatus("Test"); - error.status = status; - when( - gitApi.updateComment(any(), "RepoID", 10, 20, 1, "Project"), - ).thenResolve({}); - when( - gitApi.updateThread(any(), "RepoID", 10, 20, "Project"), - ).thenThrow(error); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const func: () => Promise = async () => - azureReposInvoker.updateComment( - 20, - "Content", - CommentThreadStatus.Active, - ); - - // Assert - const expectedMessage: string = - status === StatusCodes.NOT_FOUND - ? "The resource could not be found. Verify the repository and pull request exist." - : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; - const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( - func, - expectedMessage, - ); - assert.equal(result.internalMessage, "Test"); - verify( - azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), - ).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify( - gitApi.updateComment(any(), "RepoID", 10, 20, 1, "Project"), - ).once(); - verify( - gitApi.updateThread(any(), "RepoID", 10, 20, "Project"), - ).once(); - }); - }); - } - - it("should call the APIs when both the comment content and the thread status are updated", async (): Promise => { - // Arrange - const expectedComment: Comment = { - content: "Content", - }; - when( - gitApi.updateComment( - deepEqual(expectedComment), - "RepoID", - 10, - 20, - 1, - "Project", - ), - ).thenResolve({}); - const expectedCommentThread: GitPullRequestCommentThread = { - status: CommentThreadStatus.Active, - }; - when( - gitApi.updateThread( - deepEqual(expectedCommentThread), - "RepoID", - 10, - 20, - "Project", - ), - ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.updateComment( - 20, - "Content", - CommentThreadStatus.Active, - ); - - // Assert - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify( - gitApi.updateComment( - deepEqual(expectedComment), - "RepoID", - 10, - 20, - 1, - "Project", - ), - ).once(); - verify( - gitApi.updateThread( - deepEqual(expectedCommentThread), - "RepoID", - 10, - 20, - "Project", - ), - ).once(); - }); - - it("should call the API when the comment content is updated", async (): Promise => { - // Arrange - const expectedComment: Comment = { - content: "Content", - }; - when( - gitApi.updateComment( - deepEqual(expectedComment), - "RepoID", - 10, - 20, - 1, - "Project", - ), - ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.updateComment(20, "Content", null); - - // Assert - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify( - gitApi.updateComment( - deepEqual(expectedComment), - "RepoID", - 10, - 20, - 1, - "Project", - ), - ).once(); - }); - - it("should call the API when the thread status is updated", async (): Promise => { - // Arrange - const expectedComment: GitPullRequestCommentThread = { - status: CommentThreadStatus.Active, - }; - when( - gitApi.updateThread( - deepEqual(expectedComment), - "RepoID", - 10, - 20, - "Project", - ), - ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.updateComment( - 20, - null, - CommentThreadStatus.Active, - ); - - // Assert - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify( - gitApi.updateThread( - deepEqual(expectedComment), - "RepoID", - 10, - 20, - "Project", - ), - ).once(); - }); - - it("should call no APIs when neither the comment content nor the thread status are updated", async (): Promise => { - // Arrange - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.updateComment(20, null, null); - - // Assert - verify( - azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), - ).never(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).never(); - verify( - gitApi.updateComment(any(), "RepoID", 10, 20, 1, "Project"), - ).never(); - verify(gitApi.updateThread(any(), "RepoID", 10, 20, "Project")).never(); - }); - - it("should call the API when called multiple times", async (): Promise => { - // Arrange - const expectedComment: Comment = { - content: "Content", - }; - when( - gitApi.updateComment( - deepEqual(expectedComment), - "RepoID", - 10, - 20, - 1, - "Project", - ), - ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.updateComment(20, "Content", null); - await azureReposInvoker.updateComment(20, "Content", null); - - // Assert - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify( - gitApi.updateComment( - deepEqual(expectedComment), - "RepoID", - 10, - 20, - 1, - "Project", - ), - ).twice(); - }); - }); - - describe("deleteCommentThread()", (): void => { - { - const testCases: StatusCodes[] = [ - StatusCodes.UNAUTHORIZED, - StatusCodes.FORBIDDEN, - StatusCodes.NOT_FOUND, - ]; - - testCases.forEach((statusCode: StatusCodes): void => { - it(`should throw when the access token has insufficient access and the API call returns status code '${String(statusCode)}'`, async (): Promise => { - // Arrange - const error: ErrorWithStatus = new ErrorWithStatus("Test"); - error.statusCode = statusCode; - when(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).thenThrow( - error, - ); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - const func: () => Promise = async () => - azureReposInvoker.deleteCommentThread(20); - - // Assert - const expectedMessage: string = - statusCode === StatusCodes.NOT_FOUND - ? "The resource could not be found. Verify the repository and pull request exist." - : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; - const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( - func, - expectedMessage, - ); - assert.equal(result.internalMessage, "Test"); - verify( - azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), - ).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).once(); - }); - }); - } - - it("should call the API for a single comment", async (): Promise => { - // Arrange - when(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).thenResolve(); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.deleteCommentThread(20); - - // Assert - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).once(); - }); - - it("should call the API when called multiple times", async (): Promise => { - // Arrange - when( - gitApi.deleteComment("RepoID", 10, anyNumber(), 1, "Project"), - ).thenResolve(); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); - - // Act - await azureReposInvoker.deleteCommentThread(20); - await azureReposInvoker.deleteCommentThread(30); - - // Assert - verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); - verify( - azureDevOpsApiWrapper.getWebApiInstance( - "https://dev.azure.com/organization", - any(), - ), - ).once(); - verify(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).once(); - verify(gitApi.deleteComment("RepoID", 10, 30, 1, "Project")).once(); - }); - }); -}); diff --git a/src/task/tests/repos/azureReposInvoker.updateComment.spec.ts b/src/task/tests/repos/azureReposInvoker.updateComment.spec.ts new file mode 100644 index 000000000..69744fdbb --- /dev/null +++ b/src/task/tests/repos/azureReposInvoker.updateComment.spec.ts @@ -0,0 +1,408 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as AssertExtensions from "../testUtilities/assertExtensions.js"; +import { type Comment, CommentThreadStatus, type GitPullRequestCommentThread } from "azure-devops-node-api/interfaces/GitInterfaces.js"; +import { + deepEqual, + instance, + verify, + when, +} from "ts-mockito"; +import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; +import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; +import ErrorWithStatus from "../wrappers/errorWithStatus.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import type { IGitApi } from "azure-devops-node-api/GitApi.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import { StatusCodes } from "http-status-codes"; +import TokenManager from "../../src/repos/tokenManager.js"; +import { any } from "../testUtilities/mockito.js"; +import assert from "node:assert/strict"; +import { createAzureReposInvokerMocks } from "./azureReposInvokerTestSetup.js"; + +describe("azureReposInvoker.ts", (): void => { + let gitApi: IGitApi; + let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; + let gitInvoker: GitInvoker; + let logger: Logger; + let runnerInvoker: RunnerInvoker; + let tokenManager: TokenManager; + + beforeEach((): void => { + ({ + gitApi, + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + } = createAzureReposInvokerMocks()); + }); + + describe("updateComment()", (): void => { + { + const testCases: StatusCodes[] = [ + StatusCodes.UNAUTHORIZED, + StatusCodes.FORBIDDEN, + StatusCodes.NOT_FOUND, + ]; + + testCases.forEach((statusCode: StatusCodes): void => { + it(`should throw when the access token has insufficient access for the updateComment API and the API call returns status code '${String(statusCode)}'`, async (): Promise => { + // Arrange + const error: ErrorWithStatus = new ErrorWithStatus("Test"); + error.statusCode = statusCode; + when( + gitApi.updateComment(any(), "RepoID", 10, 20, 1, "Project"), + ).thenThrow(error); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const func: () => Promise = async () => + azureReposInvoker.updateComment( + 20, + "Content", + CommentThreadStatus.Active, + ); + + // Assert + const expectedMessage: string = + statusCode === StatusCodes.NOT_FOUND + ? "The resource could not be found. Verify the repository and pull request exist." + : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; + const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( + func, + expectedMessage, + ); + assert.equal(result.internalMessage, "Test"); + verify( + azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), + ).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify( + gitApi.updateComment(any(), "RepoID", 10, 20, 1, "Project"), + ).once(); + }); + }); + } + + { + const testCases: StatusCodes[] = [ + StatusCodes.UNAUTHORIZED, + StatusCodes.FORBIDDEN, + StatusCodes.NOT_FOUND, + ]; + + testCases.forEach((status: StatusCodes): void => { + it(`should throw when the access token has insufficient access for the updateComment API and the API call returns status '${String(status)}'`, async (): Promise => { + // Arrange + const error: ErrorWithStatus = new ErrorWithStatus("Test"); + error.status = status; + when( + gitApi.updateComment(any(), "RepoID", 10, 20, 1, "Project"), + ).thenResolve({}); + when( + gitApi.updateThread(any(), "RepoID", 10, 20, "Project"), + ).thenThrow(error); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + const func: () => Promise = async () => + azureReposInvoker.updateComment( + 20, + "Content", + CommentThreadStatus.Active, + ); + + // Assert + const expectedMessage: string = + status === StatusCodes.NOT_FOUND + ? "The resource could not be found. Verify the repository and pull request exist." + : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has access to 'Code' > 'Read & write' and 'Pull Request Threads' > 'Read & write'."; + const result: ErrorWithStatus = await AssertExtensions.toThrowAsync( + func, + expectedMessage, + ); + assert.equal(result.internalMessage, "Test"); + verify( + azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), + ).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify( + gitApi.updateComment(any(), "RepoID", 10, 20, 1, "Project"), + ).once(); + verify( + gitApi.updateThread(any(), "RepoID", 10, 20, "Project"), + ).once(); + }); + }); + } + + it("should call the APIs when both the comment content and the thread status are updated", async (): Promise => { + // Arrange + const expectedComment: Comment = { + content: "Content", + }; + when( + gitApi.updateComment( + deepEqual(expectedComment), + "RepoID", + 10, + 20, + 1, + "Project", + ), + ).thenResolve({}); + const expectedCommentThread: GitPullRequestCommentThread = { + status: CommentThreadStatus.Active, + }; + when( + gitApi.updateThread( + deepEqual(expectedCommentThread), + "RepoID", + 10, + 20, + "Project", + ), + ).thenResolve({}); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.updateComment( + 20, + "Content", + CommentThreadStatus.Active, + ); + + // Assert + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify( + gitApi.updateComment( + deepEqual(expectedComment), + "RepoID", + 10, + 20, + 1, + "Project", + ), + ).once(); + verify( + gitApi.updateThread( + deepEqual(expectedCommentThread), + "RepoID", + 10, + 20, + "Project", + ), + ).once(); + }); + + it("should call the API when the comment content is updated", async (): Promise => { + // Arrange + const expectedComment: Comment = { + content: "Content", + }; + when( + gitApi.updateComment( + deepEqual(expectedComment), + "RepoID", + 10, + 20, + 1, + "Project", + ), + ).thenResolve({}); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.updateComment(20, "Content", null); + + // Assert + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify( + gitApi.updateComment( + deepEqual(expectedComment), + "RepoID", + 10, + 20, + 1, + "Project", + ), + ).once(); + }); + + it("should call the API when the thread status is updated", async (): Promise => { + // Arrange + const expectedComment: GitPullRequestCommentThread = { + status: CommentThreadStatus.Active, + }; + when( + gitApi.updateThread( + deepEqual(expectedComment), + "RepoID", + 10, + 20, + "Project", + ), + ).thenResolve({}); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.updateComment( + 20, + null, + CommentThreadStatus.Active, + ); + + // Assert + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify( + gitApi.updateThread( + deepEqual(expectedComment), + "RepoID", + 10, + 20, + "Project", + ), + ).once(); + }); + + it("should call no APIs when neither the comment content nor the thread status are updated", async (): Promise => { + // Arrange + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.updateComment(20, null, null); + + // Assert + verify( + azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT"), + ).never(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).never(); + verify( + gitApi.updateComment(any(), "RepoID", 10, 20, 1, "Project"), + ).never(); + verify(gitApi.updateThread(any(), "RepoID", 10, 20, "Project")).never(); + }); + + it("should call the API when called multiple times", async (): Promise => { + // Arrange + const expectedComment: Comment = { + content: "Content", + }; + when( + gitApi.updateComment( + deepEqual(expectedComment), + "RepoID", + 10, + 20, + 1, + "Project", + ), + ).thenResolve({}); + const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); + + // Act + await azureReposInvoker.updateComment(20, "Content", null); + await azureReposInvoker.updateComment(20, "Content", null); + + // Assert + verify(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).once(); + verify( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + any(), + ), + ).once(); + verify( + gitApi.updateComment( + deepEqual(expectedComment), + "RepoID", + 10, + 20, + 1, + "Project", + ), + ).twice(); + }); + }); +}); diff --git a/src/task/tests/repos/azureReposInvokerTestSetup.ts b/src/task/tests/repos/azureReposInvokerTestSetup.ts new file mode 100644 index 000000000..70e429ef0 --- /dev/null +++ b/src/task/tests/repos/azureReposInvokerTestSetup.ts @@ -0,0 +1,79 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import { deepEqual, instance, mock, when } from "ts-mockito"; +import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import type { IGitApi } from "azure-devops-node-api/GitApi.js"; +import type { IRequestHandler } from "azure-devops-node-api/interfaces/common/VsoBaseInterfaces.js"; +import Logger from "../../src/utilities/logger.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import TokenManager from "../../src/repos/tokenManager.js"; +import { WebApi } from "azure-devops-node-api"; +import { resolvableInstance } from "../testUtilities/resolvableInstance.js"; +import { stubEnv } from "../testUtilities/stubEnv.js"; +import { stubLocalization } from "../testUtilities/stubLocalization.js"; + +export interface AzureReposInvokerMocks { + gitApi: IGitApi; + azureDevOpsApiWrapper: AzureDevOpsApiWrapper; + gitInvoker: GitInvoker; + logger: Logger; + runnerInvoker: RunnerInvoker; + tokenManager: TokenManager; +} + +/** + * Creates the mocks and environment variable stubs required by + * `azureReposInvoker.ts` tests. Individual tests can override any stub after + * calling this helper. + * @returns The paired mocks. + */ +export const createAzureReposInvokerMocks = (): AzureReposInvokerMocks => { + stubEnv( + ["BUILD_REPOSITORY_ID", "RepoID"], + ["PR_METRICS_ACCESS_TOKEN", "PAT"], + ["SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", "https://dev.azure.com/organization"], + ["SYSTEM_TEAMPROJECT", "Project"], + ); + + const gitApi: IGitApi = mock(); + const requestHandler: IRequestHandler = mock(); + const webApi: WebApi = mock(WebApi); + when(webApi.getGitApi()).thenResolve(resolvableInstance(gitApi)); + + const azureDevOpsApiWrapper: AzureDevOpsApiWrapper = mock( + AzureDevOpsApiWrapper, + ); + when(azureDevOpsApiWrapper.getPersonalAccessTokenHandler("PAT")).thenReturn( + instance(requestHandler), + ); + when( + azureDevOpsApiWrapper.getWebApiInstance( + "https://dev.azure.com/organization", + deepEqual(instance(requestHandler)), + ), + ).thenReturn(instance(webApi)); + + const pullRequestId = 10; + const gitInvoker: GitInvoker = mock(GitInvoker); + when(gitInvoker.pullRequestId).thenReturn(pullRequestId); + + const logger: Logger = mock(Logger); + + const runnerInvoker: RunnerInvoker = mock(RunnerInvoker); + stubLocalization(runnerInvoker); + + const tokenManager: TokenManager = mock(TokenManager); + + return { + azureDevOpsApiWrapper, + gitApi, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + }; +}; diff --git a/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts b/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts new file mode 100644 index 000000000..ab81747e8 --- /dev/null +++ b/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts @@ -0,0 +1,489 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as AssertExtensions from "../testUtilities/assertExtensions.js"; +import * as GitHubReposInvokerConstants from "./gitHubReposInvokerConstants.js"; +import { any, anyNumber, anyString } from "../testUtilities/mockito.js"; +import { createGitHubReposInvokerMocks, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { instance, verify, when } from "ts-mockito"; +import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import HttpError from "../testUtilities/httpError.js"; +import Logger from "../../src/utilities/logger.js"; +import type { OctokitOptions } from "@octokit/core"; +import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; +import type { RequestError } from "@octokit/request-error"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import { StatusCodes } from "http-status-codes"; +import assert from "node:assert/strict"; +import { createRequestError } from "../testUtilities/createRequestError.js"; + +describe("gitHubReposInvoker.ts", (): void => { + let gitInvoker: GitInvoker; + let logger: Logger; + let octokitWrapper: OctokitWrapper; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + } = createGitHubReposInvokerMocks()); + }); + + describe("createComment()", (): void => { + it("should succeed when a file name is specified", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + await gitHubReposInvoker.createComment("Content", "file.ts"); + + // Assert + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), + ).once(); + verify( + octokitWrapper.createReviewComment( + "microsoft", + "PR-Metrics", + 12345, + "Content", + "file.ts", + "sha54321", + ), + ).once(); + }); + + it("should throw when the commit list is empty", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + when( + octokitWrapper.listCommits( + anyString(), + anyString(), + anyNumber(), + anyNumber(), + ), + ).thenResolve({ + data: [], + headers: {}, + status: StatusCodes.OK, + url: "", + }); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + gitHubReposInvoker.createComment("Content", "file.ts"); + + // Assert + await AssertExtensions.toThrowAsync( + func, + "'result.data[-1].sha', accessed within 'GitHubReposInvoker.getCommitId()', is invalid, null, or undefined 'undefined'.", + ); + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), + ).once(); + }); + + it("should succeed when there are multiple pages of commits", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + when( + octokitWrapper.listCommits(anyString(), anyString(), anyNumber(), 1), + ).thenResolve({ + data: [], + headers: { + link: '; rel="next", ; rel="last"', + }, + status: StatusCodes.OK, + url: "", + }); + when( + octokitWrapper.listCommits(anyString(), anyString(), anyNumber(), 24), + ).thenResolve(GitHubReposInvokerConstants.listCommitsResponse); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + await gitHubReposInvoker.createComment("Content", "file.ts"); + + // Assert + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), + ).once(); + verify( + octokitWrapper.createReviewComment( + "microsoft", + "PR-Metrics", + 12345, + "Content", + "file.ts", + "sha54321", + ), + ).once(); + }); + + it("should throw when the link header does not match the expected format", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + when( + octokitWrapper.listCommits(anyString(), anyString(), anyNumber(), 1), + ).thenResolve({ + data: [], + headers: { + link: "non-matching", + }, + status: StatusCodes.OK, + url: "", + }); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + gitHubReposInvoker.createComment("Content", "file.ts"); + + // Assert + await AssertExtensions.toThrowAsync( + func, + "The regular expression did not match 'non-matching'.", + ); + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), + ).once(); + }); + + it("should succeed when a file name is specified and the method is called twice", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + await gitHubReposInvoker.createComment("Content", "file.ts"); + await gitHubReposInvoker.createComment("Content", "file.ts"); + + // Assert + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), + ).once(); + verify( + octokitWrapper.createReviewComment( + "microsoft", + "PR-Metrics", + 12345, + "Content", + "file.ts", + "sha54321", + ), + ).twice(); + }); + + it("should succeed when createReviewComment() returns undefined", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + when( + octokitWrapper.createReviewComment( + "microsoft", + "PR-Metrics", + 12345, + "Content", + "file.ts", + "sha54321", + ), + ).thenResolve(null); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + await gitHubReposInvoker.createComment("Content", "file.ts"); + + // Assert + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), + ).once(); + verify( + octokitWrapper.createReviewComment( + "microsoft", + "PR-Metrics", + 12345, + "Content", + "file.ts", + "sha54321", + ), + ).once(); + }); + + { + const testCases: string[] = [ + "file.ts is too big", + "file.ts diff is too large", + ]; + + testCases.forEach((message: string): void => { + it(`should succeed when a HTTP 422 error occurs due to: '${message}'`, async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const errorMessage = `Validation Failed: {"resource":"PullRequestReviewComment","code":"custom","field":"pull_request_review_thread.diff_entry","message":"${message}"}`; + const error: RequestError = createRequestError( + StatusCodes.UNPROCESSABLE_ENTITY, + errorMessage, + ); + when( + octokitWrapper.createReviewComment( + "microsoft", + "PR-Metrics", + 12345, + "Content", + "file.ts", + "sha54321", + ), + ).thenCall((): void => { + throw error; + }); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + await gitHubReposInvoker.createComment("Content", "file.ts"); + + // Assert + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), + ).once(); + verify( + octokitWrapper.createReviewComment( + "microsoft", + "PR-Metrics", + 12345, + "Content", + "file.ts", + "sha54321", + ), + ).once(); + verify( + logger.logInfo( + "GitHub createReviewComment() threw a 422 error related to a large diff. Ignoring as this is expected.", + ), + ).once(); + verify(logger.logErrorObject(error)).once(); + }); + }); + } + + { + const testCases: HttpError[] = [ + new HttpError( + StatusCodes.BAD_REQUEST, + 'Validation Failed: {"resource":"PullRequestReviewComment","code":"custom","field":"pull_request_review_thread.diff_entry","message":"file.ts is too big"}', + ), + new HttpError(StatusCodes.UNPROCESSABLE_ENTITY, "Unprocessable Entity"), + ]; + + testCases.forEach((error: HttpError): void => { + it("should throw when an error occurs that is not a HTTP 422 or is not due to having a too large path diff", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + when( + octokitWrapper.createReviewComment( + "microsoft", + "PR-Metrics", + 12345, + "Content", + "file.ts", + "sha54321", + ), + ).thenCall((): void => { + throw error; + }); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + gitHubReposInvoker.createComment("Content", "file.ts"); + + // Assert + await AssertExtensions.toThrowAsync(func, error.message); + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), + ).once(); + verify( + octokitWrapper.createReviewComment( + "microsoft", + "PR-Metrics", + 12345, + "Content", + "file.ts", + "sha54321", + ), + ).once(); + }); + }); + } + + it("should succeed when no file name is specified", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + await gitHubReposInvoker.createComment("Content", null); + + // Assert + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.createIssueComment( + "microsoft", + "PR-Metrics", + 12345, + "Content", + ), + ).once(); + }); + }); +}); diff --git a/src/task/tests/repos/gitHubReposInvoker.deleteCommentThread.spec.ts b/src/task/tests/repos/gitHubReposInvoker.deleteCommentThread.spec.ts new file mode 100644 index 000000000..d0ebe285b --- /dev/null +++ b/src/task/tests/repos/gitHubReposInvoker.deleteCommentThread.spec.ts @@ -0,0 +1,63 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import { createGitHubReposInvokerMocks, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { instance, verify, when } from "ts-mockito"; +import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import Logger from "../../src/utilities/logger.js"; +import type { OctokitOptions } from "@octokit/core"; +import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import { any } from "../testUtilities/mockito.js"; +import assert from "node:assert/strict"; + +describe("gitHubReposInvoker.ts", (): void => { + let gitInvoker: GitInvoker; + let logger: Logger; + let octokitWrapper: OctokitWrapper; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + } = createGitHubReposInvokerMocks()); + }); + + describe("deleteCommentThread()", (): void => { + it("should succeed", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + await gitHubReposInvoker.deleteCommentThread(54321); + + // Assert + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.deleteReviewComment("microsoft", "PR-Metrics", 54321), + ).once(); + }); + }); +}); diff --git a/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts b/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts new file mode 100644 index 000000000..a5ba8fac0 --- /dev/null +++ b/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts @@ -0,0 +1,233 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as GitHubReposInvokerConstants from "./gitHubReposInvokerConstants.js"; +import { any, anyNumber, anyString } from "../testUtilities/mockito.js"; +import { createGitHubReposInvokerMocks, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { instance, verify, when } from "ts-mockito"; +import type CommentData from "../../src/repos/interfaces/commentData.js"; +import { CommentThreadStatus } from "azure-devops-node-api/interfaces/GitInterfaces.js"; +import type GetIssueCommentsResponse from "../../src/wrappers/octokitInterfaces/getIssueCommentsResponse.js"; +import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import Logger from "../../src/utilities/logger.js"; +import type { OctokitOptions } from "@octokit/core"; +import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import assert from "node:assert/strict"; + +describe("gitHubReposInvoker.ts", (): void => { + let gitInvoker: GitInvoker; + let logger: Logger; + let octokitWrapper: OctokitWrapper; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + } = createGitHubReposInvokerMocks()); + }); + + describe("getComments()", (): void => { + it("should return the result when called with a pull request comment", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const response: GetIssueCommentsResponse = + GitHubReposInvokerConstants.getIssueCommentsResponse; + if (typeof response.data[0] === "undefined") { + throw new Error("response.data[0] is undefined"); + } + + response.data[0].body = "PR Content"; + when( + octokitWrapper.getIssueComments(anyString(), anyString(), anyNumber()), + ).thenResolve(response); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const result: CommentData = await gitHubReposInvoker.getComments(); + + // Assert + assert.equal(result.pullRequestComments.length, 1); + assert.equal(result.pullRequestComments[0]?.id, 1); + assert.equal(result.pullRequestComments[0].content, "PR Content"); + assert.equal( + result.pullRequestComments[0].status, + CommentThreadStatus.Unknown, + ); + assert.equal(result.fileComments.length, 0); + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.getIssueComments("microsoft", "PR-Metrics", 12345), + ).once(); + verify( + octokitWrapper.getReviewComments("microsoft", "PR-Metrics", 12345), + ).once(); + }); + + it("should return the result when called with a file comment", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + when( + octokitWrapper.getReviewComments(anyString(), anyString(), anyNumber()), + ).thenResolve(GitHubReposInvokerConstants.getReviewCommentsResponse); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const result: CommentData = await gitHubReposInvoker.getComments(); + + // Assert + assert.equal(result.pullRequestComments.length, 0); + assert.equal(result.fileComments.length, 1); + assert.equal(result.fileComments[0]?.id, 2); + assert.equal(result.fileComments[0].content, "File Content"); + assert.equal(result.fileComments[0].status, CommentThreadStatus.Unknown); + assert.equal(result.fileComments[0].fileName, "file.ts"); + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.getIssueComments("microsoft", "PR-Metrics", 12345), + ).once(); + verify( + octokitWrapper.getReviewComments("microsoft", "PR-Metrics", 12345), + ).once(); + }); + + it("should return the result when called with both a pull request and file comment", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const response: GetIssueCommentsResponse = + GitHubReposInvokerConstants.getIssueCommentsResponse; + if (typeof response.data[0] === "undefined") { + throw new Error("response.data[0] is undefined"); + } + + response.data[0].body = "PR Content"; + when( + octokitWrapper.getIssueComments(anyString(), anyString(), anyNumber()), + ).thenResolve(response); + when( + octokitWrapper.getReviewComments(anyString(), anyString(), anyNumber()), + ).thenResolve(GitHubReposInvokerConstants.getReviewCommentsResponse); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const result: CommentData = await gitHubReposInvoker.getComments(); + + // Assert + assert.equal(result.pullRequestComments.length, 1); + assert.equal(result.pullRequestComments[0]?.id, 1); + assert.equal(result.pullRequestComments[0].content, "PR Content"); + assert.equal( + result.pullRequestComments[0].status, + CommentThreadStatus.Unknown, + ); + assert.equal(result.fileComments.length, 1); + assert.equal(result.fileComments[0]?.id, 2); + assert.equal(result.fileComments[0].content, "File Content"); + assert.equal(result.fileComments[0].status, CommentThreadStatus.Unknown); + assert.equal(result.fileComments[0].fileName, "file.ts"); + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.getIssueComments("microsoft", "PR-Metrics", 12345), + ).once(); + verify( + octokitWrapper.getReviewComments("microsoft", "PR-Metrics", 12345), + ).once(); + }); + + it("should skip pull request comments with no body", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const response: GetIssueCommentsResponse = + GitHubReposInvokerConstants.getIssueCommentsResponse; + if (typeof response.data[0] === "undefined") { + throw new Error("response.data[0] is undefined"); + } + + response.data[0].body = undefined; + when( + octokitWrapper.getIssueComments(anyString(), anyString(), anyNumber()), + ).thenResolve(response); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const result: CommentData = await gitHubReposInvoker.getComments(); + + // Assert + assert.equal(result.pullRequestComments.length, 0); + assert.equal(result.fileComments.length, 0); + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.getIssueComments("microsoft", "PR-Metrics", 12345), + ).once(); + verify( + octokitWrapper.getReviewComments("microsoft", "PR-Metrics", 12345), + ).once(); + }); + }); +}); diff --git a/src/task/tests/repos/gitHubReposInvoker.getTitleAndDescription.spec.ts b/src/task/tests/repos/gitHubReposInvoker.getTitleAndDescription.spec.ts new file mode 100644 index 000000000..ba1dfbc8c --- /dev/null +++ b/src/task/tests/repos/gitHubReposInvoker.getTitleAndDescription.spec.ts @@ -0,0 +1,556 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as AssertExtensions from "../testUtilities/assertExtensions.js"; +import * as GitHubReposInvokerConstants from "./gitHubReposInvokerConstants.js"; +import { any, anyNumber, anyString } from "../testUtilities/mockito.js"; +import { createGitHubReposInvokerMocks, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { instance, verify, when } from "ts-mockito"; +import type ErrorWithStatusInterface from "../../src/repos/interfaces/errorWithStatusInterface.js"; +import type GetPullResponse from "../../src/wrappers/octokitInterfaces/getPullResponse.js"; +import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import Logger from "../../src/utilities/logger.js"; +import type OctokitLogObjectInterface from "../wrappers/octokitLogObjectInterface.js"; +import type { OctokitOptions } from "@octokit/core"; +import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; +import type PullRequestDetailsInterface from "../../src/repos/interfaces/pullRequestDetailsInterface.js"; +import type { RequestError } from "@octokit/request-error"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import { StatusCodes } from "http-status-codes"; +import assert from "node:assert/strict"; +import { createRequestError } from "../testUtilities/createRequestError.js"; +import { stubEnv } from "../testUtilities/stubEnv.js"; + +describe("gitHubReposInvoker.ts", (): void => { + let gitInvoker: GitInvoker; + let logger: Logger; + let octokitWrapper: OctokitWrapper; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + } = createGitHubReposInvokerMocks()); + }); + + describe("getTitleAndDescription()", (): void => { + { + const testCases: (string | undefined)[] = [undefined, ""]; + + testCases.forEach((variable: string | undefined): void => { + it(`should throw when SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI is set to the invalid value '${String(variable)}' and the task is running on Azure Pipelines`, async (): Promise => { + // Arrange + if (typeof variable === "undefined") { + stubEnv(["SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", undefined]); + } else { + stubEnv(["SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", variable]); + } + + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + gitHubReposInvoker.getTitleAndDescription(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + `'SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI', accessed within 'GitHubReposInvoker.initializeForAzureDevOps()', is invalid, null, or undefined '${String(variable)}'.`, + ); + }); + }); + } + + it("should throw when SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI is set to an invalid URL and the task is running on Azure Pipelines", async (): Promise => { + // Arrange + stubEnv( + ["SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", "https://github.com/microsoft"], + ); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + gitHubReposInvoker.getTitleAndDescription(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + "SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI 'https://github.com/microsoft' is in an unexpected format.", + ); + }); + + { + const testCases: (string | undefined)[] = [undefined, ""]; + + testCases.forEach((variable: string | undefined): void => { + it(`should throw when GITHUB_API_URL is set to the invalid value '${String(variable)}' and the task is running on GitHub`, async (): Promise => { + // Arrange + stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); + stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + if (typeof variable === "undefined") { + stubEnv(["GITHUB_API_URL", undefined]); + } else { + stubEnv(["GITHUB_API_URL", variable]); + } + + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + gitHubReposInvoker.getTitleAndDescription(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + `'GITHUB_API_URL', accessed within 'GitHubReposInvoker.initializeForGitHub()', is invalid, null, or undefined '${String(variable)}'.`, + ); + + }); + }); + } + + { + const testCases: (string | undefined)[] = [undefined, ""]; + + testCases.forEach((variable: string | undefined): void => { + it(`should throw when GITHUB_REPOSITORY_OWNER is set to the invalid value '${String(variable)}' and the task is running on GitHub`, async (): Promise => { + // Arrange + stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); + stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_API_URL", "https://api.github.com"]); + if (typeof variable === "undefined") { + stubEnv(["GITHUB_REPOSITORY_OWNER", undefined]); + } else { + stubEnv(["GITHUB_REPOSITORY_OWNER", variable]); + } + + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + gitHubReposInvoker.getTitleAndDescription(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + `'GITHUB_REPOSITORY_OWNER', accessed within 'GitHubReposInvoker.initializeForGitHub()', is invalid, null, or undefined '${String(variable)}'.`, + ); + + }); + }); + } + + { + const testCases: (string | undefined)[] = [undefined, ""]; + + testCases.forEach((variable: string | undefined): void => { + it(`should throw when GITHUB_REPOSITORY is set to the invalid value '${String(variable)}' and the task is running on GitHub`, async (): Promise => { + // Arrange + stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); + stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_API_URL", "https://api.github.com"]); + stubEnv(["GITHUB_REPOSITORY_OWNER", "microsoft"]); + if (typeof variable === "undefined") { + stubEnv(["GITHUB_REPOSITORY", undefined]); + } else { + stubEnv(["GITHUB_REPOSITORY", variable]); + } + + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + gitHubReposInvoker.getTitleAndDescription(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + `'GITHUB_REPOSITORY', accessed within 'GitHubReposInvoker.initializeForGitHub()', is invalid, null, or undefined '${String(variable)}'.`, + ); + + }); + }); + } + + it("should throw when GITHUB_REPOSITORY is in an incorrect format and the task is running on GitHub", async (): Promise => { + // Arrange + stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); + stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_API_URL", "https://api.github.com"]); + stubEnv(["GITHUB_REPOSITORY_OWNER", "microsoft"]); + stubEnv(["GITHUB_REPOSITORY", "microsoft"]); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + gitHubReposInvoker.getTitleAndDescription(); + + // Assert + await AssertExtensions.toThrowAsync( + func, + "GITHUB_REPOSITORY 'microsoft' is in an unexpected format.", + ); + + }); + + it("should succeed when the inputs are valid and the task is running on Azure Pipelines", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const result: PullRequestDetailsInterface = + await gitHubReposInvoker.getTitleAndDescription(); + + // Assert + assert.equal(result.title, "Title"); + assert.equal(result.description, "Description"); + verify(octokitWrapper.initialize(any())).once(); + verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); + }); + + it("should succeed when the inputs are valid and the task is running on GitHub", async (): Promise => { + // Arrange + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + stubEnv(["GITHUB_API_URL", "https://api.github.com"]); + stubEnv(["GITHUB_REPOSITORY_OWNER", "microsoft"]); + stubEnv(["GITHUB_REPOSITORY", "microsoft/PR-Metrics"]); + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const result: PullRequestDetailsInterface = + await gitHubReposInvoker.getTitleAndDescription(); + + // Assert + assert.equal(result.title, "Title"); + assert.equal(result.description, "Description"); + verify(octokitWrapper.initialize(any())).once(); + verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); + + }); + + it("should succeed when the inputs are valid and the URL ends with '.git'", async (): Promise => { + // Arrange + stubEnv([ + "SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", + "https://github.com/microsoft/PR-Metrics.git", + ]); + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const result: PullRequestDetailsInterface = + await gitHubReposInvoker.getTitleAndDescription(); + + // Assert + assert.equal(result.title, "Title"); + assert.equal(result.description, "Description"); + verify(octokitWrapper.initialize(any())).once(); + verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); + }); + + it("should succeed when the inputs are valid and GitHub Enterprise is in use", async (): Promise => { + // Arrange + stubEnv([ + "SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", + "https://organization.githubenterprise.com/microsoft/PR-Metrics", + ]); + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.equal( + options.baseUrl, + "https://organization.githubenterprise.com/api/v3", + ); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const result: PullRequestDetailsInterface = + await gitHubReposInvoker.getTitleAndDescription(); + + // Assert + assert.equal(result.title, "Title"); + assert.equal(result.description, "Description"); + verify(octokitWrapper.initialize(any())).once(); + verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); + }); + + it("should succeed when called twice with the inputs valid", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + await gitHubReposInvoker.getTitleAndDescription(); + const result: PullRequestDetailsInterface = + await gitHubReposInvoker.getTitleAndDescription(); + + // Assert + assert.equal(result.title, "Title"); + assert.equal(result.description, "Description"); + verify(octokitWrapper.initialize(any())).once(); + verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).twice(); + }); + + it("should succeed when the description is null", async (): Promise => { + // Arrange + const currentMockPullResponse: GetPullResponse = + GitHubReposInvokerConstants.getPullResponse; + currentMockPullResponse.data.body = null; + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + when( + octokitWrapper.getPull(anyString(), anyString(), anyNumber()), + ).thenResolve(currentMockPullResponse); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const result: PullRequestDetailsInterface = + await gitHubReposInvoker.getTitleAndDescription(); + + // Assert + assert.equal(result.title, "Title"); + assert.equal(result.description, null); + verify(octokitWrapper.initialize(any())).once(); + verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); + }); + + { + const testCases: StatusCodes[] = [ + StatusCodes.UNAUTHORIZED, + StatusCodes.FORBIDDEN, + StatusCodes.NOT_FOUND, + ]; + + testCases.forEach((status: StatusCodes): void => { + it(`should throw when the PAT has insufficient access and the API call returns status '${String(status)}'`, async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const error: RequestError = createRequestError(status, "Test"); + when( + octokitWrapper.getPull(anyString(), anyString(), anyNumber()), + ).thenThrow(error); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + gitHubReposInvoker.getTitleAndDescription(); + + // Assert + const expectedMessage: string = + status === StatusCodes.NOT_FOUND + ? "The resource could not be found. Verify the repository and pull request exist." + : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has Read and Write access to pull requests (or access to 'repos' if using a Classic PAT)."; + const result: ErrorWithStatusInterface = + await AssertExtensions.toThrowAsync(func, expectedMessage); + assert.equal(result.internalMessage, "Test"); + verify(octokitWrapper.initialize(any())).once(); + }); + }); + } + + it("should throw an error when an error occurs", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + when( + octokitWrapper.getPull(anyString(), anyString(), anyNumber()), + ).thenThrow(Error("Error")); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const func: () => Promise = async () => + gitHubReposInvoker.getTitleAndDescription(); + + // Assert + await AssertExtensions.toThrowAsync(func, "Error"); + verify(octokitWrapper.initialize(any())).once(); + }); + + it("should initialize log object correctly", async (): Promise => { + // Arrange + let logObject: OctokitLogObjectInterface | undefined; + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + logObject = options.log; + }, + ); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + await gitHubReposInvoker.getTitleAndDescription(); + + // Act + logObject?.debug("Debug Message"); + logObject?.info("Info Message"); + logObject?.warn("Warning Message"); + logObject?.error("Error Message"); + + // Assert + verify(logger.logDebug("Octokit – Debug Message")).once(); + verify(logger.logInfo("Octokit – Info Message")).once(); + verify(logger.logWarning("Octokit – Warning Message")).once(); + verify(logger.logError("Octokit – Error Message")).once(); + }); + }); +}); diff --git a/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts b/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts new file mode 100644 index 000000000..8d227c5bb --- /dev/null +++ b/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts @@ -0,0 +1,90 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import Logger from "../../src/utilities/logger.js"; +import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import assert from "node:assert/strict"; +import { createGitHubReposInvokerMocks } from "./gitHubReposInvokerTestSetup.js"; +import { instance } from "ts-mockito"; +import { stubEnv } from "../testUtilities/stubEnv.js"; + +describe("gitHubReposInvoker.ts", (): void => { + let gitInvoker: GitInvoker; + let logger: Logger; + let octokitWrapper: OctokitWrapper; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + } = createGitHubReposInvokerMocks()); + }); + + describe("isAccessTokenAvailable()", (): void => { + it("should return null when the token exists on Azure DevOps", async (): Promise => { + // Arrange + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const result: string | null = + await gitHubReposInvoker.isAccessTokenAvailable(); + + // Assert + assert.equal(result, null); + }); + + it("should return null when the token exists on GitHub", async (): Promise => { + // Arrange + stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); + stubEnv(["GITHUB_ACTION", "PR-Metrics"]); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const result: string | null = + await gitHubReposInvoker.isAccessTokenAvailable(); + + // Assert + assert.equal(result, null); + + }); + + it("should return a string when the token does not exist", async (): Promise => { + // Arrange + stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + const result: string | null = + await gitHubReposInvoker.isAccessTokenAvailable(); + + // Assert + assert.equal( + result, + "Could not access the Personal Access Token (PAT). Add 'PR_Metrics_Access_Token' as a secret environment variable with Read and Write access to Pull Requests (or access to 'repos' if using a Classic PAT, or write access to 'pull-requests' and 'statuses' if specified within the workflow YAML).", + ); + }); + }); +}); diff --git a/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts b/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts new file mode 100644 index 000000000..5aa9867b5 --- /dev/null +++ b/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts @@ -0,0 +1,156 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import { createGitHubReposInvokerMocks, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { instance, verify, when } from "ts-mockito"; +import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import Logger from "../../src/utilities/logger.js"; +import type { OctokitOptions } from "@octokit/core"; +import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import { any } from "../testUtilities/mockito.js"; +import assert from "node:assert/strict"; + +describe("gitHubReposInvoker.ts", (): void => { + let gitInvoker: GitInvoker; + let logger: Logger; + let octokitWrapper: OctokitWrapper; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + } = createGitHubReposInvokerMocks()); + }); + + describe("setTitleAndDescription()", (): void => { + it("should succeed when the title and description are both null", async (): Promise => { + // Arrange + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + await gitHubReposInvoker.setTitleAndDescription(null, null); + + // Assert + }); + + it("should succeed when the title and description are both set", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + await gitHubReposInvoker.setTitleAndDescription("Title", "Description"); + + // Assert + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.updatePull( + "microsoft", + "PR-Metrics", + 12345, + "Title", + "Description", + ), + ).once(); + }); + + it("should succeed when the title is set", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + await gitHubReposInvoker.setTitleAndDescription("Title", null); + + // Assert + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.updatePull( + "microsoft", + "PR-Metrics", + 12345, + "Title", + null, + ), + ).once(); + }); + + it("should succeed when the description is set", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + await gitHubReposInvoker.setTitleAndDescription(null, "Description"); + + // Assert + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.updatePull( + "microsoft", + "PR-Metrics", + 12345, + null, + "Description", + ), + ).once(); + }); + }); +}); diff --git a/src/task/tests/repos/gitHubReposInvoker.spec.ts b/src/task/tests/repos/gitHubReposInvoker.spec.ts deleted file mode 100644 index b2b23fa70..000000000 --- a/src/task/tests/repos/gitHubReposInvoker.spec.ts +++ /dev/null @@ -1,1513 +0,0 @@ -/* - * Copyright (c) Microsoft Corporation. - * Licensed under the MIT License. - */ - -import * as AssertExtensions from "../testUtilities/assertExtensions.js"; -import * as GitHubReposInvokerConstants from "./gitHubReposInvokerConstants.js"; -import { any, anyNumber, anyString } from "../testUtilities/mockito.js"; -import { instance, mock, verify, when } from "ts-mockito"; -import type CommentData from "../../src/repos/interfaces/commentData.js"; -import { CommentThreadStatus } from "azure-devops-node-api/interfaces/GitInterfaces.js"; -import type ErrorWithStatusInterface from "../../src/repos/interfaces/errorWithStatusInterface.js"; -import type GetIssueCommentsResponse from "../../src/wrappers/octokitInterfaces/getIssueCommentsResponse.js"; -import type GetPullResponse from "../../src/wrappers/octokitInterfaces/getPullResponse.js"; -import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; -import HttpError from "../testUtilities/httpError.js"; -import Logger from "../../src/utilities/logger.js"; -import type OctokitLogObjectInterface from "../wrappers/octokitLogObjectInterface.js"; -import type { OctokitOptions } from "@octokit/core"; -import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; -import type PullRequestDetailsInterface from "../../src/repos/interfaces/pullRequestDetailsInterface.js"; -import type { RequestError } from "@octokit/request-error"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; -import { StatusCodes } from "http-status-codes"; -import assert from "node:assert/strict"; -import { createRequestError } from "../testUtilities/createRequestError.js"; -import { stubEnv } from "../testUtilities/stubEnv.js"; -import { stubLocalization } from "../testUtilities/stubLocalization.js"; - -describe("gitHubReposInvoker.ts", (): void => { - let gitInvoker: GitInvoker; - let logger: Logger; - let octokitWrapper: OctokitWrapper; - let runnerInvoker: RunnerInvoker; - - const expectedUserAgent = "PRMetrics/v1.7.13"; - - beforeEach((): void => { - stubEnv( - ["PR_METRICS_ACCESS_TOKEN", "PAT"], - [ - "SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", - "https://github.com/microsoft/PR-Metrics", - ], - ); - - gitInvoker = mock(GitInvoker); - when(gitInvoker.pullRequestId).thenReturn(12345); - - logger = mock(Logger); - - octokitWrapper = mock(OctokitWrapper); - when( - octokitWrapper.getPull(anyString(), anyString(), anyNumber()), - ).thenResolve(GitHubReposInvokerConstants.getPullResponse); - when( - octokitWrapper.updatePull( - anyString(), - anyString(), - anyNumber(), - anyString(), - anyString(), - ), - ).thenResolve(GitHubReposInvokerConstants.getPullResponse); - when( - octokitWrapper.listCommits( - anyString(), - anyString(), - anyNumber(), - anyNumber(), - ), - ).thenResolve(GitHubReposInvokerConstants.listCommitsResponse); - - runnerInvoker = mock(RunnerInvoker); - stubLocalization(runnerInvoker); - }); - - describe("isAccessTokenAvailable()", (): void => { - it("should return null when the token exists on Azure DevOps", async (): Promise => { - // Arrange - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const result: string | null = - await gitHubReposInvoker.isAccessTokenAvailable(); - - // Assert - assert.equal(result, null); - }); - - it("should return null when the token exists on GitHub", async (): Promise => { - // Arrange - stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); - stubEnv(["GITHUB_ACTION", "PR-Metrics"]); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const result: string | null = - await gitHubReposInvoker.isAccessTokenAvailable(); - - // Assert - assert.equal(result, null); - - }); - - it("should return a string when the token does not exist", async (): Promise => { - // Arrange - stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const result: string | null = - await gitHubReposInvoker.isAccessTokenAvailable(); - - // Assert - assert.equal( - result, - "Could not access the Personal Access Token (PAT). Add 'PR_Metrics_Access_Token' as a secret environment variable with Read and Write access to Pull Requests (or access to 'repos' if using a Classic PAT, or write access to 'pull-requests' and 'statuses' if specified within the workflow YAML).", - ); - }); - }); - - describe("getTitleAndDescription()", (): void => { - { - const testCases: (string | undefined)[] = [undefined, ""]; - - testCases.forEach((variable: string | undefined): void => { - it(`should throw when SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI is set to the invalid value '${String(variable)}' and the task is running on Azure Pipelines`, async (): Promise => { - // Arrange - if (typeof variable === "undefined") { - stubEnv(["SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", undefined]); - } else { - stubEnv(["SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", variable]); - } - - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - gitHubReposInvoker.getTitleAndDescription(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - `'SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI', accessed within 'GitHubReposInvoker.initializeForAzureDevOps()', is invalid, null, or undefined '${String(variable)}'.`, - ); - }); - }); - } - - it("should throw when SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI is set to an invalid URL and the task is running on Azure Pipelines", async (): Promise => { - // Arrange - stubEnv( - ["SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", "https://github.com/microsoft"], - ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - gitHubReposInvoker.getTitleAndDescription(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - "SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI 'https://github.com/microsoft' is in an unexpected format.", - ); - }); - - { - const testCases: (string | undefined)[] = [undefined, ""]; - - testCases.forEach((variable: string | undefined): void => { - it(`should throw when GITHUB_API_URL is set to the invalid value '${String(variable)}' and the task is running on GitHub`, async (): Promise => { - // Arrange - stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); - stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); - stubEnv(["GITHUB_ACTION", "PR-Metrics"]); - if (typeof variable === "undefined") { - stubEnv(["GITHUB_API_URL", undefined]); - } else { - stubEnv(["GITHUB_API_URL", variable]); - } - - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - gitHubReposInvoker.getTitleAndDescription(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - `'GITHUB_API_URL', accessed within 'GitHubReposInvoker.initializeForGitHub()', is invalid, null, or undefined '${String(variable)}'.`, - ); - - }); - }); - } - - { - const testCases: (string | undefined)[] = [undefined, ""]; - - testCases.forEach((variable: string | undefined): void => { - it(`should throw when GITHUB_REPOSITORY_OWNER is set to the invalid value '${String(variable)}' and the task is running on GitHub`, async (): Promise => { - // Arrange - stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); - stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); - stubEnv(["GITHUB_ACTION", "PR-Metrics"]); - stubEnv(["GITHUB_API_URL", "https://api.github.com"]); - if (typeof variable === "undefined") { - stubEnv(["GITHUB_REPOSITORY_OWNER", undefined]); - } else { - stubEnv(["GITHUB_REPOSITORY_OWNER", variable]); - } - - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - gitHubReposInvoker.getTitleAndDescription(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - `'GITHUB_REPOSITORY_OWNER', accessed within 'GitHubReposInvoker.initializeForGitHub()', is invalid, null, or undefined '${String(variable)}'.`, - ); - - }); - }); - } - - { - const testCases: (string | undefined)[] = [undefined, ""]; - - testCases.forEach((variable: string | undefined): void => { - it(`should throw when GITHUB_REPOSITORY is set to the invalid value '${String(variable)}' and the task is running on GitHub`, async (): Promise => { - // Arrange - stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); - stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); - stubEnv(["GITHUB_ACTION", "PR-Metrics"]); - stubEnv(["GITHUB_API_URL", "https://api.github.com"]); - stubEnv(["GITHUB_REPOSITORY_OWNER", "microsoft"]); - if (typeof variable === "undefined") { - stubEnv(["GITHUB_REPOSITORY", undefined]); - } else { - stubEnv(["GITHUB_REPOSITORY", variable]); - } - - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - gitHubReposInvoker.getTitleAndDescription(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - `'GITHUB_REPOSITORY', accessed within 'GitHubReposInvoker.initializeForGitHub()', is invalid, null, or undefined '${String(variable)}'.`, - ); - - }); - }); - } - - it("should throw when GITHUB_REPOSITORY is in an incorrect format and the task is running on GitHub", async (): Promise => { - // Arrange - stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); - stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); - stubEnv(["GITHUB_ACTION", "PR-Metrics"]); - stubEnv(["GITHUB_API_URL", "https://api.github.com"]); - stubEnv(["GITHUB_REPOSITORY_OWNER", "microsoft"]); - stubEnv(["GITHUB_REPOSITORY", "microsoft"]); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - gitHubReposInvoker.getTitleAndDescription(); - - // Assert - await AssertExtensions.toThrowAsync( - func, - "GITHUB_REPOSITORY 'microsoft' is in an unexpected format.", - ); - - }); - - it("should succeed when the inputs are valid and the task is running on Azure Pipelines", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const result: PullRequestDetailsInterface = - await gitHubReposInvoker.getTitleAndDescription(); - - // Assert - assert.equal(result.title, "Title"); - assert.equal(result.description, "Description"); - verify(octokitWrapper.initialize(any())).once(); - verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); - }); - - it("should succeed when the inputs are valid and the task is running on GitHub", async (): Promise => { - // Arrange - stubEnv(["GITHUB_ACTION", "PR-Metrics"]); - stubEnv(["GITHUB_API_URL", "https://api.github.com"]); - stubEnv(["GITHUB_REPOSITORY_OWNER", "microsoft"]); - stubEnv(["GITHUB_REPOSITORY", "microsoft/PR-Metrics"]); - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const result: PullRequestDetailsInterface = - await gitHubReposInvoker.getTitleAndDescription(); - - // Assert - assert.equal(result.title, "Title"); - assert.equal(result.description, "Description"); - verify(octokitWrapper.initialize(any())).once(); - verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); - - }); - - it("should succeed when the inputs are valid and the URL ends with '.git'", async (): Promise => { - // Arrange - stubEnv([ - "SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", - "https://github.com/microsoft/PR-Metrics.git", - ]); - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const result: PullRequestDetailsInterface = - await gitHubReposInvoker.getTitleAndDescription(); - - // Assert - assert.equal(result.title, "Title"); - assert.equal(result.description, "Description"); - verify(octokitWrapper.initialize(any())).once(); - verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); - }); - - it("should succeed when the inputs are valid and GitHub Enterprise is in use", async (): Promise => { - // Arrange - stubEnv([ - "SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", - "https://organization.githubenterprise.com/microsoft/PR-Metrics", - ]); - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.equal( - options.baseUrl, - "https://organization.githubenterprise.com/api/v3", - ); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const result: PullRequestDetailsInterface = - await gitHubReposInvoker.getTitleAndDescription(); - - // Assert - assert.equal(result.title, "Title"); - assert.equal(result.description, "Description"); - verify(octokitWrapper.initialize(any())).once(); - verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); - }); - - it("should succeed when called twice with the inputs valid", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - await gitHubReposInvoker.getTitleAndDescription(); - const result: PullRequestDetailsInterface = - await gitHubReposInvoker.getTitleAndDescription(); - - // Assert - assert.equal(result.title, "Title"); - assert.equal(result.description, "Description"); - verify(octokitWrapper.initialize(any())).once(); - verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).twice(); - }); - - it("should succeed when the description is null", async (): Promise => { - // Arrange - const currentMockPullResponse: GetPullResponse = - GitHubReposInvokerConstants.getPullResponse; - currentMockPullResponse.data.body = null; - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - when( - octokitWrapper.getPull(anyString(), anyString(), anyNumber()), - ).thenResolve(currentMockPullResponse); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const result: PullRequestDetailsInterface = - await gitHubReposInvoker.getTitleAndDescription(); - - // Assert - assert.equal(result.title, "Title"); - assert.equal(result.description, null); - verify(octokitWrapper.initialize(any())).once(); - verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); - }); - - { - const testCases: StatusCodes[] = [ - StatusCodes.UNAUTHORIZED, - StatusCodes.FORBIDDEN, - StatusCodes.NOT_FOUND, - ]; - - testCases.forEach((status: StatusCodes): void => { - it(`should throw when the PAT has insufficient access and the API call returns status '${String(status)}'`, async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const error: RequestError = createRequestError(status, "Test"); - when( - octokitWrapper.getPull(anyString(), anyString(), anyNumber()), - ).thenThrow(error); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - gitHubReposInvoker.getTitleAndDescription(); - - // Assert - const expectedMessage: string = - status === StatusCodes.NOT_FOUND - ? "The resource could not be found. Verify the repository and pull request exist." - : "Could not access the resources. Ensure the 'PR_Metrics_Access_Token' secret environment variable has Read and Write access to pull requests (or access to 'repos' if using a Classic PAT)."; - const result: ErrorWithStatusInterface = - await AssertExtensions.toThrowAsync(func, expectedMessage); - assert.equal(result.internalMessage, "Test"); - verify(octokitWrapper.initialize(any())).once(); - }); - }); - } - - it("should throw an error when an error occurs", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - when( - octokitWrapper.getPull(anyString(), anyString(), anyNumber()), - ).thenThrow(Error("Error")); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - gitHubReposInvoker.getTitleAndDescription(); - - // Assert - await AssertExtensions.toThrowAsync(func, "Error"); - verify(octokitWrapper.initialize(any())).once(); - }); - - it("should initialize log object correctly", async (): Promise => { - // Arrange - let logObject: OctokitLogObjectInterface | undefined; - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - logObject = options.log; - }, - ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - await gitHubReposInvoker.getTitleAndDescription(); - - // Act - logObject?.debug("Debug Message"); - logObject?.info("Info Message"); - logObject?.warn("Warning Message"); - logObject?.error("Error Message"); - - // Assert - verify(logger.logDebug("Octokit – Debug Message")).once(); - verify(logger.logInfo("Octokit – Info Message")).once(); - verify(logger.logWarning("Octokit – Warning Message")).once(); - verify(logger.logError("Octokit – Error Message")).once(); - }); - }); - - describe("getComments()", (): void => { - it("should return the result when called with a pull request comment", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const response: GetIssueCommentsResponse = - GitHubReposInvokerConstants.getIssueCommentsResponse; - if (typeof response.data[0] === "undefined") { - throw new Error("response.data[0] is undefined"); - } - - response.data[0].body = "PR Content"; - when( - octokitWrapper.getIssueComments(anyString(), anyString(), anyNumber()), - ).thenResolve(response); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const result: CommentData = await gitHubReposInvoker.getComments(); - - // Assert - assert.equal(result.pullRequestComments.length, 1); - assert.equal(result.pullRequestComments[0]?.id, 1); - assert.equal(result.pullRequestComments[0].content, "PR Content"); - assert.equal( - result.pullRequestComments[0].status, - CommentThreadStatus.Unknown, - ); - assert.equal(result.fileComments.length, 0); - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.getIssueComments("microsoft", "PR-Metrics", 12345), - ).once(); - verify( - octokitWrapper.getReviewComments("microsoft", "PR-Metrics", 12345), - ).once(); - }); - - it("should return the result when called with a file comment", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - when( - octokitWrapper.getReviewComments(anyString(), anyString(), anyNumber()), - ).thenResolve(GitHubReposInvokerConstants.getReviewCommentsResponse); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const result: CommentData = await gitHubReposInvoker.getComments(); - - // Assert - assert.equal(result.pullRequestComments.length, 0); - assert.equal(result.fileComments.length, 1); - assert.equal(result.fileComments[0]?.id, 2); - assert.equal(result.fileComments[0].content, "File Content"); - assert.equal(result.fileComments[0].status, CommentThreadStatus.Unknown); - assert.equal(result.fileComments[0].fileName, "file.ts"); - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.getIssueComments("microsoft", "PR-Metrics", 12345), - ).once(); - verify( - octokitWrapper.getReviewComments("microsoft", "PR-Metrics", 12345), - ).once(); - }); - - it("should return the result when called with both a pull request and file comment", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const response: GetIssueCommentsResponse = - GitHubReposInvokerConstants.getIssueCommentsResponse; - if (typeof response.data[0] === "undefined") { - throw new Error("response.data[0] is undefined"); - } - - response.data[0].body = "PR Content"; - when( - octokitWrapper.getIssueComments(anyString(), anyString(), anyNumber()), - ).thenResolve(response); - when( - octokitWrapper.getReviewComments(anyString(), anyString(), anyNumber()), - ).thenResolve(GitHubReposInvokerConstants.getReviewCommentsResponse); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const result: CommentData = await gitHubReposInvoker.getComments(); - - // Assert - assert.equal(result.pullRequestComments.length, 1); - assert.equal(result.pullRequestComments[0]?.id, 1); - assert.equal(result.pullRequestComments[0].content, "PR Content"); - assert.equal( - result.pullRequestComments[0].status, - CommentThreadStatus.Unknown, - ); - assert.equal(result.fileComments.length, 1); - assert.equal(result.fileComments[0]?.id, 2); - assert.equal(result.fileComments[0].content, "File Content"); - assert.equal(result.fileComments[0].status, CommentThreadStatus.Unknown); - assert.equal(result.fileComments[0].fileName, "file.ts"); - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.getIssueComments("microsoft", "PR-Metrics", 12345), - ).once(); - verify( - octokitWrapper.getReviewComments("microsoft", "PR-Metrics", 12345), - ).once(); - }); - - it("should skip pull request comments with no body", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const response: GetIssueCommentsResponse = - GitHubReposInvokerConstants.getIssueCommentsResponse; - if (typeof response.data[0] === "undefined") { - throw new Error("response.data[0] is undefined"); - } - - response.data[0].body = undefined; - when( - octokitWrapper.getIssueComments(anyString(), anyString(), anyNumber()), - ).thenResolve(response); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const result: CommentData = await gitHubReposInvoker.getComments(); - - // Assert - assert.equal(result.pullRequestComments.length, 0); - assert.equal(result.fileComments.length, 0); - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.getIssueComments("microsoft", "PR-Metrics", 12345), - ).once(); - verify( - octokitWrapper.getReviewComments("microsoft", "PR-Metrics", 12345), - ).once(); - }); - }); - - describe("setTitleAndDescription()", (): void => { - it("should succeed when the title and description are both null", async (): Promise => { - // Arrange - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - await gitHubReposInvoker.setTitleAndDescription(null, null); - - // Assert - }); - - it("should succeed when the title and description are both set", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - await gitHubReposInvoker.setTitleAndDescription("Title", "Description"); - - // Assert - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.updatePull( - "microsoft", - "PR-Metrics", - 12345, - "Title", - "Description", - ), - ).once(); - }); - - it("should succeed when the title is set", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - await gitHubReposInvoker.setTitleAndDescription("Title", null); - - // Assert - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.updatePull( - "microsoft", - "PR-Metrics", - 12345, - "Title", - null, - ), - ).once(); - }); - - it("should succeed when the description is set", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - await gitHubReposInvoker.setTitleAndDescription(null, "Description"); - - // Assert - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.updatePull( - "microsoft", - "PR-Metrics", - 12345, - null, - "Description", - ), - ).once(); - }); - }); - - describe("createComment()", (): void => { - it("should succeed when a file name is specified", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - await gitHubReposInvoker.createComment("Content", "file.ts"); - - // Assert - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), - ).once(); - verify( - octokitWrapper.createReviewComment( - "microsoft", - "PR-Metrics", - 12345, - "Content", - "file.ts", - "sha54321", - ), - ).once(); - }); - - it("should throw when the commit list is empty", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - when( - octokitWrapper.listCommits( - anyString(), - anyString(), - anyNumber(), - anyNumber(), - ), - ).thenResolve({ - data: [], - headers: {}, - status: StatusCodes.OK, - url: "", - }); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - gitHubReposInvoker.createComment("Content", "file.ts"); - - // Assert - await AssertExtensions.toThrowAsync( - func, - "'result.data[-1].sha', accessed within 'GitHubReposInvoker.getCommitId()', is invalid, null, or undefined 'undefined'.", - ); - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), - ).once(); - }); - - it("should succeed when there are multiple pages of commits", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - when( - octokitWrapper.listCommits(anyString(), anyString(), anyNumber(), 1), - ).thenResolve({ - data: [], - headers: { - link: '; rel="next", ; rel="last"', - }, - status: StatusCodes.OK, - url: "", - }); - when( - octokitWrapper.listCommits(anyString(), anyString(), anyNumber(), 24), - ).thenResolve(GitHubReposInvokerConstants.listCommitsResponse); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - await gitHubReposInvoker.createComment("Content", "file.ts"); - - // Assert - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), - ).once(); - verify( - octokitWrapper.createReviewComment( - "microsoft", - "PR-Metrics", - 12345, - "Content", - "file.ts", - "sha54321", - ), - ).once(); - }); - - it("should throw when the link header does not match the expected format", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - when( - octokitWrapper.listCommits(anyString(), anyString(), anyNumber(), 1), - ).thenResolve({ - data: [], - headers: { - link: "non-matching", - }, - status: StatusCodes.OK, - url: "", - }); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - gitHubReposInvoker.createComment("Content", "file.ts"); - - // Assert - await AssertExtensions.toThrowAsync( - func, - "The regular expression did not match 'non-matching'.", - ); - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), - ).once(); - }); - - it("should succeed when a file name is specified and the method is called twice", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - await gitHubReposInvoker.createComment("Content", "file.ts"); - await gitHubReposInvoker.createComment("Content", "file.ts"); - - // Assert - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), - ).once(); - verify( - octokitWrapper.createReviewComment( - "microsoft", - "PR-Metrics", - 12345, - "Content", - "file.ts", - "sha54321", - ), - ).twice(); - }); - - it("should succeed when createReviewComment() returns undefined", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - when( - octokitWrapper.createReviewComment( - "microsoft", - "PR-Metrics", - 12345, - "Content", - "file.ts", - "sha54321", - ), - ).thenResolve(null); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - await gitHubReposInvoker.createComment("Content", "file.ts"); - - // Assert - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), - ).once(); - verify( - octokitWrapper.createReviewComment( - "microsoft", - "PR-Metrics", - 12345, - "Content", - "file.ts", - "sha54321", - ), - ).once(); - }); - - { - const testCases: string[] = [ - "file.ts is too big", - "file.ts diff is too large", - ]; - - testCases.forEach((message: string): void => { - it(`should succeed when a HTTP 422 error occurs due to: '${message}'`, async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const errorMessage = `Validation Failed: {"resource":"PullRequestReviewComment","code":"custom","field":"pull_request_review_thread.diff_entry","message":"${message}"}`; - const error: RequestError = createRequestError( - StatusCodes.UNPROCESSABLE_ENTITY, - errorMessage, - ); - when( - octokitWrapper.createReviewComment( - "microsoft", - "PR-Metrics", - 12345, - "Content", - "file.ts", - "sha54321", - ), - ).thenCall((): void => { - throw error; - }); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - await gitHubReposInvoker.createComment("Content", "file.ts"); - - // Assert - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), - ).once(); - verify( - octokitWrapper.createReviewComment( - "microsoft", - "PR-Metrics", - 12345, - "Content", - "file.ts", - "sha54321", - ), - ).once(); - verify( - logger.logInfo( - "GitHub createReviewComment() threw a 422 error related to a large diff. Ignoring as this is expected.", - ), - ).once(); - verify(logger.logErrorObject(error)).once(); - }); - }); - } - - { - const testCases: HttpError[] = [ - new HttpError( - StatusCodes.BAD_REQUEST, - 'Validation Failed: {"resource":"PullRequestReviewComment","code":"custom","field":"pull_request_review_thread.diff_entry","message":"file.ts is too big"}', - ), - new HttpError(StatusCodes.UNPROCESSABLE_ENTITY, "Unprocessable Entity"), - ]; - - testCases.forEach((error: HttpError): void => { - it("should throw when an error occurs that is not a HTTP 422 or is not due to having a too large path diff", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - when( - octokitWrapper.createReviewComment( - "microsoft", - "PR-Metrics", - 12345, - "Content", - "file.ts", - "sha54321", - ), - ).thenCall((): void => { - throw error; - }); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - const func: () => Promise = async () => - gitHubReposInvoker.createComment("Content", "file.ts"); - - // Assert - await AssertExtensions.toThrowAsync(func, error.message); - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.listCommits("microsoft", "PR-Metrics", 12345, 1), - ).once(); - verify( - octokitWrapper.createReviewComment( - "microsoft", - "PR-Metrics", - 12345, - "Content", - "file.ts", - "sha54321", - ), - ).once(); - }); - }); - } - - it("should succeed when no file name is specified", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - await gitHubReposInvoker.createComment("Content", null); - - // Assert - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.createIssueComment( - "microsoft", - "PR-Metrics", - 12345, - "Content", - ), - ).once(); - }); - }); - - describe("updateComment()", (): void => { - it("should succeed when the content is null", async (): Promise => { - // Arrange - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - await gitHubReposInvoker.updateComment(54321, null); - - // Assert - }); - - it("should succeed when the content is set", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - await gitHubReposInvoker.updateComment(54321, "Content"); - - // Assert - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.updateIssueComment( - "microsoft", - "PR-Metrics", - 12345, - 54321, - "Content", - ), - ).once(); - }); - }); - - describe("deleteCommentThread()", (): void => { - it("should succeed", async (): Promise => { - // Arrange - when(octokitWrapper.initialize(any())).thenCall( - (options: OctokitOptions): void => { - assert.equal(options.auth, "PAT"); - assert.equal(options.userAgent, expectedUserAgent); - assert.notEqual(options.log, null); - assert.notEqual(options.log?.debug, null); - assert.notEqual(options.log?.info, null); - assert.notEqual(options.log?.warn, null); - assert.notEqual(options.log?.error, null); - }, - ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); - - // Act - await gitHubReposInvoker.deleteCommentThread(54321); - - // Assert - verify(octokitWrapper.initialize(any())).once(); - verify( - octokitWrapper.deleteReviewComment("microsoft", "PR-Metrics", 54321), - ).once(); - }); - }); -}); diff --git a/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts b/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts new file mode 100644 index 000000000..d43d51080 --- /dev/null +++ b/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts @@ -0,0 +1,84 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import { createGitHubReposInvokerMocks, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { instance, verify, when } from "ts-mockito"; +import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import Logger from "../../src/utilities/logger.js"; +import type { OctokitOptions } from "@octokit/core"; +import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import { any } from "../testUtilities/mockito.js"; +import assert from "node:assert/strict"; + +describe("gitHubReposInvoker.ts", (): void => { + let gitInvoker: GitInvoker; + let logger: Logger; + let octokitWrapper: OctokitWrapper; + let runnerInvoker: RunnerInvoker; + + beforeEach((): void => { + ({ + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + } = createGitHubReposInvokerMocks()); + }); + + describe("updateComment()", (): void => { + it("should succeed when the content is null", async (): Promise => { + // Arrange + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + await gitHubReposInvoker.updateComment(54321, null); + + // Assert + }); + + it("should succeed when the content is set", async (): Promise => { + // Arrange + when(octokitWrapper.initialize(any())).thenCall( + (options: OctokitOptions): void => { + assert.equal(options.auth, "PAT"); + assert.equal(options.userAgent, expectedUserAgent); + assert.notEqual(options.log, null); + assert.notEqual(options.log?.debug, null); + assert.notEqual(options.log?.info, null); + assert.notEqual(options.log?.warn, null); + assert.notEqual(options.log?.error, null); + }, + ); + const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); + + // Act + await gitHubReposInvoker.updateComment(54321, "Content"); + + // Assert + verify(octokitWrapper.initialize(any())).once(); + verify( + octokitWrapper.updateIssueComment( + "microsoft", + "PR-Metrics", + 12345, + 54321, + "Content", + ), + ).once(); + }); + }); +}); diff --git a/src/task/tests/repos/gitHubReposInvokerTestSetup.ts b/src/task/tests/repos/gitHubReposInvokerTestSetup.ts new file mode 100644 index 000000000..63be2f131 --- /dev/null +++ b/src/task/tests/repos/gitHubReposInvokerTestSetup.ts @@ -0,0 +1,72 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +import * as GitHubReposInvokerConstants from "./gitHubReposInvokerConstants.js"; +import { anyNumber, anyString } from "../testUtilities/mockito.js"; +import { mock, when } from "ts-mockito"; +import GitInvoker from "../../src/git/gitInvoker.js"; +import Logger from "../../src/utilities/logger.js"; +import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; +import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import { stubEnv } from "../testUtilities/stubEnv.js"; +import { stubLocalization } from "../testUtilities/stubLocalization.js"; + +export const expectedUserAgent = "PRMetrics/v1.7.13"; + +export interface GitHubReposInvokerMocks { + gitInvoker: GitInvoker; + logger: Logger; + octokitWrapper: OctokitWrapper; + runnerInvoker: RunnerInvoker; +} + +/** + * Creates the mocks and environment variable stubs required by + * `gitHubReposInvoker.ts` tests. Individual tests can override any stub + * after calling this helper. + * @returns The paired mocks. + */ +export const createGitHubReposInvokerMocks = (): GitHubReposInvokerMocks => { + stubEnv( + ["PR_METRICS_ACCESS_TOKEN", "PAT"], + [ + "SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", + "https://github.com/microsoft/PR-Metrics", + ], + ); + + const pullRequestId = 12345; + const gitInvoker: GitInvoker = mock(GitInvoker); + when(gitInvoker.pullRequestId).thenReturn(pullRequestId); + + const logger: Logger = mock(Logger); + + const octokitWrapper: OctokitWrapper = mock(OctokitWrapper); + when( + octokitWrapper.getPull(anyString(), anyString(), anyNumber()), + ).thenResolve(GitHubReposInvokerConstants.getPullResponse); + when( + octokitWrapper.updatePull( + anyString(), + anyString(), + anyNumber(), + anyString(), + anyString(), + ), + ).thenResolve(GitHubReposInvokerConstants.getPullResponse); + when( + octokitWrapper.listCommits( + anyString(), + anyString(), + anyNumber(), + anyNumber(), + ), + ).thenResolve(GitHubReposInvokerConstants.listCommitsResponse); + + const runnerInvoker: RunnerInvoker = mock(RunnerInvoker); + stubLocalization(runnerInvoker); + + return { gitInvoker, logger, octokitWrapper, runnerInvoker }; +}; From 4c857c854628bf3e99c453701c2b68860af53d64 Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Fri, 17 Apr 2026 19:13:35 +0100 Subject: [PATCH 06/27] Extract createSut factory per spec family MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a `createSut(...)` helper to each `*TestSetup.ts` file that constructs the SUT from the mocked collaborators. Tests previously inlined the full `new X(instance(a), instance(b), ...)` block at every call site — now each test constructs the SUT on a single line: const sut = createSut(logger, runnerInvoker); Removes ~500 lines of boilerplate across the 22 split spec files while keeping test bodies otherwise unchanged. Unused `instance` imports pruned where no longer referenced. Implements step 6 of TestPlan.md. Step 5 (test data builders) was evaluated and skipped — the targeted constructors all take 3–5 simple positional args, where a builder would add verbosity without improving readability. --- .../metrics/codeMetrics.defaults.spec.ts | 13 +- .../metrics/codeMetrics.edgeCases.spec.ts | 20 +--- .../metrics/codeMetrics.fileMatching.spec.ts | 41 ++----- ....getDeletedFilesNotRequiringReview.spec.ts | 34 ++---- ...Metrics.getFilesNotRequiringReview.spec.ts | 34 ++---- .../metrics/codeMetrics.nonDefaults.spec.ts | 13 +- .../tests/metrics/codeMetricsTestSetup.ts | 24 +++- .../tests/metrics/inputs.allInputs.spec.ts | 15 +-- .../metrics/inputs.alwaysCloseComment.spec.ts | 15 +-- .../tests/metrics/inputs.baseSize.spec.ts | 20 ++-- .../metrics/inputs.codeFileExtensions.spec.ts | 40 ++----- .../inputs.fileMatchingPatterns.spec.ts | 40 ++----- .../tests/metrics/inputs.growthRate.spec.ts | 20 ++-- .../tests/metrics/inputs.property.spec.ts | 9 +- .../tests/metrics/inputs.testFactor.spec.ts | 25 ++-- .../inputs.testMatchingPatterns.spec.ts | 30 ++--- src/task/tests/metrics/inputsTestSetup.ts | 15 ++- .../azureReposInvoker.createComment.spec.ts | 51 ++------ ...reReposInvoker.deleteCommentThread.spec.ts | 30 ++--- .../azureReposInvoker.getComments.spec.ts | 78 ++---------- ...eposInvoker.getTitleAndDescription.spec.ts | 78 ++---------- ...eposInvoker.isAccessTokenAvailable.spec.ts | 30 ++--- ...eposInvoker.setTitleAndDescription.spec.ts | 59 ++-------- .../azureReposInvoker.updateComment.spec.ts | 67 ++--------- .../tests/repos/azureReposInvokerTestSetup.ts | 25 ++++ .../gitHubReposInvoker.createComment.spec.ts | 69 ++--------- ...ubReposInvoker.deleteCommentThread.spec.ts | 13 +- .../gitHubReposInvoker.getComments.spec.ts | 34 ++---- ...eposInvoker.getTitleAndDescription.spec.ts | 111 +++--------------- ...eposInvoker.isAccessTokenAvailable.spec.ts | 27 ++--- ...eposInvoker.setTitleAndDescription.spec.ts | 34 ++---- .../gitHubReposInvoker.updateComment.spec.ts | 20 +--- .../repos/gitHubReposInvokerTestSetup.ts | 24 +++- 33 files changed, 335 insertions(+), 823 deletions(-) diff --git a/src/task/tests/metrics/codeMetrics.defaults.spec.ts b/src/task/tests/metrics/codeMetrics.defaults.spec.ts index c33fead7c..14742d013 100644 --- a/src/task/tests/metrics/codeMetrics.defaults.spec.ts +++ b/src/task/tests/metrics/codeMetrics.defaults.spec.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. */ -import { instance, when } from "ts-mockito"; + +import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; import CodeMetrics from "../../src/metrics/codeMetrics.js"; import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; import GitInvoker from "../../src/git/gitInvoker.js"; @@ -11,7 +12,8 @@ import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; -import { createCodeMetricsMocks } from "./codeMetricsTestSetup.js"; +import { when } from "ts-mockito"; + describe("codeMetrics.ts", (): void => { let gitInvoker: GitInvoker; @@ -471,12 +473,7 @@ describe("codeMetrics.ts", (): void => { when(gitInvoker.getDiffSummary()).thenResolve(gitResponse); // Act - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // Assert assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), []); diff --git a/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts b/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts index d520ff558..9b911fcfe 100644 --- a/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts +++ b/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts @@ -3,7 +3,9 @@ * Licensed under the MIT License. */ -import { anyString, instance, when } from "ts-mockito"; + +import { anyString, when } from "ts-mockito"; +import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; import CodeMetrics from "../../src/metrics/codeMetrics.js"; import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; import GitInvoker from "../../src/git/gitInvoker.js"; @@ -11,7 +13,7 @@ import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; -import { createCodeMetricsMocks } from "./codeMetricsTestSetup.js"; + describe("codeMetrics.ts", (): void => { let gitInvoker: GitInvoker; @@ -34,12 +36,7 @@ describe("codeMetrics.ts", (): void => { when(gitInvoker.getDiffSummary()).thenResolve("1\t0\tfile.ts"); // Act - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // Assert assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), []); @@ -69,12 +66,7 @@ describe("codeMetrics.ts", (): void => { anyString() as string, ), ).thenReturn(""); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // ACT const size: string = await codeMetrics.getSize(); diff --git a/src/task/tests/metrics/codeMetrics.fileMatching.spec.ts b/src/task/tests/metrics/codeMetrics.fileMatching.spec.ts index 7e8d38c6b..84f8b118f 100644 --- a/src/task/tests/metrics/codeMetrics.fileMatching.spec.ts +++ b/src/task/tests/metrics/codeMetrics.fileMatching.spec.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. */ -import { instance, when } from "ts-mockito"; + +import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; import CodeMetrics from "../../src/metrics/codeMetrics.js"; import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; import GitInvoker from "../../src/git/gitInvoker.js"; @@ -11,7 +12,8 @@ import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; -import { createCodeMetricsMocks } from "./codeMetricsTestSetup.js"; +import { when } from "ts-mockito"; + describe("codeMetrics.ts", (): void => { let gitInvoker: GitInvoker; @@ -64,12 +66,7 @@ describe("codeMetrics.ts", (): void => { when(gitInvoker.getDiffSummary()).thenResolve(gitResponse); // Act - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // Assert assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), [ @@ -104,12 +101,7 @@ describe("codeMetrics.ts", (): void => { ); // Act - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // Assert assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), [ @@ -138,12 +130,7 @@ describe("codeMetrics.ts", (): void => { ); // Act - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // Assert assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), [ @@ -177,12 +164,7 @@ describe("codeMetrics.ts", (): void => { ); // Act - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // Assert assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), [ @@ -208,12 +190,7 @@ describe("codeMetrics.ts", (): void => { ); // Act - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // Assert assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), []); diff --git a/src/task/tests/metrics/codeMetrics.getDeletedFilesNotRequiringReview.spec.ts b/src/task/tests/metrics/codeMetrics.getDeletedFilesNotRequiringReview.spec.ts index 24b44d0e6..b9d71083e 100644 --- a/src/task/tests/metrics/codeMetrics.getDeletedFilesNotRequiringReview.spec.ts +++ b/src/task/tests/metrics/codeMetrics.getDeletedFilesNotRequiringReview.spec.ts @@ -3,15 +3,17 @@ * Licensed under the MIT License. */ + import * as AssertExtensions from "../testUtilities/assertExtensions.js"; -import { instance, when } from "ts-mockito"; +import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; import CodeMetrics from "../../src/metrics/codeMetrics.js"; import GitInvoker from "../../src/git/gitInvoker.js"; import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; -import { createCodeMetricsMocks } from "./codeMetricsTestSetup.js"; +import { when } from "ts-mockito"; + describe("codeMetrics.ts", (): void => { let gitInvoker: GitInvoker; @@ -32,12 +34,7 @@ describe("codeMetrics.ts", (): void => { it("should return an empty array when the Git diff summary '' is empty", async (): Promise => { // Arrange when(gitInvoker.getDiffSummary()).thenResolve(""); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // Act const result: string[] = @@ -50,12 +47,7 @@ describe("codeMetrics.ts", (): void => { it("should throw when the file name in the Git diff summary '0' cannot be parsed", async (): Promise => { // Arrange when(gitInvoker.getDiffSummary()).thenResolve("0"); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // Act const func: () => Promise = async () => @@ -71,12 +63,7 @@ describe("codeMetrics.ts", (): void => { it("should throw when the lines added in the Git diff summary cannot be converted", async (): Promise => { // Arrange when(gitInvoker.getDiffSummary()).thenResolve("A\t0\tfile.ts"); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // Act const func: () => Promise = async () => @@ -92,12 +79,7 @@ describe("codeMetrics.ts", (): void => { it("should throw when the lines deleted in the Git diff summary cannot be converted", async (): Promise => { // Arrange when(gitInvoker.getDiffSummary()).thenResolve("0\tA\tfile.ts"); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // Act const func: () => Promise = async () => diff --git a/src/task/tests/metrics/codeMetrics.getFilesNotRequiringReview.spec.ts b/src/task/tests/metrics/codeMetrics.getFilesNotRequiringReview.spec.ts index b3b72e3b7..7ba476c21 100644 --- a/src/task/tests/metrics/codeMetrics.getFilesNotRequiringReview.spec.ts +++ b/src/task/tests/metrics/codeMetrics.getFilesNotRequiringReview.spec.ts @@ -3,15 +3,17 @@ * Licensed under the MIT License. */ + import * as AssertExtensions from "../testUtilities/assertExtensions.js"; -import { instance, when } from "ts-mockito"; +import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; import CodeMetrics from "../../src/metrics/codeMetrics.js"; import GitInvoker from "../../src/git/gitInvoker.js"; import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; -import { createCodeMetricsMocks } from "./codeMetricsTestSetup.js"; +import { when } from "ts-mockito"; + describe("codeMetrics.ts", (): void => { let gitInvoker: GitInvoker; @@ -36,12 +38,7 @@ describe("codeMetrics.ts", (): void => { it(`should return an empty array when the Git diff summary '${gitDiffSummary}' is empty`, async (): Promise => { // Arrange when(gitInvoker.getDiffSummary()).thenResolve(gitDiffSummary); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // Act const result: string[] = @@ -90,12 +87,7 @@ describe("codeMetrics.ts", (): void => { it(`should throw when the file name in the Git diff summary '${summary}' cannot be parsed`, async (): Promise => { // Arrange when(gitInvoker.getDiffSummary()).thenResolve(summary); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // Act const func: () => Promise = async () => @@ -113,12 +105,7 @@ describe("codeMetrics.ts", (): void => { it("should throw when the lines added in the Git diff summary cannot be converted", async (): Promise => { // Arrange when(gitInvoker.getDiffSummary()).thenResolve("A\t0\tfile.ts"); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // Act const func: () => Promise = async () => @@ -134,12 +121,7 @@ describe("codeMetrics.ts", (): void => { it("should throw when the lines deleted in the Git diff summary cannot be converted", async (): Promise => { // Arrange when(gitInvoker.getDiffSummary()).thenResolve("0\tA\tfile.ts"); - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // Act const func: () => Promise = async () => diff --git a/src/task/tests/metrics/codeMetrics.nonDefaults.spec.ts b/src/task/tests/metrics/codeMetrics.nonDefaults.spec.ts index de121786f..abc4b0045 100644 --- a/src/task/tests/metrics/codeMetrics.nonDefaults.spec.ts +++ b/src/task/tests/metrics/codeMetrics.nonDefaults.spec.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. */ -import { instance, when } from "ts-mockito"; + +import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; import CodeMetrics from "../../src/metrics/codeMetrics.js"; import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; import GitInvoker from "../../src/git/gitInvoker.js"; @@ -11,7 +12,8 @@ import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; -import { createCodeMetricsMocks } from "./codeMetricsTestSetup.js"; +import { when } from "ts-mockito"; + describe("codeMetrics.ts", (): void => { let gitInvoker: GitInvoker; @@ -536,12 +538,7 @@ describe("codeMetrics.ts", (): void => { when(gitInvoker.getDiffSummary()).thenResolve(gitResponse); // Act - const codeMetrics: CodeMetrics = new CodeMetrics( - instance(gitInvoker), - instance(inputs), - instance(logger), - instance(runnerInvoker), - ); + const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); // Assert assert.deepEqual( diff --git a/src/task/tests/metrics/codeMetricsTestSetup.ts b/src/task/tests/metrics/codeMetricsTestSetup.ts index 8f989ec3f..deda70186 100644 --- a/src/task/tests/metrics/codeMetricsTestSetup.ts +++ b/src/task/tests/metrics/codeMetricsTestSetup.ts @@ -4,7 +4,8 @@ */ import * as InputsDefault from "../../src/metrics/inputsDefault.js"; -import { mock, when } from "ts-mockito"; +import { instance, mock, when } from "ts-mockito"; +import CodeMetrics from "../../src/metrics/codeMetrics.js"; import GitInvoker from "../../src/git/gitInvoker.js"; import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; @@ -48,3 +49,24 @@ export const createCodeMetricsMocks = (): CodeMetricsMocks => { return { gitInvoker, inputs, logger, runnerInvoker }; }; + +/** + * Constructs a `CodeMetrics` instance from the supplied mocks. + * @param gitInvoker The mocked git invoker. + * @param inputs The mocked inputs. + * @param logger The mocked logger. + * @param runnerInvoker The mocked runner invoker. + * @returns The constructed `CodeMetrics` instance. + */ +export const createSut = ( + gitInvoker: GitInvoker, + inputs: Inputs, + logger: Logger, + runnerInvoker: RunnerInvoker, +): CodeMetrics => + new CodeMetrics( + instance(gitInvoker), + instance(inputs), + instance(logger), + instance(runnerInvoker), + ); diff --git a/src/task/tests/metrics/inputs.allInputs.spec.ts b/src/task/tests/metrics/inputs.allInputs.spec.ts index ba722550d..69273160d 100644 --- a/src/task/tests/metrics/inputs.allInputs.spec.ts +++ b/src/task/tests/metrics/inputs.allInputs.spec.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. */ + import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { adjustingAlwaysCloseComment, @@ -13,6 +14,7 @@ import { adjustingTestFactorResource, adjustingTestMatchingPatternsResource, createInputsMocks, + createSut, settingAlwaysCloseComment, settingBaseSizeResource, settingCodeFileExtensionsResource, @@ -21,12 +23,13 @@ import { settingTestFactorResource, settingTestMatchingPatternsResource, } from "./inputsTestSetup.js"; -import { deepEqual, instance, verify, when } from "ts-mockito"; +import { deepEqual, verify, when } from "ts-mockito"; import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; + describe("inputs.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; @@ -39,10 +42,7 @@ describe("inputs.ts", (): void => { describe("all inputs", (): void => { it("should set all default values when nothing is specified", (): void => { // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.equal(inputs.baseSize, InputsDefault.baseSize); @@ -98,10 +98,7 @@ describe("inputs.ts", (): void => { ).thenReturn("js\nts"); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.equal(inputs.baseSize, 5.0); diff --git a/src/task/tests/metrics/inputs.alwaysCloseComment.spec.ts b/src/task/tests/metrics/inputs.alwaysCloseComment.spec.ts index 894ebd969..2d08a2ba3 100644 --- a/src/task/tests/metrics/inputs.alwaysCloseComment.spec.ts +++ b/src/task/tests/metrics/inputs.alwaysCloseComment.spec.ts @@ -3,18 +3,21 @@ * Licensed under the MIT License. */ + import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { adjustingAlwaysCloseComment, createInputsMocks, + createSut, settingAlwaysCloseComment, } from "./inputsTestSetup.js"; -import { deepEqual, instance, verify, when } from "ts-mockito"; +import { deepEqual, verify, when } from "ts-mockito"; import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; + describe("inputs.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; @@ -47,10 +50,7 @@ describe("inputs.ts", (): void => { ).thenReturn(alwaysCloseComment); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.equal( @@ -73,10 +73,7 @@ describe("inputs.ts", (): void => { ).thenReturn(alwaysCloseComment); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.equal(inputs.alwaysCloseComment, true); diff --git a/src/task/tests/metrics/inputs.baseSize.spec.ts b/src/task/tests/metrics/inputs.baseSize.spec.ts index 15db17a39..8af4c4b1b 100644 --- a/src/task/tests/metrics/inputs.baseSize.spec.ts +++ b/src/task/tests/metrics/inputs.baseSize.spec.ts @@ -3,19 +3,22 @@ * Licensed under the MIT License. */ + import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { adjustingBaseSizeResource, createInputsMocks, + createSut, settingBaseSizeResource, } from "./inputsTestSetup.js"; -import { deepEqual, instance, verify, when } from "ts-mockito"; +import { deepEqual, verify, when } from "ts-mockito"; import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { decimalRadix } from "../../src/utilities/constants.js"; + describe("inputs.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; @@ -46,10 +49,7 @@ describe("inputs.ts", (): void => { ).thenReturn(baseSize); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.equal(inputs.baseSize, InputsDefault.baseSize); @@ -69,10 +69,7 @@ describe("inputs.ts", (): void => { ).thenReturn(baseSize); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.equal(inputs.baseSize, InputsDefault.baseSize); @@ -92,10 +89,7 @@ describe("inputs.ts", (): void => { ).thenReturn(baseSize); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.equal(inputs.baseSize, parseInt(baseSize, decimalRadix)); diff --git a/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts b/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts index 3943777d7..fff384c56 100644 --- a/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts +++ b/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts @@ -3,18 +3,21 @@ * Licensed under the MIT License. */ + import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { adjustingCodeFileExtensionsResource, createInputsMocks, + createSut, settingCodeFileExtensionsResource, } from "./inputsTestSetup.js"; -import { deepEqual, instance, verify, when } from "ts-mockito"; +import { deepEqual, verify, when } from "ts-mockito"; import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; + describe("inputs.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; @@ -36,10 +39,7 @@ describe("inputs.ts", (): void => { ).thenReturn(codeFileExtensions); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual( @@ -69,10 +69,7 @@ describe("inputs.ts", (): void => { ).thenReturn(codeFileExtensions); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual(inputs.codeFileExtensions, expectedResult); @@ -88,10 +85,7 @@ describe("inputs.ts", (): void => { ).thenReturn("ada\nada"); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual(inputs.codeFileExtensions, new Set(["ada"])); @@ -105,10 +99,7 @@ describe("inputs.ts", (): void => { ).thenReturn("ADA\ncS\nTxT"); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual( @@ -125,10 +116,7 @@ describe("inputs.ts", (): void => { ).thenReturn("*.ada\n.txt"); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual( @@ -145,10 +133,7 @@ describe("inputs.ts", (): void => { ).thenReturn("ADA\ncS\nTxT"); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual( @@ -165,10 +150,7 @@ describe("inputs.ts", (): void => { ).thenReturn("ada\ncs\ntxt\n"); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual( diff --git a/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts b/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts index d4514b9a9..1399d9bdb 100644 --- a/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts +++ b/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts @@ -3,18 +3,21 @@ * Licensed under the MIT License. */ + import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { adjustingFileMatchingPatternsResource, createInputsMocks, + createSut, settingFileMatchingPatternsResource, } from "./inputsTestSetup.js"; -import { deepEqual, instance, verify, when } from "ts-mockito"; +import { deepEqual, verify, when } from "ts-mockito"; import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; + describe("inputs.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; @@ -38,10 +41,7 @@ describe("inputs.ts", (): void => { ).thenReturn(fileMatchingPatterns); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual( @@ -72,10 +72,7 @@ describe("inputs.ts", (): void => { ).thenReturn(fileMatchingPatterns); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual(inputs.fileMatchingPatterns, [ @@ -106,10 +103,7 @@ describe("inputs.ts", (): void => { ).thenReturn(fileMatchingPatterns); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual(inputs.fileMatchingPatterns, expectedOutput); @@ -128,10 +122,7 @@ describe("inputs.ts", (): void => { ).thenReturn("folder1\\file.js\nfolder2\\*.js"); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual(inputs.fileMatchingPatterns, [ @@ -148,10 +139,7 @@ describe("inputs.ts", (): void => { ).thenReturn("file.js\n"); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual(inputs.fileMatchingPatterns, ["file.js"]); @@ -165,10 +153,7 @@ describe("inputs.ts", (): void => { ).thenReturn(" pattern1 \n\n pattern2 \n \npattern3"); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual(inputs.fileMatchingPatterns, [ @@ -189,10 +174,7 @@ describe("inputs.ts", (): void => { ).thenReturn(patterns.join("\n")); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.equal(inputs.fileMatchingPatterns.length, 200); diff --git a/src/task/tests/metrics/inputs.growthRate.spec.ts b/src/task/tests/metrics/inputs.growthRate.spec.ts index 14e9f2c58..a245c6229 100644 --- a/src/task/tests/metrics/inputs.growthRate.spec.ts +++ b/src/task/tests/metrics/inputs.growthRate.spec.ts @@ -3,18 +3,21 @@ * Licensed under the MIT License. */ + import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { adjustingGrowthRateResource, createInputsMocks, + createSut, settingGrowthRateResource, } from "./inputsTestSetup.js"; -import { deepEqual, instance, verify, when } from "ts-mockito"; +import { deepEqual, verify, when } from "ts-mockito"; import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; + describe("inputs.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; @@ -46,10 +49,7 @@ describe("inputs.ts", (): void => { ).thenReturn(growthRate); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.equal(inputs.growthRate, InputsDefault.growthRate); @@ -77,10 +77,7 @@ describe("inputs.ts", (): void => { ).thenReturn(growthRate); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.equal(inputs.growthRate, InputsDefault.growthRate); @@ -109,10 +106,7 @@ describe("inputs.ts", (): void => { ).thenReturn(growthRate); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.equal(inputs.growthRate, parseFloat(growthRate)); diff --git a/src/task/tests/metrics/inputs.property.spec.ts b/src/task/tests/metrics/inputs.property.spec.ts index b7969b39a..898b90561 100644 --- a/src/task/tests/metrics/inputs.property.spec.ts +++ b/src/task/tests/metrics/inputs.property.spec.ts @@ -3,14 +3,21 @@ * Licensed under the MIT License. */ + import * as fc from "fast-check"; -import { deepEqual, instance, mock, when } from "ts-mockito"; +import { + deepEqual, + instance, + mock, + when, +} from "ts-mockito"; import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { anyString } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; + const numRuns = 10; describe("inputs.ts", (): void => { diff --git a/src/task/tests/metrics/inputs.testFactor.spec.ts b/src/task/tests/metrics/inputs.testFactor.spec.ts index ceae78310..f0555bf23 100644 --- a/src/task/tests/metrics/inputs.testFactor.spec.ts +++ b/src/task/tests/metrics/inputs.testFactor.spec.ts @@ -3,19 +3,22 @@ * Licensed under the MIT License. */ + import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { adjustingTestFactorResource, createInputsMocks, + createSut, disablingTestFactorResource, settingTestFactorResource, } from "./inputsTestSetup.js"; -import { deepEqual, instance, verify, when } from "ts-mockito"; +import { deepEqual, verify, when } from "ts-mockito"; import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; + describe("inputs.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; @@ -47,10 +50,7 @@ describe("inputs.ts", (): void => { ).thenReturn(testFactor); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.equal(inputs.testFactor, InputsDefault.testFactor); @@ -76,10 +76,7 @@ describe("inputs.ts", (): void => { ).thenReturn(testFactor); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.equal(inputs.testFactor, InputsDefault.testFactor); @@ -108,10 +105,7 @@ describe("inputs.ts", (): void => { ).thenReturn(testFactor); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.equal(inputs.testFactor, parseFloat(testFactor)); @@ -131,10 +125,7 @@ describe("inputs.ts", (): void => { ).thenReturn(testFactor); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.equal(inputs.testFactor, null); diff --git a/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts b/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts index b1041364b..eaade856c 100644 --- a/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts +++ b/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts @@ -3,18 +3,21 @@ * Licensed under the MIT License. */ + import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { adjustingTestMatchingPatternsResource, createInputsMocks, + createSut, settingTestMatchingPatternsResource, } from "./inputsTestSetup.js"; -import { deepEqual, instance, verify, when } from "ts-mockito"; +import { deepEqual, verify, when } from "ts-mockito"; import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; + describe("inputs.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; @@ -38,10 +41,7 @@ describe("inputs.ts", (): void => { ).thenReturn(testMatchingPatterns); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual( @@ -72,10 +72,7 @@ describe("inputs.ts", (): void => { ).thenReturn(testMatchingPatterns); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual(inputs.testMatchingPatterns, [ @@ -106,10 +103,7 @@ describe("inputs.ts", (): void => { ).thenReturn(testMatchingPatterns); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual(inputs.testMatchingPatterns, expectedOutput); @@ -128,10 +122,7 @@ describe("inputs.ts", (): void => { ).thenReturn("folder1\\file.js\nfolder2\\*.js"); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual(inputs.testMatchingPatterns, [ @@ -148,10 +139,7 @@ describe("inputs.ts", (): void => { ).thenReturn("file.js\n"); // Act - const inputs: Inputs = new Inputs( - instance(logger), - instance(runnerInvoker), - ); + const inputs: Inputs = createSut(logger, runnerInvoker); // Assert assert.deepEqual(inputs.testMatchingPatterns, ["file.js"]); diff --git a/src/task/tests/metrics/inputsTestSetup.ts b/src/task/tests/metrics/inputsTestSetup.ts index df99023bf..2457c4502 100644 --- a/src/task/tests/metrics/inputsTestSetup.ts +++ b/src/task/tests/metrics/inputsTestSetup.ts @@ -4,7 +4,8 @@ */ import * as InputsDefault from "../../src/metrics/inputsDefault.js"; -import { deepEqual, mock, when } from "ts-mockito"; +import { deepEqual, instance, mock, when } from "ts-mockito"; +import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { anyString } from "../testUtilities/mockito.js"; @@ -129,3 +130,15 @@ export const createInputsMocks = (): InputsMocks => { return { logger, runnerInvoker }; }; + +/** + * Constructs an `Inputs` instance from the supplied mocks. Tests use this in + * place of inline `new Inputs(instance(...), ...)` calls. + * @param logger The mocked logger. + * @param runnerInvoker The mocked runner invoker. + * @returns The constructed `Inputs` instance. + */ +export const createSut = ( + logger: Logger, + runnerInvoker: RunnerInvoker, +): Inputs => new Inputs(instance(logger), instance(runnerInvoker)); diff --git a/src/task/tests/repos/azureReposInvoker.createComment.spec.ts b/src/task/tests/repos/azureReposInvoker.createComment.spec.ts index 4f7032042..1ea2aceb7 100644 --- a/src/task/tests/repos/azureReposInvoker.createComment.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.createComment.spec.ts @@ -3,14 +3,11 @@ * Licensed under the MIT License. */ + import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import { CommentThreadStatus, type GitPullRequestCommentThread } from "azure-devops-node-api/interfaces/GitInterfaces.js"; -import { - deepEqual, - instance, - verify, - when, -} from "ts-mockito"; +import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; +import { deepEqual, verify, when } from "ts-mockito"; import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; import ErrorWithStatus from "../wrappers/errorWithStatus.js"; @@ -22,7 +19,7 @@ import { StatusCodes } from "http-status-codes"; import TokenManager from "../../src/repos/tokenManager.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; -import { createAzureReposInvokerMocks } from "./azureReposInvokerTestSetup.js"; + describe("azureReposInvoker.ts", (): void => { let gitApi: IGitApi; @@ -59,13 +56,7 @@ describe("azureReposInvoker.ts", (): void => { when(gitApi.createThread(any(), "RepoID", 10, "Project")).thenThrow( error, ); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const func: () => Promise = async () => @@ -113,13 +104,7 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.createComment( @@ -160,13 +145,7 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.createComment( @@ -223,13 +202,7 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.createComment( @@ -281,13 +254,7 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.createComment( diff --git a/src/task/tests/repos/azureReposInvoker.deleteCommentThread.spec.ts b/src/task/tests/repos/azureReposInvoker.deleteCommentThread.spec.ts index b59115fb6..e8f7d8b77 100644 --- a/src/task/tests/repos/azureReposInvoker.deleteCommentThread.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.deleteCommentThread.spec.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. */ + import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import { any, anyNumber } from "../testUtilities/mockito.js"; -import { instance, verify, when } from "ts-mockito"; +import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; +import { verify, when } from "ts-mockito"; import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; import ErrorWithStatus from "../wrappers/errorWithStatus.js"; @@ -16,7 +18,7 @@ import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { StatusCodes } from "http-status-codes"; import TokenManager from "../../src/repos/tokenManager.js"; import assert from "node:assert/strict"; -import { createAzureReposInvokerMocks } from "./azureReposInvokerTestSetup.js"; + describe("azureReposInvoker.ts", (): void => { let gitApi: IGitApi; @@ -53,13 +55,7 @@ describe("azureReposInvoker.ts", (): void => { when(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).thenThrow( error, ); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const func: () => Promise = async () => @@ -92,13 +88,7 @@ describe("azureReposInvoker.ts", (): void => { it("should call the API for a single comment", async (): Promise => { // Arrange when(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).thenResolve(); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.deleteCommentThread(20); @@ -119,13 +109,7 @@ describe("azureReposInvoker.ts", (): void => { when( gitApi.deleteComment("RepoID", 10, anyNumber(), 1, "Project"), ).thenResolve(); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.deleteCommentThread(20); diff --git a/src/task/tests/repos/azureReposInvoker.getComments.spec.ts b/src/task/tests/repos/azureReposInvoker.getComments.spec.ts index 98c618c7d..db0cecafd 100644 --- a/src/task/tests/repos/azureReposInvoker.getComments.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.getComments.spec.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. */ + import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import { CommentThreadStatus, type GitPullRequestCommentThread } from "azure-devops-node-api/interfaces/GitInterfaces.js"; -import { instance, verify, when } from "ts-mockito"; +import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; +import { verify, when } from "ts-mockito"; import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; import type CommentData from "../../src/repos/interfaces/commentData.js"; @@ -18,7 +20,7 @@ import { StatusCodes } from "http-status-codes"; import TokenManager from "../../src/repos/tokenManager.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; -import { createAzureReposInvokerMocks } from "./azureReposInvokerTestSetup.js"; + describe("azureReposInvoker.ts", (): void => { let gitApi: IGitApi; @@ -53,13 +55,7 @@ describe("azureReposInvoker.ts", (): void => { const error: ErrorWithStatus = new ErrorWithStatus("Test"); error.statusCode = statusCode; when(gitApi.getThreads("RepoID", 10, "Project")).thenThrow(error); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const func: () => Promise = async () => @@ -94,13 +90,7 @@ describe("azureReposInvoker.ts", (): void => { when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ { comments: [{ content: "Content" }], id: 1, status: 1 }, ]); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const result: CommentData = await azureReposInvoker.getComments(); @@ -134,13 +124,7 @@ describe("azureReposInvoker.ts", (): void => { threadContext: null as unknown as undefined, }, ]); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const result: CommentData = await azureReposInvoker.getComments(); @@ -174,13 +158,7 @@ describe("azureReposInvoker.ts", (): void => { threadContext: { filePath: "/file.ts" }, }, ]); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const result: CommentData = await azureReposInvoker.getComments(); @@ -213,13 +191,7 @@ describe("azureReposInvoker.ts", (): void => { threadContext: { filePath: "/file.ts" }, }, ]); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const result: CommentData = await azureReposInvoker.getComments(); @@ -252,13 +224,7 @@ describe("azureReposInvoker.ts", (): void => { when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ { comments: [{ content: "Content" }], id: 1, status: 1 }, ]); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.getComments(); @@ -288,13 +254,7 @@ describe("azureReposInvoker.ts", (): void => { when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ { comments: [{ content: "Content" }], status: 1 }, ]); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const func: () => Promise = async () => @@ -328,13 +288,7 @@ describe("azureReposInvoker.ts", (): void => { when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve( getThreadsResult, ); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const result: CommentData = await azureReposInvoker.getComments(); @@ -404,13 +358,7 @@ describe("azureReposInvoker.ts", (): void => { when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve( getThreadsResult, ); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const result: CommentData = await azureReposInvoker.getComments(); diff --git a/src/task/tests/repos/azureReposInvoker.getTitleAndDescription.spec.ts b/src/task/tests/repos/azureReposInvoker.getTitleAndDescription.spec.ts index bbe9293c0..b9e9b6661 100644 --- a/src/task/tests/repos/azureReposInvoker.getTitleAndDescription.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.getTitleAndDescription.spec.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. */ + import * as AssertExtensions from "../testUtilities/assertExtensions.js"; -import { instance, verify, when } from "ts-mockito"; +import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; +import { verify, when } from "ts-mockito"; import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; import ErrorWithStatus from "../wrappers/errorWithStatus.js"; @@ -17,9 +19,9 @@ import { StatusCodes } from "http-status-codes"; import TokenManager from "../../src/repos/tokenManager.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; -import { createAzureReposInvokerMocks } from "./azureReposInvokerTestSetup.js"; import { stubEnv } from "../testUtilities/stubEnv.js"; + describe("azureReposInvoker.ts", (): void => { let gitApi: IGitApi; let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; @@ -52,13 +54,7 @@ describe("azureReposInvoker.ts", (): void => { stubEnv(["SYSTEM_TEAMPROJECT", variable]); } - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const func: () => Promise = async () => @@ -85,13 +81,7 @@ describe("azureReposInvoker.ts", (): void => { stubEnv(["BUILD_REPOSITORY_ID", variable]); } - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const func: () => Promise = async () => @@ -118,13 +108,7 @@ describe("azureReposInvoker.ts", (): void => { stubEnv(["PR_METRICS_ACCESS_TOKEN", variable]); } - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const func: () => Promise = async () => @@ -151,13 +135,7 @@ describe("azureReposInvoker.ts", (): void => { stubEnv(["SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", variable]); } - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const func: () => Promise = async () => @@ -188,13 +166,7 @@ describe("azureReposInvoker.ts", (): void => { const error: ErrorWithStatus = new ErrorWithStatus("Test"); error.statusCode = statusCode; when(gitApi.getPullRequestById(10, "Project")).thenThrow(error); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const func: () => Promise = async () => @@ -230,13 +202,7 @@ describe("azureReposInvoker.ts", (): void => { description: "Description", title: "Title", }); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const result: PullRequestDetailsInterface = @@ -261,13 +227,7 @@ describe("azureReposInvoker.ts", (): void => { description: "Description", title: "Title", }); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.getTitleAndDescription(); @@ -292,13 +252,7 @@ describe("azureReposInvoker.ts", (): void => { when(gitApi.getPullRequestById(10, "Project")).thenResolve({ title: "Title", }); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const result: PullRequestDetailsInterface = @@ -320,13 +274,7 @@ describe("azureReposInvoker.ts", (): void => { it("should throw when the title is unavailable", async (): Promise => { // Arrange when(gitApi.getPullRequestById(10, "Project")).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const func: () => Promise = async () => diff --git a/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts b/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts index 32056f63b..29bcd6a94 100644 --- a/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. */ -import { instance, when } from "ts-mockito"; + +import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; import GitInvoker from "../../src/git/gitInvoker.js"; @@ -11,8 +12,9 @@ import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import TokenManager from "../../src/repos/tokenManager.js"; import assert from "node:assert/strict"; -import { createAzureReposInvokerMocks } from "./azureReposInvokerTestSetup.js"; import { stubEnv } from "../testUtilities/stubEnv.js"; +import { when } from "ts-mockito"; + describe("azureReposInvoker.ts", (): void => { let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; @@ -34,13 +36,7 @@ describe("azureReposInvoker.ts", (): void => { describe("isAccessTokenAvailable()", (): void => { it("should return null when the token exists", async (): Promise => { // Arrange - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const result: string | null = @@ -53,13 +49,7 @@ describe("azureReposInvoker.ts", (): void => { it("should return a string when the token manager fails", async (): Promise => { // Arrange stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); when(tokenManager.getToken()).thenResolve("Failure"); // Act @@ -73,13 +63,7 @@ describe("azureReposInvoker.ts", (): void => { it("should return a string when the token does not exist", async (): Promise => { // Arrange stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const result: string | null = diff --git a/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts b/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts index 286a90648..1eeb1eb83 100644 --- a/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts @@ -3,13 +3,10 @@ * Licensed under the MIT License. */ + import * as AssertExtensions from "../testUtilities/assertExtensions.js"; -import { - deepEqual, - instance, - verify, - when, -} from "ts-mockito"; +import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; +import { deepEqual, verify, when } from "ts-mockito"; import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; import ErrorWithStatus from "../wrappers/errorWithStatus.js"; @@ -22,7 +19,7 @@ import { StatusCodes } from "http-status-codes"; import TokenManager from "../../src/repos/tokenManager.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; -import { createAzureReposInvokerMocks } from "./azureReposInvokerTestSetup.js"; + describe("azureReposInvoker.ts", (): void => { let gitApi: IGitApi; @@ -59,13 +56,7 @@ describe("azureReposInvoker.ts", (): void => { when( gitApi.updatePullRequest(any(), "RepoID", 10, "Project"), ).thenThrow(error); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const func: () => Promise = async () => @@ -99,13 +90,7 @@ describe("azureReposInvoker.ts", (): void => { it("should not call the API when the title and description are null", async (): Promise => { // Arrange - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.setTitleAndDescription(null, null); @@ -136,13 +121,7 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.setTitleAndDescription("Title", null); @@ -178,13 +157,7 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.setTitleAndDescription(null, "Description"); @@ -221,13 +194,7 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.setTitleAndDescription("Title", "Description"); @@ -264,13 +231,7 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.setTitleAndDescription("Title", "Description"); diff --git a/src/task/tests/repos/azureReposInvoker.updateComment.spec.ts b/src/task/tests/repos/azureReposInvoker.updateComment.spec.ts index 69744fdbb..850cfd6cc 100644 --- a/src/task/tests/repos/azureReposInvoker.updateComment.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.updateComment.spec.ts @@ -3,14 +3,11 @@ * Licensed under the MIT License. */ + import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import { type Comment, CommentThreadStatus, type GitPullRequestCommentThread } from "azure-devops-node-api/interfaces/GitInterfaces.js"; -import { - deepEqual, - instance, - verify, - when, -} from "ts-mockito"; +import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; +import { deepEqual, verify, when } from "ts-mockito"; import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; import ErrorWithStatus from "../wrappers/errorWithStatus.js"; @@ -22,7 +19,7 @@ import { StatusCodes } from "http-status-codes"; import TokenManager from "../../src/repos/tokenManager.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; -import { createAzureReposInvokerMocks } from "./azureReposInvokerTestSetup.js"; + describe("azureReposInvoker.ts", (): void => { let gitApi: IGitApi; @@ -59,13 +56,7 @@ describe("azureReposInvoker.ts", (): void => { when( gitApi.updateComment(any(), "RepoID", 10, 20, 1, "Project"), ).thenThrow(error); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const func: () => Promise = async () => @@ -119,13 +110,7 @@ describe("azureReposInvoker.ts", (): void => { when( gitApi.updateThread(any(), "RepoID", 10, 20, "Project"), ).thenThrow(error); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act const func: () => Promise = async () => @@ -191,13 +176,7 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.updateComment( @@ -250,13 +229,7 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.updateComment(20, "Content", null); @@ -295,13 +268,7 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.updateComment( @@ -331,13 +298,7 @@ describe("azureReposInvoker.ts", (): void => { it("should call no APIs when neither the comment content nor the thread status are updated", async (): Promise => { // Arrange - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.updateComment(20, null, null); @@ -373,13 +334,7 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = new AzureReposInvoker( - instance(azureDevOpsApiWrapper), - instance(gitInvoker), - instance(logger), - instance(runnerInvoker), - instance(tokenManager), - ); + const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); // Act await azureReposInvoker.updateComment(20, "Content", null); diff --git a/src/task/tests/repos/azureReposInvokerTestSetup.ts b/src/task/tests/repos/azureReposInvokerTestSetup.ts index 70e429ef0..6408d108c 100644 --- a/src/task/tests/repos/azureReposInvokerTestSetup.ts +++ b/src/task/tests/repos/azureReposInvokerTestSetup.ts @@ -5,6 +5,7 @@ import { deepEqual, instance, mock, when } from "ts-mockito"; import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; +import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; import GitInvoker from "../../src/git/gitInvoker.js"; import type { IGitApi } from "azure-devops-node-api/GitApi.js"; import type { IRequestHandler } from "azure-devops-node-api/interfaces/common/VsoBaseInterfaces.js"; @@ -77,3 +78,27 @@ export const createAzureReposInvokerMocks = (): AzureReposInvokerMocks => { tokenManager, }; }; + +/** + * Constructs an `AzureReposInvoker` instance from the supplied mocks. + * @param azureDevOpsApiWrapper The mocked Azure DevOps API wrapper. + * @param gitInvoker The mocked git invoker. + * @param logger The mocked logger. + * @param runnerInvoker The mocked runner invoker. + * @param tokenManager The mocked token manager. + * @returns The constructed `AzureReposInvoker` instance. + */ +export const createSut = ( + azureDevOpsApiWrapper: AzureDevOpsApiWrapper, + gitInvoker: GitInvoker, + logger: Logger, + runnerInvoker: RunnerInvoker, + tokenManager: TokenManager, +): AzureReposInvoker => + new AzureReposInvoker( + instance(azureDevOpsApiWrapper), + instance(gitInvoker), + instance(logger), + instance(runnerInvoker), + instance(tokenManager), + ); diff --git a/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts b/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts index ab81747e8..621379e66 100644 --- a/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. */ + import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import * as GitHubReposInvokerConstants from "./gitHubReposInvokerConstants.js"; import { any, anyNumber, anyString } from "../testUtilities/mockito.js"; -import { createGitHubReposInvokerMocks, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; -import { instance, verify, when } from "ts-mockito"; +import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { verify, when } from "ts-mockito"; import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; import GitInvoker from "../../src/git/gitInvoker.js"; import HttpError from "../testUtilities/httpError.js"; @@ -20,6 +21,7 @@ import { StatusCodes } from "http-status-codes"; import assert from "node:assert/strict"; import { createRequestError } from "../testUtilities/createRequestError.js"; + describe("gitHubReposInvoker.ts", (): void => { let gitInvoker: GitInvoker; let logger: Logger; @@ -49,12 +51,7 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act await gitHubReposInvoker.createComment("Content", "file.ts"); @@ -102,12 +99,7 @@ describe("gitHubReposInvoker.ts", (): void => { status: StatusCodes.OK, url: "", }); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const func: () => Promise = async () => @@ -150,12 +142,7 @@ describe("gitHubReposInvoker.ts", (): void => { when( octokitWrapper.listCommits(anyString(), anyString(), anyNumber(), 24), ).thenResolve(GitHubReposInvokerConstants.listCommitsResponse); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act await gitHubReposInvoker.createComment("Content", "file.ts"); @@ -200,12 +187,7 @@ describe("gitHubReposInvoker.ts", (): void => { status: StatusCodes.OK, url: "", }); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const func: () => Promise = async () => @@ -235,12 +217,7 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act await gitHubReposInvoker.createComment("Content", "file.ts"); @@ -286,12 +263,7 @@ describe("gitHubReposInvoker.ts", (): void => { "sha54321", ), ).thenResolve(null); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act await gitHubReposInvoker.createComment("Content", "file.ts"); @@ -350,12 +322,7 @@ describe("gitHubReposInvoker.ts", (): void => { ).thenCall((): void => { throw error; }); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act await gitHubReposInvoker.createComment("Content", "file.ts"); @@ -420,12 +387,7 @@ describe("gitHubReposInvoker.ts", (): void => { ).thenCall((): void => { throw error; }); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const func: () => Promise = async () => @@ -464,12 +426,7 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act await gitHubReposInvoker.createComment("Content", null); diff --git a/src/task/tests/repos/gitHubReposInvoker.deleteCommentThread.spec.ts b/src/task/tests/repos/gitHubReposInvoker.deleteCommentThread.spec.ts index d0ebe285b..a89778e48 100644 --- a/src/task/tests/repos/gitHubReposInvoker.deleteCommentThread.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.deleteCommentThread.spec.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. */ -import { createGitHubReposInvokerMocks, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; -import { instance, verify, when } from "ts-mockito"; + +import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { verify, when } from "ts-mockito"; import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; import GitInvoker from "../../src/git/gitInvoker.js"; import Logger from "../../src/utilities/logger.js"; @@ -14,6 +15,7 @@ import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; + describe("gitHubReposInvoker.ts", (): void => { let gitInvoker: GitInvoker; let logger: Logger; @@ -43,12 +45,7 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act await gitHubReposInvoker.deleteCommentThread(54321); diff --git a/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts b/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts index a5ba8fac0..509e902a2 100644 --- a/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. */ + import * as GitHubReposInvokerConstants from "./gitHubReposInvokerConstants.js"; import { any, anyNumber, anyString } from "../testUtilities/mockito.js"; -import { createGitHubReposInvokerMocks, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; -import { instance, verify, when } from "ts-mockito"; +import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { verify, when } from "ts-mockito"; import type CommentData from "../../src/repos/interfaces/commentData.js"; import { CommentThreadStatus } from "azure-devops-node-api/interfaces/GitInterfaces.js"; import type GetIssueCommentsResponse from "../../src/wrappers/octokitInterfaces/getIssueCommentsResponse.js"; @@ -18,6 +19,7 @@ import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; + describe("gitHubReposInvoker.ts", (): void => { let gitInvoker: GitInvoker; let logger: Logger; @@ -57,12 +59,7 @@ describe("gitHubReposInvoker.ts", (): void => { when( octokitWrapper.getIssueComments(anyString(), anyString(), anyNumber()), ).thenResolve(response); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const result: CommentData = await gitHubReposInvoker.getComments(); @@ -101,12 +98,7 @@ describe("gitHubReposInvoker.ts", (): void => { when( octokitWrapper.getReviewComments(anyString(), anyString(), anyNumber()), ).thenResolve(GitHubReposInvokerConstants.getReviewCommentsResponse); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const result: CommentData = await gitHubReposInvoker.getComments(); @@ -153,12 +145,7 @@ describe("gitHubReposInvoker.ts", (): void => { when( octokitWrapper.getReviewComments(anyString(), anyString(), anyNumber()), ).thenResolve(GitHubReposInvokerConstants.getReviewCommentsResponse); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const result: CommentData = await gitHubReposInvoker.getComments(); @@ -208,12 +195,7 @@ describe("gitHubReposInvoker.ts", (): void => { when( octokitWrapper.getIssueComments(anyString(), anyString(), anyNumber()), ).thenResolve(response); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const result: CommentData = await gitHubReposInvoker.getComments(); diff --git a/src/task/tests/repos/gitHubReposInvoker.getTitleAndDescription.spec.ts b/src/task/tests/repos/gitHubReposInvoker.getTitleAndDescription.spec.ts index ba1dfbc8c..5ef1367bf 100644 --- a/src/task/tests/repos/gitHubReposInvoker.getTitleAndDescription.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.getTitleAndDescription.spec.ts @@ -3,11 +3,12 @@ * Licensed under the MIT License. */ + import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import * as GitHubReposInvokerConstants from "./gitHubReposInvokerConstants.js"; import { any, anyNumber, anyString } from "../testUtilities/mockito.js"; -import { createGitHubReposInvokerMocks, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; -import { instance, verify, when } from "ts-mockito"; +import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { verify, when } from "ts-mockito"; import type ErrorWithStatusInterface from "../../src/repos/interfaces/errorWithStatusInterface.js"; import type GetPullResponse from "../../src/wrappers/octokitInterfaces/getPullResponse.js"; import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; @@ -24,6 +25,7 @@ import assert from "node:assert/strict"; import { createRequestError } from "../testUtilities/createRequestError.js"; import { stubEnv } from "../testUtilities/stubEnv.js"; + describe("gitHubReposInvoker.ts", (): void => { let gitInvoker: GitInvoker; let logger: Logger; @@ -52,12 +54,7 @@ describe("gitHubReposInvoker.ts", (): void => { stubEnv(["SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", variable]); } - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const func: () => Promise = async () => @@ -77,12 +74,7 @@ describe("gitHubReposInvoker.ts", (): void => { stubEnv( ["SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", "https://github.com/microsoft"], ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const func: () => Promise = async () => @@ -110,12 +102,7 @@ describe("gitHubReposInvoker.ts", (): void => { stubEnv(["GITHUB_API_URL", variable]); } - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const func: () => Promise = async () => @@ -147,12 +134,7 @@ describe("gitHubReposInvoker.ts", (): void => { stubEnv(["GITHUB_REPOSITORY_OWNER", variable]); } - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const func: () => Promise = async () => @@ -185,12 +167,7 @@ describe("gitHubReposInvoker.ts", (): void => { stubEnv(["GITHUB_REPOSITORY", variable]); } - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const func: () => Promise = async () => @@ -214,12 +191,7 @@ describe("gitHubReposInvoker.ts", (): void => { stubEnv(["GITHUB_API_URL", "https://api.github.com"]); stubEnv(["GITHUB_REPOSITORY_OWNER", "microsoft"]); stubEnv(["GITHUB_REPOSITORY", "microsoft"]); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const func: () => Promise = async () => @@ -246,12 +218,7 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const result: PullRequestDetailsInterface = @@ -281,12 +248,7 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const result: PullRequestDetailsInterface = @@ -317,12 +279,7 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const result: PullRequestDetailsInterface = @@ -356,12 +313,7 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const result: PullRequestDetailsInterface = @@ -387,12 +339,7 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act await gitHubReposInvoker.getTitleAndDescription(); @@ -425,12 +372,7 @@ describe("gitHubReposInvoker.ts", (): void => { when( octokitWrapper.getPull(anyString(), anyString(), anyNumber()), ).thenResolve(currentMockPullResponse); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const result: PullRequestDetailsInterface = @@ -468,12 +410,7 @@ describe("gitHubReposInvoker.ts", (): void => { when( octokitWrapper.getPull(anyString(), anyString(), anyNumber()), ).thenThrow(error); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const func: () => Promise = async () => @@ -508,12 +445,7 @@ describe("gitHubReposInvoker.ts", (): void => { when( octokitWrapper.getPull(anyString(), anyString(), anyNumber()), ).thenThrow(Error("Error")); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const func: () => Promise = async () => @@ -532,12 +464,7 @@ describe("gitHubReposInvoker.ts", (): void => { logObject = options.log; }, ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); await gitHubReposInvoker.getTitleAndDescription(); // Act diff --git a/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts b/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts index 8d227c5bb..9cdea8f36 100644 --- a/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts @@ -3,16 +3,18 @@ * Licensed under the MIT License. */ + +import { createGitHubReposInvokerMocks, createSut } from "./gitHubReposInvokerTestSetup.js"; import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; import GitInvoker from "../../src/git/gitInvoker.js"; import Logger from "../../src/utilities/logger.js"; import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; -import { createGitHubReposInvokerMocks } from "./gitHubReposInvokerTestSetup.js"; -import { instance } from "ts-mockito"; import { stubEnv } from "../testUtilities/stubEnv.js"; + + describe("gitHubReposInvoker.ts", (): void => { let gitInvoker: GitInvoker; let logger: Logger; @@ -31,12 +33,7 @@ describe("gitHubReposInvoker.ts", (): void => { describe("isAccessTokenAvailable()", (): void => { it("should return null when the token exists on Azure DevOps", async (): Promise => { // Arrange - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const result: string | null = @@ -50,12 +47,7 @@ describe("gitHubReposInvoker.ts", (): void => { // Arrange stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); stubEnv(["GITHUB_ACTION", "PR-Metrics"]); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const result: string | null = @@ -69,12 +61,7 @@ describe("gitHubReposInvoker.ts", (): void => { it("should return a string when the token does not exist", async (): Promise => { // Arrange stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const result: string | null = diff --git a/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts b/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts index 5aa9867b5..ae1c974a7 100644 --- a/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. */ -import { createGitHubReposInvokerMocks, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; -import { instance, verify, when } from "ts-mockito"; + +import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { verify, when } from "ts-mockito"; import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; import GitInvoker from "../../src/git/gitInvoker.js"; import Logger from "../../src/utilities/logger.js"; @@ -14,6 +15,7 @@ import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; + describe("gitHubReposInvoker.ts", (): void => { let gitInvoker: GitInvoker; let logger: Logger; @@ -32,12 +34,7 @@ describe("gitHubReposInvoker.ts", (): void => { describe("setTitleAndDescription()", (): void => { it("should succeed when the title and description are both null", async (): Promise => { // Arrange - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act await gitHubReposInvoker.setTitleAndDescription(null, null); @@ -58,12 +55,7 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act await gitHubReposInvoker.setTitleAndDescription("Title", "Description"); @@ -94,12 +86,7 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act await gitHubReposInvoker.setTitleAndDescription("Title", null); @@ -130,12 +117,7 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act await gitHubReposInvoker.setTitleAndDescription(null, "Description"); diff --git a/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts b/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts index d43d51080..7395914be 100644 --- a/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. */ -import { createGitHubReposInvokerMocks, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; -import { instance, verify, when } from "ts-mockito"; + +import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { verify, when } from "ts-mockito"; import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; import GitInvoker from "../../src/git/gitInvoker.js"; import Logger from "../../src/utilities/logger.js"; @@ -14,6 +15,7 @@ import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; + describe("gitHubReposInvoker.ts", (): void => { let gitInvoker: GitInvoker; let logger: Logger; @@ -32,12 +34,7 @@ describe("gitHubReposInvoker.ts", (): void => { describe("updateComment()", (): void => { it("should succeed when the content is null", async (): Promise => { // Arrange - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act await gitHubReposInvoker.updateComment(54321, null); @@ -58,12 +55,7 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = new GitHubReposInvoker( - instance(gitInvoker), - instance(logger), - instance(octokitWrapper), - instance(runnerInvoker), - ); + const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act await gitHubReposInvoker.updateComment(54321, "Content"); diff --git a/src/task/tests/repos/gitHubReposInvokerTestSetup.ts b/src/task/tests/repos/gitHubReposInvokerTestSetup.ts index 63be2f131..c1295a2a3 100644 --- a/src/task/tests/repos/gitHubReposInvokerTestSetup.ts +++ b/src/task/tests/repos/gitHubReposInvokerTestSetup.ts @@ -5,7 +5,8 @@ import * as GitHubReposInvokerConstants from "./gitHubReposInvokerConstants.js"; import { anyNumber, anyString } from "../testUtilities/mockito.js"; -import { mock, when } from "ts-mockito"; +import { instance, mock, when } from "ts-mockito"; +import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; import GitInvoker from "../../src/git/gitInvoker.js"; import Logger from "../../src/utilities/logger.js"; import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; @@ -70,3 +71,24 @@ export const createGitHubReposInvokerMocks = (): GitHubReposInvokerMocks => { return { gitInvoker, logger, octokitWrapper, runnerInvoker }; }; + +/** + * Constructs a `GitHubReposInvoker` instance from the supplied mocks. + * @param gitInvoker The mocked git invoker. + * @param logger The mocked logger. + * @param octokitWrapper The mocked octokit wrapper. + * @param runnerInvoker The mocked runner invoker. + * @returns The constructed `GitHubReposInvoker` instance. + */ +export const createSut = ( + gitInvoker: GitInvoker, + logger: Logger, + octokitWrapper: OctokitWrapper, + runnerInvoker: RunnerInvoker, +): GitHubReposInvoker => + new GitHubReposInvoker( + instance(gitInvoker), + instance(logger), + instance(octokitWrapper), + instance(runnerInvoker), + ); From ada2c6e677772980ca7ccc0fb74722ba505d3f2f Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Fri, 17 Apr 2026 19:25:51 +0100 Subject: [PATCH 07/27] Expand property-based coverage for Inputs parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds `fast-check` property tests for `baseSize`, `growthRate`, `testFactor`, `fileMatchingPatterns`, and `testMatchingPatterns` alongside the existing `codeFileExtensions` coverage in `inputs.property.spec.ts`. The new properties cover the "any value satisfying X → converted result" and "any value failing X → default" invariants that the example-based tests in the per-input spec files can only sample pointwise. Also generalises the local `createInputs` helper to take an `InputOverrides` bag so each property can target a single input field without interfering with the others. Implements step 7 of TestPlan.md. The existing example-based tables stay in place — deletion of interior cases is deferred per the plan's prioritisation. --- .../tests/metrics/inputs.property.spec.ts | 339 +++++++++++++++--- 1 file changed, 294 insertions(+), 45 deletions(-) diff --git a/src/task/tests/metrics/inputs.property.spec.ts b/src/task/tests/metrics/inputs.property.spec.ts index 898b90561..3f704e4e4 100644 --- a/src/task/tests/metrics/inputs.property.spec.ts +++ b/src/task/tests/metrics/inputs.property.spec.ts @@ -3,59 +3,303 @@ * Licensed under the MIT License. */ - +import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import * as fc from "fast-check"; -import { - deepEqual, - instance, - mock, - when, -} from "ts-mockito"; +import { deepEqual, instance, mock, when } from "ts-mockito"; import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { anyString } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; - +import { maxPatternCount } from "../../src/utilities/constants.js"; const numRuns = 10; +interface InputOverrides { + readonly alwaysCloseComment?: string; + readonly baseSize?: string; + readonly codeFileExtensions?: string; + readonly fileMatchingPatterns?: string; + readonly growthRate?: string; + readonly testFactor?: string; + readonly testMatchingPatterns?: string; +} + +const createInputs = (overrides: InputOverrides = {}): Inputs => { + const logger: Logger = mock(Logger); + const runnerInvoker: RunnerInvoker = mock(RunnerInvoker); + when(runnerInvoker.loc(anyString())).thenReturn(""); + when(runnerInvoker.loc(anyString(), anyString())).thenReturn(""); + when(runnerInvoker.getInput(deepEqual(["Base", "Size"]))).thenReturn( + overrides.baseSize ?? null, + ); + when(runnerInvoker.getInput(deepEqual(["Growth", "Rate"]))).thenReturn( + overrides.growthRate ?? null, + ); + when(runnerInvoker.getInput(deepEqual(["Test", "Factor"]))).thenReturn( + overrides.testFactor ?? null, + ); + when( + runnerInvoker.getInput(deepEqual(["Always", "Close", "Comment"])), + ).thenReturn(overrides.alwaysCloseComment ?? null); + when( + runnerInvoker.getInput(deepEqual(["File", "Matching", "Patterns"])), + ).thenReturn(overrides.fileMatchingPatterns ?? null); + when( + runnerInvoker.getInput(deepEqual(["Test", "Matching", "Patterns"])), + ).thenReturn(overrides.testMatchingPatterns ?? null); + when( + runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), + ).thenReturn(overrides.codeFileExtensions ?? null); + return new Inputs(instance(logger), instance(runnerInvoker)); +}; + describe("inputs.ts", (): void => { describe("Property-Based Tests", (): void => { - describe("codeFileExtensions", (): void => { - const createInputs = (codeFileExtensions: string): Inputs => { - const logger: Logger = mock(Logger); - const runnerInvoker: RunnerInvoker = mock(RunnerInvoker); - when(runnerInvoker.loc(anyString())).thenReturn(""); - when(runnerInvoker.loc(anyString(), anyString())).thenReturn(""); - when(runnerInvoker.getInput(deepEqual(["Base", "Size"]))).thenReturn( - null, - ); - when(runnerInvoker.getInput(deepEqual(["Growth", "Rate"]))).thenReturn( - null, - ); - when(runnerInvoker.getInput(deepEqual(["Test", "Factor"]))).thenReturn( - null, - ); - when( - runnerInvoker.getInput(deepEqual(["Always", "Close", "Comment"])), - ).thenReturn(null); - when( - runnerInvoker.getInput(deepEqual(["File", "Matching", "Patterns"])), - ).thenReturn(null); - when( - runnerInvoker.getInput(deepEqual(["Test", "Matching", "Patterns"])), - ).thenReturn(null); - when( - runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), - ).thenReturn(codeFileExtensions); - return new Inputs(instance(logger), instance(runnerInvoker)); - }; + describe("baseSize", (): void => { + it("should use the parsed value for any positive integer string", (): void => { + fc.assert( + fc.property( + fc.integer({ max: 1_000_000, min: 1 }), + (value: number) => { + const inputs: Inputs = createInputs({ + baseSize: String(value), + }); + assert.equal(inputs.baseSize, value); + }, + ), + { numRuns }, + ); + }); + + it("should use the default for any non-positive integer string", (): void => { + fc.assert( + fc.property( + fc.integer({ max: 0, min: -1_000_000 }), + (value: number) => { + const inputs: Inputs = createInputs({ + baseSize: String(value), + }); + assert.equal(inputs.baseSize, InputsDefault.baseSize); + }, + ), + { numRuns }, + ); + }); + + it("should use the default for any non-numeric string", (): void => { + fc.assert( + fc.property( + fc.stringMatching(/^[A-Za-z!?@#]{1,10}$/u), + (text: string) => { + const inputs: Inputs = createInputs({ baseSize: text }); + assert.equal(inputs.baseSize, InputsDefault.baseSize); + }, + ), + { numRuns }, + ); + }); + }); + + describe("growthRate", (): void => { + it("should use the parsed value for any number greater than 1.0", (): void => { + fc.assert( + fc.property( + fc.double({ + max: 1_000_000, + min: 1.01, + noDefaultInfinity: true, + noNaN: true, + }), + (value: number) => { + const inputs: Inputs = createInputs({ + growthRate: String(value), + }); + assert.equal(inputs.growthRate, parseFloat(String(value))); + }, + ), + { numRuns }, + ); + }); + it("should use the default for any number less than or equal to 1.0", (): void => { + fc.assert( + fc.property( + fc.double({ + max: 1.0, + min: -1_000_000, + noDefaultInfinity: true, + noNaN: true, + }), + (value: number) => { + const inputs: Inputs = createInputs({ + growthRate: String(value), + }); + assert.equal(inputs.growthRate, InputsDefault.growthRate); + }, + ), + { numRuns }, + ); + }); + }); + + describe("testFactor", (): void => { + it("should use the parsed value for any positive number", (): void => { + fc.assert( + fc.property( + fc.double({ + max: 1_000_000, + min: 0.001, + noDefaultInfinity: true, + noNaN: true, + }), + (value: number) => { + const inputs: Inputs = createInputs({ + testFactor: String(value), + }); + assert.equal(inputs.testFactor, parseFloat(String(value))); + }, + ), + { numRuns }, + ); + }); + + it("should use the default for any negative number", (): void => { + fc.assert( + fc.property( + fc.double({ + max: -0.001, + min: -1_000_000, + noDefaultInfinity: true, + noNaN: true, + }), + (value: number) => { + const inputs: Inputs = createInputs({ + testFactor: String(value), + }); + assert.equal(inputs.testFactor, InputsDefault.testFactor); + }, + ), + { numRuns }, + ); + }); + }); + + describe("fileMatchingPatterns", (): void => { + it("should wrap any non-empty single-line pattern in a one-element array", (): void => { + fc.assert( + fc.property( + fc.stringMatching(/^[a-z]{1,20}$/u), + (pattern: string) => { + const inputs: Inputs = createInputs({ + fileMatchingPatterns: pattern, + }); + assert.deepEqual(inputs.fileMatchingPatterns, [pattern]); + }, + ), + { numRuns }, + ); + }); + + it("should split any newline-separated string into its component patterns", (): void => { + fc.assert( + fc.property( + fc.array(fc.stringMatching(/^[a-z]{1,20}$/u), { + maxLength: 10, + minLength: 2, + }), + (parts: string[]) => { + const inputs: Inputs = createInputs({ + fileMatchingPatterns: parts.join("\n"), + }); + assert.deepEqual(inputs.fileMatchingPatterns, parts); + }, + ), + { numRuns }, + ); + }); + + it("should replace backslashes with forward slashes", (): void => { + fc.assert( + fc.property( + fc.stringMatching(/^[a-z]{1,10}$/u), + fc.stringMatching(/^[a-z]{1,10}$/u), + (head: string, tail: string) => { + const inputs: Inputs = createInputs({ + fileMatchingPatterns: `${head}\\${tail}`, + }); + assert.deepEqual(inputs.fileMatchingPatterns, [`${head}/${tail}`]); + }, + ), + { numRuns }, + ); + }); + + it("should cap the pattern count at the configured maximum", (): void => { + fc.assert( + fc.property( + fc.integer({ + max: maxPatternCount + 100, + min: maxPatternCount + 1, + }), + (count: number) => { + const input: string = Array.from( + { length: count }, + (_value: unknown, index: number) => `pattern${String(index)}`, + ).join("\n"); + const inputs: Inputs = createInputs({ + fileMatchingPatterns: input, + }); + assert.equal(inputs.fileMatchingPatterns.length, maxPatternCount); + }, + ), + { numRuns }, + ); + }); + }); + + describe("testMatchingPatterns", (): void => { + it("should wrap any non-empty single-line pattern in a one-element array", (): void => { + fc.assert( + fc.property( + fc.stringMatching(/^[a-z]{1,20}$/u), + (pattern: string) => { + const inputs: Inputs = createInputs({ + testMatchingPatterns: pattern, + }); + assert.deepEqual(inputs.testMatchingPatterns, [pattern]); + }, + ), + { numRuns }, + ); + }); + + it("should split any newline-separated string into its component patterns", (): void => { + fc.assert( + fc.property( + fc.array(fc.stringMatching(/^[a-z]{1,20}$/u), { + maxLength: 10, + minLength: 2, + }), + (parts: string[]) => { + const inputs: Inputs = createInputs({ + testMatchingPatterns: parts.join("\n"), + }); + assert.deepEqual(inputs.testMatchingPatterns, parts); + }, + ), + { numRuns }, + ); + }); + }); + + describe("codeFileExtensions", (): void => { it("should normalize extensions with wildcard prefix '*.ext' to 'ext'", (): void => { fc.assert( fc.property(fc.stringMatching(/^[a-z]{1,10}$/u), (ext: string) => { - const inputs: Inputs = createInputs(`*.${ext}`); + const inputs: Inputs = createInputs({ + codeFileExtensions: `*.${ext}`, + }); const result: Set = inputs.codeFileExtensions; assert.ok(result.has(ext)); assert.equal(result.size, 1); @@ -67,7 +311,9 @@ describe("inputs.ts", (): void => { it("should normalize extensions with dot prefix '.ext' to 'ext'", (): void => { fc.assert( fc.property(fc.stringMatching(/^[a-z]{1,10}$/u), (ext: string) => { - const inputs: Inputs = createInputs(`.${ext}`); + const inputs: Inputs = createInputs({ + codeFileExtensions: `.${ext}`, + }); const result: Set = inputs.codeFileExtensions; assert.ok(result.has(ext)); assert.equal(result.size, 1); @@ -79,7 +325,7 @@ describe("inputs.ts", (): void => { it("should accept extensions without any prefix", (): void => { fc.assert( fc.property(fc.stringMatching(/^[a-z]{1,10}$/u), (ext: string) => { - const inputs: Inputs = createInputs(ext); + const inputs: Inputs = createInputs({ codeFileExtensions: ext }); const result: Set = inputs.codeFileExtensions; assert.ok(result.has(ext)); assert.equal(result.size, 1); @@ -93,7 +339,8 @@ describe("inputs.ts", (): void => { fc.property(fc.stringMatching(/^[a-z]{1,10}$/u), (ext: string) => { const formats: string[] = [`*.${ext}`, `.${ext}`, ext]; const results: Set[] = formats.map( - (format: string) => createInputs(format).codeFileExtensions, + (format: string) => + createInputs({ codeFileExtensions: format }).codeFileExtensions, ); const [first, second, third] = results; assert.deepEqual(first, second); @@ -106,7 +353,7 @@ describe("inputs.ts", (): void => { it("should convert uppercase extensions to lowercase", (): void => { fc.assert( fc.property(fc.stringMatching(/^[A-Z]{1,10}$/u), (ext: string) => { - const inputs: Inputs = createInputs(ext); + const inputs: Inputs = createInputs({ codeFileExtensions: ext }); const result: Set = inputs.codeFileExtensions; assert.ok(result.has(ext.toLowerCase())); assert.ok(!result.has(ext)); @@ -125,7 +372,9 @@ describe("inputs.ts", (): void => { (extensions: string[]) => { fc.pre(new Set(extensions).size === extensions.length); const input: string = extensions.join("\n"); - const inputs: Inputs = createInputs(input); + const inputs: Inputs = createInputs({ + codeFileExtensions: input, + }); const result: Set = inputs.codeFileExtensions; assert.equal(result.size, extensions.length); for (const ext of extensions) { @@ -141,7 +390,7 @@ describe("inputs.ts", (): void => { fc.assert( fc.property(fc.stringMatching(/^[a-z]{1,10}$/u), (ext: string) => { const input = `*.${ext}\n.${ext}\n${ext}`; - const inputs: Inputs = createInputs(input); + const inputs: Inputs = createInputs({ codeFileExtensions: input }); const result: Set = inputs.codeFileExtensions; assert.equal(result.size, 1); assert.ok(result.has(ext)); @@ -153,7 +402,7 @@ describe("inputs.ts", (): void => { it("should handle mixed case extensions consistently", (): void => { fc.assert( fc.property(fc.stringMatching(/^[a-zA-Z]{1,10}$/u), (ext: string) => { - const inputs: Inputs = createInputs(ext); + const inputs: Inputs = createInputs({ codeFileExtensions: ext }); const result: Set = inputs.codeFileExtensions; assert.ok(result.has(ext.toLowerCase())); assert.equal(result.size, 1); From 5779f5139626911d1bae903231a224df81dac192 Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Fri, 17 Apr 2026 19:56:51 +0100 Subject: [PATCH 08/27] Consolidate invalid-input fixtures shared across Inputs specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extracts two shared fixture arrays into `tests/testUtilities/fixtures/invalidInputs.ts`: - `invalidNumericStrings` — strings that fail to parse as a positive numeric value, used by the base size, growth rate, and test factor specs. Growth rate and test factor spread the fixture and append `"Infinity"` locally. - `invalidPatternStrings` — strings that reduce to empty after whitespace handling, used by the file matching pattern, test matching pattern, and code file extension specs. Prevents the set of "invalid" strings from drifting between the six input specs that shared the same values by copy-paste. Implements step 9 of TestPlan.md. --- .../tests/metrics/inputs.baseSize.spec.ts | 42 ++++++----------- .../metrics/inputs.codeFileExtensions.spec.ts | 39 +++++++-------- .../inputs.fileMatchingPatterns.spec.ts | 47 +++++++++---------- .../tests/metrics/inputs.growthRate.spec.ts | 10 +--- .../tests/metrics/inputs.testFactor.spec.ts | 10 +--- .../inputs.testMatchingPatterns.spec.ts | 47 +++++++++---------- .../testUtilities/fixtures/invalidInputs.ts | 36 ++++++++++++++ 7 files changed, 117 insertions(+), 114 deletions(-) create mode 100644 src/task/tests/testUtilities/fixtures/invalidInputs.ts diff --git a/src/task/tests/metrics/inputs.baseSize.spec.ts b/src/task/tests/metrics/inputs.baseSize.spec.ts index 8af4c4b1b..42d30ccc3 100644 --- a/src/task/tests/metrics/inputs.baseSize.spec.ts +++ b/src/task/tests/metrics/inputs.baseSize.spec.ts @@ -17,6 +17,7 @@ import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { decimalRadix } from "../../src/utilities/constants.js"; +import { invalidNumericStrings } from "../testUtilities/fixtures/invalidInputs.js"; describe("inputs.ts", (): void => { @@ -29,34 +30,21 @@ describe("inputs.ts", (): void => { describe("initialize()", (): void => { describe("baseSize", (): void => { - { - const testCases: (string | null)[] = [ - null, - "", - " ", - "abc", - "===", - "!2", - "null", - "undefined", - ]; - - testCases.forEach((baseSize: string | null): void => { - it(`should set the default when the input '${String(baseSize)}' is invalid`, (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Base", "Size"])), - ).thenReturn(baseSize); - - // Act - const inputs: Inputs = createSut(logger, runnerInvoker); - - // Assert - assert.equal(inputs.baseSize, InputsDefault.baseSize); - verify(logger.logInfo(adjustingBaseSizeResource)).once(); - }); + invalidNumericStrings.forEach((baseSize: string | null): void => { + it(`should set the default when the input '${String(baseSize)}' is invalid`, (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Base", "Size"])), + ).thenReturn(baseSize); + + // Act + const inputs: Inputs = createSut(logger, runnerInvoker); + + // Assert + assert.equal(inputs.baseSize, InputsDefault.baseSize); + verify(logger.logInfo(adjustingBaseSizeResource)).once(); }); - } + }); { const testCases: string[] = ["0", "-1", "-1000", "-5"]; diff --git a/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts b/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts index fff384c56..87093be9a 100644 --- a/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts +++ b/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts @@ -16,6 +16,7 @@ import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; +import { invalidPatternStrings } from "../testUtilities/fixtures/invalidInputs.js"; describe("inputs.ts", (): void => { @@ -28,28 +29,24 @@ describe("inputs.ts", (): void => { describe("initialize()", (): void => { describe("codeFileExtensions", (): void => { - { - const testCases: (string | null)[] = [null, "", " ", " ", "\n"]; - - testCases.forEach((codeFileExtensions: string | null): void => { - it(`should set the default when the input '${String(codeFileExtensions?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), - ).thenReturn(codeFileExtensions); - - // Act - const inputs: Inputs = createSut(logger, runnerInvoker); - - // Assert - assert.deepEqual( - inputs.codeFileExtensions, - new Set(InputsDefault.codeFileExtensions), - ); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - }); + invalidPatternStrings.forEach((codeFileExtensions: string | null): void => { + it(`should set the default when the input '${String(codeFileExtensions?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), + ).thenReturn(codeFileExtensions); + + // Act + const inputs: Inputs = createSut(logger, runnerInvoker); + + // Assert + assert.deepEqual( + inputs.codeFileExtensions, + new Set(InputsDefault.codeFileExtensions), + ); + verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); }); - } + }); { const testCases: string[] = [ diff --git a/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts b/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts index 1399d9bdb..6acc9b6bd 100644 --- a/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts +++ b/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts @@ -16,6 +16,7 @@ import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; +import { invalidPatternStrings } from "../testUtilities/fixtures/invalidInputs.js"; describe("inputs.ts", (): void => { @@ -28,32 +29,28 @@ describe("inputs.ts", (): void => { describe("initialize()", (): void => { describe("fileMatchingPatterns", (): void => { - { - const testCases: (string | null)[] = [null, "", " ", " ", "\n"]; - - testCases.forEach((fileMatchingPatterns: string | null): void => { - it(`should set the default when the input '${String(fileMatchingPatterns?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { - // Arrange - when( - runnerInvoker.getInput( - deepEqual(["File", "Matching", "Patterns"]), - ), - ).thenReturn(fileMatchingPatterns); - - // Act - const inputs: Inputs = createSut(logger, runnerInvoker); - - // Assert - assert.deepEqual( - inputs.fileMatchingPatterns, - InputsDefault.fileMatchingPatterns, - ); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - }); + invalidPatternStrings.forEach((fileMatchingPatterns: string | null): void => { + it(`should set the default when the input '${String(fileMatchingPatterns?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { + // Arrange + when( + runnerInvoker.getInput( + deepEqual(["File", "Matching", "Patterns"]), + ), + ).thenReturn(fileMatchingPatterns); + + // Act + const inputs: Inputs = createSut(logger, runnerInvoker); + + // Assert + assert.deepEqual( + inputs.fileMatchingPatterns, + InputsDefault.fileMatchingPatterns, + ); + verify( + logger.logInfo(adjustingFileMatchingPatternsResource), + ).once(); }); - } + }); { const testCases: string[] = [ diff --git a/src/task/tests/metrics/inputs.growthRate.spec.ts b/src/task/tests/metrics/inputs.growthRate.spec.ts index a245c6229..48568bae7 100644 --- a/src/task/tests/metrics/inputs.growthRate.spec.ts +++ b/src/task/tests/metrics/inputs.growthRate.spec.ts @@ -16,6 +16,7 @@ import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; +import { invalidNumericStrings } from "../testUtilities/fixtures/invalidInputs.js"; describe("inputs.ts", (): void => { @@ -30,14 +31,7 @@ describe("inputs.ts", (): void => { describe("growthRate", (): void => { { const testCases: (string | null)[] = [ - null, - "", - " ", - "abc", - "===", - "!2", - "null", - "undefined", + ...invalidNumericStrings, "Infinity", ]; diff --git a/src/task/tests/metrics/inputs.testFactor.spec.ts b/src/task/tests/metrics/inputs.testFactor.spec.ts index f0555bf23..0ca2c148f 100644 --- a/src/task/tests/metrics/inputs.testFactor.spec.ts +++ b/src/task/tests/metrics/inputs.testFactor.spec.ts @@ -17,6 +17,7 @@ import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; +import { invalidNumericStrings } from "../testUtilities/fixtures/invalidInputs.js"; describe("inputs.ts", (): void => { @@ -31,14 +32,7 @@ describe("inputs.ts", (): void => { describe("testFactor", (): void => { { const testCases: (string | null)[] = [ - null, - "", - " ", - "abc", - "===", - "!2", - "null", - "undefined", + ...invalidNumericStrings, "Infinity", ]; diff --git a/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts b/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts index eaade856c..59e4a3bcb 100644 --- a/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts +++ b/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts @@ -16,6 +16,7 @@ import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; +import { invalidPatternStrings } from "../testUtilities/fixtures/invalidInputs.js"; describe("inputs.ts", (): void => { @@ -28,32 +29,28 @@ describe("inputs.ts", (): void => { describe("initialize()", (): void => { describe("testMatchingPatterns", (): void => { - { - const testCases: (string | null)[] = [null, "", " ", " ", "\n"]; - - testCases.forEach((testMatchingPatterns: string | null): void => { - it(`should set the default when the input '${String(testMatchingPatterns?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { - // Arrange - when( - runnerInvoker.getInput( - deepEqual(["Test", "Matching", "Patterns"]), - ), - ).thenReturn(testMatchingPatterns); - - // Act - const inputs: Inputs = createSut(logger, runnerInvoker); - - // Assert - assert.deepEqual( - inputs.testMatchingPatterns, - InputsDefault.testMatchingPatterns, - ); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - }); + invalidPatternStrings.forEach((testMatchingPatterns: string | null): void => { + it(`should set the default when the input '${String(testMatchingPatterns?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { + // Arrange + when( + runnerInvoker.getInput( + deepEqual(["Test", "Matching", "Patterns"]), + ), + ).thenReturn(testMatchingPatterns); + + // Act + const inputs: Inputs = createSut(logger, runnerInvoker); + + // Assert + assert.deepEqual( + inputs.testMatchingPatterns, + InputsDefault.testMatchingPatterns, + ); + verify( + logger.logInfo(adjustingTestMatchingPatternsResource), + ).once(); }); - } + }); { const testCases: string[] = [ diff --git a/src/task/tests/testUtilities/fixtures/invalidInputs.ts b/src/task/tests/testUtilities/fixtures/invalidInputs.ts new file mode 100644 index 000000000..6d5430d40 --- /dev/null +++ b/src/task/tests/testUtilities/fixtures/invalidInputs.ts @@ -0,0 +1,36 @@ +/* + * Copyright (c) Microsoft Corporation. + * Licensed under the MIT License. + */ + +/** + * Strings that should fail to parse as a positive numeric value. Shared by + * tests of the numeric `Inputs` fields (base size, growth rate, test + * factor) to cover the "fall back to the default" branch. Tests that also + * want to cover the `Infinity` literal can spread this list and append + * `"Infinity"`. + */ +export const invalidNumericStrings: readonly (string | null)[] = [ + null, + "", + " ", + "abc", + "===", + "!2", + "null", + "undefined", +]; + +/** + * Strings that reduce to an empty value after whitespace handling. Shared + * by tests of the pattern-style `Inputs` fields (file matching patterns, + * test matching patterns, code file extensions) to cover the "fall back + * to the default" branch. + */ +export const invalidPatternStrings: readonly (string | null)[] = [ + null, + "", + " ", + " ", + "\n", +]; From 413bba8d9e850a0caa86a343d6c23bba6e096a1c Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Fri, 17 Apr 2026 19:57:33 +0100 Subject: [PATCH 09/27] Removing plan --- TestPlan.md | 272 ---------------------------------------------------- 1 file changed, 272 deletions(-) delete mode 100644 TestPlan.md diff --git a/TestPlan.md b/TestPlan.md deleted file mode 100644 index 3b952f299..000000000 --- a/TestPlan.md +++ /dev/null @@ -1,272 +0,0 @@ -# Unit Test Improvement Plan - -This plan proposes concrete improvements to the PR Metrics unit test suite. The -current suite provides strong coverage, which is vital given that end-to-end -production testing before release is infeasible. The goal here is to reduce -maintenance cost _without_ reducing coverage, so that future refactors become -cheaper and the safety net remains intact. - -## Diagnostic Summary - -Quantitative picture of the current suite: - -- 23 spec files, approximately 16,820 lines total. -- Four files over 1,800 lines: `inputs.spec.ts` (2,175), `codeMetrics.spec.ts` - (2,065), `azureReposInvoker.spec.ts` (2,042), and `gitHubReposInvoker.spec.ts` - (1,848). -- 1,118 `verify(logger.log...)` calls across 12 files. -- 302 `process.env` manipulations across 9 files. -- 297 SUT constructor calls across 12 files, roughly one per test case. - -The core problem: tests are coupled to implementation, not behaviour. Every -refactor of a private method, every rename of an internal helper, and every new -log line cascades into dozens of test edits. That is the maintenance cost -currently being felt. - -## Guiding Principles - -1. **Preserve Coverage**: Every change must keep coverage equal or better. - Branches and data cases must all still be exercised. -2. **Test Observable Behaviour, Not Tracing**: Debug log calls of the form - `* Class.method()` are internal tracing. Asserting them locks private method - names into the test suite. -3. **Shift Invariants to Property Tests**: `fast-check` is already in the - dependency set. Many exhaustive `forEach` data tables are invariants in - disguise. -4. **Move Integration Testing Up One Layer**: End-to-end-before-release is - impossible. This argues for a small in-process integration tier that wires - real components with faked external I/O, not for more mock-heavy unit tests. -5. **Factor Shared Test Infrastructure**: The duplication of mock setup, - localisation mocks, environment-variable handling, and SUT construction is - the single biggest source of line count. - -## Concrete Proposals - -### 1. Stop Verifying Debug-Trace Log Calls - -**Observation**: Roughly 800 to 900 of the 1,118 `verify(logger.log...)` calls -assert `* ClassName.methodName()` tracing. For example, a single test in -`inputs.spec.ts` (lines 162 to 202) verifies 30+ log lines including counts for -every private initialiser. - -**Proposal**: - -- Delete all `verify(logger.logDebug("* X.y()"))` assertions. -- Keep `verify(logger.logWarning(...))` and `verify(logger.logError(...))` - assertions; those are observable and meaningful. -- Keep `verify(logger.logInfo(...))` only when the info message is part of the - contract, such as user-facing progress reporting. -- Optionally, verify that tracing exists via a single test per class ("emits - entry trace when called") using `ts-mockito`'s `atLeast(1)` with a regex - matcher, rather than exhaustively per test. - -**Rationale**: Debug tracing is a cross-cutting side effect. It is useful in -production but is not the SUT's contract. Asserting it forces tests to know -about private method names and call ordering. Removing these saves roughly -5,000 lines and unblocks refactoring. - -**Coverage Impact**: None. Coverage measures code execution; these assertions -do not add execution. - -### 2. Replace Localisation Mock `beforeEach` Blocks With a Helper - -**Observation**: `codeMetrics.spec.ts` has a 186-line `beforeEach` that stubs -every `(size, coverage)` combination. `pullRequest.spec.ts` similarly has -roughly 100 lines of `runnerInvoker.loc(...)` stubs. The stubs are largely -identity mappings, such as `"titleSizeXS"` returning `"XS"`. - -**Proposal**: - -- Add `tests/testUtilities/localisationMock.ts` that reads the actual - `resources.resjson` file and wires `runnerInvoker.loc()` to return real - values. -- Each spec file replaces its 100 to 186 lines with one line, for example - `stubLocalisation(runnerInvoker)`. - -**Rationale**: The current code re-implements the resource file in mock stubs, -then tests against those mocked values. It is testing the mock. Reading the -real resource file removes the duplication and makes the tests resilient to -resource file changes. Localisation is a stable, low-risk boundary to treat as -real in tests. - -**Coverage Impact**: None. The localisation wiring is not exercised by those -stubs anyway. - -### 3. Replace `process.env` Juggling With a Scoped Helper - -**Observation**: 302 manual `process.env.X = ...` and -`delete process.env.X` pairs exist across tests. Tests often set up in an -Arrange section, repeat in a Finalisation section, and rely on `beforeEach` -and `afterEach` to clean up. Some files miss cleanups, introducing -cross-test leak risk. - -**Proposal**: - -- Add `tests/testUtilities/envSandbox.ts` exporting `withEnv(overrides, fn)` - and `stubEnv(overrides)`, the latter auto-restoring in `afterEach` via a - global hook. -- Tests become - `stubEnv({ GITHUB_ACTION: "PR-Metrics", GITHUB_REF: "refs/pull/12345/merge" })` - with no cleanup required. - -**Rationale**: Deterministic cleanup, no cross-test leak, and elimination of -the finalisation boilerplate. The existing code in `gitInvoker.spec.ts` that -sets environment variables and then deletes them at the end of each test -(lines 89 to 90, 114 to 115, 142 to 143, 174 to 175, and elsewhere) is pure -noise. - -### 4. Split Oversized Files by Describe Block - -**Proposal**: - -- `codeMetrics.spec.ts` (2,065 lines): split into - `codeMetrics.sizeIndicator.spec.ts`, `codeMetrics.testCoverage.spec.ts`, - `codeMetrics.fileMatching.spec.ts`, and so on, based on the existing - `describe` blocks. -- `inputs.spec.ts` (2,175 lines): split per input, for example - `inputs.baseSize.spec.ts`, `inputs.growthRate.spec.ts`, and - `inputs.testFactor.spec.ts`. -- `azureReposInvoker.spec.ts` and `gitHubReposInvoker.spec.ts`: split per - public method. - -**Rationale**: Files over roughly 800 lines are hard to navigate and lead to -copy-paste rot. Smaller files also parallelise better in Mocha and make test -failures easier to triage. - -**Coverage Impact**: None. Tests just move. - -### 5. Introduce Test Data Builders - -**Proposal**: Add `tests/testUtilities/builders/` with factories such as -`aCodeMetricsData()`, `aPullRequestDetails()`, and `aCommentData()`: - -```typescript -const data = aCodeMetricsData().withProductCode(400).withTestCode(0).build(); -``` - -**Rationale**: Tests currently pass raw constructor positional arguments such -as `new CodeMetricsData(400, 399, 0)`, forcing the reader to count commas. -Builders give each test a one-liner that reads as intent. - -### 6. Extract a SUT Factory Per Spec - -**Proposal**: Each spec file adds a local `createSut(overrides?)` that -constructs the SUT with all its dependency instances. Tests become: - -```typescript -const sut = createSut(); -// or, with a swap: -const sut = createSut({ gitInvoker: customGitInvoker }); -``` - -**Rationale**: 297 SUT `new X(...)` calls across tests. One factory per spec -collapses that to one constructor call site and makes dependency swaps -obvious. - -### 7. Expand Property-Based Testing to Replace Exhaustive Tables - -**Observation**: Many `forEach` tables are property tests in disguise. For -example, the size-indicator boundaries in `codeMetrics.spec.ts` can be stated -as: "for any `productCode` in `[0, baseSize)`, the indicator is `XS`." The -invalid-input tables in `inputs.spec.ts` can be stated as: "for any string -that does not parse to a positive number, the default is returned." - -**Proposal**: - -- Keep example-based tests for boundaries (199 vs 200, 399 vs 400); those - document the contract. -- Replace bulk interior cases with property tests. A single `fc.property` with - 100 runs covers more than 20 hand-picked `forEach` entries. -- Add property files for `inputs.ts`, `codeMetrics.ts` size bands, and file - pattern matching. - -**Rationale**: Property tests are shorter, catch edge cases that hand-picked -tables miss, and are less coupled to specific values. `fast-check` is already -a dependency; deepen that investment. - -### 8. Add a Small In-Process Integration Tier – THIS WON'T WORK, SKIP THIS - -**Observation**: There is a gap between 23 mock-heavy unit spec files and -manual test instructions in `manualTests/`. - -**Proposal**: Add `tests/integration/` with 5 to 10 tests that exercise -`pullRequestMetrics.run()` end-to-end with: - -- Real `Inputs`, `CodeMetrics`, `Logger`, `RunnerInvoker`, and - `PullRequestComments`. -- Faked `RunnerInvoker.exec` (canned Git output) and `ReposInvoker` (in-memory). -- Golden scenarios such as "small PR with tests", "XL PR without tests", and - "PR with ignored files". - -**Rationale**: This is the layer that catches regressions where unit tests -pass but integration is broken. Given that end-to-end production testing -before release is impossible, this is the insurance policy. Unit tests verify -behaviour of pieces; integration tests verify the pieces compose correctly. - -**Coverage Impact**: Strictly additive. - -### 9. Consolidate Common Invalid-Input Fixtures - -**Proposal**: A single `tests/testUtilities/fixtures/invalidInputs.ts` exports -shared sets such as `INVALID_STRINGS = [null, "", " ", "abc", "==="]` and -`NEGATIVE_NUMBERS = ["0", "-1", "-1000"]`. Specs import them. - -**Rationale**: `validator.spec.ts` lines 12 and 50, `inputs.spec.ts` lines -293 to 302 and 487 to 496, and other locations enumerate overlapping "invalid -string" sets. Sharing prevents drift and captures domain knowledge once. - -### 10. Document a Testing Style Guide –DON'T BOTHER - -**Proposal**: Add `docs/testing.md`, or a section in `AGENTS.md`, that -codifies: - -- When to mock a collaborator and when to use the real thing. -- The rule that debug-trace log calls are not asserted. -- The preference for property tests for invariants, and example tests for - boundaries. -- How to use the new helpers (`withEnv`, `stubLocalisation`, builders, and - `createSut`). -- The distinction between the integration tier and the unit tier. - -**Rationale**: Prevents regression to the current style once the helpers -exist. - -## Prioritisation - -Ranked by impact per unit of work: - -1. **Remove Debug-Trace Log Assertions**: Biggest single win on maintenance - burden (roughly 5,000 lines removed, refactors unblocked). Low risk, - preserves coverage. Do first. -2. **`stubLocalisation` Helper**: Eliminates the largest single block of - setup boilerplate. Low risk. -3. **`withEnv` and `stubEnv` Helpers**: Removes roughly 600 lines and a class - of cross-test-leak bugs. -4. **SUT Factory and Builders**: Tidies remaining code and enables later - splits. -5. **Split Oversized Files**: Do after steps 1 to 4 so splits happen on clean - code. -6. **Expand Property Tests**: Higher engineering investment; do last among - the unit-suite items. -7. **Integration Tier**: Separate project; worth doing but orthogonal to the - unit-test cleanup. -8. **Consolidate Invalid-Input Fixtures and Style Guide**: Cleanup sweep. - -Rough estimate: steps 1 to 4 alone cut the test codebase by roughly 30% and -eliminate the "one private method rename equals 50 test edits" problem. - -## Open Questions - -Two items to resolve before executing the plan: - -1. **Intent Behind Debug-Trace Assertions**: The current approach treats - tracing assertions as accidental complexity. If the `* Class.method()` - tracing was added deliberately to verify call paths (for instance, to - confirm that the correct helper was reached in routing logic), proposal 1 - should be narrowed: keep trace verification only where the call path - itself is the thing being tested (probably a handful of cases in - `reposInvoker.spec.ts`) and delete the rest. -2. **Scope Preference**: One big design document covering all 10 proposals, - or a two-phase split where phase A covers the high-impact, low-risk - cleanup (proposals 1 to 4) and phase B covers the deeper changes - (proposals 5 to 10) as a follow-up once the ground is clear. From 20260f4bf465007f5108a6946058dfc77555a964 Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Mon, 20 Apr 2026 10:53:53 +0100 Subject: [PATCH 10/27] Enforce consistent-type-imports via ESLint Adds the @typescript-eslint/consistent-type-imports rule and applies its auto-fix across the source and spec files, so imports used only in type positions use the dedicated `import type` syntax. --- eslint.config.mjs | 1 + src/task/src/repos/reposInvokerInterface.d.ts | 6 +++--- src/task/src/runners/runnerInvokerInterface.d.ts | 4 ++-- src/task/tests/jsonTypes/taskJsonInterface.d.ts | 2 +- src/task/tests/metrics/codeMetrics.defaults.spec.ts | 10 +++++----- src/task/tests/metrics/codeMetrics.edgeCases.spec.ts | 10 +++++----- .../tests/metrics/codeMetrics.fileMatching.spec.ts | 10 +++++----- ...Metrics.getDeletedFilesNotRequiringReview.spec.ts | 10 +++++----- .../codeMetrics.getFilesNotRequiringReview.spec.ts | 10 +++++----- .../tests/metrics/codeMetrics.nonDefaults.spec.ts | 10 +++++----- src/task/tests/metrics/inputs.allInputs.spec.ts | 6 +++--- .../tests/metrics/inputs.alwaysCloseComment.spec.ts | 6 +++--- src/task/tests/metrics/inputs.baseSize.spec.ts | 6 +++--- .../tests/metrics/inputs.codeFileExtensions.spec.ts | 6 +++--- .../metrics/inputs.fileMatchingPatterns.spec.ts | 6 +++--- src/task/tests/metrics/inputs.growthRate.spec.ts | 6 +++--- src/task/tests/metrics/inputs.testFactor.spec.ts | 6 +++--- .../metrics/inputs.testMatchingPatterns.spec.ts | 6 +++--- .../repos/azureReposInvoker.createComment.spec.ts | 12 ++++++------ .../azureReposInvoker.deleteCommentThread.spec.ts | 12 ++++++------ .../repos/azureReposInvoker.getComments.spec.ts | 12 ++++++------ .../azureReposInvoker.getTitleAndDescription.spec.ts | 12 ++++++------ .../azureReposInvoker.isAccessTokenAvailable.spec.ts | 12 ++++++------ .../azureReposInvoker.setTitleAndDescription.spec.ts | 12 ++++++------ .../repos/azureReposInvoker.updateComment.spec.ts | 12 ++++++------ .../repos/gitHubReposInvoker.createComment.spec.ts | 10 +++++----- .../gitHubReposInvoker.deleteCommentThread.spec.ts | 10 +++++----- .../repos/gitHubReposInvoker.getComments.spec.ts | 10 +++++----- ...gitHubReposInvoker.getTitleAndDescription.spec.ts | 10 +++++----- ...gitHubReposInvoker.isAccessTokenAvailable.spec.ts | 10 +++++----- ...gitHubReposInvoker.setTitleAndDescription.spec.ts | 10 +++++----- .../repos/gitHubReposInvoker.updateComment.spec.ts | 10 +++++----- 32 files changed, 138 insertions(+), 137 deletions(-) diff --git a/eslint.config.mjs b/eslint.config.mjs index 7f99d455f..97bda8a3a 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -30,6 +30,7 @@ export default tseslint.config( }, rules: { "@typescript-eslint/consistent-type-exports": "error", + "@typescript-eslint/consistent-type-imports": "error", "@typescript-eslint/default-param-last": "error", "@typescript-eslint/explicit-function-return-type": "error", "@typescript-eslint/explicit-member-accessibility": "error", diff --git a/src/task/src/repos/reposInvokerInterface.d.ts b/src/task/src/repos/reposInvokerInterface.d.ts index 6dd8cb136..210fa0ee1 100644 --- a/src/task/src/repos/reposInvokerInterface.d.ts +++ b/src/task/src/repos/reposInvokerInterface.d.ts @@ -3,9 +3,9 @@ * Licensed under the MIT License. */ -import CommentData from "./interfaces/commentData.js"; -import { CommentThreadStatus } from "azure-devops-node-api/interfaces/GitInterfaces.js"; -import PullRequestDetailsInterface from "./interfaces/pullRequestDetailsInterface.js"; +import type CommentData from "./interfaces/commentData.js"; +import type { CommentThreadStatus } from "azure-devops-node-api/interfaces/GitInterfaces.js"; +import type PullRequestDetailsInterface from "./interfaces/pullRequestDetailsInterface.js"; /** * An interface for invoking repository functionality with any underlying repository store. diff --git a/src/task/src/runners/runnerInvokerInterface.d.ts b/src/task/src/runners/runnerInvokerInterface.d.ts index bd6e7a96b..57be0d57e 100644 --- a/src/task/src/runners/runnerInvokerInterface.d.ts +++ b/src/task/src/runners/runnerInvokerInterface.d.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. */ -import { EndpointAuthorization } from "./endpointAuthorization.js"; -import ExecOutput from "./execOutput.js"; +import type { EndpointAuthorization } from "./endpointAuthorization.js"; +import type ExecOutput from "./execOutput.js"; /** * An interface for invoking runner functionality with any underlying runner. diff --git a/src/task/tests/jsonTypes/taskJsonInterface.d.ts b/src/task/tests/jsonTypes/taskJsonInterface.d.ts index 63a3e7bb2..6329119be 100644 --- a/src/task/tests/jsonTypes/taskJsonInterface.d.ts +++ b/src/task/tests/jsonTypes/taskJsonInterface.d.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. */ -import ResourcesJson from "../jsonTypes/resourcesJson.js"; +import type ResourcesJson from "../jsonTypes/resourcesJson.js"; /** * An interface defining the format of Azure Pipelines task JSON files. diff --git a/src/task/tests/metrics/codeMetrics.defaults.spec.ts b/src/task/tests/metrics/codeMetrics.defaults.spec.ts index 14742d013..a556e4122 100644 --- a/src/task/tests/metrics/codeMetrics.defaults.spec.ts +++ b/src/task/tests/metrics/codeMetrics.defaults.spec.ts @@ -5,12 +5,12 @@ import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; -import CodeMetrics from "../../src/metrics/codeMetrics.js"; +import type CodeMetrics from "../../src/metrics/codeMetrics.js"; import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; -import Inputs from "../../src/metrics/inputs.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; +import type Inputs from "../../src/metrics/inputs.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { when } from "ts-mockito"; diff --git a/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts b/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts index 9b911fcfe..1b2b35570 100644 --- a/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts +++ b/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts @@ -6,12 +6,12 @@ import { anyString, when } from "ts-mockito"; import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; -import CodeMetrics from "../../src/metrics/codeMetrics.js"; +import type CodeMetrics from "../../src/metrics/codeMetrics.js"; import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; -import Inputs from "../../src/metrics/inputs.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; +import type Inputs from "../../src/metrics/inputs.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; diff --git a/src/task/tests/metrics/codeMetrics.fileMatching.spec.ts b/src/task/tests/metrics/codeMetrics.fileMatching.spec.ts index 84f8b118f..06afd00dc 100644 --- a/src/task/tests/metrics/codeMetrics.fileMatching.spec.ts +++ b/src/task/tests/metrics/codeMetrics.fileMatching.spec.ts @@ -5,12 +5,12 @@ import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; -import CodeMetrics from "../../src/metrics/codeMetrics.js"; +import type CodeMetrics from "../../src/metrics/codeMetrics.js"; import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; -import Inputs from "../../src/metrics/inputs.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; +import type Inputs from "../../src/metrics/inputs.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { when } from "ts-mockito"; diff --git a/src/task/tests/metrics/codeMetrics.getDeletedFilesNotRequiringReview.spec.ts b/src/task/tests/metrics/codeMetrics.getDeletedFilesNotRequiringReview.spec.ts index b9d71083e..ef543c9f7 100644 --- a/src/task/tests/metrics/codeMetrics.getDeletedFilesNotRequiringReview.spec.ts +++ b/src/task/tests/metrics/codeMetrics.getDeletedFilesNotRequiringReview.spec.ts @@ -6,11 +6,11 @@ import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; -import CodeMetrics from "../../src/metrics/codeMetrics.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; -import Inputs from "../../src/metrics/inputs.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type CodeMetrics from "../../src/metrics/codeMetrics.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; +import type Inputs from "../../src/metrics/inputs.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { when } from "ts-mockito"; diff --git a/src/task/tests/metrics/codeMetrics.getFilesNotRequiringReview.spec.ts b/src/task/tests/metrics/codeMetrics.getFilesNotRequiringReview.spec.ts index 7ba476c21..59068e72c 100644 --- a/src/task/tests/metrics/codeMetrics.getFilesNotRequiringReview.spec.ts +++ b/src/task/tests/metrics/codeMetrics.getFilesNotRequiringReview.spec.ts @@ -6,11 +6,11 @@ import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; -import CodeMetrics from "../../src/metrics/codeMetrics.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; -import Inputs from "../../src/metrics/inputs.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type CodeMetrics from "../../src/metrics/codeMetrics.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; +import type Inputs from "../../src/metrics/inputs.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { when } from "ts-mockito"; diff --git a/src/task/tests/metrics/codeMetrics.nonDefaults.spec.ts b/src/task/tests/metrics/codeMetrics.nonDefaults.spec.ts index abc4b0045..4451ba31b 100644 --- a/src/task/tests/metrics/codeMetrics.nonDefaults.spec.ts +++ b/src/task/tests/metrics/codeMetrics.nonDefaults.spec.ts @@ -5,12 +5,12 @@ import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; -import CodeMetrics from "../../src/metrics/codeMetrics.js"; +import type CodeMetrics from "../../src/metrics/codeMetrics.js"; import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; -import Inputs from "../../src/metrics/inputs.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; +import type Inputs from "../../src/metrics/inputs.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { when } from "ts-mockito"; diff --git a/src/task/tests/metrics/inputs.allInputs.spec.ts b/src/task/tests/metrics/inputs.allInputs.spec.ts index 69273160d..f57a0ae6c 100644 --- a/src/task/tests/metrics/inputs.allInputs.spec.ts +++ b/src/task/tests/metrics/inputs.allInputs.spec.ts @@ -24,9 +24,9 @@ import { settingTestMatchingPatternsResource, } from "./inputsTestSetup.js"; import { deepEqual, verify, when } from "ts-mockito"; -import Inputs from "../../src/metrics/inputs.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type Inputs from "../../src/metrics/inputs.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; diff --git a/src/task/tests/metrics/inputs.alwaysCloseComment.spec.ts b/src/task/tests/metrics/inputs.alwaysCloseComment.spec.ts index 2d08a2ba3..93ef74a36 100644 --- a/src/task/tests/metrics/inputs.alwaysCloseComment.spec.ts +++ b/src/task/tests/metrics/inputs.alwaysCloseComment.spec.ts @@ -12,9 +12,9 @@ import { settingAlwaysCloseComment, } from "./inputsTestSetup.js"; import { deepEqual, verify, when } from "ts-mockito"; -import Inputs from "../../src/metrics/inputs.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type Inputs from "../../src/metrics/inputs.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; diff --git a/src/task/tests/metrics/inputs.baseSize.spec.ts b/src/task/tests/metrics/inputs.baseSize.spec.ts index 42d30ccc3..3f419e5e0 100644 --- a/src/task/tests/metrics/inputs.baseSize.spec.ts +++ b/src/task/tests/metrics/inputs.baseSize.spec.ts @@ -12,9 +12,9 @@ import { settingBaseSizeResource, } from "./inputsTestSetup.js"; import { deepEqual, verify, when } from "ts-mockito"; -import Inputs from "../../src/metrics/inputs.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type Inputs from "../../src/metrics/inputs.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { decimalRadix } from "../../src/utilities/constants.js"; import { invalidNumericStrings } from "../testUtilities/fixtures/invalidInputs.js"; diff --git a/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts b/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts index 87093be9a..d035c386d 100644 --- a/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts +++ b/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts @@ -12,9 +12,9 @@ import { settingCodeFileExtensionsResource, } from "./inputsTestSetup.js"; import { deepEqual, verify, when } from "ts-mockito"; -import Inputs from "../../src/metrics/inputs.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type Inputs from "../../src/metrics/inputs.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { invalidPatternStrings } from "../testUtilities/fixtures/invalidInputs.js"; diff --git a/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts b/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts index 6acc9b6bd..5c0c01386 100644 --- a/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts +++ b/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts @@ -12,9 +12,9 @@ import { settingFileMatchingPatternsResource, } from "./inputsTestSetup.js"; import { deepEqual, verify, when } from "ts-mockito"; -import Inputs from "../../src/metrics/inputs.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type Inputs from "../../src/metrics/inputs.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { invalidPatternStrings } from "../testUtilities/fixtures/invalidInputs.js"; diff --git a/src/task/tests/metrics/inputs.growthRate.spec.ts b/src/task/tests/metrics/inputs.growthRate.spec.ts index 48568bae7..e5967f8bc 100644 --- a/src/task/tests/metrics/inputs.growthRate.spec.ts +++ b/src/task/tests/metrics/inputs.growthRate.spec.ts @@ -12,9 +12,9 @@ import { settingGrowthRateResource, } from "./inputsTestSetup.js"; import { deepEqual, verify, when } from "ts-mockito"; -import Inputs from "../../src/metrics/inputs.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type Inputs from "../../src/metrics/inputs.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { invalidNumericStrings } from "../testUtilities/fixtures/invalidInputs.js"; diff --git a/src/task/tests/metrics/inputs.testFactor.spec.ts b/src/task/tests/metrics/inputs.testFactor.spec.ts index 0ca2c148f..56aeeac73 100644 --- a/src/task/tests/metrics/inputs.testFactor.spec.ts +++ b/src/task/tests/metrics/inputs.testFactor.spec.ts @@ -13,9 +13,9 @@ import { settingTestFactorResource, } from "./inputsTestSetup.js"; import { deepEqual, verify, when } from "ts-mockito"; -import Inputs from "../../src/metrics/inputs.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type Inputs from "../../src/metrics/inputs.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { invalidNumericStrings } from "../testUtilities/fixtures/invalidInputs.js"; diff --git a/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts b/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts index 59e4a3bcb..f58d0b873 100644 --- a/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts +++ b/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts @@ -12,9 +12,9 @@ import { settingTestMatchingPatternsResource, } from "./inputsTestSetup.js"; import { deepEqual, verify, when } from "ts-mockito"; -import Inputs from "../../src/metrics/inputs.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type Inputs from "../../src/metrics/inputs.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { invalidPatternStrings } from "../testUtilities/fixtures/invalidInputs.js"; diff --git a/src/task/tests/repos/azureReposInvoker.createComment.spec.ts b/src/task/tests/repos/azureReposInvoker.createComment.spec.ts index 1ea2aceb7..b3aefb130 100644 --- a/src/task/tests/repos/azureReposInvoker.createComment.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.createComment.spec.ts @@ -8,15 +8,15 @@ import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import { CommentThreadStatus, type GitPullRequestCommentThread } from "azure-devops-node-api/interfaces/GitInterfaces.js"; import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; import { deepEqual, verify, when } from "ts-mockito"; -import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; -import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; +import type AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; +import type AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; import ErrorWithStatus from "../wrappers/errorWithStatus.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; import type { IGitApi } from "azure-devops-node-api/GitApi.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { StatusCodes } from "http-status-codes"; -import TokenManager from "../../src/repos/tokenManager.js"; +import type TokenManager from "../../src/repos/tokenManager.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; diff --git a/src/task/tests/repos/azureReposInvoker.deleteCommentThread.spec.ts b/src/task/tests/repos/azureReposInvoker.deleteCommentThread.spec.ts index e8f7d8b77..e3529be04 100644 --- a/src/task/tests/repos/azureReposInvoker.deleteCommentThread.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.deleteCommentThread.spec.ts @@ -8,15 +8,15 @@ import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import { any, anyNumber } from "../testUtilities/mockito.js"; import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; import { verify, when } from "ts-mockito"; -import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; -import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; +import type AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; +import type AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; import ErrorWithStatus from "../wrappers/errorWithStatus.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; import type { IGitApi } from "azure-devops-node-api/GitApi.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { StatusCodes } from "http-status-codes"; -import TokenManager from "../../src/repos/tokenManager.js"; +import type TokenManager from "../../src/repos/tokenManager.js"; import assert from "node:assert/strict"; diff --git a/src/task/tests/repos/azureReposInvoker.getComments.spec.ts b/src/task/tests/repos/azureReposInvoker.getComments.spec.ts index db0cecafd..ecb5b34e6 100644 --- a/src/task/tests/repos/azureReposInvoker.getComments.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.getComments.spec.ts @@ -8,16 +8,16 @@ import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import { CommentThreadStatus, type GitPullRequestCommentThread } from "azure-devops-node-api/interfaces/GitInterfaces.js"; import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; import { verify, when } from "ts-mockito"; -import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; -import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; +import type AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; +import type AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; import type CommentData from "../../src/repos/interfaces/commentData.js"; import ErrorWithStatus from "../wrappers/errorWithStatus.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; import type { IGitApi } from "azure-devops-node-api/GitApi.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { StatusCodes } from "http-status-codes"; -import TokenManager from "../../src/repos/tokenManager.js"; +import type TokenManager from "../../src/repos/tokenManager.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; diff --git a/src/task/tests/repos/azureReposInvoker.getTitleAndDescription.spec.ts b/src/task/tests/repos/azureReposInvoker.getTitleAndDescription.spec.ts index b9e9b6661..b760482ef 100644 --- a/src/task/tests/repos/azureReposInvoker.getTitleAndDescription.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.getTitleAndDescription.spec.ts @@ -7,16 +7,16 @@ import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; import { verify, when } from "ts-mockito"; -import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; -import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; +import type AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; +import type AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; import ErrorWithStatus from "../wrappers/errorWithStatus.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; import type { IGitApi } from "azure-devops-node-api/GitApi.js"; -import Logger from "../../src/utilities/logger.js"; +import type Logger from "../../src/utilities/logger.js"; import type PullRequestDetailsInterface from "../../src/repos/interfaces/pullRequestDetailsInterface.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { StatusCodes } from "http-status-codes"; -import TokenManager from "../../src/repos/tokenManager.js"; +import type TokenManager from "../../src/repos/tokenManager.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; import { stubEnv } from "../testUtilities/stubEnv.js"; diff --git a/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts b/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts index 29bcd6a94..b77c616e1 100644 --- a/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts @@ -5,12 +5,12 @@ import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; -import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; -import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; -import TokenManager from "../../src/repos/tokenManager.js"; +import type AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; +import type AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type TokenManager from "../../src/repos/tokenManager.js"; import assert from "node:assert/strict"; import { stubEnv } from "../testUtilities/stubEnv.js"; import { when } from "ts-mockito"; diff --git a/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts b/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts index 1eeb1eb83..c30fc60bf 100644 --- a/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts @@ -7,16 +7,16 @@ import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; import { deepEqual, verify, when } from "ts-mockito"; -import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; -import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; +import type AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; +import type AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; import ErrorWithStatus from "../wrappers/errorWithStatus.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; import { type GitPullRequest } from "azure-devops-node-api/interfaces/GitInterfaces.js"; import type { IGitApi } from "azure-devops-node-api/GitApi.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { StatusCodes } from "http-status-codes"; -import TokenManager from "../../src/repos/tokenManager.js"; +import type TokenManager from "../../src/repos/tokenManager.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; diff --git a/src/task/tests/repos/azureReposInvoker.updateComment.spec.ts b/src/task/tests/repos/azureReposInvoker.updateComment.spec.ts index 850cfd6cc..a00731112 100644 --- a/src/task/tests/repos/azureReposInvoker.updateComment.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.updateComment.spec.ts @@ -8,15 +8,15 @@ import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import { type Comment, CommentThreadStatus, type GitPullRequestCommentThread } from "azure-devops-node-api/interfaces/GitInterfaces.js"; import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; import { deepEqual, verify, when } from "ts-mockito"; -import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; -import AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; +import type AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; +import type AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; import ErrorWithStatus from "../wrappers/errorWithStatus.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; import type { IGitApi } from "azure-devops-node-api/GitApi.js"; -import Logger from "../../src/utilities/logger.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type Logger from "../../src/utilities/logger.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { StatusCodes } from "http-status-codes"; -import TokenManager from "../../src/repos/tokenManager.js"; +import type TokenManager from "../../src/repos/tokenManager.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; diff --git a/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts b/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts index 621379e66..c4fc247c5 100644 --- a/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts @@ -9,14 +9,14 @@ import * as GitHubReposInvokerConstants from "./gitHubReposInvokerConstants.js"; import { any, anyNumber, anyString } from "../testUtilities/mockito.js"; import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; import { verify, when } from "ts-mockito"; -import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; +import type GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; import HttpError from "../testUtilities/httpError.js"; -import Logger from "../../src/utilities/logger.js"; +import type Logger from "../../src/utilities/logger.js"; import type { OctokitOptions } from "@octokit/core"; -import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; +import type OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; import type { RequestError } from "@octokit/request-error"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { StatusCodes } from "http-status-codes"; import assert from "node:assert/strict"; import { createRequestError } from "../testUtilities/createRequestError.js"; diff --git a/src/task/tests/repos/gitHubReposInvoker.deleteCommentThread.spec.ts b/src/task/tests/repos/gitHubReposInvoker.deleteCommentThread.spec.ts index a89778e48..afaca9b60 100644 --- a/src/task/tests/repos/gitHubReposInvoker.deleteCommentThread.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.deleteCommentThread.spec.ts @@ -6,12 +6,12 @@ import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; import { verify, when } from "ts-mockito"; -import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; -import Logger from "../../src/utilities/logger.js"; +import type GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; +import type Logger from "../../src/utilities/logger.js"; import type { OctokitOptions } from "@octokit/core"; -import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; diff --git a/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts b/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts index 509e902a2..a38e90a13 100644 --- a/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts @@ -11,12 +11,12 @@ import { verify, when } from "ts-mockito"; import type CommentData from "../../src/repos/interfaces/commentData.js"; import { CommentThreadStatus } from "azure-devops-node-api/interfaces/GitInterfaces.js"; import type GetIssueCommentsResponse from "../../src/wrappers/octokitInterfaces/getIssueCommentsResponse.js"; -import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; -import Logger from "../../src/utilities/logger.js"; +import type GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; +import type Logger from "../../src/utilities/logger.js"; import type { OctokitOptions } from "@octokit/core"; -import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; diff --git a/src/task/tests/repos/gitHubReposInvoker.getTitleAndDescription.spec.ts b/src/task/tests/repos/gitHubReposInvoker.getTitleAndDescription.spec.ts index 5ef1367bf..3bfc367e2 100644 --- a/src/task/tests/repos/gitHubReposInvoker.getTitleAndDescription.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.getTitleAndDescription.spec.ts @@ -11,15 +11,15 @@ import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./g import { verify, when } from "ts-mockito"; import type ErrorWithStatusInterface from "../../src/repos/interfaces/errorWithStatusInterface.js"; import type GetPullResponse from "../../src/wrappers/octokitInterfaces/getPullResponse.js"; -import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; -import Logger from "../../src/utilities/logger.js"; +import type GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; +import type Logger from "../../src/utilities/logger.js"; import type OctokitLogObjectInterface from "../wrappers/octokitLogObjectInterface.js"; import type { OctokitOptions } from "@octokit/core"; -import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; +import type OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; import type PullRequestDetailsInterface from "../../src/repos/interfaces/pullRequestDetailsInterface.js"; import type { RequestError } from "@octokit/request-error"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { StatusCodes } from "http-status-codes"; import assert from "node:assert/strict"; import { createRequestError } from "../testUtilities/createRequestError.js"; diff --git a/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts b/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts index 9cdea8f36..9e1940ff3 100644 --- a/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts @@ -5,11 +5,11 @@ import { createGitHubReposInvokerMocks, createSut } from "./gitHubReposInvokerTestSetup.js"; -import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; -import Logger from "../../src/utilities/logger.js"; -import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; +import type Logger from "../../src/utilities/logger.js"; +import type OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { stubEnv } from "../testUtilities/stubEnv.js"; diff --git a/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts b/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts index ae1c974a7..3cbc01267 100644 --- a/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts @@ -6,12 +6,12 @@ import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; import { verify, when } from "ts-mockito"; -import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; -import Logger from "../../src/utilities/logger.js"; +import type GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; +import type Logger from "../../src/utilities/logger.js"; import type { OctokitOptions } from "@octokit/core"; -import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; diff --git a/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts b/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts index 7395914be..bc35df729 100644 --- a/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts @@ -6,12 +6,12 @@ import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; import { verify, when } from "ts-mockito"; -import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; -import GitInvoker from "../../src/git/gitInvoker.js"; -import Logger from "../../src/utilities/logger.js"; +import type GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; +import type GitInvoker from "../../src/git/gitInvoker.js"; +import type Logger from "../../src/utilities/logger.js"; import type { OctokitOptions } from "@octokit/core"; -import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; -import RunnerInvoker from "../../src/runners/runnerInvoker.js"; +import type OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; +import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; From 69c7d037ae654b438d4b94569996f888292142bc Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Mon, 20 Apr 2026 11:20:12 +0100 Subject: [PATCH 11/27] Use separate type import for GitPullRequest Converts the inline `import { type GitPullRequest }` to `import type { GitPullRequest }` for consistency with surrounding imports. ESLint's consistent-type-imports rule accepts both forms, so this was a manual cleanup. --- .../repos/azureReposInvoker.setTitleAndDescription.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts b/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts index c30fc60bf..bd1b0f7b3 100644 --- a/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts @@ -11,7 +11,7 @@ import type AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper import type AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; import ErrorWithStatus from "../wrappers/errorWithStatus.js"; import type GitInvoker from "../../src/git/gitInvoker.js"; -import { type GitPullRequest } from "azure-devops-node-api/interfaces/GitInterfaces.js"; +import type { GitPullRequest } from "azure-devops-node-api/interfaces/GitInterfaces.js"; import type { IGitApi } from "azure-devops-node-api/GitApi.js"; import type Logger from "../../src/utilities/logger.js"; import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; From fd6e911fe2c2828edf9658b9d746b3e8e6d6bc72 Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Mon, 20 Apr 2026 11:23:34 +0100 Subject: [PATCH 12/27] chore: fix linting --- src/task/tests/git/gitInvoker.spec.ts | 17 --- .../metrics/codeMetrics.defaults.spec.ts | 16 +-- .../metrics/codeMetrics.edgeCases.spec.ts | 23 +-- .../metrics/codeMetrics.fileMatching.spec.ts | 44 ++++-- ....getDeletedFilesNotRequiringReview.spec.ts | 37 +++-- ...Metrics.getFilesNotRequiringReview.spec.ts | 37 +++-- .../metrics/codeMetrics.nonDefaults.spec.ts | 16 +-- .../metrics/codeMetricsCalculator.spec.ts | 3 - .../tests/metrics/inputs.allInputs.spec.ts | 2 - .../metrics/inputs.alwaysCloseComment.spec.ts | 2 - .../tests/metrics/inputs.baseSize.spec.ts | 8 +- .../metrics/inputs.codeFileExtensions.spec.ts | 40 +++--- .../inputs.fileMatchingPatterns.spec.ts | 48 +++---- .../tests/metrics/inputs.growthRate.spec.ts | 2 - .../tests/metrics/inputs.property.spec.ts | 4 +- .../tests/metrics/inputs.testFactor.spec.ts | 2 - .../inputs.testMatchingPatterns.spec.ts | 48 +++---- src/task/tests/metrics/inputsTestSetup.ts | 13 +- .../tests/pullRequests/pullRequest.spec.ts | 6 - .../azureReposInvoker.createComment.spec.ts | 52 +++++-- ...reReposInvoker.deleteCommentThread.spec.ts | 31 ++++- .../azureReposInvoker.getComments.spec.ts | 84 +++++++++-- ...eposInvoker.getTitleAndDescription.spec.ts | 79 +++++++++-- ...eposInvoker.isAccessTokenAvailable.spec.ts | 31 ++++- ...eposInvoker.setTitleAndDescription.spec.ts | 55 ++++++-- .../azureReposInvoker.updateComment.spec.ts | 69 +++++++-- .../tests/repos/azureReposInvokerTestSetup.ts | 5 +- .../gitHubReposInvoker.createComment.spec.ts | 79 ++++++++--- ...ubReposInvoker.deleteCommentThread.spec.ts | 23 +-- .../gitHubReposInvoker.getComments.spec.ts | 44 ++++-- ...eposInvoker.getTitleAndDescription.spec.ts | 131 +++++++++++++----- ...eposInvoker.isAccessTokenAvailable.spec.ts | 38 +++-- ...eposInvoker.setTitleAndDescription.spec.ts | 44 ++++-- .../gitHubReposInvoker.updateComment.spec.ts | 30 ++-- src/task/tests/repos/reposInvoker.spec.ts | 29 ---- src/task/tests/runners/runnerInvoker.spec.ts | 16 --- 36 files changed, 810 insertions(+), 398 deletions(-) diff --git a/src/task/tests/git/gitInvoker.spec.ts b/src/task/tests/git/gitInvoker.spec.ts index 07cd00b13..985375965 100644 --- a/src/task/tests/git/gitInvoker.spec.ts +++ b/src/task/tests/git/gitInvoker.spec.ts @@ -76,7 +76,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, 12345); - }); it("should return the correct output when the GitHub runner is being used and it is called multiple times", (): void => { @@ -95,7 +94,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result1, 12345); assert.equal(result2, 12345); - }); it("should throw an error when the GitHub runner is being used and GITHUB_REF is undefined", (): void => { @@ -117,7 +115,6 @@ describe("gitInvoker.ts", (): void => { ), ); verify(logger.logWarning("'GITHUB_REF' is undefined.")).once(); - }); it("should throw an error when the GitHub runner is being used and GITHUB_REF is in the incorrect format", (): void => { @@ -144,7 +141,6 @@ describe("gitInvoker.ts", (): void => { "'GITHUB_REF' is in an incorrect format 'refs/pull'.", ), ).once(); - }); it("should return the correct output when the Azure Pipelines runner is being used", (): void => { @@ -161,7 +157,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, 12345); - }); it("should throw an error when the Azure Pipelines runner is being used and BUILD_REPOSITORY_PROVIDER is undefined", (): void => { @@ -262,7 +257,6 @@ describe("gitInvoker.ts", (): void => { "'SYSTEM_PULLREQUEST_PULLREQUESTNUMBER' is undefined.", ), ).once(); - }); }); } @@ -291,7 +285,6 @@ describe("gitInvoker.ts", (): void => { "Pull request ID 'PullRequestID' from 'GITHUB_REF' is not numeric.", ), ).once(); - }); { @@ -322,7 +315,6 @@ describe("gitInvoker.ts", (): void => { "'SYSTEM_PULLREQUEST_PULLREQUESTNUMBER' is not numeric 'abc'.", ), ).once(); - }); }); } @@ -405,7 +397,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, true); - }); it("should return false when the GitHub runner is being used and GITHUB_REF is undefined", (): void => { @@ -422,7 +413,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, false); verify(logger.logWarning("'GITHUB_REF' is undefined.")).once(); - }); it("should return false when the GitHub runner is being used and GITHUB_REF is in the incorrect format", (): void => { @@ -444,7 +434,6 @@ describe("gitInvoker.ts", (): void => { "'GITHUB_REF' is in an incorrect format 'refs/pull'.", ), ).once(); - }); it("should return true when the Azure Pipelines runner is being used", (): void => { @@ -461,7 +450,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, true); - }); it("should throw an error when the Azure Pipelines runner is being used and BUILD_REPOSITORY_PROVIDER is undefined", (): void => { @@ -522,7 +510,6 @@ describe("gitInvoker.ts", (): void => { "'SYSTEM_PULLREQUEST_PULLREQUESTNUMBER' is undefined.", ), ).once(); - }); }); } @@ -541,7 +528,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, false); - }); }); @@ -593,7 +579,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, true); - }); it("should throw an error when the PR is using the GitHub runner and GITHUB_BASE_REF is undefined", async (): Promise => { @@ -613,7 +598,6 @@ describe("gitInvoker.ts", (): void => { func, "'GITHUB_BASE_REF', accessed within 'GitInvoker.targetBranch', is invalid, null, or undefined 'undefined'.", ); - }); { @@ -640,7 +624,6 @@ describe("gitInvoker.ts", (): void => { // Assert assert.equal(result, true); - }); }); } diff --git a/src/task/tests/metrics/codeMetrics.defaults.spec.ts b/src/task/tests/metrics/codeMetrics.defaults.spec.ts index a556e4122..ba68a3f23 100644 --- a/src/task/tests/metrics/codeMetrics.defaults.spec.ts +++ b/src/task/tests/metrics/codeMetrics.defaults.spec.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ - import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; import type CodeMetrics from "../../src/metrics/codeMetrics.js"; import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; @@ -14,7 +13,6 @@ import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { when } from "ts-mockito"; - describe("codeMetrics.ts", (): void => { let gitInvoker: GitInvoker; let inputs: Inputs; @@ -22,12 +20,7 @@ describe("codeMetrics.ts", (): void => { let runnerInvoker: RunnerInvoker; beforeEach((): void => { - ({ - gitInvoker, - inputs, - logger, - runnerInvoker, - } = createCodeMetricsMocks()); + ({ gitInvoker, inputs, logger, runnerInvoker } = createCodeMetricsMocks()); }); { @@ -473,7 +466,12 @@ describe("codeMetrics.ts", (): void => { when(gitInvoker.getDiffSummary()).thenResolve(gitResponse); // Act - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // Assert assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), []); diff --git a/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts b/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts index 1b2b35570..671c6ab4e 100644 --- a/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts +++ b/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ - import { anyString, when } from "ts-mockito"; import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; import type CodeMetrics from "../../src/metrics/codeMetrics.js"; @@ -14,7 +13,6 @@ import type Logger from "../../src/utilities/logger.js"; import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; - describe("codeMetrics.ts", (): void => { let gitInvoker: GitInvoker; let inputs: Inputs; @@ -22,12 +20,7 @@ describe("codeMetrics.ts", (): void => { let runnerInvoker: RunnerInvoker; beforeEach((): void => { - ({ - gitInvoker, - inputs, - logger, - runnerInvoker, - } = createCodeMetricsMocks()); + ({ gitInvoker, inputs, logger, runnerInvoker } = createCodeMetricsMocks()); }); it("should return the expected result with test coverage disabled", async (): Promise => { @@ -36,7 +29,12 @@ describe("codeMetrics.ts", (): void => { when(gitInvoker.getDiffSummary()).thenResolve("1\t0\tfile.ts"); // Act - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // Assert assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), []); @@ -66,7 +64,12 @@ describe("codeMetrics.ts", (): void => { anyString() as string, ), ).thenReturn(""); - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // ACT const size: string = await codeMetrics.getSize(); diff --git a/src/task/tests/metrics/codeMetrics.fileMatching.spec.ts b/src/task/tests/metrics/codeMetrics.fileMatching.spec.ts index 06afd00dc..d4813ae8a 100644 --- a/src/task/tests/metrics/codeMetrics.fileMatching.spec.ts +++ b/src/task/tests/metrics/codeMetrics.fileMatching.spec.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ - import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; import type CodeMetrics from "../../src/metrics/codeMetrics.js"; import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; @@ -14,7 +13,6 @@ import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { when } from "ts-mockito"; - describe("codeMetrics.ts", (): void => { let gitInvoker: GitInvoker; let inputs: Inputs; @@ -22,12 +20,7 @@ describe("codeMetrics.ts", (): void => { let runnerInvoker: RunnerInvoker; beforeEach((): void => { - ({ - gitInvoker, - inputs, - logger, - runnerInvoker, - } = createCodeMetricsMocks()); + ({ gitInvoker, inputs, logger, runnerInvoker } = createCodeMetricsMocks()); }); { @@ -66,7 +59,12 @@ describe("codeMetrics.ts", (): void => { when(gitInvoker.getDiffSummary()).thenResolve(gitResponse); // Act - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // Assert assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), [ @@ -101,7 +99,12 @@ describe("codeMetrics.ts", (): void => { ); // Act - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // Assert assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), [ @@ -130,7 +133,12 @@ describe("codeMetrics.ts", (): void => { ); // Act - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // Assert assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), [ @@ -164,7 +172,12 @@ describe("codeMetrics.ts", (): void => { ); // Act - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // Assert assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), [ @@ -190,7 +203,12 @@ describe("codeMetrics.ts", (): void => { ); // Act - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // Assert assert.deepEqual(await codeMetrics.getFilesNotRequiringReview(), []); diff --git a/src/task/tests/metrics/codeMetrics.getDeletedFilesNotRequiringReview.spec.ts b/src/task/tests/metrics/codeMetrics.getDeletedFilesNotRequiringReview.spec.ts index ef543c9f7..a966b1a31 100644 --- a/src/task/tests/metrics/codeMetrics.getDeletedFilesNotRequiringReview.spec.ts +++ b/src/task/tests/metrics/codeMetrics.getDeletedFilesNotRequiringReview.spec.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ - import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; import type CodeMetrics from "../../src/metrics/codeMetrics.js"; @@ -14,7 +13,6 @@ import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { when } from "ts-mockito"; - describe("codeMetrics.ts", (): void => { let gitInvoker: GitInvoker; let inputs: Inputs; @@ -22,19 +20,19 @@ describe("codeMetrics.ts", (): void => { let runnerInvoker: RunnerInvoker; beforeEach((): void => { - ({ - gitInvoker, - inputs, - logger, - runnerInvoker, - } = createCodeMetricsMocks()); + ({ gitInvoker, inputs, logger, runnerInvoker } = createCodeMetricsMocks()); }); describe("getDeletedFilesNotRequiringReview()", (): void => { it("should return an empty array when the Git diff summary '' is empty", async (): Promise => { // Arrange when(gitInvoker.getDiffSummary()).thenResolve(""); - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // Act const result: string[] = @@ -47,7 +45,12 @@ describe("codeMetrics.ts", (): void => { it("should throw when the file name in the Git diff summary '0' cannot be parsed", async (): Promise => { // Arrange when(gitInvoker.getDiffSummary()).thenResolve("0"); - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // Act const func: () => Promise = async () => @@ -63,7 +66,12 @@ describe("codeMetrics.ts", (): void => { it("should throw when the lines added in the Git diff summary cannot be converted", async (): Promise => { // Arrange when(gitInvoker.getDiffSummary()).thenResolve("A\t0\tfile.ts"); - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // Act const func: () => Promise = async () => @@ -79,7 +87,12 @@ describe("codeMetrics.ts", (): void => { it("should throw when the lines deleted in the Git diff summary cannot be converted", async (): Promise => { // Arrange when(gitInvoker.getDiffSummary()).thenResolve("0\tA\tfile.ts"); - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // Act const func: () => Promise = async () => diff --git a/src/task/tests/metrics/codeMetrics.getFilesNotRequiringReview.spec.ts b/src/task/tests/metrics/codeMetrics.getFilesNotRequiringReview.spec.ts index 59068e72c..c806b6088 100644 --- a/src/task/tests/metrics/codeMetrics.getFilesNotRequiringReview.spec.ts +++ b/src/task/tests/metrics/codeMetrics.getFilesNotRequiringReview.spec.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ - import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; import type CodeMetrics from "../../src/metrics/codeMetrics.js"; @@ -14,7 +13,6 @@ import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { when } from "ts-mockito"; - describe("codeMetrics.ts", (): void => { let gitInvoker: GitInvoker; let inputs: Inputs; @@ -22,12 +20,7 @@ describe("codeMetrics.ts", (): void => { let runnerInvoker: RunnerInvoker; beforeEach((): void => { - ({ - gitInvoker, - inputs, - logger, - runnerInvoker, - } = createCodeMetricsMocks()); + ({ gitInvoker, inputs, logger, runnerInvoker } = createCodeMetricsMocks()); }); describe("getFilesNotRequiringReview()", (): void => { @@ -38,7 +31,12 @@ describe("codeMetrics.ts", (): void => { it(`should return an empty array when the Git diff summary '${gitDiffSummary}' is empty`, async (): Promise => { // Arrange when(gitInvoker.getDiffSummary()).thenResolve(gitDiffSummary); - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // Act const result: string[] = @@ -87,7 +85,12 @@ describe("codeMetrics.ts", (): void => { it(`should throw when the file name in the Git diff summary '${summary}' cannot be parsed`, async (): Promise => { // Arrange when(gitInvoker.getDiffSummary()).thenResolve(summary); - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // Act const func: () => Promise = async () => @@ -105,7 +108,12 @@ describe("codeMetrics.ts", (): void => { it("should throw when the lines added in the Git diff summary cannot be converted", async (): Promise => { // Arrange when(gitInvoker.getDiffSummary()).thenResolve("A\t0\tfile.ts"); - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // Act const func: () => Promise = async () => @@ -121,7 +129,12 @@ describe("codeMetrics.ts", (): void => { it("should throw when the lines deleted in the Git diff summary cannot be converted", async (): Promise => { // Arrange when(gitInvoker.getDiffSummary()).thenResolve("0\tA\tfile.ts"); - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // Act const func: () => Promise = async () => diff --git a/src/task/tests/metrics/codeMetrics.nonDefaults.spec.ts b/src/task/tests/metrics/codeMetrics.nonDefaults.spec.ts index 4451ba31b..d02e49eb7 100644 --- a/src/task/tests/metrics/codeMetrics.nonDefaults.spec.ts +++ b/src/task/tests/metrics/codeMetrics.nonDefaults.spec.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ - import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; import type CodeMetrics from "../../src/metrics/codeMetrics.js"; import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; @@ -14,7 +13,6 @@ import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { when } from "ts-mockito"; - describe("codeMetrics.ts", (): void => { let gitInvoker: GitInvoker; let inputs: Inputs; @@ -22,12 +20,7 @@ describe("codeMetrics.ts", (): void => { let runnerInvoker: RunnerInvoker; beforeEach((): void => { - ({ - gitInvoker, - inputs, - logger, - runnerInvoker, - } = createCodeMetricsMocks()); + ({ gitInvoker, inputs, logger, runnerInvoker } = createCodeMetricsMocks()); }); { @@ -538,7 +531,12 @@ describe("codeMetrics.ts", (): void => { when(gitInvoker.getDiffSummary()).thenResolve(gitResponse); // Act - const codeMetrics: CodeMetrics = createSut(gitInvoker, inputs, logger, runnerInvoker); + const codeMetrics: CodeMetrics = createSut( + gitInvoker, + inputs, + logger, + runnerInvoker, + ); // Assert assert.deepEqual( diff --git a/src/task/tests/metrics/codeMetricsCalculator.spec.ts b/src/task/tests/metrics/codeMetricsCalculator.spec.ts index 680f358ac..d5e89f777 100644 --- a/src/task/tests/metrics/codeMetricsCalculator.spec.ts +++ b/src/task/tests/metrics/codeMetricsCalculator.spec.ts @@ -197,7 +197,6 @@ describe("codeMetricsCalculator.ts", (): void => { result, "No Git repo present. Run the 'actions/checkout' action prior to PR Metrics.", ); - }); it("should return the appropriate message when the pull request ID is not available on Azure DevOps", async (): Promise => { @@ -242,7 +241,6 @@ describe("codeMetricsCalculator.ts", (): void => { result, "Could not determine the Pull Request ID. Ensure 'pull_request' is the pipeline trigger.", ); - }); it("should return the appropriate message when the Git history is unavailable on Azure DevOps", async (): Promise => { @@ -290,7 +288,6 @@ describe("codeMetricsCalculator.ts", (): void => { result, "Could not access sufficient Git history. Add 'fetch-depth: 0' as a parameter to the 'actions/checkout' action.", ); - }); }); diff --git a/src/task/tests/metrics/inputs.allInputs.spec.ts b/src/task/tests/metrics/inputs.allInputs.spec.ts index f57a0ae6c..ab01dbe58 100644 --- a/src/task/tests/metrics/inputs.allInputs.spec.ts +++ b/src/task/tests/metrics/inputs.allInputs.spec.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ - import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { adjustingAlwaysCloseComment, @@ -29,7 +28,6 @@ import type Logger from "../../src/utilities/logger.js"; import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; - describe("inputs.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; diff --git a/src/task/tests/metrics/inputs.alwaysCloseComment.spec.ts b/src/task/tests/metrics/inputs.alwaysCloseComment.spec.ts index 93ef74a36..365d6a762 100644 --- a/src/task/tests/metrics/inputs.alwaysCloseComment.spec.ts +++ b/src/task/tests/metrics/inputs.alwaysCloseComment.spec.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ - import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { adjustingAlwaysCloseComment, @@ -17,7 +16,6 @@ import type Logger from "../../src/utilities/logger.js"; import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; - describe("inputs.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; diff --git a/src/task/tests/metrics/inputs.baseSize.spec.ts b/src/task/tests/metrics/inputs.baseSize.spec.ts index 3f419e5e0..7acd6a011 100644 --- a/src/task/tests/metrics/inputs.baseSize.spec.ts +++ b/src/task/tests/metrics/inputs.baseSize.spec.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ - import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { adjustingBaseSizeResource, @@ -19,7 +18,6 @@ import assert from "node:assert/strict"; import { decimalRadix } from "../../src/utilities/constants.js"; import { invalidNumericStrings } from "../testUtilities/fixtures/invalidInputs.js"; - describe("inputs.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; @@ -33,9 +31,9 @@ describe("inputs.ts", (): void => { invalidNumericStrings.forEach((baseSize: string | null): void => { it(`should set the default when the input '${String(baseSize)}' is invalid`, (): void => { // Arrange - when( - runnerInvoker.getInput(deepEqual(["Base", "Size"])), - ).thenReturn(baseSize); + when(runnerInvoker.getInput(deepEqual(["Base", "Size"]))).thenReturn( + baseSize, + ); // Act const inputs: Inputs = createSut(logger, runnerInvoker); diff --git a/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts b/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts index d035c386d..389e78d31 100644 --- a/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts +++ b/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ - import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { adjustingCodeFileExtensionsResource, @@ -18,7 +17,6 @@ import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { invalidPatternStrings } from "../testUtilities/fixtures/invalidInputs.js"; - describe("inputs.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; @@ -29,24 +27,26 @@ describe("inputs.ts", (): void => { describe("initialize()", (): void => { describe("codeFileExtensions", (): void => { - invalidPatternStrings.forEach((codeFileExtensions: string | null): void => { - it(`should set the default when the input '${String(codeFileExtensions?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), - ).thenReturn(codeFileExtensions); - - // Act - const inputs: Inputs = createSut(logger, runnerInvoker); - - // Assert - assert.deepEqual( - inputs.codeFileExtensions, - new Set(InputsDefault.codeFileExtensions), - ); - verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); - }); - }); + invalidPatternStrings.forEach( + (codeFileExtensions: string | null): void => { + it(`should set the default when the input '${String(codeFileExtensions?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { + // Arrange + when( + runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), + ).thenReturn(codeFileExtensions); + + // Act + const inputs: Inputs = createSut(logger, runnerInvoker); + + // Assert + assert.deepEqual( + inputs.codeFileExtensions, + new Set(InputsDefault.codeFileExtensions), + ); + verify(logger.logInfo(adjustingCodeFileExtensionsResource)).once(); + }); + }, + ); { const testCases: string[] = [ diff --git a/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts b/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts index 5c0c01386..dd58ee63c 100644 --- a/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts +++ b/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ - import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { adjustingFileMatchingPatternsResource, @@ -18,7 +17,6 @@ import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { invalidPatternStrings } from "../testUtilities/fixtures/invalidInputs.js"; - describe("inputs.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; @@ -29,28 +27,30 @@ describe("inputs.ts", (): void => { describe("initialize()", (): void => { describe("fileMatchingPatterns", (): void => { - invalidPatternStrings.forEach((fileMatchingPatterns: string | null): void => { - it(`should set the default when the input '${String(fileMatchingPatterns?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { - // Arrange - when( - runnerInvoker.getInput( - deepEqual(["File", "Matching", "Patterns"]), - ), - ).thenReturn(fileMatchingPatterns); - - // Act - const inputs: Inputs = createSut(logger, runnerInvoker); - - // Assert - assert.deepEqual( - inputs.fileMatchingPatterns, - InputsDefault.fileMatchingPatterns, - ); - verify( - logger.logInfo(adjustingFileMatchingPatternsResource), - ).once(); - }); - }); + invalidPatternStrings.forEach( + (fileMatchingPatterns: string | null): void => { + it(`should set the default when the input '${String(fileMatchingPatterns?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { + // Arrange + when( + runnerInvoker.getInput( + deepEqual(["File", "Matching", "Patterns"]), + ), + ).thenReturn(fileMatchingPatterns); + + // Act + const inputs: Inputs = createSut(logger, runnerInvoker); + + // Assert + assert.deepEqual( + inputs.fileMatchingPatterns, + InputsDefault.fileMatchingPatterns, + ); + verify( + logger.logInfo(adjustingFileMatchingPatternsResource), + ).once(); + }); + }, + ); { const testCases: string[] = [ diff --git a/src/task/tests/metrics/inputs.growthRate.spec.ts b/src/task/tests/metrics/inputs.growthRate.spec.ts index e5967f8bc..dc152f3df 100644 --- a/src/task/tests/metrics/inputs.growthRate.spec.ts +++ b/src/task/tests/metrics/inputs.growthRate.spec.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ - import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { adjustingGrowthRateResource, @@ -18,7 +17,6 @@ import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { invalidNumericStrings } from "../testUtilities/fixtures/invalidInputs.js"; - describe("inputs.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; diff --git a/src/task/tests/metrics/inputs.property.spec.ts b/src/task/tests/metrics/inputs.property.spec.ts index 3f704e4e4..958d084d2 100644 --- a/src/task/tests/metrics/inputs.property.spec.ts +++ b/src/task/tests/metrics/inputs.property.spec.ts @@ -228,7 +228,9 @@ describe("inputs.ts", (): void => { const inputs: Inputs = createInputs({ fileMatchingPatterns: `${head}\\${tail}`, }); - assert.deepEqual(inputs.fileMatchingPatterns, [`${head}/${tail}`]); + assert.deepEqual(inputs.fileMatchingPatterns, [ + `${head}/${tail}`, + ]); }, ), { numRuns }, diff --git a/src/task/tests/metrics/inputs.testFactor.spec.ts b/src/task/tests/metrics/inputs.testFactor.spec.ts index 56aeeac73..2353674ba 100644 --- a/src/task/tests/metrics/inputs.testFactor.spec.ts +++ b/src/task/tests/metrics/inputs.testFactor.spec.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ - import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { adjustingTestFactorResource, @@ -19,7 +18,6 @@ import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { invalidNumericStrings } from "../testUtilities/fixtures/invalidInputs.js"; - describe("inputs.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; diff --git a/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts b/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts index f58d0b873..340180698 100644 --- a/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts +++ b/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ - import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { adjustingTestMatchingPatternsResource, @@ -18,7 +17,6 @@ import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { invalidPatternStrings } from "../testUtilities/fixtures/invalidInputs.js"; - describe("inputs.ts", (): void => { let logger: Logger; let runnerInvoker: RunnerInvoker; @@ -29,28 +27,30 @@ describe("inputs.ts", (): void => { describe("initialize()", (): void => { describe("testMatchingPatterns", (): void => { - invalidPatternStrings.forEach((testMatchingPatterns: string | null): void => { - it(`should set the default when the input '${String(testMatchingPatterns?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { - // Arrange - when( - runnerInvoker.getInput( - deepEqual(["Test", "Matching", "Patterns"]), - ), - ).thenReturn(testMatchingPatterns); - - // Act - const inputs: Inputs = createSut(logger, runnerInvoker); - - // Assert - assert.deepEqual( - inputs.testMatchingPatterns, - InputsDefault.testMatchingPatterns, - ); - verify( - logger.logInfo(adjustingTestMatchingPatternsResource), - ).once(); - }); - }); + invalidPatternStrings.forEach( + (testMatchingPatterns: string | null): void => { + it(`should set the default when the input '${String(testMatchingPatterns?.replace(/\n/gu, "\\n"))}' is invalid`, (): void => { + // Arrange + when( + runnerInvoker.getInput( + deepEqual(["Test", "Matching", "Patterns"]), + ), + ).thenReturn(testMatchingPatterns); + + // Act + const inputs: Inputs = createSut(logger, runnerInvoker); + + // Assert + assert.deepEqual( + inputs.testMatchingPatterns, + InputsDefault.testMatchingPatterns, + ); + verify( + logger.logInfo(adjustingTestMatchingPatternsResource), + ).once(); + }); + }, + ); { const testCases: string[] = [ diff --git a/src/task/tests/metrics/inputsTestSetup.ts b/src/task/tests/metrics/inputsTestSetup.ts index 2457c4502..6e0ce1a16 100644 --- a/src/task/tests/metrics/inputsTestSetup.ts +++ b/src/task/tests/metrics/inputsTestSetup.ts @@ -22,7 +22,8 @@ export const disablingTestFactorResource = "Disabling the test factor validation."; export const settingAlwaysCloseComment = "Setting the always-close-comment mode input to 'true'."; -export const settingBaseSizeResource = "Setting the base size input to 'VALUE'."; +export const settingBaseSizeResource = + "Setting the base size input to 'VALUE'."; export const settingGrowthRateResource = "Setting the growth rate input to 'VALUE'."; export const settingTestFactorResource = @@ -119,10 +120,16 @@ export const createInputsMocks = (): InputsMocks => { runnerInvoker.loc("metrics.inputs.settingTestFactor", anyString()), ).thenReturn(settingTestFactorResource); when( - runnerInvoker.loc("metrics.inputs.settingFileMatchingPatterns", anyString()), + runnerInvoker.loc( + "metrics.inputs.settingFileMatchingPatterns", + anyString(), + ), ).thenReturn(settingFileMatchingPatternsResource); when( - runnerInvoker.loc("metrics.inputs.settingTestMatchingPatterns", anyString()), + runnerInvoker.loc( + "metrics.inputs.settingTestMatchingPatterns", + anyString(), + ), ).thenReturn(settingTestMatchingPatternsResource); when( runnerInvoker.loc("metrics.inputs.settingCodeFileExtensions", anyString()), diff --git a/src/task/tests/pullRequests/pullRequest.spec.ts b/src/task/tests/pullRequests/pullRequest.spec.ts index ec9a40c1d..aabf70f39 100644 --- a/src/task/tests/pullRequests/pullRequest.spec.ts +++ b/src/task/tests/pullRequests/pullRequest.spec.ts @@ -43,7 +43,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, true); - }); it("should return false when the GitHub runner is being used and GITHUB_BASE_REF is the empty string", (): void => { @@ -61,7 +60,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, false); - }); it("should return true when the Azure Pipelines runner is being used and SYSTEM_PULLREQUEST_PULLREQUESTID is defined", (): void => { @@ -78,7 +76,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, true); - }); it("should return false when the Azure Pipelines runner is being used and SYSTEM_PULLREQUEST_PULLREQUESTID is not defined", (): void => { @@ -113,7 +110,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, true); - }); it("should throw an error when the Azure Pipelines runner is being used and BUILD_REPOSITORY_PROVIDER is undefined", (): void => { @@ -156,7 +152,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, true); - }); }); } @@ -175,7 +170,6 @@ describe("pullRequest.ts", (): void => { // Assert assert.equal(result, "Other"); - }); }); diff --git a/src/task/tests/repos/azureReposInvoker.createComment.spec.ts b/src/task/tests/repos/azureReposInvoker.createComment.spec.ts index b3aefb130..a56a121f5 100644 --- a/src/task/tests/repos/azureReposInvoker.createComment.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.createComment.spec.ts @@ -3,10 +3,15 @@ * Licensed under the MIT License. */ - import * as AssertExtensions from "../testUtilities/assertExtensions.js"; -import { CommentThreadStatus, type GitPullRequestCommentThread } from "azure-devops-node-api/interfaces/GitInterfaces.js"; -import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; +import { + CommentThreadStatus, + type GitPullRequestCommentThread, +} from "azure-devops-node-api/interfaces/GitInterfaces.js"; +import { + createAzureReposInvokerMocks, + createSut, +} from "./azureReposInvokerTestSetup.js"; import { deepEqual, verify, when } from "ts-mockito"; import type AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; import type AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; @@ -20,7 +25,6 @@ import type TokenManager from "../../src/repos/tokenManager.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; - describe("azureReposInvoker.ts", (): void => { let gitApi: IGitApi; let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; @@ -56,7 +60,13 @@ describe("azureReposInvoker.ts", (): void => { when(gitApi.createThread(any(), "RepoID", 10, "Project")).thenThrow( error, ); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const func: () => Promise = async () => @@ -104,7 +114,13 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.createComment( @@ -145,7 +161,13 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.createComment( @@ -202,7 +224,13 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.createComment( @@ -254,7 +282,13 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.createComment( diff --git a/src/task/tests/repos/azureReposInvoker.deleteCommentThread.spec.ts b/src/task/tests/repos/azureReposInvoker.deleteCommentThread.spec.ts index e3529be04..5883dfefa 100644 --- a/src/task/tests/repos/azureReposInvoker.deleteCommentThread.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.deleteCommentThread.spec.ts @@ -3,10 +3,12 @@ * Licensed under the MIT License. */ - import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import { any, anyNumber } from "../testUtilities/mockito.js"; -import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; +import { + createAzureReposInvokerMocks, + createSut, +} from "./azureReposInvokerTestSetup.js"; import { verify, when } from "ts-mockito"; import type AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; import type AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; @@ -19,7 +21,6 @@ import { StatusCodes } from "http-status-codes"; import type TokenManager from "../../src/repos/tokenManager.js"; import assert from "node:assert/strict"; - describe("azureReposInvoker.ts", (): void => { let gitApi: IGitApi; let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; @@ -55,7 +56,13 @@ describe("azureReposInvoker.ts", (): void => { when(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).thenThrow( error, ); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const func: () => Promise = async () => @@ -88,7 +95,13 @@ describe("azureReposInvoker.ts", (): void => { it("should call the API for a single comment", async (): Promise => { // Arrange when(gitApi.deleteComment("RepoID", 10, 20, 1, "Project")).thenResolve(); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.deleteCommentThread(20); @@ -109,7 +122,13 @@ describe("azureReposInvoker.ts", (): void => { when( gitApi.deleteComment("RepoID", 10, anyNumber(), 1, "Project"), ).thenResolve(); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.deleteCommentThread(20); diff --git a/src/task/tests/repos/azureReposInvoker.getComments.spec.ts b/src/task/tests/repos/azureReposInvoker.getComments.spec.ts index ecb5b34e6..da48aa140 100644 --- a/src/task/tests/repos/azureReposInvoker.getComments.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.getComments.spec.ts @@ -3,10 +3,15 @@ * Licensed under the MIT License. */ - import * as AssertExtensions from "../testUtilities/assertExtensions.js"; -import { CommentThreadStatus, type GitPullRequestCommentThread } from "azure-devops-node-api/interfaces/GitInterfaces.js"; -import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; +import { + CommentThreadStatus, + type GitPullRequestCommentThread, +} from "azure-devops-node-api/interfaces/GitInterfaces.js"; +import { + createAzureReposInvokerMocks, + createSut, +} from "./azureReposInvokerTestSetup.js"; import { verify, when } from "ts-mockito"; import type AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; import type AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; @@ -21,7 +26,6 @@ import type TokenManager from "../../src/repos/tokenManager.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; - describe("azureReposInvoker.ts", (): void => { let gitApi: IGitApi; let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; @@ -55,7 +59,13 @@ describe("azureReposInvoker.ts", (): void => { const error: ErrorWithStatus = new ErrorWithStatus("Test"); error.statusCode = statusCode; when(gitApi.getThreads("RepoID", 10, "Project")).thenThrow(error); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const func: () => Promise = async () => @@ -90,7 +100,13 @@ describe("azureReposInvoker.ts", (): void => { when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ { comments: [{ content: "Content" }], id: 1, status: 1 }, ]); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const result: CommentData = await azureReposInvoker.getComments(); @@ -124,7 +140,13 @@ describe("azureReposInvoker.ts", (): void => { threadContext: null as unknown as undefined, }, ]); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const result: CommentData = await azureReposInvoker.getComments(); @@ -158,7 +180,13 @@ describe("azureReposInvoker.ts", (): void => { threadContext: { filePath: "/file.ts" }, }, ]); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const result: CommentData = await azureReposInvoker.getComments(); @@ -191,7 +219,13 @@ describe("azureReposInvoker.ts", (): void => { threadContext: { filePath: "/file.ts" }, }, ]); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const result: CommentData = await azureReposInvoker.getComments(); @@ -224,7 +258,13 @@ describe("azureReposInvoker.ts", (): void => { when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ { comments: [{ content: "Content" }], id: 1, status: 1 }, ]); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.getComments(); @@ -254,7 +294,13 @@ describe("azureReposInvoker.ts", (): void => { when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve([ { comments: [{ content: "Content" }], status: 1 }, ]); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const func: () => Promise = async () => @@ -288,7 +334,13 @@ describe("azureReposInvoker.ts", (): void => { when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve( getThreadsResult, ); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const result: CommentData = await azureReposInvoker.getComments(); @@ -358,7 +410,13 @@ describe("azureReposInvoker.ts", (): void => { when(gitApi.getThreads("RepoID", 10, "Project")).thenResolve( getThreadsResult, ); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const result: CommentData = await azureReposInvoker.getComments(); diff --git a/src/task/tests/repos/azureReposInvoker.getTitleAndDescription.spec.ts b/src/task/tests/repos/azureReposInvoker.getTitleAndDescription.spec.ts index b760482ef..d82e0bd1a 100644 --- a/src/task/tests/repos/azureReposInvoker.getTitleAndDescription.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.getTitleAndDescription.spec.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. */ - import * as AssertExtensions from "../testUtilities/assertExtensions.js"; -import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; +import { + createAzureReposInvokerMocks, + createSut, +} from "./azureReposInvokerTestSetup.js"; import { verify, when } from "ts-mockito"; import type AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; import type AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; @@ -21,7 +23,6 @@ import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; import { stubEnv } from "../testUtilities/stubEnv.js"; - describe("azureReposInvoker.ts", (): void => { let gitApi: IGitApi; let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; @@ -54,7 +55,13 @@ describe("azureReposInvoker.ts", (): void => { stubEnv(["SYSTEM_TEAMPROJECT", variable]); } - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const func: () => Promise = async () => @@ -81,7 +88,13 @@ describe("azureReposInvoker.ts", (): void => { stubEnv(["BUILD_REPOSITORY_ID", variable]); } - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const func: () => Promise = async () => @@ -108,7 +121,13 @@ describe("azureReposInvoker.ts", (): void => { stubEnv(["PR_METRICS_ACCESS_TOKEN", variable]); } - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const func: () => Promise = async () => @@ -135,7 +154,13 @@ describe("azureReposInvoker.ts", (): void => { stubEnv(["SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", variable]); } - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const func: () => Promise = async () => @@ -166,7 +191,13 @@ describe("azureReposInvoker.ts", (): void => { const error: ErrorWithStatus = new ErrorWithStatus("Test"); error.statusCode = statusCode; when(gitApi.getPullRequestById(10, "Project")).thenThrow(error); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const func: () => Promise = async () => @@ -202,7 +233,13 @@ describe("azureReposInvoker.ts", (): void => { description: "Description", title: "Title", }); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const result: PullRequestDetailsInterface = @@ -227,7 +264,13 @@ describe("azureReposInvoker.ts", (): void => { description: "Description", title: "Title", }); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.getTitleAndDescription(); @@ -252,7 +295,13 @@ describe("azureReposInvoker.ts", (): void => { when(gitApi.getPullRequestById(10, "Project")).thenResolve({ title: "Title", }); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const result: PullRequestDetailsInterface = @@ -274,7 +323,13 @@ describe("azureReposInvoker.ts", (): void => { it("should throw when the title is unavailable", async (): Promise => { // Arrange when(gitApi.getPullRequestById(10, "Project")).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const func: () => Promise = async () => diff --git a/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts b/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts index b77c616e1..c0218ee7e 100644 --- a/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. */ - -import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; +import { + createAzureReposInvokerMocks, + createSut, +} from "./azureReposInvokerTestSetup.js"; import type AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; import type AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; import type GitInvoker from "../../src/git/gitInvoker.js"; @@ -15,7 +17,6 @@ import assert from "node:assert/strict"; import { stubEnv } from "../testUtilities/stubEnv.js"; import { when } from "ts-mockito"; - describe("azureReposInvoker.ts", (): void => { let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; let gitInvoker: GitInvoker; @@ -36,7 +37,13 @@ describe("azureReposInvoker.ts", (): void => { describe("isAccessTokenAvailable()", (): void => { it("should return null when the token exists", async (): Promise => { // Arrange - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const result: string | null = @@ -49,7 +56,13 @@ describe("azureReposInvoker.ts", (): void => { it("should return a string when the token manager fails", async (): Promise => { // Arrange stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); when(tokenManager.getToken()).thenResolve("Failure"); // Act @@ -63,7 +76,13 @@ describe("azureReposInvoker.ts", (): void => { it("should return a string when the token does not exist", async (): Promise => { // Arrange stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const result: string | null = diff --git a/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts b/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts index bd1b0f7b3..ec992d3ab 100644 --- a/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.setTitleAndDescription.spec.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. */ - import * as AssertExtensions from "../testUtilities/assertExtensions.js"; -import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; +import { + createAzureReposInvokerMocks, + createSut, +} from "./azureReposInvokerTestSetup.js"; import { deepEqual, verify, when } from "ts-mockito"; import type AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; import type AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; @@ -20,7 +22,6 @@ import type TokenManager from "../../src/repos/tokenManager.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; - describe("azureReposInvoker.ts", (): void => { let gitApi: IGitApi; let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; @@ -56,7 +57,13 @@ describe("azureReposInvoker.ts", (): void => { when( gitApi.updatePullRequest(any(), "RepoID", 10, "Project"), ).thenThrow(error); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const func: () => Promise = async () => @@ -90,7 +97,13 @@ describe("azureReposInvoker.ts", (): void => { it("should not call the API when the title and description are null", async (): Promise => { // Arrange - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.setTitleAndDescription(null, null); @@ -121,7 +134,13 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.setTitleAndDescription("Title", null); @@ -157,7 +176,13 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.setTitleAndDescription(null, "Description"); @@ -194,7 +219,13 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.setTitleAndDescription("Title", "Description"); @@ -231,7 +262,13 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.setTitleAndDescription("Title", "Description"); diff --git a/src/task/tests/repos/azureReposInvoker.updateComment.spec.ts b/src/task/tests/repos/azureReposInvoker.updateComment.spec.ts index a00731112..5a49716e8 100644 --- a/src/task/tests/repos/azureReposInvoker.updateComment.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.updateComment.spec.ts @@ -3,10 +3,16 @@ * Licensed under the MIT License. */ - import * as AssertExtensions from "../testUtilities/assertExtensions.js"; -import { type Comment, CommentThreadStatus, type GitPullRequestCommentThread } from "azure-devops-node-api/interfaces/GitInterfaces.js"; -import { createAzureReposInvokerMocks, createSut } from "./azureReposInvokerTestSetup.js"; +import { + type Comment, + CommentThreadStatus, + type GitPullRequestCommentThread, +} from "azure-devops-node-api/interfaces/GitInterfaces.js"; +import { + createAzureReposInvokerMocks, + createSut, +} from "./azureReposInvokerTestSetup.js"; import { deepEqual, verify, when } from "ts-mockito"; import type AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; import type AzureReposInvoker from "../../src/repos/azureReposInvoker.js"; @@ -20,7 +26,6 @@ import type TokenManager from "../../src/repos/tokenManager.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; - describe("azureReposInvoker.ts", (): void => { let gitApi: IGitApi; let azureDevOpsApiWrapper: AzureDevOpsApiWrapper; @@ -56,7 +61,13 @@ describe("azureReposInvoker.ts", (): void => { when( gitApi.updateComment(any(), "RepoID", 10, 20, 1, "Project"), ).thenThrow(error); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const func: () => Promise = async () => @@ -110,7 +121,13 @@ describe("azureReposInvoker.ts", (): void => { when( gitApi.updateThread(any(), "RepoID", 10, 20, "Project"), ).thenThrow(error); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act const func: () => Promise = async () => @@ -176,7 +193,13 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.updateComment( @@ -229,7 +252,13 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.updateComment(20, "Content", null); @@ -268,7 +297,13 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.updateComment( @@ -298,7 +333,13 @@ describe("azureReposInvoker.ts", (): void => { it("should call no APIs when neither the comment content nor the thread status are updated", async (): Promise => { // Arrange - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.updateComment(20, null, null); @@ -334,7 +375,13 @@ describe("azureReposInvoker.ts", (): void => { "Project", ), ).thenResolve({}); - const azureReposInvoker: AzureReposInvoker = createSut(azureDevOpsApiWrapper, gitInvoker, logger, runnerInvoker, tokenManager); + const azureReposInvoker: AzureReposInvoker = createSut( + azureDevOpsApiWrapper, + gitInvoker, + logger, + runnerInvoker, + tokenManager, + ); // Act await azureReposInvoker.updateComment(20, "Content", null); diff --git a/src/task/tests/repos/azureReposInvokerTestSetup.ts b/src/task/tests/repos/azureReposInvokerTestSetup.ts index 6408d108c..13d346fee 100644 --- a/src/task/tests/repos/azureReposInvokerTestSetup.ts +++ b/src/task/tests/repos/azureReposInvokerTestSetup.ts @@ -36,7 +36,10 @@ export const createAzureReposInvokerMocks = (): AzureReposInvokerMocks => { stubEnv( ["BUILD_REPOSITORY_ID", "RepoID"], ["PR_METRICS_ACCESS_TOKEN", "PAT"], - ["SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", "https://dev.azure.com/organization"], + [ + "SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", + "https://dev.azure.com/organization", + ], ["SYSTEM_TEAMPROJECT", "Project"], ); diff --git a/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts b/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts index c4fc247c5..add0f0c5c 100644 --- a/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts @@ -3,11 +3,14 @@ * Licensed under the MIT License. */ - import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import * as GitHubReposInvokerConstants from "./gitHubReposInvokerConstants.js"; import { any, anyNumber, anyString } from "../testUtilities/mockito.js"; -import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { + createGitHubReposInvokerMocks, + createSut, + expectedUserAgent, +} from "./gitHubReposInvokerTestSetup.js"; import { verify, when } from "ts-mockito"; import type GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; import type GitInvoker from "../../src/git/gitInvoker.js"; @@ -21,7 +24,6 @@ import { StatusCodes } from "http-status-codes"; import assert from "node:assert/strict"; import { createRequestError } from "../testUtilities/createRequestError.js"; - describe("gitHubReposInvoker.ts", (): void => { let gitInvoker: GitInvoker; let logger: Logger; @@ -29,12 +31,8 @@ describe("gitHubReposInvoker.ts", (): void => { let runnerInvoker: RunnerInvoker; beforeEach((): void => { - ({ - gitInvoker, - logger, - octokitWrapper, - runnerInvoker, - } = createGitHubReposInvokerMocks()); + ({ gitInvoker, logger, octokitWrapper, runnerInvoker } = + createGitHubReposInvokerMocks()); }); describe("createComment()", (): void => { @@ -51,7 +49,12 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act await gitHubReposInvoker.createComment("Content", "file.ts"); @@ -99,7 +102,12 @@ describe("gitHubReposInvoker.ts", (): void => { status: StatusCodes.OK, url: "", }); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const func: () => Promise = async () => @@ -142,7 +150,12 @@ describe("gitHubReposInvoker.ts", (): void => { when( octokitWrapper.listCommits(anyString(), anyString(), anyNumber(), 24), ).thenResolve(GitHubReposInvokerConstants.listCommitsResponse); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act await gitHubReposInvoker.createComment("Content", "file.ts"); @@ -187,7 +200,12 @@ describe("gitHubReposInvoker.ts", (): void => { status: StatusCodes.OK, url: "", }); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const func: () => Promise = async () => @@ -217,7 +235,12 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act await gitHubReposInvoker.createComment("Content", "file.ts"); @@ -263,7 +286,12 @@ describe("gitHubReposInvoker.ts", (): void => { "sha54321", ), ).thenResolve(null); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act await gitHubReposInvoker.createComment("Content", "file.ts"); @@ -322,7 +350,12 @@ describe("gitHubReposInvoker.ts", (): void => { ).thenCall((): void => { throw error; }); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act await gitHubReposInvoker.createComment("Content", "file.ts"); @@ -387,7 +420,12 @@ describe("gitHubReposInvoker.ts", (): void => { ).thenCall((): void => { throw error; }); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const func: () => Promise = async () => @@ -426,7 +464,12 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act await gitHubReposInvoker.createComment("Content", null); diff --git a/src/task/tests/repos/gitHubReposInvoker.deleteCommentThread.spec.ts b/src/task/tests/repos/gitHubReposInvoker.deleteCommentThread.spec.ts index afaca9b60..643eb12a6 100644 --- a/src/task/tests/repos/gitHubReposInvoker.deleteCommentThread.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.deleteCommentThread.spec.ts @@ -3,8 +3,11 @@ * Licensed under the MIT License. */ - -import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { + createGitHubReposInvokerMocks, + createSut, + expectedUserAgent, +} from "./gitHubReposInvokerTestSetup.js"; import { verify, when } from "ts-mockito"; import type GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; import type GitInvoker from "../../src/git/gitInvoker.js"; @@ -15,7 +18,6 @@ import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; - describe("gitHubReposInvoker.ts", (): void => { let gitInvoker: GitInvoker; let logger: Logger; @@ -23,12 +25,8 @@ describe("gitHubReposInvoker.ts", (): void => { let runnerInvoker: RunnerInvoker; beforeEach((): void => { - ({ - gitInvoker, - logger, - octokitWrapper, - runnerInvoker, - } = createGitHubReposInvokerMocks()); + ({ gitInvoker, logger, octokitWrapper, runnerInvoker } = + createGitHubReposInvokerMocks()); }); describe("deleteCommentThread()", (): void => { @@ -45,7 +43,12 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act await gitHubReposInvoker.deleteCommentThread(54321); diff --git a/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts b/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts index a38e90a13..aef1de1b8 100644 --- a/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts @@ -3,10 +3,13 @@ * Licensed under the MIT License. */ - import * as GitHubReposInvokerConstants from "./gitHubReposInvokerConstants.js"; import { any, anyNumber, anyString } from "../testUtilities/mockito.js"; -import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { + createGitHubReposInvokerMocks, + createSut, + expectedUserAgent, +} from "./gitHubReposInvokerTestSetup.js"; import { verify, when } from "ts-mockito"; import type CommentData from "../../src/repos/interfaces/commentData.js"; import { CommentThreadStatus } from "azure-devops-node-api/interfaces/GitInterfaces.js"; @@ -19,7 +22,6 @@ import type OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; - describe("gitHubReposInvoker.ts", (): void => { let gitInvoker: GitInvoker; let logger: Logger; @@ -27,12 +29,8 @@ describe("gitHubReposInvoker.ts", (): void => { let runnerInvoker: RunnerInvoker; beforeEach((): void => { - ({ - gitInvoker, - logger, - octokitWrapper, - runnerInvoker, - } = createGitHubReposInvokerMocks()); + ({ gitInvoker, logger, octokitWrapper, runnerInvoker } = + createGitHubReposInvokerMocks()); }); describe("getComments()", (): void => { @@ -59,7 +57,12 @@ describe("gitHubReposInvoker.ts", (): void => { when( octokitWrapper.getIssueComments(anyString(), anyString(), anyNumber()), ).thenResolve(response); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const result: CommentData = await gitHubReposInvoker.getComments(); @@ -98,7 +101,12 @@ describe("gitHubReposInvoker.ts", (): void => { when( octokitWrapper.getReviewComments(anyString(), anyString(), anyNumber()), ).thenResolve(GitHubReposInvokerConstants.getReviewCommentsResponse); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const result: CommentData = await gitHubReposInvoker.getComments(); @@ -145,7 +153,12 @@ describe("gitHubReposInvoker.ts", (): void => { when( octokitWrapper.getReviewComments(anyString(), anyString(), anyNumber()), ).thenResolve(GitHubReposInvokerConstants.getReviewCommentsResponse); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const result: CommentData = await gitHubReposInvoker.getComments(); @@ -195,7 +208,12 @@ describe("gitHubReposInvoker.ts", (): void => { when( octokitWrapper.getIssueComments(anyString(), anyString(), anyNumber()), ).thenResolve(response); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const result: CommentData = await gitHubReposInvoker.getComments(); diff --git a/src/task/tests/repos/gitHubReposInvoker.getTitleAndDescription.spec.ts b/src/task/tests/repos/gitHubReposInvoker.getTitleAndDescription.spec.ts index 3bfc367e2..e80e720c6 100644 --- a/src/task/tests/repos/gitHubReposInvoker.getTitleAndDescription.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.getTitleAndDescription.spec.ts @@ -3,11 +3,14 @@ * Licensed under the MIT License. */ - import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import * as GitHubReposInvokerConstants from "./gitHubReposInvokerConstants.js"; import { any, anyNumber, anyString } from "../testUtilities/mockito.js"; -import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { + createGitHubReposInvokerMocks, + createSut, + expectedUserAgent, +} from "./gitHubReposInvokerTestSetup.js"; import { verify, when } from "ts-mockito"; import type ErrorWithStatusInterface from "../../src/repos/interfaces/errorWithStatusInterface.js"; import type GetPullResponse from "../../src/wrappers/octokitInterfaces/getPullResponse.js"; @@ -25,7 +28,6 @@ import assert from "node:assert/strict"; import { createRequestError } from "../testUtilities/createRequestError.js"; import { stubEnv } from "../testUtilities/stubEnv.js"; - describe("gitHubReposInvoker.ts", (): void => { let gitInvoker: GitInvoker; let logger: Logger; @@ -33,12 +35,8 @@ describe("gitHubReposInvoker.ts", (): void => { let runnerInvoker: RunnerInvoker; beforeEach((): void => { - ({ - gitInvoker, - logger, - octokitWrapper, - runnerInvoker, - } = createGitHubReposInvokerMocks()); + ({ gitInvoker, logger, octokitWrapper, runnerInvoker } = + createGitHubReposInvokerMocks()); }); describe("getTitleAndDescription()", (): void => { @@ -54,7 +52,12 @@ describe("gitHubReposInvoker.ts", (): void => { stubEnv(["SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", variable]); } - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const func: () => Promise = async () => @@ -71,10 +74,16 @@ describe("gitHubReposInvoker.ts", (): void => { it("should throw when SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI is set to an invalid URL and the task is running on Azure Pipelines", async (): Promise => { // Arrange - stubEnv( - ["SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", "https://github.com/microsoft"], + stubEnv([ + "SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", + "https://github.com/microsoft", + ]); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, ); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); // Act const func: () => Promise = async () => @@ -102,7 +111,12 @@ describe("gitHubReposInvoker.ts", (): void => { stubEnv(["GITHUB_API_URL", variable]); } - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const func: () => Promise = async () => @@ -113,7 +127,6 @@ describe("gitHubReposInvoker.ts", (): void => { func, `'GITHUB_API_URL', accessed within 'GitHubReposInvoker.initializeForGitHub()', is invalid, null, or undefined '${String(variable)}'.`, ); - }); }); } @@ -134,7 +147,12 @@ describe("gitHubReposInvoker.ts", (): void => { stubEnv(["GITHUB_REPOSITORY_OWNER", variable]); } - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const func: () => Promise = async () => @@ -145,7 +163,6 @@ describe("gitHubReposInvoker.ts", (): void => { func, `'GITHUB_REPOSITORY_OWNER', accessed within 'GitHubReposInvoker.initializeForGitHub()', is invalid, null, or undefined '${String(variable)}'.`, ); - }); }); } @@ -167,7 +184,12 @@ describe("gitHubReposInvoker.ts", (): void => { stubEnv(["GITHUB_REPOSITORY", variable]); } - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const func: () => Promise = async () => @@ -178,7 +200,6 @@ describe("gitHubReposInvoker.ts", (): void => { func, `'GITHUB_REPOSITORY', accessed within 'GitHubReposInvoker.initializeForGitHub()', is invalid, null, or undefined '${String(variable)}'.`, ); - }); }); } @@ -191,7 +212,12 @@ describe("gitHubReposInvoker.ts", (): void => { stubEnv(["GITHUB_API_URL", "https://api.github.com"]); stubEnv(["GITHUB_REPOSITORY_OWNER", "microsoft"]); stubEnv(["GITHUB_REPOSITORY", "microsoft"]); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const func: () => Promise = async () => @@ -202,7 +228,6 @@ describe("gitHubReposInvoker.ts", (): void => { func, "GITHUB_REPOSITORY 'microsoft' is in an unexpected format.", ); - }); it("should succeed when the inputs are valid and the task is running on Azure Pipelines", async (): Promise => { @@ -218,7 +243,12 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const result: PullRequestDetailsInterface = @@ -248,7 +278,12 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const result: PullRequestDetailsInterface = @@ -259,7 +294,6 @@ describe("gitHubReposInvoker.ts", (): void => { assert.equal(result.description, "Description"); verify(octokitWrapper.initialize(any())).once(); verify(octokitWrapper.getPull("microsoft", "PR-Metrics", 12345)).once(); - }); it("should succeed when the inputs are valid and the URL ends with '.git'", async (): Promise => { @@ -279,7 +313,12 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const result: PullRequestDetailsInterface = @@ -313,7 +352,12 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const result: PullRequestDetailsInterface = @@ -339,7 +383,12 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act await gitHubReposInvoker.getTitleAndDescription(); @@ -372,7 +421,12 @@ describe("gitHubReposInvoker.ts", (): void => { when( octokitWrapper.getPull(anyString(), anyString(), anyNumber()), ).thenResolve(currentMockPullResponse); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const result: PullRequestDetailsInterface = @@ -410,7 +464,12 @@ describe("gitHubReposInvoker.ts", (): void => { when( octokitWrapper.getPull(anyString(), anyString(), anyNumber()), ).thenThrow(error); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const func: () => Promise = async () => @@ -445,7 +504,12 @@ describe("gitHubReposInvoker.ts", (): void => { when( octokitWrapper.getPull(anyString(), anyString(), anyNumber()), ).thenThrow(Error("Error")); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const func: () => Promise = async () => @@ -464,7 +528,12 @@ describe("gitHubReposInvoker.ts", (): void => { logObject = options.log; }, ); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); await gitHubReposInvoker.getTitleAndDescription(); // Act diff --git a/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts b/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts index 9e1940ff3..2cbceea32 100644 --- a/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. */ - -import { createGitHubReposInvokerMocks, createSut } from "./gitHubReposInvokerTestSetup.js"; +import { + createGitHubReposInvokerMocks, + createSut, +} from "./gitHubReposInvokerTestSetup.js"; import type GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; import type GitInvoker from "../../src/git/gitInvoker.js"; import type Logger from "../../src/utilities/logger.js"; @@ -13,8 +15,6 @@ import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { stubEnv } from "../testUtilities/stubEnv.js"; - - describe("gitHubReposInvoker.ts", (): void => { let gitInvoker: GitInvoker; let logger: Logger; @@ -22,18 +22,19 @@ describe("gitHubReposInvoker.ts", (): void => { let runnerInvoker: RunnerInvoker; beforeEach((): void => { - ({ - gitInvoker, - logger, - octokitWrapper, - runnerInvoker, - } = createGitHubReposInvokerMocks()); + ({ gitInvoker, logger, octokitWrapper, runnerInvoker } = + createGitHubReposInvokerMocks()); }); describe("isAccessTokenAvailable()", (): void => { it("should return null when the token exists on Azure DevOps", async (): Promise => { // Arrange - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const result: string | null = @@ -47,7 +48,12 @@ describe("gitHubReposInvoker.ts", (): void => { // Arrange stubEnv(["PR_METRICS_ACCESS_TOKEN", "PAT"]); stubEnv(["GITHUB_ACTION", "PR-Metrics"]); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const result: string | null = @@ -55,13 +61,17 @@ describe("gitHubReposInvoker.ts", (): void => { // Assert assert.equal(result, null); - }); it("should return a string when the token does not exist", async (): Promise => { // Arrange stubEnv(["PR_METRICS_ACCESS_TOKEN", undefined]); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act const result: string | null = diff --git a/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts b/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts index 3cbc01267..e3d1f1b01 100644 --- a/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts @@ -3,8 +3,11 @@ * Licensed under the MIT License. */ - -import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { + createGitHubReposInvokerMocks, + createSut, + expectedUserAgent, +} from "./gitHubReposInvokerTestSetup.js"; import { verify, when } from "ts-mockito"; import type GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; import type GitInvoker from "../../src/git/gitInvoker.js"; @@ -15,7 +18,6 @@ import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; - describe("gitHubReposInvoker.ts", (): void => { let gitInvoker: GitInvoker; let logger: Logger; @@ -23,18 +25,19 @@ describe("gitHubReposInvoker.ts", (): void => { let runnerInvoker: RunnerInvoker; beforeEach((): void => { - ({ - gitInvoker, - logger, - octokitWrapper, - runnerInvoker, - } = createGitHubReposInvokerMocks()); + ({ gitInvoker, logger, octokitWrapper, runnerInvoker } = + createGitHubReposInvokerMocks()); }); describe("setTitleAndDescription()", (): void => { it("should succeed when the title and description are both null", async (): Promise => { // Arrange - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act await gitHubReposInvoker.setTitleAndDescription(null, null); @@ -55,7 +58,12 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act await gitHubReposInvoker.setTitleAndDescription("Title", "Description"); @@ -86,7 +94,12 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act await gitHubReposInvoker.setTitleAndDescription("Title", null); @@ -117,7 +130,12 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act await gitHubReposInvoker.setTitleAndDescription(null, "Description"); diff --git a/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts b/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts index bc35df729..332e3251d 100644 --- a/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts @@ -3,8 +3,11 @@ * Licensed under the MIT License. */ - -import { createGitHubReposInvokerMocks, createSut, expectedUserAgent } from "./gitHubReposInvokerTestSetup.js"; +import { + createGitHubReposInvokerMocks, + createSut, + expectedUserAgent, +} from "./gitHubReposInvokerTestSetup.js"; import { verify, when } from "ts-mockito"; import type GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; import type GitInvoker from "../../src/git/gitInvoker.js"; @@ -15,7 +18,6 @@ import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { any } from "../testUtilities/mockito.js"; import assert from "node:assert/strict"; - describe("gitHubReposInvoker.ts", (): void => { let gitInvoker: GitInvoker; let logger: Logger; @@ -23,18 +25,19 @@ describe("gitHubReposInvoker.ts", (): void => { let runnerInvoker: RunnerInvoker; beforeEach((): void => { - ({ - gitInvoker, - logger, - octokitWrapper, - runnerInvoker, - } = createGitHubReposInvokerMocks()); + ({ gitInvoker, logger, octokitWrapper, runnerInvoker } = + createGitHubReposInvokerMocks()); }); describe("updateComment()", (): void => { it("should succeed when the content is null", async (): Promise => { // Arrange - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act await gitHubReposInvoker.updateComment(54321, null); @@ -55,7 +58,12 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const gitHubReposInvoker: GitHubReposInvoker = createSut(gitInvoker, logger, octokitWrapper, runnerInvoker); + const gitHubReposInvoker: GitHubReposInvoker = createSut( + gitInvoker, + logger, + octokitWrapper, + runnerInvoker, + ); // Act await gitHubReposInvoker.updateComment(54321, "Content"); diff --git a/src/task/tests/repos/reposInvoker.spec.ts b/src/task/tests/repos/reposInvoker.spec.ts index 98fe3988e..fc4378e62 100644 --- a/src/task/tests/repos/reposInvoker.spec.ts +++ b/src/task/tests/repos/reposInvoker.spec.ts @@ -43,7 +43,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.isAccessTokenAvailable()).once(); verify(gitHubReposInvoker.isAccessTokenAvailable()).never(); assert.equal(result, null); - }); it("should invoke Azure Repos when called from an appropriate repo twice", async (): Promise => { @@ -66,7 +65,6 @@ describe("reposInvoker.ts", (): void => { verify(gitHubReposInvoker.isAccessTokenAvailable()).never(); assert.equal(result1, null); assert.equal(result2, null); - }); it("should invoke GitHub when called from a GitHub runner", async (): Promise => { @@ -85,7 +83,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.isAccessTokenAvailable()).never(); verify(gitHubReposInvoker.isAccessTokenAvailable()).once(); assert.equal(result, null); - }); { @@ -109,7 +106,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.isAccessTokenAvailable()).never(); verify(gitHubReposInvoker.isAccessTokenAvailable()).once(); assert.equal(result, null); - }); }); } @@ -156,7 +152,6 @@ describe("reposInvoker.ts", (): void => { ); verify(azureReposInvoker.isAccessTokenAvailable()).never(); verify(gitHubReposInvoker.isAccessTokenAvailable()).never(); - }); }); @@ -178,7 +173,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.getTitleAndDescription()).once(); verify(gitHubReposInvoker.getTitleAndDescription()).never(); assert.equal(result, null); - }); it("should invoke GitHub when called from a GitHub runner", async (): Promise => { @@ -198,7 +192,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.getTitleAndDescription()).never(); verify(gitHubReposInvoker.getTitleAndDescription()).once(); assert.equal(result, null); - }); { @@ -222,7 +215,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.getTitleAndDescription()).never(); verify(gitHubReposInvoker.getTitleAndDescription()).once(); assert.equal(result, null); - }); }); } @@ -269,7 +261,6 @@ describe("reposInvoker.ts", (): void => { ); verify(azureReposInvoker.getTitleAndDescription()).never(); verify(gitHubReposInvoker.getTitleAndDescription()).never(); - }); }); @@ -290,7 +281,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.getComments()).once(); verify(gitHubReposInvoker.getComments()).never(); assert.equal(result, null); - }); it("should invoke GitHub when called from a GitHub runner", async (): Promise => { @@ -309,7 +299,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.getComments()).never(); verify(gitHubReposInvoker.getComments()).once(); assert.equal(result, null); - }); { @@ -332,7 +321,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.getComments()).never(); verify(gitHubReposInvoker.getComments()).once(); assert.equal(result, null); - }); }); } @@ -379,7 +367,6 @@ describe("reposInvoker.ts", (): void => { ); verify(azureReposInvoker.getComments()).never(); verify(gitHubReposInvoker.getComments()).never(); - }); }); @@ -399,7 +386,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.setTitleAndDescription(null, null)).once(); verify(gitHubReposInvoker.setTitleAndDescription(null, null)).never(); - }); it("should invoke GitHub when called from a GitHub runner", async (): Promise => { @@ -417,7 +403,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.setTitleAndDescription(null, null)).never(); verify(gitHubReposInvoker.setTitleAndDescription(null, null)).once(); - }); { @@ -439,7 +424,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.setTitleAndDescription(null, null)).never(); verify(gitHubReposInvoker.setTitleAndDescription(null, null)).once(); - }); }); } @@ -486,7 +470,6 @@ describe("reposInvoker.ts", (): void => { ); verify(azureReposInvoker.setTitleAndDescription(null, null)).never(); verify(gitHubReposInvoker.setTitleAndDescription(null, null)).never(); - }); }); @@ -526,7 +509,6 @@ describe("reposInvoker.ts", (): void => { false, ), ).never(); - }); it("should invoke GitHub when called from a GitHub runner", async (): Promise => { @@ -564,7 +546,6 @@ describe("reposInvoker.ts", (): void => { false, ), ).once(); - }); { @@ -606,7 +587,6 @@ describe("reposInvoker.ts", (): void => { false, ), ).once(); - }); }); } @@ -683,7 +663,6 @@ describe("reposInvoker.ts", (): void => { false, ), ).never(); - }); }); @@ -704,7 +683,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.updateComment(0, null, null)).once(); // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubReposInvoker.updateComment(0, null, null)).never(); - }); it("should invoke GitHub when called from a GitHub runner", async (): Promise => { @@ -723,7 +701,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.updateComment(0, null, null)).never(); // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubReposInvoker.updateComment(0, null, null)).once(); - }); { @@ -746,7 +723,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.updateComment(0, null, null)).never(); // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubReposInvoker.updateComment(0, null, null)).once(); - }); }); } @@ -795,7 +771,6 @@ describe("reposInvoker.ts", (): void => { verify(azureReposInvoker.updateComment(0, null, null)).never(); // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubReposInvoker.updateComment(0, null, null)).never(); - }); }); @@ -815,7 +790,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.deleteCommentThread(20)).once(); verify(gitHubReposInvoker.deleteCommentThread(20)).never(); - }); it("should invoke GitHub when called from a GitHub runner", async (): Promise => { @@ -833,7 +807,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.deleteCommentThread(20)).never(); verify(gitHubReposInvoker.deleteCommentThread(20)).once(); - }); { @@ -855,7 +828,6 @@ describe("reposInvoker.ts", (): void => { // Assert verify(azureReposInvoker.deleteCommentThread(20)).never(); verify(gitHubReposInvoker.deleteCommentThread(20)).once(); - }); }); } @@ -902,7 +874,6 @@ describe("reposInvoker.ts", (): void => { ); verify(azureReposInvoker.deleteCommentThread(20)).never(); verify(gitHubReposInvoker.deleteCommentThread(20)).never(); - }); }); }); diff --git a/src/task/tests/runners/runnerInvoker.spec.ts b/src/task/tests/runners/runnerInvoker.spec.ts index f836e96d0..505b3424b 100644 --- a/src/task/tests/runners/runnerInvoker.spec.ts +++ b/src/task/tests/runners/runnerInvoker.spec.ts @@ -39,7 +39,6 @@ describe("runnerInvoker.ts", (): void => { // Assert assert.equal(result, true); - }); }); @@ -118,7 +117,6 @@ describe("runnerInvoker.ts", (): void => { verify( gitHubRunnerInvoker.exec("TOOL", deepEqual(["Argument1", "Argument2"])), ).once(); - }); it("should call the underlying method each time when running on GitHub", async (): Promise => { @@ -163,7 +161,6 @@ describe("runnerInvoker.ts", (): void => { verify( gitHubRunnerInvoker.exec("TOOL", deepEqual(["Argument1", "Argument2"])), ).twice(); - }); }); @@ -213,7 +210,6 @@ describe("runnerInvoker.ts", (): void => { verify( gitHubRunnerInvoker.getInput(deepEqual(["Test", "Suffix"])), ).once(); - }); }); @@ -274,7 +270,6 @@ describe("runnerInvoker.ts", (): void => { ).never(); // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubRunnerInvoker.getEndpointAuthorization("id")).once(); - }); }); @@ -325,7 +320,6 @@ describe("runnerInvoker.ts", (): void => { ).never(); // @ts-expect-error -- Interface is called with additional parameters not present in implementation. verify(gitHubRunnerInvoker.getEndpointAuthorizationScheme("id")).once(); - }); }); @@ -389,7 +383,6 @@ describe("runnerInvoker.ts", (): void => { // @ts-expect-error -- Interface is called with additional parameters not present in implementation. gitHubRunnerInvoker.getEndpointAuthorizationParameter("id", "key"), ).once(); - }); }); @@ -423,7 +416,6 @@ describe("runnerInvoker.ts", (): void => { // Assert verify(azurePipelinesRunnerInvoker.locInitialize("TEST")).never(); verify(gitHubRunnerInvoker.locInitialize("TEST")).once(); - }); it("should throw when locInitialize is called twice", (): void => { @@ -502,7 +494,6 @@ describe("runnerInvoker.ts", (): void => { assert.equal(result, "VALUE"); verify(azurePipelinesRunnerInvoker.loc("TEST")).never(); verify(gitHubRunnerInvoker.loc("TEST")).once(); - }); }); @@ -536,7 +527,6 @@ describe("runnerInvoker.ts", (): void => { // Assert verify(azurePipelinesRunnerInvoker.logDebug("TEST")).never(); verify(gitHubRunnerInvoker.logDebug("TEST")).once(); - }); }); @@ -570,7 +560,6 @@ describe("runnerInvoker.ts", (): void => { // Assert verify(azurePipelinesRunnerInvoker.logError("TEST")).never(); verify(gitHubRunnerInvoker.logError("TEST")).once(); - }); }); @@ -604,7 +593,6 @@ describe("runnerInvoker.ts", (): void => { // Assert verify(azurePipelinesRunnerInvoker.logWarning("TEST")).never(); verify(gitHubRunnerInvoker.logWarning("TEST")).once(); - }); }); @@ -638,7 +626,6 @@ describe("runnerInvoker.ts", (): void => { // Assert verify(azurePipelinesRunnerInvoker.setStatusFailed("TEST")).never(); verify(gitHubRunnerInvoker.setStatusFailed("TEST")).once(); - }); }); @@ -672,7 +659,6 @@ describe("runnerInvoker.ts", (): void => { // Assert verify(azurePipelinesRunnerInvoker.setStatusSkipped("TEST")).never(); verify(gitHubRunnerInvoker.setStatusSkipped("TEST")).once(); - }); }); @@ -706,7 +692,6 @@ describe("runnerInvoker.ts", (): void => { // Assert verify(azurePipelinesRunnerInvoker.setStatusSucceeded("TEST")).never(); verify(gitHubRunnerInvoker.setStatusSucceeded("TEST")).once(); - }); }); @@ -740,7 +725,6 @@ describe("runnerInvoker.ts", (): void => { // Assert verify(azurePipelinesRunnerInvoker.setSecret("id")).never(); verify(gitHubRunnerInvoker.setSecret("id")).once(); - }); }); }); From d074fb47a072e53149d5ed56519e9bee07f217e5 Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Mon, 20 Apr 2026 12:03:42 +0100 Subject: [PATCH 13/27] Clear managed env vars before the test suite starts The stubEnv helper captures an env variable's pre-test value and restores it in afterEach. On a developer workstation the pre-test value is typically unset, so the restore-to-original semantics match the old pattern of an explicit delete after each test. On GitHub Actions, however, the runner pre-populates GITHUB_ACTION, GITHUB_REF, GITHUB_BASE_REF, and related variables. When a test stubs one of these for a GitHub-runner scenario and afterEach runs, the helper restores the variable to the Actions-set value rather than clearing it, leaving later tests that rely on the variable being unset to misbehave. Adds a single before() hook that unsets every test-managed environment variable once at suite startup. With the host-inherited values cleared, the restore-to-original logic now restores to "unset" for subsequent tests, matching the behavior the old delete-after-test code provided. --- src/task/tests/testUtilities/stubEnv.ts | 35 +++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/task/tests/testUtilities/stubEnv.ts b/src/task/tests/testUtilities/stubEnv.ts index 678f751b4..79fdc6a06 100644 --- a/src/task/tests/testUtilities/stubEnv.ts +++ b/src/task/tests/testUtilities/stubEnv.ts @@ -10,10 +10,45 @@ interface PendingChange { const pending: PendingChange[] = []; +/** + * Environment variables that tests expect to be unset unless explicitly + * stubbed. Clearing them at suite start prevents values inherited from the + * host environment (notably GitHub Actions' auto-populated `GITHUB_*` vars) + * from being restored mid-suite and leaking into unrelated tests. + */ +const managedEnvVars: readonly string[] = [ + "BUILD_REPOSITORY_ID", + "BUILD_REPOSITORY_PROVIDER", + "GITHUB_ACTION", + "GITHUB_API_URL", + "GITHUB_BASE_REF", + "GITHUB_REF", + "GITHUB_REPOSITORY", + "GITHUB_REPOSITORY_OWNER", + "PR_METRICS_ACCESS_TOKEN", + "SYSTEM_COLLECTIONURI", + "SYSTEM_HOSTTYPE", + "SYSTEM_JOBID", + "SYSTEM_PLANID", + "SYSTEM_PULLREQUEST_PULLREQUESTID", + "SYSTEM_PULLREQUEST_PULLREQUESTNUMBER", + "SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI", + "SYSTEM_PULLREQUEST_TARGETBRANCH", + "SYSTEM_TEAMFOUNDATIONCOLLECTIONURI", + "SYSTEM_TEAMPROJECT", + "SYSTEM_TEAMPROJECTID", +]; + const unset = (key: string): void => { Reflect.deleteProperty(process.env, key); }; +before((): void => { + for (const key of managedEnvVars) { + unset(key); + } +}); + /** * Sets one or more environment variables for the duration of the current test. * Any previous value is captured and restored automatically by a global From d27494f715af82d7d7e374dc4d094f3d7968015c Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Mon, 20 Apr 2026 12:37:20 +0100 Subject: [PATCH 14/27] Address PR review feedback on Inputs specs - inputs.fileMatchingPatterns: replace the hard-coded 200/250 literals in the truncation test with references to the maxPatternCount constant and its toLocaleString() form, so the test stays in sync with production if the limit is ever changed. - inputs.codeFileExtensions: remove a second copy of the lowercase conversion test that was identical to the earlier one. --- .../metrics/inputs.codeFileExtensions.spec.ts | 17 ----------------- .../metrics/inputs.fileMatchingPatterns.spec.ts | 14 ++++++++++---- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts b/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts index 389e78d31..da69a12af 100644 --- a/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts +++ b/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts @@ -123,23 +123,6 @@ describe("inputs.ts", (): void => { verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); }); - it("should convert extensions to lower case", (): void => { - // Arrange - when( - runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), - ).thenReturn("ADA\ncS\nTxT"); - - // Act - const inputs: Inputs = createSut(logger, runnerInvoker); - - // Assert - assert.deepEqual( - inputs.codeFileExtensions, - new Set(["ada", "cs", "txt"]), - ); - verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); - }); - it("should remove trailing new lines", (): void => { // Arrange when( diff --git a/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts b/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts index dd58ee63c..0f631113c 100644 --- a/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts +++ b/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts @@ -16,6 +16,7 @@ import type Logger from "../../src/utilities/logger.js"; import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; import { invalidPatternStrings } from "../testUtilities/fixtures/invalidInputs.js"; +import { maxPatternCount } from "../../src/utilities/constants.js"; describe("inputs.ts", (): void => { let logger: Logger; @@ -162,10 +163,12 @@ describe("inputs.ts", (): void => { it("should truncate patterns exceeding the maximum count", (): void => { // Arrange + const excessCount: number = maxPatternCount + 50; const patterns: string[] = Array.from( - { length: 250 }, + { length: excessCount }, (_value: string, index: number) => `pattern${String(index)}`, ); + const maxPatternCountString: string = maxPatternCount.toLocaleString(); when( runnerInvoker.getInput(deepEqual(["File", "Matching", "Patterns"])), ).thenReturn(patterns.join("\n")); @@ -174,12 +177,15 @@ describe("inputs.ts", (): void => { const inputs: Inputs = createSut(logger, runnerInvoker); // Assert - assert.equal(inputs.fileMatchingPatterns.length, 200); + assert.equal(inputs.fileMatchingPatterns.length, maxPatternCount); assert.equal(inputs.fileMatchingPatterns[0], "pattern0"); - assert.equal(inputs.fileMatchingPatterns[199], "pattern199"); + assert.equal( + inputs.fileMatchingPatterns[maxPatternCount - 1], + `pattern${String(maxPatternCount - 1)}`, + ); verify( logger.logWarning( - "The matching pattern count '250' exceeds the maximum '200'. Using only the first '200'.", + `The matching pattern count '${excessCount.toLocaleString()}' exceeds the maximum '${maxPatternCountString}'. Using only the first '${maxPatternCountString}'.`, ), ).once(); }); From 1a24f5ec70fc64e42e13e32e2d440e6358a6594f Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Mon, 20 Apr 2026 12:57:48 +0100 Subject: [PATCH 15/27] Stub the correct env var in isPullRequest negative test The test claims to cover 'SYSTEM_PULLREQUEST_PULLREQUESTID is not defined' but was unsetting SYSTEM_PULLREQUEST_TARGETBRANCH, which the production isPullRequest getter never reads on the Azure Pipelines branch. Stub the variable the code actually checks so the assertion exercises the intended condition. --- src/task/tests/pullRequests/pullRequest.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/task/tests/pullRequests/pullRequest.spec.ts b/src/task/tests/pullRequests/pullRequest.spec.ts index aabf70f39..75391e07e 100644 --- a/src/task/tests/pullRequests/pullRequest.spec.ts +++ b/src/task/tests/pullRequests/pullRequest.spec.ts @@ -80,7 +80,7 @@ describe("pullRequest.ts", (): void => { it("should return false when the Azure Pipelines runner is being used and SYSTEM_PULLREQUEST_PULLREQUESTID is not defined", (): void => { // Arrange - stubEnv(["SYSTEM_PULLREQUEST_TARGETBRANCH", undefined]); + stubEnv(["SYSTEM_PULLREQUEST_PULLREQUESTID", undefined]); const pullRequest: PullRequest = new PullRequest( instance(codeMetrics), instance(logger), From 01a6ae3a1e7bc612333d72eeb5587d97b2c75ec3 Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Mon, 20 Apr 2026 15:34:49 +0100 Subject: [PATCH 16/27] chore: remove update-ci-dependencies skill This skill does not belong on this branch. --- .../skills/update-ci-dependencies/SKILL.md | 149 ------------------ 1 file changed, 149 deletions(-) delete mode 100644 .github/skills/update-ci-dependencies/SKILL.md diff --git a/.github/skills/update-ci-dependencies/SKILL.md b/.github/skills/update-ci-dependencies/SKILL.md deleted file mode 100644 index 48b3f6f57..000000000 --- a/.github/skills/update-ci-dependencies/SKILL.md +++ /dev/null @@ -1,149 +0,0 @@ ---- -name: update-ci-dependencies -description: >- - Refreshes pinned CI/CD dependencies in this repository – SHA-pinned actions in - .github/workflows/, plus task versions and 1ES template refs in - .github/azure-devops/. Also verifies that the pinned Node.js runtime stays - consistent across files without bumping its version. Use when asked to update - CI dependencies, refresh pinned GitHub Actions, bump Azure DevOps task - versions, update 1ES template tags, audit workflow dependencies, or sync - Node.js version pins across workflows, pipelines, and package.json. ---- - -# Update CI Dependencies - -Refreshes pinned versions in GitHub Actions workflows and Azure DevOps -pipelines and enforces Node.js runtime consistency. - -## When to Use - -- Performing a scheduled dependency-refresh sweep before a release. -- Reacting to a CVE that affects a CI dependency. -- Dependabot has stalled or cannot resolve an update. -- A new major version of an Azure DevOps task or 1ES template needs evaluation. -- Verifying the pinned Node.js runtime is consistent across workflows, - pipelines, and `package.json`. - -## Inventory - -Catalog every pinned version before editing. Use `grep` to locate each -pattern. - -### GitHub Workflows (`.github/workflows/*.yml`) - -- **SHA-Pinned Actions**: `uses: owner/repo@<40-char SHA> # vX.Y.Z`. The SHA - and trailing version comment must stay in sync. -- **Other Pinned Tools**: literal `version:` or `ref:` values in step inputs. - -### Azure DevOps Pipelines (`.github/azure-devops/*.yml`) - -- **Task Versions**: `task: TaskName@N` (for example, `Npm@1`, `UseNode@1`, - `EsrpCodeSigning@6`). Only the major version is declared; the latest minor - or patch resolves at runtime. -- **Template Refs**: `ref: refs/tags/` inside `resources.repositories`. - 1ES templates use the moving `release` tag; pin a specific tag only when - compliance requires it. - -## Process - -1. Ensure the working tree is clean: `git status` should show no uncommitted - changes on the target branch. -1. For each SHA-pinned GitHub Action, resolve the latest release and tag SHA: - - ```bash - gh api repos///releases/latest --jq '.tag_name' - gh api repos///git/refs/tags/ --jq '.object.sha' - ``` - - Update the SHA and the trailing `# vX.Y.Z` comment in one edit – never one - without the other. If the tag points to an annotated tag object, dereference - it with a second `gh api` call against the tag object. - -1. For each Azure DevOps task, check the task's latest major version in the - [Azure Pipelines task reference][ado-tasks]. Bump the major version only - after reviewing its release notes and adjusting inputs accordingly. This - includes `UseNode@1` itself (the task version), but never the `version:` - input it receives. -1. For template refs, list available tags and compare with the current pin: - - ```bash - git ls-remote --tags - ``` - - The 1ES templates are hosted in Azure DevOps; authenticate via `az` if the - listing is restricted. - -1. Save changes while preserving formatting: trailing newlines, comment spacing, - and two-space indentation. - -## Node.js Version Consistency - -The pinned Node.js runtime must match everywhere it appears. This skill does -not bump Node.js to a newer version – that is a deliberate, standalone change -– but it flags and resolves inconsistencies. - -Locate every occurrence with `grep`: - -- `.github/workflows/*.yml` – `node-version: X.Y.Z` under `actions/setup-node`. -- `.github/azure-devops/*.yml` – `UseNode@1` with `version: X.Y.Z`. -- `package.json` – `engines.node`. -- `.nvmrc` if present. - -If values diverge, align them on the single intended pin. Prefer the value -already set by the most recent deliberate bump (check `git log` on whichever -file changed most recently). Do not change the value itself. - -## Output Format - -A single commit (or pull request) whose diff touches only CI definition files -under `.github/workflows/` and `.github/azure-devops/`, with: - -- Each SHA-pinned action updated atomically (SHA plus `# vX.Y.Z` comment). -- Each Azure DevOps task major version either left unchanged or bumped - together with any required input adjustments. -- Each 1ES template ref left on its moving tag unless a compliance reason - dictates otherwise. -- Node.js pins aligned across files if they had diverged. - -The commit message should start with `chore:` and reference the dependency -class updated (for example, `chore: refresh GitHub Action pins`). - -## Constraints - -- **Never desynchronize SHA and version comment**: always update both, in the - same edit. -- **Never blindly bump an Azure DevOps task major version**: review release - notes and adjust inputs first. -- **Never bump the Node.js runtime value** from this skill – that is a - separate, deliberate change. -- **Never duplicate a Dependabot PR**: if Dependabot already has an open PR - for an action, rebase or merge it rather than producing a competing change. -- **Never hard-pin a 1ES template ref without justification**: the `release` - tag is intentionally moving. -- **Never modify files outside `.github/workflows/`, - `.github/azure-devops/`, `package.json`, or `.nvmrc`** during this task. - -## Quick Reference - -| Dependency Type | Files | Update Source | -| ----------------- | ---------------------------- | ---------------------- | -| GitHub Action | `.github/workflows/*.yml` | `gh api` latest tag | -| Azure DevOps Task | `.github/azure-devops/*.yml` | ADO task reference | -| 1ES Template Ref | `.github/azure-devops/*.yml` | `git ls-remote --tags` | - -## Verification - -- Lint YAML with the `Build` workflow's `validate-linter` job (Super-Linter) or - locally: `npx yaml-lint .github/workflows/*.yml .github/azure-devops/*.yml`. -- Confirm each GitHub Action SHA still matches its version comment: - - ```bash - gh api "repos///git/refs/tags/v" --jq '.object.sha' - ``` - -- Push to a branch and confirm the `Build` workflow passes on GitHub. For Azure - DevOps, run `pr-test.yml` against the branch. -- Re-run `npm run build:package` if any action-side behavior shifted, so `dist/` - stays consistent. - -[ado-tasks]: https://learn.microsoft.com/azure/devops/pipelines/tasks/reference/ From e2d7b8fd89ea7a7ac850ebb9f781ec878d459483 Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Mon, 20 Apr 2026 16:15:55 +0100 Subject: [PATCH 17/27] Address Copilot review feedback - Stub PR_METRICS_ACCESS_TOKEN in tokenManager spec so the production-code assignment cannot leak into subsequent tests. - Assert octokitWrapper.initialize/updateIssueComment are never called when updateComment is invoked with null content. - Assert octokitWrapper.initialize/updatePull are never called when setTitleAndDescription is invoked with null title and description. --- .../gitHubReposInvoker.setTitleAndDescription.spec.ts | 4 ++++ .../repos/gitHubReposInvoker.updateComment.spec.ts | 10 ++++++++++ src/task/tests/repos/tokenManager.spec.ts | 1 + 3 files changed, 15 insertions(+) diff --git a/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts b/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts index e3d1f1b01..331d5b5f6 100644 --- a/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.setTitleAndDescription.spec.ts @@ -43,6 +43,10 @@ describe("gitHubReposInvoker.ts", (): void => { await gitHubReposInvoker.setTitleAndDescription(null, null); // Assert + verify(octokitWrapper.initialize(any())).never(); + verify( + octokitWrapper.updatePull(any(), any(), any(), any(), any()), + ).never(); }); it("should succeed when the title and description are both set", async (): Promise => { diff --git a/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts b/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts index 332e3251d..27af6aae0 100644 --- a/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts @@ -43,6 +43,16 @@ describe("gitHubReposInvoker.ts", (): void => { await gitHubReposInvoker.updateComment(54321, null); // Assert + verify(octokitWrapper.initialize(any())).never(); + verify( + octokitWrapper.updateIssueComment( + any(), + any(), + any(), + any(), + any(), + ), + ).never(); }); it("should succeed when the content is set", async (): Promise => { diff --git a/src/task/tests/repos/tokenManager.spec.ts b/src/task/tests/repos/tokenManager.spec.ts index 72cd04e42..e0d169d44 100644 --- a/src/task/tests/repos/tokenManager.spec.ts +++ b/src/task/tests/repos/tokenManager.spec.ts @@ -30,6 +30,7 @@ describe("tokenManager.ts", (): void => { beforeEach((): void => { stubEnv( + ["PR_METRICS_ACCESS_TOKEN", undefined], ["SYSTEM_COLLECTIONURI", "https://dev.azure.com/organization"], ["SYSTEM_HOSTTYPE", "HostType"], ["SYSTEM_JOBID", "JobId"], From 51c9d622fa99f8958e9695036f97186af1c2acdd Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Mon, 20 Apr 2026 16:19:13 +0100 Subject: [PATCH 18/27] chore: fix linting --- .../tests/repos/gitHubReposInvoker.updateComment.spec.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts b/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts index 27af6aae0..e2a48dc52 100644 --- a/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.updateComment.spec.ts @@ -45,13 +45,7 @@ describe("gitHubReposInvoker.ts", (): void => { // Assert verify(octokitWrapper.initialize(any())).never(); verify( - octokitWrapper.updateIssueComment( - any(), - any(), - any(), - any(), - any(), - ), + octokitWrapper.updateIssueComment(any(), any(), any(), any(), any()), ).never(); }); From 09713dd2ee0ece563edda13a6df9dfb315bea872 Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Mon, 20 Apr 2026 16:57:42 +0100 Subject: [PATCH 19/27] Share user agent constant between production and tests - Export the user-agent string as a named constant from gitHubReposInvoker.ts so tests can reference the production value instead of hard-coding their own copy that can drift on version bumps. - Update the version-bump script to stop touching the deleted gitHubReposInvoker.spec.ts and rely on the test setup re-exporting the production constant. --- .github/workflow-scripts/Update-Version.ps1 | 4 ++-- src/task/src/repos/gitHubReposInvoker.ts | 10 +++++++++- src/task/tests/repos/gitHubReposInvokerTestSetup.ts | 6 ++++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/.github/workflow-scripts/Update-Version.ps1 b/.github/workflow-scripts/Update-Version.ps1 index 0aa0f5a12..51fe31343 100644 --- a/.github/workflow-scripts/Update-Version.ps1 +++ b/.github/workflow-scripts/Update-Version.ps1 @@ -63,9 +63,9 @@ Update-FileContent -Path 'src/task/task.json' -Replacements (@($friendlyNameRepl Update-FileContent -Path 'src/task/task.loc.json' -Replacements $versionComponentReplacements Update-FileContent -Path 'src/task/Strings/resources.resjson/en-US/resources.resjson' -Replacements @($friendlyNameReplacement) -# Source code user-agent. +# Source code user-agent. The test setup re-exports this constant, so only the +# production file needs updating. Update-FileContent -Path 'src/task/src/repos/gitHubReposInvoker.ts' -Replacements @($userAgentReplacement) -Update-FileContent -Path 'src/task/tests/repos/gitHubReposInvoker.spec.ts' -Replacements @($userAgentReplacement) # Release workflow defaults. The env vars in release-initiate.yml represent the # next release version, so boost the patch by 1 beyond the version being released. diff --git a/src/task/src/repos/gitHubReposInvoker.ts b/src/task/src/repos/gitHubReposInvoker.ts index 853fc7fca..1c023ce15 100644 --- a/src/task/src/repos/gitHubReposInvoker.ts +++ b/src/task/src/repos/gitHubReposInvoker.ts @@ -27,6 +27,14 @@ import type UpdateIssueCommentResponse from "../wrappers/octokitInterfaces/updat import type UpdatePullResponse from "../wrappers/octokitInterfaces/updatePullResponse.js"; import { decimalRadix } from "../utilities/constants.js"; +/** + * The user agent sent with every GitHub API request originating from PR + * Metrics. Exported so that tests can assert against the same value the + * production code uses, preventing drift between production and tests when + * the version is bumped. + */ +export const userAgent = "PRMetrics/v1.7.13"; + /** * A class for invoking GitHub Repos functionality. */ @@ -287,7 +295,7 @@ export default class GitHubReposInvoker extends BaseReposInvoker { this._logger.logWarning(`Octokit – ${message}`); }, }, - userAgent: "PRMetrics/v1.7.13", + userAgent, }; if (RunnerInvoker.isGitHub) { diff --git a/src/task/tests/repos/gitHubReposInvokerTestSetup.ts b/src/task/tests/repos/gitHubReposInvokerTestSetup.ts index c1295a2a3..49799e0eb 100644 --- a/src/task/tests/repos/gitHubReposInvokerTestSetup.ts +++ b/src/task/tests/repos/gitHubReposInvokerTestSetup.ts @@ -4,9 +4,11 @@ */ import * as GitHubReposInvokerConstants from "./gitHubReposInvokerConstants.js"; +import GitHubReposInvoker, { + userAgent, +} from "../../src/repos/gitHubReposInvoker.js"; import { anyNumber, anyString } from "../testUtilities/mockito.js"; import { instance, mock, when } from "ts-mockito"; -import GitHubReposInvoker from "../../src/repos/gitHubReposInvoker.js"; import GitInvoker from "../../src/git/gitInvoker.js"; import Logger from "../../src/utilities/logger.js"; import OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; @@ -14,7 +16,7 @@ import RunnerInvoker from "../../src/runners/runnerInvoker.js"; import { stubEnv } from "../testUtilities/stubEnv.js"; import { stubLocalization } from "../testUtilities/stubLocalization.js"; -export const expectedUserAgent = "PRMetrics/v1.7.13"; +export const expectedUserAgent = userAgent; export interface GitHubReposInvokerMocks { gitInvoker: GitInvoker; From 70ae79d453aad253fd5aea19c9a8535353470d42 Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Mon, 20 Apr 2026 16:58:51 +0100 Subject: [PATCH 20/27] chore: fix linting, update dist folder --- dist/index.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/index.mjs b/dist/index.mjs index d01226700..515ffbcc2 100644 --- a/dist/index.mjs +++ b/dist/index.mjs @@ -38,8 +38,8 @@ import{createRequire as e}from"module";var t={6445:function(e,t,i){var n=this&&t (function(t){"use strict";if(typeof bootstrap==="function"){bootstrap("promise",t)}else if(true){e.exports=t()}else{var i,n}})((function(){"use strict";var e=false;try{throw new Error}catch(t){e=!!t.stack}var t=captureLine();var i;var noop=function(){};var n=function(){var e={task:void 0,next:null};var t=e;var i=false;var r=void 0;var s=false;var o=[];function flush(){var t,n;while(e.next){e=e.next;t=e.task;e.task=void 0;n=e.domain;if(n){e.domain=void 0;n.enter()}runSingle(t,n)}while(o.length){t=o.pop();runSingle(t)}i=false}function runSingle(e,t){try{e()}catch(e){if(s){if(t){t.exit()}setTimeout(flush,0);if(t){t.enter()}throw e}else{setTimeout((function(){throw e}),0)}}if(t){t.exit()}}n=function(e){t=t.next={task:e,domain:s&&process.domain,next:null};if(!i){i=true;r()}};if(typeof process==="object"&&process.toString()==="[object process]"&&process.nextTick){s=true;r=function(){process.nextTick(flush)}}else if(typeof setImmediate==="function"){if(typeof window!=="undefined"){r=setImmediate.bind(window,flush)}else{r=function(){setImmediate(flush)}}}else if(typeof MessageChannel!=="undefined"){var a=new MessageChannel;a.port1.onmessage=function(){r=requestPortTick;a.port1.onmessage=flush;flush()};var requestPortTick=function(){a.port2.postMessage(0)};r=function(){setTimeout(flush,0);requestPortTick()}}else{r=function(){setTimeout(flush,0)}}n.runAfter=function(e){o.push(e);if(!i){i=true;r()}};return n}();var r=Function.call;function uncurryThis(e){return function(){return r.apply(e,arguments)}}var s=uncurryThis(Array.prototype.slice);var o=uncurryThis(Array.prototype.reduce||function(e,t){var i=0,n=this.length;if(arguments.length===1){do{if(i in this){t=this[i++];break}if(++i>=n){throw new TypeError}}while(1)}for(;ir.stackCounter)){c(t,"__minimumStackCounter__",{value:r.stackCounter,configurable:true});n.unshift(r.stack)}}n.unshift(t.stack);var s=n.join("\n"+h+"\n");var o=filterStackString(s);c(t,"stack",{value:o,configurable:true})}}function filterStackString(e){var t=e.split("\n");var i=[];for(var n=0;n=t&&s<=E}function captureLine(){if(!e){return}try{throw new Error}catch(e){var t=e.stack.split("\n");var n=t[0].indexOf("@")>0?t[1]:t[2];var r=getFileNameAndLineNumber(n);if(!r){return}i=r[0];return r[1]}}function deprecate(e,t,i){return function(){if(typeof console!=="undefined"&&typeof console.warn==="function"){console.warn(t+" is deprecated, use "+i+" instead.",new Error("").stack)}return e.apply(e,arguments)}}function Q(e){if(e instanceof Promise){return e}if(isPromiseAlike(e)){return coerce(e)}else{return fulfill(e)}}Q.resolve=Q;Q.nextTick=n;Q.longStackSupport=false;var g=1;if(typeof process==="object"&&process&&process.env&&process.env.Q_DEBUG){Q.longStackSupport=true}Q.defer=defer;function defer(){var t=[],i=[],n;var r=u(defer.prototype);var a=u(Promise.prototype);a.promiseDispatch=function(e,r,o){var a=s(arguments);if(t){t.push(a);if(r==="when"&&o[1]){i.push(o[1])}}else{Q.nextTick((function(){n.promiseDispatch.apply(n,a)}))}};a.valueOf=function(){if(t){return a}var e=nearer(n);if(isPromise(e)){n=e}return e};a.inspect=function(){if(!n){return{state:"pending"}}return n.inspect()};if(Q.longStackSupport&&e){try{throw new Error}catch(e){a.stack=e.stack.substring(e.stack.indexOf("\n")+1);a.stackCounter=g++}}function become(r){n=r;if(Q.longStackSupport&&e){a.source=r}o(t,(function(e,t){Q.nextTick((function(){r.promiseDispatch.apply(r,t)}))}),void 0);t=void 0;i=void 0}r.promise=a;r.resolve=function(e){if(n){return}become(Q(e))};r.fulfill=function(e){if(n){return}become(fulfill(e))};r.reject=function(e){if(n){return}become(reject(e))};r.notify=function(e){if(n){return}o(i,(function(t,i){Q.nextTick((function(){i(e)}))}),void 0)};return r}defer.prototype.makeNodeResolver=function(){var e=this;return function(t,i){if(t){e.reject(t)}else if(arguments.length>2){e.resolve(s(arguments,1))}else{e.resolve(i)}}};Q.Promise=promise;Q.promise=promise;function promise(e){if(typeof e!=="function"){throw new TypeError("resolver must be a function.")}var t=defer();try{e(t.resolve,t.reject,t.notify)}catch(e){t.reject(e)}return t.promise}promise.race=race;promise.all=all;promise.reject=reject;promise.resolve=Q;Q.passByCopy=function(e){return e};Promise.prototype.passByCopy=function(){return this};Q.join=function(e,t){return Q(e).join(t)};Promise.prototype.join=function(e){return Q([this,e]).spread((function(e,t){if(e===t){return e}else{throw new Error("Q can't join: not the same: "+e+" "+t)}}))};Q.race=race;function race(e){return promise((function(t,i){for(var n=0,r=e.length;n{var t=String.prototype.replace;var i=/%20/g;var n={RFC1738:"RFC1738",RFC3986:"RFC3986"};e.exports={default:n.RFC3986,formatters:{RFC1738:function(e){return t.call(e,i,"+")},RFC3986:function(e){return String(e)}},RFC1738:n.RFC1738,RFC3986:n.RFC3986}},240:(e,t,i)=>{var n=i(1293);var r=i(9091);var s=i(6032);e.exports={formats:s,parse:r,stringify:n}},9091:(e,t,i)=>{var n=i(5225);var r=Object.prototype.hasOwnProperty;var s=Array.isArray;var o={allowDots:false,allowEmptyArrays:false,allowPrototypes:false,allowSparse:false,arrayLimit:20,charset:"utf-8",charsetSentinel:false,comma:false,decodeDotInKeys:false,decoder:n.decode,delimiter:"&",depth:5,duplicates:"combine",ignoreQueryPrefix:false,interpretNumericEntities:false,parameterLimit:1e3,parseArrays:true,plainObjects:false,strictDepth:false,strictMerge:true,strictNullHandling:false,throwOnLimitExceeded:false};var interpretNumericEntities=function(e){return e.replace(/&#(\d+);/g,(function(e,t){return String.fromCharCode(parseInt(t,10))}))};var parseArrayValue=function(e,t,i){if(e&&typeof e==="string"&&t.comma&&e.indexOf(",")>-1){return e.split(",")}if(t.throwOnLimitExceeded&&i>=t.arrayLimit){throw new RangeError("Array limit exceeded. Only "+t.arrayLimit+" element"+(t.arrayLimit===1?"":"s")+" allowed in an array.")}return e};var a="utf8=%26%2310003%3B";var l="utf8=%E2%9C%93";var u=function parseQueryStringValues(e,t){var i={__proto__:null};var u=t.ignoreQueryPrefix?e.replace(/^\?/,""):e;u=u.replace(/%5B/gi,"[").replace(/%5D/gi,"]");var c=t.parameterLimit===Infinity?void undefined:t.parameterLimit;var d=u.split(t.delimiter,t.throwOnLimitExceeded?c+1:c);if(t.throwOnLimitExceeded&&d.length>c){throw new RangeError("Parameter limit exceeded. Only "+c+" parameter"+(c===1?"":"s")+" allowed.")}var p=-1;var A;var f=t.charset;if(t.charsetSentinel){for(A=0;A-1){I=s(I)?[I]:I}if(t.comma&&s(I)&&I.length>t.arrayLimit){if(t.throwOnLimitExceeded){throw new RangeError("Array limit exceeded. Only "+t.arrayLimit+" element"+(t.arrayLimit===1?"":"s")+" allowed in an array.")}I=n.combine([],I,t.arrayLimit,t.plainObjects)}if(m!==null){var v=r.call(i,m);if(v&&(t.duplicates==="combine"||h.indexOf("[]=")>-1)){i[m]=n.combine(i[m],I,t.arrayLimit,t.plainObjects)}else if(!v||t.duplicates==="last"){i[m]=I}}}return i};var parseObject=function(e,t,i,r){var s=0;if(e.length>0&&e[e.length-1]==="[]"){var o=e.slice(0,-1).join("");s=Array.isArray(t)&&t[o]?t[o].length:0}var a=r?t:parseArrayValue(t,i,s);for(var l=e.length-1;l>=0;--l){var u;var c=e[l];if(c==="[]"&&i.parseArrays){if(n.isOverflow(a)){u=a}else{u=i.allowEmptyArrays&&(a===""||i.strictNullHandling&&a===null)?[]:n.combine([],a,i.arrayLimit,i.plainObjects)}}else{u=i.plainObjects?{__proto__:null}:{};var d=c.charAt(0)==="["&&c.charAt(c.length-1)==="]"?c.slice(1,-1):c;var p=i.decodeDotInKeys?d.replace(/%2E/g,"."):d;var A=parseInt(p,10);var f=!isNaN(A)&&c!==p&&String(A)===p&&A>=0&&i.parseArrays;if(!i.parseArrays&&p===""){u={0:a}}else if(f&&A{var n=i(4753);var r=i(5225);var s=i(6032);var o=Object.prototype.hasOwnProperty;var a={brackets:function brackets(e){return e+"[]"},comma:"comma",indices:function indices(e,t){return e+"["+t+"]"},repeat:function repeat(e){return e}};var l=Array.isArray;var u=Array.prototype.push;var pushToArray=function(e,t){u.apply(e,l(t)?t:[t])};var c=Date.prototype.toISOString;var d=s["default"];var p={addQueryPrefix:false,allowDots:false,allowEmptyArrays:false,arrayFormat:"indices",charset:"utf-8",charsetSentinel:false,commaRoundTrip:false,delimiter:"&",encode:true,encodeDotInKeys:false,encoder:r.encode,encodeValuesOnly:false,filter:void undefined,format:d,formatter:s.formatters[d],indices:false,serializeDate:function serializeDate(e){return c.call(e)},skipNulls:false,strictNullHandling:false};var A=function isNonNullishPrimitive(e){return typeof e==="string"||typeof e==="number"||typeof e==="boolean"||typeof e==="symbol"||typeof e==="bigint"};var f={};var h=function stringify(e,t,i,s,o,a,u,c,d,h,g,y,m,I,v,E,C,T){var R=e;var b=T;var w=0;var B=false;while((b=b.get(f))!==void undefined&&!B){var D=b.get(e);w+=1;if(typeof D!=="undefined"){if(D===w){throw new RangeError("Cyclic object value")}else{B=true}}if(typeof b.get(f)==="undefined"){w=0}}if(typeof h==="function"){R=h(t,R)}else if(R instanceof Date){R=m(R)}else if(i==="comma"&&l(R)){R=r.maybeMap(R,(function(e){if(e instanceof Date){return m(e)}return e}))}if(R===null){if(a){return d&&!E?d(t,p.encoder,C,"key",I):t}R=""}if(A(R)||r.isBuffer(R)){if(d){var S=E?t:d(t,p.encoder,C,"key",I);return[v(S)+"="+v(d(R,p.encoder,C,"value",I))]}return[v(t)+"="+v(String(R))]}var k=[];if(typeof R==="undefined"){return k}var P;if(i==="comma"&&l(R)){if(E&&d){R=r.maybeMap(R,d)}P=[{value:R.length>0?R.join(",")||null:void undefined}]}else if(l(h)){P=h}else{var U=Object.keys(R);P=g?U.sort(g):U}var V=c?String(t).replace(/\./g,"%2E"):String(t);var F=s&&l(R)&&R.length===1?V+"[]":V;if(o&&l(R)&&R.length===0){return F+"[]"}for(var O=0;O0?I+m:""}},5225:(e,t,i)=>{var n=i(6032);var r=i(4753);var s=Object.prototype.hasOwnProperty;var o=Array.isArray;var a=r();var l=function markOverflow(e,t){a.set(e,t);return e};var u=function isOverflow(e){return a.has(e)};var c=function getMaxIndex(e){return a.get(e)};var d=function setMaxIndex(e,t){a.set(e,t)};var p=function(){var e=[];for(var t=0;t<256;++t){e[e.length]="%"+((t<16?"0":"")+t.toString(16)).toUpperCase()}return e}();var A=function compactQueue(e){while(e.length>1){var t=e.pop();var i=t.obj[t.prop];if(o(i)){var n=[];for(var r=0;ri.arrayLimit){return l(f(e.concat(t),i),n)}e[n]=t}else if(e&&typeof e==="object"){if(u(e)){var r=c(e)+1;e[r]=t;d(e,r)}else if(i&&i.strictMerge){return[e,t]}else if(i&&(i.plainObjects||i.allowPrototypes)||!s.call(Object.prototype,t)){e[t]=true}}else{return[e,t]}return e}if(!e||typeof e!=="object"){if(u(t)){var a=Object.keys(t);var p=i&&i.plainObjects?{__proto__:null,0:e}:{0:e};for(var A=0;Ai.arrayLimit){return l(f(g,i),g.length-1)}return g}var y=e;if(o(e)&&!o(t)){y=f(e,i)}if(o(e)&&o(t)){t.forEach((function(t,n){if(s.call(e,n)){var r=e[n];if(r&&typeof r==="object"&&t&&typeof t==="object"){e[n]=merge(r,t,i)}else{e[e.length]=t}}else{e[n]=t}}));return e}return Object.keys(t).reduce((function(e,n){var r=t[n];if(s.call(e,n)){e[n]=merge(e[n],r,i)}else{e[n]=r}if(u(t)&&!u(e)){l(e,c(t))}if(u(e)){var o=parseInt(n,10);if(String(o)===n&&o>=0&&o>c(e)){d(e,o)}}return e}),y)};var g=function assignSingleSource(e,t){return Object.keys(t).reduce((function(e,i){e[i]=t[i];return e}),e)};var decode=function(e,t,i){var n=e.replace(/\+/g," ");if(i==="iso-8859-1"){return n.replace(/%[0-9a-f]{2}/gi,unescape)}try{return decodeURIComponent(n)}catch(e){return n}};var y=1024;var m=function encode(e,t,i,r,s){if(e.length===0){return e}var o=e;if(typeof e==="symbol"){o=Symbol.prototype.toString.call(e)}else if(typeof e!=="string"){o=String(e)}if(i==="iso-8859-1"){return escape(o).replace(/%u[0-9a-f]{4}/gi,(function(e){return"%26%23"+parseInt(e.slice(2),16)+"%3B"}))}var a="";for(var l=0;l=y?o.slice(l,l+y):o;var c=[];for(var d=0;d=48&&A<=57||A>=65&&A<=90||A>=97&&A<=122||s===n.RFC1738&&(A===40||A===41)){c[c.length]=u.charAt(d);continue}if(A<128){c[c.length]=p[A];continue}if(A<2048){c[c.length]=p[192|A>>6]+p[128|A&63];continue}if(A<55296||A>=57344){c[c.length]=p[224|A>>12]+p[128|A>>6&63]+p[128|A&63];continue}d+=1;A=65536+((A&1023)<<10|u.charCodeAt(d)&1023);c[c.length]=p[240|A>>18]+p[128|A>>12&63]+p[128|A>>6&63]+p[128|A&63]}a+=c.join("")}return a};var I=function compact(e){var t=[{obj:{o:e},prop:"o"}];var i=[];for(var n=0;ni){return l(f(s,{plainObjects:n}),s.length-1)}return s};var T=function maybeMap(e,t){if(o(e)){var i=[];for(var n=0;n{var n=i(506);var r=i(3314);var listGetNode=function(e,t,i){var n=e;var r;for(;(r=n.next)!=null;n=r){if(r.key===t){n.next=r.next;if(!i){r.next=e.next;e.next=r}return r}}};var listGet=function(e,t){if(!e){return void undefined}var i=listGetNode(e,t);return i&&i.value};var listSet=function(e,t,i){var n=listGetNode(e,t);if(n){n.value=i}else{e.next={key:t,next:e.next,value:i}}};var listHas=function(e,t){if(!e){return false}return!!listGetNode(e,t)};var listDelete=function(e,t){if(e){return listGetNode(e,t,true)}};e.exports=function getSideChannelList(){var e;var t={assert:function(e){if(!t.has(e)){throw new r("Side channel does not contain "+n(e))}},delete:function(t){var i=e&&e.next;var n=listDelete(e,t);if(n&&i&&i===n){e=void undefined}return!!n},get:function(t){return listGet(e,t)},has:function(t){return listHas(e,t)},set:function(t,i){if(!e){e={next:void undefined}}listSet(e,t,i)}};return t}},2622:(e,t,i)=>{var n=i(470);var r=i(3105);var s=i(506);var o=i(3314);var a=n("%Map%",true);var l=r("Map.prototype.get",true);var u=r("Map.prototype.set",true);var c=r("Map.prototype.has",true);var d=r("Map.prototype.delete",true);var p=r("Map.prototype.size",true);e.exports=!!a&&function getSideChannelMap(){var e;var t={assert:function(e){if(!t.has(e)){throw new o("Side channel does not contain "+s(e))}},delete:function(t){if(e){var i=d(e,t);if(p(e)===0){e=void undefined}return i}return false},get:function(t){if(e){return l(e,t)}},has:function(t){if(e){return c(e,t)}return false},set:function(t,i){if(!e){e=new a}u(e,t,i)}};return t}},2870:(e,t,i)=>{var n=i(470);var r=i(3105);var s=i(506);var o=i(2622);var a=i(3314);var l=n("%WeakMap%",true);var u=r("WeakMap.prototype.get",true);var c=r("WeakMap.prototype.set",true);var d=r("WeakMap.prototype.has",true);var p=r("WeakMap.prototype.delete",true);e.exports=l?function getSideChannelWeakMap(){var e;var t;var i={assert:function(e){if(!i.has(e)){throw new a("Side channel does not contain "+s(e))}},delete:function(i){if(l&&i&&(typeof i==="object"||typeof i==="function")){if(e){return p(e,i)}}else if(o){if(t){return t["delete"](i)}}return false},get:function(i){if(l&&i&&(typeof i==="object"||typeof i==="function")){if(e){return u(e,i)}}return t&&t.get(i)},has:function(i){if(l&&i&&(typeof i==="object"||typeof i==="function")){if(e){return d(e,i)}}return!!t&&t.has(i)},set:function(i,n){if(l&&i&&(typeof i==="object"||typeof i==="function")){if(!e){e=new l}c(e,i,n)}else if(o){if(!t){t=o()}t.set(i,n)}}};return i}:o},4753:(e,t,i)=>{var n=i(3314);var r=i(506);var s=i(8948);var o=i(2622);var a=i(2870);var l=a||o||s;e.exports=function getSideChannel(){var e;var t={assert:function(e){if(!t.has(e)){throw new n("Side channel does not contain "+r(e))}},delete:function(t){return!!e&&e["delete"](t)},get:function(t){return e&&e.get(t)},has:function(t){return!!e&&e.has(t)},set:function(t,i){if(!e){e=l()}e.set(t,i)}};return t}},770:(e,t,i)=>{e.exports=i(218)},218:(e,t,i)=>{var n=i(9278);var r=i(4756);var s=i(8611);var o=i(5692);var a=i(4434);var l=i(2613);var u=i(9023);t.httpOverHttp=httpOverHttp;t.httpsOverHttp=httpsOverHttp;t.httpOverHttps=httpOverHttps;t.httpsOverHttps=httpsOverHttps;function httpOverHttp(e){var t=new TunnelingAgent(e);t.request=s.request;return t}function httpsOverHttp(e){var t=new TunnelingAgent(e);t.request=s.request;t.createSocket=createSecureSocket;t.defaultPort=443;return t}function httpOverHttps(e){var t=new TunnelingAgent(e);t.request=o.request;return t}function httpsOverHttps(e){var t=new TunnelingAgent(e);t.request=o.request;t.createSocket=createSecureSocket;t.defaultPort=443;return t}function TunnelingAgent(e){var t=this;t.options=e||{};t.proxyOptions=t.options.proxy||{};t.maxSockets=t.options.maxSockets||s.Agent.defaultMaxSockets;t.requests=[];t.sockets=[];t.on("free",(function onFree(e,i,n,r){var s=toOptions(i,n,r);for(var o=0,a=t.requests.length;o=this.maxSockets){r.requests.push(s);return}r.createSocket(s,(function(t){t.on("free",onFree);t.on("close",onCloseOrRemove);t.on("agentRemove",onCloseOrRemove);e.onSocket(t);function onFree(){r.emit("free",t,s)}function onCloseOrRemove(e){r.removeSocket(t);t.removeListener("free",onFree);t.removeListener("close",onCloseOrRemove);t.removeListener("agentRemove",onCloseOrRemove)}}))};TunnelingAgent.prototype.createSocket=function createSocket(e,t){var i=this;var n={};i.sockets.push(n);var r=mergeOptions({},i.proxyOptions,{method:"CONNECT",path:e.host+":"+e.port,agent:false,headers:{host:e.host+":"+e.port}});if(e.localAddress){r.localAddress=e.localAddress}if(r.proxyAuth){r.headers=r.headers||{};r.headers["Proxy-Authorization"]="Basic "+new Buffer(r.proxyAuth).toString("base64")}c("making CONNECT request");var s=i.request(r);s.useChunkedEncodingByDefault=false;s.once("response",onResponse);s.once("upgrade",onUpgrade);s.once("connect",onConnect);s.once("error",onError);s.end();function onResponse(e){e.upgrade=true}function onUpgrade(e,t,i){process.nextTick((function(){onConnect(e,t,i)}))}function onConnect(r,o,a){s.removeAllListeners();o.removeAllListeners();if(r.statusCode!==200){c("tunneling socket could not be established, statusCode=%d",r.statusCode);o.destroy();var l=new Error("tunneling socket could not be established, "+"statusCode="+r.statusCode);l.code="ECONNRESET";e.request.emit("error",l);i.removeSocket(n);return}if(a.length>0){c("got illegal response body from proxy");o.destroy();var l=new Error("got illegal response body from proxy");l.code="ECONNRESET";e.request.emit("error",l);i.removeSocket(n);return}c("tunneling connection has established");i.sockets[i.sockets.indexOf(n)]=o;return t(o)}function onError(t){s.removeAllListeners();c("tunneling socket could not be established, cause=%s\n",t.message,t.stack);var r=new Error("tunneling socket could not be established, "+"cause="+t.message);r.code="ECONNRESET";e.request.emit("error",r);i.removeSocket(n)}};TunnelingAgent.prototype.removeSocket=function removeSocket(e){var t=this.sockets.indexOf(e);if(t===-1){return}this.sockets.splice(t,1);var i=this.requests.shift();if(i){this.createSocket(i,(function(e){i.request.onSocket(e)}))}};function createSecureSocket(e,t){var i=this;TunnelingAgent.prototype.createSocket.call(i,e,(function(n){var s=e.request.getHeader("host");var o=mergeOptions({},i.options,{socket:n,servername:s?s.replace(/:.*$/,""):e.host});var a=r.connect(0,o);i.sockets[i.sockets.indexOf(n)]=a;t(a)}))}function toOptions(e,t,i){if(typeof e==="string"){return{host:e,port:t,localAddress:i}}return e}function mergeOptions(e){for(var t=1,i=arguments.length;t{Object.defineProperty(t,"__esModule",{value:true});t.PersonalAccessTokenCredentialHandler=t.NtlmCredentialHandler=t.BearerCredentialHandler=t.BasicCredentialHandler=void 0;var n=i(6314);Object.defineProperty(t,"BasicCredentialHandler",{enumerable:true,get:function(){return n.BasicCredentialHandler}});var r=i(6731);Object.defineProperty(t,"BearerCredentialHandler",{enumerable:true,get:function(){return r.BearerCredentialHandler}});var s=i(9688);Object.defineProperty(t,"NtlmCredentialHandler",{enumerable:true,get:function(){return s.NtlmCredentialHandler}});var o=i(4578);Object.defineProperty(t,"PersonalAccessTokenCredentialHandler",{enumerable:true,get:function(){return o.PersonalAccessTokenCredentialHandler}})},6184:function(e,t,i){var n=this&&this.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:true});t.HttpClient=t.HttpClientResponse=t.HttpCodes=void 0;t.isHttps=isHttps;const r=i(7016);const s=i(8611);const o=i(5692);const a=i(4143);let l;let u;var c;(function(e){e[e["OK"]=200]="OK";e[e["MultipleChoices"]=300]="MultipleChoices";e[e["MovedPermanently"]=301]="MovedPermanently";e[e["ResourceMoved"]=302]="ResourceMoved";e[e["SeeOther"]=303]="SeeOther";e[e["NotModified"]=304]="NotModified";e[e["UseProxy"]=305]="UseProxy";e[e["SwitchProxy"]=306]="SwitchProxy";e[e["TemporaryRedirect"]=307]="TemporaryRedirect";e[e["PermanentRedirect"]=308]="PermanentRedirect";e[e["BadRequest"]=400]="BadRequest";e[e["Unauthorized"]=401]="Unauthorized";e[e["PaymentRequired"]=402]="PaymentRequired";e[e["Forbidden"]=403]="Forbidden";e[e["NotFound"]=404]="NotFound";e[e["MethodNotAllowed"]=405]="MethodNotAllowed";e[e["NotAcceptable"]=406]="NotAcceptable";e[e["ProxyAuthenticationRequired"]=407]="ProxyAuthenticationRequired";e[e["RequestTimeout"]=408]="RequestTimeout";e[e["Conflict"]=409]="Conflict";e[e["Gone"]=410]="Gone";e[e["TooManyRequests"]=429]="TooManyRequests";e[e["InternalServerError"]=500]="InternalServerError";e[e["NotImplemented"]=501]="NotImplemented";e[e["BadGateway"]=502]="BadGateway";e[e["ServiceUnavailable"]=503]="ServiceUnavailable";e[e["GatewayTimeout"]=504]="GatewayTimeout"})(c||(t.HttpCodes=c={}));const d=[c.MovedPermanently,c.ResourceMoved,c.SeeOther,c.TemporaryRedirect,c.PermanentRedirect];const p=[c.BadGateway,c.ServiceUnavailable,c.GatewayTimeout];const A=["ECONNRESET","ENOTFOUND","ESOCKETTIMEDOUT","ETIMEDOUT","ECONNREFUSED","EHOSTUNREACH"];const f=["OPTIONS","GET","DELETE","HEAD"];const h=10;const g=5;class HttpClientResponse{constructor(e){this.message=e}readBody(){return new Promise(((e,t)=>n(this,void 0,void 0,(function*(){const i=[];const r=a.obtainContentCharset(this);const s=this.message.headers["content-encoding"]||"";const o=new RegExp("(gzip$)|(gzip, *deflate)").test(s);this.message.on("data",(function(e){const t=typeof e==="string"?Buffer.from(e,r):e;i.push(t)})).on("end",(function(){return n(this,void 0,void 0,(function*(){const t=Buffer.concat(i);if(o){const i=yield a.decompressGzippedContent(t,r);e(i)}else{e(t.toString(r))}}))})).on("error",(function(e){t(e)}))}))))}}t.HttpClientResponse=HttpClientResponse;function isHttps(e){let t=r.parse(e);return t.protocol==="https:"}var y;(function(e){e["HTTP_PROXY"]="HTTP_PROXY";e["HTTPS_PROXY"]="HTTPS_PROXY";e["NO_PROXY"]="NO_PROXY"})(y||(y={}));class HttpClient{constructor(e,t,n){this._ignoreSslError=false;this._allowRedirects=true;this._allowRedirectDowngrade=false;this._maxRedirects=50;this._allowRetries=false;this._maxRetries=1;this._keepAlive=false;this._disposed=false;this._httpGlobalAgentOptions={keepAlive:false,timeout:3e4};this.userAgent=e;this.handlers=t||[];let r=process.env[y.NO_PROXY];if(r){this._httpProxyBypassHosts=[];r.split(",").forEach((e=>{this._httpProxyBypassHosts.push(a.buildProxyBypassRegexFromEnv(e))}))}this.requestOptions=n;if(n){if(n.ignoreSslError!=null){this._ignoreSslError=n.ignoreSslError}this._socketTimeout=n.socketTimeout;this._httpProxy=n.proxy;if(n.proxy&&n.proxy.proxyBypassHosts){this._httpProxyBypassHosts=[];n.proxy.proxyBypassHosts.forEach((e=>{this._httpProxyBypassHosts.push(new RegExp(e,"i"))}))}if(n.globalAgentOptions){this._httpGlobalAgentOptions=n.globalAgentOptions}this._certConfig=n.cert;if(this._certConfig){l=i(9896);if(this._certConfig.caFile&&l.existsSync(this._certConfig.caFile)){this._ca=l.readFileSync(this._certConfig.caFile,"utf8")}if(this._certConfig.certFile&&l.existsSync(this._certConfig.certFile)){this._cert=l.readFileSync(this._certConfig.certFile,"utf8")}if(this._certConfig.keyFile&&l.existsSync(this._certConfig.keyFile)){this._key=l.readFileSync(this._certConfig.keyFile,"utf8")}}if(n.allowRedirects!=null){this._allowRedirects=n.allowRedirects}if(n.allowRedirectDowngrade!=null){this._allowRedirectDowngrade=n.allowRedirectDowngrade}if(n.maxRedirects!=null){this._maxRedirects=Math.max(n.maxRedirects,0)}if(n.keepAlive!=null){this._keepAlive=n.keepAlive}if(n.allowRetries!=null){this._allowRetries=n.allowRetries}if(n.maxRetries!=null){this._maxRetries=n.maxRetries}}}options(e,t){return this.request("OPTIONS",e,null,t||{})}get(e,t){return this.request("GET",e,null,t||{})}del(e,t){return this.request("DELETE",e,null,t||{})}post(e,t,i){return this.request("POST",e,t,i||{})}patch(e,t,i){return this.request("PATCH",e,t,i||{})}put(e,t,i){return this.request("PUT",e,t,i||{})}head(e,t){return this.request("HEAD",e,null,t||{})}sendStream(e,t,i,n){return this.request(e,t,i,n)}request(e,t,i,s){return n(this,void 0,void 0,(function*(){if(this._disposed){throw new Error("Client has already been disposed.")}let n=r.parse(t);let o=this._prepareRequest(e,n,s);let a=this._allowRetries&&f.indexOf(e)!=-1?this._maxRetries+1:1;let l=0;let u;while(l-1&&l0){const a=u.message.headers["location"];if(!a){break}let l=r.parse(a);if(n.protocol=="https:"&&n.protocol!=l.protocol&&!this._allowRedirectDowngrade){throw new Error("Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.")}yield u.readBody();o=this._prepareRequest(e,l,s);u=yield this.requestRaw(o,i);t--}if(p.indexOf(u.message.statusCode)==-1){return u}l+=1;if(l{let callbackForResult=function(e,t){if(e){n(e)}i(t)};this.requestRawWithCallback(e,t,callbackForResult)}))}requestRawWithCallback(e,t,i){let n;if(typeof t==="string"){e.options.headers["Content-Length"]=Buffer.byteLength(t,"utf8")}let r=false;let handleResult=(e,t)=>{if(!r){r=true;i(e,t)}};let s=e.httpModule.request(e.options,(e=>{let t=new HttpClientResponse(e);handleResult(null,t)}));s.on("socket",(e=>{n=e}));s.setTimeout(this._socketTimeout||3*6e4,(()=>{if(n){n.destroy()}handleResult(new Error("Request timeout: "+e.options.path),null)}));s.on("error",(function(e){handleResult(e,null)}));if(t&&typeof t==="string"){s.write(t,"utf8")}if(t&&typeof t!=="string"){t.on("close",(function(){s.end()}));t.pipe(s)}else{s.end()}}_prepareRequest(e,t,i){const n={};n.parsedUrl=t;const a=n.parsedUrl.protocol==="https:";n.httpModule=a?o:s;const l=a?443:80;n.options={};n.options.host=n.parsedUrl.hostname;n.options.port=n.parsedUrl.port?parseInt(n.parsedUrl.port):l;n.options.path=(n.parsedUrl.pathname||"")+(n.parsedUrl.search||"");n.options.method=e;n.options.timeout=this.requestOptions&&this.requestOptions.socketTimeout||this._socketTimeout;this._socketTimeout=n.options.timeout;n.options.headers=this._mergeHeaders(i);if(this.userAgent!=null){n.options.headers["user-agent"]=this.userAgent}n.options.agent=this._getAgent(n.parsedUrl);if(this.handlers&&!this._isPresigned(r.format(t))){this.handlers.forEach((e=>{e.prepareRequest(n.options)}))}return n}_isPresigned(e){if(this.requestOptions&&this.requestOptions.presignedUrlPatterns){const t=this.requestOptions.presignedUrlPatterns;for(let i=0;iObject.keys(e).reduce(((t,i)=>(t[i.toLowerCase()]=e[i],t)),{});if(this.requestOptions&&this.requestOptions.headers){return Object.assign({},lowercaseKeys(this.requestOptions.headers),lowercaseKeys(e))}return lowercaseKeys(e||{})}_getAgent(e){let t;let n=this._getProxy(e);let r=n.proxyUrl&&n.proxyUrl.hostname&&!this._isMatchInBypassProxyList(e);if(this._keepAlive&&r){t=this._proxyAgent}if(this._keepAlive&&!r){t=this._agent}if(!!t){return t}const a=e.protocol==="https:";let l=100;if(!!this.requestOptions){l=this.requestOptions.maxSockets||s.globalAgent.maxSockets}if(r){if(!u){u=i(770)}const e={maxSockets:l,keepAlive:this._keepAlive,proxy:{proxyAuth:n.proxyAuth,host:n.proxyUrl.hostname,port:n.proxyUrl.port}};let r;const s=n.proxyUrl.protocol==="https:";if(a){r=s?u.httpsOverHttps:u.httpsOverHttp}else{r=s?u.httpOverHttps:u.httpOverHttp}t=r(e);this._proxyAgent=t}if(this._keepAlive&&!t){const e={keepAlive:this._keepAlive,maxSockets:l};t=a?new o.Agent(e):new s.Agent(e);this._agent=t}if(!t){const e={keepAlive:this._httpGlobalAgentOptions.keepAlive,timeout:this._httpGlobalAgentOptions.timeout};t=a?new o.Agent(e):new s.Agent(e)}if(a&&this._ignoreSslError){t.options=Object.assign(t.options||{},{rejectUnauthorized:false})}if(a&&this._certConfig){t.options=Object.assign(t.options||{},{ca:this._ca,cert:this._cert,key:this._key,passphrase:this._certConfig.passphrase})}return t}_getProxy(e){let t=e.protocol==="https:";let i=this._httpProxy;let n=process.env[y.HTTPS_PROXY];let s=process.env[y.HTTP_PROXY];if(!i){if(n&&t){i={proxyUrl:n}}else if(s){i={proxyUrl:s}}}let o;let a;if(i){if(i.proxyUrl.length>0){o=r.parse(i.proxyUrl)}if(i.proxyUsername||i.proxyPassword){a=i.proxyUsername+":"+i.proxyPassword}}return{proxyUrl:o,proxyAuth:a}}_isMatchInBypassProxyList(e){if(!this._httpProxyBypassHosts){return false}let t=false;this._httpProxyBypassHosts.forEach((i=>{if(i.test(e.href)){t=true}}));return t}_performExponentialBackoff(e){e=Math.min(h,e);const t=g*Math.pow(2,e);return new Promise((e=>setTimeout((()=>e()),t)))}}t.HttpClient=HttpClient},3338:function(e,t,i){var n=this&&this.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:true});t.RestClient=void 0;const r=i(6184);const s=i(4143);class RestClient{constructor(e,t,i,n){this.client=new r.HttpClient(e,i,n);if(t){this._baseUrl=t}}options(e,t){return n(this,void 0,void 0,(function*(){let i=s.getUrl(e,this._baseUrl);let n=yield this.client.options(i,this._headersFromOptions(t));return this.processResponse(n,t)}))}get(e,t){return n(this,void 0,void 0,(function*(){let i=s.getUrl(e,this._baseUrl,(t||{}).queryParameters);let n=yield this.client.get(i,this._headersFromOptions(t));return this.processResponse(n,t)}))}del(e,t){return n(this,void 0,void 0,(function*(){let i=s.getUrl(e,this._baseUrl,(t||{}).queryParameters);let n=yield this.client.del(i,this._headersFromOptions(t));return this.processResponse(n,t)}))}create(e,t,i){return n(this,void 0,void 0,(function*(){let n=s.getUrl(e,this._baseUrl);let r=this._headersFromOptions(i,true);let o=JSON.stringify(t,null,2);let a=yield this.client.post(n,o,r);return this.processResponse(a,i)}))}update(e,t,i){return n(this,void 0,void 0,(function*(){let n=s.getUrl(e,this._baseUrl);let r=this._headersFromOptions(i,true);let o=JSON.stringify(t,null,2);let a=yield this.client.patch(n,o,r);return this.processResponse(a,i)}))}replace(e,t,i){return n(this,void 0,void 0,(function*(){let n=s.getUrl(e,this._baseUrl);let r=this._headersFromOptions(i,true);let o=JSON.stringify(t,null,2);let a=yield this.client.put(n,o,r);return this.processResponse(a,i)}))}uploadStream(e,t,i,r){return n(this,void 0,void 0,(function*(){let n=s.getUrl(t,this._baseUrl);let o=this._headersFromOptions(r,true);let a=yield this.client.sendStream(e,n,i,o);return this.processResponse(a,r)}))}_headersFromOptions(e,t){e=e||{};let i=e.additionalHeaders||{};i["Accept"]=e.acceptHeader||"application/json";if(t){let e=false;for(let t in i){if(t.toLowerCase()=="content-type"){e=true}}if(!e){i["Content-Type"]="application/json; charset=utf-8"}}return i}static dateTimeDeserializer(e,t){if(typeof t==="string"){let e=new Date(t);if(!isNaN(e.valueOf())){return e}}return t}processResponse(e,t){return n(this,void 0,void 0,(function*(){return new Promise(((i,s)=>n(this,void 0,void 0,(function*(){const n=e.message.statusCode;const o={statusCode:n,result:null,headers:{}};if(n==r.HttpCodes.NotFound){i(o)}let a;let l;try{l=yield e.readBody();if(l&&l.length>0){if(t&&t.deserializeDates){a=JSON.parse(l,RestClient.dateTimeDeserializer)}else{a=JSON.parse(l)}if(t&&t.responseProcessor){o.result=t.responseProcessor(a)}else{o.result=a}}o.headers=e.message.headers}catch(e){}if(n>299){let e;if(a&&a.message){e=a.message}else if(l&&l.length>0){e=l}else{e="Failed request: ("+n+")"}let t=new Error(e);t["statusCode"]=n;if(o.result){t["result"]=o.result}if(o.headers){t["responseHeaders"]=o.headers}s(t)}else{i(o)}}))))}))}}t.RestClient=RestClient},4143:function(e,t,i){var n=this&&this.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};Object.defineProperty(t,"__esModule",{value:true});t.getUrl=getUrl;t.decompressGzippedContent=decompressGzippedContent;t.buildProxyBypassRegexFromEnv=buildProxyBypassRegexFromEnv;t.obtainContentCharset=obtainContentCharset;const r=i(240);const s=i(7016);const o=i(6928);const a=i(3106);function getUrl(e,t,i){const n=o.posix||o;let r="";if(!t){r=e}else if(!e){r=t}else{const i=s.parse(t);const o=s.parse(e);o.protocol=o.protocol||i.protocol;o.auth=o.auth||i.auth;o.host=o.host||i.host;o.pathname=n.resolve(i.pathname,o.pathname);if(!o.pathname.endsWith("/")&&e.endsWith("/")){o.pathname+="/"}r=s.format(o)}return i?getUrlWithParsedQueryParams(r,i):r}function getUrlWithParsedQueryParams(e,t){const i=e.replace(/\?$/g,"");const n=r.stringify(t.params,buildParamsStringifyOptions(t));return`${i}${n}`}function buildParamsStringifyOptions(e){let t={addQueryPrefix:true,delimiter:(e.options||{}).separator||"&",allowDots:(e.options||{}).shouldAllowDots||false,arrayFormat:(e.options||{}).arrayFormat||"repeat",encodeValuesOnly:(e.options||{}).shouldOnlyEncodeValues||true};return t}function decompressGzippedContent(e,t){return n(this,void 0,void 0,(function*(){return new Promise(((i,r)=>n(this,void 0,void 0,(function*(){a.gunzip(e,(function(e,n){if(e){r(e)}else{i(n.toString(t||"utf-8"))}}))}))))}))}function buildProxyBypassRegexFromEnv(e){try{return new RegExp(e,"i")}catch(t){if(t instanceof SyntaxError&&(e||"").startsWith("*")){let t=e.replace("*","(.*)");return new RegExp(t,"i")}throw t}}function obtainContentCharset(e){const t=["ascii","utf8","utf16le","ucs2","base64","binary","hex"];const i=e.message.headers["content-type"]||"";const n=i.match(/charset=([^;,\r\n]+)/i);if(n&&n[1]&&t.indexOf(n[1])!=-1){return n[1]}return"utf-8"}},6314:(e,t)=>{Object.defineProperty(t,"__esModule",{value:true});t.BasicCredentialHandler=void 0;class BasicCredentialHandler{constructor(e,t,i){this.username=e;this.password=t;this.allowCrossOriginAuthentication=i}prepareRequest(e){if(!this.origin){this.origin=e.host}if(this.origin===e.host||this.allowCrossOriginAuthentication){e.headers["Authorization"]=`Basic ${Buffer.from(`${this.username}:${this.password}`).toString("base64")}`}e.headers["X-TFS-FedAuthRedirect"]="Suppress"}canHandleAuthentication(e){return false}handleAuthentication(e,t,i){return null}}t.BasicCredentialHandler=BasicCredentialHandler},6731:(e,t)=>{Object.defineProperty(t,"__esModule",{value:true});t.BearerCredentialHandler=void 0;class BearerCredentialHandler{constructor(e,t){this.token=e;this.allowCrossOriginAuthentication=t}prepareRequest(e){if(!this.origin){this.origin=e.host}if(this.origin===e.host||this.allowCrossOriginAuthentication){e.headers["Authorization"]=`Bearer ${this.token}`}e.headers["X-TFS-FedAuthRedirect"]="Suppress"}canHandleAuthentication(e){return false}handleAuthentication(e,t,i){return null}}t.BearerCredentialHandler=BearerCredentialHandler},9688:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:true});t.NtlmCredentialHandler=void 0;const n=i(8611);const r=i(5692);const s=i(3582);const o=i(1624);class NtlmCredentialHandler{constructor(e,t,i,n){this._ntlmOptions={};this._ntlmOptions.username=e;this._ntlmOptions.password=t;this._ntlmOptions.domain=n||"";this._ntlmOptions.workstation=i||""}prepareRequest(e){if(e.agent){delete e.agent}}canHandleAuthentication(e){if(e&&e.message&&e.message.statusCode===401){const t=e.message.headers["www-authenticate"];return t&&t.split(", ").indexOf("NTLM")>=0}return false}handleAuthentication(e,t,i){return new Promise(((n,r)=>{const callbackForResult=function(e,t){if(e){r(e);return}t.readBody().then((()=>{n(t)}))};this.handleAuthenticationPrivate(e,t,i,callbackForResult)}))}handleAuthenticationPrivate(e,t,i,o){t.options=s.extend(t.options,{username:this._ntlmOptions.username,password:this._ntlmOptions.password,domain:this._ntlmOptions.domain,workstation:this._ntlmOptions.workstation});t.options.agent=e.isSsl?new r.Agent({keepAlive:true}):new n.Agent({keepAlive:true});let a=this;this.sendType1Message(e,t,i,(function(n,r){if(n){return o(n,null,null)}r.readBody().then((()=>{setImmediate((function(){a.sendType3Message(e,t,i,r,o)}))}))}))}sendType1Message(e,t,i,n){const r=o.encodeType1(this._ntlmOptions.workstation,this._ntlmOptions.domain);const a=`NTLM ${r.toString("base64")}`;const l={headers:{Connection:"keep-alive",Authorization:a},timeout:t.options.timeout||0,agent:t.httpModule};const u={};u.httpModule=t.httpModule;u.parsedUrl=t.parsedUrl;u.options=s.extend(l,s.omit(t.options,"headers"));return e.requestRawWithCallback(u,i,n)}sendType3Message(e,t,i,n,r){if(!n.message.headers&&!n.message.headers["www-authenticate"]){throw new Error("www-authenticate not found on response of second request")}const a=/^NTLM\s+(.+?)(,|\s+|$)/;const l=Buffer.from((n.message.headers["www-authenticate"].match(a)||[])[1],"base64");let u;try{u=o.decodeType2(l)}catch(e){throw new Error(`Decoding Server's Challenge to Obtain Type2Message failed with error: ${e.message}`)}const c=o.encodeType3(this._ntlmOptions.username,this._ntlmOptions.workstation,this._ntlmOptions.domain,u,this._ntlmOptions.password).toString("base64");const d={headers:{Authorization:`NTLM ${c}`,Connection:"Close"},agent:t.httpModule};const p={};p.httpModule=t.httpModule;p.parsedUrl=t.parsedUrl;d.headers=s.extend(d.headers,t.options.headers);p.options=s.extend(d,s.omit(t.options,"headers"));return e.requestRawWithCallback(p,i,r)}}t.NtlmCredentialHandler=NtlmCredentialHandler},4578:(e,t)=>{Object.defineProperty(t,"__esModule",{value:true});t.PersonalAccessTokenCredentialHandler=void 0;class PersonalAccessTokenCredentialHandler{constructor(e,t){this.token=e;this.allowCrossOriginAuthentication=t}prepareRequest(e){if(!this.origin){this.origin=e.host}if(this.origin===e.host||this.allowCrossOriginAuthentication){e.headers["Authorization"]=`Basic ${Buffer.from(`PAT:${this.token}`).toString("base64")}`}e.headers["X-TFS-FedAuthRedirect"]="Suppress"}canHandleAuthentication(e){return false}handleAuthentication(e,t,i){return null}}t.PersonalAccessTokenCredentialHandler=PersonalAccessTokenCredentialHandler},8256:(e,t,i)=>{var n=i(6982);function zeroextend(e,t){while(e.length>n&1))%2}e[t]|=i&1}return e}function expandkey(e){var t=Buffer.alloc(8);t[0]=e[0]&254;t[1]=e[0]<<7&255|e[1]>>1;t[2]=e[1]<<6&255|e[2]>>2;t[3]=e[2]<<5&255|e[3]>>3;t[4]=e[3]<<4&255|e[4]>>4;t[5]=e[4]<<3&255|e[5]>>5;t[6]=e[5]<<2&255|e[6]>>6;t[7]=e[6]<<1&255;return t}function bintohex(e){var t=Buffer.isBuffer(t)?t:Buffer.from(e,"binary");var i=t.toString("hex").toUpperCase();return zeroextend(i,32)}e.exports.zeroextend=zeroextend;e.exports.oddpar=oddpar;e.exports.expandkey=expandkey;e.exports.bintohex=bintohex},1624:(e,t,i)=>{var n=console.log;var r=i(6982);var s=i(8256);var o=i(5919).lmhashbuf;var a=i(5919).nthashbuf;var l=i(769);function encodeType1(e,t){e=e.toUpperCase();t=t.toUpperCase();var i=Buffer.byteLength(e,"ascii");var n=Buffer.byteLength(t,"ascii");var r=0;var s=Buffer.alloc(32+i+n);s.write("NTLMSSP",r,7,"ascii");r+=7;s.writeUInt8(0,r);r++;s.writeUInt8(1,r);r++;s.fill(0,r,r+3);r+=3;s.writeUInt16LE(45571,r);r+=2;s.fill(0,r,r+2);r+=2;s.writeUInt16LE(n,r);r+=2;s.writeUInt16LE(n,r);r+=2;var o=32+i;s.writeUInt16LE(o,r);r+=2;s.fill(0,r,r+2);r+=2;s.writeUInt16LE(i,r);r+=2;s.writeUInt16LE(i,r);r+=2;s.writeUInt16LE(32,r);r+=2;s.fill(0,r,r+2);r+=2;s.write(e,32,i,"ascii");s.write(t,o,n,"ascii");return s}function decodeType2(e){var t=e.toString("ascii",0,7);if(e[7]!==0||t!=="NTLMSSP")throw new Error("magic was not NTLMSSP");var i=e.readUInt8(8);if(i!==2)throw new Error("message was not NTLMSSP type 0x02");var n=e.slice(24,32);return n}function encodeType3(e,t,i,n,r){t=t.toUpperCase();i=i.toUpperCase();var s=Buffer.alloc(21);o(r).copy(s);s.fill(0,16);var l=Buffer.alloc(21);a(r).copy(l);l.fill(0,16);var u=makeResponse(s,n);var c=makeResponse(l,n);var d=Buffer.byteLength(e,"ucs2");var p=Buffer.byteLength(t,"ucs2");var A=Buffer.byteLength(i,"ucs2");var f=24;var h=24;var g=64;var y=g+A;var m=y+d;var I=m+p;var v=I+f;var E=0;var C=64+A+d+p+f+h;var T=Buffer.alloc(C);T.write("NTLMSSP",E,7,"ascii");E+=7;T.writeUInt8(0,E);E++;T.writeUInt8(3,E);E++;T.fill(0,E,E+3);E+=3;T.writeUInt16LE(f,E);E+=2;T.writeUInt16LE(f,E);E+=2;T.writeUInt16LE(I,E);E+=2;T.fill(0,E,E+2);E+=2;T.writeUInt16LE(h,E);E+=2;T.writeUInt16LE(h,E);E+=2;T.writeUInt16LE(v,E);E+=2;T.fill(0,E,E+2);E+=2;T.writeUInt16LE(A,E);E+=2;T.writeUInt16LE(A,E);E+=2;T.writeUInt16LE(g,E);E+=2;T.fill(0,E,E+2);E+=2;T.writeUInt16LE(d,E);E+=2;T.writeUInt16LE(d,E);E+=2;T.writeUInt16LE(y,E);E+=2;T.fill(0,E,E+2);E+=2;T.writeUInt16LE(p,E);E+=2;T.writeUInt16LE(p,E);E+=2;T.writeUInt16LE(m,E);E+=2;T.fill(0,E,E+6);E+=6;T.writeUInt16LE(C,E);E+=2;T.fill(0,E,E+2);E+=2;T.writeUInt16LE(33281,E);E+=2;T.fill(0,E,E+2);E+=2;T.write(i,g,A,"ucs2");T.write(e,y,d,"ucs2");T.write(t,m,p,"ucs2");u.copy(T,I,0,f);c.copy(T,v,0,h);return T}function makeResponse(e,t){var i=Buffer.alloc(24);for(var n=0;n<3;n++){var r=s.oddpar(s.expandkey(e.slice(n*7,n*7+7)));var o=l.DES.create({type:"encrypt",key:r});var a=Buffer.from(t.toString("binary"));var u=Buffer.from(o.update(a));i.fill(u,n*8,n*8+8,"binary")}return i}t.encodeType1=encodeType1;t.decodeType2=decodeType2;t.encodeType3=encodeType3;t.challengeHeader=function(e,i){return"NTLM "+t.encodeType1(e,i).toString("base64")};t.responseHeader=function(e,n,r,s,o){var a=Buffer.from((e.headers["www-authenticate"].match(/^NTLM\s+(.+?)(,|\s+|$)/)||[])[1],"base64");var l=i(7016).parse(n).hostname;return"NTLM "+t.encodeType3(s,l,r,t.decodeType2(a),o).toString("base64")};t.smbhash=i(5919)},5919:(e,t,i)=>{var n=i(8256);var r=i(4915);var s=i(769);function lmhashbuf(e){var t=e.substring(0,14).toUpperCase();var i=Buffer.byteLength(t,"ascii");var r=Buffer.alloc(14);r.write(t,0,i,"ascii");r.fill(0,i);var o=[n.oddpar(n.expandkey(r.slice(0,7))),n.oddpar(n.expandkey(r.slice(7,14)))];var a=Buffer.alloc(16);var l=0;var u=o.forEach((function(e){var t=s.DES.create({type:"encrypt",key:e});var i=Buffer.from("KGS!@#$%","ascii");var n=Buffer.from(t.update(i));a.fill(n,l,l+8,"binary");l+=8}));return a}function nthashbuf(e){var t=Buffer.from(e,"ucs2");var i=r.create();i.update(t);return Buffer.from(i.digest("binary"),"binary")}function lmhash(e){return n.bintohex(lmhashbuf(e))}function nthash(e){return n.bintohex(nthashbuf(e))}e.exports.nthashbuf=nthashbuf;e.exports.lmhashbuf=lmhashbuf;e.exports.nthash=nthash;e.exports.lmhash=lmhash},6752:(e,t,i)=>{var n;const r=i(3701);const s=i(883);const o=i(628);const a=i(837);const l=i(7405);const u=i(6672);const c=i(3137);const d=i(50);const p=i(8707);const A=i(3440);const{InvalidArgumentError:f}=p;const h=i(6615);const g=i(9136);const y=i(7365);const m=i(7501);const I=i(4004);const v=i(2429);const E=i(7816);const{getGlobalDispatcher:C,setGlobalDispatcher:T}=i(2581);const R=i(8155);const b=i(8754);const w=i(5092);Object.assign(s.prototype,h);n=s;n=r;n=o;n=a;n=l;n=u;n=c;n=d;n=E;n=R;n=b;n=w;n={redirect:i(1514),retry:i(2026),dump:i(8060),dns:i(379)};n=g;n=p;n={parseHeaders:A.parseHeaders,headerNameToString:A.headerNameToString};function makeDispatcher(e){return(t,i,n)=>{if(typeof i==="function"){n=i;i=null}if(!t||typeof t!=="string"&&typeof t!=="object"&&!(t instanceof URL)){throw new f("invalid url")}if(i!=null&&typeof i!=="object"){throw new f("invalid opts")}if(i&&i.path!=null){if(typeof i.path!=="string"){throw new f("invalid opts.path")}let e=i.path;if(!i.path.startsWith("/")){e=`/${e}`}t=new URL(A.parseOrigin(t).origin+e)}else{if(!i){i=typeof t==="object"?t:{}}t=A.parseURL(t)}const{agent:r,dispatcher:s=C()}=i;if(r){throw new f("unsupported opts.agent. Did you mean opts.client?")}return e.call(s,{...i,origin:t.origin,path:t.search?`${t.pathname}${t.search}`:t.pathname,method:i.method||(i.body?"PUT":"GET")},n)}}n=T;n=C;const B=i(4398).fetch;n=async function fetch(e,t=undefined){try{return await B(e,t)}catch(e){if(e&&typeof e==="object"){Error.captureStackTrace(e)}throw e}};i(660).Headers;i(9051).Response;i(9967).Request;i(5910).FormData;n=globalThis.File??i(4573).File;i(8355).FileReader;const{setGlobalOrigin:D,getGlobalOrigin:S}=i(1059);n=D;n=S;const{CacheStorage:k}=i(3245);const{kConstruct:P}=i(109);n=new k(P);const{deleteCookie:U,getCookies:V,getSetCookies:F,setCookie:O}=i(9061);n=U;n=V;n=F;n=O;const{parseMIMEType:N,serializeAMimeType:q}=i(1900);n=N;n=q;const{CloseEvent:_,ErrorEvent:M,MessageEvent:L}=i(5188);i(3726).WebSocket;n=_;n=M;n=L;n=makeDispatcher(h.request);n=makeDispatcher(h.stream);n=makeDispatcher(h.pipeline);n=makeDispatcher(h.connect);n=makeDispatcher(h.upgrade);n=y;n=I;n=m;n=v;const{EventSource:G}=i(1238);n=G},158:(e,t,i)=>{const{addAbortListener:n}=i(3440);const{RequestAbortedError:r}=i(8707);const s=Symbol("kListener");const o=Symbol("kSignal");function abort(e){if(e.abort){e.abort(e[o]?.reason)}else{e.reason=e[o]?.reason??new r}removeSignal(e)}function addSignal(e,t){e.reason=null;e[o]=null;e[s]=null;if(!t){return}if(t.aborted){abort(e);return}e[o]=t;e[s]=()=>{abort(e)};n(e[o],e[s])}function removeSignal(e){if(!e[o]){return}if("removeEventListener"in e[o]){e[o].removeEventListener("abort",e[s])}else{e[o].removeListener("abort",e[s])}e[o]=null;e[s]=null}e.exports={addSignal:addSignal,removeSignal:removeSignal}},2279:(e,t,i)=>{const n=i(4589);const{AsyncResource:r}=i(6698);const{InvalidArgumentError:s,SocketError:o}=i(8707);const a=i(3440);const{addSignal:l,removeSignal:u}=i(158);class ConnectHandler extends r{constructor(e,t){if(!e||typeof e!=="object"){throw new s("invalid opts")}if(typeof t!=="function"){throw new s("invalid callback")}const{signal:i,opaque:n,responseHeaders:r}=e;if(i&&typeof i.on!=="function"&&typeof i.addEventListener!=="function"){throw new s("signal must be an EventEmitter or EventTarget")}super("UNDICI_CONNECT");this.opaque=n||null;this.responseHeaders=r||null;this.callback=t;this.abort=null;l(this,i)}onConnect(e,t){if(this.reason){e(this.reason);return}n(this.callback);this.abort=e;this.context=t}onHeaders(){throw new o("bad connect",null)}onUpgrade(e,t,i){const{callback:n,opaque:r,context:s}=this;u(this);this.callback=null;let o=t;if(o!=null){o=this.responseHeaders==="raw"?a.parseRawHeaders(t):a.parseHeaders(t)}this.runInAsyncScope(n,null,null,{statusCode:e,headers:o,socket:i,opaque:r,context:s})}onError(e){const{callback:t,opaque:i}=this;u(this);if(t){this.callback=null;queueMicrotask((()=>{this.runInAsyncScope(t,null,e,{opaque:i})}))}}}function connect(e,t){if(t===undefined){return new Promise(((t,i)=>{connect.call(this,e,((e,n)=>e?i(e):t(n)))}))}try{const i=new ConnectHandler(e,t);this.dispatch({...e,method:"CONNECT"},i)}catch(i){if(typeof t!=="function"){throw i}const n=e?.opaque;queueMicrotask((()=>t(i,{opaque:n})))}}e.exports=connect},6862:(e,t,i)=>{const{Readable:n,Duplex:r,PassThrough:s}=i(7075);const{InvalidArgumentError:o,InvalidReturnValueError:a,RequestAbortedError:l}=i(8707);const u=i(3440);const{AsyncResource:c}=i(6698);const{addSignal:d,removeSignal:p}=i(158);const A=i(4589);const f=Symbol("resume");class PipelineRequest extends n{constructor(){super({autoDestroy:true});this[f]=null}_read(){const{[f]:e}=this;if(e){this[f]=null;e()}}_destroy(e,t){this._read();t(e)}}class PipelineResponse extends n{constructor(e){super({autoDestroy:true});this[f]=e}_read(){this[f]()}_destroy(e,t){if(!e&&!this._readableState.endEmitted){e=new l}t(e)}}class PipelineHandler extends c{constructor(e,t){if(!e||typeof e!=="object"){throw new o("invalid opts")}if(typeof t!=="function"){throw new o("invalid handler")}const{signal:i,method:n,opaque:s,onInfo:a,responseHeaders:c}=e;if(i&&typeof i.on!=="function"&&typeof i.addEventListener!=="function"){throw new o("signal must be an EventEmitter or EventTarget")}if(n==="CONNECT"){throw new o("invalid method")}if(a&&typeof a!=="function"){throw new o("invalid onInfo callback")}super("UNDICI_PIPELINE");this.opaque=s||null;this.responseHeaders=c||null;this.handler=t;this.abort=null;this.context=null;this.onInfo=a||null;this.req=(new PipelineRequest).on("error",u.nop);this.ret=new r({readableObjectMode:e.objectMode,autoDestroy:true,read:()=>{const{body:e}=this;if(e?.resume){e.resume()}},write:(e,t,i)=>{const{req:n}=this;if(n.push(e,t)||n._readableState.destroyed){i()}else{n[f]=i}},destroy:(e,t)=>{const{body:i,req:n,res:r,ret:s,abort:o}=this;if(!e&&!s._readableState.endEmitted){e=new l}if(o&&e){o()}u.destroy(i,e);u.destroy(n,e);u.destroy(r,e);p(this);t(e)}}).on("prefinish",(()=>{const{req:e}=this;e.push(null)}));this.res=null;d(this,i)}onConnect(e,t){const{ret:i,res:n}=this;if(this.reason){e(this.reason);return}A(!n,"pipeline cannot be retried");A(!i.destroyed);this.abort=e;this.context=t}onHeaders(e,t,i){const{opaque:n,handler:r,context:s}=this;if(e<200){if(this.onInfo){const i=this.responseHeaders==="raw"?u.parseRawHeaders(t):u.parseHeaders(t);this.onInfo({statusCode:e,headers:i})}return}this.res=new PipelineResponse(i);let o;try{this.handler=null;const i=this.responseHeaders==="raw"?u.parseRawHeaders(t):u.parseHeaders(t);o=this.runInAsyncScope(r,null,{statusCode:e,headers:i,opaque:n,body:this.res,context:s})}catch(e){this.res.on("error",u.nop);throw e}if(!o||typeof o.on!=="function"){throw new a("expected Readable")}o.on("data",(e=>{const{ret:t,body:i}=this;if(!t.push(e)&&i.pause){i.pause()}})).on("error",(e=>{const{ret:t}=this;u.destroy(t,e)})).on("end",(()=>{const{ret:e}=this;e.push(null)})).on("close",(()=>{const{ret:e}=this;if(!e._readableState.ended){u.destroy(e,new l)}}));this.body=o}onData(e){const{res:t}=this;return t.push(e)}onComplete(e){const{res:t}=this;t.push(null)}onError(e){const{ret:t}=this;this.handler=null;u.destroy(t,e)}}function pipeline(e,t){try{const i=new PipelineHandler(e,t);this.dispatch({...e,body:i.req},i);return i.ret}catch(e){return(new s).destroy(e)}}e.exports=pipeline},4043:(e,t,i)=>{const n=i(4589);const{Readable:r}=i(9927);const{InvalidArgumentError:s,RequestAbortedError:o}=i(8707);const a=i(3440);const{getResolveErrorBodyCallback:l}=i(7655);const{AsyncResource:u}=i(6698);class RequestHandler extends u{constructor(e,t){if(!e||typeof e!=="object"){throw new s("invalid opts")}const{signal:i,method:n,opaque:r,body:l,onInfo:u,responseHeaders:c,throwOnError:d,highWaterMark:p}=e;try{if(typeof t!=="function"){throw new s("invalid callback")}if(p&&(typeof p!=="number"||p<0)){throw new s("invalid highWaterMark")}if(i&&typeof i.on!=="function"&&typeof i.addEventListener!=="function"){throw new s("signal must be an EventEmitter or EventTarget")}if(n==="CONNECT"){throw new s("invalid method")}if(u&&typeof u!=="function"){throw new s("invalid onInfo callback")}super("UNDICI_REQUEST")}catch(e){if(a.isStream(l)){a.destroy(l.on("error",a.nop),e)}throw e}this.method=n;this.responseHeaders=c||null;this.opaque=r||null;this.callback=t;this.res=null;this.abort=null;this.body=l;this.trailers={};this.context=null;this.onInfo=u||null;this.throwOnError=d;this.highWaterMark=p;this.signal=i;this.reason=null;this.removeAbortListener=null;if(a.isStream(l)){l.on("error",(e=>{this.onError(e)}))}if(this.signal){if(this.signal.aborted){this.reason=this.signal.reason??new o}else{this.removeAbortListener=a.addAbortListener(this.signal,(()=>{this.reason=this.signal.reason??new o;if(this.res){a.destroy(this.res.on("error",a.nop),this.reason)}else if(this.abort){this.abort(this.reason)}if(this.removeAbortListener){this.res?.off("close",this.removeAbortListener);this.removeAbortListener();this.removeAbortListener=null}}))}}}onConnect(e,t){if(this.reason){e(this.reason);return}n(this.callback);this.abort=e;this.context=t}onHeaders(e,t,i,n){const{callback:s,opaque:o,abort:u,context:c,responseHeaders:d,highWaterMark:p}=this;const A=d==="raw"?a.parseRawHeaders(t):a.parseHeaders(t);if(e<200){if(this.onInfo){this.onInfo({statusCode:e,headers:A})}return}const f=d==="raw"?a.parseHeaders(t):A;const h=f["content-type"];const g=f["content-length"];const y=new r({resume:i,abort:u,contentType:h,contentLength:this.method!=="HEAD"&&g?Number(g):null,highWaterMark:p});if(this.removeAbortListener){y.on("close",this.removeAbortListener)}this.callback=null;this.res=y;if(s!==null){if(this.throwOnError&&e>=400){this.runInAsyncScope(l,null,{callback:s,body:y,contentType:h,statusCode:e,statusMessage:n,headers:A})}else{this.runInAsyncScope(s,null,null,{statusCode:e,headers:A,trailers:this.trailers,opaque:o,body:y,context:c})}}}onData(e){return this.res.push(e)}onComplete(e){a.parseHeaders(e,this.trailers);this.res.push(null)}onError(e){const{res:t,callback:i,body:n,opaque:r}=this;if(i){this.callback=null;queueMicrotask((()=>{this.runInAsyncScope(i,null,e,{opaque:r})}))}if(t){this.res=null;queueMicrotask((()=>{a.destroy(t,e)}))}if(n){this.body=null;a.destroy(n,e)}if(this.removeAbortListener){t?.off("close",this.removeAbortListener);this.removeAbortListener();this.removeAbortListener=null}}}function request(e,t){if(t===undefined){return new Promise(((t,i)=>{request.call(this,e,((e,n)=>e?i(e):t(n)))}))}try{this.dispatch(e,new RequestHandler(e,t))}catch(i){if(typeof t!=="function"){throw i}const n=e?.opaque;queueMicrotask((()=>t(i,{opaque:n})))}}e.exports=request;e.exports.RequestHandler=RequestHandler},3560:(e,t,i)=>{const n=i(4589);const{finished:r,PassThrough:s}=i(7075);const{InvalidArgumentError:o,InvalidReturnValueError:a}=i(8707);const l=i(3440);const{getResolveErrorBodyCallback:u}=i(7655);const{AsyncResource:c}=i(6698);const{addSignal:d,removeSignal:p}=i(158);class StreamHandler extends c{constructor(e,t,i){if(!e||typeof e!=="object"){throw new o("invalid opts")}const{signal:n,method:r,opaque:s,body:a,onInfo:u,responseHeaders:c,throwOnError:p}=e;try{if(typeof i!=="function"){throw new o("invalid callback")}if(typeof t!=="function"){throw new o("invalid factory")}if(n&&typeof n.on!=="function"&&typeof n.addEventListener!=="function"){throw new o("signal must be an EventEmitter or EventTarget")}if(r==="CONNECT"){throw new o("invalid method")}if(u&&typeof u!=="function"){throw new o("invalid onInfo callback")}super("UNDICI_STREAM")}catch(e){if(l.isStream(a)){l.destroy(a.on("error",l.nop),e)}throw e}this.responseHeaders=c||null;this.opaque=s||null;this.factory=t;this.callback=i;this.res=null;this.abort=null;this.context=null;this.trailers=null;this.body=a;this.onInfo=u||null;this.throwOnError=p||false;if(l.isStream(a)){a.on("error",(e=>{this.onError(e)}))}d(this,n)}onConnect(e,t){if(this.reason){e(this.reason);return}n(this.callback);this.abort=e;this.context=t}onHeaders(e,t,i,n){const{factory:o,opaque:c,context:d,callback:p,responseHeaders:A}=this;const f=A==="raw"?l.parseRawHeaders(t):l.parseHeaders(t);if(e<200){if(this.onInfo){this.onInfo({statusCode:e,headers:f})}return}this.factory=null;let h;if(this.throwOnError&&e>=400){const i=A==="raw"?l.parseHeaders(t):f;const r=i["content-type"];h=new s;this.callback=null;this.runInAsyncScope(u,null,{callback:p,body:h,contentType:r,statusCode:e,statusMessage:n,headers:f})}else{if(o===null){return}h=this.runInAsyncScope(o,null,{statusCode:e,headers:f,opaque:c,context:d});if(!h||typeof h.write!=="function"||typeof h.end!=="function"||typeof h.on!=="function"){throw new a("expected Writable")}r(h,{readable:false},(e=>{const{callback:t,res:i,opaque:n,trailers:r,abort:s}=this;this.res=null;if(e||!i.readable){l.destroy(i,e)}this.callback=null;this.runInAsyncScope(t,null,e||null,{opaque:n,trailers:r});if(e){s()}}))}h.on("drain",i);this.res=h;const g=h.writableNeedDrain!==undefined?h.writableNeedDrain:h._writableState?.needDrain;return g!==true}onData(e){const{res:t}=this;return t?t.write(e):true}onComplete(e){const{res:t}=this;p(this);if(!t){return}this.trailers=l.parseHeaders(e);t.end()}onError(e){const{res:t,callback:i,opaque:n,body:r}=this;p(this);this.factory=null;if(t){this.res=null;l.destroy(t,e)}else if(i){this.callback=null;queueMicrotask((()=>{this.runInAsyncScope(i,null,e,{opaque:n})}))}if(r){this.body=null;l.destroy(r,e)}}}function stream(e,t,i){if(i===undefined){return new Promise(((i,n)=>{stream.call(this,e,t,((e,t)=>e?n(e):i(t)))}))}try{this.dispatch(e,new StreamHandler(e,t,i))}catch(t){if(typeof i!=="function"){throw t}const n=e?.opaque;queueMicrotask((()=>i(t,{opaque:n})))}}e.exports=stream},1882:(e,t,i)=>{const{InvalidArgumentError:n,SocketError:r}=i(8707);const{AsyncResource:s}=i(6698);const o=i(3440);const{addSignal:a,removeSignal:l}=i(158);const u=i(4589);class UpgradeHandler extends s{constructor(e,t){if(!e||typeof e!=="object"){throw new n("invalid opts")}if(typeof t!=="function"){throw new n("invalid callback")}const{signal:i,opaque:r,responseHeaders:s}=e;if(i&&typeof i.on!=="function"&&typeof i.addEventListener!=="function"){throw new n("signal must be an EventEmitter or EventTarget")}super("UNDICI_UPGRADE");this.responseHeaders=s||null;this.opaque=r||null;this.callback=t;this.abort=null;this.context=null;a(this,i)}onConnect(e,t){if(this.reason){e(this.reason);return}u(this.callback);this.abort=e;this.context=null}onHeaders(){throw new r("bad upgrade",null)}onUpgrade(e,t,i){u(e===101);const{callback:n,opaque:r,context:s}=this;l(this);this.callback=null;const a=this.responseHeaders==="raw"?o.parseRawHeaders(t):o.parseHeaders(t);this.runInAsyncScope(n,null,null,{headers:a,socket:i,opaque:r,context:s})}onError(e){const{callback:t,opaque:i}=this;l(this);if(t){this.callback=null;queueMicrotask((()=>{this.runInAsyncScope(t,null,e,{opaque:i})}))}}}function upgrade(e,t){if(t===undefined){return new Promise(((t,i)=>{upgrade.call(this,e,((e,n)=>e?i(e):t(n)))}))}try{const i=new UpgradeHandler(e,t);this.dispatch({...e,method:e.method||"GET",upgrade:e.protocol||"Websocket"},i)}catch(i){if(typeof t!=="function"){throw i}const n=e?.opaque;queueMicrotask((()=>t(i,{opaque:n})))}}e.exports=upgrade},6615:(e,t,i)=>{e.exports.request=i(4043);e.exports.stream=i(3560);e.exports.pipeline=i(6862);e.exports.upgrade=i(1882);e.exports.connect=i(2279)},9927:(e,t,i)=>{const n=i(4589);const{Readable:r}=i(7075);const{RequestAbortedError:s,NotSupportedError:o,InvalidArgumentError:a,AbortError:l}=i(8707);const u=i(3440);const{ReadableStreamFrom:c}=i(3440);const d=Symbol("kConsume");const p=Symbol("kReading");const A=Symbol("kBody");const f=Symbol("kAbort");const h=Symbol("kContentType");const g=Symbol("kContentLength");const noop=()=>{};class BodyReadable extends r{constructor({resume:e,abort:t,contentType:i="",contentLength:n,highWaterMark:r=64*1024}){super({autoDestroy:true,read:e,highWaterMark:r});this._readableState.dataEmitted=false;this[f]=t;this[d]=null;this[A]=null;this[h]=i;this[g]=n;this[p]=false}destroy(e){if(!e&&!this._readableState.endEmitted){e=new s}if(e){this[f]()}return super.destroy(e)}_destroy(e,t){if(!this[p]){setImmediate((()=>{t(e)}))}else{t(e)}}on(e,...t){if(e==="data"||e==="readable"){this[p]=true}return super.on(e,...t)}addListener(e,...t){return this.on(e,...t)}off(e,...t){const i=super.off(e,...t);if(e==="data"||e==="readable"){this[p]=this.listenerCount("data")>0||this.listenerCount("readable")>0}return i}removeListener(e,...t){return this.off(e,...t)}push(e){if(this[d]&&e!==null){consumePush(this[d],e);return this[p]?super.push(e):true}return super.push(e)}async text(){return consume(this,"text")}async json(){return consume(this,"json")}async blob(){return consume(this,"blob")}async bytes(){return consume(this,"bytes")}async arrayBuffer(){return consume(this,"arrayBuffer")}async formData(){throw new o}get bodyUsed(){return u.isDisturbed(this)}get body(){if(!this[A]){this[A]=c(this);if(this[d]){this[A].getReader();n(this[A].locked)}}return this[A]}async dump(e){let t=Number.isFinite(e?.limit)?e.limit:128*1024;const i=e?.signal;if(i!=null&&(typeof i!=="object"||!("aborted"in i))){throw new a("signal must be an AbortSignal")}i?.throwIfAborted();if(this._readableState.closeEmitted){return null}return await new Promise(((e,n)=>{if(this[g]>t){this.destroy(new l)}const onAbort=()=>{this.destroy(i.reason??new l)};i?.addEventListener("abort",onAbort);this.on("close",(function(){i?.removeEventListener("abort",onAbort);if(i?.aborted){n(i.reason??new l)}else{e(null)}})).on("error",noop).on("data",(function(e){t-=e.length;if(t<=0){this.destroy()}})).resume()}))}}function isLocked(e){return e[A]&&e[A].locked===true||e[d]}function isUnusable(e){return u.isDisturbed(e)||isLocked(e)}async function consume(e,t){n(!e[d]);return new Promise(((i,n)=>{if(isUnusable(e)){const t=e._readableState;if(t.destroyed&&t.closeEmitted===false){e.on("error",(e=>{n(e)})).on("close",(()=>{n(new TypeError("unusable"))}))}else{n(t.errored??new TypeError("unusable"))}}else{queueMicrotask((()=>{e[d]={type:t,stream:e,resolve:i,reject:n,length:0,body:[]};e.on("error",(function(e){consumeFinish(this[d],e)})).on("close",(function(){if(this[d].body!==null){consumeFinish(this[d],new s)}}));consumeStart(e[d])}))}}))}function consumeStart(e){if(e.body===null){return}const{_readableState:t}=e.stream;if(t.bufferIndex){const i=t.bufferIndex;const n=t.buffer.length;for(let r=i;r2&&i[0]===239&&i[1]===187&&i[2]===191?3:0;return i.utf8Slice(r,n)}function chunksConcat(e,t){if(e.length===0||t===0){return new Uint8Array(0)}if(e.length===1){return new Uint8Array(e[0])}const i=new Uint8Array(Buffer.allocUnsafeSlow(t).buffer);let n=0;for(let t=0;t{const n=i(4589);const{ResponseStatusCodeError:r}=i(8707);const{chunksDecode:s}=i(9927);const o=128*1024;async function getResolveErrorBodyCallback({callback:e,body:t,contentType:i,statusCode:a,statusMessage:l,headers:u}){n(t);let c=[];let d=0;try{for await(const e of t){c.push(e);d+=e.length;if(d>o){c=[];d=0;break}}}catch{c=[];d=0}const p=`Response status code ${a}${l?`: ${l}`:""}`;if(a===204||!i||!d){queueMicrotask((()=>e(new r(p,a,u))));return}const A=Error.stackTraceLimit;Error.stackTraceLimit=0;let f;try{if(isContentTypeApplicationJson(i)){f=JSON.parse(s(c,d))}else if(isContentTypeText(i)){f=s(c,d)}}catch{}finally{Error.stackTraceLimit=A}queueMicrotask((()=>e(new r(p,a,u,f))))}const isContentTypeApplicationJson=e=>e.length>15&&e[11]==="/"&&e[0]==="a"&&e[1]==="p"&&e[2]==="p"&&e[3]==="l"&&e[4]==="i"&&e[5]==="c"&&e[6]==="a"&&e[7]==="t"&&e[8]==="i"&&e[9]==="o"&&e[10]==="n"&&e[12]==="j"&&e[13]==="s"&&e[14]==="o"&&e[15]==="n";const isContentTypeText=e=>e.length>4&&e[4]==="/"&&e[0]==="t"&&e[1]==="e"&&e[2]==="x"&&e[3]==="t";e.exports={getResolveErrorBodyCallback:getResolveErrorBodyCallback,isContentTypeApplicationJson:isContentTypeApplicationJson,isContentTypeText:isContentTypeText}},9136:(e,t,i)=>{const n=i(7030);const r=i(4589);const s=i(3440);const{InvalidArgumentError:o,ConnectTimeoutError:a}=i(8707);const l=i(6603);function noop(){}let u;let c;if(global.FinalizationRegistry&&!(process.env.NODE_V8_COVERAGE||process.env.UNDICI_NO_FG)){c=class WeakSessionCache{constructor(e){this._maxCachedSessions=e;this._sessionCache=new Map;this._sessionRegistry=new global.FinalizationRegistry((e=>{if(this._sessionCache.size=this._maxCachedSessions){const{value:e}=this._sessionCache.keys().next();this._sessionCache.delete(e)}this._sessionCache.set(e,t)}}}function buildConnector({allowH2:e,maxCachedSessions:t,socketPath:a,timeout:l,session:p,...A}){if(t!=null&&(!Number.isInteger(t)||t<0)){throw new o("maxCachedSessions must be a positive integer or zero")}const f={path:a,...A};const h=new c(t==null?100:t);l=l==null?1e4:l;e=e!=null?e:false;return function connect({hostname:t,host:o,protocol:a,port:c,servername:A,localAddress:g,httpSocket:y},m){let I;if(a==="https:"){if(!u){u=i(1692)}A=A||f.servername||s.getServerName(o)||null;const n=A||t;r(n);const a=p||h.get(n)||null;c=c||443;I=u.connect({highWaterMark:16384,...f,servername:A,session:a,localAddress:g,ALPNProtocols:e?["http/1.1","h2"]:["http/1.1"],socket:y,port:c,host:t});I.on("session",(function(e){h.set(n,e)}))}else{r(!y,"httpSocket can only be sent on TLS update");c=c||80;I=n.connect({highWaterMark:64*1024,...f,localAddress:g,port:c,host:t})}if(f.keepAlive==null||f.keepAlive){const e=f.keepAliveInitialDelay===undefined?6e4:f.keepAliveInitialDelay;I.setKeepAlive(true,e)}const v=d(new WeakRef(I),{timeout:l,hostname:t,port:c});I.setNoDelay(true).once(a==="https:"?"secureConnect":"connect",(function(){queueMicrotask(v);if(m){const e=m;m=null;e(null,this)}})).on("error",(function(e){queueMicrotask(v);if(m){const t=m;m=null;t(e)}}));return I}}const d=process.platform==="win32"?(e,t)=>{if(!t.timeout){return noop}let i=null;let n=null;const r=l.setFastTimeout((()=>{i=setImmediate((()=>{n=setImmediate((()=>onConnectTimeout(e.deref(),t)))}))}),t.timeout);return()=>{l.clearFastTimeout(r);clearImmediate(i);clearImmediate(n)}}:(e,t)=>{if(!t.timeout){return noop}let i=null;const n=l.setFastTimeout((()=>{i=setImmediate((()=>{onConnectTimeout(e.deref(),t)}))}),t.timeout);return()=>{l.clearFastTimeout(n);clearImmediate(i)}};function onConnectTimeout(e,t){if(e==null){return}let i="Connect Timeout Error";if(Array.isArray(e.autoSelectFamilyAttemptedAddresses)){i+=` (attempted addresses: ${e.autoSelectFamilyAttemptedAddresses.join(", ")},`}else{i+=` (attempted address: ${t.hostname}:${t.port},`}i+=` timeout: ${t.timeout}ms)`;s.destroy(e,new a(i))}e.exports=buildConnector},735:e=>{const t={};const i=["Accept","Accept-Encoding","Accept-Language","Accept-Ranges","Access-Control-Allow-Credentials","Access-Control-Allow-Headers","Access-Control-Allow-Methods","Access-Control-Allow-Origin","Access-Control-Expose-Headers","Access-Control-Max-Age","Access-Control-Request-Headers","Access-Control-Request-Method","Age","Allow","Alt-Svc","Alt-Used","Authorization","Cache-Control","Clear-Site-Data","Connection","Content-Disposition","Content-Encoding","Content-Language","Content-Length","Content-Location","Content-Range","Content-Security-Policy","Content-Security-Policy-Report-Only","Content-Type","Cookie","Cross-Origin-Embedder-Policy","Cross-Origin-Opener-Policy","Cross-Origin-Resource-Policy","Date","Device-Memory","Downlink","ECT","ETag","Expect","Expect-CT","Expires","Forwarded","From","Host","If-Match","If-Modified-Since","If-None-Match","If-Range","If-Unmodified-Since","Keep-Alive","Last-Modified","Link","Location","Max-Forwards","Origin","Permissions-Policy","Pragma","Proxy-Authenticate","Proxy-Authorization","RTT","Range","Referer","Referrer-Policy","Refresh","Retry-After","Sec-WebSocket-Accept","Sec-WebSocket-Extensions","Sec-WebSocket-Key","Sec-WebSocket-Protocol","Sec-WebSocket-Version","Server","Server-Timing","Service-Worker-Allowed","Service-Worker-Navigation-Preload","Set-Cookie","SourceMap","Strict-Transport-Security","Supports-Loading-Mode","TE","Timing-Allow-Origin","Trailer","Transfer-Encoding","Upgrade","Upgrade-Insecure-Requests","User-Agent","Vary","Via","WWW-Authenticate","X-Content-Type-Options","X-DNS-Prefetch-Control","X-Frame-Options","X-Permitted-Cross-Domain-Policies","X-Powered-By","X-Requested-With","X-XSS-Protection"];for(let e=0;e{const n=i(3053);const r=i(7975);const s=r.debuglog("undici");const o=r.debuglog("fetch");const a=r.debuglog("websocket");let l=false;const u={beforeConnect:n.channel("undici:client:beforeConnect"),connected:n.channel("undici:client:connected"),connectError:n.channel("undici:client:connectError"),sendHeaders:n.channel("undici:client:sendHeaders"),create:n.channel("undici:request:create"),bodySent:n.channel("undici:request:bodySent"),headers:n.channel("undici:request:headers"),trailers:n.channel("undici:request:trailers"),error:n.channel("undici:request:error"),open:n.channel("undici:websocket:open"),close:n.channel("undici:websocket:close"),socketError:n.channel("undici:websocket:socket_error"),ping:n.channel("undici:websocket:ping"),pong:n.channel("undici:websocket:pong")};if(s.enabled||o.enabled){const e=o.enabled?o:s;n.channel("undici:client:beforeConnect").subscribe((t=>{const{connectParams:{version:i,protocol:n,port:r,host:s}}=t;e("connecting to %s using %s%s",`${s}${r?`:${r}`:""}`,n,i)}));n.channel("undici:client:connected").subscribe((t=>{const{connectParams:{version:i,protocol:n,port:r,host:s}}=t;e("connected to %s using %s%s",`${s}${r?`:${r}`:""}`,n,i)}));n.channel("undici:client:connectError").subscribe((t=>{const{connectParams:{version:i,protocol:n,port:r,host:s},error:o}=t;e("connection to %s using %s%s errored - %s",`${s}${r?`:${r}`:""}`,n,i,o.message)}));n.channel("undici:client:sendHeaders").subscribe((t=>{const{request:{method:i,path:n,origin:r}}=t;e("sending request to %s %s/%s",i,r,n)}));n.channel("undici:request:headers").subscribe((t=>{const{request:{method:i,path:n,origin:r},response:{statusCode:s}}=t;e("received response to %s %s/%s - HTTP %d",i,r,n,s)}));n.channel("undici:request:trailers").subscribe((t=>{const{request:{method:i,path:n,origin:r}}=t;e("trailers received from %s %s/%s",i,r,n)}));n.channel("undici:request:error").subscribe((t=>{const{request:{method:i,path:n,origin:r},error:s}=t;e("request to %s %s/%s errored - %s",i,r,n,s.message)}));l=true}if(a.enabled){if(!l){const e=s.enabled?s:a;n.channel("undici:client:beforeConnect").subscribe((t=>{const{connectParams:{version:i,protocol:n,port:r,host:s}}=t;e("connecting to %s%s using %s%s",s,r?`:${r}`:"",n,i)}));n.channel("undici:client:connected").subscribe((t=>{const{connectParams:{version:i,protocol:n,port:r,host:s}}=t;e("connected to %s%s using %s%s",s,r?`:${r}`:"",n,i)}));n.channel("undici:client:connectError").subscribe((t=>{const{connectParams:{version:i,protocol:n,port:r,host:s},error:o}=t;e("connection to %s%s using %s%s errored - %s",s,r?`:${r}`:"",n,i,o.message)}));n.channel("undici:client:sendHeaders").subscribe((t=>{const{request:{method:i,path:n,origin:r}}=t;e("sending request to %s %s/%s",i,r,n)}))}n.channel("undici:websocket:open").subscribe((e=>{const{address:{address:t,port:i}}=e;a("connection opened %s%s",t,i?`:${i}`:"")}));n.channel("undici:websocket:close").subscribe((e=>{const{websocket:t,code:i,reason:n}=e;a("closed connection to %s - %s %s",t.url,i,n)}));n.channel("undici:websocket:socket_error").subscribe((e=>{a("connection errored - %s",e.message)}));n.channel("undici:websocket:ping").subscribe((e=>{a("ping received")}));n.channel("undici:websocket:pong").subscribe((e=>{a("pong received")}))}e.exports={channels:u}},8707:e=>{const t=Symbol.for("undici.error.UND_ERR");class UndiciError extends Error{constructor(e){super(e);this.name="UndiciError";this.code="UND_ERR"}static[Symbol.hasInstance](e){return e&&e[t]===true}[t]=true}const i=Symbol.for("undici.error.UND_ERR_CONNECT_TIMEOUT");class ConnectTimeoutError extends UndiciError{constructor(e){super(e);this.name="ConnectTimeoutError";this.message=e||"Connect Timeout Error";this.code="UND_ERR_CONNECT_TIMEOUT"}static[Symbol.hasInstance](e){return e&&e[i]===true}[i]=true}const n=Symbol.for("undici.error.UND_ERR_HEADERS_TIMEOUT");class HeadersTimeoutError extends UndiciError{constructor(e){super(e);this.name="HeadersTimeoutError";this.message=e||"Headers Timeout Error";this.code="UND_ERR_HEADERS_TIMEOUT"}static[Symbol.hasInstance](e){return e&&e[n]===true}[n]=true}const r=Symbol.for("undici.error.UND_ERR_HEADERS_OVERFLOW");class HeadersOverflowError extends UndiciError{constructor(e){super(e);this.name="HeadersOverflowError";this.message=e||"Headers Overflow Error";this.code="UND_ERR_HEADERS_OVERFLOW"}static[Symbol.hasInstance](e){return e&&e[r]===true}[r]=true}const s=Symbol.for("undici.error.UND_ERR_BODY_TIMEOUT");class BodyTimeoutError extends UndiciError{constructor(e){super(e);this.name="BodyTimeoutError";this.message=e||"Body Timeout Error";this.code="UND_ERR_BODY_TIMEOUT"}static[Symbol.hasInstance](e){return e&&e[s]===true}[s]=true}const o=Symbol.for("undici.error.UND_ERR_RESPONSE_STATUS_CODE");class ResponseStatusCodeError extends UndiciError{constructor(e,t,i,n){super(e);this.name="ResponseStatusCodeError";this.message=e||"Response Status Code Error";this.code="UND_ERR_RESPONSE_STATUS_CODE";this.body=n;this.status=t;this.statusCode=t;this.headers=i}static[Symbol.hasInstance](e){return e&&e[o]===true}[o]=true}const a=Symbol.for("undici.error.UND_ERR_INVALID_ARG");class InvalidArgumentError extends UndiciError{constructor(e){super(e);this.name="InvalidArgumentError";this.message=e||"Invalid Argument Error";this.code="UND_ERR_INVALID_ARG"}static[Symbol.hasInstance](e){return e&&e[a]===true}[a]=true}const l=Symbol.for("undici.error.UND_ERR_INVALID_RETURN_VALUE");class InvalidReturnValueError extends UndiciError{constructor(e){super(e);this.name="InvalidReturnValueError";this.message=e||"Invalid Return Value Error";this.code="UND_ERR_INVALID_RETURN_VALUE"}static[Symbol.hasInstance](e){return e&&e[l]===true}[l]=true}const u=Symbol.for("undici.error.UND_ERR_ABORT");class AbortError extends UndiciError{constructor(e){super(e);this.name="AbortError";this.message=e||"The operation was aborted";this.code="UND_ERR_ABORT"}static[Symbol.hasInstance](e){return e&&e[u]===true}[u]=true}const c=Symbol.for("undici.error.UND_ERR_ABORTED");class RequestAbortedError extends AbortError{constructor(e){super(e);this.name="AbortError";this.message=e||"Request aborted";this.code="UND_ERR_ABORTED"}static[Symbol.hasInstance](e){return e&&e[c]===true}[c]=true}const d=Symbol.for("undici.error.UND_ERR_INFO");class InformationalError extends UndiciError{constructor(e){super(e);this.name="InformationalError";this.message=e||"Request information";this.code="UND_ERR_INFO"}static[Symbol.hasInstance](e){return e&&e[d]===true}[d]=true}const p=Symbol.for("undici.error.UND_ERR_REQ_CONTENT_LENGTH_MISMATCH");class RequestContentLengthMismatchError extends UndiciError{constructor(e){super(e);this.name="RequestContentLengthMismatchError";this.message=e||"Request body length does not match content-length header";this.code="UND_ERR_REQ_CONTENT_LENGTH_MISMATCH"}static[Symbol.hasInstance](e){return e&&e[p]===true}[p]=true}const A=Symbol.for("undici.error.UND_ERR_RES_CONTENT_LENGTH_MISMATCH");class ResponseContentLengthMismatchError extends UndiciError{constructor(e){super(e);this.name="ResponseContentLengthMismatchError";this.message=e||"Response body length does not match content-length header";this.code="UND_ERR_RES_CONTENT_LENGTH_MISMATCH"}static[Symbol.hasInstance](e){return e&&e[A]===true}[A]=true}const f=Symbol.for("undici.error.UND_ERR_DESTROYED");class ClientDestroyedError extends UndiciError{constructor(e){super(e);this.name="ClientDestroyedError";this.message=e||"The client is destroyed";this.code="UND_ERR_DESTROYED"}static[Symbol.hasInstance](e){return e&&e[f]===true}[f]=true}const h=Symbol.for("undici.error.UND_ERR_CLOSED");class ClientClosedError extends UndiciError{constructor(e){super(e);this.name="ClientClosedError";this.message=e||"The client is closed";this.code="UND_ERR_CLOSED"}static[Symbol.hasInstance](e){return e&&e[h]===true}[h]=true}const g=Symbol.for("undici.error.UND_ERR_SOCKET");class SocketError extends UndiciError{constructor(e,t){super(e);this.name="SocketError";this.message=e||"Socket error";this.code="UND_ERR_SOCKET";this.socket=t}static[Symbol.hasInstance](e){return e&&e[g]===true}[g]=true}const y=Symbol.for("undici.error.UND_ERR_NOT_SUPPORTED");class NotSupportedError extends UndiciError{constructor(e){super(e);this.name="NotSupportedError";this.message=e||"Not supported error";this.code="UND_ERR_NOT_SUPPORTED"}static[Symbol.hasInstance](e){return e&&e[y]===true}[y]=true}const m=Symbol.for("undici.error.UND_ERR_BPL_MISSING_UPSTREAM");class BalancedPoolMissingUpstreamError extends UndiciError{constructor(e){super(e);this.name="MissingUpstreamError";this.message=e||"No upstream has been added to the BalancedPool";this.code="UND_ERR_BPL_MISSING_UPSTREAM"}static[Symbol.hasInstance](e){return e&&e[m]===true}[m]=true}const I=Symbol.for("undici.error.UND_ERR_HTTP_PARSER");class HTTPParserError extends Error{constructor(e,t,i){super(e);this.name="HTTPParserError";this.code=t?`HPE_${t}`:undefined;this.data=i?i.toString():undefined}static[Symbol.hasInstance](e){return e&&e[I]===true}[I]=true}const v=Symbol.for("undici.error.UND_ERR_RES_EXCEEDED_MAX_SIZE");class ResponseExceededMaxSizeError extends UndiciError{constructor(e){super(e);this.name="ResponseExceededMaxSizeError";this.message=e||"Response content exceeded max size";this.code="UND_ERR_RES_EXCEEDED_MAX_SIZE"}static[Symbol.hasInstance](e){return e&&e[v]===true}[v]=true}const E=Symbol.for("undici.error.UND_ERR_REQ_RETRY");class RequestRetryError extends UndiciError{constructor(e,t,{headers:i,data:n}){super(e);this.name="RequestRetryError";this.message=e||"Request retry error";this.code="UND_ERR_REQ_RETRY";this.statusCode=t;this.data=n;this.headers=i}static[Symbol.hasInstance](e){return e&&e[E]===true}[E]=true}const C=Symbol.for("undici.error.UND_ERR_RESPONSE");class ResponseError extends UndiciError{constructor(e,t,{headers:i,data:n}){super(e);this.name="ResponseError";this.message=e||"Response error";this.code="UND_ERR_RESPONSE";this.statusCode=t;this.data=n;this.headers=i}static[Symbol.hasInstance](e){return e&&e[C]===true}[C]=true}const T=Symbol.for("undici.error.UND_ERR_PRX_TLS");class SecureProxyConnectionError extends UndiciError{constructor(e,t,i){super(t,{cause:e,...i??{}});this.name="SecureProxyConnectionError";this.message=t||"Secure Proxy Connection failed";this.code="UND_ERR_PRX_TLS";this.cause=e}static[Symbol.hasInstance](e){return e&&e[T]===true}[T]=true}const R=Symbol.for("undici.error.UND_ERR_WS_MESSAGE_SIZE_EXCEEDED");class MessageSizeExceededError extends UndiciError{constructor(e){super(e);this.name="MessageSizeExceededError";this.message=e||"Max decompressed message size exceeded";this.code="UND_ERR_WS_MESSAGE_SIZE_EXCEEDED"}static[Symbol.hasInstance](e){return e&&e[R]===true}get[R](){return true}}e.exports={AbortError:AbortError,HTTPParserError:HTTPParserError,UndiciError:UndiciError,HeadersTimeoutError:HeadersTimeoutError,HeadersOverflowError:HeadersOverflowError,BodyTimeoutError:BodyTimeoutError,RequestContentLengthMismatchError:RequestContentLengthMismatchError,ConnectTimeoutError:ConnectTimeoutError,ResponseStatusCodeError:ResponseStatusCodeError,InvalidArgumentError:InvalidArgumentError,InvalidReturnValueError:InvalidReturnValueError,RequestAbortedError:RequestAbortedError,ClientDestroyedError:ClientDestroyedError,ClientClosedError:ClientClosedError,InformationalError:InformationalError,SocketError:SocketError,NotSupportedError:NotSupportedError,ResponseContentLengthMismatchError:ResponseContentLengthMismatchError,BalancedPoolMissingUpstreamError:BalancedPoolMissingUpstreamError,ResponseExceededMaxSizeError:ResponseExceededMaxSizeError,RequestRetryError:RequestRetryError,ResponseError:ResponseError,SecureProxyConnectionError:SecureProxyConnectionError,MessageSizeExceededError:MessageSizeExceededError}},4655:(e,t,i)=>{const{InvalidArgumentError:n,NotSupportedError:r}=i(8707);const s=i(4589);const{isValidHTTPToken:o,isValidHeaderValue:a,isStream:l,destroy:u,isBuffer:c,isFormDataLike:d,isIterable:p,isBlobLike:A,buildURL:f,validateHandler:h,getServerName:g,normalizedMethodRecords:y}=i(3440);const{channels:m}=i(2414);const{headerNameLowerCasedRecord:I}=i(735);const v=/[^\u0021-\u00ff]/;const E=Symbol("handler");class Request{constructor(e,{path:t,method:i,body:r,headers:s,query:I,idempotent:C,blocking:T,upgrade:R,headersTimeout:b,bodyTimeout:w,reset:B,throwOnError:D,expectContinue:S,servername:k},P){if(typeof t!=="string"){throw new n("path must be a string")}else if(t[0]!=="/"&&!(t.startsWith("http://")||t.startsWith("https://"))&&i!=="CONNECT"){throw new n("path must be an absolute URL or start with a slash")}else if(v.test(t)){throw new n("invalid request path")}if(typeof i!=="string"){throw new n("method must be a string")}else if(y[i]===undefined&&!o(i)){throw new n("invalid request method")}if(R&&typeof R!=="string"){throw new n("upgrade must be a string")}if(R&&!a(R)){throw new n("invalid upgrade header")}if(b!=null&&(!Number.isFinite(b)||b<0)){throw new n("invalid headersTimeout")}if(w!=null&&(!Number.isFinite(w)||w<0)){throw new n("invalid bodyTimeout")}if(B!=null&&typeof B!=="boolean"){throw new n("invalid reset")}if(S!=null&&typeof S!=="boolean"){throw new n("invalid expectContinue")}this.headersTimeout=b;this.bodyTimeout=w;this.throwOnError=D===true;this.method=i;this.abort=null;if(r==null){this.body=null}else if(l(r)){this.body=r;const e=this.body._readableState;if(!e||!e.autoDestroy){this.endHandler=function autoDestroy(){u(this)};this.body.on("end",this.endHandler)}this.errorHandler=e=>{if(this.abort){this.abort(e)}else{this.error=e}};this.body.on("error",this.errorHandler)}else if(c(r)){this.body=r.byteLength?r:null}else if(ArrayBuffer.isView(r)){this.body=r.buffer.byteLength?Buffer.from(r.buffer,r.byteOffset,r.byteLength):null}else if(r instanceof ArrayBuffer){this.body=r.byteLength?Buffer.from(r):null}else if(typeof r==="string"){this.body=r.length?Buffer.from(r):null}else if(d(r)||p(r)||A(r)){this.body=r}else{throw new n("body must be a string, a Buffer, a Readable stream, an iterable, or an async iterable")}this.completed=false;this.aborted=false;this.upgrade=R||null;this.path=I?f(t,I):t;this.origin=e;this.idempotent=C==null?i==="HEAD"||i==="GET":C;this.blocking=T==null?false:T;this.reset=B==null?null:B;this.host=null;this.contentLength=null;this.contentType=null;this.headers=[];this.expectContinue=S!=null?S:false;if(Array.isArray(s)){if(s.length%2!==0){throw new n("headers array must be even")}for(let e=0;e{e.exports={kClose:Symbol("close"),kDestroy:Symbol("destroy"),kDispatch:Symbol("dispatch"),kUrl:Symbol("url"),kWriting:Symbol("writing"),kResuming:Symbol("resuming"),kQueue:Symbol("queue"),kConnect:Symbol("connect"),kConnecting:Symbol("connecting"),kKeepAliveDefaultTimeout:Symbol("default keep alive timeout"),kKeepAliveMaxTimeout:Symbol("max keep alive timeout"),kKeepAliveTimeoutThreshold:Symbol("keep alive timeout threshold"),kKeepAliveTimeoutValue:Symbol("keep alive timeout"),kKeepAlive:Symbol("keep alive"),kHeadersTimeout:Symbol("headers timeout"),kBodyTimeout:Symbol("body timeout"),kServerName:Symbol("server name"),kLocalAddress:Symbol("local address"),kHost:Symbol("host"),kNoRef:Symbol("no ref"),kBodyUsed:Symbol("used"),kBody:Symbol("abstracted request body"),kRunning:Symbol("running"),kBlocking:Symbol("blocking"),kPending:Symbol("pending"),kSize:Symbol("size"),kBusy:Symbol("busy"),kQueued:Symbol("queued"),kFree:Symbol("free"),kConnected:Symbol("connected"),kClosed:Symbol("closed"),kNeedDrain:Symbol("need drain"),kReset:Symbol("reset"),kDestroyed:Symbol.for("nodejs.stream.destroyed"),kResume:Symbol("resume"),kOnError:Symbol("on error"),kMaxHeadersSize:Symbol("max headers size"),kRunningIdx:Symbol("running index"),kPendingIdx:Symbol("pending index"),kError:Symbol("error"),kClients:Symbol("clients"),kClient:Symbol("client"),kParser:Symbol("parser"),kOnDestroyed:Symbol("destroy callbacks"),kPipelining:Symbol("pipelining"),kSocket:Symbol("socket"),kHostHeader:Symbol("host header"),kConnector:Symbol("connector"),kStrictContentLength:Symbol("strict content length"),kMaxRedirections:Symbol("maxRedirections"),kMaxRequests:Symbol("maxRequestsPerClient"),kProxy:Symbol("proxy agent options"),kCounter:Symbol("socket request counter"),kInterceptors:Symbol("dispatch interceptors"),kMaxResponseSize:Symbol("max response size"),kHTTP2Session:Symbol("http2Session"),kHTTP2SessionState:Symbol("http2Session state"),kRetryHandlerDefaultRetry:Symbol("retry agent default retry"),kConstruct:Symbol("constructable"),kListeners:Symbol("listeners"),kHTTPContext:Symbol("http context"),kMaxConcurrentStreams:Symbol("max concurrent streams"),kNoProxyAgent:Symbol("no proxy agent"),kHttpProxyAgent:Symbol("http proxy agent"),kHttpsProxyAgent:Symbol("https proxy agent")}},7752:(e,t,i)=>{const{wellknownHeaderNames:n,headerNameLowerCasedRecord:r}=i(735);class TstNode{value=null;left=null;middle=null;right=null;code;constructor(e,t,i){if(i===undefined||i>=e.length){throw new TypeError("Unreachable")}const n=this.code=e.charCodeAt(i);if(n>127){throw new TypeError("key must be ascii string")}if(e.length!==++i){this.middle=new TstNode(e,t,i)}else{this.value=t}}add(e,t){const i=e.length;if(i===0){throw new TypeError("Unreachable")}let n=0;let r=this;while(true){const s=e.charCodeAt(n);if(s>127){throw new TypeError("key must be ascii string")}if(r.code===s){if(i===++n){r.value=t;break}else if(r.middle!==null){r=r.middle}else{r.middle=new TstNode(e,t,n);break}}else if(r.code=65){r|=32}while(n!==null){if(r===n.code){if(t===++i){return n}n=n.middle;break}n=n.code{const n=i(4589);const{kDestroyed:r,kBodyUsed:s,kListeners:o,kBody:a}=i(6443);const{IncomingMessage:l}=i(7067);const u=i(7075);const c=i(7030);const{Blob:d}=i(4573);const p=i(7975);const{stringify:A}=i(1792);const{EventEmitter:f}=i(8474);const{InvalidArgumentError:h}=i(8707);const{headerNameLowerCasedRecord:g}=i(735);const{tree:y}=i(7752);const[m,I]=process.versions.node.split(".").map((e=>Number(e)));class BodyAsyncIterable{constructor(e){this[a]=e;this[s]=false}async*[Symbol.asyncIterator](){n(!this[s],"disturbed");this[s]=true;yield*this[a]}}function wrapRequestBody(e){if(isStream(e)){if(bodyLength(e)===0){e.on("data",(function(){n(false)}))}if(typeof e.readableDidRead!=="boolean"){e[s]=false;f.prototype.on.call(e,"data",(function(){this[s]=true}))}return e}else if(e&&typeof e.pipeTo==="function"){return new BodyAsyncIterable(e)}else if(e&&typeof e!=="string"&&!ArrayBuffer.isView(e)&&isIterable(e)){return new BodyAsyncIterable(e)}else{return e}}function nop(){}function isStream(e){return e&&typeof e==="object"&&typeof e.pipe==="function"&&typeof e.on==="function"}function isBlobLike(e){if(e===null){return false}else if(e instanceof d){return true}else if(typeof e!=="object"){return false}else{const t=e[Symbol.toStringTag];return(t==="Blob"||t==="File")&&("stream"in e&&typeof e.stream==="function"||"arrayBuffer"in e&&typeof e.arrayBuffer==="function")}}function buildURL(e,t){if(e.includes("?")||e.includes("#")){throw new Error('Query params cannot be passed when url already contains "?" or "#".')}const i=A(t);if(i){e+="?"+i}return e}function isValidPort(e){const t=parseInt(e,10);return t===Number(e)&&t>=0&&t<=65535}function isHttpOrHttpsPrefixed(e){return e!=null&&e[0]==="h"&&e[1]==="t"&&e[2]==="t"&&e[3]==="p"&&(e[4]===":"||e[4]==="s"&&e[5]===":")}function parseURL(e){if(typeof e==="string"){e=new URL(e);if(!isHttpOrHttpsPrefixed(e.origin||e.protocol)){throw new h("Invalid URL protocol: the URL must start with `http:` or `https:`.")}return e}if(!e||typeof e!=="object"){throw new h("Invalid URL: The URL argument must be a non-null object.")}if(!(e instanceof URL)){if(e.port!=null&&e.port!==""&&isValidPort(e.port)===false){throw new h("Invalid URL: port must be a valid integer or a string representation of an integer.")}if(e.path!=null&&typeof e.path!=="string"){throw new h("Invalid URL path: the path must be a string or null/undefined.")}if(e.pathname!=null&&typeof e.pathname!=="string"){throw new h("Invalid URL pathname: the pathname must be a string or null/undefined.")}if(e.hostname!=null&&typeof e.hostname!=="string"){throw new h("Invalid URL hostname: the hostname must be a string or null/undefined.")}if(e.origin!=null&&typeof e.origin!=="string"){throw new h("Invalid URL origin: the origin must be a string or null/undefined.")}if(!isHttpOrHttpsPrefixed(e.origin||e.protocol)){throw new h("Invalid URL protocol: the URL must start with `http:` or `https:`.")}const t=e.port!=null?e.port:e.protocol==="https:"?443:80;let i=e.origin!=null?e.origin:`${e.protocol||""}//${e.hostname||""}:${t}`;let n=e.path!=null?e.path:`${e.pathname||""}${e.search||""}`;if(i[i.length-1]==="/"){i=i.slice(0,i.length-1)}if(n&&n[0]!=="/"){n=`/${n}`}return new URL(`${i}${n}`)}if(!isHttpOrHttpsPrefixed(e.origin||e.protocol)){throw new h("Invalid URL protocol: the URL must start with `http:` or `https:`.")}return e}function parseOrigin(e){e=parseURL(e);if(e.pathname!=="/"||e.search||e.hash){throw new h("invalid url")}return e}function getHostname(e){if(e[0]==="["){const t=e.indexOf("]");n(t!==-1);return e.substring(1,t)}const t=e.indexOf(":");if(t===-1)return e;return e.substring(0,t)}function getServerName(e){if(!e){return null}n(typeof e==="string");const t=getHostname(e);if(c.isIP(t)){return""}return t}function deepClone(e){return JSON.parse(JSON.stringify(e))}function isAsyncIterable(e){return!!(e!=null&&typeof e[Symbol.asyncIterator]==="function")}function isIterable(e){return!!(e!=null&&(typeof e[Symbol.iterator]==="function"||typeof e[Symbol.asyncIterator]==="function"))}function bodyLength(e){if(e==null){return 0}else if(isStream(e)){const t=e._readableState;return t&&t.objectMode===false&&t.ended===true&&Number.isFinite(t.length)?t.length:null}else if(isBlobLike(e)){return e.size!=null?e.size:null}else if(isBuffer(e)){return e.byteLength}return null}function isDestroyed(e){return e&&!!(e.destroyed||e[r]||u.isDestroyed?.(e))}function destroy(e,t){if(e==null||!isStream(e)||isDestroyed(e)){return}if(typeof e.destroy==="function"){if(Object.getPrototypeOf(e).constructor===l){e.socket=null}e.destroy(t)}else if(t){queueMicrotask((()=>{e.emit("error",t)}))}if(e.destroyed!==true){e[r]=true}}const v=/timeout=(\d+)/;function parseKeepAliveTimeout(e){const t=e.toString().match(v);return t?parseInt(t[1],10)*1e3:null}function headerNameToString(e){return typeof e==="string"?g[e]??e.toLowerCase():y.lookup(e)??e.toString("latin1").toLowerCase()}function bufferToLowerCasedHeaderName(e){return y.lookup(e)??e.toString("latin1").toLowerCase()}function parseHeaders(e,t){if(t===undefined)t={};for(let i=0;ie.toString("utf8"))):r.toString("utf8")}}}if("content-length"in t&&"content-disposition"in t){t["content-disposition"]=Buffer.from(t["content-disposition"]).toString("latin1")}return t}function parseRawHeaders(e){const t=e.length;const i=new Array(t);let n=false;let r=-1;let s;let o;let a=0;for(let t=0;t{e.close();e.byobRequest?.respond(0)}))}else{const t=Buffer.isBuffer(n)?n:Buffer.from(n);if(t.byteLength){e.enqueue(new Uint8Array(t))}}return e.desiredSize>0},async cancel(e){await t.return()},type:"bytes"})}function isFormDataLike(e){return e&&typeof e==="object"&&typeof e.append==="function"&&typeof e.delete==="function"&&typeof e.get==="function"&&typeof e.getAll==="function"&&typeof e.has==="function"&&typeof e.set==="function"&&e[Symbol.toStringTag]==="FormData"}function addAbortListener(e,t){if("addEventListener"in e){e.addEventListener("abort",t,{once:true});return()=>e.removeEventListener("abort",t)}e.addListener("abort",t);return()=>e.removeListener("abort",t)}const E=typeof String.prototype.toWellFormed==="function";const C=typeof String.prototype.isWellFormed==="function";function toUSVString(e){return E?`${e}`.toWellFormed():p.toUSVString(e)}function isUSVString(e){return C?`${e}`.isWellFormed():toUSVString(e)===`${e}`}function isTokenCharCode(e){switch(e){case 34:case 40:case 41:case 44:case 47:case 58:case 59:case 60:case 61:case 62:case 63:case 64:case 91:case 92:case 93:case 123:case 125:return false;default:return e>=33&&e<=126}}function isValidHTTPToken(e){if(e.length===0){return false}for(let t=0;t{const{InvalidArgumentError:n}=i(8707);const{kClients:r,kRunning:s,kClose:o,kDestroy:a,kDispatch:l,kInterceptors:u}=i(6443);const c=i(1841);const d=i(628);const p=i(3701);const A=i(3440);const f=i(5092);const h=Symbol("onConnect");const g=Symbol("onDisconnect");const y=Symbol("onConnectionError");const m=Symbol("maxRedirections");const I=Symbol("onDrain");const v=Symbol("factory");const E=Symbol("options");function defaultFactory(e,t){return t&&t.connections===1?new p(e,t):new d(e,t)}class Agent extends c{constructor({factory:e=defaultFactory,maxRedirections:t=0,connect:i,...s}={}){super();if(typeof e!=="function"){throw new n("factory must be a function.")}if(i!=null&&typeof i!=="function"&&typeof i!=="object"){throw new n("connect must be a function or an object")}if(!Number.isInteger(t)||t<0){throw new n("maxRedirections must be a positive number")}if(i&&typeof i!=="function"){i={...i}}this[u]=s.interceptors?.Agent&&Array.isArray(s.interceptors.Agent)?s.interceptors.Agent:[f({maxRedirections:t})];this[E]={...A.deepClone(s),connect:i};this[E].interceptors=s.interceptors?{...s.interceptors}:undefined;this[m]=t;this[v]=e;this[r]=new Map;this[I]=(e,t)=>{this.emit("drain",e,[this,...t])};this[h]=(e,t)=>{this.emit("connect",e,[this,...t])};this[g]=(e,t,i)=>{this.emit("disconnect",e,[this,...t],i)};this[y]=(e,t,i)=>{this.emit("connectionError",e,[this,...t],i)}}get[s](){let e=0;for(const t of this[r].values()){e+=t[s]}return e}[l](e,t){let i;if(e.origin&&(typeof e.origin==="string"||e.origin instanceof URL)){i=String(e.origin)}else{throw new n("opts.origin must be a non-empty string or URL.")}let s=this[r].get(i);if(!s){s=this[v](e.origin,this[E]).on("drain",this[I]).on("connect",this[h]).on("disconnect",this[g]).on("connectionError",this[y]);this[r].set(i,s)}return s.dispatch(e,t)}async[o](){const e=[];for(const t of this[r].values()){e.push(t.close())}this[r].clear();await Promise.all(e)}async[a](e){const t=[];for(const i of this[r].values()){t.push(i.destroy(e))}this[r].clear();await Promise.all(t)}}e.exports=Agent},837:(e,t,i)=>{const{BalancedPoolMissingUpstreamError:n,InvalidArgumentError:r}=i(8707);const{PoolBase:s,kClients:o,kNeedDrain:a,kAddClient:l,kRemoveClient:u,kGetDispatcher:c}=i(2128);const d=i(628);const{kUrl:p,kInterceptors:A}=i(6443);const{parseOrigin:f}=i(3440);const h=Symbol("factory");const g=Symbol("options");const y=Symbol("kGreatestCommonDivisor");const m=Symbol("kCurrentWeight");const I=Symbol("kIndex");const v=Symbol("kWeight");const E=Symbol("kMaxWeightPerServer");const C=Symbol("kErrorPenalty");function getGreatestCommonDivisor(e,t){if(e===0)return t;while(t!==0){const i=t;t=e%t;e=i}return e}function defaultFactory(e,t){return new d(e,t)}class BalancedPool extends s{constructor(e=[],{factory:t=defaultFactory,...i}={}){super();this[g]=i;this[I]=-1;this[m]=0;this[E]=this[g].maxWeightPerServer||100;this[C]=this[g].errorPenalty||15;if(!Array.isArray(e)){e=[e]}if(typeof t!=="function"){throw new r("factory must be a function.")}this[A]=i.interceptors?.BalancedPool&&Array.isArray(i.interceptors.BalancedPool)?i.interceptors.BalancedPool:[];this[h]=t;for(const t of e){this.addUpstream(t)}this._updateBalancedPoolStats()}addUpstream(e){const t=f(e).origin;if(this[o].find((e=>e[p].origin===t&&e.closed!==true&&e.destroyed!==true))){return this}const i=this[h](t,Object.assign({},this[g]));this[l](i);i.on("connect",(()=>{i[v]=Math.min(this[E],i[v]+this[C])}));i.on("connectionError",(()=>{i[v]=Math.max(1,i[v]-this[C]);this._updateBalancedPoolStats()}));i.on("disconnect",((...e)=>{const t=e[2];if(t&&t.code==="UND_ERR_SOCKET"){i[v]=Math.max(1,i[v]-this[C]);this._updateBalancedPoolStats()}}));for(const e of this[o]){e[v]=this[E]}this._updateBalancedPoolStats();return this}_updateBalancedPoolStats(){let e=0;for(let t=0;te[p].origin===t&&e.closed!==true&&e.destroyed!==true));if(i){this[u](i)}return this}get upstreams(){return this[o].filter((e=>e.closed!==true&&e.destroyed!==true)).map((e=>e[p].origin))}[c](){if(this[o].length===0){throw new n}const e=this[o].find((e=>!e[a]&&e.closed!==true&&e.destroyed!==true));if(!e){return}const t=this[o].map((e=>e[a])).reduce(((e,t)=>e&&t),true);if(t){return}let i=0;let r=this[o].findIndex((e=>!e[a]));while(i++this[o][r][v]&&!e[a]){r=this[I]}if(this[I]===0){this[m]=this[m]-this[y];if(this[m]<=0){this[m]=this[E]}}if(e[v]>=this[m]&&!e[a]){return e}}this[m]=this[o][r][v];this[I]=r;return this[o][r]}}e.exports=BalancedPool},637:(e,t,i)=>{const n=i(4589);const r=i(3440);const{channels:s}=i(2414);const o=i(6603);const{RequestContentLengthMismatchError:a,ResponseContentLengthMismatchError:l,RequestAbortedError:u,HeadersTimeoutError:c,HeadersOverflowError:d,SocketError:p,InformationalError:A,BodyTimeoutError:f,HTTPParserError:h,ResponseExceededMaxSizeError:g}=i(8707);const{kUrl:y,kReset:m,kClient:I,kParser:v,kBlocking:E,kRunning:C,kPending:T,kSize:R,kWriting:b,kQueue:w,kNoRef:B,kKeepAliveDefaultTimeout:D,kHostHeader:S,kPendingIdx:k,kRunningIdx:P,kError:U,kPipelining:V,kSocket:F,kKeepAliveTimeoutValue:O,kMaxHeadersSize:N,kKeepAliveMaxTimeout:q,kKeepAliveTimeoutThreshold:_,kHeadersTimeout:M,kBodyTimeout:L,kStrictContentLength:G,kMaxRequests:j,kCounter:x,kMaxResponseSize:H,kOnError:W,kResume:Y,kHTTPContext:J}=i(6443);const z=i(2824);const $=Buffer.alloc(0);const K=Buffer[Symbol.species];const Z=r.addListener;const X=r.removeAllListeners;let ee;async function lazyllhttp(){const e=process.env.JEST_WORKER_ID?i(3870):undefined;let t;try{t=await WebAssembly.compile(i(3434))}catch(n){t=await WebAssembly.compile(e||i(3870))}return await WebAssembly.instantiate(t,{env:{wasm_on_url:(e,t,i)=>0,wasm_on_status:(e,t,i)=>{n(ne.ptr===e);const r=t-oe+re.byteOffset;return ne.onStatus(new K(re.buffer,r,i))||0},wasm_on_message_begin:e=>{n(ne.ptr===e);return ne.onMessageBegin()||0},wasm_on_header_field:(e,t,i)=>{n(ne.ptr===e);const r=t-oe+re.byteOffset;return ne.onHeaderField(new K(re.buffer,r,i))||0},wasm_on_header_value:(e,t,i)=>{n(ne.ptr===e);const r=t-oe+re.byteOffset;return ne.onHeaderValue(new K(re.buffer,r,i))||0},wasm_on_headers_complete:(e,t,i,r)=>{n(ne.ptr===e);return ne.onHeadersComplete(t,Boolean(i),Boolean(r))||0},wasm_on_body:(e,t,i)=>{n(ne.ptr===e);const r=t-oe+re.byteOffset;return ne.onBody(new K(re.buffer,r,i))||0},wasm_on_message_complete:e=>{n(ne.ptr===e);return ne.onMessageComplete()||0}}})}let te=null;let ie=lazyllhttp();ie.catch();let ne=null;let re=null;let se=0;let oe=null;const ae=0;const le=1;const ue=2|le;const ce=4|le;const de=8|ae;class Parser{constructor(e,t,{exports:i}){n(Number.isFinite(e[N])&&e[N]>0);this.llhttp=i;this.ptr=this.llhttp.llhttp_alloc(z.TYPE.RESPONSE);this.client=e;this.socket=t;this.timeout=null;this.timeoutValue=null;this.timeoutType=null;this.statusCode=null;this.statusText="";this.upgrade=false;this.headers=[];this.headersSize=0;this.headersMaxSize=e[N];this.shouldKeepAlive=false;this.paused=false;this.resume=this.resume.bind(this);this.bytesRead=0;this.keepAlive="";this.contentLength="";this.connection="";this.maxResponseSize=e[H]}setTimeout(e,t){if(e!==this.timeoutValue||t&le^this.timeoutType&le){if(this.timeout){o.clearTimeout(this.timeout);this.timeout=null}if(e){if(t&le){this.timeout=o.setFastTimeout(onParserTimeout,e,new WeakRef(this))}else{this.timeout=setTimeout(onParserTimeout,e,new WeakRef(this));this.timeout.unref()}}this.timeoutValue=e}else if(this.timeout){if(this.timeout.refresh){this.timeout.refresh()}}this.timeoutType=t}resume(){if(this.socket.destroyed||!this.paused){return}n(this.ptr!=null);n(ne==null);this.llhttp.llhttp_resume(this.ptr);n(this.timeoutType===ce);if(this.timeout){if(this.timeout.refresh){this.timeout.refresh()}}this.paused=false;this.execute(this.socket.read()||$);this.readMore()}readMore(){while(!this.paused&&this.ptr){const e=this.socket.read();if(e===null){break}this.execute(e)}}execute(e){n(this.ptr!=null);n(ne==null);n(!this.paused);const{socket:t,llhttp:i}=this;if(e.length>se){if(oe){i.free(oe)}se=Math.ceil(e.length/4096)*4096;oe=i.malloc(se)}new Uint8Array(i.memory.buffer,oe,se).set(e);try{let n;try{re=e;ne=this;n=i.llhttp_execute(this.ptr,oe,e.length)}catch(e){throw e}finally{ne=null;re=null}const r=i.llhttp_get_error_pos(this.ptr)-oe;if(n===z.ERROR.PAUSED_UPGRADE){this.onUpgrade(e.slice(r))}else if(n===z.ERROR.PAUSED){this.paused=true;t.unshift(e.slice(r))}else if(n!==z.ERROR.OK){const t=i.llhttp_get_error_reason(this.ptr);let s="";if(t){const e=new Uint8Array(i.memory.buffer,t).indexOf(0);s="Response does not match the HTTP/1.1 protocol ("+Buffer.from(i.memory.buffer,t,e).toString()+")"}throw new h(s,z.ERROR[n],e.slice(r))}}catch(e){r.destroy(t,e)}}destroy(){n(this.ptr!=null);n(ne==null);this.llhttp.llhttp_free(this.ptr);this.ptr=null;this.timeout&&o.clearTimeout(this.timeout);this.timeout=null;this.timeoutValue=null;this.timeoutType=null;this.paused=false}onStatus(e){this.statusText=e.toString()}onMessageBegin(){const{socket:e,client:t}=this;if(e.destroyed){return-1}const i=t[w][t[P]];if(!i){return-1}i.onResponseStarted()}onHeaderField(e){const t=this.headers.length;if((t&1)===0){this.headers.push(e)}else{this.headers[t-1]=Buffer.concat([this.headers[t-1],e])}this.trackHeader(e.length)}onHeaderValue(e){let t=this.headers.length;if((t&1)===1){this.headers.push(e);t+=1}else{this.headers[t-1]=Buffer.concat([this.headers[t-1],e])}const i=this.headers[t-2];if(i.length===10){const t=r.bufferToLowerCasedHeaderName(i);if(t==="keep-alive"){this.keepAlive+=e.toString()}else if(t==="connection"){this.connection+=e.toString()}}else if(i.length===14&&r.bufferToLowerCasedHeaderName(i)==="content-length"){this.contentLength+=e.toString()}this.trackHeader(e.length)}trackHeader(e){this.headersSize+=e;if(this.headersSize>=this.headersMaxSize){r.destroy(this.socket,new d)}}onUpgrade(e){const{upgrade:t,client:i,socket:s,headers:o,statusCode:a}=this;n(t);n(i[F]===s);n(!s.destroyed);n(!this.paused);n((o.length&1)===0);const l=i[w][i[P]];n(l);n(l.upgrade||l.method==="CONNECT");this.statusCode=null;this.statusText="";this.shouldKeepAlive=null;this.headers=[];this.headersSize=0;s.unshift(e);s[v].destroy();s[v]=null;s[I]=null;s[U]=null;X(s);i[F]=null;i[J]=null;i[w][i[P]++]=null;i.emit("disconnect",i[y],[i],new A("upgrade"));try{l.onUpgrade(a,o,s)}catch(e){r.destroy(s,e)}i[Y]()}onHeadersComplete(e,t,i){const{client:s,socket:o,headers:a,statusText:l}=this;if(o.destroyed){return-1}const u=s[w][s[P]];if(!u){return-1}n(!this.upgrade);n(this.statusCode<200);if(e===100){r.destroy(o,new p("bad response",r.getSocketInfo(o)));return-1}if(t&&!u.upgrade){r.destroy(o,new p("bad upgrade",r.getSocketInfo(o)));return-1}n(this.timeoutType===ue);this.statusCode=e;this.shouldKeepAlive=i||u.method==="HEAD"&&!o[m]&&this.connection.toLowerCase()==="keep-alive";if(this.statusCode>=200){const e=u.bodyTimeout!=null?u.bodyTimeout:s[L];this.setTimeout(e,ce)}else if(this.timeout){if(this.timeout.refresh){this.timeout.refresh()}}if(u.method==="CONNECT"){n(s[C]===1);this.upgrade=true;return 2}if(t){n(s[C]===1);this.upgrade=true;return 2}n((this.headers.length&1)===0);this.headers=[];this.headersSize=0;if(this.shouldKeepAlive&&s[V]){const e=this.keepAlive?r.parseKeepAliveTimeout(this.keepAlive):null;if(e!=null){const t=Math.min(e-s[_],s[q]);if(t<=0){o[m]=true}else{s[O]=t}}else{s[O]=s[D]}}else{o[m]=true}const c=u.onHeaders(e,a,this.resume,l)===false;if(u.aborted){return-1}if(u.method==="HEAD"){return 1}if(e<200){return 1}if(o[E]){o[E]=false;s[Y]()}return c?z.ERROR.PAUSED:0}onBody(e){const{client:t,socket:i,statusCode:s,maxResponseSize:o}=this;if(i.destroyed){return-1}const a=t[w][t[P]];n(a);n(this.timeoutType===ce);if(this.timeout){if(this.timeout.refresh){this.timeout.refresh()}}n(s>=200);if(o>-1&&this.bytesRead+e.length>o){r.destroy(i,new g);return-1}this.bytesRead+=e.length;if(a.onData(e)===false){return z.ERROR.PAUSED}}onMessageComplete(){const{client:e,socket:t,statusCode:i,upgrade:s,headers:o,contentLength:a,bytesRead:u,shouldKeepAlive:c}=this;if(t.destroyed&&(!i||c)){return-1}if(s){return}n(i>=100);n((this.headers.length&1)===0);const d=e[w][e[P]];n(d);this.statusCode=null;this.statusText="";this.bytesRead=0;this.contentLength="";this.keepAlive="";this.connection="";this.headers=[];this.headersSize=0;if(i<200){return}if(d.method!=="HEAD"&&a&&u!==parseInt(a,10)){r.destroy(t,new l);return-1}d.onComplete(o);e[w][e[P]++]=null;if(t[b]){n(e[C]===0);r.destroy(t,new A("reset"));return z.ERROR.PAUSED}else if(!c){r.destroy(t,new A("reset"));return z.ERROR.PAUSED}else if(t[m]&&e[C]===0){r.destroy(t,new A("reset"));return z.ERROR.PAUSED}else if(e[V]==null||e[V]===1){setImmediate((()=>e[Y]()))}else{e[Y]()}}}function onParserTimeout(e){const{socket:t,timeoutType:i,client:s,paused:o}=e.deref();if(i===ue){if(!t[b]||t.writableNeedDrain||s[C]>1){n(!o,"cannot be paused while waiting for headers");r.destroy(t,new c)}}else if(i===ce){if(!o){r.destroy(t,new f)}}else if(i===de){n(s[C]===0&&s[O]);r.destroy(t,new A("socket idle timeout"))}}async function connectH1(e,t){e[F]=t;if(!te){te=await ie;ie=null}t[B]=false;t[b]=false;t[m]=false;t[E]=false;t[v]=new Parser(e,t,te);Z(t,"error",(function(e){n(e.code!=="ERR_TLS_CERT_ALTNAME_INVALID");const t=this[v];if(e.code==="ECONNRESET"&&t.statusCode&&!t.shouldKeepAlive){t.onMessageComplete();return}this[U]=e;this[I][W](e)}));Z(t,"readable",(function(){const e=this[v];if(e){e.readMore()}}));Z(t,"end",(function(){const e=this[v];if(e.statusCode&&!e.shouldKeepAlive){e.onMessageComplete();return}r.destroy(this,new p("other side closed",r.getSocketInfo(this)))}));Z(t,"close",(function(){const e=this[I];const t=this[v];if(t){if(!this[U]&&t.statusCode&&!t.shouldKeepAlive){t.onMessageComplete()}this[v].destroy();this[v]=null}const i=this[U]||new p("closed",r.getSocketInfo(this));e[F]=null;e[J]=null;if(e.destroyed){n(e[T]===0);const t=e[w].splice(e[P]);for(let n=0;n0&&i.code!=="UND_ERR_INFO"){const t=e[w][e[P]];e[w][e[P]++]=null;r.errorRequest(e,t,i)}e[k]=e[P];n(e[C]===0);e.emit("disconnect",e[y],[e],i);e[Y]()}));let i=false;t.on("close",(()=>{i=true}));return{version:"h1",defaultPipelining:1,write(...t){return writeH1(e,...t)},resume(){resumeH1(e)},destroy(e,n){if(i){queueMicrotask(n)}else{t.destroy(e).on("close",n)}},get destroyed(){return t.destroyed},busy(i){if(t[b]||t[m]||t[E]){return true}if(i){if(e[C]>0&&!i.idempotent){return true}if(e[C]>0&&(i.upgrade||i.method==="CONNECT")){return true}if(e[C]>0&&r.bodyLength(i.body)!==0&&(r.isStream(i.body)||r.isAsyncIterable(i.body)||r.isFormDataLike(i.body))){return true}}return false}}}function resumeH1(e){const t=e[F];if(t&&!t.destroyed){if(e[R]===0){if(!t[B]&&t.unref){t.unref();t[B]=true}}else if(t[B]&&t.ref){t.ref();t[B]=false}if(e[R]===0){if(t[v].timeoutType!==de){t[v].setTimeout(e[O],de)}}else if(e[C]>0&&t[v].statusCode<200){if(t[v].timeoutType!==ue){const i=e[w][e[P]];const n=i.headersTimeout!=null?i.headersTimeout:e[M];t[v].setTimeout(n,ue)}}}}function shouldSendContentLength(e){return e!=="GET"&&e!=="HEAD"&&e!=="OPTIONS"&&e!=="TRACE"&&e!=="CONNECT"}function writeH1(e,t){const{method:o,path:l,host:c,upgrade:d,blocking:p,reset:f}=t;let{body:h,headers:g,contentLength:y}=t;const I=o==="PUT"||o==="POST"||o==="PATCH"||o==="QUERY"||o==="PROPFIND"||o==="PROPPATCH";if(r.isFormDataLike(h)){if(!ee){ee=i(4492).extractBody}const[e,n]=ee(h);if(t.contentType==null){g.push("content-type",n)}h=e.stream;y=e.length}else if(r.isBlobLike(h)&&t.contentType==null&&h.type){g.push("content-type",h.type)}if(h&&typeof h.read==="function"){h.read(0)}const v=r.bodyLength(h);y=v??y;if(y===null){y=t.contentLength}if(y===0&&!I){y=null}if(shouldSendContentLength(o)&&y>0&&t.contentLength!==null&&t.contentLength!==y){if(e[G]){r.errorRequest(e,t,new a);return false}process.emitWarning(new a)}const C=e[F];const abort=i=>{if(t.aborted||t.completed){return}r.errorRequest(e,t,i||new u);r.destroy(h);r.destroy(C,new A("aborted"))};try{t.onConnect(abort)}catch(i){r.errorRequest(e,t,i)}if(t.aborted){return false}if(o==="HEAD"){C[m]=true}if(d||o==="CONNECT"){C[m]=true}if(f!=null){C[m]=f}if(e[j]&&C[x]++>=e[j]){C[m]=true}if(p){C[E]=true}let T=`${o} ${l} HTTP/1.1\r\n`;if(typeof c==="string"){T+=`host: ${c}\r\n`}else{T+=e[S]}if(d){T+=`connection: upgrade\r\nupgrade: ${d}\r\n`}else if(e[V]&&!C[m]){T+="connection: keep-alive\r\n"}else{T+="connection: close\r\n"}if(Array.isArray(g)){for(let e=0;e{t.removeListener("error",onFinished)}));if(!d){const e=new u;queueMicrotask((()=>onFinished(e)))}};const onFinished=function(e){if(d){return}d=true;n(o.destroyed||o[b]&&i[C]<=1);o.off("drain",onDrain).off("error",onFinished);t.removeListener("data",onData).removeListener("end",onFinished).removeListener("close",onClose);if(!e){try{p.end()}catch(t){e=t}}p.destroy(e);if(e&&(e.code!=="UND_ERR_INFO"||e.message!=="reset")){r.destroy(t,e)}else{r.destroy(t)}};t.on("data",onData).on("end",onFinished).on("error",onFinished).on("close",onClose);if(t.resume){t.resume()}o.on("drain",onDrain).on("error",onFinished);if(t.errorEmitted??t.errored){setImmediate((()=>onFinished(t.errored)))}else if(t.endEmitted??t.readableEnded){setImmediate((()=>onFinished(null)))}if(t.closeEmitted??t.closed){setImmediate(onClose)}}function writeBuffer(e,t,i,s,o,a,l,u){try{if(!t){if(a===0){o.write(`${l}content-length: 0\r\n\r\n`,"latin1")}else{n(a===null,"no body must not have content length");o.write(`${l}\r\n`,"latin1")}}else if(r.isBuffer(t)){n(a===t.byteLength,"buffer body must have content length");o.cork();o.write(`${l}content-length: ${a}\r\n\r\n`,"latin1");o.write(t);o.uncork();s.onBodySent(t);if(!u&&s.reset!==false){o[m]=true}}s.onRequestSent();i[Y]()}catch(t){e(t)}}async function writeBlob(e,t,i,r,s,o,l,u){n(o===t.size,"blob body must have content length");try{if(o!=null&&o!==t.size){throw new a}const e=Buffer.from(await t.arrayBuffer());s.cork();s.write(`${l}content-length: ${o}\r\n\r\n`,"latin1");s.write(e);s.uncork();r.onBodySent(e);r.onRequestSent();if(!u&&r.reset!==false){s[m]=true}i[Y]()}catch(t){e(t)}}async function writeIterable(e,t,i,r,s,o,a,l){n(o!==0||i[C]===0,"iterator body cannot be pipelined");let u=null;function onDrain(){if(u){const e=u;u=null;e()}}const waitForDrain=()=>new Promise(((e,t)=>{n(u===null);if(s[U]){t(s[U])}else{u=e}}));s.on("close",onDrain).on("drain",onDrain);const c=new AsyncWriter({abort:e,socket:s,request:r,contentLength:o,client:i,expectsPayload:l,header:a});try{for await(const e of t){if(s[U]){throw s[U]}if(!c.write(e)){await waitForDrain()}}c.end()}catch(e){c.destroy(e)}finally{s.off("close",onDrain).off("drain",onDrain)}}class AsyncWriter{constructor({abort:e,socket:t,request:i,contentLength:n,client:r,expectsPayload:s,header:o}){this.socket=t;this.request=i;this.contentLength=n;this.client=r;this.bytesWritten=0;this.expectsPayload=s;this.header=o;this.abort=e;t[b]=true}write(e){const{socket:t,request:i,contentLength:n,client:r,bytesWritten:s,expectsPayload:o,header:l}=this;if(t[U]){throw t[U]}if(t.destroyed){return false}const u=Buffer.byteLength(e);if(!u){return true}if(n!==null&&s+u>n){if(r[G]){throw new a}process.emitWarning(new a)}t.cork();if(s===0){if(!o&&i.reset!==false){t[m]=true}if(n===null){t.write(`${l}transfer-encoding: chunked\r\n`,"latin1")}else{t.write(`${l}content-length: ${n}\r\n\r\n`,"latin1")}}if(n===null){t.write(`\r\n${u.toString(16)}\r\n`,"latin1")}this.bytesWritten+=u;const c=t.write(e);t.uncork();i.onBodySent(e);if(!c){if(t[v].timeout&&t[v].timeoutType===ue){if(t[v].timeout.refresh){t[v].timeout.refresh()}}}return c}end(){const{socket:e,contentLength:t,client:i,bytesWritten:n,expectsPayload:r,header:s,request:o}=this;o.onRequestSent();e[b]=false;if(e[U]){throw e[U]}if(e.destroyed){return}if(n===0){if(r){e.write(`${s}content-length: 0\r\n\r\n`,"latin1")}else{e.write(`${s}\r\n`,"latin1")}}else if(t===null){e.write("\r\n0\r\n\r\n","latin1")}if(t!==null&&n!==t){if(i[G]){throw new a}else{process.emitWarning(new a)}}if(e[v].timeout&&e[v].timeoutType===ue){if(e[v].timeout.refresh){e[v].timeout.refresh()}}i[Y]()}destroy(e){const{socket:t,client:i,abort:r}=this;t[b]=false;if(e){n(i[C]<=1,"pipeline should only contain this request");r(e)}}}e.exports=connectH1},8788:(e,t,i)=>{const n=i(4589);const{pipeline:r}=i(7075);const s=i(3440);const{RequestContentLengthMismatchError:o,RequestAbortedError:a,SocketError:l,InformationalError:u}=i(8707);const{kUrl:c,kReset:d,kClient:p,kRunning:A,kPending:f,kQueue:h,kPendingIdx:g,kRunningIdx:y,kError:m,kSocket:I,kStrictContentLength:v,kOnError:E,kMaxConcurrentStreams:C,kHTTP2Session:T,kResume:R,kSize:b,kHTTPContext:w}=i(6443);const B=Symbol("open streams");let D;let S=false;let k;try{k=i(2467)}catch{k={constants:{}}}const{constants:{HTTP2_HEADER_AUTHORITY:P,HTTP2_HEADER_METHOD:U,HTTP2_HEADER_PATH:V,HTTP2_HEADER_SCHEME:F,HTTP2_HEADER_CONTENT_LENGTH:O,HTTP2_HEADER_EXPECT:N,HTTP2_HEADER_STATUS:q}}=k;function parseH2Headers(e){const t=[];for(const[i,n]of Object.entries(e)){if(Array.isArray(n)){for(const e of n){t.push(Buffer.from(i),Buffer.from(e))}}else{t.push(Buffer.from(i),Buffer.from(n))}}return t}async function connectH2(e,t){e[I]=t;if(!S){S=true;process.emitWarning("H2 support is experimental, expect them to change at any time.",{code:"UNDICI-H2"})}const i=k.connect(e[c],{createConnection:()=>t,peerMaxConcurrentStreams:e[C]});i[B]=0;i[p]=e;i[I]=t;s.addListener(i,"error",onHttp2SessionError);s.addListener(i,"frameError",onHttp2FrameError);s.addListener(i,"end",onHttp2SessionEnd);s.addListener(i,"goaway",onHTTP2GoAway);s.addListener(i,"close",(function(){const{[p]:e}=this;const{[I]:t}=e;const i=this[I][m]||this[m]||new l("closed",s.getSocketInfo(t));e[T]=null;if(e.destroyed){n(e[f]===0);const t=e[h].splice(e[y]);for(let n=0;n{r=true}));return{version:"h2",defaultPipelining:Infinity,write(...t){return writeH2(e,...t)},resume(){resumeH2(e)},destroy(e,i){if(r){queueMicrotask(i)}else{t.destroy(e).on("close",i)}},get destroyed(){return t.destroyed},busy(){return false}}}function resumeH2(e){const t=e[I];if(t?.destroyed===false){if(e[b]===0&&e[C]===0){t.unref();e[T].unref()}else{t.ref();e[T].ref()}}}function onHttp2SessionError(e){n(e.code!=="ERR_TLS_CERT_ALTNAME_INVALID");this[I][m]=e;this[p][E](e)}function onHttp2FrameError(e,t,i){if(i===0){const i=new u(`HTTP/2: "frameError" received - type ${e}, code ${t}`);this[I][m]=i;this[p][E](i)}}function onHttp2SessionEnd(){const e=new l("other side closed",s.getSocketInfo(this[I]));this.destroy(e);s.destroy(this[I],e)}function onHTTP2GoAway(e){const t=this[m]||new l(`HTTP/2: "GOAWAY" frame received with code ${e}`,s.getSocketInfo(this));const i=this[p];i[I]=null;i[w]=null;if(this[T]!=null){this[T].destroy(t);this[T]=null}s.destroy(this[I],t);if(i[y]{if(t.aborted||t.completed){return}i=i||new a;s.errorRequest(e,t,i);if(w!=null){s.destroy(w,i)}s.destroy(C,i);e[h][e[y]++]=null;e[R]()};try{t.onConnect(abort)}catch(i){s.errorRequest(e,t,i)}if(t.aborted){return false}if(l==="CONNECT"){r.ref();w=r.request(b,{endStream:false,signal:m});if(w.id&&!w.pending){t.onUpgrade(null,null,w);++r[B];e[h][e[y]++]=null}else{w.once("ready",(()=>{t.onUpgrade(null,null,w);++r[B];e[h][e[y]++]=null}))}w.once("close",(()=>{r[B]-=1;if(r[B]===0)r.unref()}));return true}b[V]=d;b[F]="https";const _=l==="PUT"||l==="POST"||l==="PATCH";if(C&&typeof C.read==="function"){C.read(0)}let M=s.bodyLength(C);if(s.isFormDataLike(C)){D??=i(4492).extractBody;const[e,t]=D(C);b["content-type"]=t;C=e.stream;M=e.length}if(M==null){M=t.contentLength}if(M===0||!_){M=null}if(shouldSendContentLength(l)&&M>0&&t.contentLength!=null&&t.contentLength!==M){if(e[v]){s.errorRequest(e,t,new o);return false}process.emitWarning(new o)}if(M!=null){n(C,"no body must not have content length");b[O]=`${M}`}r.ref();const L=l==="GET"||l==="HEAD"||C===null;if(f){b[N]="100-continue";w=r.request(b,{endStream:L,signal:m});w.once("continue",writeBodyH2)}else{w=r.request(b,{endStream:L,signal:m});writeBodyH2()}++r[B];w.once("response",(i=>{const{[q]:n,...r}=i;t.onResponseStarted();if(t.aborted){const i=new a;s.errorRequest(e,t,i);s.destroy(w,i);return}if(t.onHeaders(Number(n),parseH2Headers(r),w.resume.bind(w),"")===false){w.pause()}w.on("data",(e=>{if(t.onData(e)===false){w.pause()}}))}));w.once("end",(()=>{if(w.state?.state==null||w.state.state<6){t.onComplete([])}if(r[B]===0){r.unref()}abort(new u("HTTP/2: stream half-closed (remote)"));e[h][e[y]++]=null;e[g]=e[y];e[R]()}));w.once("close",(()=>{r[B]-=1;if(r[B]===0){r.unref()}}));w.once("error",(function(e){abort(e)}));w.once("frameError",((e,t)=>{abort(new u(`HTTP/2: "frameError" received - type ${e}, code ${t}`))}));return true;function writeBodyH2(){if(!C||M===0){writeBuffer(abort,w,null,e,t,e[I],M,_)}else if(s.isBuffer(C)){writeBuffer(abort,w,C,e,t,e[I],M,_)}else if(s.isBlobLike(C)){if(typeof C.stream==="function"){writeIterable(abort,w,C.stream(),e,t,e[I],M,_)}else{writeBlob(abort,w,C,e,t,e[I],M,_)}}else if(s.isStream(C)){writeStream(abort,e[I],_,w,C,e,t,M)}else if(s.isIterable(C)){writeIterable(abort,w,C,e,t,e[I],M,_)}else{n(false)}}}function writeBuffer(e,t,i,r,o,a,l,u){try{if(i!=null&&s.isBuffer(i)){n(l===i.byteLength,"buffer body must have content length");t.cork();t.write(i);t.uncork();t.end();o.onBodySent(i)}if(!u){a[d]=true}o.onRequestSent();r[R]()}catch(t){e(t)}}function writeStream(e,t,i,o,a,l,u,c){n(c!==0||l[A]===0,"stream body cannot be pipelined");const p=r(a,o,(n=>{if(n){s.destroy(p,n);e(n)}else{s.removeAllListeners(p);u.onRequestSent();if(!i){t[d]=true}l[R]()}}));s.addListener(p,"data",onPipeData);function onPipeData(e){u.onBodySent(e)}}async function writeBlob(e,t,i,r,s,a,l,u){n(l===i.size,"blob body must have content length");try{if(l!=null&&l!==i.size){throw new o}const e=Buffer.from(await i.arrayBuffer());t.cork();t.write(e);t.uncork();t.end();s.onBodySent(e);s.onRequestSent();if(!u){a[d]=true}r[R]()}catch(t){e(t)}}async function writeIterable(e,t,i,r,s,o,a,l){n(a!==0||r[A]===0,"iterator body cannot be pipelined");let u=null;function onDrain(){if(u){const e=u;u=null;e()}}const waitForDrain=()=>new Promise(((e,t)=>{n(u===null);if(o[m]){t(o[m])}else{u=e}}));t.on("close",onDrain).on("drain",onDrain);try{for await(const e of i){if(o[m]){throw o[m]}const i=t.write(e);s.onBodySent(e);if(!i){await waitForDrain()}}t.end();s.onRequestSent();if(!l){o[d]=true}r[R]()}catch(t){e(t)}finally{t.off("close",onDrain).off("drain",onDrain)}}e.exports=connectH2},3701:(e,t,i)=>{const n=i(4589);const r=i(7030);const s=i(7067);const o=i(3440);const{channels:a}=i(2414);const l=i(4655);const u=i(1841);const{InvalidArgumentError:c,InformationalError:d,ClientDestroyedError:p}=i(8707);const A=i(9136);const{kUrl:f,kServerName:h,kClient:g,kBusy:y,kConnect:m,kResuming:I,kRunning:v,kPending:E,kSize:C,kQueue:T,kConnected:R,kConnecting:b,kNeedDrain:w,kKeepAliveDefaultTimeout:B,kHostHeader:D,kPendingIdx:S,kRunningIdx:k,kError:P,kPipelining:U,kKeepAliveTimeoutValue:V,kMaxHeadersSize:F,kKeepAliveMaxTimeout:O,kKeepAliveTimeoutThreshold:N,kHeadersTimeout:q,kBodyTimeout:_,kStrictContentLength:M,kConnector:L,kMaxRedirections:G,kMaxRequests:j,kCounter:x,kClose:H,kDestroy:W,kDispatch:Y,kInterceptors:J,kLocalAddress:z,kMaxResponseSize:$,kOnError:K,kHTTPContext:Z,kMaxConcurrentStreams:X,kResume:ee}=i(6443);const te=i(637);const ie=i(8788);let ne=false;const re=Symbol("kClosedResolve");const noop=()=>{};function getPipelining(e){return e[U]??e[Z]?.defaultPipelining??1}class Client extends u{constructor(e,{interceptors:t,maxHeaderSize:i,headersTimeout:n,socketTimeout:a,requestTimeout:l,connectTimeout:u,bodyTimeout:d,idleTimeout:p,keepAlive:g,keepAliveTimeout:y,maxKeepAliveTimeout:m,keepAliveMaxTimeout:v,keepAliveTimeoutThreshold:E,socketPath:C,pipelining:R,tls:b,strictContentLength:P,maxCachedSessions:x,maxRedirections:H,connect:W,maxRequestsPerClient:Y,localAddress:te,maxResponseSize:ie,autoSelectFamily:oe,autoSelectFamilyAttemptTimeout:ae,maxConcurrentStreams:le,allowH2:ue}={}){super();if(g!==undefined){throw new c("unsupported keepAlive, use pipelining=0 instead")}if(a!==undefined){throw new c("unsupported socketTimeout, use headersTimeout & bodyTimeout instead")}if(l!==undefined){throw new c("unsupported requestTimeout, use headersTimeout & bodyTimeout instead")}if(p!==undefined){throw new c("unsupported idleTimeout, use keepAliveTimeout instead")}if(m!==undefined){throw new c("unsupported maxKeepAliveTimeout, use keepAliveMaxTimeout instead")}if(i!=null&&!Number.isFinite(i)){throw new c("invalid maxHeaderSize")}if(C!=null&&typeof C!=="string"){throw new c("invalid socketPath")}if(u!=null&&(!Number.isFinite(u)||u<0)){throw new c("invalid connectTimeout")}if(y!=null&&(!Number.isFinite(y)||y<=0)){throw new c("invalid keepAliveTimeout")}if(v!=null&&(!Number.isFinite(v)||v<=0)){throw new c("invalid keepAliveMaxTimeout")}if(E!=null&&!Number.isFinite(E)){throw new c("invalid keepAliveTimeoutThreshold")}if(n!=null&&(!Number.isInteger(n)||n<0)){throw new c("headersTimeout must be a positive integer or zero")}if(d!=null&&(!Number.isInteger(d)||d<0)){throw new c("bodyTimeout must be a positive integer or zero")}if(W!=null&&typeof W!=="function"&&typeof W!=="object"){throw new c("connect must be a function or an object")}if(H!=null&&(!Number.isInteger(H)||H<0)){throw new c("maxRedirections must be a positive number")}if(Y!=null&&(!Number.isInteger(Y)||Y<0)){throw new c("maxRequestsPerClient must be a positive number")}if(te!=null&&(typeof te!=="string"||r.isIP(te)===0)){throw new c("localAddress must be valid string IP address")}if(ie!=null&&(!Number.isInteger(ie)||ie<-1)){throw new c("maxResponseSize must be a positive number")}if(ae!=null&&(!Number.isInteger(ae)||ae<-1)){throw new c("autoSelectFamilyAttemptTimeout must be a positive number")}if(ue!=null&&typeof ue!=="boolean"){throw new c("allowH2 must be a valid boolean value")}if(le!=null&&(typeof le!=="number"||le<1)){throw new c("maxConcurrentStreams must be a positive integer, greater than 0")}if(typeof W!=="function"){W=A({...b,maxCachedSessions:x,allowH2:ue,socketPath:C,timeout:u,...oe?{autoSelectFamily:oe,autoSelectFamilyAttemptTimeout:ae}:undefined,...W})}if(t?.Client&&Array.isArray(t.Client)){this[J]=t.Client;if(!ne){ne=true;process.emitWarning("Client.Options#interceptor is deprecated. Use Dispatcher#compose instead.",{code:"UNDICI-CLIENT-INTERCEPTOR-DEPRECATED"})}}else{this[J]=[se({maxRedirections:H})]}this[f]=o.parseOrigin(e);this[L]=W;this[U]=R!=null?R:1;this[F]=i||s.maxHeaderSize;this[B]=y==null?4e3:y;this[O]=v==null?6e5:v;this[N]=E==null?2e3:E;this[V]=this[B];this[h]=null;this[z]=te!=null?te:null;this[I]=0;this[w]=0;this[D]=`host: ${this[f].hostname}${this[f].port?`:${this[f].port}`:""}\r\n`;this[_]=d!=null?d:3e5;this[q]=n!=null?n:3e5;this[M]=P==null?true:P;this[G]=H;this[j]=Y;this[re]=null;this[$]=ie>-1?ie:-1;this[X]=le!=null?le:100;this[Z]=null;this[T]=[];this[k]=0;this[S]=0;this[ee]=e=>resume(this,e);this[K]=e=>onError(this,e)}get pipelining(){return this[U]}set pipelining(e){this[U]=e;this[ee](true)}get[E](){return this[T].length-this[S]}get[v](){return this[S]-this[k]}get[C](){return this[T].length-this[k]}get[R](){return!!this[Z]&&!this[b]&&!this[Z].destroyed}get[y](){return Boolean(this[Z]?.busy(null)||this[C]>=(getPipelining(this)||1)||this[E]>0)}[m](e){connect(this);this.once("connect",e)}[Y](e,t){const i=e.origin||this[f].origin;const n=new l(i,e,t);this[T].push(n);if(this[I]){}else if(o.bodyLength(n.body)==null&&o.isIterable(n.body)){this[I]=1;queueMicrotask((()=>resume(this)))}else{this[ee](true)}if(this[I]&&this[w]!==2&&this[y]){this[w]=2}return this[w]<2}async[H](){return new Promise((e=>{if(this[C]){this[re]=e}else{e(null)}}))}async[W](e){return new Promise((t=>{const i=this[T].splice(this[S]);for(let t=0;t{if(this[re]){this[re]();this[re]=null}t(null)};if(this[Z]){this[Z].destroy(e,callback);this[Z]=null}else{queueMicrotask(callback)}this[ee]()}))}}const se=i(5092);function onError(e,t){if(e[v]===0&&t.code!=="UND_ERR_INFO"&&t.code!=="UND_ERR_SOCKET"){n(e[S]===e[k]);const i=e[T].splice(e[k]);for(let n=0;n{e[L]({host:t,hostname:i,protocol:s,port:l,servername:e[h],localAddress:e[z]},((e,t)=>{if(e){r(e)}else{n(t)}}))}));if(e.destroyed){o.destroy(r.on("error",noop),new p);return}n(r);try{e[Z]=r.alpnProtocol==="h2"?await ie(e,r):await te(e,r)}catch(e){r.destroy().on("error",noop);throw e}e[b]=false;r[x]=0;r[j]=e[j];r[g]=e;r[P]=null;if(a.connected.hasSubscribers){a.connected.publish({connectParams:{host:t,hostname:i,protocol:s,port:l,version:e[Z]?.version,servername:e[h],localAddress:e[z]},connector:e[L],socket:r})}e.emit("connect",e[f],[e])}catch(r){if(e.destroyed){return}e[b]=false;if(a.connectError.hasSubscribers){a.connectError.publish({connectParams:{host:t,hostname:i,protocol:s,port:l,version:e[Z]?.version,servername:e[h],localAddress:e[z]},connector:e[L],error:r})}if(r.code==="ERR_TLS_CERT_ALTNAME_INVALID"){n(e[v]===0);while(e[E]>0&&e[T][e[S]].servername===e[h]){const t=e[T][e[S]++];o.errorRequest(e,t,r)}}else{onError(e,r)}e.emit("connectionError",e[f],[e],r)}e[ee]()}function emitDrain(e){e[w]=0;e.emit("drain",e[f],[e])}function resume(e,t){if(e[I]===2){return}e[I]=2;_resume(e,t);e[I]=0;if(e[k]>256){e[T].splice(0,e[k]);e[S]-=e[k];e[k]=0}}function _resume(e,t){while(true){if(e.destroyed){n(e[E]===0);return}if(e[re]&&!e[C]){e[re]();e[re]=null;return}if(e[Z]){e[Z].resume()}if(e[y]){e[w]=2}else if(e[w]===2){if(t){e[w]=1;queueMicrotask((()=>emitDrain(e)))}else{emitDrain(e)}continue}if(e[E]===0){return}if(e[v]>=(getPipelining(e)||1)){return}const i=e[T][e[S]];if(e[f].protocol==="https:"&&e[h]!==i.servername){if(e[v]>0){return}e[h]=i.servername;e[Z]?.destroy(new d("servername changed"),(()=>{e[Z]=null;resume(e)}))}if(e[b]){return}if(!e[Z]){connect(e);return}if(e[Z].destroyed){return}if(e[Z].busy(i)){return}if(!i.aborted&&e[Z].write(i)){e[S]++}else{e[T].splice(e[S],1)}}}e.exports=Client},1841:(e,t,i)=>{const n=i(883);const{ClientDestroyedError:r,ClientClosedError:s,InvalidArgumentError:o}=i(8707);const{kDestroy:a,kClose:l,kClosed:u,kDestroyed:c,kDispatch:d,kInterceptors:p}=i(6443);const A=Symbol("onDestroyed");const f=Symbol("onClosed");const h=Symbol("Intercepted Dispatch");class DispatcherBase extends n{constructor(){super();this[c]=false;this[A]=null;this[u]=false;this[f]=[]}get destroyed(){return this[c]}get closed(){return this[u]}get interceptors(){return this[p]}set interceptors(e){if(e){for(let t=e.length-1;t>=0;t--){const e=this[p][t];if(typeof e!=="function"){throw new o("interceptor must be an function")}}}this[p]=e}close(e){if(e===undefined){return new Promise(((e,t)=>{this.close(((i,n)=>i?t(i):e(n)))}))}if(typeof e!=="function"){throw new o("invalid callback")}if(this[c]){queueMicrotask((()=>e(new r,null)));return}if(this[u]){if(this[f]){this[f].push(e)}else{queueMicrotask((()=>e(null,null)))}return}this[u]=true;this[f].push(e);const onClosed=()=>{const e=this[f];this[f]=null;for(let t=0;tthis.destroy())).then((()=>{queueMicrotask(onClosed)}))}destroy(e,t){if(typeof e==="function"){t=e;e=null}if(t===undefined){return new Promise(((t,i)=>{this.destroy(e,((e,n)=>e?i(e):t(n)))}))}if(typeof t!=="function"){throw new o("invalid callback")}if(this[c]){if(this[A]){this[A].push(t)}else{queueMicrotask((()=>t(null,null)))}return}if(!e){e=new r}this[c]=true;this[A]=this[A]||[];this[A].push(t);const onDestroyed=()=>{const e=this[A];this[A]=null;for(let t=0;t{queueMicrotask(onDestroyed)}))}[h](e,t){if(!this[p]||this[p].length===0){this[h]=this[d];return this[d](e,t)}let i=this[d].bind(this);for(let e=this[p].length-1;e>=0;e--){i=this[p][e](i)}this[h]=i;return i(e,t)}dispatch(e,t){if(!t||typeof t!=="object"){throw new o("handler must be an object")}try{if(!e||typeof e!=="object"){throw new o("opts must be an object.")}if(this[c]||this[A]){throw new r}if(this[u]){throw new s}return this[h](e,t)}catch(e){if(typeof t.onError!=="function"){throw new o("invalid onError method")}t.onError(e);return false}}}e.exports=DispatcherBase},883:(e,t,i)=>{const n=i(8474);class Dispatcher extends n{dispatch(){throw new Error("not implemented")}close(){throw new Error("not implemented")}destroy(){throw new Error("not implemented")}compose(...e){const t=Array.isArray(e[0])?e[0]:e;let i=this.dispatch.bind(this);for(const e of t){if(e==null){continue}if(typeof e!=="function"){throw new TypeError(`invalid interceptor, expected function received ${typeof e}`)}i=e(i);if(i==null||typeof i!=="function"||i.length!==2){throw new TypeError("invalid interceptor")}}return new ComposedDispatcher(this,i)}}class ComposedDispatcher extends Dispatcher{#e=null;#t=null;constructor(e,t){super();this.#e=e;this.#t=t}dispatch(...e){this.#t(...e)}close(...e){return this.#e.close(...e)}destroy(...e){return this.#e.destroy(...e)}}e.exports=Dispatcher},3137:(e,t,i)=>{const n=i(1841);const{kClose:r,kDestroy:s,kClosed:o,kDestroyed:a,kDispatch:l,kNoProxyAgent:u,kHttpProxyAgent:c,kHttpsProxyAgent:d}=i(6443);const p=i(6672);const A=i(7405);const f={"http:":80,"https:":443};let h=false;class EnvHttpProxyAgent extends n{#i=null;#n=null;#r=null;constructor(e={}){super();this.#r=e;if(!h){h=true;process.emitWarning("EnvHttpProxyAgent is experimental, expect them to change at any time.",{code:"UNDICI-EHPA"})}const{httpProxy:t,httpsProxy:i,noProxy:n,...r}=e;this[u]=new A(r);const s=t??process.env.http_proxy??process.env.HTTP_PROXY;if(s){this[c]=new p({...r,uri:s})}else{this[c]=this[u]}const o=i??process.env.https_proxy??process.env.HTTPS_PROXY;if(o){this[d]=new p({...r,uri:o})}else{this[d]=this[c]}this.#s()}[l](e,t){const i=new URL(e.origin);const n=this.#o(i);return n.dispatch(e,t)}async[r](){await this[u].close();if(!this[c][o]){await this[c].close()}if(!this[d][o]){await this[d].close()}}async[s](e){await this[u].destroy(e);if(!this[c][a]){await this[c].destroy(e)}if(!this[d][a]){await this[d].destroy(e)}}#o(e){let{protocol:t,host:i,port:n}=e;i=i.replace(/:\d*$/,"").toLowerCase();n=Number.parseInt(n,10)||f[t]||0;if(!this.#a(i,n)){return this[u]}if(t==="https:"){return this[d]}return this[c]}#a(e,t){if(this.#l){this.#s()}if(this.#n.length===0){return true}if(this.#i==="*"){return false}for(let i=0;i{const t=2048;const i=t-1;class FixedCircularBuffer{constructor(){this.bottom=0;this.top=0;this.list=new Array(t);this.next=null}isEmpty(){return this.top===this.bottom}isFull(){return(this.top+1&i)===this.bottom}push(e){this.list[this.top]=e;this.top=this.top+1&i}shift(){const e=this.list[this.bottom];if(e===undefined)return null;this.list[this.bottom]=undefined;this.bottom=this.bottom+1&i;return e}}e.exports=class FixedQueue{constructor(){this.head=this.tail=new FixedCircularBuffer}isEmpty(){return this.head.isEmpty()}push(e){if(this.head.isFull()){this.head=this.head.next=new FixedCircularBuffer}this.head.push(e)}shift(){const e=this.tail;const t=e.shift();if(e.isEmpty()&&e.next!==null){this.tail=e.next}return t}}},2128:(e,t,i)=>{const n=i(1841);const r=i(4660);const{kConnected:s,kSize:o,kRunning:a,kPending:l,kQueued:u,kBusy:c,kFree:d,kUrl:p,kClose:A,kDestroy:f,kDispatch:h}=i(6443);const g=i(3246);const y=Symbol("clients");const m=Symbol("needDrain");const I=Symbol("queue");const v=Symbol("closed resolve");const E=Symbol("onDrain");const C=Symbol("onConnect");const T=Symbol("onDisconnect");const R=Symbol("onConnectionError");const b=Symbol("get dispatcher");const w=Symbol("add client");const B=Symbol("remove client");const D=Symbol("stats");class PoolBase extends n{constructor(){super();this[I]=new r;this[y]=[];this[u]=0;const e=this;this[E]=function onDrain(t,i){const n=e[I];let r=false;while(!r){const t=n.shift();if(!t){break}e[u]--;r=!this.dispatch(t.opts,t.handler)}this[m]=r;if(!this[m]&&e[m]){e[m]=false;e.emit("drain",t,[e,...i])}if(e[v]&&n.isEmpty()){Promise.all(e[y].map((e=>e.close()))).then(e[v])}};this[C]=(t,i)=>{e.emit("connect",t,[e,...i])};this[T]=(t,i,n)=>{e.emit("disconnect",t,[e,...i],n)};this[R]=(t,i,n)=>{e.emit("connectionError",t,[e,...i],n)};this[D]=new g(this)}get[c](){return this[m]}get[s](){return this[y].filter((e=>e[s])).length}get[d](){return this[y].filter((e=>e[s]&&!e[m])).length}get[l](){let e=this[u];for(const{[l]:t}of this[y]){e+=t}return e}get[a](){let e=0;for(const{[a]:t}of this[y]){e+=t}return e}get[o](){let e=this[u];for(const{[o]:t}of this[y]){e+=t}return e}get stats(){return this[D]}async[A](){if(this[I].isEmpty()){await Promise.all(this[y].map((e=>e.close())))}else{await new Promise((e=>{this[v]=e}))}}async[f](e){while(true){const t=this[I].shift();if(!t){break}t.handler.onError(e)}await Promise.all(this[y].map((t=>t.destroy(e))))}[h](e,t){const i=this[b]();if(!i){this[m]=true;this[I].push({opts:e,handler:t});this[u]++}else if(!i.dispatch(e,t)){i[m]=true;this[m]=!this[b]()}return!this[m]}[w](e){e.on("drain",this[E]).on("connect",this[C]).on("disconnect",this[T]).on("connectionError",this[R]);this[y].push(e);if(this[m]){queueMicrotask((()=>{if(this[m]){this[E](e[p],[this,e])}}))}return this}[B](e){e.close((()=>{const t=this[y].indexOf(e);if(t!==-1){this[y].splice(t,1)}}));this[m]=this[y].some((e=>!e[m]&&e.closed!==true&&e.destroyed!==true))}}e.exports={PoolBase:PoolBase,kClients:y,kNeedDrain:m,kAddClient:w,kRemoveClient:B,kGetDispatcher:b}},3246:(e,t,i)=>{const{kFree:n,kConnected:r,kPending:s,kQueued:o,kRunning:a,kSize:l}=i(6443);const u=Symbol("pool");class PoolStats{constructor(e){this[u]=e}get connected(){return this[u][r]}get free(){return this[u][n]}get pending(){return this[u][s]}get queued(){return this[u][o]}get running(){return this[u][a]}get size(){return this[u][l]}}e.exports=PoolStats},628:(e,t,i)=>{const{PoolBase:n,kClients:r,kNeedDrain:s,kAddClient:o,kGetDispatcher:a}=i(2128);const l=i(3701);const{InvalidArgumentError:u}=i(8707);const c=i(3440);const{kUrl:d,kInterceptors:p}=i(6443);const A=i(9136);const f=Symbol("options");const h=Symbol("connections");const g=Symbol("factory");function defaultFactory(e,t){return new l(e,t)}class Pool extends n{constructor(e,{connections:t,factory:i=defaultFactory,connect:n,connectTimeout:s,tls:o,maxCachedSessions:a,socketPath:l,autoSelectFamily:y,autoSelectFamilyAttemptTimeout:m,allowH2:I,...v}={}){super();if(t!=null&&(!Number.isFinite(t)||t<0)){throw new u("invalid connections")}if(typeof i!=="function"){throw new u("factory must be a function.")}if(n!=null&&typeof n!=="function"&&typeof n!=="object"){throw new u("connect must be a function or an object")}if(typeof n!=="function"){n=A({...o,maxCachedSessions:a,allowH2:I,socketPath:l,timeout:s,...y?{autoSelectFamily:y,autoSelectFamilyAttemptTimeout:m}:undefined,...n})}this[p]=v.interceptors?.Pool&&Array.isArray(v.interceptors.Pool)?v.interceptors.Pool:[];this[h]=t||null;this[d]=c.parseOrigin(e);this[f]={...c.deepClone(v),connect:n,allowH2:I};this[f].interceptors=v.interceptors?{...v.interceptors}:undefined;this[g]=i;this.on("connectionError",((e,t,i)=>{for(const e of t){const t=this[r].indexOf(e);if(t!==-1){this[r].splice(t,1)}}}))}[a](){for(const e of this[r]){if(!e[s]){return e}}if(!this[h]||this[r].length{const{kProxy:n,kClose:r,kDestroy:s,kDispatch:o,kInterceptors:a}=i(6443);const{URL:l}=i(3136);const u=i(7405);const c=i(628);const d=i(1841);const{InvalidArgumentError:p,RequestAbortedError:A,SecureProxyConnectionError:f}=i(8707);const h=i(9136);const g=i(3701);const y=Symbol("proxy agent");const m=Symbol("proxy client");const I=Symbol("proxy headers");const v=Symbol("request tls settings");const E=Symbol("proxy tls settings");const C=Symbol("connect endpoint function");const T=Symbol("tunnel proxy");function defaultProtocolPort(e){return e==="https:"?443:80}function defaultFactory(e,t){return new c(e,t)}const noop=()=>{};function defaultAgentFactory(e,t){if(t.connections===1){return new g(e,t)}return new c(e,t)}class Http1ProxyWrapper extends d{#c;constructor(e,{headers:t={},connect:i,factory:n}){super();if(!e){throw new p("Proxy URL is mandatory")}this[I]=t;if(n){this.#c=n(e,{connect:i})}else{this.#c=new g(e,{connect:i})}}[o](e,t){const i=t.onHeaders;t.onHeaders=function(e,n,r){if(e===407){if(typeof t.onError==="function"){t.onError(new p("Proxy Authentication Required (407)"))}return}if(i)i.call(this,e,n,r)};const{origin:n,path:r="/",headers:s={}}=e;e.path=n+r;if(!("host"in s)&&!("Host"in s)){const{host:e}=new l(n);s.host=e}e.headers={...this[I],...s};return this.#c[o](e,t)}async[r](){return this.#c.close()}async[s](e){return this.#c.destroy(e)}}class ProxyAgent extends d{constructor(e){super();if(!e||typeof e==="object"&&!(e instanceof l)&&!e.uri){throw new p("Proxy uri is mandatory")}const{clientFactory:t=defaultFactory}=e;if(typeof t!=="function"){throw new p("Proxy opts.clientFactory must be a function.")}const{proxyTunnel:i=true}=e;const r=this.#d(e);const{href:s,origin:o,port:c,protocol:d,username:g,password:R,hostname:b}=r;this[n]={uri:s,protocol:d};this[a]=e.interceptors?.ProxyAgent&&Array.isArray(e.interceptors.ProxyAgent)?e.interceptors.ProxyAgent:[];this[v]=e.requestTls;this[E]=e.proxyTls;this[I]=e.headers||{};this[T]=i;if(e.auth&&e.token){throw new p("opts.auth cannot be used in combination with opts.token")}else if(e.auth){this[I]["proxy-authorization"]=`Basic ${e.auth}`}else if(e.token){this[I]["proxy-authorization"]=e.token}else if(g&&R){this[I]["proxy-authorization"]=`Basic ${Buffer.from(`${decodeURIComponent(g)}:${decodeURIComponent(R)}`).toString("base64")}`}const w=h({...e.proxyTls});this[C]=h({...e.requestTls});const B=e.factory||defaultAgentFactory;const factory=(e,t)=>{const{protocol:i}=new l(e);if(!this[T]&&i==="http:"&&this[n].protocol==="http:"){return new Http1ProxyWrapper(this[n].uri,{headers:this[I],connect:w,factory:B})}return B(e,t)};this[m]=t(r,{connect:w});this[y]=new u({...e,factory:factory,connect:async(e,t)=>{let i=e.host;if(!e.port){i+=`:${defaultProtocolPort(e.protocol)}`}try{const{socket:n,statusCode:r}=await this[m].connect({origin:o,port:c,path:i,signal:e.signal,headers:{...this[I],host:e.host},servername:this[E]?.servername||b});if(r!==200){n.on("error",noop).destroy();t(new A(`Proxy response (${r}) !== 200 when HTTP Tunneling`))}if(e.protocol!=="https:"){t(null,n);return}let s;if(this[v]){s=this[v].servername}else{s=e.servername}this[C]({...e,servername:s,httpSocket:n},t)}catch(e){if(e.code==="ERR_TLS_CERT_ALTNAME_INVALID"){t(new f(e))}else{t(e)}}}})}dispatch(e,t){const i=buildHeaders(e.headers);throwIfProxyAuthIsSent(i);if(i&&!("host"in i)&&!("Host"in i)){const{host:t}=new l(e.origin);i.host=t}return this[y].dispatch({...e,headers:i},t)}#d(e){if(typeof e==="string"){return new l(e)}else if(e instanceof l){return e}else{return new l(e.uri)}}async[r](){await this[y].close();await this[m].close()}async[s](){await this[y].destroy();await this[m].destroy()}}function buildHeaders(e){if(Array.isArray(e)){const t={};for(let i=0;ie.toLowerCase()==="proxy-authorization"));if(t){throw new p("Proxy-Authorization should be sent in ProxyAgent constructor")}}e.exports=ProxyAgent},50:(e,t,i)=>{const n=i(883);const r=i(7816);class RetryAgent extends n{#p=null;#A=null;constructor(e,t={}){super(t);this.#p=e;this.#A=t}dispatch(e,t){const i=new r({...e,retryOptions:this.#A},{dispatch:this.#p.dispatch.bind(this.#p),handler:t});return this.#p.dispatch(e,i)}close(){return this.#p.close()}destroy(){return this.#p.destroy()}}e.exports=RetryAgent},2581:(e,t,i)=>{const n=Symbol.for("undici.globalDispatcher.1");const{InvalidArgumentError:r}=i(8707);const s=i(7405);if(getGlobalDispatcher()===undefined){setGlobalDispatcher(new s)}function setGlobalDispatcher(e){if(!e||typeof e.dispatch!=="function"){throw new r("Argument agent must implement Agent")}Object.defineProperty(globalThis,n,{value:e,writable:true,enumerable:false,configurable:false})}function getGlobalDispatcher(){return globalThis[n]}e.exports={setGlobalDispatcher:setGlobalDispatcher,getGlobalDispatcher:getGlobalDispatcher}},8155:e=>{e.exports=class DecoratorHandler{#f;constructor(e){if(typeof e!=="object"||e===null){throw new TypeError("handler must be an object")}this.#f=e}onConnect(...e){return this.#f.onConnect?.(...e)}onError(...e){return this.#f.onError?.(...e)}onUpgrade(...e){return this.#f.onUpgrade?.(...e)}onResponseStarted(...e){return this.#f.onResponseStarted?.(...e)}onHeaders(...e){return this.#f.onHeaders?.(...e)}onData(...e){return this.#f.onData?.(...e)}onComplete(...e){return this.#f.onComplete?.(...e)}onBodySent(...e){return this.#f.onBodySent?.(...e)}}},8754:(e,t,i)=>{const n=i(3440);const{kBodyUsed:r}=i(6443);const s=i(4589);const{InvalidArgumentError:o}=i(8707);const a=i(8474);const l=[300,301,302,303,307,308];const u=Symbol("body");class BodyAsyncIterable{constructor(e){this[u]=e;this[r]=false}async*[Symbol.asyncIterator](){s(!this[r],"disturbed");this[r]=true;yield*this[u]}}class RedirectHandler{constructor(e,t,i,l){if(t!=null&&(!Number.isInteger(t)||t<0)){throw new o("maxRedirections must be a positive number")}n.validateHandler(l,i.method,i.upgrade);this.dispatch=e;this.location=null;this.abort=null;this.opts={...i,maxRedirections:0};this.maxRedirections=t;this.handler=l;this.history=[];this.redirectionLimitReached=false;if(n.isStream(this.opts.body)){if(n.bodyLength(this.opts.body)===0){this.opts.body.on("data",(function(){s(false)}))}if(typeof this.opts.body.readableDidRead!=="boolean"){this.opts.body[r]=false;a.prototype.on.call(this.opts.body,"data",(function(){this[r]=true}))}}else if(this.opts.body&&typeof this.opts.body.pipeTo==="function"){this.opts.body=new BodyAsyncIterable(this.opts.body)}else if(this.opts.body&&typeof this.opts.body!=="string"&&!ArrayBuffer.isView(this.opts.body)&&n.isIterable(this.opts.body)){this.opts.body=new BodyAsyncIterable(this.opts.body)}}onConnect(e){this.abort=e;this.handler.onConnect(e,{history:this.history})}onUpgrade(e,t,i){this.handler.onUpgrade(e,t,i)}onError(e){this.handler.onError(e)}onHeaders(e,t,i,r){this.location=this.history.length>=this.maxRedirections||n.isDisturbed(this.opts.body)?null:parseLocation(e,t);if(this.opts.throwOnMaxRedirect&&this.history.length>=this.maxRedirections){if(this.request){this.request.abort(new Error("max redirects"))}this.redirectionLimitReached=true;this.abort(new Error("max redirects"));return}if(this.opts.origin){this.history.push(new URL(this.opts.path,this.opts.origin))}if(!this.location){return this.handler.onHeaders(e,t,i,r)}const{origin:s,pathname:o,search:a}=n.parseURL(new URL(this.location,this.opts.origin&&new URL(this.opts.path,this.opts.origin)));const l=a?`${o}${a}`:o;this.opts.headers=cleanRequestHeaders(this.opts.headers,e===303,this.opts.origin!==s);this.opts.path=l;this.opts.origin=s;this.opts.maxRedirections=0;this.opts.query=null;if(e===303&&this.opts.method!=="HEAD"){this.opts.method="GET";this.opts.body=null}}onData(e){if(this.location){}else{return this.handler.onData(e)}}onComplete(e){if(this.location){this.location=null;this.abort=null;this.dispatch(this.opts,this)}else{this.handler.onComplete(e)}}onBodySent(e){if(this.handler.onBodySent){this.handler.onBodySent(e)}}}function parseLocation(e,t){if(l.indexOf(e)===-1){return null}for(let e=0;e{const n=i(4589);const{kRetryHandlerDefaultRetry:r}=i(6443);const{RequestRetryError:s}=i(8707);const{isDisturbed:o,parseHeaders:a,parseRangeHeader:l,wrapRequestBody:u}=i(3440);function calculateRetryAfterHeader(e){const t=Date.now();return new Date(e).getTime()-t}class RetryHandler{constructor(e,t){const{retryOptions:i,...n}=e;const{retry:s,maxRetries:o,maxTimeout:a,minTimeout:l,timeoutFactor:c,methods:d,errorCodes:p,retryAfter:A,statusCodes:f}=i??{};this.dispatch=t.dispatch;this.handler=t.handler;this.opts={...n,body:u(e.body)};this.abort=null;this.aborted=false;this.retryOpts={retry:s??RetryHandler[r],retryAfter:A??true,maxTimeout:a??30*1e3,minTimeout:l??500,timeoutFactor:c??2,maxRetries:o??5,methods:d??["GET","HEAD","OPTIONS","PUT","DELETE","TRACE"],statusCodes:f??[500,502,503,504,429],errorCodes:p??["ECONNRESET","ECONNREFUSED","ENOTFOUND","ENETDOWN","ENETUNREACH","EHOSTDOWN","EHOSTUNREACH","EPIPE","UND_ERR_SOCKET"]};this.retryCount=0;this.retryCountCheckpoint=0;this.start=0;this.end=null;this.etag=null;this.resume=null;this.handler.onConnect((e=>{this.aborted=true;if(this.abort){this.abort(e)}else{this.reason=e}}))}onRequestSent(){if(this.handler.onRequestSent){this.handler.onRequestSent()}}onUpgrade(e,t,i){if(this.handler.onUpgrade){this.handler.onUpgrade(e,t,i)}}onConnect(e){if(this.aborted){e(this.reason)}else{this.abort=e}}onBodySent(e){if(this.handler.onBodySent)return this.handler.onBodySent(e)}static[r](e,{state:t,opts:i},n){const{statusCode:r,code:s,headers:o}=e;const{method:a,retryOptions:l}=i;const{maxRetries:u,minTimeout:c,maxTimeout:d,timeoutFactor:p,statusCodes:A,errorCodes:f,methods:h}=l;const{counter:g}=t;if(s&&s!=="UND_ERR_REQ_RETRY"&&!f.includes(s)){n(e);return}if(Array.isArray(h)&&!h.includes(a)){n(e);return}if(r!=null&&Array.isArray(A)&&!A.includes(r)){n(e);return}if(g>u){n(e);return}let y=o?.["retry-after"];if(y){y=Number(y);y=Number.isNaN(y)?calculateRetryAfterHeader(y):y*1e3}const m=y>0?Math.min(y,d):Math.min(c*p**(g-1),d);setTimeout((()=>n(null)),m)}onHeaders(e,t,i,r){const o=a(t);this.retryCount+=1;if(e>=300){if(this.retryOpts.statusCodes.includes(e)===false){return this.handler.onHeaders(e,t,i,r)}else{this.abort(new s("Request failed",e,{headers:o,data:{count:this.retryCount}}));return false}}if(this.resume!=null){this.resume=null;if(e!==206&&(this.start>0||e!==200)){this.abort(new s("server does not support the range header and the payload was partially consumed",e,{headers:o,data:{count:this.retryCount}}));return false}const t=l(o["content-range"]);if(!t){this.abort(new s("Content-Range mismatch",e,{headers:o,data:{count:this.retryCount}}));return false}if(this.etag!=null&&this.etag!==o.etag){this.abort(new s("ETag mismatch",e,{headers:o,data:{count:this.retryCount}}));return false}const{start:r,size:a,end:u=a-1}=t;n(this.start===r,"content-range mismatch");n(this.end==null||this.end===u,"content-range mismatch");this.resume=i;return true}if(this.end==null){if(e===206){const s=l(o["content-range"]);if(s==null){return this.handler.onHeaders(e,t,i,r)}const{start:a,size:u,end:c=u-1}=s;n(a!=null&&Number.isFinite(a),"content-range mismatch");n(c!=null&&Number.isFinite(c),"invalid content-length");this.start=a;this.end=c}if(this.end==null){const e=o["content-length"];this.end=e!=null?Number(e)-1:null}n(Number.isFinite(this.start));n(this.end==null||Number.isFinite(this.end),"invalid content-length");this.resume=i;this.etag=o.etag!=null?o.etag:null;if(this.etag!=null&&this.etag.startsWith("W/")){this.etag=null}return this.handler.onHeaders(e,t,i,r)}const u=new s("Request failed",e,{headers:o,data:{count:this.retryCount}});this.abort(u);return false}onData(e){this.start+=e.length;return this.handler.onData(e)}onComplete(e){this.retryCount=0;return this.handler.onComplete(e)}onError(e){if(this.aborted||o(this.opts.body)){return this.handler.onError(e)}if(this.retryCount-this.retryCountCheckpoint>0){this.retryCount=this.retryCountCheckpoint+(this.retryCount-this.retryCountCheckpoint)}else{this.retryCount+=1}this.retryOpts.retry(e,{state:{counter:this.retryCount},opts:{retryOptions:this.retryOpts,...this.opts}},onRetry.bind(this));function onRetry(e){if(e!=null||this.aborted||o(this.opts.body)){return this.handler.onError(e)}if(this.start!==0){const e={range:`bytes=${this.start}-${this.end??""}`};if(this.etag!=null){e["if-match"]=this.etag}this.opts={...this.opts,headers:{...this.opts.headers,...e}}}try{this.retryCountCheckpoint=this.retryCount;this.dispatch(this.opts,this)}catch(e){this.handler.onError(e)}}}}e.exports=RetryHandler},379:(e,t,i)=>{const{isIP:n}=i(7030);const{lookup:r}=i(610);const s=i(8155);const{InvalidArgumentError:o,InformationalError:a}=i(8707);const l=Math.pow(2,31)-1;class DNSInstance{#h=0;#g=0;#y=new Map;dualStack=true;affinity=null;lookup=null;pick=null;constructor(e){this.#h=e.maxTTL;this.#g=e.maxItems;this.dualStack=e.dualStack;this.affinity=e.affinity;this.lookup=e.lookup??this.#m;this.pick=e.pick??this.#I}get full(){return this.#y.size===this.#g}runLookup(e,t,i){const n=this.#y.get(e.hostname);if(n==null&&this.full){i(null,e.origin);return}const r={affinity:this.affinity,dualStack:this.dualStack,lookup:this.lookup,pick:this.pick,...t.dns,maxTTL:this.#h,maxItems:this.#g};if(n==null){this.lookup(e,r,((t,n)=>{if(t||n==null||n.length===0){i(t??new a("No DNS entries found"));return}this.setRecords(e,n);const s=this.#y.get(e.hostname);const o=this.pick(e,s,r.affinity);let l;if(typeof o.port==="number"){l=`:${o.port}`}else if(e.port!==""){l=`:${e.port}`}else{l=""}i(null,`${e.protocol}//${o.family===6?`[${o.address}]`:o.address}${l}`)}))}else{const s=this.pick(e,n,r.affinity);if(s==null){this.#y.delete(e.hostname);this.runLookup(e,t,i);return}let o;if(typeof s.port==="number"){o=`:${s.port}`}else if(e.port!==""){o=`:${e.port}`}else{o=""}i(null,`${e.protocol}//${s.family===6?`[${s.address}]`:s.address}${o}`)}}#m(e,t,i){r(e.hostname,{all:true,family:this.dualStack===false?this.affinity:0,order:"ipv4first"},((e,t)=>{if(e){return i(e)}const n=new Map;for(const e of t){n.set(`${e.address}:${e.family}`,e)}i(null,n.values())}))}#I(e,t,i){let n=null;const{records:r,offset:s}=t;let o;if(this.dualStack){if(i==null){if(s==null||s===l){t.offset=0;i=4}else{t.offset++;i=(t.offset&1)===1?6:4}}if(r[i]!=null&&r[i].ips.length>0){o=r[i]}else{o=r[i===4?6:4]}}else{o=r[i]}if(o==null||o.ips.length===0){return n}if(o.offset==null||o.offset===l){o.offset=0}else{o.offset++}const a=o.offset%o.ips.length;n=o.ips[a]??null;if(n==null){return n}if(Date.now()-n.timestamp>n.ttl){o.ips.splice(a,1);return this.pick(e,t,i)}return n}setRecords(e,t){const i=Date.now();const n={records:{4:null,6:null}};for(const e of t){e.timestamp=i;if(typeof e.ttl==="number"){e.ttl=Math.min(e.ttl,this.#h)}else{e.ttl=this.#h}const t=n.records[e.family]??{ips:[]};t.ips.push(e);n.records[e.family]=t}this.#y.set(e.hostname,n)}getHandler(e,t){return new DNSDispatchHandler(this,e,t)}}class DNSDispatchHandler extends s{#v=null;#r=null;#t=null;#f=null;#E=null;constructor(e,{origin:t,handler:i,dispatch:n},r){super(i);this.#E=t;this.#f=i;this.#r={...r};this.#v=e;this.#t=n}onError(e){switch(e.code){case"ETIMEDOUT":case"ECONNREFUSED":{if(this.#v.dualStack){this.#v.runLookup(this.#E,this.#r,((e,t)=>{if(e){return this.#f.onError(e)}const i={...this.#r,origin:t};this.#t(i,this)}));return}this.#f.onError(e);return}case"ENOTFOUND":this.#v.deleteRecord(this.#E);default:this.#f.onError(e);break}}}e.exports=e=>{if(e?.maxTTL!=null&&(typeof e?.maxTTL!=="number"||e?.maxTTL<0)){throw new o("Invalid maxTTL. Must be a positive number")}if(e?.maxItems!=null&&(typeof e?.maxItems!=="number"||e?.maxItems<1)){throw new o("Invalid maxItems. Must be a positive number and greater than zero")}if(e?.affinity!=null&&e?.affinity!==4&&e?.affinity!==6){throw new o("Invalid affinity. Must be either 4 or 6")}if(e?.dualStack!=null&&typeof e?.dualStack!=="boolean"){throw new o("Invalid dualStack. Must be a boolean")}if(e?.lookup!=null&&typeof e?.lookup!=="function"){throw new o("Invalid lookup. Must be a function")}if(e?.pick!=null&&typeof e?.pick!=="function"){throw new o("Invalid pick. Must be a function")}const t=e?.dualStack??true;let i;if(t){i=e?.affinity??null}else{i=e?.affinity??4}const r={maxTTL:e?.maxTTL??1e4,lookup:e?.lookup??null,pick:e?.pick??null,dualStack:t,affinity:i,maxItems:e?.maxItems??Infinity};const s=new DNSInstance(r);return e=>function dnsInterceptor(t,i){const r=t.origin.constructor===URL?t.origin:new URL(t.origin);if(n(r.hostname)!==0){return e(t,i)}s.runLookup(r,t,((n,o)=>{if(n){return i.onError(n)}let a=null;a={...t,servername:r.hostname,origin:o,headers:{host:r.hostname,...t.headers}};e(a,s.getHandler({origin:r,dispatch:e,handler:i},t))}));return true}}},8060:(e,t,i)=>{const n=i(3440);const{InvalidArgumentError:r,RequestAbortedError:s}=i(8707);const o=i(8155);class DumpHandler extends o{#C=1024*1024;#T=null;#R=false;#b=false;#w=0;#B=null;#f=null;constructor({maxSize:e},t){super(t);if(e!=null&&(!Number.isFinite(e)||e<1)){throw new r("maxSize must be a number greater than 0")}this.#C=e??this.#C;this.#f=t}onConnect(e){this.#T=e;this.#f.onConnect(this.#Q.bind(this))}#Q(e){this.#b=true;this.#B=e}onHeaders(e,t,i,r){const o=n.parseHeaders(t);const a=o["content-length"];if(a!=null&&a>this.#C){throw new s(`Response size (${a}) larger than maxSize (${this.#C})`)}if(this.#b){return true}return this.#f.onHeaders(e,t,i,r)}onError(e){if(this.#R){return}e=this.#B??e;this.#f.onError(e)}onData(e){this.#w=this.#w+e.length;if(this.#w>=this.#C){this.#R=true;if(this.#b){this.#f.onError(this.#B)}else{this.#f.onComplete([])}}return true}onComplete(e){if(this.#R){return}if(this.#b){this.#f.onError(this.reason);return}this.#f.onComplete(e)}}function createDumpInterceptor({maxSize:e}={maxSize:1024*1024}){return t=>function Intercept(i,n){const{dumpMaxSize:r=e}=i;const s=new DumpHandler({maxSize:r},n);return t(i,s)}}e.exports=createDumpInterceptor},5092:(e,t,i)=>{const n=i(8754);function createRedirectInterceptor({maxRedirections:e}){return t=>function Intercept(i,r){const{maxRedirections:s=e}=i;if(!s){return t(i,r)}const o=new n(t,s,i,r);i={...i,maxRedirections:0};return t(i,o)}}e.exports=createRedirectInterceptor},1514:(e,t,i)=>{const n=i(8754);e.exports=e=>{const t=e?.maxRedirections;return e=>function redirectInterceptor(i,r){const{maxRedirections:s=t,...o}=i;if(!s){return e(i,r)}const a=new n(e,s,i,r);return e(o,a)}}},2026:(e,t,i)=>{const n=i(7816);e.exports=e=>t=>function retryInterceptor(i,r){return t(i,new n({...i,retryOptions:{...e,...i.retryOptions}},{handler:r,dispatch:t}))}},2824:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:true});t.SPECIAL_HEADERS=t.HEADER_STATE=t.MINOR=t.MAJOR=t.CONNECTION_TOKEN_CHARS=t.HEADER_CHARS=t.TOKEN=t.STRICT_TOKEN=t.HEX=t.URL_CHAR=t.STRICT_URL_CHAR=t.USERINFO_CHARS=t.MARK=t.ALPHANUM=t.NUM=t.HEX_MAP=t.NUM_MAP=t.ALPHA=t.FINISH=t.H_METHOD_MAP=t.METHOD_MAP=t.METHODS_RTSP=t.METHODS_ICE=t.METHODS_HTTP=t.METHODS=t.LENIENT_FLAGS=t.FLAGS=t.TYPE=t.ERROR=void 0;const n=i(172);var r;(function(e){e[e["OK"]=0]="OK";e[e["INTERNAL"]=1]="INTERNAL";e[e["STRICT"]=2]="STRICT";e[e["LF_EXPECTED"]=3]="LF_EXPECTED";e[e["UNEXPECTED_CONTENT_LENGTH"]=4]="UNEXPECTED_CONTENT_LENGTH";e[e["CLOSED_CONNECTION"]=5]="CLOSED_CONNECTION";e[e["INVALID_METHOD"]=6]="INVALID_METHOD";e[e["INVALID_URL"]=7]="INVALID_URL";e[e["INVALID_CONSTANT"]=8]="INVALID_CONSTANT";e[e["INVALID_VERSION"]=9]="INVALID_VERSION";e[e["INVALID_HEADER_TOKEN"]=10]="INVALID_HEADER_TOKEN";e[e["INVALID_CONTENT_LENGTH"]=11]="INVALID_CONTENT_LENGTH";e[e["INVALID_CHUNK_SIZE"]=12]="INVALID_CHUNK_SIZE";e[e["INVALID_STATUS"]=13]="INVALID_STATUS";e[e["INVALID_EOF_STATE"]=14]="INVALID_EOF_STATE";e[e["INVALID_TRANSFER_ENCODING"]=15]="INVALID_TRANSFER_ENCODING";e[e["CB_MESSAGE_BEGIN"]=16]="CB_MESSAGE_BEGIN";e[e["CB_HEADERS_COMPLETE"]=17]="CB_HEADERS_COMPLETE";e[e["CB_MESSAGE_COMPLETE"]=18]="CB_MESSAGE_COMPLETE";e[e["CB_CHUNK_HEADER"]=19]="CB_CHUNK_HEADER";e[e["CB_CHUNK_COMPLETE"]=20]="CB_CHUNK_COMPLETE";e[e["PAUSED"]=21]="PAUSED";e[e["PAUSED_UPGRADE"]=22]="PAUSED_UPGRADE";e[e["PAUSED_H2_UPGRADE"]=23]="PAUSED_H2_UPGRADE";e[e["USER"]=24]="USER"})(r=t.ERROR||(t.ERROR={}));var s;(function(e){e[e["BOTH"]=0]="BOTH";e[e["REQUEST"]=1]="REQUEST";e[e["RESPONSE"]=2]="RESPONSE"})(s=t.TYPE||(t.TYPE={}));var o;(function(e){e[e["CONNECTION_KEEP_ALIVE"]=1]="CONNECTION_KEEP_ALIVE";e[e["CONNECTION_CLOSE"]=2]="CONNECTION_CLOSE";e[e["CONNECTION_UPGRADE"]=4]="CONNECTION_UPGRADE";e[e["CHUNKED"]=8]="CHUNKED";e[e["UPGRADE"]=16]="UPGRADE";e[e["CONTENT_LENGTH"]=32]="CONTENT_LENGTH";e[e["SKIPBODY"]=64]="SKIPBODY";e[e["TRAILING"]=128]="TRAILING";e[e["TRANSFER_ENCODING"]=512]="TRANSFER_ENCODING"})(o=t.FLAGS||(t.FLAGS={}));var a;(function(e){e[e["HEADERS"]=1]="HEADERS";e[e["CHUNKED_LENGTH"]=2]="CHUNKED_LENGTH";e[e["KEEP_ALIVE"]=4]="KEEP_ALIVE"})(a=t.LENIENT_FLAGS||(t.LENIENT_FLAGS={}));var l;(function(e){e[e["DELETE"]=0]="DELETE";e[e["GET"]=1]="GET";e[e["HEAD"]=2]="HEAD";e[e["POST"]=3]="POST";e[e["PUT"]=4]="PUT";e[e["CONNECT"]=5]="CONNECT";e[e["OPTIONS"]=6]="OPTIONS";e[e["TRACE"]=7]="TRACE";e[e["COPY"]=8]="COPY";e[e["LOCK"]=9]="LOCK";e[e["MKCOL"]=10]="MKCOL";e[e["MOVE"]=11]="MOVE";e[e["PROPFIND"]=12]="PROPFIND";e[e["PROPPATCH"]=13]="PROPPATCH";e[e["SEARCH"]=14]="SEARCH";e[e["UNLOCK"]=15]="UNLOCK";e[e["BIND"]=16]="BIND";e[e["REBIND"]=17]="REBIND";e[e["UNBIND"]=18]="UNBIND";e[e["ACL"]=19]="ACL";e[e["REPORT"]=20]="REPORT";e[e["MKACTIVITY"]=21]="MKACTIVITY";e[e["CHECKOUT"]=22]="CHECKOUT";e[e["MERGE"]=23]="MERGE";e[e["M-SEARCH"]=24]="M-SEARCH";e[e["NOTIFY"]=25]="NOTIFY";e[e["SUBSCRIBE"]=26]="SUBSCRIBE";e[e["UNSUBSCRIBE"]=27]="UNSUBSCRIBE";e[e["PATCH"]=28]="PATCH";e[e["PURGE"]=29]="PURGE";e[e["MKCALENDAR"]=30]="MKCALENDAR";e[e["LINK"]=31]="LINK";e[e["UNLINK"]=32]="UNLINK";e[e["SOURCE"]=33]="SOURCE";e[e["PRI"]=34]="PRI";e[e["DESCRIBE"]=35]="DESCRIBE";e[e["ANNOUNCE"]=36]="ANNOUNCE";e[e["SETUP"]=37]="SETUP";e[e["PLAY"]=38]="PLAY";e[e["PAUSE"]=39]="PAUSE";e[e["TEARDOWN"]=40]="TEARDOWN";e[e["GET_PARAMETER"]=41]="GET_PARAMETER";e[e["SET_PARAMETER"]=42]="SET_PARAMETER";e[e["REDIRECT"]=43]="REDIRECT";e[e["RECORD"]=44]="RECORD";e[e["FLUSH"]=45]="FLUSH"})(l=t.METHODS||(t.METHODS={}));t.METHODS_HTTP=[l.DELETE,l.GET,l.HEAD,l.POST,l.PUT,l.CONNECT,l.OPTIONS,l.TRACE,l.COPY,l.LOCK,l.MKCOL,l.MOVE,l.PROPFIND,l.PROPPATCH,l.SEARCH,l.UNLOCK,l.BIND,l.REBIND,l.UNBIND,l.ACL,l.REPORT,l.MKACTIVITY,l.CHECKOUT,l.MERGE,l["M-SEARCH"],l.NOTIFY,l.SUBSCRIBE,l.UNSUBSCRIBE,l.PATCH,l.PURGE,l.MKCALENDAR,l.LINK,l.UNLINK,l.PRI,l.SOURCE];t.METHODS_ICE=[l.SOURCE];t.METHODS_RTSP=[l.OPTIONS,l.DESCRIBE,l.ANNOUNCE,l.SETUP,l.PLAY,l.PAUSE,l.TEARDOWN,l.GET_PARAMETER,l.SET_PARAMETER,l.REDIRECT,l.RECORD,l.FLUSH,l.GET,l.POST];t.METHOD_MAP=n.enumToMap(l);t.H_METHOD_MAP={};Object.keys(t.METHOD_MAP).forEach((e=>{if(/^H/.test(e)){t.H_METHOD_MAP[e]=t.METHOD_MAP[e]}}));var u;(function(e){e[e["SAFE"]=0]="SAFE";e[e["SAFE_WITH_CB"]=1]="SAFE_WITH_CB";e[e["UNSAFE"]=2]="UNSAFE"})(u=t.FINISH||(t.FINISH={}));t.ALPHA=[];for(let e="A".charCodeAt(0);e<="Z".charCodeAt(0);e++){t.ALPHA.push(String.fromCharCode(e));t.ALPHA.push(String.fromCharCode(e+32))}t.NUM_MAP={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9};t.HEX_MAP={0:0,1:1,2:2,3:3,4:4,5:5,6:6,7:7,8:8,9:9,A:10,B:11,C:12,D:13,E:14,F:15,a:10,b:11,c:12,d:13,e:14,f:15};t.NUM=["0","1","2","3","4","5","6","7","8","9"];t.ALPHANUM=t.ALPHA.concat(t.NUM);t.MARK=["-","_",".","!","~","*","'","(",")"];t.USERINFO_CHARS=t.ALPHANUM.concat(t.MARK).concat(["%",";",":","&","=","+","$",","]);t.STRICT_URL_CHAR=["!",'"',"$","%","&","'","(",")","*","+",",","-",".","/",":",";","<","=",">","@","[","\\","]","^","_","`","{","|","}","~"].concat(t.ALPHANUM);t.URL_CHAR=t.STRICT_URL_CHAR.concat(["\t","\f"]);for(let e=128;e<=255;e++){t.URL_CHAR.push(e)}t.HEX=t.NUM.concat(["a","b","c","d","e","f","A","B","C","D","E","F"]);t.STRICT_TOKEN=["!","#","$","%","&","'","*","+","-",".","^","_","`","|","~"].concat(t.ALPHANUM);t.TOKEN=t.STRICT_TOKEN.concat([" "]);t.HEADER_CHARS=["\t"];for(let e=32;e<=255;e++){if(e!==127){t.HEADER_CHARS.push(e)}}t.CONNECTION_TOKEN_CHARS=t.HEADER_CHARS.filter((e=>e!==44));t.MAJOR=t.NUM_MAP;t.MINOR=t.MAJOR;var c;(function(e){e[e["GENERAL"]=0]="GENERAL";e[e["CONNECTION"]=1]="CONNECTION";e[e["CONTENT_LENGTH"]=2]="CONTENT_LENGTH";e[e["TRANSFER_ENCODING"]=3]="TRANSFER_ENCODING";e[e["UPGRADE"]=4]="UPGRADE";e[e["CONNECTION_KEEP_ALIVE"]=5]="CONNECTION_KEEP_ALIVE";e[e["CONNECTION_CLOSE"]=6]="CONNECTION_CLOSE";e[e["CONNECTION_UPGRADE"]=7]="CONNECTION_UPGRADE";e[e["TRANSFER_ENCODING_CHUNKED"]=8]="TRANSFER_ENCODING_CHUNKED"})(c=t.HEADER_STATE||(t.HEADER_STATE={}));t.SPECIAL_HEADERS={connection:c.CONNECTION,"content-length":c.CONTENT_LENGTH,"proxy-connection":c.CONNECTION,"transfer-encoding":c.TRANSFER_ENCODING,upgrade:c.UPGRADE}},3870:(e,t,i)=>{const{Buffer:n}=i(4573);e.exports=n.from("AGFzbQEAAAABJwdgAX8Bf2ADf39/AX9gAX8AYAJ/fwBgBH9/f38Bf2AAAGADf39/AALLAQgDZW52GHdhc21fb25faGVhZGVyc19jb21wbGV0ZQAEA2VudhV3YXNtX29uX21lc3NhZ2VfYmVnaW4AAANlbnYLd2FzbV9vbl91cmwAAQNlbnYOd2FzbV9vbl9zdGF0dXMAAQNlbnYUd2FzbV9vbl9oZWFkZXJfZmllbGQAAQNlbnYUd2FzbV9vbl9oZWFkZXJfdmFsdWUAAQNlbnYMd2FzbV9vbl9ib2R5AAEDZW52GHdhc21fb25fbWVzc2FnZV9jb21wbGV0ZQAAAy0sBQYAAAIAAAAAAAACAQIAAgICAAADAAAAAAMDAwMBAQEBAQEBAQEAAAIAAAAEBQFwARISBQMBAAIGCAF/AUGA1AQLB9EFIgZtZW1vcnkCAAtfaW5pdGlhbGl6ZQAIGV9faW5kaXJlY3RfZnVuY3Rpb25fdGFibGUBAAtsbGh0dHBfaW5pdAAJGGxsaHR0cF9zaG91bGRfa2VlcF9hbGl2ZQAvDGxsaHR0cF9hbGxvYwALBm1hbGxvYwAxC2xsaHR0cF9mcmVlAAwEZnJlZQAMD2xsaHR0cF9nZXRfdHlwZQANFWxsaHR0cF9nZXRfaHR0cF9tYWpvcgAOFWxsaHR0cF9nZXRfaHR0cF9taW5vcgAPEWxsaHR0cF9nZXRfbWV0aG9kABAWbGxodHRwX2dldF9zdGF0dXNfY29kZQAREmxsaHR0cF9nZXRfdXBncmFkZQASDGxsaHR0cF9yZXNldAATDmxsaHR0cF9leGVjdXRlABQUbGxodHRwX3NldHRpbmdzX2luaXQAFQ1sbGh0dHBfZmluaXNoABYMbGxodHRwX3BhdXNlABcNbGxodHRwX3Jlc3VtZQAYG2xsaHR0cF9yZXN1bWVfYWZ0ZXJfdXBncmFkZQAZEGxsaHR0cF9nZXRfZXJybm8AGhdsbGh0dHBfZ2V0X2Vycm9yX3JlYXNvbgAbF2xsaHR0cF9zZXRfZXJyb3JfcmVhc29uABwUbGxodHRwX2dldF9lcnJvcl9wb3MAHRFsbGh0dHBfZXJybm9fbmFtZQAeEmxsaHR0cF9tZXRob2RfbmFtZQAfEmxsaHR0cF9zdGF0dXNfbmFtZQAgGmxsaHR0cF9zZXRfbGVu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PgtBhwEMPQtBiAEMPAtBiQEMOwtBigEMOgtBiwEMOQtBjAEMOAtBjQEMNwtBjgEMNgtBjwEMNQtBkAEMNAtBkQEMMwtBkgEMMgtBkwEMMQtBlAEMMAtBlQEMLwtBlgEMLgtBlwEMLQtBmAEMLAtBmQEMKwtBmgEMKgtBmwEMKQtBnAEMKAtBnQEMJwtBngEMJgtBnwEMJQtBoAEMJAtBoQEMIwtBogEMIgtBowEMIQtBpAEMIAtBpQEMHwtBpgEMHgtBpwEMHQtBqAEMHAtBqQEMGwtBqgEMGgtBqwEMGQtBrAEMGAtBrQEMFwtBrgEMFgtBAQwVC0GvAQwUC0GwAQwTC0GxAQwSC0GzAQwRC0GyAQwQC0G0AQwPC0G1AQwOC0G2AQwNC0G3AQwMC0G4AQwLC0G5AQwKC0G6AQwJC0G7AQwIC0HGAQwHC0G8AQwGC0G9AQwFC0G+AQwEC0G/AQwDC0HAAQwCC0HCAQwBC0HBAQshAwNAAkACQAJAAkACQAJAAkACQAJAIAICfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAgJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACfwJAAkACfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAC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iQEhAwykAgtBASABLQAAQd8ARw1nGiABQQFqIQEMXgtBigEhAyABIARGDaICIAIoAgAiACAEIAFraiEFIAEgAGtBAWohBgNAIAEtAAAgAEGMzwBqLQAARw1kIABBAUYN+gEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMogILQYsBIQMgASAERg2hAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGOzwBqLQAARw1kIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyiAgsgAkEANgIAIAZBAWohAUECDGULQYwBIQMgASAERg2gAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHwzwBqLQAARw1jIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyhAgsgAkEANgIAIAZBAWohAUEfDGQLQY0BIQMgASAERg2fAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHyzwBqLQAARw1iIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAygAgsgAkEANgIAIAZBAWohAUEJDGMLIAEgBEYEQEGOASEDDJ8CCwJAAkAgAS0AAEHJAGsOBwBiYmJiYgFiCyABQQFqIQFB+AAhAwyGAgsgAUEBaiEBQfkAIQMMhQILQY8BIQMgASAERg2dAiACKAIAIgAgBCABa2ohBSABIABrQQVqIQYCQANAIAEtAAAgAEGRzwBqLQAARw1gIABBBUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyeAgsgAkEANgIAIAZBAWohAUEYDGELQZABIQMgASAERg2cAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGXzwBqLQAARw1fIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAydAgsgAkEANgIAIAZBAWohAUEXDGALQZEBIQMgASAERg2bAiACKAIAIgAgBCABa2ohBSABIABrQQZqIQYCQANAIAEtAAAgAEGazwBqLQAARw1eIABBBkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAycAgsgAkEANgIAIAZBAWohAUEV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JSIARQ0RIAJB5QA2AhwgAiABNgIUIAIgADYCDEEAIQMMtAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0gIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMswELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0iIAJB0gA2AhwgAiABNgIUIAIgADYCDEEAIQMMsgELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0OIAJB5QA2AhwgAiABNgIUIAIgADYCDEEAIQMMsQELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0dIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMsAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0fIAJB0gA2AhwgAiABNgIUIAIgADYCDEEAIQMMrwELIABBP0cNASABQQFqCyEBQQUhAwyUAQtBACEDIAJBADYCHCACIAE2AhQgAkH9EjYCECACQQc2AgwMrAELIAJBADYCHCACIAE2AhQgAkHcCDYCECACQQc2AgxBACEDDKsBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNByACQeUANgIcIAIgATYCFCACIAA2AgxBACEDDKoBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNFiACQdMANgIcIAIgATYCFCACIAA2AgxBACEDDKkBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNGCACQdIANgIcIAIgATYCFCACIAA2AgxBACEDDKgBCyACQQA2AhwgAiABNgIUIAJBxgo2AhAgAkEHNgIMQQAhAwynAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDQMgAkHlADYCHCACIAE2AhQgAiAANgIMQQAhAwymAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDRIgAkHTADYCHCACIAE2AhQgAiAANgIMQQAhAwylAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDRQgAkHSADYCHCACIAE2AhQgAiAANgIMQQAhAwykAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDQAgAkHlADYCHCACIAE2AhQgAiAANgIMQQAhAwyjAQtB1QAhAwyJAQsgAEEVRwRAIAJBADYCHCACIAE2AhQgAkG5DTYCECACQRo2AgxB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AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIABB5ABrDvQDY2IAAWFhYWFhYQIDBAVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhBgcICQoLDA0OD2FhYWFhEGFhYWFhYWFhYWFhEWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYRITFBUWFxgZGhthYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2YTc4OTphYWFhYWFhYTthYWE8YWFhYT0+P2FhYWFhYWFhQGFhQWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYUJDREVGR0hJSktMTU5PUFFSU2FhYWFhYWFhVFVWV1hZWlthXF1hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFeYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhX2BhC0HhJw8LQaQhDwtByywPC0H+MQ8LQcAkDwtBqyQPC0GNKA8LQeImDwtBgDAPC0G5Lw8LQdckDwtB7x8PC0HhHw8LQfofDwtB8iAPC0GoLw8LQa4yDwtBiDAPC0HsJw8LQYIiDwtBjh0PC0HQLg8LQcojDwtBxTIPC0HfHA8LQdIcDwtBxCAPC0HXIA8LQaIfDwtB7S4PC0GrMA8LQdQlDwtBzC4PC0H6Lg8LQfwrDwtB0jAPC0HxHQ8LQbsgDwtB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CABB3AgLii1JbnZhbGlkIGNoYXIgaW4gdXJsIHF1ZXJ5AFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fYm9keQBDb250ZW50LUxlbmd0aCBvdmVyZmxvdwBDaHVuayBzaXplIG92ZXJmbG93AFJlc3BvbnNlIG92ZXJmbG93AEludmFsaWQgbWV0aG9kIGZvciBIVFRQL3gueCByZXF1ZXN0AEludmFsaWQgbWV0aG9kIGZvciBSVFNQL3gueCByZXF1ZXN0AEV4cGVjdGVkIFNPVVJDRSBtZXRob2QgZm9yIElDRS94LnggcmVxdWVzdABJbnZhbGlkIGNoYXIgaW4gdXJsIGZyYWdtZW50IHN0YXJ0AEV4cGVjdGVkIGRvdABTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3N0YXR1cwBJbnZhbGlkIHJlc3BvbnNlIHN0YXR1cwBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zAFVzZXIgY2FsbGJhY2sgZXJyb3IAYG9uX3Jlc2V0YCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfaGVhZGVyYCBjYWxsYmFjayBlcnJvcgBgb25fbWVzc2FnZV9iZWdpbmAgY2FsbGJhY2sgZXJyb3IAYG9uX2NodW5rX2V4dGVuc2lvbl92YWx1ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX3N0YXR1c19jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX3ZlcnNpb25fY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl91cmxfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9jaHVua19jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX2hlYWRlcl92YWx1ZV9jb21wbGV0ZWAgY2FsbGJhY2sgZXJyb3IAYG9uX21lc3NhZ2VfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9tZXRob2RfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9oZWFkZXJfZmllbGRfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9jaHVua19leHRlbnNpb25fbmFtZWAgY2FsbGJhY2sgZXJyb3IAVW5leHBlY3RlZCBjaGFyIGluIHVybCBzZXJ2ZXIASW52YWxpZCBoZWFkZXIgdmFsdWUgY2hhcgBJbnZhbGlkIGhlYWRlciBmaWVsZCBjaGFyAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fdmVyc2lvbgBJbnZhbGlkIG1pbm9yIHZlcnNpb24ASW52YWxpZCBtYWpvciB2ZXJzaW9uAEV4cGVjdGVkIHNwYWNlIGFmdGVyIHZlcnNpb24ARXhwZWN0ZWQgQ1JMRiBhZnRlciB2ZXJzaW9uAEludmFsaWQgSFRUUCB2ZXJzaW9uAEludmFsaWQgaGVhZGVyIHRva2VuAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fdXJsAEludmFsaWQgY2hhcmFjdGVycyBpbiB1cmwAVW5leHBlY3RlZCBzdGFydCBjaGFyIGluIHVybABEb3VibGUgQCBpbiB1cmwARW1wdHkgQ29udGVudC1MZW5ndGgASW52YWxpZCBjaGFyYWN0ZXIgaW4gQ29udGVudC1MZW5ndGgARHVwbGljYXRlIENvbnRlbnQtTGVuZ3RoAEludmFsaWQgY2hhciBpbiB1cmwgcGF0aABDb250ZW50LUxlbmd0aCBjYW4ndCBiZSBwcmVzZW50IHdpdGggVHJhbnNmZXItRW5jb2RpbmcASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgc2l6ZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2hlYWRlcl92YWx1ZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2NodW5rX2V4dGVuc2lvbl92YWx1ZQBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zIHZhbHVlAE1pc3NpbmcgZXhwZWN0ZWQgTEYgYWZ0ZXIgaGVhZGVyIHZhbHVlAEludmFsaWQgYFRyYW5zZmVyLUVuY29kaW5nYCBoZWFkZXIgdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyBxdW90ZSB2YWx1ZQBJbnZhbGlkIGNoYXJhY3RlciBpbiBjaHVuayBleHRlbnNpb25zIHF1b3RlZCB2YWx1ZQBQYXVzZWQgYnkgb25faGVhZGVyc19jb21wbGV0ZQBJbnZhbGlkIEVPRiBzdGF0ZQBvbl9yZXNldCBwYXVzZQBvbl9jaHVua19oZWFkZXIgcGF1c2UAb25fbWVzc2FnZV9iZWdpbiBwYXVzZQBvbl9jaHVua19leHRlbnNpb25fdmFsdWUgcGF1c2UAb25fc3RhdHVzX2NvbXBsZXRlIHBhdXNlAG9uX3ZlcnNpb25fY29tcGxldGUgcGF1c2UAb25fdXJsX2NvbXBsZXRlIHBhdXNlAG9uX2NodW5rX2NvbXBsZXRlIHBhdXNlAG9uX2hlYWRlcl92YWx1ZV9jb21wbGV0ZSBwYXVzZQBvbl9tZXNzYWdlX2NvbXBsZXRlIHBhdXNlAG9uX21ldGhvZF9jb21wbGV0ZSBwYXVzZQBvbl9oZWFkZXJfZmllbGRfY29tcGxldGUgcGF1c2UAb25fY2h1bmtfZXh0ZW5zaW9uX25hbWUgcGF1c2UAVW5leHBlY3RlZCBzcGFjZSBhZnRlciBzdGFydCBsaW5lAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fY2h1bmtfZXh0ZW5zaW9uX25hbWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyBuYW1lAFBhdXNlIG9uIENPTk5FQ1QvVXBncmFkZQBQYXVzZSBvbiBQUkkvVXBncmFkZQBFeHBlY3RlZCBIVFRQLzIgQ29ubmVjdGlvbiBQcmVmYWNlAFNwYW4gY2FsbGJhY2sgZXJyb3IgaW4gb25fbWV0aG9kAEV4cGVjdGVkIHNwYWNlIGFmdGVyIG1ldGhvZABTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2hlYWRlcl9maWVsZABQYXVzZWQASW52YWxpZCB3b3JkIGVuY291bnRlcmVkAEludmFsaWQgbWV0aG9kIGVuY291bnRlcmVkAFVuZXhwZWN0ZWQgY2hhciBpbiB1cmwgc2NoZW1hAFJlcXVlc3QgaGFzIGludmFsaWQgYFRyYW5zZmVyLUVuY29kaW5nYABTV0lUQ0hfUFJPWFkAVVNFX1BST1hZAE1LQUNUSVZJVFkAVU5QUk9DRVNTQUJMRV9FTlRJVFkAQ09QWQBNT1ZFRF9QRVJNQU5FTlRMWQBUT09fRUFSTFkATk9USUZZAEZBSUxFRF9ERVBFTkRFTkNZAEJBRF9HQVRFV0FZAFBMQVkAUFVUAENIRUNLT1VUAEdBVEVXQVlfVElNRU9VVABSRVFVRVNUX1RJTUVPVVQATkVUV09SS19DT05ORUNUX1RJTUVPVVQAQ09OTkVDVElPTl9USU1FT1VUAExPR0lOX1RJTUVPVVQATkVUV09SS19SRUFEX1RJTUVPVVQAUE9TVABNSVNESVJFQ1RFRF9SRVFVRVNUAENMSUVOVF9DTE9TRURfUkVRVUVTVABDTElFTlRfQ0xPU0VEX0xPQURfQkFMQU5DRURfUkVRVUVTVABCQURfUkVRVUVTVABIVFRQX1JFUVVFU1RfU0VOVF9UT19IVFRQU19QT1JUAFJFUE9SVABJTV9BX1RFQVBPVABSRVNFVF9DT05URU5UAE5PX0NPTlRFTlQAUEFSVElBTF9DT05URU5UAEhQRV9JTlZBTElEX0NPTlNUQU5UAEhQRV9DQl9SRVNFVABHRVQASFBFX1NUUklDVABDT05GTElDVABURU1QT1JBUllfUkVESVJFQ1QAUEVSTUFORU5UX1JFRElSRUNUAENPTk5FQ1QATVVMVElfU1RBVFVTAEhQRV9JTlZBTElEX1NUQVRVUwBUT09fTUFOWV9SRVFVRVNUUwBFQVJMWV9ISU5UUwBVTkFWQUlMQUJMRV9GT1JfTEVHQUxfUkVBU09OUwBPUFRJT05TAFNXSVRDSElOR19QUk9UT0NPTFMAVkFSSUFOVF9BTFNPX05FR09USUFURVMATVVMVElQTEVfQ0hPSUNFUwBJTlRFUk5BTF9TRVJWRVJfRVJST1IAV0VCX1NFUlZFUl9VTktOT1dOX0VSUk9SAFJBSUxHVU5fRVJST1IASURFTlRJVFlfUFJPVklERVJfQVVUSEVOVElDQVRJT05fRVJST1IAU1NMX0NFUlRJRklDQVRFX0VSUk9SAElOVkFMSURfWF9GT1JXQVJERURfRk9SAFNFVF9QQVJBTUVURVIAR0VUX1BBUkFNRVRFUgBIUEVfVVNFUgBTRUVfT1RIRVIASFBFX0NCX0NIVU5LX0hFQURFUgBNS0NBTEVOREFSAFNFVFVQAFdFQl9TRVJWRVJfSVNfRE9XTgBURUFSRE9XTgBIUEVfQ0xPU0VEX0NPTk5FQ1RJT04ASEVVUklTVElDX0VYUElSQVRJT04ARElTQ09OTkVDVEVEX09QRVJBVElPTgBOT05fQVVUSE9SSVRBVElWRV9JTkZPUk1BVElPTgBIUEVfSU5WQUxJRF9WRVJTSU9OAEhQRV9DQl9NRVNTQUdFX0JFR0lOAFNJVEVfSVNfRlJPWkVOAEhQRV9JTlZBTElEX0hFQURFUl9UT0tFTgBJTlZBTElEX1RPS0VOAEZPUkJJRERFTgBFTkhBTkNFX1lPVVJfQ0FMTQBIUEVfSU5WQUxJRF9VUkwAQkxPQ0tFRF9CWV9QQVJFTlRBTF9DT05UUk9MAE1LQ09MAEFDTABIUEVfSU5URVJOQUwAUkVRVUVTVF9IRUFERVJfRklFTERTX1RPT19MQVJHRV9VTk9GRklDSUFMAEhQRV9PSwBVTkxJTksAVU5MT0NLAFBSSQBSRVRSWV9XSVRIAEhQRV9JTlZBTElEX0NPTlRFTlRfTEVOR1RIAEhQRV9VTkVYUEVDVEVEX0NPTlRFTlRfTEVOR1RIAEZMVVNIAFBST1BQQVRDSABNLVNFQVJDSABVUklfVE9PX0xPTkcAUFJPQ0VTU0lORwBNSVNDRUxMQU5FT1VTX1BFUlNJU1RFTlRfV0FSTklORwBNSVNDRUxMQU5FT1VTX1dBUk5JTkcASFBFX0lOVkFMSURfVFJBTlNGRVJfRU5DT0RJTkcARXhwZWN0ZWQgQ1JMRgBIUEVfSU5WQUxJRF9DSFVOS19TSVpFAE1PVkUAQ09OVElOVUUASFBFX0NCX1NUQVRVU19DT01QTEVURQBIUEVfQ0JfSEVBREVSU19DT01QTEVURQBIUEVfQ0JfVkVSU0lPTl9DT01QTEVURQBIUEVfQ0JfVVJMX0NPTVBMRVRFAEhQRV9DQl9DSFVOS19DT01QTEVURQBIUEVfQ0JfSEVBREVSX1ZBTFVFX0NPTVBMRVRFAEhQRV9DQl9DSFVOS19FWFRFTlNJT05fVkFMVUVfQ09NUExFVEUASFBFX0NCX0NIVU5LX0VYVEVOU0lPTl9OQU1FX0NPTVBMRVRFAEhQRV9DQl9NRVNTQUdFX0NPTVBMRVRFAEhQRV9DQl9NRVRIT0RfQ09NUExFVEUASFBFX0NCX0hFQURFUl9GSUVMRF9DT01QTEVURQBERUxFVEUASFBFX0lOVkFMSURfRU9GX1NUQVRFAElOVkFMSURfU1NMX0NFUlRJRklDQVRFAFBBVVNFAE5PX1JFU1BPTlNFAFVOU1VQUE9SVEVEX01FRElBX1RZUEUAR09ORQBOT1RfQUNDRVBUQUJMRQBTRVJWSUNFX1VOQVZBSUxBQkxFAFJBTkdFX05PVF9TQVRJU0ZJQUJMRQBPUklHSU5fSVNfVU5SRUFDSEFCTEUAUkVTUE9OU0VfSVNfU1RBTEUAUFVSR0UATUVSR0UAUkVRVUVTVF9IRUFERVJfRklFTERTX1RPT19MQVJHRQBSRVFVRVNUX0hFQURFUl9UT09fTEFSR0UAUEFZTE9BRF9UT09fTEFSR0UASU5TVUZGSUNJRU5UX1NUT1JBR0UASFBFX1BBVVNFRF9VUEdSQURFAEhQRV9QQVVTRURfSDJfVVBHUkFERQBTT1VSQ0UAQU5OT1VOQ0UAVFJBQ0UASFBFX1VORVhQRUNURURfU1BBQ0UAREVTQ1JJQkUAVU5TVUJTQ1JJQkUAUkVDT1JEAEhQRV9JTlZBTElEX01FVEhPRABOT1RfRk9VTkQAUFJPUEZJTkQAVU5CSU5EAFJFQklORABVTkFVVEhPUklaRUQATUVUSE9EX05PVF9BTExPV0VEAEhUVFBfVkVSU0lPTl9OT1RfU1VQUE9SVEVEAEFMUkVBRFlfUkVQT1JURUQAQUNDRVBURUQATk9UX0lNUExFTUVOVEVEAExPT1BfREVURUNURUQASFBFX0NSX0VYUEVDVEVEAEhQRV9MRl9FWFBFQ1RFRABDUkVBVEVEAElNX1VTRUQASFBFX1BBVVNFRABUSU1FT1VUX09DQ1VSRUQAUEFZTUVOVF9SRVFVSVJFRABQUkVDT05ESVRJT05fUkVRVUlSRUQAUFJPWFlfQVVUSEVOVElDQVRJT05fUkVRVUlSRUQATkVUV09SS19BVVRIRU5USUNBVElPTl9SRVFVSVJFRABMRU5HVEhfUkVRVUlSRUQAU1NMX0NFUlRJRklDQVRFX1JFUVVJUkVEAFVQR1JBREVfUkVRVUlSRUQAUEFHRV9FWFBJUkVEAFBSRUNPTkRJVElPTl9GQUlMRUQARVhQRUNUQVRJT05fRkFJTEVEAFJFVkFMSURBVElPTl9GQUlMRUQAU1NMX0hBTkRTSEFLRV9GQUlMRUQATE9DS0VEAFRSQU5TRk9STUFUSU9OX0FQUExJRUQATk9UX01PRElGSUVEAE5PVF9FWFRFTkRFRABCQU5EV0lEVEhfTElNSVRfRVhDRUVERUQAU0lURV9JU19PVkVSTE9BREVEAEhFQUQARXhwZWN0ZWQgSFRUUC8AAF4TAAAmEwAAMBAAAPAXAACdEwAAFRIAADkXAADwEgAAChAAAHUSAACtEgAAghMAAE8UAAB/EAAAoBUAACMUAACJEgAAixQAAE0VAADUEQAAzxQAABAYAADJFgAA3BYAAMERAADgFwAAuxQAAHQUAAB8FQAA5RQAAAgXAAAfEAAAZRUAAKMUAAAoFQAAAhUAAJkVAAAsEAAAixkAAE8PAADUDgAAahAAAM4QAAACFwAAiQ4AAG4TAAAcEwAAZhQAAFYXAADBEwAAzRMAAGwTAABoFwAAZhcAAF8XAAAiEwAAzg8AAGkOAADYDgAAYxYAAMsTAACqDgAAKBcAACYXAADFEwAAXRYAAOgRAABnEwAAZRMAAPIWAABzEwAAHRcAAPkWAADzEQAAzw4AAM4VAAAMEgAAsxEAAKURAABhEAAAMhcAALsTAEH5NQsBAQBBkDYL4AEBAQIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBB/TcLAQEAQZE4C14CAwICAgICAAACAgACAgACAgICAgICAgICAAQAAAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAgACAEH9OQsBAQBBkToLXgIAAgICAgIAAAICAAICAAICAgICAgICAgIAAwAEAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgIAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgICAgACAAIAQfA7Cw1sb3NlZWVwLWFsaXZlAEGJPAsBAQBBoDwL4AEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBBiT4LAQEAQaA+C+cBAQEBAQEBAQEBAQEBAgEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQFjaHVua2VkAEGwwAALXwEBAAEBAQEBAAABAQABAQABAQEBAQEBAQEBAAAAAAAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQABAEGQwgALIWVjdGlvbmVudC1sZW5ndGhvbnJveHktY29ubmVjdGlvbgBBwMIACy1yYW5zZmVyLWVuY29kaW5ncGdyYWRlDQoNCg0KU00NCg0KVFRQL0NFL1RTUC8AQfnCAAsFAQIAAQMAQZDDAAvgAQQBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAEH5xAALBQECAAEDAEGQxQAL4AEEAQEFAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBB+cYACwQBAAABAEGRxwAL3wEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAEH6yAALBAEAAAIAQZDJAAtfAwQAAAQEBAQEBAQEBAQEBQQEBAQEBAQEBAQEBAAEAAYHBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQABAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAAAAQAQfrKAAsEAQAAAQBBkMsACwEBAEGqywALQQIAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwAAAAAAAAMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAEH6zAALBAEAAAEAQZDNAAsBAQBBms0ACwYCAAAAAAIAQbHNAAs6AwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBB8M4AC5YBTk9VTkNFRUNLT1VUTkVDVEVURUNSSUJFTFVTSEVURUFEU0VBUkNIUkdFQ1RJVklUWUxFTkRBUlZFT1RJRllQVElPTlNDSFNFQVlTVEFUQ0hHRU9SRElSRUNUT1JUUkNIUEFSQU1FVEVSVVJDRUJTQ1JJQkVBUkRPV05BQ0VJTkROS0NLVUJTQ1JJQkVIVFRQL0FEVFAv","base64")},3434:(e,t,i)=>{const{Buffer:n}=i(4573);e.exports=n.from("AGFzbQEAAAABJwdgAX8Bf2ADf39/AX9gAX8AYAJ/fwBgBH9/f38Bf2AAAGADf39/AALLAQgDZW52GHdhc21fb25faGVhZGVyc19jb21wbGV0ZQAEA2VudhV3YXNtX29uX21lc3NhZ2VfYmVnaW4AAANlbnYLd2FzbV9vbl91cmwAAQNlbnYOd2FzbV9vbl9zdGF0dXMAAQNlbnYUd2FzbV9vbl9oZWFkZXJfZmllbGQAAQNlbnYUd2FzbV9vbl9oZWFkZXJfdmFsdWUAAQNlbnYMd2FzbV9vbl9ib2R5AAEDZW52GHdhc21fb25fbWVzc2FnZV9jb21wbGV0ZQAAAy0sBQYAAAIAAAAAAAACAQIAAgICAAADAAAAAAMDAwMBAQEBAQEBAQEAAAIAAAAEBQFwARISBQMBAAIGCAF/AUGA1AQLB9EFIgZtZW1vcnkCAAtfaW5pdGlhbGl6ZQAIGV9faW5kaXJlY3RfZnVuY3Rpb25fdGFibGUBAAtsbGh0dHBfaW5pdAAJGGxsaHR0cF9zaG91bGRfa2VlcF9hbGl2ZQAvDGxsaHR0cF9hbGxvYwALBm1hbGxvYwAxC2xsaHR0cF9mcmVlAAwEZnJlZQAMD2xsaHR0cF9nZXRfdHlwZQANFWxsaHR0cF9nZXRfaHR0cF9tYWpvcgAOFWxsaHR0cF9nZXRfaHR0cF9taW5vcgAPEWxsaHR0cF9nZXRfbWV0aG9kABAWbGxodHRwX2dldF9zdGF0dXNfY29kZQAREmxsaHR0cF9nZXRfdXBncmFkZQASDGxsaHR0cF9yZXNldAATDmxsaHR0cF9leGVjdXRlABQUbGxodHRwX3NldHRpbmdzX2luaXQAFQ1sbGh0dHBfZmluaXNoABYMbGxodHRwX3BhdXNlABcNbGxodHRwX3Jlc3VtZQAYG2xsaHR0cF9yZXN1bWVfYWZ0ZXJfdXBncmFkZQAZEGxsaHR0cF9nZXRfZXJybm8AGhdsbGh0dHBfZ2V0X2Vycm9yX3JlYXNvbgAbF2xsaHR0cF9zZXRfZXJyb3JfcmVhc29uABwUbGxodHRwX2dldF9lcnJvcl9wb3MAHRFsbGh0dHBfZXJybm9fbmFtZQAeEmxsaHR0cF9tZXRob2RfbmFtZQAfEmxsaHR0cF9zdGF0dXNfbmFtZQAgGmxsaHR0cF9zZXRfbGVu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PgtBhwEMPQtBiAEMPAtBiQEMOwtBigEMOgtBiwEMOQtBjAEMOAtBjQEMNwtBjgEMNgtBjwEMNQtBkAEMNAtBkQEMMwtBkgEMMgtBkwEMMQtBlAEMMAtBlQEMLwtBlgEMLgtBlwEMLQtBmAEMLAtBmQEMKwtBmgEMKgtBmwEMKQtBnAEMKAtBnQEMJwtBngEMJgtBnwEMJQtBoAEMJAtBoQEMIwtBogEMIgtBowEMIQtBpAEMIAtBpQEMHwtBpgEMHgtBpwEMHQtBqAEMHAtBqQEMGwtBqgEMGgtBqwEMGQtBrAEMGAtBrQEMFwtBrgEMFgtBAQwVC0GvAQwUC0GwAQwTC0GxAQwSC0GzAQwRC0GyAQwQC0G0AQwPC0G1AQwOC0G2AQwNC0G3AQwMC0G4AQwLC0G5AQwKC0G6AQwJC0G7AQwIC0HGAQwHC0G8AQwGC0G9AQwFC0G+AQwEC0G/AQwDC0HAAQwCC0HCAQwBC0HBAQshAwNAAkACQAJAAkACQAJAAkACQAJAIAICfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAgAgJ/AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACfwJAAkACfwJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkAC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iQEhAwykAgtBASABLQAAQd8ARw1nGiABQQFqIQEMXgtBigEhAyABIARGDaICIAIoAgAiACAEIAFraiEFIAEgAGtBAWohBgNAIAEtAAAgAEGMzwBqLQAARw1kIABBAUYN+gEgAEEBaiEAIAQgAUEBaiIBRw0ACyACIAU2AgAMogILQYsBIQMgASAERg2hAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGOzwBqLQAARw1kIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyiAgsgAkEANgIAIAZBAWohAUECDGULQYwBIQMgASAERg2gAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHwzwBqLQAARw1jIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyhAgsgAkEANgIAIAZBAWohAUEfDGQLQY0BIQMgASAERg2fAiACKAIAIgAgBCABa2ohBSABIABrQQFqIQYCQANAIAEtAAAgAEHyzwBqLQAARw1iIABBAUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAygAgsgAkEANgIAIAZBAWohAUEJDGMLIAEgBEYEQEGOASEDDJ8CCwJAAkAgAS0AAEHJAGsOBwBiYmJiYgFiCyABQQFqIQFB+AAhAwyGAgsgAUEBaiEBQfkAIQMMhQILQY8BIQMgASAERg2dAiACKAIAIgAgBCABa2ohBSABIABrQQVqIQYCQANAIAEtAAAgAEGRzwBqLQAARw1gIABBBUYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAyeAgsgAkEANgIAIAZBAWohAUEYDGELQZABIQMgASAERg2cAiACKAIAIgAgBCABa2ohBSABIABrQQJqIQYCQANAIAEtAAAgAEGXzwBqLQAARw1fIABBAkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAydAgsgAkEANgIAIAZBAWohAUEXDGALQZEBIQMgASAERg2bAiACKAIAIgAgBCABa2ohBSABIABrQQZqIQYCQANAIAEtAAAgAEGazwBqLQAARw1eIABBBkYNASAAQQFqIQAgBCABQQFqIgFHDQALIAIgBTYCAAycAgsgAkEANgIAIAZBAWohAUEV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JSIARQ0RIAJB5QA2AhwgAiABNgIUIAIgADYCDEEAIQMMtAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0gIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMswELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0iIAJB0gA2AhwgAiABNgIUIAIgADYCDEEAIQMMsgELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0OIAJB5QA2AhwgAiABNgIUIAIgADYCDEEAIQMMsQELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0dIAJB0wA2AhwgAiABNgIUIAIgADYCDEEAIQMMsAELIAIoAgQhACACQQA2AgQgAiAAIAEQJSIARQ0fIAJB0gA2AhwgAiABNgIUIAIgADYCDEEAIQMMrwELIABBP0cNASABQQFqCyEBQQUhAwyUAQtBACEDIAJBADYCHCACIAE2AhQgAkH9EjYCECACQQc2AgwMrAELIAJBADYCHCACIAE2AhQgAkHcCDYCECACQQc2AgxBACEDDKsBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNByACQeUANgIcIAIgATYCFCACIAA2AgxBACEDDKoBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNFiACQdMANgIcIAIgATYCFCACIAA2AgxBACEDDKkBCyACKAIEIQAgAkEANgIEIAIgACABECUiAEUNGCACQdIANgIcIAIgATYCFCACIAA2AgxBACEDDKgBCyACQQA2AhwgAiABNgIUIAJBxgo2AhAgAkEHNgIMQQAhAwynAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDQMgAkHlADYCHCACIAE2AhQgAiAANgIMQQAhAwymAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDRIgAkHTADYCHCACIAE2AhQgAiAANgIMQQAhAwylAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDRQgAkHSADYCHCACIAE2AhQgAiAANgIMQQAhAwykAQsgAigCBCEAIAJBADYCBCACIAAgARAlIgBFDQAgAkHlADYCHCACIAE2AhQgAiAANgIMQQAhAwyjAQtB1QAhAwyJAQsgAEEVRwRAIAJBADYCHCACIAE2AhQgAkG5DTYCECACQRo2AgxB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AkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAAkACQAJAIABB5ABrDvQDY2IAAWFhYWFhYQIDBAVhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhBgcICQoLDA0OD2FhYWFhEGFhYWFhYWFhYWFhEWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYRITFBUWFxgZGhthYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhHB0eHyAhIiMkJSYnKCkqKywtLi8wMTIzNDU2YTc4OTphYWFhYWFhYTthYWE8YWFhYT0+P2FhYWFhYWFhQGFhQWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYUJDREVGR0hJSktMTU5PUFFSU2FhYWFhYWFhVFVWV1hZWlthXF1hYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFeYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhX2BhC0HhJw8LQaQhDwtByywPC0H+MQ8LQcAkDwtBqyQPC0GNKA8LQeImDwtBgDAPC0G5Lw8LQdckDwtB7x8PC0HhHw8LQfofDwtB8iAPC0GoLw8LQa4yDwtBiDAPC0HsJw8LQYIiDwtBjh0PC0HQLg8LQcojDwtBxTIPC0HfHA8LQdIcDwtBxCAPC0HXIA8LQaIfDwtB7S4PC0GrMA8LQdQlDwtBzC4PC0H6Lg8LQfwrDwtB0jAPC0HxHQ8LQbsgDwtB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AAADAEGUCAsFBAAAAAUAQaQICwkGAAAABwAAAAgAQdwIC4otSW52YWxpZCBjaGFyIGluIHVybCBxdWVyeQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2JvZHkAQ29udGVudC1MZW5ndGggb3ZlcmZsb3cAQ2h1bmsgc2l6ZSBvdmVyZmxvdwBSZXNwb25zZSBvdmVyZmxvdwBJbnZhbGlkIG1ldGhvZCBmb3IgSFRUUC94LnggcmVxdWVzdABJbnZhbGlkIG1ldGhvZCBmb3IgUlRTUC94LnggcmVxdWVzdABFeHBlY3RlZCBTT1VSQ0UgbWV0aG9kIGZvciBJQ0UveC54IHJlcXVlc3QASW52YWxpZCBjaGFyIGluIHVybCBmcmFnbWVudCBzdGFydABFeHBlY3RlZCBkb3QAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9zdGF0dXMASW52YWxpZCByZXNwb25zZSBzdGF0dXMASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucwBVc2VyIGNhbGxiYWNrIGVycm9yAGBvbl9yZXNldGAgY2FsbGJhY2sgZXJyb3IAYG9uX2NodW5rX2hlYWRlcmAgY2FsbGJhY2sgZXJyb3IAYG9uX21lc3NhZ2VfYmVnaW5gIGNhbGxiYWNrIGVycm9yAGBvbl9jaHVua19leHRlbnNpb25fdmFsdWVgIGNhbGxiYWNrIGVycm9yAGBvbl9zdGF0dXNfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl92ZXJzaW9uX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fdXJsX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9oZWFkZXJfdmFsdWVfY29tcGxldGVgIGNhbGxiYWNrIGVycm9yAGBvbl9tZXNzYWdlX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fbWV0aG9kX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25faGVhZGVyX2ZpZWxkX2NvbXBsZXRlYCBjYWxsYmFjayBlcnJvcgBgb25fY2h1bmtfZXh0ZW5zaW9uX25hbWVgIGNhbGxiYWNrIGVycm9yAFVuZXhwZWN0ZWQgY2hhciBpbiB1cmwgc2VydmVyAEludmFsaWQgaGVhZGVyIHZhbHVlIGNoYXIASW52YWxpZCBoZWFkZXIgZmllbGQgY2hhcgBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3ZlcnNpb24ASW52YWxpZCBtaW5vciB2ZXJzaW9uAEludmFsaWQgbWFqb3IgdmVyc2lvbgBFeHBlY3RlZCBzcGFjZSBhZnRlciB2ZXJzaW9uAEV4cGVjdGVkIENSTEYgYWZ0ZXIgdmVyc2lvbgBJbnZhbGlkIEhUVFAgdmVyc2lvbgBJbnZhbGlkIGhlYWRlciB0b2tlbgBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX3VybABJbnZhbGlkIGNoYXJhY3RlcnMgaW4gdXJsAFVuZXhwZWN0ZWQgc3RhcnQgY2hhciBpbiB1cmwARG91YmxlIEAgaW4gdXJsAEVtcHR5IENvbnRlbnQtTGVuZ3RoAEludmFsaWQgY2hhcmFjdGVyIGluIENvbnRlbnQtTGVuZ3RoAER1cGxpY2F0ZSBDb250ZW50LUxlbmd0aABJbnZhbGlkIGNoYXIgaW4gdXJsIHBhdGgAQ29udGVudC1MZW5ndGggY2FuJ3QgYmUgcHJlc2VudCB3aXRoIFRyYW5zZmVyLUVuY29kaW5nAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIHNpemUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9oZWFkZXJfdmFsdWUAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9jaHVua19leHRlbnNpb25fdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyB2YWx1ZQBNaXNzaW5nIGV4cGVjdGVkIExGIGFmdGVyIGhlYWRlciB2YWx1ZQBJbnZhbGlkIGBUcmFuc2Zlci1FbmNvZGluZ2AgaGVhZGVyIHZhbHVlAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgcXVvdGUgdmFsdWUASW52YWxpZCBjaGFyYWN0ZXIgaW4gY2h1bmsgZXh0ZW5zaW9ucyBxdW90ZWQgdmFsdWUAUGF1c2VkIGJ5IG9uX2hlYWRlcnNfY29tcGxldGUASW52YWxpZCBFT0Ygc3RhdGUAb25fcmVzZXQgcGF1c2UAb25fY2h1bmtfaGVhZGVyIHBhdXNlAG9uX21lc3NhZ2VfYmVnaW4gcGF1c2UAb25fY2h1bmtfZXh0ZW5zaW9uX3ZhbHVlIHBhdXNlAG9uX3N0YXR1c19jb21wbGV0ZSBwYXVzZQBvbl92ZXJzaW9uX2NvbXBsZXRlIHBhdXNlAG9uX3VybF9jb21wbGV0ZSBwYXVzZQBvbl9jaHVua19jb21wbGV0ZSBwYXVzZQBvbl9oZWFkZXJfdmFsdWVfY29tcGxldGUgcGF1c2UAb25fbWVzc2FnZV9jb21wbGV0ZSBwYXVzZQBvbl9tZXRob2RfY29tcGxldGUgcGF1c2UAb25faGVhZGVyX2ZpZWxkX2NvbXBsZXRlIHBhdXNlAG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lIHBhdXNlAFVuZXhwZWN0ZWQgc3BhY2UgYWZ0ZXIgc3RhcnQgbGluZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX2NodW5rX2V4dGVuc2lvbl9uYW1lAEludmFsaWQgY2hhcmFjdGVyIGluIGNodW5rIGV4dGVuc2lvbnMgbmFtZQBQYXVzZSBvbiBDT05ORUNUL1VwZ3JhZGUAUGF1c2Ugb24gUFJJL1VwZ3JhZGUARXhwZWN0ZWQgSFRUUC8yIENvbm5lY3Rpb24gUHJlZmFjZQBTcGFuIGNhbGxiYWNrIGVycm9yIGluIG9uX21ldGhvZABFeHBlY3RlZCBzcGFjZSBhZnRlciBtZXRob2QAU3BhbiBjYWxsYmFjayBlcnJvciBpbiBvbl9oZWFkZXJfZmllbGQAUGF1c2VkAEludmFsaWQgd29yZCBlbmNvdW50ZXJlZABJbnZhbGlkIG1ldGhvZCBlbmNvdW50ZXJlZABVbmV4cGVjdGVkIGNoYXIgaW4gdXJsIHNjaGVtYQBSZXF1ZXN0IGhhcyBpbnZhbGlkIGBUcmFuc2Zlci1FbmNvZGluZ2AAU1dJVENIX1BST1hZAFVTRV9QUk9YWQBNS0FDVElWSVRZAFVOUFJPQ0VTU0FCTEVfRU5USVRZAENPUFkATU9WRURfUEVSTUFORU5UTFkAVE9PX0VBUkxZAE5PVElGWQBGQUlMRURfREVQRU5ERU5DWQBCQURfR0FURVdBWQBQTEFZAFBVVABDSEVDS09VVABHQVRFV0FZX1RJTUVPVVQAUkVRVUVTVF9USU1FT1VUAE5FVFdPUktfQ09OTkVDVF9USU1FT1VUAENPTk5FQ1RJT05fVElNRU9VVABMT0dJTl9USU1FT1VUAE5FVFdPUktfUkVBRF9USU1FT1VUAFBPU1QATUlTRElSRUNURURfUkVRVUVTVABDTElFTlRfQ0xPU0VEX1JFUVVFU1QAQ0xJRU5UX0NMT1NFRF9MT0FEX0JBTEFOQ0VEX1JFUVVFU1QAQkFEX1JFUVVFU1QASFRUUF9SRVFVRVNUX1NFTlRfVE9fSFRUUFNfUE9SVABSRVBPUlQASU1fQV9URUFQT1QAUkVTRVRfQ09OVEVOVABOT19DT05URU5UAFBBUlRJQUxfQ09OVEVOVABIUEVfSU5WQUxJRF9DT05TVEFOVABIUEVfQ0JfUkVTRVQAR0VUAEhQRV9TVFJJQ1QAQ09ORkxJQ1QAVEVNUE9SQVJZX1JFRElSRUNUAFBFUk1BTkVOVF9SRURJUkVDVABDT05ORUNUAE1VTFRJX1NUQVRVUwBIUEVfSU5WQUxJRF9TVEFUVVMAVE9PX01BTllfUkVRVUVTVFMARUFSTFlfSElOVFMAVU5BVkFJTEFCTEVfRk9SX0xFR0FMX1JFQVNPTlMAT1BUSU9OUwBTV0lUQ0hJTkdfUFJPVE9DT0xTAFZBUklBTlRfQUxTT19ORUdPVElBVEVTAE1VTFRJUExFX0NIT0lDRVMASU5URVJOQUxfU0VSVkVSX0VSUk9SAFdFQl9TRVJWRVJfVU5LTk9XTl9FUlJPUgBSQUlMR1VOX0VSUk9SAElERU5USVRZX1BST1ZJREVSX0FVVEhFTlRJQ0FUSU9OX0VSUk9SAFNTTF9DRVJUSUZJQ0FURV9FUlJPUgBJTlZBTElEX1hfRk9SV0FSREVEX0ZPUgBTRVRfUEFSQU1FVEVSAEdFVF9QQVJBTUVURVIASFBFX1VTRVIAU0VFX09USEVSAEhQRV9DQl9DSFVOS19IRUFERVIATUtDQUxFTkRBUgBTRVRVUABXRUJfU0VSVkVSX0lTX0RPV04AVEVBUkRPV04ASFBFX0NMT1NFRF9DT05ORUNUSU9OAEhFVVJJU1RJQ19FWFBJUkFUSU9OAERJU0NPTk5FQ1RFRF9PUEVSQVRJT04ATk9OX0FVVEhPUklUQVRJVkVfSU5GT1JNQVRJT04ASFBFX0lOVkFMSURfVkVSU0lPTgBIUEVfQ0JfTUVTU0FHRV9CRUdJTgBTSVRFX0lTX0ZST1pFTgBIUEVfSU5WQUxJRF9IRUFERVJfVE9LRU4ASU5WQUxJRF9UT0tFTgBGT1JCSURERU4ARU5IQU5DRV9ZT1VSX0NBTE0ASFBFX0lOVkFMSURfVVJMAEJMT0NLRURfQllfUEFSRU5UQUxfQ09OVFJPTABNS0NPTABBQ0wASFBFX0lOVEVSTkFMAFJFUVVFU1RfSEVBREVSX0ZJRUxEU19UT09fTEFSR0VfVU5PRkZJQ0lBTABIUEVfT0sAVU5MSU5LAFVOTE9DSwBQUkkAUkVUUllfV0lUSABIUEVfSU5WQUxJRF9DT05URU5UX0xFTkdUSABIUEVfVU5FWFBFQ1RFRF9DT05URU5UX0xFTkdUSABGTFVTSABQUk9QUEFUQ0gATS1TRUFSQ0gAVVJJX1RPT19MT05HAFBST0NFU1NJTkcATUlTQ0VMTEFORU9VU19QRVJTSVNURU5UX1dBUk5JTkcATUlTQ0VMTEFORU9VU19XQVJOSU5HAEhQRV9JTlZBTElEX1RSQU5TRkVSX0VOQ09ESU5HAEV4cGVjdGVkIENSTEYASFBFX0lOVkFMSURfQ0hVTktfU0laRQBNT1ZFAENPTlRJTlVFAEhQRV9DQl9TVEFUVVNfQ09NUExFVEUASFBFX0NCX0hFQURFUlNfQ09NUExFVEUASFBFX0NCX1ZFUlNJT05fQ09NUExFVEUASFBFX0NCX1VSTF9DT01QTEVURQBIUEVfQ0JfQ0hVTktfQ09NUExFVEUASFBFX0NCX0hFQURFUl9WQUxVRV9DT01QTEVURQBIUEVfQ0JfQ0hVTktfRVhURU5TSU9OX1ZBTFVFX0NPTVBMRVRFAEhQRV9DQl9DSFVOS19FWFRFTlNJT05fTkFNRV9DT01QTEVURQBIUEVfQ0JfTUVTU0FHRV9DT01QTEVURQBIUEVfQ0JfTUVUSE9EX0NPTVBMRVRFAEhQRV9DQl9IRUFERVJfRklFTERfQ09NUExFVEUAREVMRVRFAEhQRV9JTlZBTElEX0VPRl9TVEFURQBJTlZBTElEX1NTTF9DRVJUSUZJQ0FURQBQQVVTRQBOT19SRVNQT05TRQBVTlNVUFBPUlRFRF9NRURJQV9UWVBFAEdPTkUATk9UX0FDQ0VQVEFCTEUAU0VSVklDRV9VTkFWQUlMQUJMRQBSQU5HRV9OT1RfU0FUSVNGSUFCTEUAT1JJR0lOX0lTX1VOUkVBQ0hBQkxFAFJFU1BPTlNFX0lTX1NUQUxFAFBVUkdFAE1FUkdFAFJFUVVFU1RfSEVBREVSX0ZJRUxEU19UT09fTEFSR0UAUkVRVUVTVF9IRUFERVJfVE9PX0xBUkdFAFBBWUxPQURfVE9PX0xBUkdFAElOU1VGRklDSUVOVF9TVE9SQUdFAEhQRV9QQVVTRURfVVBHUkFERQBIUEVfUEFVU0VEX0gyX1VQR1JBREUAU09VUkNFAEFOTk9VTkNFAFRSQUNFAEhQRV9VTkVYUEVDVEVEX1NQQUNFAERFU0NSSUJFAFVOU1VCU0NSSUJFAFJFQ09SRABIUEVfSU5WQUxJRF9NRVRIT0QATk9UX0ZPVU5EAFBST1BGSU5EAFVOQklORABSRUJJTkQAVU5BVVRIT1JJWkVEAE1FVEhPRF9OT1RfQUxMT1dFRABIVFRQX1ZFUlNJT05fTk9UX1NVUFBPUlRFRABBTFJFQURZX1JFUE9SVEVEAEFDQ0VQVEVEAE5PVF9JTVBMRU1FTlRFRABMT09QX0RFVEVDVEVEAEhQRV9DUl9FWFBFQ1RFRABIUEVfTEZfRVhQRUNURUQAQ1JFQVRFRABJTV9VU0VEAEhQRV9QQVVTRUQAVElNRU9VVF9PQ0NVUkVEAFBBWU1FTlRfUkVRVUlSRUQAUFJFQ09ORElUSU9OX1JFUVVJUkVEAFBST1hZX0FVVEhFTlRJQ0FUSU9OX1JFUVVJUkVEAE5FVFdPUktfQVVUSEVOVElDQVRJT05fUkVRVUlSRUQATEVOR1RIX1JFUVVJUkVEAFNTTF9DRVJUSUZJQ0FURV9SRVFVSVJFRABVUEdSQURFX1JFUVVJUkVEAFBBR0VfRVhQSVJFRABQUkVDT05ESVRJT05fRkFJTEVEAEVYUEVDVEFUSU9OX0ZBSUxFRABSRVZBTElEQVRJT05fRkFJTEVEAFNTTF9IQU5EU0hBS0VfRkFJTEVEAExPQ0tFRABUUkFOU0ZPUk1BVElPTl9BUFBMSUVEAE5PVF9NT0RJRklFRABOT1RfRVhURU5ERUQAQkFORFdJRFRIX0xJTUlUX0VYQ0VFREVEAFNJVEVfSVNfT1ZFUkxPQURFRABIRUFEAEV4cGVjdGVkIEhUVFAvAABeEwAAJhMAADAQAADwFwAAnRMAABUSAAA5FwAA8BIAAAoQAAB1EgAArRIAAIITAABPFAAAfxAAAKAVAAAjFAAAiRIAAIsUAABNFQAA1BEAAM8UAAAQGAAAyRYAANwWAADBEQAA4BcAALsUAAB0FAAAfBUAAOUUAAAIFwAAHxAAAGUVAACjFAAAKBUAAAIVAACZFQAALBAAAIsZAABPDwAA1A4AAGoQAADOEAAAAhcAAIkOAABuEwAAHBMAAGYUAABWFwAAwRMAAM0TAABsEwAAaBcAAGYXAABfFwAAIhMAAM4PAABpDgAA2A4AAGMWAADLEwAAqg4AACgXAAAmFwAAxRMAAF0WAADoEQAAZxMAAGUTAADyFgAAcxMAAB0XAAD5FgAA8xEAAM8OAADOFQAADBIAALMRAAClEQAAYRAAADIXAAC7EwBB+TULAQEAQZA2C+ABAQECAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAQf03CwEBAEGROAteAgMCAgICAgAAAgIAAgIAAgICAgICAgICAgAEAAAAAAACAgICAgICAgICAgICAgICAgICAgICAgICAgAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAgICAAIAAgBB/TkLAQEAQZE6C14CAAICAgICAAACAgACAgACAgICAgICAgICAAMABAAAAAICAgICAgICAgICAgICAgICAgICAgICAgICAAAAAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAAgACAEHwOwsNbG9zZWVlcC1hbGl2ZQBBiTwLAQEAQaA8C+ABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAQYk+CwEBAEGgPgvnAQEBAQEBAQEBAQEBAQIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBY2h1bmtlZABBsMAAC18BAQABAQEBAQAAAQEAAQEAAQEBAQEBAQEBAQAAAAAAAAABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEAAQBBkMIACyFlY3Rpb25lbnQtbGVuZ3Rob25yb3h5LWNvbm5lY3Rpb24AQcDCAAstcmFuc2Zlci1lbmNvZGluZ3BncmFkZQ0KDQoNClNNDQoNClRUUC9DRS9UU1AvAEH5wgALBQECAAEDAEGQwwAL4AEEAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBB+cQACwUBAgABAwBBkMUAC+ABBAEBBQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEAQfnGAAsEAQAAAQBBkccAC98BAQEAAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQBB+sgACwQBAAACAEGQyQALXwMEAAAEBAQEBAQEBAQEBAUEBAQEBAQEBAQEBAQABAAGBwQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAAEAAQABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQAAAAEAEH6ygALBAEAAAEAQZDLAAsBAQBBqssAC0ECAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAAAAAAAADAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwBB+swACwQBAAABAEGQzQALAQEAQZrNAAsGAgAAAAACAEGxzQALOgMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAAAAAAAAAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAQfDOAAuWAU5PVU5DRUVDS09VVE5FQ1RFVEVDUklCRUxVU0hFVEVBRFNFQVJDSFJHRUNUSVZJVFlMRU5EQVJWRU9USUZZUFRJT05TQ0hTRUFZU1RBVENIR0VPUkRJUkVDVE9SVFJDSFBBUkFNRVRFUlVSQ0VCU0NSSUJFQVJET1dOQUNFSU5ETktDS1VCU0NSSUJFSFRUUC9BRFRQLw==","base64")},172:(e,t)=>{Object.defineProperty(t,"__esModule",{value:true});t.enumToMap=void 0;function enumToMap(e){const t={};Object.keys(e).forEach((i=>{const n=e[i];if(typeof n==="number"){t[i]=n}}));return t}t.enumToMap=enumToMap},7501:(e,t,i)=>{const{kClients:n}=i(6443);const r=i(7405);const{kAgent:s,kMockAgentSet:o,kMockAgentGet:a,kDispatches:l,kIsMockActive:u,kNetConnect:c,kGetNetConnect:d,kOptions:p,kFactory:A}=i(1117);const f=i(7365);const h=i(4004);const{matchValue:g,buildMockOptions:y}=i(3397);const{InvalidArgumentError:m,UndiciError:I}=i(8707);const v=i(883);const E=i(1529);const C=i(6142);class MockAgent extends v{constructor(e){super(e);this[c]=true;this[u]=true;if(e?.agent&&typeof e.agent.dispatch!=="function"){throw new m("Argument opts.agent must implement Agent")}const t=e?.agent?e.agent:new r(e);this[s]=t;this[n]=t[n];this[p]=y(e)}get(e){let t=this[a](e);if(!t){t=this[A](e);this[o](e,t)}return t}dispatch(e,t){this.get(e.origin);return this[s].dispatch(e,t)}async close(){await this[s].close();this[n].clear()}deactivate(){this[u]=false}activate(){this[u]=true}enableNetConnect(e){if(typeof e==="string"||typeof e==="function"||e instanceof RegExp){if(Array.isArray(this[c])){this[c].push(e)}else{this[c]=[e]}}else if(typeof e==="undefined"){this[c]=true}else{throw new m("Unsupported matcher. Must be one of String|Function|RegExp.")}}disableNetConnect(){this[c]=false}get isMockActive(){return this[u]}[o](e,t){this[n].set(e,t)}[A](e){const t=Object.assign({agent:this},this[p]);return this[p]&&this[p].connections===1?new f(e,t):new h(e,t)}[a](e){const t=this[n].get(e);if(t){return t}if(typeof e!=="string"){const t=this[A]("http://localhost:9999");this[o](e,t);return t}for(const[t,i]of Array.from(this[n])){if(i&&typeof t!=="string"&&g(t,e)){const t=this[A](e);this[o](e,t);t[l]=i[l];return t}}}[d](){return this[c]}pendingInterceptors(){const e=this[n];return Array.from(e.entries()).flatMap((([e,t])=>t[l].map((t=>({...t,origin:e}))))).filter((({pending:e})=>e))}assertNoPendingInterceptors({pendingInterceptorsFormatter:e=new C}={}){const t=this.pendingInterceptors();if(t.length===0){return}const i=new E("interceptor","interceptors").pluralize(t.length);throw new I(`\n${i.count} ${i.noun} ${i.is} pending:\n\n${e.format(t)}\n`.trim())}}e.exports=MockAgent},7365:(e,t,i)=>{const{promisify:n}=i(7975);const r=i(3701);const{buildMockDispatch:s}=i(3397);const{kDispatches:o,kMockAgent:a,kClose:l,kOriginalClose:u,kOrigin:c,kOriginalDispatch:d,kConnected:p}=i(1117);const{MockInterceptor:A}=i(1511);const f=i(6443);const{InvalidArgumentError:h}=i(8707);class MockClient extends r{constructor(e,t){super(e,t);if(!t||!t.agent||typeof t.agent.dispatch!=="function"){throw new h("Argument opts.agent must implement Agent")}this[a]=t.agent;this[c]=e;this[o]=[];this[p]=1;this[d]=this.dispatch;this[u]=this.close.bind(this);this.dispatch=s.call(this);this.close=this[l]}get[f.kConnected](){return this[p]}intercept(e){return new A(e,this[o])}async[l](){await n(this[u])();this[p]=0;this[a][f.kClients].delete(this[c])}}e.exports=MockClient},2429:(e,t,i)=>{const{UndiciError:n}=i(8707);const r=Symbol.for("undici.error.UND_MOCK_ERR_MOCK_NOT_MATCHED");class MockNotMatchedError extends n{constructor(e){super(e);Error.captureStackTrace(this,MockNotMatchedError);this.name="MockNotMatchedError";this.message=e||"The request does not match any registered mock dispatches";this.code="UND_MOCK_ERR_MOCK_NOT_MATCHED"}static[Symbol.hasInstance](e){return e&&e[r]===true}[r]=true}e.exports={MockNotMatchedError:MockNotMatchedError}},1511:(e,t,i)=>{const{getResponseData:n,buildKey:r,addMockDispatch:s}=i(3397);const{kDispatches:o,kDispatchKey:a,kDefaultHeaders:l,kDefaultTrailers:u,kContentLength:c,kMockDispatch:d}=i(1117);const{InvalidArgumentError:p}=i(8707);const{buildURL:A}=i(3440);class MockScope{constructor(e){this[d]=e}delay(e){if(typeof e!=="number"||!Number.isInteger(e)||e<=0){throw new p("waitInMs must be a valid integer > 0")}this[d].delay=e;return this}persist(){this[d].persist=true;return this}times(e){if(typeof e!=="number"||!Number.isInteger(e)||e<=0){throw new p("repeatTimes must be a valid integer > 0")}this[d].times=e;return this}}class MockInterceptor{constructor(e,t){if(typeof e!=="object"){throw new p("opts must be an object")}if(typeof e.path==="undefined"){throw new p("opts.path must be defined")}if(typeof e.method==="undefined"){e.method="GET"}if(typeof e.path==="string"){if(e.query){e.path=A(e.path,e.query)}else{const t=new URL(e.path,"data://");e.path=t.pathname+t.search}}if(typeof e.method==="string"){e.method=e.method.toUpperCase()}this[a]=r(e);this[o]=t;this[l]={};this[u]={};this[c]=false}createMockScopeDispatchData({statusCode:e,data:t,responseOptions:i}){const r=n(t);const s=this[c]?{"content-length":r.length}:{};const o={...this[l],...s,...i.headers};const a={...this[u],...i.trailers};return{statusCode:e,data:t,headers:o,trailers:a}}validateReplyParameters(e){if(typeof e.statusCode==="undefined"){throw new p("statusCode must be defined")}if(typeof e.responseOptions!=="object"||e.responseOptions===null){throw new p("responseOptions must be an object")}}reply(e){if(typeof e==="function"){const wrappedDefaultsCallback=t=>{const i=e(t);if(typeof i!=="object"||i===null){throw new p("reply options callback must return an object")}const n={data:"",responseOptions:{},...i};this.validateReplyParameters(n);return{...this.createMockScopeDispatchData(n)}};const t=s(this[o],this[a],wrappedDefaultsCallback);return new MockScope(t)}const t={statusCode:e,data:arguments[1]===undefined?"":arguments[1],responseOptions:arguments[2]===undefined?{}:arguments[2]};this.validateReplyParameters(t);const i=this.createMockScopeDispatchData(t);const n=s(this[o],this[a],i);return new MockScope(n)}replyWithError(e){if(typeof e==="undefined"){throw new p("error must be defined")}const t=s(this[o],this[a],{error:e});return new MockScope(t)}defaultReplyHeaders(e){if(typeof e==="undefined"){throw new p("headers must be defined")}this[l]=e;return this}defaultReplyTrailers(e){if(typeof e==="undefined"){throw new p("trailers must be defined")}this[u]=e;return this}replyContentLength(){this[c]=true;return this}}e.exports.MockInterceptor=MockInterceptor;e.exports.MockScope=MockScope},4004:(e,t,i)=>{const{promisify:n}=i(7975);const r=i(628);const{buildMockDispatch:s}=i(3397);const{kDispatches:o,kMockAgent:a,kClose:l,kOriginalClose:u,kOrigin:c,kOriginalDispatch:d,kConnected:p}=i(1117);const{MockInterceptor:A}=i(1511);const f=i(6443);const{InvalidArgumentError:h}=i(8707);class MockPool extends r{constructor(e,t){super(e,t);if(!t||!t.agent||typeof t.agent.dispatch!=="function"){throw new h("Argument opts.agent must implement Agent")}this[a]=t.agent;this[c]=e;this[o]=[];this[p]=1;this[d]=this.dispatch;this[u]=this.close.bind(this);this.dispatch=s.call(this);this.close=this[l]}get[f.kConnected](){return this[p]}intercept(e){return new A(e,this[o])}async[l](){await n(this[u])();this[p]=0;this[a][f.kClients].delete(this[c])}}e.exports=MockPool},1117:e=>{e.exports={kAgent:Symbol("agent"),kOptions:Symbol("options"),kFactory:Symbol("factory"),kDispatches:Symbol("dispatches"),kDispatchKey:Symbol("dispatch key"),kDefaultHeaders:Symbol("default headers"),kDefaultTrailers:Symbol("default trailers"),kContentLength:Symbol("content length"),kMockAgent:Symbol("mock agent"),kMockAgentSet:Symbol("mock agent set"),kMockAgentGet:Symbol("mock agent get"),kMockDispatch:Symbol("mock dispatch"),kClose:Symbol("close"),kOriginalClose:Symbol("original agent close"),kOrigin:Symbol("origin"),kIsMockActive:Symbol("is mock active"),kNetConnect:Symbol("net connect"),kGetNetConnect:Symbol("get net connect"),kConnected:Symbol("connected")}},3397:(e,t,i)=>{const{MockNotMatchedError:n}=i(2429);const{kDispatches:r,kMockAgent:s,kOriginalDispatch:o,kOrigin:a,kGetNetConnect:l}=i(1117);const{buildURL:u}=i(3440);const{STATUS_CODES:c}=i(7067);const{types:{isPromise:d}}=i(7975);function matchValue(e,t){if(typeof e==="string"){return e===t}if(e instanceof RegExp){return e.test(t)}if(typeof e==="function"){return e(t)===true}return false}function lowerCaseEntries(e){return Object.fromEntries(Object.entries(e).map((([e,t])=>[e.toLocaleLowerCase(),t])))}function getHeaderByName(e,t){if(Array.isArray(e)){for(let i=0;i!e)).filter((({path:e})=>matchValue(safeUrl(e),r)));if(s.length===0){throw new n(`Mock dispatch not matched for path '${r}'`)}s=s.filter((({method:e})=>matchValue(e,t.method)));if(s.length===0){throw new n(`Mock dispatch not matched for method '${t.method}' on path '${r}'`)}s=s.filter((({body:e})=>typeof e!=="undefined"?matchValue(e,t.body):true));if(s.length===0){throw new n(`Mock dispatch not matched for body '${t.body}' on path '${r}'`)}s=s.filter((e=>matchHeaders(e,t.headers)));if(s.length===0){const e=typeof t.headers==="object"?JSON.stringify(t.headers):t.headers;throw new n(`Mock dispatch not matched for headers '${e}' on path '${r}'`)}return s[0]}function addMockDispatch(e,t,i){const n={timesInvoked:0,times:1,persist:false,consumed:false};const r=typeof i==="function"?{callback:i}:{...i};const s={...n,...t,pending:true,data:{error:null,...r}};e.push(s);return s}function deleteMockDispatch(e,t){const i=e.findIndex((e=>{if(!e.consumed){return false}return matchKey(e,t)}));if(i!==-1){e.splice(i,1)}}function buildKey(e){const{path:t,method:i,body:n,headers:r,query:s}=e;return{path:t,method:i,body:n,headers:r,query:s}}function generateKeyValues(e){const t=Object.keys(e);const i=[];for(let n=0;n=f;n.pending=A0){setTimeout((()=>{handleReply(this[r])}),c)}else{handleReply(this[r])}function handleReply(n,r=o){const u=Array.isArray(e.headers)?buildHeadersFromArray(e.headers):e.headers;const c=typeof r==="function"?r({...e,headers:u}):r;if(d(c)){c.then((e=>handleReply(n,e)));return}const p=getResponseData(c);const A=generateKeyValues(a);const f=generateKeyValues(l);t.onConnect?.((e=>t.onError(e)),null);t.onHeaders?.(s,A,resume,getStatusText(s));t.onData?.(Buffer.from(p));t.onComplete?.(f);deleteMockDispatch(n,i)}function resume(){}return true}function buildMockDispatch(){const e=this[s];const t=this[a];const i=this[o];return function dispatch(r,s){if(e.isMockActive){try{mockDispatch.call(this,r,s)}catch(o){if(o instanceof n){const a=e[l]();if(a===false){throw new n(`${o.message}: subsequent request to origin ${t} was not allowed (net.connect disabled)`)}if(checkNetConnect(a,t)){i.call(this,r,s)}else{throw new n(`${o.message}: subsequent request to origin ${t} was not allowed (net.connect is not enabled for this origin)`)}}else{throw o}}}else{i.call(this,r,s)}}}function checkNetConnect(e,t){const i=new URL(t);if(e===true){return true}else if(Array.isArray(e)&&e.some((e=>matchValue(e,i.host)))){return true}return false}function buildMockOptions(e){if(e){const{agent:t,...i}=e;return i}}e.exports={getResponseData:getResponseData,getMockDispatch:getMockDispatch,addMockDispatch:addMockDispatch,deleteMockDispatch:deleteMockDispatch,buildKey:buildKey,generateKeyValues:generateKeyValues,matchValue:matchValue,getResponse:getResponse,getStatusText:getStatusText,mockDispatch:mockDispatch,buildMockDispatch:buildMockDispatch,checkNetConnect:checkNetConnect,buildMockOptions:buildMockOptions,getHeaderByName:getHeaderByName,buildHeadersFromArray:buildHeadersFromArray}},6142:(e,t,i)=>{const{Transform:n}=i(7075);const{Console:r}=i(7540);const s=process.versions.icu?"✅":"Y ";const o=process.versions.icu?"❌":"N ";e.exports=class PendingInterceptorsFormatter{constructor({disableColors:e}={}){this.transform=new n({transform(e,t,i){i(null,e)}});this.logger=new r({stdout:this.transform,inspectOptions:{colors:!e&&!process.env.CI}})}format(e){const t=e.map((({method:e,path:t,data:{statusCode:i},persist:n,times:r,timesInvoked:a,origin:l})=>({Method:e,Origin:l,Path:t,"Status code":i,Persistent:n?s:o,Invocations:a,Remaining:n?Infinity:r-a})));this.logger.table(t);return this.transform.read().toString()}}},1529:e=>{const t={pronoun:"it",is:"is",was:"was",this:"this"};const i={pronoun:"they",is:"are",was:"were",this:"these"};e.exports=class Pluralizer{constructor(e,t){this.singular=e;this.plural=t}pluralize(e){const n=e===1;const r=n?t:i;const s=n?this.singular:this.plural;return{...r,count:e,noun:s}}}},6603:e=>{let t=0;const i=1e3;const n=(i>>1)-1;let r;const s=Symbol("kFastTimer");const o=[];const a=-2;const l=-1;const u=0;const c=1;function onTick(){t+=n;let e=0;let i=o.length;while(e=r._idleStart+r._idleTimeout){r._state=l;r._idleStart=-1;r._onTimeout(r._timerArg)}if(r._state===l){r._state=a;if(--i!==0){o[e]=o[i]}}else{++e}}o.length=i;if(o.length!==0){refreshTimeout()}}function refreshTimeout(){if(r){r.refresh()}else{clearTimeout(r);r=setTimeout(onTick,n);if(r.unref){r.unref()}}}class FastTimer{[s]=true;_state=a;_idleTimeout=-1;_idleStart=-1;_onTimeout;_timerArg;constructor(e,t,i){this._onTimeout=e;this._idleTimeout=t;this._timerArg=i;this.refresh()}refresh(){if(this._state===a){o.push(this)}if(!r||o.length===1){refreshTimeout()}this._state=u}clear(){this._state=l;this._idleStart=-1}}e.exports={setTimeout(e,t,n){return t<=i?setTimeout(e,t,n):new FastTimer(e,t,n)},clearTimeout(e){if(e[s]){e.clear()}else{clearTimeout(e)}},setFastTimeout(e,t,i){return new FastTimer(e,t,i)},clearFastTimeout(e){e.clear()},now(){return t},tick(e=0){t+=e-i+1;onTick();onTick()},reset(){t=0;o.length=0;clearTimeout(r);r=null},kFastTimer:s}},9634:(e,t,i)=>{const{kConstruct:n}=i(109);const{urlEquals:r,getFieldValues:s}=i(6798);const{kEnumerableProperty:o,isDisturbed:a}=i(3440);const{webidl:l}=i(5893);const{Response:u,cloneResponse:c,fromInnerResponse:d}=i(9051);const{Request:p,fromInnerRequest:A}=i(9967);const{kState:f}=i(3627);const{fetching:h}=i(4398);const{urlIsHttpHttpsScheme:g,createDeferredPromise:y,readAllBytes:m}=i(3168);const I=i(4589);class Cache{#D;constructor(){if(arguments[0]!==n){l.illegalConstructor()}l.util.markAsUncloneable(this);this.#D=arguments[1]}async match(e,t={}){l.brandCheck(this,Cache);const i="Cache.match";l.argumentLengthCheck(arguments,1,i);e=l.converters.RequestInfo(e,i,"request");t=l.converters.CacheQueryOptions(t,i,"options");const n=this.#S(e,t,1);if(n.length===0){return}return n[0]}async matchAll(e=undefined,t={}){l.brandCheck(this,Cache);const i="Cache.matchAll";if(e!==undefined)e=l.converters.RequestInfo(e,i,"request");t=l.converters.CacheQueryOptions(t,i,"options");return this.#S(e,t)}async add(e){l.brandCheck(this,Cache);const t="Cache.add";l.argumentLengthCheck(arguments,1,t);e=l.converters.RequestInfo(e,t,"request");const i=[e];const n=this.addAll(i);return await n}async addAll(e){l.brandCheck(this,Cache);const t="Cache.addAll";l.argumentLengthCheck(arguments,1,t);const i=[];const n=[];for(let i of e){if(i===undefined){throw l.errors.conversionFailed({prefix:t,argument:"Argument 1",types:["undefined is not allowed"]})}i=l.converters.RequestInfo(i);if(typeof i==="string"){continue}const e=i[f];if(!g(e.url)||e.method!=="GET"){throw l.errors.exception({header:t,message:"Expected http/s scheme when method is not GET."})}}const r=[];for(const o of e){const e=new p(o)[f];if(!g(e.url)){throw l.errors.exception({header:t,message:"Expected http/s scheme."})}e.initiator="fetch";e.destination="subresource";n.push(e);const a=y();r.push(h({request:e,processResponse(e){if(e.type==="error"||e.status===206||e.status<200||e.status>299){a.reject(l.errors.exception({header:"Cache.addAll",message:"Received an invalid status code or the request failed."}))}else if(e.headersList.contains("vary")){const t=s(e.headersList.get("vary"));for(const e of t){if(e==="*"){a.reject(l.errors.exception({header:"Cache.addAll",message:"invalid vary field value"}));for(const e of r){e.abort()}return}}}},processResponseEndOfBody(e){if(e.aborted){a.reject(new DOMException("aborted","AbortError"));return}a.resolve(e)}}));i.push(a.promise)}const o=Promise.all(i);const a=await o;const u=[];let c=0;for(const e of a){const t={type:"put",request:n[c],response:e};u.push(t);c++}const d=y();let A=null;try{this.#k(u)}catch(e){A=e}queueMicrotask((()=>{if(A===null){d.resolve(undefined)}else{d.reject(A)}}));return d.promise}async put(e,t){l.brandCheck(this,Cache);const i="Cache.put";l.argumentLengthCheck(arguments,2,i);e=l.converters.RequestInfo(e,i,"request");t=l.converters.Response(t,i,"response");let n=null;if(e instanceof p){n=e[f]}else{n=new p(e)[f]}if(!g(n.url)||n.method!=="GET"){throw l.errors.exception({header:i,message:"Expected an http/s scheme when method is not GET"})}const r=t[f];if(r.status===206){throw l.errors.exception({header:i,message:"Got 206 status"})}if(r.headersList.contains("vary")){const e=s(r.headersList.get("vary"));for(const t of e){if(t==="*"){throw l.errors.exception({header:i,message:"Got * vary field value"})}}}if(r.body&&(a(r.body.stream)||r.body.stream.locked)){throw l.errors.exception({header:i,message:"Response body is locked or disturbed"})}const o=c(r);const u=y();if(r.body!=null){const e=r.body.stream;const t=e.getReader();m(t).then(u.resolve,u.reject)}else{u.resolve(undefined)}const d=[];const A={type:"put",request:n,response:o};d.push(A);const h=await u.promise;if(o.body!=null){o.body.source=h}const I=y();let v=null;try{this.#k(d)}catch(e){v=e}queueMicrotask((()=>{if(v===null){I.resolve()}else{I.reject(v)}}));return I.promise}async delete(e,t={}){l.brandCheck(this,Cache);const i="Cache.delete";l.argumentLengthCheck(arguments,1,i);e=l.converters.RequestInfo(e,i,"request");t=l.converters.CacheQueryOptions(t,i,"options");let n=null;if(e instanceof p){n=e[f];if(n.method!=="GET"&&!t.ignoreMethod){return false}}else{I(typeof e==="string");n=new p(e)[f]}const r=[];const s={type:"delete",request:n,options:t};r.push(s);const o=y();let a=null;let u;try{u=this.#k(r)}catch(e){a=e}queueMicrotask((()=>{if(a===null){o.resolve(!!u?.length)}else{o.reject(a)}}));return o.promise}async keys(e=undefined,t={}){l.brandCheck(this,Cache);const i="Cache.keys";if(e!==undefined)e=l.converters.RequestInfo(e,i,"request");t=l.converters.CacheQueryOptions(t,i,"options");let n=null;if(e!==undefined){if(e instanceof p){n=e[f];if(n.method!=="GET"&&!t.ignoreMethod){return[]}}else if(typeof e==="string"){n=new p(e)[f]}}const r=y();const s=[];if(e===undefined){for(const e of this.#D){s.push(e[0])}}else{const e=this.#P(n,t);for(const t of e){s.push(t[0])}}queueMicrotask((()=>{const e=[];for(const t of s){const i=A(t,(new AbortController).signal,"immutable");e.push(i)}r.resolve(Object.freeze(e))}));return r.promise}#k(e){const t=this.#D;const i=[...t];const n=[];const r=[];try{for(const i of e){if(i.type!=="delete"&&i.type!=="put"){throw l.errors.exception({header:"Cache.#batchCacheOperations",message:'operation type does not match "delete" or "put"'})}if(i.type==="delete"&&i.response!=null){throw l.errors.exception({header:"Cache.#batchCacheOperations",message:"delete operation should not have an associated response"})}if(this.#P(i.request,i.options,n).length){throw new DOMException("???","InvalidStateError")}let e;if(i.type==="delete"){e=this.#P(i.request,i.options);if(e.length===0){return[]}for(const i of e){const e=t.indexOf(i);I(e!==-1);t.splice(e,1)}}else if(i.type==="put"){if(i.response==null){throw l.errors.exception({header:"Cache.#batchCacheOperations",message:"put operation should have an associated response"})}const r=i.request;if(!g(r.url)){throw l.errors.exception({header:"Cache.#batchCacheOperations",message:"expected http or https scheme"})}if(r.method!=="GET"){throw l.errors.exception({header:"Cache.#batchCacheOperations",message:"not get method"})}if(i.options!=null){throw l.errors.exception({header:"Cache.#batchCacheOperations",message:"options must not be defined"})}e=this.#P(i.request);for(const i of e){const e=t.indexOf(i);I(e!==-1);t.splice(e,1)}t.push([i.request,i.response]);n.push([i.request,i.response])}r.push([i.request,i.response])}return r}catch(e){this.#D.length=0;this.#D=i;throw e}}#P(e,t,i){const n=[];const r=i??this.#D;for(const i of r){const[r,s]=i;if(this.#U(e,r,s,t)){n.push(i)}}return n}#U(e,t,i=null,n){const o=new URL(e.url);const a=new URL(t.url);if(n?.ignoreSearch){a.search="";o.search=""}if(!r(o,a,true)){return false}if(i==null||n?.ignoreVary||!i.headersList.contains("vary")){return true}const l=s(i.headersList.get("vary"));for(const i of l){if(i==="*"){return false}const n=t.headersList.get(i);const r=e.headersList.get(i);if(n!==r){return false}}return true}#S(e,t,i=Infinity){let n=null;if(e!==undefined){if(e instanceof p){n=e[f];if(n.method!=="GET"&&!t.ignoreMethod){return[]}}else if(typeof e==="string"){n=new p(e)[f]}}const r=[];if(e===undefined){for(const e of this.#D){r.push(e[1])}}else{const e=this.#P(n,t);for(const t of e){r.push(t[1])}}const s=[];for(const e of r){const t=d(e,"immutable");s.push(t.clone());if(s.length>=i){break}}return Object.freeze(s)}}Object.defineProperties(Cache.prototype,{[Symbol.toStringTag]:{value:"Cache",configurable:true},match:o,matchAll:o,add:o,addAll:o,put:o,delete:o,keys:o});const v=[{key:"ignoreSearch",converter:l.converters.boolean,defaultValue:()=>false},{key:"ignoreMethod",converter:l.converters.boolean,defaultValue:()=>false},{key:"ignoreVary",converter:l.converters.boolean,defaultValue:()=>false}];l.converters.CacheQueryOptions=l.dictionaryConverter(v);l.converters.MultiCacheQueryOptions=l.dictionaryConverter([...v,{key:"cacheName",converter:l.converters.DOMString}]);l.converters.Response=l.interfaceConverter(u);l.converters["sequence"]=l.sequenceConverter(l.converters.RequestInfo);e.exports={Cache:Cache}},3245:(e,t,i)=>{const{kConstruct:n}=i(109);const{Cache:r}=i(9634);const{webidl:s}=i(5893);const{kEnumerableProperty:o}=i(3440);class CacheStorage{#V=new Map;constructor(){if(arguments[0]!==n){s.illegalConstructor()}s.util.markAsUncloneable(this)}async match(e,t={}){s.brandCheck(this,CacheStorage);s.argumentLengthCheck(arguments,1,"CacheStorage.match");e=s.converters.RequestInfo(e);t=s.converters.MultiCacheQueryOptions(t);if(t.cacheName!=null){if(this.#V.has(t.cacheName)){const i=this.#V.get(t.cacheName);const s=new r(n,i);return await s.match(e,t)}}else{for(const i of this.#V.values()){const s=new r(n,i);const o=await s.match(e,t);if(o!==undefined){return o}}}}async has(e){s.brandCheck(this,CacheStorage);const t="CacheStorage.has";s.argumentLengthCheck(arguments,1,t);e=s.converters.DOMString(e,t,"cacheName");return this.#V.has(e)}async open(e){s.brandCheck(this,CacheStorage);const t="CacheStorage.open";s.argumentLengthCheck(arguments,1,t);e=s.converters.DOMString(e,t,"cacheName");if(this.#V.has(e)){const t=this.#V.get(e);return new r(n,t)}const i=[];this.#V.set(e,i);return new r(n,i)}async delete(e){s.brandCheck(this,CacheStorage);const t="CacheStorage.delete";s.argumentLengthCheck(arguments,1,t);e=s.converters.DOMString(e,t,"cacheName");return this.#V.delete(e)}async keys(){s.brandCheck(this,CacheStorage);const e=this.#V.keys();return[...e]}}Object.defineProperties(CacheStorage.prototype,{[Symbol.toStringTag]:{value:"CacheStorage",configurable:true},match:o,has:o,open:o,delete:o,keys:o});e.exports={CacheStorage:CacheStorage}},109:(e,t,i)=>{e.exports={kConstruct:i(6443).kConstruct}},6798:(e,t,i)=>{const n=i(4589);const{URLSerializer:r}=i(1900);const{isValidHeaderName:s}=i(3168);function urlEquals(e,t,i=false){const n=r(e,i);const s=r(t,i);return n===s}function getFieldValues(e){n(e!==null);const t=[];for(let i of e.split(",")){i=i.trim();if(s(i)){t.push(i)}}return t}e.exports={urlEquals:urlEquals,getFieldValues:getFieldValues}},1276:e=>{const t=1024;const i=4096;e.exports={maxAttributeValueSize:t,maxNameValuePairSize:i}},9061:(e,t,i)=>{const{parseSetCookie:n}=i(1978);const{stringify:r}=i(7797);const{webidl:s}=i(5893);const{Headers:o}=i(660);function getCookies(e){s.argumentLengthCheck(arguments,1,"getCookies");s.brandCheck(e,o,{strict:false});const t=e.get("cookie");const i={};if(!t){return i}for(const e of t.split(";")){const[t,...n]=e.split("=");i[t.trim()]=n.join("=")}return i}function deleteCookie(e,t,i){s.brandCheck(e,o,{strict:false});const n="deleteCookie";s.argumentLengthCheck(arguments,2,n);t=s.converters.DOMString(t,n,"name");i=s.converters.DeleteCookieAttributes(i);setCookie(e,{name:t,value:"",expires:new Date(0),...i})}function getSetCookies(e){s.argumentLengthCheck(arguments,1,"getSetCookies");s.brandCheck(e,o,{strict:false});const t=e.getSetCookie();if(!t){return[]}return t.map((e=>n(e)))}function setCookie(e,t){s.argumentLengthCheck(arguments,2,"setCookie");s.brandCheck(e,o,{strict:false});t=s.converters.Cookie(t);const i=r(t);if(i){e.append("Set-Cookie",i)}}s.converters.DeleteCookieAttributes=s.dictionaryConverter([{converter:s.nullableConverter(s.converters.DOMString),key:"path",defaultValue:()=>null},{converter:s.nullableConverter(s.converters.DOMString),key:"domain",defaultValue:()=>null}]);s.converters.Cookie=s.dictionaryConverter([{converter:s.converters.DOMString,key:"name"},{converter:s.converters.DOMString,key:"value"},{converter:s.nullableConverter((e=>{if(typeof e==="number"){return s.converters["unsigned long long"](e)}return new Date(e)})),key:"expires",defaultValue:()=>null},{converter:s.nullableConverter(s.converters["long long"]),key:"maxAge",defaultValue:()=>null},{converter:s.nullableConverter(s.converters.DOMString),key:"domain",defaultValue:()=>null},{converter:s.nullableConverter(s.converters.DOMString),key:"path",defaultValue:()=>null},{converter:s.nullableConverter(s.converters.boolean),key:"secure",defaultValue:()=>null},{converter:s.nullableConverter(s.converters.boolean),key:"httpOnly",defaultValue:()=>null},{converter:s.converters.USVString,key:"sameSite",allowedValues:["Strict","Lax","None"]},{converter:s.sequenceConverter(s.converters.DOMString),key:"unparsed",defaultValue:()=>new Array(0)}]);e.exports={getCookies:getCookies,deleteCookie:deleteCookie,getSetCookies:getSetCookies,setCookie:setCookie}},1978:(e,t,i)=>{const{maxNameValuePairSize:n,maxAttributeValueSize:r}=i(1276);const{isCTLExcludingHtab:s}=i(7797);const{collectASequenceOfCodePointsFast:o}=i(1900);const a=i(4589);function parseSetCookie(e){if(s(e)){return null}let t="";let i="";let r="";let a="";if(e.includes(";")){const n={position:0};t=o(";",e,n);i=e.slice(n.position)}else{t=e}if(!t.includes("=")){a=t}else{const e={position:0};r=o("=",t,e);a=t.slice(e.position+1)}r=r.trim();a=a.trim();if(r.length+a.length>n){return null}return{name:r,value:a,...parseUnparsedAttributes(i)}}function parseUnparsedAttributes(e,t={}){if(e.length===0){return t}a(e[0]===";");e=e.slice(1);let i="";if(e.includes(";")){i=o(";",e,{position:0});e=e.slice(i.length)}else{i=e;e=""}let n="";let s="";if(i.includes("=")){const e={position:0};n=o("=",i,e);s=i.slice(e.position+1)}else{n=i}n=n.trim();s=s.trim();if(s.length>r){return parseUnparsedAttributes(e,t)}const l=n.toLowerCase();if(l==="expires"){const e=new Date(s);t.expires=e}else if(l==="max-age"){const i=s.charCodeAt(0);if((i<48||i>57)&&s[0]!=="-"){return parseUnparsedAttributes(e,t)}if(!/^\d+$/.test(s)){return parseUnparsedAttributes(e,t)}const n=Number(s);t.maxAge=n}else if(l==="domain"){let e=s;if(e[0]==="."){e=e.slice(1)}e=e.toLowerCase();t.domain=e}else if(l==="path"){let e="";if(s.length===0||s[0]!=="/"){e="/"}else{e=s}t.path=e}else if(l==="secure"){t.secure=true}else if(l==="httponly"){t.httpOnly=true}else if(l==="samesite"){let e="Default";const i=s.toLowerCase();if(i.includes("none")){e="None"}if(i.includes("strict")){e="Strict"}if(i.includes("lax")){e="Lax"}t.sameSite=e}else{t.unparsed??=[];t.unparsed.push(`${n}=${s}`)}return parseUnparsedAttributes(e,t)}e.exports={parseSetCookie:parseSetCookie,parseUnparsedAttributes:parseUnparsedAttributes}},7797:e=>{function isCTLExcludingHtab(e){for(let t=0;t=0&&i<=8||i>=10&&i<=31||i===127){return true}}return false}function validateCookieName(e){for(let t=0;t126||i===34||i===40||i===41||i===60||i===62||i===64||i===44||i===59||i===58||i===92||i===47||i===91||i===93||i===63||i===61||i===123||i===125){throw new Error("Invalid cookie name")}}}function validateCookieValue(e){let t=e.length;let i=0;if(e[0]==='"'){if(t===1||e[t-1]!=='"'){throw new Error("Invalid cookie value")}--t;++i}while(i126||t===34||t===44||t===59||t===92){throw new Error("Invalid cookie value")}}}function validateCookiePath(e){for(let t=0;tt.toString().padStart(2,"0")));function toIMFDate(e){if(typeof e==="number"){e=new Date(e)}return`${t[e.getUTCDay()]}, ${n[e.getUTCDate()]} ${i[e.getUTCMonth()]} ${e.getUTCFullYear()} ${n[e.getUTCHours()]}:${n[e.getUTCMinutes()]}:${n[e.getUTCSeconds()]} GMT`}function validateCookieMaxAge(e){if(e<0){throw new Error("Invalid cookie max-age")}}function stringify(e){if(e.name.length===0){return null}validateCookieName(e.name);validateCookieValue(e.value);const t=[`${e.name}=${e.value}`];if(e.name.startsWith("__Secure-")){e.secure=true}if(e.name.startsWith("__Host-")){e.secure=true;e.domain=null;e.path="/"}if(e.secure){t.push("Secure")}if(e.httpOnly){t.push("HttpOnly")}if(typeof e.maxAge==="number"){validateCookieMaxAge(e.maxAge);t.push(`Max-Age=${e.maxAge}`)}if(e.domain){validateCookieDomain(e.domain);t.push(`Domain=${e.domain}`)}if(e.path){validateCookiePath(e.path);t.push(`Path=${e.path}`)}if(e.expires&&e.expires.toString()!=="Invalid Date"){t.push(`Expires=${toIMFDate(e.expires)}`)}if(e.sameSite){t.push(`SameSite=${e.sameSite}`)}for(const i of e.unparsed){if(!i.includes("=")){throw new Error("Invalid unparsed")}const[e,...n]=i.split("=");t.push(`${e.trim()}=${n.join("=")}`)}return t.join("; ")}e.exports={isCTLExcludingHtab:isCTLExcludingHtab,validateCookieName:validateCookieName,validateCookiePath:validateCookiePath,validateCookieValue:validateCookieValue,toIMFDate:toIMFDate,stringify:stringify}},4031:(e,t,i)=>{const{Transform:n}=i(7075);const{isASCIINumber:r,isValidLastEventId:s}=i(4811);const o=[239,187,191];const a=10;const l=13;const u=58;const c=32;class EventSourceStream extends n{state=null;checkBOM=true;crlfCheck=false;eventEndCheck=false;buffer=null;pos=0;event={data:undefined,event:undefined,id:undefined,retry:undefined};constructor(e={}){e.readableObjectMode=true;super(e);this.state=e.eventSourceSettings||{};if(e.push){this.push=e.push}}_transform(e,t,i){if(e.length===0){i();return}if(this.buffer){this.buffer=Buffer.concat([this.buffer,e])}else{this.buffer=e}if(this.checkBOM){switch(this.buffer.length){case 1:if(this.buffer[0]===o[0]){i();return}this.checkBOM=false;i();return;case 2:if(this.buffer[0]===o[0]&&this.buffer[1]===o[1]){i();return}this.checkBOM=false;break;case 3:if(this.buffer[0]===o[0]&&this.buffer[1]===o[1]&&this.buffer[2]===o[2]){this.buffer=Buffer.alloc(0);this.checkBOM=false;i();return}this.checkBOM=false;break;default:if(this.buffer[0]===o[0]&&this.buffer[1]===o[1]&&this.buffer[2]===o[2]){this.buffer=this.buffer.subarray(3)}this.checkBOM=false;break}}while(this.pos0){t[n]=o}break}}processEvent(e){if(e.retry&&r(e.retry)){this.state.reconnectionTime=parseInt(e.retry,10)}if(e.id&&s(e.id)){this.state.lastEventId=e.id}if(e.data!==undefined){this.push({type:e.event||"message",options:{data:e.data,lastEventId:this.state.lastEventId,origin:this.state.origin}})}}clearEvent(){this.event={data:undefined,event:undefined,id:undefined,retry:undefined}}}e.exports={EventSourceStream:EventSourceStream}},1238:(e,t,i)=>{const{pipeline:n}=i(7075);const{fetching:r}=i(4398);const{makeRequest:s}=i(9967);const{webidl:o}=i(5893);const{EventSourceStream:a}=i(4031);const{parseMIMEType:l}=i(1900);const{createFastMessageEvent:u}=i(5188);const{isNetworkError:c}=i(9051);const{delay:d}=i(4811);const{kEnumerableProperty:p}=i(3440);const{environmentSettingsObject:A}=i(3168);let f=false;const h=3e3;const g=0;const y=1;const m=2;const I="anonymous";const v="use-credentials";class EventSource extends EventTarget{#F={open:null,error:null,message:null};#O=null;#N=false;#q=g;#_=null;#M=null;#e;#v;constructor(e,t={}){super();o.util.markAsUncloneable(this);const i="EventSource constructor";o.argumentLengthCheck(arguments,1,i);if(!f){f=true;process.emitWarning("EventSource is experimental, expect them to change at any time.",{code:"UNDICI-ES"})}e=o.converters.USVString(e,i,"url");t=o.converters.EventSourceInitDict(t,i,"eventSourceInitDict");this.#e=t.dispatcher;this.#v={lastEventId:"",reconnectionTime:h};const n=A;let r;try{r=new URL(e,n.settingsObject.baseUrl);this.#v.origin=r.origin}catch(e){throw new DOMException(e,"SyntaxError")}this.#O=r.href;let a=I;if(t.withCredentials){a=v;this.#N=true}const l={redirect:"follow",keepalive:true,mode:"cors",credentials:a==="anonymous"?"same-origin":"omit",referrer:"no-referrer"};l.client=A.settingsObject;l.headersList=[["accept",{name:"accept",value:"text/event-stream"}]];l.cache="no-store";l.initiator="other";l.urlList=[new URL(this.#O)];this.#_=s(l);this.#L()}get readyState(){return this.#q}get url(){return this.#O}get withCredentials(){return this.#N}#L(){if(this.#q===m)return;this.#q=g;const e={request:this.#_,dispatcher:this.#e};const processEventSourceEndOfBody=e=>{if(c(e)){this.dispatchEvent(new Event("error"));this.close()}this.#G()};e.processResponseEndOfBody=processEventSourceEndOfBody;e.processResponse=e=>{if(c(e)){if(e.aborted){this.close();this.dispatchEvent(new Event("error"));return}else{this.#G();return}}const t=e.headersList.get("content-type",true);const i=t!==null?l(t):"failure";const r=i!=="failure"&&i.essence==="text/event-stream";if(e.status!==200||r===false){this.close();this.dispatchEvent(new Event("error"));return}this.#q=y;this.dispatchEvent(new Event("open"));this.#v.origin=e.urlList[e.urlList.length-1].origin;const s=new a({eventSourceSettings:this.#v,push:e=>{this.dispatchEvent(u(e.type,e.options))}});n(e.body.stream,s,(e=>{if(e?.aborted===false){this.close();this.dispatchEvent(new Event("error"))}}))};this.#M=r(e)}async#G(){if(this.#q===m)return;this.#q=g;this.dispatchEvent(new Event("error"));await d(this.#v.reconnectionTime);if(this.#q!==g)return;if(this.#v.lastEventId.length){this.#_.headersList.set("last-event-id",this.#v.lastEventId,true)}this.#L()}close(){o.brandCheck(this,EventSource);if(this.#q===m)return;this.#q=m;this.#M.abort();this.#_=null}get onopen(){return this.#F.open}set onopen(e){if(this.#F.open){this.removeEventListener("open",this.#F.open)}if(typeof e==="function"){this.#F.open=e;this.addEventListener("open",e)}else{this.#F.open=null}}get onmessage(){return this.#F.message}set onmessage(e){if(this.#F.message){this.removeEventListener("message",this.#F.message)}if(typeof e==="function"){this.#F.message=e;this.addEventListener("message",e)}else{this.#F.message=null}}get onerror(){return this.#F.error}set onerror(e){if(this.#F.error){this.removeEventListener("error",this.#F.error)}if(typeof e==="function"){this.#F.error=e;this.addEventListener("error",e)}else{this.#F.error=null}}}const E={CONNECTING:{__proto__:null,configurable:false,enumerable:true,value:g,writable:false},OPEN:{__proto__:null,configurable:false,enumerable:true,value:y,writable:false},CLOSED:{__proto__:null,configurable:false,enumerable:true,value:m,writable:false}};Object.defineProperties(EventSource,E);Object.defineProperties(EventSource.prototype,E);Object.defineProperties(EventSource.prototype,{close:p,onerror:p,onmessage:p,onopen:p,readyState:p,url:p,withCredentials:p});o.converters.EventSourceInitDict=o.dictionaryConverter([{key:"withCredentials",converter:o.converters.boolean,defaultValue:()=>false},{key:"dispatcher",converter:o.converters.any}]);e.exports={EventSource:EventSource,defaultReconnectionTime:h}},4811:e=>{function isValidLastEventId(e){return e.indexOf("\0")===-1}function isASCIINumber(e){if(e.length===0)return false;for(let t=0;t57)return false}return true}function delay(e){return new Promise((t=>{setTimeout(t,e).unref()}))}e.exports={isValidLastEventId:isValidLastEventId,isASCIINumber:isASCIINumber,delay:delay}},4492:(e,t,i)=>{const n=i(3440);const{ReadableStreamFrom:r,isBlobLike:s,isReadableStreamLike:o,readableStreamClose:a,createDeferredPromise:l,fullyReadBody:u,extractMimeType:c,utf8DecodeBytes:d}=i(3168);const{FormData:p}=i(5910);const{kState:A}=i(3627);const{webidl:f}=i(5893);const{Blob:h}=i(4573);const g=i(4589);const{isErrored:y,isDisturbed:m}=i(7075);const{isArrayBuffer:I}=i(3429);const{serializeAMimeType:v}=i(1900);const{multipartFormDataParser:E}=i(116);let C;try{const e=i(7598);C=t=>e.randomInt(0,t)}catch{C=e=>Math.floor(Math.random(e))}const T=new TextEncoder;function noop(){}const R=globalThis.FinalizationRegistry&&process.version.indexOf("v18")!==0;let b;if(R){b=new FinalizationRegistry((e=>{const t=e.deref();if(t&&!t.locked&&!m(t)&&!y(t)){t.cancel("Response object has been garbage collected").catch(noop)}}))}function extractBody(e,t=false){let i=null;if(e instanceof ReadableStream){i=e}else if(s(e)){i=e.stream()}else{i=new ReadableStream({async pull(e){const t=typeof u==="string"?T.encode(u):u;if(t.byteLength){e.enqueue(t)}queueMicrotask((()=>a(e)))},start(){},type:"bytes"})}g(o(i));let l=null;let u=null;let c=null;let d=null;if(typeof e==="string"){u=e;d="text/plain;charset=UTF-8"}else if(e instanceof URLSearchParams){u=e.toString();d="application/x-www-form-urlencoded;charset=UTF-8"}else if(I(e)){u=new Uint8Array(e.slice())}else if(ArrayBuffer.isView(e)){u=new Uint8Array(e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength))}else if(n.isFormDataLike(e)){const t=`----formdata-undici-0${`${C(1e11)}`.padStart(11,"0")}`;const i=`--${t}\r\nContent-Disposition: form-data` /*! formdata-polyfill. MIT License. Jimmy Wärting */;const escape=e=>e.replace(/\n/g,"%0A").replace(/\r/g,"%0D").replace(/"/g,"%22");const normalizeLinefeeds=e=>e.replace(/\r?\n|\r/g,"\r\n");const n=[];const r=new Uint8Array([13,10]);c=0;let s=false;for(const[t,o]of e){if(typeof o==="string"){const e=T.encode(i+`; name="${escape(normalizeLinefeeds(t))}"`+`\r\n\r\n${normalizeLinefeeds(o)}\r\n`);n.push(e);c+=e.byteLength}else{const e=T.encode(`${i}; name="${escape(normalizeLinefeeds(t))}"`+(o.name?`; filename="${escape(o.name)}"`:"")+"\r\n"+`Content-Type: ${o.type||"application/octet-stream"}\r\n\r\n`);n.push(e,o,r);if(typeof o.size==="number"){c+=e.byteLength+o.size+r.byteLength}else{s=true}}}const o=T.encode(`--${t}--\r\n`);n.push(o);c+=o.byteLength;if(s){c=null}u=e;l=async function*(){for(const e of n){if(e.stream){yield*e.stream()}else{yield e}}};d=`multipart/form-data; boundary=${t}`}else if(s(e)){u=e;c=e.size;if(e.type){d=e.type}}else if(typeof e[Symbol.asyncIterator]==="function"){if(t){throw new TypeError("keepalive")}if(n.isDisturbed(e)||e.locked){throw new TypeError("Response body object should not be disturbed or locked")}i=e instanceof ReadableStream?e:r(e)}if(typeof u==="string"||n.isBuffer(u)){c=Buffer.byteLength(u)}if(l!=null){let t;i=new ReadableStream({async start(){t=l(e)[Symbol.asyncIterator]()},async pull(e){const{value:n,done:r}=await t.next();if(r){queueMicrotask((()=>{e.close();e.byobRequest?.respond(0)}))}else{if(!y(i)){const t=new Uint8Array(n);if(t.byteLength){e.enqueue(t)}}}return e.desiredSize>0},async cancel(e){await t.return()},type:"bytes"})}const p={stream:i,source:u,length:c};return[p,d]}function safelyExtractBody(e,t=false){if(e instanceof ReadableStream){g(!n.isDisturbed(e),"The body has already been consumed.");g(!e.locked,"The stream is locked.")}return extractBody(e,t)}function cloneBody(e,t){const[i,n]=t.stream.tee();t.stream=i;return{stream:n,length:t.length,source:t.source}}function throwIfAborted(e){if(e.aborted){throw new DOMException("The operation was aborted.","AbortError")}}function bodyMixinMethods(e){const t={blob(){return consumeBody(this,(e=>{let t=bodyMimeType(this);if(t===null){t=""}else if(t){t=v(t)}return new h([e],{type:t})}),e)},arrayBuffer(){return consumeBody(this,(e=>new Uint8Array(e).buffer),e)},text(){return consumeBody(this,d,e)},json(){return consumeBody(this,parseJSONFromBytes,e)},formData(){return consumeBody(this,(e=>{const t=bodyMimeType(this);if(t!==null){switch(t.essence){case"multipart/form-data":{const i=E(e,t);if(i==="failure"){throw new TypeError("Failed to parse body as FormData.")}const n=new p;n[A]=i;return n}case"application/x-www-form-urlencoded":{const t=new URLSearchParams(e.toString());const i=new p;for(const[e,n]of t){i.append(e,n)}return i}}}throw new TypeError('Content-Type was not one of "multipart/form-data" or "application/x-www-form-urlencoded".')}),e)},bytes(){return consumeBody(this,(e=>new Uint8Array(e)),e)}};return t}function mixinBody(e){Object.assign(e.prototype,bodyMixinMethods(e))}async function consumeBody(e,t,i){f.brandCheck(e,i);if(bodyUnusable(e)){throw new TypeError("Body is unusable: Body has already been read")}throwIfAborted(e[A]);const n=l();const errorSteps=e=>n.reject(e);const successSteps=e=>{try{n.resolve(t(e))}catch(e){errorSteps(e)}};if(e[A].body==null){successSteps(Buffer.allocUnsafe(0));return n.promise}await u(e[A].body,successSteps,errorSteps);return n.promise}function bodyUnusable(e){const t=e[A].body;return t!=null&&(t.stream.locked||n.isDisturbed(t.stream))}function parseJSONFromBytes(e){return JSON.parse(d(e))}function bodyMimeType(e){const t=e[A].headersList;const i=c(t);if(i==="failure"){return null}return i}e.exports={extractBody:extractBody,safelyExtractBody:safelyExtractBody,cloneBody:cloneBody,mixinBody:mixinBody,streamRegistry:b,hasFinalizationRegistry:R,bodyUnusable:bodyUnusable}},4495:e=>{const t=["GET","HEAD","POST"];const i=new Set(t);const n=[101,204,205,304];const r=[301,302,303,307,308];const s=new Set(r);const o=["1","7","9","11","13","15","17","19","20","21","22","23","25","37","42","43","53","69","77","79","87","95","101","102","103","104","109","110","111","113","115","117","119","123","135","137","139","143","161","179","389","427","465","512","513","514","515","526","530","531","532","540","548","554","556","563","587","601","636","989","990","993","995","1719","1720","1723","2049","3659","4045","4190","5060","5061","6000","6566","6665","6666","6667","6668","6669","6679","6697","10080"];const a=new Set(o);const l=["","no-referrer","no-referrer-when-downgrade","same-origin","origin","strict-origin","origin-when-cross-origin","strict-origin-when-cross-origin","unsafe-url"];const u=new Set(l);const c=["follow","manual","error"];const d=["GET","HEAD","OPTIONS","TRACE"];const p=new Set(d);const A=["navigate","same-origin","no-cors","cors"];const f=["omit","same-origin","include"];const h=["default","no-store","reload","no-cache","force-cache","only-if-cached"];const g=["content-encoding","content-language","content-location","content-type","content-length"];const y=["half"];const m=["CONNECT","TRACE","TRACK"];const I=new Set(m);const v=["audio","audioworklet","font","image","manifest","paintworklet","script","style","track","video","xslt",""];const E=new Set(v);e.exports={subresource:v,forbiddenMethods:m,requestBodyHeader:g,referrerPolicy:l,requestRedirect:c,requestMode:A,requestCredentials:f,requestCache:h,redirectStatus:r,corsSafeListedMethods:t,nullBodyStatus:n,safeMethods:d,badPorts:o,requestDuplex:y,subresourceSet:E,badPortsSet:a,redirectStatusSet:s,corsSafeListedMethodsSet:i,safeMethodsSet:p,forbiddenMethodsSet:I,referrerPolicySet:u}},1900:(e,t,i)=>{const n=i(4589);const r=new TextEncoder;const s=/^[!#$%&'*+\-.^_|~A-Za-z0-9]+$/;const o=/[\u000A\u000D\u0009\u0020]/;const a=/[\u0009\u000A\u000C\u000D\u0020]/g;const l=/^[\u0009\u0020-\u007E\u0080-\u00FF]+$/;function dataURLProcessor(e){n(e.protocol==="data:");let t=URLSerializer(e,true);t=t.slice(5);const i={position:0};let r=collectASequenceOfCodePointsFast(",",t,i);const s=r.length;r=removeASCIIWhitespace(r,true,true);if(i.position>=t.length){return"failure"}i.position++;const o=t.slice(s+1);let a=stringPercentDecode(o);if(/;(\u0020){0,}base64$/i.test(r)){const e=isomorphicDecode(a);a=forgivingBase64(e);if(a==="failure"){return"failure"}r=r.slice(0,-6);r=r.replace(/(\u0020)+$/,"");r=r.slice(0,-1)}if(r.startsWith(";")){r="text/plain"+r}let l=parseMIMEType(r);if(l==="failure"){l=parseMIMEType("text/plain;charset=US-ASCII")}return{mimeType:l,body:a}}function URLSerializer(e,t=false){if(!t){return e.href}const i=e.href;const n=e.hash.length;const r=n===0?i:i.substring(0,i.length-n);if(!n&&i.endsWith("#")){return r.slice(0,-1)}return r}function collectASequenceOfCodePoints(e,t,i){let n="";while(i.position=48&&e<=57||e>=65&&e<=70||e>=97&&e<=102}function hexByteToNumber(e){return e>=48&&e<=57?e-48:(e&223)-55}function percentDecode(e){const t=e.length;const i=new Uint8Array(t);let n=0;for(let r=0;re.length){return"failure"}t.position++;let n=collectASequenceOfCodePointsFast(";",e,t);n=removeHTTPWhitespace(n,false,true);if(n.length===0||!s.test(n)){return"failure"}const r=i.toLowerCase();const a=n.toLowerCase();const u={type:r,subtype:a,parameters:new Map,essence:`${r}/${a}`};while(t.positiono.test(e)),e,t);let i=collectASequenceOfCodePoints((e=>e!==";"&&e!=="="),e,t);i=i.toLowerCase();if(t.positione.length){break}let n=null;if(e[t.position]==='"'){n=collectAnHTTPQuotedString(e,t,true);collectASequenceOfCodePointsFast(";",e,t)}else{n=collectASequenceOfCodePointsFast(";",e,t);n=removeHTTPWhitespace(n,false,true);if(n.length===0){continue}}if(i.length!==0&&s.test(i)&&(n.length===0||l.test(n))&&!u.parameters.has(i)){u.parameters.set(i,n)}}return u}function forgivingBase64(e){e=e.replace(a,"");let t=e.length;if(t%4===0){if(e.charCodeAt(t-1)===61){--t;if(e.charCodeAt(t-1)===61){--t}}}if(t%4===1){return"failure"}if(/[^+/0-9A-Za-z]/.test(e.length===t?e:e.substring(0,t))){return"failure"}const i=Buffer.from(e,"base64");return new Uint8Array(i.buffer,i.byteOffset,i.byteLength)}function collectAnHTTPQuotedString(e,t,i){const r=t.position;let s="";n(e[t.position]==='"');t.position++;while(true){s+=collectASequenceOfCodePoints((e=>e!=='"'&&e!=="\\"),e,t);if(t.position>=e.length){break}const i=e[t.position];t.position++;if(i==="\\"){if(t.position>=e.length){s+="\\";break}s+=e[t.position];t.position++}else{n(i==='"');break}}if(i){return s}return e.slice(r,t.position)}function serializeAMimeType(e){n(e!=="failure");const{parameters:t,essence:i}=e;let r=i;for(let[e,i]of t.entries()){r+=";";r+=e;r+="=";if(!s.test(i)){i=i.replace(/(\\|")/g,"\\$1");i='"'+i;i+='"'}r+=i}return r}function isHTTPWhiteSpace(e){return e===13||e===10||e===9||e===32}function removeHTTPWhitespace(e,t=true,i=true){return removeChars(e,t,i,isHTTPWhiteSpace)}function isASCIIWhitespace(e){return e===13||e===10||e===9||e===12||e===32}function removeASCIIWhitespace(e,t=true,i=true){return removeChars(e,t,i,isASCIIWhitespace)}function removeChars(e,t,i,n){let r=0;let s=e.length-1;if(t){while(r0&&n(e.charCodeAt(s)))s--}return r===0&&s===e.length-1?e:e.slice(r,s+1)}function isomorphicDecode(e){const t=e.length;if((2<<15)-1>t){return String.fromCharCode.apply(null,e)}let i="";let n=0;let r=(2<<15)-1;while(nt){r=t-n}i+=String.fromCharCode.apply(null,e.subarray(n,n+=r))}return i}function minimizeSupportedMimeType(e){switch(e.essence){case"application/ecmascript":case"application/javascript":case"application/x-ecmascript":case"application/x-javascript":case"text/ecmascript":case"text/javascript":case"text/javascript1.0":case"text/javascript1.1":case"text/javascript1.2":case"text/javascript1.3":case"text/javascript1.4":case"text/javascript1.5":case"text/jscript":case"text/livescript":case"text/x-ecmascript":case"text/x-javascript":return"text/javascript";case"application/json":case"text/json":return"application/json";case"image/svg+xml":return"image/svg+xml";case"text/xml":case"application/xml":return"application/xml"}if(e.subtype.endsWith("+json")){return"application/json"}if(e.subtype.endsWith("+xml")){return"application/xml"}return""}e.exports={dataURLProcessor:dataURLProcessor,URLSerializer:URLSerializer,collectASequenceOfCodePoints:collectASequenceOfCodePoints,collectASequenceOfCodePointsFast:collectASequenceOfCodePointsFast,stringPercentDecode:stringPercentDecode,parseMIMEType:parseMIMEType,collectAnHTTPQuotedString:collectAnHTTPQuotedString,serializeAMimeType:serializeAMimeType,removeChars:removeChars,removeHTTPWhitespace:removeHTTPWhitespace,minimizeSupportedMimeType:minimizeSupportedMimeType,HTTP_TOKEN_CODEPOINTS:s,isomorphicDecode:isomorphicDecode}},6653:(e,t,i)=>{const{kConnected:n,kSize:r}=i(6443);class CompatWeakRef{constructor(e){this.value=e}deref(){return this.value[n]===0&&this.value[r]===0?undefined:this.value}}class CompatFinalizer{constructor(e){this.finalizer=e}register(e,t){if(e.on){e.on("disconnect",(()=>{if(e[n]===0&&e[r]===0){this.finalizer(t)}}))}}unregister(e){}}e.exports=function(){if(process.env.NODE_V8_COVERAGE&&process.version.startsWith("v18")){process._rawDebug("Using compatibility WeakRef and FinalizationRegistry");return{WeakRef:CompatWeakRef,FinalizationRegistry:CompatFinalizer}}return{WeakRef:WeakRef,FinalizationRegistry:FinalizationRegistry}}},7114:(e,t,i)=>{const{Blob:n,File:r}=i(4573);const{kState:s}=i(3627);const{webidl:o}=i(5893);class FileLike{constructor(e,t,i={}){const n=t;const r=i.type;const o=i.lastModified??Date.now();this[s]={blobLike:e,name:n,type:r,lastModified:o}}stream(...e){o.brandCheck(this,FileLike);return this[s].blobLike.stream(...e)}arrayBuffer(...e){o.brandCheck(this,FileLike);return this[s].blobLike.arrayBuffer(...e)}slice(...e){o.brandCheck(this,FileLike);return this[s].blobLike.slice(...e)}text(...e){o.brandCheck(this,FileLike);return this[s].blobLike.text(...e)}get size(){o.brandCheck(this,FileLike);return this[s].blobLike.size}get type(){o.brandCheck(this,FileLike);return this[s].blobLike.type}get name(){o.brandCheck(this,FileLike);return this[s].name}get lastModified(){o.brandCheck(this,FileLike);return this[s].lastModified}get[Symbol.toStringTag](){return"File"}}o.converters.Blob=o.interfaceConverter(n);function isFileLike(e){return e instanceof r||e&&(typeof e.stream==="function"||typeof e.arrayBuffer==="function")&&e[Symbol.toStringTag]==="File"}e.exports={FileLike:FileLike,isFileLike:isFileLike}},116:(e,t,i)=>{const{isUSVString:n,bufferToLowerCasedHeaderName:r}=i(3440);const{utf8DecodeBytes:s}=i(3168);const{HTTP_TOKEN_CODEPOINTS:o,isomorphicDecode:a}=i(1900);const{isFileLike:l}=i(7114);const{makeEntry:u}=i(5910);const c=i(4589);const{File:d}=i(4573);const p=globalThis.File??d;const A=Buffer.from('form-data; name="');const f=Buffer.from("; filename");const h=Buffer.from("--");const g=Buffer.from("--\r\n");function isAsciiString(e){for(let t=0;t70){return false}for(let i=0;i=48&&t<=57||t>=65&&t<=90||t>=97&&t<=122||t===39||t===45||t===95)){return false}}return true}function multipartFormDataParser(e,t){c(t!=="failure"&&t.essence==="multipart/form-data");const i=t.parameters.get("boundary");if(i===undefined){return"failure"}const r=Buffer.from(`--${i}`,"utf8");const o=[];const a={position:0};while(e[a.position]===13&&e[a.position+1]===10){a.position+=2}let d=e.length;while(e[d-1]===10&&e[d-2]===13){d-=2}if(d!==e.length){e=e.subarray(0,d)}while(true){if(e.subarray(a.position,a.position+r.length).equals(r)){a.position+=r.length}else{return"failure"}if(a.position===e.length-2&&bufferStartsWith(e,h,a)||a.position===e.length-4&&bufferStartsWith(e,g,a)){return o}if(e[a.position]!==13||e[a.position+1]!==10){return"failure"}a.position+=2;const t=parseMultipartFormDataHeaders(e,a);if(t==="failure"){return"failure"}let{name:i,filename:d,contentType:A,encoding:f}=t;a.position+=2;let y;{const t=e.indexOf(r.subarray(2),a.position);if(t===-1){return"failure"}y=e.subarray(a.position,t-4);a.position+=y.length;if(f==="base64"){y=Buffer.from(y.toString(),"base64")}}if(e[a.position]!==13||e[a.position+1]!==10){return"failure"}else{a.position+=2}let m;if(d!==null){A??="text/plain";if(!isAsciiString(A)){A=""}m=new p([y],d,{type:A})}else{m=s(Buffer.from(y))}c(n(i));c(typeof m==="string"&&n(m)||l(m));o.push(u(i,m,d))}}function parseMultipartFormDataHeaders(e,t){let i=null;let n=null;let s=null;let l=null;while(true){if(e[t.position]===13&&e[t.position+1]===10){if(i===null){return"failure"}return{name:i,filename:n,contentType:s,encoding:l}}let u=collectASequenceOfBytes((e=>e!==10&&e!==13&&e!==58),e,t);u=removeChars(u,true,true,(e=>e===9||e===32));if(!o.test(u.toString())){return"failure"}if(e[t.position]!==58){return"failure"}t.position++;collectASequenceOfBytes((e=>e===32||e===9),e,t);switch(r(u)){case"content-disposition":{i=n=null;if(!bufferStartsWith(e,A,t)){return"failure"}t.position+=17;i=parseMultipartFormDataName(e,t);if(i===null){return"failure"}if(bufferStartsWith(e,f,t)){let i=t.position+f.length;if(e[i]===42){t.position+=1;i+=1}if(e[i]!==61||e[i+1]!==34){return"failure"}t.position+=12;n=parseMultipartFormDataName(e,t);if(n===null){return"failure"}}break}case"content-type":{let i=collectASequenceOfBytes((e=>e!==10&&e!==13),e,t);i=removeChars(i,false,true,(e=>e===9||e===32));s=a(i);break}case"content-transfer-encoding":{let i=collectASequenceOfBytes((e=>e!==10&&e!==13),e,t);i=removeChars(i,false,true,(e=>e===9||e===32));l=a(i);break}default:{collectASequenceOfBytes((e=>e!==10&&e!==13),e,t)}}if(e[t.position]!==13&&e[t.position+1]!==10){return"failure"}else{t.position+=2}}}function parseMultipartFormDataName(e,t){c(e[t.position-1]===34);let i=collectASequenceOfBytes((e=>e!==10&&e!==13&&e!==34),e,t);if(e[t.position]!==34){return null}else{t.position++}i=(new TextDecoder).decode(i).replace(/%0A/gi,"\n").replace(/%0D/gi,"\r").replace(/%22/g,'"');return i}function collectASequenceOfBytes(e,t,i){let n=i.position;while(n0&&n(e[s]))s--}return r===0&&s===e.length-1?e:e.subarray(r,s+1)}function bufferStartsWith(e,t,i){if(e.length{const{isBlobLike:n,iteratorMixin:r}=i(3168);const{kState:s}=i(3627);const{kEnumerableProperty:o}=i(3440);const{FileLike:a,isFileLike:l}=i(7114);const{webidl:u}=i(5893);const{File:c}=i(4573);const d=i(7975);const p=globalThis.File??c;class FormData{constructor(e){u.util.markAsUncloneable(this);if(e!==undefined){throw u.errors.conversionFailed({prefix:"FormData constructor",argument:"Argument 1",types:["undefined"]})}this[s]=[]}append(e,t,i=undefined){u.brandCheck(this,FormData);const r="FormData.append";u.argumentLengthCheck(arguments,2,r);if(arguments.length===3&&!n(t)){throw new TypeError("Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'")}e=u.converters.USVString(e,r,"name");t=n(t)?u.converters.Blob(t,r,"value",{strict:false}):u.converters.USVString(t,r,"value");i=arguments.length===3?u.converters.USVString(i,r,"filename"):undefined;const o=makeEntry(e,t,i);this[s].push(o)}delete(e){u.brandCheck(this,FormData);const t="FormData.delete";u.argumentLengthCheck(arguments,1,t);e=u.converters.USVString(e,t,"name");this[s]=this[s].filter((t=>t.name!==e))}get(e){u.brandCheck(this,FormData);const t="FormData.get";u.argumentLengthCheck(arguments,1,t);e=u.converters.USVString(e,t,"name");const i=this[s].findIndex((t=>t.name===e));if(i===-1){return null}return this[s][i].value}getAll(e){u.brandCheck(this,FormData);const t="FormData.getAll";u.argumentLengthCheck(arguments,1,t);e=u.converters.USVString(e,t,"name");return this[s].filter((t=>t.name===e)).map((e=>e.value))}has(e){u.brandCheck(this,FormData);const t="FormData.has";u.argumentLengthCheck(arguments,1,t);e=u.converters.USVString(e,t,"name");return this[s].findIndex((t=>t.name===e))!==-1}set(e,t,i=undefined){u.brandCheck(this,FormData);const r="FormData.set";u.argumentLengthCheck(arguments,2,r);if(arguments.length===3&&!n(t)){throw new TypeError("Failed to execute 'set' on 'FormData': parameter 2 is not of type 'Blob'")}e=u.converters.USVString(e,r,"name");t=n(t)?u.converters.Blob(t,r,"name",{strict:false}):u.converters.USVString(t,r,"name");i=arguments.length===3?u.converters.USVString(i,r,"name"):undefined;const o=makeEntry(e,t,i);const a=this[s].findIndex((t=>t.name===e));if(a!==-1){this[s]=[...this[s].slice(0,a),o,...this[s].slice(a+1).filter((t=>t.name!==e))]}else{this[s].push(o)}}[d.inspect.custom](e,t){const i=this[s].reduce(((e,t)=>{if(e[t.name]){if(Array.isArray(e[t.name])){e[t.name].push(t.value)}else{e[t.name]=[e[t.name],t.value]}}else{e[t.name]=t.value}return e}),{__proto__:null});t.depth??=e;t.colors??=true;const n=d.formatWithOptions(t,i);return`FormData ${n.slice(n.indexOf("]")+2)}`}}r("FormData",FormData,s,"name","value");Object.defineProperties(FormData.prototype,{append:o,delete:o,get:o,getAll:o,has:o,set:o,[Symbol.toStringTag]:{value:"FormData",configurable:true}});function makeEntry(e,t,i){if(typeof t==="string"){}else{if(!l(t)){t=t instanceof Blob?new p([t],"blob",{type:t.type}):new a(t,"blob",{type:t.type})}if(i!==undefined){const e={type:t.type,lastModified:t.lastModified};t=t instanceof c?new p([t],i,e):new a(t,i,e)}}return{name:e,value:t}}e.exports={FormData:FormData,makeEntry:makeEntry}},1059:e=>{const t=Symbol.for("undici.globalOrigin.1");function getGlobalOrigin(){return globalThis[t]}function setGlobalOrigin(e){if(e===undefined){Object.defineProperty(globalThis,t,{value:undefined,writable:true,enumerable:false,configurable:false});return}const i=new URL(e);if(i.protocol!=="http:"&&i.protocol!=="https:"){throw new TypeError(`Only http & https urls are allowed, received ${i.protocol}`)}Object.defineProperty(globalThis,t,{value:i,writable:true,enumerable:false,configurable:false})}e.exports={getGlobalOrigin:getGlobalOrigin,setGlobalOrigin:setGlobalOrigin}},660:(e,t,i)=>{const{kConstruct:n}=i(6443);const{kEnumerableProperty:r}=i(3440);const{iteratorMixin:s,isValidHeaderName:o,isValidHeaderValue:a}=i(3168);const{webidl:l}=i(5893);const u=i(4589);const c=i(7975);const d=Symbol("headers map");const p=Symbol("headers map sorted");function isHTTPWhiteSpaceCharCode(e){return e===10||e===13||e===9||e===32}function headerValueNormalize(e){let t=0;let i=e.length;while(i>t&&isHTTPWhiteSpaceCharCode(e.charCodeAt(i-1)))--i;while(i>t&&isHTTPWhiteSpaceCharCode(e.charCodeAt(t)))++t;return t===0&&i===e.length?e:e.substring(t,i)}function fill(e,t){if(Array.isArray(t)){for(let i=0;i>","record"]})}}function appendHeader(e,t,i){i=headerValueNormalize(i);if(!o(t)){throw l.errors.invalidArgument({prefix:"Headers.append",value:t,type:"header name"})}else if(!a(i)){throw l.errors.invalidArgument({prefix:"Headers.append",value:i,type:"header value"})}if(A(e)==="immutable"){throw new TypeError("immutable")}return h(e).append(t,i,false)}function compareHeaderName(e,t){return e[0]>1);if(t[a][0]<=l[0]){o=a+1}else{s=a}}if(n!==a){r=n;while(r>o){t[r]=t[--r]}t[o]=l}}if(!i.next().done){throw new TypeError("Unreachable")}return t}else{let e=0;for(const{0:i,1:{value:n}}of this[d]){t[e++]=[i,n];u(n!==null)}return t.sort(compareHeaderName)}}}class Headers{#j;#x;constructor(e=undefined){l.util.markAsUncloneable(this);if(e===n){return}this.#x=new HeadersList;this.#j="none";if(e!==undefined){e=l.converters.HeadersInit(e,"Headers contructor","init");fill(this,e)}}append(e,t){l.brandCheck(this,Headers);l.argumentLengthCheck(arguments,2,"Headers.append");const i="Headers.append";e=l.converters.ByteString(e,i,"name");t=l.converters.ByteString(t,i,"value");return appendHeader(this,e,t)}delete(e){l.brandCheck(this,Headers);l.argumentLengthCheck(arguments,1,"Headers.delete");const t="Headers.delete";e=l.converters.ByteString(e,t,"name");if(!o(e)){throw l.errors.invalidArgument({prefix:"Headers.delete",value:e,type:"header name"})}if(this.#j==="immutable"){throw new TypeError("immutable")}if(!this.#x.contains(e,false)){return}this.#x.delete(e,false)}get(e){l.brandCheck(this,Headers);l.argumentLengthCheck(arguments,1,"Headers.get");const t="Headers.get";e=l.converters.ByteString(e,t,"name");if(!o(e)){throw l.errors.invalidArgument({prefix:t,value:e,type:"header name"})}return this.#x.get(e,false)}has(e){l.brandCheck(this,Headers);l.argumentLengthCheck(arguments,1,"Headers.has");const t="Headers.has";e=l.converters.ByteString(e,t,"name");if(!o(e)){throw l.errors.invalidArgument({prefix:t,value:e,type:"header name"})}return this.#x.contains(e,false)}set(e,t){l.brandCheck(this,Headers);l.argumentLengthCheck(arguments,2,"Headers.set");const i="Headers.set";e=l.converters.ByteString(e,i,"name");t=l.converters.ByteString(t,i,"value");t=headerValueNormalize(t);if(!o(e)){throw l.errors.invalidArgument({prefix:i,value:e,type:"header name"})}else if(!a(t)){throw l.errors.invalidArgument({prefix:i,value:t,type:"header value"})}if(this.#j==="immutable"){throw new TypeError("immutable")}this.#x.set(e,t,false)}getSetCookie(){l.brandCheck(this,Headers);const e=this.#x.cookies;if(e){return[...e]}return[]}get[p](){if(this.#x[p]){return this.#x[p]}const e=[];const t=this.#x.toSortedArray();const i=this.#x.cookies;if(i===null||i.length===1){return this.#x[p]=t}for(let n=0;n>"](e,t,i,n.bind(e))}return l.converters["record"](e,t,i)}throw l.errors.conversionFailed({prefix:"Headers constructor",argument:"Argument 1",types:["sequence>","record"]})};e.exports={fill:fill,compareHeaderName:compareHeaderName,Headers:Headers,HeadersList:HeadersList,getHeadersGuard:A,setHeadersGuard:f,setHeadersList:g,getHeadersList:h}},4398:(e,t,i)=>{const{makeNetworkError:n,makeAppropriateNetworkError:r,filterResponse:s,makeResponse:o,fromInnerResponse:a}=i(9051);const{HeadersList:l}=i(660);const{Request:u,cloneRequest:c}=i(9967);const d=i(8522);const{bytesMatch:p,makePolicyContainer:A,clonePolicyContainer:f,requestBadPort:h,TAOCheck:g,appendRequestOriginHeader:y,responseLocationURL:m,requestCurrentURL:I,setRequestReferrerPolicyOnRedirect:v,tryUpgradeRequestToAPotentiallyTrustworthyURL:E,createOpaqueTimingInfo:C,appendFetchMetadata:T,corsCheck:R,crossOriginResourcePolicyCheck:b,determineRequestsReferrer:w,coarsenedSharedCurrentTime:B,createDeferredPromise:D,isBlobLike:S,sameOrigin:k,isCancelled:P,isAborted:U,isErrorLike:V,fullyReadBody:F,readableStreamClose:O,isomorphicEncode:N,urlIsLocal:q,urlIsHttpHttpsScheme:_,urlHasHttpsScheme:M,clampAndCoarsenConnectionTimingInfo:L,simpleRangeHeaderValue:G,buildContentRange:j,createInflate:x,extractMimeType:H}=i(3168);const{kState:W,kDispatcher:Y}=i(3627);const J=i(4589);const{safelyExtractBody:z,extractBody:$}=i(4492);const{redirectStatusSet:K,nullBodyStatus:Z,safeMethodsSet:X,requestBodyHeader:ee,subresourceSet:te}=i(4495);const ie=i(8474);const{Readable:ne,pipeline:re,finished:se}=i(7075);const{addAbortListener:oe,isErrored:ae,isReadable:le,bufferToLowerCasedHeaderName:ue}=i(3440);const{dataURLProcessor:ce,serializeAMimeType:de,minimizeSupportedMimeType:pe}=i(1900);const{getGlobalDispatcher:Ae}=i(2581);const{webidl:fe}=i(5893);const{STATUS_CODES:he}=i(7067);const ge=["GET","HEAD"];const ye=typeof __UNDICI_IS_NODE__!=="undefined"||typeof esbuildDetection!=="undefined"?"node":"undici";let me;class Fetch extends ie{constructor(e){super();this.dispatcher=e;this.connection=null;this.dump=false;this.state="ongoing"}terminate(e){if(this.state!=="ongoing"){return}this.state="terminated";this.connection?.destroy(e);this.emit("terminated",e)}abort(e){if(this.state!=="ongoing"){return}this.state="aborted";if(!e){e=new DOMException("The operation was aborted.","AbortError")}this.serializedAbortReason=e;this.connection?.destroy(e);this.emit("terminated",e)}}function handleFetchDone(e){finalizeAndReportTiming(e,"fetch")}function fetch(e,t=undefined){fe.argumentLengthCheck(arguments,1,"globalThis.fetch");let i=D();let n;try{n=new u(e,t)}catch(e){i.reject(e);return i.promise}const r=n[W];if(n.signal.aborted){abortFetch(i,r,null,n.signal.reason);return i.promise}const s=r.client.globalObject;if(s?.constructor?.name==="ServiceWorkerGlobalScope"){r.serviceWorkers="none"}let o=null;let l=false;let c=null;oe(n.signal,(()=>{l=true;J(c!=null);c.abort(n.signal.reason);const e=o?.deref();abortFetch(i,r,e,n.signal.reason)}));const processResponse=e=>{if(l){return}if(e.aborted){abortFetch(i,r,o,c.serializedAbortReason);return}if(e.type==="error"){i.reject(new TypeError("fetch failed",{cause:e.error}));return}o=new WeakRef(a(e,"immutable"));i.resolve(o.deref());i=null};c=fetching({request:r,processResponseEndOfBody:handleFetchDone,processResponse:processResponse,dispatcher:n[Y]});return i.promise}function finalizeAndReportTiming(e,t="other"){if(e.type==="error"&&e.aborted){return}if(!e.urlList?.length){return}const i=e.urlList[0];let n=e.timingInfo;let r=e.cacheState;if(!_(i)){return}if(n===null){return}if(!e.timingAllowPassed){n=C({startTime:n.startTime});r=""}n.endTime=B();e.timingInfo=n;Ie(n,i.href,t,globalThis,r)}const Ie=performance.markResourceTiming;function abortFetch(e,t,i,n){if(e){e.reject(n)}if(t.body!=null&&le(t.body?.stream)){t.body.stream.cancel(n).catch((e=>{if(e.code==="ERR_INVALID_STATE"){return}throw e}))}if(i==null){return}const r=i[W];if(r.body!=null&&le(r.body?.stream)){r.body.stream.cancel(n).catch((e=>{if(e.code==="ERR_INVALID_STATE"){return}throw e}))}}function fetching({request:e,processRequestBodyChunkLength:t,processRequestEndOfBody:i,processResponse:n,processResponseEndOfBody:r,processResponseConsumeBody:s,useParallelQueue:o=false,dispatcher:a=Ae()}){J(a);let l=null;let u=false;if(e.client!=null){l=e.client.globalObject;u=e.client.crossOriginIsolatedCapability}const c=B(u);const d=C({startTime:c});const p={controller:new Fetch(a),request:e,timingInfo:d,processRequestBodyChunkLength:t,processRequestEndOfBody:i,processResponse:n,processResponseConsumeBody:s,processResponseEndOfBody:r,taskDestination:l,crossOriginIsolatedCapability:u};J(!e.body||e.body.stream);if(e.window==="client"){e.window=e.client?.globalObject?.constructor?.name==="Window"?e.client:"no-window"}if(e.origin==="client"){e.origin=e.client.origin}if(e.policyContainer==="client"){if(e.client!=null){e.policyContainer=f(e.client.policyContainer)}else{e.policyContainer=A()}}if(!e.headersList.contains("accept",true)){const t="*/*";e.headersList.append("accept",t,true)}if(!e.headersList.contains("accept-language",true)){e.headersList.append("accept-language","*",true)}if(e.priority===null){}if(te.has(e.destination)){}mainFetch(p).catch((e=>{p.controller.terminate(e)}));return p.controller}async function mainFetch(e,t=false){const i=e.request;let r=null;if(i.localURLsOnly&&!q(I(i))){r=n("local URLs only")}E(i);if(h(i)==="blocked"){r=n("bad port")}if(i.referrerPolicy===""){i.referrerPolicy=i.policyContainer.referrerPolicy}if(i.referrer!=="no-referrer"){i.referrer=w(i)}if(r===null){r=await(async()=>{const t=I(i);if(k(t,i.url)&&i.responseTainting==="basic"||t.protocol==="data:"||(i.mode==="navigate"||i.mode==="websocket")){i.responseTainting="basic";return await schemeFetch(e)}if(i.mode==="same-origin"){return n('request mode cannot be "same-origin"')}if(i.mode==="no-cors"){if(i.redirect!=="follow"){return n('redirect mode cannot be "follow" for "no-cors" request')}i.responseTainting="opaque";return await schemeFetch(e)}if(!_(I(i))){return n("URL scheme must be a HTTP(S) scheme")}i.responseTainting="cors";return await httpFetch(e)})()}if(t){return r}if(r.status!==0&&!r.internalResponse){if(i.responseTainting==="cors"){}if(i.responseTainting==="basic"){r=s(r,"basic")}else if(i.responseTainting==="cors"){r=s(r,"cors")}else if(i.responseTainting==="opaque"){r=s(r,"opaque")}else{J(false)}}let o=r.status===0?r:r.internalResponse;if(o.urlList.length===0){o.urlList.push(...i.urlList)}if(!i.timingAllowFailed){r.timingAllowPassed=true}if(r.type==="opaque"&&o.status===206&&o.rangeRequested&&!i.headers.contains("range",true)){r=o=n()}if(r.status!==0&&(i.method==="HEAD"||i.method==="CONNECT"||Z.includes(o.status))){o.body=null;e.controller.dump=true}if(i.integrity){const processBodyError=t=>fetchFinale(e,n(t));if(i.responseTainting==="opaque"||r.body==null){processBodyError(r.error);return}const processBody=t=>{if(!p(t,i.integrity)){processBodyError("integrity mismatch");return}r.body=z(t)[0];fetchFinale(e,r)};await F(r.body,processBody,processBodyError)}else{fetchFinale(e,r)}}function schemeFetch(e){if(P(e)&&e.request.redirectCount===0){return Promise.resolve(r(e))}const{request:t}=e;const{protocol:s}=I(t);switch(s){case"about:":{return Promise.resolve(n("about scheme is not supported"))}case"blob:":{if(!me){me=i(4573).resolveObjectURL}const e=I(t);if(e.search.length!==0){return Promise.resolve(n("NetworkError when attempting to fetch resource."))}const r=me(e.toString());if(t.method!=="GET"||!S(r)){return Promise.resolve(n("invalid method"))}const s=o();const a=r.size;const l=N(`${a}`);const u=r.type;if(!t.headersList.contains("range",true)){const e=$(r);s.statusText="OK";s.body=e[0];s.headersList.set("content-length",l,true);s.headersList.set("content-type",u,true)}else{s.rangeRequested=true;const e=t.headersList.get("range",true);const i=G(e,true);if(i==="failure"){return Promise.resolve(n("failed to fetch the data URL"))}let{rangeStartValue:o,rangeEndValue:l}=i;if(o===null){o=a-l;l=o+l-1}else{if(o>=a){return Promise.resolve(n("Range start is greater than the blob's size."))}if(l===null||l>=a){l=a-1}}const c=r.slice(o,l,u);const d=$(c);s.body=d[0];const p=N(`${c.size}`);const A=j(o,l,a);s.status=206;s.statusText="Partial Content";s.headersList.set("content-length",p,true);s.headersList.set("content-type",u,true);s.headersList.set("content-range",A,true)}return Promise.resolve(s)}case"data:":{const e=I(t);const i=ce(e);if(i==="failure"){return Promise.resolve(n("failed to fetch the data URL"))}const r=de(i.mimeType);return Promise.resolve(o({statusText:"OK",headersList:[["content-type",{name:"Content-Type",value:r}]],body:z(i.body)[0]}))}case"file:":{return Promise.resolve(n("not implemented... yet..."))}case"http:":case"https:":{return httpFetch(e).catch((e=>n(e)))}default:{return Promise.resolve(n("unknown scheme"))}}}function finalizeResponse(e,t){e.request.done=true;if(e.processResponseDone!=null){queueMicrotask((()=>e.processResponseDone(t)))}}function fetchFinale(e,t){let i=e.timingInfo;const processResponseEndOfBody=()=>{const n=Date.now();if(e.request.destination==="document"){e.controller.fullTimingInfo=i}e.controller.reportTimingSteps=()=>{if(e.request.url.protocol!=="https:"){return}i.endTime=n;let r=t.cacheState;const s=t.bodyInfo;if(!t.timingAllowPassed){i=C(i);r=""}let o=0;if(e.request.mode!=="navigator"||!t.hasCrossOriginRedirects){o=t.status;const e=H(t.headersList);if(e!=="failure"){s.contentType=pe(e)}}if(e.request.initiatorType!=null){Ie(i,e.request.url.href,e.request.initiatorType,globalThis,r,s,o)}};const processResponseEndOfBodyTask=()=>{e.request.done=true;if(e.processResponseEndOfBody!=null){queueMicrotask((()=>e.processResponseEndOfBody(t)))}if(e.request.initiatorType!=null){e.controller.reportTimingSteps()}};queueMicrotask((()=>processResponseEndOfBodyTask()))};if(e.processResponse!=null){queueMicrotask((()=>{e.processResponse(t);e.processResponse=null}))}const n=t.type==="error"?t:t.internalResponse??t;if(n.body==null){processResponseEndOfBody()}else{se(n.body.stream,(()=>{processResponseEndOfBody()}))}}async function httpFetch(e){const t=e.request;let i=null;let r=null;const s=e.timingInfo;if(t.serviceWorkers==="all"){}if(i===null){if(t.redirect==="follow"){t.serviceWorkers="none"}r=i=await httpNetworkOrCacheFetch(e);if(t.responseTainting==="cors"&&R(t,i)==="failure"){return n("cors failure")}if(g(t,i)==="failure"){t.timingAllowFailed=true}}if((t.responseTainting==="opaque"||i.type==="opaque")&&b(t.origin,t.client,t.destination,r)==="blocked"){return n("blocked")}if(K.has(r.status)){if(t.redirect!=="manual"){e.controller.connection.destroy(undefined,false)}if(t.redirect==="error"){i=n("unexpected redirect")}else if(t.redirect==="manual"){i=r}else if(t.redirect==="follow"){i=await httpRedirectFetch(e,i)}else{J(false)}}i.timingInfo=s;return i}function httpRedirectFetch(e,t){const i=e.request;const r=t.internalResponse?t.internalResponse:t;let s;try{s=m(r,I(i).hash);if(s==null){return t}}catch(e){return Promise.resolve(n(e))}if(!_(s)){return Promise.resolve(n("URL scheme must be a HTTP(S) scheme"))}if(i.redirectCount===20){return Promise.resolve(n("redirect count exceeded"))}i.redirectCount+=1;if(i.mode==="cors"&&(s.username||s.password)&&!k(i,s)){return Promise.resolve(n('cross origin not allowed for request mode "cors"'))}if(i.responseTainting==="cors"&&(s.username||s.password)){return Promise.resolve(n('URL cannot contain credentials for request mode "cors"'))}if(r.status!==303&&i.body!=null&&i.body.source==null){return Promise.resolve(n())}if([301,302].includes(r.status)&&i.method==="POST"||r.status===303&&!ge.includes(i.method)){i.method="GET";i.body=null;for(const e of ee){i.headersList.delete(e)}}if(!k(I(i),s)){i.headersList.delete("authorization",true);i.headersList.delete("proxy-authorization",true);i.headersList.delete("cookie",true);i.headersList.delete("host",true)}if(i.body!=null){J(i.body.source!=null);i.body=z(i.body.source)[0]}const o=e.timingInfo;o.redirectEndTime=o.postRedirectStartTime=B(e.crossOriginIsolatedCapability);if(o.redirectStartTime===0){o.redirectStartTime=o.startTime}i.urlList.push(s);v(i,r);return mainFetch(e,true)}async function httpNetworkOrCacheFetch(e,t=false,i=false){const s=e.request;let o=null;let a=null;let l=null;const u=null;const d=false;if(s.window==="no-window"&&s.redirect==="error"){o=e;a=s}else{a=c(s);o={...e};o.request=a}const p=s.credentials==="include"||s.credentials==="same-origin"&&s.responseTainting==="basic";const A=a.body?a.body.length:null;let f=null;if(a.body==null&&["POST","PUT"].includes(a.method)){f="0"}if(A!=null){f=N(`${A}`)}if(f!=null){a.headersList.append("content-length",f,true)}if(A!=null&&a.keepalive){}if(a.referrer instanceof URL){a.headersList.append("referer",N(a.referrer.href),true)}y(a);T(a);if(!a.headersList.contains("user-agent",true)){a.headersList.append("user-agent",ye)}if(a.cache==="default"&&(a.headersList.contains("if-modified-since",true)||a.headersList.contains("if-none-match",true)||a.headersList.contains("if-unmodified-since",true)||a.headersList.contains("if-match",true)||a.headersList.contains("if-range",true))){a.cache="no-store"}if(a.cache==="no-cache"&&!a.preventNoCacheCacheControlHeaderModification&&!a.headersList.contains("cache-control",true)){a.headersList.append("cache-control","max-age=0",true)}if(a.cache==="no-store"||a.cache==="reload"){if(!a.headersList.contains("pragma",true)){a.headersList.append("pragma","no-cache",true)}if(!a.headersList.contains("cache-control",true)){a.headersList.append("cache-control","no-cache",true)}}if(a.headersList.contains("range",true)){a.headersList.append("accept-encoding","identity",true)}if(!a.headersList.contains("accept-encoding",true)){if(M(I(a))){a.headersList.append("accept-encoding","br, gzip, deflate",true)}else{a.headersList.append("accept-encoding","gzip, deflate",true)}}a.headersList.delete("host",true);if(p){}if(u==null){a.cache="no-store"}if(a.cache!=="no-store"&&a.cache!=="reload"){}if(l==null){if(a.cache==="only-if-cached"){return n("only if cached")}const e=await httpNetworkFetch(o,p,i);if(!X.has(a.method)&&e.status>=200&&e.status<=399){}if(d&&e.status===304){}if(l==null){l=e}}l.urlList=[...a.urlList];if(a.headersList.contains("range",true)){l.rangeRequested=true}l.requestIncludesCredentials=p;if(l.status===407){if(s.window==="no-window"){return n()}if(P(e)){return r(e)}return n("proxy authentication required")}if(l.status===421&&!i&&(s.body==null||s.body.source!=null)){if(P(e)){return r(e)}e.controller.connection.destroy();l=await httpNetworkOrCacheFetch(e,t,true)}if(t){}return l}async function httpNetworkFetch(e,t=false,i=false){J(!e.controller.connection||e.controller.connection.destroyed);e.controller.connection={abort:null,destroyed:false,destroy(e,t=true){if(!this.destroyed){this.destroyed=true;if(t){this.abort?.(e??new DOMException("The operation was aborted.","AbortError"))}}}};const s=e.request;let a=null;const u=e.timingInfo;const c=null;if(c==null){s.cache="no-store"}const p=i?"yes":"no";if(s.mode==="websocket"){}else{}let A=null;if(s.body==null&&e.processRequestEndOfBody){queueMicrotask((()=>e.processRequestEndOfBody()))}else if(s.body!=null){const processBodyChunk=async function*(t){if(P(e)){return}yield t;e.processRequestBodyChunkLength?.(t.byteLength)};const processEndOfBody=()=>{if(P(e)){return}if(e.processRequestEndOfBody){e.processRequestEndOfBody()}};const processBodyError=t=>{if(P(e)){return}if(t.name==="AbortError"){e.controller.abort()}else{e.controller.terminate(t)}};A=async function*(){try{for await(const e of s.body.stream){yield*processBodyChunk(e)}processEndOfBody()}catch(e){processBodyError(e)}}()}try{const{body:t,status:i,statusText:n,headersList:r,socket:s}=await dispatch({body:A});if(s){a=o({status:i,statusText:n,headersList:r,socket:s})}else{const s=t[Symbol.asyncIterator]();e.controller.next=()=>s.next();a=o({status:i,statusText:n,headersList:r})}}catch(t){if(t.name==="AbortError"){e.controller.connection.destroy();return r(e,t)}return n(t)}const pullAlgorithm=async()=>{await e.controller.resume()};const cancelAlgorithm=t=>{if(!P(e)){e.controller.abort(t)}};const f=new ReadableStream({async start(t){e.controller.controller=t},async pull(e){await pullAlgorithm(e)},async cancel(e){await cancelAlgorithm(e)},type:"bytes"});a.body={stream:f,source:null,length:null};e.controller.onAborted=onAborted;e.controller.on("terminated",onAborted);e.controller.resume=async()=>{while(true){let t;let i;try{const{done:i,value:n}=await e.controller.next();if(U(e)){break}t=i?undefined:n}catch(n){if(e.controller.ended&&!u.encodedBodySize){t=undefined}else{t=n;i=true}}if(t===undefined){O(e.controller.controller);finalizeResponse(e,a);return}u.decodedBodySize+=t?.byteLength??0;if(i){e.controller.terminate(t);return}const n=new Uint8Array(t);if(n.byteLength){e.controller.controller.enqueue(n)}if(ae(f)){e.controller.terminate();return}if(e.controller.controller.desiredSize<=0){return}}};function onAborted(t){if(U(e)){a.aborted=true;if(le(f)){e.controller.controller.error(e.controller.serializedAbortReason)}}else{if(le(f)){e.controller.controller.error(new TypeError("terminated",{cause:V(t)?t:undefined}))}}e.controller.connection.destroy()}return a;function dispatch({body:t}){const i=I(s);const n=e.controller.dispatcher;return new Promise(((r,o)=>n.dispatch({path:i.pathname+i.search,origin:i.origin,method:s.method,body:n.isMockActive?s.body&&(s.body.source||s.body.stream):t,headers:s.headersList.entries,maxRedirections:0,upgrade:s.mode==="websocket"?"websocket":undefined},{body:null,abort:null,onConnect(t){const{connection:i}=e.controller;u.finalConnectionTimingInfo=L(undefined,u.postRedirectStartTime,e.crossOriginIsolatedCapability);if(i.destroyed){t(new DOMException("The operation was aborted.","AbortError"))}else{e.controller.on("terminated",t);this.abort=i.abort=t}u.finalNetworkRequestStartTime=B(e.crossOriginIsolatedCapability)},onResponseStarted(){u.finalNetworkResponseStartTime=B(e.crossOriginIsolatedCapability)},onHeaders(e,t,i,n){if(e<200){return}let a="";const u=new l;for(let e=0;ei){o(new Error(`too many content-encodings in response: ${t.length}, maximum allowed is ${i}`));return true}for(let e=t.length-1;e>=0;--e){const i=t[e].trim();if(i==="x-gzip"||i==="gzip"){c.push(d.createGunzip({flush:d.constants.Z_SYNC_FLUSH,finishFlush:d.constants.Z_SYNC_FLUSH}))}else if(i==="deflate"){c.push(x({flush:d.constants.Z_SYNC_FLUSH,finishFlush:d.constants.Z_SYNC_FLUSH}))}else if(i==="br"){c.push(d.createBrotliDecompress({flush:d.constants.BROTLI_OPERATION_FLUSH,finishFlush:d.constants.BROTLI_OPERATION_FLUSH}))}else{c.length=0;break}}}const A=this.onError.bind(this);r({status:e,statusText:n,headersList:u,body:c.length?re(this.body,...c,(e=>{if(e){this.onError(e)}})).on("error",A):this.body.on("error",A)});return true},onData(t){if(e.controller.dump){return}const i=t;u.encodedBodySize+=i.byteLength;return this.body.push(i)},onComplete(){if(this.abort){e.controller.off("terminated",this.abort)}if(e.controller.onAborted){e.controller.off("terminated",e.controller.onAborted)}e.controller.ended=true;this.body.push(null)},onError(t){if(this.abort){e.controller.off("terminated",this.abort)}this.body?.destroy(t);e.controller.terminate(t);o(t)},onUpgrade(e,t,i){if(e!==101){return}const n=new l;for(let e=0;e{const{extractBody:n,mixinBody:r,cloneBody:s,bodyUnusable:o}=i(4492);const{Headers:a,fill:l,HeadersList:u,setHeadersGuard:c,getHeadersGuard:d,setHeadersList:p,getHeadersList:A}=i(660);const{FinalizationRegistry:f}=i(6653)();const h=i(3440);const g=i(7975);const{isValidHTTPToken:y,sameOrigin:m,environmentSettingsObject:I}=i(3168);const{forbiddenMethodsSet:v,corsSafeListedMethodsSet:E,referrerPolicy:C,requestRedirect:T,requestMode:R,requestCredentials:b,requestCache:w,requestDuplex:B}=i(4495);const{kEnumerableProperty:D,normalizedMethodRecordsBase:S,normalizedMethodRecords:k}=h;const{kHeaders:P,kSignal:U,kState:V,kDispatcher:F}=i(3627);const{webidl:O}=i(5893);const{URLSerializer:N}=i(1900);const{kConstruct:q}=i(6443);const _=i(4589);const{getMaxListeners:M,setMaxListeners:L,getEventListeners:G,defaultMaxListeners:j}=i(8474);const x=Symbol("abortController");const H=new f((({signal:e,abort:t})=>{e.removeEventListener("abort",t)}));const W=new WeakMap;function buildAbort(e){return abort;function abort(){const t=e.deref();if(t!==undefined){H.unregister(abort);this.removeEventListener("abort",abort);t.abort(this.reason);const e=W.get(t.signal);if(e!==undefined){if(e.size!==0){for(const t of e){const e=t.deref();if(e!==undefined){e.abort(this.reason)}}e.clear()}W.delete(t.signal)}}}}let Y=false;class Request{constructor(e,t={}){O.util.markAsUncloneable(this);if(e===q){return}const i="Request constructor";O.argumentLengthCheck(arguments,1,i);e=O.converters.RequestInfo(e,i,"input");t=O.converters.RequestInit(t,i,"init");let r=null;let s=null;const d=I.settingsObject.baseUrl;let f=null;if(typeof e==="string"){this[F]=t.dispatcher;let i;try{i=new URL(e,d)}catch(t){throw new TypeError("Failed to parse URL from "+e,{cause:t})}if(i.username||i.password){throw new TypeError("Request cannot be constructed from a URL that includes credentials: "+e)}r=makeRequest({urlList:[i]});s="cors"}else{this[F]=t.dispatcher||e[F];_(e instanceof Request);r=e[V];f=e[U]}const g=I.settingsObject.origin;let C="client";if(r.window?.constructor?.name==="EnvironmentSettingsObject"&&m(r.window,g)){C=r.window}if(t.window!=null){throw new TypeError(`'window' option '${C}' must be null`)}if("window"in t){C="no-window"}r=makeRequest({method:r.method,headersList:r.headersList,unsafeRequest:r.unsafeRequest,client:I.settingsObject,window:C,priority:r.priority,origin:r.origin,referrer:r.referrer,referrerPolicy:r.referrerPolicy,mode:r.mode,credentials:r.credentials,cache:r.cache,redirect:r.redirect,integrity:r.integrity,keepalive:r.keepalive,reloadNavigation:r.reloadNavigation,historyNavigation:r.historyNavigation,urlList:[...r.urlList]});const T=Object.keys(t).length!==0;if(T){if(r.mode==="navigate"){r.mode="same-origin"}r.reloadNavigation=false;r.historyNavigation=false;r.origin="client";r.referrer="client";r.referrerPolicy="";r.url=r.urlList[r.urlList.length-1];r.urlList=[r.url]}if(t.referrer!==undefined){const e=t.referrer;if(e===""){r.referrer="no-referrer"}else{let t;try{t=new URL(e,d)}catch(t){throw new TypeError(`Referrer "${e}" is not a valid URL.`,{cause:t})}if(t.protocol==="about:"&&t.hostname==="client"||g&&!m(t,I.settingsObject.baseUrl)){r.referrer="client"}else{r.referrer=t}}}if(t.referrerPolicy!==undefined){r.referrerPolicy=t.referrerPolicy}let R;if(t.mode!==undefined){R=t.mode}else{R=s}if(R==="navigate"){throw O.errors.exception({header:"Request constructor",message:"invalid request mode navigate."})}if(R!=null){r.mode=R}if(t.credentials!==undefined){r.credentials=t.credentials}if(t.cache!==undefined){r.cache=t.cache}if(r.cache==="only-if-cached"&&r.mode!=="same-origin"){throw new TypeError("'only-if-cached' can be set only with 'same-origin' mode")}if(t.redirect!==undefined){r.redirect=t.redirect}if(t.integrity!=null){r.integrity=String(t.integrity)}if(t.keepalive!==undefined){r.keepalive=Boolean(t.keepalive)}if(t.method!==undefined){let e=t.method;const i=k[e];if(i!==undefined){r.method=i}else{if(!y(e)){throw new TypeError(`'${e}' is not a valid HTTP method.`)}const t=e.toUpperCase();if(v.has(t)){throw new TypeError(`'${e}' HTTP method is unsupported.`)}e=S[t]??e;r.method=e}if(!Y&&r.method==="patch"){process.emitWarning("Using `patch` is highly likely to result in a `405 Method Not Allowed`. `PATCH` is much more likely to succeed.",{code:"UNDICI-FETCH-patch"});Y=true}}if(t.signal!==undefined){f=t.signal}this[V]=r;const b=new AbortController;this[U]=b.signal;if(f!=null){if(!f||typeof f.aborted!=="boolean"||typeof f.addEventListener!=="function"){throw new TypeError("Failed to construct 'Request': member signal is not of type AbortSignal.")}if(f.aborted){b.abort(f.reason)}else{this[x]=b;const e=new WeakRef(b);const t=buildAbort(e);try{if(typeof M==="function"&&M(f)===j){L(1500,f)}else if(G(f,"abort").length>=j){L(1500,f)}}catch{}h.addAbortListener(f,t);H.register(b,{signal:f,abort:t},t)}}this[P]=new a(q);p(this[P],r.headersList);c(this[P],"request");if(R==="no-cors"){if(!E.has(r.method)){throw new TypeError(`'${r.method} is unsupported in no-cors mode.`)}c(this[P],"request-no-cors")}if(T){const e=A(this[P]);const i=t.headers!==undefined?t.headers:new u(e);e.clear();if(i instanceof u){for(const{name:t,value:n}of i.rawValues()){e.append(t,n,false)}e.cookies=i.cookies}else{l(this[P],i)}}const w=e instanceof Request?e[V].body:null;if((t.body!=null||w!=null)&&(r.method==="GET"||r.method==="HEAD")){throw new TypeError("Request with GET/HEAD method cannot have body.")}let B=null;if(t.body!=null){const[e,i]=n(t.body,r.keepalive);B=e;if(i&&!A(this[P]).contains("content-type",true)){this[P].append("content-type",i)}}const D=B??w;if(D!=null&&D.source==null){if(B!=null&&t.duplex==null){throw new TypeError("RequestInit: duplex option is required when sending a body.")}if(r.mode!=="same-origin"&&r.mode!=="cors"){throw new TypeError('If request is made from ReadableStream, mode should be "same-origin" or "cors"')}r.useCORSPreflightFlag=true}let N=D;if(B==null&&w!=null){if(o(e)){throw new TypeError("Cannot construct a Request with a Request object that has already been used.")}const t=new TransformStream;w.stream.pipeThrough(t);N={source:w.source,length:w.length,stream:t.readable}}this[V].body=N}get method(){O.brandCheck(this,Request);return this[V].method}get url(){O.brandCheck(this,Request);return N(this[V].url)}get headers(){O.brandCheck(this,Request);return this[P]}get destination(){O.brandCheck(this,Request);return this[V].destination}get referrer(){O.brandCheck(this,Request);if(this[V].referrer==="no-referrer"){return""}if(this[V].referrer==="client"){return"about:client"}return this[V].referrer.toString()}get referrerPolicy(){O.brandCheck(this,Request);return this[V].referrerPolicy}get mode(){O.brandCheck(this,Request);return this[V].mode}get credentials(){return this[V].credentials}get cache(){O.brandCheck(this,Request);return this[V].cache}get redirect(){O.brandCheck(this,Request);return this[V].redirect}get integrity(){O.brandCheck(this,Request);return this[V].integrity}get keepalive(){O.brandCheck(this,Request);return this[V].keepalive}get isReloadNavigation(){O.brandCheck(this,Request);return this[V].reloadNavigation}get isHistoryNavigation(){O.brandCheck(this,Request);return this[V].historyNavigation}get signal(){O.brandCheck(this,Request);return this[U]}get body(){O.brandCheck(this,Request);return this[V].body?this[V].body.stream:null}get bodyUsed(){O.brandCheck(this,Request);return!!this[V].body&&h.isDisturbed(this[V].body.stream)}get duplex(){O.brandCheck(this,Request);return"half"}clone(){O.brandCheck(this,Request);if(o(this)){throw new TypeError("unusable")}const e=cloneRequest(this[V]);const t=new AbortController;if(this.signal.aborted){t.abort(this.signal.reason)}else{let e=W.get(this.signal);if(e===undefined){e=new Set;W.set(this.signal,e)}const i=new WeakRef(t);e.add(i);h.addAbortListener(t.signal,buildAbort(i))}return fromInnerRequest(e,t.signal,d(this[P]))}[g.inspect.custom](e,t){if(t.depth===null){t.depth=2}t.colors??=true;const i={method:this.method,url:this.url,headers:this.headers,destination:this.destination,referrer:this.referrer,referrerPolicy:this.referrerPolicy,mode:this.mode,credentials:this.credentials,cache:this.cache,redirect:this.redirect,integrity:this.integrity,keepalive:this.keepalive,isReloadNavigation:this.isReloadNavigation,isHistoryNavigation:this.isHistoryNavigation,signal:this.signal};return`Request ${g.formatWithOptions(t,i)}`}}r(Request);function makeRequest(e){return{method:e.method??"GET",localURLsOnly:e.localURLsOnly??false,unsafeRequest:e.unsafeRequest??false,body:e.body??null,client:e.client??null,reservedClient:e.reservedClient??null,replacesClientId:e.replacesClientId??"",window:e.window??"client",keepalive:e.keepalive??false,serviceWorkers:e.serviceWorkers??"all",initiator:e.initiator??"",destination:e.destination??"",priority:e.priority??null,origin:e.origin??"client",policyContainer:e.policyContainer??"client",referrer:e.referrer??"client",referrerPolicy:e.referrerPolicy??"",mode:e.mode??"no-cors",useCORSPreflightFlag:e.useCORSPreflightFlag??false,credentials:e.credentials??"same-origin",useCredentials:e.useCredentials??false,cache:e.cache??"default",redirect:e.redirect??"follow",integrity:e.integrity??"",cryptoGraphicsNonceMetadata:e.cryptoGraphicsNonceMetadata??"",parserMetadata:e.parserMetadata??"",reloadNavigation:e.reloadNavigation??false,historyNavigation:e.historyNavigation??false,userActivation:e.userActivation??false,taintedOrigin:e.taintedOrigin??false,redirectCount:e.redirectCount??0,responseTainting:e.responseTainting??"basic",preventNoCacheCacheControlHeaderModification:e.preventNoCacheCacheControlHeaderModification??false,done:e.done??false,timingAllowFailed:e.timingAllowFailed??false,urlList:e.urlList,url:e.urlList[0],headersList:e.headersList?new u(e.headersList):new u}}function cloneRequest(e){const t=makeRequest({...e,body:null});if(e.body!=null){t.body=s(t,e.body)}return t}function fromInnerRequest(e,t,i){const n=new Request(q);n[V]=e;n[U]=t;n[P]=new a(q);p(n[P],e.headersList);c(n[P],i);return n}Object.defineProperties(Request.prototype,{method:D,url:D,headers:D,redirect:D,clone:D,signal:D,duplex:D,destination:D,body:D,bodyUsed:D,isHistoryNavigation:D,isReloadNavigation:D,keepalive:D,integrity:D,cache:D,credentials:D,attribute:D,referrerPolicy:D,referrer:D,mode:D,[Symbol.toStringTag]:{value:"Request",configurable:true}});O.converters.Request=O.interfaceConverter(Request);O.converters.RequestInfo=function(e,t,i){if(typeof e==="string"){return O.converters.USVString(e,t,i)}if(e instanceof Request){return O.converters.Request(e,t,i)}return O.converters.USVString(e,t,i)};O.converters.AbortSignal=O.interfaceConverter(AbortSignal);O.converters.RequestInit=O.dictionaryConverter([{key:"method",converter:O.converters.ByteString},{key:"headers",converter:O.converters.HeadersInit},{key:"body",converter:O.nullableConverter(O.converters.BodyInit)},{key:"referrer",converter:O.converters.USVString},{key:"referrerPolicy",converter:O.converters.DOMString,allowedValues:C},{key:"mode",converter:O.converters.DOMString,allowedValues:R},{key:"credentials",converter:O.converters.DOMString,allowedValues:b},{key:"cache",converter:O.converters.DOMString,allowedValues:w},{key:"redirect",converter:O.converters.DOMString,allowedValues:T},{key:"integrity",converter:O.converters.DOMString},{key:"keepalive",converter:O.converters.boolean},{key:"signal",converter:O.nullableConverter((e=>O.converters.AbortSignal(e,"RequestInit","signal",{strict:false})))},{key:"window",converter:O.converters.any},{key:"duplex",converter:O.converters.DOMString,allowedValues:B},{key:"dispatcher",converter:O.converters.any}]);e.exports={Request:Request,makeRequest:makeRequest,fromInnerRequest:fromInnerRequest,cloneRequest:cloneRequest}},9051:(e,t,i)=>{const{Headers:n,HeadersList:r,fill:s,getHeadersGuard:o,setHeadersGuard:a,setHeadersList:l}=i(660);const{extractBody:u,cloneBody:c,mixinBody:d,hasFinalizationRegistry:p,streamRegistry:A,bodyUnusable:f}=i(4492);const h=i(3440);const g=i(7975);const{kEnumerableProperty:y}=h;const{isValidReasonPhrase:m,isCancelled:I,isAborted:v,isBlobLike:E,serializeJavascriptValueToJSONString:C,isErrorLike:T,isomorphicEncode:R,environmentSettingsObject:b}=i(3168);const{redirectStatusSet:w,nullBodyStatus:B}=i(4495);const{kState:D,kHeaders:S}=i(3627);const{webidl:k}=i(5893);const{FormData:P}=i(5910);const{URLSerializer:U}=i(1900);const{kConstruct:V}=i(6443);const F=i(4589);const{types:O}=i(7975);const N=new TextEncoder("utf-8");class Response{static error(){const e=fromInnerResponse(makeNetworkError(),"immutable");return e}static json(e,t={}){k.argumentLengthCheck(arguments,1,"Response.json");if(t!==null){t=k.converters.ResponseInit(t)}const i=N.encode(C(e));const n=u(i);const r=fromInnerResponse(makeResponse({}),"response");initializeResponse(r,t,{body:n[0],type:"application/json"});return r}static redirect(e,t=302){k.argumentLengthCheck(arguments,1,"Response.redirect");e=k.converters.USVString(e);t=k.converters["unsigned short"](t);let i;try{i=new URL(e,b.settingsObject.baseUrl)}catch(t){throw new TypeError(`Failed to parse URL from ${e}`,{cause:t})}if(!w.has(t)){throw new RangeError(`Invalid status code ${t}`)}const n=fromInnerResponse(makeResponse({}),"immutable");n[D].status=t;const r=R(U(i));n[D].headersList.append("location",r,true);return n}constructor(e=null,t={}){k.util.markAsUncloneable(this);if(e===V){return}if(e!==null){e=k.converters.BodyInit(e)}t=k.converters.ResponseInit(t);this[D]=makeResponse({});this[S]=new n(V);a(this[S],"response");l(this[S],this[D].headersList);let i=null;if(e!=null){const[t,n]=u(e);i={body:t,type:n}}initializeResponse(this,t,i)}get type(){k.brandCheck(this,Response);return this[D].type}get url(){k.brandCheck(this,Response);const e=this[D].urlList;const t=e[e.length-1]??null;if(t===null){return""}return U(t,true)}get redirected(){k.brandCheck(this,Response);return this[D].urlList.length>1}get status(){k.brandCheck(this,Response);return this[D].status}get ok(){k.brandCheck(this,Response);return this[D].status>=200&&this[D].status<=299}get statusText(){k.brandCheck(this,Response);return this[D].statusText}get headers(){k.brandCheck(this,Response);return this[S]}get body(){k.brandCheck(this,Response);return this[D].body?this[D].body.stream:null}get bodyUsed(){k.brandCheck(this,Response);return!!this[D].body&&h.isDisturbed(this[D].body.stream)}clone(){k.brandCheck(this,Response);if(f(this)){throw k.errors.exception({header:"Response.clone",message:"Body has already been consumed."})}const e=cloneResponse(this[D]);if(p&&this[D].body?.stream){A.register(this,new WeakRef(this[D].body.stream))}return fromInnerResponse(e,o(this[S]))}[g.inspect.custom](e,t){if(t.depth===null){t.depth=2}t.colors??=true;const i={status:this.status,statusText:this.statusText,headers:this.headers,body:this.body,bodyUsed:this.bodyUsed,ok:this.ok,redirected:this.redirected,type:this.type,url:this.url};return`Response ${g.formatWithOptions(t,i)}`}}d(Response);Object.defineProperties(Response.prototype,{type:y,url:y,status:y,ok:y,redirected:y,statusText:y,headers:y,clone:y,body:y,bodyUsed:y,[Symbol.toStringTag]:{value:"Response",configurable:true}});Object.defineProperties(Response,{json:y,redirect:y,error:y});function cloneResponse(e){if(e.internalResponse){return filterResponse(cloneResponse(e.internalResponse),e.type)}const t=makeResponse({...e,body:null});if(e.body!=null){t.body=c(t,e.body)}return t}function makeResponse(e){return{aborted:false,rangeRequested:false,timingAllowPassed:false,requestIncludesCredentials:false,type:"default",status:200,timingInfo:null,cacheState:"",statusText:"",...e,headersList:e?.headersList?new r(e?.headersList):new r,urlList:e?.urlList?[...e.urlList]:[]}}function makeNetworkError(e){const t=T(e);return makeResponse({type:"error",status:0,error:t?e:new Error(e?String(e):e),aborted:e&&e.name==="AbortError"})}function isNetworkError(e){return e.type==="error"&&e.status===0}function makeFilteredResponse(e,t){t={internalResponse:e,...t};return new Proxy(e,{get(e,i){return i in t?t[i]:e[i]},set(e,i,n){F(!(i in t));e[i]=n;return true}})}function filterResponse(e,t){if(t==="basic"){return makeFilteredResponse(e,{type:"basic",headersList:e.headersList})}else if(t==="cors"){return makeFilteredResponse(e,{type:"cors",headersList:e.headersList})}else if(t==="opaque"){return makeFilteredResponse(e,{type:"opaque",urlList:Object.freeze([]),status:0,statusText:"",body:null})}else if(t==="opaqueredirect"){return makeFilteredResponse(e,{type:"opaqueredirect",status:0,statusText:"",headersList:[],body:null})}else{F(false)}}function makeAppropriateNetworkError(e,t=null){F(I(e));return v(e)?makeNetworkError(Object.assign(new DOMException("The operation was aborted.","AbortError"),{cause:t})):makeNetworkError(Object.assign(new DOMException("Request was cancelled."),{cause:t}))}function initializeResponse(e,t,i){if(t.status!==null&&(t.status<200||t.status>599)){throw new RangeError('init["status"] must be in the range of 200 to 599, inclusive.')}if("statusText"in t&&t.statusText!=null){if(!m(String(t.statusText))){throw new TypeError("Invalid statusText")}}if("status"in t&&t.status!=null){e[D].status=t.status}if("statusText"in t&&t.statusText!=null){e[D].statusText=t.statusText}if("headers"in t&&t.headers!=null){s(e[S],t.headers)}if(i){if(B.includes(e.status)){throw k.errors.exception({header:"Response constructor",message:`Invalid response status code ${e.status}`})}e[D].body=i.body;if(i.type!=null&&!e[D].headersList.contains("content-type",true)){e[D].headersList.append("content-type",i.type,true)}}}function fromInnerResponse(e,t){const i=new Response(V);i[D]=e;i[S]=new n(V);l(i[S],e.headersList);a(i[S],t);if(p&&e.body?.stream){A.register(i,new WeakRef(e.body.stream))}return i}k.converters.ReadableStream=k.interfaceConverter(ReadableStream);k.converters.FormData=k.interfaceConverter(P);k.converters.URLSearchParams=k.interfaceConverter(URLSearchParams);k.converters.XMLHttpRequestBodyInit=function(e,t,i){if(typeof e==="string"){return k.converters.USVString(e,t,i)}if(E(e)){return k.converters.Blob(e,t,i,{strict:false})}if(ArrayBuffer.isView(e)||O.isArrayBuffer(e)){return k.converters.BufferSource(e,t,i)}if(h.isFormDataLike(e)){return k.converters.FormData(e,t,i,{strict:false})}if(e instanceof URLSearchParams){return k.converters.URLSearchParams(e,t,i)}return k.converters.DOMString(e,t,i)};k.converters.BodyInit=function(e,t,i){if(e instanceof ReadableStream){return k.converters.ReadableStream(e,t,i)}if(e?.[Symbol.asyncIterator]){return e}return k.converters.XMLHttpRequestBodyInit(e,t,i)};k.converters.ResponseInit=k.dictionaryConverter([{key:"status",converter:k.converters["unsigned short"],defaultValue:()=>200},{key:"statusText",converter:k.converters.ByteString,defaultValue:()=>""},{key:"headers",converter:k.converters.HeadersInit}]);e.exports={isNetworkError:isNetworkError,makeNetworkError:makeNetworkError,makeResponse:makeResponse,makeAppropriateNetworkError:makeAppropriateNetworkError,filterResponse:filterResponse,Response:Response,cloneResponse:cloneResponse,fromInnerResponse:fromInnerResponse}},3627:e=>{e.exports={kUrl:Symbol("url"),kHeaders:Symbol("headers"),kSignal:Symbol("signal"),kState:Symbol("state"),kDispatcher:Symbol("dispatcher")}},3168:(e,t,i)=>{const{Transform:n}=i(7075);const r=i(8522);const{redirectStatusSet:s,referrerPolicySet:o,badPortsSet:a}=i(4495);const{getGlobalOrigin:l}=i(1059);const{collectASequenceOfCodePoints:u,collectAnHTTPQuotedString:c,removeChars:d,parseMIMEType:p}=i(1900);const{performance:A}=i(643);const{isBlobLike:f,ReadableStreamFrom:h,isValidHTTPToken:g,normalizedMethodRecordsBase:y}=i(3440);const m=i(4589);const{isUint8Array:I}=i(3429);const{webidl:v}=i(5893);let E=[];let C;try{C=i(7598);const e=["sha256","sha384","sha512"];E=C.getHashes().filter((t=>e.includes(t)))}catch{}function responseURL(e){const t=e.urlList;const i=t.length;return i===0?null:t[i-1].toString()}function responseLocationURL(e,t){if(!s.has(e.status)){return null}let i=e.headersList.get("location",true);if(i!==null&&isValidHeaderValue(i)){if(!isValidEncodedURL(i)){i=normalizeBinaryStringToUtf8(i)}i=new URL(i,responseURL(e))}if(i&&!i.hash){i.hash=t}return i}function isValidEncodedURL(e){for(let t=0;t126||i<32){return false}}return true}function normalizeBinaryStringToUtf8(e){return Buffer.from(e,"binary").toString("utf8")}function requestCurrentURL(e){return e.urlList[e.urlList.length-1]}function requestBadPort(e){const t=requestCurrentURL(e);if(urlIsHttpHttpsScheme(t)&&a.has(t.port)){return"blocked"}return"allowed"}function isErrorLike(e){return e instanceof Error||(e?.constructor?.name==="Error"||e?.constructor?.name==="DOMException")}function isValidReasonPhrase(e){for(let t=0;t=32&&i<=126||i>=128&&i<=255)){return false}}return true}const T=g;function isValidHeaderValue(e){return(e[0]==="\t"||e[0]===" "||e[e.length-1]==="\t"||e[e.length-1]===" "||e.includes("\n")||e.includes("\r")||e.includes("\0"))===false}function setRequestReferrerPolicyOnRedirect(e,t){const{headersList:i}=t;const n=(i.get("referrer-policy",true)??"").split(",");let r="";if(n.length>0){for(let e=n.length;e!==0;e--){const t=n[e-1].trim();if(o.has(t)){r=t;break}}}if(r!==""){e.referrerPolicy=r}}function crossOriginResourcePolicyCheck(){return"allowed"}function corsCheck(){return"success"}function TAOCheck(){return"success"}function appendFetchMetadata(e){let t=null;t=e.mode;e.headersList.set("sec-fetch-mode",t,true)}function appendRequestOriginHeader(e){let t=e.origin;if(t==="client"||t===undefined){return}if(e.responseTainting==="cors"||e.mode==="websocket"){e.headersList.append("origin",t,true)}else if(e.method!=="GET"&&e.method!=="HEAD"){switch(e.referrerPolicy){case"no-referrer":t=null;break;case"no-referrer-when-downgrade":case"strict-origin":case"strict-origin-when-cross-origin":if(e.origin&&urlHasHttpsScheme(e.origin)&&!urlHasHttpsScheme(requestCurrentURL(e))){t=null}break;case"same-origin":if(!sameOrigin(e,requestCurrentURL(e))){t=null}break;default:}e.headersList.append("origin",t,true)}}function coarsenTime(e,t){return e}function clampAndCoarsenConnectionTimingInfo(e,t,i){if(!e?.startTime||e.startTime4096){n=r}const s=sameOrigin(e,n);const o=isURLPotentiallyTrustworthy(n)&&!isURLPotentiallyTrustworthy(e.url);switch(t){case"origin":return r!=null?r:stripURLForReferrer(i,true);case"unsafe-url":return n;case"same-origin":return s?r:"no-referrer";case"origin-when-cross-origin":return s?n:r;case"strict-origin-when-cross-origin":{const t=requestCurrentURL(e);if(sameOrigin(n,t)){return n}if(isURLPotentiallyTrustworthy(n)&&!isURLPotentiallyTrustworthy(t)){return"no-referrer"}return r}case"strict-origin":case"no-referrer-when-downgrade":default:return o?"no-referrer":r}}function stripURLForReferrer(e,t){m(e instanceof URL);e=new URL(e);if(e.protocol==="file:"||e.protocol==="about:"||e.protocol==="blank:"){return"no-referrer"}e.username="";e.password="";e.hash="";if(t){e.pathname="";e.search=""}return e}function isURLPotentiallyTrustworthy(e){if(!(e instanceof URL)){return false}if(e.href==="about:blank"||e.href==="about:srcdoc"){return true}if(e.protocol==="data:")return true;if(e.protocol==="file:")return true;return isOriginPotentiallyTrustworthy(e.origin);function isOriginPotentiallyTrustworthy(e){if(e==null||e==="null")return false;const t=new URL(e);if(t.protocol==="https:"||t.protocol==="wss:"){return true}if(/^127(?:\.[0-9]+){0,2}\.[0-9]+$|^\[(?:0*:)*?:?0*1\]$/.test(t.hostname)||(t.hostname==="localhost"||t.hostname.includes("localhost."))||t.hostname.endsWith(".localhost")){return true}return false}}function bytesMatch(e,t){if(C===undefined){return true}const i=parseMetadata(t);if(i==="no metadata"){return true}if(i.length===0){return true}const n=getStrongestMetadata(i);const r=filterMetadataListByAlgorithm(i,n);for(const t of r){const i=t.algo;const n=t.hash;let r=C.createHash(i).update(e).digest("base64");if(r[r.length-1]==="="){if(r[r.length-2]==="="){r=r.slice(0,-2)}else{r=r.slice(0,-1)}}if(compareBase64Mixed(r,n)){return true}}return false}const R=/(?sha256|sha384|sha512)-((?[A-Za-z0-9+/]+|[A-Za-z0-9_-]+)={0,2}(?:\s|$)( +[!-~]*)?)?/i;function parseMetadata(e){const t=[];let i=true;for(const n of e.split(" ")){i=false;const e=R.exec(n);if(e===null||e.groups===undefined||e.groups.algo===undefined){continue}const r=e.groups.algo.toLowerCase();if(E.includes(r)){t.push(e.groups)}}if(i===true){return"no metadata"}return t}function getStrongestMetadata(e){let t=e[0].algo;if(t[3]==="5"){return t}for(let i=1;i{e=i;t=n}));return{promise:i,resolve:e,reject:t}}function isAborted(e){return e.controller.state==="aborted"}function isCancelled(e){return e.controller.state==="aborted"||e.controller.state==="terminated"}function normalizeMethod(e){return y[e.toLowerCase()]??e}function serializeJavascriptValueToJSONString(e){const t=JSON.stringify(e);if(t===undefined){throw new TypeError("Value is not JSON serializable")}m(typeof t==="string");return t}const b=Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));function createIterator(e,t,i=0,n=1){class FastIterableIterator{#H;#W;#Y;constructor(e,t){this.#H=e;this.#W=t;this.#Y=0}next(){if(typeof this!=="object"||this===null||!(#H in this)){throw new TypeError(`'next' called on an object that does not implement interface ${e} Iterator.`)}const r=this.#Y;const s=this.#H[t];const o=s.length;if(r>=o){return{value:undefined,done:true}}const{[i]:a,[n]:l}=s[r];this.#Y=r+1;let u;switch(this.#W){case"key":u=a;break;case"value":u=l;break;case"key+value":u=[a,l];break}return{value:u,done:false}}}delete FastIterableIterator.prototype.constructor;Object.setPrototypeOf(FastIterableIterator.prototype,b);Object.defineProperties(FastIterableIterator.prototype,{[Symbol.toStringTag]:{writable:false,enumerable:false,configurable:true,value:`${e} Iterator`},next:{writable:true,enumerable:true,configurable:true}});return function(e,t){return new FastIterableIterator(e,t)}}function iteratorMixin(e,t,i,n=0,r=1){const s=createIterator(e,i,n,r);const o={keys:{writable:true,enumerable:true,configurable:true,value:function keys(){v.brandCheck(this,t);return s(this,"key")}},values:{writable:true,enumerable:true,configurable:true,value:function values(){v.brandCheck(this,t);return s(this,"value")}},entries:{writable:true,enumerable:true,configurable:true,value:function entries(){v.brandCheck(this,t);return s(this,"key+value")}},forEach:{writable:true,enumerable:true,configurable:true,value:function forEach(i,n=globalThis){v.brandCheck(this,t);v.argumentLengthCheck(arguments,1,`${e}.forEach`);if(typeof i!=="function"){throw new TypeError(`Failed to execute 'forEach' on '${e}': parameter 1 is not of type 'Function'.`)}for(const{0:e,1:t}of s(this,"key+value")){i.call(n,t,e,this)}}}};return Object.defineProperties(t.prototype,{...o,[Symbol.iterator]:{writable:true,enumerable:false,configurable:true,value:o.entries.value}})}async function fullyReadBody(e,t,i){const n=t;const r=i;let s;try{s=e.stream.getReader()}catch(e){r(e);return}try{n(await readAllBytes(s))}catch(e){r(e)}}function isReadableStreamLike(e){return e instanceof ReadableStream||e[Symbol.toStringTag]==="ReadableStream"&&typeof e.tee==="function"}function readableStreamClose(e){try{e.close();e.byobRequest?.respond(0)}catch(e){if(!e.message.includes("Controller is already closed")&&!e.message.includes("ReadableStream is already closed")){throw e}}}const w=/[^\x00-\xFF]/;function isomorphicEncode(e){m(!w.test(e));return e}async function readAllBytes(e){const t=[];let i=0;while(true){const{done:n,value:r}=await e.read();if(n){return Buffer.concat(t,i)}if(!I(r)){throw new TypeError("Received non-Uint8Array chunk")}t.push(r);i+=r.length}}function urlIsLocal(e){m("protocol"in e);const t=e.protocol;return t==="about:"||t==="blob:"||t==="data:"}function urlHasHttpsScheme(e){return typeof e==="string"&&e[5]===":"&&e[0]==="h"&&e[1]==="t"&&e[2]==="t"&&e[3]==="p"&&e[4]==="s"||e.protocol==="https:"}function urlIsHttpHttpsScheme(e){m("protocol"in e);const t=e.protocol;return t==="http:"||t==="https:"}function simpleRangeHeaderValue(e,t){const i=e;if(!i.startsWith("bytes")){return"failure"}const n={position:5};if(t){u((e=>e==="\t"||e===" "),i,n)}if(i.charCodeAt(n.position)!==61){return"failure"}n.position++;if(t){u((e=>e==="\t"||e===" "),i,n)}const r=u((e=>{const t=e.charCodeAt(0);return t>=48&&t<=57}),i,n);const s=r.length?Number(r):null;if(t){u((e=>e==="\t"||e===" "),i,n)}if(i.charCodeAt(n.position)!==45){return"failure"}n.position++;if(t){u((e=>e==="\t"||e===" "),i,n)}const o=u((e=>{const t=e.charCodeAt(0);return t>=48&&t<=57}),i,n);const a=o.length?Number(o):null;if(n.positiona){return"failure"}return{rangeStartValue:s,rangeEndValue:a}}function buildContentRange(e,t,i){let n="bytes ";n+=isomorphicEncode(`${e}`);n+="-";n+=isomorphicEncode(`${t}`);n+="/";n+=isomorphicEncode(`${i}`);return n}class InflateStream extends n{#J;constructor(e){super();this.#J=e}_transform(e,t,i){if(!this._inflateStream){if(e.length===0){i();return}this._inflateStream=(e[0]&15)===8?r.createInflate(this.#J):r.createInflateRaw(this.#J);this._inflateStream.on("data",this.push.bind(this));this._inflateStream.on("end",(()=>this.push(null)));this._inflateStream.on("error",(e=>this.destroy(e)))}this._inflateStream.write(e,t,i)}_final(e){if(this._inflateStream){this._inflateStream.end();this._inflateStream=null}e()}}function createInflate(e){return new InflateStream(e)}function extractMimeType(e){let t=null;let i=null;let n=null;const r=getDecodeSplit("content-type",e);if(r===null){return"failure"}for(const e of r){const r=p(e);if(r==="failure"||r.essence==="*/*"){continue}n=r;if(n.essence!==i){t=null;if(n.parameters.has("charset")){t=n.parameters.get("charset")}i=n.essence}else if(!n.parameters.has("charset")&&t!==null){n.parameters.set("charset",t)}}if(n==null){return"failure"}return n}function gettingDecodingSplitting(e){const t=e;const i={position:0};const n=[];let r="";while(i.positione!=='"'&&e!==","),t,i);if(i.positione===9||e===32));n.push(r);r=""}return n}function getDecodeSplit(e,t){const i=t.get(e,true);if(i===null){return null}return gettingDecodingSplitting(i)}const B=new TextDecoder;function utf8DecodeBytes(e){if(e.length===0){return""}if(e[0]===239&&e[1]===187&&e[2]===191){e=e.subarray(3)}const t=B.decode(e);return t}class EnvironmentSettingsObjectBase{get baseUrl(){return l()}get origin(){return this.baseUrl?.origin}policyContainer=makePolicyContainer()}class EnvironmentSettingsObject{settingsObject=new EnvironmentSettingsObjectBase}const D=new EnvironmentSettingsObject;e.exports={isAborted:isAborted,isCancelled:isCancelled,isValidEncodedURL:isValidEncodedURL,createDeferredPromise:createDeferredPromise,ReadableStreamFrom:h,tryUpgradeRequestToAPotentiallyTrustworthyURL:tryUpgradeRequestToAPotentiallyTrustworthyURL,clampAndCoarsenConnectionTimingInfo:clampAndCoarsenConnectionTimingInfo,coarsenedSharedCurrentTime:coarsenedSharedCurrentTime,determineRequestsReferrer:determineRequestsReferrer,makePolicyContainer:makePolicyContainer,clonePolicyContainer:clonePolicyContainer,appendFetchMetadata:appendFetchMetadata,appendRequestOriginHeader:appendRequestOriginHeader,TAOCheck:TAOCheck,corsCheck:corsCheck,crossOriginResourcePolicyCheck:crossOriginResourcePolicyCheck,createOpaqueTimingInfo:createOpaqueTimingInfo,setRequestReferrerPolicyOnRedirect:setRequestReferrerPolicyOnRedirect,isValidHTTPToken:g,requestBadPort:requestBadPort,requestCurrentURL:requestCurrentURL,responseURL:responseURL,responseLocationURL:responseLocationURL,isBlobLike:f,isURLPotentiallyTrustworthy:isURLPotentiallyTrustworthy,isValidReasonPhrase:isValidReasonPhrase,sameOrigin:sameOrigin,normalizeMethod:normalizeMethod,serializeJavascriptValueToJSONString:serializeJavascriptValueToJSONString,iteratorMixin:iteratorMixin,createIterator:createIterator,isValidHeaderName:T,isValidHeaderValue:isValidHeaderValue,isErrorLike:isErrorLike,fullyReadBody:fullyReadBody,bytesMatch:bytesMatch,isReadableStreamLike:isReadableStreamLike,readableStreamClose:readableStreamClose,isomorphicEncode:isomorphicEncode,urlIsLocal:urlIsLocal,urlHasHttpsScheme:urlHasHttpsScheme,urlIsHttpHttpsScheme:urlIsHttpHttpsScheme,readAllBytes:readAllBytes,simpleRangeHeaderValue:simpleRangeHeaderValue,buildContentRange:buildContentRange,parseMetadata:parseMetadata,createInflate:createInflate,extractMimeType:extractMimeType,getDecodeSplit:getDecodeSplit,utf8DecodeBytes:utf8DecodeBytes,environmentSettingsObject:D}},5893:(e,t,i)=>{const{types:n,inspect:r}=i(7975);const{markAsUncloneable:s}=i(8300);const{toUSVString:o}=i(3440);const a={};a.converters={};a.util={};a.errors={};a.errors.exception=function(e){return new TypeError(`${e.header}: ${e.message}`)};a.errors.conversionFailed=function(e){const t=e.types.length===1?"":" one of";const i=`${e.argument} could not be converted to`+`${t}: ${e.types.join(", ")}.`;return a.errors.exception({header:e.prefix,message:i})};a.errors.invalidArgument=function(e){return a.errors.exception({header:e.prefix,message:`"${e.value}" is an invalid ${e.type}.`})};a.brandCheck=function(e,t,i){if(i?.strict!==false){if(!(e instanceof t)){const e=new TypeError("Illegal invocation");e.code="ERR_INVALID_THIS";throw e}}else{if(e?.[Symbol.toStringTag]!==t.prototype[Symbol.toStringTag]){const e=new TypeError("Illegal invocation");e.code="ERR_INVALID_THIS";throw e}}};a.argumentLengthCheck=function({length:e},t,i){if(e{});a.util.ConvertToInt=function(e,t,i,n){let r;let s;if(t===64){r=Math.pow(2,53)-1;if(i==="unsigned"){s=0}else{s=Math.pow(-2,53)+1}}else if(i==="unsigned"){s=0;r=Math.pow(2,t)-1}else{s=Math.pow(-2,t)-1;r=Math.pow(2,t-1)-1}let o=Number(e);if(o===0){o=0}if(n?.enforceRange===true){if(Number.isNaN(o)||o===Number.POSITIVE_INFINITY||o===Number.NEGATIVE_INFINITY){throw a.errors.exception({header:"Integer conversion",message:`Could not convert ${a.util.Stringify(e)} to an integer.`})}o=a.util.IntegerPart(o);if(or){throw a.errors.exception({header:"Integer conversion",message:`Value must be between ${s}-${r}, got ${o}.`})}return o}if(!Number.isNaN(o)&&n?.clamp===true){o=Math.min(Math.max(o,s),r);if(Math.floor(o)%2===0){o=Math.floor(o)}else{o=Math.ceil(o)}return o}if(Number.isNaN(o)||o===0&&Object.is(0,o)||o===Number.POSITIVE_INFINITY||o===Number.NEGATIVE_INFINITY){return 0}o=a.util.IntegerPart(o);o=o%Math.pow(2,t);if(i==="signed"&&o>=Math.pow(2,t)-1){return o-Math.pow(2,t)}return o};a.util.IntegerPart=function(e){const t=Math.floor(Math.abs(e));if(e<0){return-1*t}return t};a.util.Stringify=function(e){const t=a.util.Type(e);switch(t){case"Symbol":return`Symbol(${e.description})`;case"Object":return r(e);case"String":return`"${e}"`;default:return`${e}`}};a.sequenceConverter=function(e){return(t,i,n,r)=>{if(a.util.Type(t)!=="Object"){throw a.errors.exception({header:i,message:`${n} (${a.util.Stringify(t)}) is not iterable.`})}const s=typeof r==="function"?r():t?.[Symbol.iterator]?.();const o=[];let l=0;if(s===undefined||typeof s.next!=="function"){throw a.errors.exception({header:i,message:`${n} is not iterable.`})}while(true){const{done:t,value:r}=s.next();if(t){break}o.push(e(r,i,`${n}[${l++}]`))}return o}};a.recordConverter=function(e,t){return(i,r,s)=>{if(a.util.Type(i)!=="Object"){throw a.errors.exception({header:r,message:`${s} ("${a.util.Type(i)}") is not an Object.`})}const o={};if(!n.isProxy(i)){const n=[...Object.getOwnPropertyNames(i),...Object.getOwnPropertySymbols(i)];for(const a of n){const n=e(a,r,s);const l=t(i[a],r,s);o[n]=l}return o}const l=Reflect.ownKeys(i);for(const n of l){const a=Reflect.getOwnPropertyDescriptor(i,n);if(a?.enumerable){const a=e(n,r,s);const l=t(i[n],r,s);o[a]=l}}return o}};a.interfaceConverter=function(e){return(t,i,n,r)=>{if(r?.strict!==false&&!(t instanceof e)){throw a.errors.exception({header:i,message:`Expected ${n} ("${a.util.Stringify(t)}") to be an instance of ${e.name}.`})}return t}};a.dictionaryConverter=function(e){return(t,i,n)=>{const r=a.util.Type(t);const s={};if(r==="Null"||r==="Undefined"){return s}else if(r!=="Object"){throw a.errors.exception({header:i,message:`Expected ${t} to be one of: Null, Undefined, Object.`})}for(const r of e){const{key:e,defaultValue:o,required:l,converter:u}=r;if(l===true){if(!Object.hasOwn(t,e)){throw a.errors.exception({header:i,message:`Missing required key "${e}".`})}}let c=t[e];const d=Object.hasOwn(r,"defaultValue");if(d&&c!==null){c??=o()}if(l||d||c!==undefined){c=u(c,i,`${n}.${e}`);if(r.allowedValues&&!r.allowedValues.includes(c)){throw a.errors.exception({header:i,message:`${c} is not an accepted type. Expected one of ${r.allowedValues.join(", ")}.`})}s[e]=c}}return s}};a.nullableConverter=function(e){return(t,i,n)=>{if(t===null){return t}return e(t,i,n)}};a.converters.DOMString=function(e,t,i,n){if(e===null&&n?.legacyNullToEmptyString){return""}if(typeof e==="symbol"){throw a.errors.exception({header:t,message:`${i} is a symbol, which cannot be converted to a DOMString.`})}return String(e)};a.converters.ByteString=function(e,t,i){const n=a.converters.DOMString(e,t,i);for(let e=0;e255){throw new TypeError("Cannot convert argument to a ByteString because the character at "+`index ${e} has a value of ${n.charCodeAt(e)} which is greater than 255.`)}}return n};a.converters.USVString=o;a.converters.boolean=function(e){const t=Boolean(e);return t};a.converters.any=function(e){return e};a.converters["long long"]=function(e,t,i){const n=a.util.ConvertToInt(e,64,"signed",undefined,t,i);return n};a.converters["unsigned long long"]=function(e,t,i){const n=a.util.ConvertToInt(e,64,"unsigned",undefined,t,i);return n};a.converters["unsigned long"]=function(e,t,i){const n=a.util.ConvertToInt(e,32,"unsigned",undefined,t,i);return n};a.converters["unsigned short"]=function(e,t,i,n){const r=a.util.ConvertToInt(e,16,"unsigned",n,t,i);return r};a.converters.ArrayBuffer=function(e,t,i,r){if(a.util.Type(e)!=="Object"||!n.isAnyArrayBuffer(e)){throw a.errors.conversionFailed({prefix:t,argument:`${i} ("${a.util.Stringify(e)}")`,types:["ArrayBuffer"]})}if(r?.allowShared===false&&n.isSharedArrayBuffer(e)){throw a.errors.exception({header:"ArrayBuffer",message:"SharedArrayBuffer is not allowed."})}if(e.resizable||e.growable){throw a.errors.exception({header:"ArrayBuffer",message:"Received a resizable ArrayBuffer."})}return e};a.converters.TypedArray=function(e,t,i,r,s){if(a.util.Type(e)!=="Object"||!n.isTypedArray(e)||e.constructor.name!==t.name){throw a.errors.conversionFailed({prefix:i,argument:`${r} ("${a.util.Stringify(e)}")`,types:[t.name]})}if(s?.allowShared===false&&n.isSharedArrayBuffer(e.buffer)){throw a.errors.exception({header:"ArrayBuffer",message:"SharedArrayBuffer is not allowed."})}if(e.buffer.resizable||e.buffer.growable){throw a.errors.exception({header:"ArrayBuffer",message:"Received a resizable ArrayBuffer."})}return e};a.converters.DataView=function(e,t,i,r){if(a.util.Type(e)!=="Object"||!n.isDataView(e)){throw a.errors.exception({header:t,message:`${i} is not a DataView.`})}if(r?.allowShared===false&&n.isSharedArrayBuffer(e.buffer)){throw a.errors.exception({header:"ArrayBuffer",message:"SharedArrayBuffer is not allowed."})}if(e.buffer.resizable||e.buffer.growable){throw a.errors.exception({header:"ArrayBuffer",message:"Received a resizable ArrayBuffer."})}return e};a.converters.BufferSource=function(e,t,i,r){if(n.isAnyArrayBuffer(e)){return a.converters.ArrayBuffer(e,t,i,{...r,allowShared:false})}if(n.isTypedArray(e)){return a.converters.TypedArray(e,e.constructor,t,i,{...r,allowShared:false})}if(n.isDataView(e)){return a.converters.DataView(e,t,i,{...r,allowShared:false})}throw a.errors.conversionFailed({prefix:t,argument:`${i} ("${a.util.Stringify(e)}")`,types:["BufferSource"]})};a.converters["sequence"]=a.sequenceConverter(a.converters.ByteString);a.converters["sequence>"]=a.sequenceConverter(a.converters["sequence"]);a.converters["record"]=a.recordConverter(a.converters.ByteString,a.converters.ByteString);e.exports={webidl:a}},2607:e=>{function getEncoding(e){if(!e){return"failure"}switch(e.trim().toLowerCase()){case"unicode-1-1-utf-8":case"unicode11utf8":case"unicode20utf8":case"utf-8":case"utf8":case"x-unicode20utf8":return"UTF-8";case"866":case"cp866":case"csibm866":case"ibm866":return"IBM866";case"csisolatin2":case"iso-8859-2":case"iso-ir-101":case"iso8859-2":case"iso88592":case"iso_8859-2":case"iso_8859-2:1987":case"l2":case"latin2":return"ISO-8859-2";case"csisolatin3":case"iso-8859-3":case"iso-ir-109":case"iso8859-3":case"iso88593":case"iso_8859-3":case"iso_8859-3:1988":case"l3":case"latin3":return"ISO-8859-3";case"csisolatin4":case"iso-8859-4":case"iso-ir-110":case"iso8859-4":case"iso88594":case"iso_8859-4":case"iso_8859-4:1988":case"l4":case"latin4":return"ISO-8859-4";case"csisolatincyrillic":case"cyrillic":case"iso-8859-5":case"iso-ir-144":case"iso8859-5":case"iso88595":case"iso_8859-5":case"iso_8859-5:1988":return"ISO-8859-5";case"arabic":case"asmo-708":case"csiso88596e":case"csiso88596i":case"csisolatinarabic":case"ecma-114":case"iso-8859-6":case"iso-8859-6-e":case"iso-8859-6-i":case"iso-ir-127":case"iso8859-6":case"iso88596":case"iso_8859-6":case"iso_8859-6:1987":return"ISO-8859-6";case"csisolatingreek":case"ecma-118":case"elot_928":case"greek":case"greek8":case"iso-8859-7":case"iso-ir-126":case"iso8859-7":case"iso88597":case"iso_8859-7":case"iso_8859-7:1987":case"sun_eu_greek":return"ISO-8859-7";case"csiso88598e":case"csisolatinhebrew":case"hebrew":case"iso-8859-8":case"iso-8859-8-e":case"iso-ir-138":case"iso8859-8":case"iso88598":case"iso_8859-8":case"iso_8859-8:1988":case"visual":return"ISO-8859-8";case"csiso88598i":case"iso-8859-8-i":case"logical":return"ISO-8859-8-I";case"csisolatin6":case"iso-8859-10":case"iso-ir-157":case"iso8859-10":case"iso885910":case"l6":case"latin6":return"ISO-8859-10";case"iso-8859-13":case"iso8859-13":case"iso885913":return"ISO-8859-13";case"iso-8859-14":case"iso8859-14":case"iso885914":return"ISO-8859-14";case"csisolatin9":case"iso-8859-15":case"iso8859-15":case"iso885915":case"iso_8859-15":case"l9":return"ISO-8859-15";case"iso-8859-16":return"ISO-8859-16";case"cskoi8r":case"koi":case"koi8":case"koi8-r":case"koi8_r":return"KOI8-R";case"koi8-ru":case"koi8-u":return"KOI8-U";case"csmacintosh":case"mac":case"macintosh":case"x-mac-roman":return"macintosh";case"iso-8859-11":case"iso8859-11":case"iso885911":case"tis-620":case"windows-874":return"windows-874";case"cp1250":case"windows-1250":case"x-cp1250":return"windows-1250";case"cp1251":case"windows-1251":case"x-cp1251":return"windows-1251";case"ansi_x3.4-1968":case"ascii":case"cp1252":case"cp819":case"csisolatin1":case"ibm819":case"iso-8859-1":case"iso-ir-100":case"iso8859-1":case"iso88591":case"iso_8859-1":case"iso_8859-1:1987":case"l1":case"latin1":case"us-ascii":case"windows-1252":case"x-cp1252":return"windows-1252";case"cp1253":case"windows-1253":case"x-cp1253":return"windows-1253";case"cp1254":case"csisolatin5":case"iso-8859-9":case"iso-ir-148":case"iso8859-9":case"iso88599":case"iso_8859-9":case"iso_8859-9:1989":case"l5":case"latin5":case"windows-1254":case"x-cp1254":return"windows-1254";case"cp1255":case"windows-1255":case"x-cp1255":return"windows-1255";case"cp1256":case"windows-1256":case"x-cp1256":return"windows-1256";case"cp1257":case"windows-1257":case"x-cp1257":return"windows-1257";case"cp1258":case"windows-1258":case"x-cp1258":return"windows-1258";case"x-mac-cyrillic":case"x-mac-ukrainian":return"x-mac-cyrillic";case"chinese":case"csgb2312":case"csiso58gb231280":case"gb2312":case"gb_2312":case"gb_2312-80":case"gbk":case"iso-ir-58":case"x-gbk":return"GBK";case"gb18030":return"gb18030";case"big5":case"big5-hkscs":case"cn-big5":case"csbig5":case"x-x-big5":return"Big5";case"cseucpkdfmtjapanese":case"euc-jp":case"x-euc-jp":return"EUC-JP";case"csiso2022jp":case"iso-2022-jp":return"ISO-2022-JP";case"csshiftjis":case"ms932":case"ms_kanji":case"shift-jis":case"shift_jis":case"sjis":case"windows-31j":case"x-sjis":return"Shift_JIS";case"cseuckr":case"csksc56011987":case"euc-kr":case"iso-ir-149":case"korean":case"ks_c_5601-1987":case"ks_c_5601-1989":case"ksc5601":case"ksc_5601":case"windows-949":return"EUC-KR";case"csiso2022kr":case"hz-gb-2312":case"iso-2022-cn":case"iso-2022-cn-ext":case"iso-2022-kr":case"replacement":return"replacement";case"unicodefffe":case"utf-16be":return"UTF-16BE";case"csunicode":case"iso-10646-ucs-2":case"ucs-2":case"unicode":case"unicodefeff":case"utf-16":case"utf-16le":return"UTF-16LE";case"x-user-defined":return"x-user-defined";default:return"failure"}}e.exports={getEncoding:getEncoding}},8355:(e,t,i)=>{const{staticPropertyDescriptors:n,readOperation:r,fireAProgressEvent:s}=i(3610);const{kState:o,kError:a,kResult:l,kEvents:u,kAborted:c}=i(961);const{webidl:d}=i(5893);const{kEnumerableProperty:p}=i(3440);class FileReader extends EventTarget{constructor(){super();this[o]="empty";this[l]=null;this[a]=null;this[u]={loadend:null,error:null,abort:null,load:null,progress:null,loadstart:null}}readAsArrayBuffer(e){d.brandCheck(this,FileReader);d.argumentLengthCheck(arguments,1,"FileReader.readAsArrayBuffer");e=d.converters.Blob(e,{strict:false});r(this,e,"ArrayBuffer")}readAsBinaryString(e){d.brandCheck(this,FileReader);d.argumentLengthCheck(arguments,1,"FileReader.readAsBinaryString");e=d.converters.Blob(e,{strict:false});r(this,e,"BinaryString")}readAsText(e,t=undefined){d.brandCheck(this,FileReader);d.argumentLengthCheck(arguments,1,"FileReader.readAsText");e=d.converters.Blob(e,{strict:false});if(t!==undefined){t=d.converters.DOMString(t,"FileReader.readAsText","encoding")}r(this,e,"Text",t)}readAsDataURL(e){d.brandCheck(this,FileReader);d.argumentLengthCheck(arguments,1,"FileReader.readAsDataURL");e=d.converters.Blob(e,{strict:false});r(this,e,"DataURL")}abort(){if(this[o]==="empty"||this[o]==="done"){this[l]=null;return}if(this[o]==="loading"){this[o]="done";this[l]=null}this[c]=true;s("abort",this);if(this[o]!=="loading"){s("loadend",this)}}get readyState(){d.brandCheck(this,FileReader);switch(this[o]){case"empty":return this.EMPTY;case"loading":return this.LOADING;case"done":return this.DONE}}get result(){d.brandCheck(this,FileReader);return this[l]}get error(){d.brandCheck(this,FileReader);return this[a]}get onloadend(){d.brandCheck(this,FileReader);return this[u].loadend}set onloadend(e){d.brandCheck(this,FileReader);if(this[u].loadend){this.removeEventListener("loadend",this[u].loadend)}if(typeof e==="function"){this[u].loadend=e;this.addEventListener("loadend",e)}else{this[u].loadend=null}}get onerror(){d.brandCheck(this,FileReader);return this[u].error}set onerror(e){d.brandCheck(this,FileReader);if(this[u].error){this.removeEventListener("error",this[u].error)}if(typeof e==="function"){this[u].error=e;this.addEventListener("error",e)}else{this[u].error=null}}get onloadstart(){d.brandCheck(this,FileReader);return this[u].loadstart}set onloadstart(e){d.brandCheck(this,FileReader);if(this[u].loadstart){this.removeEventListener("loadstart",this[u].loadstart)}if(typeof e==="function"){this[u].loadstart=e;this.addEventListener("loadstart",e)}else{this[u].loadstart=null}}get onprogress(){d.brandCheck(this,FileReader);return this[u].progress}set onprogress(e){d.brandCheck(this,FileReader);if(this[u].progress){this.removeEventListener("progress",this[u].progress)}if(typeof e==="function"){this[u].progress=e;this.addEventListener("progress",e)}else{this[u].progress=null}}get onload(){d.brandCheck(this,FileReader);return this[u].load}set onload(e){d.brandCheck(this,FileReader);if(this[u].load){this.removeEventListener("load",this[u].load)}if(typeof e==="function"){this[u].load=e;this.addEventListener("load",e)}else{this[u].load=null}}get onabort(){d.brandCheck(this,FileReader);return this[u].abort}set onabort(e){d.brandCheck(this,FileReader);if(this[u].abort){this.removeEventListener("abort",this[u].abort)}if(typeof e==="function"){this[u].abort=e;this.addEventListener("abort",e)}else{this[u].abort=null}}}FileReader.EMPTY=FileReader.prototype.EMPTY=0;FileReader.LOADING=FileReader.prototype.LOADING=1;FileReader.DONE=FileReader.prototype.DONE=2;Object.defineProperties(FileReader.prototype,{EMPTY:n,LOADING:n,DONE:n,readAsArrayBuffer:p,readAsBinaryString:p,readAsText:p,readAsDataURL:p,abort:p,readyState:p,result:p,error:p,onloadstart:p,onprogress:p,onload:p,onabort:p,onerror:p,onloadend:p,[Symbol.toStringTag]:{value:"FileReader",writable:false,enumerable:false,configurable:true}});Object.defineProperties(FileReader,{EMPTY:n,LOADING:n,DONE:n});e.exports={FileReader:FileReader}},8573:(e,t,i)=>{const{webidl:n}=i(5893);const r=Symbol("ProgressEvent state");class ProgressEvent extends Event{constructor(e,t={}){e=n.converters.DOMString(e,"ProgressEvent constructor","type");t=n.converters.ProgressEventInit(t??{});super(e,t);this[r]={lengthComputable:t.lengthComputable,loaded:t.loaded,total:t.total}}get lengthComputable(){n.brandCheck(this,ProgressEvent);return this[r].lengthComputable}get loaded(){n.brandCheck(this,ProgressEvent);return this[r].loaded}get total(){n.brandCheck(this,ProgressEvent);return this[r].total}}n.converters.ProgressEventInit=n.dictionaryConverter([{key:"lengthComputable",converter:n.converters.boolean,defaultValue:()=>false},{key:"loaded",converter:n.converters["unsigned long long"],defaultValue:()=>0},{key:"total",converter:n.converters["unsigned long long"],defaultValue:()=>0},{key:"bubbles",converter:n.converters.boolean,defaultValue:()=>false},{key:"cancelable",converter:n.converters.boolean,defaultValue:()=>false},{key:"composed",converter:n.converters.boolean,defaultValue:()=>false}]);e.exports={ProgressEvent:ProgressEvent}},961:e=>{e.exports={kState:Symbol("FileReader state"),kResult:Symbol("FileReader result"),kError:Symbol("FileReader error"),kLastProgressEventFired:Symbol("FileReader last progress event fired timestamp"),kEvents:Symbol("FileReader events"),kAborted:Symbol("FileReader aborted")}},3610:(e,t,i)=>{const{kState:n,kError:r,kResult:s,kAborted:o,kLastProgressEventFired:a}=i(961);const{ProgressEvent:l}=i(8573);const{getEncoding:u}=i(2607);const{serializeAMimeType:c,parseMIMEType:d}=i(1900);const{types:p}=i(7975);const{StringDecoder:A}=i(3193);const{btoa:f}=i(4573);const h={enumerable:true,writable:false,configurable:false};function readOperation(e,t,i,l){if(e[n]==="loading"){throw new DOMException("Invalid state","InvalidStateError")}e[n]="loading";e[s]=null;e[r]=null;const u=t.stream();const c=u.getReader();const d=[];let A=c.read();let f=true;(async()=>{while(!e[o]){try{const{done:u,value:h}=await A;if(f&&!e[o]){queueMicrotask((()=>{fireAProgressEvent("loadstart",e)}))}f=false;if(!u&&p.isUint8Array(h)){d.push(h);if((e[a]===undefined||Date.now()-e[a]>=50)&&!e[o]){e[a]=Date.now();queueMicrotask((()=>{fireAProgressEvent("progress",e)}))}A=c.read()}else if(u){queueMicrotask((()=>{e[n]="done";try{const n=packageData(d,i,t.type,l);if(e[o]){return}e[s]=n;fireAProgressEvent("load",e)}catch(t){e[r]=t;fireAProgressEvent("error",e)}if(e[n]!=="loading"){fireAProgressEvent("loadend",e)}}));break}}catch(t){if(e[o]){return}queueMicrotask((()=>{e[n]="done";e[r]=t;fireAProgressEvent("error",e);if(e[n]!=="loading"){fireAProgressEvent("loadend",e)}}));break}}})()}function fireAProgressEvent(e,t){const i=new l(e,{bubbles:false,cancelable:false});t.dispatchEvent(i)}function packageData(e,t,i,n){switch(t){case"DataURL":{let t="data:";const n=d(i||"application/octet-stream");if(n!=="failure"){t+=c(n)}t+=";base64,";const r=new A("latin1");for(const i of e){t+=f(r.write(i))}t+=f(r.end());return t}case"Text":{let t="failure";if(n){t=u(n)}if(t==="failure"&&i){const e=d(i);if(e!=="failure"){t=u(e.parameters.get("charset"))}}if(t==="failure"){t="UTF-8"}return decode(e,t)}case"ArrayBuffer":{const t=combineByteSequences(e);return t.buffer}case"BinaryString":{let t="";const i=new A("latin1");for(const n of e){t+=i.write(n)}t+=i.end();return t}}}function decode(e,t){const i=combineByteSequences(e);const n=BOMSniffing(i);let r=0;if(n!==null){t=n;r=n==="UTF-8"?3:2}const s=i.slice(r);return new TextDecoder(t).decode(s)}function BOMSniffing(e){const[t,i,n]=e;if(t===239&&i===187&&n===191){return"UTF-8"}else if(t===254&&i===255){return"UTF-16BE"}else if(t===255&&i===254){return"UTF-16LE"}return null}function combineByteSequences(e){const t=e.reduce(((e,t)=>e+t.byteLength),0);let i=0;return e.reduce(((e,t)=>{e.set(t,i);i+=t.byteLength;return e}),new Uint8Array(t))}e.exports={staticPropertyDescriptors:h,readOperation:readOperation,fireAProgressEvent:fireAProgressEvent}},6897:(e,t,i)=>{const{uid:n,states:r,sentCloseFrameState:s,emptyBuffer:o,opcodes:a}=i(736);const{kReadyState:l,kSentClose:u,kByteParser:c,kReceivedClose:d,kResponse:p}=i(1216);const{fireEvent:A,failWebsocketConnection:f,isClosing:h,isClosed:g,isEstablished:y,parseExtensions:m}=i(8625);const{channels:I}=i(2414);const{CloseEvent:v}=i(5188);const{makeRequest:E}=i(9967);const{fetching:C}=i(4398);const{Headers:T,getHeadersList:R}=i(660);const{getDecodeSplit:b}=i(3168);const{WebsocketFrameSend:w}=i(3264);let B;try{B=i(7598)}catch{}function establishWebSocketConnection(e,t,i,r,s,o){const a=e;a.protocol=e.protocol==="ws:"?"http:":"https:";const l=E({urlList:[a],client:i,serviceWorkers:"none",referrer:"no-referrer",mode:"websocket",credentials:"include",cache:"no-store",redirect:"error"});if(o.headers){const e=R(new T(o.headers));l.headersList=e}const u=B.randomBytes(16).toString("base64");l.headersList.append("sec-websocket-key",u);l.headersList.append("sec-websocket-version","13");for(const e of t){l.headersList.append("sec-websocket-protocol",e)}const c="permessage-deflate; client_max_window_bits";l.headersList.append("sec-websocket-extensions",c);const d=C({request:l,useParallelQueue:true,dispatcher:o.dispatcher,processResponse(e){if(e.type==="error"||e.status!==101){f(r,"Received network error or non-101 status code.");return}if(t.length!==0&&!e.headersList.get("Sec-WebSocket-Protocol")){f(r,"Server did not respond with sent protocols.");return}if(e.headersList.get("Upgrade")?.toLowerCase()!=="websocket"){f(r,'Server did not set Upgrade header to "websocket".');return}if(e.headersList.get("Connection")?.toLowerCase()!=="upgrade"){f(r,'Server did not set Connection header to "upgrade".');return}const i=e.headersList.get("Sec-WebSocket-Accept");const o=B.createHash("sha1").update(u+n).digest("base64");if(i!==o){f(r,"Incorrect hash received in Sec-WebSocket-Accept header.");return}const a=e.headersList.get("Sec-WebSocket-Extensions");let c;if(a!==null){c=m(a);if(!c.has("permessage-deflate")){f(r,"Sec-WebSocket-Extensions header does not match.");return}}const d=e.headersList.get("Sec-WebSocket-Protocol");if(d!==null){const e=b("sec-websocket-protocol",l.headersList);if(!e.includes(d)){f(r,"Protocol was not set in the opening handshake.");return}}e.socket.on("data",onSocketData);e.socket.on("close",onSocketClose);e.socket.on("error",onSocketError);if(I.open.hasSubscribers){I.open.publish({address:e.socket.address(),protocol:d,extensions:a})}s(e,c)}});return d}function closeWebSocketConnection(e,t,i,n){if(h(e)||g(e)){}else if(!y(e)){f(e,"Connection was closed before it was established.");e[l]=r.CLOSING}else if(e[u]===s.NOT_SENT){e[u]=s.PROCESSING;const c=new w;if(t!==undefined&&i===undefined){c.frameData=Buffer.allocUnsafe(2);c.frameData.writeUInt16BE(t,0)}else if(t!==undefined&&i!==undefined){c.frameData=Buffer.allocUnsafe(2+n);c.frameData.writeUInt16BE(t,0);c.frameData.write(i,2,"utf-8")}else{c.frameData=o}const d=e[p].socket;d.write(c.createFrame(a.CLOSE));e[u]=s.SENT;e[l]=r.CLOSING}else{e[l]=r.CLOSING}}function onSocketData(e){if(!this.ws[c].write(e)){this.pause()}}function onSocketClose(){const{ws:e}=this;const{[p]:t}=e;t.socket.off("data",onSocketData);t.socket.off("close",onSocketClose);t.socket.off("error",onSocketError);const i=e[u]===s.SENT&&e[d];let n=1005;let o="";const a=e[c].closingInfo;if(a&&!a.error){n=a.code??1005;o=a.reason}else if(!e[d]){n=1006}e[l]=r.CLOSED;A("close",e,((e,t)=>new v(e,t)),{wasClean:i,code:n,reason:o});if(I.close.hasSubscribers){I.close.publish({websocket:e,code:n,reason:o})}}function onSocketError(e){const{ws:t}=this;t[l]=r.CLOSING;if(I.socketError.hasSubscribers){I.socketError.publish(e)}this.destroy()}e.exports={establishWebSocketConnection:establishWebSocketConnection,closeWebSocketConnection:closeWebSocketConnection}},736:e=>{const t="258EAFA5-E914-47DA-95CA-C5AB0DC85B11";const i={enumerable:true,writable:false,configurable:false};const n={CONNECTING:0,OPEN:1,CLOSING:2,CLOSED:3};const r={NOT_SENT:0,PROCESSING:1,SENT:2};const s={CONTINUATION:0,TEXT:1,BINARY:2,CLOSE:8,PING:9,PONG:10};const o=2**16-1;const a={INFO:0,PAYLOADLENGTH_16:2,PAYLOADLENGTH_64:3,READ_DATA:4};const l=Buffer.allocUnsafe(0);const u={string:1,typedArray:2,arrayBuffer:3,blob:4};e.exports={uid:t,sentCloseFrameState:r,staticPropertyDescriptors:i,states:n,opcodes:s,maxUnsigned16Bit:o,parserStates:a,emptyBuffer:l,sendHints:u}},5188:(e,t,i)=>{const{webidl:n}=i(5893);const{kEnumerableProperty:r}=i(3440);const{kConstruct:s}=i(6443);const{MessagePort:o}=i(8300);class MessageEvent extends Event{#z;constructor(e,t={}){if(e===s){super(arguments[1],arguments[2]);n.util.markAsUncloneable(this);return}const i="MessageEvent constructor";n.argumentLengthCheck(arguments,1,i);e=n.converters.DOMString(e,i,"type");t=n.converters.MessageEventInit(t,i,"eventInitDict");super(e,t);this.#z=t;n.util.markAsUncloneable(this)}get data(){n.brandCheck(this,MessageEvent);return this.#z.data}get origin(){n.brandCheck(this,MessageEvent);return this.#z.origin}get lastEventId(){n.brandCheck(this,MessageEvent);return this.#z.lastEventId}get source(){n.brandCheck(this,MessageEvent);return this.#z.source}get ports(){n.brandCheck(this,MessageEvent);if(!Object.isFrozen(this.#z.ports)){Object.freeze(this.#z.ports)}return this.#z.ports}initMessageEvent(e,t=false,i=false,r=null,s="",o="",a=null,l=[]){n.brandCheck(this,MessageEvent);n.argumentLengthCheck(arguments,1,"MessageEvent.initMessageEvent");return new MessageEvent(e,{bubbles:t,cancelable:i,data:r,origin:s,lastEventId:o,source:a,ports:l})}static createFastMessageEvent(e,t){const i=new MessageEvent(s,e,t);i.#z=t;i.#z.data??=null;i.#z.origin??="";i.#z.lastEventId??="";i.#z.source??=null;i.#z.ports??=[];return i}}const{createFastMessageEvent:a}=MessageEvent;delete MessageEvent.createFastMessageEvent;class CloseEvent extends Event{#z;constructor(e,t={}){const i="CloseEvent constructor";n.argumentLengthCheck(arguments,1,i);e=n.converters.DOMString(e,i,"type");t=n.converters.CloseEventInit(t);super(e,t);this.#z=t;n.util.markAsUncloneable(this)}get wasClean(){n.brandCheck(this,CloseEvent);return this.#z.wasClean}get code(){n.brandCheck(this,CloseEvent);return this.#z.code}get reason(){n.brandCheck(this,CloseEvent);return this.#z.reason}}class ErrorEvent extends Event{#z;constructor(e,t){const i="ErrorEvent constructor";n.argumentLengthCheck(arguments,1,i);super(e,t);n.util.markAsUncloneable(this);e=n.converters.DOMString(e,i,"type");t=n.converters.ErrorEventInit(t??{});this.#z=t}get message(){n.brandCheck(this,ErrorEvent);return this.#z.message}get filename(){n.brandCheck(this,ErrorEvent);return this.#z.filename}get lineno(){n.brandCheck(this,ErrorEvent);return this.#z.lineno}get colno(){n.brandCheck(this,ErrorEvent);return this.#z.colno}get error(){n.brandCheck(this,ErrorEvent);return this.#z.error}}Object.defineProperties(MessageEvent.prototype,{[Symbol.toStringTag]:{value:"MessageEvent",configurable:true},data:r,origin:r,lastEventId:r,source:r,ports:r,initMessageEvent:r});Object.defineProperties(CloseEvent.prototype,{[Symbol.toStringTag]:{value:"CloseEvent",configurable:true},reason:r,code:r,wasClean:r});Object.defineProperties(ErrorEvent.prototype,{[Symbol.toStringTag]:{value:"ErrorEvent",configurable:true},message:r,filename:r,lineno:r,colno:r,error:r});n.converters.MessagePort=n.interfaceConverter(o);n.converters["sequence"]=n.sequenceConverter(n.converters.MessagePort);const l=[{key:"bubbles",converter:n.converters.boolean,defaultValue:()=>false},{key:"cancelable",converter:n.converters.boolean,defaultValue:()=>false},{key:"composed",converter:n.converters.boolean,defaultValue:()=>false}];n.converters.MessageEventInit=n.dictionaryConverter([...l,{key:"data",converter:n.converters.any,defaultValue:()=>null},{key:"origin",converter:n.converters.USVString,defaultValue:()=>""},{key:"lastEventId",converter:n.converters.DOMString,defaultValue:()=>""},{key:"source",converter:n.nullableConverter(n.converters.MessagePort),defaultValue:()=>null},{key:"ports",converter:n.converters["sequence"],defaultValue:()=>new Array(0)}]);n.converters.CloseEventInit=n.dictionaryConverter([...l,{key:"wasClean",converter:n.converters.boolean,defaultValue:()=>false},{key:"code",converter:n.converters["unsigned short"],defaultValue:()=>0},{key:"reason",converter:n.converters.USVString,defaultValue:()=>""}]);n.converters.ErrorEventInit=n.dictionaryConverter([...l,{key:"message",converter:n.converters.DOMString,defaultValue:()=>""},{key:"filename",converter:n.converters.USVString,defaultValue:()=>""},{key:"lineno",converter:n.converters["unsigned long"],defaultValue:()=>0},{key:"colno",converter:n.converters["unsigned long"],defaultValue:()=>0},{key:"error",converter:n.converters.any}]);e.exports={MessageEvent:MessageEvent,CloseEvent:CloseEvent,ErrorEvent:ErrorEvent,createFastMessageEvent:a}},3264:(e,t,i)=>{const{maxUnsigned16Bit:n}=i(736);const r=16386;let s;let o=null;let a=r;try{s=i(7598)}catch{s={randomFillSync:function randomFillSync(e,t,i){for(let t=0;tn){o+=8;s=127}else if(r>125){o+=2;s=126}const a=Buffer.allocUnsafe(r+o);a[0]=a[1]=0;a[0]|=128;a[0]=(a[0]&240)+e; /*! ws. MIT License. Einar Otto Stangvik */a[o-4]=i[0];a[o-3]=i[1];a[o-2]=i[2];a[o-1]=i[3];a[1]=s;if(s===126){a.writeUInt16BE(r,2)}else if(s===127){a[2]=a[3]=0;a.writeUIntBE(r,4,6)}a[1]|=128;for(let e=0;e{const{createInflateRaw:n,Z_DEFAULT_WINDOWBITS:r}=i(8522);const{isValidClientWindowBits:s}=i(8625);const{MessageSizeExceededError:o}=i(8707);const a=Buffer.from([0,0,255,255]);const l=Symbol("kBuffer");const u=Symbol("kLength");const c=4*1024*1024;class PerMessageDeflate{#$;#A={};#b=false;#K=null;constructor(e){this.#A.serverNoContextTakeover=e.has("server_no_context_takeover");this.#A.serverMaxWindowBits=e.get("server_max_window_bits")}decompress(e,t,i){if(this.#b){i(new o);return}if(!this.#$){let e=r;if(this.#A.serverMaxWindowBits){if(!s(this.#A.serverMaxWindowBits)){i(new Error("Invalid server_max_window_bits"));return}e=Number.parseInt(this.#A.serverMaxWindowBits)}try{this.#$=n({windowBits:e})}catch(e){i(e);return}this.#$[l]=[];this.#$[u]=0;this.#$.on("data",(e=>{if(this.#b){return}this.#$[u]+=e.length;if(this.#$[u]>c){this.#b=true;this.#$.removeAllListeners();this.#$.destroy();this.#$=null;if(this.#K){const e=this.#K;this.#K=null;e(new o)}return}this.#$[l].push(e)}));this.#$.on("error",(e=>{this.#$=null;i(e)}))}this.#K=i;this.#$.write(e);if(t){this.#$.write(a)}this.#$.flush((()=>{if(this.#b||!this.#$){return}const e=Buffer.concat(this.#$[l],this.#$[u]);this.#$[l].length=0;this.#$[u]=0;this.#K=null;i(null,e)}))}}e.exports={PerMessageDeflate:PerMessageDeflate}},1652:(e,t,i)=>{const{Writable:n}=i(7075);const r=i(4589);const{parserStates:s,opcodes:o,states:a,emptyBuffer:l,sentCloseFrameState:u}=i(736);const{kReadyState:c,kSentClose:d,kResponse:p,kReceivedClose:A}=i(1216);const{channels:f}=i(2414);const{isValidStatusCode:h,isValidOpcode:g,failWebsocketConnection:y,websocketMessageReceived:m,utf8Decode:I,isControlFrame:v,isTextBinaryFrame:E,isContinuationFrame:C}=i(8625);const{WebsocketFrameSend:T}=i(3264);const{closeWebSocketConnection:R}=i(6897);const{PerMessageDeflate:b}=i(9469);class ByteParser extends n{#Z=[];#X=0;#ee=false;#v=s.INFO;#te={};#ie=[];#ne;constructor(e,t){super();this.ws=e;this.#ne=t==null?new Map:t;if(this.#ne.has("permessage-deflate")){this.#ne.set("permessage-deflate",new b(t))}}_write(e,t,i){this.#Z.push(e);this.#X+=e.length;this.#ee=true;this.run(i)}run(e){while(this.#ee){if(this.#v===s.INFO){if(this.#X<2){return e()}const t=this.consume(2);const i=(t[0]&128)!==0;const n=t[0]&15;const r=(t[1]&128)===128;const a=!i&&n!==o.CONTINUATION;const l=t[1]&127;const u=t[0]&64;const c=t[0]&32;const d=t[0]&16;if(!g(n)){y(this.ws,"Invalid opcode received");return e()}if(r){y(this.ws,"Frame cannot be masked");return e()}if(u!==0&&!this.#ne.has("permessage-deflate")){y(this.ws,"Expected RSV1 to be clear.");return}if(c!==0||d!==0){y(this.ws,"RSV1, RSV2, RSV3 must be clear");return}if(a&&!E(n)){y(this.ws,"Invalid frame type was fragmented.");return}if(E(n)&&this.#ie.length>0){y(this.ws,"Expected continuation frame");return}if(this.#te.fragmented&&a){y(this.ws,"Fragmented frame exceeded 125 bytes.");return}if((l>125||a)&&v(n)){y(this.ws,"Control frame either too large or fragmented");return}if(C(n)&&this.#ie.length===0&&!this.#te.compressed){y(this.ws,"Unexpected continuation frame");return}if(l<=125){this.#te.payloadLength=l;this.#v=s.READ_DATA}else if(l===126){this.#v=s.PAYLOADLENGTH_16}else if(l===127){this.#v=s.PAYLOADLENGTH_64}if(E(n)){this.#te.binaryType=n;this.#te.compressed=u!==0}this.#te.opcode=n;this.#te.masked=r;this.#te.fin=i;this.#te.fragmented=a}else if(this.#v===s.PAYLOADLENGTH_16){if(this.#X<2){return e()}const t=this.consume(2);this.#te.payloadLength=t.readUInt16BE(0);this.#v=s.READ_DATA}else if(this.#v===s.PAYLOADLENGTH_64){if(this.#X<8){return e()}const t=this.consume(8);const i=t.readUInt32BE(0);const n=t.readUInt32BE(4);if(i!==0||n>2**31-1){y(this.ws,"Received payload length > 2^31 bytes.");return}this.#te.payloadLength=n;this.#v=s.READ_DATA}else if(this.#v===s.READ_DATA){if(this.#X{if(t){y(this.ws,t.message);return}this.#ie.push(i);if(!this.#te.fin){this.#v=s.INFO;this.#ee=true;this.run(e);return}m(this.ws,this.#te.binaryType,Buffer.concat(this.#ie));this.#ee=true;this.#v=s.INFO;this.#ie.length=0;this.run(e)}));this.#ee=false;break}}}}}consume(e){if(e>this.#X){throw new Error("Called consume() before buffers satiated.")}else if(e===0){return l}if(this.#Z[0].length===e){this.#X-=this.#Z[0].length;return this.#Z.shift()}const t=Buffer.allocUnsafe(e);let i=0;while(i!==e){const n=this.#Z[0];const{length:r}=n;if(r+i===e){t.set(this.#Z.shift(),i);break}else if(r+i>e){t.set(n.subarray(0,e-i),i);this.#Z[0]=n.subarray(e-i);break}else{t.set(this.#Z.shift(),i);i+=n.length}}this.#X-=e;return t}parseCloseBody(e){r(e.length!==1);let t;if(e.length>=2){t=e.readUInt16BE(0)}if(t!==undefined&&!h(t)){return{code:1002,reason:"Invalid status code",error:true}}let i=e.subarray(2);if(i[0]===239&&i[1]===187&&i[2]===191){i=i.subarray(3)}try{i=I(i)}catch{return{code:1007,reason:"Invalid UTF-8",error:true}}return{code:t,reason:i,error:false}}parseControlFrame(e){const{opcode:t,payloadLength:i}=this.#te;if(t===o.CLOSE){if(i===1){y(this.ws,"Received close frame with a 1-byte body.");return false}this.#te.closeInfo=this.parseCloseBody(e);if(this.#te.closeInfo.error){const{code:e,reason:t}=this.#te.closeInfo;R(this.ws,e,t,t.length);y(this.ws,t);return false}if(this.ws[d]!==u.SENT){let e=l;if(this.#te.closeInfo.code){e=Buffer.allocUnsafe(2);e.writeUInt16BE(this.#te.closeInfo.code,0)}const t=new T(e);this.ws[p].socket.write(t.createFrame(o.CLOSE),(e=>{if(!e){this.ws[d]=u.SENT}}))}this.ws[c]=a.CLOSING;this.ws[A]=true;return false}else if(t===o.PING){if(!this.ws[A]){const t=new T(e);this.ws[p].socket.write(t.createFrame(o.PONG));if(f.ping.hasSubscribers){f.ping.publish({payload:e})}}}else if(t===o.PONG){if(f.pong.hasSubscribers){f.pong.publish({payload:e})}}return true}get closingInfo(){return this.#te.closeInfo}}e.exports={ByteParser:ByteParser}},3900:(e,t,i)=>{const{WebsocketFrameSend:n}=i(3264);const{opcodes:r,sendHints:s}=i(736);const o=i(4660);const a=Buffer[Symbol.species];class SendQueue{#re=new o;#se=false;#oe;constructor(e){this.#oe=e}add(e,t,i){if(i!==s.blob){const n=createFrame(e,i);if(!this.#se){this.#oe.write(n,t)}else{const e={promise:null,callback:t,frame:n};this.#re.push(e)}return}const n={promise:e.arrayBuffer().then((e=>{n.promise=null;n.frame=createFrame(e,i)})),callback:t,frame:null};this.#re.push(n);if(!this.#se){this.#ae()}}async#ae(){this.#se=true;const e=this.#re;while(!e.isEmpty()){const t=e.shift();if(t.promise!==null){await t.promise}this.#oe.write(t.frame,t.callback);t.callback=t.frame=null}this.#se=false}}function createFrame(e,t){return new n(toBuffer(e,t)).createFrame(t===s.string?r.TEXT:r.BINARY)}function toBuffer(e,t){switch(t){case s.string:return Buffer.from(e);case s.arrayBuffer:case s.blob:return new a(e);case s.typedArray:return new a(e.buffer,e.byteOffset,e.byteLength)}}e.exports={SendQueue:SendQueue}},1216:e=>{e.exports={kWebSocketURL:Symbol("url"),kReadyState:Symbol("ready state"),kController:Symbol("controller"),kResponse:Symbol("response"),kBinaryType:Symbol("binary type"),kSentClose:Symbol("sent close"),kReceivedClose:Symbol("received close"),kByteParser:Symbol("byte parser")}},8625:(e,t,i)=>{const{kReadyState:n,kController:r,kResponse:s,kBinaryType:o,kWebSocketURL:a}=i(1216);const{states:l,opcodes:u}=i(736);const{ErrorEvent:c,createFastMessageEvent:d}=i(5188);const{isUtf8:p}=i(4573);const{collectASequenceOfCodePointsFast:A,removeHTTPWhitespace:f}=i(1900);function isConnecting(e){return e[n]===l.CONNECTING}function isEstablished(e){return e[n]===l.OPEN}function isClosing(e){return e[n]===l.CLOSING}function isClosed(e){return e[n]===l.CLOSED}function fireEvent(e,t,i=(e,t)=>new Event(e,t),n={}){const r=i(e,n);t.dispatchEvent(r)}function websocketMessageReceived(e,t,i){if(e[n]!==l.OPEN){return}let r;if(t===u.TEXT){try{r=y(i)}catch{failWebsocketConnection(e,"Received invalid UTF-8 in text frame.");return}}else if(t===u.BINARY){if(e[o]==="blob"){r=new Blob([i])}else{r=toArrayBuffer(i)}}fireEvent("message",e,d,{origin:e[a].origin,data:r})}function toArrayBuffer(e){if(e.byteLength===e.buffer.byteLength){return e.buffer}return e.buffer.slice(e.byteOffset,e.byteOffset+e.byteLength)}function isValidSubprotocol(e){if(e.length===0){return false}for(let t=0;t126||i===34||i===40||i===41||i===44||i===47||i===58||i===59||i===60||i===61||i===62||i===63||i===64||i===91||i===92||i===93||i===123||i===125){return false}}return true}function isValidStatusCode(e){if(e>=1e3&&e<1015){return e!==1004&&e!==1005&&e!==1006}return e>=3e3&&e<=4999}function failWebsocketConnection(e,t){const{[r]:i,[s]:n}=e;i.abort();if(n?.socket&&!n.socket.destroyed){n.socket.destroy()}if(t){fireEvent("error",e,((e,t)=>new c(e,t)),{error:new Error(t),message:t})}}function isControlFrame(e){return e===u.CLOSE||e===u.PING||e===u.PONG}function isContinuationFrame(e){return e===u.CONTINUATION}function isTextBinaryFrame(e){return e===u.TEXT||e===u.BINARY}function isValidOpcode(e){return isTextBinaryFrame(e)||isContinuationFrame(e)||isControlFrame(e)}function parseExtensions(e){const t={position:0};const i=new Map;while(t.position57){return false}}const t=Number.parseInt(e,10);return t>=8&&t<=15}const h=typeof process.versions.icu==="string";const g=h?new TextDecoder("utf-8",{fatal:true}):undefined;const y=h?g.decode.bind(g):function(e){if(p(e)){return e.toString("utf-8")}throw new TypeError("Invalid utf-8 received.")};e.exports={isConnecting:isConnecting,isEstablished:isEstablished,isClosing:isClosing,isClosed:isClosed,fireEvent:fireEvent,isValidSubprotocol:isValidSubprotocol,isValidStatusCode:isValidStatusCode,failWebsocketConnection:failWebsocketConnection,websocketMessageReceived:websocketMessageReceived,utf8Decode:y,isControlFrame:isControlFrame,isContinuationFrame:isContinuationFrame,isTextBinaryFrame:isTextBinaryFrame,isValidOpcode:isValidOpcode,parseExtensions:parseExtensions,isValidClientWindowBits:isValidClientWindowBits}},3726:(e,t,i)=>{const{webidl:n}=i(5893);const{URLSerializer:r}=i(1900);const{environmentSettingsObject:s}=i(3168);const{staticPropertyDescriptors:o,states:a,sentCloseFrameState:l,sendHints:u}=i(736);const{kWebSocketURL:c,kReadyState:d,kController:p,kBinaryType:A,kResponse:f,kSentClose:h,kByteParser:g}=i(1216);const{isConnecting:y,isEstablished:m,isClosing:I,isValidSubprotocol:v,fireEvent:E}=i(8625);const{establishWebSocketConnection:C,closeWebSocketConnection:T}=i(6897);const{ByteParser:R}=i(1652);const{kEnumerableProperty:b,isBlobLike:w}=i(3440);const{getGlobalDispatcher:B}=i(2581);const{types:D}=i(7975);const{ErrorEvent:S,CloseEvent:k}=i(5188);const{SendQueue:P}=i(3900);class WebSocket extends EventTarget{#F={open:null,error:null,close:null,message:null};#le=0;#ue="";#ne="";#ce;constructor(e,t=[]){super();n.util.markAsUncloneable(this);const i="WebSocket constructor";n.argumentLengthCheck(arguments,1,i);const r=n.converters["DOMString or sequence or WebSocketInit"](t,i,"options");e=n.converters.USVString(e,i,"url");t=r.protocols;const o=s.settingsObject.baseUrl;let a;try{a=new URL(e,o)}catch(e){throw new DOMException(e,"SyntaxError")}if(a.protocol==="http:"){a.protocol="ws:"}else if(a.protocol==="https:"){a.protocol="wss:"}if(a.protocol!=="ws:"&&a.protocol!=="wss:"){throw new DOMException(`Expected a ws: or wss: protocol, got ${a.protocol}`,"SyntaxError")}if(a.hash||a.href.endsWith("#")){throw new DOMException("Got fragment","SyntaxError")}if(typeof t==="string"){t=[t]}if(t.length!==new Set(t.map((e=>e.toLowerCase()))).size){throw new DOMException("Invalid Sec-WebSocket-Protocol value","SyntaxError")}if(t.length>0&&!t.every((e=>v(e)))){throw new DOMException("Invalid Sec-WebSocket-Protocol value","SyntaxError")}this[c]=new URL(a.href);const u=s.settingsObject;this[p]=C(a,t,u,this,((e,t)=>this.#de(e,t)),r);this[d]=WebSocket.CONNECTING;this[h]=l.NOT_SENT;this[A]="blob"}close(e=undefined,t=undefined){n.brandCheck(this,WebSocket);const i="WebSocket.close";if(e!==undefined){e=n.converters["unsigned short"](e,i,"code",{clamp:true})}if(t!==undefined){t=n.converters.USVString(t,i,"reason")}if(e!==undefined){if(e!==1e3&&(e<3e3||e>4999)){throw new DOMException("invalid code","InvalidAccessError")}}let r=0;if(t!==undefined){r=Buffer.byteLength(t);if(r>123){throw new DOMException(`Reason must be less than 123 bytes; received ${r}`,"SyntaxError")}}T(this,e,t,r)}send(e){n.brandCheck(this,WebSocket);const t="WebSocket.send";n.argumentLengthCheck(arguments,1,t);e=n.converters.WebSocketSendData(e,t,"data");if(y(this)){throw new DOMException("Sent before connected.","InvalidStateError")}if(!m(this)||I(this)){return}if(typeof e==="string"){const t=Buffer.byteLength(e);this.#le+=t;this.#ce.add(e,(()=>{this.#le-=t}),u.string)}else if(D.isArrayBuffer(e)){this.#le+=e.byteLength;this.#ce.add(e,(()=>{this.#le-=e.byteLength}),u.arrayBuffer)}else if(ArrayBuffer.isView(e)){this.#le+=e.byteLength;this.#ce.add(e,(()=>{this.#le-=e.byteLength}),u.typedArray)}else if(w(e)){this.#le+=e.size;this.#ce.add(e,(()=>{this.#le-=e.size}),u.blob)}}get readyState(){n.brandCheck(this,WebSocket);return this[d]}get bufferedAmount(){n.brandCheck(this,WebSocket);return this.#le}get url(){n.brandCheck(this,WebSocket);return r(this[c])}get extensions(){n.brandCheck(this,WebSocket);return this.#ne}get protocol(){n.brandCheck(this,WebSocket);return this.#ue}get onopen(){n.brandCheck(this,WebSocket);return this.#F.open}set onopen(e){n.brandCheck(this,WebSocket);if(this.#F.open){this.removeEventListener("open",this.#F.open)}if(typeof e==="function"){this.#F.open=e;this.addEventListener("open",e)}else{this.#F.open=null}}get onerror(){n.brandCheck(this,WebSocket);return this.#F.error}set onerror(e){n.brandCheck(this,WebSocket);if(this.#F.error){this.removeEventListener("error",this.#F.error)}if(typeof e==="function"){this.#F.error=e;this.addEventListener("error",e)}else{this.#F.error=null}}get onclose(){n.brandCheck(this,WebSocket);return this.#F.close}set onclose(e){n.brandCheck(this,WebSocket);if(this.#F.close){this.removeEventListener("close",this.#F.close)}if(typeof e==="function"){this.#F.close=e;this.addEventListener("close",e)}else{this.#F.close=null}}get onmessage(){n.brandCheck(this,WebSocket);return this.#F.message}set onmessage(e){n.brandCheck(this,WebSocket);if(this.#F.message){this.removeEventListener("message",this.#F.message)}if(typeof e==="function"){this.#F.message=e;this.addEventListener("message",e)}else{this.#F.message=null}}get binaryType(){n.brandCheck(this,WebSocket);return this[A]}set binaryType(e){n.brandCheck(this,WebSocket);if(e!=="blob"&&e!=="arraybuffer"){this[A]="blob"}else{this[A]=e}}#de(e,t){this[f]=e;const i=new R(this,t);i.on("drain",onParserDrain);i.on("error",onParserError.bind(this));e.socket.ws=this;this[g]=i;this.#ce=new P(e.socket);this[d]=a.OPEN;const n=e.headersList.get("sec-websocket-extensions");if(n!==null){this.#ne=n}const r=e.headersList.get("sec-websocket-protocol");if(r!==null){this.#ue=r}E("open",this)}}WebSocket.CONNECTING=WebSocket.prototype.CONNECTING=a.CONNECTING;WebSocket.OPEN=WebSocket.prototype.OPEN=a.OPEN;WebSocket.CLOSING=WebSocket.prototype.CLOSING=a.CLOSING;WebSocket.CLOSED=WebSocket.prototype.CLOSED=a.CLOSED;Object.defineProperties(WebSocket.prototype,{CONNECTING:o,OPEN:o,CLOSING:o,CLOSED:o,url:b,readyState:b,bufferedAmount:b,onopen:b,onerror:b,onclose:b,close:b,onmessage:b,binaryType:b,send:b,extensions:b,protocol:b,[Symbol.toStringTag]:{value:"WebSocket",writable:false,enumerable:false,configurable:true}});Object.defineProperties(WebSocket,{CONNECTING:o,OPEN:o,CLOSING:o,CLOSED:o});n.converters["sequence"]=n.sequenceConverter(n.converters.DOMString);n.converters["DOMString or sequence"]=function(e,t,i){if(n.util.Type(e)==="Object"&&Symbol.iterator in e){return n.converters["sequence"](e)}return n.converters.DOMString(e,t,i)};n.converters.WebSocketInit=n.dictionaryConverter([{key:"protocols",converter:n.converters["DOMString or sequence"],defaultValue:()=>new Array(0)},{key:"dispatcher",converter:n.converters.any,defaultValue:()=>B()},{key:"headers",converter:n.nullableConverter(n.converters.HeadersInit)}]);n.converters["DOMString or sequence or WebSocketInit"]=function(e){if(n.util.Type(e)==="Object"&&!(Symbol.iterator in e)){return n.converters.WebSocketInit(e)}return{protocols:n.converters["DOMString or sequence"](e)}};n.converters.WebSocketSendData=function(e){if(n.util.Type(e)==="Object"){if(w(e)){return n.converters.Blob(e,{strict:false})}if(ArrayBuffer.isView(e)||D.isArrayBuffer(e)){return n.converters.BufferSource(e)}}return n.converters.USVString(e)};function onParserDrain(){this.ws[f].socket.resume()}function onParserError(e){let t;let i;if(e instanceof k){t=e.reason;i=e.code}else{t=e.message}E("error",this,(()=>new S("error",{error:e,message:t})));T(this,i)}e.exports={WebSocket:WebSocket}},8682:e=>{var t=[];for(var i=0;i<256;++i){t[i]=(i+256).toString(16).substr(1)}function bytesToUuid(e,i){var n=i||0;var r=t;return[r[e[n++]],r[e[n++]],r[e[n++]],r[e[n++]],"-",r[e[n++]],r[e[n++]],"-",r[e[n++]],r[e[n++]],"-",r[e[n++]],r[e[n++]],"-",r[e[n++]],r[e[n++]],r[e[n++]],r[e[n++]],r[e[n++]],r[e[n++]]].join("")}e.exports=bytesToUuid},1694:(e,t,i)=>{var n=i(6982);e.exports=function nodeRNG(){return n.randomBytes(16)}},9021:(e,t,i)=>{var n=i(1694);var r=i(8682);function v4(e,t,i){var s=t&&i||0;if(typeof e=="string"){t=e==="binary"?new Array(16):null;e=null}e=e||{};var o=e.random||(e.rng||n)();o[6]=o[6]&15|64;o[8]=o[8]&63|128;if(t){for(var a=0;a<16;++a){t[s+a]=o[a]}}return t||r(o)}e.exports=v4},2613:t=>{t.exports=e(import.meta.url)("assert")},181:t=>{t.exports=e(import.meta.url)("buffer")},5317:t=>{t.exports=e(import.meta.url)("child_process")},6982:t=>{t.exports=e(import.meta.url)("crypto")},4434:t=>{t.exports=e(import.meta.url)("events")},9896:t=>{t.exports=e(import.meta.url)("fs")},8611:t=>{t.exports=e(import.meta.url)("http")},5692:t=>{t.exports=e(import.meta.url)("https")},9278:t=>{t.exports=e(import.meta.url)("net")},4589:t=>{t.exports=e(import.meta.url)("node:assert")},6698:t=>{t.exports=e(import.meta.url)("node:async_hooks")},4573:t=>{t.exports=e(import.meta.url)("node:buffer")},7540:t=>{t.exports=e(import.meta.url)("node:console")},7598:t=>{t.exports=e(import.meta.url)("node:crypto")},3053:t=>{t.exports=e(import.meta.url)("node:diagnostics_channel")},610:t=>{t.exports=e(import.meta.url)("node:dns")},8474:t=>{t.exports=e(import.meta.url)("node:events")},7067:t=>{t.exports=e(import.meta.url)("node:http")},2467:t=>{t.exports=e(import.meta.url)("node:http2")},7030:t=>{t.exports=e(import.meta.url)("node:net")},643:t=>{t.exports=e(import.meta.url)("node:perf_hooks")},1792:t=>{t.exports=e(import.meta.url)("node:querystring")},7075:t=>{t.exports=e(import.meta.url)("node:stream")},1692:t=>{t.exports=e(import.meta.url)("node:tls")},3136:t=>{t.exports=e(import.meta.url)("node:url")},7975:t=>{t.exports=e(import.meta.url)("node:util")},3429:t=>{t.exports=e(import.meta.url)("node:util/types")},8300:t=>{t.exports=e(import.meta.url)("node:worker_threads")},8522:t=>{t.exports=e(import.meta.url)("node:zlib")},857:t=>{t.exports=e(import.meta.url)("os")},6928:t=>{t.exports=e(import.meta.url)("path")},2203:t=>{t.exports=e(import.meta.url)("stream")},3193:t=>{t.exports=e(import.meta.url)("string_decoder")},4756:t=>{t.exports=e(import.meta.url)("tls")},7016:t=>{t.exports=e(import.meta.url)("url")},9023:t=>{t.exports=e(import.meta.url)("util")},3106:t=>{t.exports=e(import.meta.url)("zlib")},1120:e=>{var t;const i=function NullObject(){};i.prototype=Object.create(null);const n=/; *([!#$%&'*+.^\w`|~-]+)=("(?:[\v\u0020\u0021\u0023-\u005b\u005d-\u007e\u0080-\u00ff]|\\[\v\u0020-\u00ff])*"|[!#$%&'*+.^\w`|~-]+) */gu;const r=/\\([\v\u0020-\u00ff])/gu;const s=/^[!#$%&'*+.^\w|~-]+\/[!#$%&'*+.^\w|~-]+$/u;const o={type:"",parameters:new i};Object.freeze(o.parameters);Object.freeze(o);function parse(e){if(typeof e!=="string"){throw new TypeError("argument header is required and must be a string")}let t=e.indexOf(";");const o=t!==-1?e.slice(0,t).trim():e.trim();if(s.test(o)===false){throw new TypeError("invalid media type")}const a={type:o.toLowerCase(),parameters:new i};if(t===-1){return a}let l;let u;let c;n.lastIndex=t;while(u=n.exec(e)){if(u.index!==t){throw new TypeError("invalid parameter format")}t+=u[0].length;l=u[1].toLowerCase();c=u[2];if(c[0]==='"'){c=c.slice(1,c.length-1);r.test(c)&&(c=c.replace(r,"$1"))}a.parameters[l]=c}if(t!==e.length){throw new TypeError("invalid parameter format")}return a}function safeParse(e){if(typeof e!=="string"){return o}let t=e.indexOf(";");const a=t!==-1?e.slice(0,t).trim():e.trim();if(s.test(a)===false){return o}const l={type:a.toLowerCase(),parameters:new i};if(t===-1){return l}let u;let c;let d;n.lastIndex=t;while(c=n.exec(e)){if(c.index!==t){return o}t+=c[0].length;u=c[1].toLowerCase();d=c[2];if(d[0]==='"'){d=d.slice(1,d.length-1);r.test(d)&&(d=d.replace(r,"$1"))}l.parameters[u]=d}if(t!==e.length){return o}return l}t={parse:parse,safeParse:safeParse};t=parse;e.exports.xL=safeParse;t=o},5643:(e,t)=>{Object.defineProperty(t,"__esModule",{value:true});var i="1.13.8";var n=typeof self=="object"&&self.self===self&&self||typeof global=="object"&&global.global===global&&global||Function("return this")()||{};var r=Array.prototype,s=Object.prototype;var o=typeof Symbol!=="undefined"?Symbol.prototype:null;var a=r.push,l=r.slice,u=s.toString,c=s.hasOwnProperty;var d=typeof ArrayBuffer!=="undefined",p=typeof DataView!=="undefined";var A=Array.isArray,f=Object.keys,h=Object.create,g=d&&ArrayBuffer.isView;var y=isNaN,m=isFinite;var I=!{toString:null}.propertyIsEnumerable("toString");var v=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];var E=Math.pow(2,53)-1;function restArguments(e,t){t=t==null?e.length-1:+t;return function(){var i=Math.max(arguments.length-t,0),n=Array(i),r=0;for(;r=0&&i<=E}}function shallowProperty(e){return function(t){return t==null?void 0:t[e]}}var L=shallowProperty("byteLength");var G=createSizePropertyCheck(L);var j=/\[object ((I|Ui)nt(8|16|32)|Float(32|64)|Uint8Clamped|Big(I|Ui)nt64)Array\]/;function isTypedArray(e){return g?g(e)&&!N(e):G(e)&&j.test(u.call(e))}var x=d?isTypedArray:constant(false);var H=shallowProperty("length");function emulatedSet(e){var t={};for(var i=e.length,n=0;n=0)continue;n.push(e);r.push(t);i.push(true);if(c){f=e.length;if(f!==t.length)return false;while(f--){i.push({a:e[f],b:t[f]})}}else{var h=keys(e),g;f=h.length;if(keys(t).length!==f)return false;while(f--){g=h[f];if(!has$1(t,g))return false;i.push({a:e[g],b:t[g]})}}}return true}function allKeys(e){if(!isObject(e))return[];var t=[];for(var i in e)t.push(i);if(I)collectNonEnumProps(e,t);return t}function ie11fingerprint(e){var t=H(e);return function(i){if(i==null)return false;var n=allKeys(i);if(H(n))return false;for(var r=0;r":">",'"':""","'":"'","`":"`"};var ue=createEscaper(le);var ce=invert(le);var de=createEscaper(ce);var pe=_$1.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var Ae=/(.)^/;var fe={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"};var he=/\\|'|\r|\n|\u2028|\u2029/g;function escapeChar(e){return"\\"+fe[e]}var ge=/^\s*(\w|\$)+\s*$/;function template(e,t,i){if(!t&&i)t=i;t=oe({},t,_$1.templateSettings);var n=RegExp([(t.escape||Ae).source,(t.interpolate||Ae).source,(t.evaluate||Ae).source].join("|")+"|$","g");var r=0;var s="__p+='";e.replace(n,(function(t,i,n,o,a){s+=e.slice(r,a).replace(he,escapeChar);r=a+t.length;if(i){s+="'+\n((__t=("+i+"))==null?'':_.escape(__t))+\n'"}else if(n){s+="'+\n((__t=("+n+"))==null?'':__t)+\n'"}else if(o){s+="';\n"+o+"\n__p+='"}return t}));s+="';\n";var o=t.variable;if(o){if(!ge.test(o))throw new Error("variable is not a bare identifier: "+o)}else{s="with(obj||{}){\n"+s+"}\n";o="obj"}s="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+s+"return __p;\n";var a;try{a=new Function(o,"_",s)}catch(e){e.source=s;throw e}var template=function(e){return a.call(this,e,_$1)};template.source="function("+o+"){\n"+s+"}";return template}function result(e,t,i){t=toPath(t);var n=t.length;if(!n){return P(i)?i.call(e):i}for(var r=0;r=o){if(!a.length)break;var l=a.pop();s=l.i;e=l.v;o=H(e);continue}var u=e[s++];if(a.length>=t){n[r++]=u}else if(ve(u)&&(q(u)||M(u))){a.push({i:s,v:e});s=0;e=u;o=H(e)}else if(!i){n[r++]=u}}return n}var Ee=restArguments((function(e,t){t=flatten$1(t,false,false);var i=t.length;if(i<1)throw new Error("bindAll must be passed function names");while(i--){var n=t[i];e[n]=Ie(e[n],e)}return e}));function memoize(e,t){var memoize=function(i){var n=memoize.cache;var r=""+(t?t.apply(this,arguments):i);if(!has$1(n,r))n[r]=e.apply(this,arguments);return n[r]};memoize.cache={};return memoize}var Ce=restArguments((function(e,t,i){return setTimeout((function(){return e.apply(null,i)}),t)}));var Te=me(Ce,_$1,1);function throttle(e,t,i){var n,r,s,o;var a=0;if(!i)i={};var later=function(){a=i.leading===false?0:ae();n=null;o=e.apply(r,s);if(!n)r=s=null};var throttled=function(){var l=ae();if(!a&&i.leading===false)a=l;var u=t-(l-a);r=this;s=arguments;if(u<=0||u>t){if(n){clearTimeout(n);n=null}a=l;o=e.apply(r,s);if(!n)r=s=null}else if(!n&&i.trailing!==false){n=setTimeout(later,u)}return o};throttled.cancel=function(){clearTimeout(n);a=0;n=r=s=null};return throttled}function debounce(e,t,i){var n,r,s,o,a;var later=function(){var l=ae()-r;if(t>l){n=setTimeout(later,t-l)}else{n=null;if(!i)o=e.apply(a,s);if(!n)s=a=null}};var l=restArguments((function(l){a=this;s=l;r=ae();if(!n){n=setTimeout(later,t);if(i)o=e.apply(a,s)}return o}));l.cancel=function(){clearTimeout(n);n=s=a=null};return l}function wrap(e,t){return me(t,e)}function negate(e){return function(){return!e.apply(this,arguments)}}function compose(){var e=arguments;var t=e.length-1;return function(){var i=t;var n=e[t].apply(this,arguments);while(i--)n=e[i].call(this,n);return n}}function after(e,t){return function(){if(--e<1){return t.apply(this,arguments)}}}function before(e,t){var i;return function(){if(--e>0){i=t.apply(this,arguments)}if(e<=1)t=null;return i}}var Re=me(before,2);function findKey(e,t,i){t=cb(t,i);var n=keys(e),r;for(var s=0,o=n.length;s0?0:r-1;for(;s>=0&&s0){o=s>=0?s:Math.max(s+a,o)}else{a=s>=0?Math.min(s+1,a):s+a+1}}else if(i&&s&&a){s=i(n,r);return n[s]===r?s:-1}if(r!==r){s=t(l.call(n,o,a),isNaN$1);return s>=0?s+o:-1}for(s=e>0?o:a-1;s>=0&&s0?0:o-1;if(!r){n=t[s?s[a]:a];a+=e}for(;a>=0&&a=3;return reducer(e,optimizeCb(t,n,4),i,r)}}var De=createReduce(1);var Se=createReduce(-1);function filter(e,t,i){var n=[];t=cb(t,i);each(e,(function(e,i,r){if(t(e,i,r))n.push(e)}));return n}function reject(e,t,i){return filter(e,negate(cb(t)),i)}function every(e,t,i){t=cb(t,i);var n=!ve(e)&&keys(e),r=(n||e).length;for(var s=0;s=0}var ke=restArguments((function(e,t,i){var n,r;if(P(t)){r=t}else{t=toPath(t);n=t.slice(0,-1);t=t[t.length-1]}return map(e,(function(e){var s=r;if(!s){if(n&&n.length){e=deepGet(e,n)}if(e==null)return void 0;s=e[t]}return s==null?s:s.apply(e,i)}))}));function pluck(e,t){return map(e,property(t))}function where(e,t){return filter(e,matcher(t))}function max(e,t,i){var n=-Infinity,r=-Infinity,s,o;if(t==null||typeof t=="number"&&typeof e[0]!="object"&&e!=null){e=ve(e)?e:values(e);for(var a=0,l=e.length;an){n=s}}}else{t=cb(t,i);each(e,(function(e,i,s){o=t(e,i,s);if(o>r||o===-Infinity&&n===-Infinity){n=e;r=o}}))}return n}function min(e,t,i){var n=Infinity,r=Infinity,s,o;if(t==null||typeof t=="number"&&typeof e[0]!="object"&&e!=null){e=ve(e)?e:values(e);for(var a=0,l=e.length;an||i===void 0)return 1;if(i1)n=optimizeCb(n,t[1]);t=allKeys(e)}else{n=keyInObj;t=flatten$1(t,false,false);e=Object(e)}for(var r=0,s=t.length;r1)n=t[1]}else{t=map(flatten$1(t,false,false),String);i=function(e,i){return!contains(t,i)}}return Ne(e,i,n)}));function initial(e,t,i){return l.call(e,0,Math.max(0,e.length-(t==null||i?1:t)))}function first(e,t,i){if(e==null||e.length<1)return t==null||i?void 0:[];if(t==null||i)return e[0];return initial(e,e.length-t)}function rest(e,t,i){return l.call(e,t==null||i?1:t)}function last(e,t,i){if(e==null||e.length<1)return t==null||i?void 0:[];if(t==null||i)return e[e.length-1];return rest(e,Math.max(0,e.length-t))}function compact(e){return filter(e,Boolean)}function flatten(e,t){return flatten$1(e,t,false)}var _e=restArguments((function(e,t){t=flatten$1(t,true,true);return filter(e,(function(e){return!contains(t,e)}))}));var Me=restArguments((function(e,t){return _e(e,t)}));function uniq(e,t,i,n){if(!isBoolean(t)){n=i;i=t;t=false}if(i!=null)i=cb(i,n);var r=[];var s=[];for(var o=0,a=H(e);o{var n=i(5643);e.exports=n._}};var i={};function __nccwpck_require__(e){var n=i[e];if(n!==undefined){return n.exports}var r=i[e]={exports:{}};var s=true;try{t[e].call(r.exports,r,r.exports,__nccwpck_require__);s=false}finally{if(s)delete i[e]}return r.exports}(()=>{__nccwpck_require__.n=e=>{var t=e&&e.__esModule?()=>e["default"]:()=>e;__nccwpck_require__.d(t,{a:t});return t}})();(()=>{__nccwpck_require__.d=(e,t)=>{for(var i in t){if(__nccwpck_require__.o(t,i)&&!__nccwpck_require__.o(e,i)){Object.defineProperty(e,i,{enumerable:true,get:t[i]})}}}})();(()=>{__nccwpck_require__.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t)})();if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=new URL(".",import.meta.url).pathname.slice(import.meta.url.match(/^file:\/\/\/\w:/)?1:0,-1)+"/";var n={};var r=__nccwpck_require__(3485);class AzureDevOpsApiWrapper{getPersonalAccessTokenHandler(e){return r.getPersonalAccessTokenHandler(e)}getHandlerFromToken(e){return r.getHandlerFromToken(e)}getWebApiInstance(e,t){return new r.WebApi(e,t)}}const s=e(import.meta.url)("node:path");var o=__nccwpck_require__(358);class AzurePipelinesRunnerInvoker{_azurePipelinesRunnerWrapper;constructor(e){this._azurePipelinesRunnerWrapper=e}async exec(e,t){const i={failOnStdErr:true,silent:true};const n=this._azurePipelinesRunnerWrapper.execSync(e,t,i);return Promise.resolve({exitCode:n.code,stderr:n.stderr,stdout:n.stdout})}getInput(e){const t=e.join("");return this._azurePipelinesRunnerWrapper.getInput(t)}getEndpointAuthorization(e){const t=this._azurePipelinesRunnerWrapper.getEndpointAuthorization(e,true);if(t===null){return null}return{parameters:t.parameters,scheme:t.scheme}}getEndpointAuthorizationScheme(e){return this._azurePipelinesRunnerWrapper.getEndpointAuthorizationScheme(e,true)}getEndpointAuthorizationParameter(e,t){return this._azurePipelinesRunnerWrapper.getEndpointAuthorizationParameter(e,t,true)}locInitialize(e){this._azurePipelinesRunnerWrapper.setResourcePath(s.join(e,"task.json"))}loc(e,...t){return this._azurePipelinesRunnerWrapper.loc(e,...t)}logDebug(e){this._azurePipelinesRunnerWrapper.debug(e)}logError(e){this._azurePipelinesRunnerWrapper.error(e)}logWarning(e){this._azurePipelinesRunnerWrapper.warning(e)}setStatusFailed(e){this._azurePipelinesRunnerWrapper.setResult(o.TaskResult.Failed,e)}setStatusSkipped(e){this._azurePipelinesRunnerWrapper.setResult(o.TaskResult.Skipped,e)}setStatusSucceeded(e){this._azurePipelinesRunnerWrapper.setResult(o.TaskResult.Succeeded,e)}setSecret(e){this._azurePipelinesRunnerWrapper.setSecret(e)}}class AzurePipelinesRunnerWrapper{debug(e){o.debug(e)}error(e){o.error(e)}execSync(e,t,i){return o.execSync(e,t,i)}getInput(e){return o.getInput(e)??null}getEndpointAuthorization(e,t){return o.getEndpointAuthorization(e,t)??null}getEndpointAuthorizationScheme(e,t){return o.getEndpointAuthorizationScheme(e,t)??null}getEndpointAuthorizationParameter(e,t,i){return o.getEndpointAuthorizationParameter(e,t,i)??null}setSecret(e){o.setSecret(e)}setResourcePath(e){o.setResourcePath(e)}loc(e,...t){return o.loc(e,...t)}setResult(e,t){o.setResult(e,t)}warning(e){o.warning(e)}}const validateString=(e,t,i)=>{if(e===null||typeof e==="undefined"||e===""){throw new TypeError(`'${t}', accessed within '${i}', is invalid, null, or undefined '${String(e)}'.`)}return e};const validateVariable=(e,t)=>{const i=process.env[e];return validateString(i,e,t)};const validateNumber=(e,t,i)=>{if(e===null||typeof e==="undefined"||e===0||Number.isNaN(e)){throw new TypeError(`'${t}', accessed within '${i}', is invalid, null, or undefined '${String(e)}'.`)}return e};const validateGuid=(e,t,i)=>{if(!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(e)){throw new TypeError(`'${t}', accessed within '${i}', is not a valid GUID '${e}'.`)}};var a=__nccwpck_require__(6648);var l=__nccwpck_require__(3268);class BaseReposInvoker{static async invokeApiCall(e,t,i){try{return await e()}catch(e){const n=e;const r=n.status??n.statusCode;if(r===l.StatusCodes.UNAUTHORIZED||r===l.StatusCodes.FORBIDDEN){n.internalMessage=n.message;n.message=t}else if(r===l.StatusCodes.NOT_FOUND){n.internalMessage=n.message;n.message=i}throw n}}}class CommentData{pullRequestComments=[];fileComments=[]}class PullRequestCommentData{id;content;status;constructor(e,t,i){this.id=e;this.content=t;this.status=i??a.CommentThreadStatus.Unknown}}class FileCommentData extends PullRequestCommentData{fileName;constructor(e,t,i,n){super(e,t,n);this.fileName=i}}class AzureReposInvoker extends BaseReposInvoker{_azureDevOpsApiWrapper;_gitInvoker;_logger;_runnerInvoker;_tokenManager;_project="";_repositoryId="";_pullRequestId=0;_gitApi=null;constructor(e,t,i,n,r){super();this._azureDevOpsApiWrapper=e;this._gitInvoker=t;this._logger=i;this._runnerInvoker=n;this._tokenManager=r}static convertPullRequestComments(e){const t=new CommentData;let i=0;for(const n of e){const e=validateNumber(n.id,`commentThread[${String(i)}].id`,"AzureReposInvoker.convertPullRequestComments()");const r=n.comments;if(typeof r==="undefined"){continue}const s=r[0]?.content;if(typeof s==="undefined"||s===""){continue}const o=n.status??a.CommentThreadStatus.Unknown;if(n.threadContext===null||typeof n.threadContext==="undefined"){t.pullRequestComments.push(new PullRequestCommentData(e,s,o))}else{const i=n.threadContext.filePath;if(typeof i==="undefined"||i.length<=1){continue}t.fileComments.push(new FileCommentData(e,s,i.substring(1),o))}i+=1}return t}async isAccessTokenAvailable(){this._logger.logDebug("* AzureReposInvoker.isAccessTokenAvailable()");const e=await this._tokenManager.getToken();if(e!==null){return e}if(typeof process.env.PR_METRICS_ACCESS_TOKEN==="undefined"||process.env.PR_METRICS_ACCESS_TOKEN.trim()===""){return this._runnerInvoker.loc("repos.azureReposInvoker.noAzureReposAccessToken")}return null}async getTitleAndDescription(){this._logger.logDebug("* AzureReposInvoker.getTitleAndDescription()");const e=this.getGitApi();const t=await this.invokeApiCall((async()=>(await e).getPullRequestById(this._pullRequestId,this._project)));this._logger.logDebug(JSON.stringify(t));const i=validateString(t.title,"title","AzureReposInvoker.getTitleAndDescription()");return{description:t.description??null,title:i}}async getComments(){this._logger.logDebug("* AzureReposInvoker.getComments()");const e=this.getGitApi();const t=await this.invokeApiCall((async()=>(await e).getThreads(this._repositoryId,this._pullRequestId,this._project)));this._logger.logDebug(JSON.stringify(t));return AzureReposInvoker.convertPullRequestComments(t)}async setTitleAndDescription(e,t){this._logger.logDebug("* AzureReposInvoker.setTitleAndDescription()");if(e===null&&t===null){return}const i=this.getGitApi();const n={};if(e!==null){n.title=e}if(t!==null){n.description=t}const r=await this.invokeApiCall((async()=>(await i).updatePullRequest(n,this._repositoryId,this._pullRequestId,this._project)));this._logger.logDebug(JSON.stringify(r))}async createComment(e,t,i,n){this._logger.logDebug("* AzureReposInvoker.createComment()");const r=this.getGitApi();const s={comments:[{content:e}],status:i};if(t!==null){s.threadContext={filePath:`/${t}`};const e={line:1,offset:1};const i={line:1,offset:2};if(n??false){s.threadContext.leftFileStart=e;s.threadContext.leftFileEnd=i}else{s.threadContext.rightFileStart=e;s.threadContext.rightFileEnd=i}}const o=await this.invokeApiCall((async()=>(await r).createThread(s,this._repositoryId,this._pullRequestId,this._project)));this._logger.logDebug(JSON.stringify(o))}async updateComment(e,t,i){this._logger.logDebug("* AzureReposInvoker.updateComment()");if(t===null&&i===null){return}const n=this.getGitApi();if(t!==null){const i={content:t};const r=await this.invokeApiCall((async()=>(await n).updateComment(i,this._repositoryId,this._pullRequestId,e,1,this._project)));this._logger.logDebug(JSON.stringify(r))}if(i!==null){const t={status:i};const r=await this.invokeApiCall((async()=>(await n).updateThread(t,this._repositoryId,this._pullRequestId,e,this._project)));this._logger.logDebug(JSON.stringify(r))}}async deleteCommentThread(e){this._logger.logDebug("* AzureReposInvoker.deleteCommentThread()");const t=this.getGitApi();await this.invokeApiCall((async()=>(await t).deleteComment(this._repositoryId,this._pullRequestId,e,1,this._project)))}async invokeApiCall(e){return BaseReposInvoker.invokeApiCall(e,this._runnerInvoker.loc("repos.azureReposInvoker.insufficientAzureReposAccessTokenPermissions"),this._runnerInvoker.loc("repos.baseReposInvoker.resourceNotFound"))}async getGitApi(){this._logger.logDebug("* AzureReposInvoker.getGitApi()");if(this._gitApi!==null){return this._gitApi}this._project=validateVariable("SYSTEM_TEAMPROJECT","AzureReposInvoker.getGitApi()");this._repositoryId=validateVariable("BUILD_REPOSITORY_ID","AzureReposInvoker.getGitApi()");this._pullRequestId=this._gitInvoker.pullRequestId;const e=validateVariable("PR_METRICS_ACCESS_TOKEN","AzureReposInvoker.getGitApi()");const t=this._azureDevOpsApiWrapper.getPersonalAccessTokenHandler(e);const i=validateVariable("SYSTEM_TEAMFOUNDATIONCOLLECTIONURI","AzureReposInvoker.getGitApi()");const n=this._azureDevOpsApiWrapper.getWebApiInstance(i,t);this._gitApi=await n.getGitApi();return this._gitApi}}class CodeMetricsData{productCode;testCode;ignoredCode;constructor(e,t,i){if(e<0){throw new RangeError(`Product code '${String(e)}' must be >= 0.`)}if(t<0){throw new RangeError(`Test code '${String(t)}' must be >= 0.`)}if(i<0){throw new RangeError(`Ignored code '${String(i)}' must be >= 0.`)}this.productCode=e;this.testCode=t;this.ignoredCode=i}get subtotal(){return this.productCode+this.testCode}get total(){return this.subtotal+this.ignoredCode}}const u=10;const c=1;const d=3e4;const p=200;var A=__nccwpck_require__(4006);var f=__nccwpck_require__.n(A);class CodeMetrics{static _picomatchOptions={dot:true};_gitInvoker;_inputs;_logger;_runnerInvoker;_isInitialized=false;_filesNotRequiringReview=[];_deletedFilesNotRequiringReview=[];_size="";_sizeIndicator="";_metrics=new CodeMetricsData(0,0,0);_isSufficientlyTested=null;constructor(e,t,i,n){this._gitInvoker=e;this._inputs=t;this._logger=i;this._runnerInvoker=n}static parseChangedLines(e,t,i){let n;if(e==="-"){n=0}else{n=parseInt(e,u);if(Number.isNaN(n)){throw new Error(`Could not parse ${i} lines '${e}' from line '${t}'.`)}}return n}async getFilesNotRequiringReview(){this._logger.logDebug("* CodeMetrics.getFilesNotRequiringReview()");await this.initialize();return this._filesNotRequiringReview}async getDeletedFilesNotRequiringReview(){this._logger.logDebug("* CodeMetrics.getDeletedFilesNotRequiringReview()");await this.initialize();return this._deletedFilesNotRequiringReview}async getSize(){this._logger.logDebug("* CodeMetrics.getSize()");await this.initialize();return this._size}async getSizeIndicator(){this._logger.logDebug("* CodeMetrics.getSizeIndicator()");await this.initialize();return this._sizeIndicator}async getMetrics(){this._logger.logDebug("* CodeMetrics.getMetrics()");await this.initialize();return this._metrics}async isSmall(){this._logger.logDebug("* CodeMetrics.isSmall()");await this.initialize();return this._metrics.productCode{if(e.startsWith(t)){return"doubleNegative"}if(e.startsWith(i)){return"negative"}return"positive"}));const s=r.positive??[];const o=(r.negative??[]).map((e=>e.substring(i.length)));const a=(r.doubleNegative??[]).map((e=>e.substring(t.length)));const l=[];const u=[];const c=[];for(const e of n){const t=this.determineIfValidFilePattern(e,s,o,a);const i=this.matchFileExtension(e.fileName);if(t&&i){l.push(e)}else if(t){u.push(e)}else{c.push(e)}}this.constructMetrics(l,u,c)}determineIfValidFilePattern(e,t,i,n){this._logger.logDebug("* CodeMetrics.determineIfValidFilePattern()");let r=t.some((t=>this.performGlobCheck(e.fileName,t)));if(r){if(i.some((t=>this.performGlobCheck(e.fileName,t)))){r=n.some((t=>this.performGlobCheck(e.fileName,t)))}}return r}performGlobCheck(e,t){this._logger.logDebug("* CodeMetrics.performGlobCheck()");return f().isMatch(e,t,CodeMetrics._picomatchOptions)}matchFileExtension(e){this._logger.logDebug("* CodeMetrics.matchFileExtension()");const t=e.lastIndexOf(".");const i=e.substring(t+1).toLowerCase();const n=this._inputs.codeFileExtensions.has(i);this._logger.logDebug(`File name '${e}' has extension '${i}', which is ${n?"in":"ex"}cluded.`);return n}constructMetrics(e,t,i){this._logger.logDebug("* CodeMetrics.constructMetrics()");let n=0;let r=0;let s=0;for(const t of e){const e=this._inputs.testMatchingPatterns.some((e=>this.performGlobCheck(t.fileName,e)));if(e){this._logger.logDebug(`Test File: ${t.fileName} (${String(t.linesAdded)} lines)`);r+=t.linesAdded}else{this._logger.logDebug(`Product File: ${t.fileName} (${String(t.linesAdded)} lines)`);n+=t.linesAdded}}for(const e of t){this._logger.logDebug(`Ignored File: ${e.fileName} (${String(e.linesAdded)} lines)`);s+=e.linesAdded}for(const e of i){if(e.linesAdded>0||e.linesAdded===0&&e.linesDeleted===0){this._logger.logDebug(`Ignored File: ${e.fileName} (${String(e.linesAdded)} lines), comment to be added`);s+=e.linesAdded;this._filesNotRequiringReview.push(e.fileName)}else{this._logger.logDebug(`Ignored File: ${e.fileName} (deleted), comment to be added`);this._deletedFilesNotRequiringReview.push(e.fileName)}}this._metrics=new CodeMetricsData(n,r,s)}createFileMetricsMap(e){this._logger.logDebug("* CodeMetrics.createFileMetricsMap()");const t=e.split("\n");const i=[];for(const n of t){const t=n.split("\t");if(typeof t[0]==="undefined"||typeof t[1]==="undefined"||typeof t[2]==="undefined"){throw new RangeError(`The number of elements '${String(t.length)}' in '${n}' in input '${e}' did not match the expected 3.`)}const r=t[2].replace(/\{.*? => (?[^}]+?)\}/gu,"$").replace(/.*? => (?[^}]+?)/gu,"$");i.push({fileName:r,linesAdded:CodeMetrics.parseChangedLines(t[0],n,"added"),linesDeleted:CodeMetrics.parseChangedLines(t[1],n,"deleted")})}return i}initializeIsSufficientlyTested(){this._logger.logDebug("* CodeMetrics.initializeIsSufficientlyTested()");if(this._inputs.testFactor===null){this._isSufficientlyTested=null}else{this._isSufficientlyTested=this._metrics.testCode>=this._metrics.productCode*this._inputs.testFactor}}initializeSizeIndicator(){this._logger.logDebug("* CodeMetrics.initializeSizeIndicator()");this._size=this.calculateSize();let e="";if(this._isSufficientlyTested!==null){if(this._isSufficientlyTested){e=this._runnerInvoker.loc("metrics.codeMetrics.titleTestsSufficient")}else{e=this._runnerInvoker.loc("metrics.codeMetrics.titleTestsInsufficient")}}this._sizeIndicator=this._runnerInvoker.loc("metrics.codeMetrics.titleSizeIndicatorFormat",this._size,e)}calculateSize(){this._logger.logDebug("* CodeMetrics.calculateSize()");const e=0;const t=1;const i=2;const n=3;const r=4;const s=[()=>this._runnerInvoker.loc("metrics.codeMetrics.titleSizeXS"),()=>this._runnerInvoker.loc("metrics.codeMetrics.titleSizeS"),()=>this._runnerInvoker.loc("metrics.codeMetrics.titleSizeM"),()=>this._runnerInvoker.loc("metrics.codeMetrics.titleSizeL"),e=>`${e}${this._runnerInvoker.loc("metrics.codeMetrics.titleSizeXL")}`];if(this._metrics.productCode=l){l*=this._inputs.growthRate;o+=1;if(o===i||o===n||o===r){a=s[o]("")}else{a=s[r](String(o-s.length+i))}}return a}}class RunnerInvoker{_azurePipelinesRunnerInvoker;_gitHubRunnerInvoker;_runnerInvoker=null;_localizationInitialized=false;constructor(e,t){this._azurePipelinesRunnerInvoker=e;this._gitHubRunnerInvoker=t}static get isGitHub(){return typeof process.env.GITHUB_ACTION!=="undefined"}get runner(){if(this._runnerInvoker!==null){return this._runnerInvoker}this._runnerInvoker=RunnerInvoker.isGitHub?this._gitHubRunnerInvoker:this._azurePipelinesRunnerInvoker;return this._runnerInvoker}async exec(e,t){return this.runner.exec(e,t)}getInput(e){return this.runner.getInput(e)}getEndpointAuthorization(e){return this.runner.getEndpointAuthorization(e)}getEndpointAuthorizationScheme(e){return this.runner.getEndpointAuthorizationScheme(e)}getEndpointAuthorizationParameter(e,t){return this.runner.getEndpointAuthorizationParameter(e,t)}locInitialize(e){if(this._localizationInitialized){throw new Error("RunnerInvoker.locInitialize must not be called multiple times.")}this._localizationInitialized=true;this.runner.locInitialize(e)}loc(e,...t){if(!this._localizationInitialized){throw new Error("RunnerInvoker.locInitialize must be called before RunnerInvoker.loc.")}return this.runner.loc(e,...t)}logDebug(e){this.runner.logDebug(e)}logError(e){this.runner.logError(e)}logWarning(e){this.runner.logWarning(e)}setStatusFailed(e){this.runner.setStatusFailed(e)}setStatusSkipped(e){this.runner.setStatusSkipped(e)}setStatusSucceeded(e){this.runner.setStatusSucceeded(e)}setSecret(e){this.runner.setSecret(e)}}class CodeMetricsCalculator{_gitInvoker;_logger;_pullRequest;_pullRequestComments;_reposInvoker;_runnerInvoker;constructor(e,t,i,n,r,s){this._gitInvoker=e;this._logger=t;this._pullRequest=i;this._pullRequestComments=n;this._reposInvoker=r;this._runnerInvoker=s}get shouldSkip(){this._logger.logDebug("* CodeMetricsCalculator.shouldSkip");if(!this._pullRequest.isPullRequest){return this._runnerInvoker.loc("metrics.codeMetricsCalculator.noPullRequest")}const e=this._pullRequest.isSupportedProvider;if(e!==true){return this._runnerInvoker.loc("metrics.codeMetricsCalculator.unsupportedProvider",String(e))}return null}async shouldStop(){this._logger.logDebug("* CodeMetricsCalculator.shouldStop()");const e=await this._reposInvoker.isAccessTokenAvailable();if(e!==null){return e}if(!await this._gitInvoker.isGitRepo()){return RunnerInvoker.isGitHub?this._runnerInvoker.loc("metrics.codeMetricsCalculator.noGitRepoGitHub"):this._runnerInvoker.loc("metrics.codeMetricsCalculator.noGitRepoAzureDevOps")}if(!this._gitInvoker.isPullRequestIdAvailable()){return RunnerInvoker.isGitHub?this._runnerInvoker.loc("metrics.codeMetricsCalculator.noPullRequestIdGitHub"):this._runnerInvoker.loc("metrics.codeMetricsCalculator.noPullRequestIdAzureDevOps")}if(!await this._gitInvoker.isGitHistoryAvailable()){return RunnerInvoker.isGitHub?this._runnerInvoker.loc("metrics.codeMetricsCalculator.noGitHistoryGitHub"):this._runnerInvoker.loc("metrics.codeMetricsCalculator.noGitHistoryAzureDevOps")}return null}async updateDetails(){this._logger.logDebug("* CodeMetricsCalculator.updateDetails()");const e=await this._reposInvoker.getTitleAndDescription();const t=await this._pullRequest.getUpdatedTitle(e.title);const i=this._pullRequest.getUpdatedDescription(e.description);await this._reposInvoker.setTitleAndDescription(t,i)}async updateComments(){this._logger.logDebug("* CodeMetricsCalculator.updateComments()");const e=[];const t=await this._pullRequestComments.getCommentData();e.push(this.updateMetricsComment(t));for(const i of t.commentThreadsRequiringDeletion){e.push(this._reposInvoker.deleteCommentThread(i))}await Promise.all(e);for(const e of t.filesNotRequiringReview){await this.updateNoReviewRequiredComment(e,false)}for(const e of t.deletedFilesNotRequiringReview){await this.updateNoReviewRequiredComment(e,true)}}async updateMetricsComment(e){this._logger.logDebug("* CodeMetricsCalculator.updateMetricsComment()");const t=await this._pullRequestComments.getMetricsComment();const i=await this._pullRequestComments.getMetricsCommentStatus();if(e.metricsCommentThreadId===null){await this._reposInvoker.createComment(t,null,i)}else{await this._reposInvoker.updateComment(e.metricsCommentThreadId,e.metricsCommentContent===t?null:t,e.metricsCommentThreadStatus===i?null:i)}}async updateNoReviewRequiredComment(e,t){this._logger.logDebug("* CodeMetricsCalculator.updateNoReviewRequiredComment()");await this._reposInvoker.createComment(this._pullRequestComments.noReviewRequiredComment,e,a.CommentThreadStatus.Closed,t)}}class ConsoleWrapper{log(e,...t){const i=e.replace(/[\n\r]/gu," ");console.log(i,...t)}}class RequestError extends Error{name;status;request;response;constructor(e,t,i){super(e,{cause:i.cause});this.name="HttpError";this.status=Number.parseInt(t);if(Number.isNaN(this.status)){this.status=0} -/* v8 ignore else -- @preserve -- Bug with vitest coverage where it sees an else branch that doesn't exist */if("response"in i){this.response=i.response}const n=Object.assign({},i.request);if(i.request.headers.authorization){n.headers=Object.assign({},i.request.headers,{authorization:i.request.headers.authorization.replace(/(?{const e=await this._octokitWrapper.getPull(this._owner,this._repo,this._pullRequestId);this._logger.logDebug(JSON.stringify(e));return e}));return{description:e.data.body??null,title:e.data.title}}async getComments(){this._logger.logDebug("* GitHubReposInvoker.getComments()");this.initialize();let e=null;let t=null;await Promise.all([this.invokeApiCall((async()=>{e=await this._octokitWrapper.getIssueComments(this._owner,this._repo,this._pullRequestId);this._logger.logDebug(JSON.stringify(e))})),this.invokeApiCall((async()=>{t=await this._octokitWrapper.getReviewComments(this._owner,this._repo,this._pullRequestId);this._logger.logDebug(JSON.stringify(t))}))]);return this.convertPullRequestComments(e,t)}async setTitleAndDescription(e,t){this._logger.logDebug("* GitHubReposInvoker.setTitleAndDescription()");if(e===null&&t===null){return}this.initialize();await this.invokeApiCall((async()=>{const i=await this._octokitWrapper.updatePull(this._owner,this._repo,this._pullRequestId,e,t);this._logger.logDebug(JSON.stringify(i))}))}async createComment(e,t){this._logger.logDebug("* GitHubReposInvoker.createComment()");this.initialize();if(t===null){await this.invokeApiCall((async()=>{const t=await this._octokitWrapper.createIssueComment(this._owner,this._repo,this._pullRequestId,e);this._logger.logDebug(JSON.stringify(t))}))}else{if(this._commitId===""){await this.getCommitId()}await this.invokeApiCall((async()=>{try{const i=await this._octokitWrapper.createReviewComment(this._owner,this._repo,this._pullRequestId,e,t,this._commitId);this._logger.logDebug(JSON.stringify(i))}catch(e){if(e instanceof RequestError&&e.status===l.StatusCodes.UNPROCESSABLE_ENTITY&&(e.message.includes("is too big")||e.message.includes("diff is too large"))){this._logger.logInfo("GitHub createReviewComment() threw a 422 error related to a large diff. Ignoring as this is expected.");this._logger.logErrorObject(e)}else{throw e}}}))}}async updateComment(e,t){this._logger.logDebug("* GitHubReposInvoker.updateComment()");if(t===null){return}this.initialize();await this.invokeApiCall((async()=>{const i=await this._octokitWrapper.updateIssueComment(this._owner,this._repo,this._pullRequestId,e,t);this._logger.logDebug(JSON.stringify(i))}))}async deleteCommentThread(e){this._logger.logDebug("* GitHubReposInvoker.deleteCommentThread()");this.initialize();await this.invokeApiCall((async()=>{const t=await this._octokitWrapper.deleteReviewComment(this._owner,this._repo,e);this._logger.logDebug(JSON.stringify(t))}))}async invokeApiCall(e){return BaseReposInvoker.invokeApiCall(e,this._runnerInvoker.loc("repos.gitHubReposInvoker.insufficientGitHubAccessTokenPermissions"),this._runnerInvoker.loc("repos.baseReposInvoker.resourceNotFound"))}initialize(){this._logger.logDebug("* GitHubReposInvoker.initialize()");if(this._isInitialized){return}const e={auth:process.env.PR_METRICS_ACCESS_TOKEN,log:{debug:e=>{this._logger.logDebug(`Octokit – ${e}`)},error:e=>{this._logger.logError(`Octokit – ${e}`)},info:e=>{this._logger.logInfo(`Octokit – ${e}`)},warn:e=>{this._logger.logWarning(`Octokit – ${e}`)}},userAgent:"PRMetrics/v1.7.13"};if(RunnerInvoker.isGitHub){e.baseUrl=this.initializeForGitHub()}else{e.baseUrl=this.initializeForAzureDevOps()}this._logger.logDebug(`Using Base URL '${e.baseUrl}'.`);this._octokitWrapper.initialize(e);this._pullRequestId=this._gitInvoker.pullRequestId;this._isInitialized=true}initializeForGitHub(){this._logger.logDebug("* GitHubReposInvoker.initializeForGitHub()");const e=validateVariable("GITHUB_API_URL","GitHubReposInvoker.initializeForGitHub()");this._owner=validateVariable("GITHUB_REPOSITORY_OWNER","GitHubReposInvoker.initializeForGitHub()");const t=validateVariable("GITHUB_REPOSITORY","GitHubReposInvoker.initializeForGitHub()");const i=t.split("/");if(typeof i[1]==="undefined"){throw new Error(`GITHUB_REPOSITORY '${t}' is in an unexpected format.`)}[,this._repo]=i;return e}initializeForAzureDevOps(){this._logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()");const e=validateVariable("SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI","GitHubReposInvoker.initializeForAzureDevOps()");const t=e.split("/");if(typeof t[2]==="undefined"||typeof t[3]==="undefined"||typeof t[4]==="undefined"){throw new Error(`SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI '${e}' is in an unexpected format.`)}let i="";let n;[,,n,this._owner,this._repo]=t;if(n!=="github.com"){i=`https://${n}/api/v3`}if(this._repo.endsWith(".git")){this._repo=this._repo.substring(0,this._repo.length-".git".length)}return i}convertPullRequestComments(e,t){this._logger.logDebug("* GitHubReposInvoker.convertPullRequestComments()");const i=new CommentData;if(e!==null){for(const t of e.data){const e=t.body;if(typeof e!=="undefined"){i.pullRequestComments.push(new PullRequestCommentData(t.id,e))}}}if(t!==null){for(const e of t.data){const t=e.body;const n=e.path;i.fileComments.push(new FileCommentData(e.id,t,n))}}return i}async getCommitId(){this._logger.logDebug("* GitHubReposInvoker.getCommitId()");let e=await this.invokeApiCall((async()=>{const e=await this._octokitWrapper.listCommits(this._owner,this._repo,this._pullRequestId,1);this._logger.logDebug(JSON.stringify(e));return e}));if(typeof e.headers.link!=="undefined"){const t=e.headers.link;const i=/<.+?page=(?\d+)>;\s*rel="last"/u.exec(t);if(typeof i?.groups?.pageNumber==="undefined"){throw new Error(`The regular expression did not match '${t}'.`)}const n=parseInt(i.groups.pageNumber,u);e=await this.invokeApiCall((async()=>{const e=await this._octokitWrapper.listCommits(this._owner,this._repo,this._pullRequestId,n);this._logger.logDebug(JSON.stringify(e));return e}))}this._commitId=validateString(e.data[e.data.length-1]?.sha,`result.data[${String(e.data.length-1)}].sha`,"GitHubReposInvoker.getCommitId()")}}const h=e(import.meta.url)("node:fs");var g=__nccwpck_require__(7975);class GitHubRunnerInvoker{_azurePipelinesRunnerWrapper;_consoleWrapper;_gitHubRunnerWrapper;_resources=new Map;constructor(e,t,i){this._azurePipelinesRunnerWrapper=e;this._consoleWrapper=t;this._gitHubRunnerWrapper=i}async exec(e,t){const i={failOnStdErr:true,silent:true};const n=await this._gitHubRunnerWrapper.exec(e,t,i);return{exitCode:n.exitCode,stderr:n.stderr,stdout:n.stdout}}getInput(e){const t=e.join("-").toUpperCase();return this._azurePipelinesRunnerWrapper.getInput(t)}getEndpointAuthorization(){throw new Error("getEndpointAuthorization() unavailable in GitHub.")}getEndpointAuthorizationScheme(){throw new Error("getEndpointAuthorizationScheme() unavailable in GitHub.")}getEndpointAuthorizationParameter(){throw new Error("getEndpointAuthorizationParameter() unavailable in GitHub.")}locInitialize(e){const t=h.readFileSync(s.join(e,"resources.resjson"),"utf8");const i=JSON.parse(t);const n=Object.entries(i);const r="loc.messages.";for(const[e,t]of n){if(e.startsWith(r)){this._resources.set(e.substring(r.length),t)}}}loc(e,...t){return g.format(this._resources.get(e),...t)}logDebug(e){this._gitHubRunnerWrapper.debug(e)}logError(e){this._gitHubRunnerWrapper.error(e)}logWarning(e){this._gitHubRunnerWrapper.warning(e)}setStatusFailed(e){this._gitHubRunnerWrapper.setFailed(e)}setStatusSkipped(e){this._consoleWrapper.log(e)}setStatusSucceeded(e){this._consoleWrapper.log(e)}setSecret(e){this._gitHubRunnerWrapper.setSecret(e)}}var y=__nccwpck_require__(857);function utils_toCommandValue(e){if(e===null||e===undefined){return""}else if(typeof e==="string"||e instanceof String){return e}return JSON.stringify(e)}function utils_toCommandProperties(e){if(!Object.keys(e).length){return{}}return{title:e.title,file:e.file,line:e.startLine,endLine:e.endLine,col:e.startColumn,endColumn:e.endColumn}}function command_issueCommand(e,t,i){const n=new Command(e,t,i);process.stdout.write(n.toString()+y.EOL)}function command_issue(e,t=""){command_issueCommand(e,{},t)}const m="::";class Command{constructor(e,t,i){if(!e){e="missing.command"}this.command=e;this.properties=t;this.message=i}toString(){let e=m+this.command;if(this.properties&&Object.keys(this.properties).length>0){e+=" ";let t=true;for(const i in this.properties){if(this.properties.hasOwnProperty(i)){const n=this.properties[i];if(n){if(t){t=false}else{e+=","}e+=`${i}=${escapeProperty(n)}`}}}}e+=`${m}${escapeData(this.message)}`;return e}}function escapeData(e){return utils_toCommandValue(e).replace(/%/g,"%25").replace(/\r/g,"%0D").replace(/\n/g,"%0A")}function escapeProperty(e){return utils_toCommandValue(e).replace(/%/g,"%25").replace(/\r/g,"%0D").replace(/\n/g,"%0A").replace(/:/g,"%3A").replace(/,/g,"%2C")}var I=__nccwpck_require__(6982);var v=__nccwpck_require__(9896);function file_command_issueFileCommand(e,t){const i=process.env[`GITHUB_${e}`];if(!i){throw new Error(`Unable to find environment variable for file command ${e}`)}if(!fs.existsSync(i)){throw new Error(`Missing file at path: ${i}`)}fs.appendFileSync(i,`${toCommandValue(t)}${os.EOL}`,{encoding:"utf8"})}function file_command_prepareKeyValueMessage(e,t){const i=`ghadelimiter_${crypto.randomUUID()}`;const n=toCommandValue(t);if(e.includes(i)){throw new Error(`Unexpected input: name should not contain the delimiter "${i}"`)}if(n.includes(i)){throw new Error(`Unexpected input: value should not contain the delimiter "${i}"`)}return`${e}<<${i}${os.EOL}${n}${os.EOL}${i}`}var E=__nccwpck_require__(6928);var C=__nccwpck_require__(8611);var T=__nccwpck_require__(5692);function getProxyUrl(e){const t=e.protocol==="https:";if(checkBypass(e)){return undefined}const i=(()=>{if(t){return process.env["https_proxy"]||process.env["HTTPS_PROXY"]}else{return process.env["http_proxy"]||process.env["HTTP_PROXY"]}})();if(i){try{return new DecodedURL(i)}catch(e){if(!i.startsWith("http://")&&!i.startsWith("https://"))return new DecodedURL(`http://${i}`)}}else{return undefined}}function checkBypass(e){if(!e.hostname){return false}const t=e.hostname;if(isLoopbackAddress(t)){return true}const i=process.env["no_proxy"]||process.env["NO_PROXY"]||"";if(!i){return false}let n;if(e.port){n=Number(e.port)}else if(e.protocol==="http:"){n=80}else if(e.protocol==="https:"){n=443}const r=[e.hostname.toUpperCase()];if(typeof n==="number"){r.push(`${r[0]}:${n}`)}for(const e of i.split(",").map((e=>e.trim().toUpperCase())).filter((e=>e))){if(e==="*"||r.some((t=>t===e||t.endsWith(`.${e}`)||e.startsWith(".")&&t.endsWith(`${e}`)))){return true}}return false}function isLoopbackAddress(e){const t=e.toLowerCase();return t==="localhost"||t.startsWith("127.")||t.startsWith("[::1]")||t.startsWith("[0:0:0:0:0:0:0:1]")}class DecodedURL extends URL{constructor(e,t){super(e,t);this._decodedUsername=decodeURIComponent(super.username);this._decodedPassword=decodeURIComponent(super.password)}get username(){return this._decodedUsername}get password(){return this._decodedPassword}}var R=__nccwpck_require__(770);var b=__nccwpck_require__(6752);var w=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};var B;(function(e){e[e["OK"]=200]="OK";e[e["MultipleChoices"]=300]="MultipleChoices";e[e["MovedPermanently"]=301]="MovedPermanently";e[e["ResourceMoved"]=302]="ResourceMoved";e[e["SeeOther"]=303]="SeeOther";e[e["NotModified"]=304]="NotModified";e[e["UseProxy"]=305]="UseProxy";e[e["SwitchProxy"]=306]="SwitchProxy";e[e["TemporaryRedirect"]=307]="TemporaryRedirect";e[e["PermanentRedirect"]=308]="PermanentRedirect";e[e["BadRequest"]=400]="BadRequest";e[e["Unauthorized"]=401]="Unauthorized";e[e["PaymentRequired"]=402]="PaymentRequired";e[e["Forbidden"]=403]="Forbidden";e[e["NotFound"]=404]="NotFound";e[e["MethodNotAllowed"]=405]="MethodNotAllowed";e[e["NotAcceptable"]=406]="NotAcceptable";e[e["ProxyAuthenticationRequired"]=407]="ProxyAuthenticationRequired";e[e["RequestTimeout"]=408]="RequestTimeout";e[e["Conflict"]=409]="Conflict";e[e["Gone"]=410]="Gone";e[e["TooManyRequests"]=429]="TooManyRequests";e[e["InternalServerError"]=500]="InternalServerError";e[e["NotImplemented"]=501]="NotImplemented";e[e["BadGateway"]=502]="BadGateway";e[e["ServiceUnavailable"]=503]="ServiceUnavailable";e[e["GatewayTimeout"]=504]="GatewayTimeout"})(B||(B={}));var D;(function(e){e["Accept"]="accept";e["ContentType"]="content-type"})(D||(D={}));var S;(function(e){e["ApplicationJson"]="application/json"})(S||(S={}));function lib_getProxyUrl(e){const t=pm.getProxyUrl(new URL(e));return t?t.href:""}const k=[B.MovedPermanently,B.ResourceMoved,B.SeeOther,B.TemporaryRedirect,B.PermanentRedirect];const P=[B.BadGateway,B.ServiceUnavailable,B.GatewayTimeout];const U=null&&["OPTIONS","GET","DELETE","HEAD"];const V=10;const F=5;class HttpClientError extends Error{constructor(e,t){super(e);this.name="HttpClientError";this.statusCode=t;Object.setPrototypeOf(this,HttpClientError.prototype)}}class HttpClientResponse{constructor(e){this.message=e}readBody(){return w(this,void 0,void 0,(function*(){return new Promise((e=>w(this,void 0,void 0,(function*(){let t=Buffer.alloc(0);this.message.on("data",(e=>{t=Buffer.concat([t,e])}));this.message.on("end",(()=>{e(t.toString())}))}))))}))}readBodyBuffer(){return w(this,void 0,void 0,(function*(){return new Promise((e=>w(this,void 0,void 0,(function*(){const t=[];this.message.on("data",(e=>{t.push(e)}));this.message.on("end",(()=>{e(Buffer.concat(t))}))}))))}))}}function isHttps(e){const t=new URL(e);return t.protocol==="https:"}class lib_HttpClient{constructor(e,t,i){this._ignoreSslError=false;this._allowRedirects=true;this._allowRedirectDowngrade=false;this._maxRedirects=50;this._allowRetries=false;this._maxRetries=1;this._keepAlive=false;this._disposed=false;this.userAgent=this._getUserAgentWithOrchestrationId(e);this.handlers=t||[];this.requestOptions=i;if(i){if(i.ignoreSslError!=null){this._ignoreSslError=i.ignoreSslError}this._socketTimeout=i.socketTimeout;if(i.allowRedirects!=null){this._allowRedirects=i.allowRedirects}if(i.allowRedirectDowngrade!=null){this._allowRedirectDowngrade=i.allowRedirectDowngrade}if(i.maxRedirects!=null){this._maxRedirects=Math.max(i.maxRedirects,0)}if(i.keepAlive!=null){this._keepAlive=i.keepAlive}if(i.allowRetries!=null){this._allowRetries=i.allowRetries}if(i.maxRetries!=null){this._maxRetries=i.maxRetries}}}options(e,t){return w(this,void 0,void 0,(function*(){return this.request("OPTIONS",e,null,t||{})}))}get(e,t){return w(this,void 0,void 0,(function*(){return this.request("GET",e,null,t||{})}))}del(e,t){return w(this,void 0,void 0,(function*(){return this.request("DELETE",e,null,t||{})}))}post(e,t,i){return w(this,void 0,void 0,(function*(){return this.request("POST",e,t,i||{})}))}patch(e,t,i){return w(this,void 0,void 0,(function*(){return this.request("PATCH",e,t,i||{})}))}put(e,t,i){return w(this,void 0,void 0,(function*(){return this.request("PUT",e,t,i||{})}))}head(e,t){return w(this,void 0,void 0,(function*(){return this.request("HEAD",e,null,t||{})}))}sendStream(e,t,i,n){return w(this,void 0,void 0,(function*(){return this.request(e,t,i,n)}))}getJson(e){return w(this,arguments,void 0,(function*(e,t={}){t[D.Accept]=this._getExistingOrDefaultHeader(t,D.Accept,S.ApplicationJson);const i=yield this.get(e,t);return this._processResponse(i,this.requestOptions)}))}postJson(e,t){return w(this,arguments,void 0,(function*(e,t,i={}){const n=JSON.stringify(t,null,2);i[D.Accept]=this._getExistingOrDefaultHeader(i,D.Accept,S.ApplicationJson);i[D.ContentType]=this._getExistingOrDefaultContentTypeHeader(i,S.ApplicationJson);const r=yield this.post(e,n,i);return this._processResponse(r,this.requestOptions)}))}putJson(e,t){return w(this,arguments,void 0,(function*(e,t,i={}){const n=JSON.stringify(t,null,2);i[D.Accept]=this._getExistingOrDefaultHeader(i,D.Accept,S.ApplicationJson);i[D.ContentType]=this._getExistingOrDefaultContentTypeHeader(i,S.ApplicationJson);const r=yield this.put(e,n,i);return this._processResponse(r,this.requestOptions)}))}patchJson(e,t){return w(this,arguments,void 0,(function*(e,t,i={}){const n=JSON.stringify(t,null,2);i[D.Accept]=this._getExistingOrDefaultHeader(i,D.Accept,S.ApplicationJson);i[D.ContentType]=this._getExistingOrDefaultContentTypeHeader(i,S.ApplicationJson);const r=yield this.patch(e,n,i);return this._processResponse(r,this.requestOptions)}))}request(e,t,i,n){return w(this,void 0,void 0,(function*(){if(this._disposed){throw new Error("Client has already been disposed.")}const r=new URL(t);let s=this._prepareRequest(e,r,n);const o=this._allowRetries&&U.includes(e)?this._maxRetries+1:1;let a=0;let l;do{l=yield this.requestRaw(s,i);if(l&&l.message&&l.message.statusCode===B.Unauthorized){let e;for(const t of this.handlers){if(t.canHandleAuthentication(l)){e=t;break}}if(e){return e.handleAuthentication(this,s,i)}else{return l}}let t=this._maxRedirects;while(l.message.statusCode&&k.includes(l.message.statusCode)&&this._allowRedirects&&t>0){const o=l.message.headers["location"];if(!o){break}const a=new URL(o);if(r.protocol==="https:"&&r.protocol!==a.protocol&&!this._allowRedirectDowngrade){throw new Error("Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.")}yield l.readBody();if(a.hostname!==r.hostname){for(const e in n){if(e.toLowerCase()==="authorization"){delete n[e]}}}s=this._prepareRequest(e,a,n);l=yield this.requestRaw(s,i);t--}if(!l.message.statusCode||!P.includes(l.message.statusCode)){return l}a+=1;if(a{function callbackForResult(e,t){if(e){n(e)}else if(!t){n(new Error("Unknown error"))}else{i(t)}}this.requestRawWithCallback(e,t,callbackForResult)}))}))}requestRawWithCallback(e,t,i){if(typeof t==="string"){if(!e.options.headers){e.options.headers={}}e.options.headers["Content-Length"]=Buffer.byteLength(t,"utf8")}let n=false;function handleResult(e,t){if(!n){n=true;i(e,t)}}const r=e.httpModule.request(e.options,(e=>{const t=new HttpClientResponse(e);handleResult(undefined,t)}));let s;r.on("socket",(e=>{s=e}));r.setTimeout(this._socketTimeout||3*6e4,(()=>{if(s){s.end()}handleResult(new Error(`Request timeout: ${e.options.path}`))}));r.on("error",(function(e){handleResult(e)}));if(t&&typeof t==="string"){r.write(t,"utf8")}if(t&&typeof t!=="string"){t.on("close",(function(){r.end()}));t.pipe(r)}else{r.end()}}getAgent(e){const t=new URL(e);return this._getAgent(t)}getAgentDispatcher(e){const t=new URL(e);const i=pm.getProxyUrl(t);const n=i&&i.hostname;if(!n){return}return this._getProxyAgentDispatcher(t,i)}_prepareRequest(e,t,i){const n={};n.parsedUrl=t;const r=n.parsedUrl.protocol==="https:";n.httpModule=r?https:http;const s=r?443:80;n.options={};n.options.host=n.parsedUrl.hostname;n.options.port=n.parsedUrl.port?parseInt(n.parsedUrl.port):s;n.options.path=(n.parsedUrl.pathname||"")+(n.parsedUrl.search||"");n.options.method=e;n.options.headers=this._mergeHeaders(i);if(this.userAgent!=null){n.options.headers["user-agent"]=this.userAgent}n.options.agent=this._getAgent(n.parsedUrl);if(this.handlers){for(const e of this.handlers){e.prepareRequest(n.options)}}return n}_mergeHeaders(e){if(this.requestOptions&&this.requestOptions.headers){return Object.assign({},lowercaseKeys(this.requestOptions.headers),lowercaseKeys(e||{}))}return lowercaseKeys(e||{})}_getExistingOrDefaultHeader(e,t,i){let n;if(this.requestOptions&&this.requestOptions.headers){const e=lowercaseKeys(this.requestOptions.headers)[t];if(e){n=typeof e==="number"?e.toString():e}}const r=e[t];if(r!==undefined){return typeof r==="number"?r.toString():r}if(n!==undefined){return n}return i}_getExistingOrDefaultContentTypeHeader(e,t){let i;if(this.requestOptions&&this.requestOptions.headers){const e=lowercaseKeys(this.requestOptions.headers)[D.ContentType];if(e){if(typeof e==="number"){i=String(e)}else if(Array.isArray(e)){i=e.join(", ")}else{i=e}}}const n=e[D.ContentType];if(n!==undefined){if(typeof n==="number"){return String(n)}else if(Array.isArray(n)){return n.join(", ")}else{return n}}if(i!==undefined){return i}return t}_getAgent(e){let t;const i=pm.getProxyUrl(e);const n=i&&i.hostname;if(this._keepAlive&&n){t=this._proxyAgent}if(!n){t=this._agent}if(t){return t}const r=e.protocol==="https:";let s=100;if(this.requestOptions){s=this.requestOptions.maxSockets||http.globalAgent.maxSockets}if(i&&i.hostname){const e={maxSockets:s,keepAlive:this._keepAlive,proxy:Object.assign(Object.assign({},(i.username||i.password)&&{proxyAuth:`${i.username}:${i.password}`}),{host:i.hostname,port:i.port})};let n;const o=i.protocol==="https:";if(r){n=o?tunnel.httpsOverHttps:tunnel.httpsOverHttp}else{n=o?tunnel.httpOverHttps:tunnel.httpOverHttp}t=n(e);this._proxyAgent=t}if(!t){const e={keepAlive:this._keepAlive,maxSockets:s};t=r?new https.Agent(e):new http.Agent(e);this._agent=t}if(r&&this._ignoreSslError){t.options=Object.assign(t.options||{},{rejectUnauthorized:false})}return t}_getProxyAgentDispatcher(e,t){let i;if(this._keepAlive){i=this._proxyAgentDispatcher}if(i){return i}const n=e.protocol==="https:";i=new ProxyAgent(Object.assign({uri:t.href,pipelining:!this._keepAlive?0:1},(t.username||t.password)&&{token:`Basic ${Buffer.from(`${t.username}:${t.password}`).toString("base64")}`}));this._proxyAgentDispatcher=i;if(n&&this._ignoreSslError){i.options=Object.assign(i.options.requestTls||{},{rejectUnauthorized:false})}return i}_getUserAgentWithOrchestrationId(e){const t=e||"actions/http-client";const i=process.env["ACTIONS_ORCHESTRATION_ID"];if(i){const e=i.replace(/[^a-z0-9_.-]/gi,"_");return`${t} actions_orchestration_id/${e}`}return t}_performExponentialBackoff(e){return w(this,void 0,void 0,(function*(){e=Math.min(V,e);const t=F*Math.pow(2,e);return new Promise((e=>setTimeout((()=>e()),t)))}))}_processResponse(e,t){return w(this,void 0,void 0,(function*(){return new Promise(((i,n)=>w(this,void 0,void 0,(function*(){const r=e.message.statusCode||0;const s={statusCode:r,result:null,headers:{}};if(r===B.NotFound){i(s)}function dateTimeDeserializer(e,t){if(typeof t==="string"){const e=new Date(t);if(!isNaN(e.valueOf())){return e}}return t}let o;let a;try{a=yield e.readBody();if(a&&a.length>0){if(t&&t.deserializeDates){o=JSON.parse(a,dateTimeDeserializer)}else{o=JSON.parse(a)}s.result=o}s.headers=e.message.headers}catch(e){}if(r>299){let e;if(o&&o.message){e=o.message}else if(a&&a.length>0){e=a}else{e=`Failed request: (${r})`}const t=new HttpClientError(e,r);t.result=s.result;n(t)}else{i(s)}}))))}))}}const lowercaseKeys=e=>Object.keys(e).reduce(((t,i)=>(t[i.toLowerCase()]=e[i],t)),{});var O=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};class BasicCredentialHandler{constructor(e,t){this.username=e;this.password=t}prepareRequest(e){if(!e.headers){throw Error("The request has no headers")}e.headers["Authorization"]=`Basic ${Buffer.from(`${this.username}:${this.password}`).toString("base64")}`}canHandleAuthentication(){return false}handleAuthentication(){return O(this,void 0,void 0,(function*(){throw new Error("not implemented")}))}}class auth_BearerCredentialHandler{constructor(e){this.token=e}prepareRequest(e){if(!e.headers){throw Error("The request has no headers")}e.headers["Authorization"]=`Bearer ${this.token}`}canHandleAuthentication(){return false}handleAuthentication(){return O(this,void 0,void 0,(function*(){throw new Error("not implemented")}))}}class PersonalAccessTokenCredentialHandler{constructor(e){this.token=e}prepareRequest(e){if(!e.headers){throw Error("The request has no headers")}e.headers["Authorization"]=`Basic ${Buffer.from(`PAT:${this.token}`).toString("base64")}`}canHandleAuthentication(){return false}handleAuthentication(){return O(this,void 0,void 0,(function*(){throw new Error("not implemented")}))}}var N=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};class oidc_utils_OidcClient{static createHttpClient(e=true,t=10){const i={allowRetries:e,maxRetries:t};return new HttpClient("actions/oidc-client",[new BearerCredentialHandler(oidc_utils_OidcClient.getRequestToken())],i)}static getRequestToken(){const e=process.env["ACTIONS_ID_TOKEN_REQUEST_TOKEN"];if(!e){throw new Error("Unable to get ACTIONS_ID_TOKEN_REQUEST_TOKEN env variable")}return e}static getIDTokenUrl(){const e=process.env["ACTIONS_ID_TOKEN_REQUEST_URL"];if(!e){throw new Error("Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable")}return e}static getCall(e){return N(this,void 0,void 0,(function*(){var t;const i=oidc_utils_OidcClient.createHttpClient();const n=yield i.getJson(e).catch((e=>{throw new Error(`Failed to get ID Token. \n \n Error Code : ${e.statusCode}\n \n Error Message: ${e.message}`)}));const r=(t=n.result)===null||t===void 0?void 0:t.value;if(!r){throw new Error("Response json body do not have ID Token field")}return r}))}static getIDToken(e){return N(this,void 0,void 0,(function*(){try{let t=oidc_utils_OidcClient.getIDTokenUrl();if(e){const i=encodeURIComponent(e);t=`${t}&audience=${i}`}debug(`ID token url is ${t}`);const i=yield oidc_utils_OidcClient.getCall(t);setSecret(i);return i}catch(e){throw new Error(`Error message: ${e.message}`)}}))}}var q=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};const{access:_,appendFile:M,writeFile:L}=v.promises;const G="GITHUB_STEP_SUMMARY";const j="https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary";class Summary{constructor(){this._buffer=""}filePath(){return q(this,void 0,void 0,(function*(){if(this._filePath){return this._filePath}const e=process.env[G];if(!e){throw new Error(`Unable to find environment variable for $${G}. Check if your runtime environment supports job summaries.`)}try{yield _(e,v.constants.R_OK|v.constants.W_OK)}catch(t){throw new Error(`Unable to access summary file: '${e}'. Check if the file has correct read/write permissions.`)}this._filePath=e;return this._filePath}))}wrap(e,t,i={}){const n=Object.entries(i).map((([e,t])=>` ${e}="${t}"`)).join("");if(!t){return`<${e}${n}>`}return`<${e}${n}>${t}`}write(e){return q(this,void 0,void 0,(function*(){const t=!!(e===null||e===void 0?void 0:e.overwrite);const i=yield this.filePath();const n=t?L:M;yield n(i,this._buffer,{encoding:"utf8"});return this.emptyBuffer()}))}clear(){return q(this,void 0,void 0,(function*(){return this.emptyBuffer().write({overwrite:true})}))}stringify(){return this._buffer}isEmptyBuffer(){return this._buffer.length===0}emptyBuffer(){this._buffer="";return this}addRaw(e,t=false){this._buffer+=e;return t?this.addEOL():this}addEOL(){return this.addRaw(y.EOL)}addCodeBlock(e,t){const i=Object.assign({},t&&{lang:t});const n=this.wrap("pre",this.wrap("code",e),i);return this.addRaw(n).addEOL()}addList(e,t=false){const i=t?"ol":"ul";const n=e.map((e=>this.wrap("li",e))).join("");const r=this.wrap(i,n);return this.addRaw(r).addEOL()}addTable(e){const t=e.map((e=>{const t=e.map((e=>{if(typeof e==="string"){return this.wrap("td",e)}const{header:t,data:i,colspan:n,rowspan:r}=e;const s=t?"th":"td";const o=Object.assign(Object.assign({},n&&{colspan:n}),r&&{rowspan:r});return this.wrap(s,i,o)})).join("");return this.wrap("tr",t)})).join("");const i=this.wrap("table",t);return this.addRaw(i).addEOL()}addDetails(e,t){const i=this.wrap("details",this.wrap("summary",e)+t);return this.addRaw(i).addEOL()}addImage(e,t,i){const{width:n,height:r}=i||{};const s=Object.assign(Object.assign({},n&&{width:n}),r&&{height:r});const o=this.wrap("img",null,Object.assign({src:e,alt:t},s));return this.addRaw(o).addEOL()}addHeading(e,t){const i=`h${t}`;const n=["h1","h2","h3","h4","h5","h6"].includes(i)?i:"h1";const r=this.wrap(n,e);return this.addRaw(r).addEOL()}addSeparator(){const e=this.wrap("hr",null);return this.addRaw(e).addEOL()}addBreak(){const e=this.wrap("br",null);return this.addRaw(e).addEOL()}addQuote(e,t){const i=Object.assign({},t&&{cite:t});const n=this.wrap("blockquote",e,i);return this.addRaw(n).addEOL()}addLink(e,t){const i=this.wrap("a",e,{href:t});return this.addRaw(i).addEOL()}}const x=new Summary;const H=null&&x;const W=null&&x;function toPosixPath(e){return e.replace(/[\\]/g,"/")}function toWin32Path(e){return e.replace(/[/]/g,"\\")}function toPlatformPath(e){return e.replace(/[/\\]/g,path.sep)}var Y=__nccwpck_require__(3193);var J=__nccwpck_require__(4434);var z=__nccwpck_require__(5317);var $=__nccwpck_require__(2613);var K=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};const{chmod:Z,copyFile:X,lstat:ee,mkdir:te,open:ie,readdir:ne,rename:re,rm:se,rmdir:oe,stat:ae,symlink:le,unlink:ue}=v.promises;const ce=process.platform==="win32";function readlink(e){return K(this,void 0,void 0,(function*(){const t=yield fs.promises.readlink(e);if(ce&&!t.endsWith("\\")){return`${t}\\`}return t}))}const de=268435456;const pe=v.constants.O_RDONLY;function exists(e){return K(this,void 0,void 0,(function*(){try{yield ae(e)}catch(e){if(e.code==="ENOENT"){return false}throw e}return true}))}function isDirectory(e){return K(this,arguments,void 0,(function*(e,t=false){const i=t?yield ae(e):yield ee(e);return i.isDirectory()}))}function isRooted(e){e=normalizeSeparators(e);if(!e){throw new Error('isRooted() parameter "p" cannot be empty')}if(ce){return e.startsWith("\\")||/^[A-Z]:/i.test(e)}return e.startsWith("/")}function tryGetExecutablePath(e,t){return K(this,void 0,void 0,(function*(){let i=undefined;try{i=yield ae(e)}catch(t){if(t.code!=="ENOENT"){console.log(`Unexpected error attempting to determine if executable file exists '${e}': ${t}`)}}if(i&&i.isFile()){if(ce){const i=E.extname(e).toUpperCase();if(t.some((e=>e.toUpperCase()===i))){return e}}else{if(isUnixExecutable(i)){return e}}}const n=e;for(const r of t){e=n+r;i=undefined;try{i=yield ae(e)}catch(t){if(t.code!=="ENOENT"){console.log(`Unexpected error attempting to determine if executable file exists '${e}': ${t}`)}}if(i&&i.isFile()){if(ce){try{const t=E.dirname(e);const i=E.basename(e).toUpperCase();for(const n of yield ne(t)){if(i===n.toUpperCase()){e=E.join(t,n);break}}}catch(t){console.log(`Unexpected error attempting to determine the actual case of the file '${e}': ${t}`)}return e}else{if(isUnixExecutable(i)){return e}}}}return""}))}function normalizeSeparators(e){e=e||"";if(ce){e=e.replace(/\//g,"\\");return e.replace(/\\\\+/g,"\\")}return e.replace(/\/\/+/g,"/")}function isUnixExecutable(e){return(e.mode&1)>0||(e.mode&8)>0&&process.getgid!==undefined&&e.gid===process.getgid()||(e.mode&64)>0&&process.getuid!==undefined&&e.uid===process.getuid()}function getCmdPath(){var e;return(e=process.env["COMSPEC"])!==null&&e!==void 0?e:`cmd.exe`}var Ae=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};function cp(e,t){return Ae(this,arguments,void 0,(function*(e,t,i={}){const{force:n,recursive:r,copySourceDirectory:s}=readCopyOptions(i);const o=(yield ioUtil.exists(t))?yield ioUtil.stat(t):null;if(o&&o.isFile()&&!n){return}const a=o&&o.isDirectory()&&s?path.join(t,path.basename(e)):t;if(!(yield ioUtil.exists(e))){throw new Error(`no such file or directory: ${e}`)}const l=yield ioUtil.stat(e);if(l.isDirectory()){if(!r){throw new Error(`Failed to copy. ${e} is a directory, but tried to copy without recursive flag.`)}else{yield cpDirRecursive(e,a,0,n)}}else{if(path.relative(e,a)===""){throw new Error(`'${a}' and '${e}' are the same file`)}yield io_copyFile(e,a,n)}}))}function mv(e,t){return Ae(this,arguments,void 0,(function*(e,t,i={}){if(yield ioUtil.exists(t)){let n=true;if(yield ioUtil.isDirectory(t)){t=path.join(t,path.basename(e));n=yield ioUtil.exists(t)}if(n){if(i.force==null||i.force){yield rmRF(t)}else{throw new Error("Destination already exists")}}}yield mkdirP(path.dirname(t));yield ioUtil.rename(e,t)}))}function rmRF(e){return Ae(this,void 0,void 0,(function*(){if(ioUtil.IS_WINDOWS){if(/[*"<>|]/.test(e)){throw new Error('File path must not contain `*`, `"`, `<`, `>` or `|` on Windows')}}try{yield ioUtil.rm(e,{force:true,maxRetries:3,recursive:true,retryDelay:300})}catch(e){throw new Error(`File was unable to be removed ${e}`)}}))}function mkdirP(e){return Ae(this,void 0,void 0,(function*(){ok(e,"a path argument must be provided");yield ioUtil.mkdir(e,{recursive:true})}))}function which(e,t){return Ae(this,void 0,void 0,(function*(){if(!e){throw new Error("parameter 'tool' is required")}if(t){const t=yield which(e,false);if(!t){if(ce){throw new Error(`Unable to locate executable file: ${e}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.`)}else{throw new Error(`Unable to locate executable file: ${e}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.`)}}return t}const i=yield findInPath(e);if(i&&i.length>0){return i[0]}return""}))}function findInPath(e){return Ae(this,void 0,void 0,(function*(){if(!e){throw new Error("parameter 'tool' is required")}const t=[];if(ce&&process.env["PATHEXT"]){for(const e of process.env["PATHEXT"].split(E.delimiter)){if(e){t.push(e)}}}if(isRooted(e)){const i=yield tryGetExecutablePath(e,t);if(i){return[i]}return[]}if(e.includes(E.sep)){return[]}const i=[];if(process.env.PATH){for(const e of process.env.PATH.split(E.delimiter)){if(e){i.push(e)}}}const n=[];for(const r of i){const i=yield tryGetExecutablePath(E.join(r,e),t);if(i){n.push(i)}}return n}))}function readCopyOptions(e){const t=e.force==null?true:e.force;const i=Boolean(e.recursive);const n=e.copySourceDirectory==null?true:Boolean(e.copySourceDirectory);return{force:t,recursive:i,copySourceDirectory:n}}function cpDirRecursive(e,t,i,n){return Ae(this,void 0,void 0,(function*(){if(i>=255)return;i++;yield mkdirP(t);const r=yield ioUtil.readdir(e);for(const s of r){const r=`${e}/${s}`;const o=`${t}/${s}`;const a=yield ioUtil.lstat(r);if(a.isDirectory()){yield cpDirRecursive(r,o,i,n)}else{yield io_copyFile(r,o,n)}}yield ioUtil.chmod(t,(yield ioUtil.stat(e)).mode)}))}function io_copyFile(e,t,i){return Ae(this,void 0,void 0,(function*(){if((yield ioUtil.lstat(e)).isSymbolicLink()){try{yield ioUtil.lstat(t);yield ioUtil.unlink(t)}catch(e){if(e.code==="EPERM"){yield ioUtil.chmod(t,"0666");yield ioUtil.unlink(t)}}const i=yield ioUtil.readlink(e);yield ioUtil.symlink(i,t,ioUtil.IS_WINDOWS?"junction":null)}else if(!(yield ioUtil.exists(t))||i){yield ioUtil.copyFile(e,t)}}))}const fe=e(import.meta.url)("timers");var he=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};const ge=process.platform==="win32";class ToolRunner extends J.EventEmitter{constructor(e,t,i){super();if(!e){throw new Error("Parameter 'toolPath' cannot be null or empty.")}this.toolPath=e;this.args=t||[];this.options=i||{}}_debug(e){if(this.options.listeners&&this.options.listeners.debug){this.options.listeners.debug(e)}}_getCommandString(e,t){const i=this._getSpawnFileName();const n=this._getSpawnArgs(e);let r=t?"":"[command]";if(ge){if(this._isCmdFile()){r+=i;for(const e of n){r+=` ${e}`}}else if(e.windowsVerbatimArguments){r+=`"${i}"`;for(const e of n){r+=` ${e}`}}else{r+=this._windowsQuoteCmdArg(i);for(const e of n){r+=` ${this._windowsQuoteCmdArg(e)}`}}}else{r+=i;for(const e of n){r+=` ${e}`}}return r}_processLineBuffer(e,t,i){try{let n=t+e.toString();let r=n.indexOf(y.EOL);while(r>-1){const e=n.substring(0,r);i(e);n=n.substring(r+y.EOL.length);r=n.indexOf(y.EOL)}return n}catch(e){this._debug(`error processing line. Failed with error ${e}`);return""}}_getSpawnFileName(){if(ge){if(this._isCmdFile()){return process.env["COMSPEC"]||"cmd.exe"}}return this.toolPath}_getSpawnArgs(e){if(ge){if(this._isCmdFile()){let t=`/D /S /C "${this._windowsQuoteCmdArg(this.toolPath)}`;for(const i of this.args){t+=" ";t+=e.windowsVerbatimArguments?i:this._windowsQuoteCmdArg(i)}t+='"';return[t]}}return this.args}_endsWith(e,t){return e.endsWith(t)}_isCmdFile(){const e=this.toolPath.toUpperCase();return this._endsWith(e,".CMD")||this._endsWith(e,".BAT")}_windowsQuoteCmdArg(e){if(!this._isCmdFile()){return this._uvQuoteCmdArg(e)}if(!e){return'""'}const t=[" ","\t","&","(",")","[","]","{","}","^","=",";","!","'","+",",","`","~","|","<",">",'"'];let i=false;for(const n of e){if(t.some((e=>e===n))){i=true;break}}if(!i){return e}let n='"';let r=true;for(let t=e.length;t>0;t--){n+=e[t-1];if(r&&e[t-1]==="\\"){n+="\\"}else if(e[t-1]==='"'){r=true;n+='"'}else{r=false}}n+='"';return n.split("").reverse().join("")}_uvQuoteCmdArg(e){if(!e){return'""'}if(!e.includes(" ")&&!e.includes("\t")&&!e.includes('"')){return e}if(!e.includes('"')&&!e.includes("\\")){return`"${e}"`}let t='"';let i=true;for(let n=e.length;n>0;n--){t+=e[n-1];if(i&&e[n-1]==="\\"){t+="\\"}else if(e[n-1]==='"'){i=true;t+="\\"}else{i=false}}t+='"';return t.split("").reverse().join("")}_cloneExecOptions(e){e=e||{};const t={cwd:e.cwd||process.cwd(),env:e.env||process.env,silent:e.silent||false,windowsVerbatimArguments:e.windowsVerbatimArguments||false,failOnStdErr:e.failOnStdErr||false,ignoreReturnCode:e.ignoreReturnCode||false,delay:e.delay||1e4};t.outStream=e.outStream||process.stdout;t.errStream=e.errStream||process.stderr;return t}_getSpawnOptions(e,t){e=e||{};const i={};i.cwd=e.cwd;i.env=e.env;i["windowsVerbatimArguments"]=e.windowsVerbatimArguments||this._isCmdFile();if(e.windowsVerbatimArguments){i.argv0=`"${t}"`}return i}exec(){return he(this,void 0,void 0,(function*(){if(!isRooted(this.toolPath)&&(this.toolPath.includes("/")||ge&&this.toolPath.includes("\\"))){this.toolPath=E.resolve(process.cwd(),this.options.cwd||process.cwd(),this.toolPath)}this.toolPath=yield which(this.toolPath,true);return new Promise(((e,t)=>he(this,void 0,void 0,(function*(){this._debug(`exec tool: ${this.toolPath}`);this._debug("arguments:");for(const e of this.args){this._debug(` ${e}`)}const i=this._cloneExecOptions(this.options);if(!i.silent&&i.outStream){i.outStream.write(this._getCommandString(i)+y.EOL)}const n=new ExecState(i,this.toolPath);n.on("debug",(e=>{this._debug(e)}));if(this.options.cwd&&!(yield exists(this.options.cwd))){return t(new Error(`The cwd: ${this.options.cwd} does not exist!`))}const r=this._getSpawnFileName();const s=z.spawn(r,this._getSpawnArgs(i),this._getSpawnOptions(this.options,r));let o="";if(s.stdout){s.stdout.on("data",(e=>{if(this.options.listeners&&this.options.listeners.stdout){this.options.listeners.stdout(e)}if(!i.silent&&i.outStream){i.outStream.write(e)}o=this._processLineBuffer(e,o,(e=>{if(this.options.listeners&&this.options.listeners.stdline){this.options.listeners.stdline(e)}}))}))}let a="";if(s.stderr){s.stderr.on("data",(e=>{n.processStderr=true;if(this.options.listeners&&this.options.listeners.stderr){this.options.listeners.stderr(e)}if(!i.silent&&i.errStream&&i.outStream){const t=i.failOnStdErr?i.errStream:i.outStream;t.write(e)}a=this._processLineBuffer(e,a,(e=>{if(this.options.listeners&&this.options.listeners.errline){this.options.listeners.errline(e)}}))}))}s.on("error",(e=>{n.processError=e.message;n.processExited=true;n.processClosed=true;n.CheckComplete()}));s.on("exit",(e=>{n.processExitCode=e;n.processExited=true;this._debug(`Exit code ${e} received from tool '${this.toolPath}'`);n.CheckComplete()}));s.on("close",(e=>{n.processExitCode=e;n.processExited=true;n.processClosed=true;this._debug(`STDIO streams have closed for tool '${this.toolPath}'`);n.CheckComplete()}));n.on("done",((i,n)=>{if(o.length>0){this.emit("stdline",o)}if(a.length>0){this.emit("errline",a)}s.removeAllListeners();if(i){t(i)}else{e(n)}}));if(this.options.input){if(!s.stdin){throw new Error("child process missing stdin")}s.stdin.end(this.options.input)}}))))}))}}function argStringToArray(e){const t=[];let i=false;let n=false;let r="";function append(e){if(n&&e!=='"'){r+="\\"}r+=e;n=false}for(let s=0;s0){t.push(r);r=""}continue}append(o)}if(r.length>0){t.push(r.trim())}return t}class ExecState extends J.EventEmitter{constructor(e,t){super();this.processClosed=false;this.processError="";this.processExitCode=0;this.processExited=false;this.processStderr=false;this.delay=1e4;this.done=false;this.timeout=null;if(!t){throw new Error("toolPath must not be empty")}this.options=e;this.toolPath=t;if(e.delay){this.delay=e.delay}}CheckComplete(){if(this.done){return}if(this.processClosed){this._setResult()}else if(this.processExited){this.timeout=(0,fe.setTimeout)(ExecState.HandleTimeout,this.delay,this)}}_debug(e){this.emit("debug",e)}_setResult(){let e;if(this.processExited){if(this.processError){e=new Error(`There was an error when attempting to execute the process '${this.toolPath}'. This may indicate the process failed to start. Error: ${this.processError}`)}else if(this.processExitCode!==0&&!this.options.ignoreReturnCode){e=new Error(`The process '${this.toolPath}' failed with exit code ${this.processExitCode}`)}else if(this.processStderr&&this.options.failOnStdErr){e=new Error(`The process '${this.toolPath}' failed because one or more lines were written to the STDERR stream`)}}if(this.timeout){clearTimeout(this.timeout);this.timeout=null}this.done=true;this.emit("done",e,this.processExitCode)}static HandleTimeout(e){if(e.done){return}if(!e.processClosed&&e.processExited){const t=`The STDIO streams did not close within ${e.delay/1e3} seconds of the exit event from process '${e.toolPath}'. This may indicate a child process inherited the STDIO streams and has not yet exited.`;e._debug(t)}e._setResult()}}var ye=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};function exec_exec(e,t,i){return ye(this,void 0,void 0,(function*(){const n=argStringToArray(e);if(n.length===0){throw new Error(`Parameter 'commandLine' cannot be null or empty.`)}const r=n[0];t=n.slice(1).concat(t||[]);const s=new ToolRunner(r,t,i);return s.exec()}))}function getExecOutput(e,t,i){return ye(this,void 0,void 0,(function*(){var n,r;let s="";let o="";const a=new Y.StringDecoder("utf8");const l=new Y.StringDecoder("utf8");const u=(n=i===null||i===void 0?void 0:i.listeners)===null||n===void 0?void 0:n.stdout;const c=(r=i===null||i===void 0?void 0:i.listeners)===null||r===void 0?void 0:r.stderr;const stdErrListener=e=>{o+=l.write(e);if(c){c(e)}};const stdOutListener=e=>{s+=a.write(e);if(u){u(e)}};const d=Object.assign(Object.assign({},i===null||i===void 0?void 0:i.listeners),{stdout:stdOutListener,stderr:stdErrListener});const p=yield exec_exec(e,t,Object.assign(Object.assign({},i),{listeners:d}));s+=a.end();o+=l.end();return{exitCode:p,stdout:s,stderr:o}}))}var me=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};const getWindowsInfo=()=>me(void 0,void 0,void 0,(function*(){const{stdout:e}=yield exec.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Version"',undefined,{silent:true});const{stdout:t}=yield exec.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Caption"',undefined,{silent:true});return{name:t.trim(),version:e.trim()}}));const getMacOsInfo=()=>me(void 0,void 0,void 0,(function*(){var e,t,i,n;const{stdout:r}=yield exec.getExecOutput("sw_vers",undefined,{silent:true});const s=(t=(e=r.match(/ProductVersion:\s*(.+)/))===null||e===void 0?void 0:e[1])!==null&&t!==void 0?t:"";const o=(n=(i=r.match(/ProductName:\s*(.+)/))===null||i===void 0?void 0:i[1])!==null&&n!==void 0?n:"";return{name:o,version:s}}));const getLinuxInfo=()=>me(void 0,void 0,void 0,(function*(){const{stdout:e}=yield exec.getExecOutput("lsb_release",["-i","-r","-s"],{silent:true});const[t,i]=e.trim().split("\n");return{name:t,version:i}}));const Ie=y.platform();const ve=y.arch();const Ee=Ie==="win32";const Ce=Ie==="darwin";const Te=Ie==="linux";function getDetails(){return me(this,void 0,void 0,(function*(){return Object.assign(Object.assign({},yield Ee?getWindowsInfo():Ce?getMacOsInfo():getLinuxInfo()),{platform:Ie,arch:ve,isWindows:Ee,isMacOS:Ce,isLinux:Te})}))}var Re=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};var be;(function(e){e[e["Success"]=0]="Success";e[e["Failure"]=1]="Failure"})(be||(be={}));function exportVariable(e,t){const i=toCommandValue(t);process.env[e]=i;const n=process.env["GITHUB_ENV"]||"";if(n){return issueFileCommand("ENV",prepareKeyValueMessage(e,t))}issueCommand("set-env",{name:e},i)}function core_setSecret(e){command_issueCommand("add-mask",{},e)}function addPath(e){const t=process.env["GITHUB_PATH"]||"";if(t){issueFileCommand("PATH",e)}else{issueCommand("add-path",{},e)}process.env["PATH"]=`${e}${path.delimiter}${process.env["PATH"]}`}function getInput(e,t){const i=process.env[`INPUT_${e.replace(/ /g,"_").toUpperCase()}`]||"";if(t&&t.required&&!i){throw new Error(`Input required and not supplied: ${e}`)}if(t&&t.trimWhitespace===false){return i}return i.trim()}function getMultilineInput(e,t){const i=getInput(e,t).split("\n").filter((e=>e!==""));if(t&&t.trimWhitespace===false){return i}return i.map((e=>e.trim()))}function getBooleanInput(e,t){const i=["true","True","TRUE"];const n=["false","False","FALSE"];const r=getInput(e,t);if(i.includes(r))return true;if(n.includes(r))return false;throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${e}\n`+`Support boolean input list: \`true | True | TRUE | false | False | FALSE\``)}function setOutput(e,t){const i=process.env["GITHUB_OUTPUT"]||"";if(i){return issueFileCommand("OUTPUT",prepareKeyValueMessage(e,t))}process.stdout.write(os.EOL);issueCommand("set-output",{name:e},toCommandValue(t))}function setCommandEcho(e){issue("echo",e?"on":"off")}function setFailed(e){process.exitCode=be.Failure;error(e)}function isDebug(){return process.env["RUNNER_DEBUG"]==="1"}function core_debug(e){command_issueCommand("debug",{},e)}function error(e,t={}){command_issueCommand("error",utils_toCommandProperties(t),e instanceof Error?e.toString():e)}function warning(e,t={}){command_issueCommand("warning",utils_toCommandProperties(t),e instanceof Error?e.toString():e)}function notice(e,t={}){issueCommand("notice",toCommandProperties(t),e instanceof Error?e.toString():e)}function info(e){process.stdout.write(e+os.EOL)}function startGroup(e){issue("group",e)}function endGroup(){issue("endgroup")}function group(e,t){return Re(this,void 0,void 0,(function*(){startGroup(e);let i;try{i=yield t()}finally{endGroup()}return i}))}function saveState(e,t){const i=process.env["GITHUB_STATE"]||"";if(i){return issueFileCommand("STATE",prepareKeyValueMessage(e,t))}issueCommand("save-state",{name:e},toCommandValue(t))}function getState(e){return process.env[`STATE_${e}`]||""}function getIDToken(e){return Re(this,void 0,void 0,(function*(){return yield OidcClient.getIDToken(e)}))}class GitHubRunnerWrapper{debug(e){core_debug(e)}error(e){error(e)}async exec(e,t,i){return getExecOutput(e,t,i)}setFailed(e){setFailed(e)}setSecret(e){core_setSecret(e)}warning(e){warning(e)}}class GitInvoker{_logger;_runnerInvoker;_isInitialized=false;_targetBranch="";_pullRequestId=0;_pullRequestIdInternal="";constructor(e,t){this._logger=e;this._runnerInvoker=t}get pullRequestId(){this._logger.logDebug("* GitInvoker.pullRequestId");if(this._pullRequestId!==0){return this._pullRequestId}this._pullRequestId=validateNumber(parseInt(this.pullRequestIdInternal,u),"Pull Request ID","GitInvoker.pullRequestId");return this._pullRequestId}get pullRequestIdInternal(){this._logger.logDebug("* GitInvoker.pullRequestIdInternal");if(this._pullRequestIdInternal!==""){return this._pullRequestIdInternal}this._pullRequestIdInternal=RunnerInvoker.isGitHub?this.pullRequestIdForGitHub:this.pullRequestIdForAzurePipelines;return this._pullRequestIdInternal}get pullRequestIdForGitHub(){this._logger.logDebug("* GitInvoker.pullRequestIdForGitHub");const e=process.env.GITHUB_REF;if(typeof e==="undefined"){this._logger.logWarning("'GITHUB_REF' is undefined.");return""}const t=e.split("/");const i=t[2];if(typeof i==="undefined"){this._logger.logWarning(`'GITHUB_REF' is in an incorrect format '${e}'.`);return""}if(!/^\d+$/u.test(i)){this._logger.logWarning(`Pull request ID '${i}' from 'GITHUB_REF' is not numeric.`);return""}return i}get pullRequestIdForAzurePipelines(){this._logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines");const e=process.env.BUILD_REPOSITORY_PROVIDER;if(typeof e==="undefined"){this._logger.logWarning("'BUILD_REPOSITORY_PROVIDER' is undefined.");return""}if(e==="GitHub"||e==="GitHubEnterprise"){const e=process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER;if(typeof e==="undefined"){this._logger.logWarning("'SYSTEM_PULLREQUEST_PULLREQUESTNUMBER' is undefined.");return""}if(!/^\d+$/u.test(e)){this._logger.logWarning(`'SYSTEM_PULLREQUEST_PULLREQUESTNUMBER' is not numeric '${e}'.`);return""}return e}const t=process.env.SYSTEM_PULLREQUEST_PULLREQUESTID;if(typeof t==="undefined"){this._logger.logWarning("'SYSTEM_PULLREQUEST_PULLREQUESTID' is undefined.");return""}if(!/^\d+$/u.test(t)){this._logger.logWarning(`'SYSTEM_PULLREQUEST_PULLREQUESTID' is not numeric '${t}'.`);return""}return t}get targetBranch(){this._logger.logDebug("* GitInvoker.targetBranch");if(RunnerInvoker.isGitHub){return validateVariable("GITHUB_BASE_REF","GitInvoker.targetBranch")}const e=validateVariable("SYSTEM_PULLREQUEST_TARGETBRANCH","GitInvoker.targetBranch");const t="refs/heads/";if(e.startsWith(t)){const i=t.length;return e.substring(i)}return e}async isGitRepo(){this._logger.logDebug("* GitInvoker.isGitRepo()");try{await this.invokeGit(["rev-parse","--is-inside-work-tree"]);return true}catch{return false}}isPullRequestIdAvailable(){this._logger.logDebug("* GitInvoker.isPullRequestIdAvailable()");return!Number.isNaN(parseInt(this.pullRequestIdInternal,u))}async isGitHistoryAvailable(){this._logger.logDebug("* GitInvoker.isGitHistoryAvailable()");this.initialize();try{await this.invokeGit(["rev-parse","--branch",`origin/${this._targetBranch}...pull/${this._pullRequestIdInternal}/merge`]);return true}catch{return false}}async getDiffSummary(){this._logger.logDebug("* GitInvoker.getDiffSummary()");this.initialize();return this.invokeGit(["diff","--numstat","--ignore-all-space",`origin/${this._targetBranch}...pull/${this._pullRequestIdInternal}/merge`])}initialize(){this._logger.logDebug("* GitInvoker.initialize()");if(this._isInitialized){return}this._targetBranch=this.targetBranch;if(/[\p{Cc}\s]/u.test(this._targetBranch)){throw new TypeError(`Target branch '${this._targetBranch}' contains whitespace or control characters, which is not allowed in command-line arguments.`)}this._pullRequestIdInternal=this.pullRequestIdInternal;this._isInitialized=true}async invokeGit(e){this._logger.logDebug("* GitInvoker.invokeGit()");const t=await this._runnerInvoker.exec("git",e);if(t.exitCode!==0){throw new Error(t.stderr)}return t.stdout}}class HttpWrapper{async getUrl(e){const t=await fetch(e,{signal:AbortSignal.timeout(d)});if(!t.ok){throw new Error(`HTTP request to '${e}' failed with status ${String(t.status)} (${t.statusText}).`)}return t.text()}}const we=200;const Be=2;const Qe=1;const De=false;const Se=["**/*","!**/package-lock.json"];const ke=["**/*{{t,T}est,TEST}*","**/*{{t,T}est,TEST}*/**","**/*.{{s,S}pec,SPEC}.*","**/*.{{s,S}pec,SPEC}.*/**"];const Pe=["js","_js","bones","cjs","es","es6","frag","gs","jake","jsb","jscad","jsfl","jsm","jss","jsx","mjs","njs","pac","sjs","ssjs","xsjs","xsjslib","epj","erb","py","cgi","fcgi","gyp","gypi","lmi","py3","pyde","pyi","pyp","pyt","pyw","rpy","smk","spec","tac","wsgi","xpy","pyx","pxd","pxi","eb","numpy","numpyw","numsc","pytb","java","jsp","ts","mts","tsx","mtsx","cs","cake","csx","linq","php","aw","ctp","fcgi","inc","php3","php4","php5","phps","phpt","cpp","c++","cc","cp","cxx","h","h++","hh","hpp","hxx","inc","inl","ino","ipp","re","tcc","tpp","c","cats","h","idc","cl","opencl","upc","xbm","xpm","pm","sh","bash","bats","cgi","command","env","fcgi","ksh","tmux","tool","zsh","fish","ebuild","eclass","ps1","psd1","psm1","tcsh","csh","rb","builder","eye","fcgi","gemspec","god","jbuilder","mspec","pluginspec","podspec","prawn","rabl","rake","rbi","rbuild","rbw","rbx","ru","ruby","spec","thor","watchr"];class Inputs{_logger;_runnerInvoker;_isInitialized=false;_baseSize=0;_growthRate=0;_testFactor=0;_alwaysCloseComment=false;_fileMatchingPatterns=[];_testMatchingPatterns=[];_codeFileExtensions=new Set;constructor(e,t){this._logger=e;this._runnerInvoker=t}get baseSize(){this._logger.logDebug("* Inputs.baseSize");this.initialize();return this._baseSize}get growthRate(){this._logger.logDebug("* Inputs.growthRate");this.initialize();return this._growthRate}get testFactor(){this._logger.logDebug("* Inputs.testFactor");this.initialize();return this._testFactor}get alwaysCloseComment(){this._logger.logDebug("* Inputs.alwaysCloseComment");this.initialize();return this._alwaysCloseComment}get fileMatchingPatterns(){this._logger.logDebug("* Inputs.fileMatchingPatterns");this.initialize();return this._fileMatchingPatterns}get testMatchingPatterns(){this._logger.logDebug("* Inputs.testMatchingPatterns");this.initialize();return this._testMatchingPatterns}get codeFileExtensions(){this._logger.logDebug("* Inputs.codeFileExtensions");this.initialize();return this._codeFileExtensions}initialize(){this._logger.logDebug("* Inputs.initialize()");if(this._isInitialized){return}const e=this._runnerInvoker.getInput(["Base","Size"]);this.initializeBaseSize(e);const t=this._runnerInvoker.getInput(["Growth","Rate"]);this.initializeGrowthRate(t);const i=this._runnerInvoker.getInput(["Test","Factor"]);this.initializeTestFactor(i);const n=this._runnerInvoker.getInput(["Always","Close","Comment"]);this.initializeAlwaysCloseComment(n);const r=this._runnerInvoker.getInput(["File","Matching","Patterns"]);this.initializeFileMatchingPatterns(r);const s=this._runnerInvoker.getInput(["Test","Matching","Patterns"]);this.initializeTestMatchingPatterns(s);const o=this._runnerInvoker.getInput(["Code","File","Extensions"]);this.initializeCodeFileExtensions(o);this._isInitialized=true}initializeBaseSize(e){this._logger.logDebug("* Inputs.initializeBaseSize()");const t=e===null?NaN:parseInt(e,u);if(!Number.isNaN(t)&&t>0){this._baseSize=t;const e=this._baseSize.toLocaleString();this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.settingBaseSize",e));return}const i=we.toLocaleString();this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.adjustingBaseSize",i));this._baseSize=we}initializeGrowthRate(e){this._logger.logDebug("* Inputs.initializeGrowthRate()");const t=e===null?NaN:parseFloat(e);if(!Number.isNaN(t)&&Number.isFinite(t)&&t>1){this._growthRate=t;const e=this._growthRate.toLocaleString();this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.settingGrowthRate",e));return}const i=Be.toLocaleString();this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.adjustingGrowthRate",i));this._growthRate=Be}initializeTestFactor(e){this._logger.logDebug("* Inputs.initializeTestFactor()");const t=e===null?NaN:parseFloat(e);if(!Number.isNaN(t)&&Number.isFinite(t)&&t>=0){if(t===0){this._testFactor=null;this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.disablingTestFactor"))}else{this._testFactor=t;const e=this._testFactor.toLocaleString();this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.settingTestFactor",e))}return}const i=Qe.toLocaleString();this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.adjustingTestFactor",i));this._testFactor=Qe}initializeAlwaysCloseComment(e){this._logger.logDebug("* Inputs.initializeAlwaysCloseComment()");const t=e?.toLowerCase()==="true";if(t){this._alwaysCloseComment=t;this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.settingAlwaysCloseComment"));return}this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.adjustingAlwaysCloseComment"));this._alwaysCloseComment=De}initializeFileMatchingPatterns(e){this._logger.logDebug("* Inputs.initializeFileMatchingPatterns()");this._fileMatchingPatterns=this.initializeMatchingPatterns(e,Se,(e=>this._runnerInvoker.loc("metrics.inputs.settingFileMatchingPatterns",e)),(e=>this._runnerInvoker.loc("metrics.inputs.adjustingFileMatchingPatterns",e)))}initializeTestMatchingPatterns(e){this._logger.logDebug("* Inputs.initializeTestMatchingPatterns()");this._testMatchingPatterns=this.initializeMatchingPatterns(e,ke,(e=>this._runnerInvoker.loc("metrics.inputs.settingTestMatchingPatterns",e)),(e=>this._runnerInvoker.loc("metrics.inputs.adjustingTestMatchingPatterns",e)))}initializeMatchingPatterns(e,t,i,n){this._logger.logDebug("* Inputs.initializeMatchingPatterns()");if(e!==null&&e.trim()!==""){const t=e.replace(/\\/gu,"/").replace(/\n$/gu,"").split("\n").map((e=>e.trim())).filter((e=>e!==""));if(t.length>p){this._logger.logWarning(`The matching pattern count '${t.length.toLocaleString()}' exceeds the maximum '${p.toLocaleString()}'. Using only the first '${p.toLocaleString()}'.`);t.length=p}const n=JSON.stringify(t);this._logger.logInfo(i(n));return t}const r=JSON.stringify(t);this._logger.logInfo(n(r));return t}initializeCodeFileExtensions(e){this._logger.logDebug("* Inputs.initializeCodeFileExtensions()");if(e!==null&&e.trim()!==""){const t="*.";const i=".";const n=e.replace(/\n$/gu,"").split("\n").map((e=>e.trim())).filter((e=>e!==""));for(const e of n){let n=e;if(n.startsWith(t)){n=n.substring(t.length)}else if(n.startsWith(i)){n=n.substring(i.length)}this._codeFileExtensions.add(n.toLowerCase())}this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.settingCodeFileExtensions",JSON.stringify([...this._codeFileExtensions])));return}this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.adjustingCodeFileExtensions",JSON.stringify(Pe)));this._codeFileExtensions=new Set(Pe)}}class Logger{static _sensitiveProperties=new Set(["AUTHORIZATION","COOKIE","PASSWORD","SECRET","TOKEN"]);_consoleWrapper;_runnerInvoker;_messages=[];constructor(e,t){this._consoleWrapper=e;this._runnerInvoker=t}static filterMessage(e){return e.replace(/##(?:vso)?\[/giu,"").replace(/[\n\r]/gu," ")}static _redactReplacer=(e,t)=>{if(e!==""&&Logger._sensitiveProperties.has(e.toUpperCase())){return"[REDACTED]"}return t};logDebug(e){const t=Logger.filterMessage(e);this._messages.push(`debug – ${t}`);this._runnerInvoker.logDebug(t)}logInfo(e){const t=Logger.filterMessage(e);this._messages.push(`info – ${t}`);this._consoleWrapper.log(t)}logWarning(e){const t=Logger.filterMessage(e);this._messages.push(`warning – ${t}`);this._runnerInvoker.logWarning(t)}logError(e){const t=Logger.filterMessage(e);this._messages.push(`error – ${t}`);this._runnerInvoker.logError(t)}logErrorObject(e){const{name:t}=e;const i=Object.getOwnPropertyNames(e);const n=e;for(const e of i){if(Logger._sensitiveProperties.has(e.toUpperCase())){this.logInfo(`${t} – ${e}: [REDACTED]`)}else{try{this.logInfo(`${t} – ${e}: ${JSON.stringify(n[e],Logger._redactReplacer)}`)}catch{this.logInfo(`${t} – ${e}: [COULD NOT SERIALIZE]`)}}}}replay(){for(const e of this._messages){this._consoleWrapper.log(`🔁 ${e}`)}}}class Context{line=1;lines=[];options={noPrefix:false};constructor(e,t){this.lines=e.split("\n");this.options.noPrefix=!!t?.noPrefix}getCurLine(){return this.lines[this.line-1]}nextLine(){this.line++;return this.getCurLine()}isEof(){return this.line>this.lines.length}}const Ue={Added:"AddedLine",Deleted:"DeletedLine",Unchanged:"UnchangedLine",Message:"MessageLine"};const Ve={Changed:"ChangedFile",Added:"AddedFile",Deleted:"DeletedFile",Renamed:"RenamedFile"};const Fe={Index:"index",OldMode:"old mode",NewMode:"new mode",Copy:"copy",Similarity:"similarity",Dissimilarity:"dissimilarity",Deleted:"deleted",NewFile:"new file",RenameFrom:"rename from",RenameTo:"rename to"};const Oe=Object.values(Fe);function parseGitDiff(e,t){const i=new Context(e,t);const n=parseFileChanges(i);return{type:"GitDiff",files:n}}function parseFileChanges(e){const t=[];while(!e.isEof()){const i=parseFileChange(e);if(!i){break}t.push(i)}return t}function parseFileChange(e){if(!isComparisonInputLine(e.getCurLine())){return}const t=parseComparisonInputLine(e);let i=false;let n=false;let r=false;let s="";let o="";let a=undefined;let l=undefined;while(!e.isEof()){const u=parseExtendedHeader(e);if(!u){break}if(u.type===Fe.Deleted){i=true;s=t?.from||""}if(u.type===Fe.NewFile){n=true;o=t?.to||""}if(u.type===Fe.RenameFrom){r=true;s=u.path}if(u.type===Fe.RenameTo){r=true;o=u.path}if(u.type===Fe.OldMode){a=u.mode}if(u.type===Fe.NewMode){l=u.mode}}const u=parseChangeMarkers(e);const c=parseChunks(e);if(i&&c.length&&c[0].type==="BinaryFilesChunk"){return{type:Ve.Deleted,chunks:c,path:c[0].pathBefore}}if(i){return{type:Ve.Deleted,chunks:c,path:u?.deleted||s}}else if(n&&c.length&&c[0].type==="BinaryFilesChunk"){return{type:Ve.Added,chunks:c,path:c[0].pathAfter}}else if(n){return{type:Ve.Added,chunks:c,path:u?.added||o}}else if(r){return{type:Ve.Renamed,pathAfter:o,pathBefore:s,chunks:c,oldMode:a,newMode:l}}else if(u){return{type:Ve.Changed,chunks:c,path:u.added,oldMode:a,newMode:l}}else if(a&&l&&t){return{type:Ve.Changed,chunks:c,path:t.to,oldMode:a,newMode:l}}else if(c.length&&c[0].type==="BinaryFilesChunk"&&c[0].pathAfter){return{type:Ve.Changed,chunks:c,path:c[0].pathAfter}}return}function isComparisonInputLine(e){return e.indexOf("diff")===0}function parseComparisonInputLine(e){const t=e.getCurLine();const[i,n]=t.split(" ").reverse();e.nextLine();if(i&&n){return{from:getFilePath(e,n,"src"),to:getFilePath(e,i,"dst")}}return null}function parseChunks(e){const t=[];while(!e.isEof()){const i=parseChunk(e);if(!i){break}t.push(i)}return t}function parseChunk(e){const t=parseChunkHeader(e);if(!t){return}if(t.type==="Normal"){const i=parseChanges(e,t.fromFileRange,t.toFileRange);return{...t,type:"Chunk",changes:i}}else if(t.type==="Combined"&&t.fromFileRangeA&&t.fromFileRangeB){const i=parseChanges(e,t.fromFileRangeA.startt.startsWith(e)));if(i){e.nextLine()}if(i===Fe.RenameFrom||i===Fe.RenameTo){return{type:i,path:t.slice(`${i} `.length)}}else if(i===Fe.OldMode||i===Fe.NewMode){return{type:i,mode:t.slice(`${i} `.length)}}else if(i){return{type:i}}return null}function parseChunkHeader(e){const t=e.getCurLine();const i=/^@@\s\-(\d+),?(\d+)?\s\+(\d+),?(\d+)?\s@@\s?(.+)?/.exec(t);if(!i){const i=/^@@@\s\-(\d+),?(\d+)?\s\-(\d+),?(\d+)?\s\+(\d+),?(\d+)?\s@@@\s?(.+)?/.exec(t);if(!i){const i=/^Binary\sfiles\s(.*)\sand\s(.*)\sdiffer$/.exec(t);if(i){const[t,n,r]=i;e.nextLine();return{type:"BinaryFiles",fileA:getFilePath(e,n,"src"),fileB:getFilePath(e,r,"dst")}}return null}const[n,r,s,o,a,l,u,c]=i;e.nextLine();return{context:c,type:"Combined",fromFileRangeA:getRange(r,s),fromFileRangeB:getRange(o,a),toFileRange:getRange(l,u)}}const[n,r,s,o,a,l]=i;e.nextLine();return{context:l,type:"Normal",toFileRange:getRange(o,a),fromFileRange:getRange(r,s)}}function getRange(e,t){const i=parseInt(e,10);return{start:i,lines:t===undefined?1:parseInt(t,10)}}function parseChangeMarkers(e){const t=parseMarker(e,"--- ");const i=t?getFilePath(e,t,"src"):t;const n=parseMarker(e,"+++ ");const r=n?getFilePath(e,n,"dst"):n;return r&&i?{added:r,deleted:i}:null}function parseMarker(e,t){const i=e.getCurLine();if(i?.startsWith(t)){e.nextLine();return i.replace(t,"")}return null}const Ne={"+":Ue.Added,"-":Ue.Deleted," ":Ue.Unchanged,"\\":Ue.Message};function parseChanges(e,t,i){const n=[];let r=t.start;let s=i.start;while(!e.isEof()){const t=e.getCurLine();const i=getLineType(t);if(!i){break}e.nextLine();let o;const a=t.slice(1);switch(i){case Ue.Added:{o={type:i,lineAfter:s++,content:a};break}case Ue.Deleted:{o={type:i,lineBefore:r++,content:a};break}case Ue.Unchanged:{o={type:i,lineBefore:r++,lineAfter:s++,content:a};break}case Ue.Message:{o={type:i,content:a.trim()};break}}n.push(o)}return n}function getLineType(e){return Ne[e[0]]||null}function getFilePath(e,t,i){if(e.options.noPrefix){return t}if(i==="src")return t.replace(/^a\//,"");if(i==="dst")return t.replace(/^b\//,"");throw new Error("Unexpected unreachable code")}const qe=parseGitDiff;class OctokitGitDiffParser{_httpWrapper;_logger;_firstLineOfFiles=null;constructor(e,t){this._httpWrapper=e;this._logger=t}async getFirstChangedLine(e,t,i,n,r){this._logger.logDebug("* OctokitGitDiffParser.getFirstChangedLine()");const s=await this.getFirstChangedLines(e,t,i,n);return s.get(r)??null}async getFirstChangedLines(e,t,i,n){this._logger.logDebug("* OctokitGitDiffParser.getFirstChangedLines()");if(this._firstLineOfFiles!==null){return this._firstLineOfFiles}const r=await this.getDiffs(e,t,i,n);this._firstLineOfFiles=this.processDiffs(r);return this._firstLineOfFiles}async getDiffs(e,t,i,n){this._logger.logDebug("* OctokitGitDiffParser.getDiffs()");const r=await e.getPull(t,i,n);const s=await this._httpWrapper.getUrl(r.data.diff_url);const o=s.split(/^diff --git/gmu);return o.slice(1).map((e=>`diff --git ${e}`))}processDiffs(e){this._logger.logDebug("* OctokitGitDiffParser.processDiffs()");const t=new Map;for(const i of e){const e=qe(i);for(const i of e.files){switch(i.type){case"AddedFile":case"ChangedFile":{const e=i;const[n]=e.chunks;if(n?.type==="BinaryFilesChunk"){this._logger.logDebug(`Skipping '${i.type}' '${e.path}' while performing diff parsing.`);break}if(n){t.set(e.path,n.toFileRange.start)}break}case"RenamedFile":{const e=i;const[n]=e.chunks;if(n?.type==="BinaryFilesChunk"){this._logger.logDebug(`Skipping '${i.type}' '${e.pathAfter}' while performing diff parsing.`);break}if(n){t.set(e.pathAfter,n.toFileRange.start)}break}case"DeletedFile":default:this._logger.logDebug(`Skipping file type '${i.type}' while performing diff parsing.`);break}}}return t}}function getUserAgent(){if(typeof navigator==="object"&&"userAgent"in navigator){return navigator.userAgent}if(typeof process==="object"&&process.version!==undefined){return`Node.js/${process.version.substr(1)} (${process.platform}; ${process.arch})`}return""}function register(e,t,i,n){if(typeof i!=="function"){throw new Error("method for before hook must be a function")}if(!n){n={}}if(Array.isArray(t)){return t.reverse().reduce(((t,i)=>register.bind(null,e,i,t,n)),i)()}return Promise.resolve().then((()=>{if(!e.registry[t]){return i(n)}return e.registry[t].reduce(((e,t)=>t.hook.bind(null,e,n)),i)()}))}function addHook(e,t,i,n){const r=n;if(!e.registry[i]){e.registry[i]=[]}if(t==="before"){n=(e,t)=>Promise.resolve().then(r.bind(null,t)).then(e.bind(null,t))}if(t==="after"){n=(e,t)=>{let i;return Promise.resolve().then(e.bind(null,t)).then((e=>{i=e;return r(i,t)})).then((()=>i))}}if(t==="error"){n=(e,t)=>Promise.resolve().then(e.bind(null,t)).catch((e=>r(e,t)))}e.registry[i].push({hook:n,orig:r})}function removeHook(e,t,i){if(!e.registry[t]){return}const n=e.registry[t].map((e=>e.orig)).indexOf(i);if(n===-1){return}e.registry[t].splice(n,1)}const _e=Function.bind;const Me=_e.bind(_e);function bindApi(e,t,i){const n=Me(removeHook,null).apply(null,i?[t,i]:[t]);e.api={remove:n};e.remove=n;["before","error","after","wrap"].forEach((n=>{const r=i?[t,n,i]:[t,n];e[n]=e.api[n]=Me(addHook,null).apply(null,r)}))}function Singular(){const e=Symbol("Singular");const t={registry:{}};const i=register.bind(null,t,e);bindApi(i,t,e);return i}function Collection(){const e={registry:{}};const t=register.bind(null,e);bindApi(t,e);return t}const Le={Singular:Singular,Collection:Collection};var Ge="0.0.0-development";var je=`octokit-endpoint.js/${Ge} ${getUserAgent()}`;var xe={method:"GET",baseUrl:"https://api.github.com",headers:{accept:"application/vnd.github.v3+json","user-agent":je},mediaType:{format:""}};function dist_bundle_lowercaseKeys(e){if(!e){return{}}return Object.keys(e).reduce(((t,i)=>{t[i.toLowerCase()]=e[i];return t}),{})}function isPlainObject(e){if(typeof e!=="object"||e===null)return false;if(Object.prototype.toString.call(e)!=="[object Object]")return false;const t=Object.getPrototypeOf(e);if(t===null)return true;const i=Object.prototype.hasOwnProperty.call(t,"constructor")&&t.constructor;return typeof i==="function"&&i instanceof i&&Function.prototype.call(i)===Function.prototype.call(e)}function mergeDeep(e,t){const i=Object.assign({},e);Object.keys(t).forEach((n=>{if(isPlainObject(t[n])){if(!(n in e))Object.assign(i,{[n]:t[n]});else i[n]=mergeDeep(e[n],t[n])}else{Object.assign(i,{[n]:t[n]})}}));return i}function removeUndefinedProperties(e){for(const t in e){if(e[t]===void 0){delete e[t]}}return e}function merge(e,t,i){if(typeof t==="string"){let[e,n]=t.split(" ");i=Object.assign(n?{method:e,url:n}:{url:e},i)}else{i=Object.assign({},t)}i.headers=dist_bundle_lowercaseKeys(i.headers);removeUndefinedProperties(i);removeUndefinedProperties(i.headers);const n=mergeDeep(e||{},i);if(i.url==="/graphql"){if(e&&e.mediaType.previews?.length){n.mediaType.previews=e.mediaType.previews.filter((e=>!n.mediaType.previews.includes(e))).concat(n.mediaType.previews)}n.mediaType.previews=(n.mediaType.previews||[]).map((e=>e.replace(/-preview/,"")))}return n}function addQueryParameters(e,t){const i=/\?/.test(e)?"&":"?";const n=Object.keys(t);if(n.length===0){return e}return e+i+n.map((e=>{if(e==="q"){return"q="+t.q.split("+").map(encodeURIComponent).join("+")}return`${e}=${encodeURIComponent(t[e])}`})).join("&")}var He=/\{[^{}}]+\}/g;function removeNonChars(e){return e.replace(/(?:^\W+)|(?:(?e.concat(t)),[])}function omit(e,t){const i={__proto__:null};for(const n of Object.keys(e)){if(t.indexOf(n)===-1){i[n]=e[n]}}return i}function encodeReserved(e){return e.split(/(%[0-9A-Fa-f]{2})/g).map((function(e){if(!/%[0-9A-Fa-f]/.test(e)){e=encodeURI(e).replace(/%5B/g,"[").replace(/%5D/g,"]")}return e})).join("")}function encodeUnreserved(e){return encodeURIComponent(e).replace(/[!'()*]/g,(function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()}))}function encodeValue(e,t,i){t=e==="+"||e==="#"?encodeReserved(t):encodeUnreserved(t);if(i){return encodeUnreserved(i)+"="+t}else{return t}}function isDefined(e){return e!==void 0&&e!==null}function isKeyOperator(e){return e===";"||e==="&"||e==="?"}function getValues(e,t,i,n){var r=e[i],s=[];if(isDefined(r)&&r!==""){if(typeof r==="string"||typeof r==="number"||typeof r==="bigint"||typeof r==="boolean"){r=r.toString();if(n&&n!=="*"){r=r.substring(0,parseInt(n,10))}s.push(encodeValue(t,r,isKeyOperator(t)?i:""))}else{if(n==="*"){if(Array.isArray(r)){r.filter(isDefined).forEach((function(e){s.push(encodeValue(t,e,isKeyOperator(t)?i:""))}))}else{Object.keys(r).forEach((function(e){if(isDefined(r[e])){s.push(encodeValue(t,r[e],e))}}))}}else{const e=[];if(Array.isArray(r)){r.filter(isDefined).forEach((function(i){e.push(encodeValue(t,i))}))}else{Object.keys(r).forEach((function(i){if(isDefined(r[i])){e.push(encodeUnreserved(i));e.push(encodeValue(t,r[i].toString()))}}))}if(isKeyOperator(t)){s.push(encodeUnreserved(i)+"="+e.join(","))}else if(e.length!==0){s.push(e.join(","))}}}}else{if(t===";"){if(isDefined(r)){s.push(encodeUnreserved(i))}}else if(r===""&&(t==="&"||t==="?")){s.push(encodeUnreserved(i)+"=")}else if(r===""){s.push("")}}return s}function parseUrl(e){return{expand:expand.bind(null,e)}}function expand(e,t){var i=["+","#",".","/",";","?","&"];e=e.replace(/\{([^\{\}]+)\}|([^\{\}]+)/g,(function(e,n,r){if(n){let e="";const r=[];if(i.indexOf(n.charAt(0))!==-1){e=n.charAt(0);n=n.substr(1)}n.split(/,/g).forEach((function(i){var n=/([^:\*]*)(?::(\d+)|(\*))?/.exec(i);r.push(getValues(t,e,n[1],n[2]||n[3]))}));if(e&&e!=="+"){var s=",";if(e==="?"){s="&"}else if(e!=="#"){s=e}return(r.length!==0?e:"")+r.join(s)}else{return r.join(",")}}else{return encodeReserved(r)}}));if(e==="/"){return e}else{return e.replace(/\/$/,"")}}function parse(e){let t=e.method.toUpperCase();let i=(e.url||"/").replace(/:([a-z]\w+)/g,"{$1}");let n=Object.assign({},e.headers);let r;let s=omit(e,["method","baseUrl","url","headers","request","mediaType"]);const o=extractUrlVariableNames(i);i=parseUrl(i).expand(s);if(!/^http/.test(i)){i=e.baseUrl+i}const a=Object.keys(e).filter((e=>o.includes(e))).concat("baseUrl");const l=omit(s,a);const u=/application\/octet-stream/i.test(n.accept);if(!u){if(e.mediaType.format){n.accept=n.accept.split(/,/).map((t=>t.replace(/application\/vnd(\.\w+)(\.v3)?(\.\w+)?(\+json)?$/,`application/vnd$1$2.${e.mediaType.format}`))).join(",")}if(i.endsWith("/graphql")){if(e.mediaType.previews?.length){const t=n.accept.match(/(?{const i=e.mediaType.format?`.${e.mediaType.format}`:"+json";return`application/vnd.github.${t}-preview${i}`})).join(",")}}}if(["GET","HEAD"].includes(t)){i=addQueryParameters(i,l)}else{if("data"in l){r=l.data}else{if(Object.keys(l).length){r=l}}}if(!n["content-type"]&&typeof r!=="undefined"){n["content-type"]="application/json; charset=utf-8"}if(["PATCH","PUT"].includes(t)&&typeof r==="undefined"){r=""}return Object.assign({method:t,url:i,headers:n},typeof r!=="undefined"?{body:r}:null,e.request?{request:e.request}:null)}function endpointWithDefaults(e,t,i){return parse(merge(e,t,i))}function withDefaults(e,t){const i=merge(e,t);const n=endpointWithDefaults.bind(null,i);return Object.assign(n,{DEFAULTS:i,defaults:withDefaults.bind(null,i),merge:merge.bind(null,i),parse:parse})}var We=withDefaults(null,xe);var Ye=__nccwpck_require__(1120);const Je=/^-?\d+$/;const ze=/^-?\d+n+$/;const $e=JSON.stringify;const Ke=JSON.parse;const Ze=/^-?\d+n$/;const Xe=/([\[:])?"(-?\d+)n"($|([\\n]|\s)*(\s|[\\n])*[,\}\]])/g;const et=/([\[:])?("-?\d+n+)n("$|"([\\n]|\s)*(\s|[\\n])*[,\}\]])/g;const JSONStringify=(e,t,i)=>{if("rawJSON"in JSON){return $e(e,((e,i)=>{if(typeof i==="bigint")return JSON.rawJSON(i.toString());if(typeof t==="function")return t(e,i);if(Array.isArray(t)&&t.includes(e))return i;return i}),i)}if(!e)return $e(e,t,i);const n=$e(e,((e,i)=>{const n=typeof i==="string"&&ze.test(i);if(n)return i.toString()+"n";if(typeof i==="bigint")return i.toString()+"n";if(typeof t==="function")return t(e,i);if(Array.isArray(t)&&t.includes(e))return i;return i}),i);const r=n.replace(Xe,"$1$2$3");const s=r.replace(et,"$1$2$3");return s};const tt=new Map;const isContextSourceSupported=()=>{const e=JSON.parse.toString();if(tt.has(e)){return tt.get(e)}try{const t=JSON.parse("1",((e,t,i)=>!!i?.source&&i.source==="1"));tt.set(e,t);return t}catch{tt.set(e,false);return false}};const convertMarkedBigIntsReviver=(e,t,i,n)=>{const r=typeof t==="string"&&Ze.test(t);if(r)return BigInt(t.slice(0,-1));const s=typeof t==="string"&&ze.test(t);if(s)return t.slice(0,-1);if(typeof n!=="function")return t;return n(e,t,i)};const JSONParseV2=(e,t)=>JSON.parse(e,((e,i,n)=>{const r=typeof i==="number"&&(i>Number.MAX_SAFE_INTEGER||i{if(!e)return Ke(e,t);if(isContextSourceSupported())return JSONParseV2(e,t);const i=e.replace(rt,((e,t,i,n)=>{const r=e[0]==='"';const s=r&&st.test(e);if(s)return e.substring(0,e.length-1)+'n"';const o=i||n;const a=t&&(t.lengthconvertMarkedBigIntsReviver(e,i,n,t)))};var ot="10.0.8";var at={headers:{"user-agent":`octokit-request.js/${ot} ${getUserAgent()}`}};function dist_bundle_isPlainObject(e){if(typeof e!=="object"||e===null)return false;if(Object.prototype.toString.call(e)!=="[object Object]")return false;const t=Object.getPrototypeOf(e);if(t===null)return true;const i=Object.prototype.hasOwnProperty.call(t,"constructor")&&t.constructor;return typeof i==="function"&&i instanceof i&&Function.prototype.call(i)===Function.prototype.call(e)}var noop=()=>"";async function fetchWrapper(e){const t=e.request?.fetch||globalThis.fetch;if(!t){throw new Error("fetch is not set. Please pass a fetch implementation as new Octokit({ request: { fetch }}). Learn more at https://github.com/octokit/octokit.js/#fetch-missing")}const i=e.request?.log||console;const n=e.request?.parseSuccessResponseBody!==false;const r=dist_bundle_isPlainObject(e.body)||Array.isArray(e.body)?JSONStringify(e.body):e.body;const s=Object.fromEntries(Object.entries(e.headers).map((([e,t])=>[e,String(t)])));let o;try{o=await t(e.url,{method:e.method,body:r,redirect:e.request?.redirect,headers:s,signal:e.request?.signal,...e.body&&{duplex:"half"}})}catch(t){let i="Unknown Error";if(t instanceof Error){if(t.name==="AbortError"){t.status=500;throw t}i=t.message;if(t.name==="TypeError"&&"cause"in t){if(t.cause instanceof Error){i=t.cause.message}else if(typeof t.cause==="string"){i=t.cause}}}const n=new RequestError(i,500,{request:e});n.cause=t;throw n}const a=o.status;const l=o.url;const u={};for(const[e,t]of o.headers){u[e]=t}const c={url:l,status:a,headers:u,data:""};if("deprecation"in u){const t=u.link&&u.link.match(/<([^<>]+)>; rel="deprecation"/);const n=t&&t.pop();i.warn(`[@octokit/request] "${e.method} ${e.url}" is deprecated. It is scheduled to be removed on ${u.sunset}${n?`. See ${n}`:""}`)}if(a===204||a===205){return c}if(e.method==="HEAD"){if(a<400){return c}throw new RequestError(o.statusText,a,{response:c,request:e})}if(a===304){c.data=await getResponseData(o);throw new RequestError("Not modified",a,{response:c,request:e})}if(a>=400){c.data=await getResponseData(o);throw new RequestError(toErrorMessage(c.data),a,{response:c,request:e})}c.data=n?await getResponseData(o):o.body;return c}async function getResponseData(e){const t=e.headers.get("content-type");if(!t){return e.text().catch(noop)}const i=(0,Ye.xL)(t);if(isJSONResponse(i)){let t="";try{t=await e.text();return JSONParse(t)}catch(e){return t}}else if(i.type.startsWith("text/")||i.parameters.charset?.toLowerCase()==="utf-8"){return e.text().catch(noop)}else{return e.arrayBuffer().catch(( +/* v8 ignore else -- @preserve -- Bug with vitest coverage where it sees an else branch that doesn't exist */if("response"in i){this.response=i.response}const n=Object.assign({},i.request);if(i.request.headers.authorization){n.headers=Object.assign({},i.request.headers,{authorization:i.request.headers.authorization.replace(/(?{const e=await this._octokitWrapper.getPull(this._owner,this._repo,this._pullRequestId);this._logger.logDebug(JSON.stringify(e));return e}));return{description:e.data.body??null,title:e.data.title}}async getComments(){this._logger.logDebug("* GitHubReposInvoker.getComments()");this.initialize();let e=null;let t=null;await Promise.all([this.invokeApiCall((async()=>{e=await this._octokitWrapper.getIssueComments(this._owner,this._repo,this._pullRequestId);this._logger.logDebug(JSON.stringify(e))})),this.invokeApiCall((async()=>{t=await this._octokitWrapper.getReviewComments(this._owner,this._repo,this._pullRequestId);this._logger.logDebug(JSON.stringify(t))}))]);return this.convertPullRequestComments(e,t)}async setTitleAndDescription(e,t){this._logger.logDebug("* GitHubReposInvoker.setTitleAndDescription()");if(e===null&&t===null){return}this.initialize();await this.invokeApiCall((async()=>{const i=await this._octokitWrapper.updatePull(this._owner,this._repo,this._pullRequestId,e,t);this._logger.logDebug(JSON.stringify(i))}))}async createComment(e,t){this._logger.logDebug("* GitHubReposInvoker.createComment()");this.initialize();if(t===null){await this.invokeApiCall((async()=>{const t=await this._octokitWrapper.createIssueComment(this._owner,this._repo,this._pullRequestId,e);this._logger.logDebug(JSON.stringify(t))}))}else{if(this._commitId===""){await this.getCommitId()}await this.invokeApiCall((async()=>{try{const i=await this._octokitWrapper.createReviewComment(this._owner,this._repo,this._pullRequestId,e,t,this._commitId);this._logger.logDebug(JSON.stringify(i))}catch(e){if(e instanceof RequestError&&e.status===l.StatusCodes.UNPROCESSABLE_ENTITY&&(e.message.includes("is too big")||e.message.includes("diff is too large"))){this._logger.logInfo("GitHub createReviewComment() threw a 422 error related to a large diff. Ignoring as this is expected.");this._logger.logErrorObject(e)}else{throw e}}}))}}async updateComment(e,t){this._logger.logDebug("* GitHubReposInvoker.updateComment()");if(t===null){return}this.initialize();await this.invokeApiCall((async()=>{const i=await this._octokitWrapper.updateIssueComment(this._owner,this._repo,this._pullRequestId,e,t);this._logger.logDebug(JSON.stringify(i))}))}async deleteCommentThread(e){this._logger.logDebug("* GitHubReposInvoker.deleteCommentThread()");this.initialize();await this.invokeApiCall((async()=>{const t=await this._octokitWrapper.deleteReviewComment(this._owner,this._repo,e);this._logger.logDebug(JSON.stringify(t))}))}async invokeApiCall(e){return BaseReposInvoker.invokeApiCall(e,this._runnerInvoker.loc("repos.gitHubReposInvoker.insufficientGitHubAccessTokenPermissions"),this._runnerInvoker.loc("repos.baseReposInvoker.resourceNotFound"))}initialize(){this._logger.logDebug("* GitHubReposInvoker.initialize()");if(this._isInitialized){return}const e={auth:process.env.PR_METRICS_ACCESS_TOKEN,log:{debug:e=>{this._logger.logDebug(`Octokit – ${e}`)},error:e=>{this._logger.logError(`Octokit – ${e}`)},info:e=>{this._logger.logInfo(`Octokit – ${e}`)},warn:e=>{this._logger.logWarning(`Octokit – ${e}`)}},userAgent:h};if(RunnerInvoker.isGitHub){e.baseUrl=this.initializeForGitHub()}else{e.baseUrl=this.initializeForAzureDevOps()}this._logger.logDebug(`Using Base URL '${e.baseUrl}'.`);this._octokitWrapper.initialize(e);this._pullRequestId=this._gitInvoker.pullRequestId;this._isInitialized=true}initializeForGitHub(){this._logger.logDebug("* GitHubReposInvoker.initializeForGitHub()");const e=validateVariable("GITHUB_API_URL","GitHubReposInvoker.initializeForGitHub()");this._owner=validateVariable("GITHUB_REPOSITORY_OWNER","GitHubReposInvoker.initializeForGitHub()");const t=validateVariable("GITHUB_REPOSITORY","GitHubReposInvoker.initializeForGitHub()");const i=t.split("/");if(typeof i[1]==="undefined"){throw new Error(`GITHUB_REPOSITORY '${t}' is in an unexpected format.`)}[,this._repo]=i;return e}initializeForAzureDevOps(){this._logger.logDebug("* GitHubReposInvoker.initializeForAzureDevOps()");const e=validateVariable("SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI","GitHubReposInvoker.initializeForAzureDevOps()");const t=e.split("/");if(typeof t[2]==="undefined"||typeof t[3]==="undefined"||typeof t[4]==="undefined"){throw new Error(`SYSTEM_PULLREQUEST_SOURCEREPOSITORYURI '${e}' is in an unexpected format.`)}let i="";let n;[,,n,this._owner,this._repo]=t;if(n!=="github.com"){i=`https://${n}/api/v3`}if(this._repo.endsWith(".git")){this._repo=this._repo.substring(0,this._repo.length-".git".length)}return i}convertPullRequestComments(e,t){this._logger.logDebug("* GitHubReposInvoker.convertPullRequestComments()");const i=new CommentData;if(e!==null){for(const t of e.data){const e=t.body;if(typeof e!=="undefined"){i.pullRequestComments.push(new PullRequestCommentData(t.id,e))}}}if(t!==null){for(const e of t.data){const t=e.body;const n=e.path;i.fileComments.push(new FileCommentData(e.id,t,n))}}return i}async getCommitId(){this._logger.logDebug("* GitHubReposInvoker.getCommitId()");let e=await this.invokeApiCall((async()=>{const e=await this._octokitWrapper.listCommits(this._owner,this._repo,this._pullRequestId,1);this._logger.logDebug(JSON.stringify(e));return e}));if(typeof e.headers.link!=="undefined"){const t=e.headers.link;const i=/<.+?page=(?\d+)>;\s*rel="last"/u.exec(t);if(typeof i?.groups?.pageNumber==="undefined"){throw new Error(`The regular expression did not match '${t}'.`)}const n=parseInt(i.groups.pageNumber,u);e=await this.invokeApiCall((async()=>{const e=await this._octokitWrapper.listCommits(this._owner,this._repo,this._pullRequestId,n);this._logger.logDebug(JSON.stringify(e));return e}))}this._commitId=validateString(e.data[e.data.length-1]?.sha,`result.data[${String(e.data.length-1)}].sha`,"GitHubReposInvoker.getCommitId()")}}const g=e(import.meta.url)("node:fs");var y=__nccwpck_require__(7975);class GitHubRunnerInvoker{_azurePipelinesRunnerWrapper;_consoleWrapper;_gitHubRunnerWrapper;_resources=new Map;constructor(e,t,i){this._azurePipelinesRunnerWrapper=e;this._consoleWrapper=t;this._gitHubRunnerWrapper=i}async exec(e,t){const i={failOnStdErr:true,silent:true};const n=await this._gitHubRunnerWrapper.exec(e,t,i);return{exitCode:n.exitCode,stderr:n.stderr,stdout:n.stdout}}getInput(e){const t=e.join("-").toUpperCase();return this._azurePipelinesRunnerWrapper.getInput(t)}getEndpointAuthorization(){throw new Error("getEndpointAuthorization() unavailable in GitHub.")}getEndpointAuthorizationScheme(){throw new Error("getEndpointAuthorizationScheme() unavailable in GitHub.")}getEndpointAuthorizationParameter(){throw new Error("getEndpointAuthorizationParameter() unavailable in GitHub.")}locInitialize(e){const t=g.readFileSync(s.join(e,"resources.resjson"),"utf8");const i=JSON.parse(t);const n=Object.entries(i);const r="loc.messages.";for(const[e,t]of n){if(e.startsWith(r)){this._resources.set(e.substring(r.length),t)}}}loc(e,...t){return y.format(this._resources.get(e),...t)}logDebug(e){this._gitHubRunnerWrapper.debug(e)}logError(e){this._gitHubRunnerWrapper.error(e)}logWarning(e){this._gitHubRunnerWrapper.warning(e)}setStatusFailed(e){this._gitHubRunnerWrapper.setFailed(e)}setStatusSkipped(e){this._consoleWrapper.log(e)}setStatusSucceeded(e){this._consoleWrapper.log(e)}setSecret(e){this._gitHubRunnerWrapper.setSecret(e)}}var m=__nccwpck_require__(857);function utils_toCommandValue(e){if(e===null||e===undefined){return""}else if(typeof e==="string"||e instanceof String){return e}return JSON.stringify(e)}function utils_toCommandProperties(e){if(!Object.keys(e).length){return{}}return{title:e.title,file:e.file,line:e.startLine,endLine:e.endLine,col:e.startColumn,endColumn:e.endColumn}}function command_issueCommand(e,t,i){const n=new Command(e,t,i);process.stdout.write(n.toString()+m.EOL)}function command_issue(e,t=""){command_issueCommand(e,{},t)}const I="::";class Command{constructor(e,t,i){if(!e){e="missing.command"}this.command=e;this.properties=t;this.message=i}toString(){let e=I+this.command;if(this.properties&&Object.keys(this.properties).length>0){e+=" ";let t=true;for(const i in this.properties){if(this.properties.hasOwnProperty(i)){const n=this.properties[i];if(n){if(t){t=false}else{e+=","}e+=`${i}=${escapeProperty(n)}`}}}}e+=`${I}${escapeData(this.message)}`;return e}}function escapeData(e){return utils_toCommandValue(e).replace(/%/g,"%25").replace(/\r/g,"%0D").replace(/\n/g,"%0A")}function escapeProperty(e){return utils_toCommandValue(e).replace(/%/g,"%25").replace(/\r/g,"%0D").replace(/\n/g,"%0A").replace(/:/g,"%3A").replace(/,/g,"%2C")}var v=__nccwpck_require__(6982);var E=__nccwpck_require__(9896);function file_command_issueFileCommand(e,t){const i=process.env[`GITHUB_${e}`];if(!i){throw new Error(`Unable to find environment variable for file command ${e}`)}if(!fs.existsSync(i)){throw new Error(`Missing file at path: ${i}`)}fs.appendFileSync(i,`${toCommandValue(t)}${os.EOL}`,{encoding:"utf8"})}function file_command_prepareKeyValueMessage(e,t){const i=`ghadelimiter_${crypto.randomUUID()}`;const n=toCommandValue(t);if(e.includes(i)){throw new Error(`Unexpected input: name should not contain the delimiter "${i}"`)}if(n.includes(i)){throw new Error(`Unexpected input: value should not contain the delimiter "${i}"`)}return`${e}<<${i}${os.EOL}${n}${os.EOL}${i}`}var C=__nccwpck_require__(6928);var T=__nccwpck_require__(8611);var R=__nccwpck_require__(5692);function getProxyUrl(e){const t=e.protocol==="https:";if(checkBypass(e)){return undefined}const i=(()=>{if(t){return process.env["https_proxy"]||process.env["HTTPS_PROXY"]}else{return process.env["http_proxy"]||process.env["HTTP_PROXY"]}})();if(i){try{return new DecodedURL(i)}catch(e){if(!i.startsWith("http://")&&!i.startsWith("https://"))return new DecodedURL(`http://${i}`)}}else{return undefined}}function checkBypass(e){if(!e.hostname){return false}const t=e.hostname;if(isLoopbackAddress(t)){return true}const i=process.env["no_proxy"]||process.env["NO_PROXY"]||"";if(!i){return false}let n;if(e.port){n=Number(e.port)}else if(e.protocol==="http:"){n=80}else if(e.protocol==="https:"){n=443}const r=[e.hostname.toUpperCase()];if(typeof n==="number"){r.push(`${r[0]}:${n}`)}for(const e of i.split(",").map((e=>e.trim().toUpperCase())).filter((e=>e))){if(e==="*"||r.some((t=>t===e||t.endsWith(`.${e}`)||e.startsWith(".")&&t.endsWith(`${e}`)))){return true}}return false}function isLoopbackAddress(e){const t=e.toLowerCase();return t==="localhost"||t.startsWith("127.")||t.startsWith("[::1]")||t.startsWith("[0:0:0:0:0:0:0:1]")}class DecodedURL extends URL{constructor(e,t){super(e,t);this._decodedUsername=decodeURIComponent(super.username);this._decodedPassword=decodeURIComponent(super.password)}get username(){return this._decodedUsername}get password(){return this._decodedPassword}}var b=__nccwpck_require__(770);var w=__nccwpck_require__(6752);var B=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};var D;(function(e){e[e["OK"]=200]="OK";e[e["MultipleChoices"]=300]="MultipleChoices";e[e["MovedPermanently"]=301]="MovedPermanently";e[e["ResourceMoved"]=302]="ResourceMoved";e[e["SeeOther"]=303]="SeeOther";e[e["NotModified"]=304]="NotModified";e[e["UseProxy"]=305]="UseProxy";e[e["SwitchProxy"]=306]="SwitchProxy";e[e["TemporaryRedirect"]=307]="TemporaryRedirect";e[e["PermanentRedirect"]=308]="PermanentRedirect";e[e["BadRequest"]=400]="BadRequest";e[e["Unauthorized"]=401]="Unauthorized";e[e["PaymentRequired"]=402]="PaymentRequired";e[e["Forbidden"]=403]="Forbidden";e[e["NotFound"]=404]="NotFound";e[e["MethodNotAllowed"]=405]="MethodNotAllowed";e[e["NotAcceptable"]=406]="NotAcceptable";e[e["ProxyAuthenticationRequired"]=407]="ProxyAuthenticationRequired";e[e["RequestTimeout"]=408]="RequestTimeout";e[e["Conflict"]=409]="Conflict";e[e["Gone"]=410]="Gone";e[e["TooManyRequests"]=429]="TooManyRequests";e[e["InternalServerError"]=500]="InternalServerError";e[e["NotImplemented"]=501]="NotImplemented";e[e["BadGateway"]=502]="BadGateway";e[e["ServiceUnavailable"]=503]="ServiceUnavailable";e[e["GatewayTimeout"]=504]="GatewayTimeout"})(D||(D={}));var S;(function(e){e["Accept"]="accept";e["ContentType"]="content-type"})(S||(S={}));var k;(function(e){e["ApplicationJson"]="application/json"})(k||(k={}));function lib_getProxyUrl(e){const t=pm.getProxyUrl(new URL(e));return t?t.href:""}const P=[D.MovedPermanently,D.ResourceMoved,D.SeeOther,D.TemporaryRedirect,D.PermanentRedirect];const U=[D.BadGateway,D.ServiceUnavailable,D.GatewayTimeout];const V=null&&["OPTIONS","GET","DELETE","HEAD"];const F=10;const O=5;class HttpClientError extends Error{constructor(e,t){super(e);this.name="HttpClientError";this.statusCode=t;Object.setPrototypeOf(this,HttpClientError.prototype)}}class HttpClientResponse{constructor(e){this.message=e}readBody(){return B(this,void 0,void 0,(function*(){return new Promise((e=>B(this,void 0,void 0,(function*(){let t=Buffer.alloc(0);this.message.on("data",(e=>{t=Buffer.concat([t,e])}));this.message.on("end",(()=>{e(t.toString())}))}))))}))}readBodyBuffer(){return B(this,void 0,void 0,(function*(){return new Promise((e=>B(this,void 0,void 0,(function*(){const t=[];this.message.on("data",(e=>{t.push(e)}));this.message.on("end",(()=>{e(Buffer.concat(t))}))}))))}))}}function isHttps(e){const t=new URL(e);return t.protocol==="https:"}class lib_HttpClient{constructor(e,t,i){this._ignoreSslError=false;this._allowRedirects=true;this._allowRedirectDowngrade=false;this._maxRedirects=50;this._allowRetries=false;this._maxRetries=1;this._keepAlive=false;this._disposed=false;this.userAgent=this._getUserAgentWithOrchestrationId(e);this.handlers=t||[];this.requestOptions=i;if(i){if(i.ignoreSslError!=null){this._ignoreSslError=i.ignoreSslError}this._socketTimeout=i.socketTimeout;if(i.allowRedirects!=null){this._allowRedirects=i.allowRedirects}if(i.allowRedirectDowngrade!=null){this._allowRedirectDowngrade=i.allowRedirectDowngrade}if(i.maxRedirects!=null){this._maxRedirects=Math.max(i.maxRedirects,0)}if(i.keepAlive!=null){this._keepAlive=i.keepAlive}if(i.allowRetries!=null){this._allowRetries=i.allowRetries}if(i.maxRetries!=null){this._maxRetries=i.maxRetries}}}options(e,t){return B(this,void 0,void 0,(function*(){return this.request("OPTIONS",e,null,t||{})}))}get(e,t){return B(this,void 0,void 0,(function*(){return this.request("GET",e,null,t||{})}))}del(e,t){return B(this,void 0,void 0,(function*(){return this.request("DELETE",e,null,t||{})}))}post(e,t,i){return B(this,void 0,void 0,(function*(){return this.request("POST",e,t,i||{})}))}patch(e,t,i){return B(this,void 0,void 0,(function*(){return this.request("PATCH",e,t,i||{})}))}put(e,t,i){return B(this,void 0,void 0,(function*(){return this.request("PUT",e,t,i||{})}))}head(e,t){return B(this,void 0,void 0,(function*(){return this.request("HEAD",e,null,t||{})}))}sendStream(e,t,i,n){return B(this,void 0,void 0,(function*(){return this.request(e,t,i,n)}))}getJson(e){return B(this,arguments,void 0,(function*(e,t={}){t[S.Accept]=this._getExistingOrDefaultHeader(t,S.Accept,k.ApplicationJson);const i=yield this.get(e,t);return this._processResponse(i,this.requestOptions)}))}postJson(e,t){return B(this,arguments,void 0,(function*(e,t,i={}){const n=JSON.stringify(t,null,2);i[S.Accept]=this._getExistingOrDefaultHeader(i,S.Accept,k.ApplicationJson);i[S.ContentType]=this._getExistingOrDefaultContentTypeHeader(i,k.ApplicationJson);const r=yield this.post(e,n,i);return this._processResponse(r,this.requestOptions)}))}putJson(e,t){return B(this,arguments,void 0,(function*(e,t,i={}){const n=JSON.stringify(t,null,2);i[S.Accept]=this._getExistingOrDefaultHeader(i,S.Accept,k.ApplicationJson);i[S.ContentType]=this._getExistingOrDefaultContentTypeHeader(i,k.ApplicationJson);const r=yield this.put(e,n,i);return this._processResponse(r,this.requestOptions)}))}patchJson(e,t){return B(this,arguments,void 0,(function*(e,t,i={}){const n=JSON.stringify(t,null,2);i[S.Accept]=this._getExistingOrDefaultHeader(i,S.Accept,k.ApplicationJson);i[S.ContentType]=this._getExistingOrDefaultContentTypeHeader(i,k.ApplicationJson);const r=yield this.patch(e,n,i);return this._processResponse(r,this.requestOptions)}))}request(e,t,i,n){return B(this,void 0,void 0,(function*(){if(this._disposed){throw new Error("Client has already been disposed.")}const r=new URL(t);let s=this._prepareRequest(e,r,n);const o=this._allowRetries&&V.includes(e)?this._maxRetries+1:1;let a=0;let l;do{l=yield this.requestRaw(s,i);if(l&&l.message&&l.message.statusCode===D.Unauthorized){let e;for(const t of this.handlers){if(t.canHandleAuthentication(l)){e=t;break}}if(e){return e.handleAuthentication(this,s,i)}else{return l}}let t=this._maxRedirects;while(l.message.statusCode&&P.includes(l.message.statusCode)&&this._allowRedirects&&t>0){const o=l.message.headers["location"];if(!o){break}const a=new URL(o);if(r.protocol==="https:"&&r.protocol!==a.protocol&&!this._allowRedirectDowngrade){throw new Error("Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.")}yield l.readBody();if(a.hostname!==r.hostname){for(const e in n){if(e.toLowerCase()==="authorization"){delete n[e]}}}s=this._prepareRequest(e,a,n);l=yield this.requestRaw(s,i);t--}if(!l.message.statusCode||!U.includes(l.message.statusCode)){return l}a+=1;if(a{function callbackForResult(e,t){if(e){n(e)}else if(!t){n(new Error("Unknown error"))}else{i(t)}}this.requestRawWithCallback(e,t,callbackForResult)}))}))}requestRawWithCallback(e,t,i){if(typeof t==="string"){if(!e.options.headers){e.options.headers={}}e.options.headers["Content-Length"]=Buffer.byteLength(t,"utf8")}let n=false;function handleResult(e,t){if(!n){n=true;i(e,t)}}const r=e.httpModule.request(e.options,(e=>{const t=new HttpClientResponse(e);handleResult(undefined,t)}));let s;r.on("socket",(e=>{s=e}));r.setTimeout(this._socketTimeout||3*6e4,(()=>{if(s){s.end()}handleResult(new Error(`Request timeout: ${e.options.path}`))}));r.on("error",(function(e){handleResult(e)}));if(t&&typeof t==="string"){r.write(t,"utf8")}if(t&&typeof t!=="string"){t.on("close",(function(){r.end()}));t.pipe(r)}else{r.end()}}getAgent(e){const t=new URL(e);return this._getAgent(t)}getAgentDispatcher(e){const t=new URL(e);const i=pm.getProxyUrl(t);const n=i&&i.hostname;if(!n){return}return this._getProxyAgentDispatcher(t,i)}_prepareRequest(e,t,i){const n={};n.parsedUrl=t;const r=n.parsedUrl.protocol==="https:";n.httpModule=r?https:http;const s=r?443:80;n.options={};n.options.host=n.parsedUrl.hostname;n.options.port=n.parsedUrl.port?parseInt(n.parsedUrl.port):s;n.options.path=(n.parsedUrl.pathname||"")+(n.parsedUrl.search||"");n.options.method=e;n.options.headers=this._mergeHeaders(i);if(this.userAgent!=null){n.options.headers["user-agent"]=this.userAgent}n.options.agent=this._getAgent(n.parsedUrl);if(this.handlers){for(const e of this.handlers){e.prepareRequest(n.options)}}return n}_mergeHeaders(e){if(this.requestOptions&&this.requestOptions.headers){return Object.assign({},lowercaseKeys(this.requestOptions.headers),lowercaseKeys(e||{}))}return lowercaseKeys(e||{})}_getExistingOrDefaultHeader(e,t,i){let n;if(this.requestOptions&&this.requestOptions.headers){const e=lowercaseKeys(this.requestOptions.headers)[t];if(e){n=typeof e==="number"?e.toString():e}}const r=e[t];if(r!==undefined){return typeof r==="number"?r.toString():r}if(n!==undefined){return n}return i}_getExistingOrDefaultContentTypeHeader(e,t){let i;if(this.requestOptions&&this.requestOptions.headers){const e=lowercaseKeys(this.requestOptions.headers)[S.ContentType];if(e){if(typeof e==="number"){i=String(e)}else if(Array.isArray(e)){i=e.join(", ")}else{i=e}}}const n=e[S.ContentType];if(n!==undefined){if(typeof n==="number"){return String(n)}else if(Array.isArray(n)){return n.join(", ")}else{return n}}if(i!==undefined){return i}return t}_getAgent(e){let t;const i=pm.getProxyUrl(e);const n=i&&i.hostname;if(this._keepAlive&&n){t=this._proxyAgent}if(!n){t=this._agent}if(t){return t}const r=e.protocol==="https:";let s=100;if(this.requestOptions){s=this.requestOptions.maxSockets||http.globalAgent.maxSockets}if(i&&i.hostname){const e={maxSockets:s,keepAlive:this._keepAlive,proxy:Object.assign(Object.assign({},(i.username||i.password)&&{proxyAuth:`${i.username}:${i.password}`}),{host:i.hostname,port:i.port})};let n;const o=i.protocol==="https:";if(r){n=o?tunnel.httpsOverHttps:tunnel.httpsOverHttp}else{n=o?tunnel.httpOverHttps:tunnel.httpOverHttp}t=n(e);this._proxyAgent=t}if(!t){const e={keepAlive:this._keepAlive,maxSockets:s};t=r?new https.Agent(e):new http.Agent(e);this._agent=t}if(r&&this._ignoreSslError){t.options=Object.assign(t.options||{},{rejectUnauthorized:false})}return t}_getProxyAgentDispatcher(e,t){let i;if(this._keepAlive){i=this._proxyAgentDispatcher}if(i){return i}const n=e.protocol==="https:";i=new ProxyAgent(Object.assign({uri:t.href,pipelining:!this._keepAlive?0:1},(t.username||t.password)&&{token:`Basic ${Buffer.from(`${t.username}:${t.password}`).toString("base64")}`}));this._proxyAgentDispatcher=i;if(n&&this._ignoreSslError){i.options=Object.assign(i.options.requestTls||{},{rejectUnauthorized:false})}return i}_getUserAgentWithOrchestrationId(e){const t=e||"actions/http-client";const i=process.env["ACTIONS_ORCHESTRATION_ID"];if(i){const e=i.replace(/[^a-z0-9_.-]/gi,"_");return`${t} actions_orchestration_id/${e}`}return t}_performExponentialBackoff(e){return B(this,void 0,void 0,(function*(){e=Math.min(F,e);const t=O*Math.pow(2,e);return new Promise((e=>setTimeout((()=>e()),t)))}))}_processResponse(e,t){return B(this,void 0,void 0,(function*(){return new Promise(((i,n)=>B(this,void 0,void 0,(function*(){const r=e.message.statusCode||0;const s={statusCode:r,result:null,headers:{}};if(r===D.NotFound){i(s)}function dateTimeDeserializer(e,t){if(typeof t==="string"){const e=new Date(t);if(!isNaN(e.valueOf())){return e}}return t}let o;let a;try{a=yield e.readBody();if(a&&a.length>0){if(t&&t.deserializeDates){o=JSON.parse(a,dateTimeDeserializer)}else{o=JSON.parse(a)}s.result=o}s.headers=e.message.headers}catch(e){}if(r>299){let e;if(o&&o.message){e=o.message}else if(a&&a.length>0){e=a}else{e=`Failed request: (${r})`}const t=new HttpClientError(e,r);t.result=s.result;n(t)}else{i(s)}}))))}))}}const lowercaseKeys=e=>Object.keys(e).reduce(((t,i)=>(t[i.toLowerCase()]=e[i],t)),{});var N=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};class BasicCredentialHandler{constructor(e,t){this.username=e;this.password=t}prepareRequest(e){if(!e.headers){throw Error("The request has no headers")}e.headers["Authorization"]=`Basic ${Buffer.from(`${this.username}:${this.password}`).toString("base64")}`}canHandleAuthentication(){return false}handleAuthentication(){return N(this,void 0,void 0,(function*(){throw new Error("not implemented")}))}}class auth_BearerCredentialHandler{constructor(e){this.token=e}prepareRequest(e){if(!e.headers){throw Error("The request has no headers")}e.headers["Authorization"]=`Bearer ${this.token}`}canHandleAuthentication(){return false}handleAuthentication(){return N(this,void 0,void 0,(function*(){throw new Error("not implemented")}))}}class PersonalAccessTokenCredentialHandler{constructor(e){this.token=e}prepareRequest(e){if(!e.headers){throw Error("The request has no headers")}e.headers["Authorization"]=`Basic ${Buffer.from(`PAT:${this.token}`).toString("base64")}`}canHandleAuthentication(){return false}handleAuthentication(){return N(this,void 0,void 0,(function*(){throw new Error("not implemented")}))}}var q=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};class oidc_utils_OidcClient{static createHttpClient(e=true,t=10){const i={allowRetries:e,maxRetries:t};return new HttpClient("actions/oidc-client",[new BearerCredentialHandler(oidc_utils_OidcClient.getRequestToken())],i)}static getRequestToken(){const e=process.env["ACTIONS_ID_TOKEN_REQUEST_TOKEN"];if(!e){throw new Error("Unable to get ACTIONS_ID_TOKEN_REQUEST_TOKEN env variable")}return e}static getIDTokenUrl(){const e=process.env["ACTIONS_ID_TOKEN_REQUEST_URL"];if(!e){throw new Error("Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable")}return e}static getCall(e){return q(this,void 0,void 0,(function*(){var t;const i=oidc_utils_OidcClient.createHttpClient();const n=yield i.getJson(e).catch((e=>{throw new Error(`Failed to get ID Token. \n \n Error Code : ${e.statusCode}\n \n Error Message: ${e.message}`)}));const r=(t=n.result)===null||t===void 0?void 0:t.value;if(!r){throw new Error("Response json body do not have ID Token field")}return r}))}static getIDToken(e){return q(this,void 0,void 0,(function*(){try{let t=oidc_utils_OidcClient.getIDTokenUrl();if(e){const i=encodeURIComponent(e);t=`${t}&audience=${i}`}debug(`ID token url is ${t}`);const i=yield oidc_utils_OidcClient.getCall(t);setSecret(i);return i}catch(e){throw new Error(`Error message: ${e.message}`)}}))}}var _=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};const{access:M,appendFile:L,writeFile:G}=E.promises;const j="GITHUB_STEP_SUMMARY";const x="https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary";class Summary{constructor(){this._buffer=""}filePath(){return _(this,void 0,void 0,(function*(){if(this._filePath){return this._filePath}const e=process.env[j];if(!e){throw new Error(`Unable to find environment variable for $${j}. Check if your runtime environment supports job summaries.`)}try{yield M(e,E.constants.R_OK|E.constants.W_OK)}catch(t){throw new Error(`Unable to access summary file: '${e}'. Check if the file has correct read/write permissions.`)}this._filePath=e;return this._filePath}))}wrap(e,t,i={}){const n=Object.entries(i).map((([e,t])=>` ${e}="${t}"`)).join("");if(!t){return`<${e}${n}>`}return`<${e}${n}>${t}`}write(e){return _(this,void 0,void 0,(function*(){const t=!!(e===null||e===void 0?void 0:e.overwrite);const i=yield this.filePath();const n=t?G:L;yield n(i,this._buffer,{encoding:"utf8"});return this.emptyBuffer()}))}clear(){return _(this,void 0,void 0,(function*(){return this.emptyBuffer().write({overwrite:true})}))}stringify(){return this._buffer}isEmptyBuffer(){return this._buffer.length===0}emptyBuffer(){this._buffer="";return this}addRaw(e,t=false){this._buffer+=e;return t?this.addEOL():this}addEOL(){return this.addRaw(m.EOL)}addCodeBlock(e,t){const i=Object.assign({},t&&{lang:t});const n=this.wrap("pre",this.wrap("code",e),i);return this.addRaw(n).addEOL()}addList(e,t=false){const i=t?"ol":"ul";const n=e.map((e=>this.wrap("li",e))).join("");const r=this.wrap(i,n);return this.addRaw(r).addEOL()}addTable(e){const t=e.map((e=>{const t=e.map((e=>{if(typeof e==="string"){return this.wrap("td",e)}const{header:t,data:i,colspan:n,rowspan:r}=e;const s=t?"th":"td";const o=Object.assign(Object.assign({},n&&{colspan:n}),r&&{rowspan:r});return this.wrap(s,i,o)})).join("");return this.wrap("tr",t)})).join("");const i=this.wrap("table",t);return this.addRaw(i).addEOL()}addDetails(e,t){const i=this.wrap("details",this.wrap("summary",e)+t);return this.addRaw(i).addEOL()}addImage(e,t,i){const{width:n,height:r}=i||{};const s=Object.assign(Object.assign({},n&&{width:n}),r&&{height:r});const o=this.wrap("img",null,Object.assign({src:e,alt:t},s));return this.addRaw(o).addEOL()}addHeading(e,t){const i=`h${t}`;const n=["h1","h2","h3","h4","h5","h6"].includes(i)?i:"h1";const r=this.wrap(n,e);return this.addRaw(r).addEOL()}addSeparator(){const e=this.wrap("hr",null);return this.addRaw(e).addEOL()}addBreak(){const e=this.wrap("br",null);return this.addRaw(e).addEOL()}addQuote(e,t){const i=Object.assign({},t&&{cite:t});const n=this.wrap("blockquote",e,i);return this.addRaw(n).addEOL()}addLink(e,t){const i=this.wrap("a",e,{href:t});return this.addRaw(i).addEOL()}}const H=new Summary;const W=null&&H;const Y=null&&H;function toPosixPath(e){return e.replace(/[\\]/g,"/")}function toWin32Path(e){return e.replace(/[/]/g,"\\")}function toPlatformPath(e){return e.replace(/[/\\]/g,path.sep)}var J=__nccwpck_require__(3193);var z=__nccwpck_require__(4434);var $=__nccwpck_require__(5317);var K=__nccwpck_require__(2613);var Z=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};const{chmod:X,copyFile:ee,lstat:te,mkdir:ie,open:ne,readdir:re,rename:se,rm:oe,rmdir:ae,stat:le,symlink:ue,unlink:ce}=E.promises;const de=process.platform==="win32";function readlink(e){return Z(this,void 0,void 0,(function*(){const t=yield fs.promises.readlink(e);if(de&&!t.endsWith("\\")){return`${t}\\`}return t}))}const pe=268435456;const Ae=E.constants.O_RDONLY;function exists(e){return Z(this,void 0,void 0,(function*(){try{yield le(e)}catch(e){if(e.code==="ENOENT"){return false}throw e}return true}))}function isDirectory(e){return Z(this,arguments,void 0,(function*(e,t=false){const i=t?yield le(e):yield te(e);return i.isDirectory()}))}function isRooted(e){e=normalizeSeparators(e);if(!e){throw new Error('isRooted() parameter "p" cannot be empty')}if(de){return e.startsWith("\\")||/^[A-Z]:/i.test(e)}return e.startsWith("/")}function tryGetExecutablePath(e,t){return Z(this,void 0,void 0,(function*(){let i=undefined;try{i=yield le(e)}catch(t){if(t.code!=="ENOENT"){console.log(`Unexpected error attempting to determine if executable file exists '${e}': ${t}`)}}if(i&&i.isFile()){if(de){const i=C.extname(e).toUpperCase();if(t.some((e=>e.toUpperCase()===i))){return e}}else{if(isUnixExecutable(i)){return e}}}const n=e;for(const r of t){e=n+r;i=undefined;try{i=yield le(e)}catch(t){if(t.code!=="ENOENT"){console.log(`Unexpected error attempting to determine if executable file exists '${e}': ${t}`)}}if(i&&i.isFile()){if(de){try{const t=C.dirname(e);const i=C.basename(e).toUpperCase();for(const n of yield re(t)){if(i===n.toUpperCase()){e=C.join(t,n);break}}}catch(t){console.log(`Unexpected error attempting to determine the actual case of the file '${e}': ${t}`)}return e}else{if(isUnixExecutable(i)){return e}}}}return""}))}function normalizeSeparators(e){e=e||"";if(de){e=e.replace(/\//g,"\\");return e.replace(/\\\\+/g,"\\")}return e.replace(/\/\/+/g,"/")}function isUnixExecutable(e){return(e.mode&1)>0||(e.mode&8)>0&&process.getgid!==undefined&&e.gid===process.getgid()||(e.mode&64)>0&&process.getuid!==undefined&&e.uid===process.getuid()}function getCmdPath(){var e;return(e=process.env["COMSPEC"])!==null&&e!==void 0?e:`cmd.exe`}var fe=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};function cp(e,t){return fe(this,arguments,void 0,(function*(e,t,i={}){const{force:n,recursive:r,copySourceDirectory:s}=readCopyOptions(i);const o=(yield ioUtil.exists(t))?yield ioUtil.stat(t):null;if(o&&o.isFile()&&!n){return}const a=o&&o.isDirectory()&&s?path.join(t,path.basename(e)):t;if(!(yield ioUtil.exists(e))){throw new Error(`no such file or directory: ${e}`)}const l=yield ioUtil.stat(e);if(l.isDirectory()){if(!r){throw new Error(`Failed to copy. ${e} is a directory, but tried to copy without recursive flag.`)}else{yield cpDirRecursive(e,a,0,n)}}else{if(path.relative(e,a)===""){throw new Error(`'${a}' and '${e}' are the same file`)}yield io_copyFile(e,a,n)}}))}function mv(e,t){return fe(this,arguments,void 0,(function*(e,t,i={}){if(yield ioUtil.exists(t)){let n=true;if(yield ioUtil.isDirectory(t)){t=path.join(t,path.basename(e));n=yield ioUtil.exists(t)}if(n){if(i.force==null||i.force){yield rmRF(t)}else{throw new Error("Destination already exists")}}}yield mkdirP(path.dirname(t));yield ioUtil.rename(e,t)}))}function rmRF(e){return fe(this,void 0,void 0,(function*(){if(ioUtil.IS_WINDOWS){if(/[*"<>|]/.test(e)){throw new Error('File path must not contain `*`, `"`, `<`, `>` or `|` on Windows')}}try{yield ioUtil.rm(e,{force:true,maxRetries:3,recursive:true,retryDelay:300})}catch(e){throw new Error(`File was unable to be removed ${e}`)}}))}function mkdirP(e){return fe(this,void 0,void 0,(function*(){ok(e,"a path argument must be provided");yield ioUtil.mkdir(e,{recursive:true})}))}function which(e,t){return fe(this,void 0,void 0,(function*(){if(!e){throw new Error("parameter 'tool' is required")}if(t){const t=yield which(e,false);if(!t){if(de){throw new Error(`Unable to locate executable file: ${e}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also verify the file has a valid extension for an executable file.`)}else{throw new Error(`Unable to locate executable file: ${e}. Please verify either the file path exists or the file can be found within a directory specified by the PATH environment variable. Also check the file mode to verify the file is executable.`)}}return t}const i=yield findInPath(e);if(i&&i.length>0){return i[0]}return""}))}function findInPath(e){return fe(this,void 0,void 0,(function*(){if(!e){throw new Error("parameter 'tool' is required")}const t=[];if(de&&process.env["PATHEXT"]){for(const e of process.env["PATHEXT"].split(C.delimiter)){if(e){t.push(e)}}}if(isRooted(e)){const i=yield tryGetExecutablePath(e,t);if(i){return[i]}return[]}if(e.includes(C.sep)){return[]}const i=[];if(process.env.PATH){for(const e of process.env.PATH.split(C.delimiter)){if(e){i.push(e)}}}const n=[];for(const r of i){const i=yield tryGetExecutablePath(C.join(r,e),t);if(i){n.push(i)}}return n}))}function readCopyOptions(e){const t=e.force==null?true:e.force;const i=Boolean(e.recursive);const n=e.copySourceDirectory==null?true:Boolean(e.copySourceDirectory);return{force:t,recursive:i,copySourceDirectory:n}}function cpDirRecursive(e,t,i,n){return fe(this,void 0,void 0,(function*(){if(i>=255)return;i++;yield mkdirP(t);const r=yield ioUtil.readdir(e);for(const s of r){const r=`${e}/${s}`;const o=`${t}/${s}`;const a=yield ioUtil.lstat(r);if(a.isDirectory()){yield cpDirRecursive(r,o,i,n)}else{yield io_copyFile(r,o,n)}}yield ioUtil.chmod(t,(yield ioUtil.stat(e)).mode)}))}function io_copyFile(e,t,i){return fe(this,void 0,void 0,(function*(){if((yield ioUtil.lstat(e)).isSymbolicLink()){try{yield ioUtil.lstat(t);yield ioUtil.unlink(t)}catch(e){if(e.code==="EPERM"){yield ioUtil.chmod(t,"0666");yield ioUtil.unlink(t)}}const i=yield ioUtil.readlink(e);yield ioUtil.symlink(i,t,ioUtil.IS_WINDOWS?"junction":null)}else if(!(yield ioUtil.exists(t))||i){yield ioUtil.copyFile(e,t)}}))}const he=e(import.meta.url)("timers");var ge=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};const ye=process.platform==="win32";class ToolRunner extends z.EventEmitter{constructor(e,t,i){super();if(!e){throw new Error("Parameter 'toolPath' cannot be null or empty.")}this.toolPath=e;this.args=t||[];this.options=i||{}}_debug(e){if(this.options.listeners&&this.options.listeners.debug){this.options.listeners.debug(e)}}_getCommandString(e,t){const i=this._getSpawnFileName();const n=this._getSpawnArgs(e);let r=t?"":"[command]";if(ye){if(this._isCmdFile()){r+=i;for(const e of n){r+=` ${e}`}}else if(e.windowsVerbatimArguments){r+=`"${i}"`;for(const e of n){r+=` ${e}`}}else{r+=this._windowsQuoteCmdArg(i);for(const e of n){r+=` ${this._windowsQuoteCmdArg(e)}`}}}else{r+=i;for(const e of n){r+=` ${e}`}}return r}_processLineBuffer(e,t,i){try{let n=t+e.toString();let r=n.indexOf(m.EOL);while(r>-1){const e=n.substring(0,r);i(e);n=n.substring(r+m.EOL.length);r=n.indexOf(m.EOL)}return n}catch(e){this._debug(`error processing line. Failed with error ${e}`);return""}}_getSpawnFileName(){if(ye){if(this._isCmdFile()){return process.env["COMSPEC"]||"cmd.exe"}}return this.toolPath}_getSpawnArgs(e){if(ye){if(this._isCmdFile()){let t=`/D /S /C "${this._windowsQuoteCmdArg(this.toolPath)}`;for(const i of this.args){t+=" ";t+=e.windowsVerbatimArguments?i:this._windowsQuoteCmdArg(i)}t+='"';return[t]}}return this.args}_endsWith(e,t){return e.endsWith(t)}_isCmdFile(){const e=this.toolPath.toUpperCase();return this._endsWith(e,".CMD")||this._endsWith(e,".BAT")}_windowsQuoteCmdArg(e){if(!this._isCmdFile()){return this._uvQuoteCmdArg(e)}if(!e){return'""'}const t=[" ","\t","&","(",")","[","]","{","}","^","=",";","!","'","+",",","`","~","|","<",">",'"'];let i=false;for(const n of e){if(t.some((e=>e===n))){i=true;break}}if(!i){return e}let n='"';let r=true;for(let t=e.length;t>0;t--){n+=e[t-1];if(r&&e[t-1]==="\\"){n+="\\"}else if(e[t-1]==='"'){r=true;n+='"'}else{r=false}}n+='"';return n.split("").reverse().join("")}_uvQuoteCmdArg(e){if(!e){return'""'}if(!e.includes(" ")&&!e.includes("\t")&&!e.includes('"')){return e}if(!e.includes('"')&&!e.includes("\\")){return`"${e}"`}let t='"';let i=true;for(let n=e.length;n>0;n--){t+=e[n-1];if(i&&e[n-1]==="\\"){t+="\\"}else if(e[n-1]==='"'){i=true;t+="\\"}else{i=false}}t+='"';return t.split("").reverse().join("")}_cloneExecOptions(e){e=e||{};const t={cwd:e.cwd||process.cwd(),env:e.env||process.env,silent:e.silent||false,windowsVerbatimArguments:e.windowsVerbatimArguments||false,failOnStdErr:e.failOnStdErr||false,ignoreReturnCode:e.ignoreReturnCode||false,delay:e.delay||1e4};t.outStream=e.outStream||process.stdout;t.errStream=e.errStream||process.stderr;return t}_getSpawnOptions(e,t){e=e||{};const i={};i.cwd=e.cwd;i.env=e.env;i["windowsVerbatimArguments"]=e.windowsVerbatimArguments||this._isCmdFile();if(e.windowsVerbatimArguments){i.argv0=`"${t}"`}return i}exec(){return ge(this,void 0,void 0,(function*(){if(!isRooted(this.toolPath)&&(this.toolPath.includes("/")||ye&&this.toolPath.includes("\\"))){this.toolPath=C.resolve(process.cwd(),this.options.cwd||process.cwd(),this.toolPath)}this.toolPath=yield which(this.toolPath,true);return new Promise(((e,t)=>ge(this,void 0,void 0,(function*(){this._debug(`exec tool: ${this.toolPath}`);this._debug("arguments:");for(const e of this.args){this._debug(` ${e}`)}const i=this._cloneExecOptions(this.options);if(!i.silent&&i.outStream){i.outStream.write(this._getCommandString(i)+m.EOL)}const n=new ExecState(i,this.toolPath);n.on("debug",(e=>{this._debug(e)}));if(this.options.cwd&&!(yield exists(this.options.cwd))){return t(new Error(`The cwd: ${this.options.cwd} does not exist!`))}const r=this._getSpawnFileName();const s=$.spawn(r,this._getSpawnArgs(i),this._getSpawnOptions(this.options,r));let o="";if(s.stdout){s.stdout.on("data",(e=>{if(this.options.listeners&&this.options.listeners.stdout){this.options.listeners.stdout(e)}if(!i.silent&&i.outStream){i.outStream.write(e)}o=this._processLineBuffer(e,o,(e=>{if(this.options.listeners&&this.options.listeners.stdline){this.options.listeners.stdline(e)}}))}))}let a="";if(s.stderr){s.stderr.on("data",(e=>{n.processStderr=true;if(this.options.listeners&&this.options.listeners.stderr){this.options.listeners.stderr(e)}if(!i.silent&&i.errStream&&i.outStream){const t=i.failOnStdErr?i.errStream:i.outStream;t.write(e)}a=this._processLineBuffer(e,a,(e=>{if(this.options.listeners&&this.options.listeners.errline){this.options.listeners.errline(e)}}))}))}s.on("error",(e=>{n.processError=e.message;n.processExited=true;n.processClosed=true;n.CheckComplete()}));s.on("exit",(e=>{n.processExitCode=e;n.processExited=true;this._debug(`Exit code ${e} received from tool '${this.toolPath}'`);n.CheckComplete()}));s.on("close",(e=>{n.processExitCode=e;n.processExited=true;n.processClosed=true;this._debug(`STDIO streams have closed for tool '${this.toolPath}'`);n.CheckComplete()}));n.on("done",((i,n)=>{if(o.length>0){this.emit("stdline",o)}if(a.length>0){this.emit("errline",a)}s.removeAllListeners();if(i){t(i)}else{e(n)}}));if(this.options.input){if(!s.stdin){throw new Error("child process missing stdin")}s.stdin.end(this.options.input)}}))))}))}}function argStringToArray(e){const t=[];let i=false;let n=false;let r="";function append(e){if(n&&e!=='"'){r+="\\"}r+=e;n=false}for(let s=0;s0){t.push(r);r=""}continue}append(o)}if(r.length>0){t.push(r.trim())}return t}class ExecState extends z.EventEmitter{constructor(e,t){super();this.processClosed=false;this.processError="";this.processExitCode=0;this.processExited=false;this.processStderr=false;this.delay=1e4;this.done=false;this.timeout=null;if(!t){throw new Error("toolPath must not be empty")}this.options=e;this.toolPath=t;if(e.delay){this.delay=e.delay}}CheckComplete(){if(this.done){return}if(this.processClosed){this._setResult()}else if(this.processExited){this.timeout=(0,he.setTimeout)(ExecState.HandleTimeout,this.delay,this)}}_debug(e){this.emit("debug",e)}_setResult(){let e;if(this.processExited){if(this.processError){e=new Error(`There was an error when attempting to execute the process '${this.toolPath}'. This may indicate the process failed to start. Error: ${this.processError}`)}else if(this.processExitCode!==0&&!this.options.ignoreReturnCode){e=new Error(`The process '${this.toolPath}' failed with exit code ${this.processExitCode}`)}else if(this.processStderr&&this.options.failOnStdErr){e=new Error(`The process '${this.toolPath}' failed because one or more lines were written to the STDERR stream`)}}if(this.timeout){clearTimeout(this.timeout);this.timeout=null}this.done=true;this.emit("done",e,this.processExitCode)}static HandleTimeout(e){if(e.done){return}if(!e.processClosed&&e.processExited){const t=`The STDIO streams did not close within ${e.delay/1e3} seconds of the exit event from process '${e.toolPath}'. This may indicate a child process inherited the STDIO streams and has not yet exited.`;e._debug(t)}e._setResult()}}var me=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};function exec_exec(e,t,i){return me(this,void 0,void 0,(function*(){const n=argStringToArray(e);if(n.length===0){throw new Error(`Parameter 'commandLine' cannot be null or empty.`)}const r=n[0];t=n.slice(1).concat(t||[]);const s=new ToolRunner(r,t,i);return s.exec()}))}function getExecOutput(e,t,i){return me(this,void 0,void 0,(function*(){var n,r;let s="";let o="";const a=new J.StringDecoder("utf8");const l=new J.StringDecoder("utf8");const u=(n=i===null||i===void 0?void 0:i.listeners)===null||n===void 0?void 0:n.stdout;const c=(r=i===null||i===void 0?void 0:i.listeners)===null||r===void 0?void 0:r.stderr;const stdErrListener=e=>{o+=l.write(e);if(c){c(e)}};const stdOutListener=e=>{s+=a.write(e);if(u){u(e)}};const d=Object.assign(Object.assign({},i===null||i===void 0?void 0:i.listeners),{stdout:stdOutListener,stderr:stdErrListener});const p=yield exec_exec(e,t,Object.assign(Object.assign({},i),{listeners:d}));s+=a.end();o+=l.end();return{exitCode:p,stdout:s,stderr:o}}))}var Ie=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};const getWindowsInfo=()=>Ie(void 0,void 0,void 0,(function*(){const{stdout:e}=yield exec.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Version"',undefined,{silent:true});const{stdout:t}=yield exec.getExecOutput('powershell -command "(Get-CimInstance -ClassName Win32_OperatingSystem).Caption"',undefined,{silent:true});return{name:t.trim(),version:e.trim()}}));const getMacOsInfo=()=>Ie(void 0,void 0,void 0,(function*(){var e,t,i,n;const{stdout:r}=yield exec.getExecOutput("sw_vers",undefined,{silent:true});const s=(t=(e=r.match(/ProductVersion:\s*(.+)/))===null||e===void 0?void 0:e[1])!==null&&t!==void 0?t:"";const o=(n=(i=r.match(/ProductName:\s*(.+)/))===null||i===void 0?void 0:i[1])!==null&&n!==void 0?n:"";return{name:o,version:s}}));const getLinuxInfo=()=>Ie(void 0,void 0,void 0,(function*(){const{stdout:e}=yield exec.getExecOutput("lsb_release",["-i","-r","-s"],{silent:true});const[t,i]=e.trim().split("\n");return{name:t,version:i}}));const ve=m.platform();const Ee=m.arch();const Ce=ve==="win32";const Te=ve==="darwin";const Re=ve==="linux";function getDetails(){return Ie(this,void 0,void 0,(function*(){return Object.assign(Object.assign({},yield Ce?getWindowsInfo():Te?getMacOsInfo():getLinuxInfo()),{platform:ve,arch:Ee,isWindows:Ce,isMacOS:Te,isLinux:Re})}))}var be=undefined&&undefined.__awaiter||function(e,t,i,n){function adopt(e){return e instanceof i?e:new i((function(t){t(e)}))}return new(i||(i=Promise))((function(i,r){function fulfilled(e){try{step(n.next(e))}catch(e){r(e)}}function rejected(e){try{step(n["throw"](e))}catch(e){r(e)}}function step(e){e.done?i(e.value):adopt(e.value).then(fulfilled,rejected)}step((n=n.apply(e,t||[])).next())}))};var we;(function(e){e[e["Success"]=0]="Success";e[e["Failure"]=1]="Failure"})(we||(we={}));function exportVariable(e,t){const i=toCommandValue(t);process.env[e]=i;const n=process.env["GITHUB_ENV"]||"";if(n){return issueFileCommand("ENV",prepareKeyValueMessage(e,t))}issueCommand("set-env",{name:e},i)}function core_setSecret(e){command_issueCommand("add-mask",{},e)}function addPath(e){const t=process.env["GITHUB_PATH"]||"";if(t){issueFileCommand("PATH",e)}else{issueCommand("add-path",{},e)}process.env["PATH"]=`${e}${path.delimiter}${process.env["PATH"]}`}function getInput(e,t){const i=process.env[`INPUT_${e.replace(/ /g,"_").toUpperCase()}`]||"";if(t&&t.required&&!i){throw new Error(`Input required and not supplied: ${e}`)}if(t&&t.trimWhitespace===false){return i}return i.trim()}function getMultilineInput(e,t){const i=getInput(e,t).split("\n").filter((e=>e!==""));if(t&&t.trimWhitespace===false){return i}return i.map((e=>e.trim()))}function getBooleanInput(e,t){const i=["true","True","TRUE"];const n=["false","False","FALSE"];const r=getInput(e,t);if(i.includes(r))return true;if(n.includes(r))return false;throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${e}\n`+`Support boolean input list: \`true | True | TRUE | false | False | FALSE\``)}function setOutput(e,t){const i=process.env["GITHUB_OUTPUT"]||"";if(i){return issueFileCommand("OUTPUT",prepareKeyValueMessage(e,t))}process.stdout.write(os.EOL);issueCommand("set-output",{name:e},toCommandValue(t))}function setCommandEcho(e){issue("echo",e?"on":"off")}function setFailed(e){process.exitCode=we.Failure;error(e)}function isDebug(){return process.env["RUNNER_DEBUG"]==="1"}function core_debug(e){command_issueCommand("debug",{},e)}function error(e,t={}){command_issueCommand("error",utils_toCommandProperties(t),e instanceof Error?e.toString():e)}function warning(e,t={}){command_issueCommand("warning",utils_toCommandProperties(t),e instanceof Error?e.toString():e)}function notice(e,t={}){issueCommand("notice",toCommandProperties(t),e instanceof Error?e.toString():e)}function info(e){process.stdout.write(e+os.EOL)}function startGroup(e){issue("group",e)}function endGroup(){issue("endgroup")}function group(e,t){return be(this,void 0,void 0,(function*(){startGroup(e);let i;try{i=yield t()}finally{endGroup()}return i}))}function saveState(e,t){const i=process.env["GITHUB_STATE"]||"";if(i){return issueFileCommand("STATE",prepareKeyValueMessage(e,t))}issueCommand("save-state",{name:e},toCommandValue(t))}function getState(e){return process.env[`STATE_${e}`]||""}function getIDToken(e){return be(this,void 0,void 0,(function*(){return yield OidcClient.getIDToken(e)}))}class GitHubRunnerWrapper{debug(e){core_debug(e)}error(e){error(e)}async exec(e,t,i){return getExecOutput(e,t,i)}setFailed(e){setFailed(e)}setSecret(e){core_setSecret(e)}warning(e){warning(e)}}class GitInvoker{_logger;_runnerInvoker;_isInitialized=false;_targetBranch="";_pullRequestId=0;_pullRequestIdInternal="";constructor(e,t){this._logger=e;this._runnerInvoker=t}get pullRequestId(){this._logger.logDebug("* GitInvoker.pullRequestId");if(this._pullRequestId!==0){return this._pullRequestId}this._pullRequestId=validateNumber(parseInt(this.pullRequestIdInternal,u),"Pull Request ID","GitInvoker.pullRequestId");return this._pullRequestId}get pullRequestIdInternal(){this._logger.logDebug("* GitInvoker.pullRequestIdInternal");if(this._pullRequestIdInternal!==""){return this._pullRequestIdInternal}this._pullRequestIdInternal=RunnerInvoker.isGitHub?this.pullRequestIdForGitHub:this.pullRequestIdForAzurePipelines;return this._pullRequestIdInternal}get pullRequestIdForGitHub(){this._logger.logDebug("* GitInvoker.pullRequestIdForGitHub");const e=process.env.GITHUB_REF;if(typeof e==="undefined"){this._logger.logWarning("'GITHUB_REF' is undefined.");return""}const t=e.split("/");const i=t[2];if(typeof i==="undefined"){this._logger.logWarning(`'GITHUB_REF' is in an incorrect format '${e}'.`);return""}if(!/^\d+$/u.test(i)){this._logger.logWarning(`Pull request ID '${i}' from 'GITHUB_REF' is not numeric.`);return""}return i}get pullRequestIdForAzurePipelines(){this._logger.logDebug("* GitInvoker.pullRequestIdForAzurePipelines");const e=process.env.BUILD_REPOSITORY_PROVIDER;if(typeof e==="undefined"){this._logger.logWarning("'BUILD_REPOSITORY_PROVIDER' is undefined.");return""}if(e==="GitHub"||e==="GitHubEnterprise"){const e=process.env.SYSTEM_PULLREQUEST_PULLREQUESTNUMBER;if(typeof e==="undefined"){this._logger.logWarning("'SYSTEM_PULLREQUEST_PULLREQUESTNUMBER' is undefined.");return""}if(!/^\d+$/u.test(e)){this._logger.logWarning(`'SYSTEM_PULLREQUEST_PULLREQUESTNUMBER' is not numeric '${e}'.`);return""}return e}const t=process.env.SYSTEM_PULLREQUEST_PULLREQUESTID;if(typeof t==="undefined"){this._logger.logWarning("'SYSTEM_PULLREQUEST_PULLREQUESTID' is undefined.");return""}if(!/^\d+$/u.test(t)){this._logger.logWarning(`'SYSTEM_PULLREQUEST_PULLREQUESTID' is not numeric '${t}'.`);return""}return t}get targetBranch(){this._logger.logDebug("* GitInvoker.targetBranch");if(RunnerInvoker.isGitHub){return validateVariable("GITHUB_BASE_REF","GitInvoker.targetBranch")}const e=validateVariable("SYSTEM_PULLREQUEST_TARGETBRANCH","GitInvoker.targetBranch");const t="refs/heads/";if(e.startsWith(t)){const i=t.length;return e.substring(i)}return e}async isGitRepo(){this._logger.logDebug("* GitInvoker.isGitRepo()");try{await this.invokeGit(["rev-parse","--is-inside-work-tree"]);return true}catch{return false}}isPullRequestIdAvailable(){this._logger.logDebug("* GitInvoker.isPullRequestIdAvailable()");return!Number.isNaN(parseInt(this.pullRequestIdInternal,u))}async isGitHistoryAvailable(){this._logger.logDebug("* GitInvoker.isGitHistoryAvailable()");this.initialize();try{await this.invokeGit(["rev-parse","--branch",`origin/${this._targetBranch}...pull/${this._pullRequestIdInternal}/merge`]);return true}catch{return false}}async getDiffSummary(){this._logger.logDebug("* GitInvoker.getDiffSummary()");this.initialize();return this.invokeGit(["diff","--numstat","--ignore-all-space",`origin/${this._targetBranch}...pull/${this._pullRequestIdInternal}/merge`])}initialize(){this._logger.logDebug("* GitInvoker.initialize()");if(this._isInitialized){return}this._targetBranch=this.targetBranch;if(/[\p{Cc}\s]/u.test(this._targetBranch)){throw new TypeError(`Target branch '${this._targetBranch}' contains whitespace or control characters, which is not allowed in command-line arguments.`)}this._pullRequestIdInternal=this.pullRequestIdInternal;this._isInitialized=true}async invokeGit(e){this._logger.logDebug("* GitInvoker.invokeGit()");const t=await this._runnerInvoker.exec("git",e);if(t.exitCode!==0){throw new Error(t.stderr)}return t.stdout}}class HttpWrapper{async getUrl(e){const t=await fetch(e,{signal:AbortSignal.timeout(d)});if(!t.ok){throw new Error(`HTTP request to '${e}' failed with status ${String(t.status)} (${t.statusText}).`)}return t.text()}}const Be=200;const Qe=2;const De=1;const Se=false;const ke=["**/*","!**/package-lock.json"];const Pe=["**/*{{t,T}est,TEST}*","**/*{{t,T}est,TEST}*/**","**/*.{{s,S}pec,SPEC}.*","**/*.{{s,S}pec,SPEC}.*/**"];const Ue=["js","_js","bones","cjs","es","es6","frag","gs","jake","jsb","jscad","jsfl","jsm","jss","jsx","mjs","njs","pac","sjs","ssjs","xsjs","xsjslib","epj","erb","py","cgi","fcgi","gyp","gypi","lmi","py3","pyde","pyi","pyp","pyt","pyw","rpy","smk","spec","tac","wsgi","xpy","pyx","pxd","pxi","eb","numpy","numpyw","numsc","pytb","java","jsp","ts","mts","tsx","mtsx","cs","cake","csx","linq","php","aw","ctp","fcgi","inc","php3","php4","php5","phps","phpt","cpp","c++","cc","cp","cxx","h","h++","hh","hpp","hxx","inc","inl","ino","ipp","re","tcc","tpp","c","cats","h","idc","cl","opencl","upc","xbm","xpm","pm","sh","bash","bats","cgi","command","env","fcgi","ksh","tmux","tool","zsh","fish","ebuild","eclass","ps1","psd1","psm1","tcsh","csh","rb","builder","eye","fcgi","gemspec","god","jbuilder","mspec","pluginspec","podspec","prawn","rabl","rake","rbi","rbuild","rbw","rbx","ru","ruby","spec","thor","watchr"];class Inputs{_logger;_runnerInvoker;_isInitialized=false;_baseSize=0;_growthRate=0;_testFactor=0;_alwaysCloseComment=false;_fileMatchingPatterns=[];_testMatchingPatterns=[];_codeFileExtensions=new Set;constructor(e,t){this._logger=e;this._runnerInvoker=t}get baseSize(){this._logger.logDebug("* Inputs.baseSize");this.initialize();return this._baseSize}get growthRate(){this._logger.logDebug("* Inputs.growthRate");this.initialize();return this._growthRate}get testFactor(){this._logger.logDebug("* Inputs.testFactor");this.initialize();return this._testFactor}get alwaysCloseComment(){this._logger.logDebug("* Inputs.alwaysCloseComment");this.initialize();return this._alwaysCloseComment}get fileMatchingPatterns(){this._logger.logDebug("* Inputs.fileMatchingPatterns");this.initialize();return this._fileMatchingPatterns}get testMatchingPatterns(){this._logger.logDebug("* Inputs.testMatchingPatterns");this.initialize();return this._testMatchingPatterns}get codeFileExtensions(){this._logger.logDebug("* Inputs.codeFileExtensions");this.initialize();return this._codeFileExtensions}initialize(){this._logger.logDebug("* Inputs.initialize()");if(this._isInitialized){return}const e=this._runnerInvoker.getInput(["Base","Size"]);this.initializeBaseSize(e);const t=this._runnerInvoker.getInput(["Growth","Rate"]);this.initializeGrowthRate(t);const i=this._runnerInvoker.getInput(["Test","Factor"]);this.initializeTestFactor(i);const n=this._runnerInvoker.getInput(["Always","Close","Comment"]);this.initializeAlwaysCloseComment(n);const r=this._runnerInvoker.getInput(["File","Matching","Patterns"]);this.initializeFileMatchingPatterns(r);const s=this._runnerInvoker.getInput(["Test","Matching","Patterns"]);this.initializeTestMatchingPatterns(s);const o=this._runnerInvoker.getInput(["Code","File","Extensions"]);this.initializeCodeFileExtensions(o);this._isInitialized=true}initializeBaseSize(e){this._logger.logDebug("* Inputs.initializeBaseSize()");const t=e===null?NaN:parseInt(e,u);if(!Number.isNaN(t)&&t>0){this._baseSize=t;const e=this._baseSize.toLocaleString();this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.settingBaseSize",e));return}const i=Be.toLocaleString();this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.adjustingBaseSize",i));this._baseSize=Be}initializeGrowthRate(e){this._logger.logDebug("* Inputs.initializeGrowthRate()");const t=e===null?NaN:parseFloat(e);if(!Number.isNaN(t)&&Number.isFinite(t)&&t>1){this._growthRate=t;const e=this._growthRate.toLocaleString();this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.settingGrowthRate",e));return}const i=Qe.toLocaleString();this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.adjustingGrowthRate",i));this._growthRate=Qe}initializeTestFactor(e){this._logger.logDebug("* Inputs.initializeTestFactor()");const t=e===null?NaN:parseFloat(e);if(!Number.isNaN(t)&&Number.isFinite(t)&&t>=0){if(t===0){this._testFactor=null;this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.disablingTestFactor"))}else{this._testFactor=t;const e=this._testFactor.toLocaleString();this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.settingTestFactor",e))}return}const i=De.toLocaleString();this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.adjustingTestFactor",i));this._testFactor=De}initializeAlwaysCloseComment(e){this._logger.logDebug("* Inputs.initializeAlwaysCloseComment()");const t=e?.toLowerCase()==="true";if(t){this._alwaysCloseComment=t;this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.settingAlwaysCloseComment"));return}this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.adjustingAlwaysCloseComment"));this._alwaysCloseComment=Se}initializeFileMatchingPatterns(e){this._logger.logDebug("* Inputs.initializeFileMatchingPatterns()");this._fileMatchingPatterns=this.initializeMatchingPatterns(e,ke,(e=>this._runnerInvoker.loc("metrics.inputs.settingFileMatchingPatterns",e)),(e=>this._runnerInvoker.loc("metrics.inputs.adjustingFileMatchingPatterns",e)))}initializeTestMatchingPatterns(e){this._logger.logDebug("* Inputs.initializeTestMatchingPatterns()");this._testMatchingPatterns=this.initializeMatchingPatterns(e,Pe,(e=>this._runnerInvoker.loc("metrics.inputs.settingTestMatchingPatterns",e)),(e=>this._runnerInvoker.loc("metrics.inputs.adjustingTestMatchingPatterns",e)))}initializeMatchingPatterns(e,t,i,n){this._logger.logDebug("* Inputs.initializeMatchingPatterns()");if(e!==null&&e.trim()!==""){const t=e.replace(/\\/gu,"/").replace(/\n$/gu,"").split("\n").map((e=>e.trim())).filter((e=>e!==""));if(t.length>p){this._logger.logWarning(`The matching pattern count '${t.length.toLocaleString()}' exceeds the maximum '${p.toLocaleString()}'. Using only the first '${p.toLocaleString()}'.`);t.length=p}const n=JSON.stringify(t);this._logger.logInfo(i(n));return t}const r=JSON.stringify(t);this._logger.logInfo(n(r));return t}initializeCodeFileExtensions(e){this._logger.logDebug("* Inputs.initializeCodeFileExtensions()");if(e!==null&&e.trim()!==""){const t="*.";const i=".";const n=e.replace(/\n$/gu,"").split("\n").map((e=>e.trim())).filter((e=>e!==""));for(const e of n){let n=e;if(n.startsWith(t)){n=n.substring(t.length)}else if(n.startsWith(i)){n=n.substring(i.length)}this._codeFileExtensions.add(n.toLowerCase())}this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.settingCodeFileExtensions",JSON.stringify([...this._codeFileExtensions])));return}this._logger.logInfo(this._runnerInvoker.loc("metrics.inputs.adjustingCodeFileExtensions",JSON.stringify(Ue)));this._codeFileExtensions=new Set(Ue)}}class Logger{static _sensitiveProperties=new Set(["AUTHORIZATION","COOKIE","PASSWORD","SECRET","TOKEN"]);_consoleWrapper;_runnerInvoker;_messages=[];constructor(e,t){this._consoleWrapper=e;this._runnerInvoker=t}static filterMessage(e){return e.replace(/##(?:vso)?\[/giu,"").replace(/[\n\r]/gu," ")}static _redactReplacer=(e,t)=>{if(e!==""&&Logger._sensitiveProperties.has(e.toUpperCase())){return"[REDACTED]"}return t};logDebug(e){const t=Logger.filterMessage(e);this._messages.push(`debug – ${t}`);this._runnerInvoker.logDebug(t)}logInfo(e){const t=Logger.filterMessage(e);this._messages.push(`info – ${t}`);this._consoleWrapper.log(t)}logWarning(e){const t=Logger.filterMessage(e);this._messages.push(`warning – ${t}`);this._runnerInvoker.logWarning(t)}logError(e){const t=Logger.filterMessage(e);this._messages.push(`error – ${t}`);this._runnerInvoker.logError(t)}logErrorObject(e){const{name:t}=e;const i=Object.getOwnPropertyNames(e);const n=e;for(const e of i){if(Logger._sensitiveProperties.has(e.toUpperCase())){this.logInfo(`${t} – ${e}: [REDACTED]`)}else{try{this.logInfo(`${t} – ${e}: ${JSON.stringify(n[e],Logger._redactReplacer)}`)}catch{this.logInfo(`${t} – ${e}: [COULD NOT SERIALIZE]`)}}}}replay(){for(const e of this._messages){this._consoleWrapper.log(`🔁 ${e}`)}}}class Context{line=1;lines=[];options={noPrefix:false};constructor(e,t){this.lines=e.split("\n");this.options.noPrefix=!!t?.noPrefix}getCurLine(){return this.lines[this.line-1]}nextLine(){this.line++;return this.getCurLine()}isEof(){return this.line>this.lines.length}}const Ve={Added:"AddedLine",Deleted:"DeletedLine",Unchanged:"UnchangedLine",Message:"MessageLine"};const Fe={Changed:"ChangedFile",Added:"AddedFile",Deleted:"DeletedFile",Renamed:"RenamedFile"};const Oe={Index:"index",OldMode:"old mode",NewMode:"new mode",Copy:"copy",Similarity:"similarity",Dissimilarity:"dissimilarity",Deleted:"deleted",NewFile:"new file",RenameFrom:"rename from",RenameTo:"rename to"};const Ne=Object.values(Oe);function parseGitDiff(e,t){const i=new Context(e,t);const n=parseFileChanges(i);return{type:"GitDiff",files:n}}function parseFileChanges(e){const t=[];while(!e.isEof()){const i=parseFileChange(e);if(!i){break}t.push(i)}return t}function parseFileChange(e){if(!isComparisonInputLine(e.getCurLine())){return}const t=parseComparisonInputLine(e);let i=false;let n=false;let r=false;let s="";let o="";let a=undefined;let l=undefined;while(!e.isEof()){const u=parseExtendedHeader(e);if(!u){break}if(u.type===Oe.Deleted){i=true;s=t?.from||""}if(u.type===Oe.NewFile){n=true;o=t?.to||""}if(u.type===Oe.RenameFrom){r=true;s=u.path}if(u.type===Oe.RenameTo){r=true;o=u.path}if(u.type===Oe.OldMode){a=u.mode}if(u.type===Oe.NewMode){l=u.mode}}const u=parseChangeMarkers(e);const c=parseChunks(e);if(i&&c.length&&c[0].type==="BinaryFilesChunk"){return{type:Fe.Deleted,chunks:c,path:c[0].pathBefore}}if(i){return{type:Fe.Deleted,chunks:c,path:u?.deleted||s}}else if(n&&c.length&&c[0].type==="BinaryFilesChunk"){return{type:Fe.Added,chunks:c,path:c[0].pathAfter}}else if(n){return{type:Fe.Added,chunks:c,path:u?.added||o}}else if(r){return{type:Fe.Renamed,pathAfter:o,pathBefore:s,chunks:c,oldMode:a,newMode:l}}else if(u){return{type:Fe.Changed,chunks:c,path:u.added,oldMode:a,newMode:l}}else if(a&&l&&t){return{type:Fe.Changed,chunks:c,path:t.to,oldMode:a,newMode:l}}else if(c.length&&c[0].type==="BinaryFilesChunk"&&c[0].pathAfter){return{type:Fe.Changed,chunks:c,path:c[0].pathAfter}}return}function isComparisonInputLine(e){return e.indexOf("diff")===0}function parseComparisonInputLine(e){const t=e.getCurLine();const[i,n]=t.split(" ").reverse();e.nextLine();if(i&&n){return{from:getFilePath(e,n,"src"),to:getFilePath(e,i,"dst")}}return null}function parseChunks(e){const t=[];while(!e.isEof()){const i=parseChunk(e);if(!i){break}t.push(i)}return t}function parseChunk(e){const t=parseChunkHeader(e);if(!t){return}if(t.type==="Normal"){const i=parseChanges(e,t.fromFileRange,t.toFileRange);return{...t,type:"Chunk",changes:i}}else if(t.type==="Combined"&&t.fromFileRangeA&&t.fromFileRangeB){const i=parseChanges(e,t.fromFileRangeA.startt.startsWith(e)));if(i){e.nextLine()}if(i===Oe.RenameFrom||i===Oe.RenameTo){return{type:i,path:t.slice(`${i} `.length)}}else if(i===Oe.OldMode||i===Oe.NewMode){return{type:i,mode:t.slice(`${i} `.length)}}else if(i){return{type:i}}return null}function parseChunkHeader(e){const t=e.getCurLine();const i=/^@@\s\-(\d+),?(\d+)?\s\+(\d+),?(\d+)?\s@@\s?(.+)?/.exec(t);if(!i){const i=/^@@@\s\-(\d+),?(\d+)?\s\-(\d+),?(\d+)?\s\+(\d+),?(\d+)?\s@@@\s?(.+)?/.exec(t);if(!i){const i=/^Binary\sfiles\s(.*)\sand\s(.*)\sdiffer$/.exec(t);if(i){const[t,n,r]=i;e.nextLine();return{type:"BinaryFiles",fileA:getFilePath(e,n,"src"),fileB:getFilePath(e,r,"dst")}}return null}const[n,r,s,o,a,l,u,c]=i;e.nextLine();return{context:c,type:"Combined",fromFileRangeA:getRange(r,s),fromFileRangeB:getRange(o,a),toFileRange:getRange(l,u)}}const[n,r,s,o,a,l]=i;e.nextLine();return{context:l,type:"Normal",toFileRange:getRange(o,a),fromFileRange:getRange(r,s)}}function getRange(e,t){const i=parseInt(e,10);return{start:i,lines:t===undefined?1:parseInt(t,10)}}function parseChangeMarkers(e){const t=parseMarker(e,"--- ");const i=t?getFilePath(e,t,"src"):t;const n=parseMarker(e,"+++ ");const r=n?getFilePath(e,n,"dst"):n;return r&&i?{added:r,deleted:i}:null}function parseMarker(e,t){const i=e.getCurLine();if(i?.startsWith(t)){e.nextLine();return i.replace(t,"")}return null}const qe={"+":Ve.Added,"-":Ve.Deleted," ":Ve.Unchanged,"\\":Ve.Message};function parseChanges(e,t,i){const n=[];let r=t.start;let s=i.start;while(!e.isEof()){const t=e.getCurLine();const i=getLineType(t);if(!i){break}e.nextLine();let o;const a=t.slice(1);switch(i){case Ve.Added:{o={type:i,lineAfter:s++,content:a};break}case Ve.Deleted:{o={type:i,lineBefore:r++,content:a};break}case Ve.Unchanged:{o={type:i,lineBefore:r++,lineAfter:s++,content:a};break}case Ve.Message:{o={type:i,content:a.trim()};break}}n.push(o)}return n}function getLineType(e){return qe[e[0]]||null}function getFilePath(e,t,i){if(e.options.noPrefix){return t}if(i==="src")return t.replace(/^a\//,"");if(i==="dst")return t.replace(/^b\//,"");throw new Error("Unexpected unreachable code")}const _e=parseGitDiff;class OctokitGitDiffParser{_httpWrapper;_logger;_firstLineOfFiles=null;constructor(e,t){this._httpWrapper=e;this._logger=t}async getFirstChangedLine(e,t,i,n,r){this._logger.logDebug("* OctokitGitDiffParser.getFirstChangedLine()");const s=await this.getFirstChangedLines(e,t,i,n);return s.get(r)??null}async getFirstChangedLines(e,t,i,n){this._logger.logDebug("* OctokitGitDiffParser.getFirstChangedLines()");if(this._firstLineOfFiles!==null){return this._firstLineOfFiles}const r=await this.getDiffs(e,t,i,n);this._firstLineOfFiles=this.processDiffs(r);return this._firstLineOfFiles}async getDiffs(e,t,i,n){this._logger.logDebug("* OctokitGitDiffParser.getDiffs()");const r=await e.getPull(t,i,n);const s=await this._httpWrapper.getUrl(r.data.diff_url);const o=s.split(/^diff --git/gmu);return o.slice(1).map((e=>`diff --git ${e}`))}processDiffs(e){this._logger.logDebug("* OctokitGitDiffParser.processDiffs()");const t=new Map;for(const i of e){const e=_e(i);for(const i of e.files){switch(i.type){case"AddedFile":case"ChangedFile":{const e=i;const[n]=e.chunks;if(n?.type==="BinaryFilesChunk"){this._logger.logDebug(`Skipping '${i.type}' '${e.path}' while performing diff parsing.`);break}if(n){t.set(e.path,n.toFileRange.start)}break}case"RenamedFile":{const e=i;const[n]=e.chunks;if(n?.type==="BinaryFilesChunk"){this._logger.logDebug(`Skipping '${i.type}' '${e.pathAfter}' while performing diff parsing.`);break}if(n){t.set(e.pathAfter,n.toFileRange.start)}break}case"DeletedFile":default:this._logger.logDebug(`Skipping file type '${i.type}' while performing diff parsing.`);break}}}return t}}function getUserAgent(){if(typeof navigator==="object"&&"userAgent"in navigator){return navigator.userAgent}if(typeof process==="object"&&process.version!==undefined){return`Node.js/${process.version.substr(1)} (${process.platform}; ${process.arch})`}return""}function register(e,t,i,n){if(typeof i!=="function"){throw new Error("method for before hook must be a function")}if(!n){n={}}if(Array.isArray(t)){return t.reverse().reduce(((t,i)=>register.bind(null,e,i,t,n)),i)()}return Promise.resolve().then((()=>{if(!e.registry[t]){return i(n)}return e.registry[t].reduce(((e,t)=>t.hook.bind(null,e,n)),i)()}))}function addHook(e,t,i,n){const r=n;if(!e.registry[i]){e.registry[i]=[]}if(t==="before"){n=(e,t)=>Promise.resolve().then(r.bind(null,t)).then(e.bind(null,t))}if(t==="after"){n=(e,t)=>{let i;return Promise.resolve().then(e.bind(null,t)).then((e=>{i=e;return r(i,t)})).then((()=>i))}}if(t==="error"){n=(e,t)=>Promise.resolve().then(e.bind(null,t)).catch((e=>r(e,t)))}e.registry[i].push({hook:n,orig:r})}function removeHook(e,t,i){if(!e.registry[t]){return}const n=e.registry[t].map((e=>e.orig)).indexOf(i);if(n===-1){return}e.registry[t].splice(n,1)}const Me=Function.bind;const Le=Me.bind(Me);function bindApi(e,t,i){const n=Le(removeHook,null).apply(null,i?[t,i]:[t]);e.api={remove:n};e.remove=n;["before","error","after","wrap"].forEach((n=>{const r=i?[t,n,i]:[t,n];e[n]=e.api[n]=Le(addHook,null).apply(null,r)}))}function Singular(){const e=Symbol("Singular");const t={registry:{}};const i=register.bind(null,t,e);bindApi(i,t,e);return i}function Collection(){const e={registry:{}};const t=register.bind(null,e);bindApi(t,e);return t}const Ge={Singular:Singular,Collection:Collection};var je="0.0.0-development";var xe=`octokit-endpoint.js/${je} ${getUserAgent()}`;var He={method:"GET",baseUrl:"https://api.github.com",headers:{accept:"application/vnd.github.v3+json","user-agent":xe},mediaType:{format:""}};function dist_bundle_lowercaseKeys(e){if(!e){return{}}return Object.keys(e).reduce(((t,i)=>{t[i.toLowerCase()]=e[i];return t}),{})}function isPlainObject(e){if(typeof e!=="object"||e===null)return false;if(Object.prototype.toString.call(e)!=="[object Object]")return false;const t=Object.getPrototypeOf(e);if(t===null)return true;const i=Object.prototype.hasOwnProperty.call(t,"constructor")&&t.constructor;return typeof i==="function"&&i instanceof i&&Function.prototype.call(i)===Function.prototype.call(e)}function mergeDeep(e,t){const i=Object.assign({},e);Object.keys(t).forEach((n=>{if(isPlainObject(t[n])){if(!(n in e))Object.assign(i,{[n]:t[n]});else i[n]=mergeDeep(e[n],t[n])}else{Object.assign(i,{[n]:t[n]})}}));return i}function removeUndefinedProperties(e){for(const t in e){if(e[t]===void 0){delete e[t]}}return e}function merge(e,t,i){if(typeof t==="string"){let[e,n]=t.split(" ");i=Object.assign(n?{method:e,url:n}:{url:e},i)}else{i=Object.assign({},t)}i.headers=dist_bundle_lowercaseKeys(i.headers);removeUndefinedProperties(i);removeUndefinedProperties(i.headers);const n=mergeDeep(e||{},i);if(i.url==="/graphql"){if(e&&e.mediaType.previews?.length){n.mediaType.previews=e.mediaType.previews.filter((e=>!n.mediaType.previews.includes(e))).concat(n.mediaType.previews)}n.mediaType.previews=(n.mediaType.previews||[]).map((e=>e.replace(/-preview/,"")))}return n}function addQueryParameters(e,t){const i=/\?/.test(e)?"&":"?";const n=Object.keys(t);if(n.length===0){return e}return e+i+n.map((e=>{if(e==="q"){return"q="+t.q.split("+").map(encodeURIComponent).join("+")}return`${e}=${encodeURIComponent(t[e])}`})).join("&")}var We=/\{[^{}}]+\}/g;function removeNonChars(e){return e.replace(/(?:^\W+)|(?:(?e.concat(t)),[])}function omit(e,t){const i={__proto__:null};for(const n of Object.keys(e)){if(t.indexOf(n)===-1){i[n]=e[n]}}return i}function encodeReserved(e){return e.split(/(%[0-9A-Fa-f]{2})/g).map((function(e){if(!/%[0-9A-Fa-f]/.test(e)){e=encodeURI(e).replace(/%5B/g,"[").replace(/%5D/g,"]")}return e})).join("")}function encodeUnreserved(e){return encodeURIComponent(e).replace(/[!'()*]/g,(function(e){return"%"+e.charCodeAt(0).toString(16).toUpperCase()}))}function encodeValue(e,t,i){t=e==="+"||e==="#"?encodeReserved(t):encodeUnreserved(t);if(i){return encodeUnreserved(i)+"="+t}else{return t}}function isDefined(e){return e!==void 0&&e!==null}function isKeyOperator(e){return e===";"||e==="&"||e==="?"}function getValues(e,t,i,n){var r=e[i],s=[];if(isDefined(r)&&r!==""){if(typeof r==="string"||typeof r==="number"||typeof r==="bigint"||typeof r==="boolean"){r=r.toString();if(n&&n!=="*"){r=r.substring(0,parseInt(n,10))}s.push(encodeValue(t,r,isKeyOperator(t)?i:""))}else{if(n==="*"){if(Array.isArray(r)){r.filter(isDefined).forEach((function(e){s.push(encodeValue(t,e,isKeyOperator(t)?i:""))}))}else{Object.keys(r).forEach((function(e){if(isDefined(r[e])){s.push(encodeValue(t,r[e],e))}}))}}else{const e=[];if(Array.isArray(r)){r.filter(isDefined).forEach((function(i){e.push(encodeValue(t,i))}))}else{Object.keys(r).forEach((function(i){if(isDefined(r[i])){e.push(encodeUnreserved(i));e.push(encodeValue(t,r[i].toString()))}}))}if(isKeyOperator(t)){s.push(encodeUnreserved(i)+"="+e.join(","))}else if(e.length!==0){s.push(e.join(","))}}}}else{if(t===";"){if(isDefined(r)){s.push(encodeUnreserved(i))}}else if(r===""&&(t==="&"||t==="?")){s.push(encodeUnreserved(i)+"=")}else if(r===""){s.push("")}}return s}function parseUrl(e){return{expand:expand.bind(null,e)}}function expand(e,t){var i=["+","#",".","/",";","?","&"];e=e.replace(/\{([^\{\}]+)\}|([^\{\}]+)/g,(function(e,n,r){if(n){let e="";const r=[];if(i.indexOf(n.charAt(0))!==-1){e=n.charAt(0);n=n.substr(1)}n.split(/,/g).forEach((function(i){var n=/([^:\*]*)(?::(\d+)|(\*))?/.exec(i);r.push(getValues(t,e,n[1],n[2]||n[3]))}));if(e&&e!=="+"){var s=",";if(e==="?"){s="&"}else if(e!=="#"){s=e}return(r.length!==0?e:"")+r.join(s)}else{return r.join(",")}}else{return encodeReserved(r)}}));if(e==="/"){return e}else{return e.replace(/\/$/,"")}}function parse(e){let t=e.method.toUpperCase();let i=(e.url||"/").replace(/:([a-z]\w+)/g,"{$1}");let n=Object.assign({},e.headers);let r;let s=omit(e,["method","baseUrl","url","headers","request","mediaType"]);const o=extractUrlVariableNames(i);i=parseUrl(i).expand(s);if(!/^http/.test(i)){i=e.baseUrl+i}const a=Object.keys(e).filter((e=>o.includes(e))).concat("baseUrl");const l=omit(s,a);const u=/application\/octet-stream/i.test(n.accept);if(!u){if(e.mediaType.format){n.accept=n.accept.split(/,/).map((t=>t.replace(/application\/vnd(\.\w+)(\.v3)?(\.\w+)?(\+json)?$/,`application/vnd$1$2.${e.mediaType.format}`))).join(",")}if(i.endsWith("/graphql")){if(e.mediaType.previews?.length){const t=n.accept.match(/(?{const i=e.mediaType.format?`.${e.mediaType.format}`:"+json";return`application/vnd.github.${t}-preview${i}`})).join(",")}}}if(["GET","HEAD"].includes(t)){i=addQueryParameters(i,l)}else{if("data"in l){r=l.data}else{if(Object.keys(l).length){r=l}}}if(!n["content-type"]&&typeof r!=="undefined"){n["content-type"]="application/json; charset=utf-8"}if(["PATCH","PUT"].includes(t)&&typeof r==="undefined"){r=""}return Object.assign({method:t,url:i,headers:n},typeof r!=="undefined"?{body:r}:null,e.request?{request:e.request}:null)}function endpointWithDefaults(e,t,i){return parse(merge(e,t,i))}function withDefaults(e,t){const i=merge(e,t);const n=endpointWithDefaults.bind(null,i);return Object.assign(n,{DEFAULTS:i,defaults:withDefaults.bind(null,i),merge:merge.bind(null,i),parse:parse})}var Ye=withDefaults(null,He);var Je=__nccwpck_require__(1120);const ze=/^-?\d+$/;const $e=/^-?\d+n+$/;const Ke=JSON.stringify;const Ze=JSON.parse;const Xe=/^-?\d+n$/;const et=/([\[:])?"(-?\d+)n"($|([\\n]|\s)*(\s|[\\n])*[,\}\]])/g;const tt=/([\[:])?("-?\d+n+)n("$|"([\\n]|\s)*(\s|[\\n])*[,\}\]])/g;const JSONStringify=(e,t,i)=>{if("rawJSON"in JSON){return Ke(e,((e,i)=>{if(typeof i==="bigint")return JSON.rawJSON(i.toString());if(typeof t==="function")return t(e,i);if(Array.isArray(t)&&t.includes(e))return i;return i}),i)}if(!e)return Ke(e,t,i);const n=Ke(e,((e,i)=>{const n=typeof i==="string"&&$e.test(i);if(n)return i.toString()+"n";if(typeof i==="bigint")return i.toString()+"n";if(typeof t==="function")return t(e,i);if(Array.isArray(t)&&t.includes(e))return i;return i}),i);const r=n.replace(et,"$1$2$3");const s=r.replace(tt,"$1$2$3");return s};const it=new Map;const isContextSourceSupported=()=>{const e=JSON.parse.toString();if(it.has(e)){return it.get(e)}try{const t=JSON.parse("1",((e,t,i)=>!!i?.source&&i.source==="1"));it.set(e,t);return t}catch{it.set(e,false);return false}};const convertMarkedBigIntsReviver=(e,t,i,n)=>{const r=typeof t==="string"&&Xe.test(t);if(r)return BigInt(t.slice(0,-1));const s=typeof t==="string"&&$e.test(t);if(s)return t.slice(0,-1);if(typeof n!=="function")return t;return n(e,t,i)};const JSONParseV2=(e,t)=>JSON.parse(e,((e,i,n)=>{const r=typeof i==="number"&&(i>Number.MAX_SAFE_INTEGER||i{if(!e)return Ze(e,t);if(isContextSourceSupported())return JSONParseV2(e,t);const i=e.replace(st,((e,t,i,n)=>{const r=e[0]==='"';const s=r&&ot.test(e);if(s)return e.substring(0,e.length-1)+'n"';const o=i||n;const a=t&&(t.lengthconvertMarkedBigIntsReviver(e,i,n,t)))};var at="10.0.8";var ut={headers:{"user-agent":`octokit-request.js/${at} ${getUserAgent()}`}};function dist_bundle_isPlainObject(e){if(typeof e!=="object"||e===null)return false;if(Object.prototype.toString.call(e)!=="[object Object]")return false;const t=Object.getPrototypeOf(e);if(t===null)return true;const i=Object.prototype.hasOwnProperty.call(t,"constructor")&&t.constructor;return typeof i==="function"&&i instanceof i&&Function.prototype.call(i)===Function.prototype.call(e)}var noop=()=>"";async function fetchWrapper(e){const t=e.request?.fetch||globalThis.fetch;if(!t){throw new Error("fetch is not set. Please pass a fetch implementation as new Octokit({ request: { fetch }}). Learn more at https://github.com/octokit/octokit.js/#fetch-missing")}const i=e.request?.log||console;const n=e.request?.parseSuccessResponseBody!==false;const r=dist_bundle_isPlainObject(e.body)||Array.isArray(e.body)?JSONStringify(e.body):e.body;const s=Object.fromEntries(Object.entries(e.headers).map((([e,t])=>[e,String(t)])));let o;try{o=await t(e.url,{method:e.method,body:r,redirect:e.request?.redirect,headers:s,signal:e.request?.signal,...e.body&&{duplex:"half"}})}catch(t){let i="Unknown Error";if(t instanceof Error){if(t.name==="AbortError"){t.status=500;throw t}i=t.message;if(t.name==="TypeError"&&"cause"in t){if(t.cause instanceof Error){i=t.cause.message}else if(typeof t.cause==="string"){i=t.cause}}}const n=new RequestError(i,500,{request:e});n.cause=t;throw n}const a=o.status;const l=o.url;const u={};for(const[e,t]of o.headers){u[e]=t}const c={url:l,status:a,headers:u,data:""};if("deprecation"in u){const t=u.link&&u.link.match(/<([^<>]+)>; rel="deprecation"/);const n=t&&t.pop();i.warn(`[@octokit/request] "${e.method} ${e.url}" is deprecated. It is scheduled to be removed on ${u.sunset}${n?`. See ${n}`:""}`)}if(a===204||a===205){return c}if(e.method==="HEAD"){if(a<400){return c}throw new RequestError(o.statusText,a,{response:c,request:e})}if(a===304){c.data=await getResponseData(o);throw new RequestError("Not modified",a,{response:c,request:e})}if(a>=400){c.data=await getResponseData(o);throw new RequestError(toErrorMessage(c.data),a,{response:c,request:e})}c.data=n?await getResponseData(o):o.body;return c}async function getResponseData(e){const t=e.headers.get("content-type");if(!t){return e.text().catch(noop)}const i=(0,Je.xL)(t);if(isJSONResponse(i)){let t="";try{t=await e.text();return JSONParse(t)}catch(e){return t}}else if(i.type.startsWith("text/")||i.parameters.charset?.toLowerCase()==="utf-8"){return e.text().catch(noop)}else{return e.arrayBuffer().catch(( /* v8 ignore next -- @preserve */ -()=>new ArrayBuffer(0)))}}function isJSONResponse(e){return e.type==="application/json"||e.type==="application/scim+json"}function toErrorMessage(e){if(typeof e==="string"){return e}if(e instanceof ArrayBuffer){return"Unknown error"}if("message"in e){const t="documentation_url"in e?` - ${e.documentation_url}`:"";return Array.isArray(e.errors)?`${e.message}: ${e.errors.map((e=>JSON.stringify(e))).join(", ")}${t}`:`${e.message}${t}`}return`Unknown error: ${JSON.stringify(e)}`}function dist_bundle_withDefaults(e,t){const i=e.defaults(t);const newApi=function(e,t){const n=i.merge(e,t);if(!n.request||!n.request.hook){return fetchWrapper(i.parse(n))}const request2=(e,t)=>fetchWrapper(i.parse(i.merge(e,t)));Object.assign(request2,{endpoint:i,defaults:dist_bundle_withDefaults.bind(null,i)});return n.request.hook(request2,n)};return Object.assign(newApi,{endpoint:i,defaults:dist_bundle_withDefaults.bind(null,i)})}var ut=dist_bundle_withDefaults(We,at); +()=>new ArrayBuffer(0)))}}function isJSONResponse(e){return e.type==="application/json"||e.type==="application/scim+json"}function toErrorMessage(e){if(typeof e==="string"){return e}if(e instanceof ArrayBuffer){return"Unknown error"}if("message"in e){const t="documentation_url"in e?` - ${e.documentation_url}`:"";return Array.isArray(e.errors)?`${e.message}: ${e.errors.map((e=>JSON.stringify(e))).join(", ")}${t}`:`${e.message}${t}`}return`Unknown error: ${JSON.stringify(e)}`}function dist_bundle_withDefaults(e,t){const i=e.defaults(t);const newApi=function(e,t){const n=i.merge(e,t);if(!n.request||!n.request.hook){return fetchWrapper(i.parse(n))}const request2=(e,t)=>fetchWrapper(i.parse(i.merge(e,t)));Object.assign(request2,{endpoint:i,defaults:dist_bundle_withDefaults.bind(null,i)});return n.request.hook(request2,n)};return Object.assign(newApi,{endpoint:i,defaults:dist_bundle_withDefaults.bind(null,i)})}var ct=dist_bundle_withDefaults(Ye,ut); /* v8 ignore next -- @preserve */ -/* v8 ignore else -- @preserve */var ct="0.0.0-development";function _buildMessageForResponseErrors(e){return`Request failed due to following response errors:\n`+e.errors.map((e=>` - ${e.message}`)).join("\n")}var dt=class extends Error{constructor(e,t,i){super(_buildMessageForResponseErrors(i));this.request=e;this.headers=t;this.response=i;this.errors=i.errors;this.data=i.data;if(Error.captureStackTrace){Error.captureStackTrace(this,this.constructor)}}name="GraphqlResponseError";errors;data};var pt=["method","baseUrl","url","headers","request","query","mediaType","operationName"];var At=["query","method","url"];var ft=/\/api\/v3\/?$/;function graphql(e,t,i){if(i){if(typeof t==="string"&&"query"in i){return Promise.reject(new Error(`[@octokit/graphql] "query" cannot be used as variable name`))}for(const e in i){if(!At.includes(e))continue;return Promise.reject(new Error(`[@octokit/graphql] "${e}" cannot be used as variable name`))}}const n=typeof t==="string"?Object.assign({query:t},i):t;const r=Object.keys(n).reduce(((e,t)=>{if(pt.includes(t)){e[t]=n[t];return e}if(!e.variables){e.variables={}}e.variables[t]=n[t];return e}),{});const s=n.baseUrl||e.endpoint.DEFAULTS.baseUrl;if(ft.test(s)){r.url=s.replace(ft,"/api/graphql")}return e(r).then((e=>{if(e.data.errors){const t={};for(const i of Object.keys(e.headers)){t[i]=e.headers[i]}throw new dt(r,t,e.data)}return e.data.data}))}function graphql_dist_bundle_withDefaults(e,t){const i=e.defaults(t);const newApi=(e,t)=>graphql(i,e,t);return Object.assign(newApi,{defaults:graphql_dist_bundle_withDefaults.bind(null,i),endpoint:i.endpoint})}var ht=graphql_dist_bundle_withDefaults(ut,{headers:{"user-agent":`octokit-graphql.js/${ct} ${getUserAgent()}`},method:"POST",url:"/graphql"});function withCustomRequest(e){return graphql_dist_bundle_withDefaults(e,{method:"POST",url:"/graphql"})}var yt="(?:[a-zA-Z0-9_-]+)";var mt="\\.";var It=new RegExp(`^${yt}${mt}${yt}${mt}${yt}$`);var vt=It.test.bind(It);async function auth(e){const t=vt(e);const i=e.startsWith("v1.")||e.startsWith("ghs_");const n=e.startsWith("ghu_");const r=t?"app":i?"installation":n?"user-to-server":"oauth";return{type:"token",token:e,tokenType:r}}function withAuthorizationPrefix(e){if(e.split(/\./).length===3){return`bearer ${e}`}return`token ${e}`}async function hook(e,t,i,n){const r=t.endpoint.merge(i,n);r.headers.authorization=withAuthorizationPrefix(e);return t(r)}var Et=function createTokenAuth2(e){if(!e){throw new Error("[@octokit/auth-token] No token passed to createTokenAuth")}if(typeof e!=="string"){throw new Error("[@octokit/auth-token] Token passed to createTokenAuth is not a string")}e=e.replace(/^(token|bearer) +/i,"");return Object.assign(auth.bind(null,e),{hook:hook.bind(null,e)})};const Ct="7.0.6";const dist_src_noop=()=>{};const Tt=console.warn.bind(console);const Rt=console.error.bind(console);function createLogger(e={}){if(typeof e.debug!=="function"){e.debug=dist_src_noop}if(typeof e.info!=="function"){e.info=dist_src_noop}if(typeof e.warn!=="function"){e.warn=Tt}if(typeof e.error!=="function"){e.error=Rt}return e}const bt=`octokit-core.js/${Ct} ${getUserAgent()}`;class Octokit{static VERSION=Ct;static defaults(e){const t=class extends(this){constructor(...t){const i=t[0]||{};if(typeof e==="function"){super(e(i));return}super(Object.assign({},e,i,i.userAgent&&e.userAgent?{userAgent:`${i.userAgent} ${e.userAgent}`}:null))}};return t}static plugins=[];static plugin(...e){const t=this.plugins;const i=class extends(this){static plugins=t.concat(e.filter((e=>!t.includes(e))))};return i}constructor(e={}){const t=new Le.Collection;const i={baseUrl:ut.endpoint.DEFAULTS.baseUrl,headers:{},request:Object.assign({},e.request,{hook:t.bind(null,"request")}),mediaType:{previews:[],format:""}};i.headers["user-agent"]=e.userAgent?`${e.userAgent} ${bt}`:bt;if(e.baseUrl){i.baseUrl=e.baseUrl}if(e.previews){i.mediaType.previews=e.previews}if(e.timeZone){i.headers["time-zone"]=e.timeZone}this.request=ut.defaults(i);this.graphql=withCustomRequest(this.request).defaults(i);this.log=createLogger(e.log);this.hook=t;if(!e.authStrategy){if(!e.auth){this.auth=async()=>({type:"unauthenticated"})}else{const i=Et(e.auth);t.wrap("request",i.hook);this.auth=i}}else{const{authStrategy:i,...n}=e;const r=i(Object.assign({request:this.request,log:this.log,octokit:this,octokitOptions:n},e.auth));t.wrap("request",r.hook);this.auth=r}const n=this.constructor;for(let t=0;t{e.log.debug("request",i);const n=Date.now();const r=e.request.endpoint.parse(i);const s=r.url.replace(i.baseUrl,"");return t(i).then((t=>{const i=t.headers["x-github-request-id"];e.log.info(`${r.method} ${s} - ${t.status} with id ${i} in ${Date.now()-n}ms`);return t})).catch((t=>{const i=t.response?.headers["x-github-request-id"]||"UNKNOWN";e.log.error(`${r.method} ${s} - ${t.status} with id ${i} in ${Date.now()-n}ms`);throw t}))}))}requestLog.VERSION=wt;var Bt="0.0.0-development";function normalizePaginatedListResponse(e){if(!e.data){return{...e,data:[]}}const t=("total_count"in e.data||"total_commits"in e.data)&&!("url"in e.data);if(!t)return e;const i=e.data.incomplete_results;const n=e.data.repository_selection;const r=e.data.total_count;const s=e.data.total_commits;delete e.data.incomplete_results;delete e.data.repository_selection;delete e.data.total_count;delete e.data.total_commits;const o=Object.keys(e.data)[0];const a=e.data[o];e.data=a;if(typeof i!=="undefined"){e.data.incomplete_results=i}if(typeof n!=="undefined"){e.data.repository_selection=n}e.data.total_count=r;e.data.total_commits=s;return e}function iterator(e,t,i){const n=typeof t==="function"?t.endpoint(i):e.request.endpoint(t,i);const r=typeof t==="function"?t:e.request;const s=n.method;const o=n.headers;let a=n.url;return{[Symbol.asyncIterator]:()=>({async next(){if(!a)return{done:true};try{const e=await r({method:s,url:a,headers:o});const t=normalizePaginatedListResponse(e);a=((t.headers.link||"").match(/<([^<>]+)>;\s*rel="next"/)||[])[1];if(!a&&"total_commits"in t.data){const e=new URL(t.url);const i=e.searchParams;const n=parseInt(i.get("page")||"1",10);const r=parseInt(i.get("per_page")||"250",10);if(n*r{if(r.done){return t}let s=false;function done(){s=true}t=t.concat(n?n(r.value,done):r.value.data);if(s){return t}return gather(e,t,i,n)}))}var Qt=Object.assign(paginate,{iterator:iterator});var Dt=null&&["GET /advisories","GET /app/hook/deliveries","GET /app/installation-requests","GET /app/installations","GET /assignments/{assignment_id}/accepted_assignments","GET /classrooms","GET /classrooms/{classroom_id}/assignments","GET /enterprises/{enterprise}/code-security/configurations","GET /enterprises/{enterprise}/code-security/configurations/{configuration_id}/repositories","GET /enterprises/{enterprise}/dependabot/alerts","GET /enterprises/{enterprise}/teams","GET /enterprises/{enterprise}/teams/{enterprise-team}/memberships","GET /enterprises/{enterprise}/teams/{enterprise-team}/organizations","GET /events","GET /gists","GET /gists/public","GET /gists/starred","GET /gists/{gist_id}/comments","GET /gists/{gist_id}/commits","GET /gists/{gist_id}/forks","GET /installation/repositories","GET /issues","GET /licenses","GET /marketplace_listing/plans","GET /marketplace_listing/plans/{plan_id}/accounts","GET /marketplace_listing/stubbed/plans","GET /marketplace_listing/stubbed/plans/{plan_id}/accounts","GET /networks/{owner}/{repo}/events","GET /notifications","GET /organizations","GET /organizations/{org}/dependabot/repository-access","GET /orgs/{org}/actions/cache/usage-by-repository","GET /orgs/{org}/actions/hosted-runners","GET /orgs/{org}/actions/permissions/repositories","GET /orgs/{org}/actions/permissions/self-hosted-runners/repositories","GET /orgs/{org}/actions/runner-groups","GET /orgs/{org}/actions/runner-groups/{runner_group_id}/hosted-runners","GET /orgs/{org}/actions/runner-groups/{runner_group_id}/repositories","GET /orgs/{org}/actions/runner-groups/{runner_group_id}/runners","GET /orgs/{org}/actions/runners","GET /orgs/{org}/actions/secrets","GET /orgs/{org}/actions/secrets/{secret_name}/repositories","GET /orgs/{org}/actions/variables","GET /orgs/{org}/actions/variables/{name}/repositories","GET /orgs/{org}/attestations/repositories","GET /orgs/{org}/attestations/{subject_digest}","GET /orgs/{org}/blocks","GET /orgs/{org}/campaigns","GET /orgs/{org}/code-scanning/alerts","GET /orgs/{org}/code-security/configurations","GET /orgs/{org}/code-security/configurations/{configuration_id}/repositories","GET /orgs/{org}/codespaces","GET /orgs/{org}/codespaces/secrets","GET /orgs/{org}/codespaces/secrets/{secret_name}/repositories","GET /orgs/{org}/copilot/billing/seats","GET /orgs/{org}/copilot/metrics","GET /orgs/{org}/dependabot/alerts","GET /orgs/{org}/dependabot/secrets","GET /orgs/{org}/dependabot/secrets/{secret_name}/repositories","GET /orgs/{org}/events","GET /orgs/{org}/failed_invitations","GET /orgs/{org}/hooks","GET /orgs/{org}/hooks/{hook_id}/deliveries","GET /orgs/{org}/insights/api/route-stats/{actor_type}/{actor_id}","GET /orgs/{org}/insights/api/subject-stats","GET /orgs/{org}/insights/api/user-stats/{user_id}","GET /orgs/{org}/installations","GET /orgs/{org}/invitations","GET /orgs/{org}/invitations/{invitation_id}/teams","GET /orgs/{org}/issues","GET /orgs/{org}/members","GET /orgs/{org}/members/{username}/codespaces","GET /orgs/{org}/migrations","GET /orgs/{org}/migrations/{migration_id}/repositories","GET /orgs/{org}/organization-roles/{role_id}/teams","GET /orgs/{org}/organization-roles/{role_id}/users","GET /orgs/{org}/outside_collaborators","GET /orgs/{org}/packages","GET /orgs/{org}/packages/{package_type}/{package_name}/versions","GET /orgs/{org}/personal-access-token-requests","GET /orgs/{org}/personal-access-token-requests/{pat_request_id}/repositories","GET /orgs/{org}/personal-access-tokens","GET /orgs/{org}/personal-access-tokens/{pat_id}/repositories","GET /orgs/{org}/private-registries","GET /orgs/{org}/projects","GET /orgs/{org}/projectsV2","GET /orgs/{org}/projectsV2/{project_number}/fields","GET /orgs/{org}/projectsV2/{project_number}/items","GET /orgs/{org}/properties/values","GET /orgs/{org}/public_members","GET /orgs/{org}/repos","GET /orgs/{org}/rulesets","GET /orgs/{org}/rulesets/rule-suites","GET /orgs/{org}/rulesets/{ruleset_id}/history","GET /orgs/{org}/secret-scanning/alerts","GET /orgs/{org}/security-advisories","GET /orgs/{org}/settings/immutable-releases/repositories","GET /orgs/{org}/settings/network-configurations","GET /orgs/{org}/team/{team_slug}/copilot/metrics","GET /orgs/{org}/teams","GET /orgs/{org}/teams/{team_slug}/discussions","GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments","GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions","GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions","GET /orgs/{org}/teams/{team_slug}/invitations","GET /orgs/{org}/teams/{team_slug}/members","GET /orgs/{org}/teams/{team_slug}/projects","GET /orgs/{org}/teams/{team_slug}/repos","GET /orgs/{org}/teams/{team_slug}/teams","GET /projects/{project_id}/collaborators","GET /repos/{owner}/{repo}/actions/artifacts","GET /repos/{owner}/{repo}/actions/caches","GET /repos/{owner}/{repo}/actions/organization-secrets","GET /repos/{owner}/{repo}/actions/organization-variables","GET /repos/{owner}/{repo}/actions/runners","GET /repos/{owner}/{repo}/actions/runs","GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts","GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/jobs","GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs","GET /repos/{owner}/{repo}/actions/secrets","GET /repos/{owner}/{repo}/actions/variables","GET /repos/{owner}/{repo}/actions/workflows","GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs","GET /repos/{owner}/{repo}/activity","GET /repos/{owner}/{repo}/assignees","GET /repos/{owner}/{repo}/attestations/{subject_digest}","GET /repos/{owner}/{repo}/branches","GET /repos/{owner}/{repo}/check-runs/{check_run_id}/annotations","GET /repos/{owner}/{repo}/check-suites/{check_suite_id}/check-runs","GET /repos/{owner}/{repo}/code-scanning/alerts","GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances","GET /repos/{owner}/{repo}/code-scanning/analyses","GET /repos/{owner}/{repo}/codespaces","GET /repos/{owner}/{repo}/codespaces/devcontainers","GET /repos/{owner}/{repo}/codespaces/secrets","GET /repos/{owner}/{repo}/collaborators","GET /repos/{owner}/{repo}/comments","GET /repos/{owner}/{repo}/comments/{comment_id}/reactions","GET /repos/{owner}/{repo}/commits","GET /repos/{owner}/{repo}/commits/{commit_sha}/comments","GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls","GET /repos/{owner}/{repo}/commits/{ref}/check-runs","GET /repos/{owner}/{repo}/commits/{ref}/check-suites","GET /repos/{owner}/{repo}/commits/{ref}/status","GET /repos/{owner}/{repo}/commits/{ref}/statuses","GET /repos/{owner}/{repo}/compare/{basehead}","GET /repos/{owner}/{repo}/compare/{base}...{head}","GET /repos/{owner}/{repo}/contributors","GET /repos/{owner}/{repo}/dependabot/alerts","GET /repos/{owner}/{repo}/dependabot/secrets","GET /repos/{owner}/{repo}/deployments","GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses","GET /repos/{owner}/{repo}/environments","GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies","GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/apps","GET /repos/{owner}/{repo}/environments/{environment_name}/secrets","GET /repos/{owner}/{repo}/environments/{environment_name}/variables","GET /repos/{owner}/{repo}/events","GET /repos/{owner}/{repo}/forks","GET /repos/{owner}/{repo}/hooks","GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries","GET /repos/{owner}/{repo}/invitations","GET /repos/{owner}/{repo}/issues","GET /repos/{owner}/{repo}/issues/comments","GET /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions","GET /repos/{owner}/{repo}/issues/events","GET /repos/{owner}/{repo}/issues/{issue_number}/comments","GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by","GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocking","GET /repos/{owner}/{repo}/issues/{issue_number}/events","GET /repos/{owner}/{repo}/issues/{issue_number}/labels","GET /repos/{owner}/{repo}/issues/{issue_number}/reactions","GET /repos/{owner}/{repo}/issues/{issue_number}/sub_issues","GET /repos/{owner}/{repo}/issues/{issue_number}/timeline","GET /repos/{owner}/{repo}/keys","GET /repos/{owner}/{repo}/labels","GET /repos/{owner}/{repo}/milestones","GET /repos/{owner}/{repo}/milestones/{milestone_number}/labels","GET /repos/{owner}/{repo}/notifications","GET /repos/{owner}/{repo}/pages/builds","GET /repos/{owner}/{repo}/projects","GET /repos/{owner}/{repo}/pulls","GET /repos/{owner}/{repo}/pulls/comments","GET /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions","GET /repos/{owner}/{repo}/pulls/{pull_number}/comments","GET /repos/{owner}/{repo}/pulls/{pull_number}/commits","GET /repos/{owner}/{repo}/pulls/{pull_number}/files","GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews","GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/comments","GET /repos/{owner}/{repo}/releases","GET /repos/{owner}/{repo}/releases/{release_id}/assets","GET /repos/{owner}/{repo}/releases/{release_id}/reactions","GET /repos/{owner}/{repo}/rules/branches/{branch}","GET /repos/{owner}/{repo}/rulesets","GET /repos/{owner}/{repo}/rulesets/rule-suites","GET /repos/{owner}/{repo}/rulesets/{ruleset_id}/history","GET /repos/{owner}/{repo}/secret-scanning/alerts","GET /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}/locations","GET /repos/{owner}/{repo}/security-advisories","GET /repos/{owner}/{repo}/stargazers","GET /repos/{owner}/{repo}/subscribers","GET /repos/{owner}/{repo}/tags","GET /repos/{owner}/{repo}/teams","GET /repos/{owner}/{repo}/topics","GET /repositories","GET /search/code","GET /search/commits","GET /search/issues","GET /search/labels","GET /search/repositories","GET /search/topics","GET /search/users","GET /teams/{team_id}/discussions","GET /teams/{team_id}/discussions/{discussion_number}/comments","GET /teams/{team_id}/discussions/{discussion_number}/comments/{comment_number}/reactions","GET /teams/{team_id}/discussions/{discussion_number}/reactions","GET /teams/{team_id}/invitations","GET /teams/{team_id}/members","GET /teams/{team_id}/projects","GET /teams/{team_id}/repos","GET /teams/{team_id}/teams","GET /user/blocks","GET /user/codespaces","GET /user/codespaces/secrets","GET /user/emails","GET /user/followers","GET /user/following","GET /user/gpg_keys","GET /user/installations","GET /user/installations/{installation_id}/repositories","GET /user/issues","GET /user/keys","GET /user/marketplace_purchases","GET /user/marketplace_purchases/stubbed","GET /user/memberships/orgs","GET /user/migrations","GET /user/migrations/{migration_id}/repositories","GET /user/orgs","GET /user/packages","GET /user/packages/{package_type}/{package_name}/versions","GET /user/public_emails","GET /user/repos","GET /user/repository_invitations","GET /user/social_accounts","GET /user/ssh_signing_keys","GET /user/starred","GET /user/subscriptions","GET /user/teams","GET /users","GET /users/{username}/attestations/{subject_digest}","GET /users/{username}/events","GET /users/{username}/events/orgs/{org}","GET /users/{username}/events/public","GET /users/{username}/followers","GET /users/{username}/following","GET /users/{username}/gists","GET /users/{username}/gpg_keys","GET /users/{username}/keys","GET /users/{username}/orgs","GET /users/{username}/packages","GET /users/{username}/projects","GET /users/{username}/projectsV2","GET /users/{username}/projectsV2/{project_number}/fields","GET /users/{username}/projectsV2/{project_number}/items","GET /users/{username}/received_events","GET /users/{username}/received_events/public","GET /users/{username}/repos","GET /users/{username}/social_accounts","GET /users/{username}/ssh_signing_keys","GET /users/{username}/starred","GET /users/{username}/subscriptions"];function isPaginatingEndpoint(e){if(typeof e==="string"){return Dt.includes(e)}else{return false}}function paginateRest(e){return{paginate:Object.assign(paginate.bind(null,e),{iterator:iterator.bind(null,e)})}}paginateRest.VERSION=Bt;const St="17.0.0";const kt={actions:{addCustomLabelsToSelfHostedRunnerForOrg:["POST /orgs/{org}/actions/runners/{runner_id}/labels"],addCustomLabelsToSelfHostedRunnerForRepo:["POST /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],addRepoAccessToSelfHostedRunnerGroupInOrg:["PUT /orgs/{org}/actions/runner-groups/{runner_group_id}/repositories/{repository_id}"],addSelectedRepoToOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}/repositories/{repository_id}"],addSelectedRepoToOrgVariable:["PUT /orgs/{org}/actions/variables/{name}/repositories/{repository_id}"],approveWorkflowRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/approve"],cancelWorkflowRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/cancel"],createEnvironmentVariable:["POST /repos/{owner}/{repo}/environments/{environment_name}/variables"],createHostedRunnerForOrg:["POST /orgs/{org}/actions/hosted-runners"],createOrUpdateEnvironmentSecret:["PUT /repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}"],createOrUpdateOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}"],createOrUpdateRepoSecret:["PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}"],createOrgVariable:["POST /orgs/{org}/actions/variables"],createRegistrationTokenForOrg:["POST /orgs/{org}/actions/runners/registration-token"],createRegistrationTokenForRepo:["POST /repos/{owner}/{repo}/actions/runners/registration-token"],createRemoveTokenForOrg:["POST /orgs/{org}/actions/runners/remove-token"],createRemoveTokenForRepo:["POST /repos/{owner}/{repo}/actions/runners/remove-token"],createRepoVariable:["POST /repos/{owner}/{repo}/actions/variables"],createWorkflowDispatch:["POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches"],deleteActionsCacheById:["DELETE /repos/{owner}/{repo}/actions/caches/{cache_id}"],deleteActionsCacheByKey:["DELETE /repos/{owner}/{repo}/actions/caches{?key,ref}"],deleteArtifact:["DELETE /repos/{owner}/{repo}/actions/artifacts/{artifact_id}"],deleteCustomImageFromOrg:["DELETE /orgs/{org}/actions/hosted-runners/images/custom/{image_definition_id}"],deleteCustomImageVersionFromOrg:["DELETE /orgs/{org}/actions/hosted-runners/images/custom/{image_definition_id}/versions/{version}"],deleteEnvironmentSecret:["DELETE /repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}"],deleteEnvironmentVariable:["DELETE /repos/{owner}/{repo}/environments/{environment_name}/variables/{name}"],deleteHostedRunnerForOrg:["DELETE /orgs/{org}/actions/hosted-runners/{hosted_runner_id}"],deleteOrgSecret:["DELETE /orgs/{org}/actions/secrets/{secret_name}"],deleteOrgVariable:["DELETE /orgs/{org}/actions/variables/{name}"],deleteRepoSecret:["DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}"],deleteRepoVariable:["DELETE /repos/{owner}/{repo}/actions/variables/{name}"],deleteSelfHostedRunnerFromOrg:["DELETE /orgs/{org}/actions/runners/{runner_id}"],deleteSelfHostedRunnerFromRepo:["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}"],deleteWorkflowRun:["DELETE /repos/{owner}/{repo}/actions/runs/{run_id}"],deleteWorkflowRunLogs:["DELETE /repos/{owner}/{repo}/actions/runs/{run_id}/logs"],disableSelectedRepositoryGithubActionsOrganization:["DELETE /orgs/{org}/actions/permissions/repositories/{repository_id}"],disableWorkflow:["PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/disable"],downloadArtifact:["GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}/{archive_format}"],downloadJobLogsForWorkflowRun:["GET /repos/{owner}/{repo}/actions/jobs/{job_id}/logs"],downloadWorkflowRunAttemptLogs:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/logs"],downloadWorkflowRunLogs:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/logs"],enableSelectedRepositoryGithubActionsOrganization:["PUT /orgs/{org}/actions/permissions/repositories/{repository_id}"],enableWorkflow:["PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/enable"],forceCancelWorkflowRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/force-cancel"],generateRunnerJitconfigForOrg:["POST /orgs/{org}/actions/runners/generate-jitconfig"],generateRunnerJitconfigForRepo:["POST /repos/{owner}/{repo}/actions/runners/generate-jitconfig"],getActionsCacheList:["GET /repos/{owner}/{repo}/actions/caches"],getActionsCacheUsage:["GET /repos/{owner}/{repo}/actions/cache/usage"],getActionsCacheUsageByRepoForOrg:["GET /orgs/{org}/actions/cache/usage-by-repository"],getActionsCacheUsageForOrg:["GET /orgs/{org}/actions/cache/usage"],getAllowedActionsOrganization:["GET /orgs/{org}/actions/permissions/selected-actions"],getAllowedActionsRepository:["GET /repos/{owner}/{repo}/actions/permissions/selected-actions"],getArtifact:["GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}"],getCustomImageForOrg:["GET /orgs/{org}/actions/hosted-runners/images/custom/{image_definition_id}"],getCustomImageVersionForOrg:["GET /orgs/{org}/actions/hosted-runners/images/custom/{image_definition_id}/versions/{version}"],getCustomOidcSubClaimForRepo:["GET /repos/{owner}/{repo}/actions/oidc/customization/sub"],getEnvironmentPublicKey:["GET /repos/{owner}/{repo}/environments/{environment_name}/secrets/public-key"],getEnvironmentSecret:["GET /repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}"],getEnvironmentVariable:["GET /repos/{owner}/{repo}/environments/{environment_name}/variables/{name}"],getGithubActionsDefaultWorkflowPermissionsOrganization:["GET /orgs/{org}/actions/permissions/workflow"],getGithubActionsDefaultWorkflowPermissionsRepository:["GET /repos/{owner}/{repo}/actions/permissions/workflow"],getGithubActionsPermissionsOrganization:["GET /orgs/{org}/actions/permissions"],getGithubActionsPermissionsRepository:["GET /repos/{owner}/{repo}/actions/permissions"],getHostedRunnerForOrg:["GET /orgs/{org}/actions/hosted-runners/{hosted_runner_id}"],getHostedRunnersGithubOwnedImagesForOrg:["GET /orgs/{org}/actions/hosted-runners/images/github-owned"],getHostedRunnersLimitsForOrg:["GET /orgs/{org}/actions/hosted-runners/limits"],getHostedRunnersMachineSpecsForOrg:["GET /orgs/{org}/actions/hosted-runners/machine-sizes"],getHostedRunnersPartnerImagesForOrg:["GET /orgs/{org}/actions/hosted-runners/images/partner"],getHostedRunnersPlatformsForOrg:["GET /orgs/{org}/actions/hosted-runners/platforms"],getJobForWorkflowRun:["GET /repos/{owner}/{repo}/actions/jobs/{job_id}"],getOrgPublicKey:["GET /orgs/{org}/actions/secrets/public-key"],getOrgSecret:["GET /orgs/{org}/actions/secrets/{secret_name}"],getOrgVariable:["GET /orgs/{org}/actions/variables/{name}"],getPendingDeploymentsForRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments"],getRepoPermissions:["GET /repos/{owner}/{repo}/actions/permissions",{},{renamed:["actions","getGithubActionsPermissionsRepository"]}],getRepoPublicKey:["GET /repos/{owner}/{repo}/actions/secrets/public-key"],getRepoSecret:["GET /repos/{owner}/{repo}/actions/secrets/{secret_name}"],getRepoVariable:["GET /repos/{owner}/{repo}/actions/variables/{name}"],getReviewsForRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/approvals"],getSelfHostedRunnerForOrg:["GET /orgs/{org}/actions/runners/{runner_id}"],getSelfHostedRunnerForRepo:["GET /repos/{owner}/{repo}/actions/runners/{runner_id}"],getWorkflow:["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}"],getWorkflowAccessToRepository:["GET /repos/{owner}/{repo}/actions/permissions/access"],getWorkflowRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}"],getWorkflowRunAttempt:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}"],getWorkflowRunUsage:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/timing"],getWorkflowUsage:["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/timing"],listArtifactsForRepo:["GET /repos/{owner}/{repo}/actions/artifacts"],listCustomImageVersionsForOrg:["GET /orgs/{org}/actions/hosted-runners/images/custom/{image_definition_id}/versions"],listCustomImagesForOrg:["GET /orgs/{org}/actions/hosted-runners/images/custom"],listEnvironmentSecrets:["GET /repos/{owner}/{repo}/environments/{environment_name}/secrets"],listEnvironmentVariables:["GET /repos/{owner}/{repo}/environments/{environment_name}/variables"],listGithubHostedRunnersInGroupForOrg:["GET /orgs/{org}/actions/runner-groups/{runner_group_id}/hosted-runners"],listHostedRunnersForOrg:["GET /orgs/{org}/actions/hosted-runners"],listJobsForWorkflowRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs"],listJobsForWorkflowRunAttempt:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/jobs"],listLabelsForSelfHostedRunnerForOrg:["GET /orgs/{org}/actions/runners/{runner_id}/labels"],listLabelsForSelfHostedRunnerForRepo:["GET /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],listOrgSecrets:["GET /orgs/{org}/actions/secrets"],listOrgVariables:["GET /orgs/{org}/actions/variables"],listRepoOrganizationSecrets:["GET /repos/{owner}/{repo}/actions/organization-secrets"],listRepoOrganizationVariables:["GET /repos/{owner}/{repo}/actions/organization-variables"],listRepoSecrets:["GET /repos/{owner}/{repo}/actions/secrets"],listRepoVariables:["GET /repos/{owner}/{repo}/actions/variables"],listRepoWorkflows:["GET /repos/{owner}/{repo}/actions/workflows"],listRunnerApplicationsForOrg:["GET /orgs/{org}/actions/runners/downloads"],listRunnerApplicationsForRepo:["GET /repos/{owner}/{repo}/actions/runners/downloads"],listSelectedReposForOrgSecret:["GET /orgs/{org}/actions/secrets/{secret_name}/repositories"],listSelectedReposForOrgVariable:["GET /orgs/{org}/actions/variables/{name}/repositories"],listSelectedRepositoriesEnabledGithubActionsOrganization:["GET /orgs/{org}/actions/permissions/repositories"],listSelfHostedRunnersForOrg:["GET /orgs/{org}/actions/runners"],listSelfHostedRunnersForRepo:["GET /repos/{owner}/{repo}/actions/runners"],listWorkflowRunArtifacts:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts"],listWorkflowRuns:["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs"],listWorkflowRunsForRepo:["GET /repos/{owner}/{repo}/actions/runs"],reRunJobForWorkflowRun:["POST /repos/{owner}/{repo}/actions/jobs/{job_id}/rerun"],reRunWorkflow:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun"],reRunWorkflowFailedJobs:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun-failed-jobs"],removeAllCustomLabelsFromSelfHostedRunnerForOrg:["DELETE /orgs/{org}/actions/runners/{runner_id}/labels"],removeAllCustomLabelsFromSelfHostedRunnerForRepo:["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],removeCustomLabelFromSelfHostedRunnerForOrg:["DELETE /orgs/{org}/actions/runners/{runner_id}/labels/{name}"],removeCustomLabelFromSelfHostedRunnerForRepo:["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}/labels/{name}"],removeSelectedRepoFromOrgSecret:["DELETE /orgs/{org}/actions/secrets/{secret_name}/repositories/{repository_id}"],removeSelectedRepoFromOrgVariable:["DELETE /orgs/{org}/actions/variables/{name}/repositories/{repository_id}"],reviewCustomGatesForRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/deployment_protection_rule"],reviewPendingDeploymentsForRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments"],setAllowedActionsOrganization:["PUT /orgs/{org}/actions/permissions/selected-actions"],setAllowedActionsRepository:["PUT /repos/{owner}/{repo}/actions/permissions/selected-actions"],setCustomLabelsForSelfHostedRunnerForOrg:["PUT /orgs/{org}/actions/runners/{runner_id}/labels"],setCustomLabelsForSelfHostedRunnerForRepo:["PUT /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],setCustomOidcSubClaimForRepo:["PUT /repos/{owner}/{repo}/actions/oidc/customization/sub"],setGithubActionsDefaultWorkflowPermissionsOrganization:["PUT /orgs/{org}/actions/permissions/workflow"],setGithubActionsDefaultWorkflowPermissionsRepository:["PUT /repos/{owner}/{repo}/actions/permissions/workflow"],setGithubActionsPermissionsOrganization:["PUT /orgs/{org}/actions/permissions"],setGithubActionsPermissionsRepository:["PUT /repos/{owner}/{repo}/actions/permissions"],setSelectedReposForOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}/repositories"],setSelectedReposForOrgVariable:["PUT /orgs/{org}/actions/variables/{name}/repositories"],setSelectedRepositoriesEnabledGithubActionsOrganization:["PUT /orgs/{org}/actions/permissions/repositories"],setWorkflowAccessToRepository:["PUT /repos/{owner}/{repo}/actions/permissions/access"],updateEnvironmentVariable:["PATCH /repos/{owner}/{repo}/environments/{environment_name}/variables/{name}"],updateHostedRunnerForOrg:["PATCH /orgs/{org}/actions/hosted-runners/{hosted_runner_id}"],updateOrgVariable:["PATCH /orgs/{org}/actions/variables/{name}"],updateRepoVariable:["PATCH /repos/{owner}/{repo}/actions/variables/{name}"]},activity:{checkRepoIsStarredByAuthenticatedUser:["GET /user/starred/{owner}/{repo}"],deleteRepoSubscription:["DELETE /repos/{owner}/{repo}/subscription"],deleteThreadSubscription:["DELETE /notifications/threads/{thread_id}/subscription"],getFeeds:["GET /feeds"],getRepoSubscription:["GET /repos/{owner}/{repo}/subscription"],getThread:["GET /notifications/threads/{thread_id}"],getThreadSubscriptionForAuthenticatedUser:["GET /notifications/threads/{thread_id}/subscription"],listEventsForAuthenticatedUser:["GET /users/{username}/events"],listNotificationsForAuthenticatedUser:["GET /notifications"],listOrgEventsForAuthenticatedUser:["GET /users/{username}/events/orgs/{org}"],listPublicEvents:["GET /events"],listPublicEventsForRepoNetwork:["GET /networks/{owner}/{repo}/events"],listPublicEventsForUser:["GET /users/{username}/events/public"],listPublicOrgEvents:["GET /orgs/{org}/events"],listReceivedEventsForUser:["GET /users/{username}/received_events"],listReceivedPublicEventsForUser:["GET /users/{username}/received_events/public"],listRepoEvents:["GET /repos/{owner}/{repo}/events"],listRepoNotificationsForAuthenticatedUser:["GET /repos/{owner}/{repo}/notifications"],listReposStarredByAuthenticatedUser:["GET /user/starred"],listReposStarredByUser:["GET /users/{username}/starred"],listReposWatchedByUser:["GET /users/{username}/subscriptions"],listStargazersForRepo:["GET /repos/{owner}/{repo}/stargazers"],listWatchedReposForAuthenticatedUser:["GET /user/subscriptions"],listWatchersForRepo:["GET /repos/{owner}/{repo}/subscribers"],markNotificationsAsRead:["PUT /notifications"],markRepoNotificationsAsRead:["PUT /repos/{owner}/{repo}/notifications"],markThreadAsDone:["DELETE /notifications/threads/{thread_id}"],markThreadAsRead:["PATCH /notifications/threads/{thread_id}"],setRepoSubscription:["PUT /repos/{owner}/{repo}/subscription"],setThreadSubscription:["PUT /notifications/threads/{thread_id}/subscription"],starRepoForAuthenticatedUser:["PUT /user/starred/{owner}/{repo}"],unstarRepoForAuthenticatedUser:["DELETE /user/starred/{owner}/{repo}"]},apps:{addRepoToInstallation:["PUT /user/installations/{installation_id}/repositories/{repository_id}",{},{renamed:["apps","addRepoToInstallationForAuthenticatedUser"]}],addRepoToInstallationForAuthenticatedUser:["PUT /user/installations/{installation_id}/repositories/{repository_id}"],checkToken:["POST /applications/{client_id}/token"],createFromManifest:["POST /app-manifests/{code}/conversions"],createInstallationAccessToken:["POST /app/installations/{installation_id}/access_tokens"],deleteAuthorization:["DELETE /applications/{client_id}/grant"],deleteInstallation:["DELETE /app/installations/{installation_id}"],deleteToken:["DELETE /applications/{client_id}/token"],getAuthenticated:["GET /app"],getBySlug:["GET /apps/{app_slug}"],getInstallation:["GET /app/installations/{installation_id}"],getOrgInstallation:["GET /orgs/{org}/installation"],getRepoInstallation:["GET /repos/{owner}/{repo}/installation"],getSubscriptionPlanForAccount:["GET /marketplace_listing/accounts/{account_id}"],getSubscriptionPlanForAccountStubbed:["GET /marketplace_listing/stubbed/accounts/{account_id}"],getUserInstallation:["GET /users/{username}/installation"],getWebhookConfigForApp:["GET /app/hook/config"],getWebhookDelivery:["GET /app/hook/deliveries/{delivery_id}"],listAccountsForPlan:["GET /marketplace_listing/plans/{plan_id}/accounts"],listAccountsForPlanStubbed:["GET /marketplace_listing/stubbed/plans/{plan_id}/accounts"],listInstallationReposForAuthenticatedUser:["GET /user/installations/{installation_id}/repositories"],listInstallationRequestsForAuthenticatedApp:["GET /app/installation-requests"],listInstallations:["GET /app/installations"],listInstallationsForAuthenticatedUser:["GET /user/installations"],listPlans:["GET /marketplace_listing/plans"],listPlansStubbed:["GET /marketplace_listing/stubbed/plans"],listReposAccessibleToInstallation:["GET /installation/repositories"],listSubscriptionsForAuthenticatedUser:["GET /user/marketplace_purchases"],listSubscriptionsForAuthenticatedUserStubbed:["GET /user/marketplace_purchases/stubbed"],listWebhookDeliveries:["GET /app/hook/deliveries"],redeliverWebhookDelivery:["POST /app/hook/deliveries/{delivery_id}/attempts"],removeRepoFromInstallation:["DELETE /user/installations/{installation_id}/repositories/{repository_id}",{},{renamed:["apps","removeRepoFromInstallationForAuthenticatedUser"]}],removeRepoFromInstallationForAuthenticatedUser:["DELETE /user/installations/{installation_id}/repositories/{repository_id}"],resetToken:["PATCH /applications/{client_id}/token"],revokeInstallationAccessToken:["DELETE /installation/token"],scopeToken:["POST /applications/{client_id}/token/scoped"],suspendInstallation:["PUT /app/installations/{installation_id}/suspended"],unsuspendInstallation:["DELETE /app/installations/{installation_id}/suspended"],updateWebhookConfigForApp:["PATCH /app/hook/config"]},billing:{getGithubActionsBillingOrg:["GET /orgs/{org}/settings/billing/actions"],getGithubActionsBillingUser:["GET /users/{username}/settings/billing/actions"],getGithubBillingPremiumRequestUsageReportOrg:["GET /organizations/{org}/settings/billing/premium_request/usage"],getGithubBillingPremiumRequestUsageReportUser:["GET /users/{username}/settings/billing/premium_request/usage"],getGithubBillingUsageReportOrg:["GET /organizations/{org}/settings/billing/usage"],getGithubBillingUsageReportUser:["GET /users/{username}/settings/billing/usage"],getGithubPackagesBillingOrg:["GET /orgs/{org}/settings/billing/packages"],getGithubPackagesBillingUser:["GET /users/{username}/settings/billing/packages"],getSharedStorageBillingOrg:["GET /orgs/{org}/settings/billing/shared-storage"],getSharedStorageBillingUser:["GET /users/{username}/settings/billing/shared-storage"]},campaigns:{createCampaign:["POST /orgs/{org}/campaigns"],deleteCampaign:["DELETE /orgs/{org}/campaigns/{campaign_number}"],getCampaignSummary:["GET /orgs/{org}/campaigns/{campaign_number}"],listOrgCampaigns:["GET /orgs/{org}/campaigns"],updateCampaign:["PATCH /orgs/{org}/campaigns/{campaign_number}"]},checks:{create:["POST /repos/{owner}/{repo}/check-runs"],createSuite:["POST /repos/{owner}/{repo}/check-suites"],get:["GET /repos/{owner}/{repo}/check-runs/{check_run_id}"],getSuite:["GET /repos/{owner}/{repo}/check-suites/{check_suite_id}"],listAnnotations:["GET /repos/{owner}/{repo}/check-runs/{check_run_id}/annotations"],listForRef:["GET /repos/{owner}/{repo}/commits/{ref}/check-runs"],listForSuite:["GET /repos/{owner}/{repo}/check-suites/{check_suite_id}/check-runs"],listSuitesForRef:["GET /repos/{owner}/{repo}/commits/{ref}/check-suites"],rerequestRun:["POST /repos/{owner}/{repo}/check-runs/{check_run_id}/rerequest"],rerequestSuite:["POST /repos/{owner}/{repo}/check-suites/{check_suite_id}/rerequest"],setSuitesPreferences:["PATCH /repos/{owner}/{repo}/check-suites/preferences"],update:["PATCH /repos/{owner}/{repo}/check-runs/{check_run_id}"]},codeScanning:{commitAutofix:["POST /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/autofix/commits"],createAutofix:["POST /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/autofix"],createVariantAnalysis:["POST /repos/{owner}/{repo}/code-scanning/codeql/variant-analyses"],deleteAnalysis:["DELETE /repos/{owner}/{repo}/code-scanning/analyses/{analysis_id}{?confirm_delete}"],deleteCodeqlDatabase:["DELETE /repos/{owner}/{repo}/code-scanning/codeql/databases/{language}"],getAlert:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}",{},{renamedParameters:{alert_id:"alert_number"}}],getAnalysis:["GET /repos/{owner}/{repo}/code-scanning/analyses/{analysis_id}"],getAutofix:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/autofix"],getCodeqlDatabase:["GET /repos/{owner}/{repo}/code-scanning/codeql/databases/{language}"],getDefaultSetup:["GET /repos/{owner}/{repo}/code-scanning/default-setup"],getSarif:["GET /repos/{owner}/{repo}/code-scanning/sarifs/{sarif_id}"],getVariantAnalysis:["GET /repos/{owner}/{repo}/code-scanning/codeql/variant-analyses/{codeql_variant_analysis_id}"],getVariantAnalysisRepoTask:["GET /repos/{owner}/{repo}/code-scanning/codeql/variant-analyses/{codeql_variant_analysis_id}/repos/{repo_owner}/{repo_name}"],listAlertInstances:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances"],listAlertsForOrg:["GET /orgs/{org}/code-scanning/alerts"],listAlertsForRepo:["GET /repos/{owner}/{repo}/code-scanning/alerts"],listAlertsInstances:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances",{},{renamed:["codeScanning","listAlertInstances"]}],listCodeqlDatabases:["GET /repos/{owner}/{repo}/code-scanning/codeql/databases"],listRecentAnalyses:["GET /repos/{owner}/{repo}/code-scanning/analyses"],updateAlert:["PATCH /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}"],updateDefaultSetup:["PATCH /repos/{owner}/{repo}/code-scanning/default-setup"],uploadSarif:["POST /repos/{owner}/{repo}/code-scanning/sarifs"]},codeSecurity:{attachConfiguration:["POST /orgs/{org}/code-security/configurations/{configuration_id}/attach"],attachEnterpriseConfiguration:["POST /enterprises/{enterprise}/code-security/configurations/{configuration_id}/attach"],createConfiguration:["POST /orgs/{org}/code-security/configurations"],createConfigurationForEnterprise:["POST /enterprises/{enterprise}/code-security/configurations"],deleteConfiguration:["DELETE /orgs/{org}/code-security/configurations/{configuration_id}"],deleteConfigurationForEnterprise:["DELETE /enterprises/{enterprise}/code-security/configurations/{configuration_id}"],detachConfiguration:["DELETE /orgs/{org}/code-security/configurations/detach"],getConfiguration:["GET /orgs/{org}/code-security/configurations/{configuration_id}"],getConfigurationForRepository:["GET /repos/{owner}/{repo}/code-security-configuration"],getConfigurationsForEnterprise:["GET /enterprises/{enterprise}/code-security/configurations"],getConfigurationsForOrg:["GET /orgs/{org}/code-security/configurations"],getDefaultConfigurations:["GET /orgs/{org}/code-security/configurations/defaults"],getDefaultConfigurationsForEnterprise:["GET /enterprises/{enterprise}/code-security/configurations/defaults"],getRepositoriesForConfiguration:["GET /orgs/{org}/code-security/configurations/{configuration_id}/repositories"],getRepositoriesForEnterpriseConfiguration:["GET /enterprises/{enterprise}/code-security/configurations/{configuration_id}/repositories"],getSingleConfigurationForEnterprise:["GET /enterprises/{enterprise}/code-security/configurations/{configuration_id}"],setConfigurationAsDefault:["PUT /orgs/{org}/code-security/configurations/{configuration_id}/defaults"],setConfigurationAsDefaultForEnterprise:["PUT /enterprises/{enterprise}/code-security/configurations/{configuration_id}/defaults"],updateConfiguration:["PATCH /orgs/{org}/code-security/configurations/{configuration_id}"],updateEnterpriseConfiguration:["PATCH /enterprises/{enterprise}/code-security/configurations/{configuration_id}"]},codesOfConduct:{getAllCodesOfConduct:["GET /codes_of_conduct"],getConductCode:["GET /codes_of_conduct/{key}"]},codespaces:{addRepositoryForSecretForAuthenticatedUser:["PUT /user/codespaces/secrets/{secret_name}/repositories/{repository_id}"],addSelectedRepoToOrgSecret:["PUT /orgs/{org}/codespaces/secrets/{secret_name}/repositories/{repository_id}"],checkPermissionsForDevcontainer:["GET /repos/{owner}/{repo}/codespaces/permissions_check"],codespaceMachinesForAuthenticatedUser:["GET /user/codespaces/{codespace_name}/machines"],createForAuthenticatedUser:["POST /user/codespaces"],createOrUpdateOrgSecret:["PUT /orgs/{org}/codespaces/secrets/{secret_name}"],createOrUpdateRepoSecret:["PUT /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"],createOrUpdateSecretForAuthenticatedUser:["PUT /user/codespaces/secrets/{secret_name}"],createWithPrForAuthenticatedUser:["POST /repos/{owner}/{repo}/pulls/{pull_number}/codespaces"],createWithRepoForAuthenticatedUser:["POST /repos/{owner}/{repo}/codespaces"],deleteForAuthenticatedUser:["DELETE /user/codespaces/{codespace_name}"],deleteFromOrganization:["DELETE /orgs/{org}/members/{username}/codespaces/{codespace_name}"],deleteOrgSecret:["DELETE /orgs/{org}/codespaces/secrets/{secret_name}"],deleteRepoSecret:["DELETE /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"],deleteSecretForAuthenticatedUser:["DELETE /user/codespaces/secrets/{secret_name}"],exportForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/exports"],getCodespacesForUserInOrg:["GET /orgs/{org}/members/{username}/codespaces"],getExportDetailsForAuthenticatedUser:["GET /user/codespaces/{codespace_name}/exports/{export_id}"],getForAuthenticatedUser:["GET /user/codespaces/{codespace_name}"],getOrgPublicKey:["GET /orgs/{org}/codespaces/secrets/public-key"],getOrgSecret:["GET /orgs/{org}/codespaces/secrets/{secret_name}"],getPublicKeyForAuthenticatedUser:["GET /user/codespaces/secrets/public-key"],getRepoPublicKey:["GET /repos/{owner}/{repo}/codespaces/secrets/public-key"],getRepoSecret:["GET /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"],getSecretForAuthenticatedUser:["GET /user/codespaces/secrets/{secret_name}"],listDevcontainersInRepositoryForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces/devcontainers"],listForAuthenticatedUser:["GET /user/codespaces"],listInOrganization:["GET /orgs/{org}/codespaces",{},{renamedParameters:{org_id:"org"}}],listInRepositoryForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces"],listOrgSecrets:["GET /orgs/{org}/codespaces/secrets"],listRepoSecrets:["GET /repos/{owner}/{repo}/codespaces/secrets"],listRepositoriesForSecretForAuthenticatedUser:["GET /user/codespaces/secrets/{secret_name}/repositories"],listSecretsForAuthenticatedUser:["GET /user/codespaces/secrets"],listSelectedReposForOrgSecret:["GET /orgs/{org}/codespaces/secrets/{secret_name}/repositories"],preFlightWithRepoForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces/new"],publishForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/publish"],removeRepositoryForSecretForAuthenticatedUser:["DELETE /user/codespaces/secrets/{secret_name}/repositories/{repository_id}"],removeSelectedRepoFromOrgSecret:["DELETE /orgs/{org}/codespaces/secrets/{secret_name}/repositories/{repository_id}"],repoMachinesForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces/machines"],setRepositoriesForSecretForAuthenticatedUser:["PUT /user/codespaces/secrets/{secret_name}/repositories"],setSelectedReposForOrgSecret:["PUT /orgs/{org}/codespaces/secrets/{secret_name}/repositories"],startForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/start"],stopForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/stop"],stopInOrganization:["POST /orgs/{org}/members/{username}/codespaces/{codespace_name}/stop"],updateForAuthenticatedUser:["PATCH /user/codespaces/{codespace_name}"]},copilot:{addCopilotSeatsForTeams:["POST /orgs/{org}/copilot/billing/selected_teams"],addCopilotSeatsForUsers:["POST /orgs/{org}/copilot/billing/selected_users"],cancelCopilotSeatAssignmentForTeams:["DELETE /orgs/{org}/copilot/billing/selected_teams"],cancelCopilotSeatAssignmentForUsers:["DELETE /orgs/{org}/copilot/billing/selected_users"],copilotMetricsForOrganization:["GET /orgs/{org}/copilot/metrics"],copilotMetricsForTeam:["GET /orgs/{org}/team/{team_slug}/copilot/metrics"],getCopilotOrganizationDetails:["GET /orgs/{org}/copilot/billing"],getCopilotSeatDetailsForUser:["GET /orgs/{org}/members/{username}/copilot"],listCopilotSeats:["GET /orgs/{org}/copilot/billing/seats"]},credentials:{revoke:["POST /credentials/revoke"]},dependabot:{addSelectedRepoToOrgSecret:["PUT /orgs/{org}/dependabot/secrets/{secret_name}/repositories/{repository_id}"],createOrUpdateOrgSecret:["PUT /orgs/{org}/dependabot/secrets/{secret_name}"],createOrUpdateRepoSecret:["PUT /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"],deleteOrgSecret:["DELETE /orgs/{org}/dependabot/secrets/{secret_name}"],deleteRepoSecret:["DELETE /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"],getAlert:["GET /repos/{owner}/{repo}/dependabot/alerts/{alert_number}"],getOrgPublicKey:["GET /orgs/{org}/dependabot/secrets/public-key"],getOrgSecret:["GET /orgs/{org}/dependabot/secrets/{secret_name}"],getRepoPublicKey:["GET /repos/{owner}/{repo}/dependabot/secrets/public-key"],getRepoSecret:["GET /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"],listAlertsForEnterprise:["GET /enterprises/{enterprise}/dependabot/alerts"],listAlertsForOrg:["GET /orgs/{org}/dependabot/alerts"],listAlertsForRepo:["GET /repos/{owner}/{repo}/dependabot/alerts"],listOrgSecrets:["GET /orgs/{org}/dependabot/secrets"],listRepoSecrets:["GET /repos/{owner}/{repo}/dependabot/secrets"],listSelectedReposForOrgSecret:["GET /orgs/{org}/dependabot/secrets/{secret_name}/repositories"],removeSelectedRepoFromOrgSecret:["DELETE /orgs/{org}/dependabot/secrets/{secret_name}/repositories/{repository_id}"],repositoryAccessForOrg:["GET /organizations/{org}/dependabot/repository-access"],setRepositoryAccessDefaultLevel:["PUT /organizations/{org}/dependabot/repository-access/default-level"],setSelectedReposForOrgSecret:["PUT /orgs/{org}/dependabot/secrets/{secret_name}/repositories"],updateAlert:["PATCH /repos/{owner}/{repo}/dependabot/alerts/{alert_number}"],updateRepositoryAccessForOrg:["PATCH /organizations/{org}/dependabot/repository-access"]},dependencyGraph:{createRepositorySnapshot:["POST /repos/{owner}/{repo}/dependency-graph/snapshots"],diffRange:["GET /repos/{owner}/{repo}/dependency-graph/compare/{basehead}"],exportSbom:["GET /repos/{owner}/{repo}/dependency-graph/sbom"]},emojis:{get:["GET /emojis"]},enterpriseTeamMemberships:{add:["PUT /enterprises/{enterprise}/teams/{enterprise-team}/memberships/{username}"],bulkAdd:["POST /enterprises/{enterprise}/teams/{enterprise-team}/memberships/add"],bulkRemove:["POST /enterprises/{enterprise}/teams/{enterprise-team}/memberships/remove"],get:["GET /enterprises/{enterprise}/teams/{enterprise-team}/memberships/{username}"],list:["GET /enterprises/{enterprise}/teams/{enterprise-team}/memberships"],remove:["DELETE /enterprises/{enterprise}/teams/{enterprise-team}/memberships/{username}"]},enterpriseTeamOrganizations:{add:["PUT /enterprises/{enterprise}/teams/{enterprise-team}/organizations/{org}"],bulkAdd:["POST /enterprises/{enterprise}/teams/{enterprise-team}/organizations/add"],bulkRemove:["POST /enterprises/{enterprise}/teams/{enterprise-team}/organizations/remove"],delete:["DELETE /enterprises/{enterprise}/teams/{enterprise-team}/organizations/{org}"],getAssignment:["GET /enterprises/{enterprise}/teams/{enterprise-team}/organizations/{org}"],getAssignments:["GET /enterprises/{enterprise}/teams/{enterprise-team}/organizations"]},enterpriseTeams:{create:["POST /enterprises/{enterprise}/teams"],delete:["DELETE /enterprises/{enterprise}/teams/{team_slug}"],get:["GET /enterprises/{enterprise}/teams/{team_slug}"],list:["GET /enterprises/{enterprise}/teams"],update:["PATCH /enterprises/{enterprise}/teams/{team_slug}"]},gists:{checkIsStarred:["GET /gists/{gist_id}/star"],create:["POST /gists"],createComment:["POST /gists/{gist_id}/comments"],delete:["DELETE /gists/{gist_id}"],deleteComment:["DELETE /gists/{gist_id}/comments/{comment_id}"],fork:["POST /gists/{gist_id}/forks"],get:["GET /gists/{gist_id}"],getComment:["GET /gists/{gist_id}/comments/{comment_id}"],getRevision:["GET /gists/{gist_id}/{sha}"],list:["GET /gists"],listComments:["GET /gists/{gist_id}/comments"],listCommits:["GET /gists/{gist_id}/commits"],listForUser:["GET /users/{username}/gists"],listForks:["GET /gists/{gist_id}/forks"],listPublic:["GET /gists/public"],listStarred:["GET /gists/starred"],star:["PUT /gists/{gist_id}/star"],unstar:["DELETE /gists/{gist_id}/star"],update:["PATCH /gists/{gist_id}"],updateComment:["PATCH /gists/{gist_id}/comments/{comment_id}"]},git:{createBlob:["POST /repos/{owner}/{repo}/git/blobs"],createCommit:["POST /repos/{owner}/{repo}/git/commits"],createRef:["POST /repos/{owner}/{repo}/git/refs"],createTag:["POST /repos/{owner}/{repo}/git/tags"],createTree:["POST /repos/{owner}/{repo}/git/trees"],deleteRef:["DELETE /repos/{owner}/{repo}/git/refs/{ref}"],getBlob:["GET /repos/{owner}/{repo}/git/blobs/{file_sha}"],getCommit:["GET /repos/{owner}/{repo}/git/commits/{commit_sha}"],getRef:["GET /repos/{owner}/{repo}/git/ref/{ref}"],getTag:["GET /repos/{owner}/{repo}/git/tags/{tag_sha}"],getTree:["GET /repos/{owner}/{repo}/git/trees/{tree_sha}"],listMatchingRefs:["GET /repos/{owner}/{repo}/git/matching-refs/{ref}"],updateRef:["PATCH /repos/{owner}/{repo}/git/refs/{ref}"]},gitignore:{getAllTemplates:["GET /gitignore/templates"],getTemplate:["GET /gitignore/templates/{name}"]},hostedCompute:{createNetworkConfigurationForOrg:["POST /orgs/{org}/settings/network-configurations"],deleteNetworkConfigurationFromOrg:["DELETE /orgs/{org}/settings/network-configurations/{network_configuration_id}"],getNetworkConfigurationForOrg:["GET /orgs/{org}/settings/network-configurations/{network_configuration_id}"],getNetworkSettingsForOrg:["GET /orgs/{org}/settings/network-settings/{network_settings_id}"],listNetworkConfigurationsForOrg:["GET /orgs/{org}/settings/network-configurations"],updateNetworkConfigurationForOrg:["PATCH /orgs/{org}/settings/network-configurations/{network_configuration_id}"]},interactions:{getRestrictionsForAuthenticatedUser:["GET /user/interaction-limits"],getRestrictionsForOrg:["GET /orgs/{org}/interaction-limits"],getRestrictionsForRepo:["GET /repos/{owner}/{repo}/interaction-limits"],getRestrictionsForYourPublicRepos:["GET /user/interaction-limits",{},{renamed:["interactions","getRestrictionsForAuthenticatedUser"]}],removeRestrictionsForAuthenticatedUser:["DELETE /user/interaction-limits"],removeRestrictionsForOrg:["DELETE /orgs/{org}/interaction-limits"],removeRestrictionsForRepo:["DELETE /repos/{owner}/{repo}/interaction-limits"],removeRestrictionsForYourPublicRepos:["DELETE /user/interaction-limits",{},{renamed:["interactions","removeRestrictionsForAuthenticatedUser"]}],setRestrictionsForAuthenticatedUser:["PUT /user/interaction-limits"],setRestrictionsForOrg:["PUT /orgs/{org}/interaction-limits"],setRestrictionsForRepo:["PUT /repos/{owner}/{repo}/interaction-limits"],setRestrictionsForYourPublicRepos:["PUT /user/interaction-limits",{},{renamed:["interactions","setRestrictionsForAuthenticatedUser"]}]},issues:{addAssignees:["POST /repos/{owner}/{repo}/issues/{issue_number}/assignees"],addBlockedByDependency:["POST /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by"],addLabels:["POST /repos/{owner}/{repo}/issues/{issue_number}/labels"],addSubIssue:["POST /repos/{owner}/{repo}/issues/{issue_number}/sub_issues"],checkUserCanBeAssigned:["GET /repos/{owner}/{repo}/assignees/{assignee}"],checkUserCanBeAssignedToIssue:["GET /repos/{owner}/{repo}/issues/{issue_number}/assignees/{assignee}"],create:["POST /repos/{owner}/{repo}/issues"],createComment:["POST /repos/{owner}/{repo}/issues/{issue_number}/comments"],createLabel:["POST /repos/{owner}/{repo}/labels"],createMilestone:["POST /repos/{owner}/{repo}/milestones"],deleteComment:["DELETE /repos/{owner}/{repo}/issues/comments/{comment_id}"],deleteLabel:["DELETE /repos/{owner}/{repo}/labels/{name}"],deleteMilestone:["DELETE /repos/{owner}/{repo}/milestones/{milestone_number}"],get:["GET /repos/{owner}/{repo}/issues/{issue_number}"],getComment:["GET /repos/{owner}/{repo}/issues/comments/{comment_id}"],getEvent:["GET /repos/{owner}/{repo}/issues/events/{event_id}"],getLabel:["GET /repos/{owner}/{repo}/labels/{name}"],getMilestone:["GET /repos/{owner}/{repo}/milestones/{milestone_number}"],getParent:["GET /repos/{owner}/{repo}/issues/{issue_number}/parent"],list:["GET /issues"],listAssignees:["GET /repos/{owner}/{repo}/assignees"],listComments:["GET /repos/{owner}/{repo}/issues/{issue_number}/comments"],listCommentsForRepo:["GET /repos/{owner}/{repo}/issues/comments"],listDependenciesBlockedBy:["GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by"],listDependenciesBlocking:["GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocking"],listEvents:["GET /repos/{owner}/{repo}/issues/{issue_number}/events"],listEventsForRepo:["GET /repos/{owner}/{repo}/issues/events"],listEventsForTimeline:["GET /repos/{owner}/{repo}/issues/{issue_number}/timeline"],listForAuthenticatedUser:["GET /user/issues"],listForOrg:["GET /orgs/{org}/issues"],listForRepo:["GET /repos/{owner}/{repo}/issues"],listLabelsForMilestone:["GET /repos/{owner}/{repo}/milestones/{milestone_number}/labels"],listLabelsForRepo:["GET /repos/{owner}/{repo}/labels"],listLabelsOnIssue:["GET /repos/{owner}/{repo}/issues/{issue_number}/labels"],listMilestones:["GET /repos/{owner}/{repo}/milestones"],listSubIssues:["GET /repos/{owner}/{repo}/issues/{issue_number}/sub_issues"],lock:["PUT /repos/{owner}/{repo}/issues/{issue_number}/lock"],removeAllLabels:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels"],removeAssignees:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/assignees"],removeDependencyBlockedBy:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by/{issue_id}"],removeLabel:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}"],removeSubIssue:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/sub_issue"],reprioritizeSubIssue:["PATCH /repos/{owner}/{repo}/issues/{issue_number}/sub_issues/priority"],setLabels:["PUT /repos/{owner}/{repo}/issues/{issue_number}/labels"],unlock:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/lock"],update:["PATCH /repos/{owner}/{repo}/issues/{issue_number}"],updateComment:["PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}"],updateLabel:["PATCH /repos/{owner}/{repo}/labels/{name}"],updateMilestone:["PATCH /repos/{owner}/{repo}/milestones/{milestone_number}"]},licenses:{get:["GET /licenses/{license}"],getAllCommonlyUsed:["GET /licenses"],getForRepo:["GET /repos/{owner}/{repo}/license"]},markdown:{render:["POST /markdown"],renderRaw:["POST /markdown/raw",{headers:{"content-type":"text/plain; charset=utf-8"}}]},meta:{get:["GET /meta"],getAllVersions:["GET /versions"],getOctocat:["GET /octocat"],getZen:["GET /zen"],root:["GET /"]},migrations:{deleteArchiveForAuthenticatedUser:["DELETE /user/migrations/{migration_id}/archive"],deleteArchiveForOrg:["DELETE /orgs/{org}/migrations/{migration_id}/archive"],downloadArchiveForOrg:["GET /orgs/{org}/migrations/{migration_id}/archive"],getArchiveForAuthenticatedUser:["GET /user/migrations/{migration_id}/archive"],getStatusForAuthenticatedUser:["GET /user/migrations/{migration_id}"],getStatusForOrg:["GET /orgs/{org}/migrations/{migration_id}"],listForAuthenticatedUser:["GET /user/migrations"],listForOrg:["GET /orgs/{org}/migrations"],listReposForAuthenticatedUser:["GET /user/migrations/{migration_id}/repositories"],listReposForOrg:["GET /orgs/{org}/migrations/{migration_id}/repositories"],listReposForUser:["GET /user/migrations/{migration_id}/repositories",{},{renamed:["migrations","listReposForAuthenticatedUser"]}],startForAuthenticatedUser:["POST /user/migrations"],startForOrg:["POST /orgs/{org}/migrations"],unlockRepoForAuthenticatedUser:["DELETE /user/migrations/{migration_id}/repos/{repo_name}/lock"],unlockRepoForOrg:["DELETE /orgs/{org}/migrations/{migration_id}/repos/{repo_name}/lock"]},oidc:{getOidcCustomSubTemplateForOrg:["GET /orgs/{org}/actions/oidc/customization/sub"],updateOidcCustomSubTemplateForOrg:["PUT /orgs/{org}/actions/oidc/customization/sub"]},orgs:{addSecurityManagerTeam:["PUT /orgs/{org}/security-managers/teams/{team_slug}",{},{deprecated:"octokit.rest.orgs.addSecurityManagerTeam() is deprecated, see https://docs.github.com/rest/orgs/security-managers#add-a-security-manager-team"}],assignTeamToOrgRole:["PUT /orgs/{org}/organization-roles/teams/{team_slug}/{role_id}"],assignUserToOrgRole:["PUT /orgs/{org}/organization-roles/users/{username}/{role_id}"],blockUser:["PUT /orgs/{org}/blocks/{username}"],cancelInvitation:["DELETE /orgs/{org}/invitations/{invitation_id}"],checkBlockedUser:["GET /orgs/{org}/blocks/{username}"],checkMembershipForUser:["GET /orgs/{org}/members/{username}"],checkPublicMembershipForUser:["GET /orgs/{org}/public_members/{username}"],convertMemberToOutsideCollaborator:["PUT /orgs/{org}/outside_collaborators/{username}"],createArtifactStorageRecord:["POST /orgs/{org}/artifacts/metadata/storage-record"],createInvitation:["POST /orgs/{org}/invitations"],createIssueType:["POST /orgs/{org}/issue-types"],createWebhook:["POST /orgs/{org}/hooks"],customPropertiesForOrgsCreateOrUpdateOrganizationValues:["PATCH /organizations/{org}/org-properties/values"],customPropertiesForOrgsGetOrganizationValues:["GET /organizations/{org}/org-properties/values"],customPropertiesForReposCreateOrUpdateOrganizationDefinition:["PUT /orgs/{org}/properties/schema/{custom_property_name}"],customPropertiesForReposCreateOrUpdateOrganizationDefinitions:["PATCH /orgs/{org}/properties/schema"],customPropertiesForReposCreateOrUpdateOrganizationValues:["PATCH /orgs/{org}/properties/values"],customPropertiesForReposDeleteOrganizationDefinition:["DELETE /orgs/{org}/properties/schema/{custom_property_name}"],customPropertiesForReposGetOrganizationDefinition:["GET /orgs/{org}/properties/schema/{custom_property_name}"],customPropertiesForReposGetOrganizationDefinitions:["GET /orgs/{org}/properties/schema"],customPropertiesForReposGetOrganizationValues:["GET /orgs/{org}/properties/values"],delete:["DELETE /orgs/{org}"],deleteAttestationsBulk:["POST /orgs/{org}/attestations/delete-request"],deleteAttestationsById:["DELETE /orgs/{org}/attestations/{attestation_id}"],deleteAttestationsBySubjectDigest:["DELETE /orgs/{org}/attestations/digest/{subject_digest}"],deleteIssueType:["DELETE /orgs/{org}/issue-types/{issue_type_id}"],deleteWebhook:["DELETE /orgs/{org}/hooks/{hook_id}"],disableSelectedRepositoryImmutableReleasesOrganization:["DELETE /orgs/{org}/settings/immutable-releases/repositories/{repository_id}"],enableSelectedRepositoryImmutableReleasesOrganization:["PUT /orgs/{org}/settings/immutable-releases/repositories/{repository_id}"],get:["GET /orgs/{org}"],getImmutableReleasesSettings:["GET /orgs/{org}/settings/immutable-releases"],getImmutableReleasesSettingsRepositories:["GET /orgs/{org}/settings/immutable-releases/repositories"],getMembershipForAuthenticatedUser:["GET /user/memberships/orgs/{org}"],getMembershipForUser:["GET /orgs/{org}/memberships/{username}"],getOrgRole:["GET /orgs/{org}/organization-roles/{role_id}"],getOrgRulesetHistory:["GET /orgs/{org}/rulesets/{ruleset_id}/history"],getOrgRulesetVersion:["GET /orgs/{org}/rulesets/{ruleset_id}/history/{version_id}"],getWebhook:["GET /orgs/{org}/hooks/{hook_id}"],getWebhookConfigForOrg:["GET /orgs/{org}/hooks/{hook_id}/config"],getWebhookDelivery:["GET /orgs/{org}/hooks/{hook_id}/deliveries/{delivery_id}"],list:["GET /organizations"],listAppInstallations:["GET /orgs/{org}/installations"],listArtifactStorageRecords:["GET /orgs/{org}/artifacts/{subject_digest}/metadata/storage-records"],listAttestationRepositories:["GET /orgs/{org}/attestations/repositories"],listAttestations:["GET /orgs/{org}/attestations/{subject_digest}"],listAttestationsBulk:["POST /orgs/{org}/attestations/bulk-list{?per_page,before,after}"],listBlockedUsers:["GET /orgs/{org}/blocks"],listFailedInvitations:["GET /orgs/{org}/failed_invitations"],listForAuthenticatedUser:["GET /user/orgs"],listForUser:["GET /users/{username}/orgs"],listInvitationTeams:["GET /orgs/{org}/invitations/{invitation_id}/teams"],listIssueTypes:["GET /orgs/{org}/issue-types"],listMembers:["GET /orgs/{org}/members"],listMembershipsForAuthenticatedUser:["GET /user/memberships/orgs"],listOrgRoleTeams:["GET /orgs/{org}/organization-roles/{role_id}/teams"],listOrgRoleUsers:["GET /orgs/{org}/organization-roles/{role_id}/users"],listOrgRoles:["GET /orgs/{org}/organization-roles"],listOrganizationFineGrainedPermissions:["GET /orgs/{org}/organization-fine-grained-permissions"],listOutsideCollaborators:["GET /orgs/{org}/outside_collaborators"],listPatGrantRepositories:["GET /orgs/{org}/personal-access-tokens/{pat_id}/repositories"],listPatGrantRequestRepositories:["GET /orgs/{org}/personal-access-token-requests/{pat_request_id}/repositories"],listPatGrantRequests:["GET /orgs/{org}/personal-access-token-requests"],listPatGrants:["GET /orgs/{org}/personal-access-tokens"],listPendingInvitations:["GET /orgs/{org}/invitations"],listPublicMembers:["GET /orgs/{org}/public_members"],listSecurityManagerTeams:["GET /orgs/{org}/security-managers",{},{deprecated:"octokit.rest.orgs.listSecurityManagerTeams() is deprecated, see https://docs.github.com/rest/orgs/security-managers#list-security-manager-teams"}],listWebhookDeliveries:["GET /orgs/{org}/hooks/{hook_id}/deliveries"],listWebhooks:["GET /orgs/{org}/hooks"],pingWebhook:["POST /orgs/{org}/hooks/{hook_id}/pings"],redeliverWebhookDelivery:["POST /orgs/{org}/hooks/{hook_id}/deliveries/{delivery_id}/attempts"],removeMember:["DELETE /orgs/{org}/members/{username}"],removeMembershipForUser:["DELETE /orgs/{org}/memberships/{username}"],removeOutsideCollaborator:["DELETE /orgs/{org}/outside_collaborators/{username}"],removePublicMembershipForAuthenticatedUser:["DELETE /orgs/{org}/public_members/{username}"],removeSecurityManagerTeam:["DELETE /orgs/{org}/security-managers/teams/{team_slug}",{},{deprecated:"octokit.rest.orgs.removeSecurityManagerTeam() is deprecated, see https://docs.github.com/rest/orgs/security-managers#remove-a-security-manager-team"}],reviewPatGrantRequest:["POST /orgs/{org}/personal-access-token-requests/{pat_request_id}"],reviewPatGrantRequestsInBulk:["POST /orgs/{org}/personal-access-token-requests"],revokeAllOrgRolesTeam:["DELETE /orgs/{org}/organization-roles/teams/{team_slug}"],revokeAllOrgRolesUser:["DELETE /orgs/{org}/organization-roles/users/{username}"],revokeOrgRoleTeam:["DELETE /orgs/{org}/organization-roles/teams/{team_slug}/{role_id}"],revokeOrgRoleUser:["DELETE /orgs/{org}/organization-roles/users/{username}/{role_id}"],setImmutableReleasesSettings:["PUT /orgs/{org}/settings/immutable-releases"],setImmutableReleasesSettingsRepositories:["PUT /orgs/{org}/settings/immutable-releases/repositories"],setMembershipForUser:["PUT /orgs/{org}/memberships/{username}"],setPublicMembershipForAuthenticatedUser:["PUT /orgs/{org}/public_members/{username}"],unblockUser:["DELETE /orgs/{org}/blocks/{username}"],update:["PATCH /orgs/{org}"],updateIssueType:["PUT /orgs/{org}/issue-types/{issue_type_id}"],updateMembershipForAuthenticatedUser:["PATCH /user/memberships/orgs/{org}"],updatePatAccess:["POST /orgs/{org}/personal-access-tokens/{pat_id}"],updatePatAccesses:["POST /orgs/{org}/personal-access-tokens"],updateWebhook:["PATCH /orgs/{org}/hooks/{hook_id}"],updateWebhookConfigForOrg:["PATCH /orgs/{org}/hooks/{hook_id}/config"]},packages:{deletePackageForAuthenticatedUser:["DELETE /user/packages/{package_type}/{package_name}"],deletePackageForOrg:["DELETE /orgs/{org}/packages/{package_type}/{package_name}"],deletePackageForUser:["DELETE /users/{username}/packages/{package_type}/{package_name}"],deletePackageVersionForAuthenticatedUser:["DELETE /user/packages/{package_type}/{package_name}/versions/{package_version_id}"],deletePackageVersionForOrg:["DELETE /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}"],deletePackageVersionForUser:["DELETE /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}"],getAllPackageVersionsForAPackageOwnedByAnOrg:["GET /orgs/{org}/packages/{package_type}/{package_name}/versions",{},{renamed:["packages","getAllPackageVersionsForPackageOwnedByOrg"]}],getAllPackageVersionsForAPackageOwnedByTheAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}/versions",{},{renamed:["packages","getAllPackageVersionsForPackageOwnedByAuthenticatedUser"]}],getAllPackageVersionsForPackageOwnedByAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}/versions"],getAllPackageVersionsForPackageOwnedByOrg:["GET /orgs/{org}/packages/{package_type}/{package_name}/versions"],getAllPackageVersionsForPackageOwnedByUser:["GET /users/{username}/packages/{package_type}/{package_name}/versions"],getPackageForAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}"],getPackageForOrganization:["GET /orgs/{org}/packages/{package_type}/{package_name}"],getPackageForUser:["GET /users/{username}/packages/{package_type}/{package_name}"],getPackageVersionForAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}/versions/{package_version_id}"],getPackageVersionForOrganization:["GET /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}"],getPackageVersionForUser:["GET /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}"],listDockerMigrationConflictingPackagesForAuthenticatedUser:["GET /user/docker/conflicts"],listDockerMigrationConflictingPackagesForOrganization:["GET /orgs/{org}/docker/conflicts"],listDockerMigrationConflictingPackagesForUser:["GET /users/{username}/docker/conflicts"],listPackagesForAuthenticatedUser:["GET /user/packages"],listPackagesForOrganization:["GET /orgs/{org}/packages"],listPackagesForUser:["GET /users/{username}/packages"],restorePackageForAuthenticatedUser:["POST /user/packages/{package_type}/{package_name}/restore{?token}"],restorePackageForOrg:["POST /orgs/{org}/packages/{package_type}/{package_name}/restore{?token}"],restorePackageForUser:["POST /users/{username}/packages/{package_type}/{package_name}/restore{?token}"],restorePackageVersionForAuthenticatedUser:["POST /user/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"],restorePackageVersionForOrg:["POST /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"],restorePackageVersionForUser:["POST /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"]},privateRegistries:{createOrgPrivateRegistry:["POST /orgs/{org}/private-registries"],deleteOrgPrivateRegistry:["DELETE /orgs/{org}/private-registries/{secret_name}"],getOrgPrivateRegistry:["GET /orgs/{org}/private-registries/{secret_name}"],getOrgPublicKey:["GET /orgs/{org}/private-registries/public-key"],listOrgPrivateRegistries:["GET /orgs/{org}/private-registries"],updateOrgPrivateRegistry:["PATCH /orgs/{org}/private-registries/{secret_name}"]},projects:{addItemForOrg:["POST /orgs/{org}/projectsV2/{project_number}/items"],addItemForUser:["POST /users/{username}/projectsV2/{project_number}/items"],deleteItemForOrg:["DELETE /orgs/{org}/projectsV2/{project_number}/items/{item_id}"],deleteItemForUser:["DELETE /users/{username}/projectsV2/{project_number}/items/{item_id}"],getFieldForOrg:["GET /orgs/{org}/projectsV2/{project_number}/fields/{field_id}"],getFieldForUser:["GET /users/{username}/projectsV2/{project_number}/fields/{field_id}"],getForOrg:["GET /orgs/{org}/projectsV2/{project_number}"],getForUser:["GET /users/{username}/projectsV2/{project_number}"],getOrgItem:["GET /orgs/{org}/projectsV2/{project_number}/items/{item_id}"],getUserItem:["GET /users/{username}/projectsV2/{project_number}/items/{item_id}"],listFieldsForOrg:["GET /orgs/{org}/projectsV2/{project_number}/fields"],listFieldsForUser:["GET /users/{username}/projectsV2/{project_number}/fields"],listForOrg:["GET /orgs/{org}/projectsV2"],listForUser:["GET /users/{username}/projectsV2"],listItemsForOrg:["GET /orgs/{org}/projectsV2/{project_number}/items"],listItemsForUser:["GET /users/{username}/projectsV2/{project_number}/items"],updateItemForOrg:["PATCH /orgs/{org}/projectsV2/{project_number}/items/{item_id}"],updateItemForUser:["PATCH /users/{username}/projectsV2/{project_number}/items/{item_id}"]},pulls:{checkIfMerged:["GET /repos/{owner}/{repo}/pulls/{pull_number}/merge"],create:["POST /repos/{owner}/{repo}/pulls"],createReplyForReviewComment:["POST /repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies"],createReview:["POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews"],createReviewComment:["POST /repos/{owner}/{repo}/pulls/{pull_number}/comments"],deletePendingReview:["DELETE /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"],deleteReviewComment:["DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}"],dismissReview:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/dismissals"],get:["GET /repos/{owner}/{repo}/pulls/{pull_number}"],getReview:["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"],getReviewComment:["GET /repos/{owner}/{repo}/pulls/comments/{comment_id}"],list:["GET /repos/{owner}/{repo}/pulls"],listCommentsForReview:["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/comments"],listCommits:["GET /repos/{owner}/{repo}/pulls/{pull_number}/commits"],listFiles:["GET /repos/{owner}/{repo}/pulls/{pull_number}/files"],listRequestedReviewers:["GET /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"],listReviewComments:["GET /repos/{owner}/{repo}/pulls/{pull_number}/comments"],listReviewCommentsForRepo:["GET /repos/{owner}/{repo}/pulls/comments"],listReviews:["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews"],merge:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge"],removeRequestedReviewers:["DELETE /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"],requestReviewers:["POST /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"],submitReview:["POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/events"],update:["PATCH /repos/{owner}/{repo}/pulls/{pull_number}"],updateBranch:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/update-branch"],updateReview:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"],updateReviewComment:["PATCH /repos/{owner}/{repo}/pulls/comments/{comment_id}"]},rateLimit:{get:["GET /rate_limit"]},reactions:{createForCommitComment:["POST /repos/{owner}/{repo}/comments/{comment_id}/reactions"],createForIssue:["POST /repos/{owner}/{repo}/issues/{issue_number}/reactions"],createForIssueComment:["POST /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions"],createForPullRequestReviewComment:["POST /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions"],createForRelease:["POST /repos/{owner}/{repo}/releases/{release_id}/reactions"],createForTeamDiscussionCommentInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions"],createForTeamDiscussionInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions"],deleteForCommitComment:["DELETE /repos/{owner}/{repo}/comments/{comment_id}/reactions/{reaction_id}"],deleteForIssue:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/reactions/{reaction_id}"],deleteForIssueComment:["DELETE /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions/{reaction_id}"],deleteForPullRequestComment:["DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions/{reaction_id}"],deleteForRelease:["DELETE /repos/{owner}/{repo}/releases/{release_id}/reactions/{reaction_id}"],deleteForTeamDiscussion:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions/{reaction_id}"],deleteForTeamDiscussionComment:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions/{reaction_id}"],listForCommitComment:["GET /repos/{owner}/{repo}/comments/{comment_id}/reactions"],listForIssue:["GET /repos/{owner}/{repo}/issues/{issue_number}/reactions"],listForIssueComment:["GET /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions"],listForPullRequestReviewComment:["GET /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions"],listForRelease:["GET /repos/{owner}/{repo}/releases/{release_id}/reactions"],listForTeamDiscussionCommentInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions"],listForTeamDiscussionInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions"]},repos:{acceptInvitation:["PATCH /user/repository_invitations/{invitation_id}",{},{renamed:["repos","acceptInvitationForAuthenticatedUser"]}],acceptInvitationForAuthenticatedUser:["PATCH /user/repository_invitations/{invitation_id}"],addAppAccessRestrictions:["POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps",{},{mapToData:"apps"}],addCollaborator:["PUT /repos/{owner}/{repo}/collaborators/{username}"],addStatusCheckContexts:["POST /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts",{},{mapToData:"contexts"}],addTeamAccessRestrictions:["POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams",{},{mapToData:"teams"}],addUserAccessRestrictions:["POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users",{},{mapToData:"users"}],cancelPagesDeployment:["POST /repos/{owner}/{repo}/pages/deployments/{pages_deployment_id}/cancel"],checkAutomatedSecurityFixes:["GET /repos/{owner}/{repo}/automated-security-fixes"],checkCollaborator:["GET /repos/{owner}/{repo}/collaborators/{username}"],checkImmutableReleases:["GET /repos/{owner}/{repo}/immutable-releases"],checkPrivateVulnerabilityReporting:["GET /repos/{owner}/{repo}/private-vulnerability-reporting"],checkVulnerabilityAlerts:["GET /repos/{owner}/{repo}/vulnerability-alerts"],codeownersErrors:["GET /repos/{owner}/{repo}/codeowners/errors"],compareCommits:["GET /repos/{owner}/{repo}/compare/{base}...{head}"],compareCommitsWithBasehead:["GET /repos/{owner}/{repo}/compare/{basehead}"],createAttestation:["POST /repos/{owner}/{repo}/attestations"],createAutolink:["POST /repos/{owner}/{repo}/autolinks"],createCommitComment:["POST /repos/{owner}/{repo}/commits/{commit_sha}/comments"],createCommitSignatureProtection:["POST /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"],createCommitStatus:["POST /repos/{owner}/{repo}/statuses/{sha}"],createDeployKey:["POST /repos/{owner}/{repo}/keys"],createDeployment:["POST /repos/{owner}/{repo}/deployments"],createDeploymentBranchPolicy:["POST /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies"],createDeploymentProtectionRule:["POST /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules"],createDeploymentStatus:["POST /repos/{owner}/{repo}/deployments/{deployment_id}/statuses"],createDispatchEvent:["POST /repos/{owner}/{repo}/dispatches"],createForAuthenticatedUser:["POST /user/repos"],createFork:["POST /repos/{owner}/{repo}/forks"],createInOrg:["POST /orgs/{org}/repos"],createOrUpdateEnvironment:["PUT /repos/{owner}/{repo}/environments/{environment_name}"],createOrUpdateFileContents:["PUT /repos/{owner}/{repo}/contents/{path}"],createOrgRuleset:["POST /orgs/{org}/rulesets"],createPagesDeployment:["POST /repos/{owner}/{repo}/pages/deployments"],createPagesSite:["POST /repos/{owner}/{repo}/pages"],createRelease:["POST /repos/{owner}/{repo}/releases"],createRepoRuleset:["POST /repos/{owner}/{repo}/rulesets"],createUsingTemplate:["POST /repos/{template_owner}/{template_repo}/generate"],createWebhook:["POST /repos/{owner}/{repo}/hooks"],customPropertiesForReposCreateOrUpdateRepositoryValues:["PATCH /repos/{owner}/{repo}/properties/values"],customPropertiesForReposGetRepositoryValues:["GET /repos/{owner}/{repo}/properties/values"],declineInvitation:["DELETE /user/repository_invitations/{invitation_id}",{},{renamed:["repos","declineInvitationForAuthenticatedUser"]}],declineInvitationForAuthenticatedUser:["DELETE /user/repository_invitations/{invitation_id}"],delete:["DELETE /repos/{owner}/{repo}"],deleteAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions"],deleteAdminBranchProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"],deleteAnEnvironment:["DELETE /repos/{owner}/{repo}/environments/{environment_name}"],deleteAutolink:["DELETE /repos/{owner}/{repo}/autolinks/{autolink_id}"],deleteBranchProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection"],deleteCommitComment:["DELETE /repos/{owner}/{repo}/comments/{comment_id}"],deleteCommitSignatureProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"],deleteDeployKey:["DELETE /repos/{owner}/{repo}/keys/{key_id}"],deleteDeployment:["DELETE /repos/{owner}/{repo}/deployments/{deployment_id}"],deleteDeploymentBranchPolicy:["DELETE /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"],deleteFile:["DELETE /repos/{owner}/{repo}/contents/{path}"],deleteInvitation:["DELETE /repos/{owner}/{repo}/invitations/{invitation_id}"],deleteOrgRuleset:["DELETE /orgs/{org}/rulesets/{ruleset_id}"],deletePagesSite:["DELETE /repos/{owner}/{repo}/pages"],deletePullRequestReviewProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"],deleteRelease:["DELETE /repos/{owner}/{repo}/releases/{release_id}"],deleteReleaseAsset:["DELETE /repos/{owner}/{repo}/releases/assets/{asset_id}"],deleteRepoRuleset:["DELETE /repos/{owner}/{repo}/rulesets/{ruleset_id}"],deleteWebhook:["DELETE /repos/{owner}/{repo}/hooks/{hook_id}"],disableAutomatedSecurityFixes:["DELETE /repos/{owner}/{repo}/automated-security-fixes"],disableDeploymentProtectionRule:["DELETE /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/{protection_rule_id}"],disableImmutableReleases:["DELETE /repos/{owner}/{repo}/immutable-releases"],disablePrivateVulnerabilityReporting:["DELETE /repos/{owner}/{repo}/private-vulnerability-reporting"],disableVulnerabilityAlerts:["DELETE /repos/{owner}/{repo}/vulnerability-alerts"],downloadArchive:["GET /repos/{owner}/{repo}/zipball/{ref}",{},{renamed:["repos","downloadZipballArchive"]}],downloadTarballArchive:["GET /repos/{owner}/{repo}/tarball/{ref}"],downloadZipballArchive:["GET /repos/{owner}/{repo}/zipball/{ref}"],enableAutomatedSecurityFixes:["PUT /repos/{owner}/{repo}/automated-security-fixes"],enableImmutableReleases:["PUT /repos/{owner}/{repo}/immutable-releases"],enablePrivateVulnerabilityReporting:["PUT /repos/{owner}/{repo}/private-vulnerability-reporting"],enableVulnerabilityAlerts:["PUT /repos/{owner}/{repo}/vulnerability-alerts"],generateReleaseNotes:["POST /repos/{owner}/{repo}/releases/generate-notes"],get:["GET /repos/{owner}/{repo}"],getAccessRestrictions:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions"],getAdminBranchProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"],getAllDeploymentProtectionRules:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules"],getAllEnvironments:["GET /repos/{owner}/{repo}/environments"],getAllStatusCheckContexts:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts"],getAllTopics:["GET /repos/{owner}/{repo}/topics"],getAppsWithAccessToProtectedBranch:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps"],getAutolink:["GET /repos/{owner}/{repo}/autolinks/{autolink_id}"],getBranch:["GET /repos/{owner}/{repo}/branches/{branch}"],getBranchProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection"],getBranchRules:["GET /repos/{owner}/{repo}/rules/branches/{branch}"],getClones:["GET /repos/{owner}/{repo}/traffic/clones"],getCodeFrequencyStats:["GET /repos/{owner}/{repo}/stats/code_frequency"],getCollaboratorPermissionLevel:["GET /repos/{owner}/{repo}/collaborators/{username}/permission"],getCombinedStatusForRef:["GET /repos/{owner}/{repo}/commits/{ref}/status"],getCommit:["GET /repos/{owner}/{repo}/commits/{ref}"],getCommitActivityStats:["GET /repos/{owner}/{repo}/stats/commit_activity"],getCommitComment:["GET /repos/{owner}/{repo}/comments/{comment_id}"],getCommitSignatureProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"],getCommunityProfileMetrics:["GET /repos/{owner}/{repo}/community/profile"],getContent:["GET /repos/{owner}/{repo}/contents/{path}"],getContributorsStats:["GET /repos/{owner}/{repo}/stats/contributors"],getCustomDeploymentProtectionRule:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/{protection_rule_id}"],getDeployKey:["GET /repos/{owner}/{repo}/keys/{key_id}"],getDeployment:["GET /repos/{owner}/{repo}/deployments/{deployment_id}"],getDeploymentBranchPolicy:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"],getDeploymentStatus:["GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses/{status_id}"],getEnvironment:["GET /repos/{owner}/{repo}/environments/{environment_name}"],getLatestPagesBuild:["GET /repos/{owner}/{repo}/pages/builds/latest"],getLatestRelease:["GET /repos/{owner}/{repo}/releases/latest"],getOrgRuleSuite:["GET /orgs/{org}/rulesets/rule-suites/{rule_suite_id}"],getOrgRuleSuites:["GET /orgs/{org}/rulesets/rule-suites"],getOrgRuleset:["GET /orgs/{org}/rulesets/{ruleset_id}"],getOrgRulesets:["GET /orgs/{org}/rulesets"],getPages:["GET /repos/{owner}/{repo}/pages"],getPagesBuild:["GET /repos/{owner}/{repo}/pages/builds/{build_id}"],getPagesDeployment:["GET /repos/{owner}/{repo}/pages/deployments/{pages_deployment_id}"],getPagesHealthCheck:["GET /repos/{owner}/{repo}/pages/health"],getParticipationStats:["GET /repos/{owner}/{repo}/stats/participation"],getPullRequestReviewProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"],getPunchCardStats:["GET /repos/{owner}/{repo}/stats/punch_card"],getReadme:["GET /repos/{owner}/{repo}/readme"],getReadmeInDirectory:["GET /repos/{owner}/{repo}/readme/{dir}"],getRelease:["GET /repos/{owner}/{repo}/releases/{release_id}"],getReleaseAsset:["GET /repos/{owner}/{repo}/releases/assets/{asset_id}"],getReleaseByTag:["GET /repos/{owner}/{repo}/releases/tags/{tag}"],getRepoRuleSuite:["GET /repos/{owner}/{repo}/rulesets/rule-suites/{rule_suite_id}"],getRepoRuleSuites:["GET /repos/{owner}/{repo}/rulesets/rule-suites"],getRepoRuleset:["GET /repos/{owner}/{repo}/rulesets/{ruleset_id}"],getRepoRulesetHistory:["GET /repos/{owner}/{repo}/rulesets/{ruleset_id}/history"],getRepoRulesetVersion:["GET /repos/{owner}/{repo}/rulesets/{ruleset_id}/history/{version_id}"],getRepoRulesets:["GET /repos/{owner}/{repo}/rulesets"],getStatusChecksProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"],getTeamsWithAccessToProtectedBranch:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams"],getTopPaths:["GET /repos/{owner}/{repo}/traffic/popular/paths"],getTopReferrers:["GET /repos/{owner}/{repo}/traffic/popular/referrers"],getUsersWithAccessToProtectedBranch:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users"],getViews:["GET /repos/{owner}/{repo}/traffic/views"],getWebhook:["GET /repos/{owner}/{repo}/hooks/{hook_id}"],getWebhookConfigForRepo:["GET /repos/{owner}/{repo}/hooks/{hook_id}/config"],getWebhookDelivery:["GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries/{delivery_id}"],listActivities:["GET /repos/{owner}/{repo}/activity"],listAttestations:["GET /repos/{owner}/{repo}/attestations/{subject_digest}"],listAutolinks:["GET /repos/{owner}/{repo}/autolinks"],listBranches:["GET /repos/{owner}/{repo}/branches"],listBranchesForHeadCommit:["GET /repos/{owner}/{repo}/commits/{commit_sha}/branches-where-head"],listCollaborators:["GET /repos/{owner}/{repo}/collaborators"],listCommentsForCommit:["GET /repos/{owner}/{repo}/commits/{commit_sha}/comments"],listCommitCommentsForRepo:["GET /repos/{owner}/{repo}/comments"],listCommitStatusesForRef:["GET /repos/{owner}/{repo}/commits/{ref}/statuses"],listCommits:["GET /repos/{owner}/{repo}/commits"],listContributors:["GET /repos/{owner}/{repo}/contributors"],listCustomDeploymentRuleIntegrations:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/apps"],listDeployKeys:["GET /repos/{owner}/{repo}/keys"],listDeploymentBranchPolicies:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies"],listDeploymentStatuses:["GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses"],listDeployments:["GET /repos/{owner}/{repo}/deployments"],listForAuthenticatedUser:["GET /user/repos"],listForOrg:["GET /orgs/{org}/repos"],listForUser:["GET /users/{username}/repos"],listForks:["GET /repos/{owner}/{repo}/forks"],listInvitations:["GET /repos/{owner}/{repo}/invitations"],listInvitationsForAuthenticatedUser:["GET /user/repository_invitations"],listLanguages:["GET /repos/{owner}/{repo}/languages"],listPagesBuilds:["GET /repos/{owner}/{repo}/pages/builds"],listPublic:["GET /repositories"],listPullRequestsAssociatedWithCommit:["GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls"],listReleaseAssets:["GET /repos/{owner}/{repo}/releases/{release_id}/assets"],listReleases:["GET /repos/{owner}/{repo}/releases"],listTags:["GET /repos/{owner}/{repo}/tags"],listTeams:["GET /repos/{owner}/{repo}/teams"],listWebhookDeliveries:["GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries"],listWebhooks:["GET /repos/{owner}/{repo}/hooks"],merge:["POST /repos/{owner}/{repo}/merges"],mergeUpstream:["POST /repos/{owner}/{repo}/merge-upstream"],pingWebhook:["POST /repos/{owner}/{repo}/hooks/{hook_id}/pings"],redeliverWebhookDelivery:["POST /repos/{owner}/{repo}/hooks/{hook_id}/deliveries/{delivery_id}/attempts"],removeAppAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps",{},{mapToData:"apps"}],removeCollaborator:["DELETE /repos/{owner}/{repo}/collaborators/{username}"],removeStatusCheckContexts:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts",{},{mapToData:"contexts"}],removeStatusCheckProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"],removeTeamAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams",{},{mapToData:"teams"}],removeUserAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users",{},{mapToData:"users"}],renameBranch:["POST /repos/{owner}/{repo}/branches/{branch}/rename"],replaceAllTopics:["PUT /repos/{owner}/{repo}/topics"],requestPagesBuild:["POST /repos/{owner}/{repo}/pages/builds"],setAdminBranchProtection:["POST /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"],setAppAccessRestrictions:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps",{},{mapToData:"apps"}],setStatusCheckContexts:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts",{},{mapToData:"contexts"}],setTeamAccessRestrictions:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams",{},{mapToData:"teams"}],setUserAccessRestrictions:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users",{},{mapToData:"users"}],testPushWebhook:["POST /repos/{owner}/{repo}/hooks/{hook_id}/tests"],transfer:["POST /repos/{owner}/{repo}/transfer"],update:["PATCH /repos/{owner}/{repo}"],updateBranchProtection:["PUT /repos/{owner}/{repo}/branches/{branch}/protection"],updateCommitComment:["PATCH /repos/{owner}/{repo}/comments/{comment_id}"],updateDeploymentBranchPolicy:["PUT /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"],updateInformationAboutPagesSite:["PUT /repos/{owner}/{repo}/pages"],updateInvitation:["PATCH /repos/{owner}/{repo}/invitations/{invitation_id}"],updateOrgRuleset:["PUT /orgs/{org}/rulesets/{ruleset_id}"],updatePullRequestReviewProtection:["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"],updateRelease:["PATCH /repos/{owner}/{repo}/releases/{release_id}"],updateReleaseAsset:["PATCH /repos/{owner}/{repo}/releases/assets/{asset_id}"],updateRepoRuleset:["PUT /repos/{owner}/{repo}/rulesets/{ruleset_id}"],updateStatusCheckPotection:["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks",{},{renamed:["repos","updateStatusCheckProtection"]}],updateStatusCheckProtection:["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"],updateWebhook:["PATCH /repos/{owner}/{repo}/hooks/{hook_id}"],updateWebhookConfigForRepo:["PATCH /repos/{owner}/{repo}/hooks/{hook_id}/config"],uploadReleaseAsset:["POST /repos/{owner}/{repo}/releases/{release_id}/assets{?name,label}",{baseUrl:"https://uploads.github.com"}]},search:{code:["GET /search/code"],commits:["GET /search/commits"],issuesAndPullRequests:["GET /search/issues"],labels:["GET /search/labels"],repos:["GET /search/repositories"],topics:["GET /search/topics"],users:["GET /search/users"]},secretScanning:{createPushProtectionBypass:["POST /repos/{owner}/{repo}/secret-scanning/push-protection-bypasses"],getAlert:["GET /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}"],getScanHistory:["GET /repos/{owner}/{repo}/secret-scanning/scan-history"],listAlertsForOrg:["GET /orgs/{org}/secret-scanning/alerts"],listAlertsForRepo:["GET /repos/{owner}/{repo}/secret-scanning/alerts"],listLocationsForAlert:["GET /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}/locations"],listOrgPatternConfigs:["GET /orgs/{org}/secret-scanning/pattern-configurations"],updateAlert:["PATCH /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}"],updateOrgPatternConfigs:["PATCH /orgs/{org}/secret-scanning/pattern-configurations"]},securityAdvisories:{createFork:["POST /repos/{owner}/{repo}/security-advisories/{ghsa_id}/forks"],createPrivateVulnerabilityReport:["POST /repos/{owner}/{repo}/security-advisories/reports"],createRepositoryAdvisory:["POST /repos/{owner}/{repo}/security-advisories"],createRepositoryAdvisoryCveRequest:["POST /repos/{owner}/{repo}/security-advisories/{ghsa_id}/cve"],getGlobalAdvisory:["GET /advisories/{ghsa_id}"],getRepositoryAdvisory:["GET /repos/{owner}/{repo}/security-advisories/{ghsa_id}"],listGlobalAdvisories:["GET /advisories"],listOrgRepositoryAdvisories:["GET /orgs/{org}/security-advisories"],listRepositoryAdvisories:["GET /repos/{owner}/{repo}/security-advisories"],updateRepositoryAdvisory:["PATCH /repos/{owner}/{repo}/security-advisories/{ghsa_id}"]},teams:{addOrUpdateMembershipForUserInOrg:["PUT /orgs/{org}/teams/{team_slug}/memberships/{username}"],addOrUpdateRepoPermissionsInOrg:["PUT /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"],checkPermissionsForRepoInOrg:["GET /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"],create:["POST /orgs/{org}/teams"],createDiscussionCommentInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments"],createDiscussionInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions"],deleteDiscussionCommentInOrg:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"],deleteDiscussionInOrg:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"],deleteInOrg:["DELETE /orgs/{org}/teams/{team_slug}"],getByName:["GET /orgs/{org}/teams/{team_slug}"],getDiscussionCommentInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"],getDiscussionInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"],getMembershipForUserInOrg:["GET /orgs/{org}/teams/{team_slug}/memberships/{username}"],list:["GET /orgs/{org}/teams"],listChildInOrg:["GET /orgs/{org}/teams/{team_slug}/teams"],listDiscussionCommentsInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments"],listDiscussionsInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions"],listForAuthenticatedUser:["GET /user/teams"],listMembersInOrg:["GET /orgs/{org}/teams/{team_slug}/members"],listPendingInvitationsInOrg:["GET /orgs/{org}/teams/{team_slug}/invitations"],listReposInOrg:["GET /orgs/{org}/teams/{team_slug}/repos"],removeMembershipForUserInOrg:["DELETE /orgs/{org}/teams/{team_slug}/memberships/{username}"],removeRepoInOrg:["DELETE /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"],updateDiscussionCommentInOrg:["PATCH /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"],updateDiscussionInOrg:["PATCH /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"],updateInOrg:["PATCH /orgs/{org}/teams/{team_slug}"]},users:{addEmailForAuthenticated:["POST /user/emails",{},{renamed:["users","addEmailForAuthenticatedUser"]}],addEmailForAuthenticatedUser:["POST /user/emails"],addSocialAccountForAuthenticatedUser:["POST /user/social_accounts"],block:["PUT /user/blocks/{username}"],checkBlocked:["GET /user/blocks/{username}"],checkFollowingForUser:["GET /users/{username}/following/{target_user}"],checkPersonIsFollowedByAuthenticated:["GET /user/following/{username}"],createGpgKeyForAuthenticated:["POST /user/gpg_keys",{},{renamed:["users","createGpgKeyForAuthenticatedUser"]}],createGpgKeyForAuthenticatedUser:["POST /user/gpg_keys"],createPublicSshKeyForAuthenticated:["POST /user/keys",{},{renamed:["users","createPublicSshKeyForAuthenticatedUser"]}],createPublicSshKeyForAuthenticatedUser:["POST /user/keys"],createSshSigningKeyForAuthenticatedUser:["POST /user/ssh_signing_keys"],deleteAttestationsBulk:["POST /users/{username}/attestations/delete-request"],deleteAttestationsById:["DELETE /users/{username}/attestations/{attestation_id}"],deleteAttestationsBySubjectDigest:["DELETE /users/{username}/attestations/digest/{subject_digest}"],deleteEmailForAuthenticated:["DELETE /user/emails",{},{renamed:["users","deleteEmailForAuthenticatedUser"]}],deleteEmailForAuthenticatedUser:["DELETE /user/emails"],deleteGpgKeyForAuthenticated:["DELETE /user/gpg_keys/{gpg_key_id}",{},{renamed:["users","deleteGpgKeyForAuthenticatedUser"]}],deleteGpgKeyForAuthenticatedUser:["DELETE /user/gpg_keys/{gpg_key_id}"],deletePublicSshKeyForAuthenticated:["DELETE /user/keys/{key_id}",{},{renamed:["users","deletePublicSshKeyForAuthenticatedUser"]}],deletePublicSshKeyForAuthenticatedUser:["DELETE /user/keys/{key_id}"],deleteSocialAccountForAuthenticatedUser:["DELETE /user/social_accounts"],deleteSshSigningKeyForAuthenticatedUser:["DELETE /user/ssh_signing_keys/{ssh_signing_key_id}"],follow:["PUT /user/following/{username}"],getAuthenticated:["GET /user"],getById:["GET /user/{account_id}"],getByUsername:["GET /users/{username}"],getContextForUser:["GET /users/{username}/hovercard"],getGpgKeyForAuthenticated:["GET /user/gpg_keys/{gpg_key_id}",{},{renamed:["users","getGpgKeyForAuthenticatedUser"]}],getGpgKeyForAuthenticatedUser:["GET /user/gpg_keys/{gpg_key_id}"],getPublicSshKeyForAuthenticated:["GET /user/keys/{key_id}",{},{renamed:["users","getPublicSshKeyForAuthenticatedUser"]}],getPublicSshKeyForAuthenticatedUser:["GET /user/keys/{key_id}"],getSshSigningKeyForAuthenticatedUser:["GET /user/ssh_signing_keys/{ssh_signing_key_id}"],list:["GET /users"],listAttestations:["GET /users/{username}/attestations/{subject_digest}"],listAttestationsBulk:["POST /users/{username}/attestations/bulk-list{?per_page,before,after}"],listBlockedByAuthenticated:["GET /user/blocks",{},{renamed:["users","listBlockedByAuthenticatedUser"]}],listBlockedByAuthenticatedUser:["GET /user/blocks"],listEmailsForAuthenticated:["GET /user/emails",{},{renamed:["users","listEmailsForAuthenticatedUser"]}],listEmailsForAuthenticatedUser:["GET /user/emails"],listFollowedByAuthenticated:["GET /user/following",{},{renamed:["users","listFollowedByAuthenticatedUser"]}],listFollowedByAuthenticatedUser:["GET /user/following"],listFollowersForAuthenticatedUser:["GET /user/followers"],listFollowersForUser:["GET /users/{username}/followers"],listFollowingForUser:["GET /users/{username}/following"],listGpgKeysForAuthenticated:["GET /user/gpg_keys",{},{renamed:["users","listGpgKeysForAuthenticatedUser"]}],listGpgKeysForAuthenticatedUser:["GET /user/gpg_keys"],listGpgKeysForUser:["GET /users/{username}/gpg_keys"],listPublicEmailsForAuthenticated:["GET /user/public_emails",{},{renamed:["users","listPublicEmailsForAuthenticatedUser"]}],listPublicEmailsForAuthenticatedUser:["GET /user/public_emails"],listPublicKeysForUser:["GET /users/{username}/keys"],listPublicSshKeysForAuthenticated:["GET /user/keys",{},{renamed:["users","listPublicSshKeysForAuthenticatedUser"]}],listPublicSshKeysForAuthenticatedUser:["GET /user/keys"],listSocialAccountsForAuthenticatedUser:["GET /user/social_accounts"],listSocialAccountsForUser:["GET /users/{username}/social_accounts"],listSshSigningKeysForAuthenticatedUser:["GET /user/ssh_signing_keys"],listSshSigningKeysForUser:["GET /users/{username}/ssh_signing_keys"],setPrimaryEmailVisibilityForAuthenticated:["PATCH /user/email/visibility",{},{renamed:["users","setPrimaryEmailVisibilityForAuthenticatedUser"]}],setPrimaryEmailVisibilityForAuthenticatedUser:["PATCH /user/email/visibility"],unblock:["DELETE /user/blocks/{username}"],unfollow:["DELETE /user/following/{username}"],updateAuthenticated:["PATCH /user"]}};var Pt=kt;const Ut=new Map;for(const[e,t]of Object.entries(Pt)){for(const[i,n]of Object.entries(t)){const[t,r,s]=n;const[o,a]=t.split(/ /);const l=Object.assign({method:o,url:a},r);if(!Ut.has(e)){Ut.set(e,new Map)}Ut.get(e).set(i,{scope:e,methodName:i,endpointDefaults:l,decorations:s})}}const Vt={has({scope:e},t){return Ut.get(e).has(t)},getOwnPropertyDescriptor(e,t){return{value:this.get(e,t),configurable:true,writable:true,enumerable:true}},defineProperty(e,t,i){Object.defineProperty(e.cache,t,i);return true},deleteProperty(e,t){delete e.cache[t];return true},ownKeys({scope:e}){return[...Ut.get(e).keys()]},set(e,t,i){return e.cache[t]=i},get({octokit:e,scope:t,cache:i},n){if(i[n]){return i[n]}const r=Ut.get(t).get(n);if(!r){return void 0}const{endpointDefaults:s,decorations:o}=r;if(o){i[n]=decorate(e,t,n,s,o)}else{i[n]=e.request.defaults(s)}return i[n]}};function endpointsToMethods(e){const t={};for(const i of Ut.keys()){t[i]=new Proxy({octokit:e,scope:i,cache:{}},Vt)}return t}function decorate(e,t,i,n,r){const s=e.request.defaults(n);function withDecorations(...n){let o=s.endpoint.merge(...n);if(r.mapToData){o=Object.assign({},o,{data:o[r.mapToData],[r.mapToData]:void 0});return s(o)}if(r.renamed){const[n,s]=r.renamed;e.log.warn(`octokit.${t}.${i}() has been renamed to octokit.${n}.${s}()`)}if(r.deprecated){e.log.warn(r.deprecated)}if(r.renamedParameters){const o=s.endpoint.merge(...n);for(const[n,s]of Object.entries(r.renamedParameters)){if(n in o){e.log.warn(`"${n}" parameter is deprecated for "octokit.${t}.${i}()". Use "${s}" instead`);if(!(s in o)){o[s]=o[n]}delete o[n]}}return s(o)}return s(...n)}return Object.assign(withDecorations,s)}function restEndpointMethods(e){const t=endpointsToMethods(e);return{rest:t}}restEndpointMethods.VERSION=St;function legacyRestEndpointMethods(e){const t=endpointsToMethods(e);return{...t,rest:t}}legacyRestEndpointMethods.VERSION=St;const Ft="22.0.1";const Ot=Octokit.plugin(requestLog,legacyRestEndpointMethods,paginateRest).defaults({userAgent:`octokit-rest.js/${Ft}`});class OctokitWrapper{_octokitGitDiffParser;_octokit=null;constructor(e){this._octokitGitDiffParser=e}get octokit(){if(this._octokit===null){throw new Error("OctokitWrapper was not initialized. Call OctokitWrapper.initialize() before calling other methods.")}return this._octokit}initialize(e){if(this._octokit!==null){throw new Error("OctokitWrapper was already initialized prior to calling OctokitWrapper.initialize().")}this._octokit=new Ot(e)}async getPull(e,t,i){return this.octokit.rest.pulls.get({owner:e,pull_number:i,repo:t})}async updatePull(e,t,i,n,r){const s={owner:e,pull_number:i,repo:t};if(n!==null){s.title=n}if(r!==null){s.body=r}return this.octokit.rest.pulls.update(s)}async getIssueComments(e,t,i){return this.octokit.rest.issues.listComments({issue_number:i,owner:e,repo:t})}async getReviewComments(e,t,i){return this.octokit.rest.pulls.listReviewComments({owner:e,pull_number:i,repo:t})}async createIssueComment(e,t,i,n){return this.octokit.rest.issues.createComment({body:n,issue_number:i,owner:e,repo:t})}async listCommits(e,t,i,n){return this.octokit.rest.pulls.listCommits({owner:e,page:n,pull_number:i,repo:t})}async createReviewComment(e,t,i,n,r,s){const o=await this._octokitGitDiffParser.getFirstChangedLine(this,e,t,i,r);if(o===null){return null}return this.octokit.rest.pulls.createReviewComment({body:n,commit_id:s,line:o,owner:e,path:r,pull_number:i,repo:t})}async updateIssueComment(e,t,i,n,r){return this.octokit.rest.issues.updateComment({body:r,comment_id:n,issue_number:i,owner:e,repo:t})}async deleteReviewComment(e,t,i){return this.octokit.rest.pulls.deleteReviewComment({comment_id:i,owner:e,repo:t})}}class PullRequest{_codeMetrics;_logger;_runnerInvoker;constructor(e,t,i){this._codeMetrics=e;this._logger=t;this._runnerInvoker=i}get isPullRequest(){this._logger.logDebug("* PullRequest.isPullRequest");return RunnerInvoker.isGitHub?typeof process.env.GITHUB_BASE_REF!=="undefined"&&process.env.GITHUB_BASE_REF!=="":typeof process.env.SYSTEM_PULLREQUEST_PULLREQUESTID!=="undefined"}get isSupportedProvider(){this._logger.logDebug("* PullRequest.isSupportedProvider");if(RunnerInvoker.isGitHub){return true}const e=validateVariable("BUILD_REPOSITORY_PROVIDER","PullRequest.isSupportedProvider");if(e==="TfsGit"||e==="GitHub"||e==="GitHubEnterprise"){return true}return e}getUpdatedDescription(e){this._logger.logDebug("* PullRequest.getUpdatedDescription()");if(e!==null&&e.trim()!==""){return null}return this._runnerInvoker.loc("pullRequests.pullRequest.addDescription")}async getUpdatedTitle(e){this._logger.logDebug("* PullRequest.getUpdatedTitle()");const t=await this._codeMetrics.getSizeIndicator();if(e.startsWith(this._runnerInvoker.loc("pullRequests.pullRequest.titleFormat",t,""))){return null}const i=`(${this._runnerInvoker.loc("metrics.codeMetrics.titleSizeXS")}`+`|${this._runnerInvoker.loc("metrics.codeMetrics.titleSizeS")}`+`|${this._runnerInvoker.loc("metrics.codeMetrics.titleSizeM")}`+`|${this._runnerInvoker.loc("metrics.codeMetrics.titleSizeL")}`+`|\\d*${this._runnerInvoker.loc("metrics.codeMetrics.titleSizeXL")})`;const n=`(${this._runnerInvoker.loc("metrics.codeMetrics.titleTestsSufficient")}`+`|${this._runnerInvoker.loc("metrics.codeMetrics.titleTestsInsufficient")})?`;const r=this._runnerInvoker.loc("metrics.codeMetrics.titleSizeIndicatorFormat",i,n);const s=`^${this._runnerInvoker.loc("pullRequests.pullRequest.titleFormat",r,"(?.*)")}$`;const o=new RegExp(s,"u");const a=e.match(o);const l=a?.groups?.originalTitle??e;return this._runnerInvoker.loc("pullRequests.pullRequest.titleFormat",t,l)}}class PullRequestCommentsData{metricsCommentThreadId=null;metricsCommentContent=null;metricsCommentThreadStatus=null;filesNotRequiringReview;deletedFilesNotRequiringReview;commentThreadsRequiringDeletion=[];constructor(e,t){this.filesNotRequiringReview=e;this.deletedFilesNotRequiringReview=t}}class PullRequestComments{_codeMetrics;_inputs;_logger;_reposInvoker;_runnerInvoker;constructor(e,t,i,n,r){this._codeMetrics=e;this._inputs=t;this._logger=i;this._reposInvoker=n;this._runnerInvoker=r}get noReviewRequiredComment(){this._logger.logDebug("* PullRequestComments.noReviewRequiredComment");return this._runnerInvoker.loc("pullRequests.pullRequestComments.noReviewRequiredComment")}async getCommentData(){this._logger.logDebug("* PullRequestComments.getCommentData()");const e=await this._codeMetrics.getFilesNotRequiringReview();const t=await this._codeMetrics.getDeletedFilesNotRequiringReview();let i=new PullRequestCommentsData(e,t);const n=await this._reposInvoker.getComments();for(const e of n.pullRequestComments){i=this.getMetricsCommentData(i,e)}for(const e of n.fileComments){i=this.getFilesRequiringCommentUpdates(i,e)}return i}async getMetricsComment(){this._logger.logDebug("* PullRequestComments.getMetricsComment()");const e=await this._codeMetrics.getMetrics();let t=`${this._runnerInvoker.loc("pullRequests.pullRequestComments.commentTitle")}\n`;t+=await this.addCommentSizeStatus();t+=await this.addCommentTestStatus();t+=`||${this._runnerInvoker.loc("pullRequests.pullRequestComments.tableLines")}\n`;t+="-|-:\n";t+=this.addCommentMetrics(this._runnerInvoker.loc("pullRequests.pullRequestComments.tableProductCode"),e.productCode,false);t+=this.addCommentMetrics(this._runnerInvoker.loc("pullRequests.pullRequestComments.tableTestCode"),e.testCode,false);t+=this.addCommentMetrics(this._runnerInvoker.loc("pullRequests.pullRequestComments.tableSubtotal"),e.subtotal,true);t+=this.addCommentMetrics(this._runnerInvoker.loc("pullRequests.pullRequestComments.tableIgnoredCode"),e.ignoredCode,false);t+=this.addCommentMetrics(this._runnerInvoker.loc("pullRequests.pullRequestComments.tableTotal"),e.total,true);t+="\n";t+=this._runnerInvoker.loc("pullRequests.pullRequestComments.commentFooter");return t}async getMetricsCommentStatus(){this._logger.logDebug("* PullRequestComments.getMetricsCommentStatus()");if(this._inputs.alwaysCloseComment){return a.CommentThreadStatus.Closed}if(await this._codeMetrics.isSmall()){const e=await this._codeMetrics.isSufficientlyTested();if(e??true){return a.CommentThreadStatus.Closed}}return a.CommentThreadStatus.Active}getMetricsCommentData(e,t){this._logger.logDebug("* PullRequestComments.getMetricsCommentData()");if(!t.content.startsWith(`${this._runnerInvoker.loc("pullRequests.pullRequestComments.commentTitle")}\n`)){return e}e.metricsCommentThreadId=t.id;e.metricsCommentContent=t.content;e.metricsCommentThreadStatus=t.status;return e}getFilesRequiringCommentUpdates(e,t){this._logger.logDebug("* PullRequestComments.getFilesRequiringCommentUpdates()");if(t.content!==this.noReviewRequiredComment){return e}const i=-1;const n=e.filesNotRequiringReview.indexOf(t.fileName);if(n!==i){e.filesNotRequiringReview.splice(n,1);return e}const r=e.deletedFilesNotRequiringReview.indexOf(t.fileName);if(r!==i){e.deletedFilesNotRequiringReview.splice(r,1);return e}e.commentThreadsRequiringDeletion.push(t.id);return e}async addCommentSizeStatus(){this._logger.logDebug("* PullRequestComments.addCommentSizeStatus()");let e="";if(await this._codeMetrics.isSmall()){e+=this._runnerInvoker.loc("pullRequests.pullRequestComments.smallPullRequestComment")}else{const t=(this._inputs.baseSize*this._inputs.growthRate).toLocaleString();e+=this._runnerInvoker.loc("pullRequests.pullRequestComments.largePullRequestComment",t)}e+="\n";return e}async addCommentTestStatus(){this._logger.logDebug("* PullRequestComments.addCommentTestStatus()");let e="";const t=await this._codeMetrics.isSufficientlyTested();if(t!==null){if(t){e+=this._runnerInvoker.loc("pullRequests.pullRequestComments.testsSufficientComment")}else{e+=this._runnerInvoker.loc("pullRequests.pullRequestComments.testsInsufficientComment")}e+="\n"}return e}addCommentMetrics(e,t,i){this._logger.logDebug("* PullRequestComments.addCommentMetrics()");const n=i?"**":"";let r=t.toLocaleString();if(r==="0"){r="-"}return`${n}${e}${n}|${n}${r}${n}\n`}}class PullRequestMetrics{_codeMetricsCalculator;_logger;_runnerInvoker;constructor(e,t,i){this._codeMetricsCalculator=e;this._logger=t;this._runnerInvoker=i}async run(e){try{this._runnerInvoker.locInitialize(e);const t=this._codeMetricsCalculator.shouldSkip;if(t!==null){this._runnerInvoker.setStatusSkipped(t);return}const i=await this._codeMetricsCalculator.shouldStop();if(i!==null){this._runnerInvoker.setStatusFailed(i);return}await Promise.all([this._codeMetricsCalculator.updateDetails(),this._codeMetricsCalculator.updateComments()]);this._runnerInvoker.setStatusSucceeded(this._runnerInvoker.loc("pullRequestMetrics.succeeded"))}catch(e){if(e instanceof Error){this._logger.logErrorObject(e);this._logger.replay();this._runnerInvoker.setStatusFailed(e.message)}else{this._logger.replay();this._runnerInvoker.setStatusFailed(String(e))}}}}class ReposInvoker{_azureReposInvoker;_gitHubReposInvoker;_logger;_reposInvoker=null;constructor(e,t,i){this._azureReposInvoker=e;this._gitHubReposInvoker=t;this._logger=i}get reposInvoker(){this._logger.logDebug("* ReposInvoker.getReposInvoker()");if(this._reposInvoker!==null){return this._reposInvoker}if(RunnerInvoker.isGitHub){this._reposInvoker=this._gitHubReposInvoker;return this._reposInvoker}const e=validateVariable("BUILD_REPOSITORY_PROVIDER","ReposInvoker.getReposInvoker()");switch(e){case"TfsGit":this._reposInvoker=this._azureReposInvoker;break;case"GitHub":case"GitHubEnterprise":this._reposInvoker=this._gitHubReposInvoker;break;default:throw new RangeError(`BUILD_REPOSITORY_PROVIDER '${e}' is unsupported.`)}return this._reposInvoker}async isAccessTokenAvailable(){this._logger.logDebug("* ReposInvoker.isAccessTokenAvailable()");return this.reposInvoker.isAccessTokenAvailable()}async getTitleAndDescription(){this._logger.logDebug("* ReposInvoker.getTitleAndDescription()");return this.reposInvoker.getTitleAndDescription()}async getComments(){this._logger.logDebug("* ReposInvoker.getComments()");return this.reposInvoker.getComments()}async setTitleAndDescription(e,t){this._logger.logDebug("* ReposInvoker.setTitleAndDescription()");return this.reposInvoker.setTitleAndDescription(e,t)}async createComment(e,t,i,n){this._logger.logDebug("* ReposInvoker.createComment()");return this.reposInvoker.createComment(e,t,i,n)}async updateComment(e,t,i){this._logger.logDebug("* ReposInvoker.updateComment()");return this.reposInvoker.updateComment(e,t,i)}async deleteCommentThread(e){this._logger.logDebug("* ReposInvoker.deleteCommentThread()");return this.reposInvoker.deleteCommentThread(e)}}class TokenManager{_azureDevOpsApiWrapper;_logger;_runnerInvoker;_previouslyInvoked=false;constructor(e,t,i){this._azureDevOpsApiWrapper=e;this._logger=t;this._runnerInvoker=i}async getToken(){this._logger.logDebug("* TokenManager.getToken()");if(this._previouslyInvoked){return null}const e=this._runnerInvoker.getInput(["Workload","Identity","Federation"]);if(e===null){this._logger.logDebug("No workload identity federation specified. Using Personal Access Token (PAT) for authentication.");return null}this._logger.logDebug(`Using workload identity federation '${e}' for authentication.`);const t=this._runnerInvoker.getEndpointAuthorizationScheme(e);if(t!=="WorkloadIdentityFederation"){return this._runnerInvoker.loc("repos.tokenManager.incorrectAuthorizationScheme",e,String(t))}process.env.PR_METRICS_ACCESS_TOKEN=await this.getAccessToken(e);this._previouslyInvoked=true;return null}async getAccessToken(e){this._logger.logDebug("* TokenManager.getAccessToken()");const t=validateString(this._runnerInvoker.getEndpointAuthorizationParameter(e,"serviceprincipalid"),"servicePrincipalId","TokenManager.getAccessToken()");const i=validateString(this._runnerInvoker.getEndpointAuthorizationParameter(e,"tenantid"),"tenantId","TokenManager.getAccessToken()");validateGuid(t,"servicePrincipalId","TokenManager.getAccessToken()");validateGuid(i,"tenantId","TokenManager.getAccessToken()");const n=await this.getFederatedToken(e);this._runnerInvoker.setSecret(n);const r=await this._runnerInvoker.exec("az",["login","--service-principal","-u",t,"--tenant",i,"--allow-no-subscriptions","--federated-token",n]);if(r.exitCode!==0){throw new Error(r.stderr)}const s=await this._runnerInvoker.exec("az",["account","get-access-token","--query","accessToken","--resource","499b84ac-1321-427f-aa17-267ca6975798","-o","tsv"]);if(s.exitCode!==0){throw new Error(s.stderr)}const o=s.stdout.trim();this._runnerInvoker.setSecret(o);return o}async getFederatedToken(e){this._logger.logDebug("* TokenManager.getFederatedToken()");const t=this.getSystemAccessToken();const i=this._azureDevOpsApiWrapper.getHandlerFromToken(t);const n=validateVariable("SYSTEM_COLLECTIONURI","TokenManager.getFederatedToken()");const r=this._azureDevOpsApiWrapper.getWebApiInstance(n,i);const s=await r.getTaskApi();const o=validateVariable("SYSTEM_TEAMPROJECTID","TokenManager.getFederatedToken()");const a=validateVariable("SYSTEM_HOSTTYPE","TokenManager.getFederatedToken()");const l=validateVariable("SYSTEM_PLANID","TokenManager.getFederatedToken()");const u=validateVariable("SYSTEM_JOBID","TokenManager.getFederatedToken()");const c=await s.createOidcToken({},o,a,l,u,e);return validateString(c.oidcToken,"response.oidcToken","TokenManager.getFederatedToken()")}getSystemAccessToken(){this._logger.logDebug("* TokenManager.getSystemAccessToken()");const e=this._runnerInvoker.getEndpointAuthorization("SYSTEMVSSCONNECTION");const t=e?.scheme;if(t!=="OAuth"){throw new Error(`Could not acquire authorization token from workload identity federation as the scheme was '${t??""}'.`)}this._logger.logDebug("Acquired authorization token from workload identity federation.");return validateString(e?.parameters.AccessToken,"endpointAuthorization.parameters.AccessToken","TokenManager.getSystemAccessToken()")}}const createPullRequestMetrics=()=>{const e=new HttpWrapper;const t=new AzureDevOpsApiWrapper;const i=new AzurePipelinesRunnerWrapper;const n=new ConsoleWrapper;const r=new GitHubRunnerWrapper;const s=new AzurePipelinesRunnerInvoker(i);const o=new GitHubRunnerInvoker(i,n,r);const a=new RunnerInvoker(s,o);const l=new Logger(n,a);const u=new GitInvoker(l,a);const c=new OctokitGitDiffParser(e,l);const d=new Inputs(l,a);const p=new CodeMetrics(u,d,l,a);const A=new OctokitWrapper(c);const f=new TokenManager(t,l,a);const h=new AzureReposInvoker(t,u,l,a,f);const g=new GitHubReposInvoker(u,l,A,a);const y=new ReposInvoker(h,g,l);const m=new PullRequest(p,l,a);const I=new PullRequestComments(p,d,l,y,a);const v=new CodeMetricsCalculator(u,l,m,I,y,a);return new PullRequestMetrics(v,l,a)};const Nt=createPullRequestMetrics;const run=async()=>{const e=Nt();await e.run(import.meta.dirname)};run().catch((()=>{process.exit(c)})); \ No newline at end of file +/* v8 ignore else -- @preserve */var dt="0.0.0-development";function _buildMessageForResponseErrors(e){return`Request failed due to following response errors:\n`+e.errors.map((e=>` - ${e.message}`)).join("\n")}var pt=class extends Error{constructor(e,t,i){super(_buildMessageForResponseErrors(i));this.request=e;this.headers=t;this.response=i;this.errors=i.errors;this.data=i.data;if(Error.captureStackTrace){Error.captureStackTrace(this,this.constructor)}}name="GraphqlResponseError";errors;data};var At=["method","baseUrl","url","headers","request","query","mediaType","operationName"];var ft=["query","method","url"];var ht=/\/api\/v3\/?$/;function graphql(e,t,i){if(i){if(typeof t==="string"&&"query"in i){return Promise.reject(new Error(`[@octokit/graphql] "query" cannot be used as variable name`))}for(const e in i){if(!ft.includes(e))continue;return Promise.reject(new Error(`[@octokit/graphql] "${e}" cannot be used as variable name`))}}const n=typeof t==="string"?Object.assign({query:t},i):t;const r=Object.keys(n).reduce(((e,t)=>{if(At.includes(t)){e[t]=n[t];return e}if(!e.variables){e.variables={}}e.variables[t]=n[t];return e}),{});const s=n.baseUrl||e.endpoint.DEFAULTS.baseUrl;if(ht.test(s)){r.url=s.replace(ht,"/api/graphql")}return e(r).then((e=>{if(e.data.errors){const t={};for(const i of Object.keys(e.headers)){t[i]=e.headers[i]}throw new pt(r,t,e.data)}return e.data.data}))}function graphql_dist_bundle_withDefaults(e,t){const i=e.defaults(t);const newApi=(e,t)=>graphql(i,e,t);return Object.assign(newApi,{defaults:graphql_dist_bundle_withDefaults.bind(null,i),endpoint:i.endpoint})}var yt=graphql_dist_bundle_withDefaults(ct,{headers:{"user-agent":`octokit-graphql.js/${dt} ${getUserAgent()}`},method:"POST",url:"/graphql"});function withCustomRequest(e){return graphql_dist_bundle_withDefaults(e,{method:"POST",url:"/graphql"})}var mt="(?:[a-zA-Z0-9_-]+)";var It="\\.";var vt=new RegExp(`^${mt}${It}${mt}${It}${mt}$`);var Et=vt.test.bind(vt);async function auth(e){const t=Et(e);const i=e.startsWith("v1.")||e.startsWith("ghs_");const n=e.startsWith("ghu_");const r=t?"app":i?"installation":n?"user-to-server":"oauth";return{type:"token",token:e,tokenType:r}}function withAuthorizationPrefix(e){if(e.split(/\./).length===3){return`bearer ${e}`}return`token ${e}`}async function hook(e,t,i,n){const r=t.endpoint.merge(i,n);r.headers.authorization=withAuthorizationPrefix(e);return t(r)}var Ct=function createTokenAuth2(e){if(!e){throw new Error("[@octokit/auth-token] No token passed to createTokenAuth")}if(typeof e!=="string"){throw new Error("[@octokit/auth-token] Token passed to createTokenAuth is not a string")}e=e.replace(/^(token|bearer) +/i,"");return Object.assign(auth.bind(null,e),{hook:hook.bind(null,e)})};const Tt="7.0.6";const dist_src_noop=()=>{};const Rt=console.warn.bind(console);const bt=console.error.bind(console);function createLogger(e={}){if(typeof e.debug!=="function"){e.debug=dist_src_noop}if(typeof e.info!=="function"){e.info=dist_src_noop}if(typeof e.warn!=="function"){e.warn=Rt}if(typeof e.error!=="function"){e.error=bt}return e}const wt=`octokit-core.js/${Tt} ${getUserAgent()}`;class Octokit{static VERSION=Tt;static defaults(e){const t=class extends(this){constructor(...t){const i=t[0]||{};if(typeof e==="function"){super(e(i));return}super(Object.assign({},e,i,i.userAgent&&e.userAgent?{userAgent:`${i.userAgent} ${e.userAgent}`}:null))}};return t}static plugins=[];static plugin(...e){const t=this.plugins;const i=class extends(this){static plugins=t.concat(e.filter((e=>!t.includes(e))))};return i}constructor(e={}){const t=new Ge.Collection;const i={baseUrl:ct.endpoint.DEFAULTS.baseUrl,headers:{},request:Object.assign({},e.request,{hook:t.bind(null,"request")}),mediaType:{previews:[],format:""}};i.headers["user-agent"]=e.userAgent?`${e.userAgent} ${wt}`:wt;if(e.baseUrl){i.baseUrl=e.baseUrl}if(e.previews){i.mediaType.previews=e.previews}if(e.timeZone){i.headers["time-zone"]=e.timeZone}this.request=ct.defaults(i);this.graphql=withCustomRequest(this.request).defaults(i);this.log=createLogger(e.log);this.hook=t;if(!e.authStrategy){if(!e.auth){this.auth=async()=>({type:"unauthenticated"})}else{const i=Ct(e.auth);t.wrap("request",i.hook);this.auth=i}}else{const{authStrategy:i,...n}=e;const r=i(Object.assign({request:this.request,log:this.log,octokit:this,octokitOptions:n},e.auth));t.wrap("request",r.hook);this.auth=r}const n=this.constructor;for(let t=0;t{e.log.debug("request",i);const n=Date.now();const r=e.request.endpoint.parse(i);const s=r.url.replace(i.baseUrl,"");return t(i).then((t=>{const i=t.headers["x-github-request-id"];e.log.info(`${r.method} ${s} - ${t.status} with id ${i} in ${Date.now()-n}ms`);return t})).catch((t=>{const i=t.response?.headers["x-github-request-id"]||"UNKNOWN";e.log.error(`${r.method} ${s} - ${t.status} with id ${i} in ${Date.now()-n}ms`);throw t}))}))}requestLog.VERSION=Bt;var Qt="0.0.0-development";function normalizePaginatedListResponse(e){if(!e.data){return{...e,data:[]}}const t=("total_count"in e.data||"total_commits"in e.data)&&!("url"in e.data);if(!t)return e;const i=e.data.incomplete_results;const n=e.data.repository_selection;const r=e.data.total_count;const s=e.data.total_commits;delete e.data.incomplete_results;delete e.data.repository_selection;delete e.data.total_count;delete e.data.total_commits;const o=Object.keys(e.data)[0];const a=e.data[o];e.data=a;if(typeof i!=="undefined"){e.data.incomplete_results=i}if(typeof n!=="undefined"){e.data.repository_selection=n}e.data.total_count=r;e.data.total_commits=s;return e}function iterator(e,t,i){const n=typeof t==="function"?t.endpoint(i):e.request.endpoint(t,i);const r=typeof t==="function"?t:e.request;const s=n.method;const o=n.headers;let a=n.url;return{[Symbol.asyncIterator]:()=>({async next(){if(!a)return{done:true};try{const e=await r({method:s,url:a,headers:o});const t=normalizePaginatedListResponse(e);a=((t.headers.link||"").match(/<([^<>]+)>;\s*rel="next"/)||[])[1];if(!a&&"total_commits"in t.data){const e=new URL(t.url);const i=e.searchParams;const n=parseInt(i.get("page")||"1",10);const r=parseInt(i.get("per_page")||"250",10);if(n*r{if(r.done){return t}let s=false;function done(){s=true}t=t.concat(n?n(r.value,done):r.value.data);if(s){return t}return gather(e,t,i,n)}))}var Dt=Object.assign(paginate,{iterator:iterator});var St=null&&["GET /advisories","GET /app/hook/deliveries","GET /app/installation-requests","GET /app/installations","GET /assignments/{assignment_id}/accepted_assignments","GET /classrooms","GET /classrooms/{classroom_id}/assignments","GET /enterprises/{enterprise}/code-security/configurations","GET /enterprises/{enterprise}/code-security/configurations/{configuration_id}/repositories","GET /enterprises/{enterprise}/dependabot/alerts","GET /enterprises/{enterprise}/teams","GET /enterprises/{enterprise}/teams/{enterprise-team}/memberships","GET /enterprises/{enterprise}/teams/{enterprise-team}/organizations","GET /events","GET /gists","GET /gists/public","GET /gists/starred","GET /gists/{gist_id}/comments","GET /gists/{gist_id}/commits","GET /gists/{gist_id}/forks","GET /installation/repositories","GET /issues","GET /licenses","GET /marketplace_listing/plans","GET /marketplace_listing/plans/{plan_id}/accounts","GET /marketplace_listing/stubbed/plans","GET /marketplace_listing/stubbed/plans/{plan_id}/accounts","GET /networks/{owner}/{repo}/events","GET /notifications","GET /organizations","GET /organizations/{org}/dependabot/repository-access","GET /orgs/{org}/actions/cache/usage-by-repository","GET /orgs/{org}/actions/hosted-runners","GET /orgs/{org}/actions/permissions/repositories","GET /orgs/{org}/actions/permissions/self-hosted-runners/repositories","GET /orgs/{org}/actions/runner-groups","GET /orgs/{org}/actions/runner-groups/{runner_group_id}/hosted-runners","GET /orgs/{org}/actions/runner-groups/{runner_group_id}/repositories","GET /orgs/{org}/actions/runner-groups/{runner_group_id}/runners","GET /orgs/{org}/actions/runners","GET /orgs/{org}/actions/secrets","GET /orgs/{org}/actions/secrets/{secret_name}/repositories","GET /orgs/{org}/actions/variables","GET /orgs/{org}/actions/variables/{name}/repositories","GET /orgs/{org}/attestations/repositories","GET /orgs/{org}/attestations/{subject_digest}","GET /orgs/{org}/blocks","GET /orgs/{org}/campaigns","GET /orgs/{org}/code-scanning/alerts","GET /orgs/{org}/code-security/configurations","GET /orgs/{org}/code-security/configurations/{configuration_id}/repositories","GET /orgs/{org}/codespaces","GET /orgs/{org}/codespaces/secrets","GET /orgs/{org}/codespaces/secrets/{secret_name}/repositories","GET /orgs/{org}/copilot/billing/seats","GET /orgs/{org}/copilot/metrics","GET /orgs/{org}/dependabot/alerts","GET /orgs/{org}/dependabot/secrets","GET /orgs/{org}/dependabot/secrets/{secret_name}/repositories","GET /orgs/{org}/events","GET /orgs/{org}/failed_invitations","GET /orgs/{org}/hooks","GET /orgs/{org}/hooks/{hook_id}/deliveries","GET /orgs/{org}/insights/api/route-stats/{actor_type}/{actor_id}","GET /orgs/{org}/insights/api/subject-stats","GET /orgs/{org}/insights/api/user-stats/{user_id}","GET /orgs/{org}/installations","GET /orgs/{org}/invitations","GET /orgs/{org}/invitations/{invitation_id}/teams","GET /orgs/{org}/issues","GET /orgs/{org}/members","GET /orgs/{org}/members/{username}/codespaces","GET /orgs/{org}/migrations","GET /orgs/{org}/migrations/{migration_id}/repositories","GET /orgs/{org}/organization-roles/{role_id}/teams","GET /orgs/{org}/organization-roles/{role_id}/users","GET /orgs/{org}/outside_collaborators","GET /orgs/{org}/packages","GET /orgs/{org}/packages/{package_type}/{package_name}/versions","GET /orgs/{org}/personal-access-token-requests","GET /orgs/{org}/personal-access-token-requests/{pat_request_id}/repositories","GET /orgs/{org}/personal-access-tokens","GET /orgs/{org}/personal-access-tokens/{pat_id}/repositories","GET /orgs/{org}/private-registries","GET /orgs/{org}/projects","GET /orgs/{org}/projectsV2","GET /orgs/{org}/projectsV2/{project_number}/fields","GET /orgs/{org}/projectsV2/{project_number}/items","GET /orgs/{org}/properties/values","GET /orgs/{org}/public_members","GET /orgs/{org}/repos","GET /orgs/{org}/rulesets","GET /orgs/{org}/rulesets/rule-suites","GET /orgs/{org}/rulesets/{ruleset_id}/history","GET /orgs/{org}/secret-scanning/alerts","GET /orgs/{org}/security-advisories","GET /orgs/{org}/settings/immutable-releases/repositories","GET /orgs/{org}/settings/network-configurations","GET /orgs/{org}/team/{team_slug}/copilot/metrics","GET /orgs/{org}/teams","GET /orgs/{org}/teams/{team_slug}/discussions","GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments","GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions","GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions","GET /orgs/{org}/teams/{team_slug}/invitations","GET /orgs/{org}/teams/{team_slug}/members","GET /orgs/{org}/teams/{team_slug}/projects","GET /orgs/{org}/teams/{team_slug}/repos","GET /orgs/{org}/teams/{team_slug}/teams","GET /projects/{project_id}/collaborators","GET /repos/{owner}/{repo}/actions/artifacts","GET /repos/{owner}/{repo}/actions/caches","GET /repos/{owner}/{repo}/actions/organization-secrets","GET /repos/{owner}/{repo}/actions/organization-variables","GET /repos/{owner}/{repo}/actions/runners","GET /repos/{owner}/{repo}/actions/runs","GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts","GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/jobs","GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs","GET /repos/{owner}/{repo}/actions/secrets","GET /repos/{owner}/{repo}/actions/variables","GET /repos/{owner}/{repo}/actions/workflows","GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs","GET /repos/{owner}/{repo}/activity","GET /repos/{owner}/{repo}/assignees","GET /repos/{owner}/{repo}/attestations/{subject_digest}","GET /repos/{owner}/{repo}/branches","GET /repos/{owner}/{repo}/check-runs/{check_run_id}/annotations","GET /repos/{owner}/{repo}/check-suites/{check_suite_id}/check-runs","GET /repos/{owner}/{repo}/code-scanning/alerts","GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances","GET /repos/{owner}/{repo}/code-scanning/analyses","GET /repos/{owner}/{repo}/codespaces","GET /repos/{owner}/{repo}/codespaces/devcontainers","GET /repos/{owner}/{repo}/codespaces/secrets","GET /repos/{owner}/{repo}/collaborators","GET /repos/{owner}/{repo}/comments","GET /repos/{owner}/{repo}/comments/{comment_id}/reactions","GET /repos/{owner}/{repo}/commits","GET /repos/{owner}/{repo}/commits/{commit_sha}/comments","GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls","GET /repos/{owner}/{repo}/commits/{ref}/check-runs","GET /repos/{owner}/{repo}/commits/{ref}/check-suites","GET /repos/{owner}/{repo}/commits/{ref}/status","GET /repos/{owner}/{repo}/commits/{ref}/statuses","GET /repos/{owner}/{repo}/compare/{basehead}","GET /repos/{owner}/{repo}/compare/{base}...{head}","GET /repos/{owner}/{repo}/contributors","GET /repos/{owner}/{repo}/dependabot/alerts","GET /repos/{owner}/{repo}/dependabot/secrets","GET /repos/{owner}/{repo}/deployments","GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses","GET /repos/{owner}/{repo}/environments","GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies","GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/apps","GET /repos/{owner}/{repo}/environments/{environment_name}/secrets","GET /repos/{owner}/{repo}/environments/{environment_name}/variables","GET /repos/{owner}/{repo}/events","GET /repos/{owner}/{repo}/forks","GET /repos/{owner}/{repo}/hooks","GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries","GET /repos/{owner}/{repo}/invitations","GET /repos/{owner}/{repo}/issues","GET /repos/{owner}/{repo}/issues/comments","GET /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions","GET /repos/{owner}/{repo}/issues/events","GET /repos/{owner}/{repo}/issues/{issue_number}/comments","GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by","GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocking","GET /repos/{owner}/{repo}/issues/{issue_number}/events","GET /repos/{owner}/{repo}/issues/{issue_number}/labels","GET /repos/{owner}/{repo}/issues/{issue_number}/reactions","GET /repos/{owner}/{repo}/issues/{issue_number}/sub_issues","GET /repos/{owner}/{repo}/issues/{issue_number}/timeline","GET /repos/{owner}/{repo}/keys","GET /repos/{owner}/{repo}/labels","GET /repos/{owner}/{repo}/milestones","GET /repos/{owner}/{repo}/milestones/{milestone_number}/labels","GET /repos/{owner}/{repo}/notifications","GET /repos/{owner}/{repo}/pages/builds","GET /repos/{owner}/{repo}/projects","GET /repos/{owner}/{repo}/pulls","GET /repos/{owner}/{repo}/pulls/comments","GET /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions","GET /repos/{owner}/{repo}/pulls/{pull_number}/comments","GET /repos/{owner}/{repo}/pulls/{pull_number}/commits","GET /repos/{owner}/{repo}/pulls/{pull_number}/files","GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews","GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/comments","GET /repos/{owner}/{repo}/releases","GET /repos/{owner}/{repo}/releases/{release_id}/assets","GET /repos/{owner}/{repo}/releases/{release_id}/reactions","GET /repos/{owner}/{repo}/rules/branches/{branch}","GET /repos/{owner}/{repo}/rulesets","GET /repos/{owner}/{repo}/rulesets/rule-suites","GET /repos/{owner}/{repo}/rulesets/{ruleset_id}/history","GET /repos/{owner}/{repo}/secret-scanning/alerts","GET /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}/locations","GET /repos/{owner}/{repo}/security-advisories","GET /repos/{owner}/{repo}/stargazers","GET /repos/{owner}/{repo}/subscribers","GET /repos/{owner}/{repo}/tags","GET /repos/{owner}/{repo}/teams","GET /repos/{owner}/{repo}/topics","GET /repositories","GET /search/code","GET /search/commits","GET /search/issues","GET /search/labels","GET /search/repositories","GET /search/topics","GET /search/users","GET /teams/{team_id}/discussions","GET /teams/{team_id}/discussions/{discussion_number}/comments","GET /teams/{team_id}/discussions/{discussion_number}/comments/{comment_number}/reactions","GET /teams/{team_id}/discussions/{discussion_number}/reactions","GET /teams/{team_id}/invitations","GET /teams/{team_id}/members","GET /teams/{team_id}/projects","GET /teams/{team_id}/repos","GET /teams/{team_id}/teams","GET /user/blocks","GET /user/codespaces","GET /user/codespaces/secrets","GET /user/emails","GET /user/followers","GET /user/following","GET /user/gpg_keys","GET /user/installations","GET /user/installations/{installation_id}/repositories","GET /user/issues","GET /user/keys","GET /user/marketplace_purchases","GET /user/marketplace_purchases/stubbed","GET /user/memberships/orgs","GET /user/migrations","GET /user/migrations/{migration_id}/repositories","GET /user/orgs","GET /user/packages","GET /user/packages/{package_type}/{package_name}/versions","GET /user/public_emails","GET /user/repos","GET /user/repository_invitations","GET /user/social_accounts","GET /user/ssh_signing_keys","GET /user/starred","GET /user/subscriptions","GET /user/teams","GET /users","GET /users/{username}/attestations/{subject_digest}","GET /users/{username}/events","GET /users/{username}/events/orgs/{org}","GET /users/{username}/events/public","GET /users/{username}/followers","GET /users/{username}/following","GET /users/{username}/gists","GET /users/{username}/gpg_keys","GET /users/{username}/keys","GET /users/{username}/orgs","GET /users/{username}/packages","GET /users/{username}/projects","GET /users/{username}/projectsV2","GET /users/{username}/projectsV2/{project_number}/fields","GET /users/{username}/projectsV2/{project_number}/items","GET /users/{username}/received_events","GET /users/{username}/received_events/public","GET /users/{username}/repos","GET /users/{username}/social_accounts","GET /users/{username}/ssh_signing_keys","GET /users/{username}/starred","GET /users/{username}/subscriptions"];function isPaginatingEndpoint(e){if(typeof e==="string"){return St.includes(e)}else{return false}}function paginateRest(e){return{paginate:Object.assign(paginate.bind(null,e),{iterator:iterator.bind(null,e)})}}paginateRest.VERSION=Qt;const kt="17.0.0";const Pt={actions:{addCustomLabelsToSelfHostedRunnerForOrg:["POST /orgs/{org}/actions/runners/{runner_id}/labels"],addCustomLabelsToSelfHostedRunnerForRepo:["POST /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],addRepoAccessToSelfHostedRunnerGroupInOrg:["PUT /orgs/{org}/actions/runner-groups/{runner_group_id}/repositories/{repository_id}"],addSelectedRepoToOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}/repositories/{repository_id}"],addSelectedRepoToOrgVariable:["PUT /orgs/{org}/actions/variables/{name}/repositories/{repository_id}"],approveWorkflowRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/approve"],cancelWorkflowRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/cancel"],createEnvironmentVariable:["POST /repos/{owner}/{repo}/environments/{environment_name}/variables"],createHostedRunnerForOrg:["POST /orgs/{org}/actions/hosted-runners"],createOrUpdateEnvironmentSecret:["PUT /repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}"],createOrUpdateOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}"],createOrUpdateRepoSecret:["PUT /repos/{owner}/{repo}/actions/secrets/{secret_name}"],createOrgVariable:["POST /orgs/{org}/actions/variables"],createRegistrationTokenForOrg:["POST /orgs/{org}/actions/runners/registration-token"],createRegistrationTokenForRepo:["POST /repos/{owner}/{repo}/actions/runners/registration-token"],createRemoveTokenForOrg:["POST /orgs/{org}/actions/runners/remove-token"],createRemoveTokenForRepo:["POST /repos/{owner}/{repo}/actions/runners/remove-token"],createRepoVariable:["POST /repos/{owner}/{repo}/actions/variables"],createWorkflowDispatch:["POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches"],deleteActionsCacheById:["DELETE /repos/{owner}/{repo}/actions/caches/{cache_id}"],deleteActionsCacheByKey:["DELETE /repos/{owner}/{repo}/actions/caches{?key,ref}"],deleteArtifact:["DELETE /repos/{owner}/{repo}/actions/artifacts/{artifact_id}"],deleteCustomImageFromOrg:["DELETE /orgs/{org}/actions/hosted-runners/images/custom/{image_definition_id}"],deleteCustomImageVersionFromOrg:["DELETE /orgs/{org}/actions/hosted-runners/images/custom/{image_definition_id}/versions/{version}"],deleteEnvironmentSecret:["DELETE /repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}"],deleteEnvironmentVariable:["DELETE /repos/{owner}/{repo}/environments/{environment_name}/variables/{name}"],deleteHostedRunnerForOrg:["DELETE /orgs/{org}/actions/hosted-runners/{hosted_runner_id}"],deleteOrgSecret:["DELETE /orgs/{org}/actions/secrets/{secret_name}"],deleteOrgVariable:["DELETE /orgs/{org}/actions/variables/{name}"],deleteRepoSecret:["DELETE /repos/{owner}/{repo}/actions/secrets/{secret_name}"],deleteRepoVariable:["DELETE /repos/{owner}/{repo}/actions/variables/{name}"],deleteSelfHostedRunnerFromOrg:["DELETE /orgs/{org}/actions/runners/{runner_id}"],deleteSelfHostedRunnerFromRepo:["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}"],deleteWorkflowRun:["DELETE /repos/{owner}/{repo}/actions/runs/{run_id}"],deleteWorkflowRunLogs:["DELETE /repos/{owner}/{repo}/actions/runs/{run_id}/logs"],disableSelectedRepositoryGithubActionsOrganization:["DELETE /orgs/{org}/actions/permissions/repositories/{repository_id}"],disableWorkflow:["PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/disable"],downloadArtifact:["GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}/{archive_format}"],downloadJobLogsForWorkflowRun:["GET /repos/{owner}/{repo}/actions/jobs/{job_id}/logs"],downloadWorkflowRunAttemptLogs:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/logs"],downloadWorkflowRunLogs:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/logs"],enableSelectedRepositoryGithubActionsOrganization:["PUT /orgs/{org}/actions/permissions/repositories/{repository_id}"],enableWorkflow:["PUT /repos/{owner}/{repo}/actions/workflows/{workflow_id}/enable"],forceCancelWorkflowRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/force-cancel"],generateRunnerJitconfigForOrg:["POST /orgs/{org}/actions/runners/generate-jitconfig"],generateRunnerJitconfigForRepo:["POST /repos/{owner}/{repo}/actions/runners/generate-jitconfig"],getActionsCacheList:["GET /repos/{owner}/{repo}/actions/caches"],getActionsCacheUsage:["GET /repos/{owner}/{repo}/actions/cache/usage"],getActionsCacheUsageByRepoForOrg:["GET /orgs/{org}/actions/cache/usage-by-repository"],getActionsCacheUsageForOrg:["GET /orgs/{org}/actions/cache/usage"],getAllowedActionsOrganization:["GET /orgs/{org}/actions/permissions/selected-actions"],getAllowedActionsRepository:["GET /repos/{owner}/{repo}/actions/permissions/selected-actions"],getArtifact:["GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}"],getCustomImageForOrg:["GET /orgs/{org}/actions/hosted-runners/images/custom/{image_definition_id}"],getCustomImageVersionForOrg:["GET /orgs/{org}/actions/hosted-runners/images/custom/{image_definition_id}/versions/{version}"],getCustomOidcSubClaimForRepo:["GET /repos/{owner}/{repo}/actions/oidc/customization/sub"],getEnvironmentPublicKey:["GET /repos/{owner}/{repo}/environments/{environment_name}/secrets/public-key"],getEnvironmentSecret:["GET /repos/{owner}/{repo}/environments/{environment_name}/secrets/{secret_name}"],getEnvironmentVariable:["GET /repos/{owner}/{repo}/environments/{environment_name}/variables/{name}"],getGithubActionsDefaultWorkflowPermissionsOrganization:["GET /orgs/{org}/actions/permissions/workflow"],getGithubActionsDefaultWorkflowPermissionsRepository:["GET /repos/{owner}/{repo}/actions/permissions/workflow"],getGithubActionsPermissionsOrganization:["GET /orgs/{org}/actions/permissions"],getGithubActionsPermissionsRepository:["GET /repos/{owner}/{repo}/actions/permissions"],getHostedRunnerForOrg:["GET /orgs/{org}/actions/hosted-runners/{hosted_runner_id}"],getHostedRunnersGithubOwnedImagesForOrg:["GET /orgs/{org}/actions/hosted-runners/images/github-owned"],getHostedRunnersLimitsForOrg:["GET /orgs/{org}/actions/hosted-runners/limits"],getHostedRunnersMachineSpecsForOrg:["GET /orgs/{org}/actions/hosted-runners/machine-sizes"],getHostedRunnersPartnerImagesForOrg:["GET /orgs/{org}/actions/hosted-runners/images/partner"],getHostedRunnersPlatformsForOrg:["GET /orgs/{org}/actions/hosted-runners/platforms"],getJobForWorkflowRun:["GET /repos/{owner}/{repo}/actions/jobs/{job_id}"],getOrgPublicKey:["GET /orgs/{org}/actions/secrets/public-key"],getOrgSecret:["GET /orgs/{org}/actions/secrets/{secret_name}"],getOrgVariable:["GET /orgs/{org}/actions/variables/{name}"],getPendingDeploymentsForRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments"],getRepoPermissions:["GET /repos/{owner}/{repo}/actions/permissions",{},{renamed:["actions","getGithubActionsPermissionsRepository"]}],getRepoPublicKey:["GET /repos/{owner}/{repo}/actions/secrets/public-key"],getRepoSecret:["GET /repos/{owner}/{repo}/actions/secrets/{secret_name}"],getRepoVariable:["GET /repos/{owner}/{repo}/actions/variables/{name}"],getReviewsForRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/approvals"],getSelfHostedRunnerForOrg:["GET /orgs/{org}/actions/runners/{runner_id}"],getSelfHostedRunnerForRepo:["GET /repos/{owner}/{repo}/actions/runners/{runner_id}"],getWorkflow:["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}"],getWorkflowAccessToRepository:["GET /repos/{owner}/{repo}/actions/permissions/access"],getWorkflowRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}"],getWorkflowRunAttempt:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}"],getWorkflowRunUsage:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/timing"],getWorkflowUsage:["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/timing"],listArtifactsForRepo:["GET /repos/{owner}/{repo}/actions/artifacts"],listCustomImageVersionsForOrg:["GET /orgs/{org}/actions/hosted-runners/images/custom/{image_definition_id}/versions"],listCustomImagesForOrg:["GET /orgs/{org}/actions/hosted-runners/images/custom"],listEnvironmentSecrets:["GET /repos/{owner}/{repo}/environments/{environment_name}/secrets"],listEnvironmentVariables:["GET /repos/{owner}/{repo}/environments/{environment_name}/variables"],listGithubHostedRunnersInGroupForOrg:["GET /orgs/{org}/actions/runner-groups/{runner_group_id}/hosted-runners"],listHostedRunnersForOrg:["GET /orgs/{org}/actions/hosted-runners"],listJobsForWorkflowRun:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs"],listJobsForWorkflowRunAttempt:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/jobs"],listLabelsForSelfHostedRunnerForOrg:["GET /orgs/{org}/actions/runners/{runner_id}/labels"],listLabelsForSelfHostedRunnerForRepo:["GET /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],listOrgSecrets:["GET /orgs/{org}/actions/secrets"],listOrgVariables:["GET /orgs/{org}/actions/variables"],listRepoOrganizationSecrets:["GET /repos/{owner}/{repo}/actions/organization-secrets"],listRepoOrganizationVariables:["GET /repos/{owner}/{repo}/actions/organization-variables"],listRepoSecrets:["GET /repos/{owner}/{repo}/actions/secrets"],listRepoVariables:["GET /repos/{owner}/{repo}/actions/variables"],listRepoWorkflows:["GET /repos/{owner}/{repo}/actions/workflows"],listRunnerApplicationsForOrg:["GET /orgs/{org}/actions/runners/downloads"],listRunnerApplicationsForRepo:["GET /repos/{owner}/{repo}/actions/runners/downloads"],listSelectedReposForOrgSecret:["GET /orgs/{org}/actions/secrets/{secret_name}/repositories"],listSelectedReposForOrgVariable:["GET /orgs/{org}/actions/variables/{name}/repositories"],listSelectedRepositoriesEnabledGithubActionsOrganization:["GET /orgs/{org}/actions/permissions/repositories"],listSelfHostedRunnersForOrg:["GET /orgs/{org}/actions/runners"],listSelfHostedRunnersForRepo:["GET /repos/{owner}/{repo}/actions/runners"],listWorkflowRunArtifacts:["GET /repos/{owner}/{repo}/actions/runs/{run_id}/artifacts"],listWorkflowRuns:["GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs"],listWorkflowRunsForRepo:["GET /repos/{owner}/{repo}/actions/runs"],reRunJobForWorkflowRun:["POST /repos/{owner}/{repo}/actions/jobs/{job_id}/rerun"],reRunWorkflow:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun"],reRunWorkflowFailedJobs:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/rerun-failed-jobs"],removeAllCustomLabelsFromSelfHostedRunnerForOrg:["DELETE /orgs/{org}/actions/runners/{runner_id}/labels"],removeAllCustomLabelsFromSelfHostedRunnerForRepo:["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],removeCustomLabelFromSelfHostedRunnerForOrg:["DELETE /orgs/{org}/actions/runners/{runner_id}/labels/{name}"],removeCustomLabelFromSelfHostedRunnerForRepo:["DELETE /repos/{owner}/{repo}/actions/runners/{runner_id}/labels/{name}"],removeSelectedRepoFromOrgSecret:["DELETE /orgs/{org}/actions/secrets/{secret_name}/repositories/{repository_id}"],removeSelectedRepoFromOrgVariable:["DELETE /orgs/{org}/actions/variables/{name}/repositories/{repository_id}"],reviewCustomGatesForRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/deployment_protection_rule"],reviewPendingDeploymentsForRun:["POST /repos/{owner}/{repo}/actions/runs/{run_id}/pending_deployments"],setAllowedActionsOrganization:["PUT /orgs/{org}/actions/permissions/selected-actions"],setAllowedActionsRepository:["PUT /repos/{owner}/{repo}/actions/permissions/selected-actions"],setCustomLabelsForSelfHostedRunnerForOrg:["PUT /orgs/{org}/actions/runners/{runner_id}/labels"],setCustomLabelsForSelfHostedRunnerForRepo:["PUT /repos/{owner}/{repo}/actions/runners/{runner_id}/labels"],setCustomOidcSubClaimForRepo:["PUT /repos/{owner}/{repo}/actions/oidc/customization/sub"],setGithubActionsDefaultWorkflowPermissionsOrganization:["PUT /orgs/{org}/actions/permissions/workflow"],setGithubActionsDefaultWorkflowPermissionsRepository:["PUT /repos/{owner}/{repo}/actions/permissions/workflow"],setGithubActionsPermissionsOrganization:["PUT /orgs/{org}/actions/permissions"],setGithubActionsPermissionsRepository:["PUT /repos/{owner}/{repo}/actions/permissions"],setSelectedReposForOrgSecret:["PUT /orgs/{org}/actions/secrets/{secret_name}/repositories"],setSelectedReposForOrgVariable:["PUT /orgs/{org}/actions/variables/{name}/repositories"],setSelectedRepositoriesEnabledGithubActionsOrganization:["PUT /orgs/{org}/actions/permissions/repositories"],setWorkflowAccessToRepository:["PUT /repos/{owner}/{repo}/actions/permissions/access"],updateEnvironmentVariable:["PATCH /repos/{owner}/{repo}/environments/{environment_name}/variables/{name}"],updateHostedRunnerForOrg:["PATCH /orgs/{org}/actions/hosted-runners/{hosted_runner_id}"],updateOrgVariable:["PATCH /orgs/{org}/actions/variables/{name}"],updateRepoVariable:["PATCH /repos/{owner}/{repo}/actions/variables/{name}"]},activity:{checkRepoIsStarredByAuthenticatedUser:["GET /user/starred/{owner}/{repo}"],deleteRepoSubscription:["DELETE /repos/{owner}/{repo}/subscription"],deleteThreadSubscription:["DELETE /notifications/threads/{thread_id}/subscription"],getFeeds:["GET /feeds"],getRepoSubscription:["GET /repos/{owner}/{repo}/subscription"],getThread:["GET /notifications/threads/{thread_id}"],getThreadSubscriptionForAuthenticatedUser:["GET /notifications/threads/{thread_id}/subscription"],listEventsForAuthenticatedUser:["GET /users/{username}/events"],listNotificationsForAuthenticatedUser:["GET /notifications"],listOrgEventsForAuthenticatedUser:["GET /users/{username}/events/orgs/{org}"],listPublicEvents:["GET /events"],listPublicEventsForRepoNetwork:["GET /networks/{owner}/{repo}/events"],listPublicEventsForUser:["GET /users/{username}/events/public"],listPublicOrgEvents:["GET /orgs/{org}/events"],listReceivedEventsForUser:["GET /users/{username}/received_events"],listReceivedPublicEventsForUser:["GET /users/{username}/received_events/public"],listRepoEvents:["GET /repos/{owner}/{repo}/events"],listRepoNotificationsForAuthenticatedUser:["GET /repos/{owner}/{repo}/notifications"],listReposStarredByAuthenticatedUser:["GET /user/starred"],listReposStarredByUser:["GET /users/{username}/starred"],listReposWatchedByUser:["GET /users/{username}/subscriptions"],listStargazersForRepo:["GET /repos/{owner}/{repo}/stargazers"],listWatchedReposForAuthenticatedUser:["GET /user/subscriptions"],listWatchersForRepo:["GET /repos/{owner}/{repo}/subscribers"],markNotificationsAsRead:["PUT /notifications"],markRepoNotificationsAsRead:["PUT /repos/{owner}/{repo}/notifications"],markThreadAsDone:["DELETE /notifications/threads/{thread_id}"],markThreadAsRead:["PATCH /notifications/threads/{thread_id}"],setRepoSubscription:["PUT /repos/{owner}/{repo}/subscription"],setThreadSubscription:["PUT /notifications/threads/{thread_id}/subscription"],starRepoForAuthenticatedUser:["PUT /user/starred/{owner}/{repo}"],unstarRepoForAuthenticatedUser:["DELETE /user/starred/{owner}/{repo}"]},apps:{addRepoToInstallation:["PUT /user/installations/{installation_id}/repositories/{repository_id}",{},{renamed:["apps","addRepoToInstallationForAuthenticatedUser"]}],addRepoToInstallationForAuthenticatedUser:["PUT /user/installations/{installation_id}/repositories/{repository_id}"],checkToken:["POST /applications/{client_id}/token"],createFromManifest:["POST /app-manifests/{code}/conversions"],createInstallationAccessToken:["POST /app/installations/{installation_id}/access_tokens"],deleteAuthorization:["DELETE /applications/{client_id}/grant"],deleteInstallation:["DELETE /app/installations/{installation_id}"],deleteToken:["DELETE /applications/{client_id}/token"],getAuthenticated:["GET /app"],getBySlug:["GET /apps/{app_slug}"],getInstallation:["GET /app/installations/{installation_id}"],getOrgInstallation:["GET /orgs/{org}/installation"],getRepoInstallation:["GET /repos/{owner}/{repo}/installation"],getSubscriptionPlanForAccount:["GET /marketplace_listing/accounts/{account_id}"],getSubscriptionPlanForAccountStubbed:["GET /marketplace_listing/stubbed/accounts/{account_id}"],getUserInstallation:["GET /users/{username}/installation"],getWebhookConfigForApp:["GET /app/hook/config"],getWebhookDelivery:["GET /app/hook/deliveries/{delivery_id}"],listAccountsForPlan:["GET /marketplace_listing/plans/{plan_id}/accounts"],listAccountsForPlanStubbed:["GET /marketplace_listing/stubbed/plans/{plan_id}/accounts"],listInstallationReposForAuthenticatedUser:["GET /user/installations/{installation_id}/repositories"],listInstallationRequestsForAuthenticatedApp:["GET /app/installation-requests"],listInstallations:["GET /app/installations"],listInstallationsForAuthenticatedUser:["GET /user/installations"],listPlans:["GET /marketplace_listing/plans"],listPlansStubbed:["GET /marketplace_listing/stubbed/plans"],listReposAccessibleToInstallation:["GET /installation/repositories"],listSubscriptionsForAuthenticatedUser:["GET /user/marketplace_purchases"],listSubscriptionsForAuthenticatedUserStubbed:["GET /user/marketplace_purchases/stubbed"],listWebhookDeliveries:["GET /app/hook/deliveries"],redeliverWebhookDelivery:["POST /app/hook/deliveries/{delivery_id}/attempts"],removeRepoFromInstallation:["DELETE /user/installations/{installation_id}/repositories/{repository_id}",{},{renamed:["apps","removeRepoFromInstallationForAuthenticatedUser"]}],removeRepoFromInstallationForAuthenticatedUser:["DELETE /user/installations/{installation_id}/repositories/{repository_id}"],resetToken:["PATCH /applications/{client_id}/token"],revokeInstallationAccessToken:["DELETE /installation/token"],scopeToken:["POST /applications/{client_id}/token/scoped"],suspendInstallation:["PUT /app/installations/{installation_id}/suspended"],unsuspendInstallation:["DELETE /app/installations/{installation_id}/suspended"],updateWebhookConfigForApp:["PATCH /app/hook/config"]},billing:{getGithubActionsBillingOrg:["GET /orgs/{org}/settings/billing/actions"],getGithubActionsBillingUser:["GET /users/{username}/settings/billing/actions"],getGithubBillingPremiumRequestUsageReportOrg:["GET /organizations/{org}/settings/billing/premium_request/usage"],getGithubBillingPremiumRequestUsageReportUser:["GET /users/{username}/settings/billing/premium_request/usage"],getGithubBillingUsageReportOrg:["GET /organizations/{org}/settings/billing/usage"],getGithubBillingUsageReportUser:["GET /users/{username}/settings/billing/usage"],getGithubPackagesBillingOrg:["GET /orgs/{org}/settings/billing/packages"],getGithubPackagesBillingUser:["GET /users/{username}/settings/billing/packages"],getSharedStorageBillingOrg:["GET /orgs/{org}/settings/billing/shared-storage"],getSharedStorageBillingUser:["GET /users/{username}/settings/billing/shared-storage"]},campaigns:{createCampaign:["POST /orgs/{org}/campaigns"],deleteCampaign:["DELETE /orgs/{org}/campaigns/{campaign_number}"],getCampaignSummary:["GET /orgs/{org}/campaigns/{campaign_number}"],listOrgCampaigns:["GET /orgs/{org}/campaigns"],updateCampaign:["PATCH /orgs/{org}/campaigns/{campaign_number}"]},checks:{create:["POST /repos/{owner}/{repo}/check-runs"],createSuite:["POST /repos/{owner}/{repo}/check-suites"],get:["GET /repos/{owner}/{repo}/check-runs/{check_run_id}"],getSuite:["GET /repos/{owner}/{repo}/check-suites/{check_suite_id}"],listAnnotations:["GET /repos/{owner}/{repo}/check-runs/{check_run_id}/annotations"],listForRef:["GET /repos/{owner}/{repo}/commits/{ref}/check-runs"],listForSuite:["GET /repos/{owner}/{repo}/check-suites/{check_suite_id}/check-runs"],listSuitesForRef:["GET /repos/{owner}/{repo}/commits/{ref}/check-suites"],rerequestRun:["POST /repos/{owner}/{repo}/check-runs/{check_run_id}/rerequest"],rerequestSuite:["POST /repos/{owner}/{repo}/check-suites/{check_suite_id}/rerequest"],setSuitesPreferences:["PATCH /repos/{owner}/{repo}/check-suites/preferences"],update:["PATCH /repos/{owner}/{repo}/check-runs/{check_run_id}"]},codeScanning:{commitAutofix:["POST /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/autofix/commits"],createAutofix:["POST /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/autofix"],createVariantAnalysis:["POST /repos/{owner}/{repo}/code-scanning/codeql/variant-analyses"],deleteAnalysis:["DELETE /repos/{owner}/{repo}/code-scanning/analyses/{analysis_id}{?confirm_delete}"],deleteCodeqlDatabase:["DELETE /repos/{owner}/{repo}/code-scanning/codeql/databases/{language}"],getAlert:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}",{},{renamedParameters:{alert_id:"alert_number"}}],getAnalysis:["GET /repos/{owner}/{repo}/code-scanning/analyses/{analysis_id}"],getAutofix:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/autofix"],getCodeqlDatabase:["GET /repos/{owner}/{repo}/code-scanning/codeql/databases/{language}"],getDefaultSetup:["GET /repos/{owner}/{repo}/code-scanning/default-setup"],getSarif:["GET /repos/{owner}/{repo}/code-scanning/sarifs/{sarif_id}"],getVariantAnalysis:["GET /repos/{owner}/{repo}/code-scanning/codeql/variant-analyses/{codeql_variant_analysis_id}"],getVariantAnalysisRepoTask:["GET /repos/{owner}/{repo}/code-scanning/codeql/variant-analyses/{codeql_variant_analysis_id}/repos/{repo_owner}/{repo_name}"],listAlertInstances:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances"],listAlertsForOrg:["GET /orgs/{org}/code-scanning/alerts"],listAlertsForRepo:["GET /repos/{owner}/{repo}/code-scanning/alerts"],listAlertsInstances:["GET /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}/instances",{},{renamed:["codeScanning","listAlertInstances"]}],listCodeqlDatabases:["GET /repos/{owner}/{repo}/code-scanning/codeql/databases"],listRecentAnalyses:["GET /repos/{owner}/{repo}/code-scanning/analyses"],updateAlert:["PATCH /repos/{owner}/{repo}/code-scanning/alerts/{alert_number}"],updateDefaultSetup:["PATCH /repos/{owner}/{repo}/code-scanning/default-setup"],uploadSarif:["POST /repos/{owner}/{repo}/code-scanning/sarifs"]},codeSecurity:{attachConfiguration:["POST /orgs/{org}/code-security/configurations/{configuration_id}/attach"],attachEnterpriseConfiguration:["POST /enterprises/{enterprise}/code-security/configurations/{configuration_id}/attach"],createConfiguration:["POST /orgs/{org}/code-security/configurations"],createConfigurationForEnterprise:["POST /enterprises/{enterprise}/code-security/configurations"],deleteConfiguration:["DELETE /orgs/{org}/code-security/configurations/{configuration_id}"],deleteConfigurationForEnterprise:["DELETE /enterprises/{enterprise}/code-security/configurations/{configuration_id}"],detachConfiguration:["DELETE /orgs/{org}/code-security/configurations/detach"],getConfiguration:["GET /orgs/{org}/code-security/configurations/{configuration_id}"],getConfigurationForRepository:["GET /repos/{owner}/{repo}/code-security-configuration"],getConfigurationsForEnterprise:["GET /enterprises/{enterprise}/code-security/configurations"],getConfigurationsForOrg:["GET /orgs/{org}/code-security/configurations"],getDefaultConfigurations:["GET /orgs/{org}/code-security/configurations/defaults"],getDefaultConfigurationsForEnterprise:["GET /enterprises/{enterprise}/code-security/configurations/defaults"],getRepositoriesForConfiguration:["GET /orgs/{org}/code-security/configurations/{configuration_id}/repositories"],getRepositoriesForEnterpriseConfiguration:["GET /enterprises/{enterprise}/code-security/configurations/{configuration_id}/repositories"],getSingleConfigurationForEnterprise:["GET /enterprises/{enterprise}/code-security/configurations/{configuration_id}"],setConfigurationAsDefault:["PUT /orgs/{org}/code-security/configurations/{configuration_id}/defaults"],setConfigurationAsDefaultForEnterprise:["PUT /enterprises/{enterprise}/code-security/configurations/{configuration_id}/defaults"],updateConfiguration:["PATCH /orgs/{org}/code-security/configurations/{configuration_id}"],updateEnterpriseConfiguration:["PATCH /enterprises/{enterprise}/code-security/configurations/{configuration_id}"]},codesOfConduct:{getAllCodesOfConduct:["GET /codes_of_conduct"],getConductCode:["GET /codes_of_conduct/{key}"]},codespaces:{addRepositoryForSecretForAuthenticatedUser:["PUT /user/codespaces/secrets/{secret_name}/repositories/{repository_id}"],addSelectedRepoToOrgSecret:["PUT /orgs/{org}/codespaces/secrets/{secret_name}/repositories/{repository_id}"],checkPermissionsForDevcontainer:["GET /repos/{owner}/{repo}/codespaces/permissions_check"],codespaceMachinesForAuthenticatedUser:["GET /user/codespaces/{codespace_name}/machines"],createForAuthenticatedUser:["POST /user/codespaces"],createOrUpdateOrgSecret:["PUT /orgs/{org}/codespaces/secrets/{secret_name}"],createOrUpdateRepoSecret:["PUT /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"],createOrUpdateSecretForAuthenticatedUser:["PUT /user/codespaces/secrets/{secret_name}"],createWithPrForAuthenticatedUser:["POST /repos/{owner}/{repo}/pulls/{pull_number}/codespaces"],createWithRepoForAuthenticatedUser:["POST /repos/{owner}/{repo}/codespaces"],deleteForAuthenticatedUser:["DELETE /user/codespaces/{codespace_name}"],deleteFromOrganization:["DELETE /orgs/{org}/members/{username}/codespaces/{codespace_name}"],deleteOrgSecret:["DELETE /orgs/{org}/codespaces/secrets/{secret_name}"],deleteRepoSecret:["DELETE /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"],deleteSecretForAuthenticatedUser:["DELETE /user/codespaces/secrets/{secret_name}"],exportForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/exports"],getCodespacesForUserInOrg:["GET /orgs/{org}/members/{username}/codespaces"],getExportDetailsForAuthenticatedUser:["GET /user/codespaces/{codespace_name}/exports/{export_id}"],getForAuthenticatedUser:["GET /user/codespaces/{codespace_name}"],getOrgPublicKey:["GET /orgs/{org}/codespaces/secrets/public-key"],getOrgSecret:["GET /orgs/{org}/codespaces/secrets/{secret_name}"],getPublicKeyForAuthenticatedUser:["GET /user/codespaces/secrets/public-key"],getRepoPublicKey:["GET /repos/{owner}/{repo}/codespaces/secrets/public-key"],getRepoSecret:["GET /repos/{owner}/{repo}/codespaces/secrets/{secret_name}"],getSecretForAuthenticatedUser:["GET /user/codespaces/secrets/{secret_name}"],listDevcontainersInRepositoryForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces/devcontainers"],listForAuthenticatedUser:["GET /user/codespaces"],listInOrganization:["GET /orgs/{org}/codespaces",{},{renamedParameters:{org_id:"org"}}],listInRepositoryForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces"],listOrgSecrets:["GET /orgs/{org}/codespaces/secrets"],listRepoSecrets:["GET /repos/{owner}/{repo}/codespaces/secrets"],listRepositoriesForSecretForAuthenticatedUser:["GET /user/codespaces/secrets/{secret_name}/repositories"],listSecretsForAuthenticatedUser:["GET /user/codespaces/secrets"],listSelectedReposForOrgSecret:["GET /orgs/{org}/codespaces/secrets/{secret_name}/repositories"],preFlightWithRepoForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces/new"],publishForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/publish"],removeRepositoryForSecretForAuthenticatedUser:["DELETE /user/codespaces/secrets/{secret_name}/repositories/{repository_id}"],removeSelectedRepoFromOrgSecret:["DELETE /orgs/{org}/codespaces/secrets/{secret_name}/repositories/{repository_id}"],repoMachinesForAuthenticatedUser:["GET /repos/{owner}/{repo}/codespaces/machines"],setRepositoriesForSecretForAuthenticatedUser:["PUT /user/codespaces/secrets/{secret_name}/repositories"],setSelectedReposForOrgSecret:["PUT /orgs/{org}/codespaces/secrets/{secret_name}/repositories"],startForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/start"],stopForAuthenticatedUser:["POST /user/codespaces/{codespace_name}/stop"],stopInOrganization:["POST /orgs/{org}/members/{username}/codespaces/{codespace_name}/stop"],updateForAuthenticatedUser:["PATCH /user/codespaces/{codespace_name}"]},copilot:{addCopilotSeatsForTeams:["POST /orgs/{org}/copilot/billing/selected_teams"],addCopilotSeatsForUsers:["POST /orgs/{org}/copilot/billing/selected_users"],cancelCopilotSeatAssignmentForTeams:["DELETE /orgs/{org}/copilot/billing/selected_teams"],cancelCopilotSeatAssignmentForUsers:["DELETE /orgs/{org}/copilot/billing/selected_users"],copilotMetricsForOrganization:["GET /orgs/{org}/copilot/metrics"],copilotMetricsForTeam:["GET /orgs/{org}/team/{team_slug}/copilot/metrics"],getCopilotOrganizationDetails:["GET /orgs/{org}/copilot/billing"],getCopilotSeatDetailsForUser:["GET /orgs/{org}/members/{username}/copilot"],listCopilotSeats:["GET /orgs/{org}/copilot/billing/seats"]},credentials:{revoke:["POST /credentials/revoke"]},dependabot:{addSelectedRepoToOrgSecret:["PUT /orgs/{org}/dependabot/secrets/{secret_name}/repositories/{repository_id}"],createOrUpdateOrgSecret:["PUT /orgs/{org}/dependabot/secrets/{secret_name}"],createOrUpdateRepoSecret:["PUT /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"],deleteOrgSecret:["DELETE /orgs/{org}/dependabot/secrets/{secret_name}"],deleteRepoSecret:["DELETE /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"],getAlert:["GET /repos/{owner}/{repo}/dependabot/alerts/{alert_number}"],getOrgPublicKey:["GET /orgs/{org}/dependabot/secrets/public-key"],getOrgSecret:["GET /orgs/{org}/dependabot/secrets/{secret_name}"],getRepoPublicKey:["GET /repos/{owner}/{repo}/dependabot/secrets/public-key"],getRepoSecret:["GET /repos/{owner}/{repo}/dependabot/secrets/{secret_name}"],listAlertsForEnterprise:["GET /enterprises/{enterprise}/dependabot/alerts"],listAlertsForOrg:["GET /orgs/{org}/dependabot/alerts"],listAlertsForRepo:["GET /repos/{owner}/{repo}/dependabot/alerts"],listOrgSecrets:["GET /orgs/{org}/dependabot/secrets"],listRepoSecrets:["GET /repos/{owner}/{repo}/dependabot/secrets"],listSelectedReposForOrgSecret:["GET /orgs/{org}/dependabot/secrets/{secret_name}/repositories"],removeSelectedRepoFromOrgSecret:["DELETE /orgs/{org}/dependabot/secrets/{secret_name}/repositories/{repository_id}"],repositoryAccessForOrg:["GET /organizations/{org}/dependabot/repository-access"],setRepositoryAccessDefaultLevel:["PUT /organizations/{org}/dependabot/repository-access/default-level"],setSelectedReposForOrgSecret:["PUT /orgs/{org}/dependabot/secrets/{secret_name}/repositories"],updateAlert:["PATCH /repos/{owner}/{repo}/dependabot/alerts/{alert_number}"],updateRepositoryAccessForOrg:["PATCH /organizations/{org}/dependabot/repository-access"]},dependencyGraph:{createRepositorySnapshot:["POST /repos/{owner}/{repo}/dependency-graph/snapshots"],diffRange:["GET /repos/{owner}/{repo}/dependency-graph/compare/{basehead}"],exportSbom:["GET /repos/{owner}/{repo}/dependency-graph/sbom"]},emojis:{get:["GET /emojis"]},enterpriseTeamMemberships:{add:["PUT /enterprises/{enterprise}/teams/{enterprise-team}/memberships/{username}"],bulkAdd:["POST /enterprises/{enterprise}/teams/{enterprise-team}/memberships/add"],bulkRemove:["POST /enterprises/{enterprise}/teams/{enterprise-team}/memberships/remove"],get:["GET /enterprises/{enterprise}/teams/{enterprise-team}/memberships/{username}"],list:["GET /enterprises/{enterprise}/teams/{enterprise-team}/memberships"],remove:["DELETE /enterprises/{enterprise}/teams/{enterprise-team}/memberships/{username}"]},enterpriseTeamOrganizations:{add:["PUT /enterprises/{enterprise}/teams/{enterprise-team}/organizations/{org}"],bulkAdd:["POST /enterprises/{enterprise}/teams/{enterprise-team}/organizations/add"],bulkRemove:["POST /enterprises/{enterprise}/teams/{enterprise-team}/organizations/remove"],delete:["DELETE /enterprises/{enterprise}/teams/{enterprise-team}/organizations/{org}"],getAssignment:["GET /enterprises/{enterprise}/teams/{enterprise-team}/organizations/{org}"],getAssignments:["GET /enterprises/{enterprise}/teams/{enterprise-team}/organizations"]},enterpriseTeams:{create:["POST /enterprises/{enterprise}/teams"],delete:["DELETE /enterprises/{enterprise}/teams/{team_slug}"],get:["GET /enterprises/{enterprise}/teams/{team_slug}"],list:["GET /enterprises/{enterprise}/teams"],update:["PATCH /enterprises/{enterprise}/teams/{team_slug}"]},gists:{checkIsStarred:["GET /gists/{gist_id}/star"],create:["POST /gists"],createComment:["POST /gists/{gist_id}/comments"],delete:["DELETE /gists/{gist_id}"],deleteComment:["DELETE /gists/{gist_id}/comments/{comment_id}"],fork:["POST /gists/{gist_id}/forks"],get:["GET /gists/{gist_id}"],getComment:["GET /gists/{gist_id}/comments/{comment_id}"],getRevision:["GET /gists/{gist_id}/{sha}"],list:["GET /gists"],listComments:["GET /gists/{gist_id}/comments"],listCommits:["GET /gists/{gist_id}/commits"],listForUser:["GET /users/{username}/gists"],listForks:["GET /gists/{gist_id}/forks"],listPublic:["GET /gists/public"],listStarred:["GET /gists/starred"],star:["PUT /gists/{gist_id}/star"],unstar:["DELETE /gists/{gist_id}/star"],update:["PATCH /gists/{gist_id}"],updateComment:["PATCH /gists/{gist_id}/comments/{comment_id}"]},git:{createBlob:["POST /repos/{owner}/{repo}/git/blobs"],createCommit:["POST /repos/{owner}/{repo}/git/commits"],createRef:["POST /repos/{owner}/{repo}/git/refs"],createTag:["POST /repos/{owner}/{repo}/git/tags"],createTree:["POST /repos/{owner}/{repo}/git/trees"],deleteRef:["DELETE /repos/{owner}/{repo}/git/refs/{ref}"],getBlob:["GET /repos/{owner}/{repo}/git/blobs/{file_sha}"],getCommit:["GET /repos/{owner}/{repo}/git/commits/{commit_sha}"],getRef:["GET /repos/{owner}/{repo}/git/ref/{ref}"],getTag:["GET /repos/{owner}/{repo}/git/tags/{tag_sha}"],getTree:["GET /repos/{owner}/{repo}/git/trees/{tree_sha}"],listMatchingRefs:["GET /repos/{owner}/{repo}/git/matching-refs/{ref}"],updateRef:["PATCH /repos/{owner}/{repo}/git/refs/{ref}"]},gitignore:{getAllTemplates:["GET /gitignore/templates"],getTemplate:["GET /gitignore/templates/{name}"]},hostedCompute:{createNetworkConfigurationForOrg:["POST /orgs/{org}/settings/network-configurations"],deleteNetworkConfigurationFromOrg:["DELETE /orgs/{org}/settings/network-configurations/{network_configuration_id}"],getNetworkConfigurationForOrg:["GET /orgs/{org}/settings/network-configurations/{network_configuration_id}"],getNetworkSettingsForOrg:["GET /orgs/{org}/settings/network-settings/{network_settings_id}"],listNetworkConfigurationsForOrg:["GET /orgs/{org}/settings/network-configurations"],updateNetworkConfigurationForOrg:["PATCH /orgs/{org}/settings/network-configurations/{network_configuration_id}"]},interactions:{getRestrictionsForAuthenticatedUser:["GET /user/interaction-limits"],getRestrictionsForOrg:["GET /orgs/{org}/interaction-limits"],getRestrictionsForRepo:["GET /repos/{owner}/{repo}/interaction-limits"],getRestrictionsForYourPublicRepos:["GET /user/interaction-limits",{},{renamed:["interactions","getRestrictionsForAuthenticatedUser"]}],removeRestrictionsForAuthenticatedUser:["DELETE /user/interaction-limits"],removeRestrictionsForOrg:["DELETE /orgs/{org}/interaction-limits"],removeRestrictionsForRepo:["DELETE /repos/{owner}/{repo}/interaction-limits"],removeRestrictionsForYourPublicRepos:["DELETE /user/interaction-limits",{},{renamed:["interactions","removeRestrictionsForAuthenticatedUser"]}],setRestrictionsForAuthenticatedUser:["PUT /user/interaction-limits"],setRestrictionsForOrg:["PUT /orgs/{org}/interaction-limits"],setRestrictionsForRepo:["PUT /repos/{owner}/{repo}/interaction-limits"],setRestrictionsForYourPublicRepos:["PUT /user/interaction-limits",{},{renamed:["interactions","setRestrictionsForAuthenticatedUser"]}]},issues:{addAssignees:["POST /repos/{owner}/{repo}/issues/{issue_number}/assignees"],addBlockedByDependency:["POST /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by"],addLabels:["POST /repos/{owner}/{repo}/issues/{issue_number}/labels"],addSubIssue:["POST /repos/{owner}/{repo}/issues/{issue_number}/sub_issues"],checkUserCanBeAssigned:["GET /repos/{owner}/{repo}/assignees/{assignee}"],checkUserCanBeAssignedToIssue:["GET /repos/{owner}/{repo}/issues/{issue_number}/assignees/{assignee}"],create:["POST /repos/{owner}/{repo}/issues"],createComment:["POST /repos/{owner}/{repo}/issues/{issue_number}/comments"],createLabel:["POST /repos/{owner}/{repo}/labels"],createMilestone:["POST /repos/{owner}/{repo}/milestones"],deleteComment:["DELETE /repos/{owner}/{repo}/issues/comments/{comment_id}"],deleteLabel:["DELETE /repos/{owner}/{repo}/labels/{name}"],deleteMilestone:["DELETE /repos/{owner}/{repo}/milestones/{milestone_number}"],get:["GET /repos/{owner}/{repo}/issues/{issue_number}"],getComment:["GET /repos/{owner}/{repo}/issues/comments/{comment_id}"],getEvent:["GET /repos/{owner}/{repo}/issues/events/{event_id}"],getLabel:["GET /repos/{owner}/{repo}/labels/{name}"],getMilestone:["GET /repos/{owner}/{repo}/milestones/{milestone_number}"],getParent:["GET /repos/{owner}/{repo}/issues/{issue_number}/parent"],list:["GET /issues"],listAssignees:["GET /repos/{owner}/{repo}/assignees"],listComments:["GET /repos/{owner}/{repo}/issues/{issue_number}/comments"],listCommentsForRepo:["GET /repos/{owner}/{repo}/issues/comments"],listDependenciesBlockedBy:["GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by"],listDependenciesBlocking:["GET /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocking"],listEvents:["GET /repos/{owner}/{repo}/issues/{issue_number}/events"],listEventsForRepo:["GET /repos/{owner}/{repo}/issues/events"],listEventsForTimeline:["GET /repos/{owner}/{repo}/issues/{issue_number}/timeline"],listForAuthenticatedUser:["GET /user/issues"],listForOrg:["GET /orgs/{org}/issues"],listForRepo:["GET /repos/{owner}/{repo}/issues"],listLabelsForMilestone:["GET /repos/{owner}/{repo}/milestones/{milestone_number}/labels"],listLabelsForRepo:["GET /repos/{owner}/{repo}/labels"],listLabelsOnIssue:["GET /repos/{owner}/{repo}/issues/{issue_number}/labels"],listMilestones:["GET /repos/{owner}/{repo}/milestones"],listSubIssues:["GET /repos/{owner}/{repo}/issues/{issue_number}/sub_issues"],lock:["PUT /repos/{owner}/{repo}/issues/{issue_number}/lock"],removeAllLabels:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels"],removeAssignees:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/assignees"],removeDependencyBlockedBy:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/dependencies/blocked_by/{issue_id}"],removeLabel:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/labels/{name}"],removeSubIssue:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/sub_issue"],reprioritizeSubIssue:["PATCH /repos/{owner}/{repo}/issues/{issue_number}/sub_issues/priority"],setLabels:["PUT /repos/{owner}/{repo}/issues/{issue_number}/labels"],unlock:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/lock"],update:["PATCH /repos/{owner}/{repo}/issues/{issue_number}"],updateComment:["PATCH /repos/{owner}/{repo}/issues/comments/{comment_id}"],updateLabel:["PATCH /repos/{owner}/{repo}/labels/{name}"],updateMilestone:["PATCH /repos/{owner}/{repo}/milestones/{milestone_number}"]},licenses:{get:["GET /licenses/{license}"],getAllCommonlyUsed:["GET /licenses"],getForRepo:["GET /repos/{owner}/{repo}/license"]},markdown:{render:["POST /markdown"],renderRaw:["POST /markdown/raw",{headers:{"content-type":"text/plain; charset=utf-8"}}]},meta:{get:["GET /meta"],getAllVersions:["GET /versions"],getOctocat:["GET /octocat"],getZen:["GET /zen"],root:["GET /"]},migrations:{deleteArchiveForAuthenticatedUser:["DELETE /user/migrations/{migration_id}/archive"],deleteArchiveForOrg:["DELETE /orgs/{org}/migrations/{migration_id}/archive"],downloadArchiveForOrg:["GET /orgs/{org}/migrations/{migration_id}/archive"],getArchiveForAuthenticatedUser:["GET /user/migrations/{migration_id}/archive"],getStatusForAuthenticatedUser:["GET /user/migrations/{migration_id}"],getStatusForOrg:["GET /orgs/{org}/migrations/{migration_id}"],listForAuthenticatedUser:["GET /user/migrations"],listForOrg:["GET /orgs/{org}/migrations"],listReposForAuthenticatedUser:["GET /user/migrations/{migration_id}/repositories"],listReposForOrg:["GET /orgs/{org}/migrations/{migration_id}/repositories"],listReposForUser:["GET /user/migrations/{migration_id}/repositories",{},{renamed:["migrations","listReposForAuthenticatedUser"]}],startForAuthenticatedUser:["POST /user/migrations"],startForOrg:["POST /orgs/{org}/migrations"],unlockRepoForAuthenticatedUser:["DELETE /user/migrations/{migration_id}/repos/{repo_name}/lock"],unlockRepoForOrg:["DELETE /orgs/{org}/migrations/{migration_id}/repos/{repo_name}/lock"]},oidc:{getOidcCustomSubTemplateForOrg:["GET /orgs/{org}/actions/oidc/customization/sub"],updateOidcCustomSubTemplateForOrg:["PUT /orgs/{org}/actions/oidc/customization/sub"]},orgs:{addSecurityManagerTeam:["PUT /orgs/{org}/security-managers/teams/{team_slug}",{},{deprecated:"octokit.rest.orgs.addSecurityManagerTeam() is deprecated, see https://docs.github.com/rest/orgs/security-managers#add-a-security-manager-team"}],assignTeamToOrgRole:["PUT /orgs/{org}/organization-roles/teams/{team_slug}/{role_id}"],assignUserToOrgRole:["PUT /orgs/{org}/organization-roles/users/{username}/{role_id}"],blockUser:["PUT /orgs/{org}/blocks/{username}"],cancelInvitation:["DELETE /orgs/{org}/invitations/{invitation_id}"],checkBlockedUser:["GET /orgs/{org}/blocks/{username}"],checkMembershipForUser:["GET /orgs/{org}/members/{username}"],checkPublicMembershipForUser:["GET /orgs/{org}/public_members/{username}"],convertMemberToOutsideCollaborator:["PUT /orgs/{org}/outside_collaborators/{username}"],createArtifactStorageRecord:["POST /orgs/{org}/artifacts/metadata/storage-record"],createInvitation:["POST /orgs/{org}/invitations"],createIssueType:["POST /orgs/{org}/issue-types"],createWebhook:["POST /orgs/{org}/hooks"],customPropertiesForOrgsCreateOrUpdateOrganizationValues:["PATCH /organizations/{org}/org-properties/values"],customPropertiesForOrgsGetOrganizationValues:["GET /organizations/{org}/org-properties/values"],customPropertiesForReposCreateOrUpdateOrganizationDefinition:["PUT /orgs/{org}/properties/schema/{custom_property_name}"],customPropertiesForReposCreateOrUpdateOrganizationDefinitions:["PATCH /orgs/{org}/properties/schema"],customPropertiesForReposCreateOrUpdateOrganizationValues:["PATCH /orgs/{org}/properties/values"],customPropertiesForReposDeleteOrganizationDefinition:["DELETE /orgs/{org}/properties/schema/{custom_property_name}"],customPropertiesForReposGetOrganizationDefinition:["GET /orgs/{org}/properties/schema/{custom_property_name}"],customPropertiesForReposGetOrganizationDefinitions:["GET /orgs/{org}/properties/schema"],customPropertiesForReposGetOrganizationValues:["GET /orgs/{org}/properties/values"],delete:["DELETE /orgs/{org}"],deleteAttestationsBulk:["POST /orgs/{org}/attestations/delete-request"],deleteAttestationsById:["DELETE /orgs/{org}/attestations/{attestation_id}"],deleteAttestationsBySubjectDigest:["DELETE /orgs/{org}/attestations/digest/{subject_digest}"],deleteIssueType:["DELETE /orgs/{org}/issue-types/{issue_type_id}"],deleteWebhook:["DELETE /orgs/{org}/hooks/{hook_id}"],disableSelectedRepositoryImmutableReleasesOrganization:["DELETE /orgs/{org}/settings/immutable-releases/repositories/{repository_id}"],enableSelectedRepositoryImmutableReleasesOrganization:["PUT /orgs/{org}/settings/immutable-releases/repositories/{repository_id}"],get:["GET /orgs/{org}"],getImmutableReleasesSettings:["GET /orgs/{org}/settings/immutable-releases"],getImmutableReleasesSettingsRepositories:["GET /orgs/{org}/settings/immutable-releases/repositories"],getMembershipForAuthenticatedUser:["GET /user/memberships/orgs/{org}"],getMembershipForUser:["GET /orgs/{org}/memberships/{username}"],getOrgRole:["GET /orgs/{org}/organization-roles/{role_id}"],getOrgRulesetHistory:["GET /orgs/{org}/rulesets/{ruleset_id}/history"],getOrgRulesetVersion:["GET /orgs/{org}/rulesets/{ruleset_id}/history/{version_id}"],getWebhook:["GET /orgs/{org}/hooks/{hook_id}"],getWebhookConfigForOrg:["GET /orgs/{org}/hooks/{hook_id}/config"],getWebhookDelivery:["GET /orgs/{org}/hooks/{hook_id}/deliveries/{delivery_id}"],list:["GET /organizations"],listAppInstallations:["GET /orgs/{org}/installations"],listArtifactStorageRecords:["GET /orgs/{org}/artifacts/{subject_digest}/metadata/storage-records"],listAttestationRepositories:["GET /orgs/{org}/attestations/repositories"],listAttestations:["GET /orgs/{org}/attestations/{subject_digest}"],listAttestationsBulk:["POST /orgs/{org}/attestations/bulk-list{?per_page,before,after}"],listBlockedUsers:["GET /orgs/{org}/blocks"],listFailedInvitations:["GET /orgs/{org}/failed_invitations"],listForAuthenticatedUser:["GET /user/orgs"],listForUser:["GET /users/{username}/orgs"],listInvitationTeams:["GET /orgs/{org}/invitations/{invitation_id}/teams"],listIssueTypes:["GET /orgs/{org}/issue-types"],listMembers:["GET /orgs/{org}/members"],listMembershipsForAuthenticatedUser:["GET /user/memberships/orgs"],listOrgRoleTeams:["GET /orgs/{org}/organization-roles/{role_id}/teams"],listOrgRoleUsers:["GET /orgs/{org}/organization-roles/{role_id}/users"],listOrgRoles:["GET /orgs/{org}/organization-roles"],listOrganizationFineGrainedPermissions:["GET /orgs/{org}/organization-fine-grained-permissions"],listOutsideCollaborators:["GET /orgs/{org}/outside_collaborators"],listPatGrantRepositories:["GET /orgs/{org}/personal-access-tokens/{pat_id}/repositories"],listPatGrantRequestRepositories:["GET /orgs/{org}/personal-access-token-requests/{pat_request_id}/repositories"],listPatGrantRequests:["GET /orgs/{org}/personal-access-token-requests"],listPatGrants:["GET /orgs/{org}/personal-access-tokens"],listPendingInvitations:["GET /orgs/{org}/invitations"],listPublicMembers:["GET /orgs/{org}/public_members"],listSecurityManagerTeams:["GET /orgs/{org}/security-managers",{},{deprecated:"octokit.rest.orgs.listSecurityManagerTeams() is deprecated, see https://docs.github.com/rest/orgs/security-managers#list-security-manager-teams"}],listWebhookDeliveries:["GET /orgs/{org}/hooks/{hook_id}/deliveries"],listWebhooks:["GET /orgs/{org}/hooks"],pingWebhook:["POST /orgs/{org}/hooks/{hook_id}/pings"],redeliverWebhookDelivery:["POST /orgs/{org}/hooks/{hook_id}/deliveries/{delivery_id}/attempts"],removeMember:["DELETE /orgs/{org}/members/{username}"],removeMembershipForUser:["DELETE /orgs/{org}/memberships/{username}"],removeOutsideCollaborator:["DELETE /orgs/{org}/outside_collaborators/{username}"],removePublicMembershipForAuthenticatedUser:["DELETE /orgs/{org}/public_members/{username}"],removeSecurityManagerTeam:["DELETE /orgs/{org}/security-managers/teams/{team_slug}",{},{deprecated:"octokit.rest.orgs.removeSecurityManagerTeam() is deprecated, see https://docs.github.com/rest/orgs/security-managers#remove-a-security-manager-team"}],reviewPatGrantRequest:["POST /orgs/{org}/personal-access-token-requests/{pat_request_id}"],reviewPatGrantRequestsInBulk:["POST /orgs/{org}/personal-access-token-requests"],revokeAllOrgRolesTeam:["DELETE /orgs/{org}/organization-roles/teams/{team_slug}"],revokeAllOrgRolesUser:["DELETE /orgs/{org}/organization-roles/users/{username}"],revokeOrgRoleTeam:["DELETE /orgs/{org}/organization-roles/teams/{team_slug}/{role_id}"],revokeOrgRoleUser:["DELETE /orgs/{org}/organization-roles/users/{username}/{role_id}"],setImmutableReleasesSettings:["PUT /orgs/{org}/settings/immutable-releases"],setImmutableReleasesSettingsRepositories:["PUT /orgs/{org}/settings/immutable-releases/repositories"],setMembershipForUser:["PUT /orgs/{org}/memberships/{username}"],setPublicMembershipForAuthenticatedUser:["PUT /orgs/{org}/public_members/{username}"],unblockUser:["DELETE /orgs/{org}/blocks/{username}"],update:["PATCH /orgs/{org}"],updateIssueType:["PUT /orgs/{org}/issue-types/{issue_type_id}"],updateMembershipForAuthenticatedUser:["PATCH /user/memberships/orgs/{org}"],updatePatAccess:["POST /orgs/{org}/personal-access-tokens/{pat_id}"],updatePatAccesses:["POST /orgs/{org}/personal-access-tokens"],updateWebhook:["PATCH /orgs/{org}/hooks/{hook_id}"],updateWebhookConfigForOrg:["PATCH /orgs/{org}/hooks/{hook_id}/config"]},packages:{deletePackageForAuthenticatedUser:["DELETE /user/packages/{package_type}/{package_name}"],deletePackageForOrg:["DELETE /orgs/{org}/packages/{package_type}/{package_name}"],deletePackageForUser:["DELETE /users/{username}/packages/{package_type}/{package_name}"],deletePackageVersionForAuthenticatedUser:["DELETE /user/packages/{package_type}/{package_name}/versions/{package_version_id}"],deletePackageVersionForOrg:["DELETE /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}"],deletePackageVersionForUser:["DELETE /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}"],getAllPackageVersionsForAPackageOwnedByAnOrg:["GET /orgs/{org}/packages/{package_type}/{package_name}/versions",{},{renamed:["packages","getAllPackageVersionsForPackageOwnedByOrg"]}],getAllPackageVersionsForAPackageOwnedByTheAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}/versions",{},{renamed:["packages","getAllPackageVersionsForPackageOwnedByAuthenticatedUser"]}],getAllPackageVersionsForPackageOwnedByAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}/versions"],getAllPackageVersionsForPackageOwnedByOrg:["GET /orgs/{org}/packages/{package_type}/{package_name}/versions"],getAllPackageVersionsForPackageOwnedByUser:["GET /users/{username}/packages/{package_type}/{package_name}/versions"],getPackageForAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}"],getPackageForOrganization:["GET /orgs/{org}/packages/{package_type}/{package_name}"],getPackageForUser:["GET /users/{username}/packages/{package_type}/{package_name}"],getPackageVersionForAuthenticatedUser:["GET /user/packages/{package_type}/{package_name}/versions/{package_version_id}"],getPackageVersionForOrganization:["GET /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}"],getPackageVersionForUser:["GET /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}"],listDockerMigrationConflictingPackagesForAuthenticatedUser:["GET /user/docker/conflicts"],listDockerMigrationConflictingPackagesForOrganization:["GET /orgs/{org}/docker/conflicts"],listDockerMigrationConflictingPackagesForUser:["GET /users/{username}/docker/conflicts"],listPackagesForAuthenticatedUser:["GET /user/packages"],listPackagesForOrganization:["GET /orgs/{org}/packages"],listPackagesForUser:["GET /users/{username}/packages"],restorePackageForAuthenticatedUser:["POST /user/packages/{package_type}/{package_name}/restore{?token}"],restorePackageForOrg:["POST /orgs/{org}/packages/{package_type}/{package_name}/restore{?token}"],restorePackageForUser:["POST /users/{username}/packages/{package_type}/{package_name}/restore{?token}"],restorePackageVersionForAuthenticatedUser:["POST /user/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"],restorePackageVersionForOrg:["POST /orgs/{org}/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"],restorePackageVersionForUser:["POST /users/{username}/packages/{package_type}/{package_name}/versions/{package_version_id}/restore"]},privateRegistries:{createOrgPrivateRegistry:["POST /orgs/{org}/private-registries"],deleteOrgPrivateRegistry:["DELETE /orgs/{org}/private-registries/{secret_name}"],getOrgPrivateRegistry:["GET /orgs/{org}/private-registries/{secret_name}"],getOrgPublicKey:["GET /orgs/{org}/private-registries/public-key"],listOrgPrivateRegistries:["GET /orgs/{org}/private-registries"],updateOrgPrivateRegistry:["PATCH /orgs/{org}/private-registries/{secret_name}"]},projects:{addItemForOrg:["POST /orgs/{org}/projectsV2/{project_number}/items"],addItemForUser:["POST /users/{username}/projectsV2/{project_number}/items"],deleteItemForOrg:["DELETE /orgs/{org}/projectsV2/{project_number}/items/{item_id}"],deleteItemForUser:["DELETE /users/{username}/projectsV2/{project_number}/items/{item_id}"],getFieldForOrg:["GET /orgs/{org}/projectsV2/{project_number}/fields/{field_id}"],getFieldForUser:["GET /users/{username}/projectsV2/{project_number}/fields/{field_id}"],getForOrg:["GET /orgs/{org}/projectsV2/{project_number}"],getForUser:["GET /users/{username}/projectsV2/{project_number}"],getOrgItem:["GET /orgs/{org}/projectsV2/{project_number}/items/{item_id}"],getUserItem:["GET /users/{username}/projectsV2/{project_number}/items/{item_id}"],listFieldsForOrg:["GET /orgs/{org}/projectsV2/{project_number}/fields"],listFieldsForUser:["GET /users/{username}/projectsV2/{project_number}/fields"],listForOrg:["GET /orgs/{org}/projectsV2"],listForUser:["GET /users/{username}/projectsV2"],listItemsForOrg:["GET /orgs/{org}/projectsV2/{project_number}/items"],listItemsForUser:["GET /users/{username}/projectsV2/{project_number}/items"],updateItemForOrg:["PATCH /orgs/{org}/projectsV2/{project_number}/items/{item_id}"],updateItemForUser:["PATCH /users/{username}/projectsV2/{project_number}/items/{item_id}"]},pulls:{checkIfMerged:["GET /repos/{owner}/{repo}/pulls/{pull_number}/merge"],create:["POST /repos/{owner}/{repo}/pulls"],createReplyForReviewComment:["POST /repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies"],createReview:["POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews"],createReviewComment:["POST /repos/{owner}/{repo}/pulls/{pull_number}/comments"],deletePendingReview:["DELETE /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"],deleteReviewComment:["DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}"],dismissReview:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/dismissals"],get:["GET /repos/{owner}/{repo}/pulls/{pull_number}"],getReview:["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"],getReviewComment:["GET /repos/{owner}/{repo}/pulls/comments/{comment_id}"],list:["GET /repos/{owner}/{repo}/pulls"],listCommentsForReview:["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/comments"],listCommits:["GET /repos/{owner}/{repo}/pulls/{pull_number}/commits"],listFiles:["GET /repos/{owner}/{repo}/pulls/{pull_number}/files"],listRequestedReviewers:["GET /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"],listReviewComments:["GET /repos/{owner}/{repo}/pulls/{pull_number}/comments"],listReviewCommentsForRepo:["GET /repos/{owner}/{repo}/pulls/comments"],listReviews:["GET /repos/{owner}/{repo}/pulls/{pull_number}/reviews"],merge:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/merge"],removeRequestedReviewers:["DELETE /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"],requestReviewers:["POST /repos/{owner}/{repo}/pulls/{pull_number}/requested_reviewers"],submitReview:["POST /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}/events"],update:["PATCH /repos/{owner}/{repo}/pulls/{pull_number}"],updateBranch:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/update-branch"],updateReview:["PUT /repos/{owner}/{repo}/pulls/{pull_number}/reviews/{review_id}"],updateReviewComment:["PATCH /repos/{owner}/{repo}/pulls/comments/{comment_id}"]},rateLimit:{get:["GET /rate_limit"]},reactions:{createForCommitComment:["POST /repos/{owner}/{repo}/comments/{comment_id}/reactions"],createForIssue:["POST /repos/{owner}/{repo}/issues/{issue_number}/reactions"],createForIssueComment:["POST /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions"],createForPullRequestReviewComment:["POST /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions"],createForRelease:["POST /repos/{owner}/{repo}/releases/{release_id}/reactions"],createForTeamDiscussionCommentInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions"],createForTeamDiscussionInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions"],deleteForCommitComment:["DELETE /repos/{owner}/{repo}/comments/{comment_id}/reactions/{reaction_id}"],deleteForIssue:["DELETE /repos/{owner}/{repo}/issues/{issue_number}/reactions/{reaction_id}"],deleteForIssueComment:["DELETE /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions/{reaction_id}"],deleteForPullRequestComment:["DELETE /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions/{reaction_id}"],deleteForRelease:["DELETE /repos/{owner}/{repo}/releases/{release_id}/reactions/{reaction_id}"],deleteForTeamDiscussion:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions/{reaction_id}"],deleteForTeamDiscussionComment:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions/{reaction_id}"],listForCommitComment:["GET /repos/{owner}/{repo}/comments/{comment_id}/reactions"],listForIssue:["GET /repos/{owner}/{repo}/issues/{issue_number}/reactions"],listForIssueComment:["GET /repos/{owner}/{repo}/issues/comments/{comment_id}/reactions"],listForPullRequestReviewComment:["GET /repos/{owner}/{repo}/pulls/comments/{comment_id}/reactions"],listForRelease:["GET /repos/{owner}/{repo}/releases/{release_id}/reactions"],listForTeamDiscussionCommentInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}/reactions"],listForTeamDiscussionInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/reactions"]},repos:{acceptInvitation:["PATCH /user/repository_invitations/{invitation_id}",{},{renamed:["repos","acceptInvitationForAuthenticatedUser"]}],acceptInvitationForAuthenticatedUser:["PATCH /user/repository_invitations/{invitation_id}"],addAppAccessRestrictions:["POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps",{},{mapToData:"apps"}],addCollaborator:["PUT /repos/{owner}/{repo}/collaborators/{username}"],addStatusCheckContexts:["POST /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts",{},{mapToData:"contexts"}],addTeamAccessRestrictions:["POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams",{},{mapToData:"teams"}],addUserAccessRestrictions:["POST /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users",{},{mapToData:"users"}],cancelPagesDeployment:["POST /repos/{owner}/{repo}/pages/deployments/{pages_deployment_id}/cancel"],checkAutomatedSecurityFixes:["GET /repos/{owner}/{repo}/automated-security-fixes"],checkCollaborator:["GET /repos/{owner}/{repo}/collaborators/{username}"],checkImmutableReleases:["GET /repos/{owner}/{repo}/immutable-releases"],checkPrivateVulnerabilityReporting:["GET /repos/{owner}/{repo}/private-vulnerability-reporting"],checkVulnerabilityAlerts:["GET /repos/{owner}/{repo}/vulnerability-alerts"],codeownersErrors:["GET /repos/{owner}/{repo}/codeowners/errors"],compareCommits:["GET /repos/{owner}/{repo}/compare/{base}...{head}"],compareCommitsWithBasehead:["GET /repos/{owner}/{repo}/compare/{basehead}"],createAttestation:["POST /repos/{owner}/{repo}/attestations"],createAutolink:["POST /repos/{owner}/{repo}/autolinks"],createCommitComment:["POST /repos/{owner}/{repo}/commits/{commit_sha}/comments"],createCommitSignatureProtection:["POST /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"],createCommitStatus:["POST /repos/{owner}/{repo}/statuses/{sha}"],createDeployKey:["POST /repos/{owner}/{repo}/keys"],createDeployment:["POST /repos/{owner}/{repo}/deployments"],createDeploymentBranchPolicy:["POST /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies"],createDeploymentProtectionRule:["POST /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules"],createDeploymentStatus:["POST /repos/{owner}/{repo}/deployments/{deployment_id}/statuses"],createDispatchEvent:["POST /repos/{owner}/{repo}/dispatches"],createForAuthenticatedUser:["POST /user/repos"],createFork:["POST /repos/{owner}/{repo}/forks"],createInOrg:["POST /orgs/{org}/repos"],createOrUpdateEnvironment:["PUT /repos/{owner}/{repo}/environments/{environment_name}"],createOrUpdateFileContents:["PUT /repos/{owner}/{repo}/contents/{path}"],createOrgRuleset:["POST /orgs/{org}/rulesets"],createPagesDeployment:["POST /repos/{owner}/{repo}/pages/deployments"],createPagesSite:["POST /repos/{owner}/{repo}/pages"],createRelease:["POST /repos/{owner}/{repo}/releases"],createRepoRuleset:["POST /repos/{owner}/{repo}/rulesets"],createUsingTemplate:["POST /repos/{template_owner}/{template_repo}/generate"],createWebhook:["POST /repos/{owner}/{repo}/hooks"],customPropertiesForReposCreateOrUpdateRepositoryValues:["PATCH /repos/{owner}/{repo}/properties/values"],customPropertiesForReposGetRepositoryValues:["GET /repos/{owner}/{repo}/properties/values"],declineInvitation:["DELETE /user/repository_invitations/{invitation_id}",{},{renamed:["repos","declineInvitationForAuthenticatedUser"]}],declineInvitationForAuthenticatedUser:["DELETE /user/repository_invitations/{invitation_id}"],delete:["DELETE /repos/{owner}/{repo}"],deleteAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions"],deleteAdminBranchProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"],deleteAnEnvironment:["DELETE /repos/{owner}/{repo}/environments/{environment_name}"],deleteAutolink:["DELETE /repos/{owner}/{repo}/autolinks/{autolink_id}"],deleteBranchProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection"],deleteCommitComment:["DELETE /repos/{owner}/{repo}/comments/{comment_id}"],deleteCommitSignatureProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"],deleteDeployKey:["DELETE /repos/{owner}/{repo}/keys/{key_id}"],deleteDeployment:["DELETE /repos/{owner}/{repo}/deployments/{deployment_id}"],deleteDeploymentBranchPolicy:["DELETE /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"],deleteFile:["DELETE /repos/{owner}/{repo}/contents/{path}"],deleteInvitation:["DELETE /repos/{owner}/{repo}/invitations/{invitation_id}"],deleteOrgRuleset:["DELETE /orgs/{org}/rulesets/{ruleset_id}"],deletePagesSite:["DELETE /repos/{owner}/{repo}/pages"],deletePullRequestReviewProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"],deleteRelease:["DELETE /repos/{owner}/{repo}/releases/{release_id}"],deleteReleaseAsset:["DELETE /repos/{owner}/{repo}/releases/assets/{asset_id}"],deleteRepoRuleset:["DELETE /repos/{owner}/{repo}/rulesets/{ruleset_id}"],deleteWebhook:["DELETE /repos/{owner}/{repo}/hooks/{hook_id}"],disableAutomatedSecurityFixes:["DELETE /repos/{owner}/{repo}/automated-security-fixes"],disableDeploymentProtectionRule:["DELETE /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/{protection_rule_id}"],disableImmutableReleases:["DELETE /repos/{owner}/{repo}/immutable-releases"],disablePrivateVulnerabilityReporting:["DELETE /repos/{owner}/{repo}/private-vulnerability-reporting"],disableVulnerabilityAlerts:["DELETE /repos/{owner}/{repo}/vulnerability-alerts"],downloadArchive:["GET /repos/{owner}/{repo}/zipball/{ref}",{},{renamed:["repos","downloadZipballArchive"]}],downloadTarballArchive:["GET /repos/{owner}/{repo}/tarball/{ref}"],downloadZipballArchive:["GET /repos/{owner}/{repo}/zipball/{ref}"],enableAutomatedSecurityFixes:["PUT /repos/{owner}/{repo}/automated-security-fixes"],enableImmutableReleases:["PUT /repos/{owner}/{repo}/immutable-releases"],enablePrivateVulnerabilityReporting:["PUT /repos/{owner}/{repo}/private-vulnerability-reporting"],enableVulnerabilityAlerts:["PUT /repos/{owner}/{repo}/vulnerability-alerts"],generateReleaseNotes:["POST /repos/{owner}/{repo}/releases/generate-notes"],get:["GET /repos/{owner}/{repo}"],getAccessRestrictions:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions"],getAdminBranchProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"],getAllDeploymentProtectionRules:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules"],getAllEnvironments:["GET /repos/{owner}/{repo}/environments"],getAllStatusCheckContexts:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts"],getAllTopics:["GET /repos/{owner}/{repo}/topics"],getAppsWithAccessToProtectedBranch:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps"],getAutolink:["GET /repos/{owner}/{repo}/autolinks/{autolink_id}"],getBranch:["GET /repos/{owner}/{repo}/branches/{branch}"],getBranchProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection"],getBranchRules:["GET /repos/{owner}/{repo}/rules/branches/{branch}"],getClones:["GET /repos/{owner}/{repo}/traffic/clones"],getCodeFrequencyStats:["GET /repos/{owner}/{repo}/stats/code_frequency"],getCollaboratorPermissionLevel:["GET /repos/{owner}/{repo}/collaborators/{username}/permission"],getCombinedStatusForRef:["GET /repos/{owner}/{repo}/commits/{ref}/status"],getCommit:["GET /repos/{owner}/{repo}/commits/{ref}"],getCommitActivityStats:["GET /repos/{owner}/{repo}/stats/commit_activity"],getCommitComment:["GET /repos/{owner}/{repo}/comments/{comment_id}"],getCommitSignatureProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_signatures"],getCommunityProfileMetrics:["GET /repos/{owner}/{repo}/community/profile"],getContent:["GET /repos/{owner}/{repo}/contents/{path}"],getContributorsStats:["GET /repos/{owner}/{repo}/stats/contributors"],getCustomDeploymentProtectionRule:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/{protection_rule_id}"],getDeployKey:["GET /repos/{owner}/{repo}/keys/{key_id}"],getDeployment:["GET /repos/{owner}/{repo}/deployments/{deployment_id}"],getDeploymentBranchPolicy:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"],getDeploymentStatus:["GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses/{status_id}"],getEnvironment:["GET /repos/{owner}/{repo}/environments/{environment_name}"],getLatestPagesBuild:["GET /repos/{owner}/{repo}/pages/builds/latest"],getLatestRelease:["GET /repos/{owner}/{repo}/releases/latest"],getOrgRuleSuite:["GET /orgs/{org}/rulesets/rule-suites/{rule_suite_id}"],getOrgRuleSuites:["GET /orgs/{org}/rulesets/rule-suites"],getOrgRuleset:["GET /orgs/{org}/rulesets/{ruleset_id}"],getOrgRulesets:["GET /orgs/{org}/rulesets"],getPages:["GET /repos/{owner}/{repo}/pages"],getPagesBuild:["GET /repos/{owner}/{repo}/pages/builds/{build_id}"],getPagesDeployment:["GET /repos/{owner}/{repo}/pages/deployments/{pages_deployment_id}"],getPagesHealthCheck:["GET /repos/{owner}/{repo}/pages/health"],getParticipationStats:["GET /repos/{owner}/{repo}/stats/participation"],getPullRequestReviewProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"],getPunchCardStats:["GET /repos/{owner}/{repo}/stats/punch_card"],getReadme:["GET /repos/{owner}/{repo}/readme"],getReadmeInDirectory:["GET /repos/{owner}/{repo}/readme/{dir}"],getRelease:["GET /repos/{owner}/{repo}/releases/{release_id}"],getReleaseAsset:["GET /repos/{owner}/{repo}/releases/assets/{asset_id}"],getReleaseByTag:["GET /repos/{owner}/{repo}/releases/tags/{tag}"],getRepoRuleSuite:["GET /repos/{owner}/{repo}/rulesets/rule-suites/{rule_suite_id}"],getRepoRuleSuites:["GET /repos/{owner}/{repo}/rulesets/rule-suites"],getRepoRuleset:["GET /repos/{owner}/{repo}/rulesets/{ruleset_id}"],getRepoRulesetHistory:["GET /repos/{owner}/{repo}/rulesets/{ruleset_id}/history"],getRepoRulesetVersion:["GET /repos/{owner}/{repo}/rulesets/{ruleset_id}/history/{version_id}"],getRepoRulesets:["GET /repos/{owner}/{repo}/rulesets"],getStatusChecksProtection:["GET /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"],getTeamsWithAccessToProtectedBranch:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams"],getTopPaths:["GET /repos/{owner}/{repo}/traffic/popular/paths"],getTopReferrers:["GET /repos/{owner}/{repo}/traffic/popular/referrers"],getUsersWithAccessToProtectedBranch:["GET /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users"],getViews:["GET /repos/{owner}/{repo}/traffic/views"],getWebhook:["GET /repos/{owner}/{repo}/hooks/{hook_id}"],getWebhookConfigForRepo:["GET /repos/{owner}/{repo}/hooks/{hook_id}/config"],getWebhookDelivery:["GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries/{delivery_id}"],listActivities:["GET /repos/{owner}/{repo}/activity"],listAttestations:["GET /repos/{owner}/{repo}/attestations/{subject_digest}"],listAutolinks:["GET /repos/{owner}/{repo}/autolinks"],listBranches:["GET /repos/{owner}/{repo}/branches"],listBranchesForHeadCommit:["GET /repos/{owner}/{repo}/commits/{commit_sha}/branches-where-head"],listCollaborators:["GET /repos/{owner}/{repo}/collaborators"],listCommentsForCommit:["GET /repos/{owner}/{repo}/commits/{commit_sha}/comments"],listCommitCommentsForRepo:["GET /repos/{owner}/{repo}/comments"],listCommitStatusesForRef:["GET /repos/{owner}/{repo}/commits/{ref}/statuses"],listCommits:["GET /repos/{owner}/{repo}/commits"],listContributors:["GET /repos/{owner}/{repo}/contributors"],listCustomDeploymentRuleIntegrations:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment_protection_rules/apps"],listDeployKeys:["GET /repos/{owner}/{repo}/keys"],listDeploymentBranchPolicies:["GET /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies"],listDeploymentStatuses:["GET /repos/{owner}/{repo}/deployments/{deployment_id}/statuses"],listDeployments:["GET /repos/{owner}/{repo}/deployments"],listForAuthenticatedUser:["GET /user/repos"],listForOrg:["GET /orgs/{org}/repos"],listForUser:["GET /users/{username}/repos"],listForks:["GET /repos/{owner}/{repo}/forks"],listInvitations:["GET /repos/{owner}/{repo}/invitations"],listInvitationsForAuthenticatedUser:["GET /user/repository_invitations"],listLanguages:["GET /repos/{owner}/{repo}/languages"],listPagesBuilds:["GET /repos/{owner}/{repo}/pages/builds"],listPublic:["GET /repositories"],listPullRequestsAssociatedWithCommit:["GET /repos/{owner}/{repo}/commits/{commit_sha}/pulls"],listReleaseAssets:["GET /repos/{owner}/{repo}/releases/{release_id}/assets"],listReleases:["GET /repos/{owner}/{repo}/releases"],listTags:["GET /repos/{owner}/{repo}/tags"],listTeams:["GET /repos/{owner}/{repo}/teams"],listWebhookDeliveries:["GET /repos/{owner}/{repo}/hooks/{hook_id}/deliveries"],listWebhooks:["GET /repos/{owner}/{repo}/hooks"],merge:["POST /repos/{owner}/{repo}/merges"],mergeUpstream:["POST /repos/{owner}/{repo}/merge-upstream"],pingWebhook:["POST /repos/{owner}/{repo}/hooks/{hook_id}/pings"],redeliverWebhookDelivery:["POST /repos/{owner}/{repo}/hooks/{hook_id}/deliveries/{delivery_id}/attempts"],removeAppAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps",{},{mapToData:"apps"}],removeCollaborator:["DELETE /repos/{owner}/{repo}/collaborators/{username}"],removeStatusCheckContexts:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts",{},{mapToData:"contexts"}],removeStatusCheckProtection:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"],removeTeamAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams",{},{mapToData:"teams"}],removeUserAccessRestrictions:["DELETE /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users",{},{mapToData:"users"}],renameBranch:["POST /repos/{owner}/{repo}/branches/{branch}/rename"],replaceAllTopics:["PUT /repos/{owner}/{repo}/topics"],requestPagesBuild:["POST /repos/{owner}/{repo}/pages/builds"],setAdminBranchProtection:["POST /repos/{owner}/{repo}/branches/{branch}/protection/enforce_admins"],setAppAccessRestrictions:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/apps",{},{mapToData:"apps"}],setStatusCheckContexts:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks/contexts",{},{mapToData:"contexts"}],setTeamAccessRestrictions:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/teams",{},{mapToData:"teams"}],setUserAccessRestrictions:["PUT /repos/{owner}/{repo}/branches/{branch}/protection/restrictions/users",{},{mapToData:"users"}],testPushWebhook:["POST /repos/{owner}/{repo}/hooks/{hook_id}/tests"],transfer:["POST /repos/{owner}/{repo}/transfer"],update:["PATCH /repos/{owner}/{repo}"],updateBranchProtection:["PUT /repos/{owner}/{repo}/branches/{branch}/protection"],updateCommitComment:["PATCH /repos/{owner}/{repo}/comments/{comment_id}"],updateDeploymentBranchPolicy:["PUT /repos/{owner}/{repo}/environments/{environment_name}/deployment-branch-policies/{branch_policy_id}"],updateInformationAboutPagesSite:["PUT /repos/{owner}/{repo}/pages"],updateInvitation:["PATCH /repos/{owner}/{repo}/invitations/{invitation_id}"],updateOrgRuleset:["PUT /orgs/{org}/rulesets/{ruleset_id}"],updatePullRequestReviewProtection:["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_pull_request_reviews"],updateRelease:["PATCH /repos/{owner}/{repo}/releases/{release_id}"],updateReleaseAsset:["PATCH /repos/{owner}/{repo}/releases/assets/{asset_id}"],updateRepoRuleset:["PUT /repos/{owner}/{repo}/rulesets/{ruleset_id}"],updateStatusCheckPotection:["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks",{},{renamed:["repos","updateStatusCheckProtection"]}],updateStatusCheckProtection:["PATCH /repos/{owner}/{repo}/branches/{branch}/protection/required_status_checks"],updateWebhook:["PATCH /repos/{owner}/{repo}/hooks/{hook_id}"],updateWebhookConfigForRepo:["PATCH /repos/{owner}/{repo}/hooks/{hook_id}/config"],uploadReleaseAsset:["POST /repos/{owner}/{repo}/releases/{release_id}/assets{?name,label}",{baseUrl:"https://uploads.github.com"}]},search:{code:["GET /search/code"],commits:["GET /search/commits"],issuesAndPullRequests:["GET /search/issues"],labels:["GET /search/labels"],repos:["GET /search/repositories"],topics:["GET /search/topics"],users:["GET /search/users"]},secretScanning:{createPushProtectionBypass:["POST /repos/{owner}/{repo}/secret-scanning/push-protection-bypasses"],getAlert:["GET /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}"],getScanHistory:["GET /repos/{owner}/{repo}/secret-scanning/scan-history"],listAlertsForOrg:["GET /orgs/{org}/secret-scanning/alerts"],listAlertsForRepo:["GET /repos/{owner}/{repo}/secret-scanning/alerts"],listLocationsForAlert:["GET /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}/locations"],listOrgPatternConfigs:["GET /orgs/{org}/secret-scanning/pattern-configurations"],updateAlert:["PATCH /repos/{owner}/{repo}/secret-scanning/alerts/{alert_number}"],updateOrgPatternConfigs:["PATCH /orgs/{org}/secret-scanning/pattern-configurations"]},securityAdvisories:{createFork:["POST /repos/{owner}/{repo}/security-advisories/{ghsa_id}/forks"],createPrivateVulnerabilityReport:["POST /repos/{owner}/{repo}/security-advisories/reports"],createRepositoryAdvisory:["POST /repos/{owner}/{repo}/security-advisories"],createRepositoryAdvisoryCveRequest:["POST /repos/{owner}/{repo}/security-advisories/{ghsa_id}/cve"],getGlobalAdvisory:["GET /advisories/{ghsa_id}"],getRepositoryAdvisory:["GET /repos/{owner}/{repo}/security-advisories/{ghsa_id}"],listGlobalAdvisories:["GET /advisories"],listOrgRepositoryAdvisories:["GET /orgs/{org}/security-advisories"],listRepositoryAdvisories:["GET /repos/{owner}/{repo}/security-advisories"],updateRepositoryAdvisory:["PATCH /repos/{owner}/{repo}/security-advisories/{ghsa_id}"]},teams:{addOrUpdateMembershipForUserInOrg:["PUT /orgs/{org}/teams/{team_slug}/memberships/{username}"],addOrUpdateRepoPermissionsInOrg:["PUT /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"],checkPermissionsForRepoInOrg:["GET /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"],create:["POST /orgs/{org}/teams"],createDiscussionCommentInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments"],createDiscussionInOrg:["POST /orgs/{org}/teams/{team_slug}/discussions"],deleteDiscussionCommentInOrg:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"],deleteDiscussionInOrg:["DELETE /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"],deleteInOrg:["DELETE /orgs/{org}/teams/{team_slug}"],getByName:["GET /orgs/{org}/teams/{team_slug}"],getDiscussionCommentInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"],getDiscussionInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"],getMembershipForUserInOrg:["GET /orgs/{org}/teams/{team_slug}/memberships/{username}"],list:["GET /orgs/{org}/teams"],listChildInOrg:["GET /orgs/{org}/teams/{team_slug}/teams"],listDiscussionCommentsInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments"],listDiscussionsInOrg:["GET /orgs/{org}/teams/{team_slug}/discussions"],listForAuthenticatedUser:["GET /user/teams"],listMembersInOrg:["GET /orgs/{org}/teams/{team_slug}/members"],listPendingInvitationsInOrg:["GET /orgs/{org}/teams/{team_slug}/invitations"],listReposInOrg:["GET /orgs/{org}/teams/{team_slug}/repos"],removeMembershipForUserInOrg:["DELETE /orgs/{org}/teams/{team_slug}/memberships/{username}"],removeRepoInOrg:["DELETE /orgs/{org}/teams/{team_slug}/repos/{owner}/{repo}"],updateDiscussionCommentInOrg:["PATCH /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}/comments/{comment_number}"],updateDiscussionInOrg:["PATCH /orgs/{org}/teams/{team_slug}/discussions/{discussion_number}"],updateInOrg:["PATCH /orgs/{org}/teams/{team_slug}"]},users:{addEmailForAuthenticated:["POST /user/emails",{},{renamed:["users","addEmailForAuthenticatedUser"]}],addEmailForAuthenticatedUser:["POST /user/emails"],addSocialAccountForAuthenticatedUser:["POST /user/social_accounts"],block:["PUT /user/blocks/{username}"],checkBlocked:["GET /user/blocks/{username}"],checkFollowingForUser:["GET /users/{username}/following/{target_user}"],checkPersonIsFollowedByAuthenticated:["GET /user/following/{username}"],createGpgKeyForAuthenticated:["POST /user/gpg_keys",{},{renamed:["users","createGpgKeyForAuthenticatedUser"]}],createGpgKeyForAuthenticatedUser:["POST /user/gpg_keys"],createPublicSshKeyForAuthenticated:["POST /user/keys",{},{renamed:["users","createPublicSshKeyForAuthenticatedUser"]}],createPublicSshKeyForAuthenticatedUser:["POST /user/keys"],createSshSigningKeyForAuthenticatedUser:["POST /user/ssh_signing_keys"],deleteAttestationsBulk:["POST /users/{username}/attestations/delete-request"],deleteAttestationsById:["DELETE /users/{username}/attestations/{attestation_id}"],deleteAttestationsBySubjectDigest:["DELETE /users/{username}/attestations/digest/{subject_digest}"],deleteEmailForAuthenticated:["DELETE /user/emails",{},{renamed:["users","deleteEmailForAuthenticatedUser"]}],deleteEmailForAuthenticatedUser:["DELETE /user/emails"],deleteGpgKeyForAuthenticated:["DELETE /user/gpg_keys/{gpg_key_id}",{},{renamed:["users","deleteGpgKeyForAuthenticatedUser"]}],deleteGpgKeyForAuthenticatedUser:["DELETE /user/gpg_keys/{gpg_key_id}"],deletePublicSshKeyForAuthenticated:["DELETE /user/keys/{key_id}",{},{renamed:["users","deletePublicSshKeyForAuthenticatedUser"]}],deletePublicSshKeyForAuthenticatedUser:["DELETE /user/keys/{key_id}"],deleteSocialAccountForAuthenticatedUser:["DELETE /user/social_accounts"],deleteSshSigningKeyForAuthenticatedUser:["DELETE /user/ssh_signing_keys/{ssh_signing_key_id}"],follow:["PUT /user/following/{username}"],getAuthenticated:["GET /user"],getById:["GET /user/{account_id}"],getByUsername:["GET /users/{username}"],getContextForUser:["GET /users/{username}/hovercard"],getGpgKeyForAuthenticated:["GET /user/gpg_keys/{gpg_key_id}",{},{renamed:["users","getGpgKeyForAuthenticatedUser"]}],getGpgKeyForAuthenticatedUser:["GET /user/gpg_keys/{gpg_key_id}"],getPublicSshKeyForAuthenticated:["GET /user/keys/{key_id}",{},{renamed:["users","getPublicSshKeyForAuthenticatedUser"]}],getPublicSshKeyForAuthenticatedUser:["GET /user/keys/{key_id}"],getSshSigningKeyForAuthenticatedUser:["GET /user/ssh_signing_keys/{ssh_signing_key_id}"],list:["GET /users"],listAttestations:["GET /users/{username}/attestations/{subject_digest}"],listAttestationsBulk:["POST /users/{username}/attestations/bulk-list{?per_page,before,after}"],listBlockedByAuthenticated:["GET /user/blocks",{},{renamed:["users","listBlockedByAuthenticatedUser"]}],listBlockedByAuthenticatedUser:["GET /user/blocks"],listEmailsForAuthenticated:["GET /user/emails",{},{renamed:["users","listEmailsForAuthenticatedUser"]}],listEmailsForAuthenticatedUser:["GET /user/emails"],listFollowedByAuthenticated:["GET /user/following",{},{renamed:["users","listFollowedByAuthenticatedUser"]}],listFollowedByAuthenticatedUser:["GET /user/following"],listFollowersForAuthenticatedUser:["GET /user/followers"],listFollowersForUser:["GET /users/{username}/followers"],listFollowingForUser:["GET /users/{username}/following"],listGpgKeysForAuthenticated:["GET /user/gpg_keys",{},{renamed:["users","listGpgKeysForAuthenticatedUser"]}],listGpgKeysForAuthenticatedUser:["GET /user/gpg_keys"],listGpgKeysForUser:["GET /users/{username}/gpg_keys"],listPublicEmailsForAuthenticated:["GET /user/public_emails",{},{renamed:["users","listPublicEmailsForAuthenticatedUser"]}],listPublicEmailsForAuthenticatedUser:["GET /user/public_emails"],listPublicKeysForUser:["GET /users/{username}/keys"],listPublicSshKeysForAuthenticated:["GET /user/keys",{},{renamed:["users","listPublicSshKeysForAuthenticatedUser"]}],listPublicSshKeysForAuthenticatedUser:["GET /user/keys"],listSocialAccountsForAuthenticatedUser:["GET /user/social_accounts"],listSocialAccountsForUser:["GET /users/{username}/social_accounts"],listSshSigningKeysForAuthenticatedUser:["GET /user/ssh_signing_keys"],listSshSigningKeysForUser:["GET /users/{username}/ssh_signing_keys"],setPrimaryEmailVisibilityForAuthenticated:["PATCH /user/email/visibility",{},{renamed:["users","setPrimaryEmailVisibilityForAuthenticatedUser"]}],setPrimaryEmailVisibilityForAuthenticatedUser:["PATCH /user/email/visibility"],unblock:["DELETE /user/blocks/{username}"],unfollow:["DELETE /user/following/{username}"],updateAuthenticated:["PATCH /user"]}};var Ut=Pt;const Vt=new Map;for(const[e,t]of Object.entries(Ut)){for(const[i,n]of Object.entries(t)){const[t,r,s]=n;const[o,a]=t.split(/ /);const l=Object.assign({method:o,url:a},r);if(!Vt.has(e)){Vt.set(e,new Map)}Vt.get(e).set(i,{scope:e,methodName:i,endpointDefaults:l,decorations:s})}}const Ft={has({scope:e},t){return Vt.get(e).has(t)},getOwnPropertyDescriptor(e,t){return{value:this.get(e,t),configurable:true,writable:true,enumerable:true}},defineProperty(e,t,i){Object.defineProperty(e.cache,t,i);return true},deleteProperty(e,t){delete e.cache[t];return true},ownKeys({scope:e}){return[...Vt.get(e).keys()]},set(e,t,i){return e.cache[t]=i},get({octokit:e,scope:t,cache:i},n){if(i[n]){return i[n]}const r=Vt.get(t).get(n);if(!r){return void 0}const{endpointDefaults:s,decorations:o}=r;if(o){i[n]=decorate(e,t,n,s,o)}else{i[n]=e.request.defaults(s)}return i[n]}};function endpointsToMethods(e){const t={};for(const i of Vt.keys()){t[i]=new Proxy({octokit:e,scope:i,cache:{}},Ft)}return t}function decorate(e,t,i,n,r){const s=e.request.defaults(n);function withDecorations(...n){let o=s.endpoint.merge(...n);if(r.mapToData){o=Object.assign({},o,{data:o[r.mapToData],[r.mapToData]:void 0});return s(o)}if(r.renamed){const[n,s]=r.renamed;e.log.warn(`octokit.${t}.${i}() has been renamed to octokit.${n}.${s}()`)}if(r.deprecated){e.log.warn(r.deprecated)}if(r.renamedParameters){const o=s.endpoint.merge(...n);for(const[n,s]of Object.entries(r.renamedParameters)){if(n in o){e.log.warn(`"${n}" parameter is deprecated for "octokit.${t}.${i}()". Use "${s}" instead`);if(!(s in o)){o[s]=o[n]}delete o[n]}}return s(o)}return s(...n)}return Object.assign(withDecorations,s)}function restEndpointMethods(e){const t=endpointsToMethods(e);return{rest:t}}restEndpointMethods.VERSION=kt;function legacyRestEndpointMethods(e){const t=endpointsToMethods(e);return{...t,rest:t}}legacyRestEndpointMethods.VERSION=kt;const Ot="22.0.1";const Nt=Octokit.plugin(requestLog,legacyRestEndpointMethods,paginateRest).defaults({userAgent:`octokit-rest.js/${Ot}`});class OctokitWrapper{_octokitGitDiffParser;_octokit=null;constructor(e){this._octokitGitDiffParser=e}get octokit(){if(this._octokit===null){throw new Error("OctokitWrapper was not initialized. Call OctokitWrapper.initialize() before calling other methods.")}return this._octokit}initialize(e){if(this._octokit!==null){throw new Error("OctokitWrapper was already initialized prior to calling OctokitWrapper.initialize().")}this._octokit=new Nt(e)}async getPull(e,t,i){return this.octokit.rest.pulls.get({owner:e,pull_number:i,repo:t})}async updatePull(e,t,i,n,r){const s={owner:e,pull_number:i,repo:t};if(n!==null){s.title=n}if(r!==null){s.body=r}return this.octokit.rest.pulls.update(s)}async getIssueComments(e,t,i){return this.octokit.rest.issues.listComments({issue_number:i,owner:e,repo:t})}async getReviewComments(e,t,i){return this.octokit.rest.pulls.listReviewComments({owner:e,pull_number:i,repo:t})}async createIssueComment(e,t,i,n){return this.octokit.rest.issues.createComment({body:n,issue_number:i,owner:e,repo:t})}async listCommits(e,t,i,n){return this.octokit.rest.pulls.listCommits({owner:e,page:n,pull_number:i,repo:t})}async createReviewComment(e,t,i,n,r,s){const o=await this._octokitGitDiffParser.getFirstChangedLine(this,e,t,i,r);if(o===null){return null}return this.octokit.rest.pulls.createReviewComment({body:n,commit_id:s,line:o,owner:e,path:r,pull_number:i,repo:t})}async updateIssueComment(e,t,i,n,r){return this.octokit.rest.issues.updateComment({body:r,comment_id:n,issue_number:i,owner:e,repo:t})}async deleteReviewComment(e,t,i){return this.octokit.rest.pulls.deleteReviewComment({comment_id:i,owner:e,repo:t})}}class PullRequest{_codeMetrics;_logger;_runnerInvoker;constructor(e,t,i){this._codeMetrics=e;this._logger=t;this._runnerInvoker=i}get isPullRequest(){this._logger.logDebug("* PullRequest.isPullRequest");return RunnerInvoker.isGitHub?typeof process.env.GITHUB_BASE_REF!=="undefined"&&process.env.GITHUB_BASE_REF!=="":typeof process.env.SYSTEM_PULLREQUEST_PULLREQUESTID!=="undefined"}get isSupportedProvider(){this._logger.logDebug("* PullRequest.isSupportedProvider");if(RunnerInvoker.isGitHub){return true}const e=validateVariable("BUILD_REPOSITORY_PROVIDER","PullRequest.isSupportedProvider");if(e==="TfsGit"||e==="GitHub"||e==="GitHubEnterprise"){return true}return e}getUpdatedDescription(e){this._logger.logDebug("* PullRequest.getUpdatedDescription()");if(e!==null&&e.trim()!==""){return null}return this._runnerInvoker.loc("pullRequests.pullRequest.addDescription")}async getUpdatedTitle(e){this._logger.logDebug("* PullRequest.getUpdatedTitle()");const t=await this._codeMetrics.getSizeIndicator();if(e.startsWith(this._runnerInvoker.loc("pullRequests.pullRequest.titleFormat",t,""))){return null}const i=`(${this._runnerInvoker.loc("metrics.codeMetrics.titleSizeXS")}`+`|${this._runnerInvoker.loc("metrics.codeMetrics.titleSizeS")}`+`|${this._runnerInvoker.loc("metrics.codeMetrics.titleSizeM")}`+`|${this._runnerInvoker.loc("metrics.codeMetrics.titleSizeL")}`+`|\\d*${this._runnerInvoker.loc("metrics.codeMetrics.titleSizeXL")})`;const n=`(${this._runnerInvoker.loc("metrics.codeMetrics.titleTestsSufficient")}`+`|${this._runnerInvoker.loc("metrics.codeMetrics.titleTestsInsufficient")})?`;const r=this._runnerInvoker.loc("metrics.codeMetrics.titleSizeIndicatorFormat",i,n);const s=`^${this._runnerInvoker.loc("pullRequests.pullRequest.titleFormat",r,"(?.*)")}$`;const o=new RegExp(s,"u");const a=e.match(o);const l=a?.groups?.originalTitle??e;return this._runnerInvoker.loc("pullRequests.pullRequest.titleFormat",t,l)}}class PullRequestCommentsData{metricsCommentThreadId=null;metricsCommentContent=null;metricsCommentThreadStatus=null;filesNotRequiringReview;deletedFilesNotRequiringReview;commentThreadsRequiringDeletion=[];constructor(e,t){this.filesNotRequiringReview=e;this.deletedFilesNotRequiringReview=t}}class PullRequestComments{_codeMetrics;_inputs;_logger;_reposInvoker;_runnerInvoker;constructor(e,t,i,n,r){this._codeMetrics=e;this._inputs=t;this._logger=i;this._reposInvoker=n;this._runnerInvoker=r}get noReviewRequiredComment(){this._logger.logDebug("* PullRequestComments.noReviewRequiredComment");return this._runnerInvoker.loc("pullRequests.pullRequestComments.noReviewRequiredComment")}async getCommentData(){this._logger.logDebug("* PullRequestComments.getCommentData()");const e=await this._codeMetrics.getFilesNotRequiringReview();const t=await this._codeMetrics.getDeletedFilesNotRequiringReview();let i=new PullRequestCommentsData(e,t);const n=await this._reposInvoker.getComments();for(const e of n.pullRequestComments){i=this.getMetricsCommentData(i,e)}for(const e of n.fileComments){i=this.getFilesRequiringCommentUpdates(i,e)}return i}async getMetricsComment(){this._logger.logDebug("* PullRequestComments.getMetricsComment()");const e=await this._codeMetrics.getMetrics();let t=`${this._runnerInvoker.loc("pullRequests.pullRequestComments.commentTitle")}\n`;t+=await this.addCommentSizeStatus();t+=await this.addCommentTestStatus();t+=`||${this._runnerInvoker.loc("pullRequests.pullRequestComments.tableLines")}\n`;t+="-|-:\n";t+=this.addCommentMetrics(this._runnerInvoker.loc("pullRequests.pullRequestComments.tableProductCode"),e.productCode,false);t+=this.addCommentMetrics(this._runnerInvoker.loc("pullRequests.pullRequestComments.tableTestCode"),e.testCode,false);t+=this.addCommentMetrics(this._runnerInvoker.loc("pullRequests.pullRequestComments.tableSubtotal"),e.subtotal,true);t+=this.addCommentMetrics(this._runnerInvoker.loc("pullRequests.pullRequestComments.tableIgnoredCode"),e.ignoredCode,false);t+=this.addCommentMetrics(this._runnerInvoker.loc("pullRequests.pullRequestComments.tableTotal"),e.total,true);t+="\n";t+=this._runnerInvoker.loc("pullRequests.pullRequestComments.commentFooter");return t}async getMetricsCommentStatus(){this._logger.logDebug("* PullRequestComments.getMetricsCommentStatus()");if(this._inputs.alwaysCloseComment){return a.CommentThreadStatus.Closed}if(await this._codeMetrics.isSmall()){const e=await this._codeMetrics.isSufficientlyTested();if(e??true){return a.CommentThreadStatus.Closed}}return a.CommentThreadStatus.Active}getMetricsCommentData(e,t){this._logger.logDebug("* PullRequestComments.getMetricsCommentData()");if(!t.content.startsWith(`${this._runnerInvoker.loc("pullRequests.pullRequestComments.commentTitle")}\n`)){return e}e.metricsCommentThreadId=t.id;e.metricsCommentContent=t.content;e.metricsCommentThreadStatus=t.status;return e}getFilesRequiringCommentUpdates(e,t){this._logger.logDebug("* PullRequestComments.getFilesRequiringCommentUpdates()");if(t.content!==this.noReviewRequiredComment){return e}const i=-1;const n=e.filesNotRequiringReview.indexOf(t.fileName);if(n!==i){e.filesNotRequiringReview.splice(n,1);return e}const r=e.deletedFilesNotRequiringReview.indexOf(t.fileName);if(r!==i){e.deletedFilesNotRequiringReview.splice(r,1);return e}e.commentThreadsRequiringDeletion.push(t.id);return e}async addCommentSizeStatus(){this._logger.logDebug("* PullRequestComments.addCommentSizeStatus()");let e="";if(await this._codeMetrics.isSmall()){e+=this._runnerInvoker.loc("pullRequests.pullRequestComments.smallPullRequestComment")}else{const t=(this._inputs.baseSize*this._inputs.growthRate).toLocaleString();e+=this._runnerInvoker.loc("pullRequests.pullRequestComments.largePullRequestComment",t)}e+="\n";return e}async addCommentTestStatus(){this._logger.logDebug("* PullRequestComments.addCommentTestStatus()");let e="";const t=await this._codeMetrics.isSufficientlyTested();if(t!==null){if(t){e+=this._runnerInvoker.loc("pullRequests.pullRequestComments.testsSufficientComment")}else{e+=this._runnerInvoker.loc("pullRequests.pullRequestComments.testsInsufficientComment")}e+="\n"}return e}addCommentMetrics(e,t,i){this._logger.logDebug("* PullRequestComments.addCommentMetrics()");const n=i?"**":"";let r=t.toLocaleString();if(r==="0"){r="-"}return`${n}${e}${n}|${n}${r}${n}\n`}}class PullRequestMetrics{_codeMetricsCalculator;_logger;_runnerInvoker;constructor(e,t,i){this._codeMetricsCalculator=e;this._logger=t;this._runnerInvoker=i}async run(e){try{this._runnerInvoker.locInitialize(e);const t=this._codeMetricsCalculator.shouldSkip;if(t!==null){this._runnerInvoker.setStatusSkipped(t);return}const i=await this._codeMetricsCalculator.shouldStop();if(i!==null){this._runnerInvoker.setStatusFailed(i);return}await Promise.all([this._codeMetricsCalculator.updateDetails(),this._codeMetricsCalculator.updateComments()]);this._runnerInvoker.setStatusSucceeded(this._runnerInvoker.loc("pullRequestMetrics.succeeded"))}catch(e){if(e instanceof Error){this._logger.logErrorObject(e);this._logger.replay();this._runnerInvoker.setStatusFailed(e.message)}else{this._logger.replay();this._runnerInvoker.setStatusFailed(String(e))}}}}class ReposInvoker{_azureReposInvoker;_gitHubReposInvoker;_logger;_reposInvoker=null;constructor(e,t,i){this._azureReposInvoker=e;this._gitHubReposInvoker=t;this._logger=i}get reposInvoker(){this._logger.logDebug("* ReposInvoker.getReposInvoker()");if(this._reposInvoker!==null){return this._reposInvoker}if(RunnerInvoker.isGitHub){this._reposInvoker=this._gitHubReposInvoker;return this._reposInvoker}const e=validateVariable("BUILD_REPOSITORY_PROVIDER","ReposInvoker.getReposInvoker()");switch(e){case"TfsGit":this._reposInvoker=this._azureReposInvoker;break;case"GitHub":case"GitHubEnterprise":this._reposInvoker=this._gitHubReposInvoker;break;default:throw new RangeError(`BUILD_REPOSITORY_PROVIDER '${e}' is unsupported.`)}return this._reposInvoker}async isAccessTokenAvailable(){this._logger.logDebug("* ReposInvoker.isAccessTokenAvailable()");return this.reposInvoker.isAccessTokenAvailable()}async getTitleAndDescription(){this._logger.logDebug("* ReposInvoker.getTitleAndDescription()");return this.reposInvoker.getTitleAndDescription()}async getComments(){this._logger.logDebug("* ReposInvoker.getComments()");return this.reposInvoker.getComments()}async setTitleAndDescription(e,t){this._logger.logDebug("* ReposInvoker.setTitleAndDescription()");return this.reposInvoker.setTitleAndDescription(e,t)}async createComment(e,t,i,n){this._logger.logDebug("* ReposInvoker.createComment()");return this.reposInvoker.createComment(e,t,i,n)}async updateComment(e,t,i){this._logger.logDebug("* ReposInvoker.updateComment()");return this.reposInvoker.updateComment(e,t,i)}async deleteCommentThread(e){this._logger.logDebug("* ReposInvoker.deleteCommentThread()");return this.reposInvoker.deleteCommentThread(e)}}class TokenManager{_azureDevOpsApiWrapper;_logger;_runnerInvoker;_previouslyInvoked=false;constructor(e,t,i){this._azureDevOpsApiWrapper=e;this._logger=t;this._runnerInvoker=i}async getToken(){this._logger.logDebug("* TokenManager.getToken()");if(this._previouslyInvoked){return null}const e=this._runnerInvoker.getInput(["Workload","Identity","Federation"]);if(e===null){this._logger.logDebug("No workload identity federation specified. Using Personal Access Token (PAT) for authentication.");return null}this._logger.logDebug(`Using workload identity federation '${e}' for authentication.`);const t=this._runnerInvoker.getEndpointAuthorizationScheme(e);if(t!=="WorkloadIdentityFederation"){return this._runnerInvoker.loc("repos.tokenManager.incorrectAuthorizationScheme",e,String(t))}process.env.PR_METRICS_ACCESS_TOKEN=await this.getAccessToken(e);this._previouslyInvoked=true;return null}async getAccessToken(e){this._logger.logDebug("* TokenManager.getAccessToken()");const t=validateString(this._runnerInvoker.getEndpointAuthorizationParameter(e,"serviceprincipalid"),"servicePrincipalId","TokenManager.getAccessToken()");const i=validateString(this._runnerInvoker.getEndpointAuthorizationParameter(e,"tenantid"),"tenantId","TokenManager.getAccessToken()");validateGuid(t,"servicePrincipalId","TokenManager.getAccessToken()");validateGuid(i,"tenantId","TokenManager.getAccessToken()");const n=await this.getFederatedToken(e);this._runnerInvoker.setSecret(n);const r=await this._runnerInvoker.exec("az",["login","--service-principal","-u",t,"--tenant",i,"--allow-no-subscriptions","--federated-token",n]);if(r.exitCode!==0){throw new Error(r.stderr)}const s=await this._runnerInvoker.exec("az",["account","get-access-token","--query","accessToken","--resource","499b84ac-1321-427f-aa17-267ca6975798","-o","tsv"]);if(s.exitCode!==0){throw new Error(s.stderr)}const o=s.stdout.trim();this._runnerInvoker.setSecret(o);return o}async getFederatedToken(e){this._logger.logDebug("* TokenManager.getFederatedToken()");const t=this.getSystemAccessToken();const i=this._azureDevOpsApiWrapper.getHandlerFromToken(t);const n=validateVariable("SYSTEM_COLLECTIONURI","TokenManager.getFederatedToken()");const r=this._azureDevOpsApiWrapper.getWebApiInstance(n,i);const s=await r.getTaskApi();const o=validateVariable("SYSTEM_TEAMPROJECTID","TokenManager.getFederatedToken()");const a=validateVariable("SYSTEM_HOSTTYPE","TokenManager.getFederatedToken()");const l=validateVariable("SYSTEM_PLANID","TokenManager.getFederatedToken()");const u=validateVariable("SYSTEM_JOBID","TokenManager.getFederatedToken()");const c=await s.createOidcToken({},o,a,l,u,e);return validateString(c.oidcToken,"response.oidcToken","TokenManager.getFederatedToken()")}getSystemAccessToken(){this._logger.logDebug("* TokenManager.getSystemAccessToken()");const e=this._runnerInvoker.getEndpointAuthorization("SYSTEMVSSCONNECTION");const t=e?.scheme;if(t!=="OAuth"){throw new Error(`Could not acquire authorization token from workload identity federation as the scheme was '${t??""}'.`)}this._logger.logDebug("Acquired authorization token from workload identity federation.");return validateString(e?.parameters.AccessToken,"endpointAuthorization.parameters.AccessToken","TokenManager.getSystemAccessToken()")}}const createPullRequestMetrics=()=>{const e=new HttpWrapper;const t=new AzureDevOpsApiWrapper;const i=new AzurePipelinesRunnerWrapper;const n=new ConsoleWrapper;const r=new GitHubRunnerWrapper;const s=new AzurePipelinesRunnerInvoker(i);const o=new GitHubRunnerInvoker(i,n,r);const a=new RunnerInvoker(s,o);const l=new Logger(n,a);const u=new GitInvoker(l,a);const c=new OctokitGitDiffParser(e,l);const d=new Inputs(l,a);const p=new CodeMetrics(u,d,l,a);const A=new OctokitWrapper(c);const f=new TokenManager(t,l,a);const h=new AzureReposInvoker(t,u,l,a,f);const g=new GitHubReposInvoker(u,l,A,a);const y=new ReposInvoker(h,g,l);const m=new PullRequest(p,l,a);const I=new PullRequestComments(p,d,l,y,a);const v=new CodeMetricsCalculator(u,l,m,I,y,a);return new PullRequestMetrics(v,l,a)};const qt=createPullRequestMetrics;const run=async()=>{const e=qt();await e.run(import.meta.dirname)};run().catch((()=>{process.exit(c)})); \ No newline at end of file From 9078ed94752baa62ce5814a03aab3a8b5a373ea2 Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Mon, 20 Apr 2026 17:45:09 +0100 Subject: [PATCH 21/27] Clone getIssueCommentsResponse before mutating in tests The `getIssueCommentsResponse` constant is a shared object. Assigning `body` on `response.data[0]` previously mutated the constant itself, leaving hidden coupling between tests in the same file (and any future suite that touches the constant). `structuredClone` makes each test work on an independent copy so mutations never escape. --- .../repos/gitHubReposInvoker.getComments.spec.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts b/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts index aef1de1b8..7477031a3 100644 --- a/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.getComments.spec.ts @@ -47,8 +47,9 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const response: GetIssueCommentsResponse = - GitHubReposInvokerConstants.getIssueCommentsResponse; + const response: GetIssueCommentsResponse = structuredClone( + GitHubReposInvokerConstants.getIssueCommentsResponse, + ); if (typeof response.data[0] === "undefined") { throw new Error("response.data[0] is undefined"); } @@ -140,8 +141,9 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const response: GetIssueCommentsResponse = - GitHubReposInvokerConstants.getIssueCommentsResponse; + const response: GetIssueCommentsResponse = structuredClone( + GitHubReposInvokerConstants.getIssueCommentsResponse, + ); if (typeof response.data[0] === "undefined") { throw new Error("response.data[0] is undefined"); } @@ -198,8 +200,9 @@ describe("gitHubReposInvoker.ts", (): void => { assert.notEqual(options.log?.error, null); }, ); - const response: GetIssueCommentsResponse = - GitHubReposInvokerConstants.getIssueCommentsResponse; + const response: GetIssueCommentsResponse = structuredClone( + GitHubReposInvokerConstants.getIssueCommentsResponse, + ); if (typeof response.data[0] === "undefined") { throw new Error("response.data[0] is undefined"); } From cd4ef52d7d669ada13c6035e124417f3aa59b24d Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Wed, 22 Apr 2026 13:12:08 +0100 Subject: [PATCH 22/27] refactor(tests): improve localization handling in tests - Update test files to use localized strings for logging assertions. - Refactor localization stubbing to utilize a new localize function. - Enhance clarity and maintainability of test code by reducing duplication. --- .github/workflow-scripts/Update-Version.ps1 | 3 +- .../metrics/codeMetrics.edgeCases.spec.ts | 9 +- .../tests/metrics/inputs.allInputs.spec.ts | 30 +++- .../tests/metrics/inputs.baseSize.spec.ts | 8 +- .../metrics/inputs.codeFileExtensions.spec.ts | 36 ++++- .../inputs.fileMatchingPatterns.spec.ts | 30 +++- .../tests/metrics/inputs.growthRate.spec.ts | 8 +- .../tests/metrics/inputs.testFactor.spec.ts | 8 +- .../inputs.testMatchingPatterns.spec.ts | 30 +++- src/task/tests/metrics/inputsTestSetup.ts | 145 ++++++------------ src/task/tests/pullRequestMetrics.spec.ts | 5 +- .../tests/testUtilities/stubLocalization.ts | 37 +++-- 12 files changed, 205 insertions(+), 144 deletions(-) diff --git a/.github/workflow-scripts/Update-Version.ps1 b/.github/workflow-scripts/Update-Version.ps1 index 51fe31343..0cf3b8f03 100644 --- a/.github/workflow-scripts/Update-Version.ps1 +++ b/.github/workflow-scripts/Update-Version.ps1 @@ -63,8 +63,7 @@ Update-FileContent -Path 'src/task/task.json' -Replacements (@($friendlyNameRepl Update-FileContent -Path 'src/task/task.loc.json' -Replacements $versionComponentReplacements Update-FileContent -Path 'src/task/Strings/resources.resjson/en-US/resources.resjson' -Replacements @($friendlyNameReplacement) -# Source code user-agent. The test setup re-exports this constant, so only the -# production file needs updating. +# Source code user-agent. Update-FileContent -Path 'src/task/src/repos/gitHubReposInvoker.ts' -Replacements @($userAgentReplacement) # Release workflow defaults. The env vars in release-initiate.yml represent the diff --git a/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts b/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts index 671c6ab4e..f2d20fec7 100644 --- a/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts +++ b/src/task/tests/metrics/codeMetrics.edgeCases.spec.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. */ -import { anyString, when } from "ts-mockito"; import { createCodeMetricsMocks, createSut } from "./codeMetricsTestSetup.js"; import type CodeMetrics from "../../src/metrics/codeMetrics.js"; import CodeMetricsData from "../../src/metrics/codeMetricsData.js"; @@ -12,6 +11,7 @@ import type Inputs from "../../src/metrics/inputs.js"; import type Logger from "../../src/utilities/logger.js"; import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; +import { when } from "ts-mockito"; describe("codeMetrics.ts", (): void => { let gitInvoker: GitInvoker; @@ -57,13 +57,6 @@ describe("codeMetrics.ts", (): void => { when(inputs.fileMatchingPatterns).thenReturn(["**/*"]); when(inputs.codeFileExtensions).thenReturn(new Set(["ts"])); when(gitInvoker.getDiffSummary()).thenResolve("3\t0\tfile.ts"); - when( - runnerInvoker.loc( - "metrics.codeMetrics.titleSizeIndicatorFormat", - anyString() as string, - anyString() as string, - ), - ).thenReturn(""); const codeMetrics: CodeMetrics = createSut( gitInvoker, inputs, diff --git a/src/task/tests/metrics/inputs.allInputs.spec.ts b/src/task/tests/metrics/inputs.allInputs.spec.ts index ab01dbe58..356e7f4ac 100644 --- a/src/task/tests/metrics/inputs.allInputs.spec.ts +++ b/src/task/tests/metrics/inputs.allInputs.spec.ts @@ -110,12 +110,30 @@ describe("inputs.ts", (): void => { new Set(["js", "ts"]), ); verify(logger.logInfo(settingAlwaysCloseComment)).once(); - verify(logger.logInfo(settingBaseSizeResource)).once(); - verify(logger.logInfo(settingGrowthRateResource)).once(); - verify(logger.logInfo(settingTestFactorResource)).once(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); - verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); + verify( + logger.logInfo(settingBaseSizeResource((5).toLocaleString())), + ).once(); + verify( + logger.logInfo(settingGrowthRateResource((4.4).toLocaleString())), + ).once(); + verify( + logger.logInfo(settingTestFactorResource((2.7).toLocaleString())), + ).once(); + verify( + logger.logInfo( + settingFileMatchingPatternsResource(JSON.stringify(["aa", "bb"])), + ), + ).once(); + verify( + logger.logInfo( + settingTestMatchingPatternsResource(JSON.stringify(["cc", "dd"])), + ), + ).once(); + verify( + logger.logInfo( + settingCodeFileExtensionsResource(JSON.stringify(["js", "ts"])), + ), + ).once(); }); }); }); diff --git a/src/task/tests/metrics/inputs.baseSize.spec.ts b/src/task/tests/metrics/inputs.baseSize.spec.ts index 7acd6a011..24dab836d 100644 --- a/src/task/tests/metrics/inputs.baseSize.spec.ts +++ b/src/task/tests/metrics/inputs.baseSize.spec.ts @@ -79,7 +79,13 @@ describe("inputs.ts", (): void => { // Assert assert.equal(inputs.baseSize, parseInt(baseSize, decimalRadix)); - verify(logger.logInfo(settingBaseSizeResource)).once(); + verify( + logger.logInfo( + settingBaseSizeResource( + parseInt(baseSize, decimalRadix).toLocaleString(), + ), + ), + ).once(); }); }); } diff --git a/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts b/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts index da69a12af..bd9039ebf 100644 --- a/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts +++ b/src/task/tests/metrics/inputs.codeFileExtensions.spec.ts @@ -70,7 +70,13 @@ describe("inputs.ts", (): void => { // Assert assert.deepEqual(inputs.codeFileExtensions, expectedResult); - verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); + verify( + logger.logInfo( + settingCodeFileExtensionsResource( + JSON.stringify([...expectedResult]), + ), + ), + ).once(); }); }); } @@ -86,7 +92,11 @@ describe("inputs.ts", (): void => { // Assert assert.deepEqual(inputs.codeFileExtensions, new Set(["ada"])); - verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); + verify( + logger.logInfo( + settingCodeFileExtensionsResource(JSON.stringify(["ada"])), + ), + ).once(); }); it("should convert extensions to lower case", (): void => { @@ -103,7 +113,13 @@ describe("inputs.ts", (): void => { inputs.codeFileExtensions, new Set(["ada", "cs", "txt"]), ); - verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); + verify( + logger.logInfo( + settingCodeFileExtensionsResource( + JSON.stringify(["ada", "cs", "txt"]), + ), + ), + ).once(); }); it("should remove . and * from extension names", (): void => { @@ -120,7 +136,11 @@ describe("inputs.ts", (): void => { inputs.codeFileExtensions, new Set(["ada", "txt"]), ); - verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); + verify( + logger.logInfo( + settingCodeFileExtensionsResource(JSON.stringify(["ada", "txt"])), + ), + ).once(); }); it("should remove trailing new lines", (): void => { @@ -137,7 +157,13 @@ describe("inputs.ts", (): void => { inputs.codeFileExtensions, new Set(["ada", "cs", "txt"]), ); - verify(logger.logInfo(settingCodeFileExtensionsResource)).once(); + verify( + logger.logInfo( + settingCodeFileExtensionsResource( + JSON.stringify(["ada", "cs", "txt"]), + ), + ), + ).once(); }); }); }); diff --git a/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts b/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts index 0f631113c..83809814f 100644 --- a/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts +++ b/src/task/tests/metrics/inputs.fileMatchingPatterns.spec.ts @@ -79,7 +79,13 @@ describe("inputs.ts", (): void => { verify( logger.logInfo(adjustingFileMatchingPatternsResource), ).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); + verify( + logger.logInfo( + settingFileMatchingPatternsResource( + JSON.stringify([fileMatchingPatterns]), + ), + ), + ).once(); }); }); } @@ -108,7 +114,13 @@ describe("inputs.ts", (): void => { verify( logger.logInfo(adjustingFileMatchingPatternsResource), ).never(); - verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); + verify( + logger.logInfo( + settingFileMatchingPatternsResource( + JSON.stringify(expectedOutput), + ), + ), + ).once(); }); }); } @@ -127,7 +139,13 @@ describe("inputs.ts", (): void => { "folder1/file.js", "folder2/*.js", ]); - verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); + verify( + logger.logInfo( + settingFileMatchingPatternsResource( + JSON.stringify(["folder1/file.js", "folder2/*.js"]), + ), + ), + ).once(); }); it("should remove trailing new lines", (): void => { @@ -141,7 +159,11 @@ describe("inputs.ts", (): void => { // Assert assert.deepEqual(inputs.fileMatchingPatterns, ["file.js"]); - verify(logger.logInfo(settingFileMatchingPatternsResource)).once(); + verify( + logger.logInfo( + settingFileMatchingPatternsResource(JSON.stringify(["file.js"])), + ), + ).once(); }); it("should trim whitespace and filter empty lines", (): void => { diff --git a/src/task/tests/metrics/inputs.growthRate.spec.ts b/src/task/tests/metrics/inputs.growthRate.spec.ts index dc152f3df..a10519d1d 100644 --- a/src/task/tests/metrics/inputs.growthRate.spec.ts +++ b/src/task/tests/metrics/inputs.growthRate.spec.ts @@ -102,7 +102,13 @@ describe("inputs.ts", (): void => { // Assert assert.equal(inputs.growthRate, parseFloat(growthRate)); - verify(logger.logInfo(settingGrowthRateResource)).once(); + verify( + logger.logInfo( + settingGrowthRateResource( + parseFloat(growthRate).toLocaleString(), + ), + ), + ).once(); }); }); } diff --git a/src/task/tests/metrics/inputs.testFactor.spec.ts b/src/task/tests/metrics/inputs.testFactor.spec.ts index 2353674ba..025a7772f 100644 --- a/src/task/tests/metrics/inputs.testFactor.spec.ts +++ b/src/task/tests/metrics/inputs.testFactor.spec.ts @@ -101,7 +101,13 @@ describe("inputs.ts", (): void => { // Assert assert.equal(inputs.testFactor, parseFloat(testFactor)); - verify(logger.logInfo(settingTestFactorResource)).once(); + verify( + logger.logInfo( + settingTestFactorResource( + parseFloat(testFactor).toLocaleString(), + ), + ), + ).once(); }); }); } diff --git a/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts b/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts index 340180698..610088968 100644 --- a/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts +++ b/src/task/tests/metrics/inputs.testMatchingPatterns.spec.ts @@ -78,7 +78,13 @@ describe("inputs.ts", (): void => { verify( logger.logInfo(adjustingTestMatchingPatternsResource), ).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); + verify( + logger.logInfo( + settingTestMatchingPatternsResource( + JSON.stringify([testMatchingPatterns]), + ), + ), + ).once(); }); }); } @@ -107,7 +113,13 @@ describe("inputs.ts", (): void => { verify( logger.logInfo(adjustingTestMatchingPatternsResource), ).never(); - verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); + verify( + logger.logInfo( + settingTestMatchingPatternsResource( + JSON.stringify(expectedOutput), + ), + ), + ).once(); }); }); } @@ -126,7 +138,13 @@ describe("inputs.ts", (): void => { "folder1/file.js", "folder2/*.js", ]); - verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); + verify( + logger.logInfo( + settingTestMatchingPatternsResource( + JSON.stringify(["folder1/file.js", "folder2/*.js"]), + ), + ), + ).once(); }); it("should remove trailing new lines", (): void => { @@ -140,7 +158,11 @@ describe("inputs.ts", (): void => { // Assert assert.deepEqual(inputs.testMatchingPatterns, ["file.js"]); - verify(logger.logInfo(settingTestMatchingPatternsResource)).once(); + verify( + logger.logInfo( + settingTestMatchingPatternsResource(JSON.stringify(["file.js"])), + ), + ).once(); }); }); }); diff --git a/src/task/tests/metrics/inputsTestSetup.ts b/src/task/tests/metrics/inputsTestSetup.ts index 6e0ce1a16..884d241b3 100644 --- a/src/task/tests/metrics/inputsTestSetup.ts +++ b/src/task/tests/metrics/inputsTestSetup.ts @@ -5,35 +5,60 @@ import * as InputsDefault from "../../src/metrics/inputsDefault.js"; import { deepEqual, instance, mock, when } from "ts-mockito"; +import { + localize, + stubLocalization, +} from "../testUtilities/stubLocalization.js"; import Inputs from "../../src/metrics/inputs.js"; import Logger from "../../src/utilities/logger.js"; import RunnerInvoker from "../../src/runners/runnerInvoker.js"; -import { anyString } from "../testUtilities/mockito.js"; -export const adjustingAlwaysCloseComment = - "Adjusting the always-close-comment mode input to 'false'."; -export const adjustingBaseSizeResource = `Adjusting the base size input to '${String(InputsDefault.baseSize)}'.`; -export const adjustingGrowthRateResource = `Adjusting the growth rate input to '${String(InputsDefault.growthRate)}'.`; -export const adjustingTestFactorResource = `Adjusting the test factor input to '${String(InputsDefault.testFactor)}'.`; -export const adjustingFileMatchingPatternsResource = `Adjusting the file matching patterns input to '${JSON.stringify(InputsDefault.fileMatchingPatterns)}'.`; -export const adjustingTestMatchingPatternsResource = `Adjusting the test matching patterns input to '${JSON.stringify(InputsDefault.testMatchingPatterns)}'.`; -export const adjustingCodeFileExtensionsResource = `Adjusting the code file extensions input to '${JSON.stringify(InputsDefault.codeFileExtensions)}'.`; -export const disablingTestFactorResource = - "Disabling the test factor validation."; -export const settingAlwaysCloseComment = - "Setting the always-close-comment mode input to 'true'."; -export const settingBaseSizeResource = - "Setting the base size input to 'VALUE'."; -export const settingGrowthRateResource = - "Setting the growth rate input to 'VALUE'."; -export const settingTestFactorResource = - "Setting the test factor input to 'VALUE'."; -export const settingFileMatchingPatternsResource = - "Setting the file matching patterns input to 'VALUE'."; -export const settingTestMatchingPatternsResource = - "Setting the test matching patterns input to 'VALUE'."; -export const settingCodeFileExtensionsResource = - "Setting the code file extensions input to 'VALUE'."; +export const adjustingAlwaysCloseComment = localize( + "metrics.inputs.adjustingAlwaysCloseComment", +); +export const adjustingBaseSizeResource = localize( + "metrics.inputs.adjustingBaseSize", + InputsDefault.baseSize.toLocaleString(), +); +export const adjustingGrowthRateResource = localize( + "metrics.inputs.adjustingGrowthRate", + InputsDefault.growthRate.toLocaleString(), +); +export const adjustingTestFactorResource = localize( + "metrics.inputs.adjustingTestFactor", + InputsDefault.testFactor.toLocaleString(), +); +export const adjustingFileMatchingPatternsResource = localize( + "metrics.inputs.adjustingFileMatchingPatterns", + JSON.stringify(InputsDefault.fileMatchingPatterns), +); +export const adjustingTestMatchingPatternsResource = localize( + "metrics.inputs.adjustingTestMatchingPatterns", + JSON.stringify(InputsDefault.testMatchingPatterns), +); +export const adjustingCodeFileExtensionsResource = localize( + "metrics.inputs.adjustingCodeFileExtensions", + JSON.stringify(InputsDefault.codeFileExtensions), +); +export const disablingTestFactorResource = localize( + "metrics.inputs.disablingTestFactor", +); +export const settingAlwaysCloseComment = localize( + "metrics.inputs.settingAlwaysCloseComment", +); + +export const settingBaseSizeResource = (value: string): string => + localize("metrics.inputs.settingBaseSize", value); +export const settingGrowthRateResource = (value: string): string => + localize("metrics.inputs.settingGrowthRate", value); +export const settingTestFactorResource = (value: string): string => + localize("metrics.inputs.settingTestFactor", value); +export const settingFileMatchingPatternsResource = (value: string): string => + localize("metrics.inputs.settingFileMatchingPatterns", value); +export const settingTestMatchingPatternsResource = (value: string): string => + localize("metrics.inputs.settingTestMatchingPatterns", value); +export const settingCodeFileExtensionsResource = (value: string): string => + localize("metrics.inputs.settingCodeFileExtensions", value); export interface InputsMocks { logger: Logger; @@ -65,75 +90,7 @@ export const createInputsMocks = (): InputsMocks => { when( runnerInvoker.getInput(deepEqual(["Code", "File", "Extensions"])), ).thenReturn(""); - when( - runnerInvoker.loc("metrics.inputs.adjustingAlwaysCloseComment"), - ).thenReturn(adjustingAlwaysCloseComment); - when( - runnerInvoker.loc( - "metrics.inputs.adjustingBaseSize", - InputsDefault.baseSize.toLocaleString(), - ), - ).thenReturn(adjustingBaseSizeResource); - when( - runnerInvoker.loc( - "metrics.inputs.adjustingGrowthRate", - InputsDefault.growthRate.toLocaleString(), - ), - ).thenReturn(adjustingGrowthRateResource); - when( - runnerInvoker.loc( - "metrics.inputs.adjustingTestFactor", - InputsDefault.testFactor.toLocaleString(), - ), - ).thenReturn(adjustingTestFactorResource); - when( - runnerInvoker.loc( - "metrics.inputs.adjustingFileMatchingPatterns", - JSON.stringify(InputsDefault.fileMatchingPatterns), - ), - ).thenReturn(adjustingFileMatchingPatternsResource); - when( - runnerInvoker.loc( - "metrics.inputs.adjustingTestMatchingPatterns", - JSON.stringify(InputsDefault.testMatchingPatterns), - ), - ).thenReturn(adjustingTestMatchingPatternsResource); - when( - runnerInvoker.loc( - "metrics.inputs.adjustingCodeFileExtensions", - JSON.stringify(InputsDefault.codeFileExtensions), - ), - ).thenReturn(adjustingCodeFileExtensionsResource); - when(runnerInvoker.loc("metrics.inputs.disablingTestFactor")).thenReturn( - disablingTestFactorResource, - ); - when( - runnerInvoker.loc("metrics.inputs.settingAlwaysCloseComment"), - ).thenReturn(settingAlwaysCloseComment); - when( - runnerInvoker.loc("metrics.inputs.settingBaseSize", anyString()), - ).thenReturn(settingBaseSizeResource); - when( - runnerInvoker.loc("metrics.inputs.settingGrowthRate", anyString()), - ).thenReturn(settingGrowthRateResource); - when( - runnerInvoker.loc("metrics.inputs.settingTestFactor", anyString()), - ).thenReturn(settingTestFactorResource); - when( - runnerInvoker.loc( - "metrics.inputs.settingFileMatchingPatterns", - anyString(), - ), - ).thenReturn(settingFileMatchingPatternsResource); - when( - runnerInvoker.loc( - "metrics.inputs.settingTestMatchingPatterns", - anyString(), - ), - ).thenReturn(settingTestMatchingPatternsResource); - when( - runnerInvoker.loc("metrics.inputs.settingCodeFileExtensions", anyString()), - ).thenReturn(settingCodeFileExtensionsResource); + stubLocalization(runnerInvoker); return { logger, runnerInvoker }; }; diff --git a/src/task/tests/pullRequestMetrics.spec.ts b/src/task/tests/pullRequestMetrics.spec.ts index 617e3f3ca..18ae0b841 100644 --- a/src/task/tests/pullRequestMetrics.spec.ts +++ b/src/task/tests/pullRequestMetrics.spec.ts @@ -8,6 +8,7 @@ import CodeMetricsCalculator from "../src/metrics/codeMetricsCalculator.js"; import Logger from "../src/utilities/logger.js"; import PullRequestMetrics from "../src/pullRequestMetrics.js"; import RunnerInvoker from "../src/runners/runnerInvoker.js"; +import { stubLocalization } from "./testUtilities/stubLocalization.js"; describe("pullRequestMetrics.ts", (): void => { let codeMetricsCalculator: CodeMetricsCalculator; @@ -19,9 +20,7 @@ describe("pullRequestMetrics.ts", (): void => { logger = mock(Logger); runnerInvoker = mock(RunnerInvoker); - when(runnerInvoker.loc("pullRequestMetrics.succeeded")).thenReturn( - "PR Metrics succeeded", - ); + stubLocalization(runnerInvoker); }); describe("run()", (): void => { diff --git a/src/task/tests/testUtilities/stubLocalization.ts b/src/task/tests/testUtilities/stubLocalization.ts index 9bf3d83f7..d3e56029b 100644 --- a/src/task/tests/testUtilities/stubLocalization.ts +++ b/src/task/tests/testUtilities/stubLocalization.ts @@ -44,6 +44,24 @@ const loadResources = (): Map => { return map; }; +/** + * Resolves a localization key against the real `resources.resjson` file and + * applies parameter substitution via `util.format`. Tests use this to compute + * expected `logger` assertions without duplicating English text from the + * resource file. + * @param key The localization key (without the `loc.messages.` prefix). + * @param params The values to substitute into the template. + * @returns The formatted string. + */ +export const localize = (key: string, ...params: string[]): string => { + const template: string | undefined = loadResources().get(key); + if (typeof template === "undefined") { + throw new Error(`Unknown localization key: '${key}'.`); + } + + return params.length > 0 ? util.format(template, ...params) : template; +}; + /** * Wires the `loc()` method on a mocked `RunnerInvoker` to return values read * from the real `resources.resjson` file, with parameter substitution via @@ -51,23 +69,12 @@ const loadResources = (): Map => { * @param runnerInvoker The mocked runner invoker. */ export const stubLocalization = (runnerInvoker: RunnerInvoker): void => { - const resources: Map = loadResources(); - - const lookup = (key: string, ...params: string[]): string => { - const template: string | undefined = resources.get(key); - if (typeof template === "undefined") { - throw new Error(`Unknown localization key: '${key}'.`); - } - - return params.length > 0 ? util.format(template, ...params) : template; - }; - - when(runnerInvoker.loc(anyString())).thenCall(lookup); - when(runnerInvoker.loc(anyString(), anyString())).thenCall(lookup); + when(runnerInvoker.loc(anyString())).thenCall(localize); + when(runnerInvoker.loc(anyString(), anyString())).thenCall(localize); when(runnerInvoker.loc(anyString(), anyString(), anyString())).thenCall( - lookup, + localize, ); when( runnerInvoker.loc(anyString(), anyString(), anyString(), anyString()), - ).thenCall(lookup); + ).thenCall(localize); }; From c8b7618fb0d827bf3cc0091873e59f90a98cd64b Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Wed, 22 Apr 2026 14:18:11 +0100 Subject: [PATCH 23/27] refactor(scripts): enhance build initialization commands - Add cleanup step for debug and release directories before copying source files. - Ensure test:fast command also cleans up debug directory before execution. --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index da032b8d8..0a638605e 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,8 @@ "main": "dist/index.mjs", "type": "module", "scripts": { - "build:initialization:debug": "npm ci && node scripts/fs-helpers.mjs cp src debug", - "build:initialization:release": "npm ci && node scripts/fs-helpers.mjs cp src release", + "build:initialization:debug": "npm ci && node scripts/fs-helpers.mjs rm debug && node scripts/fs-helpers.mjs cp src debug", + "build:initialization:release": "npm ci && node scripts/fs-helpers.mjs rm release && node scripts/fs-helpers.mjs cp src release", "build:debug": "npm run build:initialization:debug && cd debug/task && tsc --sourceMap", "build:release": "npm run build:initialization:release && npm run build:release:compile && npm run build:release:clean && npm run build:release:package", "build:release:compile": "cd release/task && tsc && ncc build index.js --out . --minify", @@ -28,7 +28,7 @@ "deploy:upload": "tfx build tasks upload --task-path release/task --no-prompt", "lint": "eslint --fix **/*.ts", "test": "npm run build:debug && cd debug/task && c8 --reporter=text --reporter=text-summary mocha tests/**/*.spec.js", - "test:fast": "node scripts/fs-helpers.mjs cp src debug && cd debug/task && tsc --sourceMap && c8 --reporter=text --reporter=text-summary mocha tests/**/*.spec.js", + "test:fast": "node scripts/fs-helpers.mjs rm debug && node scripts/fs-helpers.mjs cp src debug && cd debug/task && tsc --sourceMap && c8 --reporter=text --reporter=text-summary mocha tests/**/*.spec.js", "update:dependencies": "npm update", "update:versions": "ncu -u" }, From c37c3487a8a1840d61bd6747ebda05f71023d80c Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Wed, 22 Apr 2026 14:34:14 +0100 Subject: [PATCH 24/27] refactor(tests): derive hard-coded error strings via localize() Replace hard-coded English strings in test assertions with localize() lookups against resources.resjson. Covers pullRequestMetrics.succeeded, repos.azureReposInvoker.noAzureReposAccessToken, repos.gitHubReposInvoker.noGitHubAccessToken, and repos.tokenManager.incorrectAuthorizationScheme. --- src/task/tests/pullRequestMetrics.spec.ts | 9 +++++++-- .../azureReposInvoker.isAccessTokenAvailable.spec.ts | 3 ++- .../gitHubReposInvoker.isAccessTokenAvailable.spec.ts | 3 ++- src/task/tests/repos/tokenManager.spec.ts | 11 +++++++++-- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/task/tests/pullRequestMetrics.spec.ts b/src/task/tests/pullRequestMetrics.spec.ts index 18ae0b841..fc527123c 100644 --- a/src/task/tests/pullRequestMetrics.spec.ts +++ b/src/task/tests/pullRequestMetrics.spec.ts @@ -4,11 +4,14 @@ */ import { instance, mock, verify, when } from "ts-mockito"; +import { + localize, + stubLocalization, +} from "./testUtilities/stubLocalization.js"; import CodeMetricsCalculator from "../src/metrics/codeMetricsCalculator.js"; import Logger from "../src/utilities/logger.js"; import PullRequestMetrics from "../src/pullRequestMetrics.js"; import RunnerInvoker from "../src/runners/runnerInvoker.js"; -import { stubLocalization } from "./testUtilities/stubLocalization.js"; describe("pullRequestMetrics.ts", (): void => { let codeMetricsCalculator: CodeMetricsCalculator; @@ -73,7 +76,9 @@ describe("pullRequestMetrics.ts", (): void => { verify(runnerInvoker.locInitialize("Folder")).once(); verify(codeMetricsCalculator.updateDetails()).once(); verify(codeMetricsCalculator.updateComments()).once(); - verify(runnerInvoker.setStatusSucceeded("PR Metrics succeeded")).once(); + verify( + runnerInvoker.setStatusSucceeded(localize("pullRequestMetrics.succeeded")), + ).once(); }); it("should catch and log errors", async (): Promise => { diff --git a/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts b/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts index c0218ee7e..1c24fc173 100644 --- a/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts +++ b/src/task/tests/repos/azureReposInvoker.isAccessTokenAvailable.spec.ts @@ -14,6 +14,7 @@ import type Logger from "../../src/utilities/logger.js"; import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import type TokenManager from "../../src/repos/tokenManager.js"; import assert from "node:assert/strict"; +import { localize } from "../testUtilities/stubLocalization.js"; import { stubEnv } from "../testUtilities/stubEnv.js"; import { when } from "ts-mockito"; @@ -91,7 +92,7 @@ describe("azureReposInvoker.ts", (): void => { // Assert assert.equal( result, - "Could not access the Workload Identity Federation or Personal Access Token (PAT). Add the 'WorkloadIdentityFederation' input or 'PR_Metrics_Access_Token' as a secret environment variable.", + localize("repos.azureReposInvoker.noAzureReposAccessToken"), ); }); }); diff --git a/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts b/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts index 2cbceea32..16f132e21 100644 --- a/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.isAccessTokenAvailable.spec.ts @@ -13,6 +13,7 @@ import type Logger from "../../src/utilities/logger.js"; import type OctokitWrapper from "../../src/wrappers/octokitWrapper.js"; import type RunnerInvoker from "../../src/runners/runnerInvoker.js"; import assert from "node:assert/strict"; +import { localize } from "../testUtilities/stubLocalization.js"; import { stubEnv } from "../testUtilities/stubEnv.js"; describe("gitHubReposInvoker.ts", (): void => { @@ -80,7 +81,7 @@ describe("gitHubReposInvoker.ts", (): void => { // Assert assert.equal( result, - "Could not access the Personal Access Token (PAT). Add 'PR_Metrics_Access_Token' as a secret environment variable with Read and Write access to Pull Requests (or access to 'repos' if using a Classic PAT, or write access to 'pull-requests' and 'statuses' if specified within the workflow YAML).", + localize("repos.gitHubReposInvoker.noGitHubAccessToken"), ); }); }); diff --git a/src/task/tests/repos/tokenManager.spec.ts b/src/task/tests/repos/tokenManager.spec.ts index e0d169d44..31bd5154e 100644 --- a/src/task/tests/repos/tokenManager.spec.ts +++ b/src/task/tests/repos/tokenManager.spec.ts @@ -5,6 +5,10 @@ import * as AssertExtensions from "../testUtilities/assertExtensions.js"; import { deepEqual, instance, mock, verify, when } from "ts-mockito"; +import { + localize, + stubLocalization, +} from "../testUtilities/stubLocalization.js"; import AzureDevOpsApiWrapper from "../../src/wrappers/azureDevOpsApiWrapper.js"; import type { EndpointAuthorization } from "azure-pipelines-task-lib"; import type { IRequestHandler } from "azure-devops-node-api/interfaces/common/VsoBaseInterfaces.js"; @@ -16,7 +20,6 @@ import { WebApi } from "azure-devops-node-api"; import assert from "node:assert/strict"; import { resolvableInstance } from "../testUtilities/resolvableInstance.js"; import { stubEnv } from "../testUtilities/stubEnv.js"; -import { stubLocalization } from "../testUtilities/stubLocalization.js"; describe("tokenManager.ts", (): void => { let taskApi: ITaskApi; @@ -173,7 +176,11 @@ describe("tokenManager.ts", (): void => { // Assert assert.equal( result, - "Authorization scheme of workload identity federation 'Id' must be 'WorkloadIdentityFederation' instead of 'Other'.", + localize( + "repos.tokenManager.incorrectAuthorizationScheme", + "Id", + "Other", + ), ); }); From 4902304a349d0c3110f35b01e8e006d8c8471264 Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Wed, 22 Apr 2026 14:38:15 +0100 Subject: [PATCH 25/27] chore: fix linting --- src/task/tests/pullRequestMetrics.spec.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/task/tests/pullRequestMetrics.spec.ts b/src/task/tests/pullRequestMetrics.spec.ts index fc527123c..ec8712ba3 100644 --- a/src/task/tests/pullRequestMetrics.spec.ts +++ b/src/task/tests/pullRequestMetrics.spec.ts @@ -77,7 +77,9 @@ describe("pullRequestMetrics.ts", (): void => { verify(codeMetricsCalculator.updateDetails()).once(); verify(codeMetricsCalculator.updateComments()).once(); verify( - runnerInvoker.setStatusSucceeded(localize("pullRequestMetrics.succeeded")), + runnerInvoker.setStatusSucceeded( + localize("pullRequestMetrics.succeeded"), + ), ).once(); }); From 85b57b706c7b0209f40695487bad25497a25f15e Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Wed, 22 Apr 2026 14:47:31 +0100 Subject: [PATCH 26/27] test: rename createReviewComment test to match its stub The test stubs thenResolve(null), so rename the case from "returns undefined" to "returns null" to match the actual behaviour. --- src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts b/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts index add0f0c5c..6d67561c7 100644 --- a/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts +++ b/src/task/tests/repos/gitHubReposInvoker.createComment.spec.ts @@ -263,7 +263,7 @@ describe("gitHubReposInvoker.ts", (): void => { ).twice(); }); - it("should succeed when createReviewComment() returns undefined", async (): Promise => { + it("should succeed when createReviewComment() returns null", async (): Promise => { // Arrange when(octokitWrapper.initialize(any())).thenCall( (options: OctokitOptions): void => { From 8d93b946586b7069296470adf9b341b4b8daaf8e Mon Sep 17 00:00:00 2001 From: Muiris Woulfe Date: Mon, 27 Apr 2026 13:57:57 +0100 Subject: [PATCH 27/27] Removing files --- .commitlintrc.yml | 6 ------ .github/workflows/build.yml | 3 ++- biome.json | 14 -------------- 3 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 .commitlintrc.yml delete mode 100644 biome.json diff --git a/.commitlintrc.yml b/.commitlintrc.yml deleted file mode 100644 index 72bde9ed7..000000000 --- a/.commitlintrc.yml +++ /dev/null @@ -1,6 +0,0 @@ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. - ---- -extends: - - "@commitlint/config-conventional" diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5625b6e0d..5ae4828bc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -187,9 +187,10 @@ jobs: MARKDOWN_CONFIG_FILE: ../../.markdownlint.json SAVE_SUPER_LINTER_SUMMARY: true SPELL_CODESPELL_CONFIG_FILE: .codespellrc - # Disable linters that conflict with other linters. + # Disable linters that conflict with other linters or are not enforced. VALIDATE_BIOME_FORMAT: false VALIDATE_BIOME_LINT: false + VALIDATE_GIT_COMMITLINT: false VALIDATE_JSON: false VALIDATE_PYTHON_BLACK: false VALIDATE_TYPESCRIPT_ES: false diff --git a/biome.json b/biome.json deleted file mode 100644 index 9dd2dd1dc..000000000 --- a/biome.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$schema": "https://biomejs.dev/schemas/latest/schema.json", - "files": { - "includes": ["src/**/*.ts"] - }, - "linter": { - "rules": { - "recommended": true, - "complexity": { - "noUselessSwitchCase": "off" - } - } - } -}