feat(quick-260914-eym): forSystem(), is_system_context(), Systemleseregel auf fuenf Tabellen, DKV-Planer je Mandant — ein Pfad (WINDOWS #21)

- Helfer forSystem(prisma) in prisma-tenant.extension.ts (Array-Form,
  setzt app.system_context='true' und die beiden anderen Variablen
  ausdruecklich leer); forTenant()/withTenantTransaction() setzen
  app.system_context='' als Literal (4 neue Spec-Tests)
- Migration 20260914120000_rls_system_context_read: is_system_context()
  (COALESCE, STABLE) und system_read_policy FOR SELECT auf DkvModuleConfig,
  LdapConfig, LdapFieldMapping, TenderMatch, TenderSavedSearch — lokal
  angewendet (36 Migrationen, pg_proc 1, 5 system_read_policy, 34 Regeln)
- migration-sql.spec.ts: describe-Block fuer die neue Migration (6 Tests)
- rls-scratch-check.mjs: Funktion aus der Migration geschnitten,
  forSystemQuery/buildInlineSystemClient, Reset in forTenantQuery/
  buildInlineExtendedClient, runSystemContextChecks (4 Funktionsfaelle +
  9 Kennungen DkvModuleConfig) -> Alle 216 Pruefungen bestanden
- rls-access-inventory.spec.ts: fuenfte Erkennungsform const X = forSystem(,
  Stand system-gebunden mit Vorrangregel, FORSYSTEM_ALLOWED_CALL_SITES
  (exakte Zahl je Datei, 3 Tests), Proben C/D/E
- DKV: loadActiveConfigsForScheduler() ueber forSystem (findMany isActive,
  CONFIG_SAFE_SELECT, orderBy tenantId); DkvSchedulerService mit Auftrag je
  Mandant dkv-inbox-poll:<tenantId>, activeTenantId ersatzlos entfernt,
  setInterval/stopJob je Mandant, registeredTenantIds(); Controller
  stopJob(tenantId); neue dkv-scheduler.service.spec.ts (7 Tests),
  dkv.service.spec.ts Tests 6/7 umgestellt
- Klassifikation: dkv.service.ts/dkvModuleConfig system-gebunden, Header
  mit fuenfter Erkennungsform und viertem Stand-Wert
- Baseline: 63 Dateien / 1051 Tests, tsc 0, Werkzeug 216

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018N9CD3ebPKm1b32bPpBknY
This commit is contained in:
2026-09-14 11:32:54 +02:00
parent 02016e19eb
commit 3d645674f0
12 changed files with 1272 additions and 139 deletions
+56 -6
View File
@@ -22,6 +22,8 @@ import { DkvService } from './dkv.service';
vi.mock('../prisma/prisma-tenant.extension', () => ({
forTenant: vi.fn((prisma: any, tenantId: string) => prisma.__makeBoundClient(tenantId)),
// Systemkontext (260914-eym): der Planer-Startpfad liest ueber forSystem().
forSystem: vi.fn((prisma: any) => prisma.__makeSystemClient()),
}));
// `import * as fs from 'fs'` under ESM has a non-configurable module
@@ -45,6 +47,7 @@ function _applySelect(row: any, select: Record<string, boolean> | undefined) {
function makeFakePrisma() {
const configs = new Map<string, any>(); // key: tenantId
const boundCallLog: { tenantId: string; model: string; method: string }[] = [];
const systemCallLog: { model: string; method: string }[] = [];
const dkvModuleConfig = {
findFirst: vi.fn(async ({ select }: { select?: Record<string, boolean> } = {}) => {
@@ -215,6 +218,7 @@ function makeFakePrisma() {
dkvVehicleMaster,
dkvInvoiceHistory,
__boundCallLog: boundCallLog,
__systemCallLog: systemCallLog,
__seedConfig(tenantId: string, row: Record<string, unknown>) {
configs.set(tenantId, { tenantId, ...row });
},
@@ -231,6 +235,40 @@ function makeFakePrisma() {
...row,
});
},
/**
* Systemkontext-Klient (260914-eym): protokolliert in __systemCallLog,
* NICHT in __boundCallLog. dkvModuleConfig.findMany filtert ueber die
* Map nach where.isActive und liefert nach tenantId sortiert.
*/
__makeSystemClient() {
return {
dkvModuleConfig: {
findMany: async ({
where,
select,
orderBy,
}: {
where?: { isActive?: boolean };
select?: Record<string, boolean>;
orderBy?: { tenantId?: 'asc' | 'desc' };
} = {}) => {
systemCallLog.push({ model: 'dkvModuleConfig', method: 'findMany' });
let rows = Array.from(configs.values());
if (where && typeof where.isActive === 'boolean') {
rows = rows.filter((r) => r.isActive === where.isActive);
}
if (orderBy?.tenantId) {
rows = rows.sort((a, b) =>
orderBy.tenantId === 'asc'
? a.tenantId.localeCompare(b.tenantId)
: b.tenantId.localeCompare(a.tenantId),
);
}
return rows.map((r) => _applySelect(r, select));
},
},
};
},
__makeBoundClient(tenantId: string) {
const wrapModel = (model: Record<string, any>, modelName: string, methods: string[]) => {
const wrapped: any = {};
@@ -409,33 +447,45 @@ describe('DkvService — Bindung an forTenant() (260909-mir)', () => {
expect(result.status).toBe('ok');
});
it('Test 6: der bewusst uebergreifende Planer-Startpfad steht NICHT im Bindungsprotokoll — Fehlen der Bindung ist hier die bestandene Erwartung, NICHT spaeter "reparieren"', async () => {
it('Test 6 (umgedreht, 260914-eym): der Planer-Startpfad erzeugt GENAU EINEN System-Aufruf (dkvModuleConfig.findMany) und KEINEN gebundenen — WINDOWS #21 geschlossen', async () => {
const prisma = makeFakePrisma();
prisma.__seedConfig('t1', { id: 'cfg-1', protocol: 'imap', isActive: true, encryptedInboxCreds: 'enc(egal)' });
const { service } = makeDkvService(prisma);
await service.loadAnyActiveConfigForScheduler();
await service.loadActiveConfigsForScheduler();
expect(prisma.__systemCallLog).toEqual([{ model: 'dkvModuleConfig', method: 'findMany' }]);
expect(
prisma.__boundCallLog.length,
`der Planer-Startpfad darf KEINEN gebundenen Aufruf erzeugen, gefunden: ${JSON.stringify(prisma.__boundCallLog)}`,
).toBe(0);
});
it('Test 7: der Planer-Startpfad liefert die verschluesselten Zugangsdaten NICHT mit (Befund D — Entlastung wird festgeschrieben, nicht geglaubt)', async () => {
it('Test 7: der Planer-Startpfad liefert die verschluesselten Zugangsdaten NICHT mit und genau die aktiven Mandanten, nach tenantId sortiert (260914-eym)', async () => {
const prisma = makeFakePrisma();
prisma.__seedConfig('t2', {
id: 'cfg-2',
protocol: 'imap',
isActive: true,
pollIntervalMin: 30,
encryptedInboxCreds: 'enc(sollte-nie-hier-auftauchen)',
});
prisma.__seedConfig('t3', { id: 'cfg-3', protocol: 'imap', isActive: false, pollIntervalMin: 60 });
prisma.__seedConfig('t1', {
id: 'cfg-1',
protocol: 'imap',
isActive: true,
pollIntervalMin: 15,
encryptedInboxCreds: 'enc(sollte-nie-hier-auftauchen)',
});
const { service } = makeDkvService(prisma);
const result = await service.loadAnyActiveConfigForScheduler();
const result = await service.loadActiveConfigsForScheduler();
expect(result).not.toBeNull();
expect((result as any).encryptedInboxCreds).toBeUndefined();
expect(result.map((r: any) => r.tenantId)).toEqual(['t1', 't2']);
for (const row of result) {
expect((row as any).encryptedInboxCreds).toBeUndefined();
}
});
// ─── Aufgabe 3 (260909-mir): dkvVehicleMaster / dkvInvoiceHistory / getExportFile ───