Files
nekonest-cloud/tests/invitations.test.mjs

194 lines
6.0 KiB
JavaScript

import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { DatabaseSync } from "node:sqlite";
import test from "node:test";
import {
AUDIT_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, /不创建报价、订单、付款单、积分、钱包、余额/);
});