Import AITURK IDE 1.0.0-beta.1 from Hermes 63279301; preserve MIT license
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
"""Regression tests: plugin-family credential reads honor the profile secret scope.
|
||||
|
||||
Class-closure follow-up to the profile secret-scope cluster (#76462). Memory,
|
||||
image_gen, and browser plugins, plus a handful of tier-3 tool helpers, read
|
||||
credentials straight from ``os.environ``. Under a multiplexed gateway the
|
||||
process environment may hold ANOTHER profile's key (or none), so every
|
||||
credential read must route through ``agent.secret_scope.get_secret`` and honor
|
||||
its verdict — a scoped miss under multiplexing returns the default and must
|
||||
NOT borrow from ``os.environ``.
|
||||
|
||||
One representative test pair (scoped-wins / scoped-miss-no-borrow) per plugin
|
||||
family, plus the two behavioral sites:
|
||||
|
||||
* supermemory ``post_setup`` must not write a profile's key into the
|
||||
process-global environ when multiplexing is active (sibling-profile
|
||||
pollution).
|
||||
* google_meet ``process_manager.start`` must resolve OPENAI_API_KEY through
|
||||
the scope AT SPAWN TIME and pass it explicitly in the child environment —
|
||||
the detached child inherits the process env, not the contextvar scope.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from typing import Any, Dict
|
||||
|
||||
import pytest
|
||||
|
||||
from agent.secret_scope import (
|
||||
reset_secret_scope,
|
||||
set_multiplex_active,
|
||||
set_secret_scope,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def multiplex_scope():
|
||||
"""Install a secret scope with multiplexing ON; restore state after."""
|
||||
|
||||
def _install(scope: Dict[str, str]):
|
||||
set_multiplex_active(True)
|
||||
token = set_secret_scope(scope)
|
||||
return token
|
||||
|
||||
tokens = []
|
||||
|
||||
def install(scope: Dict[str, str]):
|
||||
tokens.append(_install(scope))
|
||||
|
||||
yield install
|
||||
|
||||
for token in tokens:
|
||||
reset_secret_scope(token)
|
||||
set_multiplex_active(False)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Family A — memory plugins
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestMemoryFamily:
|
||||
def test_retaindb_scoped_key_wins(self, multiplex_scope, monkeypatch):
|
||||
monkeypatch.setenv("RETAINDB_API_KEY", "env-other-profile")
|
||||
multiplex_scope({"RETAINDB_API_KEY": "scoped-key"})
|
||||
|
||||
from plugins.memory.retaindb import RetainDBMemoryProvider
|
||||
|
||||
assert RetainDBMemoryProvider().is_available() is True
|
||||
|
||||
def test_retaindb_scoped_miss_does_not_borrow_environ(
|
||||
self, multiplex_scope, monkeypatch
|
||||
):
|
||||
# Env holds another profile's key; the active profile's scope has none.
|
||||
monkeypatch.setenv("RETAINDB_API_KEY", "env-other-profile")
|
||||
multiplex_scope({})
|
||||
|
||||
from plugins.memory.retaindb import RetainDBMemoryProvider
|
||||
|
||||
assert RetainDBMemoryProvider().is_available() is False
|
||||
|
||||
def test_supermemory_scoped_miss_does_not_borrow_environ(
|
||||
self, multiplex_scope, monkeypatch
|
||||
):
|
||||
monkeypatch.setenv("SUPERMEMORY_API_KEY", "env-other-profile")
|
||||
multiplex_scope({})
|
||||
|
||||
from plugins.memory.supermemory import SupermemoryMemoryProvider
|
||||
|
||||
assert SupermemoryMemoryProvider().is_available() is False
|
||||
|
||||
def test_supermemory_post_setup_no_environ_write_under_multiplex(
|
||||
self, multiplex_scope, monkeypatch, tmp_path
|
||||
):
|
||||
"""post_setup must not pollute process env with a profile's key."""
|
||||
multiplex_scope({})
|
||||
monkeypatch.delenv("SUPERMEMORY_API_KEY", raising=False)
|
||||
|
||||
import hermes_cli.config as cli_config
|
||||
import hermes_cli.memory_setup as memory_setup
|
||||
import plugins.memory.supermemory as sm
|
||||
|
||||
monkeypatch.setattr(memory_setup, "_prompt", lambda *a, **k: "sm-fresh-key")
|
||||
monkeypatch.setattr(memory_setup, "_write_env_vars", lambda *a, **k: None)
|
||||
monkeypatch.setattr(cli_config, "save_config", lambda *a, **k: None)
|
||||
monkeypatch.setattr(
|
||||
sm,
|
||||
"_probe_supermemory_connection",
|
||||
lambda *a, **k: {"ok": True, "detail": "stub"},
|
||||
)
|
||||
monkeypatch.setattr(sm, "_format_connection_summary", lambda s: "stub")
|
||||
|
||||
sm.SupermemoryMemoryProvider().post_setup(str(tmp_path), {})
|
||||
|
||||
assert "SUPERMEMORY_API_KEY" not in os.environ
|
||||
|
||||
def test_supermemory_post_setup_environ_write_kept_single_profile(
|
||||
self, monkeypatch, tmp_path
|
||||
):
|
||||
"""Single-profile (multiplex off): the convenience write still happens."""
|
||||
set_multiplex_active(False)
|
||||
monkeypatch.delenv("SUPERMEMORY_API_KEY", raising=False)
|
||||
|
||||
import hermes_cli.config as cli_config
|
||||
import hermes_cli.memory_setup as memory_setup
|
||||
import plugins.memory.supermemory as sm
|
||||
|
||||
monkeypatch.setattr(memory_setup, "_prompt", lambda *a, **k: "sm-fresh-key")
|
||||
monkeypatch.setattr(memory_setup, "_write_env_vars", lambda *a, **k: None)
|
||||
monkeypatch.setattr(cli_config, "save_config", lambda *a, **k: None)
|
||||
monkeypatch.setattr(
|
||||
sm,
|
||||
"_probe_supermemory_connection",
|
||||
lambda *a, **k: {"ok": True, "detail": "stub"},
|
||||
)
|
||||
monkeypatch.setattr(sm, "_format_connection_summary", lambda s: "stub")
|
||||
|
||||
try:
|
||||
sm.SupermemoryMemoryProvider().post_setup(str(tmp_path), {})
|
||||
assert os.environ.get("SUPERMEMORY_API_KEY") == "sm-fresh-key"
|
||||
finally:
|
||||
os.environ.pop("SUPERMEMORY_API_KEY", None)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Family B — image_gen plugins
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestImageGenFamily:
|
||||
def test_deepinfra_scoped_key_wins(self, multiplex_scope, monkeypatch):
|
||||
monkeypatch.delenv("DEEPINFRA_API_KEY", raising=False)
|
||||
multiplex_scope({"DEEPINFRA_API_KEY": "scoped-key"})
|
||||
|
||||
from plugins.image_gen.deepinfra import DeepInfraImageGenProvider
|
||||
|
||||
assert DeepInfraImageGenProvider().is_available() is True
|
||||
|
||||
def test_deepinfra_scoped_miss_does_not_borrow_environ(
|
||||
self, multiplex_scope, monkeypatch
|
||||
):
|
||||
monkeypatch.setenv("DEEPINFRA_API_KEY", "env-other-profile")
|
||||
multiplex_scope({})
|
||||
|
||||
from plugins.image_gen.deepinfra import DeepInfraImageGenProvider
|
||||
|
||||
assert DeepInfraImageGenProvider().is_available() is False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Family C — browser/web plugins
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestBrowserFamily:
|
||||
def test_firecrawl_scoped_key_wins(self, multiplex_scope, monkeypatch):
|
||||
monkeypatch.delenv("FIRECRAWL_API_KEY", raising=False)
|
||||
multiplex_scope({"FIRECRAWL_API_KEY": "scoped-key"})
|
||||
|
||||
from plugins.browser.firecrawl.provider import FirecrawlBrowserProvider
|
||||
|
||||
provider = FirecrawlBrowserProvider()
|
||||
assert provider.is_available() is True
|
||||
assert provider._headers()["Authorization"] == "Bearer scoped-key"
|
||||
|
||||
def test_firecrawl_scoped_miss_does_not_borrow_environ(
|
||||
self, multiplex_scope, monkeypatch
|
||||
):
|
||||
monkeypatch.setenv("FIRECRAWL_API_KEY", "env-other-profile")
|
||||
multiplex_scope({})
|
||||
|
||||
from plugins.browser.firecrawl.provider import FirecrawlBrowserProvider
|
||||
|
||||
assert FirecrawlBrowserProvider().is_available() is False
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Family E — google_meet spawn-wrap
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestGoogleMeetSpawn:
|
||||
def test_child_env_carries_scoped_openai_key(
|
||||
self, multiplex_scope, monkeypatch, tmp_path
|
||||
):
|
||||
"""start() resolves the key from the scope and injects it explicitly."""
|
||||
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||
monkeypatch.delenv("HERMES_MEET_REALTIME_KEY", raising=False)
|
||||
multiplex_scope({"OPENAI_API_KEY": "scoped-openai-key"})
|
||||
|
||||
import plugins.google_meet.process_manager as pm
|
||||
|
||||
monkeypatch.setattr(pm, "_root", lambda: tmp_path)
|
||||
|
||||
captured: Dict[str, Any] = {}
|
||||
|
||||
class _FakeProc:
|
||||
pid = 4242
|
||||
|
||||
def fake_popen(cmd, **kwargs):
|
||||
captured["env"] = kwargs.get("env")
|
||||
return _FakeProc()
|
||||
|
||||
monkeypatch.setattr(pm.subprocess, "Popen", fake_popen)
|
||||
|
||||
result = pm.start(
|
||||
"https://meet.google.com/abc-defg-hij",
|
||||
out_dir=tmp_path / "meeting",
|
||||
mode="realtime",
|
||||
)
|
||||
|
||||
assert result["ok"] is True
|
||||
child_env = captured["env"]
|
||||
# The scoped key crosses the process boundary explicitly, not via
|
||||
# inherited os.environ (which had no key at all).
|
||||
assert child_env["HERMES_MEET_REALTIME_KEY"] == "scoped-openai-key"
|
||||
|
||||
def test_explicit_key_argument_still_wins(
|
||||
self, multiplex_scope, monkeypatch, tmp_path
|
||||
):
|
||||
multiplex_scope({"OPENAI_API_KEY": "scoped-openai-key"})
|
||||
|
||||
import plugins.google_meet.process_manager as pm
|
||||
|
||||
monkeypatch.setattr(pm, "_root", lambda: tmp_path)
|
||||
|
||||
captured: Dict[str, Any] = {}
|
||||
|
||||
class _FakeProc:
|
||||
pid = 4243
|
||||
|
||||
monkeypatch.setattr(
|
||||
pm.subprocess,
|
||||
"Popen",
|
||||
lambda cmd, **kw: (captured.update(env=kw.get("env")), _FakeProc())[1],
|
||||
)
|
||||
|
||||
pm.start(
|
||||
"https://meet.google.com/abc-defg-hij",
|
||||
out_dir=tmp_path / "meeting2",
|
||||
realtime_api_key="explicit-key",
|
||||
)
|
||||
|
||||
assert captured["env"]["HERMES_MEET_REALTIME_KEY"] == "explicit-key"
|
||||
Reference in New Issue
Block a user