Files
nekonest-cloud/tests/pairing-progress.test.mjs

163 lines
5.5 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 {
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, /自动确认认领、过期、锁定或取消/);
});