From f19f84807774df897b1916498e570c074d9aa9ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Tue, 10 Sep 2024 22:21:54 +0200 Subject: [PATCH] flesh out the mock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Fredrik Adelöw --- .../catalog-client/api-report-testUtils.md | 52 ++------- .../src/testUtils/InMemoryCatalogClient.ts | 108 +++++++++--------- .../src/testUtils/catalogServiceMock.ts | 60 +++++++++- 3 files changed, 123 insertions(+), 97 deletions(-) diff --git a/packages/catalog-client/api-report-testUtils.md b/packages/catalog-client/api-report-testUtils.md index f78c6de5c9..a33470e35d 100644 --- a/packages/catalog-client/api-report-testUtils.md +++ b/packages/catalog-client/api-report-testUtils.md @@ -6,7 +6,6 @@ import { AddLocationRequest } from '@backstage/catalog-client'; import { AddLocationResponse } from '@backstage/catalog-client'; import { CatalogApi } from '@backstage/catalog-client'; -import { CatalogRequestOptions } from '@backstage/catalog-client'; import { CompoundEntityRef } from '@backstage/catalog-model'; import { Entity } from '@backstage/catalog-model'; import { GetEntitiesByRefsRequest } from '@backstage/catalog-client'; @@ -24,76 +23,49 @@ import { ValidateEntityResponse } from '@backstage/catalog-client'; // @public (undocumented) export class InMemoryCatalogClient implements CatalogApi { + constructor(options?: { entities?: Entity[] }); // (undocumented) - addLocation( - _location: AddLocationRequest, - _options?: CatalogRequestOptions | undefined, - ): Promise; + addLocation(_location: AddLocationRequest): Promise; // (undocumented) - getEntities( - _request?: GetEntitiesRequest | undefined, - _options?: CatalogRequestOptions | undefined, - ): Promise; + getEntities(_request?: GetEntitiesRequest): Promise; // (undocumented) getEntitiesByRefs( - _request: GetEntitiesByRefsRequest, - _options?: CatalogRequestOptions | undefined, + request: GetEntitiesByRefsRequest, ): Promise; // (undocumented) getEntityAncestors( - _request: GetEntityAncestorsRequest, - _options?: CatalogRequestOptions | undefined, + request: GetEntityAncestorsRequest, ): Promise; // (undocumented) getEntityByRef( - _entityRef: string | CompoundEntityRef, - _options?: CatalogRequestOptions | undefined, + entityRef: string | CompoundEntityRef, ): Promise; // (undocumented) getEntityFacets( _request: GetEntityFacetsRequest, - _options?: CatalogRequestOptions | undefined, ): Promise; // (undocumented) getLocationByEntity( _entityRef: string | CompoundEntityRef, - _options?: CatalogRequestOptions | undefined, ): Promise; // (undocumented) - getLocationById( - _id: string, - _options?: CatalogRequestOptions | undefined, - ): Promise; + getLocationById(_id: string): Promise; // (undocumented) - getLocationByRef( - _locationRef: string, - _options?: CatalogRequestOptions | undefined, - ): Promise; + getLocationByRef(_locationRef: string): Promise; // (undocumented) queryEntities( - _request?: QueryEntitiesRequest | undefined, - _options?: CatalogRequestOptions | undefined, + _request?: QueryEntitiesRequest, ): Promise; // (undocumented) - refreshEntity( - _entityRef: string, - _options?: CatalogRequestOptions | undefined, - ): Promise; + refreshEntity(_entityRef: string): Promise; // (undocumented) - removeEntityByUid( - _uid: string, - _options?: CatalogRequestOptions | undefined, - ): Promise; + removeEntityByUid(uid: string): Promise; // (undocumented) - removeLocationById( - _id: string, - _options?: CatalogRequestOptions | undefined, - ): Promise; + removeLocationById(_id: string): Promise; // (undocumented) validateEntity( _entity: Entity, _locationRef: string, - _options?: CatalogRequestOptions | undefined, ): Promise; } diff --git a/packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts b/packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts index c5ceafcd46..ce65290e04 100644 --- a/packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts +++ b/packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts @@ -18,7 +18,6 @@ import { AddLocationRequest, AddLocationResponse, CatalogApi, - CatalogRequestOptions, GetEntitiesByRefsRequest, GetEntitiesByRefsResponse, GetEntitiesRequest, @@ -32,106 +31,111 @@ import { QueryEntitiesResponse, ValidateEntityResponse, } from '@backstage/catalog-client'; -import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; -import { NotImplementedError } from '@backstage/errors'; +import { + CompoundEntityRef, + Entity, + stringifyEntityRef, +} from '@backstage/catalog-model'; +import { NotFoundError, NotImplementedError } from '@backstage/errors'; /** @public */ export class InMemoryCatalogClient implements CatalogApi { - getEntities( - _request?: GetEntitiesRequest | undefined, - _options?: CatalogRequestOptions | undefined, + #entities: Entity[]; + + constructor(options?: { entities?: Entity[] }) { + this.#entities = options?.entities ?? []; + } + + async getEntities( + _request?: GetEntitiesRequest, ): Promise { throw new NotImplementedError('Method not implemented.'); } - getEntitiesByRefs( - _request: GetEntitiesByRefsRequest, - _options?: CatalogRequestOptions | undefined, + async getEntitiesByRefs( + request: GetEntitiesByRefsRequest, ): Promise { - throw new NotImplementedError('Method not implemented.'); + const entitiesByRef = new Map(); + for (const entity of this.#entities) { + entitiesByRef.set(stringifyEntityRef(entity), entity); + } + + const items = request.entityRefs.map(candidateRef => + entitiesByRef.get(candidateRef), + ); + + // TODO(freben): Fields, filters etc + return { items }; } - queryEntities( - _request?: QueryEntitiesRequest | undefined, - _options?: CatalogRequestOptions | undefined, + async queryEntities( + _request?: QueryEntitiesRequest, ): Promise { throw new NotImplementedError('Method not implemented.'); } - getEntityAncestors( - _request: GetEntityAncestorsRequest, - _options?: CatalogRequestOptions | undefined, + async getEntityAncestors( + request: GetEntityAncestorsRequest, ): Promise { - throw new NotImplementedError('Method not implemented.'); + const entity = this.#entities.find( + e => stringifyEntityRef(e) === request.entityRef, + ); + if (!entity) { + throw new NotFoundError(`Entity with ref ${request.entityRef} not found`); + } + return { + rootEntityRef: request.entityRef, + items: [{ entity, parentEntityRefs: [] }], + }; } - getEntityByRef( - _entityRef: string | CompoundEntityRef, - _options?: CatalogRequestOptions | undefined, + async getEntityByRef( + entityRef: string | CompoundEntityRef, ): Promise { - throw new NotImplementedError('Method not implemented.'); + const ref = + typeof entityRef === 'string' ? entityRef : stringifyEntityRef(entityRef); + return this.#entities.find(e => stringifyEntityRef(e) === ref); } - removeEntityByUid( - _uid: string, - _options?: CatalogRequestOptions | undefined, - ): Promise { - throw new NotImplementedError('Method not implemented.'); + async removeEntityByUid(uid: string): Promise { + this.#entities = this.#entities.filter(e => e.metadata.uid !== uid); } - refreshEntity( - _entityRef: string, - _options?: CatalogRequestOptions | undefined, - ): Promise { - throw new NotImplementedError('Method not implemented.'); - } + async refreshEntity(_entityRef: string): Promise {} - getEntityFacets( + async getEntityFacets( _request: GetEntityFacetsRequest, - _options?: CatalogRequestOptions | undefined, ): Promise { throw new NotImplementedError('Method not implemented.'); } - getLocationById( - _id: string, - _options?: CatalogRequestOptions | undefined, - ): Promise { + async getLocationById(_id: string): Promise { throw new NotImplementedError('Method not implemented.'); } - getLocationByRef( - _locationRef: string, - _options?: CatalogRequestOptions | undefined, - ): Promise { + async getLocationByRef(_locationRef: string): Promise { throw new NotImplementedError('Method not implemented.'); } - addLocation( + async addLocation( _location: AddLocationRequest, - _options?: CatalogRequestOptions | undefined, ): Promise { throw new NotImplementedError('Method not implemented.'); } - removeLocationById( - _id: string, - _options?: CatalogRequestOptions | undefined, - ): Promise { + async removeLocationById(_id: string): Promise { throw new NotImplementedError('Method not implemented.'); } - getLocationByEntity( + async getLocationByEntity( _entityRef: string | CompoundEntityRef, - _options?: CatalogRequestOptions | undefined, ): Promise { throw new NotImplementedError('Method not implemented.'); } - validateEntity( + async validateEntity( _entity: Entity, _locationRef: string, - _options?: CatalogRequestOptions | undefined, ): Promise { throw new NotImplementedError('Method not implemented.'); } diff --git a/plugins/catalog-node/src/testUtils/catalogServiceMock.ts b/plugins/catalog-node/src/testUtils/catalogServiceMock.ts index 080609986c..dc73176a84 100644 --- a/plugins/catalog-node/src/testUtils/catalogServiceMock.ts +++ b/plugins/catalog-node/src/testUtils/catalogServiceMock.ts @@ -14,19 +14,69 @@ * limitations under the License. */ -import { createServiceFactory } from '@backstage/backend-plugin-api'; -import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; +import { + ServiceRef, + createServiceFactory, +} from '@backstage/backend-plugin-api'; import { InMemoryCatalogClient } from '@backstage/catalog-client/testUtils'; +import { Entity } from '@backstage/catalog-model'; +import { catalogServiceRef } from '@backstage/plugin-catalog-node/alpha'; +// eslint-disable-next-line @backstage/no-undeclared-imports +import { ServiceMock } from '@backstage/backend-test-utils'; + +/** @internal */ +function simpleMock( + ref: ServiceRef, + mockFactory: () => jest.Mocked, +): (partialImpl?: Partial) => ServiceMock { + return partialImpl => { + const mock = mockFactory(); + if (partialImpl) { + for (const [key, impl] of Object.entries(partialImpl)) { + if (typeof impl === 'function') { + (mock as any)[key].mockImplementation(impl); + } else { + (mock as any)[key] = impl; + } + } + } + return Object.assign(mock, { + factory: createServiceFactory({ + service: ref, + deps: {}, + factory: () => mock, + }), + }) as ServiceMock; + }; +} /** @public */ -export function catalogServiceMock() {} +export function catalogServiceMock(options?: { entities?: Entity[] }) { + return new InMemoryCatalogClient(options); +} /** @public */ export namespace catalogServiceMock { - export const factory = () => + export const factory = (options?: { entities?: Entity[] }) => createServiceFactory({ service: catalogServiceRef, deps: {}, - factory: () => new InMemoryCatalogClient(), + factory: () => new InMemoryCatalogClient(options), }); + export const mock = simpleMock(catalogServiceRef, () => ({ + getEntities: jest.fn(), + getEntitiesByRefs: jest.fn(), + queryEntities: jest.fn(), + getEntityAncestors: jest.fn(), + getEntityByRef: jest.fn(), + removeEntityByUid: jest.fn(), + refreshEntity: jest.fn(), + getEntityFacets: jest.fn(), + getLocationById: jest.fn(), + getLocationByRef: jest.fn(), + addLocation: jest.fn(), + removeLocationById: jest.fn(), + getLocationByEntity: jest.fn(), + validateEntity: jest.fn(), + })); }