diff --git a/packages/catalog-client/src/testUtils/InMemoryCatalogClient.test.ts b/packages/catalog-client/src/testUtils/InMemoryCatalogClient.test.ts index f719204401..d867ab1d28 100644 --- a/packages/catalog-client/src/testUtils/InMemoryCatalogClient.test.ts +++ b/packages/catalog-client/src/testUtils/InMemoryCatalogClient.test.ts @@ -683,12 +683,11 @@ describe('InMemoryCatalogClient', () => { ]); }); - it('returns empty result for invalid cursor', async () => { + it('throws InputError for invalid cursor', async () => { const client = new InMemoryCatalogClient({ entities }); - const result = await client.queryEntities({ - cursor: 'not-valid-base64!', - }); - expect(result).toEqual({ items: [], pageInfo: {}, totalItems: 0 }); + await expect( + client.queryEntities({ cursor: 'not-valid-base64!' }), + ).rejects.toThrow('Invalid cursor'); }); }); diff --git a/packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts b/packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts index 0c93148c07..48111c96cf 100644 --- a/packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts +++ b/packages/catalog-client/src/testUtils/InMemoryCatalogClient.ts @@ -43,7 +43,11 @@ import { parseEntityRef, stringifyEntityRef, } from '@backstage/catalog-model'; -import { NotFoundError, NotImplementedError } from '@backstage/errors'; +import { + InputError, + NotFoundError, + NotImplementedError, +} from '@backstage/errors'; import lodash from 'lodash'; // eslint-disable-next-line @backstage/no-relative-monorepo-imports import { traverse } from '../../../../plugins/catalog-backend/src/database/operations/stitcher/buildEntitySearch'; @@ -53,12 +57,23 @@ import type { } from '@backstage/plugin-catalog-common'; import { DEFAULT_STREAM_ENTITIES_LIMIT } from '../constants.ts'; +function base64Encode(str: string): string { + const bytes = new TextEncoder().encode(str); + return btoa(Array.from(bytes, b => String.fromCodePoint(b)).join('')); +} + +function base64Decode(str: string): string { + const bin = atob(str); + const bytes = Uint8Array.from(bin, c => c.codePointAt(0)!); + return new TextDecoder().decode(bytes); +} + function makeCursor(data: Record): string { - return Buffer.from(JSON.stringify(data)).toString('base64'); + return base64Encode(JSON.stringify(data)); } function parseCursor(cursor: string): Record { - return JSON.parse(Buffer.from(cursor, 'base64').toString('utf8')); + return JSON.parse(base64Decode(cursor)); } // CATALOG_FILTER_EXISTS is a Symbol that doesn't survive JSON serialization, @@ -328,7 +343,7 @@ export class InMemoryCatalogClient implements CatalogApi { if (cursor.offset !== undefined) offset = cursor.offset as number; if (cursor.limit !== undefined) limit = cursor.limit as number; } catch { - // ignore invalid cursor + throw new InputError('Invalid cursor'); } } @@ -376,7 +391,7 @@ export class InMemoryCatalogClient implements CatalogApi { try { c = parseCursor(request.cursor); } catch { - return { items: [], pageInfo: {}, totalItems: 0 }; + throw new InputError('Invalid cursor'); } filter = deserializeFilter(c.filter as any[]); orderFields = c.orderFields as EntityOrderQuery | undefined;