| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374 |
- /**
- * ================================================================
- * 商城价格排序 · 接口级契约 E2E · TC-S-U01 ~ TC-S-U04
- * ================================================================
- * 需求来源:docs/需求分析/2026-11-07-商城价格排序需求变更.md(变更 S1)
- * - 后端能力已完备(SortInterceptor 读 body.sort 数组 + SortUtil 白名单注入)
- * - 本次为纯前端改动(排序栏 UI + buildSort() 传参)
- * → 故先锁定「后端排序契约」:前端 buildSort() 的三种取值必须被后端正确消费。
- *
- * 契约映射(对应 buildSort 输出):
- * price_asc → [{column:'price',order:'asc'}]
- * price_desc → [{column:'price',order:'desc'}]
- * default → [](不传,走后端默认 sort_order asc + created_at desc)
- *
- * 只读:仅查询商品列表,不写任何数据。
- * ================================================================
- */
- const { test, expect } = require('@playwright/test');
- const API = process.env.LIVE_API_BASE || 'http://cfc.iwintrue.com';
- async function listProducts(request, body) {
- const resp = await request.post(`${API}/api/product/list`, { data: body, timeout: 30000 });
- expect(resp.status(), '商品列表接口应 200').toBe(200);
- const json = await resp.json();
- expect(json.code, `业务码应为 200,实际 ${json.code} ${json.message || ''}`).toBe(200);
- const data = json.data || {};
- const rows = data.records || data.list || data.rows || (Array.isArray(data) ? data : []);
- return rows;
- }
- function prices(rows) {
- return rows.map((r) => Number(r.price)).filter((n) => !Number.isNaN(n));
- }
- test.describe('商城价格排序 · 后端排序契约(需求变更 S1)', () => {
- test('TC-S-U01 price asc 生效(对应 buildSort → price_asc)', async ({ request }) => {
- const rows = await listProducts(request, { page: 1, size: 8, sort: [{ column: 'price', order: 'asc' }] });
- expect(rows.length, '应返回商品').toBeGreaterThan(1);
- const p = prices(rows);
- const sorted = [...p].sort((a, b) => a - b);
- expect(p, `价格应非降序,实际 ${p.join(',')}`).toEqual(sorted);
- });
- test('TC-S-U02 price desc 生效(对应 buildSort → price_desc)', async ({ request }) => {
- const rows = await listProducts(request, { page: 1, size: 8, sort: [{ column: 'price', order: 'desc' }] });
- expect(rows.length).toBeGreaterThan(1);
- const p = prices(rows);
- const sorted = [...p].sort((a, b) => b - a);
- expect(p, `价格应非升序,实际 ${p.join(',')}`).toEqual(sorted);
- });
- test('TC-S-U03 默认排序不变(对应 buildSort → default,不传 sort)', async ({ request }) => {
- const rows = await listProducts(request, { page: 1, size: 8 });
- expect(rows.length, '默认查询应返回商品').toBeGreaterThan(0);
- // 默认口径:sort_order asc, created_at desc —— 不做值断言,只锁定「结构不回归」
- expect(rows[0]).toHaveProperty('id');
- expect(rows[0]).toHaveProperty('price');
- });
- test('TC-S-U04 非法排序列被白名单拦截(不 5xx、不注入)', async ({ request }) => {
- const resp = await request.post(`${API}/api/product/list`, {
- data: { page: 1, size: 5, sort: [{ column: 'price;drop table product', order: 'asc' }] },
- timeout: 30000,
- });
- expect(resp.status(), '非法列名不应导致 5xx').toBeLessThan(500);
- const json = await resp.json();
- expect([200, 400], `应返回 200(忽略非法列)或 400(显式拒绝),实际 ${json.code}`).toContain(json.code);
- if (json.code === 200) {
- const rows = (json.data && (json.data.records || json.data.list)) || [];
- expect(rows.length, '合法查询仍应返回数据(证明未抛异常)').toBeGreaterThan(0);
- }
- });
- });
|