Files

741 lines
28 KiB
Python
Raw Permalink Normal View History

2026-04-18 07:23:55 +12:00
"""
Tests for the members area.
Covers:
- Startup regression: app imports without crashing (no email-validator missing)
- Claim flow: request code → complete with password
- Login flow: password check → 2FA → JWT tokens
- Token refresh
- Protected member endpoints: me, walks, bookings, contract, messages
- Admin endpoints: create member, list members, record walk, send message
"""
import pytest
from datetime import datetime, timezone
from httpx import AsyncClient
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.models.audit import AuditLog
from app.models.member import Member, MemberVerificationCode, Walk, Booking, AdminMessage
from app.models.settings import SiteSettings
from app.auth.password import hash_password
pytestmark = [pytest.mark.asyncio, pytest.mark.members_admin]
# ── Startup regression ─────────────────────────────────────────────────────────
# ── Helpers ────────────────────────────────────────────────────────────────────
async def _create_member(
db: AsyncSession,
email: str = "member@example.com",
claimed: bool = False,
member_status: str | None = None,
) -> Member:
m = Member(
email=email,
first_name="Jane",
last_name="Doe",
phone="021 000 0000",
is_claimed=claimed,
is_active=True,
member_status=member_status or ("active" if claimed else "invited"),
hashed_password=hash_password("Password1!") if claimed else None,
onboarding_data={"dog_name": "Buddy", "breed": "Labrador"},
)
db.add(m)
await db.commit()
await db.refresh(m)
return m
async def _get_latest_code(db: AsyncSession, member_id, purpose: str) -> str:
result = await db.execute(
select(MemberVerificationCode)
.where(
MemberVerificationCode.member_id == member_id,
MemberVerificationCode.purpose == purpose,
MemberVerificationCode.used_at.is_(None),
)
.order_by(MemberVerificationCode.created_at.desc())
)
vc = result.scalars().first()
assert vc is not None, f"No unused {purpose} code found"
# Reverse-lookup the plaintext from the hash by brute-force on our fixture value.
# Since we control _generate_code in tests via known fixture, we read the hash
# and return it for use in the full flow — we need to inject a known code instead.
return vc # caller gets the row and patches it directly
# ── Admin: create member ───────────────────────────────────────────────────────
async def test_admin_create_member(client: AsyncClient, admin_token: str):
resp = await client.post(
"/api/v1/admin/members",
headers={"Authorization": f"Bearer {admin_token}"},
json={
"email": "newmember@example.com",
"first_name": "John",
"last_name": "Smith",
"phone": "021 111 1111",
"onboarding_data": {"dog_name": "Rex"},
},
)
assert resp.status_code == 201
data = resp.json()
assert data["email"] == "newmember@example.com"
assert data["is_claimed"] is False
assert data["member_status"] == "invited"
async def test_admin_create_member_with_special_rate_and_force_two_factor(client: AsyncClient, admin_token: str):
resp = await client.post(
"/api/v1/admin/members",
headers={"Authorization": f"Bearer {admin_token}"},
json={
"email": "specialrate@example.com",
"first_name": "Casey",
"last_name": "Ngata",
"service_pricing_overrides": {"pack_walk": 49.5},
"force_two_factor": True,
},
)
assert resp.status_code == 201
data = resp.json()
assert data["service_pricing_overrides"] == {"pack_walk": 49.5}
assert data["force_two_factor"] is True
async def test_admin_create_member_duplicate(client: AsyncClient, admin_token: str, db_session: AsyncSession):
await _create_member(db_session, "dup@example.com")
resp = await client.post(
"/api/v1/admin/members",
headers={"Authorization": f"Bearer {admin_token}"},
json={"email": "dup@example.com", "first_name": "X", "last_name": "Y"},
)
assert resp.status_code == 409
async def test_admin_list_members(client: AsyncClient, admin_token: str, db_session: AsyncSession):
await _create_member(db_session, "list1@example.com")
await _create_member(db_session, "list2@example.com")
resp = await client.get(
"/api/v1/admin/members",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert resp.status_code == 200
assert len(resp.json()) >= 2
async def test_admin_can_move_member_back_to_onboarding(client: AsyncClient, admin_token: str, db_session: AsyncSession):
member = await _create_member(db_session, "statusreset@example.com", claimed=True, member_status="active")
member.onboarding_completed_at = datetime.now(timezone.utc)
member.contract_signed_at = datetime.now(timezone.utc)
member.contract_signer_name = "Jane Doe"
member.contract_version = "goodwalk-service-agreement-2026-03"
member.activated_at = datetime.now(timezone.utc)
await db_session.commit()
resp = await client.put(
f"/api/v1/admin/members/{member.id}",
headers={"Authorization": f"Bearer {admin_token}"},
json={"member_status": "onboarding"},
)
assert resp.status_code == 200
data = resp.json()
assert data["member_status"] == "onboarding"
assert data["activated_at"] is None
assert data["onboarding_completed_at"] is None
assert data["contract_signed_at"] is None
async def test_admin_cannot_move_unclaimed_member_into_onboarding(client: AsyncClient, admin_token: str, db_session: AsyncSession):
member = await _create_member(db_session, "unclaimedstatus@example.com", claimed=False, member_status="invited")
resp = await client.put(
f"/api/v1/admin/members/{member.id}",
headers={"Authorization": f"Bearer {admin_token}"},
json={"member_status": "onboarding"},
)
assert resp.status_code == 400
async def test_admin_requires_auth(client: AsyncClient):
resp = await client.get("/api/v1/admin/members")
assert resp.status_code in (401, 403) # HTTPBearer raises 403; get_current_user raises 401
# ── Claim flow ─────────────────────────────────────────────────────────────────
async def test_claim_request_unknown_email(client: AsyncClient):
"""Unknown email still returns 200 (no enumeration)."""
resp = await client.post(
"/api/v1/members/claim/request",
json={"email": "nobody@example.com"},
)
assert resp.status_code == 200
async def test_claim_request_already_claimed(client: AsyncClient, db_session: AsyncSession):
await _create_member(db_session, "claimed@example.com", claimed=True)
resp = await client.post(
"/api/v1/members/claim/request",
json={"email": "claimed@example.com"},
)
assert resp.status_code == 200 # still generic response
async def test_full_claim_flow(client: AsyncClient, db_session: AsyncSession):
"""Unclaimed member can claim their account and then log in."""
member = await _create_member(db_session, "claimme@example.com", claimed=False)
# Step 1 — request claim code
resp = await client.post(
"/api/v1/members/claim/request",
json={"email": "claimme@example.com"},
)
assert resp.status_code == 200
# Step 2 — retrieve the code row and inject a known plaintext
import hashlib, secrets
known_code = "AABBCC"
code_hash = hashlib.sha256(known_code.encode()).hexdigest()
await db_session.execute(
MemberVerificationCode.__table__.update()
.where(
MemberVerificationCode.member_id == member.id,
MemberVerificationCode.purpose == "claim",
)
.values(code_hash=code_hash)
)
await db_session.commit()
# Step 3 — complete claim
resp = await client.post(
"/api/v1/members/claim/complete",
json={"email": "claimme@example.com", "code": known_code, "password": "NewPass99!"},
)
assert resp.status_code == 200
# Verify member is now claimed
await db_session.refresh(member)
assert member.is_claimed is True
assert member.hashed_password is not None
assert member.member_status == "onboarding"
async def test_claim_wrong_code(client: AsyncClient, db_session: AsyncSession):
member = await _create_member(db_session, "wrongcode@example.com", claimed=False)
await client.post("/api/v1/members/claim/request", json={"email": "wrongcode@example.com"})
resp = await client.post(
"/api/v1/members/claim/complete",
json={"email": "wrongcode@example.com", "code": "ZZZZZZ", "password": "NewPass99!"},
)
assert resp.status_code == 400
async def test_claim_short_password(client: AsyncClient, db_session: AsyncSession):
await _create_member(db_session, "shortpw@example.com", claimed=False)
resp = await client.post(
"/api/v1/members/claim/complete",
json={"email": "shortpw@example.com", "code": "AABBCC", "password": "short"},
)
assert resp.status_code == 422
# ── Login / 2FA flow ───────────────────────────────────────────────────────────
async def _do_full_login(client: AsyncClient, db_session: AsyncSession, email: str = "login@example.com") -> dict:
"""Helper: create claimed member, go through full 2FA login, return tokens."""
import hashlib
member = await _create_member(db_session, email, claimed=True)
resp = await client.post(
"/api/v1/members/auth/login",
json={"email": email, "password": "Password1!"},
)
assert resp.status_code == 200
known_code = "112233"
code_hash = hashlib.sha256(known_code.encode()).hexdigest()
await db_session.execute(
MemberVerificationCode.__table__.update()
.where(
MemberVerificationCode.member_id == member.id,
MemberVerificationCode.purpose == "login_2fa",
)
.values(code_hash=code_hash)
)
await db_session.commit()
resp = await client.post(
"/api/v1/members/auth/login/verify",
json={"email": email, "code": known_code},
)
assert resp.status_code == 200
return resp.json()
async def test_login_invalid_password(client: AsyncClient, db_session: AsyncSession):
await _create_member(db_session, "badpw@example.com", claimed=True)
resp = await client.post(
"/api/v1/members/auth/login",
json={"email": "badpw@example.com", "password": "WrongPass!"},
)
assert resp.status_code == 401
async def test_login_unclaimed_member(client: AsyncClient, db_session: AsyncSession):
await _create_member(db_session, "unclaimed@example.com", claimed=False)
resp = await client.post(
"/api/v1/members/auth/login",
json={"email": "unclaimed@example.com", "password": "Password1!"},
)
assert resp.status_code == 401
async def test_full_login_flow(client: AsyncClient, db_session: AsyncSession):
tokens = await _do_full_login(client, db_session, "fulllogin@example.com")
assert "access_token" in tokens
assert "refresh_token" in tokens
assert tokens["token_type"] == "bearer"
async def test_login_bypasses_two_factor_when_disabled(client: AsyncClient, db_session: AsyncSession):
await _create_member(db_session, "no2fa@example.com", claimed=True)
db_session.add(SiteSettings(site_name="", two_factor_enabled=False))
await db_session.commit()
resp = await client.post(
"/api/v1/members/auth/login",
json={"email": "no2fa@example.com", "password": "Password1!"},
)
assert resp.status_code == 200
body = resp.json()
assert body["two_factor_required"] is False
assert body["access_token"]
assert body["refresh_token"]
code_result = await db_session.execute(
select(MemberVerificationCode).where(MemberVerificationCode.purpose == "login_2fa")
)
assert code_result.scalars().all() == []
async def test_login_requires_two_factor_when_member_override_enabled(client: AsyncClient, db_session: AsyncSession):
member = await _create_member(db_session, "force2fa@example.com", claimed=True)
member.force_two_factor = True
db_session.add(SiteSettings(site_name="", two_factor_enabled=False))
await db_session.commit()
resp = await client.post(
"/api/v1/members/auth/login",
json={"email": "force2fa@example.com", "password": "Password1!"},
)
assert resp.status_code == 200
body = resp.json()
assert body["two_factor_required"] is True
import hashlib
known_code = "445566"
code_hash = hashlib.sha256(known_code.encode()).hexdigest()
await db_session.execute(
MemberVerificationCode.__table__.update()
.where(
MemberVerificationCode.member_id == member.id,
MemberVerificationCode.purpose == "login_2fa",
)
.values(code_hash=code_hash)
)
await db_session.commit()
verify_resp = await client.post(
"/api/v1/members/auth/login/verify",
json={"email": "force2fa@example.com", "code": known_code},
)
assert verify_resp.status_code == 200
assert "access_token" in verify_resp.json()
async def test_login_wrong_2fa_code(client: AsyncClient, db_session: AsyncSession):
await _create_member(db_session, "bad2fa@example.com", claimed=True)
await client.post(
"/api/v1/members/auth/login",
json={"email": "bad2fa@example.com", "password": "Password1!"},
)
resp = await client.post(
"/api/v1/members/auth/login/verify",
json={"email": "bad2fa@example.com", "code": "ZZZZZZ"},
)
assert resp.status_code == 401
async def test_member_token_refresh(client: AsyncClient, db_session: AsyncSession):
tokens = await _do_full_login(client, db_session, "refresh@example.com")
resp = await client.post(
"/api/v1/members/auth/refresh",
json={"refresh_token": tokens["refresh_token"]},
)
assert resp.status_code == 200
new_tokens = resp.json()
assert "access_token" in new_tokens
assert "refresh_token" in new_tokens
# New refresh token must differ from the old one (it's a random secret)
assert new_tokens["refresh_token"] != tokens["refresh_token"]
# Old refresh token is now revoked
resp2 = await client.post(
"/api/v1/members/auth/refresh",
json={"refresh_token": tokens["refresh_token"]},
)
assert resp2.status_code == 401
async def test_member_logout_revokes_refresh_token_and_logs_audit(client: AsyncClient, db_session: AsyncSession):
tokens = await _do_full_login(client, db_session, "logout@example.com")
logout_resp = await client.post(
"/api/v1/members/auth/logout",
headers={"Authorization": f"Bearer {tokens['access_token']}"},
json={"refresh_token": tokens["refresh_token"]},
)
assert logout_resp.status_code == 204
refresh_resp = await client.post(
"/api/v1/members/auth/refresh",
json={"refresh_token": tokens["refresh_token"]},
)
assert refresh_resp.status_code == 401
audit_result = await db_session.execute(
select(AuditLog)
.where(AuditLog.member_email == "logout@example.com", AuditLog.action_type == "logout")
.order_by(AuditLog.timestamp.desc())
)
audit_entry = audit_result.scalars().first()
assert audit_entry is not None
assert audit_entry.area == "members/logout"
async def test_member_logout_does_not_log_audit_when_audit_history_disabled(
client: AsyncClient,
db_session: AsyncSession,
):
db_session.add(SiteSettings(site_name="", audit_history_enabled=False))
await db_session.commit()
tokens = await _do_full_login(client, db_session, "noauditlogout@example.com")
logout_resp = await client.post(
"/api/v1/members/auth/logout",
headers={"Authorization": f"Bearer {tokens['access_token']}"},
json={"refresh_token": tokens["refresh_token"]},
)
assert logout_resp.status_code == 204
audit_result = await db_session.execute(
select(AuditLog)
.where(AuditLog.member_email == "noauditlogout@example.com", AuditLog.action_type == "logout")
.order_by(AuditLog.timestamp.desc())
)
assert audit_result.scalars().first() is None
# ── Member-authenticated endpoints ─────────────────────────────────────────────
async def test_get_profile(client: AsyncClient, db_session: AsyncSession):
tokens = await _do_full_login(client, db_session, "profile@example.com")
resp = await client.get(
"/api/v1/members/me",
headers={"Authorization": f"Bearer {tokens['access_token']}"},
)
assert resp.status_code == 200
data = resp.json()
assert data["email"] == "profile@example.com"
assert data["first_name"] == "Jane"
assert data["member_status"] == "active"
async def test_update_profile(client: AsyncClient, db_session: AsyncSession):
tokens = await _do_full_login(client, db_session, "updateme@example.com")
resp = await client.put(
"/api/v1/members/me",
headers={"Authorization": f"Bearer {tokens['access_token']}"},
json={"first_name": "Updated", "phone": "021 999 9999"},
)
assert resp.status_code == 200
assert resp.json()["first_name"] == "Updated"
assert resp.json()["phone"] == "021 999 9999"
async def test_get_walks_empty(client: AsyncClient, db_session: AsyncSession):
tokens = await _do_full_login(client, db_session, "nowalks@example.com")
resp = await client.get(
"/api/v1/members/walks",
headers={"Authorization": f"Bearer {tokens['access_token']}"},
)
assert resp.status_code == 200
assert resp.json() == []
async def test_get_contract(client: AsyncClient, db_session: AsyncSession):
tokens = await _do_full_login(client, db_session, "contract@example.com")
resp = await client.get(
"/api/v1/members/contract",
headers={"Authorization": f"Bearer {tokens['access_token']}"},
)
assert resp.status_code == 200
data = resp.json()
assert data["email"] == "contract@example.com"
assert data["onboarding_data"]["dog_name"] == "Buddy"
async def test_create_and_get_booking(client: AsyncClient, db_session: AsyncSession):
tokens = await _do_full_login(client, db_session, "booking@example.com")
auth = {"Authorization": f"Bearer {tokens['access_token']}"}
resp = await client.post(
"/api/v1/members/bookings",
headers=auth,
json={"service_type": "pack_walk", "notes": "Morning preferred"},
)
assert resp.status_code == 201
assert resp.json()["status"] == "pending"
resp2 = await client.get("/api/v1/members/bookings", headers=auth)
assert resp2.status_code == 200
assert len(resp2.json()) == 1
async def test_bookings_feature_can_be_disabled(client: AsyncClient, db_session: AsyncSession):
tokens = await _do_full_login(client, db_session, "bookingflag@example.com")
auth = {"Authorization": f"Bearer {tokens['access_token']}"}
db_session.add(SiteSettings(site_name="", bookings_enabled=False))
await db_session.commit()
get_resp = await client.get("/api/v1/members/bookings", headers=auth)
assert get_resp.status_code == 404
post_resp = await client.post(
"/api/v1/members/bookings",
headers=auth,
json={"service_type": "pack_walk"},
)
assert post_resp.status_code == 404
async def test_messages_and_mark_read(client: AsyncClient, db_session: AsyncSession, admin_token: str):
tokens = await _do_full_login(client, db_session, "messages@example.com")
auth = {"Authorization": f"Bearer {tokens['access_token']}"}
admin_auth = {"Authorization": f"Bearer {admin_token}"}
# Find member id
members_resp = await client.get("/api/v1/admin/members", headers=admin_auth)
member_id = next(m["id"] for m in members_resp.json() if m["email"] == "messages@example.com")
# Admin sends a message
send_resp = await client.post(
"/api/v1/admin/messages",
headers=admin_auth,
json={"member_id": member_id, "subject": "Hello!", "body": "Welcome to Goodwalk."},
)
assert send_resp.status_code == 201
# Member reads messages
msgs_resp = await client.get("/api/v1/members/messages", headers=auth)
assert msgs_resp.status_code == 200
messages = msgs_resp.json()
assert len(messages) == 1
assert messages[0]["read_at"] is None
# Mark as read
msg_id = messages[0]["id"]
read_resp = await client.put(f"/api/v1/members/messages/{msg_id}/read", headers=auth)
assert read_resp.status_code == 200
# Confirm read_at is now set
msgs_resp2 = await client.get("/api/v1/members/messages", headers=auth)
assert msgs_resp2.json()[0]["read_at"] is not None
async def test_messages_feature_can_be_disabled(client: AsyncClient, db_session: AsyncSession, admin_token: str):
tokens = await _do_full_login(client, db_session, "messageflag@example.com")
auth = {"Authorization": f"Bearer {tokens['access_token']}"}
admin_auth = {"Authorization": f"Bearer {admin_token}"}
members_resp = await client.get("/api/v1/admin/members", headers=admin_auth)
member_id = next(m["id"] for m in members_resp.json() if m["email"] == "messageflag@example.com")
db_session.add(SiteSettings(site_name="", messages_enabled=False))
await db_session.commit()
admin_send_resp = await client.post(
"/api/v1/admin/messages",
headers=admin_auth,
json={"member_id": member_id, "subject": "Hello!", "body": "Welcome to Goodwalk."},
)
assert admin_send_resp.status_code == 404
member_messages_resp = await client.get("/api/v1/members/messages", headers=auth)
assert member_messages_resp.status_code == 404
async def test_admin_record_walk(client: AsyncClient, db_session: AsyncSession, admin_token: str):
tokens = await _do_full_login(client, db_session, "walktest@example.com")
admin_auth = {"Authorization": f"Bearer {admin_token}"}
members_resp = await client.get("/api/v1/admin/members", headers=admin_auth)
member_id = next(m["id"] for m in members_resp.json() if m["email"] == "walktest@example.com")
walk_resp = await client.post(
"/api/v1/admin/walks",
headers=admin_auth,
json={
"member_id": member_id,
"walked_at": "2026-03-31T09:00:00+00:00",
"service_type": "pack_walk",
"duration_minutes": 60,
"notes": "Great session",
},
)
assert walk_resp.status_code == 201
# Member can see their walk
member_auth = {"Authorization": f"Bearer {tokens['access_token']}"}
walks_resp = await client.get("/api/v1/members/walks", headers=member_auth)
assert walks_resp.status_code == 200
assert len(walks_resp.json()) == 1
assert walks_resp.json()[0]["notes"] == "Great session"
async def test_walks_feature_can_be_disabled(client: AsyncClient, db_session: AsyncSession, admin_token: str):
tokens = await _do_full_login(client, db_session, "walkflag@example.com")
admin_auth = {"Authorization": f"Bearer {admin_token}"}
member_auth = {"Authorization": f"Bearer {tokens['access_token']}"}
members_resp = await client.get("/api/v1/admin/members", headers=admin_auth)
member_id = next(m["id"] for m in members_resp.json() if m["email"] == "walkflag@example.com")
db_session.add(SiteSettings(site_name="", walks_enabled=False))
await db_session.commit()
member_walks_resp = await client.get("/api/v1/members/walks", headers=member_auth)
assert member_walks_resp.status_code == 404
admin_walk_resp = await client.post(
"/api/v1/admin/walks",
headers=admin_auth,
json={
"member_id": member_id,
"walked_at": "2026-03-31T09:00:00+00:00",
"service_type": "pack_walk",
"duration_minutes": 60,
},
)
assert admin_walk_resp.status_code == 404
async def test_member_token_rejected_on_admin_endpoint(client: AsyncClient, db_session: AsyncSession):
"""Member JWT must not grant access to admin-only endpoints."""
tokens = await _do_full_login(client, db_session, "notadmin@example.com")
resp = await client.get(
"/api/v1/admin/members",
headers={"Authorization": f"Bearer {tokens['access_token']}"},
)
assert resp.status_code == 401
async def test_protected_endpoint_requires_auth(client: AsyncClient):
resp = await client.get("/api/v1/members/me")
assert resp.status_code in (401, 403) # HTTPBearer raises 403; guard raises 401
async def test_onboarding_flow_requires_activation(client: AsyncClient, db_session: AsyncSession, admin_token: str):
import hashlib
member = await _create_member(db_session, "onboarding@example.com", claimed=False)
await client.post("/api/v1/members/claim/request", json={"email": "onboarding@example.com"})
known_claim_code = "AABBCC"
await db_session.execute(
MemberVerificationCode.__table__.update()
.where(
MemberVerificationCode.member_id == member.id,
MemberVerificationCode.purpose == "claim",
)
.values(code_hash=hashlib.sha256(known_claim_code.encode()).hexdigest())
)
await db_session.commit()
claim_resp = await client.post(
"/api/v1/members/claim/complete",
json={"email": "onboarding@example.com", "code": known_claim_code, "password": "NewPass99!"},
)
assert claim_resp.status_code == 200
login_resp = await client.post(
"/api/v1/members/auth/login",
json={"email": "onboarding@example.com", "password": "NewPass99!"},
)
assert login_resp.status_code == 200
known_login_code = "112233"
await db_session.execute(
MemberVerificationCode.__table__.update()
.where(
MemberVerificationCode.member_id == member.id,
MemberVerificationCode.purpose == "login_2fa",
)
.values(code_hash=hashlib.sha256(known_login_code.encode()).hexdigest())
)
await db_session.commit()
verify_resp = await client.post(
"/api/v1/members/auth/login/verify",
json={"email": "onboarding@example.com", "code": known_login_code},
)
assert verify_resp.status_code == 200
auth = {"Authorization": f"Bearer {verify_resp.json()['access_token']}"}
onboarding_resp = await client.get("/api/v1/members/onboarding", headers=auth)
assert onboarding_resp.status_code == 200
assert onboarding_resp.json()["member_status"] == "onboarding"
update_resp = await client.put(
"/api/v1/members/onboarding",
headers=auth,
json={
"phone": "021 111 1111",
"onboarding_data": {"dog_name": "Buddy", "dog_breed": "Shih Tzu"},
"complete_onboarding": True,
},
)
assert update_resp.status_code == 200
assert update_resp.json()["member_status"] == "pending_contract"
protected_resp = await client.get("/api/v1/members/me", headers=auth)
assert protected_resp.status_code == 403
sign_resp = await client.post(
"/api/v1/members/onboarding/contract",
headers=auth,
json={"signer_name": "Jane Doe", "agreed": True},
)
assert sign_resp.status_code == 200
assert sign_resp.json()["member_status"] == "pending_review"
activate_resp = await client.post(
f"/api/v1/admin/members/{member.id}/activate",
headers={"Authorization": f"Bearer {admin_token}"},
)
assert activate_resp.status_code == 200
assert activate_resp.json()["member_status"] == "active"
active_profile_resp = await client.get("/api/v1/members/me", headers=auth)
assert active_profile_resp.status_code == 200