Files
photo-live/services/api/test/ads-extensions.spec.ts
T
浅念 bfba76b5e6 fix(ads): 终审修复——staging 前缀+运行时上限核验/失效绑定编辑/白名单去 heic/杂项
- verifyStaging 硬化(services/api/src/ads/ads.service.ts):新增键前缀核验
  (仅 OBJECT_KEY_ROOT/ads/staging/ 布局,防照片域任意键被提升为公开广告)
  与运行时 2MB 上限(head.bytes > AD_UPLOAD_MAX_BYTES → 400 报文含限额值
  ——init 申明管不住实际 PUT 体,create/update 是唯一核验点);e2e ⑰ 以
  测试 storage 直接 presignPut 真实对象覆盖两分支
- 失效绑定编辑 400(admin AdsFormDialog):PATCH 仅在 albumIds 实际变化时
  携带(albumIdsChanged 序敏感深比较,纯函数落 adsLogic + vitest 用例);
  preloadOptions 解析失败的绑定 ID 加「已失效,可移除」后缀(zh/en 新增
  adsManage.form.staleAlbum)——软删绑定不再挡住其它字段保存
- 白名单去 heic(原图直出无变体兜底,Chromium/Firefox 不渲染=破图):
  contracts 三处 enum 收窄 [image/jpeg, image/png] 并重生成类型;dto 定义
  广告域独立 AD_IMAGE_CONTENT_TYPES(与照片域 UPLOAD_CONTENT_TYPES 分离),
  AD_IMAGE_EXTENSIONS 由其派生;init 对 .heic 文件名以广告域集合二次核验
  拒绝(共享 extOfFilename 仍认 heic,杜绝 init 放行→create 中段断裂);
  e2e heic 矩阵 400;admin accept/文案/类型同步收窄
- searchAlbums 补 catch(http 层已弹 toast,本地静默保持旧选项)
- ActivityPage 关闭记忆测试补位(activity.spec):dismissed 不拉取不渲染、
  close 写 sessionStorage 并清空轮播两用例(getPublicAds 入替身防真实 fetch)

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-09-29 02:20:21 +08:00

52 lines
2.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// services/api/test/ads-extensions.spec.ts —— 广告图扩展名白名单守护
// (ads-carousel Task 7 质量审查遗留项,随 Task 8 提交;终审 Fix 3 改为
// 广告域自洽守护)。守护标的:
// · AD_IMAGE_EXTENSIONS(ads.service,staging/终键扩展名合法集)必须恒等
// 于从 AD_IMAGE_CONTENT_TYPES(ads/dto,契约 contentType 白名单)派生
// 的归一扩展名集合(jpeg→jpg)——contentType 与键扩展名两端口永不漂移;
// · heic 剔除是永久约束:广告无变体流水线、原图直出,Chromium/Firefox
// 桌面浏览器不渲染 heic(=破图)。
// 不再对照片域 UPLOAD_CONTENT_TYPES/EXTENSION_WHITELIST 做一致性断言——
// 两域白名单自此分离(照片直传保留 heic,变体流水线兜底解码)。共享的
// uploads/support.extOfFilename 仍放行 .heic 文件名(照片域依赖),广告
// init 以 AD_IMAGE_EXTENSIONS 二次核验拒绝(见 ads.service.initUpload)。
// 纯函数 spec(无 DB/网络)。
import { AD_IMAGE_EXTENSIONS } from '../src/ads/ads.service';
import { AD_IMAGE_CONTENT_TYPES } from '../src/ads/dto';
import { extOfFilename } from '../src/uploads/support';
/** AD_IMAGE_CONTENT_TYPES → 归一化扩展名集合(jpeg→jpg;子类型段即扩展名)。 */
function derivedExtensions(): Set<string> {
return new Set(
AD_IMAGE_CONTENT_TYPES.map((contentType) => {
const sub = contentType.split('/')[1] ?? '';
return sub === 'jpeg' ? 'jpg' : sub;
}),
);
}
describe('AD_IMAGE_EXTENSIONS ↔ AD_IMAGE_CONTENT_TYPES 一致性(守护断言)', () => {
it('AD_IMAGE_EXTENSIONS === 从 AD_IMAGE_CONTENT_TYPES 派生的归一化扩展名集合', () => {
expect(AD_IMAGE_EXTENSIONS).toEqual(derivedExtensions());
});
it('heic 已永久剔除:contentType 集与扩展名集均不含 heic', () => {
expect(AD_IMAGE_CONTENT_TYPES).not.toContain('image/heic');
expect(AD_IMAGE_EXTENSIONS.has('heic')).toBe(false);
});
it('每个合法扩展名经 extOfFilename 归一通过(staging 键后缀恒落在合法集内)', () => {
for (const ext of derivedExtensions()) {
expect(extOfFilename(`banner.${ext}`)).toBe(ext);
}
// jpeg 双扩展名归一 jpg(共享 EXTENSION_WHITELIST 映射,大小写不敏感)。
expect(extOfFilename('banner.JPEG')).toBe('jpg');
// 共享 extOfFilename 仍放行 .heic(照片域白名单,两域分离后各自演进);
// 广告 init 的拒绝点在服务层 AD_IMAGE_EXTENSIONS 二次核验,非此处。
expect(extOfFilename('banner.heic')).toBe('heic');
// 白名单外/无扩展名 → null(init 与 verifyStaging 均拒绝)。
expect(extOfFilename('banner.gif')).toBeNull();
expect(extOfFilename('banner')).toBeNull();
});
});