feat(nextcloud-status): erneute Prüfung nach Ausfall und Hinweis auf der Kachel
- Wiederholungsauftrag jede Minute für Clouds mit einem Fehlschlag älter als fünf Minuten - Neue Adresse setzt den Prüfstand zurück, der gemeldete Zustand bleibt - Kachel: Hinweis Prüfung fehlgeschlagen, Fehlercodes als lesbarer Text mit Tooltip Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -271,11 +271,25 @@ describe('NextcloudStatusService', () => {
|
||||
expect(fetchNextcloudStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('updateInstance: neue Adresse wird normalisiert und neu geprueft, unveraenderte nicht', async () => {
|
||||
it('updateInstance: neue Adresse wird normalisiert, setzt den Pruefstand zurueck (alertState bleibt) und prueft neu, unveraenderte nicht', async () => {
|
||||
await service.updateInstance('t1', 'i1', { baseUrl: 'https://neu.example.de/index.php/' });
|
||||
expect(prisma.nextcloudInstance.update.mock.calls[0][0].data).toEqual({
|
||||
const data = prisma.nextcloudInstance.update.mock.calls[0][0].data;
|
||||
expect(data).toEqual({
|
||||
baseUrl: 'https://neu.example.de',
|
||||
reachable: null,
|
||||
maintenance: null,
|
||||
needsDbUpgrade: null,
|
||||
versionString: null,
|
||||
edition: null,
|
||||
productName: null,
|
||||
errorKind: null,
|
||||
errorDetail: null,
|
||||
lastCheckedAt: null,
|
||||
consecutiveFailures: 0,
|
||||
firstFailureAt: null,
|
||||
});
|
||||
expect(data).not.toHaveProperty('alertState');
|
||||
expect(data).not.toHaveProperty('alertReason');
|
||||
expect(fetchNextcloudStatus).toHaveBeenCalledTimes(1);
|
||||
|
||||
vi.mocked(fetchNextcloudStatus).mockClear();
|
||||
@@ -283,6 +297,20 @@ describe('NextcloudStatusService', () => {
|
||||
expect(fetchNextcloudStatus).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('updateInstance: rote Cloud, korrigierte Adresse antwortet gruen -> Meldung "wieder in Ordnung" wird entschieden', async () => {
|
||||
prisma.nextcloudInstance.findFirst.mockResolvedValue({
|
||||
id: 'i1',
|
||||
baseUrl: 'https://kaputt.example.de',
|
||||
consecutiveFailures: 0,
|
||||
});
|
||||
prisma.nextcloudInstance.update.mockResolvedValue(makeRow({ alertState: 'red' }));
|
||||
await service.updateInstance('t1', 'i1', { baseUrl: 'https://cloud.a.de' });
|
||||
expect(alerts.evaluateAfterCheck).toHaveBeenCalledTimes(1);
|
||||
const [, row, rating] = alerts.evaluateAfterCheck.mock.calls[0];
|
||||
expect(row.alertState).toBe('red');
|
||||
expect(rating).toMatchObject({ level: 'green', reason: 'current' });
|
||||
});
|
||||
|
||||
it('updateInstance: ungueltige Adresse -> BadRequest', async () => {
|
||||
await expect(service.updateInstance('t1', 'i1', { baseUrl: 'javascript:1' })).rejects.toThrow(
|
||||
BadRequestException,
|
||||
@@ -435,5 +463,25 @@ describe('NextcloudStatusService', () => {
|
||||
});
|
||||
expect(rows).toEqual([{ id: 'i1', tenantId: 't1' }]);
|
||||
});
|
||||
|
||||
it('loadAllInstancesForScheduler mit retryDueBefore filtert auf genau einen Fehlschlag, Select bleibt Kennung und Mandant', async () => {
|
||||
prisma.nextcloudInstance.findMany.mockResolvedValue([{ id: 'i1', tenantId: 't1' }]);
|
||||
const due = new Date('2026-10-02T12:00:00Z');
|
||||
await service.loadAllInstancesForScheduler({ retryDueBefore: due });
|
||||
expect(prisma.nextcloudInstance.findMany).toHaveBeenCalledWith({
|
||||
where: { consecutiveFailures: 1, firstFailureAt: { lte: due } },
|
||||
select: { id: true, tenantId: true },
|
||||
});
|
||||
});
|
||||
|
||||
it('pendingRetry ist genau bei einem Fehlschlag wahr', async () => {
|
||||
prisma.nextcloudInstance.findMany.mockResolvedValue([
|
||||
makeRow({ id: 'a', consecutiveFailures: 0 }),
|
||||
makeRow({ id: 'b', consecutiveFailures: 1 }),
|
||||
makeRow({ id: 'c', consecutiveFailures: 2 }),
|
||||
]);
|
||||
const result = await service.listForTenant('t1', 'u1');
|
||||
expect(result.instances.map((i) => i.status.pendingRetry)).toEqual([false, true, false]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user