feat(msg-bus): wire message bus on frontend
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { parseIncoming, pingFrame, subscribeFrame, unsubscribeFrame } from './frames';
|
||||
|
||||
describe('frame builders', () => {
|
||||
it('subscribeFrame produces a valid JSON-RPC 2.0 request', () => {
|
||||
expect(subscribeFrame(7, 'folder:abc')).toEqual({
|
||||
jsonrpc: '2.0',
|
||||
id: 7,
|
||||
method: 'rt.subscribe',
|
||||
params: { topic: 'folder:abc' }
|
||||
});
|
||||
});
|
||||
|
||||
it('unsubscribeFrame mirrors the subscribe shape', () => {
|
||||
expect(unsubscribeFrame(8, 'folder:abc')).toEqual({
|
||||
jsonrpc: '2.0',
|
||||
id: 8,
|
||||
method: 'rt.unsubscribe',
|
||||
params: { topic: 'folder:abc' }
|
||||
});
|
||||
});
|
||||
|
||||
it('pingFrame omits params entirely (matches the wire spec)', () => {
|
||||
const frame = pingFrame(9);
|
||||
expect(frame).toEqual({ jsonrpc: '2.0', id: 9, method: 'rt.ping' });
|
||||
expect('params' in frame).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('parseIncoming', () => {
|
||||
it('recognises an `rt.event` notification', () => {
|
||||
const raw = JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method: 'rt.event',
|
||||
params: {
|
||||
topic: 'folder:abc',
|
||||
event: 'file_created',
|
||||
data: { file_id: 'x', name: 'a.txt', parent_id: 'abc', actor: 'me' }
|
||||
}
|
||||
});
|
||||
const result = parseIncoming(raw);
|
||||
expect(result.kind).toBe('event');
|
||||
if (result.kind === 'event') {
|
||||
expect(result.params.topic).toBe('folder:abc');
|
||||
expect(result.params.event).toBe('file_created');
|
||||
}
|
||||
});
|
||||
|
||||
it('recognises an `rt.revoked` notification', () => {
|
||||
const raw = JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
method: 'rt.revoked',
|
||||
params: { topic: 'folder:abc', reason: 'grant_revoked' }
|
||||
});
|
||||
const result = parseIncoming(raw);
|
||||
expect(result.kind).toBe('revoked');
|
||||
if (result.kind === 'revoked') expect(result.params.topic).toBe('folder:abc');
|
||||
});
|
||||
|
||||
it('recognises a success response', () => {
|
||||
const raw = JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: 42,
|
||||
result: { subscribed: 'folder:abc' }
|
||||
});
|
||||
const result = parseIncoming(raw);
|
||||
expect(result.kind).toBe('success');
|
||||
if (result.kind === 'success') {
|
||||
expect(result.id).toBe(42);
|
||||
expect(result.result).toEqual({ subscribed: 'folder:abc' });
|
||||
}
|
||||
});
|
||||
|
||||
it('recognises an error response and preserves the code', () => {
|
||||
const raw = JSON.stringify({
|
||||
jsonrpc: '2.0',
|
||||
id: 42,
|
||||
error: { code: -32001, message: 'no_read', data: { topic: 'folder:xyz' } }
|
||||
});
|
||||
const result = parseIncoming(raw);
|
||||
expect(result.kind).toBe('error');
|
||||
if (result.kind === 'error') {
|
||||
expect(result.id).toBe(42);
|
||||
expect(result.error.code).toBe(-32001);
|
||||
expect(result.error.message).toBe('no_read');
|
||||
}
|
||||
});
|
||||
|
||||
it('collapses malformed frames to `ignore` with a stable reason key', () => {
|
||||
expect(parseIncoming('not-json').kind).toBe('ignore');
|
||||
expect(parseIncoming('[]').kind).toBe('ignore');
|
||||
expect(parseIncoming(JSON.stringify({ jsonrpc: '1.0', method: 'rt.event' })).kind).toBe(
|
||||
'ignore'
|
||||
);
|
||||
expect(parseIncoming(JSON.stringify({ jsonrpc: '2.0', method: 'rt.unknown' })).kind).toBe(
|
||||
'ignore'
|
||||
);
|
||||
});
|
||||
|
||||
it('never throws — always returns a discriminated result', () => {
|
||||
// Random shapes that used to trigger throws in earlier drafts.
|
||||
const cases: string[] = ['', 'null', '42', '{}', '{"jsonrpc":"2.0"}'];
|
||||
for (const c of cases) {
|
||||
expect(() => parseIncoming(c)).not.toThrow();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user