From 17ebcb09cd46d7751480467b60c761593bdbdd4f Mon Sep 17 00:00:00 2001 From: Joe Porpeglia Date: Tue, 11 Jan 2022 18:47:44 -0500 Subject: [PATCH] Refactor router tests Adds a `createApp` helper instead of creating the express app in `beforeEach`. This allows tests to mock behavior of router creation, like toggling certain middleware based on config values. Signed-off-by: Joe Porpeglia --- .../src/service/router.test.ts | 91 +++++++++++++------ .../techdocs-backend/src/service/router.ts | 2 +- 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/plugins/techdocs-backend/src/service/router.test.ts b/plugins/techdocs-backend/src/service/router.test.ts index 83a8707876..dfe3bd417e 100644 --- a/plugins/techdocs-backend/src/service/router.test.ts +++ b/plugins/techdocs-backend/src/service/router.test.ts @@ -31,7 +31,12 @@ import express, { Response } from 'express'; import request from 'supertest'; import { DocsSynchronizer, DocsSynchronizerSyncOpts } from './DocsSynchronizer'; import { CachedEntityLoader } from './CachedEntityLoader'; -import { createEventStream, createHttpResponse, createRouter } from './router'; +import { + createEventStream, + createHttpResponse, + createRouter, + RouterOptions, +} from './router'; import { TechDocsCache } from '../cache'; jest.mock('@backstage/catalog-client'); @@ -69,6 +74,13 @@ Content-Length: ${content.length}\n\n`), ]); }; +const createApp = async (options: RouterOptions) => { + const app = express(); + app.use(await createRouter(options)); + app.use(errorHandler()); + return app; +}; + describe('createRouter', () => { const entity = { apiVersion: 'backstage.io/v1alpha1', @@ -108,8 +120,22 @@ describe('createRouter', () => { const cache: jest.Mocked = { getClient: jest.fn(), }; - - let app: express.Express; + const outOfTheBoxOptions = { + preparers, + generators, + publisher, + config: new ConfigReader({}), + logger: getVoidLogger(), + discovery, + cache, + }; + const recommendedOptions = { + publisher, + config: new ConfigReader({}), + logger: getVoidLogger(), + discovery, + cache, + }; beforeEach(() => { jest.resetAllMocks(); @@ -125,33 +151,13 @@ describe('createRouter', () => { ); MockTechDocsCache.get.mockResolvedValue(undefined); MockTechDocsCache.set.mockResolvedValue(); - - const outOfTheBoxRouter = await createRouter({ - preparers, - generators, - publisher, - config: new ConfigReader({}), - logger: getVoidLogger(), - discovery, - cache, - }); - const recommendedRouter = await createRouter({ - publisher, - config: new ConfigReader({}), - logger: getVoidLogger(), - discovery, - cache, - }); - - app = express(); - app.use(outOfTheBoxRouter); - app.use('/recommended', recommendedRouter); - app.use(errorHandler()); }); describe('GET /sync/:namespace/:kind/:name', () => { describe('accept application/json', () => { it('should return not found if entity is not found', async () => { + const app = await createApp(outOfTheBoxOptions); + MockCachedEntityLoader.prototype.load.mockResolvedValue(undefined); const response = await request(app) @@ -162,6 +168,8 @@ describe('createRouter', () => { }); it('should return not found if entity has no uid', async () => { + const app = await createApp(outOfTheBoxOptions); + MockCachedEntityLoader.prototype.load.mockResolvedValue( entityWithoutMetadata, ); @@ -174,6 +182,8 @@ describe('createRouter', () => { }); it('should not check for an update without local builder', async () => { + const app = await createApp(outOfTheBoxOptions); + MockedConfigReader.prototype.getString.mockReturnValue('external'); MockCachedEntityLoader.prototype.load.mockResolvedValue(entity); MockDocsSynchronizer.prototype.doCacheSync.mockImplementation( @@ -189,11 +199,13 @@ describe('createRouter', () => { }); it('should error if missing builder', async () => { + const app = await createApp(recommendedOptions); + MockedConfigReader.prototype.getString.mockReturnValue('local'); MockCachedEntityLoader.prototype.load.mockResolvedValue(entity); const response = await request(app) - .get('/recommended/sync/default/Component/test') + .get('/sync/default/Component/test') .send(); expect(response.status).toBe(500); @@ -205,6 +217,8 @@ describe('createRouter', () => { }); it('should execute synchronization', async () => { + const app = await createApp(outOfTheBoxOptions); + MockedConfigReader.prototype.getString.mockReturnValue('local'); MockCachedEntityLoader.prototype.load.mockResolvedValue(entity); MockDocsSynchronizer.prototype.doSync.mockImplementation( @@ -228,6 +242,8 @@ describe('createRouter', () => { }); it('should return on updated', async () => { + const app = await createApp(outOfTheBoxOptions); + MockedConfigReader.prototype.getString.mockReturnValue('local'); MockCachedEntityLoader.prototype.load.mockResolvedValue(entity); MockDocsSynchronizer.prototype.doSync.mockImplementation( @@ -254,6 +270,8 @@ describe('createRouter', () => { describe('accept text/event-stream', () => { it('should return not found if entity is not found', async () => { + const app = await createApp(outOfTheBoxOptions); + MockCachedEntityLoader.prototype.load.mockResolvedValue(undefined); const response = await request(app) @@ -265,6 +283,8 @@ describe('createRouter', () => { }); it('should return not found if entity has no uid', async () => { + const app = await createApp(outOfTheBoxOptions); + MockCachedEntityLoader.prototype.load.mockResolvedValue( entityWithoutMetadata, ); @@ -278,6 +298,8 @@ describe('createRouter', () => { }); it('should not check for an update without local builder', async () => { + const app = await createApp(outOfTheBoxOptions); + MockedConfigReader.prototype.getString.mockReturnValue('external'); MockCachedEntityLoader.prototype.load.mockResolvedValue(entity); MockDocsSynchronizer.prototype.doCacheSync.mockImplementation( @@ -301,11 +323,13 @@ data: {"updated":false} }); it('should error if missing builder', async () => { + const app = await createApp(recommendedOptions); + MockedConfigReader.prototype.getString.mockReturnValue('local'); MockCachedEntityLoader.prototype.load.mockResolvedValue(entity); const response = await request(app) - .get('/recommended/sync/default/Component/test') + .get('/sync/default/Component/test') .set('accept', 'text/event-stream') .send(); @@ -322,6 +346,8 @@ data: "Invalid configuration. 'techdocs.builder' was set to 'local' but no 'prep }); it('should execute synchronization', async () => { + const app = await createApp(outOfTheBoxOptions); + MockedConfigReader.prototype.getString.mockReturnValue('local'); MockCachedEntityLoader.prototype.load.mockResolvedValue(entity); MockDocsSynchronizer.prototype.doSync.mockImplementation( @@ -348,6 +374,8 @@ data: "Invalid configuration. 'techdocs.builder' was set to 'local' but no 'prep }); it('should return an event-stream', async () => { + const app = await createApp(outOfTheBoxOptions); + MockedConfigReader.prototype.getString.mockReturnValue('local'); MockCachedEntityLoader.prototype.load.mockResolvedValue(entity); MockDocsSynchronizer.prototype.doSync.mockImplementation( @@ -386,7 +414,8 @@ data: {"updated":true} describe('GET /static/docs', () => { it('should return assets from cache', async () => { - MockCachedEntityLoader.prototype.load.mockResolvedValue(entity); + const app = await createApp(outOfTheBoxOptions); + MockTechDocsCache.get.mockResolvedValue( getMockHttpResponseFor('content'), ); @@ -400,6 +429,12 @@ data: {"updated":true} }); it('should not return assets without corresponding entity access', async () => { + MockedConfigReader.prototype.getOptionalBoolean.mockImplementation(key => + key === 'permission.enabled' ? true : undefined, + ); + + const app = await createApp(outOfTheBoxOptions); + MockCachedEntityLoader.prototype.load.mockResolvedValue(undefined); const response = await request(app) diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index 1b438c2d3d..7a07b74fa9 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -66,7 +66,7 @@ type RecommendedDeploymentOptions = { /** * One of the two deployment configurations must be provided. */ -type RouterOptions = +export type RouterOptions = | RecommendedDeploymentOptions | OutOfTheBoxDeploymentOptions;