// @vitest-environment jsdom import { cleanup, render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { afterEach, describe, expect, it, vi } from 'vitest'; import type { InstanceContext } from '../app-shell.js'; import { NotificationsModule, type NotificationsDataSource, type NotificationsSnapshot, } from './notifications-module.js'; afterEach(cleanup); const owner: InstanceContext = { id: 'alpha', name: 'Alpha', origin: 'https://alpha.example', status: 'online', authentication: 'authenticated', freshness: 'fresh', }; const snapshot: NotificationsSnapshot = { observedAt: '2026-07-17T12:00:00Z', channels: { status: 'healthy', total: 5, enabled: 4, disabled: 1, healthy: 3, degraded: 1, failed: 0, endpoint: 'https://private.example/hook', destination: '+15550000123', token: 'channel-secret', config: { provider: 'private-provider' }, }, queue: { status: 'processing', total: 18, pending: 4, processing: 2, delivered: 11, failed: 1, payload: 'private queue payload', destination: 'private@example.test', }, logs: { status: 'available', total: 30, info: 20, warning: 7, error: 3, rawLogs: ['private raw log line'], phoneNumber: '+15550000456', }, }; function deferredSource() { let resolve!: (value: unknown) => void; let reject!: (reason: unknown) => void; const load = vi.fn( (_instanceId, _signal) => new Promise((done, fail) => { void _instanceId; void _signal; resolve = done; reject = fail; }), ); return { source: { load }, load, resolve: (value: unknown) => resolve(value), reject: (reason: unknown) => reject(reason), }; } describe('Notifications isolated safe read module', () => { it('reads only through the injected exact-owner source and renders allowlisted aggregates', async () => { const pending = deferredSource(); render(); expect(screen.getByRole('status', { name: '通知加载状态' })).toBeTruthy(); expect(pending.load).toHaveBeenCalledWith('alpha', expect.any(AbortSignal)); pending.resolve(snapshot); const channels = await screen.findByRole('region', { name: '渠道汇总' }); expect(within(channels).getByText('正常', { selector: 'dd' })).toBeTruthy(); expect(within(channels).getByText('5')).toBeTruthy(); expect(within(screen.getByRole('region', { name: '队列汇总' })).getByText('18')).toBeTruthy(); expect(within(screen.getByRole('region', { name: '日志汇总' })).getByText('30')).toBeTruthy(); expect(screen.getByText(/观测时间:2026-07-17T12:00:00Z/)).toBeTruthy(); }); it('never exposes polymorphic config, endpoints, destinations, tokens, phone numbers, payloads, raw logs, or actions', async () => { render( snapshot }} />); expect(await screen.findByText(/仅显示汇总计数和状态/i)).toBeTruthy(); const text = document.body.textContent ?? ''; for (const secret of [ 'https://private.example/hook', '+15550000123', 'channel-secret', 'private-provider', 'private queue payload', 'private@example.test', 'private raw log line', '+15550000456', ]) expect(text).not.toContain(secret); expect( screen.queryByText(/endpoint|destination|token|phone number|payload|raw log/i), ).toBeNull(); expect(screen.queryByRole('button')).toBeNull(); expect(screen.queryByRole('link')).toBeNull(); }); it('fails closed without an injected source or authenticated owner', async () => { const load = vi.fn().mockResolvedValue(snapshot); const { rerender } = render(); expect(screen.getByRole('status', { name: '通知不可用' }).textContent).toMatch( /没有可用的安全通知只读数据源.*未签订契约的生产端点/i, ); rerender( , ); expect(screen.getByRole('alert').textContent).toMatch(/需要先完成认证/i); expect(load).not.toHaveBeenCalled(); }); it('clears prior-owner data, aborts replaced reads, and fences late responses', async () => { const alpha = deferredSource(); const bravo = deferredSource(); const load = vi.fn((id, signal) => id === 'alpha' ? alpha.source.load(id, signal) : bravo.source.load(id, signal), ); const source = { load }; const { rerender } = render(); const alphaSignal = load.mock.calls[0]?.[1]; rerender( , ); expect(alphaSignal?.aborted).toBe(true); alpha.resolve(snapshot); bravo.resolve({ ...snapshot, channels: { ...snapshot.channels, status: 'degraded', total: 9 }, }); expect(await screen.findByText('功能受限', { selector: 'dd' })).toBeTruthy(); expect(screen.queryByText('正常', { selector: 'dd' })).toBeNull(); }); it('uses fixed errors and retains only same-owner last-good data across refresh failure and retry', async () => { const user = userEvent.setup(); const load = vi .fn() .mockResolvedValueOnce(snapshot) .mockRejectedValueOnce(new Error('secret endpoint and token')) .mockResolvedValueOnce({ ...snapshot, channels: { status: 'available', total: 6 } }); const source = { load }; const { rerender } = render( , ); expect(await screen.findByText('正常', { selector: 'dd' })).toBeTruthy(); rerender(); const alert = await screen.findByRole('alert'); expect(alert.textContent).toMatch(/刷新失败,正在显示上次已知的通知数据/i); expect(alert.textContent).not.toContain('secret endpoint'); expect(screen.getByText('正常', { selector: 'dd' })).toBeTruthy(); await user.click(screen.getByRole('button', { name: '重试加载通知数据' })); expect( await within(screen.getByRole('region', { name: '渠道汇总' })).findByText('可用'), ).toBeTruthy(); }); it('copies a bounded safe snapshot and drops malformed nested data and nominal-field secrets', async () => { const unsafe = { observedAt: 'api-token@example.test', channels: { status: 'channel-secret', total: -1, enabled: 1.5, disabled: 2, healthy: Number.POSITIVE_INFINITY, }, queue: ['private payload'], logs: { status: { token: 'nested-secret' }, total: Number.MAX_SAFE_INTEGER + 1, error: 3 }, }; render( unsafe }} />); const logs = await screen.findByRole('region', { name: '日志汇总' }); expect(within(logs).getByText('3')).toBeTruthy(); expect(within(screen.getByRole('region', { name: '渠道汇总' })).getByText('2')).toBeTruthy(); expect(document.body.textContent).not.toMatch( /api-token|channel-secret|private payload|nested-secret|Infinity|1\.5|-1/, ); expect(screen.queryByText(/^观测时间:/)).toBeNull(); unsafe.channels.disabled = 99; expect(screen.queryByText('99')).toBeNull(); }); it('uses a fixed initial-load error without rejection details', async () => { render( Promise.reject(new Error('raw log and private token')) }} />, ); const alert = await screen.findByRole('alert'); expect(alert.textContent).toContain('无法加载通知数据。'); expect(alert.textContent).not.toContain('private token'); }); it('turns a synchronous source throw into the fixed initial-load error', async () => { render( { throw new Error('private notification token'); }, }} />, ); const alert = await screen.findByRole('alert'); expect(alert.textContent).toContain('无法加载通知数据。'); expect(alert.textContent).not.toContain('private notification token'); }); });