From ab6d9de923f0de9c4392cd4173ccfd4e349433b2 Mon Sep 17 00:00:00 2001 From: Johan Haals Date: Fri, 26 Feb 2021 13:29:36 +0100 Subject: [PATCH] Add action tests Co-authored-by: Ben Lambert Signed-off-by: Johan Haals --- .../actions/builtin/catalog/register.test.ts | 101 ++++++++++++++ .../builtin/fetch/cookiecutter.test.ts | 132 ++++++++++++++++++ .../actions/builtin/fetch/cookiecutter.ts | 3 - .../actions/builtin/fetch/plain.test.ts | 85 +++++++++++ .../actions/builtin/publish/azure.test.ts | 2 +- .../actions/builtin/publish/bitbucket.test.ts | 2 +- .../actions/builtin/publish/gitab.test.ts | 2 +- .../actions/builtin/publish/github.test.ts | 2 +- 8 files changed, 322 insertions(+), 7 deletions(-) create mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.test.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts new file mode 100644 index 0000000000..9fa96132d6 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/register.test.ts @@ -0,0 +1,101 @@ +/* + * 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 { PassThrough } from 'stream'; +import os from 'os'; +import { getVoidLogger } from '@backstage/backend-common'; +import { CatalogApi } from '@backstage/catalog-client'; +import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { createCatalogRegisterAction } from './register'; +import { Entity } from '@backstage/catalog-model'; + +describe('catalog:register', () => { + const integrations = ScmIntegrations.fromConfig( + new ConfigReader({ + integrations: { + github: [{ host: 'github.com', token: 'token' }], + }, + }), + ); + + const addLocation = jest.fn(); + const catalogClient = { + addLocation: addLocation, + }; + + const action = createCatalogRegisterAction({ + integrations, + catalogClient: (catalogClient as unknown) as CatalogApi, + }); + + const mockContext = { + workspacePath: os.tmpdir(), + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should reject registrations for locations that does not match any integration', async () => { + await expect( + action.handler({ + ...mockContext, + input: { + repoContentsUrl: 'https://google.com/foo/bar', + }, + }), + ).rejects.toThrow( + /No integration found for host https:\/\/google.com\/foo\/bar/, + ); + }); + + it('should register location in catalog', async () => { + addLocation.mockResolvedValue({ + entities: [ + { + metadata: { + namespace: 'default', + name: 'test', + }, + kind: 'service', + } as Entity, + ], + }); + await action.handler({ + ...mockContext, + input: { + catalogInfoUrl: 'http://foo/var', + }, + }); + expect(addLocation).toBeCalledWith({ + type: 'url', + target: 'http://foo/var', + }); + + expect(mockContext.output).toBeCalledWith( + 'entityRef', + 'service:default/test', + ); + expect(mockContext.output).toBeCalledWith( + 'catalogInfoUrl', + 'http://foo/var', + ); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.test.ts new file mode 100644 index 0000000000..923e22d0a5 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.test.ts @@ -0,0 +1,132 @@ +/* + * 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. + */ +jest.mock('./helpers'); + +import os from 'os'; +import { createFetchCookiecutterAction } from './cookiecutter'; +import { ScmIntegrations } from '@backstage/integration'; +import { ConfigReader } from '@backstage/config'; +import { Templaters } from '../../../stages/templater'; +import { PassThrough } from 'stream'; +import { getVoidLogger, UrlReader } from '@backstage/backend-common'; +import { fetchContents } from './helpers'; +import mock from 'mock-fs'; + +describe('fetch:cookiecutter', () => { + const integrations = ScmIntegrations.fromConfig( + new ConfigReader({ + integrations: { + azure: [ + { host: 'dev.azure.com', token: 'tokenlols' }, + { host: 'myazurehostnotoken.com' }, + ], + }, + }), + ); + + const templaters = new Templaters(); + const cookiecutterTemplater = { run: jest.fn() }; + const mockDockerClient = {}; + const mockTmpDir = os.tmpdir(); + const mockContext = { + input: { + url: 'https://google.com/cookie/cutter', + targetPath: 'something', + values: { + help: 'me', + }, + }, + baseUrl: 'somebase', + workspacePath: mockTmpDir, + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn().mockResolvedValue(mockTmpDir), + }; + + mock({ [`${mockContext.workspacePath}/result`]: {} }); + + const mockReader: UrlReader = { + read: jest.fn(), + readTree: jest.fn(), + search: jest.fn(), + }; + + const action = createFetchCookiecutterAction({ + integrations, + templaters, + dockerClient: mockDockerClient as any, + reader: mockReader, + }); + + templaters.register('cookiecutter', cookiecutterTemplater); + + beforeEach(() => { + jest.restoreAllMocks(); + }); + + it('should call fetchContents with the correct values', async () => { + await action.handler(mockContext); + + expect(fetchContents).toHaveBeenCalledWith({ + reader: mockReader, + integrations, + baseUrl: mockContext.baseUrl, + fetchUrl: mockContext.input.url, + outputPath: `${mockContext.workspacePath}/template/{{cookiecutter and 'contents'}}`, + }); + }); + + it('should execute the cookiecutter templater with the correct values', async () => { + await action.handler(mockContext); + + expect(cookiecutterTemplater.run).toHaveBeenCalledWith({ + workspacePath: mockTmpDir, + dockerClient: mockDockerClient, + logStream: mockContext.logStream, + values: mockContext.input.values, + }); + }); + + it('should throw if there is no cookiecutter templater initialized', async () => { + const templatersWithoutCookiecutter = new Templaters(); + + const newAction = createFetchCookiecutterAction({ + integrations, + templaters: templatersWithoutCookiecutter, + dockerClient: mockDockerClient as any, + reader: mockReader, + }); + + await expect(newAction.handler(mockContext)).rejects.toThrow( + /No templater registered/, + ); + }); + + it('should throw if the target directory is outside of the workspace path', async () => { + await expect( + action.handler({ + ...mockContext, + input: { + ...mockContext.input, + targetPath: '/foo', + }, + }), + ).rejects.toThrow( + /targetPath may not specify a path outside the working directory/, + ); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.ts index 4016fb69c5..2a04ed301d 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/cookiecutter.ts @@ -82,9 +82,6 @@ export function createFetchCookiecutterAction(options: { }); const cookiecutter = templaters.get('cookiecutter'); - if (!cookiecutter) { - throw new Error('No cookiecutter templater available'); - } // Will execute the template in ./template and put the result in ./result await cookiecutter.run({ diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts new file mode 100644 index 0000000000..88eeb5997b --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/plain.test.ts @@ -0,0 +1,85 @@ +/* + * 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. + */ +jest.mock('./helpers'); + +import os from 'os'; +import { getVoidLogger, UrlReader } from '@backstage/backend-common'; +import { ConfigReader } from '@backstage/config'; +import { ScmIntegrations } from '@backstage/integration'; +import { createFetchPlainAction } from './plain'; +import { PassThrough } from 'stream'; +import { fetchContents } from './helpers'; + +describe('fetch:plain', () => { + const integrations = ScmIntegrations.fromConfig( + new ConfigReader({ + integrations: { + github: [{ host: 'github.com', token: 'token' }], + }, + }), + ); + const reader: UrlReader = { + read: jest.fn(), + readTree: jest.fn(), + search: jest.fn(), + }; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + const action = createFetchPlainAction({ integrations, reader }); + const mockContext = { + workspacePath: os.tmpdir(), + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + + it('should disallow a target path outside working directory', async () => { + await expect( + action.handler({ + ...mockContext, + input: { + url: + 'https://github.com/backstage/community/tree/main/backstage-community-sessions/assets', + targetPath: '/foobar', + }, + }), + ).rejects.toThrow( + /Fetch action targetPath may not specify a path outside the working directory/, + ); + }); + + it('should fetch plain', async () => { + await action.handler({ + ...mockContext, + input: { + url: + 'https://github.com/backstage/community/tree/main/backstage-community-sessions/assets', + targetPath: 'lol', + }, + }); + expect(fetchContents).toBeCalledWith( + expect.objectContaining({ + outputPath: `${mockContext.workspacePath}/lol`, + fetchUrl: + 'https://github.com/backstage/community/tree/main/backstage-community-sessions/assets', + }), + ); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.test.ts index fe292abfe9..4e2d721af9 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/azure.test.ts @@ -27,7 +27,7 @@ import { WebApi } from 'azure-devops-node-api'; import { PassThrough } from 'stream'; import { initRepoAndPush } from '../../../stages/publish/helpers'; -describe('Azure Publish Action', () => { +describe('publish:azure', () => { const integrations = ScmIntegrations.fromConfig( new ConfigReader({ integrations: { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts index 04e3b9b0bb..40e7f613f2 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/bitbucket.test.ts @@ -25,7 +25,7 @@ import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; import { initRepoAndPush } from '../../../stages/publish/helpers'; -describe('Bitbucket Publish Action', () => { +describe('publish:bitbucket', () => { const integrations = ScmIntegrations.fromConfig( new ConfigReader({ integrations: { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitab.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitab.test.ts index 8757f7865a..6f789a7b76 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitab.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/gitab.test.ts @@ -23,7 +23,7 @@ import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; import { initRepoAndPush } from '../../../stages/publish/helpers'; -describe('Gitlab Publish Action', () => { +describe('publish:gitlab', () => { const integrations = ScmIntegrations.fromConfig( new ConfigReader({ integrations: { diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts index fe6e78b445..9880dda621 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.test.ts @@ -23,7 +23,7 @@ import { getVoidLogger } from '@backstage/backend-common'; import { PassThrough } from 'stream'; import { initRepoAndPush } from '../../../stages/publish/helpers'; -describe('Github Publish Action', () => { +describe('publish:github', () => { const integrations = ScmIntegrations.fromConfig( new ConfigReader({ integrations: {