Skip to content

Commit a80b2ff

Browse files
Copilotteddius
andcommitted
Add comprehensive test coverage: security, performance, edge cases, and advanced team tests
Co-authored-by: teddius <[email protected]>
1 parent 91013f7 commit a80b2ff

File tree

4 files changed

+1493
-0
lines changed

4 files changed

+1493
-0
lines changed
Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
#
2+
# Copyright 2025 The InfiniFlow Authors. All Rights Reserved.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
"""Advanced team management tests."""
17+
18+
from __future__ import annotations
19+
20+
import uuid
21+
from typing import Any
22+
23+
import pytest
24+
25+
from common import create_team, create_user
26+
from libs.auth import RAGFlowWebApiAuth
27+
28+
29+
@pytest.mark.p2
30+
class TestTeamAdvanced:
31+
"""Advanced team management tests."""
32+
33+
def test_team_creation_with_custom_models(
34+
self, WebApiAuth: RAGFlowWebApiAuth
35+
) -> None:
36+
"""Test creating team with custom model configurations."""
37+
team_name: str = f"Custom Models Team {uuid.uuid4().hex[:8]}"
38+
39+
# Attempt to create team with custom models
40+
# Note: Model IDs need to be valid and added by the user
41+
team_payload: dict[str, str] = {
42+
"name": team_name,
43+
# These would need to be actual model IDs added by the user
44+
# "llm_id": "custom_llm_id",
45+
# "embd_id": "custom_embd_id",
46+
}
47+
res: dict[str, Any] = create_team(WebApiAuth, team_payload)
48+
49+
# Should succeed with defaults if custom models not specified
50+
assert res["code"] == 0, res
51+
assert res["data"]["name"] == team_name
52+
assert "id" in res["data"]
53+
54+
def test_team_creation_response_structure(
55+
self, WebApiAuth: RAGFlowWebApiAuth
56+
) -> None:
57+
"""Test that team creation returns complete response structure."""
58+
team_name: str = f"Structure Test Team {uuid.uuid4().hex[:8]}"
59+
team_payload: dict[str, str] = {"name": team_name}
60+
res: dict[str, Any] = create_team(WebApiAuth, team_payload)
61+
62+
assert res["code"] == 0, res
63+
assert "data" in res
64+
65+
# Check required fields
66+
required_fields: list[str] = ["id", "name", "owner_id"]
67+
for field in required_fields:
68+
assert field in res["data"], (
69+
f"Missing required field: {field}"
70+
)
71+
72+
assert res["data"]["name"] == team_name
73+
assert len(res["data"]["id"]) > 0, "Team ID should not be empty"
74+
assert len(res["data"]["owner_id"]) > 0, "Owner ID should not be empty"
75+
76+
def test_multiple_teams_same_name_allowed(
77+
self, WebApiAuth: RAGFlowWebApiAuth
78+
) -> None:
79+
"""Test that multiple teams can have the same name."""
80+
team_name: str = f"Duplicate Name {uuid.uuid4().hex[:8]}"
81+
82+
# Create first team
83+
res1: dict[str, Any] = create_team(WebApiAuth, {"name": team_name})
84+
assert res1["code"] == 0, res1
85+
team_id_1: str = res1["data"]["id"]
86+
87+
# Create second team with same name
88+
res2: dict[str, Any] = create_team(WebApiAuth, {"name": team_name})
89+
assert res2["code"] == 0, res2
90+
team_id_2: str = res2["data"]["id"]
91+
92+
# Teams should have different IDs
93+
assert team_id_1 != team_id_2, "Teams should have unique IDs"
94+
assert res1["data"]["name"] == res2["data"]["name"], (
95+
"Both teams should have the same name"
96+
)
97+
98+
def test_team_creation_with_credit_limit(
99+
self, WebApiAuth: RAGFlowWebApiAuth
100+
) -> None:
101+
"""Test creating team with custom credit limit."""
102+
team_name: str = f"Credit Test Team {uuid.uuid4().hex[:8]}"
103+
custom_credit: int = 1000
104+
105+
team_payload: dict[str, Any] = {
106+
"name": team_name,
107+
"credit": custom_credit,
108+
}
109+
res: dict[str, Any] = create_team(WebApiAuth, team_payload)
110+
111+
# Should succeed
112+
assert res["code"] == 0, res
113+
# Note: Credit may not be in response, but should be set internally
114+
115+
def test_team_name_with_special_characters(
116+
self, WebApiAuth: RAGFlowWebApiAuth
117+
) -> None:
118+
"""Test team names with special characters."""
119+
special_names: list[str] = [
120+
f"Team-{uuid.uuid4().hex[:4]}_Test!",
121+
f"Team & Co. {uuid.uuid4().hex[:4]}",
122+
f"Team @{uuid.uuid4().hex[:4]}",
123+
f"团队{uuid.uuid4().hex[:4]}", # Unicode
124+
]
125+
126+
for name in special_names:
127+
res: dict[str, Any] = create_team(WebApiAuth, {"name": name})
128+
# Should either accept or reject with clear message
129+
if res["code"] == 0:
130+
assert res["data"]["name"] == name, (
131+
f"Team name should be preserved: {name}"
132+
)
133+
# If rejected, should have clear error
134+
# (Current implementation accepts special chars)
135+
136+
def test_team_creation_default_owner(
137+
self, WebApiAuth: RAGFlowWebApiAuth
138+
) -> None:
139+
"""Test that team creator is set as owner by default."""
140+
team_name: str = f"Owner Test Team {uuid.uuid4().hex[:8]}"
141+
res: dict[str, Any] = create_team(WebApiAuth, {"name": team_name})
142+
143+
assert res["code"] == 0, res
144+
assert "owner_id" in res["data"], "Owner ID should be in response"
145+
# Owner should be the authenticated user
146+
# (Cannot verify without knowing WebApiAuth user ID)
147+
148+
def test_concurrent_team_creation(
149+
self, WebApiAuth: RAGFlowWebApiAuth
150+
) -> None:
151+
"""Test concurrent team creation."""
152+
import concurrent.futures
153+
154+
def create_test_team(index: int) -> dict[str, Any]:
155+
team_name: str = f"Concurrent Team {index}_{uuid.uuid4().hex[:8]}"
156+
return create_team(WebApiAuth, {"name": team_name})
157+
158+
# Create 10 teams concurrently
159+
count: int = 10
160+
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
161+
futures: list[concurrent.futures.Future[dict[str, Any]]] = [
162+
executor.submit(create_test_team, i) for i in range(count)
163+
]
164+
results: list[dict[str, Any]] = [
165+
f.result() for f in concurrent.futures.as_completed(futures)
166+
]
167+
168+
# All should succeed
169+
success_count: int = sum(1 for r in results if r["code"] == 0)
170+
assert success_count == count, (
171+
f"Expected {count} successful team creations, got {success_count}"
172+
)
173+
174+
# All should have unique IDs
175+
team_ids: list[str] = [r["data"]["id"] for r in results if r["code"] == 0]
176+
assert len(team_ids) == len(set(team_ids)), (
177+
"All team IDs should be unique"
178+
)
179+
180+
def test_team_with_invalid_model_id(
181+
self, WebApiAuth: RAGFlowWebApiAuth
182+
) -> None:
183+
"""Test team creation with invalid model ID."""
184+
team_name: str = f"Invalid Model Team {uuid.uuid4().hex[:8]}"
185+
team_payload: dict[str, str] = {
186+
"name": team_name,
187+
"llm_id": "invalid_nonexistent_model_id_12345",
188+
}
189+
res: dict[str, Any] = create_team(WebApiAuth, team_payload)
190+
191+
# Should reject with clear error message
192+
assert res["code"] != 0, "Invalid model ID should be rejected"
193+
assert (
194+
"model" in res["message"].lower()
195+
or "not found" in res["message"].lower()
196+
or "invalid" in res["message"].lower()
197+
), "Error message should mention model issue"
198+
199+
def test_team_creation_with_negative_credit(
200+
self, WebApiAuth: RAGFlowWebApiAuth
201+
) -> None:
202+
"""Test team creation with negative credit."""
203+
team_name: str = f"Negative Credit Team {uuid.uuid4().hex[:8]}"
204+
team_payload: dict[str, Any] = {
205+
"name": team_name,
206+
"credit": -100,
207+
}
208+
res: dict[str, Any] = create_team(WebApiAuth, team_payload)
209+
210+
# Should reject negative credit
211+
assert res["code"] != 0, "Negative credit should be rejected"
212+
assert "credit" in res["message"].lower(), (
213+
"Error message should mention credit"
214+
)
215+
216+
def test_team_creation_empty_json_payload(
217+
self, WebApiAuth: RAGFlowWebApiAuth
218+
) -> None:
219+
"""Test team creation with completely empty payload."""
220+
res: dict[str, Any] = create_team(WebApiAuth, {})
221+
222+
# Should reject with clear error
223+
assert res["code"] != 0, "Empty payload should be rejected"
224+
assert (
225+
"name" in res["message"].lower()
226+
or "required" in res["message"].lower()
227+
), "Error should mention missing name"
228+
229+
def test_team_unicode_name(
230+
self, WebApiAuth: RAGFlowWebApiAuth
231+
) -> None:
232+
"""Test team creation with full unicode name."""
233+
unicode_names: list[str] = [
234+
f"团队{uuid.uuid4().hex[:4]}", # Chinese
235+
f"チーム{uuid.uuid4().hex[:4]}", # Japanese
236+
f"Команда{uuid.uuid4().hex[:4]}", # Russian
237+
f"فريق{uuid.uuid4().hex[:4]}", # Arabic (RTL)
238+
f"😀🎉{uuid.uuid4().hex[:4]}", # Emoji
239+
]
240+
241+
for name in unicode_names:
242+
res: dict[str, Any] = create_team(WebApiAuth, {"name": name})
243+
244+
# Should handle unicode properly
245+
if res["code"] == 0:
246+
# Verify unicode is preserved (may be normalized)
247+
assert len(res["data"]["name"]) > 0, (
248+
"Team name should not be empty after unicode"
249+
)
250+
251+
@pytest.mark.p3
252+
def test_team_creation_with_all_optional_params(
253+
self, WebApiAuth: RAGFlowWebApiAuth
254+
) -> None:
255+
"""Test team creation with all optional parameters."""
256+
team_name: str = f"Full Params Team {uuid.uuid4().hex[:8]}"
257+
team_payload: dict[str, Any] = {
258+
"name": team_name,
259+
"credit": 2000,
260+
# Note: Model IDs would need to be valid
261+
# "llm_id": "valid_llm_id",
262+
# "embd_id": "valid_embd_id",
263+
# "asr_id": "valid_asr_id",
264+
# "parser_ids": "valid_parser_ids",
265+
# "img2txt_id": "valid_img2txt_id",
266+
# "rerank_id": "valid_rerank_id",
267+
}
268+
res: dict[str, Any] = create_team(WebApiAuth, team_payload)
269+
270+
# Should succeed
271+
assert res["code"] == 0, res
272+
assert res["data"]["name"] == team_name
273+
274+
@pytest.mark.p3
275+
def test_team_max_name_length(
276+
self, WebApiAuth: RAGFlowWebApiAuth
277+
) -> None:
278+
"""Test team with maximum allowed name length."""
279+
# API spec says max 100 characters
280+
max_name: str = "A" * 100
281+
res: dict[str, Any] = create_team(WebApiAuth, {"name": max_name})
282+
283+
# Should accept 100 characters
284+
assert res["code"] == 0, "100-character name should be accepted"
285+
assert res["data"]["name"] == max_name
286+
287+
@pytest.mark.p3
288+
def test_team_name_just_over_limit(
289+
self, WebApiAuth: RAGFlowWebApiAuth
290+
) -> None:
291+
"""Test team with name just over limit."""
292+
# 101 characters (1 over limit)
293+
long_name: str = "A" * 101
294+
res: dict[str, Any] = create_team(WebApiAuth, {"name": long_name})
295+
296+
# Should reject
297+
assert res["code"] != 0, "101-character name should be rejected"
298+
assert (
299+
"100" in res["message"]
300+
or "length" in res["message"].lower()
301+
or "long" in res["message"].lower()
302+
), "Error should mention length limit"
303+
304+
@pytest.mark.p3
305+
def test_team_creation_idempotency(
306+
self, WebApiAuth: RAGFlowWebApiAuth
307+
) -> None:
308+
"""Test that repeated team creation creates separate teams."""
309+
team_name: str = f"Idempotency Test {uuid.uuid4().hex[:8]}"
310+
payload: dict[str, str] = {"name": team_name}
311+
312+
# Create same team twice
313+
res1: dict[str, Any] = create_team(WebApiAuth, payload)
314+
res2: dict[str, Any] = create_team(WebApiAuth, payload)
315+
316+
# Both should succeed and create different teams
317+
assert res1["code"] == 0, res1
318+
assert res2["code"] == 0, res2
319+
assert res1["data"]["id"] != res2["data"]["id"], (
320+
"Should create different teams, not be idempotent"
321+
)
322+
323+
@pytest.mark.p3
324+
def test_team_with_parser_ids(
325+
self, WebApiAuth: RAGFlowWebApiAuth
326+
) -> None:
327+
"""Test team creation with custom parser IDs."""
328+
team_name: str = f"Parser Test {uuid.uuid4().hex[:8]}"
329+
# parser_ids is typically a comma-separated string
330+
team_payload: dict[str, str] = {
331+
"name": team_name,
332+
"parser_ids": "naive,qa,table,paper,book,laws,presentation,manual,wiki",
333+
}
334+
res: dict[str, Any] = create_team(WebApiAuth, team_payload)
335+
336+
# Should accept valid parser IDs
337+
assert res["code"] == 0, res

0 commit comments

Comments
 (0)