Refactor code for improved readability and consistency

- Updated formatting in web-api.ts for better alignment and readability.
- Enhanced XML builders in xml.ts for clearer structure and maintainability.
- Improved test cases in s3-auth.test.ts and s3-operations.test.ts for better clarity and consistency.
- Refactored mock data in web-api.test.ts for improved readability.
This commit is contained in:
asepharyana
2026-07-06 22:36:15 +07:00
parent 2d3d7891a0
commit 245138e23a
8 changed files with 568 additions and 123 deletions
+38 -6
View File
@@ -19,35 +19,67 @@ describe('S3 Auth (SigV4)', () => {
});
it('rejects missing Authorization header', async () => {
const result = await verifySignature('GET', 'http://localhost/', {}, null, 'key', 'secret', 'us-east-1');
const result = await verifySignature(
'GET',
'http://localhost/',
{},
null,
'key',
'secret',
'us-east-1',
);
expect(result.isValid).toBe(false);
expect(result.errorCode).toBe('AccessDenied');
});
it('rejects wrong access key before signature calculation succeeds', async () => {
const headers = {
authorization: 'AWS4-HMAC-SHA256 Credential=wrongkey/20260706/us-east-1/s3/aws4_request, SignedHeaders=host, Signature=abc123',
authorization:
'AWS4-HMAC-SHA256 Credential=wrongkey/20260706/us-east-1/s3/aws4_request, SignedHeaders=host, Signature=abc123',
'x-amz-date': '20260706T120000Z',
host: 'localhost',
};
const result = await verifySignature('GET', 'http://localhost/', headers, null, 'correctkey', 'secret', 'us-east-1');
const result = await verifySignature(
'GET',
'http://localhost/',
headers,
null,
'correctkey',
'secret',
'us-east-1',
);
expect(result.isValid).toBe(false);
expect(result.errorCode).toBe('SignatureDoesNotMatch');
});
it('rejects region mismatch in Authorization credential scope', async () => {
const headers = {
authorization: 'AWS4-HMAC-SHA256 Credential=testkey/20260706/eu-west-1/s3/aws4_request, SignedHeaders=host;x-amz-date, Signature=abc123',
authorization:
'AWS4-HMAC-SHA256 Credential=testkey/20260706/eu-west-1/s3/aws4_request, SignedHeaders=host;x-amz-date, Signature=abc123',
'x-amz-date': '20260706T120000Z',
host: 'localhost',
};
const result = await verifySignature('GET', 'http://localhost/', headers, null, 'testkey', 'secret', 'us-east-1');
const result = await verifySignature(
'GET',
'http://localhost/',
headers,
null,
'testkey',
'secret',
'us-east-1',
);
expect(result.isValid).toBe(false);
expect(result.errorCode).toBe('SignatureDoesNotMatch');
});
it('rejects malformed presigned URLs', async () => {
const result = await verifyPresignedUrl('http://localhost/bucket/key', 'GET', 'key', 'secret', 'us-east-1');
const result = await verifyPresignedUrl(
'http://localhost/bucket/key',
'GET',
'key',
'secret',
'us-east-1',
);
expect(result.isValid).toBe(false);
expect(result.errorCode).toBe('AccessDenied');
});
+37 -8
View File
@@ -17,7 +17,15 @@ describe('S3 XML Builders', () => {
const xml = await import('../src/utils/s3/xml');
const result = xml.listBucketResultXml(
'my-bucket',
[{ key: 'folder/a&b.txt', sizeBytes: 100, etag: 'abc', lastModified: new Date('2026-01-01T00:00:00Z'), mimeType: 'text/plain' }],
[
{
key: 'folder/a&b.txt',
sizeBytes: 100,
etag: 'abc',
lastModified: new Date('2026-01-01T00:00:00Z'),
mimeType: 'text/plain',
},
],
['photos/'],
false,
null,
@@ -37,7 +45,15 @@ describe('S3 XML Builders', () => {
const xml = await import('../src/utils/s3/xml');
const result = xml.listBucketV2ResultXml(
'my-bucket',
[{ key: 'a.txt', sizeBytes: 50, etag: 'def', lastModified: new Date('2026-01-01T00:00:00Z'), mimeType: 'text/plain' }],
[
{
key: 'a.txt',
sizeBytes: 50,
etag: 'def',
lastModified: new Date('2026-01-01T00:00:00Z'),
mimeType: 'text/plain',
},
],
[],
false,
1000,
@@ -55,14 +71,25 @@ describe('S3 XML Builders', () => {
it('builds multipart and copy XML responses', async () => {
const xml = await import('../src/utils/s3/xml');
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');
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',
);
});
it('builds error XML and error Response', async () => {
const xml = await import('../src/utils/s3/xml');
const result = xml.s3ErrorXml('NoSuchBucket', 'The specified bucket does not exist', '/bucket', 'req-1');
const result = xml.s3ErrorXml(
'NoSuchBucket',
'The specified bucket does not exist',
'/bucket',
'req-1',
);
expect(result).toContain('<Code>NoSuchBucket</Code>');
expect(result).toContain('<RequestId>req-1</RequestId>');
@@ -73,7 +100,8 @@ describe('S3 XML Builders', () => {
it('parses DeleteObjects body', async () => {
const xml = await import('../src/utils/s3/xml');
const body = '<Delete><Object><Key>file1.txt</Key></Object><Object><Key>file2.txt</Key></Object><Quiet>true</Quiet></Delete>';
const body =
'<Delete><Object><Key>file1.txt</Key></Object><Object><Key>file2.txt</Key></Object><Quiet>true</Quiet></Delete>';
const { keys, quiet } = xml.parseDeleteObjectsBody(body);
expect(keys).toEqual(['file1.txt', 'file2.txt']);
expect(quiet).toBe(true);
@@ -81,7 +109,8 @@ describe('S3 XML Builders', () => {
it('parses CompleteMultipartUpload body', async () => {
const xml = await import('../src/utils/s3/xml');
const body = '<CompleteMultipartUpload><Part><PartNumber>1</PartNumber><ETag>"abc"</ETag></Part><Part><PartNumber>2</PartNumber><ETag>"def"</ETag></Part></CompleteMultipartUpload>';
const body =
'<CompleteMultipartUpload><Part><PartNumber>1</PartNumber><ETag>"abc"</ETag></Part><Part><PartNumber>2</PartNumber><ETag>"def"</ETag></Part></CompleteMultipartUpload>';
const parts = xml.parseCompleteMultipartBody(body);
expect(parts).toEqual([
{ partNumber: 1, etag: 'abc' },
+10 -3
View File
@@ -1,13 +1,20 @@
import { afterAll, beforeAll, describe, expect, it, mock } from 'bun:test';
const mockBuckets = [
{ id: 'uuid-1', name: 'test-bucket', createdAt: new Date('2026-01-01'), updatedAt: new Date('2026-01-01') },
{
id: 'uuid-1',
name: 'test-bucket',
createdAt: new Date('2026-01-01'),
updatedAt: new Date('2026-01-01'),
},
];
mock.module('../src/db/buckets', () => ({
listBuckets: () => Promise.resolve(mockBuckets),
findBucketByName: (name: string) => Promise.resolve(mockBuckets.find((b) => b.name === name) || null),
createBucket: (name: string) => Promise.resolve({ id: 'new-uuid', name, createdAt: new Date(), updatedAt: new Date() }),
findBucketByName: (name: string) =>
Promise.resolve(mockBuckets.find((b) => b.name === name) || null),
createBucket: (name: string) =>
Promise.resolve({ id: 'new-uuid', name, createdAt: new Date(), updatedAt: new Date() }),
deleteBucket: () => Promise.resolve(true),
bucketExists: () => Promise.resolve(false),
}));