Skip to content
Snippets Groups Projects
Select Git revision
  • ddfd94b0bab41fd01590b32c915c094dea81f42f
  • master default protected
2 results

test_assiduites.py

Blame
  • Forked from Jean-Marie Place / SCODOC_R6A06
    Source project has a limited visibility.
    test_assiduites.py 53.96 KiB
    # -*- mode: python -*-
    # -*- coding: utf-8 -*-
    
    """
    Tests unitaires vérifiant le bon fonctionnement du modèle Assiduité et de
    ses fonctions liées
    
    Ecrit par HARTMANN Matthias (en s'inspirant de tests.unit.test_abs_count.py par Fares Amer )
    """
    import pytest
    
    import app.scodoc.sco_assiduites as scass
    import app.scodoc.sco_utils as scu
    from app import db, log
    from app.models import (
        Assiduite,
        FormSemestre,
        Identite,
        Justificatif,
        ModuleImpl,
        Absence,
    )
    
    from app.scodoc.sco_exceptions import ScoValueError
    from tests.unit import sco_fake_gen
    from tools import downgrade_module, migrate_abs_to_assiduites
    
    import datetime as dt
    
    
    class BiInt(int, scu.BiDirectionalEnum):
        """Classe pour tester la classe BiDirectionalEnum"""
    
        A = 1
        B = 2
    
    
    def test_bi_directional_enum(test_client):
        """Test le bon fonctionnement de la classe BiDirectionalEnum"""
    
        assert BiInt.get("A") == BiInt.get("a") == BiInt.A == 1
        assert BiInt.get("B") == BiInt.get("b") == BiInt.B == 2
        assert BiInt.get("blabla") is None
        assert BiInt.get("blabla", -1) == -1
        assert isinstance(BiInt.inverse(), dict)
        assert BiInt.inverse()[1] == BiInt.A and BiInt.inverse()[2] == BiInt.B
    
    
    def test_general(test_client):
        """tests général du modèle assiduite"""
    
        data: dict = _setup_fake_db(
            dates_formsemestre=[
                ("01/09/2022", "31/12/2022"),
                ("01/01/2023", "31/07/2023"),
                ("01/01/2024", "31/07/2024"),
            ],
            nb_modules=2,
            nb_etuds=3,
        )
        etuds, moduleimpls, etud_faux, formsemestres = (
            data["etuds"],
            data["moduleimpls"],
            data["etud_faux"],
            data["formsemestres"],
        )
    
        verif_migration_abs_assiduites()
    
        ajouter_assiduites(etuds, moduleimpls, etud_faux)
        justificatifs: list[Justificatif] = ajouter_justificatifs(etuds[0])
        verifier_comptage_et_filtrage_assiduites(etuds, moduleimpls[:4], formsemestres)
        verifier_filtrage_justificatifs(etuds[0], justificatifs)
    
        editer_supprimer_assiduites(etuds, moduleimpls)
        editer_supprimer_justificatif(etuds[0])
    
    
    def verif_migration_abs_assiduites():
        """Vérification que le script de migration fonctionne correctement"""
        downgrade_module(assiduites=True, justificatifs=True)
    
        etudid: int = 1
    
        for debut, fin, demijournee, justifiee in [
            (
                "02/01/2023",
                "02/01/2023",
                1,
                False,
            ),  # 1 assi 02/01/2023 8h > 13h (1dj)
            (
                "03/01/2023",
                "03/01/2023",
                0,
                False,
            ),  # 1 assi 03/01/2023 13h > 18h (1dj)
            (
                "05/01/2023",
                "05/01/2023",
                2,
                False,
            ),  # 1 assi 05/01/2023 8h > 18h (2dj)
            (
                "09/01/2023",
                "09/01/2023",
                1,
                False,
            ),
            (
                "09/01/2023",
                "09/01/2023",
                0,
                False,
            ),  # 1 assi 09/01/2023 8h > 18h (2dj)
            (
                "10/01/2023",
                "10/01/2023",
                0,
                False,
            ),
            (
                "11/01/2023",
                "11/01/2023",
                1,
                False,
            ),  # 1 assi 10/01/2023 - 11/01/2023 13h > 13h (2dj)
            (
                "12/01/2023",
                "12/01/2023",
                1,
                False,
            ),  # 1 assi 12/01/2023 8h > 13h (1dj)
            (
                "13/01/2023",
                "13/01/2023",
                1,
                False,
            ),  # 1 assi 13/01/2023 8h > 13h (1dj)
            (
                "16/01/2023",
                "16/01/2023",
                1,
                False,
            ),
            (
                "16/01/2023",
                "16/01/2023",
                1,
                False,
            ),  # 1 assi 16/01/2023 8h > 13h (1dj)
            (
                "19/01/2023",
                "24/01/2023",
                2,
                False,
            ),  # 2 assi 19/01/2023 - 20/01/2023 8h > 18h (4dj) + 23/01/23 - 24/01/2023 8h>18h (4dj)
            (
                "01/02/2023",
                "01/02/2023",
                1,
                True,
            ),  # 1 assi 01/02/2023 8h > 13h (1dj) JUSTI
            (
                "02/02/2023",
                "02/02/2023",
                0,
                True,
            ),  # 1 assi 02/02/2023 13h > 18h (1dj) JUSTI
            (
                "06/02/2023",
                "06/02/2023",
                2,
                True,
            ),  # 1 assi 06/02/2023 8h > 18h (2dj) JUSTI
            (
                "07/02/2023",
                "07/02/2023",
                0,
                True,
            ),
            (
                "07/02/2023",
                "07/02/2023",
                0,
                False,
            ),  # 1 assi 07/02/2023 13h > 18h (1dj) JUSTI
            (
                "08/02/2023",
                "08/02/2023",
                0,
                False,
            ),
            (
                "08/02/2023",
                "08/02/2023",
                0,
                True,
            ),  # 1 assi 08/02/2023 13h > 18h (1dj) JUSTI
            (
                "10/02/2023",
                "10/02/2023",
                1,
                True,
            ),
            (
                "10/02/2023",
                "10/02/2023",
                0,
                False,
            ),  # 1 assi 10/02/2023 08h > 18h (2dj) JUSTI
            (
                "13/02/2023",
                "13/02/2023",
                1,
                False,
            ),
            (
                "13/02/2023",
                "13/02/2023",
                0,
                True,
            ),  # 1 assi 13/02/2023 08h > 18h (2dj) JUSTI
            (
                "15/02/2023",
                "15/02/2023",
                2,
                False,
            ),  # 1 assi 13/02/2023 08h > 18h (2dj) JUSTI(ext)
            (
                "22/02/2023",
                "24/02/2023",
                1,
                False,
            ),  # 3 assi 22-23-24/02/2023 08h > 13h (3dj) JUSTI(ext)
        ]:
            _create_abs(
                date_debut=debut,
                date_fin=fin,
                demijournee=demijournee,
                etudid=etudid,
                estjust=justifiee,
            )
    
        # --- Justification de certaines absences
    
        for debut, fin, demijournee in [
            (
                "15/02/2023",
                "15/02/2023",
                2,
            ),
            (
                "21/02/2023",
                "24/02/2023",
                2,
            ),
        ]:
            _create_abs(
                date_debut=debut,
                date_fin=fin,
                demijournee=demijournee,
                etudid=etudid,
                estjust=True,
                estabs=False,
            )
    
        migrate_abs_to_assiduites()
    
        assert Assiduite.query.count() == 21, "Migration : Nb Assiduités FAUX"
        assert Justificatif.query.count() == 9, "Migration : Nb Justificatif FAUX"
    
        # Cas classiques sans justification
    
        assert (
            _get_assi("2023-01-02T08:00", "2023-01-02T13:00") is not None
        ), "Migration : Abs n°1 mal migrée"
        assert (
            _get_assi("2023-01-03T13:00", "2023-01-03T18:00") is not None
        ), "Migration : Abs n°2 mal migrée"
        assert (
            _get_assi("2023-01-05T08:00", "2023-01-05T18:00") is not None
        ), "Migration : Abs n°3 mal migrée"
        assert (
            _get_assi("2023-01-09T08:00", "2023-01-09T18:00") is not None
        ), "Migration : Abs n°4&5 mal migrée"
        assert (
            _get_assi("2023-01-10T13:00", "2023-01-11T13:00") is not None
        ), "Migration : Abs n°6&7 mal migrée"
        assert (
            _get_assi("2023-01-12T08:00", "2023-01-12T13:00") is not None
        ), "Migration : Abs n°8 mal migrée"
        assert (
            _get_assi("2023-01-13T08:00", "2023-01-13T13:00") is not None
        ), "Migration : Abs n°9 mal migrée"
        assert (
            _get_assi("2023-01-16T08:00", "2023-01-16T13:00") is not None
        ), "Migration : Abs n°10&11 mal migrée"
        assert (
            _get_assi("2023-01-19T08:00", "2023-01-20T18:00") is not None
        ), "Migration : Abs n°12 mal migrée"
        assert (
            _get_assi("2023-01-23T08:00", "2023-01-24T18:00") is not None
        ), "Migration : Abs n°12 mal migrée"
    
        # Cas d'absences justifiées
    
        assert (
            _get_assi("2023-02-01T08:00", "2023-02-01T13:00", True) is not None
        ), "Migration : Abs n°13 mal migrée"
        assert (
            _get_assi("2023-02-02T13:00", "2023-02-02T18:00", True) is not None
        ), "Migration : Abs n°14 mal migrée"
        assert (
            _get_assi("2023-02-06T08:00", "2023-02-06T18:00", True) is not None
        ), "Migration : Abs n°15 mal migrée"
        assert (
            _get_assi("2023-02-07T13:00", "2023-02-07T18:00", True) is not None
        ), "Migration : Abs n°16&17 mal migrée"
        assert (
            _get_assi("2023-02-08T13:00", "2023-02-08T18:00", True) is not None
        ), "Migration : Abs n°18&19 mal migrée"
        assert (
            _get_assi("2023-02-10T08:00", "2023-02-10T18:00", True) is not None
        ), "Migration : Abs n°20&21 mal migrée"
        assert (
            _get_assi("2023-02-13T08:00", "2023-02-13T18:00", True) is not None
        ), "Migration : Abs n°22&23 mal migrée"
    
        # Cas Justificatifs
    
        assert (
            _get_justi("2023-02-01T08:00", "2023-02-01T13:00") is not None
        ), "Migration : Abs n°13 mal migrée"
        assert (
            _get_justi("2023-02-02T13:00", "2023-02-02T18:00") is not None
        ), "Migration : Abs n°14 mal migrée"
        assert (
            _get_justi("2023-02-06T08:00", "2023-02-06T18:00") is not None
        ), "Migration : Abs n°15 mal migrée"
        assert (
            _get_justi("2023-02-07T13:00", "2023-02-07T18:00") is not None
        ), "Migration : Abs n°16&17 mal migrée"
        assert (
            _get_justi("2023-02-08T13:00", "2023-02-08T18:00") is not None
        ), "Migration : Abs n°18&19 mal migrée"
        assert (
            _get_justi("2023-02-10T08:00", "2023-02-10T18:00") is not None
        ), "Migration : Abs n°20&21 mal migrée"
        assert (
            _get_justi("2023-02-13T08:00", "2023-02-13T18:00") is not None
        ), "Migration : Abs n°22&23 mal migrée"
        assert (
            _get_justi("2023-02-15T08:00", "2023-02-15T18:00") is not None
        ), "Migration : Justi n°1 mal migré"
        assert (
            _get_assi("2023-02-15T08:00", "2023-02-15T18:00", True) is not None
        ), "Migration : Abs n°24 mal migrée"
        assert (
            _get_justi("2023-02-21T08:00", "2023-02-24T18:00") is not None
        ), "Migration : Justi n°2 mal migré"
        assert (
            _get_assi("2023-02-22T08:00", "2023-02-22T13:00", True) is not None
        ), "Migration : Abs n°25 mal migrée"
        assert (
            _get_assi("2023-02-23T08:00", "2023-02-23T13:00", True) is not None
        ), "Migration : Abs n°26 mal migrée"
        assert (
            _get_assi("2023-02-24T08:00", "2023-02-24T13:00", True) is not None
        ), "Migration : Abs n°27 mal migrée"
    
        downgrade_module(assiduites=True, justificatifs=True)
    
    
    def _get_assi(
        deb: str,
        fin: str,
        est_just: bool = False,
    ):
        deb = scu.localize_datetime(scu.is_iso_formated(deb, True))
        fin = scu.localize_datetime(scu.is_iso_formated(fin, True))
    
        return Assiduite.query.filter(
            Assiduite.date_debut >= deb,
            Assiduite.date_fin <= fin,
            Assiduite.est_just == est_just,
        ).first()
    
    
    def _get_justi(
        deb: str,
        fin: str,
    ):
        deb = scu.localize_datetime(scu.is_iso_formated(deb, True))
        fin = scu.localize_datetime(scu.is_iso_formated(fin, True))
    
        return Justificatif.query.filter(
            Justificatif.date_debut >= deb,
            Justificatif.date_fin <= fin,
        ).first()
    
    
    def ajouter_justificatifs(etud):
        """test de l'ajout des justificatifs"""
    
        obj_justificatifs = [
            {
                "etat": scu.EtatJustificatif.ATTENTE,
                "deb": "2022-09-05T08:00+01:00",
                "fin": "2022-09-05T09:59:59+01:00",
                "raison": None,
            },
            {
                "etat": scu.EtatJustificatif.VALIDE,
                "deb": "2023-01-03T07:00+01:00",
                "fin": "2023-01-03T11:00+01:00",
                "raison": None,
            },
            {
                "etat": scu.EtatJustificatif.VALIDE,
                "deb": "2022-09-05T10:00:00+01:00",
                "fin": "2022-09-05T12:00+01:00",
                "raison": None,
            },
            {
                "etat": scu.EtatJustificatif.NON_VALIDE,
                "deb": "2022-09-05T14:00:00+01:00",
                "fin": "2022-09-05T15:00+01:00",
                "raison": "Description",
            },
            {
                "etat": scu.EtatJustificatif.MODIFIE,
                "deb": "2023-01-03T11:30+01:00",
                "fin": "2023-01-03T12:00+01:00",
                "raison": None,
            },
        ]
    
        justificatifs = []
        for just in obj_justificatifs:
            just_obj = Justificatif.create_justificatif(
                etud,
                scu.is_iso_formated(just["deb"], True),
                scu.is_iso_formated(just["fin"], True),
                just["etat"],
                just["raison"],
            )
            db.session.add(just_obj)
            db.session.commit()
            just_obj.justifier_assiduites()
            justificatifs.append(just_obj)
    
        # Vérification de la création des justificatifs
        assert [
            justi for justi in justificatifs if not isinstance(justi, Justificatif)
        ] == [], "La création des justificatifs de base n'est pas OK"
    
        return justificatifs
    
    
    def verifier_filtrage_justificatifs(etud: Identite, justificatifs: list[Justificatif]):
        """
        - vérifier le filtrage des justificatifs  (etat, debut, fin)
        """
    
        # Vérification du filtrage classique
    
        # Etat
        assert (
            scass.filter_justificatifs_by_etat(etud.justificatifs, "valide").count() == 2
        ), "Filtrage de l'état 'valide' mauvais"
        assert (
            scass.filter_justificatifs_by_etat(etud.justificatifs, "attente").count() == 1
        ), "Filtrage de l'état 'attente' mauvais"
        assert (
            scass.filter_justificatifs_by_etat(etud.justificatifs, "modifie").count() == 1
        ), "Filtrage de l'état 'modifie' mauvais"
        assert (
            scass.filter_justificatifs_by_etat(etud.justificatifs, "non_valide").count()
            == 1
        ), "Filtrage de l'état 'non_valide' mauvais"
        assert (
            scass.filter_justificatifs_by_etat(etud.justificatifs, "valide,modifie").count()
            == 3
        ), "Filtrage de l'état 'valide,modifie' mauvais"
        assert (
            scass.filter_justificatifs_by_etat(
                etud.justificatifs, "valide,modifie,attente"
            ).count()
            == 4
        ), "Filtrage de l'état 'valide,modifie,attente' mauvais"
        assert (
            scass.filter_justificatifs_by_etat(
                etud.justificatifs, "valide,modifie,attente,non_valide"
            ).count()
            == 5
        ), "Filtrage de l'état 'valide,modifie,attente,_non_valide' mauvais"
    
        assert (
            scass.filter_justificatifs_by_etat(etud.justificatifs, "autre").count() == 0
        ), "Filtrage de l'état 'autre' mauvais"
    
        # Dates
    
        assert (
            scass.filter_by_date(etud.justificatifs, Justificatif).count() == 5
        ), "Filtrage 'Toute Date' mauvais 1"
        date = scu.localize_datetime(
            scu.is_iso_formated("2022-09-01T10:00+01:00", convert=True)
        )
        assert (
            scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
            == 5
        ), "Filtrage 'Toute Date' mauvais 2"
    
        date = scu.localize_datetime(
            scu.is_iso_formated("2022-09-05T08:00+01:00", convert=True)
        )
        assert (
            scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
            == 5
        ), "Filtrage 'date début' mauvais 3"
    
        date = scu.localize_datetime(
            scu.is_iso_formated("2022-09-05T08:00:01+01:00", convert=True)
        )
        assert (
            scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
            == 5
        ), "Filtrage 'date début' mauvais 4"
    
        date = scu.localize_datetime(
            scu.is_iso_formated("2022-09-05T10:00+01:00", convert=True)
        )
        assert (
            scass.filter_by_date(etud.justificatifs, Justificatif, date_deb=date).count()
            == 4
        ), "Filtrage 'date début' mauvais 5"
    
        date = scu.localize_datetime(
            scu.is_iso_formated("2022-09-01T10:00+01:00", convert=True)
        )
        assert (
            scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
            == 0
        ), "Filtrage 'date fin' mauvais 6"
    
        date = scu.localize_datetime(
            scu.is_iso_formated("2022-09-05T08:00+01:00", convert=True)
        )
        assert (
            scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
            == 1
        ), "Filtrage 'date fin' mauvais 7"
    
        date = scu.localize_datetime(
            scu.is_iso_formated("2022-09-05T10:00:01+01:00", convert=True)
        )
        assert (
            scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
            == 2
        ), "Filtrage 'date fin' mauvais 8"
    
        date = scu.localize_datetime(
            scu.is_iso_formated("2023-01-03T12:00+01:00", convert=True)
        )
        assert (
            scass.filter_by_date(etud.justificatifs, Justificatif, date_fin=date).count()
            == 5
        ), "Filtrage 'date fin' mauvais 9"
    
        # Justifications des assiduites
    
        assert len(scass.justifies(justificatifs[2])) == 1, "Justifications mauvais"
        assert len(scass.justifies(justificatifs[0])) == 0, "Justifications mauvais"
    
    
    def editer_supprimer_justificatif(etud: Identite):
        """
        Troisième Partie:
            - Vérification de l'édition des justificatifs
            - Vérification de la suppression des justificatifs
        """
    
        justi: Justificatif = etud.justificatifs.first()
    
        # Modification de l'état
        justi.etat = scu.EtatJustificatif.MODIFIE
        # Modification du moduleimpl
        justi.date_debut = scu.localize_datetime(
            scu.is_iso_formated("2023-02-03T11:00:01+01:00", convert=True)
        )
        justi.date_fin = scu.localize_datetime(
            scu.is_iso_formated("2023-02-03T12:00:01+01:00", convert=True)
        )
    
        db.session.add(justi)
        db.session.commit()
    
        # Vérification du changement
        assert (
            scass.filter_justificatifs_by_etat(etud.justificatifs, "modifie").count() == 2
        ), "Edition de justificatif mauvais"
    
        assert (
            scass.filter_by_date(
                etud.justificatifs,
                Justificatif,
                date_deb=scu.localize_datetime(
                    scu.is_iso_formated("2023-02-01T11:00:00+01:00", convert=True)
                ),
            ).count()
            == 1
        ), "Edition de justificatif mauvais 2"
    
        # Supression d'une assiduité
    
        db.session.delete(justi)
        db.session.commit()
    
        assert etud.justificatifs.count() == 4, "Supression de justificatif mauvais"
    
    
    def editer_supprimer_assiduites(etuds: list[Identite], moduleimpls: list[int]):
        """
        Troisième Partie:
            - Vérification de l'édition des assiduitées
            - Vérification de la suppression des assiduitées
        """
    
        ass1: Assiduite = etuds[0].assiduites.first()
        ass2: Assiduite = etuds[1].assiduites.first()
        ass3: Assiduite = etuds[2].assiduites.first()
    
        # Modification de l'état
        ass1.etat = scu.EtatAssiduite.RETARD
        db.session.add(ass1)
        # Modification du moduleimpl
        ass2.moduleimpl_id = moduleimpls[0].id
        db.session.add(ass2)
        db.session.commit()
    
        # Vérification du changement
        assert (
            scass.filter_assiduites_by_etat(etuds[0].assiduites, "retard").count() == 4
        ), "Edition d'assiduité mauvais"
        assert (
            scass.filter_by_module_impl(etuds[1].assiduites, moduleimpls[0].id).count() == 2
        ), "Edition d'assiduité mauvais"
    
        # Supression d'une assiduité
    
        db.session.delete(ass3)
        db.session.commit()
    
        assert etuds[2].assiduites.count() == 6, "Supression d'assiduité mauvais"
    
    
    def ajouter_assiduites(
        etuds: list[Identite], moduleimpls: list[ModuleImpl], etud_faux: Identite
    ):
        """
        Première partie:
            - Ajoute 6 assiduités à chaque étudiant
                - 2 présence (semestre 1 et 2)
                - 2 retard (semestre 2)
                - 2 absence (semestre 1)
            - Vérifie la création des assiduités
        """
    
        for etud in etuds:
            obj_assiduites = [
                {
                    "etat": scu.EtatAssiduite.PRESENT,
                    "deb": "2022-09-05T08:00+01:00",
                    "fin": "2022-09-05T10:00+01:00",
                    "moduleimpl": None,
                    "desc": None,
                },
                {
                    "etat": scu.EtatAssiduite.PRESENT,
                    "deb": "2023-01-03T08:00+01:00",
                    "fin": "2023-01-03T10:00+01:00",
                    "moduleimpl": moduleimpls[2],
                    "desc": None,
                },
                {
                    "etat": scu.EtatAssiduite.ABSENT,
                    "deb": "2022-09-05T10:00:01+01:00",
                    "fin": "2022-09-05T11:00+01:00",
                    "moduleimpl": moduleimpls[0],
                    "desc": None,
                },
                {
                    "etat": scu.EtatAssiduite.ABSENT,
                    "deb": "2022-09-05T14:00:00+01:00",
                    "fin": "2022-09-05T15:00+01:00",
                    "moduleimpl": moduleimpls[1],
                    "desc": "Description",
                },
                {
                    "etat": scu.EtatAssiduite.RETARD,
                    "deb": "2023-01-03T11:00:01+01:00",
                    "fin": "2023-01-03T12:00+01:00",
                    "moduleimpl": moduleimpls[3],
                    "desc": None,
                },
                {
                    "etat": scu.EtatAssiduite.RETARD,
                    "deb": "2023-01-04T11:00:01+01:00",
                    "fin": "2023-01-04T12:00+01:00",
                    "moduleimpl": moduleimpls[3],
                    "desc": "Description",
                },
                {
                    "etat": scu.EtatAssiduite.RETARD,
                    "deb": "2022-11-04T11:00:01+01:00",
                    "fin": "2022-12-05T12:00+01:00",
                    "moduleimpl": None,
                    "desc": "Description",
                },
            ]
    
            assiduites = []
            for ass in obj_assiduites:
                ass_obj = Assiduite.create_assiduite(
                    etud,
                    scu.is_iso_formated(ass["deb"], True),
                    scu.is_iso_formated(ass["fin"], True),
                    ass["etat"],
                    ass["moduleimpl"],
                    ass["desc"],
                )
                assiduites.append(ass_obj)
                db.session.add(ass_obj)
                db.session.commit()
    
            # Vérification de la création des assiduités
            assert [
                ass for ass in assiduites if not isinstance(ass, Assiduite)
            ] == [], "La création des assiduités de base n'est pas OK"
    
        # Vérification de la gestion des erreurs
    
        test_assiduite = {
            "etat": scu.EtatAssiduite.RETARD,
            "deb": "2023-01-04T11:00:01+01:00",
            "fin": "2023-01-04T12:00+01:00",
            "moduleimpl": moduleimpls[3],
            "desc": "Description",
        }
    
        try:
            Assiduite.create_assiduite(
                etuds[0],
                scu.is_iso_formated(test_assiduite["deb"], True),
                scu.is_iso_formated(test_assiduite["fin"], True),
                test_assiduite["etat"],
                test_assiduite["moduleimpl"],
                test_assiduite["desc"],
            )
        except ScoValueError as excp:
            assert (
                excp.args[0]
                == "Duplication: la période rentre en conflit avec une plage enregistrée"
            )
    
        try:
            test_assiduite2 = {
                "etat": scu.EtatAssiduite.RETARD,
                "deb": "2022-09-03T11:00:01+01:00",
                "fin": "2022-09-03T12:00+01:00",
                "moduleimpl": moduleimpls[3],
                "desc": "Description",
            }
            Assiduite.create_assiduite(
                etuds[0],
                scu.is_iso_formated(test_assiduite2["deb"], True),
                scu.is_iso_formated(test_assiduite2["fin"], True),
                test_assiduite2["etat"],
                test_assiduite2["moduleimpl"],
                test_assiduite2["desc"],
            )
        except ScoValueError as excp:
            assert excp.args[0] == "La date de début n'est pas un jour travaillé"
        try:
            test_assiduite2 = {
                "etat": scu.EtatAssiduite.RETARD,
                "deb": "2022-09-02T11:00:01+01:00",
                "fin": "2022-09-03T12:00+01:00",
                "moduleimpl": moduleimpls[3],
                "desc": "Description",
            }
            Assiduite.create_assiduite(
                etuds[0],
                scu.is_iso_formated(test_assiduite2["deb"], True),
                scu.is_iso_formated(test_assiduite2["fin"], True),
                test_assiduite2["etat"],
                test_assiduite2["moduleimpl"],
                test_assiduite2["desc"],
            )
        except ScoValueError as excp:
            assert excp.args[0] == "La date de fin n'est pas un jour travaillé"
    
        try:
            Assiduite.create_assiduite(
                etud_faux,
                scu.is_iso_formated(test_assiduite["deb"], True),
                scu.is_iso_formated(test_assiduite["fin"], True),
                test_assiduite["etat"],
                test_assiduite["moduleimpl"],
                test_assiduite["desc"],
            )
        except ScoValueError as excp:
            assert excp.args[0] == "L'étudiant n'est pas inscrit au module"
    
    
    def verifier_comptage_et_filtrage_assiduites(
        etuds: list[Identite], moduleimpls: list[int], formsemestres: tuple[FormSemestre]
    ):
        """
        Deuxième partie:
            - vérifier les valeurs du comptage (compte, heure, journée, demi-journée)
            - vérifier le filtrage des assiduites  (etat, debut, fin, module, formsemestre)
    
        """
    
        etu1, etu2, etu3 = etuds
    
        mod11, mod12, mod21, mod22 = moduleimpls
    
        # Vérification du filtrage classique
    
        # Etat
        assert (
            scass.filter_assiduites_by_etat(etu2.assiduites, "present").count() == 2
        ), "Filtrage de l'état 'présent' mauvais"
        assert (
            scass.filter_assiduites_by_etat(etu2.assiduites, "retard").count() == 3
        ), "Filtrage de l'état 'retard' mauvais"
        assert (
            scass.filter_assiduites_by_etat(etu2.assiduites, "absent").count() == 2
        ), "Filtrage de l'état 'absent' mauvais"
        assert (
            scass.filter_assiduites_by_etat(etu2.assiduites, "absent,retard").count() == 5
        ), "Filtrage de l'état 'absent,retard' mauvais"
        assert (
            scass.filter_assiduites_by_etat(
                etu2.assiduites, "absent,retard,present"
            ).count()
            == 7
        ), "Filtrage de l'état 'absent,retard,present' mauvais"
        assert (
            scass.filter_assiduites_by_etat(etu2.assiduites, "autre").count() == 0
        ), "Filtrage de l'état 'autre' mauvais"
    
        # Module
        assert (
            scass.filter_by_module_impl(etu3.assiduites, mod11.id).count() == 1
        ), "Filtrage par 'Moduleimpl' mauvais"
        assert (
            scass.filter_by_module_impl(etu3.assiduites, mod12.id).count() == 1
        ), "Filtrage par 'Moduleimpl' mauvais"
        assert (
            scass.filter_by_module_impl(etu3.assiduites, mod21.id).count() == 1
        ), "Filtrage par 'Moduleimpl' mauvais"
        assert (
            scass.filter_by_module_impl(etu3.assiduites, mod22.id).count() == 2
        ), "Filtrage par 'Moduleimpl' mauvais"
        assert (
            scass.filter_by_module_impl(etu3.assiduites, None).count() == 2
        ), "Filtrage par 'Moduleimpl' mauvais"
        assert (
            scass.filter_by_module_impl(etu3.assiduites, 152).count() == 0
        ), "Filtrage par 'Moduleimpl' mauvais"
    
        # Formsemestre
        assert (
            scass.filter_by_formsemestre(
                etu1.assiduites, Assiduite, formsemestres[0]
            ).count()
            == 4
        ), "Filtrage 'Formsemestre' mauvais"
        assert (
            scass.filter_by_formsemestre(
                etu1.assiduites, Assiduite, formsemestres[1]
            ).count()
            == 3
        ), "Filtrage 'Formsemestre' mauvais"
        assert (
            scass.filter_by_formsemestre(
                etu1.assiduites, Assiduite, formsemestres[2]
            ).count()
            == 0
        ), "Filtrage 'Formsemestre' mauvais"
    
        # Date début
        assert (
            scass.filter_by_date(etu2.assiduites, Assiduite).count() == 7
        ), "Filtrage 'Date début' mauvais 1"
    
        date = scu.localize_datetime(
            scu.is_iso_formated("2022-09-01T10:00+01:00", convert=True)
        )
        assert (
            scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 7
        ), "Filtrage 'Date début' mauvais 2"
    
        date = scu.localize_datetime(
            scu.is_iso_formated("2022-09-05T10:00+01:00", convert=True)
        )
        assert (
            scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 7
        ), "Filtrage 'Date début' mauvais 3"
    
        date = scu.localize_datetime(
            scu.is_iso_formated("2022-09-05T16:00+01:00", convert=True)
        )
        assert (
            scass.filter_by_date(etu2.assiduites, Assiduite, date_deb=date).count() == 4
        ), "Filtrage 'Date début' mauvais 4"
    
        # Date Fin
    
        date = scu.localize_datetime(
            scu.is_iso_formated("2022-09-01T10:00+01:00", convert=True)
        )
        assert (
            scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 0
        ), "Filtrage 'Date fin' mauvais 1"
    
        date = scu.localize_datetime(
            scu.is_iso_formated("2022-09-05T10:00+01:00", convert=True)
        )
        assert (
            scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 1
        ), "Filtrage 'Date fin' mauvais 2"
    
        date = scu.localize_datetime(
            scu.is_iso_formated("2022-09-05T10:00:01+01:00", convert=True)
        )
        assert (
            scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 2
        ), "Filtrage 'Date fin' mauvais 3"
    
        date = scu.localize_datetime(
            scu.is_iso_formated("2022-09-05T16:00+01:00", convert=True)
        )
        assert (
            scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 3
        ), "Filtrage 'Date fin' mauvais 4"
    
        date = scu.localize_datetime(
            scu.is_iso_formated("2023-01-04T16:00+01:00", convert=True)
        )
        assert (
            scass.filter_by_date(etu2.assiduites, Assiduite, date_fin=date).count() == 7
        ), "Filtrage 'Date fin' mauvais 5"
    
    
    def _create_abs(
        date_debut, date_fin, demijournee, estjust=False, etudid=False, estabs=True
    ):
        etud = Identite.from_request(etudid)
        deb: dt.date = dt.datetime.strptime(date_debut, scu.DATE_FMT).date()
        fin: dt.date = dt.datetime.strptime(date_fin, scu.DATE_FMT).date()
        abs_list: list[Absence] = []
        while deb < fin:
            if deb.weekday() in [5, 6]:
                deb += dt.timedelta(days=1)
                continue
            if demijournee == 2:
                abs_list.append(
                    Absence(
                        etudid=etud.id,
                        jour=deb.isoformat(),
                        estabs=estabs,
                        estjust=estjust,
                        matin=True,
                    )
                )
                abs_list.append(
                    Absence(
                        etudid=etud.id,
                        jour=deb.isoformat(),
                        estabs=estabs,
                        estjust=estjust,
                        matin=False,
                    )
                )
            else:
                abs_list.append(
                    Absence(
                        etudid=etud.id,
                        jour=deb.isoformat(),
                        estabs=estabs,
                        estjust=estjust,
                        matin=demijournee,
                    )
                )
            log(
                f"create_abs [{etudid}, {deb.isoformat()}, {estabs}, {estjust}, {['aprem', 'matin', 'journee'][demijournee]}]"
            )
            deb += dt.timedelta(days=1)
    
        if deb == fin and deb.weekday() not in [5, 6]:
            if demijournee == 2:
                abs_list.append(
                    Absence(
                        etudid=etud.id,
                        jour=deb.isoformat(),
                        estabs=estabs,
                        estjust=estjust,
                        matin=True,
                    )
                )
                abs_list.append(
                    Absence(
                        etudid=etud.id,
                        jour=deb.isoformat(),
                        estabs=estabs,
                        estjust=estjust,
                        matin=False,
                    )
                )
            else:
                abs_list.append(
                    Absence(
                        etudid=etud.id,
                        jour=deb.isoformat(),
                        estabs=estabs,
                        estjust=estjust,
                        matin=demijournee,
                    )
                )
            log(
                f"create_abs [{etudid}, {deb.isoformat()}, {estabs}, {estjust}, {['aprem', 'matin', 'journee'][demijournee]}]"
            )
    
        db.session.add_all(abs_list)
        db.session.commit()
    
    
    def _setup_fake_db(
        dates_formsemestre: list[tuple[str, str]],
        nb_modules: int = 0,
        nb_etuds: int = 1,
    ) -> dict:
        g_fake = sco_fake_gen.ScoFake(verbose=False)
    
        # Création d'une formation
        formation_id = g_fake.create_formation()
        ue_id = g_fake.create_ue(
            formation_id=formation_id, acronyme="T1", titre="UE TEST 1"
        )
        matiere_id = g_fake.create_matiere(ue_id=ue_id, titre="test matière")
    
        module_ids: list[int] = [
            g_fake.create_module(
                matiere_id=matiere_id,
                code=f"Mo{i}",
                coefficient=1.0,
                titre=f"test module{i}",
            )
            for i in range(nb_modules)
        ]
    
        # Création semestre
    
        formsemestre_ids: list[int] = [
            g_fake.create_formsemestre(
                formation_id=formation_id,
                semestre_id=1,
                date_debut=deb,
                date_fin=fin,
            )
            for deb, fin in dates_formsemestre
        ]
    
        formsemestres: list[FormSemestre] = list(
            map(FormSemestre.get_formsemestre, formsemestre_ids)
        )
    
        # Création des modulesimpls (2 par semestre)
    
        moduleimpls: list[int] = []
    
        for i in range(len(dates_formsemestre)):
            for j in range(nb_modules):
                mod, form = module_ids[j], formsemestres[i]
    
                moduleimpl_id: int = g_fake.create_moduleimpl(
                    module_id=mod,
                    formsemestre_id=form.id,
                )
    
                moduleimpls.append(ModuleImpl.query.filter_by(id=moduleimpl_id).first())
    
        # Création de x étudiants
        etuds_dict: list[dict] = [
            g_fake.create_etud(prenom=f"etud{i}") for i in range(nb_etuds)
        ]
    
        etuds = []
        for etud in etuds_dict:
            for form_id in formsemestre_ids:
                g_fake.inscrit_etudiant(formsemestre_id=form_id, etud=etud)
            etuds.append(Identite.query.filter_by(id=etud["etudid"]).first())
        # Etudiant faux
        etud_faux_dict = g_fake.create_etud(prenom="etudfaux")
        etud_faux = Identite.query.filter_by(id=etud_faux_dict["etudid"]).first()
    
        return {
            "moduleimpls": moduleimpls,
            "formsemestres": formsemestres,
            "etuds": etuds,
            "etud_faux": etud_faux,
        }
    
    
    def test_calcul_assiduites(test_client):
        """Vérification du bon calcul des assiduités"""
    
        data: dict = _setup_fake_db([("01/12/2023", "31/12/2023")])
    
        formsemestre: FormSemestre = data["formsemestres"][0]
        etud: Identite = data["etuds"][0]
    
        """
        Exemple tuple:
        (
            "12-04T08:00", # Date de début
            "12-04T09:00", # Date de fin
            scu.EtatAssiduite.ABSENT, # Etat
            False # est_just
        )
        """
    
        assiduites: list[tuple] = [
            # Journée du 04/12
            (
                "12-04T08:00",
                "12-04T10:00",
                scu.EtatAssiduite.ABSENT,
                False,
            ),
            (
                "12-04T10:15",
                "12-04T12:15",
                scu.EtatAssiduite.RETARD,
                False,
            ),
            (
                "12-04T13:15",
                "12-04T15:15",
                scu.EtatAssiduite.PRESENT,
                False,
            ),
            (
                "12-04T15:15",
                "12-04T17:00",
                scu.EtatAssiduite.ABSENT,
                True,
            ),
            # 05/12
            (
                "12-05T08:00",
                "12-05T09:00",
                scu.EtatAssiduite.RETARD,
                True,
            ),
            (
                "12-05T09:00",
                "12-05T10:00",
                scu.EtatAssiduite.PRESENT,
                False,
            ),
            (
                "12-05T10:15",
                "12-05T12:15",
                scu.EtatAssiduite.PRESENT,
                False,
            ),
            (
                "12-05T13:15",
                "12-05T14:30",
                scu.EtatAssiduite.ABSENT,
                False,
            ),
            (
                "12-05T14:30",
                "12-05T16:30",
                scu.EtatAssiduite.RETARD,
                False,
            ),
            (
                "12-05T16:30",
                "12-05T17:00",
                scu.EtatAssiduite.PRESENT,
                False,
            ),
            # 06/12
            (
                "12-06T08:00",
                "12-06T10:00",
                scu.EtatAssiduite.PRESENT,
                False,
            ),
            (
                "12-06T10:15",
                "12-06T12:15",
                scu.EtatAssiduite.RETARD,
                False,
            ),
            (
                "12-06T13:15",
                "12-06T13:45",
                scu.EtatAssiduite.ABSENT,
                True,
            ),
            (
                "12-06T13:45",
                "12-06T15:00",
                scu.EtatAssiduite.PRESENT,
                False,
            ),
            (
                "12-06T15:00",
                "12-06T17:00",
                scu.EtatAssiduite.RETARD,
                False,
            ),
            # 07/12
            (
                "12-07T08:00",
                "12-07T08:30",
                scu.EtatAssiduite.RETARD,
                True,
            ),
            (
                "12-07T08:30",
                "12-07T10:00",
                scu.EtatAssiduite.PRESENT,
                False,
            ),
            (
                "12-07T10:15",
                "12-07T12:15",
                scu.EtatAssiduite.ABSENT,
                True,
            ),
            # 08/12
            (
                "12-08T08:00",
                "12-08T10:00",
                scu.EtatAssiduite.PRESENT,
                False,
            ),
            (
                "12-08T10:15",
                "12-08T12:15",
                scu.EtatAssiduite.ABSENT,
                False,
            ),
            (
                "12-08T13:15",
                "12-08T14:15",
                scu.EtatAssiduite.RETARD,
                True,
            ),
            (
                "12-08T14:15",
                "12-08T15:15",
                scu.EtatAssiduite.PRESENT,
                False,
            ),
            (
                "12-08T15:15",
                "12-08T17:00",
                scu.EtatAssiduite.ABSENT,
                False,
            ),
            # 11/12 -> 15/12
            (
                "12-11T08:00",
                "12-15T17:00",
                scu.EtatAssiduite.ABSENT,
                False,
            ),
            # 18/12
            (
                "12-18T08:00",
                "12-18T10:00",
                scu.EtatAssiduite.ABSENT,
                False,
            ),
            (
                "12-18T10:00",
                "12-18T12:00",
                scu.EtatAssiduite.ABSENT,
                False,
            ),
        ]
    
        for ass in assiduites:
            ass_obj = Assiduite.create_assiduite(
                etud=etud,
                date_debut=scu.is_iso_formated("2023-" + ass[0], True),
                date_fin=scu.is_iso_formated("2023-" + ass[1], True),
                etat=ass[2],
                est_just=ass[3],
            )
            db.session.add(ass_obj)
        db.session.commit()
    
        calculator = scass.CountCalculator(
            morning="08:00", noon="12:15", evening="17:00", nb_heures_par_jour=8
        )
    
        calculator.compute_assiduites(etud.assiduites)
    
        result: dict = calculator.to_dict(only_total=False)
    
        # Résultat attendu :
        # (les additions dans les absences corresponde à (compte_assiduite + compte_assiduite_longue))
        resultat_attendu: dict = {
            "present": {"journee": 5, "demi": 8, "heure": 13.25, "compte": 9},
            "absent": {
                "journee": 6 + 5,
                "demi": 8 + 10,
                "heure": 15.25 + 42,
                "compte": 9 + 1,
            },
            "absent_just": {"journee": 3, "demi": 3, "heure": 4.25, "compte": 3},
            "absent_non_just": {
                "journee": 4 + 5,
                "demi": 5 + 10,
                "heure": 11 + 42,
                "compte": 6 + 1,
            },
            "retard": {
                "journee": 5,
                "demi": 7,
                "heure": 10.5,
                "compte": 7,
            },
            "retard_just": {"journee": 3, "demi": 3, "heure": 2.5, "compte": 3},
            "retard_non_just": {"journee": 3, "demi": 4, "heure": 8.0, "compte": 4},
            "total": {"journee": 11, "demi": 20, "heure": 81.0, "compte": 26},
        }
    
        for key, value in resultat_attendu.items():
            assert value["journee"] * 2 >= value["demi"], f"Trop de demi-journées [{key}]"
    
        for key, value in resultat_attendu.items():
            for key2, value2 in value.items():
                assert (
                    result[key][key2] == value2
                ), f"Le calcul [{key}][{key2}] est faux (attendu > {value2}{result[key][key2]} < obtenu)"
    
    
    def test_cas_justificatifs(test_client):
        """
        Tests de certains cas particuliers des justificatifs
        - Création du justificatif avant ou après assiduité
        - Assiduité complétement couverte ou non
        - Modification de la couverture (edition du justificatif)
        """
    
        data = _setup_fake_db(
            [("2024-01-01", "2024-06-30")],
            0,
            1,
        )
    
        # <- Vérification justification si justif créé avant assi ->
        # Période : 8h -> 10h le 01/01/2024
    
        etud_1: Identite = data["etuds"][0]
        justif_1: Justificatif = Justificatif.create_justificatif(
            etudiant=etud_1,
            date_debut=scu.is_iso_formated("2024-01-01T08:00:00+01:00", True),
            date_fin=scu.is_iso_formated("2024-01-01T10:00:00+01:00", True),
            etat=scu.EtatJustificatif.VALIDE,
        )
    
        assi_1: Assiduite = Assiduite.create_assiduite(
            etud=etud_1,
            date_debut=scu.is_iso_formated("2024-01-01T08:00:00+01:00", True),
            date_fin=scu.is_iso_formated("2024-01-01T10:00:00+01:00", True),
            etat=scu.EtatAssiduite.ABSENT,
        )
    
        assert assi_1.est_just is True, "Justification non prise en compte (a1)"
        assert len(scass.justifies(justif_1)) == 1, "Justification non prise en compte (a2)"
    
        # <- Vérification justification si justif créé après assi ->
        # Période : 8h -> 10h le 02/01/2024
    
        Assiduite.create_assiduite(
            etud=etud_1,
            date_debut=scu.is_iso_formated("2024-01-02T08:00:00+01:00", True),
            date_fin=scu.is_iso_formated("2024-01-02T10:00:00+01:00", True),
            etat=scu.EtatAssiduite.ABSENT,
        )
    
        justif_2: Justificatif = Justificatif.create_justificatif(
            etudiant=etud_1,
            date_debut=scu.is_iso_formated("2024-01-02T08:00:00+01:00", True),
            date_fin=scu.is_iso_formated("2024-01-02T10:00:00+01:00", True),
            etat=scu.EtatJustificatif.VALIDE,
        )
    
        justif_2.justifier_assiduites()
    
        assert len(scass.justifies(justif_2)) == 1, "Justification non prise en compte (b1)"
    
        # Ne fonctionne pas ⬇️
        # assert assi_2.est_just is True, "Justification non prise en compte (b2)"
    
        # <- Vérification assiduité complétement couverte ->
        # Période : 12h -> 19h le 03/01/2024
    
        Assiduite.create_assiduite(
            etud=etud_1,
            date_debut=scu.is_iso_formated("2024-01-03T12:00:00+01:00", True),
            date_fin=scu.is_iso_formated("2024-01-03T19:00:00+01:00", True),
            etat=scu.EtatAssiduite.ABSENT,
        )
    
        # Justification complète
        justif_3: Justificatif = Justificatif.create_justificatif(
            etudiant=etud_1,
            date_debut=scu.is_iso_formated("2024-01-03T00:00:00", True),
            date_fin=scu.is_iso_formated("2024-01-03T23:59:59", True),
            etat=scu.EtatJustificatif.VALIDE,
        )
    
        # Justification incomplète
        justif_4: Justificatif = Justificatif.create_justificatif(
            etudiant=etud_1,
            date_debut=scu.is_iso_formated("2024-01-03T08:00:00", True),
            date_fin=scu.is_iso_formated("2024-01-03T18:00:00", True),
            etat=scu.EtatJustificatif.VALIDE,
        )
    
        # Mise à jour de l'assiduité
        justif_3.justifier_assiduites()
        justif_4.justifier_assiduites()
    
        assert (
            len(scass.justifies(justif_3)) == 1
        ), "Justification complète non prise en compte (c1)"
        assert (
            len(scass.justifies(justif_4)) == 0
        ), "Justification complète non prise en compte (c2)"
    
        # <- Vérification modification de la couverture ->
    
        # Deux assiduités, 8/01/2024 de 8h à 10h et 14h à 16h
    
        assi_2: Assiduite = Assiduite.create_assiduite(
            etud=etud_1,
            date_debut=scu.is_iso_formated("2024-01-08T08:00", True),
            date_fin=scu.is_iso_formated("2024-01-08T10:00", True),
            etat=scu.EtatAssiduite.ABSENT,
        )
        assi_3: Assiduite = Assiduite.create_assiduite(
            etud=etud_1,
            date_debut=scu.is_iso_formated("2024-01-08T14:00", True),
            date_fin=scu.is_iso_formated("2024-01-08T16:00", True),
            etat=scu.EtatAssiduite.ABSENT,
        )
    
        # <=>Justification complète<=>
        # les deux assiduités sont couvertes
    
        justif_5: Justificatif = Justificatif.create_justificatif(
            etudiant=etud_1,
            date_debut=scu.is_iso_formated("2024-01-08T00:00:00", True),
            date_fin=scu.is_iso_formated("2024-01-08T23:59:59", True),
            etat=scu.EtatJustificatif.VALIDE,
        )
    
        # Justification des assiduités
        assi_ids: list[int] = justif_5.justifier_assiduites()
        assert len(assi_ids) == 2, "Vérification Modification couverture (d1)"
        assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (d2)"
        assert assi_3.assiduite_id in assi_ids, "Vérification Modification couverture (d3)"
        assert assi_2.est_just is True, "Vérification Modification couverture (d4)"
        assert assi_3.est_just is True, "Vérification Modification couverture (d5)"
    
        # Déjustification des assiduités
        justif_5.dejustifier_assiduites()
        assi_ids: list[int] = justif_5.dejustifier_assiduites()
        assert len(assi_ids) == 2, "Vérification Modification couverture (d6)"
        assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (d7)"
        assert assi_3.assiduite_id in assi_ids, "Vérification Modification couverture (d8)"
        assert assi_2.est_just is False, "Vérification Modification couverture (d9)"
        assert assi_3.est_just is False, "Vérification Modification couverture (d10)"
    
        # <=>Justification Partielle<=>
        # Seule la première assiduité est couverte
    
        justif_5.date_fin = scu.is_iso_formated("2024-01-08T11:00", True)
        db.session.add(justif_5)
        db.session.commit()
    
        assi_ids: list[int] = justif_5.justifier_assiduites()
        assert len(assi_ids) == 1, "Vérification Modification couverture (e1)"
        assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (e2)"
        assert (
            assi_3.assiduite_id not in assi_ids
        ), "Vérification Modification couverture (e3)"
        assert assi_2.est_just is True, "Vérification Modification couverture (e4)"
        assert assi_3.est_just is False, "Vérification Modification couverture (e5)"
    
        assi_ids: list[int] = justif_5.dejustifier_assiduites()
        assert len(assi_ids) == 1, "Vérification Modification couverture (e6)"
        assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (e7)"
        assert (
            assi_3.assiduite_id not in assi_ids
        ), "Vérification Modification couverture (e3)"
        assert assi_2.est_just is False, "Vérification Modification couverture (e8)"
        assert assi_3.est_just is False, "Vérification Modification couverture (e9)"
    
        # <=>Justification Multiple<=>
        # Deux justificatifs couvrent une même assiduité
    
        # on justifie la première assiduité avec le premier justificatif
        justif_5.justifier_assiduites()
    
        # deuxième justificatif
        justif_6: Justificatif = Justificatif.create_justificatif(
            etudiant=etud_1,
            date_debut=scu.is_iso_formated("2024-01-08T08:00", True),
            date_fin=scu.is_iso_formated("2024-01-08T10:00", True),
            etat=scu.EtatJustificatif.VALIDE,
        )
    
        assi_ids: list[int] = justif_6.justifier_assiduites()
        assert len(assi_ids) == 1, "Vérification Modification couverture (f1)"
        assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (f2)"
        assert (
            assi_3.assiduite_id not in assi_ids
        ), "Vérification Modification couverture (f3)"
        assert assi_2.est_just is True, "Vérification Modification couverture (f4)"
        assert assi_3.est_just is False, "Vérification Modification couverture (f5)"
    
        # on déjustifie le justificatif 5
        justif_5.etat = scu.EtatJustificatif.NON_VALIDE
        db.session.add(justif_5)
        db.session.commit()
    
        assi_ids: list[int] = justif_5.dejustifier_assiduites()
        assert len(assi_ids) == 0, "Vérification Modification couverture (f6)"
        assert (
            assi_2.assiduite_id not in assi_ids
        ), "Vérification Modification couverture (f7)"
        assert assi_2.est_just is True, "Vérification Modification couverture (f8)"
    
        # on déjustifie le justificatif 6
        justif_6.etat = scu.EtatJustificatif.NON_VALIDE
        db.session.add(justif_6)
        db.session.commit()
        assi_ids: list[int] = justif_6.dejustifier_assiduites()
        assert len(assi_ids) == 1, "Vérification Modification couverture (f9)"
        assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (f10)"
        assert assi_2.est_just is False, "Vérification Modification couverture (f11)"
    
        # <=>Justification Chevauchée<=>
        # 1 justificatif chevauche une assiduité (8h -> 10h) (9h -> 11h)
    
        justif_7: Justificatif = Justificatif.create_justificatif(
            etudiant=etud_1,
            date_debut=scu.is_iso_formated("2024-01-08T09:00", True),
            date_fin=scu.is_iso_formated("2024-01-08T11:00", True),
            etat=scu.EtatJustificatif.VALIDE,
        )
    
        assi_ids: list[int] = justif_7.justifier_assiduites()
        assert len(assi_ids) == 0, "Vérification Modification couverture (g1)"
        assert (
            assi_2.assiduite_id not in assi_ids
        ), "Vérification Modification couverture (g2)"
        assert assi_2.est_just is False, "Vérification Modification couverture (g3)"
    
        # Modification pour correspondre à l'assiduité
        justif_7.date_debut = scu.is_iso_formated("2024-01-08T08:00", True)
        db.session.add(justif_7)
        db.session.commit()
    
        assi_ids: list[int] = justif_7.justifier_assiduites()
        assert len(assi_ids) == 1, "Vérification Modification couverture (g4)"
        assert assi_2.assiduite_id in assi_ids, "Vérification Modification couverture (g5)"
        assert assi_2.est_just is True, "Vérification Modification couverture (g6)"
    
    
    def test_cache_assiduites(test_client):
        """Vérification du bon fonctionnement du cache des assiduités"""
    
        data = _setup_fake_db(
            [("2024-01-01", "2024-06-30"), ("2024-07-01", "2024-12-31")],
            1,
            1,
        )
    
        formsemestre1: FormSemestre = data["formsemestres"][0]
        formsemestre2: FormSemestre = data["formsemestres"][1]
    
        moduleimpl: ModuleImpl = data["moduleimpls"][0]
        etud: Identite = data["etuds"][0]
    
        # Création des assiduités
        assiduites: list[dict] = [
            # Semestre 1
            {
                "date_debut": "2024-01-08T08:00",
                "date_fin": "2024-01-08T10:00",
                "moduleimpl": moduleimpl,
            },
            {
                "date_debut": "2024-01-08T14:00",
                "date_fin": "2024-01-08T16:00",
                "moduleimpl": moduleimpl,
            },
            {
                "date_debut": "2024-01-09T08:00",
                "date_fin": "2024-01-09T10:00",
                "moduleimpl": None,
            },
            {
                "date_debut": "2024-01-09T14:00",
                "date_fin": "2024-01-09T16:00",
                "moduleimpl": None,
            },
            {
                "date_debut": "2024-01-10T08:00",
                "date_fin": "2024-01-10T10:00",
                "moduleimpl": None,
            },
            {
                "date_debut": "2024-01-10T14:00",
                "date_fin": "2024-01-10T16:00",
                "moduleimpl": moduleimpl,
            },
            # Semestre 2
            {
                "date_debut": "2024-07-09T14:00",
                "date_fin": "2024-07-09T16:00",
                "moduleimpl": None,
            },
            {
                "date_debut": "2024-07-10T08:00",
                "date_fin": "2024-07-10T10:00",
                "moduleimpl": None,
            },
            {
                "date_debut": "2024-07-10T14:00",
                "date_fin": "2024-07-10T16:00",
                "moduleimpl": None,
            },
        ]
    
        justificatifs: list[dict] = [
            {
                "date_debut": "2024-01-10T00:00",
                "date_fin": "2024-01-10T23:59",
            },
            {
                "date_debut": "2024-07-09T00:00",
                "date_fin": "2024-07-09T23:59",
            },
        ]
    
        # On ajoute les assiduités et les justificatifs
    
        for assi in assiduites:
            Assiduite.create_assiduite(
                etud=etud,
                date_debut=scu.is_iso_formated(assi["date_debut"], True),
                date_fin=scu.is_iso_formated(assi["date_fin"], True),
                moduleimpl=assi["moduleimpl"],
                etat=scu.EtatAssiduite.ABSENT,
            )
    
        for justi in justificatifs:
            Justificatif.create_justificatif(
                etudiant=etud,
                date_debut=scu.is_iso_formated(justi["date_debut"], True),
                date_fin=scu.is_iso_formated(justi["date_fin"], True),
                etat=scu.EtatJustificatif.VALIDE,
            ).justifier_assiduites()
    
        # Premier semestre 4nj / 2j / 6t
        assert scass.get_assiduites_count(etud.id, formsemestre1.to_dict()) == (4, 2, 6)
        assert scass.formsemestre_get_assiduites_count(etud.id, formsemestre1) == (4, 2, 6)
    
        # ModuleImpl 2nj / 1j / 3t
        assert scass.formsemestre_get_assiduites_count(
            etud.id, formsemestre1, moduleimpl.id
        ) == (2, 1, 3)
        # Deuxième semestre 2nj / 1j / 3t
        assert scass.get_assiduites_count(etud.id, formsemestre2.to_dict()) == (2, 1, 3)
    
        # On supprime la première assiduité (sans invalider le cache)
        assi: Assiduite = Assiduite.query.filter_by(etudid=etud.id).first()
        db.session.delete(assi)
        db.session.commit()
    
        # Premier semestre 4nj / 2j / 6t (Identique car cache)
        assert scass.get_assiduites_count(etud.id, formsemestre1.to_dict()) == (4, 2, 6)
        assert scass.formsemestre_get_assiduites_count(etud.id, formsemestre1) == (4, 2, 6)
        # ModuleImpl 1nj / 1j / 2t (Change car non cache)
        assert scass.formsemestre_get_assiduites_count(
            etud.id, formsemestre1, moduleimpl.id
        ) == (1, 1, 2)
        # Deuxième semestre 2nj / 1j / 3t (Identique car cache et non modifié)
        assert scass.get_assiduites_count(etud.id, formsemestre2.to_dict()) == (2, 1, 3)
    
        # On invalide maintenant le cache
        scass.invalidate_assiduites_count(etud.id, formsemestre1.to_dict())
    
        # Premier semestre 3nj / 2j / 5t (Change car cache invalidé)
        assert scass.get_assiduites_count(etud.id, formsemestre1.to_dict()) == (3, 2, 5)
        assert scass.formsemestre_get_assiduites_count(etud.id, formsemestre1) == (3, 2, 5)
        # ModuleImpl 1nj / 1j / 2t (Ne change pas car pas de changement)
        assert scass.formsemestre_get_assiduites_count(
            etud.id, formsemestre1, moduleimpl.id
        ) == (1, 1, 2)
        # Deuxième semestre 2nj / 1j / 3t (Identique car cache et non modifié)
        assert scass.get_assiduites_count(etud.id, formsemestre2.to_dict()) == (2, 1, 3)