11f5731029
Die seit Etappe 1 offene Architekturfrage zum gebundenen Klienten auf dem Anfrageobjekt ist entschieden: neun umgestellte Bereiche binden ausnahmslos dienst-intern (ein Klient je Methode), ein Klient auf req.tenantPrisma ohne Leser war tote Verdrahtung, die wie ein Sicherheitsmechanismus aussah. tenant.guard.ts verliert die Prisma-Abhaengigkeit und setzt nur noch req.tenantId; die nie verdrahtete tenant.middleware.ts (identische Logik, in keinem Modul registriert) ist geloescht. tenant.guard.spec.ts legt die Testlage aus dem Nichts an — alle fuenf Zweige (kein Nutzer, USER, ADMIN mit ignorierter x-tenant-id-Kopfzeile T-04-03, SUPER_ADMIN mit/ohne Wechsel, mandantenloser Nicht-SUPER_ADMIN) sowie die Abwesenheit der alten Eigenschaft in jedem Durchlass-Fall. Falsifizierungsnachweis durchgefuehrt: das probeweise Wiedereinfuehren der alten Zuweisung macht 4 der 7 Faelle rot (u. a. "expected true to be false" auf 'tenantPrisma' in req), danach zurueckgenommen. rls-access-inventory.spec.ts: FORTENANT_ASSIGNMENT_EXCEPTIONS ist leer und selbstpruefend (neuer Wachhund gegen veraltete Eintraege). Drei Fremdkommentare (app.module.ts, module.guard.ts, dkv.controller.ts) korrigiert, die noch auf die nie verdrahtete Middleware verwiesen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AMASaSxv5QMY7RncqZriRR
113 lines
4.0 KiB
TypeScript
113 lines
4.0 KiB
TypeScript
import { ForbiddenException } from '@nestjs/common';
|
|
import { describe, expect, it } from 'vitest';
|
|
import { TenantGuard } from './tenant.guard';
|
|
|
|
/**
|
|
* TenantGuard.canActivate — legt die Testlage fuer diesen Bereich aus dem
|
|
* Nichts an (260911-e2s, Aufgabe 2, Befund I: es gab vorher KEINE Testdatei
|
|
* fuer Guard oder Middleware). Muster fuer `makeContext` wie in
|
|
* `../module-registry/module.guard.spec.ts`.
|
|
*
|
|
* Der Guard wird OHNE Argumente konstruiert (`new TenantGuard()`) — das ist
|
|
* zugleich die Typpruefungs-Aussage, dass er keine Prisma-Abhaengigkeit mehr
|
|
* hat (260911-e2s, Aufgabe 2).
|
|
*
|
|
* Jeder durchlassende Fall prueft zusaetzlich, dass die alte
|
|
* Anfrageobjekt-Eigenschaft NICHT als Schluessel auf dem Anfrageobjekt
|
|
* vorhanden ist (`'tenantPrisma' in req`) — ueber den `in`-Operator, nicht
|
|
* ueber einen Property-Zugriff mit Punkt, weil das Gate dieser Aufgabe den
|
|
* Punkt-Zugriff im gesamten apps/api/src auf null zaehlt, Kommentare
|
|
* eingeschlossen.
|
|
*/
|
|
|
|
function makeContext(request: any) {
|
|
return {
|
|
switchToHttp: () => ({
|
|
getRequest: () => request,
|
|
}),
|
|
} as any;
|
|
}
|
|
|
|
describe('TenantGuard.canActivate', () => {
|
|
it('kein req.user (oeffentliche Route): liefert true, weder tenantId noch die alte Anfrageobjekt-Eigenschaft sind gesetzt', () => {
|
|
const guard = new TenantGuard();
|
|
const req: any = {};
|
|
|
|
const result = guard.canActivate(makeContext(req));
|
|
|
|
expect(result).toBe(true);
|
|
expect('tenantId' in req).toBe(false);
|
|
expect('tenantPrisma' in req).toBe(false);
|
|
});
|
|
|
|
it('Nutzer der Rolle USER mit tenantId, keine Kopfzeile: true, req.tenantId === die eigene Kennung', () => {
|
|
const guard = new TenantGuard();
|
|
const req: any = { user: { role: 'USER', tenantId: 't1' }, headers: {} };
|
|
|
|
const result = guard.canActivate(makeContext(req));
|
|
|
|
expect(result).toBe(true);
|
|
expect(req.tenantId).toBe('t1');
|
|
expect('tenantPrisma' in req).toBe(false);
|
|
});
|
|
|
|
it('Nutzer der Rolle ADMIN mit tenantId UND x-tenant-id-Kopfzeile: die Kopfzeile wird IGNORIERT, req.tenantId bleibt die eigene Kennung (T-04-03)', () => {
|
|
const guard = new TenantGuard();
|
|
const req: any = {
|
|
user: { role: 'ADMIN', tenantId: 't1' },
|
|
headers: { 'x-tenant-id': 't2' },
|
|
};
|
|
|
|
const result = guard.canActivate(makeContext(req));
|
|
|
|
expect(result).toBe(true);
|
|
expect(req.tenantId).toBe('t1');
|
|
expect('tenantPrisma' in req).toBe(false);
|
|
});
|
|
|
|
it('SUPER_ADMIN mit tenantId und x-tenant-id-Kopfzeile: der Wechsel gelingt, req.tenantId === der Header-Wert (D-10)', () => {
|
|
const guard = new TenantGuard();
|
|
const req: any = {
|
|
user: { role: 'SUPER_ADMIN', tenantId: 't1' },
|
|
headers: { 'x-tenant-id': 't2' },
|
|
};
|
|
|
|
const result = guard.canActivate(makeContext(req));
|
|
|
|
expect(result).toBe(true);
|
|
expect(req.tenantId).toBe('t2');
|
|
expect('tenantPrisma' in req).toBe(false);
|
|
});
|
|
|
|
it('SUPER_ADMIN mit tenantId, ohne Kopfzeile: req.tenantId === die eigene Kennung', () => {
|
|
const guard = new TenantGuard();
|
|
const req: any = { user: { role: 'SUPER_ADMIN', tenantId: 't1' }, headers: {} };
|
|
|
|
const result = guard.canActivate(makeContext(req));
|
|
|
|
expect(result).toBe(true);
|
|
expect(req.tenantId).toBe('t1');
|
|
expect('tenantPrisma' in req).toBe(false);
|
|
});
|
|
|
|
it('SUPER_ADMIN ohne tenantId und ohne Kopfzeile: true, req.tenantId === null (heutiges Verhalten, mit dem heutigen Sitzungsnachweis unerreichbar, trotzdem festgenagelt)', () => {
|
|
const guard = new TenantGuard();
|
|
const req: any = { user: { role: 'SUPER_ADMIN' }, headers: {} };
|
|
|
|
const result = guard.canActivate(makeContext(req));
|
|
|
|
expect(result).toBe(true);
|
|
expect(req.tenantId).toBeNull();
|
|
expect('tenantPrisma' in req).toBe(false);
|
|
});
|
|
|
|
it('Nutzer der Rolle USER ohne tenantId: wirft ForbiddenException("No tenant context")', () => {
|
|
const guard = new TenantGuard();
|
|
const req: any = { user: { role: 'USER' }, headers: {} };
|
|
|
|
expect(() => guard.canActivate(makeContext(req))).toThrow(
|
|
new ForbiddenException('No tenant context'),
|
|
);
|
|
});
|
|
});
|