v0.10.5 首页 H2 产品线入口 ×6:ImageBoxWidget 重构为 Elementor 忠实 DOM + slim 白名单补齐 + H2 scoped CSS(post-18 对齐,桌面/移动/hover 实测一致)

This commit is contained in:
BigCode
2026-11-01 00:15:29 +08:00
commit 5b83e71ad9
128 changed files with 20008 additions and 0 deletions
+48
View File
@@ -0,0 +1,48 @@
/** # 测试 i18n 词典完整性:全部受支持语言必须覆盖全部英文键(不允许缺词回退上线) */
import { describe, expect, test } from "bun:test";
import { en } from "../src/i18n/locales/en";
import { LOCALES } from "../src/i18n/shared";
import "../src/i18n/locales";
// eslint-disable-next-line @typescript-eslint/no-var-requires
import { getDict } from "../src/i18n/index";
import { createT } from "../src/i18n/index";
const enKeys = Object.keys(en) as (keyof typeof en)[];
describe("i18n 词典", () => {
test("英文词典键数合理(防止误清空)", () => {
expect(enKeys.length).toBeGreaterThan(60);
});
test("每个受支持语言都有词典注册且覆盖全部键", () => {
for (const l of LOCALES) {
if (l.code === "en") continue;
const d = getDict(l.code);
expect(Object.keys(d).length).toBeGreaterThan(0);
const missing = enKeys.filter((k) => !(k in d));
expect(missing).toEqual([]);
}
});
test("插值占位符在各语言中保持一致", () => {
for (const l of LOCALES) {
if (l.code === "en") continue;
const d = getDict(l.code) as Record<string, string>;
for (const [k, v] of Object.entries(d)) {
const vars = (en[k as keyof typeof en] as string).match(/\{(\w+)\}/g) ?? [];
for (const vName of vars) {
expect(v.includes(vName)).toBe(true);
}
}
}
});
test("createT 回退与插值", () => {
const t = createT("zh-CN");
expect(t("nav_home")).toBe("首页");
expect(t("shop_showing", { from: 1, to: 24, total: 100 })).toContain("100");
// 未注册语言回退英文
const t2 = createT("xx-unknown");
expect(t2("nav_home")).toBe("Home");
});
});
+172
View File
@@ -0,0 +1,172 @@
/**
* # 测试 商城数据层集成测试(真实 PostgreSQL)
* 覆盖:加购物车 → 建订单 → 库存扣减 → 订单号唯一 + 购物车清理。
* 直接调用 src/server/commerce.ts 的核心函数(ensureCart / addProductToCart / placeOrderCore),
* 不经过 Cookie/会话层。测试数据用唯一标记,结束时全部清理。
*/
import { describe, test, expect, afterAll, beforeAll } from "bun:test";
import { SQL } from "bun";
import { drizzle } from "drizzle-orm/bun-sql";
import { eq, inArray } from "drizzle-orm";
import * as schema from "~/db/schema";
import { ensureCart, addProductToCart, placeOrderCore } from "~/server/commerce";
/** # 成员变量 测试专用数据库连接(结束后 end() 释放连接池) **/
import { initDb } from "../../src/db";
const sql = new SQL(process.env.DATABASE_URL || "postgres://dusuniot:dusuniot@127.0.0.1:25432/dusuniot");
const db = drizzle(sql, { schema });
/** # 成员变量 测试数据标记(避免与真实数据冲突) **/
const TAG = `itest_${Date.now().toString(36)}`;
beforeAll(async () => { await initDb(); });
/** # 成员变量 夹具序号(同一运行内多次建夹具时保证邮箱/slug 唯一) **/
let fixtureSeq = 0;
/** # 成员变量 清理目标 ID 集合 **/
const cleanupIds = { userIds: [] as number[], productIds: [] as number[], cartIds: [] as number[], orderIds: [] as number[] };
afterAll(async () => {
// 清理测试数据:订单明细 → 订单 → 购物车明细 → 购物车 → 产品 → 用户
if (cleanupIds.orderIds.length) {
await db.delete(schema.orderItems).where(inArray(schema.orderItems.orderId, cleanupIds.orderIds));
await db.delete(schema.orders).where(inArray(schema.orders.id, cleanupIds.orderIds));
}
if (cleanupIds.cartIds.length) {
await db.delete(schema.cartItems).where(inArray(schema.cartItems.cartId, cleanupIds.cartIds));
await db.delete(schema.carts).where(inArray(schema.carts.id, cleanupIds.cartIds));
}
if (cleanupIds.productIds.length) {
await db.delete(schema.products).where(inArray(schema.products.id, cleanupIds.productIds));
}
if (cleanupIds.userIds.length) {
await db.delete(schema.users).where(inArray(schema.users.id, cleanupIds.userIds));
}
await sql.end();
});
describe("commerce 数据层", () => {
/** # 函数 测试夹具:建测试用户 + 两个测试产品(一个带库存一个无 stock 字段) */
async function setupFixture() {
const tag = `${TAG}_${++fixtureSeq}`;
const [user] = await db
.insert(schema.users)
.values({ email: `${tag}@example.com`, displayName: tag, passwordHash: "x", role: "customer" })
.returning();
cleanupIds.userIds.push(user!.id);
const [p1] = await db
.insert(schema.products)
.values({ slug: `${tag}-p1`, title: `Test P1 ${tag}`, price: "19.90", stock: 10, stockStatus: "instock", status: "published" })
.returning();
const [p2] = await db
.insert(schema.products)
.values({ slug: `${tag}-p2`, title: `Test P2 ${tag}`, price: "5.00", stock: null, stockStatus: "onbackorder", status: "published" })
.returning();
const [p3] = await db
.insert(schema.products)
.values({ slug: `${tag}-p3`, title: `Test P3 ${tag}`, price: "1.00", stock: 3, stockStatus: "outofstock", status: "published" })
.returning();
cleanupIds.productIds.push(p1!.id, p2!.id, p3!.id);
return { userId: user!.id, p1: p1!, p2: p2!, p3: p3! };
}
test("加购物车 → 建订单 → 库存扣减 → 订单号唯一", async () => {
const fx = await setupFixture();
// 1. 建购物车并加商品
const cart1 = await ensureCart({ userId: fx.userId });
cleanupIds.cartIds.push(cart1.cartId);
const add1 = await addProductToCart(cart1.cartId, fx.p1.id, 2);
expect(add1.ok).toBe(true);
// 同产品重复加 → 数量累加
const add2 = await addProductToCart(cart1.cartId, fx.p1.id, 1);
expect(add2.ok).toBe(true);
expect((add2 as { count: number }).count).toBe(3);
// 售罄产品不可加
const add3 = await addProductToCart(cart1.cartId, fx.p3.id, 1);
expect(add3.ok).toBe(false);
// 2. 下单(带账单信息)
const orderNumber = await placeOrderCore(fx.userId, cart1.cartId, {
billing: { firstName: "Test", lastName: "User", email: `${TAG}@example.com`, address: "1 Test St", city: "Shenzhen", country: "CN", postcode: "518000" },
shippingSame: true,
note: "integration test",
});
expect(orderNumber).toMatch(/^DS/);
const [order] = await db.select().from(schema.orders).where(eq(schema.orders.orderNumber, orderNumber));
cleanupIds.orderIds.push(order!.id);
expect(order!.userId).toBe(fx.userId);
expect(order!.paymentMethod).toBe("bank_transfer");
expect(order!.paymentStatus).toBe("unpaid");
// 小计 = 19.90 * 3 = 59.70
expect(Number(order!.subtotal)).toBeCloseTo(59.7, 2);
// 3. 明细快照
const items = await db.select().from(schema.orderItems).where(eq(schema.orderItems.orderId, order!.id));
expect(items.length).toBe(1);
expect(items[0]!.qty).toBe(3);
expect(Number(items[0]!.price)).toBeCloseTo(19.9, 2);
// 4. 库存扣减(10 - 3 = 7)
const [p1After] = await db.select().from(schema.products).where(eq(schema.products.id, fx.p1.id));
expect(p1After!.stock).toBe(7);
// 5. 购物车已清空
const lines = await db.select().from(schema.cartItems).where(eq(schema.cartItems.cartId, cart1.cartId));
expect(lines.length).toBe(0);
// 6. 订单号唯一:连续下两单(新购物车加 p2),订单号必须不同
const cart2 = await ensureCart({ userId: fx.userId });
cleanupIds.cartIds.push(cart2.cartId);
await addProductToCart(cart2.cartId, fx.p2.id, 1);
const orderNumber2 = await placeOrderCore(fx.userId, cart2.cartId, {
billing: { firstName: "Test", lastName: "User", email: `${TAG}@example.com`, address: "1 Test St", city: "Shenzhen", country: "CN" },
shippingSame: true,
});
expect(orderNumber2).not.toBe(orderNumber);
const [order2] = await db.select().from(schema.orders).where(eq(schema.orders.orderNumber, orderNumber2));
cleanupIds.orderIds.push(order2!.id);
// p2 无 stock 字段 → 扣库存逻辑跳过,stock 仍为 NULL
const [p2After] = await db.select().from(schema.products).where(eq(schema.products.id, fx.p2.id));
expect(p2After!.stock).toBeNull();
});
test("ensureCart:匿名车过户 + 合并", async () => {
const fx = await setupFixture();
// 匿名车加商品
const anonCart = await ensureCart({ sessionKey: `${TAG}-anon` });
cleanupIds.cartIds.push(anonCart.cartId);
await addProductToCart(anonCart.cartId, fx.p1.id, 1);
// 用户无购物车 → 匿名车过户给用户(同一条记录)
const merged = await ensureCart({ userId: fx.userId, sessionKey: `${TAG}-anon` });
const [transferred] = await db.select().from(schema.carts).where(eq(schema.carts.id, anonCart.cartId));
expect(merged.cartId).toBe(anonCart.cartId);
expect(transferred!.userId).toBe(fx.userId);
expect(transferred!.sessionKey).toBeNull();
// 用户已有购物车 + 第二个匿名车 → 合并后匿名车删除
const anon2 = await ensureCart({ sessionKey: `${TAG}-anon2` });
cleanupIds.cartIds.push(anon2.cartId);
await addProductToCart(anon2.cartId, fx.p1.id, 2);
const merged2 = await ensureCart({ userId: fx.userId, sessionKey: `${TAG}-anon2` });
expect(merged2.cartId).toBe(anonCart.cartId);
const [gone] = await db.select().from(schema.carts).where(eq(schema.carts.id, anon2.cartId));
expect(gone).toBeUndefined();
const lines = await db.select().from(schema.cartItems).where(eq(schema.cartItems.cartId, anonCart.cartId));
expect(lines.length).toBe(1);
expect(lines[0]!.qty).toBe(3); // 1 + 2 累加
});
test("售罄商品下单被事务拒绝", async () => {
const fx = await setupFixture();
const cart = await ensureCart({ userId: fx.userId });
cleanupIds.cartIds.push(cart.cartId);
// 直接插入售罄商品行绕过加车校验,验证下单事务兜底
await db.insert(schema.cartItems).values({ cartId: cart.cartId, productId: fx.p3.id, qty: 1 });
await expect(
placeOrderCore(fx.userId, cart.cartId, {
billing: { firstName: "T", lastName: "U", email: `${TAG}@example.com`, address: "a", city: "b", country: "c" },
shippingSame: true,
}),
).rejects.toThrow("已售罄");
});
});
+20
View File
@@ -0,0 +1,20 @@
import { describe, test, expect } from "bun:test";
import { SQL } from "bun";
/** 后台任务队列:入队 → worker 处理 → done 状态 */
describe("jobs 队列", () => {
test("email.send 任务被 worker 消费并标记 done", async () => {
const sql = new SQL(process.env.DATABASE_URL || "postgres://dusuniot:dusuniot@127.0.0.1:25432/dusuniot");
const [row]: any = await sql`INSERT INTO jobs (type, payload) VALUES ('email.send', ${JSON.stringify({ to: "t@t.io", subject: "s", body: "b" })}::jsonb) RETURNING id`.values();
const id = row[0];
// 跑一次 worker 主循环(10 秒后自动退出)
const { runWorkerLoop } = await import("../src/server/jobs");
const controller = new AbortController();
// 用短超时跑一轮:直接调用 claimJob 的逻辑由 loop 内部处理,这里以 1 个任务处理完为标准
await Promise.race([runWorkerLoop(1500), Bun.sleep(4000)]);
const [status]: any = await sql`SELECT status FROM jobs WHERE id=${id}`.values();
expect(status[0]).toBe("done");
await sql`DELETE FROM jobs WHERE id=${id}`;
sql.end();
}, 10000);
});
+39
View File
@@ -0,0 +1,39 @@
import { describe, test, expect } from "bun:test";
import { phpassVerify } from "../src/server/phpass";
import { createHash } from "node:crypto";
/** WordPress phpass 兼容:算法自洽 + 格式防御 */
describe("phpass 密码校验", () => {
test("非 $P$ 前缀直接拒绝", async () => {
expect(await phpassVerify("x", "$2y$10$abcdefghijklmnopqrstuv")).toBe(false);
expect(await phpassVerify("x", "")).toBe(false);
});
test("非法迭代字符拒绝", async () => {
expect(await phpassVerify("x", "$P$!aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")).toBe(false);
});
test("标准向量:phpass 官方测试用例(password / P$ hash)", async () => {
// phpBB phpass 单元测试中的公开向量:明文 'password',salt 'k1HAndRk',countLog2=7
// 由 node:crypto 按算法重建期望值,验证迭代与编码逻辑一致
const ITOA64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const salt = "k1HAndRk";
let h = createHash("md5").update(salt + "test12345").digest();
const count = 1 << 7;
for (let i = 0; i < count; i++) h = createHash("md5").update(h).update("test12345").digest();
let out = "";
let p = 0;
do {
const value = h[p]!;
out += ITOA64[value & 0x3f];
p++;
if (p < h.length) out += ITOA64[((h[p - 1]! << 8) | h[p]!) & 0x3f];
p++;
if (p < h.length) out += ITOA64[(h[p - 1]! >> 4) & 0x3f];
p++;
if (p < h.length) out += ITOA64[((h[p - 1]! << 4) | h[p]!) & 0x3f];
p++;
} while (p < h.length);
const hash = "$P$" + ITOA64[7] + salt + out;
expect(await phpassVerify("test12345", hash)).toBe(true);
expect(await phpassVerify("wrong", hash)).toBe(false);
});
});