fix: renvoie un 404 sur une URL d'API inconnue

L'API et l'interface partagent le même port, et la route attrape-tout qui
sert le SPA passait avant le 404 : n'importe quelle URL /api/* erronée
repartait en index.html avec un 200. Côté client, une faute de frappe
dans une URL ne ressemblait pas à une erreur mais à une réponse vide, et
le vrai motif n'apparaissait qu'en inspectant le corps de la réponse.

Les URL commençant par /api/ sont désormais exclues de l'attrape-tout et
retombent sur un 404 JSON. Les routes de l'interface restent servies par
index.html.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 09:57:06 +02:00
parent 49e060e6e4
commit 5a132b1258
2 changed files with 49 additions and 1 deletions

View File

@@ -3,7 +3,7 @@
import mimetypes
from contextlib import asynccontextmanager
from fastapi import FastAPI
from fastapi import FastAPI, HTTPException
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
@@ -87,6 +87,13 @@ if FRONTEND_DIST.exists():
@app.get("/{full_path:path}", include_in_schema=False)
async def serve_spa(full_path: str):
"""Serve the SPA for all non-API routes."""
# Une URL d'API inconnue doit se dire inconnue. Sans cette garde, elle
# tomberait dans le catch-all et repartirait en index.html avec un 200 :
# côté client, une faute de frappe dans une URL ne ressemblerait plus à
# une erreur mais à une réponse vide.
if full_path == "api" or full_path.startswith("api/"):
raise HTTPException(status_code=404, detail="Endpoint inconnu")
# If requesting a file that exists (and stays within dist), serve it
file_path = (FRONTEND_DIST / full_path).resolve()
if file_path.is_file() and file_path.is_relative_to(_dist_root):

41
tests/test_routage_spa.py Normal file
View File

@@ -0,0 +1,41 @@
"""Tests du partage des URL entre l'API et le SPA.
L'application sert l'API et l'interface sur le meme port : une route attrape
tout ce qui ne correspond a aucun endpoint pour le renvoyer au routeur Vue. Elle
ne doit pas avaler les URL d'API, sous peine de transformer une adresse erronee
en page HTML repondue avec un 200.
"""
import pytest
from plesna_gerance.api.app import FRONTEND_DIST
def test_une_url_d_api_inconnue_donne_404(api_client):
response = api_client.get("/api/nexiste-pas")
assert response.status_code == 404
assert response.headers["content-type"].startswith("application/json")
def test_une_url_d_api_inconnue_ne_renvoie_pas_le_spa(api_client):
"""Meme forme que les vraies routes : le prefixe seul ne suffit pas."""
response = api_client.get("/api/revenus/inconnu")
assert response.status_code == 404
def test_les_routes_d_api_existantes_repondent(api_client):
assert api_client.get("/api/health").status_code == 200
assert api_client.get("/api/tags").status_code == 200
@pytest.mark.skipif(
not FRONTEND_DIST.exists(), reason="frontend non construit (frontend/dist)"
)
def test_une_route_du_spa_renvoie_l_interface(api_client):
"""Les URL de l'interface restent servies par index.html."""
response = api_client.get("/documents")
assert response.status_code == 200
assert response.headers["content-type"].startswith("text/html")