136 lines
4.7 KiB
JavaScript
136 lines
4.7 KiB
JavaScript
#!/usr/bin/env node
|
|
import fs from 'node:fs/promises';
|
|
import path from 'node:path';
|
|
|
|
import mysql from 'mysql2/promise';
|
|
import pg from 'pg';
|
|
|
|
import { assertPortalRuntimePath, hashArtifact } from '../release-gate/artifact.mjs';
|
|
import { createLocalGateStack } from '../release-gate/local-stack.mjs';
|
|
|
|
const { Client: PgClient } = pg;
|
|
const ROOT = path.resolve(new URL('..', import.meta.url).pathname);
|
|
const runtimeRoot = assertPortalRuntimePath(path.join(ROOT, '.runtime', 'portal'), { repoRoot: ROOT });
|
|
const artifactBefore = await hashArtifact(runtimeRoot);
|
|
const runId = `upgrade-${new Date().toISOString().replace(/\D/g, '').slice(0, 14)}`;
|
|
const markerUserId = '00000000-0000-4000-8000-000000000701';
|
|
const markerUsername = 'gate_upgrade_fixture';
|
|
const markerValue = 'sanitized-page-data-fixture';
|
|
|
|
const stack = await createLocalGateStack({
|
|
root: ROOT,
|
|
runId,
|
|
port: Number(process.env.MEMIND_RELEASE_GATE_UPGRADE_PORT ?? 19085),
|
|
portalRoot: runtimeRoot,
|
|
nodeEnv: 'production',
|
|
runtimeProfile: 'production',
|
|
async beforePortalStart({ mysqlUrl, pgUrl }) {
|
|
const schemaSql = await fs.readFile(path.join(ROOT, 'schema.sql'), 'utf8');
|
|
const mysqlConnection = await mysql.createConnection({
|
|
uri: mysqlUrl,
|
|
multipleStatements: true,
|
|
});
|
|
try {
|
|
await mysqlConnection.query(schemaSql);
|
|
const now = Date.now();
|
|
await mysqlConnection.query(
|
|
`INSERT INTO h5_users
|
|
(id, username, slug, email, display_name, salt, password_hash, password_algorithm,
|
|
role, status, plan_type, workspace_root, low_balance_gift_eligible,
|
|
low_balance_gift_granted_at, created_at, updated_at)
|
|
VALUES (?, ?, ?, NULL, ?, ?, ?, 'pbkdf2-sha512',
|
|
'user', 'active', 'free', ?, 0, NULL, ?, ?)`,
|
|
[
|
|
markerUserId,
|
|
markerUsername,
|
|
markerUsername,
|
|
'脱敏升级测试用户',
|
|
'fixture-salt',
|
|
'fixture-hash',
|
|
`/sanitized/${markerUserId}`,
|
|
now,
|
|
now,
|
|
],
|
|
);
|
|
await mysqlConnection.query('ALTER TABLE h5_users DROP INDEX uq_h5_users_slug');
|
|
await mysqlConnection.query('ALTER TABLE h5_users DROP INDEX uq_h5_users_email');
|
|
await mysqlConnection.query(
|
|
`ALTER TABLE h5_users
|
|
DROP COLUMN slug,
|
|
DROP COLUMN email,
|
|
DROP COLUMN password_algorithm,
|
|
DROP COLUMN plan_type`,
|
|
);
|
|
} finally {
|
|
await mysqlConnection.end();
|
|
}
|
|
|
|
const pgClient = new PgClient({ connectionString: pgUrl });
|
|
await pgClient.connect();
|
|
try {
|
|
await pgClient.query(
|
|
`CREATE TABLE gate_sanitized_upgrade_fixture (
|
|
id TEXT PRIMARY KEY,
|
|
value TEXT NOT NULL
|
|
)`,
|
|
);
|
|
await pgClient.query(
|
|
'INSERT INTO gate_sanitized_upgrade_fixture (id, value) VALUES ($1, $2)',
|
|
['fixture-1', markerValue],
|
|
);
|
|
} finally {
|
|
await pgClient.end();
|
|
}
|
|
},
|
|
});
|
|
|
|
try {
|
|
async function verifyPreservedState() {
|
|
const mysqlConnection = await mysql.createConnection(stack.mysqlUrl);
|
|
try {
|
|
const [users] = await mysqlConnection.query(
|
|
`SELECT username, slug, password_algorithm, plan_type
|
|
FROM h5_users
|
|
WHERE id = ?`,
|
|
[markerUserId],
|
|
);
|
|
if (users.length !== 1) throw new Error('sanitized MySQL user fixture was lost');
|
|
if (users[0].slug !== markerUsername) throw new Error('legacy user slug was not migrated');
|
|
if (users[0].password_algorithm !== 'pbkdf2-sha512') {
|
|
throw new Error('legacy password algorithm default was not restored');
|
|
}
|
|
if (users[0].plan_type !== 'free') throw new Error('legacy plan default was not restored');
|
|
} finally {
|
|
await mysqlConnection.end();
|
|
}
|
|
|
|
const pgClient = new PgClient({ connectionString: stack.pgUrl });
|
|
await pgClient.connect();
|
|
try {
|
|
const result = await pgClient.query(
|
|
'SELECT value FROM gate_sanitized_upgrade_fixture WHERE id = $1',
|
|
['fixture-1'],
|
|
);
|
|
if (result.rows[0]?.value !== markerValue) {
|
|
throw new Error('sanitized PostgreSQL fixture was lost');
|
|
}
|
|
} finally {
|
|
await pgClient.end();
|
|
}
|
|
}
|
|
|
|
await verifyPreservedState();
|
|
await stack.restartPortal();
|
|
await verifyPreservedState();
|
|
console.log('PASS REL-07 legacy MySQL columns migrate and sanitized user data is preserved');
|
|
console.log('PASS REL-07 Page Data PostgreSQL fixture survives two idempotent packaged-runtime starts');
|
|
} finally {
|
|
await stack.cleanup();
|
|
const artifactAfter = await hashArtifact(runtimeRoot);
|
|
if (artifactAfter.sha256 !== artifactBefore.sha256) {
|
|
throw new Error(
|
|
`packaged runtime mutated its own artifact during upgrade verification: ${artifactBefore.sha256} -> ${artifactAfter.sha256}`,
|
|
);
|
|
}
|
|
}
|