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:
@@ -1,7 +1,7 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { join } from 'node:path';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { forTenant, withTenantTransaction } from './prisma-tenant.extension';
|
||||
import { forSystem, forTenant, withTenantTransaction } from './prisma-tenant.extension';
|
||||
|
||||
/**
|
||||
* Prueft ohne laufende Datenbank die FORM des Aufrufs, nicht seinen mit
|
||||
@@ -199,6 +199,80 @@ describe('forTenant() — Array-Form von $transaction (WINDOWS #20)', () => {
|
||||
expect(transactionCalls).toHaveLength(1);
|
||||
expect((transactionCalls[0] as unknown[]).length).toBe(2);
|
||||
});
|
||||
|
||||
// Systemkontext (Etappe 3c, 260914-eym): forTenant() setzt app.system_context
|
||||
// AUSDRUECKLICH auf den Leerstring — als Literal im Template-Text, nicht als
|
||||
// Parameter (die Parameterliste bleibt [tenantId, userId ?? '']).
|
||||
it('setzt app.system_context im Template-Text ausdruecklich auf den Leerstring (kein Erben aus einem Systemkontext, 260914-eym)', async () => {
|
||||
const fakePrisma: any = {
|
||||
$transaction: vi.fn(() => Promise.resolve(['set-config-result', 'query-result'])),
|
||||
$extends: (config: any) => ({
|
||||
async __invoke(args: unknown, query: (args: unknown) => unknown) {
|
||||
return config.query.$allOperations({ args, query });
|
||||
},
|
||||
}),
|
||||
$executeRaw: vi.fn((strings: TemplateStringsArray, ...values: unknown[]) => {
|
||||
const text = strings.join('');
|
||||
expect(text).toContain("set_config('app.system_context', '', true)");
|
||||
expect(values).toEqual(['tenant-a', '']);
|
||||
return 'set-config-promise';
|
||||
}),
|
||||
};
|
||||
|
||||
const scoped = forTenant(fakePrisma, 'tenant-a') as any;
|
||||
await scoped.__invoke({}, () => 'query-result');
|
||||
|
||||
expect(fakePrisma.$executeRaw).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('forSystem() — Systemkontext fuer Hintergrunddienste (Etappe 3c, 260914-eym)', () => {
|
||||
it("setzt alle drei Variablen als Literale im Template-Text ('true'/''/''), values leer, $transaction-Feld mit genau zwei Eintraegen", async () => {
|
||||
const transactionCalls: unknown[] = [];
|
||||
const fakeQueryResult = [{ id: 'row-1' }];
|
||||
const fakePrisma: any = {
|
||||
$transaction: vi.fn((arg: unknown) => {
|
||||
transactionCalls.push(arg);
|
||||
return Promise.resolve(['set-config-result', fakeQueryResult]);
|
||||
}),
|
||||
$extends: (config: any) => ({
|
||||
async __invoke(args: unknown, query: (args: unknown) => unknown) {
|
||||
return config.query.$allOperations({ args, query });
|
||||
},
|
||||
}),
|
||||
$executeRaw: vi.fn((strings: TemplateStringsArray, ...values: unknown[]) => {
|
||||
const text = strings.join('');
|
||||
expect(text).toContain("set_config('app.system_context', 'true', true)");
|
||||
expect(text).toContain("set_config('app.current_tenant', '', true)");
|
||||
expect(text).toContain("set_config('app.current_user', '', true)");
|
||||
expect(values).toEqual([]);
|
||||
return 'set-config-promise';
|
||||
}),
|
||||
};
|
||||
|
||||
const system = forSystem(fakePrisma) as any;
|
||||
let queryCallCount = 0;
|
||||
const result = await system.__invoke({ where: { isActive: true } }, () => {
|
||||
queryCallCount += 1;
|
||||
return fakeQueryResult;
|
||||
});
|
||||
|
||||
expect(fakePrisma.$executeRaw).toHaveBeenCalledTimes(1);
|
||||
expect(transactionCalls).toHaveLength(1);
|
||||
expect(Array.isArray(transactionCalls[0])).toBe(true);
|
||||
expect((transactionCalls[0] as unknown[]).length).toBe(2);
|
||||
expect(result).toBe(fakeQueryResult);
|
||||
expect(queryCallCount).toBe(1);
|
||||
});
|
||||
|
||||
it('nutzt im tatsaechlichen Code die Array-Form von $transaction — innerhalb von forSystem() selbst (WINDOWS-#20-Bauart)', () => {
|
||||
const source = stripComments(readFileSync(EXTENSION_SOURCE_PATH, 'utf-8'));
|
||||
const forSystemSource = extractFunctionSource(source, 'forSystem');
|
||||
expect(forSystemSource).not.toBe('');
|
||||
expect(forSystemSource).toMatch(/\$transaction\(\s*\[/);
|
||||
expect(forSystemSource).not.toMatch(/\$transaction\(\s*async/);
|
||||
expect(forSystemSource).not.toContain('$executeRawUnsafe');
|
||||
});
|
||||
});
|
||||
|
||||
describe('withTenantTransaction() — interaktive Callback-Form auf dem UNgebundenen Client (260909-jts, Aufgabe 1)', () => {
|
||||
@@ -273,6 +347,24 @@ describe('withTenantTransaction() — interaktive Callback-Form auf dem UNgebund
|
||||
|
||||
await withTenantTransaction(fakePrisma, "tenant-with-quote-' OR 1=1", async () => 'ok');
|
||||
|
||||
expect(fakeTx.$executeRaw).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
it('setzt app.system_context im Template-Text auf tx ausdruecklich auf den Leerstring (260914-eym)', async () => {
|
||||
const fakeTx: any = {
|
||||
$executeRaw: vi.fn((strings: TemplateStringsArray, ...values: unknown[]) => {
|
||||
const text = strings.join('');
|
||||
expect(text).toContain("set_config('app.current_tenant', ");
|
||||
expect(text).toContain("set_config('app.system_context', '', true)");
|
||||
expect(values).toEqual(['tenant-a']);
|
||||
return Promise.resolve(1);
|
||||
}),
|
||||
};
|
||||
const fakePrisma: any = {
|
||||
$transaction: vi.fn((fn: (tx: unknown) => unknown) => fn(fakeTx)),
|
||||
};
|
||||
|
||||
await withTenantTransaction(fakePrisma, 'tenant-a', async () => 'ok');
|
||||
|
||||
expect(fakeTx.$executeRaw).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user