From fa4dca37f71b4a55ea8f4bcb20f6c0d3d6016a50 Mon Sep 17 00:00:00 2001 From: Sergey Dolin Date: Sun, 2 Jul 2023 11:01:29 +0200 Subject: [PATCH] Add unit tests --- dist/index.js | 10 +- src/classes/state/state.spec.ts | 161 ++++++++++++++++++++++++++++++++ src/classes/state/state.ts | 14 +-- 3 files changed, 172 insertions(+), 13 deletions(-) create mode 100644 src/classes/state/state.spec.ts diff --git a/dist/index.js b/dist/index.js index bbc76f6b..63b544e2 100644 --- a/dist/index.js +++ b/dist/index.js @@ -2120,13 +2120,11 @@ class State { } addIssueToProcessed(issue) { core.debug(`state: mark ${issue.number} as processed`); - if (!this.debug) - this.processedIssuesIDs.add(issue.number); + this.processedIssuesIDs.add(issue.number); } reset() { core.debug('state: reset'); - if (!this.debug) - this.processedIssuesIDs.clear(); + this.processedIssuesIDs.clear(); } deserialize(serialized) { const issueIDs = serialized @@ -2142,7 +2140,7 @@ class State { persist() { return __awaiter(this, void 0, void 0, function* () { if (this.debug) { - core.debug('The state is not persisted in the debug mode'); + core.warning('The state is not persisted in the debug mode'); return; } core.info(`state: persisting info about ${this.processedIssuesIDs.size} issue(s)`); @@ -2154,7 +2152,7 @@ class State { this.reset(); const serialized = yield this.stateStorage.restore(); this.deserialize(serialized); - core.info(`state: rehydrated info about ${this.processedIssuesIDs.size} issue(s)`); + core.info(`state: rehydrated with info about ${this.processedIssuesIDs.size} issue(s)`); }); } } diff --git a/src/classes/state/state.spec.ts b/src/classes/state/state.spec.ts new file mode 100644 index 00000000..e5be5cb7 --- /dev/null +++ b/src/classes/state/state.spec.ts @@ -0,0 +1,161 @@ +import {IssueID, State} from './state'; +import {IIssuesProcessorOptions} from '../../interfaces/issues-processor-options'; +import {IIssue} from '../../interfaces/issue'; +import {IState} from '../../interfaces/state/state'; +import * as core from '@actions/core'; + +const mockStorage = { + save: () => Promise.resolve(), + restore: () => Promise.resolve('') +}; + +const getProcessedIssuesIDs = (state: IState): Set => + (state as unknown as {processedIssuesIDs: Set}).processedIssuesIDs; + +describe('State', () => { + 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(); + }); + + describe('initializing and resetting', () => { + it('new state should not contain any issues marked as proceeded', async () => { + const state = new State( + mockStorage, + {} as unknown as IIssuesProcessorOptions + ); + expect(getProcessedIssuesIDs(state)).toEqual(new Set()); + expect(debugSpy).not.toHaveBeenCalled(); + }); + it('reset state should not contain any issues marked as proceeded', async () => { + const state = new State( + mockStorage, + {} as unknown as IIssuesProcessorOptions + ); + state.addIssueToProcessed({number: 1} as unknown as IIssue); + expect(getProcessedIssuesIDs(state)).not.toEqual(new Set()); + state.reset(); + expect(getProcessedIssuesIDs(state)).toEqual(new Set()); + expect(debugSpy).toHaveBeenCalledTimes(2); + expect(debugSpy).toHaveBeenCalledWith('state: reset'); + }); + }); + describe('marking as proceeded', () => { + it('state marked with issues 1,2,3 as proceeded should report the as proceeded', async () => { + const state = new State( + mockStorage, + {} as unknown as IIssuesProcessorOptions + ); + state.addIssueToProcessed({number: 1} as unknown as IIssue); + state.addIssueToProcessed({number: 2} as unknown as IIssue); + state.addIssueToProcessed({number: 3} as unknown as IIssue); + expect(getProcessedIssuesIDs(state)).toEqual(new Set([1, 2, 3])); + expect( + state.isIssueProcessed({number: 1} as unknown as IIssue) + ).toBeTruthy(); + expect( + state.isIssueProcessed({number: 2} as unknown as IIssue) + ).toBeTruthy(); + expect( + state.isIssueProcessed({number: 3} as unknown as IIssue) + ).toBeTruthy(); + expect( + state.isIssueProcessed({number: 0} as unknown as IIssue) + ).toBeFalsy(); + expect( + state.isIssueProcessed({number: 4} as unknown as IIssue) + ).toBeFalsy(); + expect(debugSpy).toHaveBeenCalledTimes(3); + expect(debugSpy).toHaveBeenCalledWith('state: mark 1 as processed'); + expect(debugSpy).toHaveBeenCalledWith('state: mark 2 as processed'); + expect(debugSpy).toHaveBeenCalledWith('state: mark 3 as processed'); + }); + }); + describe('persisting', () => { + it('[1,2,3] should be serialized and persisted as to "1|2|3|', async () => { + const mockStorage = { + save: jest.fn().mockReturnValue(Promise.resolve()), + async restore(): Promise { + return ''; + } + }; + const state = new State( + mockStorage, + {} as unknown as IIssuesProcessorOptions + ); + state.addIssueToProcessed({number: 1} as unknown as IIssue); + state.addIssueToProcessed({number: 2} as unknown as IIssue); + state.addIssueToProcessed({number: 3} as unknown as IIssue); + await state.persist(); + expect(mockStorage.save).toHaveBeenCalledTimes(1); + expect(mockStorage.save).toHaveBeenCalledWith('1|2|3'); + expect(infoSpy).toHaveBeenCalledTimes(1); + expect(infoSpy).toHaveBeenCalledWith( + 'state: persisting info about 3 issue(s)' + ); + }); + }); + describe('rehydrating', () => { + it('"1|2|3" should be rehydrate to the IState with issues 1,2,3 marked as proceeded', async () => { + const mockStorage = { + save: () => Promise.resolve(), + restore: () => Promise.resolve('1|2|3') + }; + const state = new State( + mockStorage, + {} as unknown as IIssuesProcessorOptions + ); + await state.rehydrate(); + const processedIssuesIDs = ( + state as unknown as {processedIssuesIDs: Set} + ).processedIssuesIDs; + expect(processedIssuesIDs).toEqual(new Set([1, 2, 3])); + expect(infoSpy).toHaveBeenCalledTimes(1); + expect(infoSpy).toHaveBeenCalledWith( + 'state: rehydrated with info about 3 issue(s)' + ); + }); + }); + describe('debugOnly', () => { + it('state should persisted if debugOnly not set', () => { + const mockStorage = { + save: jest.fn().mockReturnValue(Promise.resolve()), + async restore(): Promise { + return ''; + } + }; + const state = new State( + mockStorage, + {} as unknown as IIssuesProcessorOptions + ); + state.persist(); + expect(mockStorage.save).toHaveBeenCalledTimes(1); + }); + it('state should not be persisted if debugOnly set true', () => { + const mockStorage = { + save: jest.fn().mockReturnValue(Promise.resolve()), + async restore(): Promise { + return ''; + } + }; + const state = new State(mockStorage, { + debugOnly: true + } as unknown as IIssuesProcessorOptions); + state.persist(); + expect(mockStorage.save).not.toHaveBeenCalled(); + expect(warningSpy).toHaveBeenCalledTimes(1); + expect(warningSpy).toHaveBeenCalledWith( + 'The state is not persisted in the debug mode' + ); + }); + }); +}); diff --git a/src/classes/state/state.ts b/src/classes/state/state.ts index 8f431487..ce16eb29 100644 --- a/src/classes/state/state.ts +++ b/src/classes/state/state.ts @@ -1,8 +1,8 @@ -import {Issue} from '../issue'; import {IState} from '../../interfaces/state/state'; import * as core from '@actions/core'; import {IIssuesProcessorOptions} from '../../interfaces/issues-processor-options'; import {IStateStorage} from '../../interfaces/state/state-storage'; +import {IIssue} from '../../interfaces/issue'; export type IssueID = number; @@ -22,18 +22,18 @@ export class State implements IState { this.stateStorage = stateStorage; } - isIssueProcessed(issue: Issue) { + isIssueProcessed(issue: IIssue) { return this.processedIssuesIDs.has(issue.number); } - addIssueToProcessed(issue: Issue) { + addIssueToProcessed(issue: IIssue) { core.debug(`state: mark ${issue.number} as processed`); - if (!this.debug) this.processedIssuesIDs.add(issue.number); + this.processedIssuesIDs.add(issue.number); } reset() { core.debug('state: reset'); - if (!this.debug) this.processedIssuesIDs.clear(); + this.processedIssuesIDs.clear(); } private deserialize(serialized: string) { @@ -51,7 +51,7 @@ export class State implements IState { async persist(): Promise { if (this.debug) { - core.debug('The state is not persisted in the debug mode'); + core.warning('The state is not persisted in the debug mode'); return; } core.info( @@ -65,7 +65,7 @@ export class State implements IState { const serialized = await this.stateStorage.restore(); this.deserialize(serialized); core.info( - `state: rehydrated info about ${this.processedIssuesIDs.size} issue(s)` + `state: rehydrated with info about ${this.processedIssuesIDs.size} issue(s)` ); } }