import { tap, expect } from '@git.zone/tstest/tapbundle'; import { makeTestBucket } from './helpers/services.js'; import type * as smartbucket from '@push.rocks/smartbucket'; import { SmartMigration } from '../ts/index.js'; // End-to-end test of an S3-backed migration. Each test uses a unique // `ledgerName` so its `.smartmigration/.json` sidecar object does // not collide with other tests in the file. let bucket: smartbucket.Bucket; tap.test('setup: connect to bucket', async () => { const r = await makeTestBucket(); bucket = r.bucket; // Wipe the smartmigration sidecar prefix from any prior runs. for await (const key of bucket.listAllObjects('.smartmigration/')) { await bucket.fastRemove({ path: key }); } }); tap.test('s3 run: applies an S3 migration step from scratch', async () => { const m = new SmartMigration({ targetVersion: '2.0.0', bucket, ledgerName: 'reorganize', }); // Pre-seed three "uploads/" objects so the migration has work to do. await bucket.fastPut({ path: 'uploads/a.txt', contents: 'a' }); await bucket.fastPut({ path: 'uploads/b.txt', contents: 'b' }); await bucket.fastPut({ path: 'uploads/c.txt', contents: 'c' }); m.step('move-uploads').from('1.0.0').to('2.0.0').up(async (ctx) => { for await (const key of ctx.bucket!.listAllObjects('uploads/')) { const newPath = 'media/' + key.slice('uploads/'.length); await ctx.bucket!.fastMove({ sourcePath: key, destinationPath: newPath, overwrite: true, }); } }); const r = await m.run(); expect(r.stepsApplied).toHaveLength(1); expect(r.currentVersionAfter).toEqual('2.0.0'); // Verify the data moved. expect(await bucket.fastExists({ path: 'media/a.txt' })).toBeTrue(); expect(await bucket.fastExists({ path: 'media/b.txt' })).toBeTrue(); expect(await bucket.fastExists({ path: 'media/c.txt' })).toBeTrue(); expect(await bucket.fastExists({ path: 'uploads/a.txt' })).toBeFalse(); // Cleanup so the next test starts clean. for (const k of ['media/a.txt', 'media/b.txt', 'media/c.txt']) { await bucket.fastRemove({ path: k }); } }); tap.test('s3 run: second run is a no-op', async () => { const m = new SmartMigration({ targetVersion: '1.1.0', bucket, ledgerName: 'noop', }); let calls = 0; m.step('once').from('1.0.0').to('1.1.0').up(async () => { calls++; }); const r1 = await m.run(); expect(r1.stepsApplied).toHaveLength(1); expect(calls).toEqual(1); // Re-run with a fresh runner. const m2 = new SmartMigration({ targetVersion: '1.1.0', bucket, ledgerName: 'noop' }); m2.step('once').from('1.0.0').to('1.1.0').up(async () => { calls++; }); const r2 = await m2.run(); expect(r2.wasUpToDate).toBeTrue(); expect(calls).toEqual(1); }); tap.test('s3 ctx exposes bucket and s3 client', async () => { const m = new SmartMigration({ targetVersion: '1.1.0', bucket, ledgerName: 'ctx', }); let observed: { hasBucket: boolean; hasS3: boolean } | null = null; m.step('check-ctx').from('1.0.0').to('1.1.0').up(async (ctx) => { observed = { hasBucket: !!ctx.bucket, hasS3: !!ctx.s3 }; }); await m.run(); expect(observed).not.toBeNull(); expect(observed!.hasBucket).toBeTrue(); expect(observed!.hasS3).toBeTrue(); }); tap.test('cleanup: wipe smartmigration sidecars', async () => { for await (const key of bucket.listAllObjects('.smartmigration/')) { await bucket.fastRemove({ path: key }); } }); export default tap.start();