chore: add husky pre-commit hook and lint-staged configuration
- Created a pre-commit hook in .husky to run lint-staged. - Added .lintstagedrc.mjs to format frontend files with Prettier and apply Spotless to backend Java files. - Introduced .prettierignore to exclude specific directories from formatting.
This commit is contained in:
1 parent
5d2d85aa8c
commit
878da155bd
564 files changed
+61467
-47179
No files matched your search
+164
-164
@@ -4,189 +4,189 @@ import { MatDialog } from '@angular/material/dialog';
|
||||
import { of, Subject, throwError } from 'rxjs';
|
||||
import { vi } from 'vitest';
|
||||
import {
|
||||
FinalizeLeaseRequest,
|
||||
FinalizationMetadata,
|
||||
LeaseFinalizationService,
|
||||
LeaseFinalizationState,
|
||||
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() };
|
||||
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 },
|
||||
],
|
||||
});
|
||||
});
|
||||
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();
|
||||
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');
|
||||
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');
|
||||
});
|
||||
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();
|
||||
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();
|
||||
});
|
||||
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,
|
||||
};
|
||||
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);
|
||||
fixture.componentInstance.requestFinalization(request);
|
||||
fixture.componentInstance.requestFinalization(request);
|
||||
|
||||
expect(dialog.open).toHaveBeenCalledOnce();
|
||||
expect(finalize).toHaveBeenCalledWith('lease-1', request);
|
||||
expect(fixture.componentInstance.submitting()).toBe(true);
|
||||
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();
|
||||
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();
|
||||
});
|
||||
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();
|
||||
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();
|
||||
});
|
||||
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(() => ({ 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,
|
||||
};
|
||||
it('keeps a finalization failure actionable without showing success', () => {
|
||||
finalize.mockReturnValue(throwError(() => ({ 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();
|
||||
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);
|
||||
});
|
||||
expect(fixture.nativeElement.textContent).toContain('PDF signé refusé');
|
||||
expect(fixture.nativeElement.textContent).not.toContain('désormais immuable');
|
||||
expect(fixture.componentInstance.submitting()).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user