diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index 842a08c2d3..6d2b8ca70a 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -1190,6 +1190,69 @@ describe('createRouter readonly disabled', () => { ); }); }); + + describe('GET /entity-facets', () => { + it('returns facets', async () => { + entitiesCatalog.facets.mockResolvedValue({ + facets: { kind: [{ value: 'Component', count: 5 }] }, + }); + + const response = await request(app).get('/entity-facets?facet=kind'); + expect(response.status).toBe(200); + expect(response.body).toEqual({ + facets: { kind: [{ value: 'Component', count: 5 }] }, + }); + }); + }); + + describe('POST /entity-facets', () => { + it('returns facets with predicate query', async () => { + entitiesCatalog.facets.mockResolvedValue({ + facets: { 'spec.type': [{ value: 'service', count: 3 }] }, + }); + + const response = await request(app) + .post('/entity-facets') + .send({ + facets: ['spec.type'], + query: { kind: 'Component' }, + }); + + expect(response.status).toBe(200); + expect(response.body).toEqual({ + facets: { 'spec.type': [{ value: 'service', count: 3 }] }, + }); + expect(entitiesCatalog.facets).toHaveBeenCalledWith( + expect.objectContaining({ + facets: ['spec.type'], + query: { kind: 'Component' }, + }), + ); + }); + + it('returns facets without query predicate', async () => { + entitiesCatalog.facets.mockResolvedValue({ + facets: { kind: [{ value: 'Component', count: 5 }] }, + }); + + const response = await request(app) + .post('/entity-facets') + .send({ facets: ['kind'] }); + + expect(response.status).toBe(200); + expect(response.body).toEqual({ + facets: { kind: [{ value: 'Component', count: 5 }] }, + }); + }); + + it('returns 400 for missing facets', async () => { + const response = await request(app) + .post('/entity-facets') + .send({ query: { kind: 'Component' } }); + + expect(response.status).toBe(400); + }); + }); }); describe('createRouter readonly and raw json enabled', () => { diff --git a/plugins/catalog-backend/src/service/request/parseEntityFacetsQuery.test.ts b/plugins/catalog-backend/src/service/request/parseEntityFacetsQuery.test.ts new file mode 100644 index 0000000000..473466f950 --- /dev/null +++ b/plugins/catalog-backend/src/service/request/parseEntityFacetsQuery.test.ts @@ -0,0 +1,81 @@ +/* + * Copyright 2026 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. + */ + +import { parseEntityFacetsQuery } from './parseEntityFacetsQuery'; + +describe('parseEntityFacetsQuery', () => { + it('parses facets with no query', () => { + expect(parseEntityFacetsQuery({ facets: ['kind'] })).toEqual({ + facets: ['kind'], + query: undefined, + }); + }); + + it('parses facets with a simple query', () => { + expect( + parseEntityFacetsQuery({ + facets: ['spec.type'], + query: { kind: 'Component' }, + }), + ).toEqual({ + facets: ['spec.type'], + query: { kind: 'Component' }, + }); + }); + + it('parses facets with complex predicate query', () => { + const query = { + $all: [{ kind: 'Component' }, { 'spec.lifecycle': 'production' }], + }; + expect(parseEntityFacetsQuery({ facets: ['spec.type'], query })).toEqual({ + facets: ['spec.type'], + query, + }); + }); + + it('throws on missing facets', () => { + expect(() => parseEntityFacetsQuery({} as any)).toThrow( + 'Missing or empty facets parameter', + ); + }); + + it('throws on empty facets array', () => { + expect(() => parseEntityFacetsQuery({ facets: [] })).toThrow( + 'Missing or empty facets parameter', + ); + }); + + it('throws on invalid query (null)', () => { + expect(() => + parseEntityFacetsQuery({ facets: ['kind'], query: null as any }), + ).toThrow(); + }); + + it('throws on invalid query (array)', () => { + expect(() => + parseEntityFacetsQuery({ facets: ['kind'], query: [] as any }), + ).toThrow(); + }); + + it('throws on invalid query (invalid operator)', () => { + expect(() => + parseEntityFacetsQuery({ + facets: ['kind'], + query: { $invalid: 'bad' } as any, + }), + ).toThrow(); + }); +});