diff --git a/package.json b/package.json index 9dbe4797..b993ed09 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@scality/cloudserverclient", - "version": "1.0.6", + "version": "1.0.7", "engines": { "node": ">=20" }, @@ -35,10 +35,10 @@ "build:generated:proxyBackbeatApis": "cd build/smithy/cloudserverProxyBackbeatApis/typescript-codegen && yarn install && yarn build", "build:wrapper": "tsc", "build": "yarn install && yarn clean:build && yarn build:smithy && yarn build:generated:backbeatRoutes && yarn build:generated:bucketQuota && yarn build:generated:proxyBackbeatApis && yarn build:wrapper", - "test": "jest", - "test:mongo-backend": "BACKEND_TYPE=mongo jest", - "test:metadata-backend": "BACKEND_TYPE=metadata jest", - "test:backbeat-apis": "BACKBEAT_SETUP=true yarn jest tests/testBackbeatProxyApis.test.ts", + "test": "NODE_OPTIONS='--experimental-vm-modules' jest", + "test:mongo-backend": "BACKEND_TYPE=mongo yarn test", + "test:metadata-backend": "BACKEND_TYPE=metadata yarn test", + "test:backbeat-apis": "BACKBEAT_SETUP=true yarn test -- tests/testBackbeatProxyApis.test.ts", "lint": "eslint src tests", "typecheck": "tsc --noEmit" }, diff --git a/src/commands/s3Extended/utils.ts b/src/commands/s3Extended/utils.ts index 820139a9..aabd4740 100644 --- a/src/commands/s3Extended/utils.ts +++ b/src/commands/s3Extended/utils.ts @@ -75,7 +75,7 @@ export function parseListObjectsUserMetadataMiddleware(captured: { xml: string } // eslint-disable-next-line @typescript-eslint/no-explicit-any return (next: any) => async (args: any) => { const result = await next(args); - const parsed = new XMLParser().parse(captured.xml); + const parsed = new XMLParser({ isArray: name => name === 'Contents' }).parse(captured.xml); const xmlContents = parsed?.ListBucketResult?.Contents; if (result.output.Contents && xmlContents) { for (let i = 0; i < result.output.Contents.length; i++) { diff --git a/tests/commands/s3Extended/utils.test.ts b/tests/commands/s3Extended/utils.test.ts new file mode 100644 index 00000000..99f22ae6 --- /dev/null +++ b/tests/commands/s3Extended/utils.test.ts @@ -0,0 +1,56 @@ +import { parseListObjectsUserMetadataMiddleware } from '../../../src/commands/s3Extended/utils'; + +describe('parseListObjectsUserMetadataMiddleware', () => { + function buildXml(contents: Record[]): string { + const inner = contents.map(obj => `${ + Object.entries(obj).map(([k, v]) => `<${k}>${v}`).join('') + }`).join(''); + return `${inner}`; + } + + function createNext(output: unknown) { + return async () => ({ output }); + } + + it('should extract user metadata when XML contains a single Contents element', async () => { + const captured = { + xml: buildXml([ + { 'Key': 'obj1', 'x-amz-meta-foo': 'bar' }, + ]), + }; + + const middleware = parseListObjectsUserMetadataMiddleware(captured); + const result = await middleware(createNext({ + Contents: [{ Key: 'obj1' }], + }))({ request: {} }); + expect(result.output.Contents[0]['x-amz-meta-foo']).toBe('bar'); + }); + + it('should extract user metadata when XML contains multiple Contents elements', async () => { + const captured = { + xml: buildXml([ + { 'Key': 'obj1', 'x-amz-meta-foo': 'bar' }, + { 'Key': 'obj2', 'x-amz-meta-foo': 'baz' }, + ]), + }; + + const middleware = parseListObjectsUserMetadataMiddleware(captured); + const result = await middleware(createNext({ + Contents: [{ Key: 'obj1' }, { Key: 'obj2' }], + }))({ request: {} }); + expect(result.output.Contents[0]['x-amz-meta-foo']).toBe('bar'); + expect(result.output.Contents[1]['x-amz-meta-foo']).toBe('baz'); + }); + + it('should handle empty Contents gracefully', async () => { + const captured = { + xml: buildXml([]), + }; + + const middleware = parseListObjectsUserMetadataMiddleware(captured); + const result = await middleware(createNext({ + Contents: undefined, + }))({ request: {} }); + expect(result.output.Contents).toBeUndefined(); + }); +});