Files
nekonest-cloud/tests/access-requests.test.mjs
T

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();
});