botrun-flow-lang 5.12.263__py3-none-any.whl → 5.12.264__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- botrun_flow_lang/api/auth_api.py +39 -39
- botrun_flow_lang/api/auth_utils.py +183 -183
- botrun_flow_lang/api/botrun_back_api.py +65 -65
- botrun_flow_lang/api/flow_api.py +3 -3
- botrun_flow_lang/api/hatch_api.py +508 -508
- botrun_flow_lang/api/langgraph_api.py +811 -811
- botrun_flow_lang/api/line_bot_api.py +1484 -1484
- botrun_flow_lang/api/model_api.py +300 -300
- botrun_flow_lang/api/rate_limit_api.py +32 -32
- botrun_flow_lang/api/routes.py +79 -79
- botrun_flow_lang/api/search_api.py +53 -53
- botrun_flow_lang/api/storage_api.py +395 -395
- botrun_flow_lang/api/subsidy_api.py +290 -290
- botrun_flow_lang/api/subsidy_api_system_prompt.txt +109 -109
- botrun_flow_lang/api/user_setting_api.py +70 -70
- botrun_flow_lang/api/version_api.py +31 -31
- botrun_flow_lang/api/youtube_api.py +26 -26
- botrun_flow_lang/constants.py +13 -13
- botrun_flow_lang/langgraph_agents/agents/agent_runner.py +178 -178
- botrun_flow_lang/langgraph_agents/agents/agent_tools/step_planner.py +77 -77
- botrun_flow_lang/langgraph_agents/agents/checkpointer/firestore_checkpointer.py +666 -666
- botrun_flow_lang/langgraph_agents/agents/gov_researcher/GOV_RESEARCHER_PRD.md +192 -192
- botrun_flow_lang/langgraph_agents/agents/gov_researcher/gemini_subsidy_graph.py +460 -460
- botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_2_graph.py +1002 -1002
- botrun_flow_lang/langgraph_agents/agents/gov_researcher/gov_researcher_graph.py +822 -822
- botrun_flow_lang/langgraph_agents/agents/langgraph_react_agent.py +723 -723
- botrun_flow_lang/langgraph_agents/agents/search_agent_graph.py +864 -864
- botrun_flow_lang/langgraph_agents/agents/tools/__init__.py +4 -4
- botrun_flow_lang/langgraph_agents/agents/tools/gemini_code_execution.py +376 -376
- botrun_flow_lang/langgraph_agents/agents/util/gemini_grounding.py +66 -66
- botrun_flow_lang/langgraph_agents/agents/util/html_util.py +316 -316
- botrun_flow_lang/langgraph_agents/agents/util/img_util.py +294 -294
- botrun_flow_lang/langgraph_agents/agents/util/local_files.py +419 -419
- botrun_flow_lang/langgraph_agents/agents/util/mermaid_util.py +86 -86
- botrun_flow_lang/langgraph_agents/agents/util/model_utils.py +143 -143
- botrun_flow_lang/langgraph_agents/agents/util/pdf_analyzer.py +486 -486
- botrun_flow_lang/langgraph_agents/agents/util/pdf_cache.py +250 -250
- botrun_flow_lang/langgraph_agents/agents/util/pdf_processor.py +204 -204
- botrun_flow_lang/langgraph_agents/agents/util/perplexity_search.py +464 -464
- botrun_flow_lang/langgraph_agents/agents/util/plotly_util.py +59 -59
- botrun_flow_lang/langgraph_agents/agents/util/tavily_search.py +199 -199
- botrun_flow_lang/langgraph_agents/agents/util/youtube_util.py +90 -90
- botrun_flow_lang/langgraph_agents/cache/langgraph_botrun_cache.py +197 -197
- botrun_flow_lang/llm_agent/llm_agent.py +19 -19
- botrun_flow_lang/llm_agent/llm_agent_util.py +83 -83
- botrun_flow_lang/log/.gitignore +2 -2
- botrun_flow_lang/main.py +61 -61
- botrun_flow_lang/main_fast.py +51 -51
- botrun_flow_lang/mcp_server/__init__.py +10 -10
- botrun_flow_lang/mcp_server/default_mcp.py +744 -744
- botrun_flow_lang/models/nodes/utils.py +205 -205
- botrun_flow_lang/models/token_usage.py +34 -34
- botrun_flow_lang/requirements.txt +21 -21
- botrun_flow_lang/services/base/firestore_base.py +30 -30
- botrun_flow_lang/services/hatch/hatch_factory.py +11 -11
- botrun_flow_lang/services/hatch/hatch_fs_store.py +419 -419
- botrun_flow_lang/services/storage/storage_cs_store.py +206 -206
- botrun_flow_lang/services/storage/storage_factory.py +12 -12
- botrun_flow_lang/services/storage/storage_store.py +65 -65
- botrun_flow_lang/services/user_setting/user_setting_factory.py +9 -9
- botrun_flow_lang/services/user_setting/user_setting_fs_store.py +66 -66
- botrun_flow_lang/static/docs/tools/index.html +926 -926
- botrun_flow_lang/tests/api_functional_tests.py +1525 -1525
- botrun_flow_lang/tests/api_stress_test.py +357 -357
- botrun_flow_lang/tests/shared_hatch_tests.py +333 -333
- botrun_flow_lang/tests/test_botrun_app.py +46 -46
- botrun_flow_lang/tests/test_html_util.py +31 -31
- botrun_flow_lang/tests/test_img_analyzer.py +190 -190
- botrun_flow_lang/tests/test_img_util.py +39 -39
- botrun_flow_lang/tests/test_local_files.py +114 -114
- botrun_flow_lang/tests/test_mermaid_util.py +103 -103
- botrun_flow_lang/tests/test_pdf_analyzer.py +104 -104
- botrun_flow_lang/tests/test_plotly_util.py +151 -151
- botrun_flow_lang/tests/test_run_workflow_engine.py +65 -65
- botrun_flow_lang/tools/generate_docs.py +133 -133
- botrun_flow_lang/tools/templates/tools.html +153 -153
- botrun_flow_lang/utils/__init__.py +7 -7
- botrun_flow_lang/utils/botrun_logger.py +344 -344
- botrun_flow_lang/utils/clients/rate_limit_client.py +209 -209
- botrun_flow_lang/utils/clients/token_verify_client.py +153 -153
- botrun_flow_lang/utils/google_drive_utils.py +654 -654
- botrun_flow_lang/utils/langchain_utils.py +324 -324
- botrun_flow_lang/utils/yaml_utils.py +9 -9
- {botrun_flow_lang-5.12.263.dist-info → botrun_flow_lang-5.12.264.dist-info}/METADATA +1 -1
- botrun_flow_lang-5.12.264.dist-info/RECORD +102 -0
- botrun_flow_lang-5.12.263.dist-info/RECORD +0 -102
- {botrun_flow_lang-5.12.263.dist-info → botrun_flow_lang-5.12.264.dist-info}/WHEEL +0 -0
|
@@ -1,333 +1,333 @@
|
|
|
1
|
-
import unittest
|
|
2
|
-
import requests
|
|
3
|
-
import json
|
|
4
|
-
import time
|
|
5
|
-
import uuid
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class TestHatchSharing(unittest.TestCase):
|
|
9
|
-
"""Test class for Hatch sharing functionality tests"""
|
|
10
|
-
|
|
11
|
-
def setUp(self):
|
|
12
|
-
"""Setup method that runs before each test"""
|
|
13
|
-
# Default base URL, can be overridden by setting the class attribute
|
|
14
|
-
if not hasattr(self, "base_url"):
|
|
15
|
-
self.base_url = "http://localhost:8080"
|
|
16
|
-
|
|
17
|
-
# Common headers
|
|
18
|
-
self.headers = {"Content-Type": "application/json"}
|
|
19
|
-
|
|
20
|
-
# Test data - create unique IDs for each test run
|
|
21
|
-
self.owner_id = f"owner_{uuid.uuid4().hex[:8]}@example.com"
|
|
22
|
-
self.shared_user_id = f"shared_{uuid.uuid4().hex[:8]}@example.com"
|
|
23
|
-
self.hatch_id = f"test_hatch_{uuid.uuid4().hex[:8]}"
|
|
24
|
-
|
|
25
|
-
# Create a test hatch for the owner
|
|
26
|
-
self.create_test_hatch()
|
|
27
|
-
|
|
28
|
-
def tearDown(self):
|
|
29
|
-
"""Cleanup method that runs after each test"""
|
|
30
|
-
# Delete the test hatch
|
|
31
|
-
self.delete_test_hatch()
|
|
32
|
-
|
|
33
|
-
def create_test_hatch(self):
|
|
34
|
-
"""Helper method to create a test hatch"""
|
|
35
|
-
url = f"{self.base_url}/api/hatch"
|
|
36
|
-
payload = {
|
|
37
|
-
"user_id": self.owner_id,
|
|
38
|
-
"id": self.hatch_id,
|
|
39
|
-
"model_name": "test-model",
|
|
40
|
-
"prompt_template": "This is a test prompt template for sharing functionality testing",
|
|
41
|
-
"name": "Test Hatch for Sharing Tests",
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
response = requests.post(url, headers=self.headers, json=payload)
|
|
45
|
-
self.assertEqual(
|
|
46
|
-
response.status_code, 200, f"Failed to create test hatch: {response.text}"
|
|
47
|
-
)
|
|
48
|
-
print(f"Created test hatch with ID: {self.hatch_id} for owner: {self.owner_id}")
|
|
49
|
-
return response.json()
|
|
50
|
-
|
|
51
|
-
def delete_test_hatch(self):
|
|
52
|
-
"""Helper method to delete the test hatch"""
|
|
53
|
-
url = f"{self.base_url}/api/hatch/{self.hatch_id}"
|
|
54
|
-
response = requests.delete(url, headers=self.headers)
|
|
55
|
-
self.assertEqual(
|
|
56
|
-
response.status_code, 200, f"Failed to delete test hatch: {response.text}"
|
|
57
|
-
)
|
|
58
|
-
print(f"Deleted test hatch with ID: {self.hatch_id}")
|
|
59
|
-
|
|
60
|
-
def test_share_hatch_workflow(self):
|
|
61
|
-
"""Test the complete hatch sharing workflow:
|
|
62
|
-
1. Share a hatch with another user
|
|
63
|
-
2. Verify the hatch is shared
|
|
64
|
-
3. Unshare the hatch
|
|
65
|
-
4. Verify the hatch is no longer shared
|
|
66
|
-
"""
|
|
67
|
-
# 1. Share the hatch
|
|
68
|
-
self.share_hatch_with_user()
|
|
69
|
-
|
|
70
|
-
# 2. Verify the hatch is shared
|
|
71
|
-
shared_hatches = self.get_shared_hatches()
|
|
72
|
-
self.assertEqual(len(shared_hatches), 1, "Expected exactly one shared hatch")
|
|
73
|
-
self.assertEqual(
|
|
74
|
-
shared_hatches[0]["id"], self.hatch_id, "Shared hatch ID doesn't match"
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
# 3. Unshare the hatch
|
|
78
|
-
self.unshare_hatch_from_user()
|
|
79
|
-
|
|
80
|
-
# 4. Verify the hatch is no longer shared
|
|
81
|
-
shared_hatches = self.get_shared_hatches()
|
|
82
|
-
self.assertEqual(
|
|
83
|
-
len(shared_hatches), 0, "Expected no shared hatches after unsharing"
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
def test_is_hatch_shared_with_user(self):
|
|
87
|
-
"""Test checking if a hatch is shared with a specific user"""
|
|
88
|
-
# Initially the hatch should not be shared
|
|
89
|
-
result = self.check_is_hatch_shared_with_user()
|
|
90
|
-
self.assertFalse(result["is_shared"], "Hatch should not be shared initially")
|
|
91
|
-
|
|
92
|
-
# Share the hatch
|
|
93
|
-
self.share_hatch_with_user()
|
|
94
|
-
|
|
95
|
-
# Verify the hatch is now shared
|
|
96
|
-
result = self.check_is_hatch_shared_with_user()
|
|
97
|
-
self.assertTrue(result["is_shared"], "Hatch should be shared after sharing")
|
|
98
|
-
|
|
99
|
-
# Unshare the hatch
|
|
100
|
-
self.unshare_hatch_from_user()
|
|
101
|
-
|
|
102
|
-
# Verify the hatch is no longer shared
|
|
103
|
-
result = self.check_is_hatch_shared_with_user()
|
|
104
|
-
self.assertFalse(
|
|
105
|
-
result["is_shared"], "Hatch should not be shared after unsharing"
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
def check_is_hatch_shared_with_user(self):
|
|
109
|
-
"""Helper method to check if a hatch is shared with a user"""
|
|
110
|
-
url = f"{self.base_url}/api/hatch/{self.hatch_id}/share/{self.shared_user_id}"
|
|
111
|
-
|
|
112
|
-
response = requests.get(url, headers=self.headers)
|
|
113
|
-
self.assertEqual(
|
|
114
|
-
response.status_code,
|
|
115
|
-
200,
|
|
116
|
-
f"Failed to check if hatch is shared: {response.text}",
|
|
117
|
-
)
|
|
118
|
-
return response.json()
|
|
119
|
-
|
|
120
|
-
def share_hatch_with_user(self):
|
|
121
|
-
"""Helper method to share a hatch with another user"""
|
|
122
|
-
url = f"{self.base_url}/api/hatch/{self.hatch_id}/share"
|
|
123
|
-
payload = {"user_id": self.shared_user_id}
|
|
124
|
-
|
|
125
|
-
response = requests.post(url, headers=self.headers, json=payload)
|
|
126
|
-
self.assertEqual(
|
|
127
|
-
response.status_code, 200, f"Failed to share hatch: {response.text}"
|
|
128
|
-
)
|
|
129
|
-
result = response.json()
|
|
130
|
-
self.assertTrue(
|
|
131
|
-
result["success"], f"Share operation failed: {result.get('message', '')}"
|
|
132
|
-
)
|
|
133
|
-
print(f"Shared hatch {self.hatch_id} with user {self.shared_user_id}")
|
|
134
|
-
return result
|
|
135
|
-
|
|
136
|
-
def unshare_hatch_from_user(self):
|
|
137
|
-
"""Helper method to unshare a hatch from a user"""
|
|
138
|
-
url = f"{self.base_url}/api/hatch/{self.hatch_id}/share/{self.shared_user_id}"
|
|
139
|
-
|
|
140
|
-
response = requests.delete(url, headers=self.headers)
|
|
141
|
-
self.assertEqual(
|
|
142
|
-
response.status_code, 200, f"Failed to unshare hatch: {response.text}"
|
|
143
|
-
)
|
|
144
|
-
result = response.json()
|
|
145
|
-
self.assertTrue(
|
|
146
|
-
result["success"], f"Unshare operation failed: {result.get('message', '')}"
|
|
147
|
-
)
|
|
148
|
-
print(f"Unshared hatch {self.hatch_id} from user {self.shared_user_id}")
|
|
149
|
-
return result
|
|
150
|
-
|
|
151
|
-
def get_shared_hatches(self):
|
|
152
|
-
"""Helper method to get all hatches shared with a user"""
|
|
153
|
-
url = f"{self.base_url}/api/hatches/shared?user_id={self.shared_user_id}"
|
|
154
|
-
|
|
155
|
-
response = requests.get(url, headers=self.headers)
|
|
156
|
-
self.assertEqual(
|
|
157
|
-
response.status_code, 200, f"Failed to get shared hatches: {response.text}"
|
|
158
|
-
)
|
|
159
|
-
return response.json()
|
|
160
|
-
|
|
161
|
-
def test_share_nonexistent_hatch(self):
|
|
162
|
-
"""Test sharing a non-existent hatch"""
|
|
163
|
-
url = f"{self.base_url}/api/hatch/nonexistent-hatch-id/share"
|
|
164
|
-
payload = {"user_id": self.shared_user_id}
|
|
165
|
-
|
|
166
|
-
response = requests.post(url, headers=self.headers, json=payload)
|
|
167
|
-
self.assertEqual(
|
|
168
|
-
response.status_code, 404, "Expected 404 for non-existent hatch"
|
|
169
|
-
)
|
|
170
|
-
|
|
171
|
-
def test_unshare_nonexistent_hatch(self):
|
|
172
|
-
"""Test unsharing a non-existent hatch"""
|
|
173
|
-
url = f"{self.base_url}/api/hatch/nonexistent-hatch-id/share/{self.shared_user_id}"
|
|
174
|
-
|
|
175
|
-
response = requests.delete(url, headers=self.headers)
|
|
176
|
-
self.assertEqual(
|
|
177
|
-
response.status_code, 404, "Expected 404 for non-existent hatch"
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
def test_share_hatch_multiple_times(self):
|
|
181
|
-
"""Test sharing a hatch with the same user multiple times"""
|
|
182
|
-
# First share
|
|
183
|
-
self.share_hatch_with_user()
|
|
184
|
-
|
|
185
|
-
# Second share (should be idempotent)
|
|
186
|
-
result = self.share_hatch_with_user()
|
|
187
|
-
self.assertTrue(result["success"], "Second share operation should succeed")
|
|
188
|
-
|
|
189
|
-
# Verify only one share exists
|
|
190
|
-
shared_hatches = self.get_shared_hatches()
|
|
191
|
-
self.assertEqual(len(shared_hatches), 1, "Expected exactly one shared hatch")
|
|
192
|
-
|
|
193
|
-
# Cleanup
|
|
194
|
-
self.unshare_hatch_from_user()
|
|
195
|
-
|
|
196
|
-
def test_share_with_multiple_users(self):
|
|
197
|
-
"""Test sharing a hatch with multiple users"""
|
|
198
|
-
# Create another user ID
|
|
199
|
-
second_user_id = f"user2_{uuid.uuid4().hex[:8]}@example.com"
|
|
200
|
-
|
|
201
|
-
# Share with first user
|
|
202
|
-
self.share_hatch_with_user()
|
|
203
|
-
|
|
204
|
-
# Share with second user
|
|
205
|
-
url = f"{self.base_url}/api/hatch/{self.hatch_id}/share"
|
|
206
|
-
payload = {"user_id": second_user_id}
|
|
207
|
-
|
|
208
|
-
response = requests.post(url, headers=self.headers, json=payload)
|
|
209
|
-
self.assertEqual(
|
|
210
|
-
response.status_code,
|
|
211
|
-
200,
|
|
212
|
-
f"Failed to share hatch with second user: {response.text}",
|
|
213
|
-
)
|
|
214
|
-
|
|
215
|
-
# Verify first user's shared hatches
|
|
216
|
-
shared_hatches_1 = self.get_shared_hatches()
|
|
217
|
-
self.assertEqual(
|
|
218
|
-
len(shared_hatches_1), 1, "Expected exactly one shared hatch for first user"
|
|
219
|
-
)
|
|
220
|
-
|
|
221
|
-
# Verify second user's shared hatches
|
|
222
|
-
url = f"{self.base_url}/api/hatches/shared?user_id={second_user_id}"
|
|
223
|
-
response = requests.get(url, headers=self.headers)
|
|
224
|
-
self.assertEqual(
|
|
225
|
-
response.status_code,
|
|
226
|
-
200,
|
|
227
|
-
f"Failed to get shared hatches for second user: {response.text}",
|
|
228
|
-
)
|
|
229
|
-
shared_hatches_2 = response.json()
|
|
230
|
-
self.assertEqual(
|
|
231
|
-
len(shared_hatches_2),
|
|
232
|
-
1,
|
|
233
|
-
"Expected exactly one shared hatch for second user",
|
|
234
|
-
)
|
|
235
|
-
|
|
236
|
-
# Cleanup
|
|
237
|
-
self.unshare_hatch_from_user()
|
|
238
|
-
url = f"{self.base_url}/api/hatch/{self.hatch_id}/share/{second_user_id}"
|
|
239
|
-
requests.delete(url, headers=self.headers)
|
|
240
|
-
|
|
241
|
-
def test_manual_curl_commands(self):
|
|
242
|
-
"""Test the API using the same curl commands provided for manual testing"""
|
|
243
|
-
# These tests follow the same pattern as the curl commands but programmatically
|
|
244
|
-
|
|
245
|
-
# Create a new hatch with curl-like parameters
|
|
246
|
-
curl_hatch_id = "123abc"
|
|
247
|
-
url = f"{self.base_url}/api/hatch"
|
|
248
|
-
payload = {
|
|
249
|
-
"user_id": "sebastian.hsu@gmail.com",
|
|
250
|
-
"id": curl_hatch_id,
|
|
251
|
-
"prompt_template": "妳是臺灣人,回答要用臺灣繁體中文正式用語,需要的時候也可以用英文,可以親切、俏皮、幽默,但不能隨便輕浮。在使用者合理的要求下請盡量配合他的需求,不要隨便拒絕",
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
create_response = requests.post(url, headers=self.headers, json=payload)
|
|
255
|
-
self.assertEqual(
|
|
256
|
-
create_response.status_code,
|
|
257
|
-
200,
|
|
258
|
-
f"Failed to create curl test hatch: {create_response.text}",
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
# Get the hatch
|
|
262
|
-
url = f"{self.base_url}/api/hatch/{curl_hatch_id}"
|
|
263
|
-
get_response = requests.get(url, headers=self.headers)
|
|
264
|
-
self.assertEqual(
|
|
265
|
-
get_response.status_code,
|
|
266
|
-
200,
|
|
267
|
-
f"Failed to get curl test hatch: {get_response.text}",
|
|
268
|
-
)
|
|
269
|
-
self.assertEqual(
|
|
270
|
-
get_response.json()["id"], curl_hatch_id, "Retrieved hatch ID doesn't match"
|
|
271
|
-
)
|
|
272
|
-
|
|
273
|
-
# Update the hatch
|
|
274
|
-
url = f"{self.base_url}/api/hatch/{curl_hatch_id}"
|
|
275
|
-
update_payload = {
|
|
276
|
-
"user_id": "sebastian.hsu@gmail.com",
|
|
277
|
-
"id": curl_hatch_id,
|
|
278
|
-
"prompt_template": "You are a helpful agent",
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
update_response = requests.put(url, headers=self.headers, json=update_payload)
|
|
282
|
-
self.assertEqual(
|
|
283
|
-
update_response.status_code,
|
|
284
|
-
200,
|
|
285
|
-
f"Failed to update curl test hatch: {update_response.text}",
|
|
286
|
-
)
|
|
287
|
-
self.assertEqual(
|
|
288
|
-
update_response.json()["prompt_template"],
|
|
289
|
-
"You are a helpful agent",
|
|
290
|
-
"Prompt template not updated",
|
|
291
|
-
)
|
|
292
|
-
|
|
293
|
-
# Share the hatch with another user
|
|
294
|
-
url = f"{self.base_url}/api/hatch/{curl_hatch_id}/share"
|
|
295
|
-
share_payload = {"user_id": self.shared_user_id}
|
|
296
|
-
|
|
297
|
-
share_response = requests.post(url, headers=self.headers, json=share_payload)
|
|
298
|
-
self.assertEqual(
|
|
299
|
-
share_response.status_code,
|
|
300
|
-
200,
|
|
301
|
-
f"Failed to share curl test hatch: {share_response.text}",
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
# Get shared hatches
|
|
305
|
-
url = f"{self.base_url}/api/hatches/shared?user_id={self.shared_user_id}"
|
|
306
|
-
shared_response = requests.get(url, headers=self.headers)
|
|
307
|
-
self.assertEqual(
|
|
308
|
-
shared_response.status_code,
|
|
309
|
-
200,
|
|
310
|
-
f"Failed to get shared hatches: {shared_response.text}",
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
# Unshare the hatch
|
|
314
|
-
url = f"{self.base_url}/api/hatch/{curl_hatch_id}/share/{self.shared_user_id}"
|
|
315
|
-
unshare_response = requests.delete(url, headers=self.headers)
|
|
316
|
-
self.assertEqual(
|
|
317
|
-
unshare_response.status_code,
|
|
318
|
-
200,
|
|
319
|
-
f"Failed to unshare curl test hatch: {unshare_response.text}",
|
|
320
|
-
)
|
|
321
|
-
|
|
322
|
-
# Delete the hatch
|
|
323
|
-
url = f"{self.base_url}/api/hatch/{curl_hatch_id}"
|
|
324
|
-
delete_response = requests.delete(url, headers=self.headers)
|
|
325
|
-
self.assertEqual(
|
|
326
|
-
delete_response.status_code,
|
|
327
|
-
200,
|
|
328
|
-
f"Failed to delete curl test hatch: {delete_response.text}",
|
|
329
|
-
)
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if __name__ == "__main__":
|
|
333
|
-
unittest.main()
|
|
1
|
+
import unittest
|
|
2
|
+
import requests
|
|
3
|
+
import json
|
|
4
|
+
import time
|
|
5
|
+
import uuid
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class TestHatchSharing(unittest.TestCase):
|
|
9
|
+
"""Test class for Hatch sharing functionality tests"""
|
|
10
|
+
|
|
11
|
+
def setUp(self):
|
|
12
|
+
"""Setup method that runs before each test"""
|
|
13
|
+
# Default base URL, can be overridden by setting the class attribute
|
|
14
|
+
if not hasattr(self, "base_url"):
|
|
15
|
+
self.base_url = "http://localhost:8080"
|
|
16
|
+
|
|
17
|
+
# Common headers
|
|
18
|
+
self.headers = {"Content-Type": "application/json"}
|
|
19
|
+
|
|
20
|
+
# Test data - create unique IDs for each test run
|
|
21
|
+
self.owner_id = f"owner_{uuid.uuid4().hex[:8]}@example.com"
|
|
22
|
+
self.shared_user_id = f"shared_{uuid.uuid4().hex[:8]}@example.com"
|
|
23
|
+
self.hatch_id = f"test_hatch_{uuid.uuid4().hex[:8]}"
|
|
24
|
+
|
|
25
|
+
# Create a test hatch for the owner
|
|
26
|
+
self.create_test_hatch()
|
|
27
|
+
|
|
28
|
+
def tearDown(self):
|
|
29
|
+
"""Cleanup method that runs after each test"""
|
|
30
|
+
# Delete the test hatch
|
|
31
|
+
self.delete_test_hatch()
|
|
32
|
+
|
|
33
|
+
def create_test_hatch(self):
|
|
34
|
+
"""Helper method to create a test hatch"""
|
|
35
|
+
url = f"{self.base_url}/api/hatch"
|
|
36
|
+
payload = {
|
|
37
|
+
"user_id": self.owner_id,
|
|
38
|
+
"id": self.hatch_id,
|
|
39
|
+
"model_name": "test-model",
|
|
40
|
+
"prompt_template": "This is a test prompt template for sharing functionality testing",
|
|
41
|
+
"name": "Test Hatch for Sharing Tests",
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
response = requests.post(url, headers=self.headers, json=payload)
|
|
45
|
+
self.assertEqual(
|
|
46
|
+
response.status_code, 200, f"Failed to create test hatch: {response.text}"
|
|
47
|
+
)
|
|
48
|
+
print(f"Created test hatch with ID: {self.hatch_id} for owner: {self.owner_id}")
|
|
49
|
+
return response.json()
|
|
50
|
+
|
|
51
|
+
def delete_test_hatch(self):
|
|
52
|
+
"""Helper method to delete the test hatch"""
|
|
53
|
+
url = f"{self.base_url}/api/hatch/{self.hatch_id}"
|
|
54
|
+
response = requests.delete(url, headers=self.headers)
|
|
55
|
+
self.assertEqual(
|
|
56
|
+
response.status_code, 200, f"Failed to delete test hatch: {response.text}"
|
|
57
|
+
)
|
|
58
|
+
print(f"Deleted test hatch with ID: {self.hatch_id}")
|
|
59
|
+
|
|
60
|
+
def test_share_hatch_workflow(self):
|
|
61
|
+
"""Test the complete hatch sharing workflow:
|
|
62
|
+
1. Share a hatch with another user
|
|
63
|
+
2. Verify the hatch is shared
|
|
64
|
+
3. Unshare the hatch
|
|
65
|
+
4. Verify the hatch is no longer shared
|
|
66
|
+
"""
|
|
67
|
+
# 1. Share the hatch
|
|
68
|
+
self.share_hatch_with_user()
|
|
69
|
+
|
|
70
|
+
# 2. Verify the hatch is shared
|
|
71
|
+
shared_hatches = self.get_shared_hatches()
|
|
72
|
+
self.assertEqual(len(shared_hatches), 1, "Expected exactly one shared hatch")
|
|
73
|
+
self.assertEqual(
|
|
74
|
+
shared_hatches[0]["id"], self.hatch_id, "Shared hatch ID doesn't match"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# 3. Unshare the hatch
|
|
78
|
+
self.unshare_hatch_from_user()
|
|
79
|
+
|
|
80
|
+
# 4. Verify the hatch is no longer shared
|
|
81
|
+
shared_hatches = self.get_shared_hatches()
|
|
82
|
+
self.assertEqual(
|
|
83
|
+
len(shared_hatches), 0, "Expected no shared hatches after unsharing"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def test_is_hatch_shared_with_user(self):
|
|
87
|
+
"""Test checking if a hatch is shared with a specific user"""
|
|
88
|
+
# Initially the hatch should not be shared
|
|
89
|
+
result = self.check_is_hatch_shared_with_user()
|
|
90
|
+
self.assertFalse(result["is_shared"], "Hatch should not be shared initially")
|
|
91
|
+
|
|
92
|
+
# Share the hatch
|
|
93
|
+
self.share_hatch_with_user()
|
|
94
|
+
|
|
95
|
+
# Verify the hatch is now shared
|
|
96
|
+
result = self.check_is_hatch_shared_with_user()
|
|
97
|
+
self.assertTrue(result["is_shared"], "Hatch should be shared after sharing")
|
|
98
|
+
|
|
99
|
+
# Unshare the hatch
|
|
100
|
+
self.unshare_hatch_from_user()
|
|
101
|
+
|
|
102
|
+
# Verify the hatch is no longer shared
|
|
103
|
+
result = self.check_is_hatch_shared_with_user()
|
|
104
|
+
self.assertFalse(
|
|
105
|
+
result["is_shared"], "Hatch should not be shared after unsharing"
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
def check_is_hatch_shared_with_user(self):
|
|
109
|
+
"""Helper method to check if a hatch is shared with a user"""
|
|
110
|
+
url = f"{self.base_url}/api/hatch/{self.hatch_id}/share/{self.shared_user_id}"
|
|
111
|
+
|
|
112
|
+
response = requests.get(url, headers=self.headers)
|
|
113
|
+
self.assertEqual(
|
|
114
|
+
response.status_code,
|
|
115
|
+
200,
|
|
116
|
+
f"Failed to check if hatch is shared: {response.text}",
|
|
117
|
+
)
|
|
118
|
+
return response.json()
|
|
119
|
+
|
|
120
|
+
def share_hatch_with_user(self):
|
|
121
|
+
"""Helper method to share a hatch with another user"""
|
|
122
|
+
url = f"{self.base_url}/api/hatch/{self.hatch_id}/share"
|
|
123
|
+
payload = {"user_id": self.shared_user_id}
|
|
124
|
+
|
|
125
|
+
response = requests.post(url, headers=self.headers, json=payload)
|
|
126
|
+
self.assertEqual(
|
|
127
|
+
response.status_code, 200, f"Failed to share hatch: {response.text}"
|
|
128
|
+
)
|
|
129
|
+
result = response.json()
|
|
130
|
+
self.assertTrue(
|
|
131
|
+
result["success"], f"Share operation failed: {result.get('message', '')}"
|
|
132
|
+
)
|
|
133
|
+
print(f"Shared hatch {self.hatch_id} with user {self.shared_user_id}")
|
|
134
|
+
return result
|
|
135
|
+
|
|
136
|
+
def unshare_hatch_from_user(self):
|
|
137
|
+
"""Helper method to unshare a hatch from a user"""
|
|
138
|
+
url = f"{self.base_url}/api/hatch/{self.hatch_id}/share/{self.shared_user_id}"
|
|
139
|
+
|
|
140
|
+
response = requests.delete(url, headers=self.headers)
|
|
141
|
+
self.assertEqual(
|
|
142
|
+
response.status_code, 200, f"Failed to unshare hatch: {response.text}"
|
|
143
|
+
)
|
|
144
|
+
result = response.json()
|
|
145
|
+
self.assertTrue(
|
|
146
|
+
result["success"], f"Unshare operation failed: {result.get('message', '')}"
|
|
147
|
+
)
|
|
148
|
+
print(f"Unshared hatch {self.hatch_id} from user {self.shared_user_id}")
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
def get_shared_hatches(self):
|
|
152
|
+
"""Helper method to get all hatches shared with a user"""
|
|
153
|
+
url = f"{self.base_url}/api/hatches/shared?user_id={self.shared_user_id}"
|
|
154
|
+
|
|
155
|
+
response = requests.get(url, headers=self.headers)
|
|
156
|
+
self.assertEqual(
|
|
157
|
+
response.status_code, 200, f"Failed to get shared hatches: {response.text}"
|
|
158
|
+
)
|
|
159
|
+
return response.json()
|
|
160
|
+
|
|
161
|
+
def test_share_nonexistent_hatch(self):
|
|
162
|
+
"""Test sharing a non-existent hatch"""
|
|
163
|
+
url = f"{self.base_url}/api/hatch/nonexistent-hatch-id/share"
|
|
164
|
+
payload = {"user_id": self.shared_user_id}
|
|
165
|
+
|
|
166
|
+
response = requests.post(url, headers=self.headers, json=payload)
|
|
167
|
+
self.assertEqual(
|
|
168
|
+
response.status_code, 404, "Expected 404 for non-existent hatch"
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
def test_unshare_nonexistent_hatch(self):
|
|
172
|
+
"""Test unsharing a non-existent hatch"""
|
|
173
|
+
url = f"{self.base_url}/api/hatch/nonexistent-hatch-id/share/{self.shared_user_id}"
|
|
174
|
+
|
|
175
|
+
response = requests.delete(url, headers=self.headers)
|
|
176
|
+
self.assertEqual(
|
|
177
|
+
response.status_code, 404, "Expected 404 for non-existent hatch"
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
def test_share_hatch_multiple_times(self):
|
|
181
|
+
"""Test sharing a hatch with the same user multiple times"""
|
|
182
|
+
# First share
|
|
183
|
+
self.share_hatch_with_user()
|
|
184
|
+
|
|
185
|
+
# Second share (should be idempotent)
|
|
186
|
+
result = self.share_hatch_with_user()
|
|
187
|
+
self.assertTrue(result["success"], "Second share operation should succeed")
|
|
188
|
+
|
|
189
|
+
# Verify only one share exists
|
|
190
|
+
shared_hatches = self.get_shared_hatches()
|
|
191
|
+
self.assertEqual(len(shared_hatches), 1, "Expected exactly one shared hatch")
|
|
192
|
+
|
|
193
|
+
# Cleanup
|
|
194
|
+
self.unshare_hatch_from_user()
|
|
195
|
+
|
|
196
|
+
def test_share_with_multiple_users(self):
|
|
197
|
+
"""Test sharing a hatch with multiple users"""
|
|
198
|
+
# Create another user ID
|
|
199
|
+
second_user_id = f"user2_{uuid.uuid4().hex[:8]}@example.com"
|
|
200
|
+
|
|
201
|
+
# Share with first user
|
|
202
|
+
self.share_hatch_with_user()
|
|
203
|
+
|
|
204
|
+
# Share with second user
|
|
205
|
+
url = f"{self.base_url}/api/hatch/{self.hatch_id}/share"
|
|
206
|
+
payload = {"user_id": second_user_id}
|
|
207
|
+
|
|
208
|
+
response = requests.post(url, headers=self.headers, json=payload)
|
|
209
|
+
self.assertEqual(
|
|
210
|
+
response.status_code,
|
|
211
|
+
200,
|
|
212
|
+
f"Failed to share hatch with second user: {response.text}",
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
# Verify first user's shared hatches
|
|
216
|
+
shared_hatches_1 = self.get_shared_hatches()
|
|
217
|
+
self.assertEqual(
|
|
218
|
+
len(shared_hatches_1), 1, "Expected exactly one shared hatch for first user"
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Verify second user's shared hatches
|
|
222
|
+
url = f"{self.base_url}/api/hatches/shared?user_id={second_user_id}"
|
|
223
|
+
response = requests.get(url, headers=self.headers)
|
|
224
|
+
self.assertEqual(
|
|
225
|
+
response.status_code,
|
|
226
|
+
200,
|
|
227
|
+
f"Failed to get shared hatches for second user: {response.text}",
|
|
228
|
+
)
|
|
229
|
+
shared_hatches_2 = response.json()
|
|
230
|
+
self.assertEqual(
|
|
231
|
+
len(shared_hatches_2),
|
|
232
|
+
1,
|
|
233
|
+
"Expected exactly one shared hatch for second user",
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Cleanup
|
|
237
|
+
self.unshare_hatch_from_user()
|
|
238
|
+
url = f"{self.base_url}/api/hatch/{self.hatch_id}/share/{second_user_id}"
|
|
239
|
+
requests.delete(url, headers=self.headers)
|
|
240
|
+
|
|
241
|
+
def test_manual_curl_commands(self):
|
|
242
|
+
"""Test the API using the same curl commands provided for manual testing"""
|
|
243
|
+
# These tests follow the same pattern as the curl commands but programmatically
|
|
244
|
+
|
|
245
|
+
# Create a new hatch with curl-like parameters
|
|
246
|
+
curl_hatch_id = "123abc"
|
|
247
|
+
url = f"{self.base_url}/api/hatch"
|
|
248
|
+
payload = {
|
|
249
|
+
"user_id": "sebastian.hsu@gmail.com",
|
|
250
|
+
"id": curl_hatch_id,
|
|
251
|
+
"prompt_template": "妳是臺灣人,回答要用臺灣繁體中文正式用語,需要的時候也可以用英文,可以親切、俏皮、幽默,但不能隨便輕浮。在使用者合理的要求下請盡量配合他的需求,不要隨便拒絕",
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
create_response = requests.post(url, headers=self.headers, json=payload)
|
|
255
|
+
self.assertEqual(
|
|
256
|
+
create_response.status_code,
|
|
257
|
+
200,
|
|
258
|
+
f"Failed to create curl test hatch: {create_response.text}",
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Get the hatch
|
|
262
|
+
url = f"{self.base_url}/api/hatch/{curl_hatch_id}"
|
|
263
|
+
get_response = requests.get(url, headers=self.headers)
|
|
264
|
+
self.assertEqual(
|
|
265
|
+
get_response.status_code,
|
|
266
|
+
200,
|
|
267
|
+
f"Failed to get curl test hatch: {get_response.text}",
|
|
268
|
+
)
|
|
269
|
+
self.assertEqual(
|
|
270
|
+
get_response.json()["id"], curl_hatch_id, "Retrieved hatch ID doesn't match"
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
# Update the hatch
|
|
274
|
+
url = f"{self.base_url}/api/hatch/{curl_hatch_id}"
|
|
275
|
+
update_payload = {
|
|
276
|
+
"user_id": "sebastian.hsu@gmail.com",
|
|
277
|
+
"id": curl_hatch_id,
|
|
278
|
+
"prompt_template": "You are a helpful agent",
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
update_response = requests.put(url, headers=self.headers, json=update_payload)
|
|
282
|
+
self.assertEqual(
|
|
283
|
+
update_response.status_code,
|
|
284
|
+
200,
|
|
285
|
+
f"Failed to update curl test hatch: {update_response.text}",
|
|
286
|
+
)
|
|
287
|
+
self.assertEqual(
|
|
288
|
+
update_response.json()["prompt_template"],
|
|
289
|
+
"You are a helpful agent",
|
|
290
|
+
"Prompt template not updated",
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
# Share the hatch with another user
|
|
294
|
+
url = f"{self.base_url}/api/hatch/{curl_hatch_id}/share"
|
|
295
|
+
share_payload = {"user_id": self.shared_user_id}
|
|
296
|
+
|
|
297
|
+
share_response = requests.post(url, headers=self.headers, json=share_payload)
|
|
298
|
+
self.assertEqual(
|
|
299
|
+
share_response.status_code,
|
|
300
|
+
200,
|
|
301
|
+
f"Failed to share curl test hatch: {share_response.text}",
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Get shared hatches
|
|
305
|
+
url = f"{self.base_url}/api/hatches/shared?user_id={self.shared_user_id}"
|
|
306
|
+
shared_response = requests.get(url, headers=self.headers)
|
|
307
|
+
self.assertEqual(
|
|
308
|
+
shared_response.status_code,
|
|
309
|
+
200,
|
|
310
|
+
f"Failed to get shared hatches: {shared_response.text}",
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# Unshare the hatch
|
|
314
|
+
url = f"{self.base_url}/api/hatch/{curl_hatch_id}/share/{self.shared_user_id}"
|
|
315
|
+
unshare_response = requests.delete(url, headers=self.headers)
|
|
316
|
+
self.assertEqual(
|
|
317
|
+
unshare_response.status_code,
|
|
318
|
+
200,
|
|
319
|
+
f"Failed to unshare curl test hatch: {unshare_response.text}",
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
# Delete the hatch
|
|
323
|
+
url = f"{self.base_url}/api/hatch/{curl_hatch_id}"
|
|
324
|
+
delete_response = requests.delete(url, headers=self.headers)
|
|
325
|
+
self.assertEqual(
|
|
326
|
+
delete_response.status_code,
|
|
327
|
+
200,
|
|
328
|
+
f"Failed to delete curl test hatch: {delete_response.text}",
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
if __name__ == "__main__":
|
|
333
|
+
unittest.main()
|