""" Tests for alfred.agent.workflows.loader.WorkflowLoader """ import pytest import yaml from alfred.agent.workflows.loader import WorkflowLoader # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @pytest.fixture def workflows_dir(tmp_path): """A temp directory pre-populated with one valid workflow YAML.""" wf = { "name": "test_workflow", "description": "A test workflow", "tools": ["list_folder", "move_media"], "steps": [ {"id": "step1", "tool": "list_folder"}, ], } (tmp_path / "test_workflow.yaml").write_text(yaml.dump(wf)) return tmp_path @pytest.fixture def loader_from_dir(workflows_dir, monkeypatch): """WorkflowLoader pointed at our temp dir.""" import alfred.agent.workflows.loader as loader_module monkeypatch.setattr(loader_module, "_WORKFLOWS_DIR", workflows_dir) return WorkflowLoader() # --------------------------------------------------------------------------- # Real loader (loads actual YAML files from the repo) # --------------------------------------------------------------------------- class TestRealWorkflows: def test_organize_media_loaded(self): loader = WorkflowLoader() assert "organize_media" in loader.names() def test_organize_media_has_required_keys(self): loader = WorkflowLoader() wf = loader.get("organize_media") assert "name" in wf assert "steps" in wf assert "tools" in wf def test_organize_media_tools_list(self): loader = WorkflowLoader() wf = loader.get("organize_media") tools = wf["tools"] assert "list_folder" in tools assert "move_media" in tools assert "manage_subtitles" in tools assert "create_seed_links" in tools assert "resolve_destination" in tools def test_organize_media_steps_order(self): loader = WorkflowLoader() wf = loader.get("organize_media") step_ids = [s["id"] for s in wf["steps"]] # resolve_destination must come before move_file assert step_ids.index("resolve_destination") < step_ids.index("move_file") # move_file before handle_subtitles assert step_ids.index("move_file") < step_ids.index("handle_subtitles") # ask_seeding before create_seed_links assert step_ids.index("ask_seeding") < step_ids.index("create_seed_links") def test_ask_seeding_has_yes_no_answers(self): loader = WorkflowLoader() wf = loader.get("organize_media") ask_step = next(s for s in wf["steps"] if s["id"] == "ask_seeding") answers = ask_step["ask_user"]["answers"] # PyYAML parses yes/no as booleans — we normalise to str in runtime answer_keys = {str(k) for k in answers.keys()} assert "yes" in answer_keys assert "no" in answer_keys def test_naming_convention_present(self): loader = WorkflowLoader() wf = loader.get("organize_media") assert "naming_convention" in wf assert "tv_show" in wf["naming_convention"] assert "movie" in wf["naming_convention"] # --------------------------------------------------------------------------- # WorkflowLoader mechanics (via monkeypatched dir) # --------------------------------------------------------------------------- class TestLoaderMechanics: def test_get_returns_workflow(self, loader_from_dir): wf = loader_from_dir.get("test_workflow") assert wf is not None assert wf["name"] == "test_workflow" def test_get_returns_none_for_unknown(self, loader_from_dir): assert loader_from_dir.get("nonexistent") is None def test_names_returns_list(self, loader_from_dir): names = loader_from_dir.names() assert isinstance(names, list) assert "test_workflow" in names def test_all_returns_dict(self, loader_from_dir): all_wf = loader_from_dir.all() assert isinstance(all_wf, dict) assert "test_workflow" in all_wf def test_uses_yaml_name_field(self, tmp_path, monkeypatch): """name from YAML content takes priority over filename stem.""" import alfred.agent.workflows.loader as loader_module monkeypatch.setattr(loader_module, "_WORKFLOWS_DIR", tmp_path) wf = {"name": "my_custom_name", "steps": []} (tmp_path / "completely_different_filename.yaml").write_text(yaml.dump(wf)) loader = WorkflowLoader() assert "my_custom_name" in loader.names() assert "completely_different_filename" not in loader.names() def test_falls_back_to_stem_when_no_name(self, tmp_path, monkeypatch): import alfred.agent.workflows.loader as loader_module monkeypatch.setattr(loader_module, "_WORKFLOWS_DIR", tmp_path) (tmp_path / "my_workflow.yaml").write_text(yaml.dump({"steps": []})) loader = WorkflowLoader() assert "my_workflow" in loader.names() def test_skips_malformed_yaml(self, tmp_path, monkeypatch): import alfred.agent.workflows.loader as loader_module monkeypatch.setattr(loader_module, "_WORKFLOWS_DIR", tmp_path) (tmp_path / "valid.yaml").write_text(yaml.dump({"name": "valid", "steps": []})) (tmp_path / "broken.yaml").write_text("key: [unclosed bracket") loader = WorkflowLoader() assert "valid" in loader.names() assert "broken" not in loader.names() def test_deterministic_load_order(self, tmp_path, monkeypatch): """Files loaded in sorted order — later file wins on name collision.""" import alfred.agent.workflows.loader as loader_module monkeypatch.setattr(loader_module, "_WORKFLOWS_DIR", tmp_path) (tmp_path / "a_workflow.yaml").write_text( yaml.dump({"name": "duplicate", "version": 1}) ) (tmp_path / "b_workflow.yaml").write_text( yaml.dump({"name": "duplicate", "version": 2}) ) loader = WorkflowLoader() # b_workflow loaded last → version 2 wins assert loader.get("duplicate")["version"] == 2 def test_empty_directory(self, tmp_path, monkeypatch): import alfred.agent.workflows.loader as loader_module monkeypatch.setattr(loader_module, "_WORKFLOWS_DIR", tmp_path) loader = WorkflowLoader() assert loader.names() == [] assert loader.all() == {}