fonzarely/regine-photos-archiverpublic⑂ Fork 0
⑂ 94fd0a0
Commits
⬇ Clone ▾
git clone https://git.rickub.com/fonzarely/regine-photos-archiver.git
git clone ssh://git@rickub.com/fonzarely/regine-photos-archiver.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

ok

Fabien Champigny committed 2026-09-20T00:31:03+02:00 Browse files
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/`
114114 **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).
115115
116116 - [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
118118
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).
120120
121121 ---
122122
@@ -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 ici116 - [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