feat: establish NekoNest Cloud control and relay
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
import test from "node:test";
|
||||
import {
|
||||
AUTHENTICATE_ACTIVE_DEVICE_SQL,
|
||||
DEACTIVATE_OWNED_HOST_SQL,
|
||||
FREE_BETA_ACCESS_BOUNDARY,
|
||||
REVOKE_ACTIVE_DEVICE_CREDENTIALS_SQL,
|
||||
} from "../db/access-boundary.ts";
|
||||
import { CLAIM_HOST_SQL } from "../db/pairing.ts";
|
||||
import { REQUIRED_PUBLIC_BETA_P0_KEYS } from "../db/launch-gates.ts";
|
||||
|
||||
function createClaimCapacityDatabase() {
|
||||
const db = new DatabaseSync(":memory:");
|
||||
db.exec(`
|
||||
CREATE TABLE beta_programs (
|
||||
id TEXT PRIMARY KEY, state TEXT NOT NULL, capacity_slots INTEGER,
|
||||
starts_at TEXT NOT NULL, ends_at TEXT, created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE entitlement_grants (
|
||||
account_id TEXT NOT NULL, state TEXT NOT NULL, capacity_slots INTEGER,
|
||||
starts_at TEXT NOT NULL, ends_at TEXT, revoked_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 pairing_requests (
|
||||
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, requested_name TEXT NOT NULL,
|
||||
os TEXT NOT NULL, code_hash TEXT NOT NULL, status TEXT NOT NULL,
|
||||
expires_at TEXT NOT NULL, locked_at TEXT, failed_attempts INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE TABLE hosts (
|
||||
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, name TEXT NOT NULL,
|
||||
os TEXT NOT NULL, lifecycle TEXT NOT NULL, slot_state TEXT NOT NULL,
|
||||
connection_state TEXT NOT NULL, daemon_version TEXT,
|
||||
ed25519_public TEXT, x25519_public TEXT,
|
||||
identity_fingerprint TEXT UNIQUE, claim_request_id TEXT, claimed_at TEXT,
|
||||
created_at TEXT NOT NULL, deactivated_at TEXT
|
||||
);
|
||||
`);
|
||||
const gate = db.prepare(
|
||||
`INSERT INTO launch_gates
|
||||
(key, priority, status, owner, notes, evidence_url)
|
||||
VALUES (?, 'P0', 'passed', 'test', 'verified', 'https://evidence.example.test/p0')`,
|
||||
);
|
||||
for (const key of REQUIRED_PUBLIC_BETA_P0_KEYS) gate.run(key);
|
||||
return db;
|
||||
}
|
||||
|
||||
function claimHost(db, digit, accountId, now) {
|
||||
const pairingId = `pair_${digit.repeat(32)}`;
|
||||
db.prepare(
|
||||
`INSERT INTO pairing_requests
|
||||
(id, account_id, requested_name, os, code_hash, status, expires_at)
|
||||
VALUES (?, ?, ?, 'linux', ?, 'waiting', '2026-08-12T13:00:00.000Z')`,
|
||||
).run(pairingId, accountId, `Host ${digit}`, `hash-${digit}`);
|
||||
return Number(db.prepare(CLAIM_HOST_SQL).run(
|
||||
`host_${digit.repeat(32)}`,
|
||||
`ed-${digit}`,
|
||||
`x-${digit}`,
|
||||
`fingerprint-${digit}`,
|
||||
now,
|
||||
now,
|
||||
pairingId,
|
||||
`hash-${digit}`,
|
||||
now,
|
||||
"linux",
|
||||
now,
|
||||
now,
|
||||
now,
|
||||
now,
|
||||
null,
|
||||
).changes);
|
||||
}
|
||||
|
||||
test("documents the free-beta expiry operation boundary", () => {
|
||||
assert.deepEqual(
|
||||
FREE_BETA_ACCESS_BOUNDARY.map(({ operation, requiresCurrentEntitlement }) => [operation, requiresCurrentEntitlement]),
|
||||
[
|
||||
["create_pairing", true],
|
||||
["claim_pairing", true],
|
||||
["cancel_pairing", false],
|
||||
["connect_device", false],
|
||||
["revoke_host", false],
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
test("keeps an existing active device usable after entitlement expiry until explicit revocation", () => {
|
||||
const db = new DatabaseSync(":memory:");
|
||||
db.exec(`
|
||||
CREATE TABLE hosts (
|
||||
id TEXT PRIMARY KEY,
|
||||
account_id TEXT NOT NULL,
|
||||
lifecycle TEXT NOT NULL,
|
||||
slot_state TEXT NOT NULL,
|
||||
connection_state TEXT NOT NULL,
|
||||
deactivated_at TEXT
|
||||
);
|
||||
CREATE TABLE device_credentials (
|
||||
id TEXT PRIMARY KEY,
|
||||
host_id TEXT NOT NULL,
|
||||
token_hash TEXT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
expires_at TEXT,
|
||||
last_used_at TEXT,
|
||||
revoked_at TEXT
|
||||
);
|
||||
CREATE TABLE entitlement_grants (
|
||||
id TEXT PRIMARY KEY,
|
||||
account_id TEXT NOT NULL,
|
||||
state TEXT NOT NULL,
|
||||
ends_at TEXT,
|
||||
revoked_at TEXT
|
||||
);
|
||||
INSERT INTO hosts VALUES ('host_a', 'account_a', 'active', 'active', 'online', NULL);
|
||||
INSERT INTO device_credentials VALUES ('credential_a', 'host_a', 'token-hash', 'active', NULL, NULL, NULL);
|
||||
INSERT INTO entitlement_grants VALUES ('grant_expired', 'account_a', 'active', '2026-08-11T00:00:00.000Z', NULL);
|
||||
`);
|
||||
const now = "2026-08-12T12:00:00.000Z";
|
||||
assert.equal(
|
||||
Number(db.prepare(AUTHENTICATE_ACTIVE_DEVICE_SQL).run(now, "host_a", "token-hash").changes),
|
||||
1,
|
||||
);
|
||||
assert.equal(db.prepare("SELECT last_used_at FROM device_credentials WHERE id = 'credential_a'").get().last_used_at, now);
|
||||
|
||||
assert.equal(Number(db.prepare(REVOKE_ACTIVE_DEVICE_CREDENTIALS_SQL).run(now, "host_a").changes), 1);
|
||||
assert.equal(Number(db.prepare(DEACTIVATE_OWNED_HOST_SQL).run(now, "host_a", "account_a").changes), 1);
|
||||
assert.equal(
|
||||
Number(db.prepare(AUTHENTICATE_ACTIVE_DEVICE_SQL).run(now, "host_a", "token-hash").changes),
|
||||
0,
|
||||
);
|
||||
db.close();
|
||||
});
|
||||
|
||||
test("rechecks current per-account capacity atomically when a daemon claims a waiting pairing", () => {
|
||||
const db = createClaimCapacityDatabase();
|
||||
const now = "2026-08-12T12:00:00.000Z";
|
||||
db.prepare(
|
||||
`INSERT INTO beta_programs
|
||||
(id, state, capacity_slots, starts_at, ends_at, created_at)
|
||||
VALUES ('beta', 'active', 2, ?, NULL, ?)`,
|
||||
).run(now, now);
|
||||
|
||||
assert.equal(claimHost(db, "1", "account_public", now), 1);
|
||||
db.prepare("UPDATE beta_programs SET capacity_slots = 1 WHERE id = 'beta'").run();
|
||||
assert.equal(claimHost(db, "2", "account_public", now), 0);
|
||||
assert.equal(
|
||||
db.prepare(
|
||||
"SELECT COUNT(*) AS count FROM hosts WHERE account_id = 'account_public' AND lifecycle = 'active'",
|
||||
).get().count,
|
||||
1,
|
||||
);
|
||||
|
||||
db.prepare(
|
||||
`INSERT INTO entitlement_grants
|
||||
(account_id, state, capacity_slots, starts_at, ends_at, revoked_at)
|
||||
VALUES ('account_public', 'active', 1, ?, NULL, NULL)`,
|
||||
).run(now);
|
||||
assert.equal(claimHost(db, "3", "account_public", now), 1);
|
||||
assert.equal(claimHost(db, "4", "account_public", now), 0);
|
||||
assert.equal(
|
||||
db.prepare(
|
||||
"SELECT COUNT(*) AS count FROM hosts WHERE account_id = 'account_public' AND lifecycle = 'active'",
|
||||
).get().count,
|
||||
2,
|
||||
);
|
||||
db.close();
|
||||
});
|
||||
|
||||
test("keeps entitlement checks on new access while security exits stay independent", async () => {
|
||||
const [pairingSql, repository, relayControl, stateModel, contract] = await Promise.all([
|
||||
readFile(new URL("../db/pairing.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../db/repository.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../db/relay-control-plane.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../docs/state-model.md", import.meta.url), "utf8"),
|
||||
readFile(new URL("../docs/commercial-contract.md", import.meta.url), "utf8"),
|
||||
]);
|
||||
assert.match(pairingSql, /CURRENT_PAIRING_ACCESS/);
|
||||
assert.match(pairingSql, /HOST_CLAIM_CAPACITY/);
|
||||
assert.match(pairingSql, /PUBLIC_BETA_GATE_READY_SQL/);
|
||||
assert.match(relayControl, /authorizeDeviceForRelay/);
|
||||
assert.match(repository, /REVOKE_ACTIVE_DEVICE_CREDENTIALS_SQL/);
|
||||
assert.match(repository, /DEACTIVATE_OWNED_HOST_SQL/);
|
||||
assert.match(stateModel, /主机撤销.*撤销所有设备凭证.*释放席位/);
|
||||
assert.match(stateModel, /第 N\+1 台返回 `device_capacity_exceeded`/);
|
||||
assert.match(contract, /设备离线不释放席位/);
|
||||
assert.match(contract, /明确撤销才释放/);
|
||||
});
|
||||
@@ -0,0 +1,266 @@
|
||||
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();
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
import test from "node:test";
|
||||
import { BETA_ACCESS_REQUESTS_SQL } from "../db/beta-operations.ts";
|
||||
|
||||
test("uses the generated request-time index for the rolling beta application funnel", async () => {
|
||||
const [base, hostClaims, requests, timeIndex, bootstrap, metricsDoc] = await Promise.all([
|
||||
readFile(new URL("../drizzle/0000_condemned_legion.sql", import.meta.url), "utf8"),
|
||||
readFile(new URL("../drizzle/0001_mushy_vance_astro.sql", import.meta.url), "utf8"),
|
||||
readFile(new URL("../drizzle/0010_windy_toxin.sql", import.meta.url), "utf8"),
|
||||
readFile(new URL("../drizzle/0011_next_thunderball.sql", import.meta.url), "utf8"),
|
||||
readFile(new URL("../db/bootstrap.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../docs/beta-operations.md", import.meta.url), "utf8"),
|
||||
]);
|
||||
const db = new DatabaseSync(":memory:");
|
||||
db.exec(base.replaceAll("--> statement-breakpoint", ""));
|
||||
db.exec(hostClaims.replaceAll("--> statement-breakpoint", ""));
|
||||
db.exec(requests.replaceAll("--> statement-breakpoint", ""));
|
||||
db.exec(timeIndex.replaceAll("--> statement-breakpoint", ""));
|
||||
const plan = db.prepare(`EXPLAIN QUERY PLAN ${BETA_ACCESS_REQUESTS_SQL}`).all("2026-07-13T00:00:00.000Z");
|
||||
assert.match(plan.map((row) => row.detail).join("\n"), /idx_beta_access_requests_requested_time/);
|
||||
assert.match(timeIndex, /CREATE INDEX `idx_beta_access_requests_requested_time`/);
|
||||
assert.match(bootstrap, /0011_next_thunderball/);
|
||||
assert.doesNotMatch(BETA_ACCESS_REQUESTS_SQL, /use_case/);
|
||||
assert.match(metricsDoc, /不读取 `use_case` 正文做分析/);
|
||||
db.close();
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import { deriveBetaOnboarding } from "../app/dashboard/onboarding.ts";
|
||||
|
||||
const baseEntitlement = {
|
||||
mode: "none",
|
||||
publicBetaState: "gated",
|
||||
unlimited: false,
|
||||
availableSlots: 0,
|
||||
};
|
||||
|
||||
test("routes every free-beta access state to one concrete next action", () => {
|
||||
assert.deepEqual(
|
||||
deriveBetaOnboarding({ entitlement: baseEntitlement, hasPendingRequest: false }),
|
||||
{
|
||||
state: "request_needed",
|
||||
pairingAccessState: "gated",
|
||||
primaryHref: "/dashboard/billing",
|
||||
primaryLabel: "申请免费闭测",
|
||||
tone: "warn",
|
||||
title: "公开接入尚未开放",
|
||||
detail: "安全门禁仍在核对,可以先申请小范围免费闭测。",
|
||||
},
|
||||
);
|
||||
|
||||
const pending = deriveBetaOnboarding({ entitlement: baseEntitlement, hasPendingRequest: true });
|
||||
assert.equal(pending.state, "request_pending");
|
||||
assert.equal(pending.pairingAccessState, "request_pending");
|
||||
assert.equal(pending.primaryLabel, "查看申请进度");
|
||||
|
||||
const inactive = deriveBetaOnboarding({
|
||||
entitlement: { ...baseEntitlement, publicBetaState: "inactive" },
|
||||
hasPendingRequest: false,
|
||||
});
|
||||
assert.equal(inactive.state, "request_needed");
|
||||
assert.equal(inactive.pairingAccessState, "inactive");
|
||||
assert.match(inactive.detail, /不会绑定支付方式/);
|
||||
|
||||
const invited = deriveBetaOnboarding({
|
||||
entitlement: { ...baseEntitlement, mode: "grant", availableSlots: 2 },
|
||||
hasPendingRequest: false,
|
||||
});
|
||||
assert.equal(invited.state, "ready");
|
||||
assert.equal(invited.primaryHref, "/dashboard/hosts/new");
|
||||
assert.match(invited.detail, /2 台/);
|
||||
|
||||
const unlimited = deriveBetaOnboarding({
|
||||
entitlement: { ...baseEntitlement, mode: "public_beta", publicBetaState: "open", unlimited: true, availableSlots: null },
|
||||
hasPendingRequest: false,
|
||||
});
|
||||
assert.equal(unlimited.state, "ready");
|
||||
assert.match(unlimited.detail, /不按主机槽位限额/);
|
||||
|
||||
const full = deriveBetaOnboarding({
|
||||
entitlement: { ...baseEntitlement, mode: "grant", availableSlots: 0 },
|
||||
hasPendingRequest: false,
|
||||
});
|
||||
assert.equal(full.state, "full");
|
||||
assert.equal(full.pairingAccessState, "full");
|
||||
assert.equal(full.primaryHref, "/dashboard/hosts");
|
||||
});
|
||||
|
||||
test("connects dashboard and blocked pairing surfaces to the derived beta journey", async () => {
|
||||
const [dashboard, newHostPage, pairingForm] = await Promise.all([
|
||||
readFile(new URL("../app/dashboard/page.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/dashboard/hosts/new/page.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/dashboard/hosts/new/PairingForm.tsx", import.meta.url), "utf8"),
|
||||
]);
|
||||
assert.match(dashboard, /deriveBetaOnboarding/);
|
||||
assert.match(dashboard, /href=\{onboarding\.primaryHref\}/);
|
||||
assert.match(dashboard, /获得免费测试资格/);
|
||||
assert.match(newHostPage, /hasPendingRequest/);
|
||||
assert.match(newHostPage, /onboarding\.pairingAccessState/);
|
||||
assert.match(pairingForm, /request_pending/);
|
||||
assert.match(pairingForm, /申请免费闭测/);
|
||||
assert.match(pairingForm, /管理主机和配对/);
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
|
||||
test("does not expose an unimplemented free-beta grace-period control", async () => {
|
||||
const [actions, route, repository, bootstrap, billing, contract, stateModel] = await Promise.all([
|
||||
readFile(new URL("../app/admin/AdminActions.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/api/admin/beta/route.ts", 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("../app/dashboard/billing/page.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../docs/commercial-contract.md", import.meta.url), "utf8"),
|
||||
readFile(new URL("../docs/state-model.md", import.meta.url), "utf8"),
|
||||
]);
|
||||
const setPublicBeta = repository.slice(
|
||||
repository.indexOf("export async function setPublicBeta"),
|
||||
repository.indexOf("export async function createExemption"),
|
||||
);
|
||||
assert.doesNotMatch(actions, /graceDays|宽限天数/);
|
||||
assert.doesNotMatch(route, /graceDays/);
|
||||
assert.doesNotMatch(setPublicBeta, /input\.graceDays|invalid_grace/);
|
||||
assert.match(setPublicBeta, /grace_days: 0/);
|
||||
assert.match(bootstrap, /NULL, 0, 'system:seed'/);
|
||||
assert.match(actions, /不自动断开既有主机/);
|
||||
assert.match(billing, /新配对和未完成认领停止,既有主机不自动断开/);
|
||||
assert.match(contract, /当前没有可配置的宽限天数或隐藏倒计时/);
|
||||
assert.match(stateModel, /不参与资格、设备认证或页面判断/);
|
||||
});
|
||||
@@ -0,0 +1,68 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import {
|
||||
buildDaemonRegistrationCommand,
|
||||
daemonStartCommand,
|
||||
quoteBash,
|
||||
quotePowerShell,
|
||||
resolveConnectOrigin,
|
||||
} from "../app/dashboard/hosts/new/onboarding.ts";
|
||||
|
||||
test("builds a PowerShell registration command without embedding the bootstrap token", () => {
|
||||
const command = buildDaemonRegistrationCommand({
|
||||
os: "windows",
|
||||
connectOrigin: "https://connect.example.test",
|
||||
hostName: "家里'; Remove-Item *; '电脑",
|
||||
});
|
||||
|
||||
assert.match(command, /NEKONEST_SERVER = 'https:\/\/connect\.example\.test'/);
|
||||
assert.match(command, /NEKONEST_TRANSPORT_MODE = 'sealed'/);
|
||||
assert.match(command, /Read-Host '粘贴一次性配对码' -AsSecureString/);
|
||||
assert.match(command, /SecureStringToBSTR/);
|
||||
assert.match(command, /PtrToStringBSTR/);
|
||||
assert.match(command, /IsNullOrWhiteSpace/);
|
||||
assert.match(command, /-register -name '家里''; Remove-Item \*; ''电脑'/);
|
||||
assert.match(command, /finally[\s\S]*Remove-Item Env:NEKONEST_BOOTSTRAP_TOKEN[\s\S]*ZeroFreeBSTR/);
|
||||
assert.doesNotMatch(command, /bootstrap-token-value/);
|
||||
assert.equal(quotePowerShell("a'b"), "'a''b'");
|
||||
assert.equal(daemonStartCommand("windows"), ".\\nekonest-daemon.exe");
|
||||
});
|
||||
|
||||
test("builds a Bash subshell that drops the secret environment after registration", () => {
|
||||
const command = buildDaemonRegistrationCommand({
|
||||
os: "linux",
|
||||
connectOrigin: "https://connect.example.test",
|
||||
hostName: "home'; rm -rf /; 'pc",
|
||||
});
|
||||
|
||||
assert.match(command, /^\([\s\S]*\)$/);
|
||||
assert.match(command, /read -rsp/);
|
||||
assert.match(command, /\[ -z "\$NEKONEST_BOOTSTRAP_TOKEN" \][\s\S]*exit 1/);
|
||||
assert.match(command, /export NEKONEST_BOOTSTRAP_TOKEN/);
|
||||
assert.match(command, /\.\/nekonest-daemon -register -name 'home'"'"'; rm -rf \/; '"'"'pc'/);
|
||||
assert.equal(quoteBash("a'b"), `'a'"'"'b'`);
|
||||
assert.equal(daemonStartCommand("linux"), "./nekonest-daemon");
|
||||
});
|
||||
|
||||
test("rejects non-origin and insecure public control-plane addresses", () => {
|
||||
const base = { os: "linux", hostName: "home" };
|
||||
assert.throws(() => buildDaemonRegistrationCommand({ ...base, connectOrigin: "http://cloud.example.test" }), /HTTPS origin/);
|
||||
assert.throws(() => buildDaemonRegistrationCommand({ ...base, connectOrigin: "https://cloud.example.test/path" }), /HTTPS origin/);
|
||||
assert.doesNotThrow(() => buildDaemonRegistrationCommand({ ...base, connectOrigin: "http://127.0.0.1:3000" }));
|
||||
assert.equal(resolveConnectOrigin("https://connect.example.test", false), "https://connect.example.test");
|
||||
assert.throws(() => resolveConnectOrigin(undefined, false), /CONNECT_ORIGIN is required/);
|
||||
assert.equal(resolveConnectOrigin(undefined, true), "http://127.0.0.1:3000");
|
||||
});
|
||||
|
||||
test("keeps the one-time token out of generated commands and browser persistence", async () => {
|
||||
const form = await readFile(new URL("../app/dashboard/hosts/new/PairingForm.tsx", import.meta.url), "utf8");
|
||||
const helper = await readFile(new URL("../app/dashboard/hosts/new/onboarding.ts", import.meta.url), "utf8");
|
||||
assert.match(form, /navigator\.clipboard\?\.writeText/);
|
||||
assert.match(form, /浏览器未允许自动复制/);
|
||||
assert.match(form, /onFocus=\{\(event\) => event\.currentTarget\.select\(\)\}/);
|
||||
assert.doesNotMatch(`${form}\n${helper}`, /localStorage|sessionStorage/);
|
||||
assert.doesNotMatch(helper, /bootstrapToken/);
|
||||
assert.doesNotMatch(form, /window\.location\.origin/);
|
||||
assert.match(form, /一次性码不会拼进命令、URL 或浏览器存储/);
|
||||
});
|
||||
@@ -0,0 +1,140 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import {
|
||||
MINIMUM_CLOUD_DAEMON_VERSION,
|
||||
checksumVerificationCommand,
|
||||
parseDaemonReleaseEnvironment,
|
||||
} from "../app/daemon-release.ts";
|
||||
import { classifyReportedDaemonVersion } from "../release/daemon-version.ts";
|
||||
|
||||
const SHA = {
|
||||
windows: "1".repeat(64),
|
||||
linuxAmd64: "2".repeat(64),
|
||||
linuxArm64: "A".repeat(64),
|
||||
};
|
||||
|
||||
function validConfig(overrides = {}) {
|
||||
return {
|
||||
NEKONEST_CLOUD_DAEMON_RELEASE_VERSION: MINIMUM_CLOUD_DAEMON_VERSION,
|
||||
NEKONEST_CLOUD_DAEMON_WINDOWS_AMD64_SHA256: SHA.windows,
|
||||
NEKONEST_CLOUD_DAEMON_LINUX_AMD64_SHA256: SHA.linuxAmd64,
|
||||
NEKONEST_CLOUD_DAEMON_LINUX_ARM64_SHA256: SHA.linuxArm64,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test("keeps daemon downloads closed until a complete compatible catalog exists", () => {
|
||||
assert.deepEqual(parseDaemonReleaseEnvironment({}), {
|
||||
available: false,
|
||||
reason: "not_configured",
|
||||
minimumVersion: "0.2.6",
|
||||
});
|
||||
assert.equal(
|
||||
parseDaemonReleaseEnvironment(validConfig({ NEKONEST_CLOUD_DAEMON_RELEASE_VERSION: "0.2.5" })).reason,
|
||||
"incompatible_version",
|
||||
);
|
||||
assert.equal(
|
||||
parseDaemonReleaseEnvironment(validConfig({ NEKONEST_CLOUD_DAEMON_LINUX_ARM64_SHA256: "bad" })).reason,
|
||||
"invalid_config",
|
||||
);
|
||||
assert.equal(
|
||||
parseDaemonReleaseEnvironment(validConfig({ NEKONEST_CLOUD_DAEMON_RELEASE_BASE_URL: "http://mirror.example.test/release" })).reason,
|
||||
"invalid_config",
|
||||
);
|
||||
assert.equal(
|
||||
parseDaemonReleaseEnvironment(validConfig({ NEKONEST_CLOUD_DAEMON_RELEASE_BASE_URL: "https://user:pass@download.example.test/release" })).reason,
|
||||
"invalid_config",
|
||||
);
|
||||
assert.equal(
|
||||
parseDaemonReleaseEnvironment(validConfig({ NEKONEST_CLOUD_DAEMON_RELEASE_BASE_URL: "https://download.example.test/release?token=secret" })).reason,
|
||||
"invalid_config",
|
||||
);
|
||||
});
|
||||
|
||||
test("accepts legacy unreported versions but rejects malformed and known-old reports", () => {
|
||||
assert.deepEqual(classifyReportedDaemonVersion(undefined), {
|
||||
state: "unreported",
|
||||
version: null,
|
||||
});
|
||||
assert.deepEqual(classifyReportedDaemonVersion(" "), {
|
||||
state: "unreported",
|
||||
version: null,
|
||||
});
|
||||
assert.deepEqual(classifyReportedDaemonVersion("0.2.5"), {
|
||||
state: "incompatible",
|
||||
version: "0.2.5",
|
||||
});
|
||||
assert.deepEqual(classifyReportedDaemonVersion("0.2.6"), {
|
||||
state: "compatible",
|
||||
version: "0.2.6",
|
||||
});
|
||||
assert.deepEqual(classifyReportedDaemonVersion("1.0.0"), {
|
||||
state: "compatible",
|
||||
version: "1.0.0",
|
||||
});
|
||||
for (const version of ["v0.2.6", "0.2", "0.2.6-beta.1", "00.2.6", "1.02.3"]) {
|
||||
assert.deepEqual(classifyReportedDaemonVersion(version), {
|
||||
state: "invalid",
|
||||
version: null,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
test("registration accepts an optional daemon version and stores it in the atomic host claim", async () => {
|
||||
const [route, repository, pairing] = await Promise.all([
|
||||
readFile(new URL("../app/api/internal/relay/register-device/route.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../db/repository.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../db/pairing.ts", import.meta.url), "utf8"),
|
||||
]);
|
||||
assert.match(route, /daemon_version\?: string/);
|
||||
assert.match(route, /daemonVersion: payload\.daemon_version \?\? ""/);
|
||||
assert.match(repository, /classifyReportedDaemonVersion\(input\.daemonVersion\)/);
|
||||
assert.match(repository, /protocol_upgrade_required/);
|
||||
assert.match(pairing, /daemon_version = COALESCE\(excluded\.daemon_version, hosts\.daemon_version\)/);
|
||||
});
|
||||
|
||||
test("publishes exact versioned URLs and normalized platform checksums", () => {
|
||||
const release = parseDaemonReleaseEnvironment(validConfig());
|
||||
assert.equal(release.available, true);
|
||||
if (!release.available) return;
|
||||
assert.equal(release.version, "0.2.6");
|
||||
assert.equal(release.assets.length, 3);
|
||||
assert.equal(release.assets[0].downloadUrl, "https://github.com/klarkxy/nekonest/releases/download/v0.2.6/nekonest-daemon-windows-amd64.zip");
|
||||
assert.equal(release.assets[2].sha256, "a".repeat(64));
|
||||
assert.equal(release.checksumsUrl, "https://github.com/klarkxy/nekonest/releases/download/v0.2.6/checksums.txt");
|
||||
});
|
||||
|
||||
test("supports an HTTPS domestic mirror without weakening digest verification", () => {
|
||||
const release = parseDaemonReleaseEnvironment(validConfig({
|
||||
NEKONEST_CLOUD_DAEMON_RELEASE_BASE_URL: "https://download.example.cn/nekonest/v0.2.6/",
|
||||
}));
|
||||
assert.equal(release.available, true);
|
||||
if (!release.available) return;
|
||||
assert.equal(release.assets[1].downloadUrl, "https://download.example.cn/nekonest/v0.2.6/nekonest-daemon-linux-amd64.tar.gz");
|
||||
assert.match(checksumVerificationCommand(release.assets[0]), /Get-FileHash[\s\S]*SHA-256 不匹配/);
|
||||
assert.match(checksumVerificationCommand(release.assets[1]), /sha256sum -c -/);
|
||||
});
|
||||
|
||||
test("download page distinguishes checksums from publisher code signing", async () => {
|
||||
const [page, pairingPage, pairingForm, shell, plan] = await Promise.all([
|
||||
readFile(new URL("../app/download/page.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/dashboard/hosts/new/page.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/dashboard/hosts/new/PairingForm.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/components/Shells.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../docs/implementation-plan.md", import.meta.url), "utf8"),
|
||||
]);
|
||||
assert.match(page, /摘要校验不是代码签名/);
|
||||
assert.match(page, /不提供“先下最新版试试”的按钮/);
|
||||
assert.match(page, /已下载,开始配对/);
|
||||
assert.match(pairingPage, /先下载兼容 daemon/);
|
||||
assert.match(pairingPage, /getDaemonReleaseState/);
|
||||
assert.match(pairingPage, /releaseAvailable=\{daemonRelease\.available\}/);
|
||||
assert.match(pairingForm, /公开 daemon 下载尚未就绪/);
|
||||
assert.match(pairingForm, /我已有经过核验的兼容闭测构建/);
|
||||
assert.match(pairingForm, /!releaseAvailable && !closedBetaBuildConfirmed/);
|
||||
assert.match(pairingForm, /disabled=\{loading \|\| \(!releaseAvailable && !closedBetaBuildConfirmed\)\}/);
|
||||
assert.match(shell, /href="\/download"/);
|
||||
assert.match(plan, /fail-closed 发布清单/);
|
||||
assert.match(plan, /取得并核验兼容闭测构建/);
|
||||
});
|
||||
@@ -0,0 +1,190 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
import test from "node:test";
|
||||
import {
|
||||
ADMIN_HOST_CONTROL_PLANE_ATTENTION_SQL,
|
||||
ADMIN_HOST_CONTROL_PLANE_SUMMARY_SQL,
|
||||
CONTROL_PLANE_CONTACT_DELAYED_MS,
|
||||
CONTROL_PLANE_CONTACT_FRESH_MS,
|
||||
OWNED_HOSTS_WITH_CONTROL_PLANE_CONTACT_SQL,
|
||||
controlPlaneContactCutoffs,
|
||||
deriveAdminHostControlPlaneSnapshot,
|
||||
deriveControlPlaneContact,
|
||||
} from "../db/device-control-plane.ts";
|
||||
|
||||
test("derives honest control-plane contact states without claiming relay presence", () => {
|
||||
const now = Date.parse("2026-08-12T12:00:00.000Z");
|
||||
assert.equal(deriveControlPlaneContact(null, now).state, "never");
|
||||
assert.equal(
|
||||
deriveControlPlaneContact(new Date(now - CONTROL_PLANE_CONTACT_FRESH_MS).toISOString(), now).state,
|
||||
"fresh",
|
||||
);
|
||||
assert.equal(
|
||||
deriveControlPlaneContact(new Date(now - CONTROL_PLANE_CONTACT_FRESH_MS - 1).toISOString(), now).state,
|
||||
"delayed",
|
||||
);
|
||||
assert.equal(
|
||||
deriveControlPlaneContact(new Date(now - CONTROL_PLANE_CONTACT_DELAYED_MS).toISOString(), now).state,
|
||||
"delayed",
|
||||
);
|
||||
assert.equal(
|
||||
deriveControlPlaneContact(new Date(now - CONTROL_PLANE_CONTACT_DELAYED_MS - 1).toISOString(), now).state,
|
||||
"stale",
|
||||
);
|
||||
assert.equal(deriveControlPlaneContact("not-a-time", now).state, "invalid");
|
||||
assert.equal(
|
||||
deriveControlPlaneContact(new Date(now + 6 * 60 * 1_000).toISOString(), now).state,
|
||||
"invalid",
|
||||
);
|
||||
});
|
||||
|
||||
test("uses the latest successful device credential use as a derived control-plane check-in", () => {
|
||||
const db = new DatabaseSync(":memory:");
|
||||
db.exec(`
|
||||
CREATE TABLE hosts (
|
||||
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, lifecycle TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE device_credentials (
|
||||
id TEXT PRIMARY KEY, host_id TEXT NOT NULL, last_used_at TEXT
|
||||
);
|
||||
INSERT INTO hosts VALUES
|
||||
('host_active', 'acct_owner', 'active', '2026-08-12T10:00:00.000Z'),
|
||||
('host_never', 'acct_owner', 'active', '2026-08-12T11:00:00.000Z'),
|
||||
('host_other', 'acct_other', 'active', '2026-08-12T11:30:00.000Z');
|
||||
INSERT INTO device_credentials VALUES
|
||||
('credential_old', 'host_active', '2026-08-12T10:10:00.000Z'),
|
||||
('credential_current', 'host_active', '2026-08-12T11:55:00.000Z'),
|
||||
('credential_other', 'host_other', '2026-08-12T11:59:00.000Z');
|
||||
`);
|
||||
const rows = db.prepare(OWNED_HOSTS_WITH_CONTROL_PLANE_CONTACT_SQL).all("acct_owner");
|
||||
assert.deepEqual(
|
||||
rows.map((row) => ({ id: row.id, lastSeen: row.control_plane_last_seen_at })),
|
||||
[
|
||||
{ id: "host_never", lastSeen: null },
|
||||
{ id: "host_active", lastSeen: "2026-08-12T11:55:00.000Z" },
|
||||
],
|
||||
);
|
||||
db.close();
|
||||
});
|
||||
|
||||
test("aggregates every active host and bounds the administrator attention list", () => {
|
||||
const db = new DatabaseSync(":memory:");
|
||||
db.exec(`
|
||||
CREATE TABLE accounts (id TEXT PRIMARY KEY, email TEXT NOT NULL);
|
||||
CREATE TABLE hosts (
|
||||
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, name TEXT NOT NULL,
|
||||
os TEXT NOT NULL, lifecycle TEXT NOT NULL, slot_state TEXT NOT NULL,
|
||||
daemon_version TEXT
|
||||
);
|
||||
CREATE TABLE device_credentials (
|
||||
id TEXT PRIMARY KEY, host_id TEXT NOT NULL, last_used_at TEXT
|
||||
);
|
||||
INSERT INTO accounts VALUES ('acct', 'owner@example.test');
|
||||
INSERT INTO hosts VALUES
|
||||
('host_fresh', 'acct', 'Fresh', 'windows', 'active', 'active', '0.2.6'),
|
||||
('host_delayed', 'acct', 'Delayed', 'linux', 'active', 'active', NULL),
|
||||
('host_stale', 'acct', 'Stale', 'linux', 'active', 'active', '0.2.6'),
|
||||
('host_never', 'acct', 'Never', 'windows', 'active', 'active', NULL),
|
||||
('host_invalid', 'acct', 'Invalid', 'linux', 'active', 'active', '0.2.6'),
|
||||
('host_revoked', 'acct', 'Revoked', 'linux', 'deactivated', 'released', '0.2.6');
|
||||
INSERT INTO device_credentials VALUES
|
||||
('credential_fresh', 'host_fresh', '2026-08-12T11:55:00.000Z'),
|
||||
('credential_delayed', 'host_delayed', '2026-08-12T11:40:00.000Z'),
|
||||
('credential_stale', 'host_stale', '2026-08-12T10:00:00.000Z'),
|
||||
('credential_invalid', 'host_invalid', '2026-08-12T12:06:00.000Z'),
|
||||
('credential_revoked', 'host_revoked', '2026-08-12T11:59:00.000Z');
|
||||
`);
|
||||
const generatedAt = "2026-08-12T12:00:00.000Z";
|
||||
const cutoffs = controlPlaneContactCutoffs(generatedAt);
|
||||
const parameters = [cutoffs.futureLimitAt, cutoffs.freshCutoff, cutoffs.delayedCutoff];
|
||||
const summary = {
|
||||
...db.prepare(ADMIN_HOST_CONTROL_PLANE_SUMMARY_SQL).get(...parameters),
|
||||
};
|
||||
const attentionHosts = db
|
||||
.prepare(ADMIN_HOST_CONTROL_PLANE_ATTENTION_SQL)
|
||||
.all(...parameters);
|
||||
const snapshot = deriveAdminHostControlPlaneSnapshot({
|
||||
generatedAt,
|
||||
summary,
|
||||
attentionHosts,
|
||||
});
|
||||
assert.deepEqual(
|
||||
{
|
||||
totalActive: snapshot.totalActive,
|
||||
fresh: snapshot.fresh,
|
||||
delayed: snapshot.delayed,
|
||||
stale: snapshot.stale,
|
||||
never: snapshot.never,
|
||||
invalid: snapshot.invalid,
|
||||
versionUnknown: snapshot.versionUnknown,
|
||||
},
|
||||
{
|
||||
totalActive: 5,
|
||||
fresh: 1,
|
||||
delayed: 1,
|
||||
stale: 1,
|
||||
never: 1,
|
||||
invalid: 1,
|
||||
versionUnknown: 2,
|
||||
},
|
||||
);
|
||||
assert.deepEqual(
|
||||
snapshot.attentionHosts.map((host) => [host.id, host.contact_state]),
|
||||
[
|
||||
["host_invalid", "invalid"],
|
||||
["host_never", "never"],
|
||||
["host_stale", "stale"],
|
||||
["host_delayed", "delayed"],
|
||||
],
|
||||
);
|
||||
const insertHost = db.prepare(
|
||||
`INSERT INTO hosts
|
||||
(id, account_id, name, os, lifecycle, slot_state, daemon_version)
|
||||
VALUES (?, 'acct', ?, 'linux', 'active', 'active', '0.2.6')`,
|
||||
);
|
||||
const insertCredential = db.prepare(
|
||||
"INSERT INTO device_credentials (id, host_id, last_used_at) VALUES (?, ?, '2026-08-12T10:00:00.000Z')",
|
||||
);
|
||||
for (let index = 0; index < 30; index += 1) {
|
||||
const suffix = String(index).padStart(2, "0");
|
||||
const hostId = `host_bulk_${suffix}`;
|
||||
insertHost.run(hostId, `Bulk ${suffix}`);
|
||||
insertCredential.run(`credential_bulk_${suffix}`, hostId);
|
||||
}
|
||||
const boundedAttention = db
|
||||
.prepare(ADMIN_HOST_CONTROL_PLANE_ATTENTION_SQL)
|
||||
.all(...parameters);
|
||||
assert.equal(boundedAttention.length, 25);
|
||||
assert.deepEqual(
|
||||
boundedAttention.slice(0, 2).map((host) => host.contact_state),
|
||||
["invalid", "never"],
|
||||
);
|
||||
db.close();
|
||||
});
|
||||
|
||||
test("labels user and administrator signals as Cloud contact instead of relay online", async () => {
|
||||
const [dashboard, hosts, admin, repository, stateModel, operationsDoc, plan] = await Promise.all([
|
||||
readFile(new URL("../app/dashboard/page.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/dashboard/hosts/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("../docs/state-model.md", import.meta.url), "utf8"),
|
||||
readFile(new URL("../docs/beta-operations.md", import.meta.url), "utf8"),
|
||||
readFile(new URL("../docs/implementation-plan.md", import.meta.url), "utf8"),
|
||||
]);
|
||||
assert.match(repository, /OWNED_HOSTS_WITH_CONTROL_PLANE_CONTACT_SQL/);
|
||||
assert.match(dashboard, /deriveControlPlaneContact/);
|
||||
assert.match(hosts, /控制面签到/);
|
||||
assert.match(hosts, /是否在线仍以共享 Relay 的实时连接为准/);
|
||||
assert.doesNotMatch(hosts, /host\.connection_state === "online"/);
|
||||
assert.match(admin, /主机控制面签到/);
|
||||
assert.match(admin, /最多列出 25 台非正常主机/);
|
||||
assert.match(admin, /只证明 daemon 已通过控制面鉴权/);
|
||||
assert.match(admin, /不单独证明长连接、重连或 sealed 会话质量/);
|
||||
assert.match(stateModel, /last_used_at/);
|
||||
assert.match(stateModel, /不证明.*relay/);
|
||||
assert.match(operationsDoc, /后台区分 active、延迟、offline/);
|
||||
assert.match(plan, /管理员后台即时汇总启用主机的控制面签到/);
|
||||
});
|
||||
@@ -0,0 +1,193 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
import test from "node:test";
|
||||
import {
|
||||
AUDIT_INVITATION_REVOCATION_SQL,
|
||||
CREATE_INVITATION_REVOCATION_IDEMPOTENCY_SQL,
|
||||
REVOKE_INVITATION_SQL,
|
||||
deriveInvitationDisplayState,
|
||||
} from "../db/invitations.ts";
|
||||
|
||||
test("derives active, expired, and revoked invitation states", () => {
|
||||
const now = "2026-08-12T12:00:00.000Z";
|
||||
assert.equal(
|
||||
deriveInvitationDisplayState(
|
||||
{ state: "active", ends_at: "2026-08-13T00:00:00.000Z", revoked_at: null },
|
||||
now,
|
||||
),
|
||||
"active",
|
||||
);
|
||||
assert.equal(
|
||||
deriveInvitationDisplayState(
|
||||
{ state: "active", ends_at: now, revoked_at: null },
|
||||
now,
|
||||
),
|
||||
"expired",
|
||||
);
|
||||
assert.equal(
|
||||
deriveInvitationDisplayState(
|
||||
{ state: "revoked", ends_at: null, revoked_at: now },
|
||||
now,
|
||||
),
|
||||
"revoked",
|
||||
);
|
||||
});
|
||||
|
||||
test("atomically revokes one administrator invitation without duplicate audit", () => {
|
||||
const database = new DatabaseSync(":memory:");
|
||||
database.exec(`
|
||||
CREATE TABLE entitlement_grants (
|
||||
id TEXT PRIMARY KEY,
|
||||
source TEXT NOT NULL,
|
||||
state TEXT NOT NULL,
|
||||
revoked_at TEXT
|
||||
);
|
||||
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)
|
||||
);
|
||||
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 entitlement_grants VALUES
|
||||
('grant_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'admin_exemption', 'active', NULL),
|
||||
('grant_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', 'public_beta', 'active', NULL);
|
||||
`);
|
||||
|
||||
const now = "2026-08-12T12:00:00.000Z";
|
||||
const scope = "admin:exemption:revoke";
|
||||
const key = "revoke-key-0001";
|
||||
const requestHash = "request-hash-1";
|
||||
const grantId = "grant_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
|
||||
const beforeJson = JSON.stringify({ id: grantId, state: "active", revoked_at: null });
|
||||
const afterJson = JSON.stringify({ id: grantId, state: "revoked", revoked_at: now });
|
||||
|
||||
database.exec("BEGIN IMMEDIATE");
|
||||
const first = [
|
||||
Number(database.prepare(CREATE_INVITATION_REVOCATION_IDEMPOTENCY_SQL).run(
|
||||
scope,
|
||||
key,
|
||||
requestHash,
|
||||
afterJson,
|
||||
"2026-08-13T12:00:00.000Z",
|
||||
now,
|
||||
grantId,
|
||||
).changes),
|
||||
Number(database.prepare(REVOKE_INVITATION_SQL).run(
|
||||
now,
|
||||
grantId,
|
||||
scope,
|
||||
key,
|
||||
requestHash,
|
||||
).changes),
|
||||
Number(database.prepare(AUDIT_INVITATION_REVOCATION_SQL).run(
|
||||
"audit_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"admin-user",
|
||||
grantId,
|
||||
"结束该账户闭测",
|
||||
beforeJson,
|
||||
afterJson,
|
||||
"corr_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
now,
|
||||
scope,
|
||||
key,
|
||||
requestHash,
|
||||
).changes),
|
||||
];
|
||||
database.exec("COMMIT");
|
||||
assert.deepEqual(first, [1, 1, 1]);
|
||||
assert.deepEqual(
|
||||
{ ...database.prepare("SELECT state, revoked_at FROM entitlement_grants WHERE id = ?").get(grantId) },
|
||||
{ state: "revoked", revoked_at: now },
|
||||
);
|
||||
assert.equal(
|
||||
database.prepare("SELECT COUNT(*) AS count FROM audit_events").get().count,
|
||||
1,
|
||||
);
|
||||
|
||||
const laterKey = "revoke-key-0002";
|
||||
const laterHash = "request-hash-2";
|
||||
database.exec("BEGIN IMMEDIATE");
|
||||
const repeated = [
|
||||
Number(database.prepare(CREATE_INVITATION_REVOCATION_IDEMPOTENCY_SQL).run(
|
||||
scope,
|
||||
laterKey,
|
||||
laterHash,
|
||||
afterJson,
|
||||
"2026-08-13T12:00:00.000Z",
|
||||
now,
|
||||
grantId,
|
||||
).changes),
|
||||
Number(database.prepare(REVOKE_INVITATION_SQL).run(
|
||||
now,
|
||||
grantId,
|
||||
scope,
|
||||
laterKey,
|
||||
laterHash,
|
||||
).changes),
|
||||
Number(database.prepare(AUDIT_INVITATION_REVOCATION_SQL).run(
|
||||
"audit_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"admin-user",
|
||||
grantId,
|
||||
"重复撤销",
|
||||
beforeJson,
|
||||
afterJson,
|
||||
"corr_bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
now,
|
||||
scope,
|
||||
laterKey,
|
||||
laterHash,
|
||||
).changes),
|
||||
];
|
||||
database.exec("COMMIT");
|
||||
assert.deepEqual(repeated, [0, 0, 0]);
|
||||
assert.equal(
|
||||
database.prepare("SELECT COUNT(*) AS count FROM audit_events").get().count,
|
||||
1,
|
||||
);
|
||||
assert.equal(
|
||||
database.prepare("SELECT state FROM entitlement_grants WHERE id LIKE 'grant_b%'").get().state,
|
||||
"active",
|
||||
);
|
||||
database.close();
|
||||
});
|
||||
|
||||
test("keeps invitation administration authenticated, explicit, and non-monetary", async () => {
|
||||
const [route, repository, invitationSql, actions, adminPage, billingPage, contract] = await Promise.all([
|
||||
readFile(new URL("../app/api/admin/exemptions/route.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../db/repository.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../db/invitations.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/admin/AdminActions.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/admin/page.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/dashboard/billing/page.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../docs/commercial-contract.md", import.meta.url), "utf8"),
|
||||
]);
|
||||
assert.match(route, /getCloudViewer/);
|
||||
assert.match(route, /viewer\.isAdmin/);
|
||||
assert.match(route, /payload\.action === "revoke"/);
|
||||
assert.match(repository, /admin:exemption:revoke/);
|
||||
assert.match(invitationSql, /entitlement\.invitation_revoked/);
|
||||
assert.match(actions, /停止该账户后续闭测配对资格/);
|
||||
assert.match(actions, /既有主机不会被自动断开/);
|
||||
assert.match(adminPage, /闭测邀请记录/);
|
||||
assert.match(billingPage, /下次资格变化/);
|
||||
assert.match(billingPage, /闭测邀请即将到期/);
|
||||
assert.match(billingPage, /deriveInvitationDisplayState/);
|
||||
assert.match(contract, /不创建报价、订单、付款单、积分、钱包、余额/);
|
||||
});
|
||||
@@ -0,0 +1,162 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
import test from "node:test";
|
||||
import {
|
||||
derivePairingProgress,
|
||||
OWNED_PAIRING_PROGRESS_SQL,
|
||||
} from "../db/pairing.ts";
|
||||
|
||||
const NOW = "2026-08-12T12:00:00.000Z";
|
||||
|
||||
function row(overrides = {}) {
|
||||
return {
|
||||
id: "pair_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
status: "waiting",
|
||||
expires_at: "2026-08-12T12:10:00.000Z",
|
||||
claimed_host_id: null,
|
||||
claimed_at: null,
|
||||
last_claim_attempt_at: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
test("derives honest waiting, terminal, and claimed pairing progress", () => {
|
||||
assert.equal(derivePairingProgress(row(), NOW).status, "waiting");
|
||||
assert.equal(
|
||||
derivePairingProgress(
|
||||
row({ expires_at: "2026-08-12T12:00:00.000Z" }),
|
||||
NOW,
|
||||
).status,
|
||||
"expired",
|
||||
);
|
||||
assert.equal(derivePairingProgress(row({ status: "locked" }), NOW).status, "locked");
|
||||
assert.equal(derivePairingProgress(row({ status: "cancelled" }), NOW).status, "cancelled");
|
||||
assert.deepEqual(
|
||||
derivePairingProgress(
|
||||
row({
|
||||
status: "claimed",
|
||||
claimed_host_id: "host_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
claimed_at: "2026-08-12T12:01:00.000Z",
|
||||
}),
|
||||
NOW,
|
||||
),
|
||||
{
|
||||
id: "pair_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
status: "claimed",
|
||||
expiresAt: "2026-08-12T12:10:00.000Z",
|
||||
claimedHostId: "host_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
claimedAt: "2026-08-12T12:01:00.000Z",
|
||||
claimAttemptState: "not_seen",
|
||||
lastClaimAttemptAt: null,
|
||||
},
|
||||
);
|
||||
assert.throws(
|
||||
() => derivePairingProgress(row({ status: "claimed" }), NOW),
|
||||
/incomplete_claimed_pairing_progress/,
|
||||
);
|
||||
assert.throws(
|
||||
() => derivePairingProgress(row({ status: "unexpected" }), NOW),
|
||||
/unknown_pairing_progress_status/,
|
||||
);
|
||||
});
|
||||
|
||||
test("distinguishes an unseen request from a safe owner-visible claim attempt signal", () => {
|
||||
assert.deepEqual(
|
||||
derivePairingProgress(row(), NOW),
|
||||
{
|
||||
id: "pair_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
status: "waiting",
|
||||
expiresAt: "2026-08-12T12:10:00.000Z",
|
||||
claimedHostId: null,
|
||||
claimedAt: null,
|
||||
claimAttemptState: "not_seen",
|
||||
lastClaimAttemptAt: null,
|
||||
},
|
||||
);
|
||||
assert.deepEqual(
|
||||
derivePairingProgress(
|
||||
row({ last_claim_attempt_at: "2026-08-12T11:59:00.000Z" }),
|
||||
NOW,
|
||||
),
|
||||
{
|
||||
id: "pair_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
status: "waiting",
|
||||
expiresAt: "2026-08-12T12:10:00.000Z",
|
||||
claimedHostId: null,
|
||||
claimedAt: null,
|
||||
claimAttemptState: "seen",
|
||||
lastClaimAttemptAt: "2026-08-12T11:59:00.000Z",
|
||||
},
|
||||
);
|
||||
assert.equal(
|
||||
derivePairingProgress(row({ last_claim_attempt_at: "bad-time" }), NOW)
|
||||
.claimAttemptState,
|
||||
"invalid",
|
||||
);
|
||||
assert.equal(
|
||||
derivePairingProgress(
|
||||
row({ last_claim_attempt_at: "2026-08-12T12:06:00.000Z" }),
|
||||
NOW,
|
||||
).claimAttemptState,
|
||||
"invalid",
|
||||
);
|
||||
});
|
||||
|
||||
test("queries pairing progress only through the owning account", () => {
|
||||
const db = new DatabaseSync(":memory:");
|
||||
db.exec(`
|
||||
CREATE TABLE pairing_requests (
|
||||
id TEXT PRIMARY KEY, account_id TEXT NOT NULL, status TEXT NOT NULL,
|
||||
expires_at TEXT NOT NULL, claimed_host_id TEXT, claimed_at TEXT
|
||||
);
|
||||
CREATE TABLE pairing_claim_attempts (
|
||||
pairing_request_id TEXT NOT NULL, created_at TEXT NOT NULL
|
||||
);
|
||||
INSERT INTO pairing_requests VALUES
|
||||
('pair_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', 'acct_a', 'waiting',
|
||||
'2026-08-12T12:10:00.000Z', NULL, NULL);
|
||||
INSERT INTO pairing_claim_attempts VALUES
|
||||
('pair_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', '2026-08-12T11:59:00.000Z');
|
||||
`);
|
||||
const query = db.prepare(OWNED_PAIRING_PROGRESS_SQL);
|
||||
assert.equal(
|
||||
query.get("pair_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "acct_a").status,
|
||||
"waiting",
|
||||
);
|
||||
assert.equal(
|
||||
query.get("pair_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "acct_a")
|
||||
.last_claim_attempt_at,
|
||||
"2026-08-12T11:59:00.000Z",
|
||||
);
|
||||
assert.equal(
|
||||
query.get("pair_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "acct_b"),
|
||||
undefined,
|
||||
);
|
||||
db.close();
|
||||
});
|
||||
|
||||
test("polls the owner-only no-store endpoint and clears one-time state at a terminal result", async () => {
|
||||
const [route, repository, form, stateModel, plan] = await Promise.all([
|
||||
readFile(new URL("../app/api/hosts/pairing/route.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../db/repository.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/dashboard/hosts/new/PairingForm.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../docs/state-model.md", import.meta.url), "utf8"),
|
||||
readFile(new URL("../docs/implementation-plan.md", import.meta.url), "utf8"),
|
||||
]);
|
||||
assert.match(route, /export async function GET/);
|
||||
assert.match(route, /getCloudViewer/);
|
||||
assert.match(route, /getOwnedPairingProgress/);
|
||||
assert.match(route, /cache-control": "no-store/);
|
||||
assert.match(repository, /OWNED_PAIRING_PROGRESS_SQL/);
|
||||
assert.match(form, /pairing_id=\$\{encodeURIComponent\(pairingId!\)\}/);
|
||||
assert.match(form, /\{ cache: "no-store" \}/);
|
||||
assert.match(form, /window\.setTimeout\(poll, 2_000\)/);
|
||||
assert.match(form, /Cloud 已收到请求,但尚未认领/);
|
||||
assert.match(form, /尚未收到注册请求/);
|
||||
assert.match(form, /当前码有效时无需反复生成/);
|
||||
assert.match(form, /setCompletion[\s\S]*setPairing\(null\)/);
|
||||
assert.match(form, /一次性配对码已经从页面状态中清除/);
|
||||
assert.match(stateModel, /账户所有者可以轮询自己创建的配对状态/);
|
||||
assert.match(plan, /自动确认认领、过期、锁定或取消/);
|
||||
});
|
||||
@@ -0,0 +1,70 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import {
|
||||
CONTROL_PLANE_EXCLUSIONS,
|
||||
DATA_INVENTORY,
|
||||
INVENTORIED_TABLES,
|
||||
} from "../app/privacy/data-inventory.ts";
|
||||
|
||||
test("keeps the public data inventory complete against the D1 schema", async () => {
|
||||
const schema = await readFile(new URL("../db/schema.ts", import.meta.url), "utf8");
|
||||
const schemaTables = [...schema.matchAll(/sqliteTable\(\s*["']([^"']+)["']/g)]
|
||||
.map((match) => match[1])
|
||||
.sort();
|
||||
const inventoryTables = [...INVENTORIED_TABLES].sort();
|
||||
|
||||
assert.equal(new Set(schemaTables).size, schemaTables.length, "schema table names must be unique");
|
||||
assert.equal(new Set(inventoryTables).size, inventoryTables.length, "inventory table names must be unique");
|
||||
assert.deepEqual(inventoryTables, schemaTables);
|
||||
});
|
||||
|
||||
test("publishes purpose, retention boundary, and user control for every data group", () => {
|
||||
assert.ok(DATA_INVENTORY.length >= 7);
|
||||
for (const group of DATA_INVENTORY) {
|
||||
assert.ok(group.summary.length > 10, `${group.id} needs a summary`);
|
||||
assert.ok(group.purpose.length > 10, `${group.id} needs a purpose`);
|
||||
assert.ok(group.retention.length > 10, `${group.id} needs a retention boundary`);
|
||||
assert.ok(group.userControl.length > 10, `${group.id} needs a user control statement`);
|
||||
assert.ok(group.examples.length > 0, `${group.id} needs examples`);
|
||||
assert.ok(group.tables.length > 0, `${group.id} needs table coverage`);
|
||||
}
|
||||
|
||||
const dormant = DATA_INVENTORY.find((group) => group.id === "dormant-billing");
|
||||
assert.equal(dormant?.status, "dormant");
|
||||
assert.match(dormant?.purpose ?? "", /服务端拒绝写入/);
|
||||
assert.ok(CONTROL_PLANE_EXCLUSIONS.some((entry) => entry.item === "项目文件和任意磁盘目录内容"));
|
||||
assert.match(
|
||||
CONTROL_PLANE_EXCLUSIONS.find((entry) => entry.item.includes("明文配对码"))?.boundary ?? "",
|
||||
/短暂处理,不持久化到 D1/,
|
||||
);
|
||||
});
|
||||
|
||||
test("keeps the public privacy page honest about unfinished deletion and compliance work", async () => {
|
||||
const [page, shell, readiness, docs, plan] = await Promise.all([
|
||||
readFile(new URL("../app/privacy/page.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/components/Shells.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/readiness/page.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../docs/data-inventory.md", import.meta.url), "utf8"),
|
||||
readFile(new URL("../docs/implementation-plan.md", import.meta.url), "utf8"),
|
||||
]);
|
||||
|
||||
assert.match(page, /这不是一份拿模板拼出的最终隐私政策/);
|
||||
assert.match(page, /隐私门禁还没有通过/);
|
||||
assert.match(page, /不会宣称“已经合规”/);
|
||||
assert.match(page, /DATA_INVENTORY\.map/);
|
||||
assert.match(shell, /href="\/privacy"/);
|
||||
assert.match(readiness, /查看公测数据说明/);
|
||||
assert.match(docs, /以上项目完成前.*P0 门禁继续保持阻止/);
|
||||
assert.match(plan, /\[x\].*D1 schema 完整对齐的控制平面数据清单/);
|
||||
assert.doesNotMatch(`${page}\n${docs}`, /隐私门禁已经通过|已经完成合规/);
|
||||
});
|
||||
|
||||
test("documents retention facts that are enforced by the current pairing code", async () => {
|
||||
const repository = await readFile(new URL("../db/repository.ts", import.meta.url), "utf8");
|
||||
assert.match(repository, /const expiresAt = isoAfterMinutes\(10\)/);
|
||||
assert.match(repository, /pairing_claim_rate_limits WHERE window_start < \?/);
|
||||
assert.match(repository, /nowMilliseconds - 24 \* 60 \* 60_000/);
|
||||
assert.match(repository, /pairing_claim_attempts WHERE created_at < \?/);
|
||||
assert.match(repository, /nowMilliseconds - 30 \* 24 \* 60 \* 60_000/);
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
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/);
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,176 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { createHash } from "node:crypto";
|
||||
import test from "node:test";
|
||||
import { DAEMON_RELEASE_POLICY } from "../release/daemon-policy.mjs";
|
||||
import {
|
||||
ReleaseVerificationError,
|
||||
formatCloudReleaseEnvironment,
|
||||
parseReleaseTag,
|
||||
verifyDaemonRelease,
|
||||
} from "../release/verify-daemon-release-core.mjs";
|
||||
|
||||
const TAG = "v0.2.6";
|
||||
const API_URL = `https://api.github.com/repos/${DAEMON_RELEASE_POLICY.repository}/releases/tags/${TAG}`;
|
||||
const RELEASE_BASE = `https://github.com/${DAEMON_RELEASE_POLICY.repository}/releases/download/${TAG}`;
|
||||
|
||||
function hash(bytes) {
|
||||
return createHash("sha256").update(bytes).digest("hex");
|
||||
}
|
||||
|
||||
function fixture() {
|
||||
const bodies = new Map(
|
||||
DAEMON_RELEASE_POLICY.assets.map((asset, index) => [
|
||||
asset.filename,
|
||||
Buffer.from(`verified-daemon-asset-${index + 1}`),
|
||||
]),
|
||||
);
|
||||
const checksumsText = [
|
||||
...DAEMON_RELEASE_POLICY.assets.map(
|
||||
(asset) => `${hash(bodies.get(asset.filename))} ${asset.filename}`,
|
||||
),
|
||||
`${"9".repeat(64)} nekonest-server-linux-amd64.tar.gz`,
|
||||
].join("\n") + "\n";
|
||||
const checksumsBytes = Buffer.from(checksumsText);
|
||||
const release = {
|
||||
tag_name: TAG,
|
||||
draft: false,
|
||||
prerelease: false,
|
||||
html_url: `https://github.com/${DAEMON_RELEASE_POLICY.repository}/releases/tag/${TAG}`,
|
||||
published_at: "2026-08-12T00:00:00Z",
|
||||
assets: [
|
||||
...DAEMON_RELEASE_POLICY.assets.map((asset) => ({
|
||||
name: asset.filename,
|
||||
browser_download_url: `${RELEASE_BASE}/${asset.filename}`,
|
||||
size: bodies.get(asset.filename).byteLength,
|
||||
state: "uploaded",
|
||||
digest: `sha256:${hash(bodies.get(asset.filename))}`,
|
||||
})),
|
||||
{
|
||||
name: "checksums.txt",
|
||||
browser_download_url: `${RELEASE_BASE}/checksums.txt`,
|
||||
size: checksumsBytes.byteLength,
|
||||
state: "uploaded",
|
||||
digest: `sha256:${hash(checksumsBytes)}`,
|
||||
},
|
||||
],
|
||||
};
|
||||
const requests = [];
|
||||
const fetchImpl = async (url, init) => {
|
||||
requests.push({ url: String(url), init });
|
||||
let bytes;
|
||||
if (String(url) === API_URL) bytes = Buffer.from(JSON.stringify(release));
|
||||
else if (String(url) === `${RELEASE_BASE}/checksums.txt`) bytes = checksumsBytes;
|
||||
else {
|
||||
const filename = String(url).slice(RELEASE_BASE.length + 1);
|
||||
bytes = bodies.get(filename);
|
||||
}
|
||||
if (!bytes) return new Response("missing", { status: 404 });
|
||||
return new Response(bytes, {
|
||||
status: 200,
|
||||
headers: { "content-length": String(bytes.byteLength) },
|
||||
});
|
||||
};
|
||||
return { bodies, checksumsBytes, fetchImpl, release, requests };
|
||||
}
|
||||
|
||||
async function expectCode(promise, code) {
|
||||
await assert.rejects(promise, (error) => {
|
||||
assert.ok(error instanceof ReleaseVerificationError);
|
||||
assert.equal(error.code, code);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
test("rejects old or prerelease tags before making a network request", async () => {
|
||||
let calls = 0;
|
||||
const fetchImpl = async () => {
|
||||
calls += 1;
|
||||
throw new Error("should not fetch");
|
||||
};
|
||||
assert.throws(() => parseReleaseTag("v0.2.5"), /不得低于 v0\.2\.6/);
|
||||
assert.throws(() => parseReleaseTag("v0.2.6-beta.1"), /稳定版本/);
|
||||
await expectCode(
|
||||
verifyDaemonRelease({ tag: "v0.2.5", fetchImpl }),
|
||||
"incompatible_release_version",
|
||||
);
|
||||
assert.equal(calls, 0);
|
||||
});
|
||||
|
||||
test("verifies API identity, checksums digest, asset digests, sizes, and downloaded bytes", async () => {
|
||||
const data = fixture();
|
||||
const token = "github-test-token-that-must-not-be-returned";
|
||||
const verified = await verifyDaemonRelease({ tag: TAG, token, fetchImpl: data.fetchImpl });
|
||||
assert.equal(verified.version, "0.2.6");
|
||||
assert.equal(verified.assets.length, 3);
|
||||
assert.equal(verified.assets[0].sha256, hash(data.bodies.get(verified.assets[0].filename)));
|
||||
assert.equal(data.requests.length, 5);
|
||||
assert.equal(data.requests[0].init.headers.authorization, `Bearer ${token}`);
|
||||
for (const request of data.requests.slice(1)) {
|
||||
assert.equal(request.init.headers.authorization, undefined);
|
||||
}
|
||||
const serialized = JSON.stringify(verified);
|
||||
assert.doesNotMatch(serialized, /github-test-token/);
|
||||
const environment = formatCloudReleaseEnvironment(verified);
|
||||
assert.match(environment, /NEKONEST_CLOUD_DAEMON_RELEASE_VERSION=0\.2\.6/);
|
||||
assert.match(environment, /NEKONEST_CLOUD_DAEMON_LINUX_ARM64_SHA256=[0-9a-f]{64}/);
|
||||
assert.doesNotMatch(environment, /GITHUB_TOKEN|github-test-token/);
|
||||
});
|
||||
|
||||
test("fails closed on duplicate assets, URL substitution, prerelease metadata, or digest drift", async () => {
|
||||
{
|
||||
const data = fixture();
|
||||
data.release.assets.push({ ...data.release.assets[0] });
|
||||
await expectCode(
|
||||
verifyDaemonRelease({ tag: TAG, fetchImpl: data.fetchImpl }),
|
||||
"release_asset_duplicate",
|
||||
);
|
||||
}
|
||||
{
|
||||
const data = fixture();
|
||||
data.release.assets[0].browser_download_url = "https://download.example.test/substituted.zip";
|
||||
await expectCode(
|
||||
verifyDaemonRelease({ tag: TAG, fetchImpl: data.fetchImpl }),
|
||||
"release_asset_url_mismatch",
|
||||
);
|
||||
}
|
||||
{
|
||||
const data = fixture();
|
||||
data.release.prerelease = true;
|
||||
await expectCode(
|
||||
verifyDaemonRelease({ tag: TAG, fetchImpl: data.fetchImpl }),
|
||||
"release_identity_invalid",
|
||||
);
|
||||
}
|
||||
{
|
||||
const data = fixture();
|
||||
data.release.assets[0].digest = `sha256:${"f".repeat(64)}`;
|
||||
await expectCode(
|
||||
verifyDaemonRelease({ tag: TAG, fetchImpl: data.fetchImpl }),
|
||||
"release_digest_mismatch",
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("detects same-sized downloaded byte substitution even when metadata still looks valid", async () => {
|
||||
const data = fixture();
|
||||
const filename = DAEMON_RELEASE_POLICY.assets[0].filename;
|
||||
const original = data.bodies.get(filename);
|
||||
data.bodies.set(filename, Buffer.from(original.toString().replace("verified", "VERIFIED")));
|
||||
assert.equal(data.bodies.get(filename).byteLength, original.byteLength);
|
||||
await expectCode(
|
||||
verifyDaemonRelease({ tag: TAG, fetchImpl: data.fetchImpl }),
|
||||
"release_asset_hash_mismatch",
|
||||
);
|
||||
});
|
||||
|
||||
test("CLI has a no-network usage failure when the exact tag is missing", () => {
|
||||
const result = spawnSync(
|
||||
process.execPath,
|
||||
["tools/verify-daemon-release.mjs"],
|
||||
{ cwd: new URL("..", import.meta.url), encoding: "utf8" },
|
||||
);
|
||||
assert.equal(result.status, 2);
|
||||
assert.match(result.stderr, /vX\.Y\.Z/);
|
||||
assert.equal(result.stdout, "");
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,357 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { DatabaseSync } from "node:sqlite";
|
||||
import test from "node:test";
|
||||
import {
|
||||
ACQUIRE_RETENTION_JOB_SQL,
|
||||
COMPLETE_RETENTION_JOB_SQL,
|
||||
DELETE_EXPIRED_CLAIM_ATTEMPTS_SQL,
|
||||
DELETE_EXPIRED_CLAIM_RATE_WINDOWS_SQL,
|
||||
DELETE_EXPIRED_IDEMPOTENCY_SQL,
|
||||
DELETE_EXPIRED_DEVICE_REGISTRATION_REPLAYS_SQL,
|
||||
DELETE_RETIRED_PENDING_PHONE_PRINCIPALS_SQL,
|
||||
DELETE_RETIRED_PENDING_PHONE_ROUTES_SQL,
|
||||
DELETE_RETIRED_PHONE_HANDOFF_TICKETS_SQL,
|
||||
deriveRetentionJobHealth,
|
||||
FAIL_RETENTION_JOB_SQL,
|
||||
getRetentionCutoffs,
|
||||
RETENTION_CRON,
|
||||
RETENTION_JOB_KEY,
|
||||
RETENTION_RUNNING_STALE_MS,
|
||||
RETIRE_EXPIRED_PAIRING_CODES_SQL,
|
||||
} from "../db/retention.ts";
|
||||
|
||||
test("derives the documented 24-hour and 30-day retention cutoffs", () => {
|
||||
assert.deepEqual(getRetentionCutoffs("2026-08-12T12:00:00.000Z"), {
|
||||
now: "2026-08-12T12:00:00.000Z",
|
||||
claimRateBefore: "2026-08-11T12:00:00.000Z",
|
||||
claimAttemptBefore: "2026-07-13T12:00:00.000Z",
|
||||
handoffTicketBefore: "2026-08-11T12:00:00.000Z",
|
||||
});
|
||||
assert.throws(() => getRetentionCutoffs("not-a-date"), /ISO timestamp/);
|
||||
});
|
||||
|
||||
test("retires only expired pairing secrets and deletes only records older than their cutoffs", () => {
|
||||
const db = new DatabaseSync(":memory:");
|
||||
db.exec(`
|
||||
CREATE TABLE pairing_requests (
|
||||
id TEXT PRIMARY KEY,
|
||||
code_hash TEXT NOT NULL UNIQUE,
|
||||
expires_at TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE pairing_claim_rate_limits (
|
||||
source_hash TEXT NOT NULL,
|
||||
window_start TEXT NOT NULL,
|
||||
PRIMARY KEY (source_hash, window_start)
|
||||
);
|
||||
CREATE TABLE pairing_claim_attempts (
|
||||
id TEXT PRIMARY KEY,
|
||||
created_at TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE idempotency_records (
|
||||
scope TEXT NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
expires_at TEXT NOT NULL,
|
||||
PRIMARY KEY (scope, key)
|
||||
);
|
||||
CREATE TABLE phone_handoff_tickets (
|
||||
id TEXT PRIMARY KEY,
|
||||
ticket_hash TEXT NOT NULL,
|
||||
tenant_id TEXT NOT NULL,
|
||||
expires_at TEXT NOT NULL,
|
||||
consumed_at TEXT,
|
||||
completed_phone_id TEXT,
|
||||
completed_phone_token_hash TEXT,
|
||||
completed_route_handle_hash TEXT
|
||||
);
|
||||
CREATE TABLE relay_phone_principals (
|
||||
phone_id TEXT PRIMARY KEY,
|
||||
tenant_id TEXT NOT NULL,
|
||||
token_hash TEXT NOT NULL,
|
||||
status TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE phone_route_handles (
|
||||
id TEXT PRIMARY KEY,
|
||||
handle_hash TEXT NOT NULL,
|
||||
tenant_id TEXT NOT NULL,
|
||||
phone_id TEXT NOT NULL,
|
||||
status TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE device_registration_replays (
|
||||
pairing_id TEXT PRIMARY KEY,
|
||||
expires_at TEXT NOT NULL
|
||||
);
|
||||
|
||||
INSERT INTO pairing_requests VALUES
|
||||
('pair_old', 'secret-old', '2026-08-12T11:59:59.999Z'),
|
||||
('pair_boundary', 'secret-boundary', '2026-08-12T12:00:00.000Z'),
|
||||
('pair_live', 'secret-live', '2026-08-12T12:00:00.001Z');
|
||||
INSERT INTO pairing_claim_rate_limits VALUES
|
||||
('old', '2026-08-11T11:59:59.999Z'),
|
||||
('boundary', '2026-08-11T12:00:00.000Z');
|
||||
INSERT INTO pairing_claim_attempts VALUES
|
||||
('old', '2026-07-13T11:59:59.999Z'),
|
||||
('boundary', '2026-07-13T12:00:00.000Z');
|
||||
INSERT INTO idempotency_records VALUES
|
||||
('scope', 'expired', '2026-08-12T12:00:00.000Z'),
|
||||
('scope', 'live', '2026-08-12T12:00:00.001Z');
|
||||
INSERT INTO phone_handoff_tickets VALUES
|
||||
('handoff_expired', 'digest-expired', 'tenant_a', '2026-08-11T11:59:59.999Z', NULL, 'phone_expired', 'token-expired', 'route-expired'),
|
||||
('handoff_consumed', 'digest-consumed', 'tenant_a', '2026-08-12T12:00:00.001Z', '2026-08-11T12:00:00.000Z', 'phone_consumed', 'token-consumed', 'route-consumed'),
|
||||
('handoff_boundary', 'digest-boundary', 'tenant_a', '2026-08-11T12:00:00.000Z', NULL, 'phone_boundary', 'token-boundary', 'route-boundary'),
|
||||
('handoff_active_old', 'digest-active', 'tenant_a', '2026-08-11T11:59:59.999Z', NULL, 'phone_active', 'token-active', 'route-active'),
|
||||
('handoff_live', 'digest-live', 'tenant_a', '2026-08-11T12:00:00.001Z', NULL, 'phone_live', 'token-live', 'route-live');
|
||||
INSERT INTO relay_phone_principals VALUES
|
||||
('phone_expired', 'tenant_a', 'token-expired', 'pending'),
|
||||
('phone_consumed', 'tenant_a', 'token-consumed', 'pending'),
|
||||
('phone_boundary', 'tenant_a', 'token-boundary', 'pending'),
|
||||
('phone_active', 'tenant_a', 'token-active', 'active'),
|
||||
('phone_live', 'tenant_a', 'token-live', 'pending');
|
||||
INSERT INTO phone_route_handles VALUES
|
||||
('handle_expired', 'route-expired', 'tenant_a', 'phone_expired', 'pending'),
|
||||
('handle_consumed', 'route-consumed', 'tenant_a', 'phone_consumed', 'pending'),
|
||||
('handle_boundary', 'route-boundary', 'tenant_a', 'phone_boundary', 'pending'),
|
||||
('handle_active', 'route-active', 'tenant_a', 'phone_active', 'active'),
|
||||
('handle_live', 'route-live', 'tenant_a', 'phone_live', 'pending');
|
||||
INSERT INTO device_registration_replays VALUES
|
||||
('replay_expired', '2026-08-12T12:00:00.000Z'),
|
||||
('replay_live', '2026-08-12T12:00:00.001Z');
|
||||
`);
|
||||
|
||||
const cutoffs = getRetentionCutoffs("2026-08-12T12:00:00.000Z");
|
||||
const changes = [
|
||||
db.prepare(RETIRE_EXPIRED_PAIRING_CODES_SQL).run(cutoffs.now).changes,
|
||||
db.prepare(DELETE_EXPIRED_CLAIM_RATE_WINDOWS_SQL).run(cutoffs.claimRateBefore).changes,
|
||||
db.prepare(DELETE_EXPIRED_CLAIM_ATTEMPTS_SQL).run(cutoffs.claimAttemptBefore).changes,
|
||||
db.prepare(DELETE_EXPIRED_IDEMPOTENCY_SQL).run(cutoffs.now).changes,
|
||||
db.prepare(DELETE_RETIRED_PENDING_PHONE_ROUTES_SQL)
|
||||
.run(cutoffs.handoffTicketBefore, cutoffs.handoffTicketBefore).changes,
|
||||
db.prepare(DELETE_RETIRED_PENDING_PHONE_PRINCIPALS_SQL)
|
||||
.run(cutoffs.handoffTicketBefore, cutoffs.handoffTicketBefore).changes,
|
||||
db.prepare(DELETE_RETIRED_PHONE_HANDOFF_TICKETS_SQL)
|
||||
.run(cutoffs.handoffTicketBefore, cutoffs.handoffTicketBefore).changes,
|
||||
db.prepare(DELETE_EXPIRED_DEVICE_REGISTRATION_REPLAYS_SQL).run(cutoffs.now).changes,
|
||||
].map(Number);
|
||||
assert.deepEqual(changes, [2, 1, 1, 1, 3, 3, 4, 1]);
|
||||
assert.deepEqual(
|
||||
db.prepare("SELECT id, code_hash FROM pairing_requests ORDER BY id").all().map((row) => ({ ...row })),
|
||||
[
|
||||
{ id: "pair_boundary", code_hash: "expired:pair_boundary" },
|
||||
{ id: "pair_live", code_hash: "secret-live" },
|
||||
{ id: "pair_old", code_hash: "expired:pair_old" },
|
||||
],
|
||||
);
|
||||
assert.equal(db.prepare("SELECT COUNT(*) AS count FROM device_registration_replays").get().count, 1);
|
||||
assert.equal(db.prepare("SELECT COUNT(*) AS count FROM pairing_claim_rate_limits").get().count, 1);
|
||||
assert.equal(db.prepare("SELECT COUNT(*) AS count FROM pairing_claim_attempts").get().count, 1);
|
||||
assert.equal(db.prepare("SELECT COUNT(*) AS count FROM idempotency_records").get().count, 1);
|
||||
assert.deepEqual(
|
||||
db.prepare("SELECT id FROM phone_handoff_tickets ORDER BY id").all().map((row) => row.id),
|
||||
["handoff_live"],
|
||||
);
|
||||
assert.deepEqual(
|
||||
db.prepare("SELECT phone_id, status FROM relay_phone_principals ORDER BY phone_id").all().map((row) => ({ ...row })),
|
||||
[
|
||||
{ phone_id: "phone_active", status: "active" },
|
||||
{ phone_id: "phone_live", status: "pending" },
|
||||
],
|
||||
);
|
||||
assert.deepEqual(
|
||||
db.prepare("SELECT id, status FROM phone_route_handles ORDER BY id").all().map((row) => ({ ...row })),
|
||||
[
|
||||
{ id: "handle_active", status: "active" },
|
||||
{ id: "handle_live", status: "pending" },
|
||||
],
|
||||
);
|
||||
|
||||
const repeated = [
|
||||
db.prepare(RETIRE_EXPIRED_PAIRING_CODES_SQL).run(cutoffs.now).changes,
|
||||
db.prepare(DELETE_EXPIRED_CLAIM_RATE_WINDOWS_SQL).run(cutoffs.claimRateBefore).changes,
|
||||
db.prepare(DELETE_EXPIRED_CLAIM_ATTEMPTS_SQL).run(cutoffs.claimAttemptBefore).changes,
|
||||
db.prepare(DELETE_EXPIRED_IDEMPOTENCY_SQL).run(cutoffs.now).changes,
|
||||
db.prepare(DELETE_RETIRED_PENDING_PHONE_ROUTES_SQL)
|
||||
.run(cutoffs.handoffTicketBefore, cutoffs.handoffTicketBefore).changes,
|
||||
db.prepare(DELETE_RETIRED_PENDING_PHONE_PRINCIPALS_SQL)
|
||||
.run(cutoffs.handoffTicketBefore, cutoffs.handoffTicketBefore).changes,
|
||||
db.prepare(DELETE_RETIRED_PHONE_HANDOFF_TICKETS_SQL)
|
||||
.run(cutoffs.handoffTicketBefore, cutoffs.handoffTicketBefore).changes,
|
||||
db.prepare(DELETE_EXPIRED_DEVICE_REGISTRATION_REPLAYS_SQL).run(cutoffs.now).changes,
|
||||
].map(Number);
|
||||
assert.deepEqual(repeated, [0, 0, 0, 0, 0, 0, 0, 0]);
|
||||
db.close();
|
||||
});
|
||||
|
||||
test("derives healthy, overdue, running, stalled, failed, and invalid automatic maintenance states", () => {
|
||||
const record = {
|
||||
key: RETENTION_JOB_KEY,
|
||||
state: "succeeded",
|
||||
run_id: "maintenance_1",
|
||||
trigger: "scheduled",
|
||||
scheduled_at: "2026-08-12T00:00:00.000Z",
|
||||
started_at: "2026-08-12T00:00:01.000Z",
|
||||
completed_at: "2026-08-12T00:00:02.000Z",
|
||||
last_success_at: "2026-08-12T00:00:02.000Z",
|
||||
result_json: "{}",
|
||||
error_code: null,
|
||||
consecutive_failures: 0,
|
||||
run_count: 1,
|
||||
created_at: "2026-08-12T00:00:01.000Z",
|
||||
updated_at: "2026-08-12T00:00:02.000Z",
|
||||
};
|
||||
|
||||
assert.equal(deriveRetentionJobHealth(null).state, "never_run");
|
||||
assert.equal(
|
||||
deriveRetentionJobHealth(record, "2026-08-13T12:00:02.000Z").state,
|
||||
"healthy",
|
||||
);
|
||||
assert.equal(
|
||||
deriveRetentionJobHealth(record, "2026-08-13T12:00:02.001Z").state,
|
||||
"overdue",
|
||||
);
|
||||
assert.equal(
|
||||
deriveRetentionJobHealth(
|
||||
{ ...record, state: "running", completed_at: null, last_success_at: null },
|
||||
"2026-08-12T00:30:01.000Z",
|
||||
).state,
|
||||
"running",
|
||||
);
|
||||
assert.equal(
|
||||
deriveRetentionJobHealth(
|
||||
{ ...record, state: "running", completed_at: null, last_success_at: null },
|
||||
"2026-08-12T00:30:01.001Z",
|
||||
).state,
|
||||
"stalled",
|
||||
);
|
||||
assert.equal(
|
||||
deriveRetentionJobHealth(
|
||||
{ ...record, state: "failed", error_code: "retention_maintenance_failed", consecutive_failures: 1 },
|
||||
"2026-08-12T00:05:00.000Z",
|
||||
).state,
|
||||
"failed",
|
||||
);
|
||||
assert.equal(
|
||||
deriveRetentionJobHealth({ ...record, trigger: "spoofed" }, "2026-08-12T00:05:00.000Z").state,
|
||||
"invalid",
|
||||
);
|
||||
assert.equal(
|
||||
deriveRetentionJobHealth({ ...record, error_code: "unsafe details" }, "2026-08-12T00:05:00.000Z").state,
|
||||
"invalid",
|
||||
);
|
||||
assert.equal(
|
||||
deriveRetentionJobHealth(
|
||||
{ ...record, started_at: "2026-08-12T00:10:00.000Z" },
|
||||
"2026-08-12T00:05:00.000Z",
|
||||
).state,
|
||||
"invalid",
|
||||
);
|
||||
});
|
||||
|
||||
test("fences overlapping automatic maintenance and preserves the latest successful result", async () => {
|
||||
const migration = await readFile(
|
||||
new URL("../drizzle/0009_flat_robbie_robertson.sql", import.meta.url),
|
||||
"utf8",
|
||||
);
|
||||
const db = new DatabaseSync(":memory:");
|
||||
db.exec(migration);
|
||||
|
||||
const acquire = (runId, scheduledAt, startedAt) => db
|
||||
.prepare(ACQUIRE_RETENTION_JOB_SQL)
|
||||
.run(
|
||||
RETENTION_JOB_KEY,
|
||||
runId,
|
||||
"scheduled",
|
||||
scheduledAt,
|
||||
startedAt,
|
||||
startedAt,
|
||||
startedAt,
|
||||
new Date(new Date(startedAt).getTime() - RETENTION_RUNNING_STALE_MS).toISOString(),
|
||||
);
|
||||
|
||||
assert.equal(Number(acquire("run_1", "2026-08-12T00:00:00.000Z", "2026-08-12T00:00:01.000Z").changes), 1);
|
||||
assert.equal(Number(acquire("run_duplicate", "2026-08-12T00:01:00.000Z", "2026-08-12T00:01:01.000Z").changes), 0);
|
||||
assert.equal(Number(acquire("run_2", "2026-08-12T00:31:00.001Z", "2026-08-12T00:31:01.001Z").changes), 1);
|
||||
|
||||
const complete = db.prepare(COMPLETE_RETENTION_JOB_SQL);
|
||||
assert.equal(Number(complete.run(
|
||||
"2026-08-12T00:31:02.000Z",
|
||||
"2026-08-12T00:31:02.000Z",
|
||||
"{}",
|
||||
"2026-08-12T00:31:02.000Z",
|
||||
RETENTION_JOB_KEY,
|
||||
"run_1",
|
||||
).changes), 0);
|
||||
assert.equal(Number(complete.run(
|
||||
"2026-08-12T00:31:02.000Z",
|
||||
"2026-08-12T00:31:02.000Z",
|
||||
JSON.stringify({ deletedClaimAttempts: 2 }),
|
||||
"2026-08-12T00:31:02.000Z",
|
||||
RETENTION_JOB_KEY,
|
||||
"run_2",
|
||||
).changes), 1);
|
||||
|
||||
assert.equal(Number(acquire("run_3", "2026-08-13T00:00:00.000Z", "2026-08-13T00:00:01.000Z").changes), 1);
|
||||
assert.equal(Number(db.prepare(FAIL_RETENTION_JOB_SQL).run(
|
||||
"2026-08-13T00:00:02.000Z",
|
||||
"retention_maintenance_failed",
|
||||
"2026-08-13T00:00:02.000Z",
|
||||
RETENTION_JOB_KEY,
|
||||
"run_3",
|
||||
).changes), 1);
|
||||
|
||||
const row = db.prepare("SELECT * FROM maintenance_jobs WHERE key = ?").get(RETENTION_JOB_KEY);
|
||||
assert.equal(row.state, "failed");
|
||||
assert.equal(row.run_id, "run_3");
|
||||
assert.equal(Number(row.run_count), 3);
|
||||
assert.equal(Number(row.consecutive_failures), 1);
|
||||
assert.equal(row.last_success_at, "2026-08-12T00:31:02.000Z");
|
||||
assert.equal(db.prepare("SELECT COUNT(*) AS count FROM maintenance_jobs").get().count, 1);
|
||||
assert.match(
|
||||
db.prepare("EXPLAIN QUERY PLAN SELECT * FROM maintenance_jobs WHERE key = ?").get(RETENTION_JOB_KEY).detail,
|
||||
/USING INDEX sqlite_autoindex_maintenance_jobs_1/,
|
||||
);
|
||||
db.close();
|
||||
});
|
||||
|
||||
test("keeps manual and scheduled retention bounded, observable, and auditable", async () => {
|
||||
const [route, repository, adminActions, adminPage, inventory, runner, worker, vite, bootstrap, runbook] = await Promise.all([
|
||||
readFile(new URL("../app/api/admin/retention/route.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../db/repository.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/admin/AdminActions.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../app/admin/page.tsx", import.meta.url), "utf8"),
|
||||
readFile(new URL("../docs/data-inventory.md", import.meta.url), "utf8"),
|
||||
readFile(new URL("../db/retention-runner.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../worker/index.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../vite.config.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../db/bootstrap.ts", import.meta.url), "utf8"),
|
||||
readFile(new URL("../docs/retention-maintenance.md", import.meta.url), "utf8"),
|
||||
]);
|
||||
|
||||
assert.match(route, /getCloudViewer/);
|
||||
assert.match(route, /!viewer\.isAdmin/);
|
||||
assert.match(route, /readJsonMutation/);
|
||||
assert.match(route, /confirmed: payload\.confirmed === true/);
|
||||
assert.match(repository, /retention_confirmation_required/);
|
||||
assert.match(repository, /privacy\.retention_cleanup/);
|
||||
assert.match(repository, /db\.batch\(\[/);
|
||||
assert.match(repository, /DELETE_EXPIRED_IDEMPOTENCY_SQL/);
|
||||
assert.match(repository, /DELETE_RETIRED_PHONE_HANDOFF_TICKETS_SQL/);
|
||||
assert.match(repository, /DELETE_EXPIRED_DEVICE_REGISTRATION_REPLAYS_SQL/);
|
||||
assert.doesNotMatch(repository.slice(repository.indexOf("export async function runRetentionMaintenance"), repository.indexOf("export async function createFeedback")), /DELETE FROM (?:accounts|hosts|audit_events|beta_feedback|tenant_instances)/);
|
||||
assert.match(adminActions, /只处理已经到期的技术记录/);
|
||||
assert.match(adminActions, /不会删除账户、主机、反馈、审计、租户卷或备份/);
|
||||
assert.match(adminPage, /自动清理正常/);
|
||||
assert.match(adminPage, /连续失败/);
|
||||
assert.match(inventory, /成功认领会在同一原子写中烧毁原摘要/);
|
||||
assert.match(runner, /await ensureDatabase\(\)/);
|
||||
const runnerBody = runner.slice(runner.indexOf("export async function runScheduledRetentionMaintenance"));
|
||||
assert.ok(runnerBody.indexOf("await ensureDatabase()") < runnerBody.indexOf(".prepare(ACQUIRE_RETENTION_JOB_SQL)"));
|
||||
assert.match(runner, /system:scheduled-retention/);
|
||||
assert.match(runner, /FAIL_RETENTION_JOB_SQL/);
|
||||
assert.match(runner, /throw error/);
|
||||
assert.match(worker, /async scheduled\(controller: ScheduledController/);
|
||||
assert.match(worker, /runScheduledRetentionMaintenance\(env\.DB, controller\.scheduledTime\)/);
|
||||
assert.match(vite, /triggers: \{ crons: \[RETENTION_CRON\] \}/);
|
||||
assert.equal(RETENTION_CRON, "17 18 * * *");
|
||||
assert.match(bootstrap, /0009_flat_robbie_robertson/);
|
||||
assert.match(runbook, /不会删除账户、主机、设备凭据、反馈、故障公告、审计、租户、开通任务、卷或备份/);
|
||||
});
|
||||
@@ -0,0 +1,82 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { readFile } from "node:fs/promises";
|
||||
import test from "node:test";
|
||||
import {
|
||||
CONTENT_SECURITY_POLICY,
|
||||
PERMISSIONS_POLICY,
|
||||
withSecurityHeaders,
|
||||
} from "../worker/security-headers.ts";
|
||||
|
||||
test("applies the browser security baseline without changing the response", async () => {
|
||||
const request = new Request("https://cloud.example.test/dashboard");
|
||||
const response = withSecurityHeaders(
|
||||
request,
|
||||
new Response("ok", {
|
||||
status: 201,
|
||||
headers: {
|
||||
"Cache-Control": "private, no-store",
|
||||
"Content-Type": "text/plain; charset=utf-8",
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
assert.equal(response.status, 201);
|
||||
assert.equal(await response.text(), "ok");
|
||||
assert.equal(response.headers.get("Cache-Control"), "private, no-store");
|
||||
assert.equal(response.headers.get("Content-Type"), "text/plain; charset=utf-8");
|
||||
assert.equal(response.headers.get("Content-Security-Policy"), CONTENT_SECURITY_POLICY);
|
||||
assert.equal(response.headers.get("Permissions-Policy"), PERMISSIONS_POLICY);
|
||||
assert.equal(response.headers.get("Referrer-Policy"), "no-referrer");
|
||||
assert.equal(response.headers.get("X-Content-Type-Options"), "nosniff");
|
||||
assert.equal(response.headers.get("X-DNS-Prefetch-Control"), "off");
|
||||
assert.equal(response.headers.get("X-Frame-Options"), "DENY");
|
||||
assert.equal(response.headers.get("X-Permitted-Cross-Domain-Policies"), "none");
|
||||
assert.equal(response.headers.get("Strict-Transport-Security"), "max-age=31536000");
|
||||
});
|
||||
|
||||
test("keeps the baseline CSP restrictive while documenting its inline compatibility exception", () => {
|
||||
assert.match(CONTENT_SECURITY_POLICY, /default-src 'self'/);
|
||||
assert.match(CONTENT_SECURITY_POLICY, /object-src 'none'/);
|
||||
assert.match(CONTENT_SECURITY_POLICY, /frame-ancestors 'none'/);
|
||||
assert.match(CONTENT_SECURITY_POLICY, /form-action 'self'/);
|
||||
assert.match(CONTENT_SECURITY_POLICY, /script-src 'self' 'unsafe-inline'/);
|
||||
assert.doesNotMatch(CONTENT_SECURITY_POLICY, /unsafe-eval/);
|
||||
assert.doesNotMatch(CONTENT_SECURITY_POLICY, /script-src[^;]*(?:\*|https:)/);
|
||||
assert.match(PERMISSIONS_POLICY, /camera=\(\)/);
|
||||
assert.match(PERMISSIONS_POLICY, /microphone=\(\)/);
|
||||
assert.match(PERMISSIONS_POLICY, /payment=\(\)/);
|
||||
});
|
||||
|
||||
test("never advertises HSTS on a plaintext development origin", () => {
|
||||
const response = withSecurityHeaders(
|
||||
new Request("http://127.0.0.1:3000/"),
|
||||
new Response(null, {
|
||||
headers: { "Strict-Transport-Security": "max-age=999999" },
|
||||
}),
|
||||
);
|
||||
|
||||
assert.equal(response.headers.get("Strict-Transport-Security"), null);
|
||||
assert.equal(response.headers.get("X-Frame-Options"), "DENY");
|
||||
});
|
||||
|
||||
test("prevents browsers and intermediaries from caching account and API data", () => {
|
||||
for (const path of ["/api/account/export", "/dashboard", "/dashboard/hosts", "/admin"]) {
|
||||
const response = withSecurityHeaders(
|
||||
new Request(`https://cloud.example.test${path}`),
|
||||
new Response("private", { headers: { "Cache-Control": "public, max-age=3600" } }),
|
||||
);
|
||||
assert.equal(response.headers.get("Cache-Control"), "private, no-store");
|
||||
}
|
||||
|
||||
const publicResponse = withSecurityHeaders(
|
||||
new Request("https://cloud.example.test/trust"),
|
||||
new Response("public", { headers: { "Cache-Control": "public, max-age=300" } }),
|
||||
);
|
||||
assert.equal(publicResponse.headers.get("Cache-Control"), "public, max-age=300");
|
||||
});
|
||||
|
||||
test("wraps both application and image responses at the Worker exit", async () => {
|
||||
const source = await readFile(new URL("../worker/index.ts", import.meta.url), "utf8");
|
||||
assert.match(source, /return withSecurityHeaders\(request, response\);[\s\S]*handler\.fetch/);
|
||||
assert.match(source, /handler\.fetch[\s\S]*return withSecurityHeaders\(request, response\);/);
|
||||
});
|
||||
Reference in New Issue
Block a user