Skip to content

Commit a149e21

Browse files
authored
Shore up multi tenant tests (#4484)
* update * fix * finalize` * remove unnecessary prints * fix
1 parent 68c6c1f commit a149e21

File tree

10 files changed

+272
-109
lines changed

10 files changed

+272
-109
lines changed

backend/tests/integration/common_utils/managers/chat.py

+9-5
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,19 @@ def send_message(
7878
use_existing_user_message=use_existing_user_message,
7979
)
8080

81+
headers = (
82+
user_performing_action.headers
83+
if user_performing_action
84+
else GENERAL_HEADERS
85+
)
86+
cookies = user_performing_action.cookies if user_performing_action else None
87+
8188
response = requests.post(
8289
f"{API_SERVER_URL}/chat/send-message",
8390
json=chat_message_req.model_dump(),
84-
headers=(
85-
user_performing_action.headers
86-
if user_performing_action
87-
else GENERAL_HEADERS
88-
),
91+
headers=headers,
8992
stream=True,
93+
cookies=cookies,
9094
)
9195

9296
return ChatSessionManager.analyze_response(response)

backend/tests/integration/multitenant_tests/invitation/invite_various_organizations.py renamed to backend/tests/integration/multitenant_tests/invitation/test_user_invitation.py

+45-15
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@
66
INVITED_BASIC_USER_EMAIL = "[email protected]"
77

88

9-
def test_user_invitation_flow(reset_multitenant: None) -> None:
9+
def test_admin_can_invite_users(reset_multitenant: None) -> None:
10+
"""Test that an admin can invite both registered and non-registered users."""
1011
# Create first user (admin)
1112
admin_user: DATestUser = UserManager.create(name="admin")
1213
assert UserManager.is_role(admin_user, UserRole.ADMIN)
@@ -19,16 +20,44 @@ def test_user_invitation_flow(reset_multitenant: None) -> None:
1920
UserManager.invite_user(invited_user.email, admin_user)
2021
UserManager.invite_user(INVITED_BASIC_USER_EMAIL, admin_user)
2122

23+
# Verify users are in the invited users list
24+
invited_users = UserManager.get_invited_users(admin_user)
25+
assert invited_user.email in [
26+
user.email for user in invited_users
27+
], f"User {invited_user.email} not found in invited users list"
28+
29+
30+
def test_non_registered_user_gets_basic_role(reset_multitenant: None) -> None:
31+
"""Test that a non-registered user gets a BASIC role when they register after being invited."""
32+
# Create admin user
33+
admin_user: DATestUser = UserManager.create(name="admin")
34+
assert UserManager.is_role(admin_user, UserRole.ADMIN)
35+
36+
# Admin user invites a non-registered user
37+
UserManager.invite_user(INVITED_BASIC_USER_EMAIL, admin_user)
38+
39+
# Non-registered user registers
2240
invited_basic_user: DATestUser = UserManager.create(
2341
name=INVITED_BASIC_USER, email=INVITED_BASIC_USER_EMAIL
2442
)
2543
assert UserManager.is_role(invited_basic_user, UserRole.BASIC)
2644

27-
# Verify the user is in the invited users list
28-
invited_users = UserManager.get_invited_users(admin_user)
29-
assert invited_user.email in [
30-
user.email for user in invited_users
31-
], f"User {invited_user.email} not found in invited users list"
45+
46+
def test_user_can_accept_invitation(reset_multitenant: None) -> None:
47+
"""Test that a user can accept an invitation and join the organization with BASIC role."""
48+
# Create admin user
49+
admin_user: DATestUser = UserManager.create(name="admin")
50+
assert UserManager.is_role(admin_user, UserRole.ADMIN)
51+
52+
# Create a user to be invited
53+
invited_user_email = "[email protected]"
54+
55+
# User registers with the same email as the invitation
56+
invited_user: DATestUser = UserManager.create(
57+
name="invited_user", email=invited_user_email
58+
)
59+
# Admin user invites the user
60+
UserManager.invite_user(invited_user_email, admin_user)
3261

3362
# Get user info to check tenant information
3463
user_info = UserManager.get_user_info(invited_user)
@@ -41,16 +70,17 @@ def test_user_invitation_flow(reset_multitenant: None) -> None:
4170
)
4271
assert invited_tenant_id is not None, "Expected to find an invitation tenant_id"
4372

73+
# User accepts invitation
4474
UserManager.accept_invitation(invited_tenant_id, invited_user)
4575

46-
# Get updated user info after accepting invitation
47-
updated_user_info = UserManager.get_user_info(invited_user)
76+
# User needs to reauthenticate after accepting invitation
77+
# Simulate this by creating a new user instance with the same credentials
78+
authenticated_user: DATestUser = UserManager.create(
79+
name="invited_user", email=invited_user_email
80+
)
4881

49-
# Verify the user is no longer in the invited users list
50-
updated_invited_users = UserManager.get_invited_users(admin_user)
51-
assert invited_user.email not in [
52-
user.email for user in updated_invited_users
53-
], f"User {invited_user.email} should not be in invited users list after accepting"
82+
# Get updated user info after accepting invitation and reauthenticating
83+
updated_user_info = UserManager.get_user_info(authenticated_user)
5484

5585
# Verify the user has BASIC role in the organization
5686
assert (
@@ -64,7 +94,7 @@ def test_user_invitation_flow(reset_multitenant: None) -> None:
6494

6595
# Check if the invited user is in the list of users with BASIC role
6696
invited_user_emails = [user.email for user in user_page.items]
67-
assert invited_user.email in invited_user_emails, (
68-
f"User {invited_user.email} not found in the list of basic users "
97+
assert invited_user_email in invited_user_emails, (
98+
f"User {invited_user_email} not found in the list of basic users "
6999
f"in the organization. Available users: {invited_user_emails}"
70100
)

backend/tests/integration/multitenant_tests/syncing/test_search_permissions.py

+114-27
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Any
2+
13
from onyx.db.models import UserRole
24
from tests.integration.common_utils.managers.api_key import APIKeyManager
35
from tests.integration.common_utils.managers.cc_pair import CCPairManager
@@ -11,12 +13,12 @@
1113
from tests.integration.common_utils.test_models import DATestUser
1214

1315

14-
def test_multi_tenant_access_control(reset_multitenant: None) -> None:
15-
# Creating an admin user (first user created is automatically an admin and also proviions the tenant
16+
def setup_test_tenants(reset_multitenant: None) -> dict[str, Any]:
17+
"""Helper function to set up test tenants with documents and users."""
18+
# Creating an admin user for Tenant 1
1619
admin_user1: DATestUser = UserManager.create(
1720
1821
)
19-
2022
assert UserManager.is_role(admin_user1, UserRole.ADMIN)
2123

2224
# Create Tenant 2 and its Admin User
@@ -35,6 +37,16 @@ def test_multi_tenant_access_control(reset_multitenant: None) -> None:
3537
api_key_1.headers.update(admin_user1.headers)
3638
LLMProviderManager.create(user_performing_action=admin_user1)
3739

40+
# Create connectors for Tenant 2
41+
cc_pair_2: DATestCCPair = CCPairManager.create_from_scratch(
42+
user_performing_action=admin_user2,
43+
)
44+
api_key_2: DATestAPIKey = APIKeyManager.create(
45+
user_performing_action=admin_user2,
46+
)
47+
api_key_2.headers.update(admin_user2.headers)
48+
LLMProviderManager.create(user_performing_action=admin_user2)
49+
3850
# Seed documents for Tenant 1
3951
cc_pair_1.documents = []
4052
doc1_tenant1 = DocumentManager.seed_doc_with_content(
@@ -49,16 +61,6 @@ def test_multi_tenant_access_control(reset_multitenant: None) -> None:
4961
)
5062
cc_pair_1.documents.extend([doc1_tenant1, doc2_tenant1])
5163

52-
# Create connectors for Tenant 2
53-
cc_pair_2: DATestCCPair = CCPairManager.create_from_scratch(
54-
user_performing_action=admin_user2,
55-
)
56-
api_key_2: DATestAPIKey = APIKeyManager.create(
57-
user_performing_action=admin_user2,
58-
)
59-
api_key_2.headers.update(admin_user2.headers)
60-
LLMProviderManager.create(user_performing_action=admin_user2)
61-
6264
# Seed documents for Tenant 2
6365
cc_pair_2.documents = []
6466
doc1_tenant2 = DocumentManager.seed_doc_with_content(
@@ -84,21 +86,36 @@ def test_multi_tenant_access_control(reset_multitenant: None) -> None:
8486
user_performing_action=admin_user2
8587
)
8688

89+
return {
90+
"admin_user1": admin_user1,
91+
"admin_user2": admin_user2,
92+
"chat_session1": chat_session1,
93+
"chat_session2": chat_session2,
94+
"tenant1_doc_ids": tenant1_doc_ids,
95+
"tenant2_doc_ids": tenant2_doc_ids,
96+
}
97+
98+
99+
def test_tenant1_can_access_own_documents(reset_multitenant: None) -> None:
100+
"""Test that Tenant 1 can access its own documents but not Tenant 2's."""
101+
test_data = setup_test_tenants(reset_multitenant)
102+
87103
# User 1 sends a message and gets a response
88104
response1 = ChatSessionManager.send_message(
89-
chat_session_id=chat_session1.id,
105+
chat_session_id=test_data["chat_session1"].id,
90106
message="What is in Tenant 1's documents?",
91-
user_performing_action=admin_user1,
107+
user_performing_action=test_data["admin_user1"],
92108
)
109+
93110
# Assert that the search tool was used
94111
assert response1.tool_name == "run_search"
95112

96113
response_doc_ids = {doc["document_id"] for doc in response1.tool_result or []}
97-
assert tenant1_doc_ids.issubset(
114+
assert test_data["tenant1_doc_ids"].issubset(
98115
response_doc_ids
99116
), "Not all Tenant 1 document IDs are in the response"
100117
assert not response_doc_ids.intersection(
101-
tenant2_doc_ids
118+
test_data["tenant2_doc_ids"]
102119
), "Tenant 2 document IDs should not be in the response"
103120

104121
# Assert that the contents are correct
@@ -107,21 +124,28 @@ def test_multi_tenant_access_control(reset_multitenant: None) -> None:
107124
for doc in response1.tool_result or []
108125
), "Tenant 1 Document Content not found in any document"
109126

127+
128+
def test_tenant2_can_access_own_documents(reset_multitenant: None) -> None:
129+
"""Test that Tenant 2 can access its own documents but not Tenant 1's."""
130+
test_data = setup_test_tenants(reset_multitenant)
131+
110132
# User 2 sends a message and gets a response
111133
response2 = ChatSessionManager.send_message(
112-
chat_session_id=chat_session2.id,
134+
chat_session_id=test_data["chat_session2"].id,
113135
message="What is in Tenant 2's documents?",
114-
user_performing_action=admin_user2,
136+
user_performing_action=test_data["admin_user2"],
115137
)
138+
116139
# Assert that the search tool was used
117140
assert response2.tool_name == "run_search"
141+
118142
# Assert that the tool_result contains Tenant 2's documents
119143
response_doc_ids = {doc["document_id"] for doc in response2.tool_result or []}
120-
assert tenant2_doc_ids.issubset(
144+
assert test_data["tenant2_doc_ids"].issubset(
121145
response_doc_ids
122146
), "Not all Tenant 2 document IDs are in the response"
123147
assert not response_doc_ids.intersection(
124-
tenant1_doc_ids
148+
test_data["tenant1_doc_ids"]
125149
), "Tenant 1 document IDs should not be in the response"
126150

127151
# Assert that the contents are correct
@@ -130,28 +154,91 @@ def test_multi_tenant_access_control(reset_multitenant: None) -> None:
130154
for doc in response2.tool_result or []
131155
), "Tenant 2 Document Content not found in any document"
132156

157+
158+
def test_tenant1_cannot_access_tenant2_documents(reset_multitenant: None) -> None:
159+
"""Test that Tenant 1 cannot access Tenant 2's documents."""
160+
test_data = setup_test_tenants(reset_multitenant)
161+
133162
# User 1 tries to access Tenant 2's documents
134163
response_cross = ChatSessionManager.send_message(
135-
chat_session_id=chat_session1.id,
164+
chat_session_id=test_data["chat_session1"].id,
136165
message="What is in Tenant 2's documents?",
137-
user_performing_action=admin_user1,
166+
user_performing_action=test_data["admin_user1"],
138167
)
168+
139169
# Assert that the search tool was used
140170
assert response_cross.tool_name == "run_search"
171+
141172
# Assert that the tool_result is empty or does not contain Tenant 2's documents
142173
response_doc_ids = {doc["document_id"] for doc in response_cross.tool_result or []}
174+
143175
# Ensure none of Tenant 2's document IDs are in the response
144-
assert not response_doc_ids.intersection(tenant2_doc_ids)
176+
assert not response_doc_ids.intersection(test_data["tenant2_doc_ids"])
177+
178+
179+
def test_tenant2_cannot_access_tenant1_documents(reset_multitenant: None) -> None:
180+
"""Test that Tenant 2 cannot access Tenant 1's documents."""
181+
test_data = setup_test_tenants(reset_multitenant)
145182

146183
# User 2 tries to access Tenant 1's documents
147184
response_cross2 = ChatSessionManager.send_message(
148-
chat_session_id=chat_session2.id,
185+
chat_session_id=test_data["chat_session2"].id,
149186
message="What is in Tenant 1's documents?",
150-
user_performing_action=admin_user2,
187+
user_performing_action=test_data["admin_user2"],
151188
)
189+
152190
# Assert that the search tool was used
153191
assert response_cross2.tool_name == "run_search"
192+
154193
# Assert that the tool_result is empty or does not contain Tenant 1's documents
155194
response_doc_ids = {doc["document_id"] for doc in response_cross2.tool_result or []}
195+
156196
# Ensure none of Tenant 1's document IDs are in the response
157-
assert not response_doc_ids.intersection(tenant1_doc_ids)
197+
assert not response_doc_ids.intersection(test_data["tenant1_doc_ids"])
198+
199+
200+
def test_multi_tenant_access_control(reset_multitenant: None) -> None:
201+
"""Legacy test for multi-tenant access control."""
202+
test_data = setup_test_tenants(reset_multitenant)
203+
204+
# User 1 sends a message and gets a response with only Tenant 1's documents
205+
response1 = ChatSessionManager.send_message(
206+
chat_session_id=test_data["chat_session1"].id,
207+
message="What is in Tenant 1's documents?",
208+
user_performing_action=test_data["admin_user1"],
209+
)
210+
assert response1.tool_name == "run_search"
211+
response_doc_ids = {doc["document_id"] for doc in response1.tool_result or []}
212+
assert test_data["tenant1_doc_ids"].issubset(response_doc_ids)
213+
assert not response_doc_ids.intersection(test_data["tenant2_doc_ids"])
214+
215+
# User 2 sends a message and gets a response with only Tenant 2's documents
216+
response2 = ChatSessionManager.send_message(
217+
chat_session_id=test_data["chat_session2"].id,
218+
message="What is in Tenant 2's documents?",
219+
user_performing_action=test_data["admin_user2"],
220+
)
221+
assert response2.tool_name == "run_search"
222+
response_doc_ids = {doc["document_id"] for doc in response2.tool_result or []}
223+
assert test_data["tenant2_doc_ids"].issubset(response_doc_ids)
224+
assert not response_doc_ids.intersection(test_data["tenant1_doc_ids"])
225+
226+
# User 1 tries to access Tenant 2's documents and fails
227+
response_cross = ChatSessionManager.send_message(
228+
chat_session_id=test_data["chat_session1"].id,
229+
message="What is in Tenant 2's documents?",
230+
user_performing_action=test_data["admin_user1"],
231+
)
232+
assert response_cross.tool_name == "run_search"
233+
response_doc_ids = {doc["document_id"] for doc in response_cross.tool_result or []}
234+
assert not response_doc_ids.intersection(test_data["tenant2_doc_ids"])
235+
236+
# User 2 tries to access Tenant 1's documents and fails
237+
response_cross2 = ChatSessionManager.send_message(
238+
chat_session_id=test_data["chat_session2"].id,
239+
message="What is in Tenant 1's documents?",
240+
user_performing_action=test_data["admin_user2"],
241+
)
242+
assert response_cross2.tool_name == "run_search"
243+
response_doc_ids = {doc["document_id"] for doc in response_cross2.tool_result or []}
244+
assert not response_doc_ids.intersection(test_data["tenant1_doc_ids"])

0 commit comments

Comments
 (0)