From 09e17c8a65abf0d6f9cbc77a0496461263bdb155 Mon Sep 17 00:00:00 2001 From: Marat Dyatko Date: Thu, 9 Apr 2026 10:24:04 +0200 Subject: [PATCH] test(org): add queryParams coverage for OwnershipCard useGetEntities The existing tests never exercised `getQueryParams` because the mock catalog API always returned empty items. These new tests verify that the generated query parameters include correctly humanized owner entity refs, handle multiple owners, and group entities by kind and type. Made-with: Cursor Signed-off-by: Marat Dyatko Made-with: Cursor --- .../OwnershipCard/useGetEntities.test.ts | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) diff --git a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts index b7331479ef..d7ee61b714 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts +++ b/plugins/org/src/components/Cards/OwnershipCard/useGetEntities.test.ts @@ -18,6 +18,7 @@ import { CompoundEntityRef, Entity } from '@backstage/catalog-model'; import { useGetEntities } from './useGetEntities'; import { renderHook, waitFor } from '@testing-library/react'; import { catalogApiMock } from '@backstage/plugin-catalog-react/testUtils'; +import qs from 'qs'; const givenParentGroup = 'team.squad1'; const givenLeafGroup = 'team.squad2'; @@ -45,8 +46,11 @@ const getEntityRelationsMock: jest.Mock< [Entity | undefined] > = jest.fn(); jest.mock('@backstage/plugin-catalog-react', () => { + const actual = jest.requireActual('@backstage/plugin-catalog-react'); return { catalogApiRef: {}, + entityPresentationSnapshot: actual.entityPresentationSnapshot, + humanizeEntityRef: actual.humanizeEntityRef, getEntityRelations: jest.fn(entity => { return getEntityRelationsMock(entity); }) as any, @@ -283,6 +287,143 @@ describe('useGetEntities', () => { }); }); + describe('queryParams generation', () => { + afterEach(() => { + getEntityRelationsMock.mockRestore(); + catalogApi.getEntities.mockRestore(); + }); + + it('should produce query params with humanized entity refs as owners', async () => { + getEntityRelationsMock.mockReturnValue([]); + catalogApi.getEntities.mockResolvedValueOnce({ + items: [ + { + kind: 'Component', + metadata: { name: 'my-service', namespace: 'default' }, + spec: { type: 'service' }, + } as Partial as Entity, + ], + }); + + const { result } = renderHook( + ({ entity }) => useGetEntities(entity, 'direct'), + { initialProps: { entity: givenLeafGroupEntity } }, + ); + + await waitFor(() => expect(result.current.loading).toBe(false)); + + expect(result.current.componentsWithCounters).toHaveLength(1); + const params = qs.parse( + result.current.componentsWithCounters![0].queryParams, + ); + expect(params).toEqual({ + filters: { + kind: 'component', + type: 'service', + owners: givenLeafGroup, + user: 'all', + }, + }); + }); + + it('should include multiple owners as an array in query params', async () => { + getEntityRelationsMock + .mockReturnValueOnce([createGroupRefFromName(givenLeafGroup)]) + .mockReturnValue([]); + + catalogApi.getEntitiesByRefs.mockResolvedValueOnce({ + items: [givenLeafGroupEntity], + }); + catalogApi.getEntities.mockResolvedValueOnce({ + items: [ + { + kind: 'API', + metadata: { name: 'my-api', namespace: 'default' }, + spec: { type: 'openapi' }, + } as Partial as Entity, + ], + }); + + const { result } = renderHook( + ({ entity }) => useGetEntities(entity, 'aggregated'), + { initialProps: { entity: givenParentGroupEntity } }, + ); + + await waitFor(() => expect(result.current.loading).toBe(false)); + + expect(result.current.componentsWithCounters).toHaveLength(1); + const params = qs.parse( + result.current.componentsWithCounters![0].queryParams, + ); + expect(params).toEqual({ + filters: { + kind: 'api', + type: 'openapi', + owners: expect.arrayContaining([givenLeafGroup, givenParentGroup]), + user: 'all', + }, + }); + }); + + it('should group entities by kind and type in query params', async () => { + getEntityRelationsMock.mockReturnValue([]); + catalogApi.getEntities.mockResolvedValueOnce({ + items: [ + { + kind: 'Component', + metadata: { name: 'svc-1', namespace: 'default' }, + spec: { type: 'service' }, + } as Partial as Entity, + { + kind: 'Component', + metadata: { name: 'svc-2', namespace: 'default' }, + spec: { type: 'service' }, + } as Partial as Entity, + { + kind: 'API', + metadata: { name: 'api-1', namespace: 'default' }, + spec: { type: 'openapi' }, + } as Partial as Entity, + ], + }); + + const { result } = renderHook( + ({ entity }) => useGetEntities(entity, 'direct'), + { initialProps: { entity: givenLeafGroupEntity } }, + ); + + await waitFor(() => expect(result.current.loading).toBe(false)); + + expect(result.current.componentsWithCounters).toHaveLength(2); + + const componentEntry = result.current.componentsWithCounters!.find( + c => c.kind === 'Component', + )!; + expect(componentEntry.counter).toBe(2); + + const componentParams = qs.parse(componentEntry.queryParams); + expect(componentParams).toEqual({ + filters: expect.objectContaining({ + kind: 'component', + type: 'service', + }), + }); + + const apiEntry = result.current.componentsWithCounters!.find( + c => c.kind === 'API', + )!; + expect(apiEntry.counter).toBe(1); + + const apiParams = qs.parse(apiEntry.queryParams); + expect(apiParams).toEqual({ + filters: expect.objectContaining({ + kind: 'api', + type: 'openapi', + }), + }); + }); + }); + describe('useGetEntities exceeding default 16384 bytes header size', () => { const largeNumberOfGroups = Array.from({ length: 600 }, (_, i) => createGroupEntityFromName(`very-long-group-name-${i + 1}`),