From dcfdaeccd377046f40b4f961a20031d2687620b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 22 Dec 2022 13:39:43 +0100 Subject: [PATCH] minor improvements to the coverage backend MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .changeset/old-planets-care.md | 5 +++++ .../src/service/CoverageUtils.test.ts | 6 ++++-- .../src/service/CoverageUtils.ts | 6 ++++-- .../src/service/converter/jacoco.ts | 1 + .../src/service/router.ts | 18 ++++++++++-------- .../src/service/standaloneServer.ts | 16 ++++++++++++++++ 6 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 .changeset/old-planets-care.md diff --git a/.changeset/old-planets-care.md b/.changeset/old-planets-care.md new file mode 100644 index 0000000000..4b30ff7d2d --- /dev/null +++ b/.changeset/old-planets-care.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-code-coverage-backend': patch +--- + +`RouterOptions` now accepts an optional `catalogApi` argument, allowing custom catalog clients to be used. This is leveraged in the local standalone development runner to pass in a catalog client that returns fake data. diff --git a/plugins/code-coverage-backend/src/service/CoverageUtils.test.ts b/plugins/code-coverage-backend/src/service/CoverageUtils.test.ts index e0b4c6589e..44b1411cc4 100644 --- a/plugins/code-coverage-backend/src/service/CoverageUtils.test.ts +++ b/plugins/code-coverage-backend/src/service/CoverageUtils.test.ts @@ -209,7 +209,7 @@ describe('CodeCoverageUtils', () => { } catch (error: any) { err = error; } - expect(err?.message).toEqual('Content-Type missing'); + expect(err?.message).toEqual('Content-Type header missing'); }); it('rejects unsupported content type', () => { @@ -223,7 +223,9 @@ describe('CodeCoverageUtils', () => { } catch (error: any) { err = error; } - expect(err?.message).toEqual('Illegal Content-Type'); + expect(err?.message).toEqual( + 'Content-Type header "application/json" not supported, expected "text/xml" possibly followed by a charset', + ); }); it('parses the body', () => { diff --git a/plugins/code-coverage-backend/src/service/CoverageUtils.ts b/plugins/code-coverage-backend/src/service/CoverageUtils.ts index d965ae6300..a4d3405868 100644 --- a/plugins/code-coverage-backend/src/service/CoverageUtils.ts +++ b/plugins/code-coverage-backend/src/service/CoverageUtils.ts @@ -162,9 +162,11 @@ export class CoverageUtils { validateRequestBody(req: Request) { const contentType = req.headers['content-type']; if (!contentType) { - throw new InputError('Content-Type missing'); + throw new InputError('Content-Type header missing'); } else if (!contentType.match(/^text\/xml($|;)/)) { - throw new InputError('Illegal Content-Type'); + throw new InputError( + `Content-Type header "${contentType}" not supported, expected "text/xml" possibly followed by a charset`, + ); } const body = req.body; if (!body) { diff --git a/plugins/code-coverage-backend/src/service/converter/jacoco.ts b/plugins/code-coverage-backend/src/service/converter/jacoco.ts index f3047d9f00..ac5ccbfa45 100644 --- a/plugins/code-coverage-backend/src/service/converter/jacoco.ts +++ b/plugins/code-coverage-backend/src/service/converter/jacoco.ts @@ -13,6 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + import { BranchHit, FileEntry } from '../types'; import { JacocoSourceFile, JacocoXML } from './types'; import { Logger } from 'winston'; diff --git a/plugins/code-coverage-backend/src/service/router.ts b/plugins/code-coverage-backend/src/service/router.ts index b87b21bffd..4d4fb779b5 100644 --- a/plugins/code-coverage-backend/src/service/router.ts +++ b/plugins/code-coverage-backend/src/service/router.ts @@ -46,6 +46,7 @@ export interface RouterOptions { database: PluginDatabaseManager; urlReader: UrlReader; logger: Logger; + catalogApi?: CatalogApi; } export interface CodeCoverageApi { @@ -59,7 +60,8 @@ export const makeRouter = async ( const codeCoverageDatabase = await CodeCoverageDatabase.create(database); const codecovUrl = await discovery.getExternalBaseUrl('code-coverage'); - const catalogApi: CatalogApi = new CatalogClient({ discoveryApi: discovery }); + const catalogApi = + options.catalogApi ?? new CatalogClient({ discoveryApi: discovery }); const scm = ScmIntegrations.fromConfig(config); const router = Router(); @@ -167,10 +169,10 @@ export const makeRouter = async ( * /report?entity=component:default/mycomponent&coverageType=cobertura */ router.post('/report', async (req, res) => { - const { entity, coverageType } = req.query; - const entityLookup = await catalogApi.getEntityByRef(entity as string); - if (!entityLookup) { - throw new NotFoundError(`No entity found matching ${entity}`); + const { entity: entityRef, coverageType } = req.query; + const entity = await catalogApi.getEntityByRef(entityRef as string); + if (!entity) { + throw new NotFoundError(`No entity found matching ${entityRef}`); } let converter: Converter; @@ -185,7 +187,7 @@ export const makeRouter = async ( } const { sourceLocation, vcs, scmFiles, body } = - await utils.processCoveragePayload(entityLookup, req); + await utils.processCoveragePayload(entity, req); const files = converter.convert(body, scmFiles); if (!files || files.length === 0) { @@ -193,7 +195,7 @@ export const makeRouter = async ( } const coverage = await utils.buildCoverage( - entityLookup, + entity, sourceLocation, vcs, files, @@ -204,7 +206,7 @@ export const makeRouter = async ( links: [ { rel: 'coverage', - href: `${codecovUrl}/report?entity=${entity}`, + href: `${codecovUrl}/report?entity=${entityRef}`, }, ], }); diff --git a/plugins/code-coverage-backend/src/service/standaloneServer.ts b/plugins/code-coverage-backend/src/service/standaloneServer.ts index c4ced6baeb..41493660fb 100644 --- a/plugins/code-coverage-backend/src/service/standaloneServer.ts +++ b/plugins/code-coverage-backend/src/service/standaloneServer.ts @@ -21,6 +21,8 @@ import { UrlReaders, useHotMemoize, } from '@backstage/backend-common'; +import { CatalogApi } from '@backstage/catalog-client'; +import { CompoundEntityRef, parseEntityRef } from '@backstage/catalog-model'; import { Server } from 'http'; import knexFactory from 'knex'; import { Logger } from 'winston'; @@ -52,13 +54,27 @@ export async function startStandaloneServer( return knex; }); + const catalogApi = { + async getEntityByRef(entityRef: string | CompoundEntityRef) { + const { kind, namespace, name } = parseEntityRef(entityRef); + return { + apiVersion: 'backstage.io/v1alpha1', + kind, + metadata: { name, namespace }, + spec: {}, + }; + }, + } as Partial as CatalogApi; + logger.debug('Starting application server...'); + const router = await createRouter({ database: { getClient: async () => db }, config, discovery: SingleHostDiscovery.fromConfig(config), urlReader: UrlReaders.default({ logger, config }), logger, + catalogApi, }); let service = createServiceBuilder(module)