Skip to content

Commit 3fbd569

Browse files
committed
project integration tests
1 parent 98e16f6 commit 3fbd569

File tree

4 files changed

+425
-3
lines changed

4 files changed

+425
-3
lines changed

backend/onyx/server/features/projects/api.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,8 @@ def create_project(
5656
user: User | None = Depends(current_user),
5757
db_session: Session = Depends(get_session),
5858
) -> UserProjectSnapshot:
59+
if name == "":
60+
raise HTTPException(status_code=400, detail="Project name cannot be empty")
5961
user_id = user.id if user is not None else None
6062
project = UserProject(name=name, user_id=user_id)
6163
db_session.add(project)
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
from typing import List
2+
3+
import requests
4+
5+
from onyx.server.features.projects.models import CategorizedFilesSnapshot
6+
from onyx.server.features.projects.models import UserFileSnapshot
7+
from onyx.server.features.projects.models import UserProjectSnapshot
8+
from tests.integration.common_utils.constants import API_SERVER_URL
9+
from tests.integration.common_utils.constants import GENERAL_HEADERS
10+
from tests.integration.common_utils.test_models import DATestUser
11+
12+
13+
class ProjectManager:
14+
@staticmethod
15+
def create(
16+
name: str,
17+
user_performing_action: DATestUser,
18+
) -> UserProjectSnapshot:
19+
"""Create a new project via API."""
20+
response = requests.post(
21+
f"{API_SERVER_URL}/user/projects/create",
22+
params={"name": name},
23+
headers=user_performing_action.headers or GENERAL_HEADERS,
24+
)
25+
response.raise_for_status()
26+
return UserProjectSnapshot.model_validate(response.json())
27+
28+
@staticmethod
29+
def get_all(
30+
user_performing_action: DATestUser,
31+
) -> List[UserProjectSnapshot]:
32+
"""Get all projects for a user via API."""
33+
response = requests.get(
34+
f"{API_SERVER_URL}/user/projects/",
35+
headers=user_performing_action.headers or GENERAL_HEADERS,
36+
)
37+
response.raise_for_status()
38+
return [UserProjectSnapshot.model_validate(obj) for obj in response.json()]
39+
40+
@staticmethod
41+
def delete(
42+
project_id: int,
43+
user_performing_action: DATestUser,
44+
) -> bool:
45+
"""Delete a project via API."""
46+
response = requests.delete(
47+
f"{API_SERVER_URL}/user/projects/{project_id}",
48+
headers=user_performing_action.headers or GENERAL_HEADERS,
49+
)
50+
return response.status_code == 204
51+
52+
@staticmethod
53+
def verify_deleted(
54+
project_id: int,
55+
user_performing_action: DATestUser,
56+
) -> bool:
57+
"""Verify that a project has been deleted by ensuring it's not in list."""
58+
response = requests.get(
59+
f"{API_SERVER_URL}/user/projects/",
60+
headers=user_performing_action.headers or GENERAL_HEADERS,
61+
)
62+
response.raise_for_status()
63+
projects = [UserProjectSnapshot.model_validate(obj) for obj in response.json()]
64+
return all(p.id != project_id for p in projects)
65+
66+
@staticmethod
67+
def verify_files_unlinked(
68+
project_id: int,
69+
user_performing_action: DATestUser | None = None,
70+
) -> bool:
71+
"""Verify that all files have been unlinked from the project via API."""
72+
response = requests.get(
73+
f"{API_SERVER_URL}/user/projects/files/{project_id}",
74+
headers=(
75+
user_performing_action.headers
76+
if user_performing_action
77+
else GENERAL_HEADERS
78+
),
79+
)
80+
if response.status_code == 404:
81+
return True
82+
if not response.ok:
83+
return False
84+
files = [UserFileSnapshot.model_validate(obj) for obj in response.json()]
85+
return len(files) == 0
86+
87+
@staticmethod
88+
def verify_chat_sessions_unlinked(
89+
project_id: int,
90+
user_performing_action: DATestUser | None = None,
91+
) -> bool:
92+
"""Verify that all chat sessions have been unlinked from the project via API."""
93+
response = requests.get(
94+
f"{API_SERVER_URL}/user/projects/{project_id}",
95+
headers=(
96+
user_performing_action.headers
97+
if user_performing_action
98+
else GENERAL_HEADERS
99+
),
100+
)
101+
if response.status_code == 404:
102+
return True
103+
if not response.ok:
104+
return False
105+
try:
106+
project = UserProjectSnapshot.model_validate(response.json())
107+
chat_sessions = getattr(project, "chat_sessions", [])
108+
return len(chat_sessions or []) == 0
109+
except Exception:
110+
# If response doesn't include chat_sessions, assume unlinked
111+
return True
112+
113+
@staticmethod
114+
def upload_files(
115+
project_id: int,
116+
files: List[tuple[str, bytes]], # List of (filename, content) tuples
117+
user_performing_action: DATestUser,
118+
) -> CategorizedFilesSnapshot:
119+
"""Upload files to a project via API."""
120+
# Build multipart form-data
121+
files_payload = [
122+
(
123+
"files",
124+
(filename, content, "text/plain"),
125+
)
126+
for filename, content in files
127+
]
128+
129+
data = {"project_id": str(project_id)} if project_id is not None else {}
130+
131+
# Let requests set Content-Type boundary by not overriding header
132+
headers = dict(user_performing_action.headers or {})
133+
headers.pop("Content-Type", None)
134+
135+
response = requests.post(
136+
f"{API_SERVER_URL}/user/projects/file/upload",
137+
data=data,
138+
files=files_payload,
139+
headers=headers,
140+
)
141+
response.raise_for_status()
142+
return CategorizedFilesSnapshot.model_validate(response.json())
143+
144+
@staticmethod
145+
def get_project_files(
146+
project_id: int,
147+
user_performing_action: DATestUser | None = None,
148+
) -> List[UserFileSnapshot]:
149+
"""Get all files associated with a project via API."""
150+
response = requests.get(
151+
f"{API_SERVER_URL}/user/projects/files/{project_id}",
152+
headers=(
153+
user_performing_action.headers
154+
if user_performing_action
155+
else GENERAL_HEADERS
156+
),
157+
)
158+
if response.status_code == 404:
159+
return []
160+
response.raise_for_status()
161+
return [UserFileSnapshot.model_validate(obj) for obj in response.json()]
162+
163+
@staticmethod
164+
def set_instructions(
165+
project_id: int,
166+
instructions: str,
167+
user_performing_action: DATestUser,
168+
) -> str:
169+
"""Set project instructions via API."""
170+
response = requests.post(
171+
f"{API_SERVER_URL}/user/projects/{project_id}/instructions",
172+
json={"instructions": instructions},
173+
headers=user_performing_action.headers or GENERAL_HEADERS,
174+
)
175+
response.raise_for_status()
176+
return (response.json() or {}).get("instructions")

0 commit comments

Comments
 (0)