@elizaos/interop 2.0.0-alpha
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.
- package/LICENSE +21 -0
- package/README.md +436 -0
- package/dist/packages/interop/tsconfig.tsbuildinfo +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/dist/typescript/index.d.ts +33 -0
- package/dist/typescript/index.d.ts.map +1 -0
- package/dist/typescript/index.js +121 -0
- package/dist/typescript/python-bridge.d.ts +80 -0
- package/dist/typescript/python-bridge.d.ts.map +1 -0
- package/dist/typescript/python-bridge.js +334 -0
- package/dist/typescript/types.d.ts +301 -0
- package/dist/typescript/types.d.ts.map +1 -0
- package/dist/typescript/types.js +10 -0
- package/dist/typescript/wasm-loader.d.ts +32 -0
- package/dist/typescript/wasm-loader.d.ts.map +1 -0
- package/dist/typescript/wasm-loader.js +269 -0
- package/package.json +43 -0
- package/python/__init__.py +50 -0
- package/python/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/__pycache__/rust_ffi.cpython-313.pyc +0 -0
- package/python/__pycache__/ts_bridge.cpython-313.pyc +0 -0
- package/python/__pycache__/wasm_loader.cpython-313.pyc +0 -0
- package/python/bridge_server.py +505 -0
- package/python/rust_ffi.py +418 -0
- package/python/tests/__init__.py +1 -0
- package/python/tests/__pycache__/__init__.cpython-313.pyc +0 -0
- package/python/tests/__pycache__/test_bridge_server.cpython-313-pytest-9.0.2.pyc +0 -0
- package/python/tests/__pycache__/test_interop.cpython-313-pytest-9.0.2.pyc +0 -0
- package/python/tests/__pycache__/test_rust_ffi.cpython-313-pytest-9.0.2.pyc +0 -0
- package/python/tests/__pycache__/test_rust_ffi_loader.cpython-313-pytest-9.0.2.pyc +0 -0
- package/python/tests/test_bridge_server.py +525 -0
- package/python/tests/test_interop.py +319 -0
- package/python/tests/test_rust_ffi.py +352 -0
- package/python/tests/test_rust_ffi_loader.py +71 -0
- package/python/ts_bridge.py +452 -0
- package/python/ts_bridge_runner.mjs +284 -0
- package/python/wasm_loader.py +517 -0
- package/rust/ffi_exports.rs +362 -0
- package/rust/mod.rs +21 -0
- package/rust/py_loader.rs +412 -0
- package/rust/ts_loader.rs +467 -0
- package/rust/wasm_plugin.rs +320 -0
|
@@ -0,0 +1,525 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Tests for Python Plugin Bridge Server
|
|
3
|
+
|
|
4
|
+
These tests validate the IPC protocol handling and message processing.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TestIPCProtocol:
|
|
12
|
+
"""Test IPC message protocol."""
|
|
13
|
+
|
|
14
|
+
def test_ready_message_format(self):
|
|
15
|
+
"""Test ready message with manifest format."""
|
|
16
|
+
manifest = {
|
|
17
|
+
"name": "python-plugin",
|
|
18
|
+
"description": "Test Python plugin",
|
|
19
|
+
"version": "2.0.0-alpha",
|
|
20
|
+
"language": "python",
|
|
21
|
+
"actions": [{"name": "TEST_ACTION", "description": "Test"}],
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
ready_msg = {"type": "ready", "manifest": manifest}
|
|
25
|
+
|
|
26
|
+
json_str = json.dumps(ready_msg)
|
|
27
|
+
parsed = json.loads(json_str)
|
|
28
|
+
|
|
29
|
+
assert parsed["type"] == "ready"
|
|
30
|
+
assert parsed["manifest"]["name"] == "python-plugin"
|
|
31
|
+
|
|
32
|
+
def test_action_invoke_request(self):
|
|
33
|
+
"""Test action.invoke request parsing."""
|
|
34
|
+
request = {
|
|
35
|
+
"type": "action.invoke",
|
|
36
|
+
"id": "req-123",
|
|
37
|
+
"action": "HELLO_PYTHON",
|
|
38
|
+
"memory": {"content": {"text": "Hello"}},
|
|
39
|
+
"state": {"values": {}},
|
|
40
|
+
"options": {"timeout": 5000},
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
json_str = json.dumps(request)
|
|
44
|
+
parsed = json.loads(json_str)
|
|
45
|
+
|
|
46
|
+
assert parsed["type"] == "action.invoke"
|
|
47
|
+
assert parsed["action"] == "HELLO_PYTHON"
|
|
48
|
+
assert parsed["memory"]["content"]["text"] == "Hello"
|
|
49
|
+
|
|
50
|
+
def test_action_validate_request(self):
|
|
51
|
+
"""Test action.validate request parsing."""
|
|
52
|
+
request = {
|
|
53
|
+
"type": "action.validate",
|
|
54
|
+
"id": "req-124",
|
|
55
|
+
"action": "TEST_ACTION",
|
|
56
|
+
"memory": {"content": {"text": "Test"}},
|
|
57
|
+
"state": None,
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
json_str = json.dumps(request)
|
|
61
|
+
parsed = json.loads(json_str)
|
|
62
|
+
|
|
63
|
+
assert parsed["type"] == "action.validate"
|
|
64
|
+
assert parsed["state"] is None
|
|
65
|
+
|
|
66
|
+
def test_provider_get_request(self):
|
|
67
|
+
"""Test provider.get request parsing."""
|
|
68
|
+
request = {
|
|
69
|
+
"type": "provider.get",
|
|
70
|
+
"id": "req-125",
|
|
71
|
+
"provider": "PYTHON_INFO",
|
|
72
|
+
"memory": {"content": {}},
|
|
73
|
+
"state": {"values": {}},
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
json_str = json.dumps(request)
|
|
77
|
+
parsed = json.loads(json_str)
|
|
78
|
+
|
|
79
|
+
assert parsed["type"] == "provider.get"
|
|
80
|
+
assert parsed["provider"] == "PYTHON_INFO"
|
|
81
|
+
|
|
82
|
+
def test_plugin_init_request(self):
|
|
83
|
+
"""Test plugin.init request parsing."""
|
|
84
|
+
request = {
|
|
85
|
+
"type": "plugin.init",
|
|
86
|
+
"id": "req-126",
|
|
87
|
+
"config": {"API_KEY": "test-key", "DEBUG": "true"},
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
json_str = json.dumps(request)
|
|
91
|
+
parsed = json.loads(json_str)
|
|
92
|
+
|
|
93
|
+
assert parsed["type"] == "plugin.init"
|
|
94
|
+
assert parsed["config"]["API_KEY"] == "test-key"
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class TestIPCResponses:
|
|
98
|
+
"""Test IPC response formatting."""
|
|
99
|
+
|
|
100
|
+
def test_action_result_success(self):
|
|
101
|
+
"""Test successful action result response."""
|
|
102
|
+
response = {
|
|
103
|
+
"type": "action.result",
|
|
104
|
+
"id": "req-123",
|
|
105
|
+
"result": {
|
|
106
|
+
"success": True,
|
|
107
|
+
"text": "Hello from Python! 🐍",
|
|
108
|
+
"data": {"language": "python"},
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
json_str = json.dumps(response)
|
|
113
|
+
parsed = json.loads(json_str)
|
|
114
|
+
|
|
115
|
+
assert parsed["type"] == "action.result"
|
|
116
|
+
assert parsed["result"]["success"] is True
|
|
117
|
+
assert "🐍" in parsed["result"]["text"]
|
|
118
|
+
|
|
119
|
+
def test_action_result_failure(self):
|
|
120
|
+
"""Test failed action result response."""
|
|
121
|
+
response = {
|
|
122
|
+
"type": "action.result",
|
|
123
|
+
"id": "req-123",
|
|
124
|
+
"result": {"success": False, "error": "Action failed"},
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
json_str = json.dumps(response)
|
|
128
|
+
parsed = json.loads(json_str)
|
|
129
|
+
|
|
130
|
+
assert parsed["result"]["success"] is False
|
|
131
|
+
assert parsed["result"]["error"] == "Action failed"
|
|
132
|
+
|
|
133
|
+
def test_validate_result(self):
|
|
134
|
+
"""Test validation result response."""
|
|
135
|
+
response = {"type": "validate.result", "id": "req-124", "valid": True}
|
|
136
|
+
|
|
137
|
+
json_str = json.dumps(response)
|
|
138
|
+
parsed = json.loads(json_str)
|
|
139
|
+
|
|
140
|
+
assert parsed["type"] == "validate.result"
|
|
141
|
+
assert parsed["valid"] is True
|
|
142
|
+
|
|
143
|
+
def test_provider_result(self):
|
|
144
|
+
"""Test provider result response."""
|
|
145
|
+
response = {
|
|
146
|
+
"type": "provider.result",
|
|
147
|
+
"id": "req-125",
|
|
148
|
+
"result": {
|
|
149
|
+
"text": "Python environment info",
|
|
150
|
+
"values": {"version": "3.11"},
|
|
151
|
+
"data": {"platform": "linux"},
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
json_str = json.dumps(response)
|
|
156
|
+
parsed = json.loads(json_str)
|
|
157
|
+
|
|
158
|
+
assert parsed["result"]["text"] == "Python environment info"
|
|
159
|
+
assert parsed["result"]["values"]["version"] == "3.11"
|
|
160
|
+
|
|
161
|
+
def test_init_result(self):
|
|
162
|
+
"""Test plugin init result response."""
|
|
163
|
+
response = {"type": "plugin.init.result", "id": "req-126", "success": True}
|
|
164
|
+
|
|
165
|
+
json_str = json.dumps(response)
|
|
166
|
+
parsed = json.loads(json_str)
|
|
167
|
+
|
|
168
|
+
assert parsed["type"] == "plugin.init.result"
|
|
169
|
+
assert parsed["success"] is True
|
|
170
|
+
|
|
171
|
+
def test_error_response(self):
|
|
172
|
+
"""Test error response format."""
|
|
173
|
+
response = {
|
|
174
|
+
"type": "error",
|
|
175
|
+
"id": "req-error",
|
|
176
|
+
"error": "Module not found",
|
|
177
|
+
"details": "Traceback (most recent call last):\n File...",
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
json_str = json.dumps(response)
|
|
181
|
+
parsed = json.loads(json_str)
|
|
182
|
+
|
|
183
|
+
assert parsed["type"] == "error"
|
|
184
|
+
assert parsed["error"] == "Module not found"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class TestRequestHandling:
|
|
188
|
+
"""Test request handling logic."""
|
|
189
|
+
|
|
190
|
+
def test_route_action_invoke(self):
|
|
191
|
+
"""Test routing action.invoke requests."""
|
|
192
|
+
request = {"type": "action.invoke", "action": "TEST"}
|
|
193
|
+
|
|
194
|
+
# Route based on type
|
|
195
|
+
handlers = {
|
|
196
|
+
"action.invoke": lambda r: {"handled": "action.invoke"},
|
|
197
|
+
"action.validate": lambda r: {"handled": "action.validate"},
|
|
198
|
+
"provider.get": lambda r: {"handled": "provider.get"},
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
result = handlers.get(request["type"], lambda r: {"error": "unknown"})(request)
|
|
202
|
+
assert result["handled"] == "action.invoke"
|
|
203
|
+
|
|
204
|
+
def test_route_unknown_type(self):
|
|
205
|
+
"""Test routing unknown request types."""
|
|
206
|
+
request = {"type": "unknown.type"}
|
|
207
|
+
|
|
208
|
+
handlers = {
|
|
209
|
+
"action.invoke": lambda r: {"handled": "action.invoke"},
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
result = handlers.get(request["type"], lambda r: {"type": "error", "error": "Unknown type"})(
|
|
213
|
+
request
|
|
214
|
+
)
|
|
215
|
+
assert result["type"] == "error"
|
|
216
|
+
|
|
217
|
+
def test_extract_request_id(self):
|
|
218
|
+
"""Test extracting request ID from messages."""
|
|
219
|
+
request = {"type": "action.invoke", "id": "unique-id-123", "action": "TEST"}
|
|
220
|
+
|
|
221
|
+
request_id = request.get("id", "")
|
|
222
|
+
assert request_id == "unique-id-123"
|
|
223
|
+
|
|
224
|
+
def test_missing_request_id(self):
|
|
225
|
+
"""Test handling missing request ID."""
|
|
226
|
+
request = {"type": "action.invoke", "action": "TEST"}
|
|
227
|
+
|
|
228
|
+
request_id = request.get("id", "")
|
|
229
|
+
assert request_id == ""
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class TestManifestGeneration:
|
|
233
|
+
"""Test manifest generation for plugins."""
|
|
234
|
+
|
|
235
|
+
def test_generate_manifest_with_actions(self):
|
|
236
|
+
"""Test generating manifest with actions."""
|
|
237
|
+
# Simulate plugin attributes
|
|
238
|
+
plugin = {
|
|
239
|
+
"name": "test-plugin",
|
|
240
|
+
"description": "Test description",
|
|
241
|
+
"version": "2.0.0-alpha",
|
|
242
|
+
"actions": [
|
|
243
|
+
{"name": "ACTION_1", "description": "First action"},
|
|
244
|
+
{"name": "ACTION_2", "description": "Second action", "similes": ["A2"]},
|
|
245
|
+
],
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
manifest = {
|
|
249
|
+
"name": plugin["name"],
|
|
250
|
+
"description": plugin["description"],
|
|
251
|
+
"version": plugin.get("version", "1.0.0"),
|
|
252
|
+
"language": "python",
|
|
253
|
+
"actions": [
|
|
254
|
+
{
|
|
255
|
+
"name": a["name"],
|
|
256
|
+
"description": a["description"],
|
|
257
|
+
"similes": a.get("similes"),
|
|
258
|
+
}
|
|
259
|
+
for a in plugin.get("actions", [])
|
|
260
|
+
],
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
assert manifest["name"] == "test-plugin"
|
|
264
|
+
assert len(manifest["actions"]) == 2
|
|
265
|
+
assert manifest["actions"][1]["similes"] == ["A2"]
|
|
266
|
+
|
|
267
|
+
def test_generate_manifest_with_providers(self):
|
|
268
|
+
"""Test generating manifest with providers."""
|
|
269
|
+
plugin = {
|
|
270
|
+
"name": "provider-plugin",
|
|
271
|
+
"description": "Provider test",
|
|
272
|
+
"providers": [
|
|
273
|
+
{
|
|
274
|
+
"name": "PROVIDER_1",
|
|
275
|
+
"description": "First provider",
|
|
276
|
+
"dynamic": True,
|
|
277
|
+
"position": 5,
|
|
278
|
+
"private": False,
|
|
279
|
+
}
|
|
280
|
+
],
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
manifest = {
|
|
284
|
+
"name": plugin["name"],
|
|
285
|
+
"description": plugin["description"],
|
|
286
|
+
"version": "2.0.0-alpha",
|
|
287
|
+
"language": "python",
|
|
288
|
+
"providers": [
|
|
289
|
+
{
|
|
290
|
+
"name": p["name"],
|
|
291
|
+
"description": p.get("description"),
|
|
292
|
+
"dynamic": p.get("dynamic"),
|
|
293
|
+
"position": p.get("position"),
|
|
294
|
+
"private": p.get("private"),
|
|
295
|
+
}
|
|
296
|
+
for p in plugin.get("providers", [])
|
|
297
|
+
],
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
assert len(manifest["providers"]) == 1
|
|
301
|
+
assert manifest["providers"][0]["dynamic"] is True
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
class TestMessageBuffering:
|
|
305
|
+
"""Test message buffering for stdin/stdout communication."""
|
|
306
|
+
|
|
307
|
+
def test_newline_delimited_messages(self):
|
|
308
|
+
"""Test parsing newline-delimited messages."""
|
|
309
|
+
messages = [
|
|
310
|
+
{"type": "action.invoke", "id": "1", "action": "A"},
|
|
311
|
+
{"type": "action.invoke", "id": "2", "action": "B"},
|
|
312
|
+
{"type": "action.invoke", "id": "3", "action": "C"},
|
|
313
|
+
]
|
|
314
|
+
|
|
315
|
+
# Simulate buffered input
|
|
316
|
+
buffer = "\n".join(json.dumps(m) for m in messages) + "\n"
|
|
317
|
+
lines = buffer.strip().split("\n")
|
|
318
|
+
|
|
319
|
+
assert len(lines) == 3
|
|
320
|
+
for i, line in enumerate(lines):
|
|
321
|
+
parsed = json.loads(line)
|
|
322
|
+
assert parsed["id"] == str(i + 1)
|
|
323
|
+
|
|
324
|
+
def test_partial_message_buffering(self):
|
|
325
|
+
"""Test handling partial messages in buffer."""
|
|
326
|
+
message = {"type": "test", "data": "complete"}
|
|
327
|
+
full_json = json.dumps(message)
|
|
328
|
+
|
|
329
|
+
# Simulate partial reads
|
|
330
|
+
part1 = full_json[:10]
|
|
331
|
+
part2 = full_json[10:]
|
|
332
|
+
|
|
333
|
+
buffer = ""
|
|
334
|
+
buffer += part1
|
|
335
|
+
# Can't parse yet
|
|
336
|
+
with pytest.raises(json.JSONDecodeError):
|
|
337
|
+
json.loads(buffer)
|
|
338
|
+
|
|
339
|
+
buffer += part2
|
|
340
|
+
# Now can parse
|
|
341
|
+
parsed = json.loads(buffer)
|
|
342
|
+
assert parsed["type"] == "test"
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
class TestErrorHandling:
|
|
346
|
+
"""Test error handling in bridge server."""
|
|
347
|
+
|
|
348
|
+
def test_malformed_json(self):
|
|
349
|
+
"""Test handling malformed JSON input."""
|
|
350
|
+
malformed = '{ type: "test" }'
|
|
351
|
+
|
|
352
|
+
with pytest.raises(json.JSONDecodeError):
|
|
353
|
+
json.loads(malformed)
|
|
354
|
+
|
|
355
|
+
def test_missing_required_field(self):
|
|
356
|
+
"""Test handling missing required fields."""
|
|
357
|
+
request = {"type": "action.invoke"}
|
|
358
|
+
# Missing action field
|
|
359
|
+
|
|
360
|
+
action = request.get("action")
|
|
361
|
+
assert action is None
|
|
362
|
+
|
|
363
|
+
def test_exception_serialization(self):
|
|
364
|
+
"""Test serializing exceptions in error responses."""
|
|
365
|
+
try:
|
|
366
|
+
raise ValueError("Test error")
|
|
367
|
+
except Exception as e:
|
|
368
|
+
response = {
|
|
369
|
+
"type": "error",
|
|
370
|
+
"id": "req-err",
|
|
371
|
+
"error": str(e),
|
|
372
|
+
"details": type(e).__name__,
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
json_str = json.dumps(response)
|
|
376
|
+
parsed = json.loads(json_str)
|
|
377
|
+
|
|
378
|
+
assert parsed["error"] == "Test error"
|
|
379
|
+
assert parsed["details"] == "ValueError"
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
class TestAsyncOperations:
|
|
383
|
+
"""Test async operation handling."""
|
|
384
|
+
|
|
385
|
+
@pytest.mark.asyncio
|
|
386
|
+
async def test_async_action_handler(self):
|
|
387
|
+
"""Test async action handler execution."""
|
|
388
|
+
|
|
389
|
+
async def mock_handler(memory, state, options):
|
|
390
|
+
# Simulate async work
|
|
391
|
+
await asyncio.sleep(0.01)
|
|
392
|
+
return {"success": True, "text": "Async result"}
|
|
393
|
+
|
|
394
|
+
import asyncio
|
|
395
|
+
|
|
396
|
+
result = await mock_handler({}, {}, {})
|
|
397
|
+
assert result["success"] is True
|
|
398
|
+
|
|
399
|
+
@pytest.mark.asyncio
|
|
400
|
+
async def test_async_provider_get(self):
|
|
401
|
+
"""Test async provider get execution."""
|
|
402
|
+
|
|
403
|
+
async def mock_get(memory, state):
|
|
404
|
+
return {"text": "Async provider data", "values": {}}
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
result = await mock_get({}, {})
|
|
408
|
+
assert result["text"] == "Async provider data"
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
class TestServiceHandling:
|
|
412
|
+
"""Test service handling in bridge server."""
|
|
413
|
+
|
|
414
|
+
def test_service_start_request(self):
|
|
415
|
+
"""Test service.start request format."""
|
|
416
|
+
request = {
|
|
417
|
+
"type": "service.start",
|
|
418
|
+
"id": "req-456",
|
|
419
|
+
"serviceType": "CUSTOM_SERVICE",
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
json_str = json.dumps(request)
|
|
423
|
+
parsed = json.loads(json_str)
|
|
424
|
+
|
|
425
|
+
assert parsed["type"] == "service.start"
|
|
426
|
+
assert parsed["serviceType"] == "CUSTOM_SERVICE"
|
|
427
|
+
|
|
428
|
+
def test_service_stop_request(self):
|
|
429
|
+
"""Test service.stop request format."""
|
|
430
|
+
request = {
|
|
431
|
+
"type": "service.stop",
|
|
432
|
+
"id": "req-789",
|
|
433
|
+
"serviceType": "CUSTOM_SERVICE",
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
json_str = json.dumps(request)
|
|
437
|
+
parsed = json.loads(json_str)
|
|
438
|
+
|
|
439
|
+
assert parsed["type"] == "service.stop"
|
|
440
|
+
assert parsed["serviceType"] == "CUSTOM_SERVICE"
|
|
441
|
+
|
|
442
|
+
def test_service_manifest_entry(self):
|
|
443
|
+
"""Test service manifest entry format."""
|
|
444
|
+
service_entry = {
|
|
445
|
+
"type": "CUSTOM_SERVICE",
|
|
446
|
+
"description": "A custom service for testing",
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
manifest = {
|
|
450
|
+
"name": "service-plugin",
|
|
451
|
+
"services": [service_entry],
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
assert manifest["services"][0]["type"] == "CUSTOM_SERVICE"
|
|
455
|
+
assert manifest["services"][0]["description"] == "A custom service for testing"
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
class TestRouteHandling:
|
|
459
|
+
"""Test route handling in bridge server."""
|
|
460
|
+
|
|
461
|
+
def test_route_handle_request(self):
|
|
462
|
+
"""Test route.handle request format."""
|
|
463
|
+
request = {
|
|
464
|
+
"type": "route.handle",
|
|
465
|
+
"id": "req-101",
|
|
466
|
+
"path": "/api/test",
|
|
467
|
+
"request": {
|
|
468
|
+
"method": "GET",
|
|
469
|
+
"body": {},
|
|
470
|
+
"params": {},
|
|
471
|
+
"query": {"limit": "10"},
|
|
472
|
+
"headers": {"authorization": "Bearer token"},
|
|
473
|
+
},
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
json_str = json.dumps(request)
|
|
477
|
+
parsed = json.loads(json_str)
|
|
478
|
+
|
|
479
|
+
assert parsed["type"] == "route.handle"
|
|
480
|
+
assert parsed["path"] == "/api/test"
|
|
481
|
+
assert parsed["request"]["query"]["limit"] == "10"
|
|
482
|
+
|
|
483
|
+
def test_route_result_success(self):
|
|
484
|
+
"""Test route.result success format."""
|
|
485
|
+
response = {
|
|
486
|
+
"type": "route.result",
|
|
487
|
+
"id": "req-101",
|
|
488
|
+
"status": 200,
|
|
489
|
+
"body": {"data": [1, 2, 3]},
|
|
490
|
+
"headers": {"content-type": "application/json"},
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
assert response["status"] == 200
|
|
494
|
+
assert response["body"]["data"] == [1, 2, 3]
|
|
495
|
+
|
|
496
|
+
def test_route_result_error(self):
|
|
497
|
+
"""Test route.result error format."""
|
|
498
|
+
response = {
|
|
499
|
+
"type": "route.result",
|
|
500
|
+
"id": "req-101",
|
|
501
|
+
"status": 404,
|
|
502
|
+
"body": {"error": "Not found"},
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
assert response["status"] == 404
|
|
506
|
+
assert response["body"]["error"] == "Not found"
|
|
507
|
+
|
|
508
|
+
def test_route_manifest_entry(self):
|
|
509
|
+
"""Test route manifest entry format."""
|
|
510
|
+
route_entry = {
|
|
511
|
+
"path": "/api/users",
|
|
512
|
+
"type": "GET",
|
|
513
|
+
"public": True,
|
|
514
|
+
"name": "get_users",
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
manifest = {
|
|
518
|
+
"name": "route-plugin",
|
|
519
|
+
"routes": [route_entry],
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
assert manifest["routes"][0]["path"] == "/api/users"
|
|
523
|
+
assert manifest["routes"][0]["type"] == "GET"
|
|
524
|
+
assert manifest["routes"][0]["public"] is True
|
|
525
|
+
|