1863a85144
---ci--- project: acdl phase: 4 milestone: v1.28 status: execute persona: backend-engineer ---
191 lines
6.2 KiB
Python
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 |