v0.10.5 首页 H2 产品线入口 ×6:ImageBoxWidget 重构为 Elementor 忠实 DOM + slim 白名单补齐 + H2 scoped CSS(post-18 对齐,桌面/移动/hover 实测一致)
This commit is contained in:
@@ -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("已售罄");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user