feat(260923-dhh): Proxmox-Modul Aufgabe 4 - Hintergrundabfrage je Mandant, Verbindungstest
- proxmox-scheduler.service.ts: ein Cron-Auftrag je aktivem Mandant (proxmox-poll:<tenantId>), onApplicationBootstrap (nicht onModuleInit, Tender-Muster), Abfrageintervall = kleinstes pollIntervalMin der aktiven Server, ein fehlgeschlagener Server bricht die Tick-Schleife nicht ab, refreshTenant() zieht nach jedem Speichern sofort nach - proxmox.service.ts: loadActiveServersForScheduler() als einziger forSystem()-Aufruf des Moduls (Erlaubnisliste in rls-access-inventory.spec.ts), testConnection() schreibt nicht ins Zwischenlager, pollServer() bekommt eine Zehn-Sekunden-Sperre (T-DHH-06) - proxmox.controller.ts: POST servers/:id/test, create() zieht den Planer nach dem Anlegen sofort nach - Zugriffsklassifikation: proxmoxServer wechselt auf system-gebunden (Startpfad des Planers), proxmoxServerStatus bleibt gebunden Tore: api 1306/1306 (>=1240), type-check 4/4, rls-access-inventory und rls-coverage gruen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -68,12 +68,19 @@ function makeFakePrisma() {
|
||||
findMany: vi.fn(async ({ where, select }: { where?: any; select?: any } = {}) => {
|
||||
let rows = [...servers.values()];
|
||||
if (where?.tenantId) rows = rows.filter((r) => r.tenantId === where.tenantId);
|
||||
if (where?.isActive !== undefined) rows = rows.filter((r) => r.isActive === where.isActive);
|
||||
return rows.map((r) => applySelect(r, select));
|
||||
}),
|
||||
findUnique: vi.fn(async ({ where }: { where: { id: string } }) => {
|
||||
const row = servers.get(where.id);
|
||||
return row ? { ...row } : null;
|
||||
}),
|
||||
findUnique: vi.fn(
|
||||
async ({ where, include }: { where: { id: string }; include?: { status?: boolean } }) => {
|
||||
const row = servers.get(where.id);
|
||||
if (!row) return null;
|
||||
if (include?.status) {
|
||||
return { ...row, status: statuses.get(row.id) ?? null };
|
||||
}
|
||||
return { ...row };
|
||||
},
|
||||
),
|
||||
};
|
||||
|
||||
const proxmoxServerStatus = {
|
||||
@@ -347,3 +354,83 @@ describe('ProxmoxService — Aufgabe 3 (PBS und PMG)', () => {
|
||||
expect(result?.errorKind).toBe('rechte');
|
||||
});
|
||||
});
|
||||
|
||||
describe('ProxmoxService — Aufgabe 4 (Verbindungstest, Zehn-Sekunden-Sperre)', () => {
|
||||
afterEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
it('testConnection liefert das Ergebnis, schreibt aber NICHT ins Zwischenlager', async () => {
|
||||
const prisma = makeFakePrisma();
|
||||
const service = new ProxmoxService(prisma as any, crypto as any);
|
||||
const created = await service.createServer('tenant-a', TOKEN_DTO);
|
||||
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn(async () => new Response(JSON.stringify(pveResourcesBody()), { status: 200 })),
|
||||
);
|
||||
|
||||
const result = await service.testConnection('tenant-a', (created as any).id);
|
||||
|
||||
expect(result?.reachable).toBe(true);
|
||||
const status = prisma.__statuses.get((created as any).id);
|
||||
// Die leere Zwischenlagerzeile aus createServer bleibt unveraendert.
|
||||
expect(status.lastPolledAt).toBeUndefined();
|
||||
expect(status.reachable).toBe(false);
|
||||
});
|
||||
|
||||
it('POST servers/:id/poll verweigert einen zweiten Durchlauf innerhalb von zehn Sekunden und liefert den vorhandenen Stand', async () => {
|
||||
const prisma = makeFakePrisma();
|
||||
const service = new ProxmoxService(prisma as any, crypto as any);
|
||||
const created = await service.createServer('tenant-a', TOKEN_DTO);
|
||||
|
||||
let fetchCalls = 0;
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn(async () => {
|
||||
fetchCalls++;
|
||||
return new Response(JSON.stringify(pveResourcesBody()), { status: 200 });
|
||||
}),
|
||||
);
|
||||
|
||||
const first = await service.pollServer('tenant-a', (created as any).id);
|
||||
const second = await service.pollServer('tenant-a', (created as any).id);
|
||||
|
||||
expect(fetchCalls).toBe(1);
|
||||
expect(second).toEqual(first);
|
||||
});
|
||||
|
||||
it('nach zehn Sekunden ist ein erneuter Durchlauf wieder erlaubt', async () => {
|
||||
const prisma = makeFakePrisma();
|
||||
const service = new ProxmoxService(prisma as any, crypto as any);
|
||||
const created = await service.createServer('tenant-a', TOKEN_DTO);
|
||||
|
||||
let fetchCalls = 0;
|
||||
vi.stubGlobal(
|
||||
'fetch',
|
||||
vi.fn(async () => {
|
||||
fetchCalls++;
|
||||
return new Response(JSON.stringify(pveResourcesBody()), { status: 200 });
|
||||
}),
|
||||
);
|
||||
|
||||
await service.pollServer('tenant-a', (created as any).id);
|
||||
const status = prisma.__statuses.get((created as any).id);
|
||||
status.lastPolledAt = new Date(Date.now() - 11_000); // Sperre kuenstlich veraltern
|
||||
|
||||
await service.pollServer('tenant-a', (created as any).id);
|
||||
|
||||
expect(fetchCalls).toBe(2);
|
||||
});
|
||||
|
||||
it('loadActiveServersForScheduler nutzt forSystem (D-08, der einzige Systemkontext-Aufruf des Moduls)', async () => {
|
||||
const prisma = makeFakePrisma();
|
||||
const service = new ProxmoxService(prisma as any, crypto as any);
|
||||
await service.createServer('tenant-a', TOKEN_DTO);
|
||||
|
||||
const servers = await service.loadActiveServersForScheduler();
|
||||
expect(servers).toHaveLength(1);
|
||||
expect(servers[0]).toMatchObject({ tenantId: 'tenant-a', pollIntervalMin: 5 });
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user