From 834a5c7eb3d002c75a017666f6827426fc29d0fb Mon Sep 17 00:00:00 2001 From: mviswanathsai Date: Sun, 3 Mar 2024 23:40:59 +0530 Subject: [PATCH] Add tests --- .../constants/default-processor-options.ts | 2 +- __tests__/main.spec.ts | 356 +++++++++++++++--- __tests__/operations-per-run.spec.ts | 2 +- __tests__/state.spec.ts | 2 +- src/classes/issue.spec.ts | 13 + src/classes/issues-processor.ts | 35 +- 6 files changed, 348 insertions(+), 62 deletions(-) diff --git a/__tests__/constants/default-processor-options.ts b/__tests__/constants/default-processor-options.ts index 74af70e2..5d61c130 100644 --- a/__tests__/constants/default-processor-options.ts +++ b/__tests__/constants/default-processor-options.ts @@ -11,7 +11,7 @@ export const DefaultProcessorOptions: IIssuesProcessorOptions = Object.freeze({ closeIssueMessage: 'This issue is being closed', closePrMessage: 'This PR is being closed', daysBeforeStale: 1, - daysBeforeRotten: 0, + daysBeforeRotten: -1, daysBeforeIssueStale: NaN, daysBeforePrStale: NaN, daysBeforeIssueRotten: NaN, diff --git a/__tests__/main.spec.ts b/__tests__/main.spec.ts index 7ad9e10a..790313fc 100644 --- a/__tests__/main.spec.ts +++ b/__tests__/main.spec.ts @@ -496,10 +496,11 @@ test('processing an issue with no label will make it stale but not close it', as expect(processor.closedIssues).toHaveLength(0); }); -test('processing a stale issue will close it', async () => { +test('processing a stale issue will rot it but not close it, given days before rotten is > -1', async () => { const opts: IIssuesProcessorOptions = { ...DefaultProcessorOptions, - daysBeforeClose: 30 + daysBeforeClose: 30, + daysBeforeRotten: 0 }; const TestIssueList: Issue[] = [ generateIssue( @@ -526,13 +527,14 @@ test('processing a stale issue will close it', async () => { expect(processor.staleIssues).toHaveLength(0); expect(processor.rottenIssues).toHaveLength(1); - expect(processor.closedIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); }); -test('processing a stale issue containing a space in the label will close it', async () => { +test('processing a stale issue containing a space in the label will rotten it but not close it, given days before rotten is > -1', async () => { const opts: IIssuesProcessorOptions = { ...DefaultProcessorOptions, - staleIssueLabel: 'state: stale' + staleIssueLabel: 'state: stale', + daysBeforeRotten: 0 }; const TestIssueList: Issue[] = [ generateIssue( @@ -558,13 +560,16 @@ test('processing a stale issue containing a space in the label will close it', a await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); - expect(processor.closedIssues).toHaveLength(1); + expect(processor.rottenIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); }); -test('processing a stale issue containing a slash in the label will close it', async () => { +test('processing a stale issue containing a slash in the label will rotten it but not close it', async () => { const opts: IIssuesProcessorOptions = { ...DefaultProcessorOptions, - staleIssueLabel: 'lifecycle/stale' + staleIssueLabel: 'lifecycle/stale', + daysBeforeRotten: 0 + }; const TestIssueList: Issue[] = [ generateIssue( @@ -590,20 +595,22 @@ test('processing a stale issue containing a slash in the label will close it', a await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); - expect(processor.closedIssues).toHaveLength(1); + expect(processor.rottenIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); }); -test('processing a stale issue will close it when days-before-issue-stale override days-before-stale', async () => { +test('processing a stale issue will rotten it but not close it when days-before-issue-rotten override days-before-rotten', async () => { const opts: IIssuesProcessorOptions = { ...DefaultProcessorOptions, - daysBeforeClose: 30, - daysBeforeIssueStale: 30 + daysBeforeRotten: -1, + daysBeforeIssueRotten: 30 + }; const TestIssueList: Issue[] = [ generateIssue( opts, 1, - 'A stale issue that should be closed', + 'A stale issue that should be rotten', '2020-01-01T17:00:00Z', '2020-01-01T17:00:00Z', false, @@ -623,13 +630,14 @@ test('processing a stale issue will close it when days-before-issue-stale overri await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); - expect(processor.closedIssues).toHaveLength(1); + expect(processor.rottenIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); }); -test('processing a stale PR will close it', async () => { +test('processing a stale PR will rotten it but not close it', async () => { const opts: IIssuesProcessorOptions = { ...DefaultProcessorOptions, - daysBeforeClose: 30 + daysBeforePrRotten: 30 }; const TestIssueList: Issue[] = [ generateIssue( @@ -655,13 +663,51 @@ test('processing a stale PR will close it', async () => { await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); - expect(processor.closedIssues).toHaveLength(1); + expect(processor.rottenIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); }); -test('processing a stale PR will close it when days-before-pr-stale override days-before-stale', async () => { + +test('processing a stale PR will rotten it it when days-before-pr-rotten override days-before-rotten', async () => { + const opts: IIssuesProcessorOptions = { + ...DefaultProcessorOptions, + daysBeforeRotten: 30, + daysBeforePrRotten: 30 + }; + const TestIssueList: Issue[] = [ + generateIssue( + opts, + 1, + 'A stale PR that should be closed', + '2020-01-01T17:00:00Z', + '2020-01-01T17:00:00Z', + false, + true, + ['Stale'] + ) + ]; + const processor = new IssuesProcessorMock( + opts, + alwaysFalseStateMock, + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() + ); + + // process our fake issue list + await processor.processIssues(1); + + expect(processor.staleIssues).toHaveLength(0); + expect(processor.rottenIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); +}); + +test('processing a stale PR will rotten it but not close it when days-before-pr-stale override days-before-stale', async () => { const opts: IIssuesProcessorOptions = { ...DefaultProcessorOptions, daysBeforeClose: 30, + daysBeforeRotten: 0, + daysBeforePrClose: 30 }; const TestIssueList: Issue[] = [ @@ -688,13 +734,15 @@ test('processing a stale PR will close it when days-before-pr-stale override day await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); - expect(processor.closedIssues).toHaveLength(1); + expect(processor.rottenIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); }); -test('processing a stale issue will close it even if configured not to mark as stale', async () => { +test('processing a stale issue will rotten it even if configured not to mark as stale', async () => { const opts = { ...DefaultProcessorOptions, daysBeforeStale: -1, + daysBeforeRotten: 0, staleIssueMessage: '' }; const TestIssueList: Issue[] = [ @@ -721,13 +769,16 @@ test('processing a stale issue will close it even if configured not to mark as s await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); - expect(processor.closedIssues).toHaveLength(1); + expect(processor.rottenIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); }); test('processing a stale issue will close it even if configured not to mark as stale when days-before-issue-stale override days-before-stale', async () => { const opts = { ...DefaultProcessorOptions, daysBeforeStale: 0, + daysBeforeRotten: 0, + daysBeforeIssueStale: -1, staleIssueMessage: '' }; @@ -755,14 +806,16 @@ test('processing a stale issue will close it even if configured not to mark as s await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); - expect(processor.closedIssues).toHaveLength(1); + expect(processor.rottenIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); }); -test('processing a stale PR will close it even if configured not to mark as stale', async () => { +test('processing a stale PR will rotten it even if configured not to mark as stale', async () => { const opts = { ...DefaultProcessorOptions, daysBeforeStale: -1, - stalePrMessage: '' + daysBeforeRotten: 0, + stalePrMessage: '', }; const TestIssueList: Issue[] = [ generateIssue( @@ -788,14 +841,52 @@ test('processing a stale PR will close it even if configured not to mark as stal await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); + expect(processor.rottenIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); +}); + +test('processing a stale PR will close it even if configured not to mark as stale or rotten', async () => { + const opts = { + ...DefaultProcessorOptions, + daysBeforeStale: -1, + stalePrMessage: '', + daysBeforeRotten: -1, + }; + const TestIssueList: Issue[] = [ + generateIssue( + opts, + 1, + 'An issue with no label', + '2020-01-01T17:00:00Z', + '2020-01-01T17:00:00Z', + false, + true, + ['Stale'] + ) + ]; + const processor = new IssuesProcessorMock( + opts, + alwaysFalseStateMock, + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() + ); + + // process our fake issue list + await processor.processIssues(1); + + expect(processor.staleIssues).toHaveLength(0); + expect(processor.rottenIssues).toHaveLength(0); expect(processor.closedIssues).toHaveLength(1); }); -test('processing a stale PR will close it even if configured not to mark as stale when days-before-pr-stale override days-before-stale', async () => { +test('processing a stale PR will rotten it even if configured not to mark as stale when days-before-pr-stale override days-before-stale', async () => { const opts = { ...DefaultProcessorOptions, daysBeforeStale: 0, daysBeforePrStale: -1, + daysBeforeRotten: 0, + stalePrMessage: '' }; const TestIssueList: Issue[] = [ @@ -822,10 +913,11 @@ test('processing a stale PR will close it even if configured not to mark as stal await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); - expect(processor.closedIssues).toHaveLength(1); + expect(processor.rottenIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); }); -test('closed issues will not be marked stale', async () => { +test('closed issues will not be marked stale or rotten', async () => { const TestIssueList: Issue[] = [ generateIssue( DefaultProcessorOptions, @@ -850,10 +942,41 @@ test('closed issues will not be marked stale', async () => { await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); + expect(processor.rottenIssues).toHaveLength(0); expect(processor.closedIssues).toHaveLength(0); }); -test('stale closed issues will not be closed', async () => { +test('rotten closed issues will not be closed', async () => { + const TestIssueList: Issue[] = [ + generateIssue( + DefaultProcessorOptions, + 1, + 'A rotten closed issue', + '2020-01-01T17:00:00Z', + '2020-01-01T17:00:00Z', + false, + false, + ['Rotten'], + true + ) + ]; + const processor = new IssuesProcessorMock( + DefaultProcessorOptions, + alwaysFalseStateMock, + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() + ); + + // process our fake issue list + await processor.processIssues(1); + + expect(processor.staleIssues).toHaveLength(0); + expect(processor.rottenIssues).toHaveLength(0); + expect(processor.closedIssues).toHaveLength(0); +}); + +test('stale closed issues will not be closed or rotten', async () => { const TestIssueList: Issue[] = [ generateIssue( DefaultProcessorOptions, @@ -879,10 +1002,11 @@ test('stale closed issues will not be closed', async () => { await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); + expect(processor.rottenIssues).toHaveLength(0); expect(processor.closedIssues).toHaveLength(0); }); -test('closed prs will not be marked stale', async () => { +test('closed prs will not be marked stale or rotten', async () => { const TestIssueList: Issue[] = [ generateIssue( DefaultProcessorOptions, @@ -908,6 +1032,7 @@ test('closed prs will not be marked stale', async () => { await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); + expect(processor.rottenIssues).toHaveLength(0); expect(processor.closedIssues).toHaveLength(0); }); @@ -940,7 +1065,7 @@ test('stale closed prs will not be closed', async () => { expect(processor.closedIssues).toHaveLength(0); }); -test('locked issues will not be marked stale', async () => { +test('locked issues will not be marked stale or rotten', async () => { const TestIssueList: Issue[] = [ generateIssue( DefaultProcessorOptions, @@ -965,10 +1090,11 @@ test('locked issues will not be marked stale', async () => { await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); + expect(processor.rottenIssues).toHaveLength(0); expect(processor.closedIssues).toHaveLength(0); }); -test('stale locked issues will not be closed', async () => { +test('stale locked issues will not be rotten or closed', async () => { const TestIssueList: Issue[] = [ generateIssue( DefaultProcessorOptions, @@ -995,10 +1121,42 @@ test('stale locked issues will not be closed', async () => { await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); + expect(processor.rottenIssues).toHaveLength(0); expect(processor.closedIssues).toHaveLength(0); }); -test('locked prs will not be marked stale', async () => { +test('rotten locked issues will not be rotten or closed', async () => { + const TestIssueList: Issue[] = [ + generateIssue( + DefaultProcessorOptions, + 1, + 'A stale locked issue that will not be closed', + '2020-01-01T17:00:00Z', + '2020-01-01T17:00:00Z', + false, + false, + ['Rotten'], + false, + true + ) + ]; + const processor = new IssuesProcessorMock( + DefaultProcessorOptions, + alwaysFalseStateMock, + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() + ); + + // process our fake issue list + await processor.processIssues(1); + + expect(processor.staleIssues).toHaveLength(0); + expect(processor.rottenIssues).toHaveLength(0); + expect(processor.closedIssues).toHaveLength(0); +}); + +test('locked prs will not be marked stale or rotten', async () => { const TestIssueList: Issue[] = [ generateIssue( DefaultProcessorOptions, @@ -1023,10 +1181,11 @@ test('locked prs will not be marked stale', async () => { await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); + expect(processor.rottenIssues).toHaveLength(0); expect(processor.closedIssues).toHaveLength(0); }); -test('stale locked prs will not be closed', async () => { +test('stale locked prs will not be rotten or closed', async () => { const TestIssueList: Issue[] = [ generateIssue( DefaultProcessorOptions, @@ -1053,11 +1212,12 @@ test('stale locked prs will not be closed', async () => { await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); + expect(processor.rottenIssues).toHaveLength(0); expect(processor.closedIssues).toHaveLength(0); }); -test('exempt issue labels will not be marked stale', async () => { - expect.assertions(3); +test('exempt issue labels will not be marked stale or rotten', async () => { + expect.assertions(4); const opts = {...DefaultProcessorOptions}; opts.exemptIssueLabels = 'Exempt'; const TestIssueList: Issue[] = [ @@ -1084,11 +1244,12 @@ test('exempt issue labels will not be marked stale', async () => { await processor.processIssues(1); expect(processor.staleIssues.length).toStrictEqual(0); + expect(processor.rottenIssues).toHaveLength(0); expect(processor.closedIssues.length).toStrictEqual(0); expect(processor.removedLabelIssues.length).toStrictEqual(0); }); -test('exempt issue labels will not be marked stale (multi issue label with spaces)', async () => { +test('exempt issue labels will not be marked stale or rotten (multi issue label with spaces)', async () => { const opts = {...DefaultProcessorOptions}; opts.exemptIssueLabels = 'Exempt, Cool, None'; const TestIssueList: Issue[] = [ @@ -1115,6 +1276,7 @@ test('exempt issue labels will not be marked stale (multi issue label with space await processor.processIssues(1); expect(processor.staleIssues).toHaveLength(0); + expect(processor.rottenIssues).toHaveLength(0); expect(processor.closedIssues).toHaveLength(0); }); @@ -1248,7 +1410,8 @@ test('stale issues should not be closed if days is set to -1', async () => { }); test('stale label should be removed if a comment was added to a stale issue', async () => { - const opts = {...DefaultProcessorOptions, removeStaleWhenUpdated: true}; + const opts = {...DefaultProcessorOptions, removeStaleWhenUpdated: true, daysBeforeRotten: 0, + }; const TestIssueList: Issue[] = [ generateIssue( opts, @@ -1375,8 +1538,9 @@ test('when the option "labelsToRemoveWhenStale" is set, the labels should be rem expect(processor.removedLabelIssues).toHaveLength(1); }); -test('stale label should not be removed if a comment was added by the bot (and the issue should be closed)', async () => { - const opts = {...DefaultProcessorOptions, removeStaleWhenUpdated: true}; +test('stale label should not be removed if a comment was added by the bot (and the issue should be rotten)', async () => { + const opts = {...DefaultProcessorOptions, removeStaleWhenUpdated: true, daysBeforeRotten: 0, + }; github.context.actor = 'abot'; const TestIssueList: Issue[] = [ generateIssue( @@ -1409,7 +1573,8 @@ test('stale label should not be removed if a comment was added by the bot (and t // process our fake issue list await processor.processIssues(1); - expect(processor.closedIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); + expect(processor.rottenIssues).toHaveLength(1); expect(processor.staleIssues).toHaveLength(0); expect(processor.removedLabelIssues).toHaveLength(0); }); @@ -1480,10 +1645,10 @@ test('stale issues should not be closed until after the closed number of days', expect(processor.staleIssues).toHaveLength(1); }); -test('stale issues should be closed if the closed nubmer of days (additive) is also passed', async () => { +test('stale issues should be rotten if the rotten nubmer of days (additive) is also passed', async () => { const opts = {...DefaultProcessorOptions}; opts.daysBeforeStale = 5; // stale after 5 days - opts.daysBeforeClose = 1; // closes after 6 days + opts.daysBeforeRotten = 1; // closes after 6 days const lastUpdate = new Date(); lastUpdate.setDate(lastUpdate.getDate() - 7); const TestIssueList: Issue[] = [ @@ -1509,7 +1674,8 @@ test('stale issues should be closed if the closed nubmer of days (additive) is a // process our fake issue list await processor.processIssues(1); - expect(processor.closedIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); + expect(processor.rottenIssues).toHaveLength(1); expect(processor.removedLabelIssues).toHaveLength(0); expect(processor.staleIssues).toHaveLength(0); }); @@ -1728,8 +1894,8 @@ test('send stale message on prs when stale-pr-message is not empty', async () => ); }); -test('git branch is deleted when option is enabled', async () => { - const opts = {...DefaultProcessorOptions, deleteBranch: true}; +test('git branch is deleted when option is enabled and days before rotten is set to -1', async () => { + const opts = {...DefaultProcessorOptions, deleteBranch: true, daysBeforeRotten: -1,}; const isPullRequest = true; const TestIssueList: Issue[] = [ generateIssue( @@ -1759,8 +1925,8 @@ test('git branch is deleted when option is enabled', async () => { expect(processor.deletedBranchIssues).toHaveLength(1); }); -test('git branch is not deleted when issue is not pull request', async () => { - const opts = {...DefaultProcessorOptions, deleteBranch: true}; +test('git branch is not deleted when issue is not pull request and days before rotten is set to -1', async () => { + const opts = {...DefaultProcessorOptions, deleteBranch: true, daysBeforeRotten: -1,}; const isPullRequest = false; const TestIssueList: Issue[] = [ generateIssue( @@ -2554,13 +2720,14 @@ test('processing a locked issue with a close label will not remove the close lab expect(processor.removedLabelIssues).toHaveLength(0); }); -test('processing an issue stale since less than the daysBeforeStale with a stale label created after daysBeforeClose should close the issue', async () => { - expect.assertions(3); +test('processing an issue stale since less than the daysBeforeStale with a stale label created after daysBeforeRotten should rotten the issue', async () => { + expect.assertions(4); const opts: IIssuesProcessorOptions = { ...DefaultProcessorOptions, staleIssueLabel: 'stale-label', daysBeforeStale: 30, daysBeforeClose: 7, + daysBeforeRotten: 0, closeIssueMessage: 'close message', removeStaleWhenUpdated: false }; @@ -2593,8 +2760,9 @@ test('processing an issue stale since less than the daysBeforeStale with a stale await processor.processIssues(1); expect(processor.removedLabelIssues).toHaveLength(0); + expect(processor.rottenIssues).toHaveLength(1); // Expected at 0 by the user expect(processor.deletedBranchIssues).toHaveLength(0); - expect(processor.closedIssues).toHaveLength(1); // Expected at 0 by the user + expect(processor.closedIssues).toHaveLength(0); }); test('processing an issue stale since less than the daysBeforeStale without a stale label should close the issue', async () => { @@ -2604,6 +2772,8 @@ test('processing an issue stale since less than the daysBeforeStale without a st staleIssueLabel: 'stale-label', daysBeforeStale: 30, daysBeforeClose: 7, + daysBeforeRotten: 0, + closeIssueMessage: 'close message', removeStaleWhenUpdated: false }; @@ -2639,13 +2809,14 @@ test('processing an issue stale since less than the daysBeforeStale without a st expect(processor.closedIssues).toHaveLength(0); }); -test('processing a pull request to be stale with the "stalePrMessage" option set will send a PR comment', async () => { +test('processing a pull request to be stale with the "stalePrMessage" option set will send a PR comment, given that days before rotten is set to -1', async () => { expect.assertions(3); const opts: IIssuesProcessorOptions = { ...DefaultProcessorOptions, stalePrMessage: 'This PR is stale', daysBeforeStale: 10, - daysBeforePrStale: 1 + daysBeforePrStale: 1, + daysBeforeRotten: -1, }; const issueDate = new Date(); issueDate.setDate(issueDate.getDate() - 2); @@ -2676,12 +2847,52 @@ test('processing a pull request to be stale with the "stalePrMessage" option set expect(processor.statistics?.addedPullRequestsCommentsCount).toStrictEqual(1); }); -test('processing a pull request to be stale with the "stalePrMessage" option set to empty will not send a PR comment', async () => { +test('processing a pull request to be stale with the "stalePrMessage" option set will send two PR comments, given that days before rotten is set to 0', async () => { + expect.assertions(3); + const opts: IIssuesProcessorOptions = { + ...DefaultProcessorOptions, + stalePrMessage: 'This PR is stale', + daysBeforeStale: 10, + daysBeforePrStale: 1, + daysBeforeRotten: 0 + }; + const issueDate = new Date(); + issueDate.setDate(issueDate.getDate() - 2); + const TestIssueList: Issue[] = [ + generateIssue( + opts, + 1, + 'A pull request with no label and a stale message', + issueDate.toDateString(), + issueDate.toDateString(), + false, + true + ) + ]; + const processor = new IssuesProcessorMock( + opts, + alwaysFalseStateMock, + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() + ); + + // process our fake issue list + await processor.processIssues(1); + + expect(processor.staleIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); + expect(processor.statistics?.addedPullRequestsCommentsCount).toStrictEqual(2); +}); + +test('processing a pull request to be stale with the "stalePrMessage" option set to empty will not send a PR comment, given that "rottenPRMessage" is also an empty string and days before rotten is not -1', async () => { expect.assertions(3); const opts: IIssuesProcessorOptions = { ...DefaultProcessorOptions, stalePrMessage: '', + rottenPrMessage: '', daysBeforeStale: 10, + daysBeforeRotten: 0, daysBeforePrStale: 1 }; const issueDate = new Date(); @@ -2713,6 +2924,45 @@ test('processing a pull request to be stale with the "stalePrMessage" option set expect(processor.statistics?.addedPullRequestsCommentsCount).toStrictEqual(0); }); +test('processing a pull request to be stale with the "stalePrMessage" option set to empty will send a PR comment from "rottenPRMessage" given that it is also an empty string', async () => { + expect.assertions(3); + const opts: IIssuesProcessorOptions = { + ...DefaultProcessorOptions, + stalePrMessage: '', + daysBeforeStale: 10, + daysBeforeRotten: 0, + + daysBeforePrStale: 1 + }; + const issueDate = new Date(); + issueDate.setDate(issueDate.getDate() - 2); + const TestIssueList: Issue[] = [ + generateIssue( + opts, + 1, + 'A pull request with no label and a stale message', + issueDate.toDateString(), + issueDate.toDateString(), + false, + true + ) + ]; + const processor = new IssuesProcessorMock( + opts, + alwaysFalseStateMock, + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() + ); + + // process our fake issue list + await processor.processIssues(1); + + expect(processor.staleIssues).toHaveLength(1); + expect(processor.closedIssues).toHaveLength(0); + expect(processor.statistics?.addedPullRequestsCommentsCount).toStrictEqual(1); +}); + test('processing an issue with the "includeOnlyAssigned" option and nonempty assignee list will stale the issue', async () => { const issueDate = new Date(); issueDate.setDate(issueDate.getDate() - 2); diff --git a/__tests__/operations-per-run.spec.ts b/__tests__/operations-per-run.spec.ts index 6be0a363..7b038fe7 100644 --- a/__tests__/operations-per-run.spec.ts +++ b/__tests__/operations-per-run.spec.ts @@ -13,7 +13,7 @@ describe('operations-per-run option', (): void => { sut = new SUT(); }); - describe('when one issue should be stale within 10 days and updated 20 days ago', (): void => { + describe('when one issue should be stale within 10 days and updated 20 days ago and days before rotten is -1', (): void => { beforeEach((): void => { sut.staleIn(10).newIssue().updated(20); }); diff --git a/__tests__/state.spec.ts b/__tests__/state.spec.ts index 8c59d861..fd7d89a8 100644 --- a/__tests__/state.spec.ts +++ b/__tests__/state.spec.ts @@ -175,7 +175,7 @@ describe('state', (): void => { it('state should be reset if all issues are proceeded', async () => { const opts: IIssuesProcessorOptions = { ...DefaultProcessorOptions, - daysBeforeClose: 0 + daysBeforeClose: 0, }; const testIssue1 = generateIssue( opts, diff --git a/src/classes/issue.spec.ts b/src/classes/issue.spec.ts index a2c82e26..2b2af9e9 100644 --- a/src/classes/issue.spec.ts +++ b/src/classes/issue.spec.ts @@ -20,9 +20,12 @@ describe('Issue', (): void => { daysBeforeClose: 0, daysBeforeIssueClose: 0, daysBeforeIssueStale: 0, + daysBeforeIssueRotten: 0, daysBeforePrClose: 0, daysBeforePrStale: 0, + daysBeforePrRotten: 0, daysBeforeStale: 0, + daysBeforeRotten: 0, debugOnly: false, deleteBranch: false, exemptIssueLabels: '', @@ -37,12 +40,19 @@ describe('Issue', (): void => { removeStaleWhenUpdated: false, removeIssueStaleWhenUpdated: undefined, removePrStaleWhenUpdated: undefined, + removeRottenWhenUpdated: false, + removeIssueRottenWhenUpdated: undefined, + removePrRottenWhenUpdated: undefined, repoToken: '', staleIssueMessage: '', stalePrMessage: '', + rottenIssueMessage: '', + rottenPrMessage: '', startDate: undefined, stalePrLabel: 'dummy-stale-pr-label', staleIssueLabel: 'dummy-stale-issue-label', + rottenPrLabel: 'dummy-rotten-pr-label', + rottenIssueLabel: 'dummy-rotten-issue-label', exemptMilestones: '', exemptIssueMilestones: '', exemptPrMilestones: '', @@ -59,6 +69,9 @@ describe('Issue', (): void => { labelsToRemoveWhenStale: '', labelsToRemoveWhenUnstale: '', labelsToAddWhenUnstale: '', + labelsToRemoveWhenRotten: '', + labelsToRemoveWhenUnrotten: '', + labelsToAddWhenUnrotten: '', ignoreUpdates: false, ignoreIssueUpdates: undefined, ignorePrUpdates: undefined, diff --git a/src/classes/issues-processor.ts b/src/classes/issues-processor.ts index b77b2b9a..12f0e46e 100644 --- a/src/classes/issues-processor.ts +++ b/src/classes/issues-processor.ts @@ -724,6 +724,8 @@ export class IssuesProcessor { ) { const issueLogger: IssueLogger = new IssueLogger(issue); + var issueHasClosed: boolean = false + // We can get the label creation date from the getLableCreationDate function const markedStaleOn: string = (await this.getLabelCreationDate(issue, staleLabel)) || issue.updated_at; @@ -820,14 +822,26 @@ export class IssuesProcessor { if (daysBeforeRotten < 0) { if (daysBeforeClose < 0) { + issueLogger.info( + `Stale $$type cannot be rotten or closed because days before rotten: ${daysBeforeRotten}, and days before close: ${daysBeforeClose}` + ); return; } else { + issueLogger.info( + `Closing issue without rottening it because days before $$type rotten: ${LoggerService.cyan(daysBeforeRotten)}` + ); + let issueHasUpdateInCloseWindow: boolean - issueHasUpdateInCloseWindow = !IssuesProcessor._updatedSince( + issueHasUpdateInCloseWindow = IssuesProcessor._updatedSince( issue.updated_at, daysBeforeClose ); + issueLogger.info( + `$$type has been updated in the last ${daysBeforeClose} days: ${LoggerService.cyan( + issueHasUpdateInCloseWindow + )}` + ); if (!issueHasUpdateInCloseWindow && !issueHasCommentsSinceStale) { issueLogger.info( `Closing $$type because it was last updated on: ${LoggerService.cyan( @@ -836,6 +850,8 @@ export class IssuesProcessor { ); await this._closeIssue(issue, closeMessage, closeLabel); + issueHasClosed = true; + if (this.options.deleteBranch && issue.pull_request) { issueLogger.info( `Deleting the branch since the option ${issueLogger.createOptionLink( @@ -856,6 +872,13 @@ export class IssuesProcessor { // TODO: make a function for shouldMarkWhenRotten const shouldMarkAsRotten: boolean = shouldMarkWhenStale(daysBeforeRotten); + if (issueHasClosed) { + issueLogger.info( + `Issue $$type has been closed, no need to process it further.` + ); + return; + } + if (!issue.isRotten) { issueLogger.info(`This $$type is not rotten`); @@ -918,7 +941,7 @@ export class IssuesProcessor { } } } - if(issue.isRotten){ + if (issue.isRotten) { issueLogger.info(`This $$type is already rotten`); // process the rotten issues this._processRottenIssue( @@ -1223,7 +1246,7 @@ export class IssuesProcessor { ): Promise { const issueLogger: IssueLogger = new IssueLogger(issue); - issueLogger.info(`Closing $$type for being stale`); + issueLogger.info(`Closing $$type for being stale/rotten`); this.closedIssues.push(issue); if (closeMessage) { @@ -1397,9 +1420,9 @@ export class IssuesProcessor { } private _getDaysBeforePrRotten(): number { - return isNaN(this.options.daysBeforePrStale) - ? this.options.daysBeforeStale - : this.options.daysBeforePrStale; + return isNaN(this.options.daysBeforePrRotten) + ? this.options.daysBeforeRotten + : this.options.daysBeforePrRotten; } private _getDaysBeforeIssueClose(): number {