test_internal_e2e.py 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import json
  2. from pathlib import Path
  3. import httpx
  4. import pytest
  5. from script_build_host.domain.records import BuildStatus
  6. from script_build_host.internal_e2e import _context_broker_diagnostics, _wait_for_root
  7. def test_recovery_checkpoint_fixtures_cover_all_segment_boundaries() -> None:
  8. path = Path(__file__).parent / "fixtures" / "recovery_checkpoints.json"
  9. payload = json.loads(path.read_text(encoding="utf-8"))
  10. assert set(payload["checkpoints"]) == {"direction", "structure", "portfolio", "root"}
  11. assert all(
  12. item["input_snapshot_digest"].startswith("sha256:")
  13. for item in payload["checkpoints"].values()
  14. )
  15. def test_context_broker_diagnostics_count_repeated_pages_not_shared_revisions() -> None:
  16. diagnostics = _context_broker_diagnostics(
  17. [
  18. {
  19. "revision": "same-artifact",
  20. "estimated_tokens": 100,
  21. "detail_reads": 1,
  22. "detail_page_key": "page-1",
  23. },
  24. {
  25. "revision": "same-artifact",
  26. "estimated_tokens": 300,
  27. "detail_reads": 1,
  28. "detail_page_key": "page-2",
  29. },
  30. {
  31. "revision": "same-artifact",
  32. "estimated_tokens": 100,
  33. "detail_reads": 1,
  34. "detail_page_key": "page-1",
  35. "semantic_calls": 1,
  36. "fallback": True,
  37. },
  38. ]
  39. )
  40. assert diagnostics["requests"] == 3
  41. assert diagnostics["bundle_tokens"] == {"p50": 100, "p95": 300, "max": 300}
  42. assert diagnostics["detail_reads"] == 3
  43. assert diagnostics["duplicate_detail_reads"] == 1
  44. assert diagnostics["semantic_calls"] == 1
  45. assert diagnostics["semantic_fallbacks"] == 1
  46. @pytest.mark.asyncio
  47. async def test_mission_poll_allows_only_bounded_startup_failures() -> None:
  48. calls = 0
  49. def handler(_request: httpx.Request) -> httpx.Response:
  50. nonlocal calls
  51. calls += 1
  52. if calls <= 5:
  53. return httpx.Response(500, json={"error_code": "LEDGER_NOT_READY"})
  54. return httpx.Response(
  55. 200,
  56. json={
  57. "root_task_id": "root",
  58. "tasks": [
  59. {
  60. "task_id": "root",
  61. "status": "blocked",
  62. "blocked_reason": "BOUNDARY",
  63. }
  64. ],
  65. },
  66. )
  67. async with httpx.AsyncClient(
  68. transport=httpx.MockTransport(handler), base_url="http://internal"
  69. ) as client:
  70. result = await _wait_for_root(
  71. client,
  72. 7,
  73. expected_status="blocked",
  74. expected_reason="BOUNDARY",
  75. timeout_seconds=1,
  76. poll_seconds=0,
  77. )
  78. assert result["task_id"] == "root"
  79. assert calls == 6
  80. @pytest.mark.asyncio
  81. async def test_mission_poll_surfaces_persistent_server_failure() -> None:
  82. def handler(_request: httpx.Request) -> httpx.Response:
  83. return httpx.Response(500, json={"error_code": "InternalServerError"})
  84. async with httpx.AsyncClient(
  85. transport=httpx.MockTransport(handler), base_url="http://internal"
  86. ) as client:
  87. with pytest.raises(RuntimeError, match="mission poll failed with HTTP 500"):
  88. await _wait_for_root(
  89. client,
  90. 7,
  91. expected_status="blocked",
  92. expected_reason="BOUNDARY",
  93. timeout_seconds=1,
  94. poll_seconds=0,
  95. )
  96. @pytest.mark.asyncio
  97. async def test_mission_poll_stops_when_build_is_terminal() -> None:
  98. class FailedState:
  99. async def get_status(self, _script_build_id):
  100. return BuildStatus.FAILED
  101. def handler(_request: httpx.Request) -> httpx.Response:
  102. return httpx.Response(
  103. 200,
  104. json={
  105. "root_task_id": "root",
  106. "tasks": [{"task_id": "root", "status": "needs_replan"}],
  107. },
  108. )
  109. async with httpx.AsyncClient(
  110. transport=httpx.MockTransport(handler), base_url="http://internal"
  111. ) as client:
  112. with pytest.raises(RuntimeError, match="terminal status=failed"):
  113. await _wait_for_root(
  114. client,
  115. 7,
  116. legacy_state=FailedState(),
  117. expected_status="blocked",
  118. expected_reason="BOUNDARY",
  119. timeout_seconds=1,
  120. poll_seconds=0,
  121. )