Ctrl K
ComunidadGuías…

test_merge_daily_homonymes.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Bug rapporté le 2026-09-06 : deux caméras portant le même nom sur deux
hubs distincts finissaient dans le même assemblage journalier, le
regroupement ne reposant que sur le nom (merge_daily.py, load_groups()).

Le fix littéral (regrouper par identité stable partout) fragmenterait à
tort le registre réel de production : une même caméra physique y a
plusieurs (network_id, device_id) incohérents pour des raisons historiques
(vieilles entrées USB), avec toujours le même network_id quand il est
connu. Les tests ci-dessous couvrent donc les deux côtés : séparation
seulement sur une vraie collision de network_id, aucun changement sinon."""

from __future__ import annotations

import datetime as dt
import json
import tempfile
import unittest
from pathlib import Path
try:
    from zoneinfo import ZoneInfo
except ImportError:  # Python 3.8, édition Windows 7
    from backports.zoneinfo import ZoneInfo

import merge_daily


def mp4_structurel() -> bytes:
    def boite(nom, contenu=b""):
        return (len(contenu) + 8).to_bytes(4, "big") + nom + contenu
    return (
        boite(b"ftyp", b"isom\x00\x00\x02\x00isom")
        + boite(b"moov") + boite(b"mdat", b"video")
    )


class TestsHomonymes(unittest.TestCase):
    def setUp(self) -> None:
        self.tmp = tempfile.TemporaryDirectory()
        self.addCleanup(self.tmp.cleanup)
        self.input_dir = Path(self.tmp.name)
        self.tz = ZoneInfo("Europe/Paris")

    def _ecrire_registre(self, clips: dict) -> None:
        chemin = self.input_dir / merge_daily.DOWNLOAD_STATE
        chemin.write_text(json.dumps({"version": 2, "clips": clips}), encoding="utf-8")

    def _clip(self, chemin: str) -> Path:
        source = self.input_dir / chemin
        source.parent.mkdir(parents=True, exist_ok=True)
        source.write_bytes(mp4_structurel())
        return source

    def test_deux_reseaux_distincts_ne_sont_plus_fusionnes(self):
        self._clip("a.mp4")
        self._clip("b.mp4")
        self._ecrire_registre({
            "a": {"camera": "jardin", "network_id": "111", "device_id": "",
                  "created_at": "2026-09-01T10:00:00+00:00", "path": "a.mp4"},
            "b": {"camera": "jardin", "network_id": "222", "device_id": "",
                  "created_at": "2026-09-01T11:00:00+00:00", "path": "b.mp4"},
        })
        groupes = merge_daily.load_groups(self.input_dir, self.tz)
        cameras_jardin = {camera for camera, _ in groupes if camera.startswith("jardin")}
        self.assertEqual(cameras_jardin, {"jardin", "jardin (2)"})
        for camera, _ in groupes:
            if camera.startswith("jardin"):
                self.assertEqual(len(groupes[(camera, "2026-09-01")]), 1)

    def test_reseau_inconnu_ou_repete_ne_fragmente_pas(self):
        """Reproduit la forme réelle de jardin/Terrasse1 en production : un
        seul network_id connu (toujours le même), le reste vide - un bug de
        clé incohérente déjà traité ailleurs, pas un homonyme."""
        self._clip("a.mp4")
        self._clip("b.mp4")
        self._clip("c.mp4")
        self._ecrire_registre({
            "a": {"camera": "jardin", "network_id": "436363", "device_id": "",
                  "created_at": "2026-09-01T09:00:00+00:00", "path": "a.mp4"},
            "b": {"camera": "jardin", "network_id": "436363", "device_id": "999",
                  "created_at": "2026-09-01T10:00:00+00:00", "path": "b.mp4"},
            "c": {"camera": "jardin", "network_id": "", "device_id": "",
                  "created_at": "2026-09-01T11:00:00+00:00", "path": "c.mp4"},
        })
        groupes = merge_daily.load_groups(self.input_dir, self.tz)
        self.assertEqual({camera for camera, _ in groupes}, {"jardin"})
        self.assertEqual(len(groupes[("jardin", "2026-09-01")]), 3)

    def test_identites_a_source_indisponible_suivent_la_meme_resolution(self):
        """journees_a_source_indisponible() doit produire les mêmes clés que
        load_groups() : sinon un clip manquant du réseau minoritaire
        exempterait à tort la journée du réseau principal (ou l'inverse)."""
        self._clip("a.mp4")
        self._ecrire_registre({
            "a": {"camera": "jardin", "network_id": "111", "device_id": "",
                  "created_at": "2026-09-01T10:00:00+00:00", "path": "a.mp4"},
            "b": {"camera": "jardin", "network_id": "222", "device_id": "",
                  "created_at": "2026-09-01T11:00:00+00:00", "path": "manquant.mp4"},
        })
        jours, _ = merge_daily.journees_a_source_indisponible(self.input_dir, self.tz)
        self.assertEqual(jours, {("jardin (2)", "2026-09-01")})
        groupes = merge_daily.load_groups(self.input_dir, self.tz)
        self.assertEqual(groupes[("jardin", "2026-09-01")], [
            (dt.datetime(2026, 9, 1, 10, tzinfo=dt.timezone.utc), (self.input_dir / "a.mp4").resolve()),
        ])
        self.assertEqual(groupes[("jardin (2)", "2026-09-01")], [])


if __name__ == "__main__":
    unittest.main(verbosity=2)