Change-detectors, tautologies, source-reading tests, redundant duplicates, mock-echo tests and dead/unrunnable tests. Per-test rationale in the lane ledger (category + reason for every removal).
178 lines
6.1 KiB
Python
178 lines
6.1 KiB
Python
"""Tests: vault.* JSON-RPC handlers (tui_gateway/methods_vault.py).
|
|
|
|
The Desktop's Settings → Credential Vault panel. Contracts:
|
|
- vault.list returns metadata only — secret values must never appear in
|
|
any response envelope;
|
|
- vault.add validates via VaultStore.add_item and surfaces clean,
|
|
secret-free error messages;
|
|
- vault.remove reports {removed: bool} idempotently.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
import tui_gateway.server as srv
|
|
|
|
|
|
@pytest.fixture
|
|
def home(tmp_path, monkeypatch):
|
|
h = tmp_path / ".hermes"
|
|
h.mkdir()
|
|
monkeypatch.setenv("HERMES_HOME", str(h))
|
|
return h
|
|
|
|
|
|
def _result(envelope):
|
|
assert "error" not in envelope, envelope
|
|
return envelope["result"]
|
|
|
|
|
|
def _error(envelope):
|
|
assert "error" in envelope, envelope
|
|
return envelope["error"]
|
|
|
|
|
|
_LOGIN_PARAMS = {
|
|
"kind": "login",
|
|
"label": "Example login",
|
|
"origin": "https://example.com",
|
|
"secret": {
|
|
"identifier_type": "email",
|
|
"identifier": "user@example.com",
|
|
"password": "s3cret-pw-9000",
|
|
},
|
|
}
|
|
|
|
|
|
def test_add_then_list_is_password_free(home):
|
|
out = _result(srv._methods["vault.add"](1, dict(_LOGIN_PARAMS)))
|
|
assert out["id"].startswith("vault_")
|
|
# add's own envelope must not echo the secret back
|
|
assert "s3cret-pw-9000" not in json.dumps(out)
|
|
|
|
listed = _result(srv._methods["vault.list"](2, {}))
|
|
assert len(listed["items"]) == 1
|
|
item = listed["items"][0]
|
|
assert item["id"] == out["id"]
|
|
assert item["kind"] == "login"
|
|
assert item["label"] == "Example login"
|
|
assert item["origin"] == "https://example.com"
|
|
assert item["created_at"]
|
|
# Identifier is agent-visible metadata (design: only the password is secret).
|
|
assert item["identifier"] == "user@example.com"
|
|
assert item["identifier_type"] == "email"
|
|
dumped = json.dumps(listed)
|
|
assert "s3cret-pw-9000" not in dumped
|
|
assert "password" not in dumped
|
|
|
|
|
|
def test_add_validation_errors_are_clean(home):
|
|
err = _error(
|
|
srv._methods["vault.add"](
|
|
1,
|
|
{
|
|
"kind": "login",
|
|
"label": "no origin",
|
|
"secret": {
|
|
"identifier_type": "email",
|
|
"identifier": "user@example.com",
|
|
"password": "s3cret-pw-9000",
|
|
},
|
|
},
|
|
)
|
|
)
|
|
assert err["code"] == 5095
|
|
assert "s3cret-pw-9000" not in json.dumps(err)
|
|
|
|
err = _error(srv._methods["vault.add"](2, {"kind": "login", "label": "x"}))
|
|
assert err["code"] == 5095
|
|
|
|
err = _error(
|
|
srv._methods["vault.add"](
|
|
3, {"kind": "wat", "label": "x", "secret": {"password": "s3cret-pw-9000"}}
|
|
)
|
|
)
|
|
assert err["code"] == 5095
|
|
assert "s3cret-pw-9000" not in json.dumps(err)
|
|
|
|
|
|
def _sources_rows(home):
|
|
out = _result(srv._methods["vault.sources"](90, {}))
|
|
return {row["name"]: row for row in out["sources"]}
|
|
|
|
|
|
def test_enabling_a_detected_manager_reports_it_enabled(home, monkeypatch):
|
|
"""The Settings toggle and `hermes vault sources --enable` both clear the opt-out override;
|
|
the shipped default must then agree with `is_enabled()`'s zero-config contract — an installed
|
|
manager becomes a login source instead of silently staying off (#109546)."""
|
|
monkeypatch.setattr(
|
|
"agent.vault_backends.base.is_installed", lambda name: name == "bitwarden"
|
|
)
|
|
_result(
|
|
srv._methods["vault.source.set"](80, {"name": "bitwarden", "enabled": True})
|
|
)
|
|
rows = _sources_rows(home)
|
|
assert rows["bitwarden"]["installed"] is True
|
|
assert rows["bitwarden"]["enabled"] is True
|
|
|
|
|
|
def test_disabling_a_manager_persists_the_opt_out(home, monkeypatch):
|
|
monkeypatch.setattr(
|
|
"agent.vault_backends.base.is_installed", lambda name: name == "bitwarden"
|
|
)
|
|
_result(
|
|
srv._methods["vault.source.set"](81, {"name": "bitwarden", "enabled": False})
|
|
)
|
|
rows = _sources_rows(home)
|
|
assert rows["bitwarden"]["installed"] is True
|
|
assert rows["bitwarden"]["enabled"] is False
|
|
|
|
|
|
def test_undetected_manager_stays_off(home, monkeypatch):
|
|
monkeypatch.setattr("agent.vault_backends.base.is_installed", lambda name: False)
|
|
rows = _sources_rows(home)
|
|
assert rows["bitwarden"]["installed"] is False
|
|
|
|
|
|
def test_source_set_tolerates_scalar_vault_section(home, monkeypatch):
|
|
"""A hand-edited ``vault: true`` in config.yaml must not crash the Desktop
|
|
Credential Vault source toggle: the malformed section is coerced to a dict
|
|
before the opt-out is written (same YAML-shape hazard class _voice_cfg_dict
|
|
documents for voice.*, #19835)."""
|
|
monkeypatch.setattr(
|
|
"agent.vault_backends.base.is_installed", lambda name: name == "bitwarden"
|
|
)
|
|
(home / "config.yaml").write_text("vault: true\n")
|
|
_result(
|
|
srv._methods["vault.source.set"](82, {"name": "bitwarden", "enabled": False})
|
|
)
|
|
rows = _sources_rows(home)
|
|
assert rows["bitwarden"]["enabled"] is False
|
|
|
|
|
|
def test_remove_is_idempotent(home):
|
|
item_id = _result(srv._methods["vault.add"](1, dict(_LOGIN_PARAMS)))["id"]
|
|
assert _result(srv._methods["vault.remove"](2, {"id": item_id}))["removed"] is True
|
|
assert _result(srv._methods["vault.remove"](3, {"id": item_id}))["removed"] is False
|
|
assert _result(srv._methods["vault.list"](4, {}))["items"] == []
|
|
|
|
|
|
def test_remove_requires_id(home):
|
|
err = _error(srv._methods["vault.remove"](1, {}))
|
|
assert err["code"] == 5095
|
|
|
|
|
|
def test_launch_profile_vault_rpcs_stay_scoped_once_the_process_multiplexes(home, monkeypatch):
|
|
"""Once a second profile has been served, ``get_secret`` fails closed for unscoped reads. The
|
|
launch profile's vault.* calls (Desktop sends no ``profile`` for it) must still bind the launch
|
|
secret scope — otherwise every enabled manager's token read raises UnscopedSecretError and the
|
|
Passwords & Logins panel shows "Could not load vault items" until the gateway restarts."""
|
|
from agent.secret_scope import set_multiplex_active
|
|
|
|
monkeypatch.setattr("agent.vault_backends.base.is_installed", lambda name: name == "onepassword")
|
|
set_multiplex_active(True) # conftest resets the latch per test
|
|
assert _sources_rows(home)["onepassword"]["enabled"] is True
|