From 31e2ca8d1836c21eb4922d171dedcc6c2424ef3a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 12 Dec 2024 14:34:16 +0100 Subject: [PATCH] catalog-backend: integration tests for raw response + fix raw json stream Signed-off-by: Patrik Oldsberg --- .../src/service/createRouter.test.ts | 5 +- plugins/catalog-backend/src/service/util.ts | 18 ++--- .../src/tests/integration.test.ts | 81 +++++++++++++++---- 3 files changed, 76 insertions(+), 28 deletions(-) diff --git a/plugins/catalog-backend/src/service/createRouter.test.ts b/plugins/catalog-backend/src/service/createRouter.test.ts index d4940e4a35..312900bcf1 100644 --- a/plugins/catalog-backend/src/service/createRouter.test.ts +++ b/plugins/catalog-backend/src/service/createRouter.test.ts @@ -860,7 +860,7 @@ describe('createRouter readonly disabled', () => { }); }); -describe('createRouter readonly enabled', () => { +describe('createRouter readonly and raw json enabled', () => { let entitiesCatalog: jest.Mocked; let app: express.Express; let locationService: jest.Mocked; @@ -883,6 +883,7 @@ describe('createRouter readonly enabled', () => { getLocationByEntity: jest.fn(), }; const router = await createRouter({ + enableRawJson: true, entitiesCatalog, locationService, logger: mockServices.logger.mock(), @@ -910,7 +911,7 @@ describe('createRouter readonly enabled', () => { ]; entitiesCatalog.queryEntities.mockResolvedValueOnce({ - items: { type: 'object', entities: [entities[0]] }, + items: { type: 'raw', entities: [JSON.stringify(entities[0])] }, pageInfo: {}, totalItems: 1, }); diff --git a/plugins/catalog-backend/src/service/util.ts b/plugins/catalog-backend/src/service/util.ts index 2126cf25b3..7ff7c8fcb3 100644 --- a/plugins/catalog-backend/src/service/util.ts +++ b/plugins/catalog-backend/src/service/util.ts @@ -192,17 +192,13 @@ export function createEntityArrayJsonStream( } if (response.type === 'raw') { - let result = true; + let needsDrain = false; for (const item of response.entities) { - if (firstSend) { - result ||= res.write('['); - firstSend = false; - } else { - result ||= res.write(','); - } - result ||= res.write(item); + const prefix = firstSend ? '[' : ','; + firstSend = false; + needsDrain ||= !res.write(prefix + item, 'utf8'); } - return result; + return !needsDrain; } let data: string; @@ -215,13 +211,13 @@ export function createEntityArrayJsonStream( } firstSend = false; - return res.write(data); + return res.write(data, 'utf8'); }, complete() { if (firstSend) { res.json([]); } else { - res.end(prettyPrint ? '\n]' : ']'); + res.end(prettyPrint ? '\n]' : ']', 'utf8'); } completed = true; }, diff --git a/plugins/catalog-backend/src/tests/integration.test.ts b/plugins/catalog-backend/src/tests/integration.test.ts index fdafbd5edf..32116ace0d 100644 --- a/plugins/catalog-backend/src/tests/integration.test.ts +++ b/plugins/catalog-backend/src/tests/integration.test.ts @@ -30,7 +30,6 @@ import { processingResult, } from '@backstage/plugin-catalog-node'; import { PermissionEvaluator } from '@backstage/plugin-permission-common'; -import { JsonObject } from '@backstage/types'; import { createHash } from 'crypto'; import { Knex } from 'knex'; import { EntitiesCatalog } from '../catalog/types'; @@ -199,7 +198,7 @@ class TestHarness { readonly #proxyProgressTracker: ProxyProgressTracker; static async create(options?: { - config?: JsonObject; + enableRawJson?: boolean; logger?: LoggerService; db?: Knex; permissions?: PermissionEvaluator; @@ -209,21 +208,19 @@ class TestHarness { emit: CatalogProcessorEmit, ): Promise; }) { - const config = new ConfigReader( - options?.config ?? { - backend: { - database: { - client: 'better-sqlite3', - connection: ':memory:', - }, - }, - catalog: { - stitchingStrategy: { - mode: 'immediate', - }, + const config = new ConfigReader({ + backend: { + database: { + client: 'better-sqlite3', + connection: ':memory:', }, }, - ); + catalog: { + stitchingStrategy: { + mode: 'immediate', + }, + }, + }); const logger = options?.logger ?? mockServices.logger.mock(); const db = options?.db ?? @@ -280,6 +277,7 @@ class TestHarness { database: db, logger, stitcher, + enableRawJson: options?.enableRawJson, }); const proxyProgressTracker = new ProxyProgressTracker( new NoopProgressTracker(), @@ -787,4 +785,57 @@ describe('Catalog Backend Integration', () => { ], }); }); + + it('should return valid responses in raw JSON mode', async () => { + const harness = await TestHarness.create({ + enableRawJson: true, + }); + + const entityA = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'a', + annotations: { + 'backstage.io/managed-by-location': 'url:.', + 'backstage.io/managed-by-origin-location': 'url:.', + }, + }, + }; + const entityB = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'b', + annotations: { + 'backstage.io/managed-by-location': 'url:.', + 'backstage.io/managed-by-origin-location': 'url:.', + }, + }, + }; + + await harness.setInputEntities([entityA, entityB]); + await expect(harness.process()).resolves.toEqual({}); + + await expect(harness.getOutputEntities()).resolves.toEqual({ + 'component:default/a': { + ...entityA, + metadata: { + ...entityA.metadata, + etag: expect.any(String), + uid: expect.any(String), + }, + relations: [], + }, + 'component:default/b': { + ...entityB, + metadata: { + ...entityB.metadata, + etag: expect.any(String), + uid: expect.any(String), + }, + relations: [], + }, + }); + }); });