- Honey batch create omits an empty batch number (was 400); product form
labels the legacy total weight, detail shows jar weight and shelf life;
bottling hints next to their fields, values cleared on product change,
post-save water-content wording; withdraw reason placeholder not
selectable; honey batch actions offline-disabled with reason, 44 px.
- Offline signal: one slow request no longer flips the app offline; two
consecutive timeouts do, any HTTP answer clears it, aborts never count.
- Phenology sheet as a side card on desktop with focus return; confirm
dialogs as alertdialog; step-up texts in du-form ("Das Passwort stimmt
nicht."); backup import gives its disabled reason; missing event labels
plus a guard test; profile buttons 44 px.
- GET /phenology/state returns intervalDays via the shared
inspectionIntervalOf (autopilot, digest, season state use one source).
- T6 plan aligned with the code after T4, plus owner decisions of
2026-10-15 (Berlin day for restrictions, COMPLIANCE category, override
without step-up).
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
62 lines
2.0 KiB
TypeScript
62 lines
2.0 KiB
TypeScript
import { UnauthorizedException } from '@nestjs/common';
|
|
import * as bcrypt from 'bcryptjs';
|
|
import { ReauthService } from './reauth.service';
|
|
|
|
describe('ReauthService', () => {
|
|
const baseUser = {
|
|
id: 'u1',
|
|
email: '[email protected]',
|
|
passwordHash: bcrypt.hashSync('richtig', 4),
|
|
mfaEnabled: false,
|
|
mfaSecret: null,
|
|
mfaLastTimeStep: null,
|
|
authLockedUntil: null,
|
|
isActive: true,
|
|
};
|
|
const accountGuard = {
|
|
isLocked: jest.fn(),
|
|
recordFailure: jest.fn(),
|
|
recordSuccess: jest.fn(),
|
|
consumeTotp: jest.fn(),
|
|
};
|
|
const prisma = { user: { findUnique: jest.fn() } };
|
|
const service = new ReauthService(prisma as never, accountGuard as never);
|
|
|
|
beforeEach(() => {
|
|
jest.resetAllMocks();
|
|
prisma.user.findUnique.mockResolvedValue(baseUser);
|
|
accountGuard.isLocked.mockReturnValue(false);
|
|
});
|
|
|
|
it('sagt bei falschem Passwort, dass es nicht stimmt', async () => {
|
|
await expect(service.verify('u1', { password: 'falsch' })).rejects.toThrow(
|
|
new UnauthorizedException('Das Passwort stimmt nicht.'),
|
|
);
|
|
expect(accountGuard.recordFailure).toHaveBeenCalledWith('u1');
|
|
});
|
|
|
|
it('sagt bei falschem Code, dass er nicht stimmt', async () => {
|
|
prisma.user.findUnique.mockResolvedValue({ ...baseUser, mfaEnabled: true });
|
|
accountGuard.consumeTotp.mockResolvedValue(false);
|
|
await expect(
|
|
service.verify('u1', { password: 'richtig', mfaCode: '000000' }),
|
|
).rejects.toThrow(new UnauthorizedException('Der Code stimmt nicht.'));
|
|
});
|
|
|
|
it('nennt bei gesperrtem Konto die Sperre', async () => {
|
|
accountGuard.isLocked.mockReturnValue(true);
|
|
await expect(service.verify('u1', { password: 'richtig' })).rejects.toThrow(
|
|
new UnauthorizedException(
|
|
'Zu viele Fehlversuche. Bestätigen ist für 15 Minuten gesperrt.',
|
|
),
|
|
);
|
|
});
|
|
|
|
it('lässt richtige Angaben durch', async () => {
|
|
await expect(
|
|
service.verify('u1', { password: 'richtig' }),
|
|
).resolves.toBeUndefined();
|
|
expect(accountGuard.recordSuccess).toHaveBeenCalledWith('u1');
|
|
});
|
|
});
|