Files
gatehouse-api/tests/integration/client/orgs.py
T
nexgen_mirrors 015c622016 test: add comprehensive integration test suite for IAM platform
Add 162 integration tests covering authentication flows, TOTP MFA,
SSH key/certificate management, organization workflows, multi-org
access, self-service features, admin operations, authorization,
security edge cases, department/principal management, CA management,
policy compliance, WebAuthn passkeys, and ZeroTier network access.

Includes:
- Reusable API client library with session management
- Test fixtures for users, organizations, memberships, and CAs
- Helper functions for SSH key generation and verification
- Documentation for running and writing tests

Also update test configuration to disable conflicting maas plugins
and configure WebAuthn/session settings for localhost testing.
2026-04-23 15:41:37 +09:30

192 lines
7.9 KiB
Python

"""Organization client for integration tests."""
import logging
logger = logging.getLogger(__name__)
class OrgsClient:
"""Wraps organization-related API calls."""
def __init__(self, client):
self._client = client
# ------------------------------------------------------------------
# Organization CRUD
# ------------------------------------------------------------------
def create(self, name: str, slug: str | None = None, description: str | None = None) -> dict:
"""Create a new organization."""
payload: dict = {"name": name}
if slug:
payload["slug"] = slug
if description:
payload["description"] = description
return self._client.post("/organizations", data=payload)
def get(self, org_id: str) -> dict:
"""Get organization details."""
return self._client.get(f"/organizations/{org_id}")
def update(self, org_id: str, **fields) -> dict:
"""Update organization fields (name, description, etc.)."""
return self._client.patch(f"/organizations/{org_id}", data=fields)
def delete(self, org_id: str, confirm: bool = False) -> dict:
"""Delete (soft-delete) an organization."""
return self._client.delete(f"/organizations/{org_id}", data={"confirm": confirm})
# ------------------------------------------------------------------
# Members
# ------------------------------------------------------------------
def list_members(self, org_id: str) -> dict:
"""List members of an organization."""
return self._client.get(f"/organizations/{org_id}/members")
def add_member(self, org_id: str, email: str, role: str = "member") -> dict:
"""Add an existing user as a member."""
return self._client.post(
f"/organizations/{org_id}/members",
data={"email": email, "role": role},
)
def remove_member(self, org_id: str, member_id: str) -> dict:
"""Remove a member from an organization."""
return self._client.delete(f"/organizations/{org_id}/members/{member_id}")
def update_member_role(self, org_id: str, member_id: str, role: str) -> dict:
"""Update a member's role."""
return self._client.patch(
f"/organizations/{org_id}/members/{member_id}/role",
data={"role": role},
)
def transfer_ownership(self, org_id: str, new_owner_id: str) -> dict:
"""Transfer organization ownership."""
return self._client.post(
f"/organizations/{org_id}/transfer-ownership",
data={"new_owner_user_id": new_owner_id},
)
# ------------------------------------------------------------------
# Invites
# ------------------------------------------------------------------
def list_invites(self, org_id: str) -> dict:
"""List pending invites."""
return self._client.get(f"/organizations/{org_id}/invites")
def create_invite(self, org_id: str, email: str, role: str = "member") -> dict:
"""Create an invite for a new user."""
return self._client.post(
f"/organizations/{org_id}/invites",
data={"email": email, "role": role},
)
def cancel_invite(self, org_id: str, invite_id: str) -> dict:
"""Cancel a pending invite."""
return self._client.delete(f"/organizations/{org_id}/invites/{invite_id}")
def get_invite_by_token(self, token: str) -> dict:
"""Get invite info by token (public endpoint)."""
return self._client.get(f"/invites/{token}")
def accept_invite(self, token: str, password: str | None = None, full_name: str | None = None, password_confirm: str | None = None) -> dict:
"""Accept an invite. For new users, password and full_name are required."""
payload: dict = {}
if password:
payload["password"] = password
if password_confirm:
payload["password_confirm"] = password_confirm
if full_name:
payload["full_name"] = full_name
result = self._client.post(f"/invites/{token}/accept", data=payload)
# Store token if returned (new user registration)
token_val = result.get("data", {}).get("token")
if token_val:
self._client.set_token(token_val)
return result
# ------------------------------------------------------------------
# Principals & Departments
# ------------------------------------------------------------------
def list_principals(self, org_id: str) -> dict:
"""List principals in an organization."""
return self._client.get(f"/organizations/{org_id}/principals")
def create_principal(self, org_id: str, name: str, description: str | None = None) -> dict:
"""Create a principal."""
payload: dict = {"name": name}
if description:
payload["description"] = description
return self._client.post(f"/organizations/{org_id}/principals", data=payload)
def add_principal_member(self, org_id: str, principal_id: str, email: str) -> dict:
"""Add a user to a principal."""
return self._client.post(
f"/organizations/{org_id}/principals/{principal_id}/members",
data={"email": email},
)
def list_departments(self, org_id: str) -> dict:
"""List departments in an organization."""
return self._client.get(f"/organizations/{org_id}/departments")
def create_department(self, org_id: str, name: str, description: str | None = None) -> dict:
"""Create a department."""
payload: dict = {"name": name}
if description:
payload["description"] = description
return self._client.post(f"/organizations/{org_id}/departments", data=payload)
def add_department_member(self, org_id: str, dept_id: str, email: str) -> dict:
"""Add a user to a department."""
return self._client.post(
f"/organizations/{org_id}/departments/{dept_id}/members",
data={"email": email},
)
def link_principal_department(self, org_id: str, principal_id: str, dept_id: str) -> dict:
"""Link a principal to a department."""
return self._client.post(
f"/organizations/{org_id}/principals/{principal_id}/departments/{dept_id}",
data={},
)
# ------------------------------------------------------------------
# CAs
# ------------------------------------------------------------------
def list_cas(self, org_id: str) -> dict:
"""List CAs for an organization."""
return self._client.get(f"/organizations/{org_id}/cas")
def create_ca(self, org_id: str, name: str, ca_type: str = "user", key_type: str = "ed25519") -> dict:
"""Create a Certificate Authority."""
return self._client.post(
f"/organizations/{org_id}/cas",
data={"name": name, "ca_type": ca_type, "key_type": key_type},
)
def get_ca(self, org_id: str, ca_id: str) -> dict:
"""Get a CA by ID."""
return self._client.get(f"/organizations/{org_id}/cas/{ca_id}")
def rotate_ca(self, org_id: str, ca_id: str) -> dict:
"""Rotate a CA key."""
return self._client.post(f"/organizations/{org_id}/cas/{ca_id}/rotate")
# ------------------------------------------------------------------
# API Keys
# ------------------------------------------------------------------
def list_api_keys(self, org_id: str) -> dict:
"""List API keys."""
return self._client.get(f"/organizations/{org_id}/api-keys")
def create_api_key(self, org_id: str, name: str, role: str = "member") -> dict:
"""Create an API key."""
return self._client.post(
f"/organizations/{org_id}/api-keys",
data={"name": name, "role": role},
)
def revoke_api_key(self, org_id: str, key_id: str) -> dict:
"""Revoke an API key."""
return self._client.delete(f"/organizations/{org_id}/api-keys/{key_id}")