2026-04-07 17:35:05 +00:00
|
|
|
import { Qenv } from '@push.rocks/qenv';
|
|
|
|
|
import * as smartdata from '@push.rocks/smartdata';
|
|
|
|
|
import * as smartbucket from '@push.rocks/smartbucket';
|
|
|
|
|
|
|
|
|
|
const qenv = new Qenv(process.cwd(), process.cwd() + '/.nogit/');
|
|
|
|
|
|
2026-04-14 12:31:34 +00:00
|
|
|
async function createSmartBucket(): Promise<smartbucket.SmartBucket> {
|
|
|
|
|
return new smartbucket.SmartBucket({
|
|
|
|
|
accessKey: await qenv.getEnvVarOnDemandStrict('S3_ACCESSKEY'),
|
|
|
|
|
accessSecret: await qenv.getEnvVarOnDemandStrict('S3_SECRETKEY'),
|
|
|
|
|
endpoint: await qenv.getEnvVarOnDemandStrict('S3_ENDPOINT'),
|
|
|
|
|
port: parseInt(await qenv.getEnvVarOnDemandStrict('S3_PORT'), 10),
|
|
|
|
|
useSsl: false,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildUniqueBucketName(baseName: string, suffix: string): string {
|
|
|
|
|
const safeBase = baseName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
|
|
|
const safeSuffix = suffix.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
|
|
|
const uniquePart = `${Date.now().toString(36)}-${Math.floor(Math.random() * 1e6).toString(36)}`;
|
|
|
|
|
const combined = `${safeBase}-${safeSuffix}-${uniquePart}`.replace(/-+/g, '-');
|
|
|
|
|
return combined.slice(0, 63).replace(/^-+|-+$/g, '');
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-07 17:35:05 +00:00
|
|
|
/**
|
|
|
|
|
* Spin up a fresh `SmartdataDb` connected to the local mongo from .nogit/env.json,
|
|
|
|
|
* scoped to a unique database name so tests cannot collide. Returns the db plus
|
|
|
|
|
* a `cleanup` function that drops the database and closes the connection.
|
|
|
|
|
*/
|
|
|
|
|
export async function makeTestDb(suffix: string) {
|
|
|
|
|
const baseUrl = await qenv.getEnvVarOnDemandStrict('MONGODB_URL');
|
|
|
|
|
const dbName = `smartmigration_test_${suffix}_${Date.now()}_${Math.floor(Math.random() * 1e6)}`;
|
|
|
|
|
// Replace the path component of the connection URL with our unique db name.
|
|
|
|
|
// Format from env.json:
|
|
|
|
|
// mongodb://defaultadmin:defaultpass@localhost:27970/push-rocks-smartmigration?authSource=admin
|
|
|
|
|
const url = new URL(baseUrl);
|
|
|
|
|
url.pathname = `/${dbName}`;
|
|
|
|
|
const db = new smartdata.SmartdataDb({
|
|
|
|
|
mongoDbUrl: url.toString(),
|
|
|
|
|
mongoDbName: dbName,
|
|
|
|
|
});
|
|
|
|
|
await db.init();
|
|
|
|
|
|
|
|
|
|
const cleanup = async () => {
|
|
|
|
|
try {
|
|
|
|
|
await db.mongoDb.dropDatabase();
|
|
|
|
|
} catch {
|
|
|
|
|
/* ignore */
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
await db.close();
|
|
|
|
|
} catch {
|
|
|
|
|
/* ignore */
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return { db, dbName, cleanup };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Connect to the local minio bucket from .nogit/env.json. The bucket is
|
|
|
|
|
* shared across tests, so each test should namespace its keys with a
|
|
|
|
|
* unique prefix and clean them up afterwards.
|
|
|
|
|
*/
|
|
|
|
|
export async function makeTestBucket() {
|
2026-04-14 12:31:34 +00:00
|
|
|
const sb = await createSmartBucket();
|
2026-04-07 17:35:05 +00:00
|
|
|
const bucketName = await qenv.getEnvVarOnDemandStrict('S3_BUCKET');
|
|
|
|
|
|
|
|
|
|
// The bucket may not exist yet — try to fetch it; if missing, create it.
|
|
|
|
|
let bucket: smartbucket.Bucket;
|
|
|
|
|
try {
|
|
|
|
|
bucket = await sb.getBucketByName(bucketName);
|
|
|
|
|
} catch {
|
|
|
|
|
bucket = (await sb.createBucket(bucketName)) as unknown as smartbucket.Bucket;
|
|
|
|
|
bucket = await sb.getBucketByName(bucketName);
|
|
|
|
|
}
|
|
|
|
|
return { sb, bucket, bucketName };
|
|
|
|
|
}
|
2026-04-14 12:31:34 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a unique bucket for tests that need a truly empty object store.
|
|
|
|
|
*/
|
|
|
|
|
export async function makeIsolatedTestBucket(suffix: string) {
|
|
|
|
|
const sb = await createSmartBucket();
|
|
|
|
|
const baseBucketName = await qenv.getEnvVarOnDemandStrict('S3_BUCKET');
|
|
|
|
|
const bucketName = buildUniqueBucketName(baseBucketName, suffix);
|
|
|
|
|
|
|
|
|
|
const bucket = await sb.createBucket(bucketName);
|
|
|
|
|
|
|
|
|
|
const cleanup = async () => {
|
|
|
|
|
try {
|
|
|
|
|
await bucket.cleanAllContents();
|
|
|
|
|
} catch {
|
|
|
|
|
/* ignore */
|
|
|
|
|
}
|
|
|
|
|
try {
|
|
|
|
|
await sb.removeBucket(bucketName);
|
|
|
|
|
} catch {
|
|
|
|
|
/* ignore */
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
return { sb, bucket, bucketName, cleanup };
|
|
|
|
|
}
|