feat: protect referenced rental resources (#27)

This commit is contained in:
nessar committed 2026-08-17 14:18:15 +02:00
1 parent fca199dfdf
commit 971ffd6feb
23 files changed
+1453 -151

No files matched your search

@@ -1,5 +1,6 @@
import { signal } from '@angular/core';
import { finalize, Observable } from 'rxjs';
import { httpErrorMessage } from './http-error-message';
import { withoutRecordKey, withoutSetValue } from './immutable-state';
export interface LoadState {
@@ -69,8 +70,11 @@ export class WorkspaceResource<T extends { id?: string }> {
this.confirmChange((items) => items.filter((item) => item.id !== id));
this.load();
},
error: () =>
this.deleteErrors.update((errors) => ({ ...errors, [id]: this.messages.delete })),
error: (error) =>
this.deleteErrors.update((errors) => ({
...errors,
[id]: httpErrorMessage(error, this.messages.delete),
})),
});
}
@@ -97,7 +97,9 @@ describe('WorkspaceService', () => {
leases.deleteLease.mockReturnValue(of(undefined));
leases.downloadPdf.mockReturnValue(of(new Blob(['pdf'])));
rentPayments.getRentPayments.mockReturnValue(of([rentPayment]));
rentPayments.markPaid.mockReturnValue(of({ ...rentPayment, status: 'PAID', paidAt: '2026-01-02' }));
rentPayments.markPaid.mockReturnValue(
of({ ...rentPayment, status: 'PAID', paidAt: '2026-01-02' }),
);
rentPayments.downloadReceipt.mockReturnValue(of(new Blob(['receipt'])));
dialog.open.mockImplementation(() => ({ afterClosed: () => of(true) }));
vi.stubGlobal('URL', { createObjectURL: vi.fn(() => 'blob:test'), revokeObjectURL: vi.fn() });
@@ -247,8 +249,10 @@ describe('WorkspaceService', () => {
expect(service.apartmentLoadState().loading).toBe(true);
});
it('preserves a record and exposes a local error when deletion fails', () => {
apartments.deleteApartment.mockReturnValueOnce(throwError(() => new Error('offline')));
it('preserves a referenced apartment and exposes the conflict message when deletion fails', () => {
apartments.deleteApartment.mockReturnValueOnce(
throwError(() => ({ error: { message: 'Cet appartement est référencé par un bail.' } })),
);
const service = TestBed.inject(WorkspaceService);
service.apartments.set([apartment]);
@@ -257,7 +261,7 @@ describe('WorkspaceService', () => {
expect(service.apartments()).toEqual([apartment]);
expect(service.deletingApartmentIds().has(apartment.id)).toBe(false);
expect(service.apartmentDeleteErrors()[apartment.id]).toBe(
'Impossible de supprimer cet appartement.',
'Cet appartement est référencé par un bail.',
);
});
@@ -357,7 +361,11 @@ describe('WorkspaceService', () => {
expect(apartments.updateApartment).toHaveBeenCalledWith('apartment-1', apartment);
expect(tenants.updateTenant).toHaveBeenCalledWith('tenant-1', tenant);
expect(leases.deleteLease).toHaveBeenCalledWith('lease-1');
expect(rentPayments.markPaid).toHaveBeenCalledWith('payment-1', rentPayment.rent, rentPayment.charges);
expect(rentPayments.markPaid).toHaveBeenCalledWith(
'payment-1',
rentPayment.rent,
rentPayment.charges,
);
});
it('opens a confirmation pop-in before deleting a resource', () => {
@@ -429,7 +437,9 @@ describe('WorkspaceService', () => {
service.downloadLeasePdf(lease);
await vi.waitFor(() =>
expect(service.leaseDownloadError(lease.id)).toBe('Complétez votre profil dans les réglages.'),
expect(service.leaseDownloadError(lease.id)).toBe(
'Complétez votre profil dans les réglages.',
),
);
});
});