gabriel / musehub public
test_api_contracts.py python
678 lines 26.9 KB
Raw
sha256:9590cee1e0ccd6c76528f005b95d634d80f5019f0dcb7c371e149adc31d1fb65 refactor: enforce gRPC framing on all MWP wire traffic Sonnet 4.6 minor ⚠ breaking 156 days ago
1 """Section 37 — API Contracts & OpenAPI: 7-layer test suite.
2
3 Layers:
4 1. Unit – to_camel, CamelModel config, model field validation
5 2. Integration – OpenAPI schema generation, all paths have 2xx + summaries
6 3. E2E – /api/openapi.json, /docs, component schemas via test client
7 4. Stress – idempotent generation, bulk serialization
8 5. Data Integrity – camelCase round-trips, required fields, alias correctness
9 6. Security – error shapes, 401 not 500, no stack traces in responses
10 7. Performance – schema under 1 second, model validation under 500ms
11 """
12 from __future__ import annotations
13
14 import time
15 import warnings
16 from datetime import datetime, timezone
17 from typing import Any
18
19 import pytest
20
21 # ── suppress known alias warnings from Pydantic union fields ──────────────────
22 warnings.filterwarnings("ignore", message=".*alias.*Field.*no effect.*")
23
24 # ── imports ───────────────────────────────────────────────────────────────────
25 from musehub.models.base import CamelModel, to_camel
26 from musehub.models.musehub import (
27 BranchResponse,
28 CommitResponse,
29 IssueResponse,
30 ReleaseResponse,
31 RepoResponse,
32 TagResponse,
33 )
34
35 # ─────────────────────────────────────────────────────────────────────────────
36 # LAYER 1 — UNIT
37 # ─────────────────────────────────────────────────────────────────────────────
38
39
40 class TestToCamel:
41 """Unit tests for the to_camel converter."""
42
43 def test_single_word_unchanged(self):
44 assert to_camel("name") == "name"
45
46 def test_two_word_snake(self):
47 assert to_camel("repo_id") == "repoId"
48
49 def test_three_word_snake(self):
50 assert to_camel("head_commit_id") == "headCommitId"
51
52 def test_is_prefix(self):
53 assert to_camel("is_verified") == "isVerified"
54
55 def test_all_caps_word(self):
56 # Each segment after split capitalised by .capitalize()
57 assert to_camel("snake_url") == "snakeUrl"
58
59 def test_empty_string(self):
60 assert to_camel("") == ""
61
62 def test_already_camel_unchanged(self):
63 # No underscores → parts[0] only
64 assert to_camel("repoId") == "repoId"
65
66 def test_trailing_underscore(self):
67 # trailing underscore adds empty segment → no change after capitalize
68 assert to_camel("name_") == "name"
69
70 def test_multiple_underscores(self):
71 assert to_camel("a_b_c_d") == "aBCD"
72
73 def test_return_type_is_str(self):
74 assert isinstance(to_camel("some_field"), str)
75
76
77 class TestCamelModelConfig:
78 """Unit tests for CamelModel base class configuration."""
79
80 def test_alias_generator_set(self):
81 assert CamelModel.model_config.get("alias_generator") is to_camel
82
83 def test_populate_by_name_enabled(self):
84 assert CamelModel.model_config.get("populate_by_name") is True
85
86 def test_subclass_inherits_alias_generator(self):
87 class MyModel(CamelModel):
88 my_field: str
89
90 m = MyModel(my_field="x")
91 dumped = m.model_dump(by_alias=True)
92 assert "myField" in dumped
93 assert dumped["myField"] == "x"
94
95 def test_populate_by_snake_name(self):
96 class MyModel(CamelModel):
97 repo_id: str
98
99 m = MyModel(repo_id="abc")
100 assert m.repo_id == "abc"
101
102 def test_populate_by_camel_alias(self):
103 class MyModel(CamelModel):
104 repo_id: str
105
106 m = MyModel(**{"repoId": "abc"})
107 assert m.repo_id == "abc"
108
109 def test_snake_dump_default(self):
110 class MyModel(CamelModel):
111 repo_id: str
112
113 m = MyModel(repo_id="x")
114 assert "repo_id" in m.model_dump()
115 assert "repoId" not in m.model_dump()
116
117 def test_camel_dump_by_alias(self):
118 class MyModel(CamelModel):
119 repo_id: str
120
121 m = MyModel(repo_id="x")
122 assert "repoId" in m.model_dump(by_alias=True)
123
124
125 class TestModelFieldValidation:
126 """Unit tests for required fields on key response models."""
127
128 def test_repo_response_required_fields(self):
129 fields = RepoResponse.model_fields
130 for f in ("repo_id", "name", "owner", "slug", "visibility", "owner_user_id",
131 "created_at", "updated_at"):
132 assert f in fields, f"{f} missing from RepoResponse"
133
134 def test_commit_response_required_fields(self):
135 fields = CommitResponse.model_fields
136 for f in ("commit_id", "branch", "message", "author", "timestamp"):
137 assert f in fields, f"{f} missing from CommitResponse"
138
139 def test_issue_response_required_fields(self):
140 fields = IssueResponse.model_fields
141 for f in ("issue_id", "number", "title", "state", "author"):
142 assert f in fields, f"{f} missing from IssueResponse"
143
144 def test_release_response_required_fields(self):
145 fields = ReleaseResponse.model_fields
146 for f in ("release_id", "tag", "title", "commit_id"):
147 assert f in fields, f"{f} missing from ReleaseResponse"
148
149 def test_tag_response_required_fields(self):
150 fields = TagResponse.model_fields
151 for f in ("tag", "namespace", "commit_id", "created_at"):
152 assert f in fields, f"{f} missing from TagResponse"
153
154 def test_branch_response_required_fields(self):
155 fields = BranchResponse.model_fields
156 for f in ("branch_id", "name", "head_commit_id"):
157 assert f in fields, f"{f} missing from BranchResponse"
158
159 def test_repo_response_json_schema_has_properties(self):
160 schema = RepoResponse.model_json_schema()
161 assert "properties" in schema or "$defs" in schema
162
163 def test_commit_response_json_schema_generated(self):
164 schema = CommitResponse.model_json_schema()
165 assert schema.get("type") == "object" or "properties" in schema or "allOf" in schema
166
167
168 # ─────────────────────────────────────────────────────────────────────────────
169 # LAYER 2 — INTEGRATION
170 # ─────────────────────────────────────────────────────────────────────────────
171
172
173 @pytest.fixture(scope="module")
174 def openapi_schema():
175 """Generate the OpenAPI schema once per module."""
176 from musehub.main import app
177
178 return app.openapi()
179
180
181 class TestOpenAPISchemaGeneration:
182 """Integration tests for the generated OpenAPI schema."""
183
184 def test_openapi_version_is_3_1(self, openapi_schema):
185 assert openapi_schema["openapi"].startswith("3.1")
186
187 def test_has_info_block(self, openapi_schema):
188 assert "info" in openapi_schema
189 assert "title" in openapi_schema["info"]
190
191 def test_path_count_at_least_170(self, openapi_schema):
192 assert len(openapi_schema["paths"]) >= 170
193
194 def test_component_schema_count_at_least_130(self, openapi_schema):
195 schemas = openapi_schema.get("components", {}).get("schemas", {})
196 assert len(schemas) >= 130
197
198 def test_all_operations_have_summary(self, openapi_schema):
199 HTTP_METHODS = {"get", "post", "put", "patch", "delete", "head", "options"}
200 missing = []
201 for path, methods in openapi_schema["paths"].items():
202 for method, op in methods.items():
203 if method in HTTP_METHODS and not op.get("summary"):
204 missing.append(f"{method.upper()} {path}")
205 assert missing == [], f"Operations missing summary: {missing[:5]}"
206
207 def test_all_operations_have_2xx_response(self, openapi_schema):
208 HTTP_METHODS = {"get", "post", "put", "patch", "delete", "head", "options"}
209 missing = []
210 for path, methods in openapi_schema["paths"].items():
211 for method, op in methods.items():
212 if method in HTTP_METHODS:
213 has_2xx = any(
214 str(code).startswith("2") for code in op.get("responses", {})
215 )
216 if not has_2xx:
217 missing.append(f"{method.upper()} {path}")
218 assert missing == [], f"Operations missing 2xx response: {missing[:5]}"
219
220 def test_schema_is_dict(self, openapi_schema):
221 assert isinstance(openapi_schema, dict)
222
223 def test_schema_has_paths_key(self, openapi_schema):
224 assert "paths" in openapi_schema
225
226 def test_schema_has_components_key(self, openapi_schema):
227 assert "components" in openapi_schema
228
229 def test_total_operation_count(self, openapi_schema):
230 HTTP_METHODS = {"get", "post", "put", "patch", "delete", "head", "options"}
231 total = sum(
232 1
233 for methods in openapi_schema["paths"].values()
234 for m in methods
235 if m in HTTP_METHODS
236 )
237 assert total >= 190
238
239 def test_key_response_models_in_components(self, openapi_schema):
240 schemas = openapi_schema.get("components", {}).get("schemas", {})
241 for model_name in ("RepoResponse", "CommitResponse", "IssueResponse"):
242 assert model_name in schemas, f"{model_name} missing from component schemas"
243
244 def test_validation_error_schema_present(self, openapi_schema):
245 schemas = openapi_schema.get("components", {}).get("schemas", {})
246 assert "HTTPValidationError" in schemas
247
248 def test_schema_serialisable_to_json(self, openapi_schema):
249 import json
250
251 raw = json.dumps(openapi_schema)
252 assert len(raw) > 100_000 # schema is large
253
254
255 # ─────────────────────────────────────────────────────────────────────────────
256 # LAYER 3 — E2E (via test client)
257 # ─────────────────────────────────────────────────────────────────────────────
258
259
260 class TestOpenAPIEndpointsE2E:
261 """E2E tests hitting /api/openapi.json and /docs via the test client."""
262
263 async def test_openapi_json_returns_200(self, client):
264 resp = await client.get("/api/openapi.json")
265 assert resp.status_code == 200
266
267 async def test_openapi_json_content_type(self, client):
268 resp = await client.get("/api/openapi.json")
269 assert "application/json" in resp.headers["content-type"]
270
271 async def test_openapi_json_has_paths(self, client):
272 resp = await client.get("/api/openapi.json")
273 body = resp.json()
274 assert "paths" in body
275 assert len(body["paths"]) >= 170
276
277 async def test_openapi_json_version_3_1(self, client):
278 resp = await client.get("/api/openapi.json")
279 assert resp.json()["openapi"].startswith("3.1")
280
281 async def test_openapi_json_has_components(self, client):
282 resp = await client.get("/api/openapi.json")
283 body = resp.json()
284 assert "components" in body
285 assert "schemas" in body["components"]
286
287 async def test_docs_returns_200(self, client):
288 resp = await client.get("/docs")
289 assert resp.status_code == 200
290
291 async def test_docs_returns_html(self, client):
292 resp = await client.get("/docs")
293 assert "text/html" in resp.headers["content-type"]
294
295 async def test_redoc_returns_200(self, client):
296 resp = await client.get("/redoc")
297 assert resp.status_code == 200
298
299 async def test_openapi_json_repo_response_in_schemas(self, client):
300 resp = await client.get("/api/openapi.json")
301 schemas = resp.json()["components"]["schemas"]
302 assert "RepoResponse" in schemas
303
304 async def test_openapi_json_commit_response_in_schemas(self, client):
305 resp = await client.get("/api/openapi.json")
306 schemas = resp.json()["components"]["schemas"]
307 assert "CommitResponse" in schemas
308
309 async def test_openapi_json_issue_response_in_schemas(self, client):
310 resp = await client.get("/api/openapi.json")
311 schemas = resp.json()["components"]["schemas"]
312 assert "IssueResponse" in schemas
313
314 async def test_openapi_json_release_response_in_schemas(self, client):
315 resp = await client.get("/api/openapi.json")
316 schemas = resp.json()["components"]["schemas"]
317 assert "ReleaseResponse" in schemas
318
319
320 # ─────────────────────────────────────────────────────────────────────────────
321 # LAYER 4 — STRESS
322 # ─────────────────────────────────────────────────────────────────────────────
323
324
325 class TestOpenAPIStress:
326 """Stress tests: idempotency and bulk serialisation."""
327
328 def test_schema_generation_idempotent(self):
329 from musehub.main import app
330
331 s1 = app.openapi()
332 s2 = app.openapi()
333 assert s1 == s2
334
335 def test_schema_path_count_stable_across_calls(self):
336 from musehub.main import app
337
338 counts = [len(app.openapi()["paths"]) for _ in range(3)]
339 assert len(set(counts)) == 1
340
341 def test_bulk_repo_response_serialisation(self):
342 """Serialise 500 RepoResponse objects — must not raise."""
343 ts = datetime(2025, 1, 1, tzinfo=timezone.utc)
344 # Build one valid instance first to discover required fields
345 sample = dict(
346 repo_id="rid",
347 name="repo",
348 owner="gabriel",
349 slug="repo",
350 visibility="public",
351 owner_user_id="uid",
352 clone_url="http://localhost:1337/gabriel/repo",
353 tags=[],
354 created_at=ts,
355 updated_at=ts,
356 )
357 # Try with optional fields omitted — Pydantic will apply defaults
358 proto = RepoResponse.model_validate(sample)
359 for i in range(500):
360 r = RepoResponse.model_validate({**sample, "repo_id": f"rid-{i}"})
361 r.model_dump(by_alias=True)
362
363 def test_bulk_commit_response_serialisation(self):
364 """Serialise 500 CommitResponse objects."""
365 ts = datetime(2025, 1, 1, tzinfo=timezone.utc)
366 for i in range(500):
367 c = CommitResponse(
368 commit_id=f"cid-{i:040d}",
369 branch="main",
370 parent_ids=[],
371 message=f"commit {i}",
372 author="gabriel",
373 timestamp=ts,
374 snapshot_id=f"sid-{i}",
375 )
376 c.model_dump(by_alias=True)
377
378 def test_to_camel_bulk(self):
379 """Convert 10 000 snake_case strings without error."""
380 samples = [
381 "repo_id", "head_commit_id", "is_verified", "owner_user_id",
382 "created_at", "updated_at", "download_urls", "semver_major",
383 ]
384 for _ in range(1250):
385 for s in samples:
386 to_camel(s)
387
388 def test_model_json_schema_repeated(self):
389 """model_json_schema() called 50 times produces consistent output."""
390 schemas = [RepoResponse.model_json_schema() for _ in range(50)]
391 first = schemas[0]
392 for s in schemas[1:]:
393 assert s == first
394
395
396 # ─────────────────────────────────────────────────────────────────────────────
397 # LAYER 5 — DATA INTEGRITY
398 # ─────────────────────────────────────────────────────────────────────────────
399
400
401 class TestCamelCaseRoundTrip:
402 """Data integrity: camelCase alias round-trips."""
403
404 def _make_repo(self) -> RepoResponse:
405 ts = datetime(2025, 6, 1, tzinfo=timezone.utc)
406 return RepoResponse.model_validate(dict(
407 repo_id="repo-abc",
408 name="my-repo",
409 owner="gabriel",
410 slug="my-repo",
411 visibility="public",
412 owner_user_id="uid-1",
413 clone_url="http://localhost:1337/gabriel/my-repo",
414 tags=["music", "demo"],
415 tempo_bpm=120.0,
416 created_at=ts,
417 updated_at=ts,
418 ))
419
420 def test_repo_camel_keys(self):
421 wire = self._make_repo().model_dump(by_alias=True)
422 assert "repoId" in wire
423 assert "ownerId" not in wire # field is owner_user_id → ownerUserId
424 assert "ownerUserId" in wire
425 assert "createdAt" in wire
426
427 def test_repo_round_trip(self):
428 original = self._make_repo()
429 wire = original.model_dump(by_alias=True)
430 restored = RepoResponse.model_validate(wire)
431 assert restored.model_dump(by_alias=True) == wire
432
433 def test_repo_snake_dump_no_camel(self):
434 wire = self._make_repo().model_dump()
435 assert "repo_id" in wire
436 assert "repoId" not in wire
437
438 def test_commit_camel_keys(self):
439 ts = datetime(2025, 1, 1, tzinfo=timezone.utc)
440 c = CommitResponse(
441 commit_id="abc123",
442 branch="main",
443 parent_ids=["p1"],
444 message="init",
445 author="gabriel",
446 timestamp=ts,
447 snapshot_id="snap1",
448 )
449 wire = c.model_dump(by_alias=True)
450 assert "commitId" in wire
451 assert "parentIds" in wire
452 assert "snapshotId" in wire
453
454 def test_issue_camel_keys(self):
455 ts = datetime(2025, 1, 1, tzinfo=timezone.utc)
456 i = IssueResponse.model_validate(dict(
457 issue_id="iss-1",
458 number=1,
459 title="Bug",
460 body="",
461 state="open",
462 labels=[],
463 author="gabriel",
464 created_at=ts,
465 updated_at=ts,
466 comment_count=0,
467 ))
468 wire = i.model_dump(by_alias=True)
469 assert "issueId" in wire
470 assert "commentCount" in wire
471
472 def test_tag_camel_keys(self):
473 ts = datetime(2025, 1, 1, tzinfo=timezone.utc)
474 t = TagResponse.model_validate(dict(
475 tag="v1.0",
476 namespace="release",
477 commit_id="abc123",
478 created_at=ts,
479 ))
480 wire = t.model_dump(by_alias=True)
481 assert "commitId" in wire
482 assert "createdAt" in wire
483
484 def test_branch_camel_keys(self):
485 b = BranchResponse(branch_id="bid-1", name="main", head_commit_id="abc")
486 wire = b.model_dump(by_alias=True)
487 assert "branchId" in wire
488 assert "headCommitId" in wire
489
490 def test_camelmodel_requires_snake_or_camel_on_input(self):
491 class M(CamelModel):
492 owner_id: str
493
494 m1 = M(owner_id="a")
495 m2 = M(**{"ownerId": "a"})
496 assert m1.owner_id == m2.owner_id
497
498 def test_alias_generator_consistent(self):
499 """All model fields with underscores produce valid camelCase aliases."""
500 for field_name in RepoResponse.model_fields:
501 alias = to_camel(field_name)
502 assert "_" not in alias or field_name == alias # no underscores in result
503
504
505 # ─────────────────────────────────────────────────────────────────────────────
506 # LAYER 6 — SECURITY
507 # ─────────────────────────────────────────────────────────────────────────────
508
509
510 class TestAPIContractsSecurity:
511 """Security: proper error shapes, no stack traces, auth enforced."""
512
513 async def test_invalid_json_body_returns_422_not_500(self, client, auth_headers):
514 # Sending invalid JSON to a POST endpoint should yield 422
515 resp = await client.post(
516 "/api/repos",
517 content=b"not-json",
518 headers={**auth_headers, "content-type": "application/json"},
519 )
520 assert resp.status_code in (400, 422), f"Expected 4xx, got {resp.status_code}"
521
522 async def test_422_response_has_detail_key(self, client, auth_headers):
523 resp = await client.post(
524 "/api/repos",
525 content=b"not-json",
526 headers={**auth_headers, "content-type": "application/json"},
527 )
528 if resp.status_code == 422:
529 body = resp.json()
530 assert "detail" in body
531
532 async def test_missing_required_body_field_returns_422(self, client, auth_headers):
533 # POST /repos with empty JSON object — missing required fields
534 resp = await client.post(
535 "/api/repos",
536 json={},
537 headers=auth_headers,
538 )
539 assert resp.status_code == 422
540
541 async def test_openapi_json_no_stack_trace(self, client):
542 resp = await client.get("/api/openapi.json")
543 body = resp.text
544 assert "Traceback" not in body
545 # Check for Python exception raise syntax (not natural English "raise")
546 assert "raise Exception" not in body
547 assert "raise ValueError" not in body
548 assert "raise HTTPException" not in body
549
550 async def test_error_body_no_python_exception_class(self, client, auth_headers):
551 resp = await client.post(
552 "/api/repos",
553 json={"x": 1},
554 headers=auth_headers,
555 )
556 # Should be 422, body should not leak Python exception names
557 if resp.status_code != 200:
558 body = resp.text
559 assert "Exception" not in body or "detail" in resp.json()
560
561 async def test_unknown_api_route_returns_404(self, client):
562 # /api/* routes are strict — unknown API paths return 404
563 resp = await client.get("/api/this-route-does-not-exist-xyz-abc-12345")
564 assert resp.status_code == 404
565
566 async def test_404_response_no_traceback(self, client):
567 resp = await client.get("/api/totally-unknown-endpoint-xyzabc-99999")
568 assert "Traceback" not in resp.text
569
570 async def test_auth_required_route_returns_401_not_500(self, client):
571 # Access a protected route WITHOUT auth headers — must yield 401, not 500
572 resp = await client.get("/api/repos")
573 assert resp.status_code == 401, f"Expected 401, got {resp.status_code}"
574
575 async def test_method_not_allowed_returns_405(self, client, auth_headers):
576 # DELETE on a GET-only endpoint should yield 405
577 resp = await client.delete("/api/openapi.json", headers=auth_headers)
578 assert resp.status_code in (405, 404) # FastAPI returns 405 for wrong method
579
580 async def test_openapi_json_not_exposing_server_internals(self, client):
581 resp = await client.get("/api/openapi.json")
582 body = resp.text
583 # Should not contain local filesystem paths
584 assert "/Users/" not in body
585 assert "/home/" not in body
586
587
588 # ─────────────────────────────────────────────────────────────────────────────
589 # LAYER 7 — PERFORMANCE
590 # ─────────────────────────────────────────────────────────────────────────────
591
592
593 class TestAPIContractsPerformance:
594 """Performance: schema generation and model validation within time budgets."""
595
596 def test_openapi_schema_generation_under_1_second(self):
597 from musehub.main import app
598
599 # Clear cached schema to force regeneration
600 app.openapi_schema = None
601 start = time.perf_counter()
602 app.openapi()
603 elapsed = time.perf_counter() - start
604 assert elapsed < 1.0, f"Schema generation took {elapsed:.3f}s (limit 1.0s)"
605
606 def test_openapi_cached_retrieval_under_10ms(self):
607 from musehub.main import app
608
609 # Ensure schema is cached
610 app.openapi()
611 start = time.perf_counter()
612 for _ in range(10):
613 app.openapi()
614 elapsed = time.perf_counter() - start
615 avg = elapsed / 10
616 assert avg < 0.010, f"Cached schema avg retrieval {avg*1000:.1f}ms (limit 10ms)"
617
618 def test_to_camel_10k_strings_under_100ms(self):
619 samples = [
620 "repo_id", "head_commit_id", "is_verified", "owner_user_id",
621 "created_at", "updated_at", "download_urls", "semver_major",
622 ]
623 start = time.perf_counter()
624 for _ in range(1250):
625 for s in samples:
626 to_camel(s)
627 elapsed = time.perf_counter() - start
628 assert elapsed < 0.100, f"to_camel 10K calls took {elapsed*1000:.1f}ms (limit 100ms)"
629
630 def test_1k_repo_responses_validated_under_500ms(self):
631 ts = datetime(2025, 1, 1, tzinfo=timezone.utc)
632 base = {
633 "repo_id": "rid",
634 "name": "repo",
635 "owner": "gabriel",
636 "slug": "repo",
637 "visibility": "public",
638 "owner_user_id": "uid",
639 "clone_url": "http://localhost:1337/gabriel/repo",
640 "tags": [],
641 "created_at": ts.isoformat(),
642 "updated_at": ts.isoformat(),
643 }
644 data = [{**base, "repo_id": f"rid-{i}"} for i in range(1000)]
645 start = time.perf_counter()
646 for d in data:
647 RepoResponse.model_validate(d)
648 elapsed = time.perf_counter() - start
649 assert elapsed < 0.500, f"1K RepoResponse validates took {elapsed*1000:.1f}ms (limit 500ms)"
650
651 async def test_openapi_json_endpoint_under_200ms(self, client):
652 # First call may be slower; measure after warmup
653 await client.get("/api/openapi.json")
654 start = time.perf_counter()
655 resp = await client.get("/api/openapi.json")
656 elapsed = time.perf_counter() - start
657 assert resp.status_code == 200
658 assert elapsed < 0.200, f"/api/openapi.json took {elapsed*1000:.1f}ms (limit 200ms)"
659
660 def test_model_dump_by_alias_1k_under_200ms(self):
661 ts = datetime(2025, 1, 1, tzinfo=timezone.utc)
662 instances = [
663 CommitResponse(
664 commit_id=f"cid-{i:040d}",
665 branch="main",
666 parent_ids=[],
667 message=f"msg {i}",
668 author="gabriel",
669 timestamp=ts,
670 snapshot_id=f"sid-{i}",
671 )
672 for i in range(1000)
673 ]
674 start = time.perf_counter()
675 for inst in instances:
676 inst.model_dump(by_alias=True)
677 elapsed = time.perf_counter() - start
678 assert elapsed < 0.200, f"1K model_dump(by_alias) took {elapsed*1000:.1f}ms (limit 200ms)"
File History 1 commit
sha256:9590cee1e0ccd6c76528f005b95d634d80f5019f0dcb7c371e149adc31d1fb65 refactor: enforce gRPC framing on all MWP wire traffic Sonnet 4.6 minor ⚠ 156 days ago