| import pytest |
| from fastapi.testclient import TestClient |
| from app.main import app |
| from app.core.config import settings |
| from app.core.security import create_access_token |
| from app.models.user import User |
| from datetime import timedelta |
|
|
| client = TestClient(app) |
|
|
| def test_sql_injection_selection() -> None: |
| """测试选股接口是否防护SQL注入""" |
| |
| access_token_expires: timedelta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) |
| access_token: str = create_access_token( |
| data={"sub": "test_user"}, |
| expires_delta=access_token_expires |
| ) |
| |
| |
| injection_payload: str = "1' OR '1'='1'--" |
| response = client.get( |
| f"/api/v1/strategies/selections?strategy_id={injection_payload}", |
| headers={"Authorization": f"Bearer {access_token}"} |
| ) |
| |
| |
| assert response.status_code == 200 |
| assert response.json() == [] |
|
|
| def test_sql_injection_portfolio() -> None: |
| """测试投资组合接口是否防护SQL注入""" |
| |
| access_token_expires: timedelta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) |
| access_token: str = create_access_token( |
| data={"sub": "test_user"}, |
| expires_delta=access_token_expires |
| ) |
| |
| |
| injection_payload: str = "1; DROP TABLE portfolios;--" |
| response = client.get( |
| f"/api/v1/portfolios/{injection_payload}", |
| headers={"Authorization": f"Bearer {access_token}"} |
| ) |
| |
| |
| assert response.status_code == 404 |
| assert response.json()["detail"] == "投资组合未找到" |
|
|
| def test_sql_injection_register() -> None: |
| """测试用户注册接口是否防护SQL注入""" |
| |
| injection_payload_username: str = "test_user_sql' OR '1'='1'--" |
| injection_payload_email: str = "test_sql@example.com' OR '1'='1'--" |
| |
| |
| response_username = client.post( |
| "/api/v1/auth/register", |
| json={ |
| "username": injection_payload_username, |
| "email": "test_sql_username@example.com", |
| "password": "testpassword" |
| } |
| ) |
| |
| assert response_username.status_code == 400 |
| assert "用户名已存在" in response_username.json()["detail"] or "注册失败" in response_username.json()["detail"] |
|
|
| |
| response_email = client.post( |
| "/api/v1/auth/register", |
| json={ |
| "username": "test_user_email_sql", |
| "email": injection_payload_email, |
| "password": "testpassword" |
| } |
| ) |
| |
| assert response_email.status_code == 400 |
| assert "邮箱已被注册" in response_email.json()["detail"] or "注册失败" in response_email.json()["detail"] |
|
|
| def test_sql_injection_login() -> None: |
| """测试用户登录接口是否防护SQL注入""" |
| |
| injection_payload_username: str = "admin' OR '1'='1'--" |
| injection_payload_password: str = "password' OR '1'='1'--" |
|
|
| |
| response_username = client.post( |
| "/api/v1/auth/login", |
| data={ |
| "username": injection_payload_username, |
| "password": "anypassword" |
| } |
| ) |
| |
| assert response_username.status_code == 401 |
| assert response_username.json()["detail"] == "用户名或密码错误" |
|
|
| |
| response_password = client.post( |
| "/api/v1/auth/login", |
| data={ |
| "username": "test_user", |
| "password": injection_payload_password |
| } |
| ) |
| |
| assert response_password.status_code == 401 |
| assert response_password.json()["detail"] == "用户名或密码错误" |
|
|
| def test_sql_injection_get_backtest_results() -> None: |
| """测试获取回测结果列表接口是否防护SQL注入""" |
| |
| access_token_expires: timedelta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) |
| access_token: str = create_access_token( |
| data={"sub": "test_user"}, |
| expires_delta=access_token_expires |
| ) |
|
|
| |
| injection_payload: str = "1' OR '1'='1'--" |
| response = client.get( |
| f"/api/v1/backtests/?strategy_id={injection_payload}", |
| headers={"Authorization": f"Bearer {access_token}"} |
| ) |
| |
| assert response.status_code == 422 |
| assert "value is not a valid integer" in response.json()["detail"][0]["msg"] |
|
|
| def test_sql_injection_create_portfolio() -> None: |
| """测试创建投资组合接口是否防护SQL注入""" |
| |
| access_token_expires: timedelta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) |
| access_token: str = create_access_token( |
| data={"sub": "test_user"}, |
| expires_delta=access_token_expires |
| ) |
|
|
| |
| injection_payload_name: str = "My Portfolio' OR '1'='1'--" |
| response = client.post( |
| "/api/v1/portfolios/", |
| headers={"Authorization": f"Bearer {access_token}"}, |
| json={ |
| "name": injection_payload_name, |
| "description": "A test portfolio" |
| } |
| ) |
| |
| |
| assert response.status_code == 201 |
| assert response.json()["name"] == injection_payload_name |
|
|
| def test_sql_injection_search_stocks() -> None: |
| """测试搜索股票接口是否防护SQL注入""" |
| |
| access_token_expires: timedelta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) |
| access_token: str = create_access_token( |
| data={"sub": "test_user"}, |
| expires_delta=access_token_expires |
| ) |
|
|
| |
| injection_payload: str = "AAPL' OR '1'='1'--" |
| response = client.get( |
| f"/api/v1/stocks/search?query={injection_payload}", |
| headers={"Authorization": f"Bearer {access_token}"} |
| ) |
| |
| assert response.status_code == 200 |
| |
| assert response.json() == [] |
|
|
| def test_sql_injection_get_stock_ranking() -> None: |
| """测试获取股票排名接口是否防护SQL注入""" |
| |
| access_token_expires: timedelta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) |
| access_token: str = create_access_token( |
| data={"sub": "test_user"}, |
| expires_delta=access_token_expires |
| ) |
|
|
| |
| injection_payload: str = "gainers' OR '1'='1'--" |
| response = client.get( |
| f"/api/v1/stocks/ranking/{injection_payload}", |
| headers={"Authorization": f"Bearer {access_token}"} |
| ) |
| |
| |
| assert response.status_code == 422 or response.status_code == 500 |
| |
| if response.status_code == 422: |
| assert "value is not a valid enumeration member" in response.json()["detail"][0]["msg"] |
| else: |
| assert "获取股票排名失败" in response.json()["detail"] |
|
|
| def test_sql_injection_get_strategy() -> None: |
| """测试获取策略详情接口是否防护SQL注入""" |
| |
| access_token_expires: timedelta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) |
| access_token: str = create_access_token( |
| data={"sub": "test_user"}, |
| expires_delta=access_token_expires |
| ) |
|
|
| |
| injection_payload: str = "invalid_uuid' OR '1'='1'--" |
| response = client.get( |
| f"/api/v1/strategies/{injection_payload}", |
| headers={"Authorization": f"Bearer {access_token}"} |
| ) |
| |
| assert response.status_code == 404 |
| assert response.json()["detail"] == "策略不存在" |
|
|
| def test_sql_injection_update_strategy() -> None: |
| """测试更新策略接口是否防护SQL注入""" |
| |
| access_token_expires: timedelta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) |
| access_token: str = create_access_token( |
| data={"sub": "test_user"}, |
| expires_delta=access_token_expires |
| ) |
|
|
| |
| create_response = client.post( |
| "/api/v1/strategies/", |
| headers={"Authorization": f"Bearer {access_token}"}, |
| json={ |
| "name": "Strategy to Update", |
| "description": "Original description", |
| "strategy_type": "momentum", |
| "parameters": {} |
| } |
| ) |
| assert create_response.status_code == 201 |
| strategy_id = create_response.json()["id"] |
|
|
| |
| injection_payload_name: str = "Updated Strategy' OR '1'='1'--" |
| response = client.put( |
| f"/api/v1/strategies/{strategy_id}", |
| headers={"Authorization": f"Bearer {access_token}"}, |
| json={ |
| "name": injection_payload_name, |
| "description": "Updated description" |
| } |
| ) |
| |
| assert response.status_code == 200 |
| assert response.json()["name"] == injection_payload_name |
|
|
| def test_sql_injection_delete_strategy() -> None: |
| """测试删除策略接口是否防护SQL注入""" |
| |
| access_token_expires: timedelta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) |
| access_token: str = create_access_token( |
| data={"sub": "test_user"}, |
| expires_delta=access_token_expires |
| ) |
|
|
| |
| create_response = client.post( |
| "/api/v1/strategies/", |
| headers={"Authorization": f"Bearer {access_token}"}, |
| json={ |
| "name": "Strategy to Delete", |
| "description": "Description", |
| "strategy_type": "momentum", |
| "parameters": {} |
| } |
| ) |
| assert create_response.status_code == 201 |
| strategy_id = create_response.json()["id"] |
|
|
| |
| injection_payload: str = "invalid_uuid' OR '1'='1'--" |
| response = client.delete( |
| f"/api/v1/strategies/{injection_payload}", |
| headers={"Authorization": f"Bearer {access_token}"} |
| ) |
| |
| assert response.status_code == 404 |
| assert response.json()["detail"] == "策略不存在" |
|
|
| |
| response_valid_delete = client.delete( |
| f"/api/v1/strategies/{strategy_id}", |
| headers={"Authorization": f"Bearer {access_token}"} |
| ) |
| assert response_valid_delete.status_code == 200 |
| assert response_valid_delete.json()["message"] == "策略已成功标记为非活跃" |
|
|
| def test_sql_injection_run_backtest() -> None: |
| """测试运行策略回测接口是否防护SQL注入""" |
| |
| access_token_expires: timedelta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) |
| access_token: str = create_access_token( |
| data={"sub": "test_user"}, |
| expires_delta=access_token_expires |
| ) |
|
|
| |
| injection_payload_id: str = "invalid_uuid' OR '1'='1'--" |
| response_id = client.post( |
| f"/api/v1/strategies/{injection_payload_id}/backtest", |
| headers={"Authorization": f"Bearer {access_token}"}, |
| json={ |
| "stock_codes": ["000001.SZ"], |
| "start_date": "2020-01-01", |
| "end_date": "2021-01-01", |
| "initial_capital": 100000 |
| } |
| ) |
| |
| assert response_id.status_code == 404 |
| assert response_id.json()["detail"] == "策略不存在" |
|
|
| |
| |
| injection_payload_stock: List[str] = ["000001.SZ", "'; DROP TABLE users;--"] |
| |
| create_response = client.post( |
| "/api/v1/strategies/", |
| headers={"Authorization": f"Bearer {access_token}"}, |
| json={ |
| "name": "Backtest Strategy", |
| "description": "Description", |
| "strategy_type": "momentum", |
| "parameters": {} |
| } |
| ) |
| assert create_response.status_code == 201 |
| valid_strategy_id = create_response.json()["id"] |
|
|
| response_stock = client.post( |
| f"/api/v1/strategies/{valid_strategy_id}/backtest", |
| headers={"Authorization": f"Bearer {access_token}"}, |
| json={ |
| "stock_codes": injection_payload_stock, |
| "start_date": "2020-01-01", |
| "end_date": "2021-01-01", |
| "initial_capital": 100000 |
| } |
| ) |
| |
| |
| assert response_stock.status_code == 500 |
| assert "回测失败" in response_stock.json()["detail"] |
|
|
| def test_sql_injection_get_selection_dates() -> None: |
| """测试获取选股日期列表接口是否防护SQL注入""" |
| |
| access_token_expires: timedelta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) |
| access_token: str = create_access_token( |
| data={"sub": "test_user"}, |
| expires_delta=access_token_expires |
| ) |
|
|
| |
| injection_payload: str = "invalid_uuid' OR '1'='1'--" |
| response = client.get( |
| f"/api/v1/strategies/selections/dates?strategy_id={injection_payload}", |
| headers={"Authorization": f"Bearer {access_token}"} |
| ) |
| |
| assert response.status_code == 200 |
| assert response.json() == [] |
|
|
| def test_sql_injection_get_stock_selections() -> None: |
| """测试获取选股结果接口是否防护SQL注入""" |
| |
| access_token_expires: timedelta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) |
| access_token: str = create_access_token( |
| data={"sub": "test_user"}, |
| expires_delta=access_token_expires |
| ) |
|
|
| |
| injection_payload: str = "invalid_uuid' OR '1'='1'--" |
| response = client.get( |
| f"/api/v1/strategies/selections?strategy_id={injection_payload}", |
| headers={"Authorization": f"Bearer {access_token}"} |
| ) |
| |
| assert response.status_code == 200 |
| assert response.json() == [] |
|
|
| def test_sql_injection_update_portfolio() -> None: |
| """测试更新投资组合接口是否防护SQL注入""" |
| |
| access_token_expires: timedelta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) |
| access_token: str = create_access_token( |
| data={"sub": "test_user"}, |
| expires_delta=access_token_expires |
| ) |
|
|
| |
| create_response = client.post( |
| "/api/v1/portfolios/", |
| headers={"Authorization": f"Bearer {access_token}"}, |
| json={ |
| "name": "Portfolio to Update", |
| "description": "Original description" |
| } |
| ) |
| assert create_response.status_code == 201 |
| portfolio_id = create_response.json()["id"] |
|
|
| |
| injection_payload_name: str = "Updated Portfolio' OR '1'='1'--" |
| response = client.put( |
| f"/api/v1/portfolios/{portfolio_id}", |
| headers={"Authorization": f"Bearer {access_token}"}, |
| json={ |
| "name": injection_payload_name, |
| "description": "Updated description" |
| } |
| ) |
| |
| assert response.status_code == 200 |
| assert response.json()["name"] == injection_payload_name |
|
|
| def test_sql_injection_get_backtest_result_detail() -> None: |
| """测试获取回测结果详情接口是否防护SQL注入""" |
| |
| access_token_expires: timedelta = timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES) |
| access_token: str = create_access_token( |
| data={"sub": "test_user"}, |
| expires_delta=access_token_expires |
| ) |
|
|
| |
| injection_payload: str = "1' OR '1'='1'--" |
| response = client.get( |
| f"/api/v1/backtests/{injection_payload}", |
| headers={"Authorization": f"Bearer {access_token}"} |
| ) |
| |
| assert response.status_code == 422 |
| assert "value is not a valid integer" in response.json()["detail"][0]["msg"] |