104 lines
3.4 KiB
TypeScript
104 lines
3.4 KiB
TypeScript
|
|
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();
|