import { expect, tap } from '@git.zone/tstest/tapbundle'; import { CreateBucketCommand, DeleteBucketCommand, ListBucketsCommand, S3Client, } from '@aws-sdk/client-s3'; import * as smartstorage from '../ts/index.js'; const TEST_PORT = 3349; const INITIAL_CREDENTIAL: smartstorage.IStorageCredential = { accessKeyId: 'RUNTIMEINITIAL', secretAccessKey: 'RUNTIMEINITIALSECRET123', }; const ROTATED_CREDENTIAL_A: smartstorage.IStorageCredential = { accessKeyId: 'RUNTIMEA', secretAccessKey: 'RUNTIMEASECRET123', }; const ROTATED_CREDENTIAL_B: smartstorage.IStorageCredential = { accessKeyId: 'RUNTIMEB', secretAccessKey: 'RUNTIMEBSECRET123', }; const TEST_BUCKET = 'runtime-credentials-bucket'; let testSmartStorageInstance: smartstorage.SmartStorage; let initialClient: S3Client; let rotatedClientA: S3Client; let rotatedClientB: S3Client; function createS3Client(credential: smartstorage.IStorageCredential): S3Client { return new S3Client({ endpoint: `http://localhost:${TEST_PORT}`, region: 'us-east-1', credentials: { accessKeyId: credential.accessKeyId, secretAccessKey: credential.secretAccessKey, }, forcePathStyle: true, }); } tap.test('setup: start storage server with runtime-managed credentials', async () => { testSmartStorageInstance = await smartstorage.SmartStorage.createAndStart({ server: { port: TEST_PORT, silent: true, region: 'us-east-1', }, storage: { cleanSlate: true, }, auth: { enabled: true, credentials: [INITIAL_CREDENTIAL], }, }); initialClient = createS3Client(INITIAL_CREDENTIAL); rotatedClientA = createS3Client(ROTATED_CREDENTIAL_A); rotatedClientB = createS3Client(ROTATED_CREDENTIAL_B); }); tap.test('startup credentials authenticate successfully', async () => { const response = await initialClient.send(new ListBucketsCommand({})); expect(response.$metadata.httpStatusCode).toEqual(200); }); tap.test('listCredentials returns the active startup credential set', async () => { const credentials = await testSmartStorageInstance.listCredentials(); expect(credentials.length).toEqual(1); expect(credentials[0].accessKeyId).toEqual(INITIAL_CREDENTIAL.accessKeyId); expect(credentials[0].secretAccessKey).toEqual(INITIAL_CREDENTIAL.secretAccessKey); }); tap.test('invalid replacement input fails cleanly and leaves old credentials active', async () => { await expect( testSmartStorageInstance.replaceCredentials([ { accessKeyId: '', secretAccessKey: 'invalid-secret', }, ]), ).rejects.toThrow(); const credentials = await testSmartStorageInstance.listCredentials(); expect(credentials.length).toEqual(1); expect(credentials[0].accessKeyId).toEqual(INITIAL_CREDENTIAL.accessKeyId); const response = await initialClient.send(new ListBucketsCommand({})); expect(response.$metadata.httpStatusCode).toEqual(200); }); tap.test('replacing credentials swaps the active set atomically', async () => { await testSmartStorageInstance.replaceCredentials([ ROTATED_CREDENTIAL_A, ROTATED_CREDENTIAL_B, ]); const credentials = await testSmartStorageInstance.listCredentials(); expect(credentials.length).toEqual(2); expect(credentials[0].accessKeyId).toEqual(ROTATED_CREDENTIAL_A.accessKeyId); expect(credentials[1].accessKeyId).toEqual(ROTATED_CREDENTIAL_B.accessKeyId); }); tap.test('old credentials stop working immediately for new requests', async () => { await expect(initialClient.send(new ListBucketsCommand({}))).rejects.toThrow(); }); tap.test('first rotated credential authenticates successfully', async () => { const response = await rotatedClientA.send( new CreateBucketCommand({ Bucket: TEST_BUCKET }), ); expect(response.$metadata.httpStatusCode).toEqual(200); }); tap.test('multiple rotated credentials remain active', async () => { const response = await rotatedClientB.send(new ListBucketsCommand({})); expect(response.$metadata.httpStatusCode).toEqual(200); expect(response.Buckets?.some((bucket) => bucket.Name === TEST_BUCKET)).toEqual(true); }); tap.test('duplicate replacement input fails cleanly without changing the active set', async () => { await expect( testSmartStorageInstance.replaceCredentials([ ROTATED_CREDENTIAL_A, { accessKeyId: ROTATED_CREDENTIAL_A.accessKeyId, secretAccessKey: 'another-secret', }, ]), ).rejects.toThrow(); const credentials = await testSmartStorageInstance.listCredentials(); expect(credentials.length).toEqual(2); expect(credentials[0].accessKeyId).toEqual(ROTATED_CREDENTIAL_A.accessKeyId); expect(credentials[1].accessKeyId).toEqual(ROTATED_CREDENTIAL_B.accessKeyId); const response = await rotatedClientA.send(new ListBucketsCommand({})); expect(response.$metadata.httpStatusCode).toEqual(200); }); tap.test('teardown: clean up bucket and stop the storage server', async () => { const response = await rotatedClientA.send( new DeleteBucketCommand({ Bucket: TEST_BUCKET }), ); expect(response.$metadata.httpStatusCode).toEqual(204); await testSmartStorageInstance.stop(); }); export default tap.start()