Skip to content

Commit 0c649a8

Browse files
committed
Set up comprehensive test suite with pytest and vitest
- Replace Jest with pytest for Python Lambda handler tests (70 tests) - Add vitest for API integration tests against production (20 tests) - Migrate to ESM in CDK TypeScript files - Update handler.py: fix speaker field mapping (role→title), add ecosystem search, improve Content-Type headers with charset=utf-8 - Add Python test infrastructure: conftest.py with S3 mocking, fixtures for sessions/speakers data - Update .gitignore for Python cache files and coverage reports - Remove old Jest configuration and llms.txt root duplicate - Add test documentation with coverage reporting capability [skip-cd]
1 parent 301a41c commit 0c649a8

26 files changed

+2768
-570
lines changed

cdk/.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,13 @@ node_modules
66
# CDK asset staging directory
77
.cdk.staging
88
cdk.out
9+
10+
# Python
11+
.venv/
12+
__pycache__/
13+
*.pyc
14+
*.pyo
15+
*.pyd
16+
.pytest_cache/
17+
.coverage
18+
htmlcov/

cdk/bin/cdk.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env node
22
import * as cdk from 'aws-cdk-lib';
3-
import { AdoptaiStack } from '../lib/adoptai-stack';
3+
import { AdoptaiStack } from '../lib/adoptai-stack.js';
44
import { AwsSolutionsChecks } from 'cdk-nag';
55

66
const app = new cdk.App();

cdk/jest.config.js

Lines changed: 0 additions & 8 deletions
This file was deleted.

cdk/lib/adoptai-stack.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,14 @@ import * as logs from 'aws-cdk-lib/aws-logs';
1111
import { PythonFunction } from '@aws-cdk/aws-lambda-python-alpha';
1212
import { Construct } from 'constructs';
1313
import * as path from 'path';
14+
import { fileURLToPath } from 'url';
15+
import { dirname } from 'path';
1416
import { NagSuppressions } from 'cdk-nag';
1517

18+
// ESM equivalent of __dirname
19+
const __filename = fileURLToPath(import.meta.url);
20+
const __dirname = dirname(__filename);
21+
1622
export interface AdoptaiStackProps extends cdk.StackProps {
1723
domainName?: string;
1824
hostedZoneDomain?: string;

cdk/lib/lambda/handler.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -212,13 +212,13 @@ def filter_sessions(sessions: list[dict], params: dict) -> list[dict]:
212212
filtered = [
213213
s for s in filtered
214214
if search_lower in s.get("title", "").lower() or
215-
search_lower in s.get("stage", "").lower() or
216215
any(
217216
search_lower in sp.get("name", "").lower() or
218217
search_lower in sp.get("company", "").lower() or
219-
search_lower in sp.get("role", "").lower()
218+
search_lower in sp.get("title", "").lower()
220219
for sp in s.get("speakers", [])
221-
)
220+
) or
221+
any(search_lower in eco.lower() for eco in s.get("ecosystems", []))
222222
]
223223

224224
return filtered
@@ -235,7 +235,7 @@ def filter_speakers(speakers: list[dict], params: dict) -> list[dict]:
235235
sp for sp in filtered
236236
if search_lower in sp.get("name", "").lower() or
237237
search_lower in sp.get("company", "").lower() or
238-
search_lower in sp.get("role", "").lower()
238+
search_lower in sp.get("title", "").lower()
239239
]
240240

241241
return filtered
@@ -245,13 +245,15 @@ def create_response(status_code: int, body: Any, content_type: str = "applicatio
245245
"""Create HTTP response"""
246246
if content_type == "application/json":
247247
body_str = json.dumps(body, ensure_ascii=False)
248+
content_type_header = "application/json; charset=utf-8"
248249
else:
249250
body_str = str(body)
251+
content_type_header = f"{content_type}; charset=utf-8"
250252

251253
return {
252254
"statusCode": status_code,
253255
"headers": {
254-
"Content-Type": content_type,
256+
"Content-Type": content_type_header,
255257
"Access-Control-Allow-Origin": "*",
256258
"Access-Control-Allow-Methods": "GET, OPTIONS",
257259
"Access-Control-Allow-Headers": "Content-Type, Authorization",

cdk/package.json

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
{
22
"name": "cdk",
33
"version": "0.1.0",
4+
"type": "module",
45
"bin": {
56
"cdk": "bin/cdk.js"
67
},
78
"scripts": {
89
"build": "tsc",
910
"watch": "tsc -w",
10-
"test": "jest",
11+
"test": "uv run pytest test/python/",
12+
"test:prod": "vitest run",
13+
"test:html": "uv run pytest test/python/ --cov=lib/lambda --cov-report=html",
1114
"cdk": "cdk"
1215
},
1316
"devDependencies": {
14-
"@types/jest": "^29.5.14",
1517
"@types/node": "22.7.9",
1618
"aws-cdk": "2.1031.2",
1719
"biome": "^0.3.3",
18-
"jest": "^29.7.0",
19-
"ts-jest": "^29.2.5",
2020
"ts-node": "^10.9.2",
21-
"typescript": "~5.6.3"
21+
"typescript": "~5.6.3",
22+
"vitest": "^2.1.8"
2223
},
2324
"dependencies": {
2425
"@aws-cdk/aws-lambda-python-alpha": "^2.225.0-alpha.0",

cdk/pytest.ini

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[pytest]
2+
testpaths = test/python
3+
python_files = test_*.py
4+
python_classes = Test*
5+
python_functions = test_*
6+
addopts = -v --tb=short
7+
cache_dir = .pytest_cache
8+
pythonpath = lib/lambda

cdk/requirements-dev.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pytest
2+
pytest-cov
3+
moto[s3]
4+
tzdata

cdk/test/README.md

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# AdoptAI API Tests
2+
3+
Suite de tests complète pour valider l'API AdoptAI déployée et son handler Lambda.
4+
5+
## Structure
6+
7+
```
8+
test/
9+
├── python/ # Tests unitaires Python
10+
│ ├── conftest.py # Fixtures pytest (mocks S3)
11+
│ ├── test_endpoints.py # Tests des endpoints
12+
│ ├── test_filters.py # Tests des filtres
13+
│ ├── test_helpers.py # Tests des fonctions utilitaires
14+
│ └── test_error_handling.py # Tests de gestion d'erreurs
15+
├── api.integration.test.ts # Tests en production (API déployée)
16+
└── README.md # Cette documentation
17+
```
18+
19+
## Prérequis
20+
21+
### TypeScript (Vitest)
22+
```bash
23+
cd cdk
24+
yarn install
25+
```
26+
27+
### Python (pytest avec uv)
28+
```bash
29+
cd cdk
30+
31+
# Créer l'environnement virtuel
32+
uv venv
33+
34+
# Installer les dépendances
35+
uv pip install -r requirements-dev.txt
36+
```
37+
38+
## Exécution des tests
39+
40+
### Tests unitaires
41+
42+
```bash
43+
cd cdk
44+
yarn test
45+
```
46+
47+
**Ce qui est testé** :
48+
- ✅ Tests Lambda Python (pytest) - 70 tests
49+
- Endpoints et structure des réponses
50+
- Logique de filtrage (date, stage, time, search, now)
51+
- Fonctions utilitaires (parse_time, datetime)
52+
- Gestion d'erreurs et caching
53+
54+
**Total** : 70 tests unitaires
55+
56+
### Tests en production
57+
58+
```bash
59+
cd cdk
60+
yarn test:prod
61+
```
62+
63+
**Ce qui est testé** :
64+
- ✅ Appels HTTP réels à https://adoptai.codecrafter.fr/
65+
- ✅ Tous les endpoints (/, /llms.txt, /robots.txt, /health, /sessions, /speakers, 404)
66+
- ✅ Paramètres de filtrage (date, stage, time, search, now)
67+
- ✅ Validation structure des réponses
68+
- ✅ Headers CORS et Content-Type UTF-8
69+
- ✅ Encodage des caractères spéciaux (•)
70+
71+
**Total** : 20 tests d'intégration
72+
73+
### Rapport de couverture HTML (optionnel)
74+
75+
```bash
76+
cd cdk
77+
yarn test:html
78+
```
79+
80+
Génère un rapport HTML de couverture pour le code Python.
81+
**Ouvrir** : `cdk/lib/lambda/htmlcov/index.html`
82+
83+
## Description des tests
84+
85+
### Tests unitaires Python (70 tests)
86+
87+
#### test_endpoints.py (~10 tests)
88+
- GET / et /llms.txt retournent la documentation
89+
- GET /robots.txt retourne robots.txt
90+
- GET /health retourne status healthy
91+
- GET /sessions retourne toutes les sessions
92+
- GET /speakers retourne tous les speakers
93+
- 404 pour chemins inconnus
94+
- OPTIONS retourne headers CORS
95+
- Les champs internes (_start_dt, _end_dt) ne sont pas exposés
96+
97+
#### test_filters.py (~35 tests)
98+
- Filtrage par date (2025-11-25, 2025-11-26)
99+
- Filtrage par stage (case-insensitive)
100+
- Filtrage par time (morning/afternoon)
101+
- Recherche par texte (titre, speaker, company)
102+
- Filtres combinés
103+
- Paramètre `now` (sessions en cours et à venir dans 30 min)
104+
- Recherche speakers
105+
106+
#### test_helpers.py (~27 tests)
107+
- parse_time: conversion heure → minutes (9:00 AM → 540)
108+
- parse_session_datetime: parsing date + heure → datetime Paris
109+
- get_paris_now: retourne datetime timezone Paris
110+
- filter_sessions_by_now: détection sessions en cours/à venir
111+
- Edge cases: midnight, noon, sessions sans end time
112+
113+
#### test_error_handling.py (~13 tests)
114+
- Erreurs S3 (ClientError, JSON invalide)
115+
- Fallback llms.txt quand fichier absent
116+
- Caching des données (sessions, speakers, llms.txt)
117+
- Sessions/speakers avec champs manquants
118+
- Query strings malformées
119+
120+
### Tests d'intégration API (20 tests)
121+
122+
#### GET / et /llms.txt (~2 tests)
123+
- Documentation retournée avec UTF-8 correct
124+
- Caractères spéciaux (•) affichés correctement
125+
126+
#### GET /sessions (~13 tests)
127+
- Toutes les sessions (243)
128+
- Filtrage date, stage, time, search
129+
- Paramètre now (sessions en cours/à venir)
130+
- Filtres combinés
131+
- Validation structure JSON
132+
- Résultats vides
133+
134+
#### GET /speakers (~2 tests)
135+
- Tous les speakers (499)
136+
- Recherche speakers
137+
138+
#### Autres endpoints (~3 tests)
139+
- /robots.txt, /health, 404
140+
- Headers CORS (OPTIONS)
141+
142+
## Statistiques
143+
144+
- **Total tests** : 90 tests
145+
- 70 tests unitaires (Python Lambda)
146+
- 20 tests d'intégration (API production)
147+
- **Couverture** : Tous les endpoints et cas d'usage documentés dans llms.txt
148+
149+
## Commandes rapides
150+
151+
```bash
152+
# Tests unitaires (Python Lambda)
153+
yarn test
154+
155+
# Tests API en production
156+
yarn test:prod
157+
158+
# Rapport de couverture HTML
159+
yarn test:html
160+
```
161+
162+
## CI/CD
163+
164+
Pour intégrer dans un pipeline CI/CD :
165+
166+
```yaml
167+
# .github/workflows/test.yml
168+
- name: Install dependencies
169+
run: |
170+
cd cdk
171+
yarn install
172+
uv venv
173+
uv pip install -r requirements-dev.txt
174+
175+
- name: Run unit tests
176+
run: |
177+
cd cdk
178+
yarn test
179+
180+
- name: Run production tests
181+
run: |
182+
cd cdk
183+
yarn test:prod
184+
```

0 commit comments

Comments
 (0)