mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 13:36:48 +00:00
661777f81f
A journal importer can create the journey, create its entries and write their stories, and then it stops: there was no way to give an entry a picture. The seven journal.* methods cover text and nothing else, so an importer reading an export archive could only upload its photos as trip files, which live on a trip and never reach the journey's gallery. journal.addEntryPhoto takes the bytes. Deliberately not a link to an existing gallery photo and not a provider asset: an importer holding an archive has neither, it has a file it just unpacked. The access decision stays where it already was. addPhoto answers null unless the acting user may edit the journey the entry belongs to, and null becomes RESOURCE_FORBIDDEN here, so a plugin reaches exactly the entries its acting user does. Everything the REST upload refuses, this refuses too: images only and no SVG, the operator's allowed-file-types setting, 10MB decoded, demo mode. The stored filename is the host's, never the plugin's, because the gallery dedupes on file_path. The object is written before any row points at it and removed again when the link is refused. JournalRpc moves into its own module for this. It now needs StorageService and the EXIF backfill out of MemoriesModule, and JourneyDomainModule exists precisely so places, assignments and the plugin host can reach a few journey functions without dragging both photo providers into their graphs. Discussion #1365
78 lines
3.7 KiB
TypeScript
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/plugins/host/rpc-kit/testing';
|
|
import { PluginRpcRegistryService } from '../../../src/nest/plugins/host/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/);
|
|
});
|
|
});
|