import tempfile import unittest from contextlib import ExitStack from pathlib import Path from unittest.mock import patch from copienator import CliError, EvaluationWorkspace, ExitCode from copienator.annotation_data import AnnotationLoadResult from copienator.commands import annotating as simple from copienator.commands import annotating_by_label as grouped from copienator.commands import annotating_with_checks as checks from copienator.commands import import_annotations class AnnotationImportCleanupTests(unittest.TestCase): def setUp(self): temporary = tempfile.TemporaryDirectory() self.addCleanup(temporary.cleanup) self.root = Path(temporary.name) self.workspace = EvaluationWorkspace(self.root / "Exam") for directory in ("Copies", "Par label"): (self.workspace.root / directory).mkdir(parents=True) (self.workspace.root / "labels").write_text("A\n") (self.workspace.root / "correction.json").write_text("{}") self.import_dir = self.root / "Annotées" self.import_dir.mkdir() self.patch = patch.object(import_annotations, "IMPORT_DIR", self.import_dir) self.patch.start() self.addCleanup(self.patch.stop) def test_cleanup_empties_folder_without_following_child_symlinks(self): (self.import_dir / "old.pdf").write_bytes(b"return") nested = self.import_dir / "nested" nested.mkdir() (nested / "old.jpg").write_bytes(b"return") outside = self.root / "keep" outside.mkdir() (outside / "keep.pdf").write_bytes(b"keep") (self.import_dir / "link").symlink_to(outside, target_is_directory=True) import_annotations.clear_import_directory(self.workspace) self.assertTrue(self.import_dir.is_dir()) self.assertEqual(list(self.import_dir.iterdir()), []) self.assertTrue((outside / "keep.pdf").is_file()) def test_cleanup_handles_unconfigured_or_missing_folder(self): for configured in (None, "", self.root / "absent"): with self.subTest(configured=configured), \ patch.object(import_annotations, "IMPORT_DIR", configured): import_annotations.clear_import_directory(self.workspace) self.assertFalse((self.root / "absent").exists()) def test_cleanup_rejects_an_ancestor_of_evaluation(self): with patch.object(import_annotations, "IMPORT_DIR", self.root), \ self.assertRaises(CliError): import_annotations.clear_import_directory(self.workspace) self.assertTrue(self.workspace.root.is_dir()) def test_all_modes_clean_only_when_checked_and_successful(self): for module in (simple, checks, grouped): for clear_import, warnings in ((False, []), (True, []), (True, ["missing answer"])): with self.subTest(mode=module.__name__, checked=clear_import, warnings=warnings): old = self.import_dir / "old.pdf" old.write_bytes(b"return") with ExitStack() as patches: patches.enter_context(patch.object( module, "load_annotation_data", return_value=AnnotationLoadResult({"01": {"A": {}}}, warnings), )) if module is simple: patches.enter_context(patch.object( simple, "process_correction", return_value=ExitCode.SUCCESS )) elif module is checks: patches.enter_context(patch.object( checks, "_render_student", return_value="success" )) else: patches.enter_context(patch.object( grouped, "_generate_groups", return_value=(1, 0) )) patches.enter_context(patch.object( grouped, "_load_label_groups", return_value=[["A"]] )) targets = (self.workspace.root,) if module is checks else () status = module.run(self.workspace, *targets, clear_import=clear_import) self.assertEqual(status, ExitCode.PARTIAL if warnings else ExitCode.SUCCESS) self.assertEqual(old.exists(), not (clear_import and not warnings)) def test_cli_forwards_cleanup_flag_for_each_mode(self): for module in (simple, checks, grouped): with self.subTest(mode=module.__name__), \ patch.object(module, "run", return_value=ExitCode.SUCCESS) as run: self.assertEqual(module.main([str(self.workspace.root), "--clear-import"]), 0) self.assertTrue(run.call_args.kwargs["clear_import"])