fix: Berechtigungen der Tagesübersicht, WebDAV-Ausbau, Tests instand gesetzt
Build and Push Multi-Platform Images / build-and-push (push) Successful in 55s
Build and Push Multi-Platform Images / build-and-push (push) Successful in 55s
Die tägliche E-Mail-Zusammenfassung zeigte Nutzern Bereiche, für die sie
keine Berechtigung haben. Zwei unabhängige Ursachen:
- Das Backend prüfte weiterhin die Altgruppe PM_Belege. Die Umbenennung
zu PM_Buchhaltung (ca1d371) war nur im Frontend angekommen, weshalb
der Digest "Manuell bearbeiten" und "In Agrarmonitor" anbot, während
die Oberfläche beide Bereiche sperrte.
- Der Cron-Versand wertet die Gruppen aus user_settings aus. Diese Spalte
wurde nur beim Aufruf der Benutzereinstellungen gefüllt; entzogene
Berechtigungen erreichten den Digest daher unter Umständen nie.
Behoben durch Angleichen des Gruppen-Mappings und den neuen
UserIdentitySyncService, der E-Mail, Benutzername und Gruppen bei jedem
authentifizierten Request aus dem Token spiegelt – ohne den Request zu
blockieren und ohne DB-Zugriff, solange sich das Token nicht ändert. Die
doppelte Identitätspflege im UserSettingsService entfällt.
WebDAV wird nicht eingesetzt und ist entfernt; Export-Ziele bieten nur
noch FTP. Damit verschwindet das ESM-Paket webdav, an dem zwei
Jest-Suites bereits beim Parsen scheiterten.
Veraltete Tests instand gesetzt: email.controller und settings.controller
mockten weniger Abhängigkeiten, als die Klassen inzwischen haben;
postprocessing.service.spec beschrieb noch das alte Regelmodell mit
Einzelfeldern statt FilterJson und ist gegen die heutige Filter-Engine
neu geschrieben (AND/OR, verschachtelte Gruppen, Fehlerprotokollierung).
Enthält außerdem den Arbeitsstand der E-Rechnungs-Mandantenzuordnung, da
sich beide Änderungen dieselben Dateien teilen (SettingsPage, package.json).
98 Tests in 12 Suites grün, Backend- und Frontend-Build sauber.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,10 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { EmailController } from './email.controller';
|
||||
import { Email } from '../database/entities/email.entity';
|
||||
import { Attachment } from '../database/entities/attachment.entity';
|
||||
import { Content } from '../database/entities/content.entity';
|
||||
import { PaperlessService } from '../paperless/paperless.service';
|
||||
import { ImapFolderService } from './imap-folder.service';
|
||||
|
||||
const mockEmails: Partial<Email>[] = [
|
||||
{
|
||||
@@ -26,25 +30,56 @@ const mockEmails: Partial<Email>[] = [
|
||||
},
|
||||
];
|
||||
|
||||
const mockQueryBuilder = {
|
||||
orderBy: jest.fn().mockReturnThis(),
|
||||
take: jest.fn().mockReturnThis(),
|
||||
where: jest.fn().mockReturnThis(),
|
||||
getMany: jest.fn().mockResolvedValue(mockEmails),
|
||||
};
|
||||
|
||||
const mockRepo = {
|
||||
createQueryBuilder: jest.fn().mockReturnValue(mockQueryBuilder),
|
||||
findOneByOrFail: jest.fn().mockResolvedValue(mockEmails[0]),
|
||||
};
|
||||
const mockAttachments: Partial<Attachment>[] = [
|
||||
{ Id: 10, EmailMessageId: 1, FileName: 'rechnung.pdf' },
|
||||
];
|
||||
|
||||
describe('EmailController', () => {
|
||||
let controller: EmailController;
|
||||
let queryBuilder: {
|
||||
leftJoinAndSelect: jest.Mock;
|
||||
orderBy: jest.Mock;
|
||||
take: jest.Mock;
|
||||
where: jest.Mock;
|
||||
getMany: jest.Mock;
|
||||
};
|
||||
let emailRepo: {
|
||||
createQueryBuilder: jest.Mock;
|
||||
findOneOrFail: jest.Mock;
|
||||
save: jest.Mock;
|
||||
};
|
||||
let attachmentRepo: { find: jest.Mock; findOne: jest.Mock };
|
||||
|
||||
beforeEach(async () => {
|
||||
queryBuilder = {
|
||||
leftJoinAndSelect: jest.fn().mockReturnThis(),
|
||||
orderBy: jest.fn().mockReturnThis(),
|
||||
take: jest.fn().mockReturnThis(),
|
||||
where: jest.fn().mockReturnThis(),
|
||||
getMany: jest.fn().mockResolvedValue(mockEmails),
|
||||
};
|
||||
emailRepo = {
|
||||
createQueryBuilder: jest.fn().mockReturnValue(queryBuilder),
|
||||
findOneOrFail: jest.fn().mockResolvedValue({ ...mockEmails[0] }),
|
||||
save: jest.fn().mockImplementation((e: Email) => Promise.resolve(e)),
|
||||
};
|
||||
attachmentRepo = {
|
||||
find: jest.fn().mockResolvedValue(mockAttachments),
|
||||
findOne: jest.fn().mockResolvedValue(mockAttachments[0]),
|
||||
};
|
||||
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [EmailController],
|
||||
providers: [{ provide: getRepositoryToken(Email), useValue: mockRepo }],
|
||||
providers: [
|
||||
{ provide: getRepositoryToken(Email), useValue: emailRepo },
|
||||
{ provide: getRepositoryToken(Attachment), useValue: attachmentRepo },
|
||||
{
|
||||
provide: getRepositoryToken(Content),
|
||||
useValue: { findOne: jest.fn() },
|
||||
},
|
||||
{ provide: PaperlessService, useValue: {} },
|
||||
{ provide: ImapFolderService, useValue: {} },
|
||||
],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<EmailController>(EmailController);
|
||||
@@ -57,19 +92,52 @@ describe('EmailController', () => {
|
||||
it('getEmails returns list', async () => {
|
||||
const result = await controller.getEmails();
|
||||
expect(result).toHaveLength(2);
|
||||
expect(mockQueryBuilder.orderBy).toHaveBeenCalledWith('e.Date', 'DESC');
|
||||
expect(queryBuilder.orderBy).toHaveBeenCalledWith('e.Date', 'DESC');
|
||||
expect(queryBuilder.leftJoinAndSelect).toHaveBeenCalledWith(
|
||||
'e.Attachments',
|
||||
'a',
|
||||
);
|
||||
});
|
||||
|
||||
it('getEmails begrenzt ohne Angabe auf 50 Einträge', async () => {
|
||||
await controller.getEmails();
|
||||
expect(queryBuilder.take).toHaveBeenCalledWith(50);
|
||||
});
|
||||
|
||||
it('getEmails filters by status', async () => {
|
||||
await controller.getEmails('1');
|
||||
expect(mockQueryBuilder.where).toHaveBeenCalledWith('e.Status = :status', {
|
||||
expect(queryBuilder.where).toHaveBeenCalledWith('e.Status = :status', {
|
||||
status: 1,
|
||||
});
|
||||
});
|
||||
|
||||
it('getEmails filtert nicht ohne Status', async () => {
|
||||
await controller.getEmails();
|
||||
expect(queryBuilder.where).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('getEmail returns single item', async () => {
|
||||
const result = await controller.getEmail('1');
|
||||
expect(result).toEqual(mockEmails[0]);
|
||||
expect(mockRepo.findOneByOrFail).toHaveBeenCalledWith({ Id: 1 });
|
||||
expect(emailRepo.findOneOrFail).toHaveBeenCalledWith({
|
||||
where: { Id: 1 },
|
||||
relations: ['Attachments'],
|
||||
});
|
||||
});
|
||||
|
||||
it('getAttachments lädt die Anhänge einer E-Mail', async () => {
|
||||
const result = await controller.getAttachments('1');
|
||||
expect(result).toEqual(mockAttachments);
|
||||
expect(attachmentRepo.find).toHaveBeenCalledWith({
|
||||
where: { EmailMessageId: 1 },
|
||||
order: { Id: 'ASC' },
|
||||
});
|
||||
});
|
||||
|
||||
it('updateStatus speichert den neuen Status', async () => {
|
||||
await controller.updateStatus('1', 2);
|
||||
expect(emailRepo.save).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ Id: 1, Status: 2 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user