feat(sync): 对账清单 ad 实体当前态清扫
Task 8(ads-carousel):cn reconcile 增 entityType='ad' 分支——纯 SELECT 当前态映射(sortOrder/createdAt 确定性排序,albumId 恒空串信封约定), 载荷复用 ads/ad-payload.ts 单一出处(与业务事务内即时事件逐字节同形, 纯函数模块无依赖环);DTO SYNC_ENTITY_TYPES 追加 'ad'(对齐契约 enum)。 随附质量审查两项: - ads.service list() 行→AdminAdItem 映射复用 toItem(删 ~14 行内联重复, ⑮ 补 admin list 200 形状断言覆盖重构路径); - 新增 test/ads-extensions.spec.ts 守护 AD_IMAGE_EXTENSIONS === UPLOAD_CONTENT_TYPES 派生归一扩展名集合(jpeg→jpg),防三处白名单漂移。 测试基建:reset.ts RESET_TABLES 补 'ads'(Task 6 建表遗漏,固定 id 直种 依赖跨 spec 清场);reconcile.e2e-spec 追加 ⑤(红→绿,5/5)。 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
// services/api/test/ads-extensions.spec.ts —— 广告图扩展名白名单一致性守护
|
||||
// (ads-carousel Task 7 质量审查遗留项,随 Task 8 提交)。三处白名单必须恒等:
|
||||
// · uploads/support.ts UPLOAD_CONTENT_TYPES(契约 contentType 白名单);
|
||||
// · uploads/support.ts EXTENSION_WHITELIST(模块私有:filename→归一扩展名);
|
||||
// · ads/ads.service.ts AD_IMAGE_EXTENSIONS(staging/终键扩展名合法集)。
|
||||
// 漂移场景(守护标的):上传域新增 contentType(如 image/webp)而广告域未跟
|
||||
// 进——init 会放行 .webp staging 键(extOfFilename 走共享白名单),但
|
||||
// verifyStaging 的 AD_IMAGE_EXTENSIONS 核验会拒绝,创建/换图在链路中段断裂。
|
||||
// 纯函数 spec(无 DB/网络):从导出的 UPLOAD_CONTENT_TYPES 派生归一化扩展名
|
||||
// 集合(jpeg→jpg,同 EXTENSION_WHITELIST 归一关系),断言与 AD_IMAGE_EXTENSIONS
|
||||
// 恒等;EXTENSION_WHITELIST 不可导出,经 extOfFilename 行为面覆盖(每个合法
|
||||
// 扩展名归一通过、jpeg 双扩展名归一 jpg、白名单外拒绝)。
|
||||
import { AD_IMAGE_EXTENSIONS } from '../src/ads/ads.service';
|
||||
import { UPLOAD_CONTENT_TYPES, extOfFilename } from '../src/uploads/support';
|
||||
|
||||
/** UPLOAD_CONTENT_TYPES → 归一化扩展名集合(jpeg→jpg;子类型段即扩展名)。 */
|
||||
function derivedExtensions(): Set<string> {
|
||||
return new Set(
|
||||
UPLOAD_CONTENT_TYPES.map((contentType) => {
|
||||
const sub = contentType.split('/')[1] ?? '';
|
||||
return sub === 'jpeg' ? 'jpg' : sub;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
describe('AD_IMAGE_EXTENSIONS 与上传域白名单一致性(守护断言)', () => {
|
||||
it('AD_IMAGE_EXTENSIONS === 从 UPLOAD_CONTENT_TYPES 派生的归一化扩展名集合', () => {
|
||||
expect(AD_IMAGE_EXTENSIONS).toEqual(derivedExtensions());
|
||||
});
|
||||
|
||||
it('每个合法扩展名经 extOfFilename 归一通过(staging 键后缀恒落在合法集内)', () => {
|
||||
for (const ext of derivedExtensions()) {
|
||||
expect(extOfFilename(`banner.${ext}`)).toBe(ext);
|
||||
}
|
||||
// jpeg 双扩展名归一 jpg(EXTENSION_WHITELIST 映射,大小写不敏感)。
|
||||
expect(extOfFilename('banner.JPEG')).toBe('jpg');
|
||||
// 白名单外/无扩展名 → null(init 与 verifyStaging 均拒绝)。
|
||||
expect(extOfFilename('banner.gif')).toBeNull();
|
||||
expect(extOfFilename('banner')).toBeNull();
|
||||
});
|
||||
});
|
||||
@@ -835,6 +835,35 @@ describe('ads domain (e2e)', () => {
|
||||
imageContentType: 'image/jpeg',
|
||||
};
|
||||
|
||||
// admin list 200(list() 复用 toItem 的映射出口,⑨-⑭ 之后存留 ⑩ 的三行:
|
||||
// targeted/emptyArr/nullIds,均 sortOrder=0、createdAt 升序):形状精确到
|
||||
// 契约 AdminAdItem 全字段键集,albumIds null/数组双形态经 toItem 归一。
|
||||
const adminList = await request(cnBase)
|
||||
.get('/v1/admin/ads')
|
||||
.set('Authorization', `Bearer ${adminToken}`);
|
||||
expect(adminList.status).toBe(200);
|
||||
expect(adminList.body.items).toHaveLength(3);
|
||||
expect(adminList.body.items[0]).toMatchObject({
|
||||
imageObjectKey: expect.stringMatching(
|
||||
/^photo-live\/dev\/ads\/c[0-9a-z]+\/v1\.(jpg|png)$/,
|
||||
),
|
||||
linkUrl: 'https://sponsor.example/landing',
|
||||
enabled: true,
|
||||
imageUrl: expect.any(String),
|
||||
});
|
||||
expect(Object.keys(adminList.body.items[0]).sort()).toEqual([
|
||||
'albumIds',
|
||||
'createdAt',
|
||||
'enabled',
|
||||
'id',
|
||||
'imageObjectKey',
|
||||
'imageUrl',
|
||||
'linkUrl',
|
||||
'sortOrder',
|
||||
'title',
|
||||
'updatedAt',
|
||||
]);
|
||||
|
||||
const staffList = await request(cnBase)
|
||||
.get('/v1/admin/ads')
|
||||
.set('Authorization', `Bearer ${staffToken}`);
|
||||
|
||||
@@ -24,6 +24,9 @@ export const RESET_TABLES: readonly string[] = [
|
||||
// 照片与上传域 + outbox(Task 10):均为扁平字符串外键(无 FK 约束),
|
||||
// 顺序按逻辑依赖排列(事件/作业/变体/会话先于 photos)。
|
||||
'outbox_events',
|
||||
// 广告位域(ads-carousel Task 6):扁平表无 FK;e2e 直种固定 id 行 +
|
||||
// reconcile 清单计数断言依赖跨 spec 清场(此前 cuid id 未见残留冲突)。
|
||||
'ads',
|
||||
// 展示位域(P4b Task 2):授权成员表先于展示位(建而未用,P5 激活)。
|
||||
'embed_slot_members',
|
||||
'embed_slots',
|
||||
|
||||
Reference in New Issue
Block a user