2026-07-06 21:15:26 +07:00
|
|
|
import { describe, expect, it } from 'bun:test';
|
|
|
|
|
|
|
|
|
|
describe('S3 XML Builders', () => {
|
|
|
|
|
it('builds ListBuckets XML', async () => {
|
|
|
|
|
const xml = await import('../src/utils/s3/xml');
|
|
|
|
|
const result = xml.listBucketsXml(
|
|
|
|
|
[{ name: 'test-bucket', createdAt: new Date('2026-01-01T00:00:00Z') }],
|
|
|
|
|
'req-1',
|
|
|
|
|
);
|
|
|
|
|
expect(result).toContain('<?xml');
|
|
|
|
|
expect(result).toContain('<ListAllMyBucketsResult');
|
|
|
|
|
expect(result).toContain('<Name>test-bucket</Name>');
|
|
|
|
|
expect(result).toContain('<CreationDate>2026-01-01T00:00:00Z</CreationDate>');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('builds escaped ListBucketResult XML', async () => {
|
|
|
|
|
const xml = await import('../src/utils/s3/xml');
|
|
|
|
|
const result = xml.listBucketResultXml(
|
|
|
|
|
'my-bucket',
|
2026-07-06 22:36:15 +07:00
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
key: 'folder/a&b.txt',
|
|
|
|
|
sizeBytes: 100,
|
|
|
|
|
etag: 'abc',
|
|
|
|
|
lastModified: new Date('2026-01-01T00:00:00Z'),
|
|
|
|
|
mimeType: 'text/plain',
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-07-06 21:15:26 +07:00
|
|
|
['photos/'],
|
|
|
|
|
false,
|
|
|
|
|
null,
|
|
|
|
|
1000,
|
|
|
|
|
'',
|
|
|
|
|
'/',
|
|
|
|
|
null,
|
|
|
|
|
'req-1',
|
|
|
|
|
);
|
|
|
|
|
expect(result).toContain('<ListBucketResult');
|
|
|
|
|
expect(result).toContain('<Key>folder/a&b.txt</Key>');
|
|
|
|
|
expect(result).toContain('<Size>100</Size>');
|
|
|
|
|
expect(result).toContain('<Prefix>photos/</Prefix>');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('builds ListBucketV2 XML', async () => {
|
|
|
|
|
const xml = await import('../src/utils/s3/xml');
|
|
|
|
|
const result = xml.listBucketV2ResultXml(
|
|
|
|
|
'my-bucket',
|
2026-07-06 22:36:15 +07:00
|
|
|
[
|
|
|
|
|
{
|
|
|
|
|
key: 'a.txt',
|
|
|
|
|
sizeBytes: 50,
|
|
|
|
|
etag: 'def',
|
|
|
|
|
lastModified: new Date('2026-01-01T00:00:00Z'),
|
|
|
|
|
mimeType: 'text/plain',
|
|
|
|
|
},
|
|
|
|
|
],
|
2026-07-06 21:15:26 +07:00
|
|
|
[],
|
|
|
|
|
false,
|
|
|
|
|
1000,
|
|
|
|
|
'',
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
null,
|
|
|
|
|
1,
|
|
|
|
|
'req-2',
|
|
|
|
|
);
|
|
|
|
|
expect(result).toContain('<ListBucketResultV2');
|
|
|
|
|
expect(result).toContain('<KeyCount>1</KeyCount>');
|
|
|
|
|
expect(result).toContain('<Key>a.txt</Key>');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('builds multipart and copy XML responses', async () => {
|
|
|
|
|
const xml = await import('../src/utils/s3/xml');
|
2026-07-06 22:36:15 +07:00
|
|
|
expect(xml.initiateMultipartUploadXml('bucket', 'key', 'upload-123')).toContain(
|
|
|
|
|
'<UploadId>upload-123</UploadId>',
|
|
|
|
|
);
|
|
|
|
|
expect(
|
|
|
|
|
xml.completeMultipartUploadXml('bucket', 'key', 'etag-abc', 'http://localhost/bucket/key'),
|
|
|
|
|
).toContain('<CompleteMultipartUploadResult');
|
|
|
|
|
expect(xml.copyObjectResultXml('etag-abc', new Date('2026-01-01T00:00:00Z'))).toContain(
|
|
|
|
|
'<CopyObjectResult',
|
|
|
|
|
);
|
2026-07-06 21:15:26 +07:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('builds error XML and error Response', async () => {
|
|
|
|
|
const xml = await import('../src/utils/s3/xml');
|
2026-07-06 22:36:15 +07:00
|
|
|
const result = xml.s3ErrorXml(
|
|
|
|
|
'NoSuchBucket',
|
|
|
|
|
'The specified bucket does not exist',
|
|
|
|
|
'/bucket',
|
|
|
|
|
'req-1',
|
|
|
|
|
);
|
2026-07-06 21:15:26 +07:00
|
|
|
expect(result).toContain('<Code>NoSuchBucket</Code>');
|
|
|
|
|
expect(result).toContain('<RequestId>req-1</RequestId>');
|
|
|
|
|
|
|
|
|
|
const res = xml.s3ErrorResponse('NoSuchBucket', 'Missing', '/bucket', 404, 'req-2');
|
|
|
|
|
expect(res.status).toBe(404);
|
|
|
|
|
expect(res.headers.get('x-amz-request-id')).toBe('req-2');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('parses DeleteObjects body', async () => {
|
|
|
|
|
const xml = await import('../src/utils/s3/xml');
|
2026-07-06 22:36:15 +07:00
|
|
|
const body =
|
|
|
|
|
'<Delete><Object><Key>file1.txt</Key></Object><Object><Key>file2.txt</Key></Object><Quiet>true</Quiet></Delete>';
|
2026-07-06 21:15:26 +07:00
|
|
|
const { keys, quiet } = xml.parseDeleteObjectsBody(body);
|
|
|
|
|
expect(keys).toEqual(['file1.txt', 'file2.txt']);
|
|
|
|
|
expect(quiet).toBe(true);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('parses CompleteMultipartUpload body', async () => {
|
|
|
|
|
const xml = await import('../src/utils/s3/xml');
|
2026-07-06 22:36:15 +07:00
|
|
|
const body =
|
|
|
|
|
'<CompleteMultipartUpload><Part><PartNumber>1</PartNumber><ETag>"abc"</ETag></Part><Part><PartNumber>2</PartNumber><ETag>"def"</ETag></Part></CompleteMultipartUpload>';
|
2026-07-06 21:15:26 +07:00
|
|
|
const parts = xml.parseCompleteMultipartBody(body);
|
|
|
|
|
expect(parts).toEqual([
|
|
|
|
|
{ partNumber: 1, etag: 'abc' },
|
|
|
|
|
{ partNumber: 2, etag: 'def' },
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
});
|