@@ -17,19 +17,33 @@ Python port of Vercel Chat SDK. Multi-platform async chat framework.
1717- ` src/chat_sdk/adapters/ ` -- 8 platform adapters
1818- ` src/chat_sdk/shared/ ` -- Markdown parser, format converter, streaming renderer
1919- ` src/chat_sdk/state/ ` -- Memory, Redis, Postgres backends
20- - ` tests/ ` -- 2,477+ tests
21-
22- ## Critical Rules
23- 1 . ** Never use ` datetime.utcnow() ` ** -- use ` datetime.now(tz=timezone.utc) `
24- 2 . ** Never use ` asyncio.ensure_future ` ** -- use ` asyncio.get_running_loop().create_task() `
25- 3 . ** Never pass raw dicts to ` self._chat.process_* ` ** -- use typed dataclasses (ActionEvent, ReactionEvent, etc.)
26- 4 . ** Never use camelCase keys in dispatch dicts** -- always snake_case
27- 5 . ** Never use ` random.choices ` for security tokens** -- use ` secrets.token_hex `
28- 6 . ** Never import optional deps at module level** -- lazy import inside functions
29- 7 . ** Always use ` hmac.compare_digest ` for signature verification** -- never ` == `
30- 8 . ** Always use ` is not None ` for empty-string-valid fields** -- never ` or `
31- 9 . ** Always validate external URLs before HTTP requests** (SSRF prevention)
32- 10 . ** Always check ` extend_lock ` return value** in processing loops
20+ - ` tests/ ` -- 3,267 tests
21+
22+ ## Principles
23+
24+ 1 . ** Every test must fail when the code is wrong.** No ` assert True ` stubs, no
25+ bare truthiness checks when specific values are available, no MagicMock where
26+ AsyncMock is needed. If a test can't catch a regression, it's not a test.
27+ 2 . ** Every async call must be awaited.** Unawaited coroutines silently return
28+ truthy objects. Use AsyncMock (not MagicMock) in tests to surface these.
29+ 3 . ** No two tests should verify the same thing.** Duplicates inflate counts
30+ without catching more bugs.
31+
32+ ## Port Rules (TS → Python)
33+
34+ These are specific patterns that broke during the port. The principles above
35+ explain * why* ; these explain * what to watch for* .
36+
37+ - ` datetime.utcnow() ` → ` datetime.now(tz=UTC) ` (deprecated, naive)
38+ - ` asyncio.ensure_future ` → ` loop.create_task() ` (deprecated)
39+ - Raw dicts to ` process_* ` → typed dataclasses (ActionEvent, etc.)
40+ - camelCase dispatch keys → snake_case
41+ - ` random.choices ` for tokens → ` secrets.token_hex `
42+ - Optional deps at module level → lazy import
43+ - ` == ` for signatures → ` hmac.compare_digest `
44+ - ` or ` for empty-string-valid fields → ` is not None `
45+ - Validate external URLs before requests (SSRF)
46+ - Check ` extend_lock ` return value in loops
3347
3448## Adding a New Adapter
3549See docs/ARCHITECTURE.md and CONTRIBUTING.md.
@@ -42,17 +56,12 @@ See docs/UPSTREAM_SYNC.md for TS->Python translation patterns.
4256- StreamingMarkdownRenderer's _ remend is simplified vs the npm ` remend ` library
4357- No setext headings, no footnotes, no HTML nodes in the parser
4458
45- ## Test Fidelity Verification
59+ ## Test Quality
4660
47- After modifying or adding tests, run:
48- ``` bash
49- python3 scripts/verify_test_fidelity.py
50- ```
51- This verifies every TS ` it("...") ` test has a matching Python ` def test_...() ` .
52- The script must show ` 0 missing ` before committing test changes.
61+ ** CI runs ` scripts/audit_test_quality.py ` before tests.** It catches phantoms,
62+ async mock bugs, and cross-file duplicates. PRs that introduce hard failures
63+ will not pass CI.
5364
54- When porting a new TS test file:
55- 1 . Add the mapping to ` scripts/verify_test_fidelity.py ` MAPPING dict
56- 2 . Run with ` --fix ` to generate stubs
57- 3 . Translate each stub by reading the TS test body line-by-line
58- 4 . Verify with the script before committing
65+ ** Fidelity check** (` scripts/verify_test_fidelity.py ` ) verifies every TS
66+ ` it("...") ` has a matching Python ` def test_*() ` . Name match ≠ faithful port —
67+ the audit script catches the quality side.
0 commit comments