mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-18 21:16:47 +00:00
refactor(nest): inject DatabaseService in db-using controllers
This commit is contained in:
@@ -16,7 +16,7 @@ import { FeedsService } from './feeds.service';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { CurrentUser } from '../auth/current-user.decorator';
|
||||
import type { User } from '../../types';
|
||||
import { db } from '../../db/database';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
|
||||
// Resolve the public origin used to build feed URLs. APP_URL wins — it is the
|
||||
// canonical externally-reachable URL behind a reverse proxy. When it is unset
|
||||
@@ -77,10 +77,13 @@ export class FeedsPublicController {
|
||||
@Controller('api/trips/:tripId/feed')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class TripFeedTokenController {
|
||||
constructor(private readonly feeds: FeedsService) {}
|
||||
constructor(
|
||||
private readonly feeds: FeedsService,
|
||||
private readonly dbs: DatabaseService,
|
||||
) {}
|
||||
|
||||
private assertAccess(tripId: string, userId: number): void {
|
||||
const row = db
|
||||
const row = this.dbs.connection
|
||||
.prepare(
|
||||
'SELECT id FROM trips WHERE id = ? AND (user_id = ? OR id IN (SELECT trip_id FROM trip_members WHERE user_id = ?))',
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { canAccessTrip, db } from '../../db/database';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { pluginsEnabled } from './kill-switch';
|
||||
import { PluginRuntimeService } from './plugin-runtime.service';
|
||||
@@ -78,7 +78,10 @@ function normalize(pluginId: string, tripDayIds: ReadonlySet<number>, raw: unkno
|
||||
@Controller('api/day-schedule')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class DayScheduleController {
|
||||
constructor(private readonly runtime: PluginRuntimeService) {}
|
||||
constructor(
|
||||
private readonly runtime: PluginRuntimeService,
|
||||
private readonly dbs: DatabaseService,
|
||||
) {}
|
||||
|
||||
@Get(':tripId')
|
||||
async get(
|
||||
@@ -88,11 +91,11 @@ export class DayScheduleController {
|
||||
if (!pluginsEnabled()) return { items: [] };
|
||||
const tripId = Number(tripIdRaw);
|
||||
const userId = req.user?.id;
|
||||
if (!Number.isFinite(tripId) || userId == null || !canAccessTrip(tripId, userId)) return { items: [] };
|
||||
if (!Number.isFinite(tripId) || userId == null || !this.dbs.canAccessTrip(tripId, userId)) return { items: [] };
|
||||
|
||||
const ids = this.runtime.providersOf('dayScheduleProvider');
|
||||
if (ids.length === 0) return { items: [] };
|
||||
const dayRows = db.prepare('SELECT id FROM days WHERE trip_id = ?').all(tripId) as Array<{ id: number }>;
|
||||
const dayRows = this.dbs.connection.prepare('SELECT id FROM days WHERE trip_id = ?').all(tripId) as Array<{ id: number }>;
|
||||
const tripDayIds: ReadonlySet<number> = new Set(dayRows.map((d) => d.id));
|
||||
|
||||
const perProvider = await Promise.all(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { db } from '../../db/database';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
import { canAccessJourney } from '../../services/journeyService';
|
||||
import { isAddonEnabled } from '../../services/adminService';
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
@@ -66,7 +66,10 @@ function normalize(raw: unknown): EntryRow[] {
|
||||
@Controller('api/journal-entry-rows')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class JournalEntryRowsController {
|
||||
constructor(private readonly runtime: PluginRuntimeService) {}
|
||||
constructor(
|
||||
private readonly runtime: PluginRuntimeService,
|
||||
private readonly dbs: DatabaseService,
|
||||
) {}
|
||||
|
||||
@Get(':entryId')
|
||||
async get(
|
||||
@@ -79,7 +82,7 @@ export class JournalEntryRowsController {
|
||||
if (!Number.isFinite(entryId) || userId == null) return { providers: [] };
|
||||
|
||||
// The entry's journey must be one the caller can access — same gate as a read.
|
||||
const row = db.prepare('SELECT journey_id FROM journey_entries WHERE id = ?').get(entryId) as { journey_id: number } | undefined;
|
||||
const row = this.dbs.connection.prepare('SELECT journey_id FROM journey_entries WHERE id = ?').get(entryId) as { journey_id: number } | undefined;
|
||||
if (!row || !canAccessJourney(row.journey_id, userId)) return { providers: [] };
|
||||
|
||||
const ids = this.runtime.providersOf('journalEntryProvider');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Controller, Get, Param, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { db, canAccessTrip } from '../../db/database';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { pluginsEnabled } from './kill-switch';
|
||||
import { PluginRuntimeService } from './plugin-runtime.service';
|
||||
@@ -61,7 +61,10 @@ function normalize(raw: unknown): DetailItem[] {
|
||||
@Controller('api/place-details')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class PlaceDetailsController {
|
||||
constructor(private readonly runtime: PluginRuntimeService) {}
|
||||
constructor(
|
||||
private readonly runtime: PluginRuntimeService,
|
||||
private readonly dbs: DatabaseService,
|
||||
) {}
|
||||
|
||||
@Get(':placeId')
|
||||
async get(
|
||||
@@ -74,8 +77,8 @@ export class PlaceDetailsController {
|
||||
if (!Number.isFinite(placeId) || userId == null) return { providers: [] };
|
||||
|
||||
// The place must belong to a trip the caller can access — same gate as a read.
|
||||
const row = db.prepare('SELECT trip_id FROM places WHERE id = ?').get(placeId) as { trip_id: number } | undefined;
|
||||
if (!row || !canAccessTrip(row.trip_id, userId)) return { providers: [] };
|
||||
const row = this.dbs.connection.prepare('SELECT trip_id FROM places WHERE id = ?').get(placeId) as { trip_id: number } | undefined;
|
||||
if (!row || !this.dbs.canAccessTrip(row.trip_id, userId)) return { providers: [] };
|
||||
|
||||
const ids = this.runtime.providersOf('placeDetailProvider');
|
||||
const results = await Promise.all(
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Controller, Get, Query, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { db } from '../../db/database';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { pluginsEnabled } from './kill-switch';
|
||||
import { readAuditForUser } from './host/plugin-audit';
|
||||
@@ -18,6 +18,8 @@ import { readAuditForUser } from './host/plugin-audit';
|
||||
@Controller('api/plugin-activity')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class PluginActivityController {
|
||||
constructor(private readonly dbs: DatabaseService) {}
|
||||
|
||||
@Get()
|
||||
mine(@Req() req: Request & { user?: { id: number } }, @Query('limit') limitRaw?: string): { activity: unknown[] } {
|
||||
if (!pluginsEnabled()) return { activity: [] };
|
||||
@@ -26,6 +28,6 @@ export class PluginActivityController {
|
||||
// Math.floor so a non-integer (e.g. ?limit=2.5) can't reach SQLite's LIMIT and 500;
|
||||
// Math.floor(NaN) stays NaN so the `|| 200` fallback still applies.
|
||||
const limit = Math.min(Math.max(Math.floor(Number(limitRaw)) || 200, 1), 500);
|
||||
return { activity: readAuditForUser(db, userId, limit) };
|
||||
return { activity: readAuditForUser(this.dbs.connection, userId, limit) };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { Request, Response } from 'express';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { pluginsEnabled } from './kill-switch';
|
||||
import { PluginOAuthService } from './plugin-oauth.service';
|
||||
import { db } from '../../db/database';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
|
||||
/**
|
||||
* Host-brokered outbound OAuth endpoints (#plugins). All are gated by JwtAuthGuard —
|
||||
@@ -15,10 +15,13 @@ import { db } from '../../db/database';
|
||||
@Controller('api/plugin-oauth')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class PluginOAuthController {
|
||||
constructor(private readonly oauth: PluginOAuthService) {}
|
||||
constructor(
|
||||
private readonly oauth: PluginOAuthService,
|
||||
private readonly dbs: DatabaseService,
|
||||
) {}
|
||||
|
||||
private isActive(id: string): boolean {
|
||||
return !!db.prepare("SELECT 1 FROM plugins WHERE id = ? AND status = 'active'").get(id);
|
||||
return !!this.dbs.connection.prepare("SELECT 1 FROM plugins WHERE id = ? AND status = 'active'").get(id);
|
||||
}
|
||||
|
||||
@Get(':id/status')
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Body, Controller, Param, Post, Req, UseGuards } from '@nestjs/common';
|
||||
import type { Request } from 'express';
|
||||
import { canAccessTrip, db } from '../../db/database';
|
||||
import type Database from 'better-sqlite3';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { pluginsEnabled } from './kill-switch';
|
||||
import { PluginRuntimeService } from './plugin-runtime.service';
|
||||
@@ -148,7 +149,10 @@ function normalize(pluginId: string, profile: string, waypointCount: number, raw
|
||||
@Controller('api/plugin-routes')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class PluginRoutesController {
|
||||
constructor(private readonly runtime: PluginRuntimeService) {}
|
||||
constructor(
|
||||
private readonly runtime: PluginRuntimeService,
|
||||
private readonly dbs: DatabaseService,
|
||||
) {}
|
||||
|
||||
@Post(':pluginId/:profileId')
|
||||
async route(
|
||||
@@ -160,7 +164,7 @@ export class PluginRoutesController {
|
||||
if (!pluginsEnabled()) return { route: null };
|
||||
const userId = req.user?.id;
|
||||
const tripId = Number(body?.tripId);
|
||||
if (userId == null || !Number.isFinite(tripId) || !canAccessTrip(tripId, userId)) return { route: null };
|
||||
if (userId == null || !Number.isFinite(tripId) || !this.dbs.canAccessTrip(tripId, userId)) return { route: null };
|
||||
if (!PROFILE_RE.test(profileId)) return { route: null };
|
||||
const waypoints = readWaypoints(body?.waypoints);
|
||||
if (!waypoints) return { route: null };
|
||||
@@ -171,7 +175,7 @@ export class PluginRoutesController {
|
||||
// one the manifest declared — re-validated from the DB row like the plugins feed,
|
||||
// so a hand-edited capabilities blob can't invent profiles.
|
||||
if (!this.runtime.providersOf('routeProvider').includes(pluginId)) return { route: null };
|
||||
if (!declaredProfiles(pluginId).includes(profileId)) return { route: null };
|
||||
if (!declaredProfiles(this.dbs.connection, pluginId).includes(profileId)) return { route: null };
|
||||
|
||||
try {
|
||||
const raw = await this.runtime.invokeHook(
|
||||
@@ -189,9 +193,9 @@ export class PluginRoutesController {
|
||||
}
|
||||
}
|
||||
|
||||
function declaredProfiles(pluginId: string): string[] {
|
||||
function declaredProfiles(conn: Database.Database, pluginId: string): string[] {
|
||||
try {
|
||||
const row = db.prepare('SELECT capabilities FROM plugins WHERE id = ?').get(pluginId) as { capabilities?: string } | undefined;
|
||||
const row = conn.prepare('SELECT capabilities FROM plugins WHERE id = ?').get(pluginId) as { capabilities?: string } | undefined;
|
||||
const c = JSON.parse(row?.capabilities || '{}') as { routeProfiles?: Array<{ id?: unknown }> };
|
||||
if (!Array.isArray(c.routeProfiles)) return [];
|
||||
return c.routeProfiles
|
||||
|
||||
@@ -4,7 +4,7 @@ import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { pluginsEnabled } from './kill-switch';
|
||||
import { PluginsService } from './plugins.service';
|
||||
import { PluginRuntimeService } from './plugin-runtime.service';
|
||||
import { db } from '../../db/database';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
|
||||
/**
|
||||
* GET/POST /api/plugin-settings/:id — a USER's own `scope:'user'` settings for a
|
||||
@@ -23,10 +23,11 @@ export class PluginUserSettingsController {
|
||||
constructor(
|
||||
private readonly plugins: PluginsService,
|
||||
private readonly runtime: PluginRuntimeService,
|
||||
private readonly dbs: DatabaseService,
|
||||
) {}
|
||||
|
||||
private activeWithUserFields(id: string): boolean {
|
||||
const row = db.prepare("SELECT 1 FROM plugins WHERE id = ? AND status = 'active'").get(id);
|
||||
const row = this.dbs.connection.prepare("SELECT 1 FROM plugins WHERE id = ? AND status = 'active'").get(id);
|
||||
return !!row;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Controller, Get, UseGuards } from '@nestjs/common';
|
||||
import { db } from '../../db/database';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
import { JwtAuthGuard } from '../auth/jwt-auth.guard';
|
||||
import { pluginsEnabled } from './kill-switch';
|
||||
|
||||
@@ -30,10 +30,12 @@ interface ActivePlugin {
|
||||
@Controller('api/plugins')
|
||||
@UseGuards(JwtAuthGuard)
|
||||
export class PluginsFeedController {
|
||||
constructor(private readonly dbs: DatabaseService) {}
|
||||
|
||||
@Get()
|
||||
list(): { plugins: ActivePlugin[] } {
|
||||
if (!pluginsEnabled()) return { plugins: [] };
|
||||
const rows = db
|
||||
const rows = this.dbs.connection
|
||||
.prepare("SELECT id, name, type, icon, capabilities, granted_permissions FROM plugins WHERE status = 'active' ORDER BY sort_order, name")
|
||||
.all() as Array<Omit<ActivePlugin, 'slot' | 'tripPage'> & { capabilities: string; granted_permissions: string }>;
|
||||
const plugins = rows.map(({ capabilities, granted_permissions, ...p }) => {
|
||||
|
||||
@@ -16,6 +16,8 @@ vi.mock('../../../src/db/database', () => ({
|
||||
db: { prepare: () => ({ all: () => tripDays.value }) },
|
||||
canAccessTrip,
|
||||
}));
|
||||
import { db as dbConn } from '../../../src/db/database';
|
||||
import { DatabaseService } from '../../../src/nest/database/database.service';
|
||||
vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
|
||||
import { DayScheduleController } from '../../../src/nest/plugins/day-schedule.controller';
|
||||
@@ -28,7 +30,7 @@ function controller(invoke: (id: string) => unknown, providers = ['p1']) {
|
||||
providersOf: vi.fn(() => providers),
|
||||
invokeHook: vi.fn(async (id: string) => invoke(id)),
|
||||
} as unknown as PluginRuntimeService;
|
||||
return { c: new DayScheduleController(runtime), runtime };
|
||||
return { c: new DayScheduleController(runtime, new DatabaseService(dbConn)), runtime };
|
||||
}
|
||||
const item = (over: Record<string, unknown> = {}) => ({ id: 's1', dayId: 10, label: 'Charging', ...over });
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ vi.mock('../../../src/db/database', () => ({
|
||||
db: { prepare: () => ({ get: (entryId: number) => entryJourney(entryId) }) },
|
||||
canAccessTrip: vi.fn(),
|
||||
}));
|
||||
import { db as dbConn } from '../../../src/db/database';
|
||||
import { DatabaseService } from '../../../src/nest/database/database.service';
|
||||
vi.mock('../../../src/services/journeyService', () => ({ canAccessJourney }));
|
||||
vi.mock('../../../src/services/adminService', () => ({ isAddonEnabled }));
|
||||
vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
@@ -31,7 +33,7 @@ function controller(invoke: (id: string) => unknown, providers = ['p1']) {
|
||||
providersOf: vi.fn(() => providers),
|
||||
invokeHook: vi.fn(async (id: string) => invoke(id)),
|
||||
} as unknown as PluginRuntimeService;
|
||||
return { c: new JournalEntryRowsController(runtime), runtime };
|
||||
return { c: new JournalEntryRowsController(runtime, new DatabaseService(dbConn)), runtime };
|
||||
}
|
||||
const row = (over: Record<string, unknown> = {}) => ({ label: 'Distance', value: '12 km', ...over });
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ vi.mock('../../../src/db/database', () => ({
|
||||
db: { prepare: () => ({ get: (placeId: number) => placeTrip(placeId) }) },
|
||||
canAccessTrip,
|
||||
}));
|
||||
import { db as dbConn } from '../../../src/db/database';
|
||||
import { DatabaseService } from '../../../src/nest/database/database.service';
|
||||
vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
|
||||
import { PlaceDetailsController } from '../../../src/nest/plugins/place-details.controller';
|
||||
@@ -22,7 +24,7 @@ function controller(over: Partial<PluginRuntimeService> = {}) {
|
||||
invokeHook: vi.fn(async (id: string) => (id === 'p2' ? [{ label: 'Rating', value: '4.5' }] : [{ label: 'Reviews', value: '12', url: 'https://x' }])),
|
||||
...over,
|
||||
} as unknown as PluginRuntimeService;
|
||||
return { c: new PlaceDetailsController(runtime), runtime };
|
||||
return { c: new PlaceDetailsController(runtime, new DatabaseService(dbConn)), runtime };
|
||||
}
|
||||
|
||||
describe('PlaceDetailsController', () => {
|
||||
|
||||
@@ -12,6 +12,8 @@ const { pluginsEnabled, getMock } = vi.hoisted(() => ({
|
||||
}));
|
||||
vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
vi.mock('../../../src/db/database', () => ({ db: { prepare: () => ({ get: getMock }) } }));
|
||||
import { db as dbConn } from '../../../src/db/database';
|
||||
import { DatabaseService } from '../../../src/nest/database/database.service';
|
||||
|
||||
import { PluginOAuthController } from '../../../src/nest/plugins/plugin-oauth.controller';
|
||||
import type { PluginOAuthService } from '../../../src/nest/plugins/plugin-oauth.service';
|
||||
@@ -30,7 +32,7 @@ function ctrl(over: Partial<PluginOAuthService> = {}) {
|
||||
disconnect: vi.fn(),
|
||||
...over,
|
||||
} as unknown as PluginOAuthService;
|
||||
return { c: new PluginOAuthController(svc), svc };
|
||||
return { c: new PluginOAuthController(svc, new DatabaseService(dbConn)), svc };
|
||||
}
|
||||
|
||||
describe('PluginOAuthController', () => {
|
||||
|
||||
@@ -16,6 +16,8 @@ vi.mock('../../../src/db/database', () => ({
|
||||
db: { prepare: () => ({ get: () => (capabilitiesRow.value === undefined ? undefined : { capabilities: capabilitiesRow.value }) }) },
|
||||
canAccessTrip,
|
||||
}));
|
||||
import { db as dbConn } from '../../../src/db/database';
|
||||
import { DatabaseService } from '../../../src/nest/database/database.service';
|
||||
vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
|
||||
import { PluginRoutesController } from '../../../src/nest/plugins/plugin-routes.controller';
|
||||
@@ -28,7 +30,7 @@ function controller(invoke: () => unknown, providers = ['ev-plug']) {
|
||||
providersOf: vi.fn(() => providers),
|
||||
invokeHook: vi.fn(async () => invoke()),
|
||||
} as unknown as PluginRuntimeService;
|
||||
return { c: new PluginRoutesController(runtime), runtime };
|
||||
return { c: new PluginRoutesController(runtime, new DatabaseService(dbConn)), runtime };
|
||||
}
|
||||
const wp = (n = 3) => Array.from({ length: n }, (_, i) => ({ lat: 48 + i * 0.1, lng: 2 + i * 0.1 }));
|
||||
const goodRoute = (n = 3, over: Record<string, unknown> = {}) => ({
|
||||
|
||||
@@ -11,6 +11,8 @@ const { pluginsEnabled, getMock } = vi.hoisted(() => ({
|
||||
}));
|
||||
vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
vi.mock('../../../src/db/database', () => ({ db: { prepare: () => ({ get: getMock }) } }));
|
||||
import { db as dbConn } from '../../../src/db/database';
|
||||
import { DatabaseService } from '../../../src/nest/database/database.service';
|
||||
|
||||
import { PluginUserSettingsController } from '../../../src/nest/plugins/plugin-user-settings.controller';
|
||||
import type { PluginRuntimeService } from '../../../src/nest/plugins/plugin-runtime.service';
|
||||
@@ -26,7 +28,7 @@ function ctrl() {
|
||||
} as unknown as PluginsService;
|
||||
// The controller now also takes the runtime (for settings-page actions).
|
||||
const runtime = { actionsOf: () => [], invokeAction: vi.fn() } as unknown as PluginRuntimeService;
|
||||
return { c: new PluginUserSettingsController(svc, runtime), svc, runtime };
|
||||
return { c: new PluginUserSettingsController(svc, runtime, new DatabaseService(dbConn)), svc, runtime };
|
||||
}
|
||||
|
||||
describe('PluginUserSettingsController', () => {
|
||||
|
||||
@@ -13,6 +13,8 @@ const { rows, pluginsEnabled } = vi.hoisted(() => ({
|
||||
pluginsEnabled: vi.fn(() => true),
|
||||
}));
|
||||
vi.mock('../../../src/db/database', () => ({ db: { prepare: () => ({ all: () => rows.value }) } }));
|
||||
import { db as dbConn } from '../../../src/db/database';
|
||||
import { DatabaseService } from '../../../src/nest/database/database.service';
|
||||
vi.mock('../../../src/nest/plugins/kill-switch', () => ({ pluginsEnabled }));
|
||||
|
||||
import { PluginsFeedController } from '../../../src/nest/plugins/plugins-feed.controller';
|
||||
@@ -24,7 +26,7 @@ const row = (over: Record<string, unknown> = {}) => ({
|
||||
});
|
||||
|
||||
describe('PluginsFeedController', () => {
|
||||
const c = new PluginsFeedController();
|
||||
const c = new PluginsFeedController(new DatabaseService(dbConn));
|
||||
beforeEach(() => { pluginsEnabled.mockReturnValue(true); rows.value = []; });
|
||||
|
||||
it('returns an empty feed when the runtime is disabled', () => {
|
||||
|
||||
Reference in New Issue
Block a user