0ef5fcb1c5
Security / Dependency audit (pip-audit) (push) Has been cancelled
Security / CodeQL (javascript-typescript) (push) Has been cancelled
Security / CodeQL (python) (push) Has been cancelled
Security / Secret scan (gitleaks) (push) Has been cancelled
rust / test (ubuntu) (push) Has been cancelled
rust / simulator e2e (macos-latest) (push) Has been cancelled
rust / simulator e2e (ubuntu-latest) (push) Has been cancelled
rust / simulator e2e (windows-latest) (push) Has been cancelled
rust / wheels (aarch64-apple-darwin) (push) Has been cancelled
rust / wheels (x86_64-unknown-linux-gnu) (push) Has been cancelled
rust / wheels (x86_64-apple-darwin) (push) Has been cancelled
rust / audit (push) Has been cancelled
rust / parity (nightly, allowed to fail during Phase 0) (push) Has been cancelled
CI / commitlint (push) Has been skipped
Dev Containers / validate (.devcontainer/devcontainer.json, default) (push) Failing after 0s
Dev Containers / validate (.devcontainer/memory-stack/devcontainer.json, memory-stack) (push) Failing after 0s
Dev Containers / validate-worktree (push) Failing after 0s
CI / changes (push) Failing after 4s
Deploy Documentation / validate (push) Has been skipped
Deploy Documentation / deploy (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, claude) (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, codex) (push) Failing after 1s
Install Native E2E / install-native (ubuntu-latest) (push) Failing after 1s
OpenCode Plugin / typecheck + build + test (push) Failing after 1s
Init Native E2E / init-native (ubuntu-latest, copilot) (push) Failing after 1s
Release Please / release-please (push) Failing after 1s
Wrap E2E / docker-wrap-e2e (push) Failing after 1s
Wrap Native E2E / wrap-native (ubuntu-latest) (push) Failing after 1s
Init E2E / docker-init-e2e (push) Failing after 4s
Merge Conflicts / merge-conflicts (push) Failing after 4s
CI / lint (push) Has been cancelled
CI / build-wheel (push) Has been cancelled
CI / build-wheel-windows (push) Has been cancelled
CI / prefetch-model (push) Has been cancelled
CI / test-dashboard-ui (push) Has been cancelled
CI / test (1) (push) Has been cancelled
CI / test (2) (push) Has been cancelled
CI / test (3) (push) Has been cancelled
CI / test (4) (push) Has been cancelled
CI / test-extras (push) Has been cancelled
CI / test-agno (push) Has been cancelled
CI / build (push) Has been cancelled
CI / workflow-validation (push) Has been cancelled
CI / docker-native-e2e (push) Has been cancelled
CI / windows-native-wrapper (push) Has been cancelled
CI / macos-native-wrapper (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-manifest (map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-nonroot name:code-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim name:code-slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-code-slim-nonroot name:code-slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-nonroot name:nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim name:slim]) (push) Has been cancelled
Docker / docker-build (map[name:amd64 platform:linux/amd64 runs_on:ubuntu-24.04], map[bake_target:runtime-slim-nonroot name:slim-nonroot]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime name:]) (push) Has been cancelled
Docker / docker-build (map[name:arm64 platform:linux/arm64 runs_on:ubuntu-24.04-arm], map[bake_target:runtime-code name:code]) (push) Has been cancelled
Docker / promote-latest (push) Has been cancelled
Init Native E2E / init-native (macos-latest, claude) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, codex) (push) Has been cancelled
Init Native E2E / init-native (macos-latest, copilot) (push) Has been cancelled
Install Native E2E / install-native (macos-latest) (push) Has been cancelled
Wrap Native E2E / wrap-native (macos-latest) (push) Has been cancelled
190 lines
6.4 KiB
Python
190 lines
6.4 KiB
Python
from __future__ import annotations
|
|
|
|
import click
|
|
import pytest
|
|
|
|
from headroom.install.models import ConfigScope, InstallPreset, ProviderSelectionMode, ToolTarget
|
|
from headroom.install.planner import PROVIDER_SCOPE_TARGETS, build_manifest, resolve_targets
|
|
|
|
|
|
def test_resolve_targets_auto_falls_back_when_detection_empty(monkeypatch) -> None:
|
|
monkeypatch.setattr("headroom.install.planner.detect_targets", lambda: [])
|
|
|
|
targets = resolve_targets(ProviderSelectionMode.AUTO.value, [])
|
|
|
|
assert targets == [
|
|
ToolTarget.CLAUDE.value,
|
|
ToolTarget.CODEX.value,
|
|
ToolTarget.COPILOT.value,
|
|
]
|
|
|
|
|
|
def test_build_manifest_for_persistent_docker_sets_expected_defaults() -> None:
|
|
manifest = build_manifest(
|
|
profile="default",
|
|
preset=InstallPreset.PERSISTENT_DOCKER.value,
|
|
runtime_kind="docker",
|
|
scope="user",
|
|
provider_mode="manual",
|
|
targets=["claude", "copilot"],
|
|
port=8787,
|
|
backend="anthropic",
|
|
anyllm_provider=None,
|
|
region=None,
|
|
proxy_mode="token",
|
|
memory_enabled=True,
|
|
telemetry_enabled=False,
|
|
image="ghcr.io/chopratejas/headroom:latest",
|
|
)
|
|
|
|
assert manifest.supervisor_kind == "none"
|
|
assert manifest.runtime_kind == "docker"
|
|
assert manifest.health_url == "http://127.0.0.1:8787/readyz"
|
|
assert manifest.base_env["HEADROOM_PORT"] == "8787"
|
|
assert manifest.base_env["HEADROOM_TELEMETRY"] == "off"
|
|
assert "--no-telemetry" in manifest.proxy_args
|
|
assert manifest.tool_envs["claude"]["ANTHROPIC_BASE_URL"] == "http://127.0.0.1:8787"
|
|
assert manifest.tool_envs["copilot"]["COPILOT_PROVIDER_TYPE"] == "anthropic"
|
|
assert "--memory" in manifest.proxy_args
|
|
|
|
|
|
def test_build_manifest_uses_provider_slice_env_builders_for_all_supported_targets() -> None:
|
|
manifest = build_manifest(
|
|
profile="default",
|
|
preset=InstallPreset.PERSISTENT_SERVICE.value,
|
|
runtime_kind="python",
|
|
scope="user",
|
|
provider_mode="manual",
|
|
targets=["claude", "copilot", "codex", "aider", "cursor"],
|
|
port=9999,
|
|
backend="anyllm",
|
|
anyllm_provider="groq",
|
|
region=None,
|
|
proxy_mode="token",
|
|
memory_enabled=False,
|
|
telemetry_enabled=True,
|
|
image="ghcr.io/chopratejas/headroom:latest",
|
|
)
|
|
|
|
# telemetry_enabled=True must write the explicit opt-in value + flag.
|
|
assert manifest.base_env["HEADROOM_TELEMETRY"] == "on"
|
|
assert "--telemetry" in manifest.proxy_args
|
|
assert manifest.tool_envs["claude"]["ANTHROPIC_BASE_URL"] == "http://127.0.0.1:9999"
|
|
assert manifest.tool_envs["codex"]["OPENAI_BASE_URL"] == "http://127.0.0.1:9999/v1"
|
|
assert manifest.tool_envs["aider"] == {
|
|
"OPENAI_API_BASE": "http://127.0.0.1:9999/v1",
|
|
"ANTHROPIC_BASE_URL": "http://127.0.0.1:9999",
|
|
}
|
|
assert manifest.tool_envs["cursor"] == {
|
|
"OPENAI_BASE_URL": "http://127.0.0.1:9999/v1",
|
|
"ANTHROPIC_BASE_URL": "http://127.0.0.1:9999",
|
|
}
|
|
assert manifest.tool_envs["copilot"] == {
|
|
"COPILOT_PROVIDER_TYPE": "openai",
|
|
"COPILOT_PROVIDER_BASE_URL": "http://127.0.0.1:9999/v1",
|
|
"COPILOT_PROVIDER_WIRE_API": "completions",
|
|
}
|
|
|
|
|
|
def test_resolve_targets_provider_scope_auto_excludes_copilot(monkeypatch) -> None:
|
|
monkeypatch.setattr("headroom.install.planner.detect_targets", lambda: [])
|
|
|
|
targets = resolve_targets(
|
|
ProviderSelectionMode.AUTO.value,
|
|
[],
|
|
scope=ConfigScope.PROVIDER.value,
|
|
)
|
|
|
|
assert targets == [ToolTarget.CLAUDE.value, ToolTarget.CODEX.value]
|
|
|
|
|
|
def test_resolve_targets_manual_dedupes_and_filters_invalid() -> None:
|
|
targets = resolve_targets(
|
|
ProviderSelectionMode.MANUAL.value,
|
|
["claude", "copilot", "claude", "invalid"],
|
|
)
|
|
|
|
assert targets == [ToolTarget.CLAUDE.value, ToolTarget.COPILOT.value]
|
|
|
|
|
|
def test_build_manifest_omits_no_http2_by_default() -> None:
|
|
manifest = build_manifest(
|
|
profile="default",
|
|
preset=InstallPreset.PERSISTENT_SERVICE.value,
|
|
runtime_kind="python",
|
|
scope="user",
|
|
provider_mode="manual",
|
|
targets=["claude"],
|
|
port=8787,
|
|
backend="anthropic",
|
|
anyllm_provider=None,
|
|
region=None,
|
|
proxy_mode="token",
|
|
memory_enabled=False,
|
|
telemetry_enabled=True,
|
|
image="ghcr.io/chopratejas/headroom:latest",
|
|
)
|
|
|
|
assert "--no-http2" not in manifest.proxy_args
|
|
|
|
|
|
def test_build_manifest_persists_no_http2_override() -> None:
|
|
manifest = build_manifest(
|
|
profile="default",
|
|
preset=InstallPreset.PERSISTENT_SERVICE.value,
|
|
runtime_kind="python",
|
|
scope="user",
|
|
provider_mode="manual",
|
|
targets=["claude"],
|
|
port=8787,
|
|
backend="anthropic",
|
|
anyllm_provider=None,
|
|
region=None,
|
|
proxy_mode="token",
|
|
memory_enabled=False,
|
|
telemetry_enabled=True,
|
|
image="ghcr.io/chopratejas/headroom:latest",
|
|
no_http2=True,
|
|
)
|
|
|
|
assert manifest.proxy_args.count("--no-http2") == 1
|
|
assert "HEADROOM_HTTP2" not in manifest.base_env
|
|
|
|
|
|
def test_resolve_targets_provider_scope_all_ignores_unsupported_requested() -> None:
|
|
"""`all` mode never consults the requested list, so an unsupported entry
|
|
like `cursor` must not make it raise — it should return the full provider
|
|
target set (regression: this used to raise a ClickException)."""
|
|
targets = resolve_targets(
|
|
ProviderSelectionMode.ALL.value,
|
|
["cursor"],
|
|
scope=ConfigScope.PROVIDER.value,
|
|
)
|
|
|
|
assert targets == [t.value for t in PROVIDER_SCOPE_TARGETS]
|
|
|
|
|
|
def test_resolve_targets_provider_scope_auto_ignores_unsupported_requested(monkeypatch) -> None:
|
|
"""`auto` mode also ignores the requested list, so an unsupported entry
|
|
must not raise."""
|
|
monkeypatch.setattr("headroom.install.planner.detect_targets", lambda: [])
|
|
|
|
targets = resolve_targets(
|
|
ProviderSelectionMode.AUTO.value,
|
|
["cursor"],
|
|
scope=ConfigScope.PROVIDER.value,
|
|
)
|
|
|
|
assert targets == [ToolTarget.CLAUDE.value, ToolTarget.CODEX.value]
|
|
|
|
|
|
def test_resolve_targets_provider_scope_manual_rejects_unsupported() -> None:
|
|
"""The manual path DOES consult the requested list, so an unsupported
|
|
target under provider scope must still be rejected."""
|
|
with pytest.raises(click.ClickException, match="cursor"):
|
|
resolve_targets(
|
|
ProviderSelectionMode.MANUAL.value,
|
|
["cursor"],
|
|
scope=ConfigScope.PROVIDER.value,
|
|
)
|