mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 05:56:53 +00:00
113 lines
4.3 KiB
TypeScript
113 lines
4.3 KiB
TypeScript
/**
|
|
* The DI-discovered registry. It scans providers only, dedupes by instance, and
|
|
* validates at boot so a wrong decorator argument fails the app start rather than a
|
|
* single plugin activation.
|
|
*/
|
|
import { PluginController, PluginMethod } from '../../../../src/nest/plugins/host/rpc-kit/decorators';
|
|
import { PluginRpcRegistryService } from '../../../../src/nest/plugins/host/rpc-kit/registry.service';
|
|
import type { DiscoveryService, MetadataScanner } from '@nestjs/core';
|
|
|
|
import { describe, it, expect, vi } from 'vitest';
|
|
|
|
@PluginController()
|
|
class TagsRpc {
|
|
@PluginMethod('tags.list', { permission: 'db:read:tags' })
|
|
list() {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
@PluginController()
|
|
class BadRpc {
|
|
@PluginMethod('tags.create', { permission: 'db:read:tags' as 'db:write:tags' })
|
|
create() {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
class PlainProvider {
|
|
helper() {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
/** Mimics Nest's provider wrappers, including the same instance listed twice. */
|
|
function serviceFor(instances: object[]): PluginRpcRegistryService {
|
|
const discovery = {
|
|
getProviders: () => instances.map((instance) => ({ instance, metatype: instance.constructor })),
|
|
} as unknown as DiscoveryService;
|
|
const scanner = {
|
|
getAllMethodNames: (proto: object) => Object.getOwnPropertyNames(proto).filter((n) => n !== 'constructor'),
|
|
} as unknown as MetadataScanner;
|
|
return new PluginRpcRegistryService(discovery, scanner);
|
|
}
|
|
|
|
describe('PluginRpcRegistryService', () => {
|
|
it('RPCKIT-SVC-001 registers the marked providers at boot', () => {
|
|
const service = serviceFor([new TagsRpc(), new PlainProvider()]);
|
|
service.scanProviders();
|
|
expect(service.methodNames()).toEqual(new Set(['tags.list']));
|
|
});
|
|
|
|
it('RPCKIT-SVC-002 the same instance wrapped twice is registered once', () => {
|
|
const instance = new TagsRpc();
|
|
const service = serviceFor([instance, instance]);
|
|
service.scanProviders();
|
|
// Without the dedupe this would be two entries and validate() would report
|
|
// tags.list as double-owned.
|
|
expect(service.list()).toHaveLength(1);
|
|
});
|
|
|
|
it('RPCKIT-SVC-003 unmarked providers are skipped rather than rejected', () => {
|
|
const service = serviceFor([new PlainProvider()]);
|
|
expect(() => service.scanProviders()).not.toThrow();
|
|
expect(service.list()).toEqual([]);
|
|
});
|
|
|
|
it('RPCKIT-SVC-004 a null instance is skipped', () => {
|
|
const discovery = {
|
|
getProviders: () => [
|
|
{ instance: null, metatype: TagsRpc },
|
|
{ instance: undefined, metatype: TagsRpc },
|
|
],
|
|
} as unknown as DiscoveryService;
|
|
const scanner = { getAllMethodNames: () => [] } as unknown as MetadataScanner;
|
|
const service = new PluginRpcRegistryService(discovery, scanner);
|
|
expect(() => service.scanProviders()).not.toThrow();
|
|
expect(service.list()).toEqual([]);
|
|
});
|
|
|
|
it('RPCKIT-SVC-005 a wrong decorator argument fails the boot, not the first plugin call', () => {
|
|
const service = serviceFor([new BadRpc()]);
|
|
expect(() => service.onModuleInit()).toThrow(
|
|
/method "tags.create" \(BadRpc.create\) declares "db:read:tags" but METHOD_PERMISSION says "db:write:tags"/,
|
|
);
|
|
});
|
|
|
|
it('RPCKIT-SVC-006 boot fails when the surface is incomplete', () => {
|
|
// The rollout is finished, so a decorated class that no module registers is no
|
|
// longer a silent PERMISSION_DENIED at runtime: the app refuses to start, and the
|
|
// message names the method that lost its owner.
|
|
const service = serviceFor([new TagsRpc()]);
|
|
expect(() => service.onModuleInit()).toThrow(/has no handler/);
|
|
});
|
|
|
|
it('RPCKIT-SVC-008 a hook nobody consumes fails boot too, so no grant is dead', () => {
|
|
// A hook:* permission on the consent screen with nothing behind it looks exactly
|
|
// like a live one to the user granting it.
|
|
const service = serviceFor([new TagsRpc()]);
|
|
expect(() => service.onModuleInit()).toThrow(/has no host-side consumer/);
|
|
});
|
|
|
|
it('RPCKIT-SVC-007 only providers are scanned, never controllers', () => {
|
|
const getControllers = vi.fn(() => []);
|
|
const discovery = {
|
|
getProviders: () => [],
|
|
getControllers,
|
|
} as unknown as DiscoveryService;
|
|
const scanner = { getAllMethodNames: () => [] } as unknown as MetadataScanner;
|
|
new PluginRpcRegistryService(discovery, scanner).scanProviders();
|
|
expect(getControllers).not.toHaveBeenCalled();
|
|
});
|
|
});
|