Files
gatehouse-api/tests/integration/test_self_service.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

171 lines
7.2 KiB
Python

"""Self-service integration tests.
Covers profile updates, password changes, and account deletion.
"""
import pytest
from tests.integration.client.base import ApiError
from gatehouse_app.utils.constants import OrganizationRole
def assert_success(response: dict, message_contains: str = "") -> dict:
data = response.get("data", {})
assert response.get("success") is not False
if message_contains:
assert message_contains.lower() in response.get("message", "").lower()
return data
class TestSelfService:
"""Test user self-service features."""
def test_get_profile_positive(self, integration_client, create_test_user):
"""TEST: SELF-01 — Get own profile.
WHAT: Login and GET /users/me.
WHY: Profile page displays user info.
EXPECTED: 200 OK with user data, has_password, totp_enabled.
"""
user = create_test_user(password="MyPassword123!")
integration_client.auth.login(email=user["email"], password="MyPassword123!")
result = integration_client.users.get_profile()
data = assert_success(result)
assert "user" in data
assert data["user"]["email"] == user["email"]
def test_update_profile_positive(self, integration_client, create_test_user):
"""TEST: SELF-02 — Update profile (full_name, avatar_url).
WHAT: PATCH /users/me with new full_name.
WHY: Users need to update their display name.
EXPECTED: 200 OK, name updated.
"""
user = create_test_user(password="MyPassword123!")
integration_client.auth.login(email=user["email"], password="MyPassword123!")
result = integration_client.users.update_profile(full_name="Updated Name")
data = assert_success(result)
assert data["user"]["full_name"] == "Updated Name"
def test_change_password_positive(self, integration_client, create_test_user):
"""TEST: SELF-03 — Change password with correct current password.
WHAT: POST /users/me/password with current + new password.
WHY: Users must be able to rotate their passwords.
EXPECTED: 200 OK. Login with new password succeeds.
"""
user = create_test_user(password="MyPassword123!")
integration_client.auth.login(email=user["email"], password="MyPassword123!")
result = integration_client.users.change_password(
current_password="MyPassword123!",
new_password="NewPass456!",
new_password_confirm="NewPass456!",
)
assert_success(result)
# Verify login with new password
integration_client.auth.logout()
login_result = integration_client.auth.login(email=user["email"], password="NewPass456!")
assert_success(login_result, "login successful")
def test_change_password_verify_login_positive(self, integration_client, create_test_user):
"""TEST: SELF-04 — Verify login with new password after change.
WHAT: Change password, logout, login with new password.
WHY: Ensures the password change actually persisted.
EXPECTED: Login succeeds.
"""
user = create_test_user(password="MyPassword123!")
integration_client.auth.login(email=user["email"], password="MyPassword123!")
integration_client.users.change_password(
current_password="MyPassword123!",
new_password="NewPass456!",
new_password_confirm="NewPass456!",
)
integration_client.auth.logout()
result = integration_client.auth.login(email=user["email"], password="NewPass456!")
assert_success(result)
def test_change_password_wrong_current_negative(self, integration_client, create_test_user):
"""TEST: SELF-05 — Change password with wrong current password.
WHAT: POST /users/me/password with incorrect current password.
WHY: Prevents account takeover if session is compromised.
EXPECTED: 401 Unauthorized. Token must NOT be cleared.
"""
user = create_test_user(password="MyPassword123!")
integration_client.auth.login(email=user["email"], password="MyPassword123!")
with pytest.raises(ApiError) as exc_info:
integration_client.users.change_password(
current_password="WrongPassword!",
new_password="NewPass456!",
new_password_confirm="NewPass456!",
)
assert exc_info.value.status_code == 401
# Token should still be valid
me = integration_client.auth.me()
assert_success(me)
def test_change_password_mismatched_negative(self, integration_client, create_test_user):
"""TEST: SELF-06 — Change password with mismatched new passwords.
WHAT: new_password and new_password_confirm differ.
WHY: Typo protection.
EXPECTED: 400 Bad Request.
"""
user = create_test_user(password="MyPassword123!")
integration_client.auth.login(email=user["email"], password="MyPassword123!")
with pytest.raises(ApiError) as exc_info:
integration_client.users.change_password(
current_password="MyPassword123!",
new_password="NewPass456!",
new_password_confirm="DifferentPass789!",
)
assert exc_info.value.status_code == 400
def test_delete_account_positive(self, integration_client, create_test_user):
"""TEST: SELF-07 — Delete own account (no orgs with members).
WHAT: Create a user with no org memberships, then DELETE
/users/me.
WHY: Users have the right to delete their data.
EXPECTED: 200 OK. Subsequent login fails.
"""
user = create_test_user(password="MyPassword123!")
integration_client.auth.login(email=user["email"], password="MyPassword123!")
result = integration_client.users.delete_account()
assert_success(result)
# Token is invalidated by account deletion; do not call logout.
integration_client.clear_token()
with pytest.raises(ApiError) as exc_info:
integration_client.auth.login(email=user["email"], password="MyPassword123!")
assert exc_info.value.status_code in (400, 401)
def test_delete_account_as_owner_with_members_negative(self, integration_client, create_test_user, create_test_org, create_test_membership):
"""TEST: SELF-09 — Reject deleting account when owner of org with members.
WHAT: User is owner of an org that has other members. Attempt
DELETE /users/me.
WHY: Prevents orphaning organizations.
EXPECTED: 409 Conflict, error about ownership transfer.
"""
owner = create_test_user(password="OwnerPass123!")
member = create_test_user(password="MemberPass123!")
org = create_test_org()
create_test_membership(owner["id"], org["id"], OrganizationRole.OWNER)
create_test_membership(member["id"], org["id"], OrganizationRole.MEMBER)
integration_client.auth.login(email=owner["email"], password="OwnerPass123!")
with pytest.raises(ApiError) as exc_info:
integration_client.users.delete_account()
assert exc_info.value.status_code == 409