"""E2E API tests for the 专线 (dedicated line) endpoints — RBAC + CRUD.""" from __future__ import annotations import pytest from httpx import AsyncClient from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.domains.cmdb.models import CmdbDedicatedLine PREFIX = "/api/v1/cmdb/dedicated-lines" async def _mk_line(db, *, location, carrier="中国电信", monthly=1000): row = CmdbDedicatedLine( location=location, carrier=carrier, contracting_entity="测试主体", payment_method="月付", monthly_cost=monthly, annual_cost=monthly * 12, currency="CNY", purpose="测试用途", ) db.add(row) await db.commit() await db.refresh(row) return row @pytest.mark.asyncio async def test_employee_cannot_read(client: AsyncClient, employee_headers): # Read gate is can_read_cloud — plain employees are excluded. resp = await client.get(PREFIX, headers=employee_headers) assert resp.status_code == 403 @pytest.mark.asyncio async def test_admin_lists_lines( client: AsyncClient, db_session: AsyncSession, admin_headers, ): await _mk_line(db_session, location="北京") await _mk_line(db_session, location="上海", carrier="中国联通") resp = await client.get(PREFIX, headers=admin_headers) assert resp.status_code == 200 locs = {r["location"] for r in resp.json()["data"]} assert {"北京", "上海"} <= locs @pytest.mark.asyncio async def test_search_filters( client: AsyncClient, db_session: AsyncSession, admin_headers, ): await _mk_line(db_session, location="广州", carrier="中国电信") await _mk_line(db_session, location="深圳", carrier="中国联通") resp = await client.get( PREFIX, headers=admin_headers, params={"search": "广州"}, ) assert resp.status_code == 200 locs = {r["location"] for r in resp.json()["data"]} assert "广州" in locs assert "深圳" not in locs @pytest.mark.asyncio async def test_employee_cannot_create(client: AsyncClient, employee_headers): resp = await client.post( PREFIX, headers=employee_headers, json={"location": "X"}, ) assert resp.status_code == 403 @pytest.mark.asyncio async def test_admin_crud_roundtrip( client: AsyncClient, db_session: AsyncSession, admin_headers, ): # Create body = { "location": "杭州", "carrier": "中国电信", "contracting_entity": "平凯星辰(北京)科技有限公司", "payment_method": "年付", "monthly_cost": 3000, "annual_cost": 36000, "currency": "CNY", "renewal_date": "每年10月份", "purpose": "杭州office办公网", } r = await client.post(PREFIX, headers=admin_headers, json=body) assert r.status_code == 201, r.text created = r.json()["data"] line_id = created["id"] assert created["location"] == "杭州" assert created["annual_cost"] == 36000 # modifier stamped assert created["last_modified_by_person_id"] is not None # Update (partial) r2 = await client.patch( f"{PREFIX}/{line_id}", headers=admin_headers, json={"monthly_cost": 3200}, ) assert r2.status_code == 200, r2.text assert r2.json()["data"]["monthly_cost"] == 3200 # untouched field preserved assert r2.json()["data"]["renewal_date"] == "每年10月份" # Get single r3 = await client.get(f"{PREFIX}/{line_id}", headers=admin_headers) assert r3.status_code == 200 assert r3.json()["data"]["monthly_cost"] == 3200 # Delete (soft) r4 = await client.delete(f"{PREFIX}/{line_id}", headers=admin_headers) assert r4.status_code == 200 assert r4.json()["data"]["deleted"] is True # Gone from list + 404 on get r5 = await client.get(f"{PREFIX}/{line_id}", headers=admin_headers) assert r5.status_code == 404 # Soft-delete: row still present with deleted_at set import uuid as _uuid row = await db_session.scalar( select(CmdbDedicatedLine).where( CmdbDedicatedLine.id == _uuid.UUID(line_id), ) ) assert row is not None assert row.deleted_at is not None @pytest.mark.asyncio async def test_update_missing_404(client: AsyncClient, admin_headers): import uuid as _uuid r = await client.patch( f"{PREFIX}/{_uuid.uuid4()}", headers=admin_headers, json={"monthly_cost": 1}, ) assert r.status_code == 404