From cdda9c671d500c26a58759d276d3cd9b4591a6ac Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Fri, 23 Apr 2021 15:03:32 +0200 Subject: [PATCH 01/13] move refresh loop to builder and use configured refreshInterval Signed-off-by: Emma Indal --- packages/backend/src/plugins/search.ts | 7 +--- .../search-backend-node/src/IndexBuilder.ts | 37 ++++++++++--------- 2 files changed, 21 insertions(+), 23 deletions(-) diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts index a980661c85..da7ee193b4 100644 --- a/packages/backend/src/plugins/search.ts +++ b/packages/backend/src/plugins/search.ts @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { useHotCleanup } from '@backstage/backend-common'; import { createRouter } from '@backstage/plugin-search-backend'; import { IndexBuilder, @@ -35,11 +34,7 @@ export default async function createPlugin({ collator: new DefaultCatalogCollator(discovery), }); - // TODO: Move refresh loop logic into the builder. - const timerId = setInterval(() => { - indexBuilder.build(); - }, 60000); - useHotCleanup(module, () => clearInterval(timerId)); + indexBuilder.build(); return await createRouter({ engine: indexBuilder.getSearchEngine(), diff --git a/plugins/search-backend-node/src/IndexBuilder.ts b/plugins/search-backend-node/src/IndexBuilder.ts index 0f5a287037..199cb7f923 100644 --- a/plugins/search-backend-node/src/IndexBuilder.ts +++ b/plugins/search-backend-node/src/IndexBuilder.ts @@ -100,28 +100,31 @@ export class IndexBuilder { async build() { return Promise.all( Object.keys(this.collators).map(async type => { - const decorators: DocumentDecorator[] = ( - this.decorators['*'] || [] - ).concat(this.decorators[type] || []); + setInterval(async () => { + const decorators: DocumentDecorator[] = ( + this.decorators['*'] || [] + ).concat(this.decorators[type] || []); - this.logger.info( - `Collating documents for ${type} via ${this.collators[type].collate.constructor.name}`, - ); - let documents = await this.collators[type].collate.execute(); - for (let i = 0; i < decorators.length; i++) { this.logger.info( - `Decorating ${type} documents via ${decorators[i].constructor.name}`, + `Collating documents for ${type} via ${this.collators[type].collate.constructor.name}`, ); - documents = await decorators[i].execute(documents); - } + let documents = await this.collators[type].collate.execute(); + for (let i = 0; i < decorators.length; i++) { + this.logger.info( + `Decorating ${type} documents via ${decorators[i].constructor.name}`, + ); + documents = await decorators[i].execute(documents); + } - if (!documents || documents.length === 0) { - this.logger.info(`No documents for type "${type}" to index`); - return; - } + if (!documents || documents.length === 0) { + this.logger.info(`No documents for type "${type}" to index`); + return; + } - // pushing documents to index to a configured search engine. - this.searchEngine.index(type, documents); + // pushing documents to index to a configured search engine. + this.searchEngine.index(type, documents); + // refreshInterval configured in seconds, setInterval want milliseconds + }, this.collators[type].refreshInterval * 1000); }), ); } From f5ba99d61c5acc77b2bc7b6cc0dad0d2a6796993 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Fri, 23 Apr 2021 15:07:12 +0200 Subject: [PATCH 02/13] add changeset Signed-off-by: Emma Indal --- .changeset/odd-ads-invite.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/odd-ads-invite.md diff --git a/.changeset/odd-ads-invite.md b/.changeset/odd-ads-invite.md new file mode 100644 index 0000000000..d487df47d3 --- /dev/null +++ b/.changeset/odd-ads-invite.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-node': patch +--- + +Moved refresh loop to builder and use configured refreshInterval for each collator From 8c0184a1c8858e1bc1673438ae44e0f4b94f019e Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 26 Apr 2021 13:37:27 +0200 Subject: [PATCH 03/13] use timers in tests Signed-off-by: Emma Indal --- plugins/search-backend-node/src/index.test.ts | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/plugins/search-backend-node/src/index.test.ts b/plugins/search-backend-node/src/index.test.ts index 0c66db372a..86233425e7 100644 --- a/plugins/search-backend-node/src/index.test.ts +++ b/plugins/search-backend-node/src/index.test.ts @@ -54,29 +54,32 @@ describe('IndexBuilder', () => { describe('addCollator', () => { it('adds a collator', async () => { + jest.useFakeTimers(); const collatorSpy = jest.spyOn(testCollator, 'execute'); // Add a collator. testIndexBuilder.addCollator({ type: 'anything', - defaultRefreshIntervalSeconds: 600, + defaultRefreshIntervalSeconds: 6, collator: testCollator, }); // Build the index and ensure the collator was invoked. await testIndexBuilder.build(); + jest.advanceTimersByTime(6000); expect(collatorSpy).toHaveBeenCalled(); }); }); describe('addDecorator', () => { it('adds a decorator', async () => { + jest.useFakeTimers(); const decoratorSpy = jest.spyOn(testDecorator, 'execute'); // Add a collator. testIndexBuilder.addCollator({ type: 'anything', - defaultRefreshIntervalSeconds: 600, + defaultRefreshIntervalSeconds: 6, collator: testCollator, }); @@ -87,10 +90,14 @@ describe('IndexBuilder', () => { // Build the index and ensure the decorator was invoked. await testIndexBuilder.build(); + jest.advanceTimersByTime(6000); + // wait for async decorator execution + await Promise.resolve(); expect(decoratorSpy).toHaveBeenCalled(); }); it('adds a type-specific decorator', async () => { + jest.useFakeTimers(); const expectedType = 'an-expected-type'; const docFixture = { title: 'Test', @@ -105,7 +112,7 @@ describe('IndexBuilder', () => { // Add a collator. testIndexBuilder.addCollator({ type: expectedType, - defaultRefreshIntervalSeconds: 600, + defaultRefreshIntervalSeconds: 6, collator: testCollator, }); @@ -117,6 +124,9 @@ describe('IndexBuilder', () => { // Build the index and ensure the decorator was invoked. await testIndexBuilder.build(); + jest.advanceTimersByTime(6000); + // wait for async decorator execution + await Promise.resolve(); expect(decoratorSpy).toHaveBeenCalled(); expect(decoratorSpy).toHaveBeenCalledWith([docFixture]); }); @@ -136,7 +146,7 @@ describe('IndexBuilder', () => { // Add a collator. testIndexBuilder.addCollator({ type: expectedType, - defaultRefreshIntervalSeconds: 600, + defaultRefreshIntervalSeconds: 6, collator: testCollator, }); @@ -148,6 +158,7 @@ describe('IndexBuilder', () => { // Build the index and ensure the decorator was not invoked. await testIndexBuilder.build(); + jest.advanceTimersByTime(6000); expect(decoratorSpy).not.toHaveBeenCalled(); }); }); From 1c2d8436ab1af5c5424d78b8c5ac04647b3e24fe Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 26 Apr 2021 13:38:15 +0200 Subject: [PATCH 04/13] change log severity from info to debug Signed-off-by: Emma Indal --- plugins/search-backend-node/src/IndexBuilder.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/search-backend-node/src/IndexBuilder.ts b/plugins/search-backend-node/src/IndexBuilder.ts index 199cb7f923..0fc881c262 100644 --- a/plugins/search-backend-node/src/IndexBuilder.ts +++ b/plugins/search-backend-node/src/IndexBuilder.ts @@ -105,19 +105,19 @@ export class IndexBuilder { this.decorators['*'] || [] ).concat(this.decorators[type] || []); - this.logger.info( + this.logger.debug( `Collating documents for ${type} via ${this.collators[type].collate.constructor.name}`, ); let documents = await this.collators[type].collate.execute(); for (let i = 0; i < decorators.length; i++) { - this.logger.info( + this.logger.debug( `Decorating ${type} documents via ${decorators[i].constructor.name}`, ); documents = await decorators[i].execute(documents); } if (!documents || documents.length === 0) { - this.logger.info(`No documents for type "${type}" to index`); + this.logger.debug(`No documents for type "${type}" to index`); return; } From da489d2cd3a29f30749829e804ff8f9668153e0f Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 26 Apr 2021 16:38:34 +0200 Subject: [PATCH 05/13] add new Scheduler Class Signed-off-by: Emma Indal --- plugins/search-backend-node/src/Scheduler.ts | 75 ++++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 plugins/search-backend-node/src/Scheduler.ts diff --git a/plugins/search-backend-node/src/Scheduler.ts b/plugins/search-backend-node/src/Scheduler.ts new file mode 100644 index 0000000000..e54a837cee --- /dev/null +++ b/plugins/search-backend-node/src/Scheduler.ts @@ -0,0 +1,75 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Logger } from 'winston'; + +type TaskEnvelope = { + task: Function; + interval: number; +}; + +/** + * TODO: coordination, error handling + */ + +export class Scheduler { + private logger: Logger; + private schedule: TaskEnvelope[]; + private intervalTimeouts: NodeJS.Timeout[] = []; + + constructor({ logger }: { logger: Logger }) { + this.logger = logger; + this.schedule = []; + } + + /** + * Adds each task and interval to the schedule + * + */ + addToSchedule(task: Function, interval: number) { + if (this.intervalTimeouts.length) { + throw new Error( + 'Cannot add task to schedule that has already been started.', + ); + } + this.schedule.push({ task, interval }); + } + + /** + * Starts the scheduling process for each task + */ + start() { + this.logger.info('Starting all scheduled search tasks.'); + this.schedule.forEach(({ task, interval }) => { + this.intervalTimeouts.push( + setInterval(() => { + task(); + }, interval), + ); + }); + } + + /** + * Stop all scheduled tasks. + */ + stop() { + this.logger.info('Stopping all scheduled search tasks.'); + this.intervalTimeouts.forEach(timeout => { + clearInterval(timeout); + }); + this.intervalTimeouts = []; + } +} From 0b9eac3a819e3185c98a339124871f85fb2383e8 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 26 Apr 2021 16:39:33 +0200 Subject: [PATCH 06/13] refactor build method in indexBuilder Signed-off-by: Emma Indal --- .../search-backend-node/src/IndexBuilder.ts | 62 ++++++++++--------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/plugins/search-backend-node/src/IndexBuilder.ts b/plugins/search-backend-node/src/IndexBuilder.ts index 0fc881c262..768dee5b67 100644 --- a/plugins/search-backend-node/src/IndexBuilder.ts +++ b/plugins/search-backend-node/src/IndexBuilder.ts @@ -16,7 +16,7 @@ import { DocumentCollator, DocumentDecorator } from '@backstage/search-common'; import { Logger } from 'winston'; - +import { Scheduler } from './index'; import { RegisterCollatorParameters, RegisterDecoratorParameters, @@ -92,40 +92,42 @@ export class IndexBuilder { } /** - * Starts the process of executing collators and decorators and building the - * search index. - * - * TODO: But like with coordination, timing, error handling, and what have you. + * Compiles collators and decorators into tasks, which are added to a + * scheduler returned to the caller. */ - async build() { - return Promise.all( - Object.keys(this.collators).map(async type => { - setInterval(async () => { - const decorators: DocumentDecorator[] = ( - this.decorators['*'] || [] - ).concat(this.decorators[type] || []); + async build(): Promise<{ scheduler: Scheduler }> { + const scheduler = new Scheduler({ logger: this.logger }); + Object.keys(this.collators).map(type => { + scheduler.addToSchedule(async () => { + // Collate, Decorate, Index. + const decorators: DocumentDecorator[] = ( + this.decorators['*'] || [] + ).concat(this.decorators[type] || []); + + this.logger.debug( + `Collating documents for ${type} via ${this.collators[type].collate.constructor.name}`, + ); + let documents = await this.collators[type].collate.execute(); + for (let i = 0; i < decorators.length; i++) { this.logger.debug( - `Collating documents for ${type} via ${this.collators[type].collate.constructor.name}`, + `Decorating ${type} documents via ${decorators[i].constructor.name}`, ); - let documents = await this.collators[type].collate.execute(); - for (let i = 0; i < decorators.length; i++) { - this.logger.debug( - `Decorating ${type} documents via ${decorators[i].constructor.name}`, - ); - documents = await decorators[i].execute(documents); - } + documents = await decorators[i].execute(documents); + } - if (!documents || documents.length === 0) { - this.logger.debug(`No documents for type "${type}" to index`); - return; - } + if (!documents || documents.length === 0) { + this.logger.debug(`No documents for type "${type}" to index`); + return; + } - // pushing documents to index to a configured search engine. - this.searchEngine.index(type, documents); - // refreshInterval configured in seconds, setInterval want milliseconds - }, this.collators[type].refreshInterval * 1000); - }), - ); + // pushing documents to index to a configured search engine. + this.searchEngine.index(type, documents); + }, this.collators[type].refreshInterval * 1000); + }); + + return { + scheduler, + }; } } From 9dff1a9839704af0ae722372a728b504ebe27bec Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 26 Apr 2021 16:43:58 +0200 Subject: [PATCH 07/13] use scheduler and fix tests Signed-off-by: Emma Indal --- packages/backend/src/plugins/search.ts | 5 ++++- plugins/search-backend-node/src/index.test.ts | 15 ++++++++++----- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts index da7ee193b4..03c7d44a18 100644 --- a/packages/backend/src/plugins/search.ts +++ b/packages/backend/src/plugins/search.ts @@ -34,7 +34,10 @@ export default async function createPlugin({ collator: new DefaultCatalogCollator(discovery), }); - indexBuilder.build(); + const { scheduler } = await indexBuilder.build(); + + scheduler.start(); + useHotCleanup(module, () => scheduler.stop()); return await createRouter({ engine: indexBuilder.getSearchEngine(), diff --git a/plugins/search-backend-node/src/index.test.ts b/plugins/search-backend-node/src/index.test.ts index 86233425e7..e1dc511b97 100644 --- a/plugins/search-backend-node/src/index.test.ts +++ b/plugins/search-backend-node/src/index.test.ts @@ -65,7 +65,8 @@ describe('IndexBuilder', () => { }); // Build the index and ensure the collator was invoked. - await testIndexBuilder.build(); + const { scheduler } = await testIndexBuilder.build(); + scheduler.start(); jest.advanceTimersByTime(6000); expect(collatorSpy).toHaveBeenCalled(); }); @@ -89,7 +90,8 @@ describe('IndexBuilder', () => { }); // Build the index and ensure the decorator was invoked. - await testIndexBuilder.build(); + const { scheduler } = await testIndexBuilder.build(); + scheduler.start(); jest.advanceTimersByTime(6000); // wait for async decorator execution await Promise.resolve(); @@ -123,7 +125,8 @@ describe('IndexBuilder', () => { }); // Build the index and ensure the decorator was invoked. - await testIndexBuilder.build(); + const { scheduler } = await testIndexBuilder.build(); + scheduler.start(); jest.advanceTimersByTime(6000); // wait for async decorator execution await Promise.resolve(); @@ -138,7 +141,7 @@ describe('IndexBuilder', () => { text: 'Test text.', location: '/test/location', }; - jest + const collatorSpy = jest .spyOn(testCollator, 'execute') .mockImplementation(async () => [docFixture]); const decoratorSpy = jest.spyOn(testDecorator, 'execute'); @@ -157,8 +160,10 @@ describe('IndexBuilder', () => { }); // Build the index and ensure the decorator was not invoked. - await testIndexBuilder.build(); + const { scheduler } = await testIndexBuilder.build(); + scheduler.start(); jest.advanceTimersByTime(6000); + expect(collatorSpy).toHaveBeenCalled(); expect(decoratorSpy).not.toHaveBeenCalled(); }); }); From 50b15bfeb3495f623f0bce521ad9e5d5b383951d Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 26 Apr 2021 16:44:32 +0200 Subject: [PATCH 08/13] imports and exports Signed-off-by: Emma Indal --- packages/backend/src/plugins/search.ts | 1 + plugins/search-backend-node/src/index.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/backend/src/plugins/search.ts b/packages/backend/src/plugins/search.ts index 03c7d44a18..dcd48aaf40 100644 --- a/packages/backend/src/plugins/search.ts +++ b/packages/backend/src/plugins/search.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { useHotCleanup } from '@backstage/backend-common'; import { createRouter } from '@backstage/plugin-search-backend'; import { IndexBuilder, diff --git a/plugins/search-backend-node/src/index.ts b/plugins/search-backend-node/src/index.ts index a3a0eb855e..7ee75e3d59 100644 --- a/plugins/search-backend-node/src/index.ts +++ b/plugins/search-backend-node/src/index.ts @@ -15,5 +15,6 @@ */ export { IndexBuilder } from './IndexBuilder'; +export { Scheduler } from './Scheduler'; export { LunrSearchEngine } from './engines'; export type { SearchEngine } from './types'; From fafd8043649dd7432bde654eaee11cf58539544f Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 26 Apr 2021 16:49:32 +0200 Subject: [PATCH 09/13] update changeset Signed-off-by: Emma Indal --- .changeset/odd-ads-invite.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/odd-ads-invite.md b/.changeset/odd-ads-invite.md index d487df47d3..0dde3d0c1f 100644 --- a/.changeset/odd-ads-invite.md +++ b/.changeset/odd-ads-invite.md @@ -2,4 +2,4 @@ '@backstage/plugin-search-backend-node': patch --- -Moved refresh loop to builder and use configured refreshInterval for each collator +Introduced Scheduler which is responsible for adding new tasks to a schedule together with it's interval timer as well as starting and stopping the scheduler processes. From e1e757569fbb2f3b9b30ab48229ec2ef97cc0c7f Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Mon, 26 Apr 2021 17:32:54 +0200 Subject: [PATCH 10/13] prefix changeset with search Signed-off-by: Emma Indal --- .changeset/{odd-ads-invite.md => search-odd-ads-invite.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .changeset/{odd-ads-invite.md => search-odd-ads-invite.md} (100%) diff --git a/.changeset/odd-ads-invite.md b/.changeset/search-odd-ads-invite.md similarity index 100% rename from .changeset/odd-ads-invite.md rename to .changeset/search-odd-ads-invite.md From 266e46d3c3a4f73f5c27f50c4275be254b4ed9e9 Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Tue, 27 Apr 2021 11:07:08 +0200 Subject: [PATCH 11/13] switch from map to forEach Signed-off-by: Emma Indal --- plugins/search-backend-node/src/IndexBuilder.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/search-backend-node/src/IndexBuilder.ts b/plugins/search-backend-node/src/IndexBuilder.ts index 768dee5b67..71d374e704 100644 --- a/plugins/search-backend-node/src/IndexBuilder.ts +++ b/plugins/search-backend-node/src/IndexBuilder.ts @@ -98,7 +98,7 @@ export class IndexBuilder { async build(): Promise<{ scheduler: Scheduler }> { const scheduler = new Scheduler({ logger: this.logger }); - Object.keys(this.collators).map(type => { + Object.keys(this.collators).forEach(type => { scheduler.addToSchedule(async () => { // Collate, Decorate, Index. const decorators: DocumentDecorator[] = ( From 4906a4c2210c38e1364bf69c882ab90df2fc7f3c Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Tue, 27 Apr 2021 11:07:55 +0200 Subject: [PATCH 12/13] rename test file to be more specific to IndexBuilder Signed-off-by: Emma Indal --- .../src/{index.test.ts => IndexBuilder.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename plugins/search-backend-node/src/{index.test.ts => IndexBuilder.test.ts} (100%) diff --git a/plugins/search-backend-node/src/index.test.ts b/plugins/search-backend-node/src/IndexBuilder.test.ts similarity index 100% rename from plugins/search-backend-node/src/index.test.ts rename to plugins/search-backend-node/src/IndexBuilder.test.ts From 6c46febf6326a8a119529e2b37f2029178fa7a3e Mon Sep 17 00:00:00 2001 From: Emma Indal Date: Tue, 27 Apr 2021 11:08:37 +0200 Subject: [PATCH 13/13] Add tests for Scheduler Signed-off-by: Emma Indal --- .../search-backend-node/src/Scheduler.test.ts | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 plugins/search-backend-node/src/Scheduler.test.ts diff --git a/plugins/search-backend-node/src/Scheduler.test.ts b/plugins/search-backend-node/src/Scheduler.test.ts new file mode 100644 index 0000000000..43fdb7ea92 --- /dev/null +++ b/plugins/search-backend-node/src/Scheduler.test.ts @@ -0,0 +1,77 @@ +/* + * Copyright 2021 Spotify AB + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { getVoidLogger } from '@backstage/backend-common'; +import { Scheduler } from './index'; + +describe('Scheduler', () => { + let testScheduler: Scheduler; + + beforeEach(() => { + const logger = getVoidLogger(); + testScheduler = new Scheduler({ + logger, + }); + }); + + describe('addToSchedule', () => { + it('should not add a task and interval to schedule, if already started', async () => { + jest.useFakeTimers(); + const mockTask1 = jest.fn(); + const mockTask2 = jest.fn(); + + // Add a task and interval to schedule + testScheduler.addToSchedule(mockTask1, 2); + + // Starts scheduling process + testScheduler.start(); + + // Throws Error if task and interval is added to a already started schedule + expect(() => testScheduler.addToSchedule(mockTask2, 2)).toThrowError(); + + jest.runOnlyPendingTimers(); + expect(mockTask1).toHaveBeenCalled(); + expect(mockTask2).not.toHaveBeenCalled(); + }); + + it('should be possible to add a task and interval to schedule, if already started, but stopped in between', async () => { + jest.useFakeTimers(); + const mockTask1 = jest.fn(); + const mockTask2 = jest.fn(); + + // Add a task and interval to schedule + testScheduler.addToSchedule(mockTask1, 2); + + // Starts scheduling process + testScheduler.start(); + + // Stop scheduling process + testScheduler.stop(); + + // Should't throw error, as it is stopped. + expect(() => + testScheduler.addToSchedule(mockTask2, 4), + ).not.toThrowError(); + + // Starts scheduling process + testScheduler.start(); + + jest.runOnlyPendingTimers(); + expect(mockTask1).toHaveBeenCalled(); + expect(mockTask2).toHaveBeenCalled(); + }); + }); +});