From 77c42b3fd3ea4f2a6791e76554b8d70228d54b51 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 3 Oct 2020 14:53:45 +0200 Subject: [PATCH] backend-common: refactor AzureUrlReader to test public methods using msw --- packages/backend-common/package.json | 1 + .../src/reading/AzureUrlReader.test.ts | 199 +++++++++--------- .../src/reading/AzureUrlReader.ts | 34 ++- packages/backend-common/src/setupTests.ts | 2 - 4 files changed, 113 insertions(+), 123 deletions(-) diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index b2550c6551..08b2cb71a0 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -73,6 +73,7 @@ "http-errors": "^1.7.3", "jest": "^26.0.1", "jest-fetch-mock": "^3.0.3", + "msw": "^0.20.5", "supertest": "^4.0.2" }, "files": [ diff --git a/packages/backend-common/src/reading/AzureUrlReader.test.ts b/packages/backend-common/src/reading/AzureUrlReader.test.ts index 50db27b314..c9e1fc5bc7 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.test.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.test.ts @@ -14,116 +14,111 @@ * limitations under the License. */ -import { AzureUrlReader, readConfig } from './AzureUrlReader'; +import { rest } from 'msw'; +import { setupServer } from 'msw/node'; import { ConfigReader } from '@backstage/config'; +import { getVoidLogger } from '../logging'; +import { AzureUrlReader } from './AzureUrlReader'; -const host = 'dev.azure.com'; +const logger = getVoidLogger(); describe('AzureUrlReader', () => { - const createConfig = (token: string | undefined) => - ConfigReader.fromConfigs([ - { - context: '', - data: { - integrations: { - azure: [ - { - host, - token, - }, - ], - }, - }, - }, - ]); + const worker = setupServer(); - it('should build raw api', () => { - const processor = new AzureUrlReader( - readConfig(createConfig(undefined))[0], + beforeAll(() => worker.listen({ onUnhandledRequest: 'error' })); + afterAll(() => worker.close()); + + beforeEach(() => { + worker.use( + rest.get('*', (req, res, ctx) => + res( + ctx.status(200), + ctx.json({ + url: req.url.toString(), + headers: req.headers.getAllHeaders(), + }), + ), + ), ); - const tests = [ - { - target: - 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml&version=GBmaster', - url: new URL( - 'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml&version=master', - ), - err: undefined, - }, - { - target: - 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml', - url: new URL( - 'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml', - ), - err: undefined, - }, - { - target: 'https://api.com/a/b/blob/master/path/to/c.yaml', - url: null, - err: - 'Incorrect url: https://api.com/a/b/blob/master/path/to/c.yaml, Error: Wrong Azure Devops URL or Invalid file path', - }, - { - target: 'com/a/b/blob/master/path/to/c.yaml', - url: null, - err: - 'Incorrect url: com/a/b/blob/master/path/to/c.yaml, TypeError: Invalid URL: com/a/b/blob/master/path/to/c.yaml', - }, - ]; + }); + afterEach(() => worker.resetHandlers()); - for (const test of tests) { - if (test.err) { - expect(() => processor.buildRawUrl(test.target)).toThrowError(test.err); - } else if (test.url) { - expect(processor.buildRawUrl(test.target).toString()).toEqual( - test.url.toString(), - ); - } else { - throw new Error( - 'This should not have happened. Either err or url should have matched.', - ); - } - } + const createConfig = (token?: string) => + new ConfigReader( + { + integrations: { azure: [{ host: 'dev.azure.com', token }] }, + }, + 'test-config', + ); + + it.each([ + { + url: + 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml&version=GBmaster', + config: createConfig(), + response: expect.objectContaining({ + url: + 'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml&version=master', + }), + }, + { + url: + 'https://dev.azure.com/org-name/project-name/_git/repo-name?path=my-template.yaml', + config: createConfig(), + response: expect.objectContaining({ + url: + 'https://dev.azure.com/org-name/project-name/_apis/git/repositories/repo-name/items?path=my-template.yaml', + }), + }, + { + url: 'https://dev.azure.com/a/b/_git/repo-name?path=my-template.yaml', + config: createConfig('0123456789'), + response: expect.objectContaining({ + headers: expect.objectContaining({ + authorization: 'Basic OjAxMjM0NTY3ODk=', + }), + }), + }, + { + url: 'https://dev.azure.com/a/b/_git/repo-name?path=my-template.yaml', + config: createConfig(undefined), + response: expect.objectContaining({ + headers: expect.not.objectContaining({ + authorization: expect.anything(), + }), + }), + }, + ])('should handle happy path %#', async ({ url, config, response }) => { + const [{ reader }] = AzureUrlReader.factory({ config, logger }); + + const data = await reader.read(url); + const res = await JSON.parse(data.toString('utf-8')); + expect(res).toEqual(response); }); - it('should return request options', () => { - const tests = [ - { - token: '0123456789', - expect: { - headers: { - Authorization: 'Basic OjAxMjM0NTY3ODk=', - }, - }, - }, - { - token: '', - expect: { - headers: {}, - }, - err: - "Invalid type in config for key 'integrations.azure[0].token' in '', got empty-string, wanted string", - }, - { - token: undefined, - expect: { - headers: {}, - }, - }, - ]; - - for (const test of tests) { - if (test.err) { - expect( - () => new AzureUrlReader(readConfig(createConfig(test.token))[0]), - ).toThrowError(test.err); - } else { - const processor = new AzureUrlReader( - readConfig(createConfig(test.token))[0], - ); - expect(processor.getRequestOptions()).toEqual(test.expect); - } - } + it.each([ + { + url: 'https://api.com/a/b/blob/master/path/to/c.yaml', + config: createConfig(), + error: + 'Incorrect url: https://api.com/a/b/blob/master/path/to/c.yaml, Error: Wrong Azure Devops URL or Invalid file path', + }, + { + url: 'com/a/b/blob/master/path/to/c.yaml', + config: createConfig(), + error: + 'Incorrect url: com/a/b/blob/master/path/to/c.yaml, TypeError: Invalid URL: com/a/b/blob/master/path/to/c.yaml', + }, + { + url: '', + config: createConfig(''), + error: + "Invalid type in config for key 'integrations.azure[0].token' in 'test-config', got empty-string, wanted string", + }, + ])('should handle error path %#', async ({ url, config, error }) => { + await expect(async () => { + const [{ reader }] = AzureUrlReader.factory({ config, logger }); + await reader.read(url); + }).rejects.toThrow(error); }); }); diff --git a/packages/backend-common/src/reading/AzureUrlReader.ts b/packages/backend-common/src/reading/AzureUrlReader.ts index ade2cd8793..aba4839c34 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.ts @@ -26,7 +26,7 @@ type Options = { token?: string; }; -export function readConfig(config: Config): Options[] { +function readConfig(config: Config): Options[] { const optionsArr = Array(); const providerConfigs = @@ -65,23 +65,6 @@ export class AzureUrlReader implements UrlReader { } } - getRequestOptions(): RequestInit { - const headers: HeadersInit = {}; - - if (this.options.token) { - headers.Authorization = `Basic ${Buffer.from( - `:${this.options.token}`, - 'utf8', - ).toString('base64')}`; - } - - const requestOptions: RequestInit = { - headers, - }; - - return requestOptions; - } - async read(url: string): Promise { const builtUrl = this.buildRawUrl(url); @@ -107,7 +90,7 @@ export class AzureUrlReader implements UrlReader { // Converts // from: https://dev.azure.com/{organization}/{project}/_git/reponame?path={path}&version=GB{commitOrBranch}&_a=contents // to: https://dev.azure.com/{organization}/{project}/_apis/git/repositories/reponame/items?path={path}&version={commitOrBranch} - buildRawUrl(target: string): URL { + private buildRawUrl(target: string): URL { try { const url = new URL(target); @@ -163,6 +146,19 @@ export class AzureUrlReader implements UrlReader { } } + private getRequestOptions(): RequestInit { + const headers: HeadersInit = {}; + + if (this.options.token) { + headers.Authorization = `Basic ${Buffer.from( + `:${this.options.token}`, + 'utf8', + ).toString('base64')}`; + } + + return { headers }; + } + toString() { const { host, token } = this.options; return `azure{host=${host},authed=${Boolean(token)}}`; diff --git a/packages/backend-common/src/setupTests.ts b/packages/backend-common/src/setupTests.ts index f7b6ca962d..ba33cf996b 100644 --- a/packages/backend-common/src/setupTests.ts +++ b/packages/backend-common/src/setupTests.ts @@ -14,6 +14,4 @@ * limitations under the License. */ -require('jest-fetch-mock').enableMocks(); - export {};