优化
This commit is contained in:
@@ -70,6 +70,8 @@ const report = {
|
||||
channelCode: null,
|
||||
roleId: null,
|
||||
testMembershipId: null,
|
||||
notificationTemplateId: null,
|
||||
notificationMessageIds: [],
|
||||
orderIds: [],
|
||||
},
|
||||
steps: [],
|
||||
@@ -92,6 +94,7 @@ try {
|
||||
await rbacAndMemberModule()
|
||||
await orderLifecycleModule()
|
||||
await isolationModule()
|
||||
await notificationInboxModule()
|
||||
await accountSecurityModule()
|
||||
report.success = report.steps.every((step) => step.passed)
|
||||
} catch (error) {
|
||||
@@ -547,7 +550,7 @@ async function rbacAndMemberModule() {
|
||||
assertSetEqual(
|
||||
visibleClientMenus(arrayData(ownerPermissions.json.data)),
|
||||
['account:profile:view', 'company:member:list', 'company:permission:list', 'company:profile:view',
|
||||
'company:role:list', 'dashboard:view', 'order:first-leg:list'],
|
||||
'company:role:list', 'dashboard:view', 'notification:inbox:view', 'order:first-leg:list'],
|
||||
'owner visible Client menus',
|
||||
)
|
||||
|
||||
@@ -1340,6 +1343,240 @@ async function isolationModule() {
|
||||
}, ({ json }) => ({ qualificationId: report.resources.businessLicenseQualificationId, rejectionCode: json?.code }))
|
||||
}
|
||||
|
||||
async function notificationInboxModule() {
|
||||
const module = 'notification-inbox'
|
||||
const templateBase = '/api/platform/v1/notification-templates'
|
||||
const inboxBase = '/api/merchant/v1/notifications'
|
||||
const suffix = marker.replace(/[^0-9A-Za-z]/g, '').slice(-24).toUpperCase()
|
||||
const templateCode = `CLIENT_INBOX_${suffix}`.slice(0, 64)
|
||||
const category = 'CLIENT_ACCEPTANCE'
|
||||
let template = null
|
||||
|
||||
try {
|
||||
const createTemplate = await apiStep(module, 'platform creates client inbox template', {
|
||||
method: 'POST', path: templateBase, token: platformToken,
|
||||
body: {
|
||||
templateCode,
|
||||
templateName: `客户消息闭环-${marker}`.slice(0, 128),
|
||||
category,
|
||||
titleTemplate: '客户消息验收 {{sequence}}',
|
||||
bodyTemplate: '客户 {{marker}} 的第 {{sequence}} 条真实站内消息。',
|
||||
variableWhitelist: ['marker', 'sequence'],
|
||||
},
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertPresent(json.data?.id, 'notification template id')
|
||||
assertEqual(json.data?.status, 'DRAFT', 'notification template status')
|
||||
assertEqual(json.data?.channel, 'IN_APP', 'notification template channel')
|
||||
}, ({ json }) => ({ templateId: json.data.id, templateCode: json.data.templateCode, status: json.data.status }))
|
||||
template = createTemplate.json.data
|
||||
report.resources.notificationTemplateId = template.id
|
||||
|
||||
const activateTemplate = await apiStep(module, 'platform activates client inbox template', {
|
||||
method: 'PUT', path: `${templateBase}/${template.id}/status`, token: platformToken,
|
||||
body: { targetStatus: 'ACTIVE', version: template.version },
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.status, 'ACTIVE', 'notification template status')
|
||||
}, ({ json }) => ({ templateId: json.data.id, status: json.data.status, version: json.data.version }))
|
||||
template = activateTemplate.json.data
|
||||
|
||||
await apiStep(module, 'client normalizes existing inbox messages to read', {
|
||||
method: 'PUT', path: `${inboxBase}/read-all`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertTrue(Number(json.data?.updatedCount) >= 0, 'updatedCount must be non-negative')
|
||||
}, ({ json }) => ({ updatedCount: json.data.updatedCount }))
|
||||
|
||||
await apiStep(module, 'client unread baseline is zero', {
|
||||
method: 'GET', path: `${inboxBase}/unread-count`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(Number(json.data?.unreadCount), 0, 'unread baseline')
|
||||
}, ({ json }) => ({ unreadCount: json.data.unreadCount }))
|
||||
|
||||
const sendMessage = async (sequence) => apiStep(module, `platform sends client message ${sequence}`, {
|
||||
method: 'POST', path: '/api/platform/v1/notifications', token: platformToken,
|
||||
body: {
|
||||
tenantId: String(report.resources.tenantId),
|
||||
templateCode,
|
||||
recipientType: 'MEMBERSHIP',
|
||||
recipientIds: [String(report.resources.membershipId)],
|
||||
variables: { marker, sequence: String(sequence) },
|
||||
scheduledAt: null,
|
||||
idempotencyKey: newIdempotencyKey(`client-notification-${sequence}`),
|
||||
sourceEventId: `client-e2e-notification-${sequence}-${marker}`.slice(0, 128),
|
||||
},
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.status, 'SENT', 'notification task status')
|
||||
assertEqual(Number(json.data?.recipientCount), 1, 'notification recipient count')
|
||||
}, ({ json }) => ({ taskId: json.data.id, taskNo: json.data.taskNo, status: json.data.status, recipientCount: json.data.recipientCount }))
|
||||
|
||||
await sendMessage(1)
|
||||
|
||||
await apiStep(module, 'client unread count increments after first delivery', {
|
||||
method: 'GET', path: `${inboxBase}/unread-count`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(Number(json.data?.unreadCount), 1, 'unread count after first delivery')
|
||||
}, ({ json }) => ({ unreadCount: json.data.unreadCount }))
|
||||
|
||||
const firstUnreadPage = await apiStep(module, 'client filters first message as unread', {
|
||||
method: 'GET', path: `${inboxBase}?page=1&size=20&status=UNREAD&category=${category}`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
const message = pageRecords(json.data).find((item) => item.title === `客户消息验收 1`)
|
||||
assertPresent(message?.id, 'first unread notification id')
|
||||
assertEqual(message.status, 'UNREAD', 'first notification status')
|
||||
}, ({ json }) => ({ total: json.data.total, unreadIds: pageRecords(json.data).map((item) => item.id) }))
|
||||
const firstMessage = pageRecords(firstUnreadPage.json.data).find((item) => item.title === '客户消息验收 1')
|
||||
const firstMessageId = firstMessage.id
|
||||
report.resources.notificationMessageIds.push(firstMessageId)
|
||||
|
||||
await apiStep(module, 'client reads first message detail while it is unread', {
|
||||
method: 'GET', path: `${inboxBase}/${firstMessageId}`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(String(json.data?.id), String(firstMessageId), 'first notification id')
|
||||
assertEqual(json.data?.status, 'UNREAD', 'first notification detail status')
|
||||
assertTrue(String(json.data?.body).includes(marker), 'first notification body must contain marker')
|
||||
}, ({ json }) => ({ messageId: json.data.id, status: json.data.status, category: json.data.category }))
|
||||
|
||||
if (existingClientToken) {
|
||||
await apiStep(module, 'other client cannot read first message detail', {
|
||||
method: 'GET', path: `${inboxBase}/${firstMessageId}`, token: existingClientToken,
|
||||
}, ({ json, status }) => {
|
||||
assertEqual(status, 200, 'HTTP status')
|
||||
assertTrue(['A0403', 'A0404'].includes(String(json?.code)), `expected A0403/A0404, actual=${json?.code}`)
|
||||
}, ({ json }) => ({ messageId: firstMessageId, rejectionCode: json.code }))
|
||||
} else {
|
||||
recordSkipped(module, 'other client cannot read first message detail', 'Cross-client credentials were not supplied')
|
||||
}
|
||||
|
||||
await apiStep(module, 'client marks first message read', {
|
||||
method: 'PUT', path: `${inboxBase}/${firstMessageId}/read`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.status, 'READ', 'first notification status after read')
|
||||
assertPresent(json.data?.readAt, 'first notification readAt')
|
||||
}, ({ json }) => ({ messageId: json.data.id, status: json.data.status, readAt: json.data.readAt }))
|
||||
|
||||
await apiStep(module, 'single-message read is idempotent', {
|
||||
method: 'PUT', path: `${inboxBase}/${firstMessageId}/read`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.status, 'READ', 'idempotent notification status')
|
||||
}, ({ json }) => ({ messageId: json.data.id, status: json.data.status, readAt: json.data.readAt }))
|
||||
|
||||
await apiStep(module, 'client filters first message as read', {
|
||||
method: 'GET', path: `${inboxBase}?page=1&size=20&status=READ&category=${category}`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertTrue(pageRecords(json.data).some((item) => String(item.id) === String(firstMessageId) && item.status === 'READ'),
|
||||
'read filter must contain first notification')
|
||||
}, ({ json }) => ({ total: json.data.total, readIds: pageRecords(json.data).map((item) => item.id) }))
|
||||
|
||||
await apiStep(module, 'client unread count returns to zero after single read', {
|
||||
method: 'GET', path: `${inboxBase}/unread-count`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(Number(json.data?.unreadCount), 0, 'unread count after single read')
|
||||
}, ({ json }) => ({ unreadCount: json.data.unreadCount }))
|
||||
|
||||
await sendMessage(2)
|
||||
const secondUnreadPage = await apiStep(module, 'client receives second unread message', {
|
||||
method: 'GET', path: `${inboxBase}?page=1&size=20&status=UNREAD&category=${category}`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
const message = pageRecords(json.data).find((item) => item.title === '客户消息验收 2')
|
||||
assertPresent(message?.id, 'second unread notification id')
|
||||
assertEqual(message.status, 'UNREAD', 'second notification status')
|
||||
}, ({ json }) => ({ total: json.data.total, unreadIds: pageRecords(json.data).map((item) => item.id) }))
|
||||
const secondMessage = pageRecords(secondUnreadPage.json.data).find((item) => item.title === '客户消息验收 2')
|
||||
const secondMessageId = secondMessage.id
|
||||
report.resources.notificationMessageIds.push(secondMessageId)
|
||||
|
||||
if (existingClientToken) {
|
||||
await apiStep(module, 'other client cannot see acceptance messages in inbox', {
|
||||
method: 'GET', path: `${inboxBase}?page=1&size=200&category=${category}`, token: existingClientToken,
|
||||
}, ({ json, status }) => {
|
||||
assertEqual(status, 200, 'HTTP status')
|
||||
if (['A0403', 'A0404'].includes(String(json?.code))) return
|
||||
requireSuccess(json)
|
||||
const foreignIds = new Set(pageRecords(json.data).map((item) => String(item.id)))
|
||||
assertTrue(!foreignIds.has(String(firstMessageId)) && !foreignIds.has(String(secondMessageId)),
|
||||
'other client inbox must exclude acceptance messages')
|
||||
}, ({ json }) => ({ rejectionCode: json?.code === '200' ? null : json?.code,
|
||||
visibleCount: json?.code === '200' ? pageRecords(json.data).length : null,
|
||||
excludedIds: [firstMessageId, secondMessageId] }))
|
||||
} else {
|
||||
recordSkipped(module, 'other client cannot see acceptance messages in inbox', 'Cross-client credentials were not supplied')
|
||||
}
|
||||
|
||||
await apiStep(module, 'client unread count increments after second delivery', {
|
||||
method: 'GET', path: `${inboxBase}/unread-count`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(Number(json.data?.unreadCount), 1, 'unread count after second delivery')
|
||||
}, ({ json }) => ({ unreadCount: json.data.unreadCount }))
|
||||
|
||||
await apiStep(module, 'client marks all messages read', {
|
||||
method: 'PUT', path: `${inboxBase}/read-all`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(Number(json.data?.updatedCount), 1, 'read-all updatedCount')
|
||||
}, ({ json }) => ({ updatedCount: json.data.updatedCount }))
|
||||
|
||||
await apiStep(module, 'second message detail is read after read-all', {
|
||||
method: 'GET', path: `${inboxBase}/${secondMessageId}`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.status, 'READ', 'second notification status after read-all')
|
||||
assertPresent(json.data?.readAt, 'second notification readAt')
|
||||
}, ({ json }) => ({ messageId: json.data.id, status: json.data.status, readAt: json.data.readAt }))
|
||||
|
||||
await apiStep(module, 'client unread count is zero after read-all', {
|
||||
method: 'GET', path: `${inboxBase}/unread-count`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(Number(json.data?.unreadCount), 0, 'unread count after read-all')
|
||||
}, ({ json }) => ({ unreadCount: json.data.unreadCount }))
|
||||
} finally {
|
||||
if (merchantToken && report.resources.notificationMessageIds.length > 0) {
|
||||
await softApiStep(module, 'cleanup keeps acceptance messages read', {
|
||||
method: 'PUT', path: `${inboxBase}/read-all`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertTrue(Number(json.data?.updatedCount) >= 0, 'cleanup updatedCount must be non-negative')
|
||||
}, ({ json }) => ({ updatedCount: json?.data?.updatedCount }))
|
||||
await softApiStep(module, 'cleanup leaves no unread acceptance messages', {
|
||||
method: 'GET', path: `${inboxBase}/unread-count`, token: merchantToken,
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(Number(json.data?.unreadCount), 0, 'cleanup unread count')
|
||||
}, ({ json }) => ({ unreadCount: json?.data?.unreadCount }))
|
||||
}
|
||||
if (template?.status === 'ACTIVE') {
|
||||
const disabled = await softApiStep(module, 'platform disables client inbox template', {
|
||||
method: 'PUT', path: `${templateBase}/${template.id}/status`, token: platformToken,
|
||||
body: { targetStatus: 'DISABLED', version: template.version },
|
||||
}, ({ json }) => {
|
||||
requireSuccess(json)
|
||||
assertEqual(json.data?.status, 'DISABLED', 'notification template final status')
|
||||
}, ({ json }) => ({ templateId: json?.data?.id, status: json?.data?.status, version: json?.data?.version }))
|
||||
if (disabled?.json?.data) template = disabled.json.data
|
||||
}
|
||||
report.notificationInbox = {
|
||||
templateId: report.resources.notificationTemplateId,
|
||||
templateCode,
|
||||
templateStatus: template?.status ?? null,
|
||||
messageIds: [...report.resources.notificationMessageIds],
|
||||
retainedAsRead: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function accountSecurityModule() {
|
||||
const consumedRefreshToken = merchantRefreshToken
|
||||
const rotated = await apiStep('account-security', 'rotate refresh token', {
|
||||
@@ -1830,6 +2067,7 @@ function requiredOwnerBackendPermissions() {
|
||||
'merchant:member:edit', 'merchant:member:grant-role', 'merchant:member:status',
|
||||
'merchant:member:reset-password', 'merchant:role:list', 'merchant:role:create',
|
||||
'merchant:role:edit', 'merchant:role:status', 'merchant:role:delete', 'merchant:role:grant',
|
||||
'merchant:notification:view', 'merchant:notification:read', 'merchant:notification:read-all',
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1840,6 +2078,7 @@ function visibleClientMenus(backendPermissions) {
|
||||
if (permissions.has('merchant:profile:view')) menus.push('company:profile:view')
|
||||
if (permissions.has('merchant:member:list')) menus.push('company:member:list')
|
||||
if (permissions.has('merchant:role:list')) menus.push('company:role:list', 'company:permission:list')
|
||||
if (permissions.has('merchant:notification:view')) menus.push('notification:inbox:view')
|
||||
return menus.sort()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user