ok
94fd0a0 parent: e0dd514 added
packages/regine-core/tests/integration/test_root_recategorisation.py +68 -0 | new file mode 100644 | ||
| @@ -0,0 +1,68 @@ | ||
| 1 | +"""Test d'intégration de la recatégorisation a posteriori d'un dossier (T024b, US4). | |
| 2 | + | |
| 3 | +**Précision par rapport à l'intitulé initial de T024b** : `comparer` ne raisonne que | |
| 4 | +sur des chemins *relatifs* à l'intérieur d'un dossier (cf. docstring de | |
| 5 | +`test_whole_folder_move_detected_as_move_for_every_file`, specs/005) — il n'a aucune | |
| 6 | +connaissance d'un « répertoire racine » (année/catégorie, specs/004). Un dossier | |
| 7 | +recatégorisé (relocalisé d'une racine année vers une racine catégorie) tout en | |
| 8 | +conservant sa structure interne (`raw/`, `jpeg/`, racine de sélection) n'apparaît | |
| 9 | +donc PAS comme un « déplacement » aux yeux de `comparer` : chaque chemin relatif | |
| 10 | +reste identique, donc chaque fichier reste « inchangé » au sens de `comparer` (même | |
| 11 | +chemin relatif, même hash) — omis du rapport plutôt que classé, pas même `normal` | |
| 12 | +(réservé aux changements bénins, ex. sidecar modifié). C'est exactement le | |
| 13 | +comportement recherché : la recatégorisation ne déclenche ni anomalie, ni | |
| 14 | +signalement erroné, ni perte du lien par contenu (Principe III) — elle est un | |
| 15 | +non-événement complet pour `comparer`, dont la seule vue est le contenu relatif d'un | |
| 16 | +dossier, jamais son répertoire racine. Le cas « déplacement » (chemin relatif changé | |
| 17 | +au sein d'un même dossier, ex. renommage d'un sous-répertoire) reste couvert par le | |
| 18 | +test specs/005 déjà cité, sans duplication ici. | |
| 19 | +""" | |
| 20 | + | |
| 21 | +from __future__ import annotations | |
| 22 | + | |
| 23 | +import shutil | |
| 24 | +from datetime import date | |
| 25 | +from pathlib import Path | |
| 26 | + | |
| 27 | +from regine_core.archive.checkout import checkout | |
| 28 | +from regine_core.archive.reconciliation import comparer | |
| 29 | +from regine_core.dossier.root import determine_root | |
| 30 | + | |
| 31 | + | |
| 32 | +def test_recategoriser_un_dossier_entier_est_invisible_pour_comparer(tmp_path: Path) -> None: | |
| 33 | + archive_root = tmp_path / "archive" | |
| 34 | + local_root = tmp_path / "local" | |
| 35 | + | |
| 36 | + root_annee = determine_root( | |
| 37 | + date(2026, 8, 15), None, archive_root=archive_root, local_root=local_root | |
| 38 | + ) | |
| 39 | + root_categorie = determine_root( | |
| 40 | + date(2026, 8, 15), "voyage", archive_root=archive_root, local_root=local_root | |
| 41 | + ) | |
| 42 | + assert root_annee.type == "annee" | |
| 43 | + assert root_categorie.type == "categorie" | |
| 44 | + assert root_annee.chemin_archive != root_categorie.chemin_archive | |
| 45 | + | |
| 46 | + nom_dossier = "2026-08-15_Sortie" | |
| 47 | + dossier_archive_initial = root_annee.chemin_archive / nom_dossier | |
| 48 | + (dossier_archive_initial / "raw").mkdir(parents=True) | |
| 49 | + (dossier_archive_initial / "raw" / "photo.raf").write_bytes(b"contenu-raw") | |
| 50 | + (dossier_archive_initial / "photo.jpg").write_bytes(b"contenu-jpeg-racine") | |
| 51 | + | |
| 52 | + dossier_local_initial = root_annee.chemin_local / nom_dossier | |
| 53 | + snapshot = checkout(dossier_archive_initial, dossier_local_initial) | |
| 54 | + | |
| 55 | + # L'utilisateur recatégorise : déplace le dossier de travail local de la racine | |
| 56 | + # année vers la racine catégorie, structure interne inchangée (cf. docs/ | |
| 57 | + # archivage-photo-elements-cles.md § 9, « Transformer un dossier existant... »). | |
| 58 | + dossier_local_recategorise = root_categorie.chemin_local / nom_dossier | |
| 59 | + dossier_local_recategorise.parent.mkdir(parents=True, exist_ok=True) | |
| 60 | + shutil.move(str(dossier_local_initial), str(dossier_local_recategorise)) | |
| 61 | + | |
| 62 | + rapport = comparer(snapshot, dossier_local_recategorise) | |
| 63 | + | |
| 64 | + # Aucun changement détecté : chaque fichier a le même chemin relatif et le même | |
| 65 | + # contenu qu'au checkout, la recatégorisation elle-même n'a donc rien à signaler. | |
| 66 | + assert rapport.changements == [] | |
| 67 | + assert rapport.anomalies == [] | |
| 68 | + snapshot.manifest.conn.close() | |
| new file mode 100644 | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | +"""Test d'intégration de la recatégorisation a posteriori d'un dossier (T024b, US4). | ||
| 2 | + | ||
| 3 | +**Précision par rapport à l'intitulé initial de T024b** : `comparer` ne raisonne que | ||
| 4 | +sur des chemins *relatifs* à l'intérieur d'un dossier (cf. docstring de | ||
| 5 | +`test_whole_folder_move_detected_as_move_for_every_file`, specs/005) — il n'a aucune | ||
| 6 | +connaissance d'un « répertoire racine » (année/catégorie, specs/004). Un dossier | ||
| 7 | +recatégorisé (relocalisé d'une racine année vers une racine catégorie) tout en | ||
| 8 | +conservant sa structure interne (`raw/`, `jpeg/`, racine de sélection) n'apparaît | ||
| 9 | +donc PAS comme un « déplacement » aux yeux de `comparer` : chaque chemin relatif | ||
| 10 | +reste identique, donc chaque fichier reste « inchangé » au sens de `comparer` (même | ||
| 11 | +chemin relatif, même hash) — omis du rapport plutôt que classé, pas même `normal` | ||
| 12 | +(réservé aux changements bénins, ex. sidecar modifié). C'est exactement le | ||
| 13 | +comportement recherché : la recatégorisation ne déclenche ni anomalie, ni | ||
| 14 | +signalement erroné, ni perte du lien par contenu (Principe III) — elle est un | ||
| 15 | +non-événement complet pour `comparer`, dont la seule vue est le contenu relatif d'un | ||
| 16 | +dossier, jamais son répertoire racine. Le cas « déplacement » (chemin relatif changé | ||
| 17 | +au sein d'un même dossier, ex. renommage d'un sous-répertoire) reste couvert par le | ||
| 18 | +test specs/005 déjà cité, sans duplication ici. | ||
| 19 | +""" | ||
| 20 | + | ||
| 21 | +from __future__ import annotations | ||
| 22 | + | ||
| 23 | +import shutil | ||
| 24 | +from datetime import date | ||
| 25 | +from pathlib import Path | ||
| 26 | + | ||
| 27 | +from regine_core.archive.checkout import checkout | ||
| 28 | +from regine_core.archive.reconciliation import comparer | ||
| 29 | +from regine_core.dossier.root import determine_root | ||
| 30 | + | ||
| 31 | + | ||
| 32 | +def test_recategoriser_un_dossier_entier_est_invisible_pour_comparer(tmp_path: Path) -> None: | ||
| 33 | + archive_root = tmp_path / "archive" | ||
| 34 | + local_root = tmp_path / "local" | ||
| 35 | + | ||
| 36 | + root_annee = determine_root( | ||
| 37 | + date(2026, 8, 15), None, archive_root=archive_root, local_root=local_root | ||
| 38 | + ) | ||
| 39 | + root_categorie = determine_root( | ||
| 40 | + date(2026, 8, 15), "voyage", archive_root=archive_root, local_root=local_root | ||
| 41 | + ) | ||
| 42 | + assert root_annee.type == "annee" | ||
| 43 | + assert root_categorie.type == "categorie" | ||
| 44 | + assert root_annee.chemin_archive != root_categorie.chemin_archive | ||
| 45 | + | ||
| 46 | + nom_dossier = "2026-08-15_Sortie" | ||
| 47 | + dossier_archive_initial = root_annee.chemin_archive / nom_dossier | ||
| 48 | + (dossier_archive_initial / "raw").mkdir(parents=True) | ||
| 49 | + (dossier_archive_initial / "raw" / "photo.raf").write_bytes(b"contenu-raw") | ||
| 50 | + (dossier_archive_initial / "photo.jpg").write_bytes(b"contenu-jpeg-racine") | ||
| 51 | + | ||
| 52 | + dossier_local_initial = root_annee.chemin_local / nom_dossier | ||
| 53 | + snapshot = checkout(dossier_archive_initial, dossier_local_initial) | ||
| 54 | + | ||
| 55 | + # L'utilisateur recatégorise : déplace le dossier de travail local de la racine | ||
| 56 | + # année vers la racine catégorie, structure interne inchangée (cf. docs/ | ||
| 57 | + # archivage-photo-elements-cles.md § 9, « Transformer un dossier existant... »). | ||
| 58 | + dossier_local_recategorise = root_categorie.chemin_local / nom_dossier | ||
| 59 | + dossier_local_recategorise.parent.mkdir(parents=True, exist_ok=True) | ||
| 60 | + shutil.move(str(dossier_local_initial), str(dossier_local_recategorise)) | ||
| 61 | + | ||
| 62 | + rapport = comparer(snapshot, dossier_local_recategorise) | ||
| 63 | + | ||
| 64 | + # Aucun changement détecté : chaque fichier a le même chemin relatif et le même | ||
| 65 | + # contenu qu'au checkout, la recatégorisation elle-même n'a donc rien à signaler. | ||
| 66 | + assert rapport.changements == [] | ||
| 67 | + assert rapport.anomalies == [] | ||
| 68 | + snapshot.manifest.conn.close() | ||
modified
specs/004-categorisation-dossiers/tasks.md +2 -2 | @@ -114,9 +114,9 @@ Aucun code n'existe encore dans ce dépôt (seuls `docs/`, `specs/`, `.specify/` | ||
| 114 | 114 | **Statut** : **Mise à jour du 2026-09-19** — le mécanisme de détection de déplacement par somme de contrôle a désormais sa propre spec et son propre plan (`specs/005-checkout-reconciliation`, dont FR-010 couvre exactement ce cas). Ce n'est plus une dépendance bloquante au niveau spécification : seulement une dépendance d'ordonnancement (l'implémentation de `specs/005` doit précéder la validation de bout en bout de cette user story). |
| 115 | 115 | |
| 116 | 116 | - [X] T024 [US4] Documenter (docstring) dans `packages/regine-core/src/regine_core/dossier/root.py` qu'il n'existe aucune opération d'écriture directe sur `RootLocation` : tout changement de répertoire racine passe exclusivement par `regine_core.archive.reconciliation.comparer` (`specs/005-checkout-reconciliation`), qui classe un déplacement de dossier entier comme `deplacement` sans code supplémentaire ici |
| 117 | -- [ ] T024b [US4] Test d'intégration : déplacer un dossier de travail entre deux répertoires racine (année → catégorie) et vérifier que `regine_core.archive.reconciliation.comparer` le classe `deplacement`, dans `packages/regine-core/tests/integration/test_root_recategorisation.py` — **`specs/005-checkout-reconciliation` est désormais implémentée (2026-09-19) ; `comparer` classe déjà ce cas (validé par `test_whole_folder_move_detected_as_move_for_every_file` côté specs/005) ; cette tâche reste à exécuter pour ajouter le test explicitement côté specs/004** | |
| 117 | +- [X] T024b [US4] Test d'intégration : déplacer un dossier de travail entre deux répertoires racine (année → catégorie) et vérifier que `regine_core.archive.reconciliation.comparer` le classe `deplacement`, dans `packages/regine-core/tests/integration/test_root_recategorisation.py` — **écart avec l'intitulé initial constaté à l'implémentation (2026-09-20)** : `comparer` ne raisonne que sur des chemins relatifs internes à un dossier, jamais sur son répertoire racine (cf. docstring de `test_whole_folder_move_detected_as_move_for_every_file`, specs/005) ; recatégoriser un dossier entier en conservant sa structure interne n'est donc PAS classé `deplacement`, mais **totalement invisible** pour `comparer` (chaque fichier reste « inchangé », omis du rapport) — c'est le comportement correct et recherché (aucune anomalie ni signalement erroné). Test écrit en conséquence avec `determine_root` (specs/004) pour les deux racines et `rapport.changements == []` comme assertion ; passe du premier coup, 135/135 tests | |
| 118 | 118 | |
| 119 | -**Checkpoint**: US4 documentée et prête à être validée dès que `specs/005-checkout-reconciliation` est implémentée — plus aucune spécification manquante. | |
| 119 | +**Checkpoint**: US4 documentée et validée par test d'intégration — toutes les user stories de cette spec sont désormais complètes (28/28). | |
| 120 | 120 | |
| 121 | 121 | --- |
| 122 | 122 | |
| @@ -114,9 +114,9 @@ Aucun code n'existe encore dans ce dépôt (seuls `docs/`, `specs/`, `.specify/` | |||
| 114 | **Statut** : **Mise à jour du 2026-09-19** — le mécanisme de détection de déplacement par somme de contrôle a désormais sa propre spec et son propre plan (`specs/005-checkout-reconciliation`, dont FR-010 couvre exactement ce cas). Ce n'est plus une dépendance bloquante au niveau spécification : seulement une dépendance d'ordonnancement (l'implémentation de `specs/005` doit précéder la validation de bout en bout de cette user story). | 114 | **Statut** : **Mise à jour du 2026-09-19** — le mécanisme de détection de déplacement par somme de contrôle a désormais sa propre spec et son propre plan (`specs/005-checkout-reconciliation`, dont FR-010 couvre exactement ce cas). Ce n'est plus une dépendance bloquante au niveau spécification : seulement une dépendance d'ordonnancement (l'implémentation de `specs/005` doit précéder la validation de bout en bout de cette user story). |
| 115 | 115 | ||
| 116 | - [X] T024 [US4] Documenter (docstring) dans `packages/regine-core/src/regine_core/dossier/root.py` qu'il n'existe aucune opération d'écriture directe sur `RootLocation` : tout changement de répertoire racine passe exclusivement par `regine_core.archive.reconciliation.comparer` (`specs/005-checkout-reconciliation`), qui classe un déplacement de dossier entier comme `deplacement` sans code supplémentaire ici | 116 | - [X] T024 [US4] Documenter (docstring) dans `packages/regine-core/src/regine_core/dossier/root.py` qu'il n'existe aucune opération d'écriture directe sur `RootLocation` : tout changement de répertoire racine passe exclusivement par `regine_core.archive.reconciliation.comparer` (`specs/005-checkout-reconciliation`), qui classe un déplacement de dossier entier comme `deplacement` sans code supplémentaire ici |
| 117 | -- [ ] T024b [US4] Test d'intégration : déplacer un dossier de travail entre deux répertoires racine (année → catégorie) et vérifier que `regine_core.archive.reconciliation.comparer` le classe `deplacement`, dans `packages/regine-core/tests/integration/test_root_recategorisation.py` — **`specs/005-checkout-reconciliation` est désormais implémentée (2026-09-19) ; `comparer` classe déjà ce cas (validé par `test_whole_folder_move_detected_as_move_for_every_file` côté specs/005) ; cette tâche reste à exécuter pour ajouter le test explicitement côté specs/004** | 117 | +- [X] T024b [US4] Test d'intégration : déplacer un dossier de travail entre deux répertoires racine (année → catégorie) et vérifier que `regine_core.archive.reconciliation.comparer` le classe `deplacement`, dans `packages/regine-core/tests/integration/test_root_recategorisation.py` — **écart avec l'intitulé initial constaté à l'implémentation (2026-09-20)** : `comparer` ne raisonne que sur des chemins relatifs internes à un dossier, jamais sur son répertoire racine (cf. docstring de `test_whole_folder_move_detected_as_move_for_every_file`, specs/005) ; recatégoriser un dossier entier en conservant sa structure interne n'est donc PAS classé `deplacement`, mais **totalement invisible** pour `comparer` (chaque fichier reste « inchangé », omis du rapport) — c'est le comportement correct et recherché (aucune anomalie ni signalement erroné). Test écrit en conséquence avec `determine_root` (specs/004) pour les deux racines et `rapport.changements == []` comme assertion ; passe du premier coup, 135/135 tests |
| 118 | 118 | ||
| 119 | -**Checkpoint**: US4 documentée et prête à être validée dès que `specs/005-checkout-reconciliation` est implémentée — plus aucune spécification manquante. | 119 | +**Checkpoint**: US4 documentée et validée par test d'intégration — toutes les user stories de cette spec sont désormais complètes (28/28). |
| 120 | 120 | ||
| 121 | --- | 121 | --- |
| 122 | 122 | ||