Первоначальная версия VidConf

This commit is contained in:
2026-07-23 01:04:01 +03:00
commit 896455381a
335 changed files with 61527 additions and 0 deletions

View File

@@ -0,0 +1,299 @@
"""Интеграционные тесты справочника команд `/api/v1/admin/teams` и привязки `users.team_id`.
Покрывает: CRUD команд, 409 на дубль имени, 403 не-админу на новые
эндпоинты, назначение/снятие команды пользователю (в т.ч. явный `null`),
404 на несуществующую команду, обнуление `team_id` при удалении команды,
разрешённое самоизменение `team_id` (в отличие от `role`/`is_blocked`).
"""
import uuid
import httpx
from sqlalchemy.ext.asyncio import AsyncSession
from core.security import create_access_token, hash_password
from models.team import Team
from models.user import User
async def _make_user(session: AsyncSession, *, role: str = "user") -> User:
user = User(
email=f"{uuid.uuid4()}@example.com",
name_user="Team API Tester",
password_hash=hash_password("password123"),
email_verified=True,
role=role,
)
session.add(user)
await session.flush()
return user
async def _make_team(session: AsyncSession, *, name: str | None = None) -> Team:
team = Team(name=name or f"Team {uuid.uuid4()}")
session.add(team)
await session.flush()
return team
def _auth_headers(user: User) -> dict[str, str]:
token = create_access_token(user.id, user.role)
return {"Authorization": f"Bearer {token}"}
# --- 403 не-админу -------------------------------------------------------------------
async def test_team_endpoints_forbidden_for_non_admin(
client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
user = await _make_user(db_session)
team = await _make_team(db_session)
await db_session.commit()
headers = _auth_headers(user)
requests = [
("GET", "/api/v1/admin/teams", None),
("POST", "/api/v1/admin/teams", {"name": "X"}),
("PATCH", f"/api/v1/admin/teams/{team.id}", {"name": "Y"}),
("DELETE", f"/api/v1/admin/teams/{team.id}", None),
]
for method, path, body in requests:
response = await client.request(method, path, json=body, headers=headers)
assert response.status_code == 403, f"{method} {path} -> {response.status_code}"
# --- CRUD команд ---------------------------------------------------------------------
async def test_create_and_list_teams_sorted_by_name(
client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
"""Список отсортирован по названию; сравниваем взаимный порядок и наличие
своих (уникальных по суффиксу) команд, а не полный список — в общей
dev-БД могут быть и другие, легитимные команды разработчика."""
admin = await _make_user(db_session, role="admin")
suffix = uuid.uuid4().hex[:8]
name_alpha, name_beta, name_zeta = f"Alpha-{suffix}", f"Beta-{suffix}", f"Zeta-{suffix}"
await _make_team(db_session, name=name_zeta)
await _make_team(db_session, name=name_alpha)
await db_session.commit()
response = await client.post(
"/api/v1/admin/teams", json={"name": name_beta}, headers=_auth_headers(admin)
)
assert response.status_code == 201, response.text
created = response.json()
assert created["name"] == name_beta
assert "id" in created and "created_at" in created
list_response = await client.get("/api/v1/admin/teams", headers=_auth_headers(admin))
assert list_response.status_code == 200
body = list_response.json()
names = [item["name"] for item in body["items"]]
assert names == sorted(names)
assert name_alpha in names and name_beta in names and name_zeta in names
assert names.index(name_alpha) < names.index(name_beta) < names.index(name_zeta)
assert body["total"] == len(body["items"])
async def test_create_team_strips_name(client: httpx.AsyncClient, db_session: AsyncSession) -> None:
admin = await _make_user(db_session, role="admin")
await db_session.commit()
response = await client.post(
"/api/v1/admin/teams", json={"name": " Padded "}, headers=_auth_headers(admin)
)
assert response.status_code == 201, response.text
assert response.json()["name"] == "Padded"
async def test_create_team_duplicate_name_returns_409(
client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
admin = await _make_user(db_session, role="admin")
name = f"Duplicate {uuid.uuid4()}"
await _make_team(db_session, name=name)
await db_session.commit()
response = await client.post(
"/api/v1/admin/teams", json={"name": name}, headers=_auth_headers(admin)
)
assert response.status_code == 409
async def test_update_team_renames(client: httpx.AsyncClient, db_session: AsyncSession) -> None:
admin = await _make_user(db_session, role="admin")
team = await _make_team(db_session, name=f"Old Name {uuid.uuid4()}")
await db_session.commit()
new_name = f"New Name {uuid.uuid4()}"
response = await client.patch(
f"/api/v1/admin/teams/{team.id}", json={"name": new_name}, headers=_auth_headers(admin)
)
assert response.status_code == 200, response.text
assert response.json()["name"] == new_name
async def test_update_team_duplicate_name_returns_409(
client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
admin = await _make_user(db_session, role="admin")
name_a = f"Team A {uuid.uuid4()}"
team_a = await _make_team(db_session, name=name_a)
team_b = await _make_team(db_session, name=f"Team B {uuid.uuid4()}")
await db_session.commit()
response = await client.patch(
f"/api/v1/admin/teams/{team_b.id}",
json={"name": name_a},
headers=_auth_headers(admin),
)
assert response.status_code == 409
assert team_a.name == name_a
async def test_update_team_unknown_returns_404(
client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
admin = await _make_user(db_session, role="admin")
await db_session.commit()
response = await client.patch(
f"/api/v1/admin/teams/{uuid.uuid4()}", json={"name": "X"}, headers=_auth_headers(admin)
)
assert response.status_code == 404
async def test_delete_team_unknown_returns_404(
client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
admin = await _make_user(db_session, role="admin")
await db_session.commit()
response = await client.delete(
f"/api/v1/admin/teams/{uuid.uuid4()}", headers=_auth_headers(admin)
)
assert response.status_code == 404
async def test_delete_team_clears_user_team_id(
client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
admin = await _make_user(db_session, role="admin")
team = await _make_team(db_session)
member = await _make_user(db_session)
member.team_id = team.id
await db_session.commit()
response = await client.delete(f"/api/v1/admin/teams/{team.id}", headers=_auth_headers(admin))
assert response.status_code == 204
await db_session.refresh(member)
assert member.team_id is None
# --- Привязка пользователя к команде ---------------------------------------------------
async def test_patch_user_assigns_team(client: httpx.AsyncClient, db_session: AsyncSession) -> None:
admin = await _make_user(db_session, role="admin")
target = await _make_user(db_session)
team = await _make_team(db_session)
await db_session.commit()
response = await client.patch(
f"/api/v1/admin/users/{target.id}",
json={"team_id": str(team.id)},
headers=_auth_headers(admin),
)
assert response.status_code == 200, response.text
assert response.json()["team_id"] == str(team.id)
async def test_patch_user_clears_team_with_explicit_null(
client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
admin = await _make_user(db_session, role="admin")
team = await _make_team(db_session)
target = await _make_user(db_session)
target.team_id = team.id
await db_session.commit()
response = await client.patch(
f"/api/v1/admin/users/{target.id}",
json={"team_id": None},
headers=_auth_headers(admin),
)
assert response.status_code == 200, response.text
assert response.json()["team_id"] is None
async def test_patch_user_missing_team_field_keeps_current_team(
client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
admin = await _make_user(db_session, role="admin")
team = await _make_team(db_session)
target = await _make_user(db_session)
target.team_id = team.id
await db_session.commit()
response = await client.patch(
f"/api/v1/admin/users/{target.id}",
json={"role": "admin"},
headers=_auth_headers(admin),
)
assert response.status_code == 200, response.text
assert response.json()["team_id"] == str(team.id)
async def test_patch_user_unknown_team_returns_404(
client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
admin = await _make_user(db_session, role="admin")
target = await _make_user(db_session)
await db_session.commit()
response = await client.patch(
f"/api/v1/admin/users/{target.id}",
json={"team_id": str(uuid.uuid4())},
headers=_auth_headers(admin),
)
assert response.status_code == 404
async def test_admin_can_change_own_team_id(
client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
admin = await _make_user(db_session, role="admin")
team = await _make_team(db_session)
await db_session.commit()
response = await client.patch(
f"/api/v1/admin/users/{admin.id}",
json={"team_id": str(team.id)},
headers=_auth_headers(admin),
)
assert response.status_code == 200, response.text
assert response.json()["team_id"] == str(team.id)
async def test_admin_still_forbidden_to_change_own_role_and_block(
client: httpx.AsyncClient, db_session: AsyncSession
) -> None:
admin = await _make_user(db_session, role="admin")
await db_session.commit()
response = await client.patch(
f"/api/v1/admin/users/{admin.id}",
json={"is_blocked": True},
headers=_auth_headers(admin),
)
assert response.status_code == 409
response = await client.patch(
f"/api/v1/admin/users/{admin.id}",
json={"role": "user"},
headers=_auth_headers(admin),
)
assert response.status_code == 409