"""公网部署安全回归测试。""" import pytest from fastapi.testclient import TestClient from starlette.websockets import WebSocketDisconnect from backend import config from backend.main import app from backend.security import hash_password, verify_password def test_passwords_use_bcrypt_and_verify(): password_hash = hash_password("a-strong-password") assert password_hash.startswith("$2") assert verify_password("a-strong-password", password_hash) == (True, False) assert verify_password("wrong-password", password_hash) == (False, False) def test_websocket_rejects_connection_without_jwt(): with TestClient(app) as unauthenticated_client: with pytest.raises(WebSocketDisconnect) as exc_info: with unauthenticated_client.websocket_connect("/ws/tasks/task-1"): pass assert exc_info.value.code == 1008 def test_production_rejects_default_security_settings(monkeypatch): monkeypatch.setattr(config, "APP_ENV", "production") monkeypatch.setattr(config, "JWT_SECRET", "ios-build-server-secret-key-change-in-production") monkeypatch.setattr(config, "ADMIN_PASSWORD", "admin123") monkeypatch.setattr(config, "CORS_ALLOWED_ORIGINS", ["*"]) monkeypatch.setattr(config, "TRUSTED_HOSTS", ["*"]) monkeypatch.setenv("TRUSTED_HOSTS", "*") with pytest.raises(RuntimeError, match="生产环境安全配置不完整"): config.validate_production_security() def test_config_routes_limit_regular_users_to_apps(client, tmp_config): """普通账号只能管理 Apps,不能读取或修改包含密钥的配置。""" response = client.post("/api/users", json={ "username": "builder", "password": "builder-password-123", "is_admin": False, }) assert response.status_code == 200 login = client.post("/api/auth/login", json={ "username": "builder", "password": "builder-password-123", }) token = login.json()["token"] headers = {"Authorization": f"Bearer {token}"} assert client.get("/api/config/apps", headers=headers).status_code == 200 assert client.post("/api/config/apps", json={"name": "普通用户 App"}, headers=headers).status_code == 200 assert client.get("/api/config", headers=headers).status_code == 403 assert client.get("/api/config/servers", headers=headers).status_code == 403 assert client.put("/api/config/upload", json={"mode": "oss"}, headers=headers).status_code == 403