add tests

Signed-off-by: Kiss Miklos <miklos@roadie.io>
This commit is contained in:
Kiss Miklos
2022-09-27 10:46:15 +02:00
parent 73db439b1d
commit 421b620af3
3 changed files with 151 additions and 26 deletions
@@ -27,7 +27,7 @@ import {
LocationAnalyzer,
} from './types';
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import { GitHubLocationAnalyzer } from './GitHubLocationAnalyzer';
import { GitHubLocationAnalyzer } from './analyzers/GitHubLocationAnalyzer';
export class RepoLocationAnalyzer implements LocationAnalyzer {
private readonly logger: Logger;
@@ -85,8 +85,6 @@ export class RepoLocationAnalyzer implements LocationAnalyzer {
if (analyzer) {
const existingEntityFiles = await analyzer.analyze(
owner,
name,
request.location.target,
);
if (existingEntityFiles.length > 0) {
@@ -0,0 +1,126 @@
/*
* Copyright 2022 The Backstage Authors
*
* 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.
*/
const octokit = {
search: {
code: jest.fn(),
},
repos: {
get: jest.fn(),
},
};
jest.mock('@octokit/rest', () => {
class Octokit {
constructor() {
return octokit;
}
}
return { Octokit };
});
import { PluginEndpointDiscovery } from '@backstage/backend-common';
import { GitHubLocationAnalyzer } from './GitHubLocationAnalyzer';
import { setupRequestMockHandlers } from '@backstage/backend-test-utils';
import { setupServer } from 'msw/node';
import { rest } from 'msw';
import { GitHubIntegration } from '@backstage/integration';
const server = setupServer();
describe('GitHubLocationAnalyzer', () => {
const mockDiscoveryApi: jest.Mocked<PluginEndpointDiscovery> = {
getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7007'),
getExternalBaseUrl: jest.fn(),
};
const integration = new GitHubIntegration({
host: 'h.com',
apiBaseUrl: 'a',
rawBaseUrl: 'r',
token: 't',
});
setupRequestMockHandlers(server);
beforeEach(() => {
server.use(
rest.post('http://localhost:7007/locations', async (req, res, ctx) => {
return res(
ctx.status(201),
ctx.json({
location: 'test',
exists: false,
entities: [
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Location',
metadata: {
name: 'test-entity',
},
spec: {
type: 'url',
target: 'whatever',
},
},
{
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
metadata: {
title: 'Test Entity',
name: 'test-entity-2',
description: 'The expected description 2',
},
spec: {
type: 'some-type',
lifecycle: 'experimental',
owner: 'someone',
},
},
],
}),
);
}),
);
});
it('should analyze', async () => {
octokit.search.code.mockImplementation((opts: { q: string }) => {
if (opts.q === 'filename:catalog-info.yaml repo:foo/bar') {
return Promise.resolve({
data: { items: [{ path: 'catalog-info.yaml' }], total_count: 1 },
});
}
return Promise.reject();
});
octokit.repos.get.mockResolvedValue({
data: { default_branch: 'my_default_branch' },
});
const analyzer = new GitHubLocationAnalyzer({
discovery: mockDiscoveryApi,
integration,
});
const result = await analyzer.analyze('https://github.com/foo/bar');
expect(result[0].isRegistered).toBeFalsy();
expect(result[0].location).toEqual({
type: 'url',
target:
'https://github.com/foo/bar/blob/my_default_branch/catalog-info.yaml',
});
});
});
@@ -14,12 +14,13 @@
* limitations under the License.
*/
import { CatalogClient } from '@backstage/catalog-client';
import { CatalogApi, CatalogClient } from '@backstage/catalog-client';
import { GitHubIntegration } from '@backstage/integration';
import { DiscoveryApi } from '@backstage/plugin-permission-common';
import { Octokit } from '@octokit/rest';
import { trimEnd } from 'lodash';
import { AnalyzeLocationExistingEntity, ScmLocationAnalyzer } from './types';
import parseGitUrl from 'git-url-parse';
import { AnalyzeLocationExistingEntity, ScmLocationAnalyzer } from '../types';
export type GitHubLocationAnalyzerOptions = {
integration: GitHubIntegration;
@@ -27,45 +28,45 @@ export type GitHubLocationAnalyzerOptions = {
discovery: DiscoveryApi;
};
export class GitHubLocationAnalyzer implements ScmLocationAnalyzer {
private readonly integration: GitHubIntegration;
private readonly catalogFilename: string;
private readonly discovery: DiscoveryApi;
private readonly octokitClient: Octokit;
private readonly catalogClient: CatalogApi;
constructor(options: GitHubLocationAnalyzerOptions) {
this.integration = options.integration;
this.catalogFilename = options.catalogFilename || 'catalog-info.yaml';
this.discovery = options.discovery;
this.octokitClient = new Octokit({
auth: options.integration.config.token,
baseUrl: options.integration.config.apiBaseUrl,
});
this.catalogClient = new CatalogClient({ discoveryApi: this.discovery });
}
async analyze(
owner: string,
repo: string,
url: string,
): Promise<AnalyzeLocationExistingEntity[]> {
const octo = new Octokit({
auth: this.integration.config.token,
baseUrl: this.integration.config.apiBaseUrl,
});
const query = `filename:${this.catalogFilename} repo:${owner}/${repo} `;
async analyze(url: string): Promise<AnalyzeLocationExistingEntity[]> {
const { owner, name: repo } = parseGitUrl(url);
const query = `filename:${this.catalogFilename} repo:${owner}/${repo}`;
const catalogClient = new CatalogClient({ discoveryApi: this.discovery });
const searchResult = await octo.search.code({ q: query }).catch(e => {
throw new Error(`Couldn't search repository for metadata file, ${e}`);
});
const searchResult = await this.octokitClient.search
.code({ q: query })
.catch(e => {
throw new Error(`Couldn't search repository for metadata file, ${e}`);
});
const exists = searchResult.data.total_count > 0;
if (exists) {
const repoInformation = await octo.repos.get({ owner, repo }).catch(e => {
throw new Error(`Couldn't fetch repo data, ${e}`);
});
const repoInformation = await this.octokitClient.repos
.get({ owner, repo })
.catch(e => {
throw new Error(`Couldn't fetch repo data, ${e}`);
});
const defaultBranch = repoInformation.data.default_branch;
const result = await Promise.all(
searchResult.data.items
.map(i => `${trimEnd(url, '/')}/blob/${defaultBranch}/${i.path}`)
.map(async target => {
const addLocationResult = await catalogClient.addLocation({
const addLocationResult = await this.catalogClient.addLocation({
type: 'url',
target,
dryRun: true,