"""Per-task archive quality overrides outrank JSON and saved environment defaults.""" import json from types import SimpleNamespace import pytest from apps.admin_console.services.task_import import TaskCase, TaskOptions from artemis.config.agent import AgentGlobalConfig, load_agent_config from artemis.config.settings import settings @pytest.fixture(autouse=True) def recording_defaults(monkeypatch): monkeypatch.setattr(settings, "ARTEMIS_RECORDING_CONFIG", {"fps": 10, "h264_kbps": 900}) monkeypatch.setattr(settings, "ARTEMIS_TASK_RECORDING_CONFIG", {}) def test_json_recording_defaults_override_saved_defaults(tmp_path): config = tmp_path / "artemis.jsonc" config.write_text(json.dumps({"agent": {"recording": {"fps": 20}}}), encoding="utf-8") result = load_agent_config(config).recording assert result.fps == 20 assert result.h264_kbps == 900 assert result.max_long_edge == 1280 def test_task_recording_override_has_highest_priority_and_is_partial(tmp_path, monkeypatch): monkeypatch.setattr(settings, "ARTEMIS_TASK_RECORDING_CONFIG", {"fps": 30}) config = tmp_path / "artemis.jsonc" config.write_text( json.dumps({"agent": {"recording": {"fps": 20, "max_long_edge": 1920}}}), encoding="utf-8" ) result = load_agent_config(config).recording assert result.fps == 30 assert result.max_long_edge == 1920 assert result.h264_kbps == 900 def test_task_recording_override_applies_without_json_recording_block(monkeypatch): monkeypatch.setattr(settings, "ARTEMIS_TASK_RECORDING_CONFIG", {"fps": 30}) assert AgentGlobalConfig().recording.fps == 30 def test_invalid_task_recording_override_is_rejected(monkeypatch): monkeypatch.setattr(settings, "ARTEMIS_TASK_RECORDING_CONFIG", {"fps": 500}) with pytest.raises(ValueError, match="fps"): AgentGlobalConfig.model_validate({"recording": {"fps": 20}}) def test_task_and_batch_serialization_do_not_inject_unset_recording_defaults(): options = TaskOptions.model_validate({"recording": {"fps": 30}}) assert options.model_dump(exclude_none=True)["recording"] == {"fps": 30} case = TaskCase(name="fixture", goal="fixture", task_config=options) assert case.model_dump()["task_config"]["recording"] == {"fps": 30} # Round trips through stored batches preserve the partial override. restored = TaskCase.model_validate(case.model_dump()) assert restored.model_dump()["task_config"]["recording"] == {"fps": 30} def test_worker_env_keeps_task_quality_separate_from_saved_defaults(monkeypatch): from apps.admin_console.services.task_queue_service import TaskQueueService from artemis.runtime.adb_endpoint import AdbEndpoint, AdbTarget monkeypatch.setenv("ARTEMIS_RECORDING_CONFIG", '{"fps":10}') monkeypatch.setenv("ARTEMIS_TASK_RECORDING_CONFIG", '{"fps":60}') _, environment = TaskQueueService._build_worker_invocation( {"runtime_overrides": {"recording": {"fps": 30}}}, "test-run", None, "fixture", "flash", AdbTarget(AdbEndpoint.local()), ) assert json.loads(environment["ARTEMIS_RECORDING_CONFIG"]) == {"fps": 10} assert json.loads(environment["ARTEMIS_TASK_RECORDING_CONFIG"]) == {"fps": 30} _, clean_environment = TaskQueueService._build_worker_invocation( {}, "test-run", None, "fixture", "flash", AdbTarget(AdbEndpoint.local()) ) assert "ARTEMIS_TASK_RECORDING_CONFIG" not in clean_environment def test_sdk_preserves_task_recording_scope_through_recorder(tmp_path, monkeypatch): from artemis.plugins.models import EvidenceSource from artemis.plugins.recording import PluginRecordings, capture_rectangle from artemis.sdk.builders.agent_config_builder import AgentConfigBuilder monkeypatch.setattr( settings, "ARTEMIS_TASK_RECORDING_CONFIG", {"computer_scope": "window", "codec": "h264", "fps": 7, "max_long_edge": 960}, ) builder = AgentConfigBuilder() config = builder.build() ctx = SimpleNamespace( agent_config=config, data_engine=SimpleNamespace(base_dir=tmp_path, session_start_time=100), execution_setup=SimpleNamespace(record_video_enabled=True), ) recorder = PluginRecordings(ctx) source = EvidenceSource( kind="computer", target_id="fixture", window={"left": 40, "top": 50, "width": 800, "height": 600}, screen={"left": 0, "top": 0, "width": 1920, "height": 1080}, ) assert recorder.config.computer_scope == "window" assert capture_rectangle(source, recorder.config.computer_scope) == source.window assert recorder.config.codec == "h264" assert recorder.config.fps == 7 assert recorder.config.max_long_edge == 960 # Building another task must not share a mutable recording configuration. config.recording.fps = 12 assert builder.build().recording.fps == 7