157 lines
6.1 KiB
JavaScript
157 lines
6.1 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"/);
|
|
});
|
|
|
|
test('我的邀请将列表、详情和响应结果写入统一状态组件', () => {
|
|
const root = path.join(__dirname, '..');
|
|
const page = fs.readFileSync(path.join(root, 'profile-gift.html'), 'utf8');
|
|
const source = fs.readFileSync(path.join(root, 'public', 'js', 'ceremony-pages.js'), 'utf8');
|
|
|
|
assert.match(page, /data-my-invitation-list[^>]*>[\s\S]*data-api-state="loading"/);
|
|
assert.match(page, /data-my-invitation-detail[^>]*>[\s\S]*data-api-state="empty"/);
|
|
assert.match(page, /data-my-invitation-status/);
|
|
assert.match(source, /root\.ProfileUI && root\.ProfileUI\.setApiState/);
|
|
assert.match(source, /setInvitationState\('\[data-my-invitation-list\]', 'loading', '正在加载我的邀请…'\)/);
|
|
assert.match(source, /setInvitationState\('\[data-my-invitation-list\]', 'empty', '暂无贺礼邀请'\)/);
|
|
assert.match(source, /当前账号无权查看邀请。/);
|
|
assert.match(source, /正在更新邀请状态…/);
|
|
assert.match(source, /当前账号无权响应该邀请。/);
|
|
assert.equal(CeremonyPages.isForbidden({ status: 403 }), true);
|
|
assert.equal(CeremonyPages.isForbidden({ status: 401 }), false);
|
|
});
|