报告创建请求、校正入口和同名类型改从 contracts 推导;同义的对象判断与 sha256 改走共享函数。调用处用 import 别名保留原名,使 diff 低于 1500 行。行为、哈希和状态字符串不变。 Co-Authored-By: Grok (agent loop)
104 lines
4.1 KiB
TypeScript
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;
|