From 339d156dba6744fb19355cf6d63c1342c51e9001 Mon Sep 17 00:00:00 2001 From: Osama Ansar Date: Mon, 14 Sep 2026 12:28:41 +0500 Subject: [PATCH] fix(data): guard deleteMany uuid stringify, guard journey afterAll A BatchDeleteObject's uuid can come back empty from the server; stringify() then throws an opaque TypeError instead of a usable error. Only stringify when the bytes are a well-formed 16-byte uuid, otherwise report id as undefined. DeleteManyObject.id widens to optional to match. Also guard journey.test.ts's afterAll so a beforeAll failure doesn't mask itself behind a second, unrelated TypeError from an undefined client. Both were flagged in #470 as confirmed, independent bugs found while investigating the tests-without-auth flake. --- src/collections/deserialize/index.ts | 5 +++- src/collections/deserialize/unit.test.ts | 30 ++++++++++++++++++++++++ src/collections/types/data.ts | 3 ++- test/collections/journey.test.ts | 2 +- 4 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 src/collections/deserialize/unit.test.ts diff --git a/src/collections/deserialize/index.ts b/src/collections/deserialize/index.ts index 5ad7489e..97723179 100644 --- a/src/collections/deserialize/index.ts +++ b/src/collections/deserialize/index.ts @@ -533,7 +533,10 @@ export class Deserialize { objects: verbose ? reply.objects.map((obj) => { return { - id: stringify(obj.uuid), + // stringify() throws on anything but a 16-byte uuid; the server + // can omit the uuid on a BatchDeleteObject, so guard it here + // instead of letting an opaque TypeError escape. + id: obj.uuid.length === 16 ? stringify(obj.uuid) : undefined, successful: obj.successful, error: obj.error, }; diff --git a/src/collections/deserialize/unit.test.ts b/src/collections/deserialize/unit.test.ts new file mode 100644 index 00000000..19581ff8 --- /dev/null +++ b/src/collections/deserialize/unit.test.ts @@ -0,0 +1,30 @@ +import { v4 } from 'uuid'; +import { describe, expect, it } from 'vitest'; +import { BatchDeleteReply } from '../../proto/v1/batch_delete.js'; +import { Deserialize } from './index.js'; + +describe('Unit testing of Deserialize.deleteMany', () => { + const baseReply = (uuid: Uint8Array): BatchDeleteReply => ({ + took: 0, + failed: 0, + matches: 1, + successful: 1, + objects: [{ uuid, successful: true, error: '' }], + }); + + it('should stringify a well-formed 16-byte uuid', () => { + const id = v4(); + const uuid = Uint8Array.from(Buffer.from(id.replace(/-/g, ''), 'hex')); + + const result = Deserialize.deleteMany(baseReply(uuid), true); + + expect(result.objects![0].id).toBe(id); + }); + + it('should return an undefined id instead of throwing when the server omits the uuid', () => { + const result = Deserialize.deleteMany(baseReply(new Uint8Array(0)), true); + + expect(result.objects![0].id).toBeUndefined(); + expect(result.objects![0].successful).toBe(true); + }); +}); diff --git a/src/collections/types/data.ts b/src/collections/types/data.ts index 0a4d33be..763ab3a7 100644 --- a/src/collections/types/data.ts +++ b/src/collections/types/data.ts @@ -9,7 +9,8 @@ export type DataObject = { }; export type DeleteManyObject = { - id: string; + /** Undefined when the server returned no uuid for this object. */ + id?: string; successful: boolean; error?: string; }; diff --git a/test/collections/journey.test.ts b/test/collections/journey.test.ts index 11d01222..094bdd83 100644 --- a/test/collections/journey.test.ts +++ b/test/collections/journey.test.ts @@ -13,7 +13,7 @@ describe('Journey testing of the client using a WCD cluster', () => { dateOfBirth: Date; }; - afterAll(() => client.collections.delete(collectionName)); + afterAll(() => client?.collections.delete(collectionName)); beforeAll(async () => { client = await weaviate.connectToWeaviateCloud(