Files
jiapu/tests/ceremony-pages.test.js
T
fizzleaf fb1743aa2a feat(api): 完善家谱系统API客户端契约
- 实现家谱管理相关方法,包括创建、详情、概览、我的家谱和选项查询
- 添加家谱加入申请功能,支持申请、审核、取消和待审核列表操作
- 集成通知详情获取方法和通知ID安全验证机制
- 完善功德记录、谱文、相册、视频、祭祀活动的完整CRUD操作契约
- 实现家谱成员管理功能,包含成员列表、更新、移除和转让所有者操作
- 优化路径ID验证逻辑,拒绝不安全的数值ID并提供明确错误提示
- 更新测试用例以验证所有新增API方法的路径和请求体白名单机制
2026-07-29 16:57:27 +08:00

139 lines
5.0 KiB
JavaScript

const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const test = require('node:test');
const CeremonyPages = require('../public/js/ceremony-pages.js');
test('我的邀请响应保留完整展示字段并保持业务 ID 为字符串', () => {
assert.deepEqual(CeremonyPages.normalizeInvitation({
invitationId: '2060000000000000001',
genealogyId: '2060000000000000002',
ceremonyId: '2060000000000000003',
inviteeUserId: '2060000000000000004',
inviteStatus: 'PENDING',
inviteVersion: 2,
deliveredTime: '2026-07-29T09:00:00+08:00',
readTime: null,
responseTime: null,
ceremonyTitle: '家族答谢宴',
ceremonyTime: '2026-08-08T18:00:00+08:00',
location: '锦江厅',
locationAddress: '成都市锦江区示例路 8 号',
longitude: 104.0668,
latitude: 30.5728
}), {
invitationId: '2060000000000000001',
genealogyId: '2060000000000000002',
ceremonyId: '2060000000000000003',
inviteeUserId: '2060000000000000004',
inviteStatus: 'PENDING',
inviteVersion: 2,
deliveredTime: '2026-07-29T09:00:00+08:00',
readTime: '',
responseTime: '',
ceremonyTitle: '家族答谢宴',
ceremonyTime: '2026-08-08T18:00:00+08:00',
location: '锦江厅',
locationAddress: '成都市锦江区示例路 8 号',
longitude: 104.0668,
latitude: 30.5728
});
});
test('邀请响应拒绝缺少稳定 ID、非法状态和不安全数字长 ID', () => {
assert.equal(CeremonyPages.normalizeInvitation({
invitationId: '1',
genealogyId: '2',
inviteeUserId: '4',
inviteStatus: 'PENDING'
}), null);
assert.equal(CeremonyPages.normalizeInvitation({
invitationId: '1',
genealogyId: '2',
ceremonyId: '3',
inviteeUserId: '4',
inviteStatus: 'UNKNOWN'
}), null);
assert.equal(CeremonyPages.normalizeInvitation({
invitationId: Number('2060000000000000001'),
genealogyId: '2',
ceremonyId: '3',
inviteeUserId: '4',
inviteStatus: 'PENDING'
}), null);
});
test('YAML 未声明必返的 inviteeUserId 缺失时仍可用活动上下文响应', () => {
const invitation = CeremonyPages.normalizeInvitation({
invitationId: '1',
genealogyId: '2',
ceremonyId: '3',
inviteStatus: 'PENDING'
});
assert.notEqual(invitation, null);
assert.equal(invitation.inviteeUserId, undefined);
assert.equal(CeremonyPages.canRespondToInvitation(invitation), true);
});
test('响应邀请只允许提交 YAML 定义的 inviteStatus 字段', () => {
assert.deepEqual(CeremonyPages.buildInvitationResponseBody({
inviteStatus: 'ACCEPTED',
invitationId: '2060000000000000001',
inviteVersion: 2
}), {
inviteStatus: 'ACCEPTED'
});
assert.deepEqual(CeremonyPages.buildInvitationResponseBody({ inviteStatus: 'DECLINED' }), {
inviteStatus: 'DECLINED'
});
assert.equal(CeremonyPages.validateInvitationResponseBody({ inviteStatus: 'PENDING' }), '邀请只能选择接受或拒绝');
});
test('只有待响应邀请显示接受和拒绝动作', () => {
assert.equal(CeremonyPages.canRespondToInvitation({ inviteStatus: 'PENDING' }), true);
assert.equal(CeremonyPages.canRespondToInvitation({ inviteStatus: 'ACCEPTED' }), false);
assert.equal(CeremonyPages.canRespondToInvitation({ inviteStatus: 'DECLINED' }), false);
assert.equal(CeremonyPages.canRespondToInvitation({ inviteStatus: 'CANCELED' }), false);
});
test('邀请列表渲染转义内容、展示详情但不直接暴露内部 ID 和坐标', () => {
const invitation = CeremonyPages.normalizeInvitation({
invitationId: '2060000000000000001',
genealogyId: '2060000000000000002',
ceremonyId: '2060000000000000003',
inviteeUserId: '2060000000000000004',
inviteStatus: 'PENDING',
inviteVersion: 1,
ceremonyTitle: '<script>alert(1)</script>',
ceremonyTime: '2026-08-08T18:00:00+08:00',
location: '锦江厅',
locationAddress: '成都市锦江区示例路 8 号',
longitude: 104.0668,
latitude: 30.5728
});
const row = CeremonyPages.renderInvitationRow(invitation);
const detail = CeremonyPages.renderInvitationDetail(invitation);
assert.doesNotMatch(row, /<script>/);
assert.match(row, /接受邀请/);
assert.match(row, /拒绝邀请/);
assert.match(detail, /成都市锦江区示例路 8 号/);
assert.match(detail, /打开地图/);
assert.doesNotMatch(detail, /206000000000000000[1-4]/);
assert.doesNotMatch(detail, />104\.0668</);
assert.doesNotMatch(detail, />30\.5728</);
});
test('贺礼邀请页面只接入我的邀请闭环,不提供活动或献礼猜测表单', () => {
const page = fs.readFileSync(path.join(__dirname, '..', 'profile-gift.html'), 'utf8');
assert.doesNotMatch(page, /data-feature-status="pending"/);
assert.match(page, /data-my-invitation-list/);
assert.match(page, /data-my-invitation-detail/);
assert.match(page, /src="public\/js\/ceremony-pages\.js"/);
assert.doesNotMatch(page, /data-ceremony-gift-form/);
assert.doesNotMatch(page, /href="profile-gift-edit\.html"/);
});