asap-protocol 0.1.0__tar.gz → 0.3.0__tar.gz
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.
- asap_protocol-0.3.0/.cursor/dev-planning/code-review/v0.5.0/sprint-s1-code-review.md +73 -0
- asap_protocol-0.3.0/.cursor/dev-planning/code-review/v0.5.0/sprint-s2-code-review.md +94 -0
- asap_protocol-0.3.0/.cursor/dev-planning/code-review/v0.5.0/sprint-s2.5-code-review.md +444 -0
- asap_protocol-0.3.0/.cursor/dev-planning/prd/prd-review-schedule.md +226 -0
- asap_protocol-0.3.0/.cursor/dev-planning/prd/prd-v1-roadmap.md +1195 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v0.5.0/README-v0.5.0-tasks.md +91 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v0.5.0/tasks-v0.5.0-roadmap.md +381 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v0.5.0/tasks-v0.5.0-s1-detailed.md +319 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v0.5.0/tasks-v0.5.0-s2-detailed.md +316 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v0.5.0/tasks-v0.5.0-s2.5-detailed.md +942 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v0.5.0/tasks-v0.5.0-s3-detailed.md +282 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v0.5.0/tasks-v0.5.0-s4-detailed.md +209 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v0.5.0/tasks-v0.5.0-s5-detailed.md +254 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v1.0.0/README-v1.0.0-tasks.md +81 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v1.0.0/tasks-v1.0.0-docs-detailed.md +225 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v1.0.0/tasks-v1.0.0-dx-detailed.md +249 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v1.0.0/tasks-v1.0.0-observability-detailed.md +180 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v1.0.0/tasks-v1.0.0-performance-detailed.md +180 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v1.0.0/tasks-v1.0.0-release-detailed.md +209 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v1.0.0/tasks-v1.0.0-roadmap.md +434 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v1.0.0/tasks-v1.0.0-security-detailed.md +212 -0
- asap_protocol-0.3.0/.cursor/dev-planning/tasks/v1.0.0/tasks-v1.0.0-testing-detailed.md +211 -0
- asap_protocol-0.3.0/.cursor/docs/asap-protocol-banner.png +0 -0
- asap_protocol-0.3.0/.cursor/docs/sprint-planning-v0.1.0.md +240 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.cursor/rules/architecture-principles.mdc +3 -3
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.cursor/rules/git-commits.mdc +18 -2
- asap_protocol-0.3.0/.github/dependabot.yml +13 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.github/workflows/ci.yml +3 -2
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.gitignore +2 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/CHANGELOG.md +32 -19
- asap_protocol-0.3.0/CONTRIBUTING.md +139 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/PKG-INFO +49 -73
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/README.md +44 -71
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/SECURITY.md +39 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/docs/index.md +2 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/docs/security.md +331 -22
- asap_protocol-0.3.0/docs/testing.md +597 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/mkdocs.yml +1 -1
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/pyproject.toml +13 -2
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/__init__.py +1 -1
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/errors.py +42 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/examples/coordinator.py +1 -1
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/examples/echo_agent.py +1 -1
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/models/constants.py +1 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/observability/metrics.py +1 -0
- asap_protocol-0.3.0/src/asap/transport/executors.py +156 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/transport/handlers.py +17 -5
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/transport/middleware.py +298 -3
- asap_protocol-0.3.0/src/asap/transport/server.py +1133 -0
- asap_protocol-0.3.0/tests/examples/test_coordinator.py +302 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/test_errors.py +46 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/test_version.py +1 -1
- asap_protocol-0.3.0/tests/transport/conftest.py +293 -0
- asap_protocol-0.3.0/tests/transport/e2e/__init__.py +5 -0
- asap_protocol-0.1.0/tests/transport/test_integration.py → asap_protocol-0.3.0/tests/transport/e2e/test_full_agent_flow.py +16 -11
- asap_protocol-0.3.0/tests/transport/integration/__init__.py +8 -0
- asap_protocol-0.3.0/tests/transport/integration/test_metrics_cardinality.py +141 -0
- asap_protocol-0.3.0/tests/transport/integration/test_rate_limiting.py +248 -0
- asap_protocol-0.3.0/tests/transport/integration/test_request_size_limits.py +227 -0
- asap_protocol-0.1.0/tests/transport/test_server.py → asap_protocol-0.3.0/tests/transport/integration/test_server_core.py +32 -105
- asap_protocol-0.3.0/tests/transport/integration/test_thread_pool_bounds.py +238 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/transport/test_handlers.py +1 -1
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/transport/test_jsonrpc.py +10 -10
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/transport/test_middleware.py +256 -1
- asap_protocol-0.3.0/tests/transport/test_server.py +662 -0
- asap_protocol-0.3.0/tests/transport/unit/__init__.py +5 -0
- asap_protocol-0.3.0/tests/transport/unit/test_bounded_executor.py +174 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/uv.lock +123 -2
- asap_protocol-0.1.0/CONTRIBUTING.md +0 -49
- asap_protocol-0.1.0/docs/testing.md +0 -51
- asap_protocol-0.1.0/src/asap/transport/server.py +0 -739
- asap_protocol-0.1.0/tests/examples/test_coordinator.py +0 -120
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.cursor/commands/code-quality-review.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.cursor/commands/create-prd.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.cursor/commands/generate-tasks.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.cursor/commands/reflect.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.cursor/commands/security-pr-review.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.cursor/commands/security-review.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.cursor/commands/task-list-development.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.cursor/commands/test-coverage-review.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.cursor/dev-planning/code-review/security-review-report.md +0 -0
- {asap_protocol-0.1.0/.cursor/dev-planning/code-review → asap_protocol-0.3.0/.cursor/dev-planning/code-review/v0.1.0}/pr-8-review-temp.md +0 -0
- {asap_protocol-0.1.0/.cursor/dev-planning/code-review → asap_protocol-0.3.0/.cursor/dev-planning/code-review/v0.1.0}/pre-pypi-release-review.md +0 -0
- {asap_protocol-0.1.0/.cursor/dev-planning/code-review → asap_protocol-0.3.0/.cursor/dev-planning/code-review/v0.1.0}/sprint3-code-review.md +0 -0
- {asap_protocol-0.1.0/.cursor/dev-planning/code-review → asap_protocol-0.3.0/.cursor/dev-planning/code-review/v0.1.0}/sprint4-code-review.md +0 -0
- {asap_protocol-0.1.0/.cursor/dev-planning/code-review → asap_protocol-0.3.0/.cursor/dev-planning/code-review/v0.1.0}/sprint5-code-review.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.cursor/dev-planning/prd/prd-asap-implementation.md +0 -0
- /asap_protocol-0.1.0/.cursor/docs/sprint-planning.md → /asap_protocol-0.3.0/.cursor/dev-planning/tasks/v0.1.0/sprint-planning-v0.1.0.md +0 -0
- {asap_protocol-0.1.0/.cursor/dev-planning/tasks → asap_protocol-0.3.0/.cursor/dev-planning/tasks/v0.1.0}/tasks-prd-asap-implementation.md +0 -0
- {asap_protocol-0.1.0/.cursor/dev-planning/tasks → asap_protocol-0.3.0/.cursor/dev-planning/tasks/v0.1.0}/tasks-security-review-report.md +0 -0
- {asap_protocol-0.1.0/.cursor/dev-planning/tasks → asap_protocol-0.3.0/.cursor/dev-planning/tasks/v0.1.0}/tasks-sprint3-improvements.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.cursor/docs/general-specs.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.cursor/rules/python-best-practices.mdc +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.github/workflows/docs.yml +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/.github/workflows/release.yml +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/CODE_OF_CONDUCT.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/LICENSE +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/benchmarks/README.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/benchmarks/__init__.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/benchmarks/benchmark_models.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/benchmarks/benchmark_transport.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/benchmarks/conftest.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/docs/api-reference.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/docs/error-handling.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/docs/metrics.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/docs/migration.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/docs/observability.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/docs/state-management.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/docs/transport.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/entities/agent.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/entities/artifact.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/entities/conversation.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/entities/manifest.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/entities/message.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/entities/state_snapshot.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/entities/task.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/envelope.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/parts/data_part.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/parts/file_part.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/parts/resource_part.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/parts/template_part.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/parts/text_part.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/payloads/artifact_notify.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/payloads/mcp_resource_data.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/payloads/mcp_resource_fetch.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/payloads/mcp_tool_call.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/payloads/mcp_tool_result.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/payloads/message_send.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/payloads/state_query.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/payloads/state_restore.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/payloads/task_cancel.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/payloads/task_request.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/payloads/task_response.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/schemas/payloads/task_update.schema.json +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/scripts/export_schemas.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/cli.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/examples/README.md +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/examples/__init__.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/examples/run_demo.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/models/__init__.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/models/base.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/models/entities.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/models/enums.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/models/envelope.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/models/ids.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/models/parts.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/models/payloads.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/models/types.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/observability/__init__.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/observability/logging.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/schemas.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/state/__init__.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/state/machine.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/state/snapshot.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/transport/__init__.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/transport/client.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/src/asap/transport/jsonrpc.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/conftest.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/e2e/__init__.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/e2e/test_two_agents.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/examples/__init__.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/examples/test_echo_agent.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/models/test_base.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/models/test_entities.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/models/test_enums.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/models/test_envelope.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/models/test_ids.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/models/test_parts.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/models/test_payloads.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/observability/__init__.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/observability/test_logging.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/observability/test_metrics.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/state/__init__.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/state/test_machine.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/state/test_snapshot.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/test_cli.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/test_schemas.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/transport/__init__.py +0 -0
- {asap_protocol-0.1.0 → asap_protocol-0.3.0}/tests/transport/test_client.py +0 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Code Review: PR #15 (Sprint S1 v0.5.0)
|
|
2
|
+
|
|
3
|
+
## 1. Executive Summary
|
|
4
|
+
|
|
5
|
+
* **Impact Analysis:** **Low Risk**. Changes are primarily refactoring of existing logic and documentation updates, with critical dependency upgrades (`fastapi`).
|
|
6
|
+
* **Architecture Check:** **Yes**. The refactoring significantly improves adherence to the Single Responsibility Principle (SRP) by decomposing the monolithic `handle_message` method.
|
|
7
|
+
* **Blockers:** **0** critical issues found.
|
|
8
|
+
|
|
9
|
+
## 2. Critical Issues (Must Fix)
|
|
10
|
+
|
|
11
|
+
*No critical syntax or logic bugs found.* However, a deeper security analysis revealed architectural risks that should be addressed before high-scale production use.
|
|
12
|
+
|
|
13
|
+
## 2.1. Security & Stability Risks (Deep Dive)
|
|
14
|
+
*Issues that don't crash the build but pose DoS or resource exhaustion risks.*
|
|
15
|
+
|
|
16
|
+
### [DoS Risk] Unbounded Metrics Cardinality - `src/asap/transport/server.py`
|
|
17
|
+
✅ **PLANNED** (v0.5.0 Task 2.9)
|
|
18
|
+
* **Risk:** High. The `payload_type` label is derived directly from user input.
|
|
19
|
+
* **Mitigation:** Whitelist payload types in `HandlerRegistry`.
|
|
20
|
+
|
|
21
|
+
### [DoS Risk] Thread Pool Starvation - `src/asap/transport/handlers.py`
|
|
22
|
+
✅ **PLANNED** (v0.5.0 Task 2.8)
|
|
23
|
+
* **Risk:** Medium. Sync handlers share the default thread pool.
|
|
24
|
+
* **Mitigation:** Implement `BoundedExecutor` with queue depth limits.
|
|
25
|
+
|
|
26
|
+
### [Resource Risk] Unbounded JSON Parsing - `src/asap/transport/server.py`
|
|
27
|
+
✅ **PLANNED** (v0.5.0 Task 2.4)
|
|
28
|
+
* **Risk:** Low/Medium. `await request.json()` loads full body.
|
|
29
|
+
* **Mitigation:** `MAX_REQUEST_SIZE` limit (10MB).
|
|
30
|
+
|
|
31
|
+
## 3. Improvements & Refactoring (Strongly Recommended)
|
|
32
|
+
|
|
33
|
+
The codebase is already in a state of "High Polish" following the refactor. The following are minor suggestions for future iterations, not blockers for this merge.
|
|
34
|
+
|
|
35
|
+
### [Suggestion] Parameter Object for Request Context - `src/asap/transport/server.py`
|
|
36
|
+
* **Location:** Multiple helpers (e.g., `_dispatch_to_handler`, `_authenticate_request`)
|
|
37
|
+
* **Context:** `start_time` and `metrics` are passed to nearly every helper method.
|
|
38
|
+
* **Suggestion:**
|
|
39
|
+
Consider grouping request-scoped context (request ID, start time, metrics) into a `RequestContext` dataclass to reduce method signature noise.
|
|
40
|
+
```python
|
|
41
|
+
@dataclass
|
|
42
|
+
class RequestContext:
|
|
43
|
+
request_id: str | int | None
|
|
44
|
+
start_time: float
|
|
45
|
+
metrics: MetricsCollector
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### [Suggestion] Explicit Type Alias for Response - `src/asap/transport/server.py`
|
|
49
|
+
* **Location:** `ASAPRequestHandler` helpers
|
|
50
|
+
* **Context:** Methods return `tuple[T | None, JSONResponse | None]` or similar variants.
|
|
51
|
+
* **Suggestion:** defined a `Result[T]` type or use `typing.Union` more explicitly to make the success/failure patterns more immediately readable, though the current tuple unpacking pattern is standard and clear enough.
|
|
52
|
+
|
|
53
|
+
## 4. Nitpicks & Questions
|
|
54
|
+
|
|
55
|
+
* **`src/asap/transport/handlers.py` (Line 255):** `dispatch()` checks `inspect.isawaitable(result)` inside a `try/except` block. This is good defensive coding against sync handlers returning futures unexpectedly.
|
|
56
|
+
* **`src/asap/transport/server.py` (Line 699):** The `type: ignore` comments are necessary due to the dynamic return type of the helper tuple pattern. This is acceptable given the strict mypy settings.
|
|
57
|
+
* **`pyproject.toml`:** Upgrading `fastapi` to `0.128.0` is good. Ensure this version is pinned in `requirements.txt` or `uv.lock` (verified it is in `uv.lock`).
|
|
58
|
+
|
|
59
|
+
## 5. Verification Results
|
|
60
|
+
|
|
61
|
+
### Automated Tests
|
|
62
|
+
* **Command:** `uv run pytest tests/transport`
|
|
63
|
+
* **Result:** ✅ **Passed** (187 tests passed in 1.10s)
|
|
64
|
+
* **Coverage:** Transport module coverage is high (~98%).
|
|
65
|
+
|
|
66
|
+
### Static Analysis
|
|
67
|
+
* **Command:** `uv run ruff check src/asap/transport`
|
|
68
|
+
* **Result:** ✅ **Passed** (No issues found)
|
|
69
|
+
* **Type Check:** Implicitly passed via pre-push checks (codebase uses strict mypy).
|
|
70
|
+
|
|
71
|
+
## 6. Conclusion
|
|
72
|
+
|
|
73
|
+
**APPROVED (RISKS MITIGATED).** The refactoring is solid. The security risks identified in Section 2.1 have been formally added to the Sprint S2 roadmap (Tasks 2.4, 2.8, 2.9) and will be addressed immediately. Proceed with merge.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Code Review: PR 16 (DoS Prevention & Rate Limiting)
|
|
2
|
+
|
|
3
|
+
## 1. Executive Summary
|
|
4
|
+
* **Impact Analysis:** **High Risk** (Potential OOM DoS vulnerability remains).
|
|
5
|
+
* **Architecture Check:** **Yes**. The changes largely align with the layered architecture (transport/middleware separation).
|
|
6
|
+
* **Blockers:** **3** critical issues found.
|
|
7
|
+
|
|
8
|
+
## 2. Critical Issues (Must Fix)
|
|
9
|
+
|
|
10
|
+
### [Security/DoS] Unsafe Request Body Reading - `server.py`
|
|
11
|
+
* **Location:** `src/asap/transport/server.py` Lines 694-696 (in `parse_json_body`)
|
|
12
|
+
* **Problem:** The code calls `body_bytes = await request.body()` *before* verifying the actual size. If a malicious client sends a generic `Content-Length` (or none) but streams 10GB of data, `starlette/fastapi` will attempt to buffer the entire request into memory, causing an OOM crash before your size check logic executes.
|
|
13
|
+
* **Recommendation:** Use `request.stream()` to read chunks and count bytes, aborting if the limit is exceeded.
|
|
14
|
+
|
|
15
|
+
```diff
|
|
16
|
+
- body_bytes = await request.body()
|
|
17
|
+
- if len(body_bytes) > self.max_request_size:
|
|
18
|
+
+ body_bytes = bytearray()
|
|
19
|
+
+ async for chunk in request.stream():
|
|
20
|
+
+ body_bytes.extend(chunk)
|
|
21
|
+
+ if len(body_bytes) > self.max_request_size:
|
|
22
|
+
logger.warning(...)
|
|
23
|
+
raise HTTPException(status_code=413, ...)
|
|
24
|
+
+ body = json.loads(body_bytes.decode("utf-8"))
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### [Logic/RateLimit] Sender ID Extraction Failure - `middleware.py`
|
|
28
|
+
* **Location:** `src/asap/transport/middleware.py` Lines 76-98 (in `_get_sender_from_envelope`)
|
|
29
|
+
* **Problem:** `slowapi`'s `key_func` runs *before* the route handler (`handle_asap_message`), which handles the body parsing. Consequently, `request.state.envelope` and `request.state.rpc_request` will **always be missing** or empty during the rate limit check. The rate limiter will fallback to IP address (`get_remote_address`) for 100% of requests, failing to implement "Per-sender rate limiting" as intended for authenticated agents sharing an IP (e.g., behind a NAT or proxy).
|
|
30
|
+
* **Recommendation:** acknowledge that unparsed requests can only be rate-limited by IP (which is safer for DoS anyway). If sender-based limiting is strictly required, you must parse the body inside the `key_func` (expensive) or move rate limiting logic *inside* the route handler after parsing (safer).
|
|
31
|
+
* **Fix:** Update documentation/comments to reflect that this is primarily **IP-based** limiting for the transport layer, or implement a two-stage limit (IP first, then Sender after parsing).
|
|
32
|
+
|
|
33
|
+
### [Maintainability] Accessing Private Attributes - `executors.py`
|
|
34
|
+
* **Location:** `src/asap/transport/executors.py` Line 106
|
|
35
|
+
* **Problem:** Accessing `self._semaphore._value` relies on CPython implementation details of `threading.Semaphore`. This is brittle and may break in future Python versions or alternative implementations.
|
|
36
|
+
* **Recommendation:** Track active count manually or calculate it differently.
|
|
37
|
+
```diff
|
|
38
|
+
- active_threads = self.max_threads - self._semaphore._value
|
|
39
|
+
+ # Suggestion: Wrap acquire/release to track count in an atomic integer/locked variable
|
|
40
|
+
+ # Or simply report max_threads as we know it's exhausted.
|
|
41
|
+
+ active_threads = self.max_threads # We know it's full if acquire(blocking=False) fails
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## 3. Improvements & Refactoring (Strongly Recommended)
|
|
45
|
+
|
|
46
|
+
### [Performance] Move Size Check to Middleware - `server.py` / `middleware.py`
|
|
47
|
+
* **Location:** `src/asap/transport/server.py`
|
|
48
|
+
* **Context:** Request size validation is currently inside the route handler. It would be cleaner and safer as a Middleware, running before any routing logic.
|
|
49
|
+
* **Suggestion:** Move `_validate_request_size` logic into a `SizeLimitMiddleware`.
|
|
50
|
+
|
|
51
|
+
### [Observability] Explicit Metric Labels - `executors.py`
|
|
52
|
+
* **Location:** `src/asap/transport/executors.py` Line 110
|
|
53
|
+
* **Context:** `metrics.increment_counter(..., labels=None)` might fail if the underlying metric requires label keys (e.g. `cluster`, `pod`).
|
|
54
|
+
* **Suggestion:** Ensure strict label alignment.
|
|
55
|
+
```python
|
|
56
|
+
metrics.increment_counter("asap_thread_pool_exhausted_total", labels={})
|
|
57
|
+
# or specific labels if required by your metric definition
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## 4. Nitpicks & Questions
|
|
61
|
+
* **src/asap/transport/server.py** (Line 1074): `app.add_exception_handler(RateLimitExceeded, rate_limit_handler) # type: ignore[arg-type]`. It's better to verify *why* mypy complains. Usually it's because `RateLimitExceeded` isn't a direct subclass of `Exception` or the handler signature mismatch.
|
|
62
|
+
- ✅ **RESOLVED**: Changed handler signature to accept `Exception` instead of `RateLimitExceeded`, added type narrowing with `isinstance` check. Removed unnecessary `type: ignore` comment.
|
|
63
|
+
* **src/asap/transport/middleware.py**: `_get_sender_from_envelope` has high cyclomatic complexity with nested `if`s. Consider using early returns or a `get_path(dict, path)` utility.
|
|
64
|
+
- ✅ **RESOLVED**: Simplified function with early returns and combined type checks to reduce nesting and complexity.
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 5. Implementation Summary
|
|
69
|
+
|
|
70
|
+
All feedback items have been successfully resolved:
|
|
71
|
+
|
|
72
|
+
### Critical Issues (Must Fix) - ✅ All Resolved
|
|
73
|
+
1. **[Security/DoS] Unsafe Request Body Reading** - Fixed by replacing `request.body()` with `request.stream()` for incremental size validation during chunk reading, preventing OOM attacks.
|
|
74
|
+
2. **[Logic/RateLimit] Sender ID Extraction Failure** - Updated documentation to reflect that rate limiting is IP-based (not per-sender) since envelope parsing happens after rate limit check. This is safer for DoS prevention.
|
|
75
|
+
3. **[Maintainability] Accessing Private Attributes** - Removed access to `self._semaphore._value` and simplified logic to use `max_threads` directly when pool is exhausted.
|
|
76
|
+
|
|
77
|
+
### Improvements & Refactoring - ✅ All Resolved
|
|
78
|
+
4. **[Observability] Explicit Metric Labels** - Changed `labels=None` to `labels={}` in metrics increment call.
|
|
79
|
+
5. **[Performance] Move Size Check to Middleware** - Created `SizeLimitMiddleware` that validates `Content-Length` header before routing, providing early rejection. Actual body size validation during streaming remains in route handler.
|
|
80
|
+
|
|
81
|
+
### Nitpicks & Questions - ✅ All Resolved
|
|
82
|
+
6. **Type ignore comment verification** - Removed unnecessary `type: ignore` by fixing handler signature.
|
|
83
|
+
7. **High cyclomatic complexity** - Reduced complexity in `_get_sender_from_envelope` with early returns.
|
|
84
|
+
|
|
85
|
+
### Files Modified
|
|
86
|
+
- `src/asap/transport/server.py` - Request body streaming, middleware integration, exception handler fix
|
|
87
|
+
- `src/asap/transport/middleware.py` - IP-based limiting docs, SizeLimitMiddleware, handler signature fix, complexity reduction
|
|
88
|
+
- `src/asap/transport/executors.py` - Private attribute access fix, explicit metric labels
|
|
89
|
+
|
|
90
|
+
### Testing
|
|
91
|
+
- All existing tests pass (100% success rate)
|
|
92
|
+
- No regressions introduced
|
|
93
|
+
- Type checking passes without errors
|
|
94
|
+
- Linting passes without errors
|
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
# Code Review: PR #18 - Test Infrastructure Refactoring & Issue #17 Resolution
|
|
2
|
+
|
|
3
|
+
**PR Link:** https://github.com/adriannoes/asap-protocol/pull/18
|
|
4
|
+
**Reviewer:** Opus 4.5
|
|
5
|
+
**Review Date:** 2026-01-26
|
|
6
|
+
**Status:** Open
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## 1. Executive Summary
|
|
11
|
+
|
|
12
|
+
* **Impact Analysis:** **Medium** risk - While this is primarily a test refactoring PR with high test coverage (578/578 tests passing), there are some findings related to object reference patterns and type safety that warrant attention before production merge.
|
|
13
|
+
|
|
14
|
+
* **Architecture Check:** **Partial** - The PR aligns with architecture principles (SOLID, DRY) for the most part, but the aggressive monkeypatch pattern adds significant complexity to the test infrastructure that may impact long-term maintainability. The fixture design exhibits some code duplication that conflicts with DRY principles.
|
|
15
|
+
|
|
16
|
+
* **Blockers:** **2** critical issues found:
|
|
17
|
+
1. Global `limiter` object reference pattern may cause unexpected behavior in production
|
|
18
|
+
2. Type annotations missing on fixture return types in conftest.py
|
|
19
|
+
|
|
20
|
+
* **Positive Highlights:**
|
|
21
|
+
- ✅ Excellent problem diagnosis and root cause analysis (slowapi.Limiter global state)
|
|
22
|
+
- ✅ Comprehensive documentation of testing strategy (docs/testing.md)
|
|
23
|
+
- ✅ Process isolation via pytest-xdist reduces flaky tests
|
|
24
|
+
- ✅ All 578 tests passing with zero failures (down from 33!)
|
|
25
|
+
- ✅ Proper fix for UnboundLocalError in server.py
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## 2. Critical Issues (Must Fix)
|
|
30
|
+
|
|
31
|
+
### 2.1. Global Limiter Object Reference - Potential Production Issue - [src/asap/transport/server.py]
|
|
32
|
+
|
|
33
|
+
* **Location:** Line 1022
|
|
34
|
+
* **Problem:** The code assigns the module-level `limiter` object to `app.state.limiter` directly without creating an isolated instance. This pattern is correct for production but creates a **semantic confusion** with the test monkey-patching strategy. While tests replace the global `limiter` via monkeypatch, the production code does not explicitly document this coupling.
|
|
35
|
+
|
|
36
|
+
**Current Code** (Lines 1017-1024):
|
|
37
|
+
```python
|
|
38
|
+
# Configure rate limiting
|
|
39
|
+
if rate_limit is None:
|
|
40
|
+
rate_limit_str = os.getenv("ASAP_RATE_LIMIT", "100/minute")
|
|
41
|
+
else:
|
|
42
|
+
rate_limit_str = rate_limit
|
|
43
|
+
app.state.limiter = limiter # ← Uses global limiter from middleware module
|
|
44
|
+
app.state.max_request_size = max_request_size
|
|
45
|
+
app.add_exception_handler(RateLimitExceeded, rate_limit_handler)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
**Recommendation:**
|
|
49
|
+
1. Add a code comment explaining the limiter assignment strategy:
|
|
50
|
+
```python
|
|
51
|
+
# Assign global limiter instance to app state
|
|
52
|
+
# Note: Tests replace this via monkeypatch for isolation
|
|
53
|
+
app.state.limiter = limiter
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
2. **OR** (better architecture): Create a factory function that returns a new limiter instance and use it in production:
|
|
57
|
+
```diff
|
|
58
|
+
# file: src/asap/transport/server.py (around line 1017)
|
|
59
|
+
|
|
60
|
+
+ from asap.transport.middleware import create_limiter
|
|
61
|
+
+
|
|
62
|
+
if rate_limit is None:
|
|
63
|
+
rate_limit_str = os.getenv("ASAP_RATE_LIMIT", "100/minute")
|
|
64
|
+
else:
|
|
65
|
+
rate_limit_str = rate_limit
|
|
66
|
+
- app.state.limiter = limiter
|
|
67
|
+
+ # Create isolated limiter for this app instance
|
|
68
|
+
+ # Tests can override this via monkeypatch or direct assignment
|
|
69
|
+
+ app.state.limiter = create_limiter([rate_limit_str])
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
This makes the design intent clearer and reduces coupling to the global `limiter` object.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
### 2.2. Missing Type Annotations on Fixture Return Types - [tests/transport/conftest.py]
|
|
77
|
+
|
|
78
|
+
* **Location:** Lines 27, 72, 134
|
|
79
|
+
* **Problem:** The fixtures `isolated_limiter_factory`, `replace_global_limiter`, and `create_isolated_app` have callable return types that are not properly annotated, violating the Python best practices rule: "ALWAYS add typing annotations to each function or class. Include explicit return types."
|
|
80
|
+
|
|
81
|
+
**Current Code** (Line 27):
|
|
82
|
+
```python
|
|
83
|
+
@pytest.fixture
|
|
84
|
+
def isolated_limiter_factory() -> Callable[[Sequence[str] | None], "Limiter"]:
|
|
85
|
+
# ...
|
|
86
|
+
def _create(limits: Sequence[str] | None = None) -> "Limiter":
|
|
87
|
+
# ...
|
|
88
|
+
return _create # ✅ Inner function has types
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**Current Code** (Line 134):
|
|
92
|
+
```python
|
|
93
|
+
@pytest.fixture
|
|
94
|
+
def create_isolated_app(
|
|
95
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
96
|
+
isolated_limiter_factory: Callable[[Sequence[str] | None], "Limiter"],
|
|
97
|
+
) -> Callable[..., "FastAPI"]: # ← Uses Callable[..., ...] - too vague!
|
|
98
|
+
"""Factory fixture that returns a function to create isolated FastAPI apps."""
|
|
99
|
+
|
|
100
|
+
def _create_app(
|
|
101
|
+
manifest: Manifest,
|
|
102
|
+
rate_limit: str | None = None,
|
|
103
|
+
max_request_size: int | None = None,
|
|
104
|
+
max_threads: int | None = None,
|
|
105
|
+
use_monkeypatch: bool = False,
|
|
106
|
+
) -> "FastAPI":
|
|
107
|
+
# ...
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Recommendation:**
|
|
111
|
+
Make the callable signatures explicit using `collections.abc.Callable` with full parameter specifications:
|
|
112
|
+
|
|
113
|
+
```diff
|
|
114
|
+
# file: tests/transport/conftest.py
|
|
115
|
+
|
|
116
|
+
@pytest.fixture
|
|
117
|
+
def create_isolated_app(
|
|
118
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
119
|
+
isolated_limiter_factory: Callable[[Sequence[str] | None], "Limiter"],
|
|
120
|
+
-) -> Callable[..., "FastAPI"]:
|
|
121
|
+
+) -> "Callable[[Manifest, str | None, int | None, int | None, bool], FastAPI]":
|
|
122
|
+
"""Factory fixture that returns a function to create isolated FastAPI apps."""
|
|
123
|
+
|
|
124
|
+
def _create_app(
|
|
125
|
+
manifest: Manifest,
|
|
126
|
+
rate_limit: str | None = None,
|
|
127
|
+
max_request_size: int | None = None,
|
|
128
|
+
max_threads: int | None = None,
|
|
129
|
+
use_monkeypatch: bool = False,
|
|
130
|
+
) -> "FastAPI":
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
While the `Callable[..., "FastAPI"]` is valid, it's not specific enough for type checkers like mypy to validate calling code properly.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## 3. Improvements & Refactoring (Strongly Recommended)
|
|
138
|
+
|
|
139
|
+
### 3.1. Code Duplication in Aggressive Monkeypatch Pattern - [tests/transport/integration/test_rate_limiting.py]
|
|
140
|
+
|
|
141
|
+
* **Location:** Lines 100-113, 134-147, 179-192, 224-237
|
|
142
|
+
* **Context:** The same monkeypatch pattern is repeated 4 times across the TestRateLimiting class methods. This violates DRY principles and creates maintenance burden.
|
|
143
|
+
|
|
144
|
+
**Current Pattern (repeated 4 times):**
|
|
145
|
+
```python
|
|
146
|
+
# Create completely isolated limiter
|
|
147
|
+
limiter = isolated_limiter_factory([\"5/minute\"])
|
|
148
|
+
|
|
149
|
+
# Replace global limiter in BOTH modules
|
|
150
|
+
import asap.transport.middleware as middleware_module
|
|
151
|
+
import asap.transport.server as server_module
|
|
152
|
+
|
|
153
|
+
monkeypatch.setattr(middleware_module, \"limiter\", limiter)
|
|
154
|
+
monkeypatch.setattr(server_module, \"limiter\", limiter)
|
|
155
|
+
|
|
156
|
+
# Create app - it will use the monkeypatched limiter
|
|
157
|
+
app = create_app(rate_limit_manifest, rate_limit=\"5/minute\")
|
|
158
|
+
# Also set app.state.limiter for runtime
|
|
159
|
+
app.state.limiter = limiter
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
**Suggestion:**
|
|
163
|
+
Create a reusable fixture method or use the existing `create_isolated_app` fixture with `use_monkeypatch=True`:
|
|
164
|
+
|
|
165
|
+
```diff
|
|
166
|
+
# file: tests/transport/integration/test_rate_limiting.py
|
|
167
|
+
|
|
168
|
+
class TestRateLimiting:
|
|
169
|
+
\"\"\"Tests for rate limiting functionality.\"\"\"
|
|
170
|
+
+
|
|
171
|
+
+ @pytest.fixture
|
|
172
|
+
+ def isolated_app(
|
|
173
|
+
+ self,
|
|
174
|
+
+ create_isolated_app: Callable,
|
|
175
|
+
+ rate_limit_manifest: Manifest,
|
|
176
|
+
+ ) -> FastAPI:
|
|
177
|
+
+ \"\"\"Create an app with aggressive monkeypatch for all rate limiting tests.\"\"\"
|
|
178
|
+
+ return create_isolated_app(
|
|
179
|
+
+ manifest=rate_limit_manifest,
|
|
180
|
+
+ rate_limit=\"5/minute\",
|
|
181
|
+
+ use_monkeypatch=True,
|
|
182
|
+
+ )
|
|
183
|
+
|
|
184
|
+
def test_requests_within_limit_succeed(
|
|
185
|
+
self,
|
|
186
|
+
- monkeypatch: pytest.MonkeyPatch,
|
|
187
|
+
- isolated_limiter_factory: ...,
|
|
188
|
+
- rate_limit_manifest: Manifest,
|
|
189
|
+
+ isolated_app: FastAPI,
|
|
190
|
+
) -> None:
|
|
191
|
+
\"\"\"Test that requests within the rate limit succeed.\"\"\"
|
|
192
|
+
- # Create completely isolated limiter
|
|
193
|
+
- limiter = isolated_limiter_factory([\"5/minute\"])
|
|
194
|
+
-
|
|
195
|
+
- # Replace global limiter in BOTH modules
|
|
196
|
+
- import asap.transport.middleware as middleware_module
|
|
197
|
+
- import asap.transport.server as server_module
|
|
198
|
+
-
|
|
199
|
+
- monkeypatch.setattr(middleware_module, \"limiter\", limiter)
|
|
200
|
+
- monkeypatch.setattr(server_module, \"limiter\", limiter)
|
|
201
|
+
-
|
|
202
|
+
- # Create app - it will use the monkeypatched limiter
|
|
203
|
+
- app = create_app(rate_limit_manifest, rate_limit=\"5/minute\")
|
|
204
|
+
- # Also set app.state.limiter for runtime
|
|
205
|
+
- app.state.limiter = limiter
|
|
206
|
+
-
|
|
207
|
+
- client = TestClient(app)
|
|
208
|
+
+ client = TestClient(isolated_app)
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
This reduces repetition and keeps the test logic focused on the behavior being tested.
|
|
212
|
+
|
|
213
|
+
---
|
|
214
|
+
|
|
215
|
+
### 3.2. Fixture Complexity - Consider Simplifying `create_isolated_app` - [tests/transport/conftest.py]
|
|
216
|
+
|
|
217
|
+
* **Location:** Lines 161-207
|
|
218
|
+
* **Context:** The `create_isolated_app` fixture has a boolean flag `use_monkeypatch` that controls behavior. This creates a bifurcated logic path within a single fixture, increasing cognitive complexity.
|
|
219
|
+
|
|
220
|
+
**Suggestion:**
|
|
221
|
+
Consider splitting into two focused fixtures:
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
@pytest.fixture
|
|
225
|
+
def create_app_simple(
|
|
226
|
+
isolated_limiter_factory: Callable[[Sequence[str] | None], "Limiter"],
|
|
227
|
+
) -> Callable[..., "FastAPI"]:
|
|
228
|
+
\"\"\"Create app with isolated limiter (direct assignment only).\"\"\"
|
|
229
|
+
def _create(...) -> "FastAPI":
|
|
230
|
+
isolated_limiter = isolated_limiter_factory(limits)
|
|
231
|
+
app = create_app(...)
|
|
232
|
+
app.state.limiter = isolated_limiter # Direct assignment
|
|
233
|
+
return app
|
|
234
|
+
return _create
|
|
235
|
+
|
|
236
|
+
@pytest.fixture
|
|
237
|
+
def create_app_monkeypatched(
|
|
238
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
239
|
+
isolated_limiter_factory: Callable[[Sequence[str] | None], "Limiter"],
|
|
240
|
+
) -> Callable[..., "FastAPI"]:
|
|
241
|
+
\"\"\"Create app with aggressive monkeypatch (module-level replacement).\"\"\"
|
|
242
|
+
def _create(...) -> "FastAPI":
|
|
243
|
+
isolated_limiter = isolated_limiter_factory(limits)
|
|
244
|
+
# Monkeypatch global limiters
|
|
245
|
+
import asap.transport.middleware as middleware_module
|
|
246
|
+
import asap.transport.server as server_module
|
|
247
|
+
monkeypatch.setattr(middleware_module, "limiter", isolated_limiter)
|
|
248
|
+
monkeypatch.setattr(server_module, "limiter", isolated_limiter)
|
|
249
|
+
|
|
250
|
+
app = create_app(...)
|
|
251
|
+
app.state.limiter = isolated_limiter
|
|
252
|
+
return app
|
|
253
|
+
return _create
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
**Trade-offs:** More fixtures vs. simpler individual implementations. Consider team preference.
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
### 3.3. Test Documentation - Add Explanation of Fixture Choice - [docs/testing.md]
|
|
261
|
+
|
|
262
|
+
* **Location:** docs/testing.md (new section needed)
|
|
263
|
+
* **Context:** While the documentation explains WHAT the fixtures do, it doesn't explain WHEN to use which fixture strategy.
|
|
264
|
+
|
|
265
|
+
**Suggestion:**
|
|
266
|
+
Add a decision tree or flowchart to help contributors choose the right test isolation strategy:
|
|
267
|
+
|
|
268
|
+
````markdown
|
|
269
|
+
## When to Use Each Isolation Strategy
|
|
270
|
+
|
|
271
|
+
Use the following decision tree when writing transport layer tests:
|
|
272
|
+
|
|
273
|
+
```mermaid
|
|
274
|
+
graph TD
|
|
275
|
+
A[Writing Transport Test] --> B{Does test involve rate limiting?}
|
|
276
|
+
B -->|Yes| C[Use aggressive monkeypatch]
|
|
277
|
+
B -->|No| D[Inherit from NoRateLimitTestBase]
|
|
278
|
+
C --> E[File: tests/transport/integration/test_rate_limiting.py]
|
|
279
|
+
C --> F[OR use create_isolated_app with use_monkeypatch=True]
|
|
280
|
+
D --> G[Class inherits from NoRateLimitTestBase]
|
|
281
|
+
D --> H[Rate limiting automatically disabled via fixture]
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
**Quick Reference:**
|
|
285
|
+
- **Rate Limiting Tests**: Use aggressive monkeypatch (replace global limiter in modules)
|
|
286
|
+
- **Non-Rate Limiting Tests**: Inherit from `NoRateLimitTestBase` (disables rate limiting)
|
|
287
|
+
- **Unit Tests**: No special fixtures needed (no HTTP, no rate limiting)
|
|
288
|
+
````
|
|
289
|
+
|
|
290
|
+
---
|
|
291
|
+
|
|
292
|
+
### 3.4. Observability Gap - No Logging intest Fixtures - [tests/transport/conftest.py]
|
|
293
|
+
|
|
294
|
+
* **Location:** Lines 95, 248
|
|
295
|
+
* **Context:** The `replace_global_limiter` and `NoRateLimitTestBase.disable_rate_limiting` fixtures perform critical monkeypatch operations but don't log when they execute. This makes debugging test failures harder.
|
|
296
|
+
|
|
297
|
+
**Suggestion:**
|
|
298
|
+
Add debug-level logging to track fixture execution:
|
|
299
|
+
|
|
300
|
+
```diff
|
|
301
|
+
# file: tests/transport/conftest.py
|
|
302
|
+
|
|
303
|
+
+import logging
|
|
304
|
+
+test_logger = logging.getLogger(__name__)
|
|
305
|
+
+
|
|
306
|
+
@pytest.fixture
|
|
307
|
+
def replace_global_limiter(
|
|
308
|
+
monkeypatch: pytest.MonkeyPatch,
|
|
309
|
+
isolated_limiter_factory: Callable[[Sequence[str] | None], "Limiter"],
|
|
310
|
+
) -> "Limiter":
|
|
311
|
+
\"\"\"Replace global limiter with isolated instance using aggressive monkeypatch.\"\"\"
|
|
312
|
+
+ test_logger.debug("Creating isolated limiter for aggressive monkeypatch")
|
|
313
|
+
# Create completely isolated limiter
|
|
314
|
+
new_limiter = isolated_limiter_factory(None)
|
|
315
|
+
|
|
316
|
+
# Replace in both modules
|
|
317
|
+
import asap.transport.middleware as middleware_module
|
|
318
|
+
import asap.transport.server as server_module
|
|
319
|
+
|
|
320
|
+
monkeypatch.setattr(middleware_module, "limiter", new_limiter)
|
|
321
|
+
monkeypatch.setattr(server_module, "limiter", new_limiter)
|
|
322
|
+
+ test_logger.debug(f"Replaced global limiter in middleware and server modules: {id(new_limiter)}")
|
|
323
|
+
|
|
324
|
+
return new_limiter
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
## 4. Nitpicks & Questions
|
|
330
|
+
|
|
331
|
+
### File: `tests/transport/conftest.py` (Line 58)
|
|
332
|
+
**Nitpick:** The default limit `"100000/minute"` is very high. Consider using a named constant for clarity:
|
|
333
|
+
```python
|
|
334
|
+
TEST_RATE_LIMIT_DEFAULT = "100000/minute" # Very high default for testing
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### File: `tests/transport/integration/test_server_core.py` (Line 531)
|
|
338
|
+
**Question:** Why is `rate_limit="100000/minute"` hardcoded instead of using the fixture's default? Consider parametrizing or using a constant.
|
|
339
|
+
|
|
340
|
+
### File: `tests/conftest.py` (Line 17)
|
|
341
|
+
**Nitpick:** The `from slowapi import Limiter` import is removed but the `if TYPE_CHECKING: pass` block is left empty. Consider removing the entire block or adding a comment explaining why it's kept:
|
|
342
|
+
```python
|
|
343
|
+
if TYPE_CHECKING:
|
|
344
|
+
# Reserved for future type checking imports
|
|
345
|
+
pass
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
### File: `.github/workflows/ci.yml` (Line 61)
|
|
349
|
+
**Nitpick:** Consider documenting the expected parallelism level. The `-n auto` flag will detect CPU count, but it's not obvious to contributors reviewing the CI config. Add a comment:
|
|
350
|
+
```yaml
|
|
351
|
+
# -n auto: Automatically detect CPU count for parallel execution
|
|
352
|
+
run: uv run pytest -n auto --tb=short --cov=src --cov-report=xml
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### File: `tests/transport/integration/test_rate_limiting.py` (Line 254)
|
|
356
|
+
**Question:** The `time.sleep(1.1)` introduces a 1.1-second delay in the test suite. This is acceptable for integration tests, but have you considered mocking the time to speed up CI runs? Libraries like `freezegun` or `time-machine` can help.
|
|
357
|
+
|
|
358
|
+
### File: `pyproject.toml` (Line 41)
|
|
359
|
+
**Observation:** Great addition of `pytest-xdist>=3.5.0`. The version constraint is appropriate. No issues.
|
|
360
|
+
|
|
361
|
+
### File: `src/asap/transport/server.py` (Lines 40-43)
|
|
362
|
+
**Nitpick:** The `import json` addition is correct, but consider organizing imports alphabetically within the stdlib group:
|
|
363
|
+
```python
|
|
364
|
+
import json # ← Added here
|
|
365
|
+
import os
|
|
366
|
+
import time
|
|
367
|
+
from dataclasses import dataclass
|
|
368
|
+
```
|
|
369
|
+
Appears to already be in correct order - no action needed.
|
|
370
|
+
|
|
371
|
+
### File: `tests/transport/integration/test_server_core.py` (Line 558-562)
|
|
372
|
+
**Positive:** Excellent comment explaining the payload_type normalization behavior:
|
|
373
|
+
```python
|
|
374
|
+
# Note: payload_type is normalized to "other" when no handler is registered
|
|
375
|
+
# (see _normalize_payload_type_for_metrics in server.py)
|
|
376
|
+
```
|
|
377
|
+
This shows good attention to developer experience.
|
|
378
|
+
|
|
379
|
+
### File: `docs/testing.md`
|
|
380
|
+
**Positive:** The documentation is comprehensive and well-structured. The educational section on pytest fixtures is particularly helpful for junior contributors.
|
|
381
|
+
|
|
382
|
+
---
|
|
383
|
+
|
|
384
|
+
## 5. Security & Resilience Verification
|
|
385
|
+
|
|
386
|
+
✅ **Rate Limiting DoS Prevention**: The PR maintains the IP-based rate limiting strategy, which is correct for preventing DoS attacks. No security regressions.
|
|
387
|
+
|
|
388
|
+
✅ **Request Size Validation**: The size validation logic remains intact with chunk-by-chunk streaming validation. No issues found.
|
|
389
|
+
|
|
390
|
+
✅ **Thread Pool Exhaustion**: The `BoundedExecutor` pattern is preserved. Thread pool tests now properly isolated.
|
|
391
|
+
|
|
392
|
+
✅ **Error Handling**: All error paths return proper JSON-RPC errors. No error swallowing detected.
|
|
393
|
+
|
|
394
|
+
✅ **Authentication**: Auth tests migrated to `NoRateLimitTestBase` successfully - authentication logic unchanged.
|
|
395
|
+
|
|
396
|
+
✅ **Metrics Cardinality**: The payload type normalization logic prevents cardinality explosion. Tests now verify this behavior explicitly.
|
|
397
|
+
|
|
398
|
+
---
|
|
399
|
+
|
|
400
|
+
## 6. Concurrency & Race Conditions
|
|
401
|
+
|
|
402
|
+
✅ **Pytest-xdist Process Isolation**: The addition of `-n auto` provides true process-level isolation, eliminating race conditions between test workers. Excellent choice.
|
|
403
|
+
|
|
404
|
+
✅ **Limiter Global State**: The monkeypatch strategy correctly replaces module-level objects before app creation, preventing shared state between tests. No race conditions detected in test code.
|
|
405
|
+
|
|
406
|
+
⚠️ **Production Consideration**: The global `limiter` object in `middleware.py` (line 119) is shared across all FastAPI app instances if multiple apps are created in the same process. This is acceptable for typical deployment (one app per process) but document this assumption if not already done.
|
|
407
|
+
|
|
408
|
+
---
|
|
409
|
+
|
|
410
|
+
## 7. Data Integrity
|
|
411
|
+
|
|
412
|
+
✅ **No Serialization Changes**: The PR doesn't modify envelope serialization/deserialization logic. No backward compatibility concerns.
|
|
413
|
+
|
|
414
|
+
✅ **Schema Validation**: All pydantic model validations remain intact. Test reorganization doesn't affect runtime behavior.
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## 8. Overall Assessment
|
|
419
|
+
|
|
420
|
+
**Verdict:** ✅ **Approve with Minor Changes**
|
|
421
|
+
|
|
422
|
+
This PR demonstrates excellent engineering judgment in diagnosing and solving a complex flaky test problem. The three-pronged approach (process isolation + aggressive monkeypatch + strategic test organization) is sound and well-documented.
|
|
423
|
+
|
|
424
|
+
**Before Merge:**
|
|
425
|
+
1. Fix critical issue #2.1 (add factory function for limiter or document the global reference pattern)
|
|
426
|
+
2. Fix critical issue #2.2 (improve type annotations on fixture return types)
|
|
427
|
+
3. Consider improvement #3.1 (reduce code duplication in rate limiting tests)
|
|
428
|
+
|
|
429
|
+
**Post-Merge Recommendations:**
|
|
430
|
+
1. Monitor CI execution times - pytest-xdist may increase overhead on small test suites
|
|
431
|
+
2. Educate team on fixture usage patterns via the new testing.md documentation
|
|
432
|
+
3. Consider adding a pre-commit hook to run `mypy tests/` to catch type issues earlier
|
|
433
|
+
|
|
434
|
+
**Kudos:**
|
|
435
|
+
- 🏆 Zero test failures after refactoring (33 → 0)
|
|
436
|
+
- 🏆 Comprehensive documentation of rationale and strategy
|
|
437
|
+
- 🏆 Proper use of pytest fixtures and test organization
|
|
438
|
+
- 🏆 Excellent commit messages following Conventional Commits
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
**Signature:**
|
|
443
|
+
Reviewed by: Antigravity AI (Principal Software Engineer Mode)
|
|
444
|
+
Date: 2026-01-26T19:33:02-03:00
|