test_internal_runtime.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. from __future__ import annotations
  2. from pathlib import Path
  3. from typing import Any
  4. import pytest
  5. from pydantic import SecretStr
  6. from script_build_host.agents.model_manifest import resolve_script_role_model_manifest
  7. from script_build_host.domain.records import Principal
  8. from script_build_host.infrastructure.config import ScriptBuildSettings
  9. from script_build_host.internal_runtime import (
  10. InternalBuildAuthorizer,
  11. InternalPrincipalProvider,
  12. create_internal_runtime,
  13. )
  14. def _settings(tmp_path: Path, **overrides: Any) -> ScriptBuildSettings:
  15. values: dict[str, Any] = {
  16. "environment": "test",
  17. "read_database_url": f"sqlite+aiosqlite:///{tmp_path / 'read.db'}",
  18. "write_database_url": f"sqlite+aiosqlite:///{tmp_path / 'write.db'}",
  19. "agent_data_root": tmp_path / "agent-data",
  20. "persona_root": tmp_path / "persona",
  21. "section_pattern_root": tmp_path / "sections",
  22. "decode_index_root": tmp_path / "decode",
  23. "knowledge_root": tmp_path / "knowledge.json",
  24. "prompt_fallback_root": tmp_path / "prompts",
  25. "role_model": "qwen3.7-max",
  26. "embedding_api_key": SecretStr("secret"),
  27. }
  28. values.update(overrides)
  29. return ScriptBuildSettings(**values)
  30. def test_role_model_manifest_covers_all_sixteen_presets_and_keeps_overrides() -> None:
  31. result = resolve_script_role_model_manifest(
  32. {
  33. "embedding_model": "text-embedding-v4",
  34. "presets": {"script_root_validator": {"temperature": 0.1}},
  35. },
  36. model="qwen3.7-max",
  37. max_iterations=120,
  38. )
  39. presets = result["presets"]
  40. assert isinstance(presets, dict)
  41. assert len(presets) == 17
  42. assert presets["script_context_broker"]["temperature"] == 0.0
  43. assert {value["model"] for value in presets.values()} == {"qwen3.7-max"}
  44. assert presets["script_root_validator"]["temperature"] == 0.0
  45. assert presets["script_planner"]["max_iterations"] == 100
  46. assert presets["script_direction_worker"]["max_iterations"] == 20
  47. assert presets["script_root_validator"]["max_iterations"] == 25
  48. assert presets["script_direction_worker"]["temperature"] == 0.2
  49. assert result["embedding_model"] == "text-embedding-v4"
  50. @pytest.mark.asyncio
  51. async def test_internal_auth_is_fixed_and_rejected_for_invalid_builds() -> None:
  52. provider = InternalPrincipalProvider(" internal-e2e ")
  53. principal = await provider.current(object())
  54. assert principal == Principal("internal-e2e")
  55. authorizer = InternalBuildAuthorizer()
  56. await authorizer.require_source_access(principal, execution_id=1, topic_build_id=2, topic_id=3)
  57. await authorizer.require_access(principal, 1)
  58. with pytest.raises(PermissionError):
  59. await authorizer.require_access(principal, 0)
  60. def test_internal_runtime_uses_qwen_and_fails_closed_outside_test(
  61. tmp_path: Path, monkeypatch: pytest.MonkeyPatch
  62. ) -> None:
  63. received: dict[str, Any] = {}
  64. def fake_qwen(**kwargs: Any) -> Any:
  65. received.update(kwargs)
  66. async def call(**_call: Any) -> dict[str, Any]:
  67. return {"content": "unused", "finish_reason": "stop"}
  68. return call
  69. monkeypatch.setattr("script_build_host.internal_runtime.create_qwen_llm_call", fake_qwen)
  70. settings = _settings(tmp_path)
  71. runtime = create_internal_runtime(settings)
  72. assert runtime.runner.trace_store is not None
  73. assert received == {
  74. "model": "qwen3.7-max",
  75. "base_url": "https://dashscope.aliyuncs.com/compatible-mode/v1",
  76. "api_key": "secret",
  77. "enable_thinking": False,
  78. "request_timeout_seconds": 300.0,
  79. "max_retries": 2,
  80. }
  81. with pytest.raises(RuntimeError, match="only in test"):
  82. create_internal_runtime(
  83. _settings(
  84. tmp_path,
  85. environment="production",
  86. read_database_url="mysql+asyncmy://reader:r@db.example/app",
  87. read_database_socks_proxy=None,
  88. write_database_url="mysql+asyncmy://runtime:r@db.example/app",
  89. final_database_url="mysql+asyncmy://publisher:r@db.example/app",
  90. )
  91. )