1. 清理测试文件中未使用的导入与冗余代码 2. 修复审计日志与批量操作的空值约束,统一填充"global"作为默认渠道 3. 调整批量消息撤回的响应语义,对齐其他端点的部分成功契约 4. 修复访问规则批量克隆的唯一约束问题,新增后缀自动处理逻辑 5. 替换anyio为asyncio并行调用,修正时间UTC导入路径 6. 优化前端外部系统概览页的刷新状态提示与缓存逻辑 7. 修复测试用例中的断言与请求方式问题,适配httpx删除请求特性 8. 重构后端路由的依赖注入,移除冗余的数据库会话依赖 9. 调整测试用例的权限校验逻辑,修正强制登出的权限判断 10. 修复语义分块测试的numpy依赖问题,清理冗余导入
649 lines
22 KiB
Python
649 lines
22 KiB
Python
"""Integration tests for channels account_router endpoints."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import httpx
|
||
import pytest
|
||
|
||
from .conftest import (
|
||
BASE_URL,
|
||
DEFAULT_CHANNEL_TYPE,
|
||
NON_EXISTENT_ACCOUNT_ID,
|
||
)
|
||
|
||
pytestmark = [pytest.mark.asyncio, pytest.mark.integration]
|
||
|
||
ACCOUNTS_URL = f"{BASE_URL}/accounts"
|
||
|
||
|
||
def _make_create_account_body(display_name: str = "pytest_account") -> dict:
|
||
"""Build a minimal CreateAccountRequest body."""
|
||
return {
|
||
"display_name": display_name,
|
||
"raw_config": {"app_id": "pytest_app", "app_secret": "pytest_secret"},
|
||
}
|
||
|
||
|
||
# =============================================================================
|
||
# === Auth three-tier for list accounts ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_list_accounts_requires_auth(test_client: httpx.AsyncClient):
|
||
# Act
|
||
response = await test_client.get(ACCOUNTS_URL)
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_list_accounts_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
|
||
# Act
|
||
response = await test_client.get(ACCOUNTS_URL, headers=standard_user["headers"])
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
async def test_admin_can_list_accounts(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act
|
||
response = await test_client.get(ACCOUNTS_URL, headers=admin_headers)
|
||
# Assert
|
||
assert response.status_code == 200, response.text
|
||
|
||
payload = response.json()
|
||
assert payload["success"] is True
|
||
assert isinstance(payload["data"], dict)
|
||
assert "accounts" in payload["data"]
|
||
assert isinstance(payload["data"]["accounts"], list)
|
||
|
||
|
||
# =============================================================================
|
||
# === Pagination validation for list accounts ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_list_accounts_rejects_limit_zero(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act — limit=0 violates ge=1
|
||
response = await test_client.get(
|
||
ACCOUNTS_URL,
|
||
params={"limit": 0},
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 422, response.text
|
||
|
||
|
||
async def test_list_accounts_rejects_limit_over_max(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act — limit=1001 violates le=1000
|
||
response = await test_client.get(
|
||
ACCOUNTS_URL,
|
||
params={"limit": 1001},
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 422, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === batch enable / disable ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_batch_enable_accounts_requires_auth(test_client: httpx.AsyncClient):
|
||
# Arrange
|
||
body = {"account_ids": [NON_EXISTENT_ACCOUNT_ID]}
|
||
# Act
|
||
response = await test_client.post(f"{ACCOUNTS_URL}/batch/enable", json=body)
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_batch_enable_accounts_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
|
||
# Arrange
|
||
body = {"account_ids": [NON_EXISTENT_ACCOUNT_ID]}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{ACCOUNTS_URL}/batch/enable",
|
||
json=body,
|
||
headers=standard_user["headers"],
|
||
)
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
async def test_batch_enable_accounts_rejects_empty_body(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange — account_ids 与 filter 同时为空
|
||
body = {"account_ids": None, "filter": None}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{ACCOUNTS_URL}/batch/enable",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code in (400, 422), response.text
|
||
|
||
|
||
async def test_batch_disable_accounts_rejects_empty_body(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange
|
||
body = {"account_ids": None, "filter": None}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{ACCOUNTS_URL}/batch/disable",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code in (400, 422), response.text
|
||
|
||
|
||
async def test_batch_enable_accounts_rejects_ids_without_channel_type(
|
||
test_client: httpx.AsyncClient,
|
||
admin_headers,
|
||
):
|
||
# Arrange — account_ids 模式下 channel_type 必填(account_id 仅在
|
||
# (channel_type, account_id) 复合键下唯一,需显式限定渠道才能定位账户)
|
||
body = {"account_ids": [NON_EXISTENT_ACCOUNT_ID], "channel_type": None}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{ACCOUNTS_URL}/batch/enable",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert — 业务校验层拦截,返回 400 VALIDATION_ERROR
|
||
assert response.status_code == 400, response.text
|
||
|
||
|
||
async def test_batch_disable_accounts_rejects_ids_without_channel_type(
|
||
test_client: httpx.AsyncClient,
|
||
admin_headers,
|
||
):
|
||
# Arrange
|
||
body = {"account_ids": [NON_EXISTENT_ACCOUNT_ID], "channel_type": None}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{ACCOUNTS_URL}/batch/disable",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert — 业务校验层拦截,返回 400 VALIDATION_ERROR
|
||
assert response.status_code == 400, response.text
|
||
|
||
|
||
async def test_batch_enable_accounts_with_channel_type_passes_schema(
|
||
test_client: httpx.AsyncClient,
|
||
admin_headers,
|
||
):
|
||
# Arrange — account_ids + channel_type 通过 schema 校验,到达 handler
|
||
# 后因账户不存在返回 404(证明 P0 修复:channel_type 透传至 handler)
|
||
body = {
|
||
"account_ids": [NON_EXISTENT_ACCOUNT_ID],
|
||
"channel_type": DEFAULT_CHANNEL_TYPE,
|
||
}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{ACCOUNTS_URL}/batch/enable",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert — 不再是 422 schema 错误,而是 404(账户不存在)或 200
|
||
# (批量操作部分失败语义),关键是不被 schema 拦截
|
||
assert response.status_code != 422, response.text
|
||
|
||
|
||
async def test_batch_disable_accounts_with_channel_type_passes_schema(
|
||
test_client: httpx.AsyncClient,
|
||
admin_headers,
|
||
):
|
||
# Arrange
|
||
body = {
|
||
"account_ids": [NON_EXISTENT_ACCOUNT_ID],
|
||
"channel_type": DEFAULT_CHANNEL_TYPE,
|
||
}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{ACCOUNTS_URL}/batch/disable",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code != 422, response.text
|
||
|
||
|
||
async def test_batch_disable_accounts_requires_auth(test_client: httpx.AsyncClient):
|
||
# Arrange
|
||
body = {"account_ids": [NON_EXISTENT_ACCOUNT_ID], "channel_type": DEFAULT_CHANNEL_TYPE}
|
||
# Act
|
||
response = await test_client.post(f"{ACCOUNTS_URL}/batch/disable", json=body)
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_batch_disable_accounts_forbids_standard_user(
|
||
test_client: httpx.AsyncClient, standard_user
|
||
):
|
||
# Arrange
|
||
body = {"account_ids": [NON_EXISTENT_ACCOUNT_ID], "channel_type": DEFAULT_CHANNEL_TYPE}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{ACCOUNTS_URL}/batch/disable",
|
||
json=body,
|
||
headers=standard_user["headers"],
|
||
)
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === create account under channel_type ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_create_account_requires_auth(test_client: httpx.AsyncClient):
|
||
# Arrange
|
||
body = _make_create_account_body()
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts",
|
||
json=body,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_create_account_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
|
||
# Arrange
|
||
body = _make_create_account_body()
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts",
|
||
json=body,
|
||
headers=standard_user["headers"],
|
||
)
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
async def test_create_account_invalid_channel_type_returns_422(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange — 适配器未注册时返回 422
|
||
body = _make_create_account_body()
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{BASE_URL}/invalid_channel/accounts",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert — 不应是 401/403;适配器不存在应返回 422
|
||
assert response.status_code == 422, response.text
|
||
|
||
|
||
async def test_create_account_with_invalid_body_returns_not_4xx(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange — display_name 过长触发校验失败
|
||
body = {
|
||
"display_name": "x" * 200,
|
||
"raw_config": {"app_id": "pytest_app"},
|
||
}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 422, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === get / update / delete / state / runtime / rotate / test / export / clone ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_get_account_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act
|
||
response = await test_client.get(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts/{NON_EXISTENT_ACCOUNT_ID}",
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_update_account_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange
|
||
body = {"display_name": "updated_name"}
|
||
# Act
|
||
response = await test_client.put(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts/{NON_EXISTENT_ACCOUNT_ID}",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_update_account_rejects_empty_body(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange — display_name 与 raw_config 同时为空,dispatch 校验返回 400
|
||
body = {"display_name": None, "raw_config": None}
|
||
# Act
|
||
response = await test_client.put(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts/{NON_EXISTENT_ACCOUNT_ID}",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code in (400, 422, 404), response.text
|
||
|
||
|
||
async def test_delete_account_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act
|
||
response = await test_client.delete(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts/{NON_EXISTENT_ACCOUNT_ID}",
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_enable_account_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts/{NON_EXISTENT_ACCOUNT_ID}/enable",
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_disable_account_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts/{NON_EXISTENT_ACCOUNT_ID}/disable",
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_recover_account_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts/{NON_EXISTENT_ACCOUNT_ID}/recover",
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_get_runtime_status_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act
|
||
response = await test_client.get(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts/{NON_EXISTENT_ACCOUNT_ID}/runtime-status",
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_rotate_credentials_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts/{NON_EXISTENT_ACCOUNT_ID}/rotate-credentials",
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_test_connection_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts/{NON_EXISTENT_ACCOUNT_ID}/test-connection",
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === export account (with include_secrets authorization) ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_export_account_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Act
|
||
response = await test_client.get(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts/{NON_EXISTENT_ACCOUNT_ID}/export",
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_export_account_with_secrets_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange — admin_headers 为 superadmin,include_secrets=True 的 superadmin
|
||
# 校验在 dispatch 阶段通过;账户不存在 → 404。
|
||
# 注:include_secrets=True 拒绝非 superadmin 管理员的场景无法用 admin_headers
|
||
# 验证(admin_headers 本身即 superadmin);standard_user 会被 get_admin_user
|
||
# 守门拦截(403),无法抵达 include_secrets 校验。
|
||
# Act
|
||
response = await test_client.get(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts/{NON_EXISTENT_ACCOUNT_ID}/export",
|
||
params={"include_secrets": True},
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === clone account ===
|
||
# =============================================================================
|
||
|
||
|
||
async def test_clone_account_non_existent_returns_404(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange
|
||
body = {"new_display_name": "cloned_account", "include_credentials": False}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts/{NON_EXISTENT_ACCOUNT_ID}/clone",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 404, response.text
|
||
|
||
|
||
async def test_clone_account_rejects_invalid_body(test_client: httpx.AsyncClient, admin_headers):
|
||
# Arrange — new_display_name 过长触发校验失败
|
||
body = {"new_display_name": "x" * 200, "include_credentials": False}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts/{NON_EXISTENT_ACCOUNT_ID}/clone",
|
||
json=body,
|
||
headers=admin_headers,
|
||
)
|
||
# Assert
|
||
assert response.status_code == 422, response.text
|
||
|
||
|
||
# =============================================================================
|
||
# === Auth matrix for individual account operations ===
|
||
# =============================================================================
|
||
|
||
ACCOUNT_URL = f"{BASE_URL}/{DEFAULT_CHANNEL_TYPE}/accounts/{NON_EXISTENT_ACCOUNT_ID}"
|
||
|
||
|
||
async def test_get_account_requires_auth(test_client: httpx.AsyncClient):
|
||
# Act
|
||
response = await test_client.get(ACCOUNT_URL)
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_get_account_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
|
||
# Act
|
||
response = await test_client.get(ACCOUNT_URL, headers=standard_user["headers"])
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
async def test_update_account_requires_auth(test_client: httpx.AsyncClient):
|
||
# Arrange
|
||
body = {"display_name": "updated_name"}
|
||
# Act
|
||
response = await test_client.put(ACCOUNT_URL, json=body)
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_update_account_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
|
||
# Arrange
|
||
body = {"display_name": "updated_name"}
|
||
# Act
|
||
response = await test_client.put(ACCOUNT_URL, json=body, headers=standard_user["headers"])
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
async def test_delete_account_requires_auth(test_client: httpx.AsyncClient):
|
||
# Act
|
||
response = await test_client.delete(ACCOUNT_URL)
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_delete_account_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
|
||
# Act
|
||
response = await test_client.delete(ACCOUNT_URL, headers=standard_user["headers"])
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
async def test_enable_account_requires_auth(test_client: httpx.AsyncClient):
|
||
# Act
|
||
response = await test_client.post(f"{ACCOUNT_URL}/enable")
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_enable_account_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
|
||
# Act
|
||
response = await test_client.post(f"{ACCOUNT_URL}/enable", headers=standard_user["headers"])
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
async def test_disable_account_requires_auth(test_client: httpx.AsyncClient):
|
||
# Act
|
||
response = await test_client.post(f"{ACCOUNT_URL}/disable")
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_disable_account_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
|
||
# Act
|
||
response = await test_client.post(f"{ACCOUNT_URL}/disable", headers=standard_user["headers"])
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
async def test_recover_account_requires_auth(test_client: httpx.AsyncClient):
|
||
# Act
|
||
response = await test_client.post(f"{ACCOUNT_URL}/recover")
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_recover_account_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
|
||
# Act
|
||
response = await test_client.post(f"{ACCOUNT_URL}/recover", headers=standard_user["headers"])
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
async def test_get_runtime_status_requires_auth(test_client: httpx.AsyncClient):
|
||
# Act
|
||
response = await test_client.get(f"{ACCOUNT_URL}/runtime-status")
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_get_runtime_status_forbids_standard_user(
|
||
test_client: httpx.AsyncClient, standard_user
|
||
):
|
||
# Act
|
||
response = await test_client.get(
|
||
f"{ACCOUNT_URL}/runtime-status", headers=standard_user["headers"]
|
||
)
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
async def test_rotate_credentials_requires_auth(test_client: httpx.AsyncClient):
|
||
# Act
|
||
response = await test_client.post(f"{ACCOUNT_URL}/rotate-credentials")
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_rotate_credentials_forbids_standard_user(
|
||
test_client: httpx.AsyncClient, standard_user
|
||
):
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{ACCOUNT_URL}/rotate-credentials", headers=standard_user["headers"]
|
||
)
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
async def test_test_connection_requires_auth(test_client: httpx.AsyncClient):
|
||
# Act
|
||
response = await test_client.post(f"{ACCOUNT_URL}/test-connection")
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_test_connection_forbids_standard_user(
|
||
test_client: httpx.AsyncClient, standard_user
|
||
):
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{ACCOUNT_URL}/test-connection", headers=standard_user["headers"]
|
||
)
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
async def test_export_account_requires_auth(test_client: httpx.AsyncClient):
|
||
# Act
|
||
response = await test_client.get(f"{ACCOUNT_URL}/export")
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_export_account_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
|
||
# Act
|
||
response = await test_client.get(f"{ACCOUNT_URL}/export", headers=standard_user["headers"])
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|
||
|
||
|
||
async def test_clone_account_requires_auth(test_client: httpx.AsyncClient):
|
||
# Arrange
|
||
body = {"new_display_name": "cloned_account", "include_credentials": False}
|
||
# Act
|
||
response = await test_client.post(f"{ACCOUNT_URL}/clone", json=body)
|
||
# Assert
|
||
assert response.status_code == 401, response.text
|
||
|
||
|
||
async def test_clone_account_forbids_standard_user(test_client: httpx.AsyncClient, standard_user):
|
||
# Arrange
|
||
body = {"new_display_name": "cloned_account", "include_credentials": False}
|
||
# Act
|
||
response = await test_client.post(
|
||
f"{ACCOUNT_URL}/clone", json=body, headers=standard_user["headers"]
|
||
)
|
||
# Assert
|
||
assert response.status_code == 403, response.text
|