| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147 |
- """The Report & Release pipeline — the highest-risk behaviour.
- Asserts on exact PPS call kwargs, not just counts: the localguid-stability rule (release
- must NOT pass deletedfolder) and the lazy guid-refind are both invisible to a call-count
- check but critical to correctness.
- """
- import pytest
- from conftest import FakePPS
- from pipeline import (
- ALLOWED_STEPS,
- describe_pipeline,
- pipeline_destination,
- run_pipeline,
- )
- from pps_client import PPSError
- def _cfg(steps, move_target="Rep", deleted="Deleted", step_delay=0):
- return {
- "deleted_folder": deleted,
- "list_query": "from=*",
- "default_days_back": 7,
- "report_release": {
- "steps": steps,
- "move_target": move_target,
- "step_delay_seconds": step_delay,
- },
- }
- def _chunk():
- return [
- {"localguid": "6:6:1", "guid": "g1"},
- {"localguid": "6:6:2", "guid": "g2"},
- ]
- def test_release_only_has_no_deletedfolder():
- pps = FakePPS()
- run_pipeline(pps, _cfg(["release"]), "Quarantine", _chunk())
- acts = pps.acts()
- assert len(acts) == 1
- assert acts[0]["action"] == "release"
- assert acts[0]["deletedfolder"] is None # THE localguid-stability rule
- assert acts[0]["folder"] == "Quarantine"
- def test_release_move_is_two_calls_zero_searches():
- pps = FakePPS()
- run_pipeline(pps, _cfg(["release", "move"]), "Quarantine", _chunk())
- assert [a["action"] for a in pps.acts()] == ["release", "move"]
- assert pps.acts()[0]["deletedfolder"] is None
- assert pps.acts()[1]["targetfolder"] == "Rep"
- assert pps.acts()[1]["folder"] == "Quarantine"
- assert pps.searches() == [] # lazy refind: no search needed
- def test_move_fallback_refinds_in_deleted_folder():
- class MovingPPS(FakePPS):
- def act(self, action, folder, localguids, *, targetfolder=None, deletedfolder=None, scan=False):
- if action == "move" and folder == "Quarantine":
- self.calls.append({"m": "act", "action": action, "folder": folder,
- "localguids": list(localguids), "targetfolder": targetfolder,
- "deletedfolder": deletedfolder, "scan": scan})
- raise PPSError("release relocated it")
- return super().act(action, folder, localguids, targetfolder=targetfolder,
- deletedfolder=deletedfolder, scan=scan)
- pps = MovingPPS(records=[{"guid": "g1", "localguid": "D:1"}, {"guid": "g2", "localguid": "D:2"}])
- run_pipeline(pps, _cfg(["release", "move"]), "Quarantine", _chunk())
- # release, failed move on Quarantine, search Deleted, move from Deleted
- assert [s["folder"] for s in pps.searches()] == ["Deleted"]
- moves = [a for a in pps.acts() if a["action"] == "move"]
- assert moves[-1]["folder"] == "Deleted"
- assert sorted(moves[-1]["localguids"]) == ["D:1", "D:2"]
- def test_release_move_delete_refinds_between_move_and_delete():
- pps = FakePPS(records=[{"guid": "g1", "localguid": "R:1"}, {"guid": "g2", "localguid": "R:2"}])
- run_pipeline(pps, _cfg(["release", "move", "delete"]), "Quarantine", _chunk())
- actions = [a["action"] for a in pps.acts()]
- assert actions == ["release", "move", "delete"]
- # delete happens in the move_target after a refind there
- assert len(pps.searches()) == 1 and pps.searches()[0]["folder"] == "Rep"
- delete = [a for a in pps.acts() if a["action"] == "delete"][0]
- assert delete["folder"] == "Rep"
- assert delete["deletedfolder"] == "Deleted"
- def test_refind_no_guids_raises():
- pps = FakePPS()
- chunk = [{"localguid": "6:6:1"}] # no guid
- with pytest.raises(PPSError):
- # force a refind by making move fail
- class P(FakePPS):
- def act(self, *a, **k):
- if a[0] == "move":
- raise PPSError("relocated")
- return super().act(*a, **k)
- run_pipeline(P(), _cfg(["release", "move"]), "Quarantine", chunk)
- def test_pipeline_destination():
- assert pipeline_destination(_cfg(["release"])) is None
- assert pipeline_destination(_cfg(["release", "move"])) == "Rep"
- assert pipeline_destination(_cfg(["release", "delete"])) == "Deleted"
- assert pipeline_destination(_cfg(["release", "move", "delete"])) == "Deleted"
- def test_describe_pipeline():
- text = describe_pipeline(_cfg(["release", "move", "delete"]))
- assert "released in place" in text and "Rep" in text and "Deleted" in text
- assert "→" in text
- def test_allowed_steps_is_canonical_order():
- assert ALLOWED_STEPS == ("release", "move", "delete")
- def test_step_delay_waits_between_steps():
- pps = FakePPS()
- waits = []
- run_pipeline(pps, _cfg(["release", "move"], step_delay=60), "Quarantine", _chunk(),
- sleep=waits.append)
- # One wait, of the configured length, between the two steps.
- assert waits == [60]
- def test_no_delay_before_first_or_when_zero():
- pps = FakePPS()
- waits = []
- run_pipeline(pps, _cfg(["release"], step_delay=60), "Quarantine", _chunk(), sleep=waits.append)
- assert waits == [] # single step -> no wait
- waits.clear()
- run_pipeline(pps, _cfg(["release", "move"], step_delay=0), "Quarantine", _chunk(),
- sleep=waits.append)
- assert waits == [] # delay 0 -> disabled
- def test_delay_scales_with_step_count():
- pps = FakePPS(records=[{"guid": "g1", "localguid": "R:1"}, {"guid": "g2", "localguid": "R:2"}])
- waits = []
- run_pipeline(pps, _cfg(["release", "move", "delete"], step_delay=30), "Quarantine",
- _chunk(), sleep=waits.append)
- assert waits == [30, 30] # before move, before delete
|