# SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc import importlib import os import uuid from pathlib import Path import pytest def test_get_talent_configs_generators(): """Test that system generators are discovered with source field.""" talent = importlib.import_module("think.talent") generators = talent.get_talent_configs(type="generate") assert "flow" in generators info = generators["flow"] assert os.path.basename(info["path"]) == "flow.md" assert isinstance(info["color"], str) assert isinstance(info["mtime"], int) assert "title" in info assert "occurrences" in info # New: check source field assert info.get("source") == "system" def test_get_output_name(): """Test generator key to filename conversion.""" talent = importlib.import_module("think.talent") # System generators: key unchanged assert talent.get_output_name("activity") == "activity" assert talent.get_output_name("flow") == "flow" # App generators: _app_name format assert talent.get_output_name("chat:sentiment") == "_chat_sentiment" assert talent.get_output_name("my_app:weekly_summary") == "_my_app_weekly_summary" def test_get_talent_configs_app_discovery(tmp_path, monkeypatch): """Test that app generators are discovered from apps/*/talent/.""" talent = importlib.import_module("think.talent") # Create a fake app with a generator app_dir = tmp_path / "apps" / "test_app" / "talent" app_dir.mkdir(parents=True) # Create generator files with frontmatter (app_dir / "custom_generator.md").write_text( '{\n "title": "Custom Generator",\n "color": "#ff0000"\n}\n\nTest prompt' ) # Also create workspace.html to make it a valid app (not strictly required for generators) (tmp_path / "apps" / "test_app" / "workspace.html").write_text("

Test

") # For now, just verify system generators have correct source generators = talent.get_talent_configs(type="generate") for key, info in generators.items(): if ":" not in key: assert info.get("source") == "system", f"{key} should have source=system" def test_get_talent_configs_by_schedule(): """Test filtering generators by schedule.""" talent = importlib.import_module("think.talent") # Get daily generators daily = talent.get_talent_configs(type="generate", schedule="daily") assert len(daily) > 0 for key, meta in daily.items(): assert meta.get("schedule") == "daily", f"{key} should have schedule=daily" # Get segment generators segment = talent.get_talent_configs(type="generate", schedule="segment") assert len(segment) > 0 for key, meta in segment.items(): assert meta.get("schedule") == "segment", f"{key} should have schedule=segment" # Verify no overlap assert not set(daily.keys()) & set(segment.keys()), ( "daily and segment should not overlap" ) # Unknown schedule returns empty dict assert talent.get_talent_configs(type="generate", schedule="hourly") == {} assert talent.get_talent_configs(type="generate", schedule="") == {} def test_get_talent_configs_include_disabled(monkeypatch): """Test include_disabled parameter.""" talent = importlib.import_module("think.talent") # Get generators without disabled (default) without_disabled = talent.get_talent_configs(type="generate", schedule="daily") # Get generators with disabled included with_disabled = talent.get_talent_configs( type="generate", schedule="daily", include_disabled=True ) # Should have at least as many with disabled included assert len(with_disabled) >= len(without_disabled) def test_scheduled_generators_have_valid_schedule(): """Test that scheduled generators have valid schedule field. Generators with a schedule field must have valid values ('segment', 'daily', or 'activity'). Some generators (like importer) have output but no schedule - they're used for ad-hoc processing, not scheduled runs. """ talent = importlib.import_module("think.talent") generators = talent.get_talent_configs(type="generate") valid_schedules = ("segment", "daily", "activity", "weekly") for key, meta in generators.items(): sched = meta.get("schedule") if sched is not None: assert sched in valid_schedules, ( f"Generator '{key}' has invalid schedule '{sched}'" ) def test_sense_in_segment_schedule(): """Test that sense generator exists in segment schedule at priority 5.""" talent = importlib.import_module("think.talent") generators = talent.get_talent_configs(type="generate", schedule="segment") assert "sense" in generators sense = generators["sense"] assert sense.get("priority") == 5, "sense should be at priority 5" sources = sense.get("load", {}) assert sources.get("transcripts") is True, "sense should include transcripts" assert sources.get("percepts") is True, "sense should include percepts" def _write_temp_talent_prompt(stem: str, frontmatter: str) -> Path: talent_dir = Path(__file__).resolve().parent.parent / "talent" prompt_path = talent_dir / f"{stem}.md" prompt_path.write_text( f"{frontmatter}\n\nTemporary test prompt\n", encoding="utf-8" ) return prompt_path def test_get_talent_configs_raises_on_missing_type_with_output(): talent = importlib.import_module("think.talent") stem = f"test_missing_type_output_{uuid.uuid4().hex}" prompt_path = _write_temp_talent_prompt( stem, '{\n "schedule": "daily",\n "priority": 10,\n "output": "md"\n}', ) try: with pytest.raises( ValueError, match=rf"Prompt '{stem}'.*missing required 'type'" ): talent.get_talent_configs(include_disabled=True) finally: prompt_path.unlink(missing_ok=True) def test_get_talent_configs_allows_missing_type_with_tools(): talent = importlib.import_module("think.talent") stem = f"test_missing_type_tools_{uuid.uuid4().hex}" prompt_path = _write_temp_talent_prompt( stem, '{\n "schedule": "daily",\n "priority": 10,\n "tools": "journal"\n}', ) try: configs = talent.get_talent_configs(include_disabled=True) assert stem in configs assert configs[stem].get("type") is None finally: prompt_path.unlink(missing_ok=True) def test_get_talent_configs_raises_when_generate_missing_output(): talent = importlib.import_module("think.talent") stem = f"test_generate_missing_output_{uuid.uuid4().hex}" prompt_path = _write_temp_talent_prompt( stem, '{\n "type": "generate",\n "schedule": "daily",\n "priority": 10\n}', ) try: with pytest.raises( ValueError, match=rf"Prompt '{stem}'.*type='generate'.*missing required 'output'", ): talent.get_talent_configs(include_disabled=True) finally: prompt_path.unlink(missing_ok=True) def test_get_talent_configs_allows_cogitate_without_tools(): talent = importlib.import_module("think.talent") stem = f"test_cogitate_missing_tools_{uuid.uuid4().hex}" prompt_path = _write_temp_talent_prompt( stem, '{\n "type": "cogitate",\n "schedule": "daily",\n "priority": 10\n}', ) try: configs = talent.get_talent_configs(include_disabled=True) assert stem in configs assert configs[stem]["type"] == "cogitate" finally: prompt_path.unlink(missing_ok=True) def test_get_talent_configs_type_generate_returns_only_generate(): talent = importlib.import_module("think.talent") generators = talent.get_talent_configs(type="generate") assert generators, "Expected at least one generate prompt" assert all(meta.get("type") == "generate" for meta in generators.values()) def test_get_talent_configs_type_cogitate_returns_only_cogitate(): talent = importlib.import_module("think.talent") cogitate_prompts = talent.get_talent_configs(type="cogitate") assert cogitate_prompts, "Expected at least one cogitate prompt" assert all(meta.get("type") == "cogitate" for meta in cogitate_prompts.values())