116 lines
4.6 KiB
JavaScript
116 lines
4.6 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFile } from "node:fs/promises";
|
|
import { DatabaseSync } from "node:sqlite";
|
|
import test from "node:test";
|
|
import {
|
|
AUDIT_PUBLIC_BETA_ACCESS_FULFILLMENT_SQL,
|
|
FULFILL_ACCESS_REQUESTS_BY_PUBLIC_BETA_SQL,
|
|
PUBLIC_BETA_ACCESS_RESPONSE,
|
|
} from "../db/access-requests.ts";
|
|
import { REQUIRED_PUBLIC_BETA_P0_KEYS } from "../db/launch-gates.ts";
|
|
|
|
function createDatabase() {
|
|
const db = new DatabaseSync(":memory:");
|
|
db.exec(`
|
|
CREATE TABLE beta_programs (
|
|
id TEXT PRIMARY KEY,
|
|
state TEXT NOT NULL,
|
|
starts_at TEXT NOT NULL,
|
|
ends_at TEXT
|
|
);
|
|
CREATE TABLE launch_gates (
|
|
key TEXT PRIMARY KEY,
|
|
priority TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
owner TEXT,
|
|
notes TEXT NOT NULL,
|
|
evidence_url TEXT
|
|
);
|
|
CREATE TABLE beta_access_requests (
|
|
id TEXT PRIMARY KEY,
|
|
status TEXT NOT NULL,
|
|
admin_response TEXT,
|
|
resolved_by TEXT,
|
|
invitation_grant_id TEXT,
|
|
resolved_at TEXT,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE TABLE audit_events (
|
|
id TEXT PRIMARY KEY,
|
|
actor_id TEXT NOT NULL,
|
|
action TEXT NOT NULL,
|
|
target_type TEXT NOT NULL,
|
|
target_id TEXT NOT NULL,
|
|
reason TEXT NOT NULL,
|
|
before_json TEXT,
|
|
after_json TEXT,
|
|
correlation_id TEXT NOT NULL,
|
|
created_at TEXT NOT NULL
|
|
);
|
|
INSERT INTO beta_access_requests VALUES
|
|
('access_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'requested', NULL, NULL, NULL, NULL, '2026-08-12T00:00:00.000Z'),
|
|
('access_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', 'declined', '此前已拒绝', 'admin-old', NULL, '2026-08-11T00:00:00.000Z', '2026-08-11T00:00:00.000Z');
|
|
INSERT INTO beta_programs VALUES
|
|
('beta_open', 'active', '2026-08-12T00:00:00.000Z', NULL);
|
|
`);
|
|
return db;
|
|
}
|
|
|
|
function reconcile(db, now = "2026-08-12T12:00:00.000Z") {
|
|
const actor = "admin-public-beta";
|
|
const correlation = "corr-public-beta";
|
|
return [
|
|
Number(db.prepare(FULFILL_ACCESS_REQUESTS_BY_PUBLIC_BETA_SQL).run(
|
|
PUBLIC_BETA_ACCESS_RESPONSE, actor, now,
|
|
).changes),
|
|
Number(db.prepare(AUDIT_PUBLIC_BETA_ACCESS_FULFILLMENT_SQL).run(
|
|
actor, PUBLIC_BETA_ACCESS_RESPONSE, correlation, now,
|
|
).changes),
|
|
];
|
|
}
|
|
|
|
test("fulfills pending requests only after active public beta has complete P0 evidence", () => {
|
|
const db = createDatabase();
|
|
for (const key of REQUIRED_PUBLIC_BETA_P0_KEYS.slice(0, -1)) {
|
|
db.prepare("INSERT INTO launch_gates VALUES (?, 'P0', 'passed', 'owner', 'verified', ?)")
|
|
.run(key, `https://evidence.example.test/${key}`);
|
|
}
|
|
assert.deepEqual(reconcile(db), [0, 0]);
|
|
assert.equal(db.prepare("SELECT status FROM beta_access_requests WHERE id LIKE 'access_a%'").get().status, "requested");
|
|
|
|
const lastKey = REQUIRED_PUBLIC_BETA_P0_KEYS.at(-1);
|
|
db.prepare("INSERT INTO launch_gates VALUES (?, 'P0', 'passed', 'owner', 'verified', ?)")
|
|
.run(lastKey, `https://evidence.example.test/${lastKey}`);
|
|
assert.deepEqual(reconcile(db), [1, 1]);
|
|
assert.deepEqual(
|
|
{ ...db.prepare("SELECT status, admin_response, resolved_by, invitation_grant_id FROM beta_access_requests WHERE id LIKE 'access_a%'").get() },
|
|
{
|
|
status: "approved",
|
|
admin_response: PUBLIC_BETA_ACCESS_RESPONSE,
|
|
resolved_by: "admin-public-beta",
|
|
invitation_grant_id: null,
|
|
},
|
|
);
|
|
assert.equal(db.prepare("SELECT status FROM beta_access_requests WHERE id LIKE 'access_b%'").get().status, "declined");
|
|
const audit = db.prepare("SELECT action, target_id, after_json FROM audit_events").get();
|
|
assert.equal(audit.action, "beta_access.fulfilled_by_public_beta");
|
|
assert.equal(audit.target_id, "access_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
|
|
assert.equal(JSON.parse(audit.after_json).adminResponse, PUBLIC_BETA_ACCESS_RESPONSE);
|
|
assert.deepEqual(reconcile(db, "2026-08-12T12:01:00.000Z"), [0, 0]);
|
|
db.close();
|
|
});
|
|
|
|
test("wires reconciliation into both ways public beta can become open", async () => {
|
|
const [repository, billingPage, adminPage] = await Promise.all([
|
|
readFile(new URL("../db/repository.ts", import.meta.url), "utf8"),
|
|
readFile(new URL("../app/dashboard/billing/page.tsx", import.meta.url), "utf8"),
|
|
readFile(new URL("../app/admin/page.tsx", import.meta.url), "utf8"),
|
|
]);
|
|
const fulfillmentUses = repository.match(/FULFILL_ACCESS_REQUESTS_BY_PUBLIC_BETA_SQL/g) ?? [];
|
|
const auditUses = repository.match(/AUDIT_PUBLIC_BETA_ACCESS_FULFILLMENT_SQL/g) ?? [];
|
|
assert.equal(fulfillmentUses.length, 3); // one import plus setPublicBeta and updateLaunchGate
|
|
assert.equal(auditUses.length, 3);
|
|
assert.match(billingPage, /request\.admin_response/);
|
|
assert.match(adminPage, /request\.admin_response/);
|
|
});
|