83 lines
4.4 KiB
JavaScript
83 lines
4.4 KiB
JavaScript
import assert from 'node:assert/strict'
|
|
import { readFile } from 'node:fs/promises'
|
|
import test from 'node:test'
|
|
|
|
const moduleRoot = new URL('./', import.meta.url)
|
|
|
|
test('activates message-center routes with the frozen merchant permission', async () => {
|
|
const source = await readFile(new URL('routes.ts', moduleRoot), 'utf8')
|
|
assert.match(source, /name: 'notifications-list'[\s\S]*release: 'active'/)
|
|
assert.match(source, /name: 'notification-detail'[\s\S]*release: 'active'/)
|
|
assert.match(source, /merchant:notification:view/)
|
|
})
|
|
|
|
test('does not accept browser-selected customer or user scope in the API boundary', async () => {
|
|
const source = await readFile(new URL('api/notifications.ts', moduleRoot), 'utf8')
|
|
assert.doesNotMatch(source, /tenantId|customerId|merchantId|userId/)
|
|
assert.match(source, /merchant\/v1\/notifications/)
|
|
assert.match(source, /unread-count/)
|
|
assert.match(source, /notifications\/\$\{encodeURIComponent\(id\)\}/)
|
|
assert.match(source, /notifications\/\$\{encodeURIComponent\(id\)\}\/read/)
|
|
assert.match(source, /read-all/)
|
|
assert.match(source, /category: query\.type/)
|
|
assert.match(source, /method: 'PUT'/)
|
|
assert.doesNotMatch(source, /method: 'POST'/)
|
|
assert.match(source, /summary: item\.summary \?\? ''/)
|
|
assert.doesNotMatch(source, /item\.body\.slice/)
|
|
})
|
|
|
|
test('keeps customer message actions first and covers the requested local states', async () => {
|
|
const source = await readFile(new URL('views/NotificationsView.vue', moduleRoot), 'utf8')
|
|
const operationColumn = source.indexOf("label: '操作'")
|
|
const titleColumn = source.indexOf("label: '消息标题'")
|
|
assert.ok(operationColumn >= 0 && operationColumn < titleColumn)
|
|
assert.match(source, /全部标为已读/)
|
|
assert.match(source, /标为已读/)
|
|
assert.match(source, /notification-state--error/)
|
|
assert.match(source, /暂无消息/)
|
|
assert.doesNotMatch(source, /清空已读|删除消息/)
|
|
assert.match(source, /Promise\.all\(\[load\(\), notifications\.refreshUnreadCount\(\)\]\)/)
|
|
assert.match(source, /auth\.hasPermission\('merchant:notification:read'\)/)
|
|
assert.match(source, /auth\.hasPermission\('merchant:notification:read-all'\)/)
|
|
assert.match(source, /v-if="canMarkAllRead"/)
|
|
assert.match(source, /canMarkRead\.value && row\.status === 'UNREAD'/)
|
|
assert.match(source, /notificationCategoryLabel\(row\.type\)/)
|
|
assert.match(source, /notificationSentAt\(row\.sentAt\)/)
|
|
})
|
|
|
|
test('uses the shared compact list language without a repeated page hero', async () => {
|
|
const source = await readFile(new URL('views/NotificationsView.vue', moduleRoot), 'utf8')
|
|
assert.match(source, /<QueryToolbar[^>]*layout="grid"/)
|
|
assert.match(source, /<DataTable[^>]*:row-actions="rowActions"/)
|
|
assert.match(source, /#toolbar-actions/)
|
|
assert.match(source, /empty-text="暂无消息"/)
|
|
assert.doesNotMatch(source, /#toolbar>|page-heading|eyebrow|table-empty|共 \{\{ total \}\} 条/)
|
|
})
|
|
|
|
test('uses friendly notification values while keeping unknown server codes visible', async () => {
|
|
const source = await readFile(new URL('presentation.ts', moduleRoot), 'utf8')
|
|
assert.match(source, /ORDER: '订单消息'/)
|
|
assert.match(source, /CLIENT_ACCEPTANCE: '验收消息'/)
|
|
assert.match(source, /categoryLabels\[code\.toUpperCase\(\)\] \?\? code/)
|
|
assert.match(source, /Number\.isNaN\(parsed\.getTime\(\)\).*return source/s)
|
|
assert.match(source, /toLocaleString\('zh-CN', \{ hour12: false \}\)/)
|
|
})
|
|
|
|
test('keeps detail readable without read permission and isolates auto-read failures', async () => {
|
|
const source = await readFile(new URL('views/NotificationDetailView.vue', moduleRoot), 'utf8')
|
|
assert.match(source, /auth\.hasPermission\('merchant:notification:read'\)/)
|
|
assert.match(source, /!canMarkRead\.value\)return/)
|
|
assert.match(source, /readErrorMessage\.value=/)
|
|
assert.match(source, /v-if="readErrorMessage"/)
|
|
assert.match(source, /detail\.value=null;errorMessage\.value=/)
|
|
assert.match(source, /notificationSentAt\(detail\?\.sentAt\)/)
|
|
assert.match(source, /notificationCategoryLabel\(detail\?\.type\)/)
|
|
})
|
|
|
|
test('maps all three notification authorities into the client session', async () => {
|
|
const source = await readFile(new URL('../tenant-management/contracts.ts', moduleRoot), 'utf8')
|
|
for (const permission of ['merchant:notification:view', 'merchant:notification:read', 'merchant:notification:read-all']) {
|
|
assert.match(source, new RegExp(`'${permission}': \\['${permission}'\\]`))
|
|
}
|
|
})
|