Files
Copies/tests/test_return_outputs.py
T

145 lines
7.1 KiB
Python

import contextlib
import io
import itertools
import runpy
import tempfile
import unittest
from pathlib import Path
from unittest.mock import patch
from copienator import ExitCode, 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_regeneration_refreshes_copied_named_files_and_removes_obsolete_pdf(self):
self.prepare()
for suffix in (".jpg", ".pdf"):
target = self.destination / ("Student" + suffix)
target.unlink()
target.write_bytes(b"old copied output")
(self.destination / "custom.pdf").write_bytes(b"keep")
with patch.multiple(configuration, RETURN_JPEG_ENABLED=True, RETURN_PDF_ENABLED=True):
status = giving_names.update_named_return_answers(
self.workspace, "BGnot", copy_ids={"01"}, refresh_named=True)
self.assertEqual(status, ExitCode.SUCCESS)
self.assertEqual((self.destination / "Student.pdf").read_bytes(), b"pdf")
self.assertEqual((self.destination / "Student.jpg").read_bytes(), b"jpeg")
(self.source / "Concat_F.pdf").unlink()
giving_names.update_named_return_answers(
self.workspace, "BGnot", copy_ids={"01"}, refresh_named=True)
self.assertFalse((self.destination / "Student.pdf").is_symlink())
self.assertFalse((self.destination / "Student.pdf").exists())
self.assertEqual((self.destination / "custom.pdf").read_bytes(), b"keep")
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()