feat(web): complete capability-driven instance modules
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
// @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<NotificationsDataSource['load']>(
|
||||
(_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(<NotificationsModule instance={owner} dataSource={pending.source} />);
|
||||
|
||||
expect(screen.getByRole('status', { name: 'Notifications loading status' })).toBeTruthy();
|
||||
expect(pending.load).toHaveBeenCalledWith('alpha', expect.any(AbortSignal));
|
||||
pending.resolve(snapshot);
|
||||
|
||||
const channels = await screen.findByRole('region', { name: 'Channel aggregate' });
|
||||
expect(within(channels).getByText('healthy')).toBeTruthy();
|
||||
expect(within(channels).getByText('5')).toBeTruthy();
|
||||
expect(
|
||||
within(screen.getByRole('region', { name: 'Queue aggregate' })).getByText('18'),
|
||||
).toBeTruthy();
|
||||
expect(
|
||||
within(screen.getByRole('region', { name: 'Log aggregate' })).getByText('30'),
|
||||
).toBeTruthy();
|
||||
expect(screen.getByText(/Observed 2026-07-17T12:00:00Z/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('never exposes polymorphic config, endpoints, destinations, tokens, phone numbers, payloads, raw logs, or actions', async () => {
|
||||
render(<NotificationsModule instance={owner} dataSource={{ load: async () => snapshot }} />);
|
||||
expect(await screen.findByText(/aggregate counts and status only/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<NotificationsDataSource['load']>().mockResolvedValue(snapshot);
|
||||
const { rerender } = render(<NotificationsModule instance={owner} />);
|
||||
expect(screen.getByRole('status', { name: 'Notifications unavailable' }).textContent).toMatch(
|
||||
/no safe notifications read data source.*uncontracted production endpoint/i,
|
||||
);
|
||||
|
||||
rerender(
|
||||
<NotificationsModule
|
||||
instance={{ ...owner, authentication: 'auth-required' }}
|
||||
dataSource={{ load }}
|
||||
/>,
|
||||
);
|
||||
expect(screen.getByRole('alert').textContent).toMatch(/authentication is required/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<NotificationsDataSource['load']>((id, signal) =>
|
||||
id === 'alpha' ? alpha.source.load(id, signal) : bravo.source.load(id, signal),
|
||||
);
|
||||
const source = { load };
|
||||
const { rerender } = render(<NotificationsModule instance={owner} dataSource={source} />);
|
||||
const alphaSignal = load.mock.calls[0]?.[1];
|
||||
|
||||
rerender(
|
||||
<NotificationsModule
|
||||
instance={{ ...owner, id: 'bravo', name: 'Bravo' }}
|
||||
dataSource={source}
|
||||
/>,
|
||||
);
|
||||
expect(alphaSignal?.aborted).toBe(true);
|
||||
alpha.resolve(snapshot);
|
||||
bravo.resolve({
|
||||
...snapshot,
|
||||
channels: { ...snapshot.channels, status: 'degraded', total: 9 },
|
||||
});
|
||||
expect(await screen.findByText('degraded')).toBeTruthy();
|
||||
expect(screen.queryByText('healthy')).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<NotificationsDataSource['load']>()
|
||||
.mockResolvedValueOnce(snapshot)
|
||||
.mockRejectedValueOnce(new Error('secret endpoint and token'))
|
||||
.mockResolvedValueOnce({ ...snapshot, channels: { status: 'available', total: 6 } });
|
||||
const source = { load };
|
||||
const { rerender } = render(
|
||||
<NotificationsModule instance={owner} dataSource={source} refreshSignal={0} />,
|
||||
);
|
||||
expect(await screen.findByText('healthy')).toBeTruthy();
|
||||
|
||||
rerender(<NotificationsModule instance={owner} dataSource={source} refreshSignal={1} />);
|
||||
const alert = await screen.findByRole('alert');
|
||||
expect(alert.textContent).toMatch(/refresh failed; showing the last known notifications data/i);
|
||||
expect(alert.textContent).not.toContain('secret endpoint');
|
||||
expect(screen.getByText('healthy')).toBeTruthy();
|
||||
await user.click(screen.getByRole('button', { name: 'Retry loading Notifications' }));
|
||||
expect(
|
||||
await within(screen.getByRole('region', { name: 'Channel aggregate' })).findByText(
|
||||
'available',
|
||||
),
|
||||
).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(<NotificationsModule instance={owner} dataSource={{ load: async () => unsafe }} />);
|
||||
const logs = await screen.findByRole('region', { name: 'Log aggregate' });
|
||||
expect(within(logs).getByText('3')).toBeTruthy();
|
||||
expect(
|
||||
within(screen.getByRole('region', { name: 'Channel aggregate' })).getByText('2'),
|
||||
).toBeTruthy();
|
||||
expect(document.body.textContent).not.toMatch(
|
||||
/api-token|channel-secret|private payload|nested-secret|Infinity|1\.5|-1/,
|
||||
);
|
||||
expect(screen.queryByText(/^Observed /)).toBeNull();
|
||||
unsafe.channels.disabled = 99;
|
||||
expect(screen.queryByText('99')).toBeNull();
|
||||
});
|
||||
|
||||
it('uses a fixed initial-load error without rejection details', async () => {
|
||||
render(
|
||||
<NotificationsModule
|
||||
instance={owner}
|
||||
dataSource={{ load: async () => Promise.reject(new Error('raw log and private token')) }}
|
||||
/>,
|
||||
);
|
||||
const alert = await screen.findByRole('alert');
|
||||
expect(alert.textContent).toContain('Notifications data could not be loaded.');
|
||||
expect(alert.textContent).not.toContain('private token');
|
||||
});
|
||||
|
||||
it('turns a synchronous source throw into the fixed initial-load error', async () => {
|
||||
render(
|
||||
<NotificationsModule
|
||||
instance={owner}
|
||||
dataSource={{
|
||||
load: () => {
|
||||
throw new Error('private notification token');
|
||||
},
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
const alert = await screen.findByRole('alert');
|
||||
expect(alert.textContent).toContain('Notifications data could not be loaded.');
|
||||
expect(alert.textContent).not.toContain('private notification token');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user