"""Integration tests for channels wizard_router endpoints.""" from __future__ import annotations import httpx import pytest from .conftest import BASE_URL, DEFAULT_CHANNEL_TYPE pytestmark = [pytest.mark.asyncio, pytest.mark.integration] def _make_validate_payload() -> dict: """Build a valid ValidateWizardRequest body (step_id 在路径参数中).""" return { "values": {"app_id": "pytest_app", "app_secret": "pytest_secret"}, } def _make_finalize_payload() -> dict: """Build a valid FinalizeWizardRequest body.""" return { "patches": [ { "step_id": "account_info", "values": {"app_id": "pytest_app", "app_secret": "pytest_secret"}, } ] } # ============================================================================= # === Auth three-tier for GET /wizard/steps === # ============================================================================= async def test_list_wizard_steps_requires_auth(test_client: httpx.AsyncClient): # Act response = await test_client.get(f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/wizard/steps") # Assert assert response.status_code == 401 async def test_list_wizard_steps_requires_admin(test_client: httpx.AsyncClient, standard_user): # Act response = await test_client.get( f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/wizard/steps", headers=standard_user["headers"], ) # Assert assert response.status_code == 403 async def test_admin_can_list_wizard_steps_returns_200_or_404_or_501(test_client: httpx.AsyncClient, admin_headers): # Act response = await test_client.get( f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/wizard/steps", headers=admin_headers, ) # Assert — adapter not registered → 404 or 501, or 200 with steps assert response.status_code in (200, 404, 501), response.text # ============================================================================= # === POST /wizard/steps/{step_id}/validate === # ============================================================================= async def test_wizard_validate_returns_200_or_404_or_501(test_client: httpx.AsyncClient, admin_headers): # Arrange step_id = "account_info" body = _make_validate_payload() # Act response = await test_client.post( f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/wizard/steps/{step_id}/validate", json=body, headers=admin_headers, ) # Assert assert response.status_code in (200, 404, 501), response.text async def test_wizard_validate_rejects_empty_step_id(test_client: httpx.AsyncClient, admin_headers): # Arrange — step_id 路径参数为空字符串,FastAPI 路由匹配规则下 # 可能不匹配(404)或匹配到空 step_id 后由下游校验拒绝(400/422) body = _make_validate_payload() # Act response = await test_client.post( f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/wizard/steps//validate", json=body, headers=admin_headers, ) # Assert — 空路径段不被接受,返回 4xx 错误 assert response.status_code in (400, 404, 422), response.text # ============================================================================= # === POST /wizard/steps/{step_id}/apply === # ============================================================================= async def test_wizard_apply_returns_200_or_404_or_501(test_client: httpx.AsyncClient, admin_headers): # Arrange step_id = "account_info" body = {"values": {"app_id": "pytest_app", "app_secret": "pytest_secret"}} # Act response = await test_client.post( f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/wizard/steps/{step_id}/apply", json=body, headers=admin_headers, ) # Assert assert response.status_code in (200, 404, 501), response.text # ============================================================================= # === POST /wizard/finalize === # ============================================================================= async def test_wizard_finalize_returns_200_or_404_or_501(test_client: httpx.AsyncClient, admin_headers): # Arrange body = _make_finalize_payload() # Act response = await test_client.post( f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/wizard/finalize", json=body, headers=admin_headers, ) # Assert assert response.status_code in (200, 404, 501), response.text async def test_wizard_finalize_rejects_empty_patches(test_client: httpx.AsyncClient, admin_headers): # Arrange — patches min_length=1 body = {"patches": []} # Act response = await test_client.post( f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/wizard/finalize", json=body, headers=admin_headers, ) # Assert assert response.status_code == 422, response.text # ============================================================================= # === POST /wizard/oauth/initiate === # ============================================================================= async def test_oauth_initiate_rejects_invalid_redirect_uri_scheme(test_client: httpx.AsyncClient, admin_headers): # Arrange — redirect_uri 必须为 http/https,ftp 等非法 scheme 拒绝 body = {"redirect_uri": "ftp://example.com/callback"} # Act response = await test_client.post( f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/wizard/oauth/initiate", json=body, headers=admin_headers, ) # Assert assert response.status_code == 422, response.text async def test_oauth_initiate_with_valid_params_returns_404_or_501_or_400( test_client: httpx.AsyncClient, admin_headers ): # Arrange — valid redirect_uri but channel/adapter 未注册 body = {"redirect_uri": "https://example.com/callback"} # Act response = await test_client.post( f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/wizard/oauth/initiate", json=body, headers=admin_headers, ) # Assert — non-existent channel / adapter → 404, 501, or 400 assert response.status_code in (400, 404, 501), response.text # ============================================================================= # === POST /wizard/oauth-callback === # ============================================================================= async def test_oauth_callback_requires_code(test_client: httpx.AsyncClient, admin_headers): # Arrange — code required (Pydantic min_length=1) body = {"state": "pytest_state", "redirect_uri": "https://example.com/callback"} # Act response = await test_client.post( f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/wizard/oauth-callback", json=body, headers=admin_headers, ) # Assert assert response.status_code == 422, response.text async def test_oauth_callback_requires_state(test_client: httpx.AsyncClient, admin_headers): # Arrange — state required (Pydantic min_length=1) body = {"code": "pytest_code", "redirect_uri": "https://example.com/callback"} # Act response = await test_client.post( f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/wizard/oauth-callback", json=body, headers=admin_headers, ) # Assert assert response.status_code == 422, response.text async def test_oauth_callback_requires_redirect_uri(test_client: httpx.AsyncClient, admin_headers): # Arrange — redirect_uri required body = {"code": "pytest_code", "state": "pytest_state"} # Act response = await test_client.post( f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/wizard/oauth-callback", json=body, headers=admin_headers, ) # Assert assert response.status_code == 422, response.text async def test_oauth_callback_rejects_invalid_redirect_uri_scheme(test_client: httpx.AsyncClient, admin_headers): # Arrange — redirect_uri 必须为 http/https,ftp 等非法 scheme 拒绝 body = { "code": "pytest_code", "state": "pytest_state", "redirect_uri": "ftp://example.com/callback", } # Act response = await test_client.post( f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/wizard/oauth-callback", json=body, headers=admin_headers, ) # Assert assert response.status_code == 422, response.text async def test_oauth_callback_with_all_params_returns_404_or_501_or_400(test_client: httpx.AsyncClient, admin_headers): # Arrange — all params present but channel not registered body = { "code": "pytest_code", "state": "pytest_state", "redirect_uri": "https://example.com/callback", } # Act response = await test_client.post( f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/wizard/oauth-callback", json=body, headers=admin_headers, ) # Assert — non-existent channel / adapter → 404, 501, or 400 assert response.status_code in (400, 404, 501), response.text # ============================================================================= # === Invalid channel_type validation === # ============================================================================= async def test_wizard_unregistered_channel_type(test_client: httpx.AsyncClient, admin_headers): # Arrange — channel_type 为 str 子类无白名单,未注册插件返回 404 unregistered_channel = "invalid_channel_type_xyz" # Act response = await test_client.get( f"{BASE_URL}/{unregistered_channel}/wizard/steps", headers=admin_headers, ) # Assert assert response.status_code == 404, response.text