636 lines
24 KiB
Python
636 lines
24 KiB
Python
# #region Test.Api.Admin [C:3] [TYPE Module] [SEMANTICS test,admin,api]
|
|
# @BRIEF Unit tests for admin API routes — users, roles, permissions, AD mappings.
|
|
# @RELATION BINDS_TO -> [AdminApi]
|
|
# @TEST_EDGE: user_not_found -> 404
|
|
# @TEST_EDGE: username_exists -> 400
|
|
# @TEST_EDGE: role_not_found -> 404
|
|
# @TEST_EDGE: role_already_exists -> 400
|
|
|
|
import os
|
|
|
|
# Set env BEFORE any source imports
|
|
os.environ.setdefault("DATABASE_URL", "sqlite:///:memory:")
|
|
os.environ.setdefault("AUTH_DATABASE_URL", "sqlite:///:memory:")
|
|
os.environ.setdefault("SECRET_KEY", "test-secret-key-for-tests")
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
from fastapi import FastAPI, HTTPException
|
|
from fastapi.testclient import TestClient
|
|
|
|
_src = str(Path(__file__).resolve().parent.parent.parent / "src")
|
|
if _src not in sys.path:
|
|
sys.path.insert(0, _src)
|
|
|
|
|
|
def _make_client(overrides: dict | None = None) -> TestClient:
|
|
"""Build TestClient with admin router and all deps overridden."""
|
|
from src.api.routes.admin import router
|
|
from src.core.database import get_auth_db
|
|
from src.dependencies import get_current_user, has_permission
|
|
from src.schemas.auth import User, RoleSchema
|
|
|
|
app = FastAPI()
|
|
app.include_router(router)
|
|
|
|
mock_db = MagicMock()
|
|
mock_user = User(
|
|
id="admin-1",
|
|
username="admin",
|
|
email="admin@example.com",
|
|
auth_source="LOCAL",
|
|
is_active=True,
|
|
created_at=__import__("datetime").datetime.now(),
|
|
roles=[RoleSchema(id="role-1", name="Admin", description="Admin", permissions=[])],
|
|
)
|
|
|
|
defaults = {
|
|
get_auth_db: lambda: mock_db,
|
|
get_current_user: lambda: mock_user,
|
|
has_permission: lambda *args, **kwargs: lambda: None,
|
|
}
|
|
if overrides:
|
|
defaults.update(overrides)
|
|
for dep, mock_fn in defaults.items():
|
|
app.dependency_overrides[dep] = mock_fn
|
|
return TestClient(app)
|
|
|
|
|
|
# ── Users ──
|
|
|
|
class TestListUsers:
|
|
"""GET /api/admin/users"""
|
|
|
|
def test_list_users_success(self):
|
|
"""Happy path: list all users returns 200."""
|
|
from src.core.database import get_auth_db
|
|
|
|
mock_user = MagicMock()
|
|
mock_user.id = "user-1"
|
|
mock_user.username = "alice"
|
|
mock_user.email = "alice@example.com"
|
|
mock_user.is_active = True
|
|
mock_user.auth_source = "LOCAL"
|
|
mock_user.created_at = __import__("datetime").datetime.now()
|
|
mock_user.last_login = None
|
|
mock_user.roles = []
|
|
|
|
mock_session = MagicMock()
|
|
mock_session.query.return_value.all.return_value = [mock_user]
|
|
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.get("/api/admin/users")
|
|
assert resp.status_code == 200
|
|
data = resp.json()
|
|
assert isinstance(data, list)
|
|
assert data[0]["username"] == "alice"
|
|
|
|
|
|
class TestCreateUser:
|
|
"""POST /api/admin/users"""
|
|
|
|
def test_create_user_success(self):
|
|
"""Happy path: user created with 201."""
|
|
from datetime import datetime
|
|
from src.core.database import get_auth_db
|
|
|
|
mock_session = MagicMock()
|
|
mock_repo = MagicMock()
|
|
mock_repo.get_user_by_username.return_value = None
|
|
|
|
# Build a proper role mock with string attributes, not MagicMock(name=...)
|
|
def _get_role_by_name(name):
|
|
if name != "Admin":
|
|
return None
|
|
role = MagicMock()
|
|
role.id = f"role-{name}"
|
|
role.name = name
|
|
role.description = "Administrator role"
|
|
role.permissions = []
|
|
return role
|
|
|
|
mock_repo.get_role_by_name.side_effect = _get_role_by_name
|
|
|
|
# Make mock_session.refresh set fields the response schema needs
|
|
def _refresh_side_effect(obj):
|
|
obj.id = "new-user-id"
|
|
obj.created_at = datetime.now()
|
|
|
|
mock_session.refresh.side_effect = _refresh_side_effect
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo):
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.post("/api/admin/users", json={
|
|
"username": "newuser",
|
|
"email": "new@example.com",
|
|
"password": "StrongPass1",
|
|
"is_active": True,
|
|
"roles": ["Admin"],
|
|
})
|
|
assert resp.status_code == 201
|
|
mock_session.add.assert_called_once()
|
|
mock_session.commit.assert_called_once()
|
|
|
|
def test_create_user_duplicate_username(self):
|
|
"""Username already exists returns 400."""
|
|
from src.core.database import get_auth_db
|
|
mock_session = MagicMock()
|
|
mock_repo = MagicMock()
|
|
mock_repo.get_user_by_username.return_value = MagicMock()
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo):
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.post("/api/admin/users", json={
|
|
"username": "exists",
|
|
"email": "dup@example.com",
|
|
"password": "StrongPass1",
|
|
})
|
|
assert resp.status_code == 400
|
|
assert "Username already exists" in resp.text
|
|
|
|
def test_create_user_weak_password(self):
|
|
"""Weak password returns 422."""
|
|
client = _make_client()
|
|
resp = client.post("/api/admin/users", json={
|
|
"username": "weakuser",
|
|
"email": "weak@example.com",
|
|
"password": "short",
|
|
})
|
|
assert resp.status_code == 422
|
|
|
|
def test_create_user_no_permission(self):
|
|
"""User without write permission gets 403."""
|
|
from src.core.database import get_auth_db
|
|
from src.dependencies import get_current_user, has_permission
|
|
from src.schemas.auth import User, RoleSchema
|
|
|
|
app = FastAPI()
|
|
from src.api.routes.admin import router
|
|
app.include_router(router)
|
|
app.dependency_overrides[get_auth_db] = lambda: MagicMock()
|
|
app.dependency_overrides[get_current_user] = lambda: User(
|
|
id="user-1", username="regular", email="u@x.com", auth_source="LOCAL",
|
|
created_at=__import__("datetime").datetime.now(), roles=[]
|
|
)
|
|
# Keep has_permission as real — it will use the mock get_current_user
|
|
client = TestClient(app)
|
|
resp = client.post("/api/admin/users", json={
|
|
"username": "test",
|
|
"email": "test@example.com",
|
|
"password": "StrongPass1",
|
|
})
|
|
assert resp.status_code == 403
|
|
|
|
|
|
class TestUpdateUser:
|
|
"""PUT /api/admin/users/{user_id}"""
|
|
|
|
def test_update_user_success(self):
|
|
"""Happy path: user updated and returned."""
|
|
from src.core.database import get_auth_db
|
|
mock_session = MagicMock()
|
|
mock_repo = MagicMock()
|
|
existing_user = MagicMock()
|
|
existing_user.id = "user-1"
|
|
existing_user.username = "oldname"
|
|
existing_user.email = "old@example.com"
|
|
existing_user.is_active = True
|
|
existing_user.auth_source = "LOCAL"
|
|
existing_user.created_at = __import__("datetime").datetime.now()
|
|
existing_user.roles = []
|
|
mock_repo.get_user_by_id.return_value = existing_user
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo):
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.put("/api/admin/users/user-1", json={"email": "new@example.com"})
|
|
assert resp.status_code == 200
|
|
assert existing_user.email == "new@example.com"
|
|
mock_session.commit.assert_called_once()
|
|
|
|
def test_update_user_not_found(self):
|
|
"""Non-existent user returns 404."""
|
|
from src.core.database import get_auth_db
|
|
mock_session = MagicMock()
|
|
mock_repo = MagicMock()
|
|
mock_repo.get_user_by_id.return_value = None
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo):
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.put("/api/admin/users/user-999", json={"email": "x@y.com"})
|
|
assert resp.status_code == 404
|
|
|
|
def test_update_user_password_and_active(self):
|
|
"""Update with password and is_active covers lines 125, 127."""
|
|
from src.core.database import get_auth_db
|
|
mock_session = MagicMock()
|
|
mock_repo = MagicMock()
|
|
existing_user = MagicMock()
|
|
existing_user.id = "user-1"
|
|
existing_user.username = "testuser"
|
|
existing_user.email = "old@example.com"
|
|
existing_user.is_active = False
|
|
existing_user.auth_source = "LOCAL"
|
|
existing_user.created_at = __import__("datetime").datetime.now()
|
|
existing_user.roles = []
|
|
mock_repo.get_user_by_id.return_value = existing_user
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo):
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.put("/api/admin/users/user-1", json={
|
|
"password": "NewStrongPass1",
|
|
"is_active": False,
|
|
})
|
|
assert resp.status_code == 200
|
|
assert existing_user.is_active is False
|
|
mock_session.commit.assert_called_once()
|
|
|
|
def test_update_user_with_roles(self):
|
|
"""Update with roles list covers lines 130-134."""
|
|
from src.core.database import get_auth_db
|
|
mock_session = MagicMock()
|
|
mock_repo = MagicMock()
|
|
existing_user = MagicMock()
|
|
existing_user.id = "user-1"
|
|
existing_user.username = "testuser"
|
|
existing_user.email = "old@example.com"
|
|
existing_user.is_active = True
|
|
existing_user.auth_source = "LOCAL"
|
|
existing_user.created_at = __import__("datetime").datetime.now()
|
|
existing_user.roles = []
|
|
mock_repo.get_user_by_id.return_value = existing_user
|
|
|
|
def _get_role_by_name(name):
|
|
role = MagicMock()
|
|
role.id = f"role-{name}"
|
|
role.name = name
|
|
role.description = "A role"
|
|
role.permissions = []
|
|
return role
|
|
|
|
mock_repo.get_role_by_name.side_effect = _get_role_by_name
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo):
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.put("/api/admin/users/user-1", json={
|
|
"roles": ["Editor"],
|
|
})
|
|
assert resp.status_code == 200
|
|
assert len(existing_user.roles) == 1
|
|
mock_session.commit.assert_called_once()
|
|
|
|
|
|
class TestDeleteUser:
|
|
"""DELETE /api/admin/users/{user_id}"""
|
|
|
|
def test_delete_user_success(self):
|
|
"""Happy path: user deleted returns 204."""
|
|
from src.core.database import get_auth_db
|
|
mock_session = MagicMock()
|
|
mock_repo = MagicMock()
|
|
mock_repo.get_user_by_id.return_value = MagicMock(username="testuser")
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo):
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.delete("/api/admin/users/user-1")
|
|
assert resp.status_code == 204
|
|
mock_session.delete.assert_called_once()
|
|
mock_session.commit.assert_called_once()
|
|
|
|
def test_delete_user_not_found(self):
|
|
"""Non-existent user returns 404."""
|
|
from src.core.database import get_auth_db
|
|
mock_session = MagicMock()
|
|
mock_repo = MagicMock()
|
|
mock_repo.get_user_by_id.return_value = None
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo):
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.delete("/api/admin/users/user-999")
|
|
assert resp.status_code == 404
|
|
|
|
|
|
# ── Roles ──
|
|
|
|
class TestListRoles:
|
|
"""GET /api/admin/roles"""
|
|
|
|
def test_list_roles_success(self):
|
|
"""Happy path: list roles returns 200."""
|
|
mock_role = MagicMock()
|
|
mock_role.id = "role-1"
|
|
mock_role.name = "Admin"
|
|
mock_role.description = "Administrator"
|
|
mock_role.permissions = []
|
|
|
|
mock_session = MagicMock()
|
|
mock_session.query.return_value.all.return_value = [mock_role]
|
|
|
|
from src.core.database import get_auth_db
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.get("/api/admin/roles")
|
|
assert resp.status_code == 200
|
|
assert isinstance(resp.json(), list)
|
|
|
|
|
|
class TestCreateRole:
|
|
"""POST /api/admin/roles"""
|
|
|
|
def test_create_role_success(self):
|
|
"""Happy path: role created with 201."""
|
|
mock_session = MagicMock()
|
|
mock_session.query.return_value.filter.return_value.first.return_value = None
|
|
mock_repo = MagicMock()
|
|
mock_perm = MagicMock()
|
|
mock_perm.id = "perm-1"
|
|
mock_perm.resource = "users"
|
|
mock_perm.action = "read"
|
|
mock_repo.get_permission_by_id.return_value = mock_perm
|
|
|
|
def _refresh_side_effect(obj):
|
|
obj.id = "new-role-id"
|
|
|
|
mock_session.refresh.side_effect = _refresh_side_effect
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo):
|
|
from src.core.database import get_auth_db
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.post("/api/admin/roles", json={
|
|
"name": "Editor",
|
|
"description": "Can edit",
|
|
"permissions": ["perm-1"],
|
|
})
|
|
assert resp.status_code == 201
|
|
mock_session.add.assert_called_once()
|
|
mock_session.commit.assert_called_once()
|
|
|
|
def test_create_role_duplicate(self):
|
|
"""Duplicate role name returns 400."""
|
|
mock_session = MagicMock()
|
|
mock_session.query.return_value.filter.return_value.first.return_value = MagicMock()
|
|
|
|
from src.core.database import get_auth_db
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.post("/api/admin/roles", json={
|
|
"name": "Admin",
|
|
"description": "Already exists",
|
|
"permissions": [],
|
|
})
|
|
assert resp.status_code == 400
|
|
assert "Role already exists" in resp.text
|
|
|
|
def test_create_role_with_string_permission(self):
|
|
"""Create role with resource:action string permission (covers lines 220-221)."""
|
|
mock_session = MagicMock()
|
|
mock_session.query.return_value.filter.return_value.first.return_value = None
|
|
mock_repo = MagicMock()
|
|
# get_permission_by_id returns None -> falls through to split
|
|
mock_repo.get_permission_by_id.return_value = None
|
|
mock_perm = MagicMock()
|
|
mock_perm.id = "perm-resolved"
|
|
mock_perm.resource = "users"
|
|
mock_perm.action = "read"
|
|
mock_repo.get_permission_by_resource_action.return_value = mock_perm
|
|
|
|
def _refresh_side_effect(obj):
|
|
obj.id = "new-role-id"
|
|
|
|
mock_session.refresh.side_effect = _refresh_side_effect
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo):
|
|
from src.core.database import get_auth_db
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.post("/api/admin/roles", json={
|
|
"name": "Editor",
|
|
"description": "Can edit",
|
|
"permissions": ["users:read"],
|
|
})
|
|
assert resp.status_code == 201
|
|
mock_repo.get_permission_by_resource_action.assert_called_once_with("users", "read")
|
|
mock_session.add.assert_called_once()
|
|
|
|
|
|
class TestUpdateRole:
|
|
"""PUT /api/admin/roles/{role_id}"""
|
|
|
|
def test_update_role_success(self):
|
|
"""Happy path: role updated."""
|
|
mock_session = MagicMock()
|
|
mock_repo = MagicMock()
|
|
existing_role = MagicMock()
|
|
existing_role.id = "role-1"
|
|
existing_role.name = "OldName"
|
|
existing_role.description = "Old desc"
|
|
existing_role.permissions = []
|
|
mock_repo.get_role_by_id.return_value = existing_role
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo):
|
|
from src.core.database import get_auth_db
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.put("/api/admin/roles/role-1", json={"name": "NewName", "description": "New desc"})
|
|
assert resp.status_code == 200
|
|
assert existing_role.name == "NewName"
|
|
mock_session.commit.assert_called_once()
|
|
|
|
def test_update_role_not_found(self):
|
|
"""Non-existent role returns 404."""
|
|
mock_session = MagicMock()
|
|
mock_repo = MagicMock()
|
|
mock_repo.get_role_by_id.return_value = None
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo):
|
|
from src.core.database import get_auth_db
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.put("/api/admin/roles/role-999", json={"name": "Ghost"})
|
|
assert resp.status_code == 404
|
|
|
|
def test_update_role_with_permissions(self):
|
|
"""Update role with permission changes (covers lines 261-269)."""
|
|
mock_session = MagicMock()
|
|
mock_repo = MagicMock()
|
|
existing_role = MagicMock()
|
|
existing_role.id = "role-1"
|
|
existing_role.name = "OldName"
|
|
existing_role.description = "Old desc"
|
|
existing_role.permissions = []
|
|
mock_repo.get_role_by_id.return_value = existing_role
|
|
mock_perm = MagicMock()
|
|
mock_perm.id = "perm-1"
|
|
mock_perm.resource = "test"
|
|
mock_perm.action = "read"
|
|
mock_repo.get_permission_by_id.return_value = mock_perm
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo):
|
|
from src.core.database import get_auth_db
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.put("/api/admin/roles/role-1", json={
|
|
"name": "Updated",
|
|
"permissions": ["perm-1"],
|
|
})
|
|
assert resp.status_code == 200
|
|
assert existing_role.name == "Updated"
|
|
assert len(existing_role.permissions) == 1
|
|
|
|
def test_update_role_with_string_permissions(self):
|
|
"""Update role with resource:action permission strings (covers lines 261-269 fallback)."""
|
|
mock_session = MagicMock()
|
|
mock_repo = MagicMock()
|
|
existing_role = MagicMock()
|
|
existing_role.id = "role-1"
|
|
existing_role.name = "OldName"
|
|
existing_role.description = "Old desc"
|
|
existing_role.permissions = []
|
|
mock_repo.get_role_by_id.return_value = existing_role
|
|
# get_permission_by_id returns None -> falls through
|
|
mock_repo.get_permission_by_id.return_value = None
|
|
mock_perm = MagicMock()
|
|
mock_perm.id = "perm-resolved"
|
|
mock_perm.resource = "users"
|
|
mock_perm.action = "write"
|
|
mock_repo.get_permission_by_resource_action.return_value = mock_perm
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo):
|
|
from src.core.database import get_auth_db
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.put("/api/admin/roles/role-1", json={
|
|
"name": "Updated",
|
|
"permissions": ["users:write"],
|
|
})
|
|
assert resp.status_code == 200
|
|
mock_repo.get_permission_by_resource_action.assert_called_once_with("users", "write")
|
|
|
|
|
|
class TestDeleteRole:
|
|
"""DELETE /api/admin/roles/{role_id}"""
|
|
|
|
def test_delete_role_success(self):
|
|
"""Happy path: role deleted returns 204."""
|
|
mock_session = MagicMock()
|
|
mock_repo = MagicMock()
|
|
mock_repo.get_role_by_id.return_value = MagicMock()
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo):
|
|
from src.core.database import get_auth_db
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.delete("/api/admin/roles/role-1")
|
|
assert resp.status_code == 204
|
|
mock_session.delete.assert_called_once()
|
|
mock_session.commit.assert_called_once()
|
|
|
|
def test_delete_role_not_found(self):
|
|
"""Non-existent role returns 404."""
|
|
mock_session = MagicMock()
|
|
mock_repo = MagicMock()
|
|
mock_repo.get_role_by_id.return_value = None
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo):
|
|
from src.core.database import get_auth_db
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.delete("/api/admin/roles/role-999")
|
|
assert resp.status_code == 404
|
|
|
|
|
|
# ── Permissions ──
|
|
|
|
class TestListPermissions:
|
|
"""GET /api/admin/permissions"""
|
|
|
|
def test_list_permissions_success(self):
|
|
"""Happy path: returns permissions list."""
|
|
mock_session = MagicMock()
|
|
mock_repo = MagicMock()
|
|
mock_perm = MagicMock()
|
|
mock_perm.id = "perm-1"
|
|
mock_perm.resource = "users"
|
|
mock_perm.action = "read"
|
|
mock_repo.list_permissions.return_value = [mock_perm]
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo), \
|
|
patch("src.api.routes.admin.discover_declared_permissions", return_value=[]), \
|
|
patch("src.api.routes.admin.sync_permission_catalog", return_value=0):
|
|
from src.core.database import get_auth_db
|
|
from src.dependencies import get_plugin_loader
|
|
client = _make_client({
|
|
get_auth_db: lambda: mock_session,
|
|
get_plugin_loader: lambda: MagicMock(),
|
|
})
|
|
resp = client.get("/api/admin/permissions")
|
|
assert resp.status_code == 200
|
|
assert isinstance(resp.json(), list)
|
|
|
|
def test_list_permissions_with_sync(self):
|
|
"""When new permissions discovered, sync is called."""
|
|
mock_session = MagicMock()
|
|
mock_repo = MagicMock()
|
|
mock_repo.list_permissions.return_value = []
|
|
|
|
with patch("src.api.routes.admin.AuthRepository", return_value=mock_repo), \
|
|
patch("src.api.routes.admin.discover_declared_permissions", return_value=["new:perm"]), \
|
|
patch("src.api.routes.admin.sync_permission_catalog", return_value=2):
|
|
from src.core.database import get_auth_db
|
|
from src.dependencies import get_plugin_loader
|
|
client = _make_client({
|
|
get_auth_db: lambda: mock_session,
|
|
get_plugin_loader: lambda: MagicMock(),
|
|
})
|
|
resp = client.get("/api/admin/permissions")
|
|
assert resp.status_code == 200
|
|
|
|
|
|
# ── AD Mappings ──
|
|
|
|
class TestListAdMappings:
|
|
"""GET /api/admin/ad-mappings"""
|
|
|
|
def test_list_mappings_success(self):
|
|
"""Happy path: returns AD mappings."""
|
|
mock_mapping = MagicMock()
|
|
mock_mapping.id = "map-1"
|
|
mock_mapping.ad_group = "DOMAIN\\group1"
|
|
mock_mapping.role_id = "role-1"
|
|
|
|
mock_session = MagicMock()
|
|
mock_session.query.return_value.all.return_value = [mock_mapping]
|
|
|
|
from src.core.database import get_auth_db
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.get("/api/admin/ad-mappings")
|
|
assert resp.status_code == 200
|
|
assert isinstance(resp.json(), list)
|
|
|
|
|
|
class TestCreateAdMapping:
|
|
"""POST /api/admin/ad-mappings"""
|
|
|
|
def test_create_mapping_success(self):
|
|
"""Happy path: AD mapping created."""
|
|
mock_session = MagicMock()
|
|
mock_session.add = MagicMock()
|
|
|
|
def _refresh_side_effect(obj):
|
|
obj.id = "new-mapping-id"
|
|
|
|
mock_session.refresh.side_effect = _refresh_side_effect
|
|
|
|
from src.core.database import get_auth_db
|
|
client = _make_client({get_auth_db: lambda: mock_session})
|
|
resp = client.post("/api/admin/ad-mappings", json={
|
|
"ad_group": "DOMAIN\\newgroup",
|
|
"role_id": "role-1",
|
|
})
|
|
assert resp.status_code == 200
|
|
mock_session.add.assert_called_once()
|
|
mock_session.commit.assert_called_once()
|
|
|
|
def test_create_mapping_invalid_ad_group(self):
|
|
"""Invalid AD group name returns 422."""
|
|
client = _make_client()
|
|
resp = client.post("/api/admin/ad-mappings", json={
|
|
"ad_group": "invalid group with spaces!!!",
|
|
"role_id": "role-1",
|
|
})
|
|
assert resp.status_code == 422
|
|
# #endregion Test.Api.Admin
|