c62826daea
- providers table seeded with 3 NPIs (Montrose, Delta, Salida) - payers + payer_configs (per-tx config) join table - clearhouse singleton (dzinesco identity, SFTP block, filename block) - config/payers.yaml loader with Pydantic schema validation - cyclone/edi/filenames.py: HCPF X12 File Naming Standards helpers - cyclone/clearhouse/sftp.py: stub that copies to ./var/sftp/staging/... - cyclone/secrets.py: macOS Keychain wrapper via keyring - 11 new validation rules R200-R210 (CO MAP + HCPF naming) - 6 new API endpoints (/api/clearhouse/*, /api/config/*, /api/admin/reload-config) - migration 0007 - 72 new tests (646 total passing, was 574) See spec: docs/superpowers/specs/2026-06-20-cyclone-multi-payer-npi-sftp-design.md
60 lines
1.9 KiB
Python
60 lines
1.9 KiB
Python
"""SP9 — macOS Keychain secret accessor tests."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from cyclone import secrets
|
|
from cyclone.secrets import STUB_SECRET, get_secret, has_keyring, set_secret
|
|
|
|
|
|
def test_has_keyring_true_when_lib_present():
|
|
# The test env has keyring installed
|
|
assert has_keyring() is True
|
|
|
|
|
|
def test_get_secret_returns_none_when_keyring_missing(monkeypatch):
|
|
monkeypatch.setattr(secrets, "_HAS_KEYRING", False)
|
|
monkeypatch.setattr(secrets, "keyring", None)
|
|
assert get_secret("anything") is None
|
|
|
|
|
|
def test_get_secret_returns_keychain_value():
|
|
with patch("cyclone.secrets.keyring") as mock_kr:
|
|
mock_kr.get_password.return_value = "p@ssw0rd"
|
|
v = get_secret("sftp.gainwell.password")
|
|
assert v == "p@ssw0rd"
|
|
mock_kr.get_password.assert_called_once_with("cyclone", "sftp.gainwell.password")
|
|
|
|
|
|
def test_get_secret_returns_none_on_keychain_exception():
|
|
with patch("cyclone.secrets.keyring") as mock_kr:
|
|
mock_kr.get_password.side_effect = RuntimeError("keychain locked")
|
|
v = get_secret("x")
|
|
assert v is None
|
|
|
|
|
|
def test_set_secret_returns_true_on_success():
|
|
with patch("cyclone.secrets.keyring") as mock_kr:
|
|
assert set_secret("a", "b") is True
|
|
mock_kr.set_password.assert_called_once_with("cyclone", "a", "b")
|
|
|
|
|
|
def test_set_secret_returns_false_when_keyring_missing(monkeypatch):
|
|
monkeypatch.setattr(secrets, "_HAS_KEYRING", False)
|
|
monkeypatch.setattr(secrets, "keyring", None)
|
|
assert set_secret("a", "b") is False
|
|
|
|
|
|
def test_set_secret_returns_false_on_keychain_exception():
|
|
with patch("cyclone.secrets.keyring") as mock_kr:
|
|
mock_kr.set_password.side_effect = RuntimeError("denied")
|
|
assert set_secret("a", "b") is False
|
|
|
|
|
|
def test_stub_secret_is_distinct_string():
|
|
assert STUB_SECRET == "<stub-secret>"
|
|
assert STUB_SECRET != ""
|