feat(worker): watermark versioning with atomic history regeneration
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
// packages/contracts/src/__tests__/watermark.test.ts —— 水印配置共享形状测试
|
||||
// (P4a Task 3):normalize 填齐缺省不改已给值;parse 对脏数据的防御回退;
|
||||
// buildAlbumUpdatedPayloadCore 的水印副本位(watermarkConfigVersion = 有效版本)
|
||||
// 与翻译排序确定性。这三者是 api/worker 双侧共用的唯一权威,漂移即事故。
|
||||
import {
|
||||
normalizeWatermarkConfig,
|
||||
parseStoredWatermarkConfig,
|
||||
WATERMARK_DEFAULT_POSITION,
|
||||
WATERMARK_DEFAULT_OPACITY,
|
||||
WATERMARK_DEFAULT_SCALE_PERCENT,
|
||||
WATERMARK_DEFAULT_FONT_SIZE,
|
||||
WATERMARK_POSITIONS,
|
||||
buildAlbumUpdatedPayloadCore,
|
||||
type AlbumPayloadRow,
|
||||
} from '../index.js';
|
||||
|
||||
describe('normalizeWatermarkConfig', () => {
|
||||
it('填齐全部缺省(与 openapi WatermarkConfig 可选字段一一对应)', () => {
|
||||
expect(normalizeWatermarkConfig({ enabled: true, text: { content: 'x' } })).toEqual({
|
||||
enabled: true,
|
||||
position: WATERMARK_DEFAULT_POSITION,
|
||||
opacity: WATERMARK_DEFAULT_OPACITY,
|
||||
scalePercent: WATERMARK_DEFAULT_SCALE_PERCENT,
|
||||
text: { content: 'x', fontSize: WATERMARK_DEFAULT_FONT_SIZE },
|
||||
});
|
||||
});
|
||||
|
||||
it('不改写已给值;enabled=false 允许无文字(关闭语义仅回显)', () => {
|
||||
expect(
|
||||
normalizeWatermarkConfig({
|
||||
enabled: false,
|
||||
position: 'topLeft',
|
||||
opacity: 80,
|
||||
scalePercent: 30,
|
||||
text: { content: '', fontSize: 40 },
|
||||
}),
|
||||
).toEqual({
|
||||
enabled: false,
|
||||
position: 'topLeft',
|
||||
opacity: 80,
|
||||
scalePercent: 30,
|
||||
text: { content: '', fontSize: 40 },
|
||||
});
|
||||
expect(normalizeWatermarkConfig({ enabled: false })).toMatchObject({
|
||||
text: { content: '' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseStoredWatermarkConfig', () => {
|
||||
it('round-trip:normalize 后的值原样读回', () => {
|
||||
const stored = normalizeWatermarkConfig({
|
||||
enabled: true,
|
||||
position: 'center',
|
||||
opacity: 10,
|
||||
scalePercent: 50,
|
||||
text: { content: '水印', fontSize: 72 },
|
||||
});
|
||||
expect(parseStoredWatermarkConfig(stored)).toEqual(stored);
|
||||
});
|
||||
|
||||
it('脏数据防御:非对象 / enabled 非 boolean → null(「配置不可用」)', () => {
|
||||
expect(parseStoredWatermarkConfig(null)).toBeNull();
|
||||
expect(parseStoredWatermarkConfig('enabled')).toBeNull();
|
||||
expect(parseStoredWatermarkConfig({ position: 'center' })).toBeNull();
|
||||
expect(parseStoredWatermarkConfig({ enabled: 'yes' })).toBeNull();
|
||||
});
|
||||
|
||||
it('缺字段回落缺省;非法 position 回落缺省(不抛错)', () => {
|
||||
expect(parseStoredWatermarkConfig({ enabled: true })).toEqual(
|
||||
normalizeWatermarkConfig({ enabled: true }),
|
||||
);
|
||||
const badPosition = parseStoredWatermarkConfig({
|
||||
enabled: true,
|
||||
position: 'middle',
|
||||
text: { content: 'x' },
|
||||
});
|
||||
expect(badPosition?.position).toBe(WATERMARK_DEFAULT_POSITION);
|
||||
expect(WATERMARK_POSITIONS).not.toContain('middle');
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildAlbumUpdatedPayloadCore', () => {
|
||||
const album: AlbumPayloadRow = {
|
||||
id: 'album-1',
|
||||
publicToken: 'pt',
|
||||
status: 'active',
|
||||
uploadEnabled: true,
|
||||
publicEnabled: true,
|
||||
downloadEnabled: false,
|
||||
originalDownloadEnabled: false,
|
||||
watermarkEnabled: true,
|
||||
watermarkEffectiveVersion: 3,
|
||||
defaultLocale: 'zh-CN',
|
||||
timezone: 'Asia/Shanghai',
|
||||
startAt: new Date('2026-11-01T10:00:00Z'),
|
||||
endAt: null,
|
||||
entityVersion: 7,
|
||||
};
|
||||
|
||||
it('水印副本位 = 有效版本(不是最新创建版本);翻译 locale 升序', () => {
|
||||
const payload = buildAlbumUpdatedPayloadCore(album, [
|
||||
{ locale: 'en', title: 'T', description: null, venue: null, address: null },
|
||||
{ locale: 'zh-CN', title: '标题', description: null, venue: null, address: null },
|
||||
]);
|
||||
expect(payload.watermarkEnabled).toBe(true);
|
||||
expect(payload.watermarkConfigVersion).toBe(3);
|
||||
expect(payload.translations.map((t) => t.locale)).toEqual(['en', 'zh-CN']);
|
||||
expect(payload.entityVersion).toBe(7);
|
||||
expect(payload.endAt).toBeNull();
|
||||
});
|
||||
|
||||
it('关闭态:enabled=false 携带版本(ov 回落 browse 键的判定依据)', () => {
|
||||
const payload = buildAlbumUpdatedPayloadCore(
|
||||
{ ...album, watermarkEnabled: false, watermarkEffectiveVersion: 4 },
|
||||
[],
|
||||
);
|
||||
expect(payload.watermarkEnabled).toBe(false);
|
||||
expect(payload.watermarkConfigVersion).toBe(4);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user