feat: add unit and integration tests for rental management domain

- Introduced RentPaymentCorrectionTest and RentPaymentTest to validate payment and refund logic.
- Added PhoneNumberTest to ensure proper normalization of phone numbers.
- Created TenantTest to verify attachment management and tenant referencing.
- Implemented ReplacementRentalApiJourneyIT for end-to-end testing of rental and payment workflows.
- Documented architectural decisions in ADR-0014, emphasizing strict DDD boundaries and transaction management.
This commit is contained in:
nessar committed 2026-08-17 14:18:15 +02:00
1 parent 6ce0719a20
commit e02ad54bcb
263 files changed
+10847 -7183

No files matched your search

+4 -1
View File
@@ -179,12 +179,14 @@ test('keeps apartment add and edit workflows usable', async ({ page }) => {
});
test('keeps the secured API check and logout available', async ({ page }) => {
let logoutMethod = '';
await mockUser(page, true);
await mockEmptyApartmentList(page);
await page.route('**/api/hello', async (route) => {
await route.fulfill({ contentType: 'text/plain', body: 'Service disponible' });
});
await page.route('**/logout', async (route) => {
logoutMethod = route.request().method();
await route.fulfill({ contentType: 'text/html', body: '<h1>Signed out</h1>' });
});
@@ -194,7 +196,8 @@ test('keeps the secured API check and logout available', async ({ page }) => {
await page.getByTestId('primary-navigation').getByRole('button', { name: 'Réglages' }).click();
await page.getByTestId('session-settings').getByRole('button', { name: 'Déconnexion' }).click();
await expect(page).toHaveURL(/\/logout$/);
await expect.poll(() => logoutMethod).toBe('POST');
await expect(page).toHaveURL(/\/$/);
});
test('follows operating-system changes for the System theme', async ({ page }) => {
+4 -2
View File
@@ -1,8 +1,10 @@
@if (session.status() === 'checking') {
@if (session.status() === 'checking' || session.status() === 'signing-out') {
<section class="session-state" role="status" aria-live="polite">
<app-brand-mark class="brand-mark" />
<mat-spinner diameter="34" />
<strong>Vérification de votre session…</strong>
<strong>{{
session.status() === 'signing-out' ? 'Déconnexion…' : 'Vérification de votre session…'
}}</strong>
</section>
} @else if (session.status() === 'error') {
<section class="session-state" role="alert">
+1
View File
@@ -12,6 +12,7 @@ describe('App', () => {
username: signal(''),
email: signal(''),
refresh: vi.fn(),
logout: vi.fn(),
};
beforeEach(() => {
+1 -1
View File
@@ -37,6 +37,6 @@ export class App implements OnInit {
}
logout() {
window.location.href = '/logout';
this.session.logout();
}
}
@@ -16,6 +16,7 @@ describe('SettingsPage', () => {
const session = {
username: signal('Camille Martin'),
email: signal('camille@example.com'),
logout: vi.fn(),
};
const theme = {
preference: signal('system'),
@@ -48,9 +49,7 @@ describe('SettingsPage', () => {
await fixture.whenStable();
expect(fixture.nativeElement.querySelector('input#firstName').value).toBe('Camille');
expect(fixture.nativeElement.querySelector('input#email').value).toBe(
'camille@example.com',
);
expect(fixture.nativeElement.querySelector('input#email').value).toBe('camille@example.com');
expect(fixture.nativeElement.querySelector('input#address').value).toBe('1 rue de Paris');
});
@@ -66,7 +66,7 @@ export class SettingsPage {
}
logout(): void {
window.location.href = '/logout';
this.session.logout();
}
loadProfile(): void {
@@ -1,11 +1,11 @@
import { HttpClient } from '@angular/common/http';
import { TestBed } from '@angular/core/testing';
import { of, Subject, throwError } from 'rxjs';
import { NEVER, of, Subject, throwError } from 'rxjs';
import { vi } from 'vitest';
import { SessionService } from './session.service';
describe('SessionService', () => {
const http = { get: vi.fn() };
const http = { get: vi.fn(), post: vi.fn() };
beforeEach(() => {
vi.clearAllMocks();
@@ -53,4 +53,15 @@ describe('SessionService', () => {
expect(http.get).toHaveBeenCalledWith('/api/user');
expect(service.username()).toBe('');
});
it('posts logout with a truthful pending state', () => {
const service = TestBed.inject(SessionService);
service.status.set('authenticated');
http.post.mockReturnValueOnce(NEVER);
service.logout();
expect(service.status()).toBe('signing-out');
expect(http.post).toHaveBeenCalledWith('/logout', null, { responseType: 'text' });
});
});
+15 -1
View File
@@ -7,7 +7,7 @@ interface UserResponse {
email?: string;
}
export type SessionStatus = 'checking' | 'authenticated' | 'anonymous' | 'error';
export type SessionStatus = 'checking' | 'authenticated' | 'anonymous' | 'signing-out' | 'error';
@Injectable({ providedIn: 'root' })
export class SessionService {
@@ -32,4 +32,18 @@ export class SessionService {
},
});
}
logout(): void {
if (this.status() === 'signing-out') return;
this.status.set('signing-out');
this.http.post('/logout', null, { responseType: 'text' }).subscribe({
next: () => {
window.location.href = '/';
},
error: () => {
this.status.set('authenticated');
window.alert('Impossible de vous déconnecter. Réessayez.');
},
});
}
}