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
+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("已售罄");
});
});