mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 05:56:53 +00:00
100 lines
4.7 KiB
TypeScript
100 lines
4.7 KiB
TypeScript
/**
|
|
* The three tenant-free plugin methods: weather, categories and exchange rates.
|
|
*
|
|
* They are grouped because what matters about them is the same property, and it is
|
|
* the one a migration could quietly lose: they carry no tenant data, so they work
|
|
* with NO acting user. Everything else in the plugin surface refuses that.
|
|
*/
|
|
import { BudgetModule } from '../../../src/nest/budget/budget.module';
|
|
import { ExchangeRatesRpc } from '../../../src/nest/budget/exchange-rates.rpc';
|
|
import type { ExchangeRatesService } from '../../../src/nest/budget/exchange-rates.service';
|
|
import { CategoriesModule } from '../../../src/nest/categories/categories.module';
|
|
import { CategoriesRpc } from '../../../src/nest/categories/categories.rpc';
|
|
import type { CategoriesService } from '../../../src/nest/categories/categories.service';
|
|
import { PluginRpcHost } from '../../../src/nest/plugins/host/rpc-host';
|
|
import { createTestPluginRegistry } from '../../../src/nest/plugins/host/rpc-kit/testing';
|
|
import type { RpcRequest, RpcError } from '../../../src/nest/plugins/protocol/envelope';
|
|
import { WeatherModule } from '../../../src/nest/weather/weather.module';
|
|
import { WeatherRpc } from '../../../src/nest/weather/weather.rpc';
|
|
import type { WeatherService } from '../../../src/nest/weather/weather.service';
|
|
import { expectRegisteredProvider } from '../../helpers/module-providers';
|
|
import { makeDeps } from '../../helpers/rpc-host-deps';
|
|
|
|
import { describe, it, expect, vi } from 'vitest';
|
|
|
|
const req = (method: string, params: Record<string, unknown> = {}): RpcRequest => ({
|
|
k: 'req',
|
|
id: 'x',
|
|
method,
|
|
params,
|
|
});
|
|
|
|
function build() {
|
|
const categories = { list: vi.fn(() => [{ id: 1, name: 'Food' }]) } as unknown as CategoriesService &
|
|
Record<string, ReturnType<typeof vi.fn>>;
|
|
const weather = { get: vi.fn(async () => ({ temp: 20 })) } as unknown as WeatherService &
|
|
Record<string, ReturnType<typeof vi.fn>>;
|
|
const rates = {
|
|
getRates: vi.fn(async (base: string) => ({ [base]: 1, USD: 1.08 })),
|
|
} as unknown as ExchangeRatesService & Record<string, ReturnType<typeof vi.fn>>;
|
|
const registry = createTestPluginRegistry([
|
|
new CategoriesRpc(categories),
|
|
new WeatherRpc(weather),
|
|
new ExchangeRatesRpc(rates),
|
|
]);
|
|
const host = (...grants: string[]) => new PluginRpcHost('p', new Set(grants), makeDeps(), registry);
|
|
return { categories, weather, rates, host };
|
|
}
|
|
|
|
describe('tenant-free plugin methods', () => {
|
|
it('TENANTFREE-001 weather.get and categories.list work without a user', async () => {
|
|
const f = build();
|
|
expect((await f.host('weather:read').dispatch(req('weather.get', { lat: 48, lng: 11 }), undefined)).ok).toBe(true);
|
|
expect((await f.host('db:read:categories').dispatch(req('categories.list', {}), undefined)).ok).toBe(true);
|
|
});
|
|
|
|
it('TENANTFREE-002 rates.get works without a user and needs rates:read', async () => {
|
|
const f = build();
|
|
expect((await f.host('rates:read').dispatch(req('rates.get', { base: 'EUR' }), undefined)).ok).toBe(true);
|
|
expect(f.rates.getRates).toHaveBeenCalledWith('EUR');
|
|
const denied = (await f.host().dispatch(req('rates.get', { base: 'EUR' }), undefined)) as RpcError;
|
|
expect(denied.error.code).toBe('PERMISSION_DENIED');
|
|
});
|
|
|
|
it('TENANTFREE-003 weather coordinates are coerced, and the language stays pinned to en', async () => {
|
|
const f = build();
|
|
// Numeric strings are accepted, as the wire contract has always allowed.
|
|
await f
|
|
.host('weather:read')
|
|
.dispatch(req('weather.get', { lat: '48.1', lng: '11.5', date: '2027-01-01' }), undefined);
|
|
expect(f.weather.get).toHaveBeenCalledWith('48.1', '11.5', '2027-01-01', 'en');
|
|
});
|
|
|
|
it('TENANTFREE-004 a non-numeric coordinate is BAD_PARAMS', async () => {
|
|
const res = (await build()
|
|
.host('weather:read')
|
|
.dispatch(req('weather.get', { lat: 'north', lng: 11 }), undefined)) as RpcError;
|
|
expect(res.error.code).toBe('BAD_PARAMS');
|
|
expect(res.error.message).toBe('lat must be a number');
|
|
});
|
|
|
|
it('TENANTFREE-005 a non-string base is BAD_PARAMS', async () => {
|
|
const res = (await build()
|
|
.host('rates:read')
|
|
.dispatch(req('rates.get', { base: 42 }), undefined)) as RpcError;
|
|
expect(res.error.code).toBe('BAD_PARAMS');
|
|
});
|
|
|
|
it('TENANTFREE-006 an absent date is passed through as undefined, not as a string', async () => {
|
|
const f = build();
|
|
await f.host('weather:read').dispatch(req('weather.get', { lat: 1, lng: 2 }), undefined);
|
|
expect(f.weather.get).toHaveBeenCalledWith('1', '2', undefined, 'en');
|
|
});
|
|
|
|
it('TENANTFREE-007 each class is listed in its module providers', () => {
|
|
expectRegisteredProvider(CategoriesModule, CategoriesRpc);
|
|
expectRegisteredProvider(WeatherModule, WeatherRpc);
|
|
expectRegisteredProvider(BudgetModule, ExchangeRatesRpc);
|
|
});
|
|
});
|