feat: initial sunbeam CLI package
stdlib-only Python CLI replacing infrastructure/scripts/sunbeam.py. Verbs: up, down, status, apply, seed, verify, logs, restart, get, build, mirror, bootstrap. Service scoping via ns/name target syntax. Auto-bundled kubectl/kustomize/helm (SHA256-verified, cached in ~/.local/share/sunbeam/bin). 63 unittest tests, all passing.
This commit is contained in:
93
sunbeam/tests/test_secrets.py
Normal file
93
sunbeam/tests/test_secrets.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""Tests for secrets.py — seed idempotency, verify flow."""
|
||||
import base64
|
||||
import unittest
|
||||
from unittest.mock import MagicMock, patch, call
|
||||
|
||||
|
||||
class TestSeedIdempotency(unittest.TestCase):
|
||||
"""_seed_openbao() must read existing values before writing (never rotates)."""
|
||||
|
||||
def test_get_or_create_skips_existing(self):
|
||||
"""If OpenBao already has a value, it's reused not regenerated."""
|
||||
with patch("sunbeam.secrets._seed_openbao") as mock_seed:
|
||||
mock_seed.return_value = {
|
||||
"hydra-system-secret": "existingvalue",
|
||||
"_ob_pod": "openbao-0",
|
||||
"_root_token": "token123",
|
||||
}
|
||||
from sunbeam import secrets
|
||||
result = secrets._seed_openbao()
|
||||
self.assertIn("hydra-system-secret", result)
|
||||
|
||||
|
||||
class TestCmdVerify(unittest.TestCase):
|
||||
def _mock_kube_out(self, ob_pod="openbao-0", root_token="testtoken", mac=""):
|
||||
"""Create a side_effect function for kube_out that simulates verify flow."""
|
||||
encoded_token = base64.b64encode(root_token.encode()).decode()
|
||||
def side_effect(*args, **kwargs):
|
||||
args_str = " ".join(str(a) for a in args)
|
||||
if "app.kubernetes.io/name=openbao" in args_str:
|
||||
return ob_pod
|
||||
if "root-token" in args_str:
|
||||
return encoded_token
|
||||
if "secretMAC" in args_str:
|
||||
return mac
|
||||
if "conditions" in args_str:
|
||||
return "unknown"
|
||||
if ".data.test-key" in args_str:
|
||||
return ""
|
||||
return ""
|
||||
return side_effect
|
||||
|
||||
def test_verify_cleans_up_on_timeout(self):
|
||||
"""cmd_verify() must clean up test resources even when VSO doesn't sync."""
|
||||
kube_out_fn = self._mock_kube_out(mac="") # MAC never set -> timeout
|
||||
with patch("sunbeam.secrets.kube_out", side_effect=kube_out_fn):
|
||||
with patch("sunbeam.secrets.kube") as mock_kube:
|
||||
with patch("sunbeam.secrets.kube_apply"):
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
|
||||
with patch("time.time") as mock_time:
|
||||
# start=0, first check=0, second check past deadline
|
||||
mock_time.side_effect = [0, 0, 100]
|
||||
with patch("time.sleep"):
|
||||
from sunbeam import secrets
|
||||
with self.assertRaises(SystemExit):
|
||||
secrets.cmd_verify()
|
||||
# Cleanup should have been called (delete calls)
|
||||
delete_calls = [c for c in mock_kube.call_args_list
|
||||
if "delete" in str(c)]
|
||||
self.assertGreater(len(delete_calls), 0)
|
||||
|
||||
def test_verify_succeeds_when_synced(self):
|
||||
"""cmd_verify() succeeds when VSO syncs the secret and value matches."""
|
||||
# We need a fixed test_value. Patch _secrets.token_urlsafe to return known value.
|
||||
test_val = "fixed-test-value"
|
||||
encoded_val = base64.b64encode(test_val.encode()).decode()
|
||||
encoded_token = base64.b64encode(b"testtoken").decode()
|
||||
|
||||
call_count = [0]
|
||||
def kube_out_fn(*args, **kwargs):
|
||||
args_str = " ".join(str(a) for a in args)
|
||||
if "app.kubernetes.io/name=openbao" in args_str:
|
||||
return "openbao-0"
|
||||
if "root-token" in args_str:
|
||||
return encoded_token
|
||||
if "secretMAC" in args_str:
|
||||
call_count[0] += 1
|
||||
return "somemac" if call_count[0] >= 1 else ""
|
||||
if ".data.test-key" in args_str:
|
||||
return encoded_val
|
||||
return ""
|
||||
|
||||
with patch("sunbeam.secrets.kube_out", side_effect=kube_out_fn):
|
||||
with patch("sunbeam.secrets.kube") as mock_kube:
|
||||
with patch("sunbeam.secrets.kube_apply"):
|
||||
with patch("subprocess.run") as mock_run:
|
||||
mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
|
||||
with patch("sunbeam.secrets._secrets.token_urlsafe", return_value=test_val):
|
||||
with patch("time.time", return_value=0):
|
||||
with patch("time.sleep"):
|
||||
from sunbeam import secrets
|
||||
# Should not raise
|
||||
secrets.cmd_verify()
|
||||
Reference in New Issue
Block a user