fix: 优化微信发送日志展示

This commit is contained in:
Wxw-Gu
2026-09-13 11:22:47 +08:00
parent cef1ad493c
commit e5bd162a01
12 changed files with 253 additions and 39 deletions
+85 -3
View File
@@ -1,4 +1,4 @@
import { render, screen, waitFor } from '@testing-library/react'
import { render, screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, it, vi } from 'vitest'
import { LogsWorkspace } from '../../src/renderer/src/features/logs/LogsWorkspace'
@@ -10,19 +10,23 @@ const entries: ActionLogEntry[] = [
category: 'wechat_send',
source: 'member_monitor',
purpose: 'member_left_notification',
triggerType: 'automation',
timestamp: '2026-09-03T08:30:21.000Z',
recipientType: 'group',
recipientId: '123456@chatroom',
recipientName: '测试 q1',
contentType: 'text',
contentPreview: 'Shinven 已退出群聊',
status: 'sent'
status: 'sent',
executionId: 'execution-member-1',
idempotencyKey: 'member_left_notification:member-log'
},
{
id: 'report-log',
category: 'wechat_send',
source: 'scheduled_report',
purpose: 'scheduled_report',
triggerType: 'automation',
timestamp: '2026-09-03T07:00:00.000Z',
recipientType: 'group',
recipientId: 'report@chatroom',
@@ -31,7 +35,26 @@ const entries: ActionLogEntry[] = [
contentPreview: 'daily-report.png',
status: 'failed',
errorCode: 'SEND_CAPABILITY_UNAVAILABLE',
reason: '个人微信发送能力不可用'
reason: '个人微信发送能力不可用',
executionId: 'execution-report-1',
idempotencyKey: 'scheduled_report:report-log'
},
{
id: 'blocked-log',
category: 'wechat_send',
source: 'user_tts',
purpose: 'tts_voice',
triggerType: 'user',
timestamp: '2026-09-03T06:00:00.000Z',
recipientType: 'contact',
recipientId: 'wxid_long_recipient_1234567890@chatroom.example',
recipientName: '文件传输助手',
contentType: 'voice',
contentPreview: 'voice_xxx.silk 中文正文可以自然换行',
status: 'blocked',
executionId: 'execution-blocked-1234567890',
idempotencyKey:
'member_left_notification:49023470180@chatroom:2026-09-03T06:00:00.000Z:very-long-tail'
}
]
@@ -49,6 +72,18 @@ describe('LogsWorkspace', () => {
await waitFor(() => expect(screen.getByText(/Shinven 已退出群聊/)).toBeVisible())
expect(screen.getByText(/daily-report.png/)).toBeVisible()
expect(screen.getByText('发送能力不可用')).toBeVisible()
expect(screen.getByText(/自动化 · 退群监控/)).toBeVisible()
expect(screen.getByText('被阻止')).toBeVisible()
expect(screen.queryByText('执行 ID:execution-member-1')).not.toBeInTheDocument()
const details = screen.getAllByText('查看详情')[0]
await user.click(details)
expect(screen.getByText('execution-member-1')).toBeVisible()
expect(screen.getByText('member_left_notification:member-log')).toBeVisible()
expect(screen.getByText('member_left_notification:member-log')).toHaveClass(
'whitespace-nowrap',
'font-mono'
)
const search = screen.getByRole('searchbox', { name: '搜索日志' })
await user.type(search, '123456')
@@ -64,5 +99,52 @@ describe('LogsWorkspace', () => {
await user.click(screen.getByRole('combobox', { name: '筛选日志类型' }))
await user.click(await screen.findByRole('option', { name: '退群通知' }))
expect(screen.getByText('没有匹配的日志')).toBeVisible()
await user.click(screen.getByRole('combobox', { name: '筛选日志类型' }))
await user.click(await screen.findByRole('option', { name: '全部类型' }))
await user.click(screen.getByRole('combobox', { name: '筛选日志状态' }))
await user.click(await screen.findByRole('option', { name: '全部状态' }))
const startDate = document.querySelector<HTMLInputElement>('input[aria-label="开始日期"]')
const endDate = document.querySelector<HTMLInputElement>('input[aria-label="结束日期"]')
expect(startDate).not.toBeNull()
expect(endDate).not.toBeNull()
await user.clear(startDate!)
await user.type(startDate!, '2026-09-03')
await user.clear(endDate!)
await user.type(endDate!, '2026-09-03')
expect(screen.getByText(/Shinven 已退出群聊/)).toBeVisible()
expect(screen.getByText(/daily-report.png/)).toBeVisible()
await user.clear(startDate!)
await user.type(startDate!, '2026-09-04')
expect(screen.getByText('没有匹配的日志')).toBeVisible()
})
it('keeps long identifiers on one line and exposes complete values in details', async () => {
Object.defineProperty(window, 'api', {
configurable: true,
value: { listWechatActionLogs: vi.fn().mockResolvedValue(entries) } as unknown as typeof window.api
})
render(<LogsWorkspace />)
const blockedCard = (await screen.findByText('被阻止')).closest('article')
expect(blockedCard).not.toBeNull()
const card = within(blockedCard!)
const recipient = card.getByTitle('wxid_long_recipient_1234567890@chatroom.example')
expect(recipient).toHaveClass('truncate', 'whitespace-nowrap')
const key = card.getByTitle(
'member_left_notification:49023470180@chatroom:2026-09-03T06:00:00.000Z:very-long-tail'
)
expect(key).toHaveClass('truncate', 'whitespace-nowrap', 'font-mono')
expect(card.getByText('语音 · voice_xxx.silk 中文正文可以自然换行')).toHaveClass(
'whitespace-normal',
'break-words'
)
await userEvent.click(card.getByText('查看详情'))
expect(card.getByText('wxid_long_recipient_1234567890@chatroom.example')).toBeVisible()
expect(
card.getByText('member_left_notification:49023470180@chatroom:2026-09-03T06:00:00.000Z:very-long-tail')
).toBeVisible()
})
})
+20
View File
@@ -143,6 +143,26 @@ test('NAV-01 NAV-02 every top-level page is unique and switchable', async () =>
}
})
test('LOGS-01 shows Action Gateway audit details and filters by date', async () => {
const fixture = await launchTestApp({ now: Date.parse('2026-09-12T10:00:00+08:00') })
try {
await fixture.page.getByRole('navigation', { name: '一级导航' }).getByRole('button', { name: '设置' }).click()
await fixture.page.getByRole('button', { name: '发送日志' }).click()
await expect(fixture.page.getByRole('heading', { name: '日志', exact: true })).toBeVisible()
await expect(
fixture.page.locator('article').filter({ hasText: '产品测试群' })
).toBeVisible()
await fixture.page.getByText('查看详情').first().click()
await expect(fixture.page.getByText('fixture-execution', { exact: true })).toBeVisible()
await expect(fixture.page.getByText(/自动化 · 定时日报/)).toBeVisible()
await fixture.page.getByLabel('开始日期').fill('2026-09-13')
await expect(fixture.page.getByText('没有匹配的日志')).toBeVisible()
} finally {
await fixture.close()
}
})
test('NAV-03 exit monitor page shows a member departure event and stays within the viewport', async () => {
const fixture = await launchTestApp()
const pageErrors: Error[] = []
+18
View File
@@ -701,6 +701,24 @@ handle('db:getContactAvatars', (usernames) =>
.map((contact) => [contact.m_nsUsrName, contact.avatar])
)
)
handle('wechat-action-log:list', () => [
{
id: 'fixture-action-log',
category: 'wechat_send',
source: 'scheduled_report',
purpose: 'scheduled_report',
triggerType: 'automation',
timestamp: new Date(fixtureNowMs - 60_000).toISOString(),
recipientType: 'group',
recipientId: 'group_regular@chatroom',
recipientName: '产品测试群',
contentType: 'image',
contentPreview: 'fixture-report.png',
status: 'sent',
executionId: 'fixture-execution',
idempotencyKey: 'scheduled_report:fixture-execution'
}
])
handle('settings:getSelf', () => ({ ready: true, info: fixture.self }))
handle('db:getCachedMessages', (md5) => fixture.messages[md5] || [])
handle('db:getCachedMessagePage', (md5) => ({