feat(260805-fok): beide Mandanten-Entstehungspfade verdrahten + Startup-Reparatur
- TenantService.create ruft nach prisma.tenant.create ensureDefaultGroup auf; Fehler werden protokolliert, nicht propagiert (Muster aus UserService.create) - tenant.module.ts importiert GroupsModule (keine Zirkularitaet, wie UserModule bereits vormacht) - AdminSeedService.onApplicationBootstrap besteht jetzt aus zwei sequenziellen await-Schritten: seedAdmin() (bisheriger Rumpf, plus ensureDefaultGroup nach dem Tenant-Upsert und VOR user.create), dann ensureDefaultGroupsForAllTenants() als abschliessende Reparatur ueber ALLE Mandanten — laeuft unabhaengig von seedAdmin()s fruehen Rueckkehrpfaden (fehlende ENV / Admin existiert bereits) und ist je Mandant sowie insgesamt try/catch-gekapselt, blockiert den API-Start nie - Reparatur sitzt bewusst NICHT als eigener onApplicationBootstrap-Hook in GroupsModule (Ordering-Falle aus tender-scheduler.service.ts) - tenant.service.spec.ts, admin-seed.service.spec.ts (neu): Reihenfolge, beide fruehen Rueckkehrpfade, Fehlerisolation je Mandant, Idempotenz ueber zwei Bootstrap-Laeufe
This commit is contained in:
@@ -1,8 +1,16 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { GroupsModule } from '../groups/groups.module';
|
||||
import { TenantController } from './tenant.controller';
|
||||
import { TenantService } from './tenant.service';
|
||||
|
||||
/**
|
||||
* Importiert GroupsModule für TenantService.create's Standardgruppen-
|
||||
* Anlage (D-06/D-13). GroupsModule importiert seinerseits nichts,
|
||||
* deshalb entsteht keine Zirkularität — UserModule bindet GroupsModule
|
||||
* bereits nach demselben Muster ein.
|
||||
*/
|
||||
@Module({
|
||||
imports: [GroupsModule],
|
||||
controllers: [TenantController],
|
||||
providers: [TenantService],
|
||||
exports: [TenantService],
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { TenantService } from './tenant.service';
|
||||
|
||||
/**
|
||||
* TenantService.create — Standardgruppen-Anlage (D-06/D-13, quick-260805-fok).
|
||||
*
|
||||
* TenantService.create ist neben AdminSeedService.seedAdmin der zweite
|
||||
* Mandanten-Entstehungspfad. Diese Tests decken ausschließlich die neue
|
||||
* ensureDefaultGroup-Anbindung ab, mit gemocktem PrismaService und
|
||||
* gemocktem GroupsService — im Stil von user.service.spec.ts.
|
||||
*/
|
||||
describe('TenantService.create — Standardgruppen-Anlage (D-06/D-13)', () => {
|
||||
let prisma: any;
|
||||
let groupsService: any;
|
||||
let service: TenantService;
|
||||
|
||||
const createdTenant = { id: 't1', name: 'Acme', slug: 'acme' };
|
||||
const baseData = { name: 'Acme', slug: 'acme' };
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
prisma = {
|
||||
tenant: {
|
||||
create: vi.fn().mockResolvedValue(createdTenant),
|
||||
},
|
||||
};
|
||||
groupsService = {
|
||||
ensureDefaultGroup: vi.fn().mockResolvedValue({ id: 'g1' }),
|
||||
};
|
||||
service = new TenantService(prisma, groupsService);
|
||||
});
|
||||
|
||||
it('ruft nach der Mandanten-Anlage genau einmal ensureDefaultGroup mit der id des neu angelegten Mandanten auf; der Rueckgabewert bleibt der Mandant', async () => {
|
||||
const result = await service.create(baseData);
|
||||
|
||||
expect(result).toEqual(createdTenant);
|
||||
expect(groupsService.ensureDefaultGroup).toHaveBeenCalledTimes(1);
|
||||
expect(groupsService.ensureDefaultGroup).toHaveBeenCalledWith('t1');
|
||||
});
|
||||
|
||||
it('gibt den Mandanten trotz werfendem ensureDefaultGroup zurueck — der Fehler wird protokolliert, nicht propagiert', async () => {
|
||||
groupsService.ensureDefaultGroup.mockRejectedValue(new Error('boom'));
|
||||
|
||||
await expect(service.create(baseData)).resolves.toEqual(createdTenant);
|
||||
expect(prisma.tenant.create).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
@@ -1,9 +1,15 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { GroupsService } from '../groups/groups.service';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
|
||||
@Injectable()
|
||||
export class TenantService {
|
||||
constructor(private prisma: PrismaService) {}
|
||||
private readonly logger = new Logger(TenantService.name);
|
||||
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private readonly groupsService: GroupsService,
|
||||
) {}
|
||||
|
||||
async findAll() {
|
||||
return this.prisma.tenant.findMany();
|
||||
@@ -13,8 +19,30 @@ export class TenantService {
|
||||
return this.prisma.tenant.findUnique({ where: { id } });
|
||||
}
|
||||
|
||||
/**
|
||||
* Legt einen Mandanten an und stellt unmittelbar danach dessen
|
||||
* Standardgruppe her (D-06/D-13), damit ein frisch angelegter Mandant
|
||||
* ab dem ersten Benutzer nutzbar ist. Schlägt ensureDefaultGroup fehl,
|
||||
* wird der Fehler protokolliert statt propagiert — die Mandanten-Anlage
|
||||
* selbst war erfolgreich, und die Startup-Reparatur in
|
||||
* AdminSeedService.ensureDefaultGroupsForAllTenants holt eine
|
||||
* gescheiterte Gruppenanlage beim nächsten API-Start nach (Muster aus
|
||||
* UserService.create).
|
||||
*/
|
||||
async create(data: { name: string; slug: string }) {
|
||||
return this.prisma.tenant.create({ data });
|
||||
const tenant = await this.prisma.tenant.create({ data });
|
||||
|
||||
try {
|
||||
await this.groupsService.ensureDefaultGroup(tenant.id);
|
||||
} catch (err) {
|
||||
this.logger.error(
|
||||
`Standardgruppen-Anlage fehlgeschlagen für Mandant '${tenant.id}': ${
|
||||
err instanceof Error ? err.message : String(err)
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
return tenant;
|
||||
}
|
||||
|
||||
async update(id: string, data: { name?: string; slug?: string; isActive?: boolean }) {
|
||||
|
||||
Reference in New Issue
Block a user