Files
acdl/tests/test_idp_setup.py
T
Jon Chery 1863a85144 feat(P04): nova idp setup --check/--apply/--verify (REQ-340/341, C-2.1, backend+cli)
---ci---
project: acdl
phase: 4
milestone: v1.28
status: execute
persona: backend-engineer
---
2026-08-19 23:13:21 +00:00

191 lines
6.2 KiB
Python

"""nova idp setup tests (REQ-340, REQ-341, C-2.1).
Tests:
* ``generate_template()`` produces a valid CFN dict with the expected
resource types (3 Lambdas, 4 DDB tables, KMS key, 3 URLs, 3 roles).
* ``--check`` (mock AWS) → prints a prerequisite report.
* ``--dry-run`` → resource summary.
* ``--apply`` (mock cloudformation deploy) → prompts + deploys.
"""
from __future__ import annotations
import importlib.util
import json
import os
import sys
from pathlib import Path
from unittest import mock
import pytest
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
os.environ.setdefault("AWS_DEFAULT_REGION", "us-east-1")
os.environ.setdefault("NOVA_LAMBDA_LOCAL_BYPASS", "1")
def _load(mod_name, rel_path):
spec = importlib.util.spec_from_file_location(mod_name, rel_path)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
return mod
_CFN_PATH = Path(__file__).resolve().parent.parent / "core" / "lambda" / "nova_idp_cfn.py"
_SETUP_PATH = Path(__file__).resolve().parent.parent / "core" / "lambda" / "nova_idp_setup.py"
cfn = _load("nova_idp_cfn_test", _CFN_PATH)
setup = _load("nova_idp_setup_test", _SETUP_PATH)
# ---------------------------------------------------------------------------
# generate_template
# ---------------------------------------------------------------------------
def test_generate_template_has_expected_resources():
t = cfn.generate_template()
res = t["Resources"]
types = [r["Type"] for r in res.values()]
assert types.count("AWS::Lambda::Function") == 3
assert types.count("AWS::DynamoDB::Table") == 4
assert types.count("AWS::KMS::Key") == 1
assert types.count("AWS::KMS::Alias") == 1
assert types.count("AWS::Lambda::Url") == 3
assert types.count("AWS::IAM::Role") == 3
def test_generate_template_kms_key_spec():
t = cfn.generate_template()
key = t["Resources"]["NovaOidcSigningKey"]["Properties"]
assert key["KeySpec"] == "ECC_NIST_P256"
assert key["KeyUsage"] == "SIGN_VERIFY"
def test_generate_template_jwks_url_auth_none():
"""JWKS function URL is AuthType NONE (public, REQ-338)."""
t = cfn.generate_template()
url = t["Resources"]["NovaIdpJwksUrl"]["Properties"]
assert url["AuthType"] == "NONE"
def test_generate_template_auth_url_iam():
t = cfn.generate_template()
url = t["Resources"]["NovaIdpAuthUrl"]["Properties"]
assert url["AuthType"] == "AWS_IAM"
def test_generate_template_public_domain_adds_cloudfront():
t = cfn.generate_template(public_jwks_domain="jwks.example.com")
types = [r["Type"] for r in t["Resources"].values()]
assert "AWS::CloudFront::Distribution" in types
assert "AWS::CertificateManager::Certificate" in types
def test_resource_summary():
t = cfn.generate_template()
s = cfn.resource_summary(t)
assert s["AWS::Lambda::Function"] == 3
assert s["AWS::DynamoDB::Table"] == 4
# ---------------------------------------------------------------------------
# --check
# ---------------------------------------------------------------------------
def test_check_prerequisites_returns_report():
with mock.patch("subprocess.check_output", side_effect=Exception("no creds")):
report = setup.check_prerequisites()
assert "aws_creds" in report
assert report["aws_creds"] is False
assert "missing" in report
assert "iam_delta" in report
assert "cloudformation:*" in report["iam_delta"]
def test_check_prerequisites_with_creds():
fake = json.dumps({"Account": "123456789012", "UserId": "u", "Arn": "arn"})
with mock.patch("subprocess.check_output", return_value=fake):
report = setup.check_prerequisites()
assert report["aws_creds"] is True
# ---------------------------------------------------------------------------
# --dry-run
# ---------------------------------------------------------------------------
def test_dry_run_returns_summary():
r = setup.generate_and_deploy(dry_run=True)
assert r["deployed"] is False
assert "AWS::Lambda::Function" in r["summary"]
assert r["summary"]["AWS::Lambda::Function"] == 3
# ---------------------------------------------------------------------------
# --apply (mock cloudformation deploy)
# ---------------------------------------------------------------------------
def test_apply_aborts_without_approval():
r = setup.generate_and_deploy(approve_fn=lambda: False)
assert r["deployed"] is False
def test_apply_deploys_with_approval():
with mock.patch("subprocess.check_call", return_value=0):
r = setup.generate_and_deploy(approve_fn=lambda: True)
assert r["deployed"] is True
def test_apply_deploy_failure_returns_not_deployed():
with mock.patch("subprocess.check_call", side_effect=RuntimeError("cfn error")):
r = setup.generate_and_deploy(approve_fn=lambda: True)
assert r["deployed"] is False
# ---------------------------------------------------------------------------
# --verify
# ---------------------------------------------------------------------------
def test_verify_roundtrip_passes():
r = setup.verify()
assert r["passed"] is True
# ---------------------------------------------------------------------------
# CLI wrapper (nova/idp/setup.py)
# ---------------------------------------------------------------------------
def test_cli_setup_check(capsys):
from nova.idp import setup as cli_setup
args = mock.MagicMock()
args.check = True; args.apply = False; args.verify = False; args.dry_run = False
args.public_jwks_domain = None
rc = cli_setup.run(args)
assert rc == 0
out = capsys.readouterr().out
assert "aws_creds" in out
def test_cli_setup_dry_run(capsys):
from nova.idp import setup as cli_setup
args = mock.MagicMock()
args.check = False; args.apply = False; args.verify = False; args.dry_run = True
args.public_jwks_domain = None
rc = cli_setup.run(args)
assert rc == 0
out = capsys.readouterr().out
assert "AWS::Lambda::Function" in out
def test_cli_setup_verify(capsys):
from nova.idp import setup as cli_setup
args = mock.MagicMock()
args.check = False; args.apply = False; args.verify = True; args.dry_run = False
args.public_jwks_domain = None
rc = cli_setup.run(args)
assert rc == 0