fix: harden release gate and page delivery
Memind CI / Test, build, and release guards (push) Failing after 12m3s
Memind CI / Test, build, and release guards (push) Failing after 12m3s
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
#!/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}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user