import { expect, tap } from '@git.zone/tstest/tapbundle'; import { S3Client, CreateBucketCommand, ListBucketsCommand, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, DeleteBucketCommand } from '@aws-sdk/client-s3'; import { Readable } from 'stream'; import * as smarts3 from '../ts/index.js'; let testSmarts3Instance: smarts3.Smarts3; let s3Client: S3Client; // Helper to convert stream to string async function streamToString(stream: Readable): Promise { const chunks: Buffer[] = []; return new Promise((resolve, reject) => { stream.on('data', (chunk) => chunks.push(Buffer.from(chunk))); stream.on('error', reject); stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); }); } tap.test('should start the S3 server and configure client', async () => { testSmarts3Instance = await smarts3.Smarts3.createAndStart({ port: 3337, cleanSlate: true, silent: true, }); const descriptor = await testSmarts3Instance.getS3Descriptor(); s3Client = new S3Client({ endpoint: `http://${descriptor.endpoint}:${descriptor.port}`, region: 'us-east-1', credentials: { accessKeyId: descriptor.accessKey, secretAccessKey: descriptor.accessSecret, }, forcePathStyle: true, }); }); tap.test('should list buckets (empty)', async () => { const response = await s3Client.send(new ListBucketsCommand({})); expect(Array.isArray(response.Buckets)).toEqual(true); expect(response.Buckets!.length).toEqual(0); }); tap.test('should create a bucket', async () => { const response = await s3Client.send(new CreateBucketCommand({ Bucket: 'test-bucket' })); expect(response.$metadata.httpStatusCode).toEqual(200); }); tap.test('should list buckets (showing created bucket)', async () => { const response = await s3Client.send(new ListBucketsCommand({})); expect(response.Buckets!.length).toEqual(1); expect(response.Buckets![0].Name).toEqual('test-bucket'); }); tap.test('should upload an object', async () => { const response = await s3Client.send(new PutObjectCommand({ Bucket: 'test-bucket', Key: 'test-file.txt', Body: 'Hello from AWS SDK!', ContentType: 'text/plain', })); expect(response.$metadata.httpStatusCode).toEqual(200); expect(response.ETag).toBeTypeofString(); }); tap.test('should download the object', async () => { const response = await s3Client.send(new GetObjectCommand({ Bucket: 'test-bucket', Key: 'test-file.txt', })); expect(response.$metadata.httpStatusCode).toEqual(200); const content = await streamToString(response.Body as Readable); expect(content).toEqual('Hello from AWS SDK!'); }); tap.test('should delete the object', async () => { const response = await s3Client.send(new DeleteObjectCommand({ Bucket: 'test-bucket', Key: 'test-file.txt', })); expect(response.$metadata.httpStatusCode).toEqual(204); }); tap.test('should fail to get deleted object', async () => { await expect( s3Client.send(new GetObjectCommand({ Bucket: 'test-bucket', Key: 'test-file.txt', })) ).rejects.toThrow(); }); tap.test('should delete the bucket', async () => { const response = await s3Client.send(new DeleteBucketCommand({ Bucket: 'test-bucket' })); expect(response.$metadata.httpStatusCode).toEqual(204); }); tap.test('should stop the S3 server', async () => { await testSmarts3Instance.stop(); }); export default tap.start();