195 lines
6.7 KiB
TypeScript
195 lines
6.7 KiB
TypeScript
import { TestBed } from '@angular/core/testing';
|
|
import { ActivatedRoute, provideRouter } from '@angular/router';
|
|
import { MatDialog } from '@angular/material/dialog';
|
|
import { of, Subject, throwError } from 'rxjs';
|
|
import { vi } from 'vitest';
|
|
import {
|
|
FinalizeLeaseRequest,
|
|
FinalizationMetadata,
|
|
LeaseFinalizationService,
|
|
LeaseFinalizationState,
|
|
} from '../../services/lease-finalization.service';
|
|
import { WorkspaceService } from '../../services/workspace.service';
|
|
import { LeaseFinalizationPage } from './lease-finalization-page';
|
|
|
|
describe('LeaseFinalizationPage', () => {
|
|
const revision = {
|
|
number: 4,
|
|
templateVersion: '2026.10',
|
|
sourceFingerprint: 'source',
|
|
sha256: 'sha256',
|
|
generatedAt: '2026-07-19T10:00:00Z',
|
|
authorId: 'owner-1',
|
|
furnitureInventory: { templateVersion: 'FR-MEUBLE-2025-01', lines: [] },
|
|
privateAreas: [],
|
|
annexes: [],
|
|
warningCodes: [],
|
|
warnings: [],
|
|
};
|
|
const state: LeaseFinalizationState = {
|
|
revision,
|
|
guaranteeSimulatorUrl: 'https://www.service-public.fr/simulateur',
|
|
warningCodes: ['SECURITY_DEPOSIT_ABOVE_TWO_MONTHS'],
|
|
warnings: [{ code: 'SECURITY_DEPOSIT_ABOVE_TWO_MONTHS', ruleVersion: '2026-07-01' }],
|
|
guaranteeRequirements: [],
|
|
suggestedInventoryCorrections: [],
|
|
finalization: null,
|
|
};
|
|
const finalization: FinalizationMetadata = {
|
|
revisionNumber: 4,
|
|
templateVersion: '2026.10',
|
|
lastSignatureDate: '2026-07-19',
|
|
signedFileName: 'bail-signe.pdf',
|
|
signedSha256: 'signed-sha256',
|
|
authorId: 'owner-1',
|
|
finalizedAt: '2026-07-19T10:30:00Z',
|
|
overrides: [],
|
|
entryInventoryDiscrepancies: false,
|
|
inventoryCorrections: [],
|
|
allInventoryCorrectionsInitialed: false,
|
|
};
|
|
const lease = {
|
|
id: 'lease-1',
|
|
apartmentId: 'apartment-1',
|
|
tenantIds: ['tenant-1'],
|
|
guarantors: [],
|
|
specialClauses: [],
|
|
leaseType: 'STANDARD' as const,
|
|
startDate: '2026-07-01',
|
|
endDate: '2027-06-30',
|
|
monthlyRent: 800,
|
|
monthlyCharges: 50,
|
|
securityDeposit: 800,
|
|
dueDay: 5,
|
|
chargeMethod: 'FLAT_RATE' as const,
|
|
indexFlatRate: false,
|
|
status: 'ACTIVE' as const,
|
|
};
|
|
const getState = vi.fn();
|
|
const uploadGuaranteeDeed = vi.fn();
|
|
const finalize = vi.fn();
|
|
const workspace = { acceptLeaseFinalization: vi.fn() };
|
|
const dialog = { open: vi.fn() };
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
getState.mockReturnValue(of(state));
|
|
dialog.open.mockReturnValue({ afterClosed: () => of(true) });
|
|
TestBed.configureTestingModule({
|
|
providers: [
|
|
provideRouter([]),
|
|
{
|
|
provide: ActivatedRoute,
|
|
useValue: { snapshot: { paramMap: { get: () => 'lease-1' } } },
|
|
},
|
|
{
|
|
provide: LeaseFinalizationService,
|
|
useValue: { getState, uploadGuaranteeDeed, finalize },
|
|
},
|
|
{ provide: WorkspaceService, useValue: workspace },
|
|
{ provide: MatDialog, useValue: dialog },
|
|
],
|
|
});
|
|
});
|
|
|
|
it('shows a skeleton, then a retryable load error', () => {
|
|
const response = new Subject<LeaseFinalizationState>();
|
|
getState.mockReturnValueOnce(response);
|
|
const fixture = TestBed.createComponent(LeaseFinalizationPage);
|
|
fixture.detectChanges();
|
|
expect(fixture.nativeElement.querySelector('.finalization-skeleton')).not.toBeNull();
|
|
|
|
response.error({ error: { message: 'Bail introuvable' } });
|
|
fixture.detectChanges();
|
|
expect(fixture.nativeElement.textContent).toContain('Bail introuvable');
|
|
|
|
getState.mockReturnValueOnce(of(state));
|
|
fixture.nativeElement.querySelector('.load-error button').click();
|
|
fixture.detectChanges();
|
|
expect(getState).toHaveBeenCalledTimes(2);
|
|
expect(fixture.nativeElement.textContent).toContain('Révision 4 · modèle 2026.10');
|
|
});
|
|
|
|
it('instructs the landlord to generate a revision when none exists', () => {
|
|
getState.mockReturnValueOnce(of({ ...state, revision: null }));
|
|
const fixture = TestBed.createComponent(LeaseFinalizationPage);
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement.textContent).toContain("Générez d'abord le bail");
|
|
expect(fixture.nativeElement.querySelector('app-lease-finalization-form')).toBeNull();
|
|
});
|
|
|
|
it('asks for final confirmation, prevents duplicates, and publishes success', () => {
|
|
const response = new Subject<{
|
|
lease: typeof lease;
|
|
rentDues: [];
|
|
finalization: FinalizationMetadata;
|
|
}>();
|
|
finalize.mockReturnValue(response);
|
|
const fixture = TestBed.createComponent(LeaseFinalizationPage);
|
|
fixture.detectChanges();
|
|
const request: FinalizeLeaseRequest = {
|
|
signedLease: new File(['lease'], 'bail-signe.pdf', { type: 'application/pdf' }),
|
|
revisionNumber: 4,
|
|
lastSignatureDate: '2026-07-19',
|
|
allPartiesSigned: true,
|
|
revisionMatches: true,
|
|
confirmedWarningCodes: ['SECURITY_DEPOSIT_ABOVE_TWO_MONTHS'],
|
|
entryInventoryDiscrepancies: false,
|
|
inventoryCorrections: [],
|
|
allInventoryCorrectionsInitialed: false,
|
|
};
|
|
|
|
fixture.componentInstance.requestFinalization(request);
|
|
fixture.componentInstance.requestFinalization(request);
|
|
|
|
expect(dialog.open).toHaveBeenCalledOnce();
|
|
expect(finalize).toHaveBeenCalledWith('lease-1', request);
|
|
expect(fixture.componentInstance.submitting()).toBe(true);
|
|
|
|
response.next({ lease, rentDues: [], finalization });
|
|
response.complete();
|
|
fixture.detectChanges();
|
|
|
|
expect(workspace.acceptLeaseFinalization).toHaveBeenCalledWith(lease, []);
|
|
expect(fixture.nativeElement.textContent).toContain('Bail finalisé et désormais immuable');
|
|
expect(fixture.nativeElement.querySelector('app-lease-finalization-form')).toBeNull();
|
|
});
|
|
|
|
it('renders an immutable summary when the lease was already finalized', () => {
|
|
getState.mockReturnValueOnce(of({ ...state, finalization }));
|
|
const fixture = TestBed.createComponent(LeaseFinalizationPage);
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement.textContent).toContain('bail-signe.pdf');
|
|
expect(fixture.nativeElement.textContent).toContain('signed-sha256');
|
|
expect(fixture.nativeElement.querySelector('input')).toBeNull();
|
|
});
|
|
|
|
it('keeps a finalization failure actionable without showing success', () => {
|
|
finalize.mockReturnValue(
|
|
throwError(() => ({ status: 409, error: { message: 'PDF signé refusé' } })),
|
|
);
|
|
const fixture = TestBed.createComponent(LeaseFinalizationPage);
|
|
fixture.detectChanges();
|
|
const request: FinalizeLeaseRequest = {
|
|
signedLease: new File(['lease'], 'bail-signe.pdf', { type: 'application/pdf' }),
|
|
revisionNumber: 4,
|
|
lastSignatureDate: '2026-07-19',
|
|
allPartiesSigned: true,
|
|
revisionMatches: true,
|
|
confirmedWarningCodes: ['SECURITY_DEPOSIT_ABOVE_TWO_MONTHS'],
|
|
entryInventoryDiscrepancies: false,
|
|
inventoryCorrections: [],
|
|
allInventoryCorrectionsInitialed: false,
|
|
};
|
|
|
|
fixture.componentInstance.requestFinalization(request);
|
|
fixture.detectChanges();
|
|
|
|
expect(fixture.nativeElement.textContent).toContain('PDF signé refusé');
|
|
expect(fixture.nativeElement.textContent).not.toContain('désormais immuable');
|
|
expect(fixture.componentInstance.submitting()).toBe(false);
|
|
});
|
|
});
|