feat: add admin and user session listing endpoints with enriched device/network details
This commit is contained in:
@@ -3,11 +3,13 @@
|
||||
Covers network CRUD, device registration, access requests, approvals,
|
||||
and membership activation. External ZeroTier API calls are mocked.
|
||||
"""
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
import pytest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
from tests.integration.client.base import ApiError
|
||||
from gatehouse_app.utils.constants import OrganizationRole
|
||||
from gatehouse_app.utils.constants import OrganizationRole, ActivationEndReason
|
||||
|
||||
|
||||
def assert_success(response: dict, message_contains: str = "") -> dict:
|
||||
@@ -1080,3 +1082,219 @@ class TestAdminForceDeleteMembership:
|
||||
f"/organizations/{org['id']}/admin/memberships/{uuid.uuid4()}",
|
||||
)
|
||||
assert exc_info.value.status_code == 403
|
||||
|
||||
|
||||
class TestSessions:
|
||||
"""Test user and admin session listing endpoints."""
|
||||
|
||||
def test_list_sessions_positive(
|
||||
self, integration_client, create_test_user, create_test_org,
|
||||
create_test_membership, integration_app,
|
||||
):
|
||||
"""ZT-SESS-01: User lists their own active sessions.
|
||||
|
||||
WHAT: GET /organizations/<id>/sessions for a user with an active session.
|
||||
EXPECTED: 200 OK with session containing device, network, and timing fields.
|
||||
"""
|
||||
from gatehouse_app.models.zerotier.device import Device
|
||||
from gatehouse_app.models.zerotier.portal_network import PortalNetwork
|
||||
from gatehouse_app.models.zerotier.network_access_request import NetworkAccessRequest
|
||||
from gatehouse_app.models.zerotier.activation_session import ActivationSession
|
||||
from gatehouse_app.extensions import db as _db
|
||||
from gatehouse_app.utils.constants import ApprovalState, ApprovalGrantType, NetworkRequestMode
|
||||
|
||||
user = create_test_user(password="Pass1234!")
|
||||
org = create_test_org()
|
||||
create_test_membership(user["id"], org["id"], OrganizationRole.MEMBER)
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
with integration_app.app_context():
|
||||
network = PortalNetwork(
|
||||
organization_id=org["id"], name="Test Net",
|
||||
owner_user_id=user["id"],
|
||||
zerotier_network_id="aabbccddee11",
|
||||
request_mode=NetworkRequestMode.OPEN,
|
||||
)
|
||||
_db.session.add(network)
|
||||
_db.session.flush()
|
||||
|
||||
device = Device(
|
||||
user_id=user["id"], organization_id=org["id"],
|
||||
node_id="deadbeef01", device_nickname="My Laptop",
|
||||
hostname="my-laptop",
|
||||
)
|
||||
_db.session.add(device)
|
||||
_db.session.flush()
|
||||
|
||||
req = NetworkAccessRequest(
|
||||
organization_id=org["id"], user_id=user["id"],
|
||||
device_id=device.id, portal_network_id=network.id,
|
||||
status=ApprovalState.APPROVED, active=True,
|
||||
grant_type=ApprovalGrantType.REQUESTED,
|
||||
)
|
||||
_db.session.add(req)
|
||||
_db.session.flush()
|
||||
|
||||
session = ActivationSession(
|
||||
organization_id=org["id"], user_id=user["id"],
|
||||
network_access_request_id=req.id,
|
||||
authenticated_at=now - timedelta(hours=1),
|
||||
expires_at=now + timedelta(hours=7),
|
||||
created_by=user["id"],
|
||||
)
|
||||
_db.session.add(session)
|
||||
_db.session.commit()
|
||||
|
||||
saved_session_id = session.id
|
||||
|
||||
integration_client.auth.login(email=user["email"], password="Pass1234!")
|
||||
result = integration_client.get(f"/organizations/{org['id']}/sessions")
|
||||
data = result.get("data", {})
|
||||
|
||||
assert data["count"] == 1
|
||||
s = data["sessions"][0]
|
||||
assert s["id"] == saved_session_id
|
||||
assert s["is_active"] is True
|
||||
assert s["is_expired"] is False
|
||||
assert s["duration_seconds"] == 28800
|
||||
assert s["remaining_seconds"] > 0
|
||||
|
||||
assert s["device"]["node_id"] == "deadbeef01"
|
||||
assert s["device"]["name"] == "My Laptop"
|
||||
|
||||
assert s["network"]["name"] == "Test Net"
|
||||
|
||||
assert "user" not in s
|
||||
|
||||
def test_list_sessions_empty(
|
||||
self, integration_client, create_test_user, create_test_org,
|
||||
create_test_membership,
|
||||
):
|
||||
"""ZT-SESS-02: User with no sessions gets empty array."""
|
||||
user = create_test_user(password="Pass1234!")
|
||||
org = create_test_org()
|
||||
create_test_membership(user["id"], org["id"], OrganizationRole.MEMBER)
|
||||
|
||||
integration_client.auth.login(email=user["email"], password="Pass1234!")
|
||||
result = integration_client.get(f"/organizations/{org['id']}/sessions")
|
||||
data = result.get("data", {})
|
||||
assert data["count"] == 0
|
||||
assert data["sessions"] == []
|
||||
|
||||
def test_list_sessions_unauth_negative(
|
||||
self, integration_client, create_test_org,
|
||||
):
|
||||
"""ZT-SESS-03: Unauthenticated user gets 401."""
|
||||
org = create_test_org()
|
||||
with pytest.raises(ApiError) as exc_info:
|
||||
integration_client.get(f"/organizations/{org['id']}/sessions")
|
||||
assert exc_info.value.status_code == 401
|
||||
|
||||
def test_admin_list_sessions_positive(
|
||||
self, integration_client, create_test_user, create_test_org,
|
||||
create_test_membership, integration_app,
|
||||
):
|
||||
"""ZT-SESS-04: Admin lists all sessions across users.
|
||||
|
||||
WHAT: GET /organizations/<id>/admin/sessions.
|
||||
EXPECTED: 200 OK with all users' sessions including user details.
|
||||
"""
|
||||
from gatehouse_app.models.zerotier.device import Device
|
||||
from gatehouse_app.models.zerotier.portal_network import PortalNetwork
|
||||
from gatehouse_app.models.zerotier.network_access_request import NetworkAccessRequest
|
||||
from gatehouse_app.models.zerotier.activation_session import ActivationSession
|
||||
from gatehouse_app.extensions import db as _db
|
||||
from gatehouse_app.utils.constants import ApprovalState, ApprovalGrantType, NetworkRequestMode
|
||||
|
||||
admin = create_test_user(password="AdminPass123!")
|
||||
member1 = create_test_user(password="Mem1Pass123!", full_name="Alice")
|
||||
member2 = create_test_user(password="Mem2Pass123!", full_name="Bob")
|
||||
org = create_test_org()
|
||||
|
||||
create_test_membership(admin["id"], org["id"], OrganizationRole.ADMIN)
|
||||
create_test_membership(member1["id"], org["id"], OrganizationRole.MEMBER)
|
||||
create_test_membership(member2["id"], org["id"], OrganizationRole.MEMBER)
|
||||
|
||||
now = datetime.now(timezone.utc)
|
||||
|
||||
with integration_app.app_context():
|
||||
network = PortalNetwork(
|
||||
organization_id=org["id"], name="Shared Net",
|
||||
owner_user_id=admin["id"],
|
||||
zerotier_network_id="ffgg00112233",
|
||||
request_mode=NetworkRequestMode.OPEN,
|
||||
)
|
||||
_db.session.add(network)
|
||||
_db.session.flush()
|
||||
|
||||
for member, node_id, nick, full_name in [
|
||||
(member1, "aaaa01", "Alice Mac", None),
|
||||
(member2, "bbbb02", None, "bob-pc"),
|
||||
]:
|
||||
device = Device(
|
||||
user_id=member["id"], organization_id=org["id"],
|
||||
node_id=node_id, device_nickname=nick,
|
||||
hostname="host",
|
||||
)
|
||||
_db.session.add(device)
|
||||
_db.session.flush()
|
||||
|
||||
req = NetworkAccessRequest(
|
||||
organization_id=org["id"], user_id=member["id"],
|
||||
device_id=device.id, portal_network_id=network.id,
|
||||
status=ApprovalState.APPROVED, active=True,
|
||||
grant_type=ApprovalGrantType.REQUESTED,
|
||||
)
|
||||
_db.session.add(req)
|
||||
_db.session.flush()
|
||||
|
||||
session = ActivationSession(
|
||||
organization_id=org["id"], user_id=member["id"],
|
||||
network_access_request_id=req.id,
|
||||
authenticated_at=now - timedelta(hours=2),
|
||||
expires_at=now + timedelta(hours=6),
|
||||
created_by=member["id"],
|
||||
)
|
||||
_db.session.add(session)
|
||||
_db.session.commit()
|
||||
|
||||
integration_client.auth.login(email=admin["email"], password="AdminPass123!")
|
||||
result = integration_client.get(f"/organizations/{org['id']}/admin/sessions")
|
||||
data = result.get("data", {})
|
||||
|
||||
assert data["count"] == 2
|
||||
|
||||
user_names = {s["user"]["full_name"] for s in data["sessions"]}
|
||||
assert "Alice" in user_names
|
||||
assert "Bob" in user_names
|
||||
|
||||
for s in data["sessions"]:
|
||||
assert s["is_active"] is True
|
||||
assert "device" in s
|
||||
assert "node_id" in s["device"]
|
||||
assert "network" in s
|
||||
assert s["network"]["name"] == "Shared Net"
|
||||
|
||||
def test_admin_list_sessions_non_admin_negative(
|
||||
self, integration_client, create_test_user, create_test_org,
|
||||
create_test_membership,
|
||||
):
|
||||
"""ZT-SESS-05: Non-admin gets 403 on admin sessions endpoint."""
|
||||
member = create_test_user(password="Pass1234!")
|
||||
org = create_test_org()
|
||||
create_test_membership(member["id"], org["id"], OrganizationRole.MEMBER)
|
||||
|
||||
integration_client.auth.login(email=member["email"], password="Pass1234!")
|
||||
with pytest.raises(ApiError) as exc_info:
|
||||
integration_client.get(f"/organizations/{org['id']}/admin/sessions")
|
||||
assert exc_info.value.status_code == 403
|
||||
|
||||
def test_admin_list_sessions_unauth_negative(
|
||||
self, integration_client, create_test_org,
|
||||
):
|
||||
"""ZT-SESS-06: Unauthenticated user gets 401 on admin endpoint."""
|
||||
org = create_test_org()
|
||||
with pytest.raises(ApiError) as exc_info:
|
||||
integration_client.get(f"/organizations/{org['id']}/admin/sessions")
|
||||
assert exc_info.value.status_code == 401
|
||||
|
||||
Reference in New Issue
Block a user