015c622016
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.
171 lines
7.2 KiB
Python
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
|