Files
FastDeploy/tests/ce/server/test_evil_cases.py
YUNSHEN XIE 3a6058e445 Add stable ci (#3460)
* add stable ci

* fix

* update

* fix

* rename tests dir;fix stable ci bug

* add timeout limit

* update
2025-08-20 08:57:17 +08:00

405 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/env python3
# -*- coding: utf-8 -*-
# @author DDDivano
# encoding=utf-8 vi:ts=4:sw=4:expandtab:ft=python
"""
边缘检测 ,攻击性测试
"""
import pytest
from core import TEMPLATE, URL, build_request_payload, send_request
def test_missing_messages_field():
"""缺失 messages 字段,服务应返回合理错误,而非崩溃"""
data = {
"stream": False,
"max_tokens": 10,
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert "detail" in resp, "返回中未包含 detail 错误信息字段"
assert any("messages" in err.get("loc", []) for err in resp["detail"]), "未检测到 messages 字段缺失的报错"
assert any("Field required" in err.get("msg", "") for err in resp["detail"]), "未检测到 'Field required' 错误提示"
def test_malformed_messages_format():
"""messages 为非列表,应报错而非崩溃"""
data = {
"stream": False,
"messages": "我是一个非法的消息结构",
"max_tokens": 10,
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert "detail" in resp, "非法结构未被识别"
assert any("messages" in err.get("loc", []) for err in resp["detail"]), "未检测到 messages 字段结构错误"
assert any(
"Input should be a valid list" in err.get("msg", "") for err in resp["detail"]
), "未检测到 'Input should be a valid list' 错误提示"
def test_extremely_large_max_tokens():
"""设置极大 max_tokens观察模型内存/容错行为"""
data = {
"stream": False,
"messages": [{"role": "user", "content": "1+1=?"}],
"max_tokens": 10000000,
}
payload = build_request_payload(TEMPLATE, data)
try:
resp = send_request(URL, payload).json()
assert "error" in resp or resp["usage"]["completion_tokens"] < 10000000
except Exception:
pytest.fail("设置极大 max_tokens 时服务崩溃")
def test_null_metadata():
"""metadata = null"""
data = {
"stream": False,
"messages": [{"role": "user", "content": "介绍下你自己"}],
"max_tokens": 10,
"metadata": None,
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert "error" not in resp, "metadata=null 应被容忍而不是报错"
def test_top_p_exceed_1():
"""top_p 超过1违反规定服务应报错"""
data = {
"stream": False,
"messages": [{"role": "user", "content": "非洲的首都是?"}],
"top_p": 1.5,
"max_tokens": 10,
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert resp.get("detail").get("object") == "error", "top_p > 1 应触发校验异常"
assert "top_p value can only be defined" in resp.get("detail").get("message", ""), "未返回预期的 top_p 错误信息"
def test_mixed_valid_invalid_fields():
"""混合合法字段与非法字段,看是否污染整个请求"""
data = {
"stream": False,
"messages": [{"role": "user", "content": "你好"}],
"max_tokens": 10,
"invalid_field": "this_should_be_ignored_or_warned",
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert "error" not in resp, "非法字段不应导致请求失败"
def test_stop_seq_exceed_num():
"""stop 字段包含超过 FD_MAX_STOP_SEQS_NUM 个元素,服务应报错"""
data = {
"stream": False,
"messages": [{"role": "user", "content": "非洲的首都是?"}],
"top_p": 0,
"stop": ["11", "22", "33", "44", "55", "66", "77"],
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert resp.get("detail").get("object") == "error", "stop 超出个数应触发异常"
assert "exceeds the limit max_stop_seqs_num" in resp.get("detail").get("message", ""), "未返回预期的报错信息"
def test_stop_seq_exceed_length():
"""stop 中包含长度超过 FD_STOP_SEQS_MAX_LEN 的元素,服务应报错"""
data = {
"stream": False,
"messages": [{"role": "user", "content": "非洲的首都是?"}],
"top_p": 0,
"stop": ["11", "今天天气比明天好多了,请问你会出门还是和我一起玩"],
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert resp.get("detail").get("object") == "error", "stop 超出长度应触发异常"
assert "exceeds the limit stop_seqs_max_len" in resp.get("detail").get("message", ""), "未返回预期的报错信息"
def test_multilingual_input():
"""测试多语言混合输入是否能够被正确处理"""
data = {
"messages": [
{
"role": "user",
"content": "这是一个包含多种语言的输入Hello, 世界Bonjour, le monde! Hola, el mundo! こんにちは、世界!",
}
],
"stream": False,
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
# 验证响应是否包含有效的回复
assert "choices" in resp, "未收到有效的回复"
assert len(resp["choices"]) > 0, "回复为空"
assert "message" in resp["choices"][0], "回复中未包含消息内容"
assert "content" in resp["choices"][0]["message"], "回复中未包含内容字段"
# 验证模型是否能够正确处理多语言输入
response_content = resp["choices"][0]["message"]["content"]
assert response_content.strip() != "", "模型未生成任何内容"
print("多语言混合输入测试通过!")
def test_too_long_input():
"""测试超长输入是否被正确处理"""
data = {"messages": [{"role": "user", "content": "a" * 200000}], "stream": False} # 超过最大输入长度
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert resp["detail"].get("object") == "error", "超长输入未被识别为错误"
assert "Input text is too long" in resp["detail"].get("message", ""), "未检测到最大长度限制错误"
def test_empty_input():
"""测试空输入是否被正确处理"""
data = {"messages": [{"role": "user", "content": ""}], "stream": False} # 空输入
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert "error" not in resp.get("object"), "空输入被识别为错误"
assert len(resp["choices"][0]["message"]["content"]) > 0, "内容为空时,回复为空"
def test_prompt_only_spaces():
"""messages content 为纯空格字符串,服务正常返回"""
data = {
"messages": [
{
"role": "user",
"content": " ", # 纯空格
}
],
"stream": False,
"max_tokens": 10,
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert resp.get("object") == "chat.completion", "应返回 chat.completion 对象"
response_content = resp["choices"][0]["message"]["content"]
assert len(response_content) > 0, "messages content为空未正常生成回复"
def test_illegal_characters():
"""测试非法字符输入是否被正确处理"""
data = {"messages": [{"role": "user", "content": "非洲的首都是?:\x00\x01\x02"}], "stream": False} # 非法字符
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert len(resp["choices"][0]["message"]["content"]) > 0, "非法字符输入影响模型回复"
def test_sql_injection():
"""测试 SQL 注入攻击是否被正确处理"""
data = {
"messages": [
{"role": "user", "content": "SELECT * FROM users WHERE username = 'admin' OR '1'='1';"} # SQL 注入攻击
],
"stream": False,
"max_tokens": 50,
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert "message" in resp["choices"][0], "回复中未包含消息内容"
assert "content" in resp["choices"][0]["message"], "回复中未包含内容字段"
response_content = resp["choices"][0]["message"]["content"]
assert len(response_content) > 0, "SQL 注入攻击影响模型回复"
def test_xss_attack():
"""测试 XSS 攻击是否被正确处理"""
data = {
"messages": [{"role": "user", "content": "<script>alert('XSS');</script>"}], # XSS 攻击
"stream": False,
"max_tokens": 50,
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert "message" in resp["choices"][0], "回复中未包含消息内容"
assert "content" in resp["choices"][0]["message"], "回复中未包含内容字段"
response_content = resp["choices"][0]["message"]["content"]
assert len(response_content) > 0, "XSS 攻击未被正确处理"
def test_stop_empty_string():
"""测试 stop 参数为空字符串时的行为"""
data = {
"stream": False,
"messages": [{"role": "user", "content": "非洲的首都是?"}],
"max_tokens": 10,
"stop": "", # 空字符串
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert resp.get("object") == "chat.completion", "应返回 chat.completion 对象"
assert len(resp.get("choices", [])[0].get("message", {}).get("content", "")) > 0, "应生成有效文本"
def test_stop_multiple_strings():
"""测试 stop 参数为多个字符串时的行为"""
data = {
"stream": False,
"messages": [{"role": "user", "content": "非洲的首都是?"}],
"max_tokens": 50,
"stop": ["", "", ""], # 多个停止条件
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert resp.get("object") == "chat.completion", "应返回 chat.completion 对象"
generated_text = resp.get("choices")[0].get("message", {}).get("content", "")
assert any(stop in generated_text for stop in data["stop"]), "生成文本应包含 stop 序列之一"
def test_stop_with_special_characters():
"""测试 stop 参数为包含特殊字符的字符串时的行为"""
data = {
"stream": False,
"messages": [{"role": "user", "content": "非洲的首都是?"}],
"max_tokens": 50,
"stop": "!@#$%^&*()", # 包含特殊字符
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert resp.get("object") == "chat.completion", "应返回 chat.completion 对象"
generated_text = resp.get("choices")[0].get("message", {}).get("content", "")
assert any(char in generated_text for char in data["stop"]), "生成文本应包含 stop 序列中的特殊字符之一"
def test_stop_with_newlines():
"""测试 stop 参数为包含换行符的字符串时的行为"""
data = {
"stream": False,
"messages": [{"role": "user", "content": "非洲的首都是?"}],
"max_tokens": 50,
"stop": "\n\n", # 包含换行符
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert resp.get("object") == "chat.completion", "应返回 chat.completion 对象"
generated_text = resp.get("choices")[0].get("message", {}).get("content", "")
assert data["stop"] in generated_text, "生成文本应包含 stop 序列"
def test_model_empty():
"""model 参数为空,不影响服务"""
data = {
"messages": [
{
"role": "user",
"content": "非洲的首都是?",
}
],
"stream": False,
"max_tokens": 10,
"model": "", # 空模型
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert resp.get("object") == "chat.completion", "应返回 chat.completion 对象"
response_content = resp["choices"][0]["message"]["content"]
assert len(response_content) > 0, "模型名为空,未正常生成回复"
def test_model_invalid():
"""model 参数为不存在的模型,不影响服务"""
data = {
"messages": [
{
"role": "user",
"content": "非洲的首都是?",
}
],
"stream": False,
"max_tokens": 10,
"model": "non-existent-model", # 不存在的模型
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert resp.get("object") == "chat.completion", "不存在的 model 应触发校验异常"
assert "non-existent-model" in resp.get("model"), "未返回预期的 model 信息"
assert len(resp.get("choices")[0].get("message").get("content")) > 0, "模型名为不存在的 model未正常生成回复"
def test_model_with_special_characters():
"""model 参数为非法格式(例如包含特殊字符),不影响服务"""
data = {
"messages": [
{
"role": "user",
"content": "非洲的首都是?",
}
],
"stream": False,
"max_tokens": 10,
"model": "!@#", # 包含特殊字符
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert resp.get("object") == "chat.completion", "不存在的 model 应触发校验异常"
assert "!@#" in resp.get("model"), "未返回预期的 model 信息"
assert (
len(resp.get("choices")[0].get("message").get("content")) > 0
), "模型名为model 参数为非法格式,未正常生成回复"
def test_max_tokens_negative():
"""max_tokens 为负数,服务应报错"""
data = {
"messages": [
{
"role": "user",
"content": "非洲的首都是?",
}
],
"stream": False,
"max_tokens": -10, # 负数
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert resp.get("detail").get("object") == "error", "max_tokens < 0 未触发校验异常"
assert "max_tokens can be defined [1," in resp.get("detail").get("message"), "未返回预期的 max_tokens 错误信息"
def test_max_tokens_min():
"""测试 max_tokens 达到异常值0 时的行为"""
data = {
"messages": [
{
"role": "user",
"content": "非洲的首都是?",
}
],
"stream": False,
"max_tokens": 0, # 最小值
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert resp.get("detail").get("object") == "error", "max_tokens未0时API未拦截住"
assert "reasoning_max_tokens must be between max_tokens and 1" in resp.get("detail").get(
"message", ""
), "未返回预期的 max_tokens 达到异常值0 的 错误信息"
def test_max_tokens_non_integer():
"""max_tokens 为非整数,服务应报错"""
data = {
"messages": [
{
"role": "user",
"content": "非洲的首都是?",
}
],
"stream": False,
"max_tokens": 10.5, # 非整数
}
payload = build_request_payload(TEMPLATE, data)
resp = send_request(URL, payload).json()
assert (
resp.get("detail")[0].get("msg") == "Input should be a valid integer, got a number with a fractional part"
), "未返回预期的 max_tokens 为非整数的错误信息"