Files
smartmigration/test/test.run.s3.ts

104 lines
3.4 KiB
TypeScript
Raw Permalink Normal View History

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/<name>.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();