"""Tests du referentiel des logements. Ces caracteristiques sont saisies a la main : elles n'ont aucune autre source que l'utilisateur, donc rien ne les reconstituerait si un enregistrement les perdait. Les tests portent sur ce qui menace cette saisie : l'ecraser depuis un PDF, la vider a moitie, ou la laisser contredire l'extraction en silence. """ import pytest from plesna_gerance.database.models import Depense, Immeuble, Lot from plesna_gerance.database.service import DatabaseService @pytest.fixture def immeuble_et_lot(db_session, sample_data): """Un immeuble et son lot 01, tels que l'extraction les cree.""" DatabaseService(db_session).save_document(data=sample_data) immeuble = db_session.query(Immeuble).filter(Immeuble.code == "IMM1").one() lot = db_session.query(Lot).filter(Lot.immeuble_id == immeuble.id).one() return immeuble, lot FICHE = { "bat": "Rue", "etage": "RC", "type": "Loc. Commercial", "surface": 148.0, "surface_date_diag": "2019-06-01", "chauffage": "Electrique", "dpe_classe": "C", "dpe_date_realisation": "2021-02-08", "numero_fiscal": "690123456789", "surface_impots": 146.0, "note_impots": "Surface relevee sur l'avis 2024", } def test_liste_les_lots_sans_fiche(api_client, immeuble_et_lot): """Un lot jamais decrit doit apparaitre, sinon il n'est pas saisissable.""" immeuble, lot = immeuble_et_lot response = api_client.get(f"/api/immeubles/{immeuble.id}/lots/referentiel") assert response.status_code == 200 lignes = response.json() assert len(lignes) == 1 assert lignes[0]["id"] == lot.id assert lignes[0]["numero"] == "01" assert lignes[0]["caracteristiques"] is None assert lignes[0]["type_extrait"] == "Appartement" assert lignes[0]["type_effectif"] == "Appartement" assert lignes[0]["type_ecart"] is False def test_liste_un_immeuble_inconnu_donne_404(api_client): assert api_client.get("/api/immeubles/99999/lots/referentiel").status_code == 404 def test_enregistre_puis_relit_une_fiche(api_client, immeuble_et_lot): immeuble, lot = immeuble_et_lot response = api_client.put(f"/api/lots/{lot.id}/caracteristiques", json=FICHE) assert response.status_code == 200 fiche = response.json()["caracteristiques"] assert fiche["surface"] == 148.0 assert fiche["etage"] == "RC" assert fiche["numero_fiscal"] == "690123456789" relu = api_client.get(f"/api/immeubles/{immeuble.id}/lots/referentiel").json()[0] assert relu["caracteristiques"]["surface"] == 148.0 assert relu["caracteristiques"]["dpe_classe"] == "C" def test_expose_les_valeurs_derivees(api_client, immeuble_et_lot): """L'echeance du DPE et l'ecart de surface arrivent calcules.""" _, lot = immeuble_et_lot fiche = api_client.put(f"/api/lots/{lot.id}/caracteristiques", json=FICHE).json()[ "caracteristiques" ] assert fiche["dpe_echeance"] == "2031-02-08" assert fiche["delta_surface"] == -2.0 def test_une_seconde_ecriture_met_a_jour_sans_dupliquer(api_client, immeuble_et_lot): immeuble, lot = immeuble_et_lot api_client.put(f"/api/lots/{lot.id}/caracteristiques", json=FICHE) api_client.put( f"/api/lots/{lot.id}/caracteristiques", json={**FICHE, "surface": 150.0} ) lignes = api_client.get(f"/api/immeubles/{immeuble.id}/lots/referentiel").json() assert len(lignes) == 1 assert lignes[0]["caracteristiques"]["surface"] == 150.0 def test_un_champ_vide_efface_la_valeur(api_client, immeuble_et_lot): """Corriger une erreur de saisie doit pouvoir revenir a « inconnu ».""" _, lot = immeuble_et_lot api_client.put(f"/api/lots/{lot.id}/caracteristiques", json=FICHE) fiche = api_client.put( f"/api/lots/{lot.id}/caracteristiques", json={**FICHE, "etage": " ", "surface": None}, ).json()["caracteristiques"] assert fiche["etage"] is None assert fiche["surface"] is None assert fiche["delta_surface"] is None def test_refuse_une_classe_dpe_inconnue(api_client, immeuble_et_lot): _, lot = immeuble_et_lot response = api_client.put( f"/api/lots/{lot.id}/caracteristiques", json={**FICHE, "dpe_classe": "Z"} ) assert response.status_code == 422 def test_refuse_une_surface_negative(api_client, immeuble_et_lot): _, lot = immeuble_et_lot response = api_client.put( f"/api/lots/{lot.id}/caracteristiques", json={**FICHE, "surface": -10} ) assert response.status_code == 422 def test_ecrire_sur_un_lot_inconnu_donne_404(api_client): assert ( api_client.put("/api/lots/99999/caracteristiques", json=FICHE).status_code == 404 ) def test_signale_un_desaccord_de_type_sans_effacer_le_pdf(api_client, immeuble_et_lot): """Les deux types restent lisibles : la fiche tranche, le PDF reste visible.""" _, lot = immeuble_et_lot ligne = api_client.put( f"/api/lots/{lot.id}/caracteristiques", json={**FICHE, "type": "Appartement T3"} ).json() assert ligne["type_extrait"] == "Appartement" assert ligne["type_effectif"] == "Appartement T3" assert ligne["type_ecart"] is True def test_le_type_saisi_prime_dans_la_liste_des_lots(api_client, immeuble_et_lot): """La priorite du referentiel vaut partout, pas seulement sur sa page.""" _, lot = immeuble_et_lot api_client.put( f"/api/lots/{lot.id}/caracteristiques", json={**FICHE, "type": "Appartement T3"} ) lots = api_client.get("/api/lots").json() assert [ligne["type"] for ligne in lots] == ["Appartement T3"] def test_une_re_extraction_ne_touche_pas_la_fiche( api_client, immeuble_et_lot, db_session, sample_data ): """Le point critique : reimporter le PDF ne doit rien perdre de la saisie.""" immeuble, lot = immeuble_et_lot api_client.put(f"/api/lots/{lot.id}/caracteristiques", json=FICHE) DatabaseService(db_session).save_document(data=sample_data, overwrite=True) lignes = api_client.get(f"/api/immeubles/{immeuble.id}/lots/referentiel").json() fiche = next(ligne for ligne in lignes if ligne["id"] == lot.id)["caracteristiques"] assert fiche is not None, "la re-extraction a perdu la fiche du lot" assert fiche["surface"] == 148.0 def test_compte_les_rattachements_du_lot(api_client, immeuble_et_lot): """Un lot avec des revenus n'est pas un orphelin : la liste doit le dire.""" immeuble, lot = immeuble_et_lot ligne = api_client.get(f"/api/immeubles/{immeuble.id}/lots/referentiel").json()[0] assert ligne["nb_revenus"] == 1 # La depense de la fixture porte sur l'immeuble (lot_id NULL). La compter # ici rendrait tout lot de l'immeuble faussement non supprimable. assert ligne["nb_depenses"] == 0 def test_compte_les_depenses_propres_au_lot(api_client, immeuble_et_lot, db_session): """Une depense rattachee au lot, elle, doit bien remonter sur sa ligne.""" immeuble, lot = immeuble_et_lot depense = db_session.query(Depense).one() depense.lot_id = lot.id db_session.commit() ligne = api_client.get(f"/api/immeubles/{immeuble.id}/lots/referentiel").json()[0] assert ligne["nb_depenses"] == 1