Files
replypier/backend/tests/api/test_user_groups.py
T
WangShen007 164f5f9615 test: 补充权限、消息与会话服务回归用例
会话读取、接管、重分配与 Socket 订阅统一校验角色、组织与可见性。
2026-10-08 22:35:00 +08:00

260 lines
8.6 KiB
Python

"""
Copyright 2024-2026 ReplyPier
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
"""
import pytest
from fastapi.testclient import TestClient
from sqlalchemy.orm import Session
from app.database import Base, get_db
from fastapi import FastAPI
from app.models.user import User, UserGroup
from app.models.role import Role
from app.models.permission import Permission
from uuid import UUID, uuid4
from app.api import user_groups as user_groups_router
from app.core.auth import get_current_user, require_permissions
from app.main import app
from app.core.config import settings
from app.core.security import get_password_hash
from tests.conftest import engine, TestingSessionLocal, create_tables, test_organization
# Create a test FastAPI app
app = FastAPI()
app.include_router(
user_groups_router.router,
prefix=f"{settings.API_V1_STR}/groups",
tags=["groups"]
)
@pytest.fixture(scope="function")
def db():
"""Create a fresh database for each test."""
# Drop all tables first
Base.metadata.drop_all(bind=engine)
# Create tables except enterprise ones
create_tables()
db = TestingSessionLocal()
try:
yield db
finally:
db.close()
Base.metadata.drop_all(bind=engine)
@pytest.fixture
def test_role(db: Session, test_organization) -> Role:
"""Create a test role with required permissions"""
role = Role(
name="Test Role",
organization_id=test_organization.id
)
db.add(role)
db.commit()
# Add required permissions
permission = Permission(
name="manage_users",
description="Can manage users and groups"
)
db.add(permission)
db.commit()
# Associate permission with role
role.permissions.append(permission)
db.commit()
return role
@pytest.fixture
def test_user(db: Session, test_organization, test_role: Role) -> User:
"""Create a test user with required permissions"""
user = User(
id=uuid4(),
email="test@test.com",
hashed_password=get_password_hash("testpassword"),
organization_id=test_organization.id,
role_id=test_role.id,
is_active=True
)
db.add(user)
db.commit()
return user
@pytest.fixture
def test_group(db: Session, test_organization) -> UserGroup:
"""Create a test user group"""
group = UserGroup(
name="Test Group",
description="Test group description",
organization_id=test_organization.id
)
db.add(group)
db.commit()
return group
@pytest.fixture
def client(test_user: User) -> TestClient:
"""Create test client with mocked dependencies"""
async def override_get_current_user():
return test_user
def override_get_db():
try:
db = TestingSessionLocal()
yield db
finally:
db.close()
app.dependency_overrides[get_current_user] = override_get_current_user
app.dependency_overrides[get_db] = override_get_db
client = TestClient(app)
yield client
app.dependency_overrides.clear()
def test_list_groups(client: TestClient, test_group: UserGroup):
"""Test listing all groups in the organization"""
response = client.get("/api/v1/groups")
assert response.status_code == 200
data = response.json()
assert isinstance(data, list)
assert len(data) == 1
assert data[0]["name"] == test_group.name
assert data[0]["description"] == test_group.description
def test_create_group(client: TestClient, test_organization_id: UUID):
"""Test creating a new group"""
group_data = {
"name": "New Test Group",
"description": "New test group description"
}
response = client.post("/api/v1/groups", json=group_data)
assert response.status_code == 200
data = response.json()
assert data["name"] == group_data["name"]
assert data["description"] == group_data["description"]
assert UUID(data["organization_id"]) == test_organization_id
def test_get_group(client: TestClient, test_group: UserGroup):
"""Test getting a specific group"""
response = client.get(f"/api/v1/groups/{test_group.id}")
assert response.status_code == 200
data = response.json()
assert data["name"] == test_group.name
assert data["description"] == test_group.description
def test_update_group(client: TestClient, test_group: UserGroup):
"""Test updating a group"""
update_data = {
"name": "Updated Group Name",
"description": "Updated group description"
}
response = client.put(f"/api/v1/groups/{test_group.id}", json=update_data)
assert response.status_code == 200
data = response.json()
assert data["name"] == update_data["name"]
assert data["description"] == update_data["description"]
def test_delete_group(client: TestClient, test_group: UserGroup):
"""Test deleting a group"""
response = client.delete(f"/api/v1/groups/{test_group.id}")
assert response.status_code == 204
def test_add_user_to_group(client: TestClient, test_group: UserGroup, test_user: User):
"""Test adding a user to a group"""
response = client.post(f"/api/v1/groups/{test_group.id}/users/{test_user.id}")
assert response.status_code == 200
data = response.json()
assert data["message"] == "User added to group"
def test_remove_user_from_group(client: TestClient, test_group: UserGroup, test_user: User, db: Session):
"""Test removing a user from a group"""
# First add the user to the group
test_group.users.append(test_user)
db.commit()
response = client.delete(f"/api/v1/groups/{test_group.id}/users/{test_user.id}")
assert response.status_code == 200
data = response.json()
assert data["message"] == "User removed from group"
def test_get_nonexistent_group(client: TestClient):
"""Test getting a nonexistent group"""
nonexistent_id = uuid4()
response = client.get(f"/api/v1/groups/{nonexistent_id}")
assert response.status_code == 404
def test_update_nonexistent_group(client: TestClient):
"""Test updating a nonexistent group"""
nonexistent_id = uuid4()
update_data = {
"name": "Updated Name",
"description": "Updated description"
}
response = client.put(f"/api/v1/groups/{nonexistent_id}", json=update_data)
assert response.status_code == 404
def test_delete_nonexistent_group(client: TestClient):
"""Test deleting a nonexistent group"""
nonexistent_id = uuid4()
response = client.delete(f"/api/v1/groups/{nonexistent_id}")
assert response.status_code == 404
def test_add_user_to_nonexistent_group(client: TestClient, test_user: User):
"""Test adding a user to a nonexistent group"""
nonexistent_id = uuid4()
response = client.post(f"/api/v1/groups/{nonexistent_id}/users/{test_user.id}")
assert response.status_code == 404
def test_remove_user_from_nonexistent_group(client: TestClient, test_user: User):
"""Test removing a user from a nonexistent group"""
nonexistent_id = uuid4()
response = client.delete(f"/api/v1/groups/{nonexistent_id}/users/{test_user.id}")
assert response.status_code == 404
@pytest.fixture
def foreign_user(db: Session) -> User:
"""A user in a different organization from the caller."""
from app.models.organization import Organization
other_org = Organization(id=uuid4(), name="Other Org", domain="other.example.com")
db.add(other_org)
db.commit()
user = User(
id=uuid4(),
email="foreign@other.example.com",
hashed_password=get_password_hash("x"),
organization_id=other_org.id,
is_active=True,
)
db.add(user)
db.commit()
return user
def test_cannot_add_user_from_another_org(client, test_group, foreign_user, db):
"""A group can't gain a member from another tenant"""
response = client.post(f"/api/v1/groups/{test_group.id}/users/{foreign_user.id}")
assert response.status_code == 400
db.refresh(test_group)
assert foreign_user not in test_group.users
def test_cannot_remove_user_from_another_org(client, test_group, foreign_user, db):
"""A foreign user id can't be used to poke at group membership"""
response = client.delete(f"/api/v1/groups/{test_group.id}/users/{foreign_user.id}")
assert response.status_code == 400