feat(apim-apps): first commit
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, patch
|
||||
from apim_apps import normalize_env, EnvConfig, Manifest, OrgSpec, AppSpec, ApiAccess
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def env():
|
||||
return EnvConfig(name="TEST", url="https://test.example.com:8075", username="u", password="p")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_client():
|
||||
with patch("apim_apps.engine.AxwayClient") as MockClient:
|
||||
instance = MockClient.return_value
|
||||
instance.list_orgs.return_value = {}
|
||||
instance.list_apps.return_value = {}
|
||||
instance.list_app_api_access.return_value = []
|
||||
yield instance
|
||||
|
||||
|
||||
def _manifest(orgs=None, apps=None):
|
||||
return Manifest(organizations=orgs or [], applications=apps or [])
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dry-run
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_dry_run_makes_no_api_calls(env, mock_client):
|
||||
manifest = _manifest(
|
||||
orgs=[OrgSpec(name="MyOrg")],
|
||||
apps=[AppSpec(name="MyApp", org_name="MyOrg", apis=[ApiAccess(api_name="MyAPI")])],
|
||||
)
|
||||
mock_client.list_orgs.return_value = {}
|
||||
mock_client.list_apps.return_value = {}
|
||||
|
||||
result = normalize_env(env, manifest, dry_run=True)
|
||||
|
||||
mock_client.create_org.assert_not_called()
|
||||
mock_client.create_app.assert_not_called()
|
||||
mock_client.grant_api_access.assert_not_called()
|
||||
assert len(result.errors) == 0
|
||||
|
||||
|
||||
def test_dry_run_reports_planned_actions(env, mock_client):
|
||||
manifest = _manifest(
|
||||
orgs=[OrgSpec(name="MyOrg")],
|
||||
apps=[AppSpec(name="MyApp", org_name="MyOrg")],
|
||||
)
|
||||
result = normalize_env(env, manifest, dry_run=True)
|
||||
|
||||
kinds = {a.kind for a in result.actions_done}
|
||||
assert "create_org" in kinds
|
||||
assert "create_app" in kinds
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Apply mode
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_apply_creates_org_and_app(env, mock_client):
|
||||
manifest = _manifest(
|
||||
orgs=[OrgSpec(name="MyOrg")],
|
||||
apps=[AppSpec(name="MyApp", org_name="MyOrg")],
|
||||
)
|
||||
mock_client.create_org.return_value = {"id": "org-1", "name": "MyOrg"}
|
||||
mock_client.create_app.return_value = {"id": "app-1", "name": "MyApp"}
|
||||
|
||||
result = normalize_env(env, manifest, dry_run=False)
|
||||
|
||||
mock_client.create_org.assert_called_once()
|
||||
mock_client.create_app.assert_called_once()
|
||||
assert len(result.errors) == 0
|
||||
|
||||
|
||||
def test_apply_grants_api_subscription(env, mock_client):
|
||||
manifest = _manifest(
|
||||
orgs=[OrgSpec(name="MyOrg")],
|
||||
apps=[AppSpec(name="MyApp", org_name="MyOrg", apis=[ApiAccess(api_name="MyAPI")])],
|
||||
)
|
||||
mock_client.create_org.return_value = {"id": "org-1", "name": "MyOrg"}
|
||||
mock_client.create_app.return_value = {"id": "app-1", "name": "MyApp"}
|
||||
mock_client.find_frontend_api.return_value = {"id": "api-1", "name": "MyAPI"}
|
||||
|
||||
normalize_env(env, manifest, dry_run=False)
|
||||
|
||||
mock_client.grant_api_access.assert_called_once_with("app-1", "api-1")
|
||||
|
||||
|
||||
def test_apply_skips_missing_frontend_api(env, mock_client):
|
||||
manifest = _manifest(
|
||||
orgs=[OrgSpec(name="MyOrg")],
|
||||
apps=[AppSpec(name="MyApp", org_name="MyOrg", apis=[ApiAccess(api_name="Ghost")])],
|
||||
)
|
||||
mock_client.create_org.return_value = {"id": "org-1", "name": "MyOrg"}
|
||||
mock_client.create_app.return_value = {"id": "app-1", "name": "MyApp"}
|
||||
mock_client.find_frontend_api.return_value = None
|
||||
|
||||
result = normalize_env(env, manifest, dry_run=False)
|
||||
|
||||
mock_client.grant_api_access.assert_not_called()
|
||||
assert any("Ghost" in e for e in result.errors)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Skip when already present
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_skips_existing_org(env, mock_client):
|
||||
manifest = _manifest(orgs=[OrgSpec(name="MyOrg")])
|
||||
mock_client.list_orgs.return_value = {"MyOrg": {"id": "org-1", "name": "MyOrg"}}
|
||||
|
||||
result = normalize_env(env, manifest, dry_run=False)
|
||||
|
||||
mock_client.create_org.assert_not_called()
|
||||
assert any(a.kind == "create_org" for a in result.actions_skipped)
|
||||
|
||||
|
||||
def test_skips_existing_app(env, mock_client):
|
||||
manifest = _manifest(
|
||||
orgs=[OrgSpec(name="MyOrg")],
|
||||
apps=[AppSpec(name="MyApp", org_name="MyOrg")],
|
||||
)
|
||||
mock_client.list_orgs.return_value = {"MyOrg": {"id": "org-1", "name": "MyOrg"}}
|
||||
mock_client.list_apps.return_value = {("MyOrg", "MyApp"): {"id": "app-1", "name": "MyApp"}}
|
||||
|
||||
result = normalize_env(env, manifest, dry_run=False)
|
||||
|
||||
mock_client.create_app.assert_not_called()
|
||||
assert any(a.kind == "create_app" for a in result.actions_skipped)
|
||||
|
||||
|
||||
def test_skips_already_granted_api(env, mock_client):
|
||||
manifest = _manifest(
|
||||
orgs=[OrgSpec(name="MyOrg")],
|
||||
apps=[AppSpec(name="MyApp", org_name="MyOrg", apis=[ApiAccess(api_name="MyAPI")])],
|
||||
)
|
||||
mock_client.list_orgs.return_value = {"MyOrg": {"id": "org-1", "name": "MyOrg"}}
|
||||
mock_client.list_apps.return_value = {("MyOrg", "MyApp"): {"id": "app-1", "name": "MyApp"}}
|
||||
mock_client.list_app_api_access.return_value = [{"apiName": "MyAPI", "id": "api-1"}]
|
||||
|
||||
result = normalize_env(env, manifest, dry_run=False)
|
||||
|
||||
mock_client.grant_api_access.assert_not_called()
|
||||
assert any(a.kind == "grant_api" for a in result.actions_skipped)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Environment filtering
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_env_filter_excludes_other_env(env, mock_client):
|
||||
manifest = _manifest(
|
||||
orgs=[
|
||||
OrgSpec(name="GlobalOrg"),
|
||||
OrgSpec(name="ProdOnly", environments=["PROD"]),
|
||||
]
|
||||
)
|
||||
result = normalize_env(env, manifest, dry_run=True)
|
||||
|
||||
org_names = [a.detail["org"] for a in result.actions_done + result.actions_skipped]
|
||||
assert "GlobalOrg" in org_names
|
||||
assert "ProdOnly" not in org_names
|
||||
|
||||
|
||||
def test_env_filter_matches_current_env(env, mock_client):
|
||||
manifest = _manifest(
|
||||
orgs=[OrgSpec(name="TestOnly", environments=["TEST"])]
|
||||
)
|
||||
result = normalize_env(env, manifest, dry_run=True)
|
||||
|
||||
org_names = [a.detail["org"] for a in result.actions_done + result.actions_skipped]
|
||||
assert "TestOnly" in org_names
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Error handling
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_error_when_org_missing_for_app(env, mock_client):
|
||||
manifest = _manifest(
|
||||
apps=[AppSpec(name="Orphan", org_name="MissingOrg")]
|
||||
)
|
||||
mock_client.list_orgs.return_value = {}
|
||||
|
||||
result = normalize_env(env, manifest, dry_run=False)
|
||||
|
||||
assert any("MissingOrg" in e for e in result.errors)
|
||||
|
||||
|
||||
def test_list_orgs_failure_returns_error(env, mock_client):
|
||||
import requests
|
||||
mock_client.list_orgs.side_effect = requests.ConnectionError("timeout")
|
||||
manifest = _manifest(orgs=[OrgSpec(name="MyOrg")])
|
||||
|
||||
result = normalize_env(env, manifest, dry_run=False)
|
||||
|
||||
assert len(result.errors) == 1
|
||||
Reference in New Issue
Block a user