"""CLI + module tests for scheduled agent execution (ADR-005, WP-0006).""" from __future__ import annotations import json from pathlib import Path import pytest from click.testing import CliRunner from kaizen_agentic.cli import cli from kaizen_agentic.schedule import ( ScheduleError, parse_schedule, schedule_path, validate_schedule, ) @pytest.fixture def runner() -> CliRunner: return CliRunner() @pytest.fixture def project_dir(tmp_path: Path) -> Path: root = tmp_path / "demo-project" root.mkdir() return root class TestScheduleModule: def test_parse_requires_version(self): with pytest.raises(ScheduleError): parse_schedule({"agents": {}}) def test_parse_rejects_non_mapping(self): with pytest.raises(ScheduleError): parse_schedule(["not", "a", "mapping"]) def test_validate_flags_unknown_agent_and_bad_cadence(self): schedule = parse_schedule( { "version": "1", "agents": { "coach": {"cadence": "weekly", "enabled": True}, "made-up": {"cadence": "hourly"}, }, } ) errors = validate_schedule(schedule, known_agents=["coach", "optimization"]) assert any("hourly" in e for e in errors) assert any("made-up" in e for e in errors) def test_validate_clean_schedule(self): schedule = parse_schedule( {"version": "1", "agents": {"coach": {"cadence": "weekly"}}} ) assert validate_schedule(schedule, known_agents=["coach"]) == [] class TestScheduleCli: def test_init_creates_default_schedule(self, runner: CliRunner, project_dir: Path): result = runner.invoke(cli, ["schedule", "init", "--target", str(project_dir)]) assert result.exit_code == 0 path = schedule_path(project_dir) assert path.exists() assert "coach" in path.read_text() def test_init_no_overwrite_without_force( self, runner: CliRunner, project_dir: Path ): runner.invoke(cli, ["schedule", "init", "--target", str(project_dir)]) result = runner.invoke(cli, ["schedule", "init", "--target", str(project_dir)]) assert result.exit_code == 0 assert "already exists" in result.output def test_validate_passes_on_default(self, runner: CliRunner, project_dir: Path): runner.invoke(cli, ["schedule", "init", "--target", str(project_dir)]) result = runner.invoke( cli, ["schedule", "validate", "--target", str(project_dir)] ) assert result.exit_code == 0 assert "valid" in result.output def test_validate_missing_file_errors(self, runner: CliRunner, project_dir: Path): result = runner.invoke( cli, ["schedule", "validate", "--target", str(project_dir)] ) assert result.exit_code == 1 def test_validate_rejects_bad_schema(self, runner: CliRunner, project_dir: Path): path = schedule_path(project_dir) path.parent.mkdir(parents=True, exist_ok=True) path.write_text("version: '1'\nagents:\n not-an-agent:\n cadence: weekly\n") result = runner.invoke( cli, ["schedule", "validate", "--target", str(project_dir)] ) assert result.exit_code == 1 assert "not-an-agent" in result.output def test_list_shows_enabled(self, runner: CliRunner, project_dir: Path): runner.invoke(cli, ["schedule", "init", "--target", str(project_dir)]) result = runner.invoke(cli, ["schedule", "list", "--target", str(project_dir)]) assert result.exit_code == 0 assert "coach" in result.output # tdd-workflow is disabled by default; hidden without --all assert "tdd-workflow" not in result.output def test_list_all_shows_disabled(self, runner: CliRunner, project_dir: Path): runner.invoke(cli, ["schedule", "init", "--target", str(project_dir)]) result = runner.invoke( cli, ["schedule", "list", "--all", "--target", str(project_dir)] ) assert "tdd-workflow" in result.output def test_prepare_markdown_includes_agent_prompt( self, runner: CliRunner, project_dir: Path ): result = runner.invoke( cli, ["schedule", "prepare", "coach", "--target", str(project_dir)] ) assert result.exit_code == 0 assert "Scheduled Run Orientation: coach" in result.output assert "## Agent Prompt" in result.output assert "Coach Agent" in result.output assert "## Session Close" in result.output def test_prepare_json_format(self, runner: CliRunner, project_dir: Path): result = runner.invoke( cli, [ "schedule", "prepare", "coach", "--target", str(project_dir), "--format", "json", ], ) assert result.exit_code == 0 payload = json.loads(result.output) assert payload["agent"] == "coach" assert payload["agent_prompt_found"] is True assert payload["session_close"] def test_prepare_unknown_agent_notes_missing( self, runner: CliRunner, project_dir: Path ): result = runner.invoke( cli, ["schedule", "prepare", "no-such-agent", "--target", str(project_dir)], ) assert result.exit_code == 0 assert "not found in registry" in result.output def test_prepare_includes_memory_when_present( self, runner: CliRunner, project_dir: Path ): runner.invoke(cli, ["memory", "init", "coach", "--target", str(project_dir)]) result = runner.invoke( cli, ["schedule", "prepare", "coach", "--target", str(project_dir)] ) assert "## Project Memory" in result.output assert "Project Context" in result.output