diff --git a/__tests__/state.spec.ts b/__tests__/state.spec.ts new file mode 100644 index 00000000..8c59d861 --- /dev/null +++ b/__tests__/state.spec.ts @@ -0,0 +1,213 @@ +import {IStateStorage} from '../src/interfaces/state/state-storage'; +import {IIssuesProcessorOptions} from '../src/interfaces/issues-processor-options'; +import {DefaultProcessorOptions} from './constants/default-processor-options'; +import {Issue} from '../src/classes/issue'; +import {generateIssue} from './functions/generate-issue'; +import {IssuesProcessorMock} from './classes/issues-processor-mock'; +import {IssueID, State} from '../src/classes/state/state'; +import {IState} from '../src/interfaces/state/state'; +import * as core from '@actions/core'; + +const stateStorage: IStateStorage = { + restore(): Promise<string> { + return Promise.resolve(''); + }, + save(serializedState: string): Promise<void> { + return Promise.resolve(); + } +}; + +const getProcessedIssuesIDs = (state: IState): Set<IssueID> => + (state as unknown as {processedIssuesIDs: Set<IssueID>}).processedIssuesIDs; + +describe('state', (): void => { + let debugSpy: jest.SpyInstance; + let infoSpy: jest.SpyInstance; + let warningSpy: jest.SpyInstance; + beforeEach(() => { + debugSpy = jest.spyOn(core, 'debug'); + infoSpy = jest.spyOn(core, 'info'); + warningSpy = jest.spyOn(core, 'warning'); + }); + + afterEach(() => { + jest.resetAllMocks(); + jest.clearAllMocks(); + }); + + it('rehydrate/persist should not be called during processing', async () => { + const opts: IIssuesProcessorOptions = { + ...DefaultProcessorOptions, + daysBeforeClose: 0 + }; + const TestIssueList: Issue[] = [ + generateIssue(opts, 1, 'An issue with no label', '2020-01-01T17:00:00Z') + ]; + const state = new State(stateStorage, opts); + const restoreSpy = jest.spyOn(stateStorage, 'restore'); + const saveSpy = jest.spyOn(stateStorage, 'save'); + const processor = new IssuesProcessorMock( + opts, + state, + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() + ); + + await processor.processIssues(1); + expect(restoreSpy).toHaveBeenCalledTimes(0); + expect(saveSpy).toHaveBeenCalledTimes(0); + }); + + it('state should be marked with the processed issue', async () => { + const opts: IIssuesProcessorOptions = { + ...DefaultProcessorOptions, + daysBeforeClose: 0 + }; + const testIssue1 = generateIssue( + opts, + 1, + 'An issue with no label', + '2020-01-01T17:00:00Z' + ); + const TestIssueList: Issue[] = [testIssue1]; + const state = new State(stateStorage, opts); + const addIssueToProcessedSpy = jest.spyOn(state, 'addIssueToProcessed'); + const processor = new IssuesProcessorMock( + opts, + state, + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() + ); + + await processor.processIssues(1); + expect(addIssueToProcessedSpy).toHaveBeenCalledTimes(1); + expect(addIssueToProcessedSpy).toHaveBeenCalledWith(testIssue1); + + expect(debugSpy).toHaveBeenCalledWith('state: reset'); + expect(debugSpy).toHaveBeenCalledWith('state: mark 1 as processed'); + }); + + it('issueProcessor should skip the issue marked as proceeded', async () => { + const opts: IIssuesProcessorOptions = { + ...DefaultProcessorOptions, + daysBeforeClose: 0 + }; + const testIssue1 = generateIssue( + opts, + 1, + 'An issue with no label', + '2020-01-01T17:00:00Z' + ); + const testIssue2 = generateIssue( + opts, + 2, + 'An issue with no label', + '2020-01-01T17:00:00Z' + ); + const TestIssueList: Issue[] = [testIssue1, testIssue2]; + const state = new State(stateStorage, opts); + state.addIssueToProcessed(testIssue1); + debugSpy.mockClear(); + const addIssueToProcessedSpy = jest.spyOn(state, 'addIssueToProcessed'); + const isIssueProcessedSpy = jest.spyOn(state, 'isIssueProcessed'); + const processor = new IssuesProcessorMock( + opts, + state, + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() + ); + + await processor.processIssues(1); + expect(addIssueToProcessedSpy).toHaveBeenCalledTimes(1); + expect(addIssueToProcessedSpy).toHaveBeenCalledWith(testIssue2); + expect(isIssueProcessedSpy).toHaveBeenCalledTimes(2); + expect(isIssueProcessedSpy).toHaveBeenCalledWith(testIssue1); + expect(isIssueProcessedSpy).toHaveBeenCalledWith(testIssue2); + expect(processor.staleIssues.length).toStrictEqual(1); + expect(processor.closedIssues.length).toStrictEqual(1); + + expect(debugSpy).toHaveBeenCalledWith('state: reset'); + expect(debugSpy).toHaveBeenCalledWith('state: mark 2 as processed'); + }); + + it('state should not be reset if not all issues are proceeded', async () => { + const opts: IIssuesProcessorOptions = { + ...DefaultProcessorOptions, + operationsPerRun: 1, + daysBeforeClose: 0 + }; + const testIssue1 = generateIssue( + opts, + 1, + 'An issue with no label', + '2020-01-01T17:00:00Z' + ); + const testIssue2 = generateIssue( + opts, + 2, + 'An issue with no label', + '2020-01-01T17:00:00Z' + ); + const TestIssueList: Issue[] = [testIssue1, testIssue2]; + const state = new State(stateStorage, opts); + const resetSpy = jest.spyOn(state, 'reset'); + const processor = new IssuesProcessorMock( + opts, + state, + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() + ); + + await processor.processIssues(1); + // make sure not all issues are proceeded + expect(warningSpy.mock.calls[2][0]).toContain( + 'No more operations left! Exiting...' + ); + + expect(resetSpy).toHaveBeenCalledTimes(0); + expect(debugSpy).toHaveBeenCalledWith('state: mark 1 as processed'); + }); + + it('state should be reset if all issues are proceeded', async () => { + const opts: IIssuesProcessorOptions = { + ...DefaultProcessorOptions, + daysBeforeClose: 0 + }; + const testIssue1 = generateIssue( + opts, + 1, + 'An issue with no label', + '2020-01-01T17:00:00Z' + ); + const testIssue2 = generateIssue( + opts, + 2, + 'An issue with no label', + '2020-01-01T17:00:00Z' + ); + const TestIssueList: Issue[] = [testIssue1, testIssue2]; + const state = new State(stateStorage, opts); + const resetSpy = jest.spyOn(state, 'reset'); + const processor = new IssuesProcessorMock( + opts, + state, + async p => (p === 1 ? TestIssueList : []), + async () => [], + async () => new Date().toDateString() + ); + + await processor.processIssues(1); + // make sure all issues are proceeded + expect(infoSpy.mock.calls[71][0]).toContain( + 'No more issues found to process. Exiting...' + ); + + expect(resetSpy).toHaveBeenCalledTimes(1); + expect(debugSpy).toHaveBeenCalledWith('state: mark 1 as processed'); + expect(debugSpy).toHaveBeenCalledWith('state: mark 2 as processed'); + }); +});