"""Tests for the Cora distribution watcher (scheduler._distribute_cora_file).""" from __future__ import annotations from dataclasses import dataclass, field from pathlib import Path from unittest.mock import MagicMock, patch from cheddahbot.config import AutoCoraConfig, Config, ContentConfig, LinkBuildingConfig @dataclass class FakeTask: """Minimal ClickUp task stub for distribution tests.""" name: str = "" task_type: str = "" custom_fields: dict = field(default_factory=dict) def _make_scheduler(tmp_path, *, lb_folder="", content_inbox="", human_inbox=""): """Build a Scheduler with temp paths and mocked dependencies.""" from cheddahbot.scheduler import Scheduler config = Config() config.link_building = LinkBuildingConfig(watch_folder=lb_folder) config.content = ContentConfig(cora_inbox=content_inbox) config.autocora = AutoCoraConfig(cora_human_inbox=human_inbox, enabled=True) config.clickup.enabled = True config.clickup.space_id = "sp1" config.clickup.api_token = "tok" db = MagicMock() agent = MagicMock() sched = Scheduler(config=config, db=db, agent=agent) return sched KW_FIELDS = {"Keyword": "ac drive repair"} def _drop_xlsx(folder: Path, name: str = "ac-drive-repair.xlsx") -> Path: """Create a dummy xlsx file in the given folder.""" folder.mkdir(parents=True, exist_ok=True) p = folder / name p.write_bytes(b"fake-xlsx-data") return p # ── Distribution logic tests ── def test_distribute_lb_only(tmp_path): """LB task matched → copies to cora-inbox only.""" human = tmp_path / "human" lb = tmp_path / "lb" content = tmp_path / "content" xlsx = _drop_xlsx(human) sched = _make_scheduler( tmp_path, lb_folder=str(lb), content_inbox=str(content), human_inbox=str(human) ) tasks = [FakeTask(name="LB task", task_type="Link Building", custom_fields=KW_FIELDS)] with patch.object(sched, "_get_clickup_client") as mock_client: mock_client.return_value.get_tasks_from_overall_lists.return_value = tasks sched._distribute_cora_file(xlsx) assert (lb / xlsx.name).exists() assert not (content / xlsx.name).exists() assert (human / "processed" / xlsx.name).exists() assert not xlsx.exists() def test_distribute_content_only(tmp_path): """Content task matched → copies to content-cora-inbox only.""" human = tmp_path / "human" lb = tmp_path / "lb" content = tmp_path / "content" xlsx = _drop_xlsx(human) sched = _make_scheduler( tmp_path, lb_folder=str(lb), content_inbox=str(content), human_inbox=str(human) ) tasks = [FakeTask(name="CC task", task_type="Content Creation", custom_fields=KW_FIELDS)] with patch.object(sched, "_get_clickup_client") as mock_client: mock_client.return_value.get_tasks_from_overall_lists.return_value = tasks sched._distribute_cora_file(xlsx) assert not (lb / xlsx.name).exists() assert (content / xlsx.name).exists() assert (human / "processed" / xlsx.name).exists() def test_distribute_mixed(tmp_path): """Both LB and Content tasks matched → copies to both inboxes.""" human = tmp_path / "human" lb = tmp_path / "lb" content = tmp_path / "content" xlsx = _drop_xlsx(human) sched = _make_scheduler( tmp_path, lb_folder=str(lb), content_inbox=str(content), human_inbox=str(human) ) tasks = [ FakeTask(name="LB task", task_type="Link Building", custom_fields=KW_FIELDS), FakeTask(name="CC task", task_type="Content Creation", custom_fields=KW_FIELDS), ] with patch.object(sched, "_get_clickup_client") as mock_client: mock_client.return_value.get_tasks_from_overall_lists.return_value = tasks sched._distribute_cora_file(xlsx) assert (lb / xlsx.name).exists() assert (content / xlsx.name).exists() assert (human / "processed" / xlsx.name).exists() def test_distribute_no_match(tmp_path): """No matching tasks → file stays in inbox, not moved to processed.""" human = tmp_path / "human" lb = tmp_path / "lb" content = tmp_path / "content" xlsx = _drop_xlsx(human) sched = _make_scheduler( tmp_path, lb_folder=str(lb), content_inbox=str(content), human_inbox=str(human) ) with patch.object(sched, "_get_clickup_client") as mock_client: mock_client.return_value.get_tasks_from_overall_lists.return_value = [] sched._distribute_cora_file(xlsx) assert xlsx.exists() # Still in inbox assert not (human / "processed" / xlsx.name).exists() def test_distribute_opo_task(tmp_path): """On Page Optimization task → copies to content inbox.""" human = tmp_path / "human" lb = tmp_path / "lb" content = tmp_path / "content" xlsx = _drop_xlsx(human) sched = _make_scheduler( tmp_path, lb_folder=str(lb), content_inbox=str(content), human_inbox=str(human) ) tasks = [FakeTask(name="OPO task", task_type="On Page Optimization", custom_fields=KW_FIELDS)] with patch.object(sched, "_get_clickup_client") as mock_client: mock_client.return_value.get_tasks_from_overall_lists.return_value = tasks sched._distribute_cora_file(xlsx) assert not (lb / xlsx.name).exists() assert (content / xlsx.name).exists() # ── Scan tests ── def test_scan_skips_processed(tmp_path): """Files already in processed/ are skipped.""" human = tmp_path / "human" lb = tmp_path / "lb" content = tmp_path / "content" # File in both top-level and processed/ _drop_xlsx(human) _drop_xlsx(human / "processed") sched = _make_scheduler( tmp_path, lb_folder=str(lb), content_inbox=str(content), human_inbox=str(human) ) with patch.object(sched, "_distribute_cora_file") as mock_dist: sched._scan_cora_human_inbox() mock_dist.assert_not_called() def test_scan_skips_temp_files(tmp_path): """Office temp files (~$...) are skipped.""" human = tmp_path / "human" lb = tmp_path / "lb" content = tmp_path / "content" _drop_xlsx(human, name="~$ac-drive-repair.xlsx") sched = _make_scheduler( tmp_path, lb_folder=str(lb), content_inbox=str(content), human_inbox=str(human) ) with patch.object(sched, "_distribute_cora_file") as mock_dist: sched._scan_cora_human_inbox() mock_dist.assert_not_called() def test_scan_empty_inbox(tmp_path): """Empty inbox → no-op.""" human = tmp_path / "human" human.mkdir() sched = _make_scheduler(tmp_path, human_inbox=str(human)) with patch.object(sched, "_distribute_cora_file") as mock_dist: sched._scan_cora_human_inbox() mock_dist.assert_not_called() def test_distribute_copy_failure_no_move(tmp_path): """If copy fails, original is NOT moved to processed.""" human = tmp_path / "human" xlsx = _drop_xlsx(human) sched = _make_scheduler(tmp_path, lb_folder="/nonexistent/network/path", human_inbox=str(human)) tasks = [FakeTask(name="LB task", task_type="Link Building", custom_fields=KW_FIELDS)] with ( patch.object(sched, "_get_clickup_client") as mock_client, patch("cheddahbot.scheduler.shutil.copy2", side_effect=OSError("network down")), ): mock_client.return_value.get_tasks_from_overall_lists.return_value = tasks sched._distribute_cora_file(xlsx) assert xlsx.exists() # Original untouched assert not (human / "processed" / xlsx.name).exists()