diff --git a/__tests__/main.test.ts b/__tests__/main.test.ts index 1dbbac71..9b926b80 100644 --- a/__tests__/main.test.ts +++ b/__tests__/main.test.ts @@ -2,60 +2,66 @@ import * as core from '@actions/core'; import * as github from '@actions/github'; import {Octokit} from '@octokit/rest'; -import {IssueProcessor, IssueProcessorOptions} from '../src/IssueProcessor'; +import { + IssueProcessor, + Issue, + Label, + IssueProcessorOptions +} from '../src/IssueProcessor'; -type Issue = Octokit.IssuesListForRepoResponseItem; -type IssueLabel = Octokit.IssuesListForRepoResponseItemLabelsItem; -type IssueList = Octokit.Response; +function generateIssue( + id: number, + title: string, + updatedAt: string, + isPullRequest: boolean = false +): Issue { + return { + number: id, + labels: [], + title: title, + updated_at: updatedAt, + pull_request: isPullRequest ? {} : null + }; +} -const FakeHeaders = { - date: 'none', - 'x-ratelimit-limit': '', - 'x-ratelimit-remaining': '', - 'x-ratelimit-reset': '', - 'x-Octokit-request-id': '', - 'x-Octokit-media-type': '', - link: '', - 'last-modified': '', - etag: '', - status: '' -}; - -const EmptyIssueList: IssueList = { - data: [], - status: 200, - headers: FakeHeaders, - - *[Symbol.iterator]() { - for (let i of this.data) { - yield i; - } - } +const DefaultProcessorOptions: IssueProcessorOptions = { + repoToken: 'none', + staleIssueMessage: 'This issue is stale', + stalePrMessage: 'This PR is stale', + daysBeforeStale: 1, + daysBeforeClose: 1, + staleIssueLabel: 'Stale', + exemptIssueLabels: '', + stalePrLabel: 'Stale', + exemptPrLabels: '', + onlyLabels: '', + operationsPerRun: 100, + debugOnly: true }; test('empty issue list results in 1 operation', async () => { - const options: IssueProcessorOptions = { - repoToken: 'none', - staleIssueMessage: 'This issue is stale', - stalePrMessage: 'This PR is stale', - daysBeforeStale: 1, - daysBeforeClose: 1, - staleIssueLabel: 'Stale', - exemptIssueLabels: '', - stalePrLabel: 'Stale', - exemptPrLabels: '', - onlyLabels: '', - operationsPerRun: 100, - debugOnly: true - }; - const processor = new IssueProcessor(options); + const processor = new IssueProcessor(DefaultProcessorOptions, async () => []); // process our fake issue list - const operationsLeft = await processor.processIssues( - 1, - () => new Promise(resolve => resolve(EmptyIssueList)) - ); + const operationsLeft = await processor.processIssues(1); // processing an empty issue list should result in 1 operation expect(operationsLeft).toEqual(99); }); + +test('processing an issue with no label will not make it stale', async () => { + const TestIssueList: Issue[] = [ + generateIssue(1, 'My first issue', Date.now().toString()) + ]; + + const processor = new IssueProcessor( + DefaultProcessorOptions, + async () => TestIssueList + ); + + // process our fake issue list + const operationsLeft = await processor.processIssues(1); + + // processing an empty issue list should result in 1 operation + expect(operationsLeft).toBeLessThan(100); +}); diff --git a/dist/index.js b/dist/index.js index 7a8b1eba..887b9876 100644 --- a/dist/index.js +++ b/dist/index.js @@ -8447,16 +8447,18 @@ const github = __importStar(__webpack_require__(469)); * Handle processing of issues for staleness/closure. */ class IssueProcessor { - constructor(options) { + constructor(options, getIssues) { this.operationsLeft = 0; this.staleIssues = []; this.closedIssues = []; this.options = options; this.operationsLeft = options.operationsPerRun; this.client = new github.GitHub(options.repoToken); + if (getIssues) { + this.getIssues = getIssues; + } } - processIssues(page = 1, getIssues = this.getIssues.bind(this) // used for injecting issues to test - ) { + processIssues(page = 1) { return __awaiter(this, void 0, void 0, function* () { if (this.options.debugOnly) { core.warning('Executing in debug mode. Debug output will be written but no issues will be processed.'); @@ -8466,13 +8468,13 @@ class IssueProcessor { return 0; } // get the next batch of issues - const issues = yield getIssues(page); + const issues = yield this.getIssues(page); this.operationsLeft -= 1; - if (issues.data.length <= 0) { + if (issues.length <= 0) { core.debug('No more issues found to process. Exiting.'); return this.operationsLeft; } - for (const issue of issues.data.values()) { + for (const issue of issues.values()) { const isPr = !!issue.pull_request; core.debug(`Found issue: issue #${issue.number} - ${issue.title} last updated ${issue.updated_at} (is pr? ${isPr})`); // calculate string based messages for this issue @@ -8517,7 +8519,7 @@ class IssueProcessor { // grab issues from github in baches of 100 getIssues(page) { return __awaiter(this, void 0, void 0, function* () { - return this.client.issues.listForRepo({ + const issueResult = yield this.client.issues.listForRepo({ owner: github.context.repo.owner, repo: github.context.repo.repo, state: 'open', @@ -8525,6 +8527,7 @@ class IssueProcessor { per_page: 100, page }); + return issueResult.data; }); } // Mark an issue as stale with a comment and a label diff --git a/src/IssueProcessor.ts b/src/IssueProcessor.ts index 523fb417..78383369 100644 --- a/src/IssueProcessor.ts +++ b/src/IssueProcessor.ts @@ -2,9 +2,19 @@ import * as core from '@actions/core'; import * as github from '@actions/github'; import {Octokit} from '@octokit/rest'; -type Issue = Octokit.IssuesListForRepoResponseItem; -type IssueLabel = Octokit.IssuesListForRepoResponseItemLabelsItem; -type IssueList = Octokit.Response; +type OcotoKitIssueList = Octokit.Response; + +export interface Issue { + title: string; + number: number; + updated_at: string; + labels: Label[]; + pull_request: any; +} + +export interface Label { + name: string; +} export interface IssueProcessorOptions { repoToken: string; @@ -32,16 +42,20 @@ export class IssueProcessor { readonly staleIssues: Issue[] = []; readonly closedIssues: Issue[] = []; - constructor(options: IssueProcessorOptions) { + constructor( + options: IssueProcessorOptions, + getIssues?: (page: number) => Promise + ) { this.options = options; this.operationsLeft = options.operationsPerRun; this.client = new github.GitHub(options.repoToken); + + if (getIssues) { + this.getIssues = getIssues; + } } - async processIssues( - page: number = 1, - getIssues: (page: number) => Promise = this.getIssues.bind(this) // used for injecting issues to test - ): Promise { + async processIssues(page: number = 1): Promise { if (this.options.debugOnly) { core.warning( 'Executing in debug mode. Debug output will be written but no issues will be processed.' @@ -54,15 +68,15 @@ export class IssueProcessor { } // get the next batch of issues - const issues: IssueList = await getIssues(page); + const issues: Issue[] = await this.getIssues(page); this.operationsLeft -= 1; - if (issues.data.length <= 0) { + if (issues.length <= 0) { core.debug('No more issues found to process. Exiting.'); return this.operationsLeft; } - for (const issue of issues.data.values()) { + for (const issue of issues.values()) { const isPr = !!issue.pull_request; core.debug( @@ -130,15 +144,19 @@ export class IssueProcessor { } // grab issues from github in baches of 100 - private async getIssues(page: number): Promise { - return this.client.issues.listForRepo({ - owner: github.context.repo.owner, - repo: github.context.repo.repo, - state: 'open', - labels: this.options.onlyLabels, - per_page: 100, - page - }); + private async getIssues(page: number): Promise { + const issueResult: OcotoKitIssueList = await this.client.issues.listForRepo( + { + owner: github.context.repo.owner, + repo: github.context.repo.repo, + state: 'open', + labels: this.options.onlyLabels, + per_page: 100, + page + } + ); + + return issueResult.data; } // Mark an issue as stale with a comment and a label @@ -191,7 +209,7 @@ export class IssueProcessor { } private static isLabeled(issue: Issue, label: string): boolean { - const labelComparer: (l: IssueLabel) => boolean = l => + const labelComparer: (l: Label) => boolean = l => label.localeCompare(l.name, undefined, {sensitivity: 'accent'}) === 0; return issue.labels.filter(labelComparer).length > 0; }