Add action tests

Co-authored-by: Ben Lambert <ben@blam.sh>
Signed-off-by: Johan Haals <johan.haals@gmail.com>
This commit is contained in:
Johan Haals
2021-02-26 13:29:36 +01:00
parent edcbcff106
commit ab6d9de923
8 changed files with 322 additions and 7 deletions
@@ -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',
);
});
});
@@ -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/,
);
});
});
@@ -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({
@@ -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',
}),
);
});
});
@@ -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: {
@@ -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: {
@@ -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: {
@@ -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: {