267 lines
12 KiB
JavaScript
267 lines
12 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 {
|
|
APPROVE_ACCESS_REQUEST_SQL,
|
|
CANCEL_ACCESS_REQUEST_SQL,
|
|
CREATE_ACCESS_CANCELLATION_IDEMPOTENCY_SQL,
|
|
CREATE_ACCESS_REQUEST_IDEMPOTENCY_SQL,
|
|
CREATE_ACCESS_REQUEST_SQL,
|
|
CREATE_ACCESS_RESOLUTION_IDEMPOTENCY_SQL,
|
|
CREATE_APPROVED_INVITATION_SQL,
|
|
CREATE_MANUAL_INVITATION_IDEMPOTENCY_SQL,
|
|
CREATE_MANUAL_INVITATION_SQL,
|
|
DECLINE_ACCESS_REQUEST_SQL,
|
|
} from "../db/access-requests.ts";
|
|
|
|
function createDatabase() {
|
|
const database = new DatabaseSync(":memory:");
|
|
database.exec(`
|
|
PRAGMA foreign_keys = ON;
|
|
CREATE TABLE accounts (id TEXT PRIMARY KEY);
|
|
CREATE TABLE entitlement_grants (
|
|
id TEXT PRIMARY KEY,
|
|
account_id TEXT NOT NULL,
|
|
host_id TEXT,
|
|
source TEXT NOT NULL,
|
|
source_ref TEXT,
|
|
capacity_slots INTEGER,
|
|
starts_at TEXT NOT NULL,
|
|
ends_at TEXT,
|
|
state TEXT NOT NULL,
|
|
reason TEXT NOT NULL,
|
|
created_by TEXT NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
revoked_at TEXT
|
|
);
|
|
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,
|
|
account_id TEXT NOT NULL,
|
|
status TEXT NOT NULL DEFAULT 'requested',
|
|
preferred_os TEXT NOT NULL,
|
|
requested_slots INTEGER NOT NULL DEFAULT 1,
|
|
use_case TEXT NOT NULL,
|
|
admin_response TEXT,
|
|
resolved_by TEXT,
|
|
invitation_grant_id TEXT,
|
|
requested_at TEXT NOT NULL,
|
|
resolved_at TEXT,
|
|
cancelled_at TEXT,
|
|
created_at TEXT NOT NULL,
|
|
updated_at TEXT NOT NULL
|
|
);
|
|
CREATE UNIQUE INDEX idx_beta_access_requests_one_pending
|
|
ON beta_access_requests (account_id) WHERE status = 'requested';
|
|
CREATE TABLE idempotency_records (
|
|
scope TEXT NOT NULL,
|
|
key TEXT NOT NULL,
|
|
request_hash TEXT NOT NULL,
|
|
response_json TEXT NOT NULL,
|
|
status_code INTEGER NOT NULL,
|
|
expires_at TEXT NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
PRIMARY KEY (scope, key)
|
|
);
|
|
INSERT INTO accounts VALUES ('account_a'), ('account_b');
|
|
`);
|
|
return database;
|
|
}
|
|
|
|
function createRequest(database, { id, key, account = "account_a", now }) {
|
|
const scope = `access-request:create:${account}`;
|
|
const hash = `hash-${key}`;
|
|
const response = JSON.stringify({ id, account_id: account, status: "requested" });
|
|
return [
|
|
Number(database.prepare(CREATE_ACCESS_REQUEST_IDEMPOTENCY_SQL).run(
|
|
scope, key, hash, response, "2026-09-01T00:00:00.000Z", now, account,
|
|
).changes),
|
|
Number(database.prepare(CREATE_ACCESS_REQUEST_SQL).run(
|
|
id, account, "windows", 1, "从手机继续本地主机上的已有开发任务。", now,
|
|
scope, key, hash,
|
|
).changes),
|
|
];
|
|
}
|
|
|
|
test("fences one pending beta request and requires account ownership to cancel", () => {
|
|
const database = createDatabase();
|
|
const now = "2026-08-12T12:00:00.000Z";
|
|
assert.deepEqual(createRequest(database, { id: "access_1", key: "create-1", now }), [1, 1]);
|
|
assert.deepEqual(createRequest(database, { id: "access_2", key: "create-2", now }), [0, 0]);
|
|
|
|
const wrongScope = "access-request:cancel:account_b";
|
|
assert.deepEqual([
|
|
Number(database.prepare(CREATE_ACCESS_CANCELLATION_IDEMPOTENCY_SQL).run(
|
|
wrongScope, "cancel-wrong", "wrong-hash", "{}", "2026-09-01T00:00:00.000Z",
|
|
now, "access_1", "account_b",
|
|
).changes),
|
|
Number(database.prepare(CANCEL_ACCESS_REQUEST_SQL).run(
|
|
now, "access_1", "account_b", wrongScope, "cancel-wrong", "wrong-hash",
|
|
).changes),
|
|
], [0, 0]);
|
|
|
|
const scope = "access-request:cancel:account_a";
|
|
assert.deepEqual([
|
|
Number(database.prepare(CREATE_ACCESS_CANCELLATION_IDEMPOTENCY_SQL).run(
|
|
scope, "cancel-1", "cancel-hash", "{}", "2026-09-01T00:00:00.000Z",
|
|
now, "access_1", "account_a",
|
|
).changes),
|
|
Number(database.prepare(CANCEL_ACCESS_REQUEST_SQL).run(
|
|
now, "access_1", "account_a", scope, "cancel-1", "cancel-hash",
|
|
).changes),
|
|
], [1, 1]);
|
|
assert.equal(database.prepare("SELECT status FROM beta_access_requests WHERE id = 'access_1'").get().status, "cancelled");
|
|
assert.deepEqual(createRequest(database, { id: "access_3", key: "create-3", now }), [1, 1]);
|
|
database.close();
|
|
});
|
|
|
|
test("approval atomically creates a bounded free invitation while decline creates none", () => {
|
|
const database = createDatabase();
|
|
const now = "2026-08-12T12:00:00.000Z";
|
|
assert.deepEqual(createRequest(database, { id: "access_approve", key: "create-a", now }), [1, 1]);
|
|
const scope = "admin:access-request:resolve";
|
|
const key = "resolve-a";
|
|
const hash = "resolve-hash-a";
|
|
const grantId = "grant_approve";
|
|
database.exec("BEGIN IMMEDIATE");
|
|
const approved = [
|
|
Number(database.prepare(CREATE_ACCESS_RESOLUTION_IDEMPOTENCY_SQL).run(
|
|
scope, key, hash, "{}", 200, "2026-09-01T00:00:00.000Z", now, "access_approve",
|
|
).changes),
|
|
Number(database.prepare(CREATE_APPROVED_INVITATION_SQL).run(
|
|
grantId, "access_approve", 2, now, "2026-11-12T12:00:00.000Z", "首批闭测",
|
|
"admin", "access_approve", scope, key, hash,
|
|
).changes),
|
|
Number(database.prepare(APPROVE_ACCESS_REQUEST_SQL).run(
|
|
"已开放两台主机。", "admin", grantId, now, "access_approve", scope, key, hash,
|
|
).changes),
|
|
];
|
|
database.exec("COMMIT");
|
|
assert.deepEqual(approved, [1, 1, 1]);
|
|
assert.deepEqual(
|
|
{ ...database.prepare("SELECT status, invitation_grant_id FROM beta_access_requests WHERE id = 'access_approve'").get() },
|
|
{ status: "approved", invitation_grant_id: grantId },
|
|
);
|
|
assert.deepEqual(
|
|
{ ...database.prepare("SELECT source, source_ref, capacity_slots, state FROM entitlement_grants WHERE id = ?").get(grantId) },
|
|
{ source: "admin_exemption", source_ref: "access_approve", capacity_slots: 2, state: "active" },
|
|
);
|
|
|
|
assert.deepEqual([
|
|
Number(database.prepare(CREATE_ACCESS_RESOLUTION_IDEMPOTENCY_SQL).run(
|
|
scope, "resolve-again", "resolve-hash-again", "{}", 200,
|
|
"2026-09-01T00:00:00.000Z", now, "access_approve",
|
|
).changes),
|
|
Number(database.prepare(DECLINE_ACCESS_REQUEST_SQL).run(
|
|
"重复处理", "admin", now, "access_approve", scope, "resolve-again", "resolve-hash-again",
|
|
).changes),
|
|
], [0, 0]);
|
|
|
|
assert.deepEqual(createRequest(database, { id: "access_decline", key: "create-d", account: "account_b", now }), [1, 1]);
|
|
const declineKey = "resolve-d";
|
|
const declineHash = "resolve-hash-d";
|
|
assert.deepEqual([
|
|
Number(database.prepare(CREATE_ACCESS_RESOLUTION_IDEMPOTENCY_SQL).run(
|
|
scope, declineKey, declineHash, "{}", 200, "2026-09-01T00:00:00.000Z", now, "access_decline",
|
|
).changes),
|
|
Number(database.prepare(DECLINE_ACCESS_REQUEST_SQL).run(
|
|
"当前名额有限。", "admin", now, "access_decline", scope, declineKey, declineHash,
|
|
).changes),
|
|
], [1, 1]);
|
|
assert.equal(database.prepare("SELECT status FROM beta_access_requests WHERE id = 'access_decline'").get().status, "declined");
|
|
assert.equal(database.prepare("SELECT COUNT(*) AS count FROM entitlement_grants").get().count, 1);
|
|
database.close();
|
|
});
|
|
|
|
test("requires pending requests to be resolved instead of bypassed by a manual invitation", () => {
|
|
const database = createDatabase();
|
|
const now = "2026-08-12T12:00:00.000Z";
|
|
assert.deepEqual(createRequest(database, { id: "access_pending", key: "create-p", now }), [1, 1]);
|
|
const scope = "admin:exemption";
|
|
assert.deepEqual([
|
|
Number(database.prepare(CREATE_MANUAL_INVITATION_IDEMPOTENCY_SQL).run(
|
|
scope, "manual-p", "manual-hash-p", "{}", "2026-09-01T00:00:00.000Z", now, "account_a",
|
|
).changes),
|
|
Number(database.prepare(CREATE_MANUAL_INVITATION_SQL).run(
|
|
"grant_manual_p", "account_a", "grant_manual_p", 1, now, "2026-11-12T12:00:00.000Z",
|
|
"proactive invitation", "admin", now, scope, "manual-p", "manual-hash-p",
|
|
).changes),
|
|
], [0, 0]);
|
|
|
|
assert.deepEqual([
|
|
Number(database.prepare(CREATE_MANUAL_INVITATION_IDEMPOTENCY_SQL).run(
|
|
scope, "manual-b", "manual-hash-b", "{}", "2026-09-01T00:00:00.000Z", now, "account_b",
|
|
).changes),
|
|
Number(database.prepare(CREATE_MANUAL_INVITATION_SQL).run(
|
|
"grant_manual_b", "account_b", "grant_manual_b", 1, now, "2026-11-12T12:00:00.000Z",
|
|
"proactive invitation", "admin", now, scope, "manual-b", "manual-hash-b",
|
|
).changes),
|
|
], [1, 1]);
|
|
assert.deepEqual(createRequest(database, { id: "access_after_invite", key: "create-after-b", account: "account_b", now }), [0, 0]);
|
|
assert.equal(database.prepare("SELECT COUNT(*) AS count FROM entitlement_grants").get().count, 1);
|
|
database.close();
|
|
});
|
|
|
|
test("keeps beta access requests authenticated, non-monetary, visible, and migrated", async () => {
|
|
const [userRoute, adminRoute, userForm, adminActions, billingPage, adminPage, repository, bootstrap, migration, contract] = await Promise.all([
|
|
readFile(new URL("../app/api/beta-access/route.ts", import.meta.url), "utf8"),
|
|
readFile(new URL("../app/api/admin/beta-access/route.ts", import.meta.url), "utf8"),
|
|
readFile(new URL("../app/dashboard/billing/AccessRequestForm.tsx", import.meta.url), "utf8"),
|
|
readFile(new URL("../app/admin/AdminActions.tsx", 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"),
|
|
readFile(new URL("../db/repository.ts", import.meta.url), "utf8"),
|
|
readFile(new URL("../db/bootstrap.ts", import.meta.url), "utf8"),
|
|
readFile(new URL("../drizzle/0010_windy_toxin.sql", import.meta.url), "utf8"),
|
|
readFile(new URL("../docs/commercial-contract.md", import.meta.url), "utf8"),
|
|
]);
|
|
assert.match(userRoute, /getCloudViewer/);
|
|
assert.match(userRoute, /getOrCreateAccount/);
|
|
assert.match(userRoute, /readJsonMutation/);
|
|
assert.match(adminRoute, /viewer\.isAdmin/);
|
|
assert.match(adminRoute, /resolveBetaAccessRequest/);
|
|
assert.match(userForm, /请勿粘贴令牌、密码、项目代码或私密会话内容/);
|
|
assert.match(adminActions, /批准并签发免费邀请/);
|
|
assert.match(billingPage, /申请免费闭测资格/);
|
|
assert.match(adminPage, /免费闭测申请队列/);
|
|
assert.match(repository, /beta_access_requests: accessRequests/);
|
|
assert.match(repository, /source_ref/);
|
|
assert.match(repository, /access_request_requires_resolution/);
|
|
assert.match(bootstrap, /0010_windy_toxin/);
|
|
assert.match(migration, /idx_beta_access_requests_one_pending/);
|
|
assert.match(contract, /申请时间不构成名额承诺或队列优先级/);
|
|
});
|
|
|
|
test("applies the generated access-request migration to an existing base database", async () => {
|
|
const [base, accessMigration] = await Promise.all([
|
|
readFile(new URL("../drizzle/0000_condemned_legion.sql", import.meta.url), "utf8"),
|
|
readFile(new URL("../drizzle/0010_windy_toxin.sql", import.meta.url), "utf8"),
|
|
]);
|
|
const database = new DatabaseSync(":memory:");
|
|
database.exec(base.replaceAll("--> statement-breakpoint", ""));
|
|
database.exec(accessMigration.replaceAll("--> statement-breakpoint", ""));
|
|
assert.equal(
|
|
database.prepare("SELECT COUNT(*) AS count FROM pragma_table_info('beta_access_requests')").get().count,
|
|
14,
|
|
);
|
|
assert.equal(
|
|
database.prepare("SELECT COUNT(*) AS count FROM pragma_index_list('beta_access_requests') WHERE origin = 'c'").get().count,
|
|
3,
|
|
);
|
|
database.close();
|
|
});
|