2026-06-22 16:08:43 +02:00
|
|
|
"""HarnessX optional-extra packaging + capability guard (Phase 3 task 5.2)."""
|
|
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
import tomllib
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
2026-06-22 17:59:44 +02:00
|
|
|
from openultrasast.harness_ext import (
|
|
|
|
|
HARNESSX_EXTRA,
|
|
|
|
|
SUPPORTED_PROVIDERS,
|
|
|
|
|
HarnessXUnavailableError,
|
|
|
|
|
build_provider,
|
|
|
|
|
has_harnessx,
|
|
|
|
|
require_harnessx,
|
|
|
|
|
)
|
2026-06-22 16:08:43 +02:00
|
|
|
|
|
|
|
|
PYPROJECT = tomllib.loads(Path("pyproject.toml").read_text())
|
|
|
|
|
|
|
|
|
|
|
2026-09-29 17:44:31 +02:00
|
|
|
def test_core_dependencies_are_pyyaml_only() -> None:
|
|
|
|
|
assert PYPROJECT["project"]["dependencies"] == ["pyyaml>=6.0"] # ai-service-plane task 1: manifests are YAML; HarnessX stays an extra
|
2026-06-22 16:08:43 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_harnessx_is_a_sha_pinned_optional_extra() -> None:
|
|
|
|
|
extras = PYPROJECT["project"]["optional-dependencies"]["harnessx"]
|
|
|
|
|
assert len(extras) == 1
|
|
|
|
|
assert re.fullmatch(
|
|
|
|
|
r"harnessx @ git\+https://github\.com/Darwin-Agent/HarnessX\.git@[0-9a-f]{40}",
|
|
|
|
|
extras[0],
|
|
|
|
|
), extras[0]
|
|
|
|
|
|
|
|
|
|
|
2026-06-22 17:59:44 +02:00
|
|
|
def test_importing_openultrasast_does_not_import_harnessx(assert_cold_of_harnessx) -> None: # type: ignore[no-untyped-def]
|
2026-06-22 16:08:43 +02:00
|
|
|
# The big modules (cli, harness_ext) must not eagerly import the heavy extra.
|
2026-06-22 17:59:44 +02:00
|
|
|
assert_cold_of_harnessx("import openultrasast.cli\nimport openultrasast.harness_ext")
|
2026-06-22 16:08:43 +02:00
|
|
|
|
|
|
|
|
|
2026-06-22 17:59:44 +02:00
|
|
|
def test_has_harnessx_probes_without_importing(assert_cold_of_harnessx) -> None: # type: ignore[no-untyped-def]
|
|
|
|
|
assert isinstance(has_harnessx(), bool)
|
|
|
|
|
# find_spec must not import the module.
|
|
|
|
|
assert_cold_of_harnessx("from openultrasast.harness_ext import has_harnessx\nassert isinstance(has_harnessx(), bool)")
|
2026-06-22 16:08:43 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_require_harnessx_raises_with_install_guidance_when_absent() -> None:
|
|
|
|
|
if has_harnessx():
|
|
|
|
|
pytest.skip("HarnessX extra is installed in this environment")
|
|
|
|
|
with pytest.raises(HarnessXUnavailableError, match=re.escape(HARNESSX_EXTRA)):
|
|
|
|
|
require_harnessx()
|
2026-06-22 17:59:44 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_build_provider_rejects_unknown_provider_before_importing(assert_cold_of_harnessx) -> None: # type: ignore[no-untyped-def]
|
|
|
|
|
# Name is validated before any import, so this fails loudly even without the extra.
|
|
|
|
|
with pytest.raises(ValueError, match="unknown HarnessX provider"):
|
|
|
|
|
build_provider("gpt-4o", "bogus")
|
|
|
|
|
assert_cold_of_harnessx(
|
|
|
|
|
"from openultrasast.harness_ext import build_provider\n"
|
|
|
|
|
"try:\n build_provider('gpt-4o', 'bogus')\n"
|
|
|
|
|
"except ValueError:\n pass\n"
|
|
|
|
|
"else:\n raise SystemExit('expected ValueError')\n"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_supported_providers_cover_anthropic_and_openai() -> None:
|
|
|
|
|
assert {"anthropic", "openai"} <= set(SUPPORTED_PROVIDERS)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize(("provider", "class_name"), [("anthropic", "AnthropicProvider"), ("openai", "OpenAIProvider")])
|
|
|
|
|
def test_build_provider_constructs_selected_provider(provider: str, class_name: str) -> None:
|
|
|
|
|
if not has_harnessx():
|
|
|
|
|
pytest.skip("HarnessX extra not installed in this environment")
|
|
|
|
|
built = build_provider("test-model", provider)
|
|
|
|
|
assert type(built).__name__ == class_name
|