import { assertEquals, assertExists } from 'jsr:@std/assert'; import { afterAll, beforeAll, describe, it } from 'jsr:@std/testing/bdd'; import { S3Client, ListBucketsCommand, CreateBucketCommand, DeleteBucketCommand, PutObjectCommand, GetObjectCommand, ListObjectsV2Command, CopyObjectCommand, DeleteObjectCommand, } from '@aws-sdk/client-s3'; import { createTestContainer, getTestPorts, TEST_ACCESS_KEY, TEST_SECRET_KEY } from './helpers/server.helper.ts'; import { ObjectStorageContainer } from '../ts/index.ts'; const PORT_INDEX = 4; const ports = getTestPorts(PORT_INDEX); const BUCKET = 's3-test-bucket'; describe('S3 SDK compatibility', { sanitizeResources: false, sanitizeOps: false }, () => { let container: ObjectStorageContainer; let s3: S3Client; beforeAll(async () => { container = createTestContainer(PORT_INDEX); await container.start(); s3 = new S3Client({ endpoint: `http://localhost:${ports.objstPort}`, region: 'us-east-1', credentials: { accessKeyId: TEST_ACCESS_KEY, secretAccessKey: TEST_SECRET_KEY, }, forcePathStyle: true, }); }); afterAll(async () => { s3.destroy(); await container.stop(); }); it('should list buckets (empty)', async () => { const response = await s3.send(new ListBucketsCommand({})); assertEquals((response.Buckets || []).length, 0); }); it('should create bucket via S3', async () => { await s3.send(new CreateBucketCommand({ Bucket: BUCKET })); const response = await s3.send(new ListBucketsCommand({})); const names = (response.Buckets || []).map((b) => b.Name); assertEquals(names.includes(BUCKET), true); }); it('should put object via S3', async () => { await s3.send( new PutObjectCommand({ Bucket: BUCKET, Key: 'test.txt', Body: new TextEncoder().encode('Hello from S3 SDK'), ContentType: 'text/plain', }), ); }); it('should get object via S3', async () => { const response = await s3.send( new GetObjectCommand({ Bucket: BUCKET, Key: 'test.txt' }), ); const body = await response.Body!.transformToString(); assertEquals(body, 'Hello from S3 SDK'); }); it('should list objects via S3', async () => { const response = await s3.send( new ListObjectsV2Command({ Bucket: BUCKET }), ); const keys = (response.Contents || []).map((o) => o.Key); assertEquals(keys.includes('test.txt'), true); }); it('should copy object via S3', async () => { await s3.send( new CopyObjectCommand({ Bucket: BUCKET, CopySource: `${BUCKET}/test.txt`, Key: 'copy.txt', }), ); const response = await s3.send( new GetObjectCommand({ Bucket: BUCKET, Key: 'copy.txt' }), ); const body = await response.Body!.transformToString(); assertEquals(body, 'Hello from S3 SDK'); }); it('should delete object via S3', async () => { await s3.send(new DeleteObjectCommand({ Bucket: BUCKET, Key: 'test.txt' })); await s3.send(new DeleteObjectCommand({ Bucket: BUCKET, Key: 'copy.txt' })); const response = await s3.send( new ListObjectsV2Command({ Bucket: BUCKET }), ); assertEquals((response.Contents || []).length, 0); }); it('should reject requests with wrong credentials', async () => { const badS3 = new S3Client({ endpoint: `http://localhost:${ports.objstPort}`, region: 'us-east-1', credentials: { accessKeyId: 'wrongkey', secretAccessKey: 'wrongsecret', }, forcePathStyle: true, }); let threw = false; try { await badS3.send(new ListBucketsCommand({})); } catch { threw = true; } badS3.destroy(); assertEquals(threw, true); }); it('cleanup: delete bucket', async () => { await s3.send(new DeleteBucketCommand({ Bucket: BUCKET })); const response = await s3.send(new ListBucketsCommand({})); assertEquals((response.Buckets || []).length, 0); }); });