import contextlib import io import itertools import runpy import tempfile import unittest from pathlib import Path from unittest.mock import patch from copienator import EvaluationWorkspace, atomic_write_json, configuration from copienator.commands import clean, giving_names class ReturnOutputTests(unittest.TestCase): def setUp(self): self.temp = tempfile.TemporaryDirectory() self.addCleanup(self.temp.cleanup) self.workspace = EvaluationWorkspace(Path(self.temp.name)) self.workspace.copies_dir.mkdir() atomic_write_json(self.workspace.copies_dir / "Copie01.json", {"name": "Student"}) (self.workspace.copies_dir / "Copie01.pdf").write_bytes(b"original") atomic_write_json(self.workspace.correction_file, {}) (self.workspace.root / "names").write_text("Student\n") self.source = self.workspace.root / "BGnot" / "Copie01" self.source.mkdir(parents=True) (self.source / "Concat.jpg").write_bytes(b"jpeg") (self.source / "Concat_F.pdf").write_bytes(b"pdf") atomic_write_json(self.source / "score.json", {"Ex 1": "4"}) atomic_write_json(self.source / "info.json", { "Ex 1": {"present": True, "not_empty": True, "touched": True, "score": "4"} }) self.answer_option = patch.object(configuration, "RETURN_ANSWERS_ENABLED", False) self.answer_option.start() self.addCleanup(self.answer_option.stop) self.destination = self.workspace.return_dir / "Student (01)" def prepare(self, jpeg=True, pdf=True): with patch.object(configuration, "RETURN_JPEG_ENABLED", jpeg), patch.object( configuration, "RETURN_PDF_ENABLED", pdf ), contextlib.redirect_stdout(io.StringIO()): self.assertEqual(giving_names.run(self.workspace, annotation_dir="BGnot"), 0) def test_all_combinations_and_reenable_preserve_sources_and_scores(self): for jpeg, pdf in itertools.product((True, False), repeat=2): with self.subTest(jpeg=jpeg, pdf=pdf): self.prepare() self.prepare(jpeg, pdf) expected = {"score.json", "info.json"} if jpeg: expected.add("Student.jpg") if pdf: expected.add("Student.pdf") self.assertEqual({p.name for p in self.destination.iterdir()}, expected) self.assertEqual((self.source / "Concat.jpg").read_bytes(), b"jpeg") self.assertEqual((self.source / "Concat_F.pdf").read_bytes(), b"pdf") self.assertEqual( (self.destination / "score.json").read_bytes(), (self.source / "score.json").read_bytes(), ) self.prepare() self.assertEqual((self.destination / "Student.jpg").read_bytes(), b"jpeg") self.assertEqual((self.destination / "Student.pdf").read_bytes(), b"pdf") def test_disabling_removes_regular_files_and_broken_links_only(self): self.prepare() jpg = self.destination / "Student.jpg" jpg.unlink() jpg.write_bytes(b"old fallback copy") pdf = self.destination / "Student.pdf" pdf.unlink() pdf.symlink_to(self.source / "missing.pdf") unrelated = self.destination / "notes.txt" unrelated.write_text("keep") self.prepare(False, False) self.assertEqual({p.name for p in self.destination.iterdir()}, {"score.json", "info.json", "notes.txt"}) self.assertTrue((self.source / "Concat_F.pdf").is_file()) def test_fallback_source_respects_options(self): self.source.parent.rename(self.workspace.root / "Anot") (self.workspace.root / "BGnot").mkdir() self.prepare(False, True) self.assertFalse((self.destination / "Student.jpg").exists()) self.assertEqual((self.destination / "Student.pdf").read_bytes(), b"pdf") self.assertTrue((self.destination / "score.json").is_file()) self.assertTrue((self.destination / "info.json").is_file()) def test_cleanup_preserves_enabled_returns_without_jpeg(self): self.prepare(False, True) with patch.object(configuration, "RETURN_JPEG_ENABLED", False): plan = clean.build_cleanup_plan(self.workspace) with contextlib.redirect_stdout(io.StringIO()): clean.apply_cleanup(self.workspace, plan) self.assertFalse(self.source.exists()) self.assertEqual((self.destination / "Student.pdf").read_bytes(), b"pdf") self.assertFalse((self.destination / "Student.pdf").is_symlink()) self.assertTrue((self.destination / "score.json").is_file()) self.assertTrue((self.destination / "info.json").is_file()) def test_cleanup_accepts_scores_only_but_still_requires_scores(self): self.prepare(False, False) with patch.object(configuration, "RETURN_JPEG_ENABLED", False): plan = clean.build_cleanup_plan(self.workspace) self.assertIn(self.destination / "score.json", plan.kept_files) (self.destination / "score.json").unlink() with self.assertRaisesRegex(clean.CliError, "score.json"): clean.build_cleanup_plan(self.workspace) def test_older_config_defaults_to_enabled(self): old_config = self.workspace.root / "old_config.py" old_config.write_text("ALWAYS_CROP = False\n") with patch.dict("os.environ", {"COPIENATOR_CONFIG": str(old_config)}): loaded = runpy.run_path(configuration.__file__) self.assertIs(loaded["RETURN_JPEG_ENABLED"], True) self.assertIs(loaded["RETURN_PDF_ENABLED"], True) self.assertIs(loaded["RETURN_ANSWERS_ENABLED"], False) self.assertIs(loaded["RETURN_ANSWERS_CONTEXT"], False) self.assertIs(loaded["RETURN_ANSWERS_QUESTION"], True) self.assertIs(loaded["RETURN_ANSWERS_SOLUTION"], False) self.assertNotIn("RETURN_JSON_ENABLED", loaded) if __name__ == "__main__": unittest.main()