Files
Jyotisha/frontend/tests/guards-and-hash.test.ts
T
jesse-uxandGrok (agent loop) d2296c2654 把前后端共用的请求契约和小工具收成一份,避免再各抄一份。
报告创建请求、校正入口和同名类型改从 contracts 推导;同义的对象判断与 sha256 改走共享函数。调用处用 import 别名保留原名,使 diff 低于 1500 行。行为、哈希和状态字符串不变。

Co-Authored-By: Grok (agent loop)
2026-10-15 22:22:44 +08:00

104 lines
4.1 KiB
TypeScript

import assert from "node:assert/strict";
import { createHash } from "node:crypto";
import { readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
import type { PersonalReportCreateRequestInput } from "../src/lib/contracts/personal-report-request.ts";
import { asRecord, finiteNumberOrNull, isRecord, recordOrEmpty } from "../src/lib/guards.ts";
import { sha256Hex } from "../src/lib/hash.ts";
import { hashLongformMarkdown } from "../src/lib/personal-report-longform-appendix.ts";
import { hashRedeemCode } from "../src/lib/supabase/codes.ts";
const samples = [null, undefined, 0, "", [], {}, new Date()] as const;
function legacyIsRecord(value: unknown): boolean {
return typeof value === "object" && value !== null && !Array.isArray(value);
}
function legacyIsRecordBoolean(value: unknown): boolean {
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
}
function legacyAsRecordTruthy(value: unknown): Record<string, unknown> | null {
return value && typeof value === "object" && !Array.isArray(value)
? value as Record<string, unknown>
: null;
}
function legacyAsRecordNull(value: unknown): Record<string, unknown> | null {
return value !== null && typeof value === "object" && !Array.isArray(value)
? value as Record<string, unknown>
: null;
}
function legacyRecordOrEmptyTruthy(value: unknown): Record<string, unknown> {
return value && typeof value === "object" && !Array.isArray(value)
? value as Record<string, unknown>
: {};
}
function legacyRecordOrEmptyNull(value: unknown): Record<string, unknown> {
return value !== null && typeof value === "object" && !Array.isArray(value)
? value as Record<string, unknown>
: {};
}
function legacyFiniteNumber(value: unknown): number | null {
return typeof value === "number" && Number.isFinite(value) ? value : null;
}
test("guards match the replaced non-array helpers on the shared sample table", () => {
for (const sample of samples) {
assert.equal(isRecord(sample), legacyIsRecord(sample));
assert.equal(isRecord(sample), legacyIsRecordBoolean(sample));
assert.equal(asRecord(sample), legacyAsRecordTruthy(sample));
assert.equal(asRecord(sample), legacyAsRecordNull(sample));
const emptyTruthy = legacyRecordOrEmptyTruthy(sample);
const emptyNull = legacyRecordOrEmptyNull(sample);
const empty = recordOrEmpty(sample);
if (isRecord(sample)) {
assert.equal(empty, sample);
assert.equal(emptyTruthy, sample);
assert.equal(emptyNull, sample);
} else {
assert.deepEqual(empty, {});
assert.deepEqual(emptyTruthy, {});
assert.deepEqual(emptyNull, {});
}
assert.equal(finiteNumberOrNull(sample), legacyFiniteNumber(sample));
}
assert.equal(finiteNumberOrNull(0), 0);
assert.equal(isRecord([]), false);
assert.equal(asRecord([]), null);
assert.deepEqual(recordOrEmpty([]), {});
});
test("sha256 helpers match createHash for ascii, chinese, and emoji", () => {
for (const sample of ["ascii", "中文", "😀"]) {
const direct = createHash("sha256").update(sample).digest("hex");
assert.equal(sha256Hex(sample), direct);
assert.equal(hashLongformMarkdown(sample), direct);
assert.equal(hashRedeemCode(sample), direct);
}
});
test("contracts and guards do not import server modules", () => {
const forbidden = /node:|server-only|@\/mastra|@\/lib\/supabase|@\/lib\/db/;
const root = fileURLToPath(new URL("../src/lib/", import.meta.url));
const files = [
join(root, "guards.ts"),
...readdirSync(join(root, "contracts")).map((name) => join(root, "contracts", name)),
];
for (const file of files) {
assert.equal(forbidden.test(readFileSync(file, "utf8")), false, file);
}
});
const okReportThemes: PersonalReportCreateRequestInput["themes"] = ["career"];
void okReportThemes;
// @ts-expect-error bogus is not a report request theme
const bogusReportThemes: PersonalReportCreateRequestInput["themes"] = ["bogus"];
void bogusReportThemes;