Files
TREK/server/tests/unit/plugins/rpc-coverage.test.ts
T
Maurice e40bc71e9f refactor(plugins): the plugin RPC kit moves out of the plugins domain into src/nest-rpc
Every domain's *.rpc.ts was written against rpc-kit, rpc-params, rpc-errors
and PluginGuards, and every domain module imported PluginGuardsModule, all
of it inside src/nest/plugins, while PluginsRuntimeModule imports those same
domains so their handlers exist at boot. That made the plugins domain part
of every cycle. The kit imports no domain but the protocol tables and
permissions/addons for the guards, so it moves to src/nest-rpc beside
src/nest-mcp, with its tests, its README and its extraction rule; the
runtime module keeps its domain imports, which the boot-time coverage check
of the wire surface needs in any subset app.
2026-10-10 20:15:58 +02:00

78 lines
3.7 KiB
TypeScript

/**
* The coverage ledger: the anchor for the whole decorator rollout.
*
* Every wire method is owned by exactly one decorated *.rpc.ts class in the registry.
* Both sets are derived by construction, so no list is maintained by hand and the test
* cannot drift.
*
* The rollout is finished, so the legacy set is empty and the ledger's job flips from
* "how far along is the move" to "nothing fell out of it": a method deleted without a
* replacement, or a new *.rpc.ts class that no one listed in allRpcControllers().
*/
import { describe, it, expect } from 'vitest';
import { PluginRpcHost } from '../../../src/nest/plugins/host/rpc-host';
import { createTestPluginRegistry } from '../../../src/nest-rpc/rpc-kit/testing';
import { PluginRpcRegistryService } from '../../../src/nest-rpc/rpc-kit/registry.service';
import {
KNOWN_METHODS,
KNOWN_PERMISSIONS,
UNCONDITIONAL_METHODS,
} from '../../../src/nest/plugins/protocol/envelope';
import { allRpcControllers, makeDeps } from '../../helpers/rpc-host-deps';
/** Reads the router's private dispatch map. That map IS the thing under test. */
const boundMethods = (host: PluginRpcHost): Set<string> =>
new Set((host as unknown as { methods: Map<string, unknown> }).methods.keys());
describe('plugin RPC coverage ledger', () => {
const registry = createTestPluginRegistry(allRpcControllers());
// Every permission granted, so the router registers everything it can.
const host = new PluginRpcHost('probe', new Set<string>(KNOWN_PERMISSIONS), makeDeps(), registry);
const all = boundMethods(host);
const fromRegistry = registry.methodNames();
const vocabulary = new Set<string>([...KNOWN_METHODS, ...UNCONDITIONAL_METHODS]);
it('RPCLEDGER-001 every bound method comes from the registry, none from anywhere else', () => {
expect([...all].filter((m) => !fromRegistry.has(m))).toEqual([]);
});
it('RPCLEDGER-002 every registered method is one the wire protocol knows', () => {
expect([...all].filter((m) => !vocabulary.has(m))).toEqual([]);
});
it('RPCLEDGER-003 no method in the vocabulary is orphaned', () => {
expect([...vocabulary].filter((m) => !all.has(m))).toEqual([]);
});
it('RPCLEDGER-004 the registry covers the vocabulary exactly', () => {
expect(all).toEqual(vocabulary);
expect(fromRegistry.size).toBe(KNOWN_METHODS.length + UNCONDITIONAL_METHODS.length);
});
it('RPCLEDGER-005 the surface is the 114 methods the protocol declares', () => {
// Pinned rather than derived: a method DELETED from KNOWN_METHODS together with
// its handler would keep every test above green, and this is the line that makes
// that show up in the diff.
expect(KNOWN_METHODS.length).toBe(111);
expect(UNCONDITIONAL_METHODS.length).toBe(3);
expect(fromRegistry.size).toBe(114);
});
it('RPCLEDGER-006 boot-time total coverage is armed in production', () => {
// The rollout's closing move: the registry service now FAILS APP BOOT when a
// KNOWN_METHOD has no decorated owner, instead of letting it surface as a runtime
// PERMISSION_DENIED nobody can explain.
const options = (new PluginRpcRegistryService({} as never, {} as never) as unknown as {
options: { requireTotalCoverage?: boolean };
}).options;
expect(options.requireTotalCoverage).toBe(true);
});
it('RPCLEDGER-007 a registry missing a method fails validate() rather than booting', () => {
// Drops TagsRpc, a method owner. Dropping the last entry would drop PluginHooks
// instead, and the failure would be about hooks rather than methods.
const short = allRpcControllers().slice(1);
expect(() => createTestPluginRegistry(short, { requireTotalCoverage: true })).toThrow(/has no handler/);
});
});