Skip to content

Commit 7c62a0b

Browse files
committed
Address Copilot comments (1)
Signed-off-by: Sergio Herrera <627709+seherv@users.noreply.github.com>
1 parent a39b0db commit 7c62a0b

7 files changed

Lines changed: 109 additions & 15 deletions

File tree

AGENTS.md

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,8 @@ Each extension is a **separate PyPI package** with its own `setup.cfg`, `setup.p
6565

6666
The `examples/` directory contains user-facing example applications. These are validated by two test suites:
6767

68-
- **`tests/examples/`** — Output-based tests that run examples via `dapr run` and check stdout for expected strings. Uses a `DaprRunner` helper to manage process lifecycle.
69-
- **`tests/integration/`** — Programmatic SDK tests that call `DaprClient` methods directly and assert on return values, gRPC status codes, and SDK types. More reliable than output-based tests since they don't depend on print statement formatting.
70-
71-
**See `examples/AGENTS.md`** for the full guide on example structure and how to add new examples.
68+
- **`tests/examples/`** — Output-based tests that run examples via `dapr run` and check stdout for expected strings. Uses a `DaprRunner` helper to manage process lifecycle. See `examples/AGENTS.md`.
69+
- **`tests/integration/`** — Programmatic SDK tests that call `DaprClient` methods directly and assert on return values, gRPC status codes, and SDK types. More reliable than output-based tests since they don't depend on print statement formatting. See `tests/integration/AGENTS.md`.
7270

7371
Quick reference:
7472
```bash

examples/AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# AGENTS.md — Dapr Python SDK Examples
22

3-
The `examples/` directory serves as both **user-facing documentation** and the project's **integration test suite**. Each example is a self-contained application validated by pytest-based tests in `tests/examples/`.
3+
The `examples/` directory serves as the **user-facing documentation**. Each example is a self-contained application validated by pytest-based tests in `tests/examples/`.
44

55
## How validation works
66

examples/conversation/real_llm_providers_example.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1237,7 +1237,7 @@ def main():
12371237

12381238
print(f'\n{"=" * 60}')
12391239
print('🎉 All Alpha2 tests completed!')
1240-
print('✅ Real LLM provider examples with Alpha2 API is working correctly')
1240+
print('✅ Real LLM provider integration with Alpha2 API is working correctly')
12411241
print('🔧 Features demonstrated:')
12421242
print(' • Alpha2 conversation API with sophisticated message types')
12431243
print(' • Automatic parameter conversion (raw Python values)')

tests/clients/test_conversation_helpers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1511,7 +1511,7 @@ def google_function(data: str):
15111511

15121512

15131513
class TestIntegrationScenarios(unittest.TestCase):
1514-
"""Test real-world examples scenarios."""
1514+
"""Test real-world integration scenarios."""
15151515

15161516
def test_restaurant_finder_scenario(self):
15171517
"""Test the restaurant finder example from the documentation."""

tests/integration/AGENTS.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# AGENTS.md — Programmatic Integration Tests
2+
3+
This directory contains **programmatic SDK tests** that call `DaprClient` methods directly and assert on return values, gRPC status codes, and SDK types. Unlike the output-based tests in `tests/examples/` (which run example scripts and check stdout), these tests don't depend on print statement formatting.
4+
5+
## How it works
6+
7+
1. `DaprTestEnvironment` (defined in `conftest.py`) manages Dapr sidecar processes
8+
2. `start_sidecar()` launches `dapr run` with explicit ports, waits for the health check, and returns a connected `DaprClient`
9+
3. Tests call SDK methods on that client and assert on the response objects
10+
4. Sidecar stdout is written to temp files (not pipes) to avoid buffer deadlocks
11+
5. Cleanup terminates sidecars, closes clients, and removes log files
12+
13+
Run locally (requires a running Dapr runtime via `dapr init`):
14+
15+
```bash
16+
# All integration tests
17+
tox -e integration
18+
19+
# Single test file
20+
tox -e integration -- test_state_store.py
21+
22+
# Single test
23+
tox -e integration -- test_state_store.py -k test_save_and_get
24+
```
25+
26+
## Directory structure
27+
28+
```
29+
tests/integration/
30+
├── conftest.py # DaprTestEnvironment + fixtures (dapr_env, apps_dir, components_dir)
31+
├── test_*.py # Test files (one per building block)
32+
├── apps/ # Helper apps started alongside sidecars
33+
│ ├── invoke_receiver.py # gRPC method handler for invoke tests
34+
│ └── pubsub_subscriber.py # Subscriber that persists messages to state store
35+
├── components/ # Dapr component YAMLs loaded by all sidecars
36+
│ ├── statestore.yaml # state.redis
37+
│ ├── pubsub.yaml # pubsub.redis
38+
│ ├── lockstore.yaml # lock.redis
39+
│ ├── configurationstore.yaml # configuration.redis
40+
│ └── localsecretstore.yaml # secretstores.local.file
41+
└── secrets.json # Secrets file for localsecretstore component
42+
```
43+
44+
## Fixtures
45+
46+
All fixtures are **module-scoped** — one sidecar per test file.
47+
48+
| Fixture | Type | Description |
49+
|---------|------|-------------|
50+
| `dapr_env` | `DaprTestEnvironment` | Manages sidecar lifecycle; call `start_sidecar()` to get a client |
51+
| `apps_dir` | `Path` | Path to `tests/integration/apps/` |
52+
| `components_dir` | `Path` | Path to `tests/integration/components/` |
53+
54+
Each test file defines its own module-scoped `client` fixture that calls `dapr_env.start_sidecar(...)`.
55+
56+
## Building blocks covered
57+
58+
| Test file | Building block | SDK methods tested |
59+
|-----------|---------------|-------------------|
60+
| `test_state_store.py` | State management | `save_state`, `get_state`, `save_bulk_state`, `get_bulk_state`, `execute_state_transaction`, `delete_state` |
61+
| `test_invoke.py` | Service invocation | `invoke_method` |
62+
| `test_pubsub.py` | Pub/sub | `publish_event`, `get_state` (to verify delivery) |
63+
| `test_secret_store.py` | Secrets | `get_secret`, `get_bulk_secret` |
64+
| `test_metadata.py` | Metadata | `get_metadata`, `set_metadata` |
65+
| `test_distributed_lock.py` | Distributed lock | `try_lock`, `unlock`, context manager |
66+
| `test_configuration.py` | Configuration | `get_configuration`, `subscribe_configuration`, `unsubscribe_configuration` |
67+
68+
## Port allocation
69+
70+
All sidecars default to gRPC port 50001 and HTTP port 3500. Since fixtures are module-scoped and tests run sequentially, only one sidecar is active at a time. If parallel execution is needed in the future, sidecars will need dynamic port allocation.
71+
72+
## Helper apps
73+
74+
Some building blocks (invoke, pubsub) require an app process running alongside the sidecar:
75+
76+
- **`invoke_receiver.py`** — A `dapr.ext.grpc.App` that handles `my-method` and returns `INVOKE_RECEIVED`.
77+
- **`pubsub_subscriber.py`** — Subscribes to `TOPIC_A` and persists received messages to the state store. This lets tests verify message delivery by reading state rather than parsing stdout.
78+
79+
## Adding a new test
80+
81+
1. Create `test_<building_block>.py`
82+
2. Add a module-scoped `client` fixture that calls `dapr_env.start_sidecar(app_id='test-<name>')`
83+
3. If the building block needs a new Dapr component, add a YAML to `components/`
84+
4. If the building block needs a running app, add it to `apps/` and pass `app_cmd` / `app_port` to `start_sidecar()`
85+
5. Use unique keys/resource IDs per test to avoid interference (the sidecar is shared within a module)
86+
6. Assert on SDK return types and gRPC status codes, not on string output
87+
88+
## Gotchas
89+
90+
- **Requires `dapr init`** — the tests assume a local Dapr runtime with Redis (`dapr_redis` container on `localhost:6379`), which `dapr init` sets up automatically.
91+
- **Configuration tests seed Redis directly** via `docker exec dapr_redis redis-cli`.
92+
- **Lock and configuration APIs are alpha** and emit `UserWarning` on every call. Tests suppress these with `pytestmark = pytest.mark.filterwarnings('ignore::UserWarning')`.
93+
- **`localsecretstore.yaml` uses a relative path** (`secrets.json`) resolved against `cwd=INTEGRATION_DIR`.
94+
- **Dapr may normalize response fields** — e.g., `content_type` may lose charset parameters when proxied through gRPC. Assert on the media type prefix, not the full string.

tests/integration/conftest.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import pytest
99

1010
from dapr.clients import DaprClient
11+
from dapr.conf import settings
1112

1213
INTEGRATION_DIR = Path(__file__).resolve().parent
1314
COMPONENTS_DIR = INTEGRATION_DIR / 'components'
@@ -42,9 +43,8 @@ def start_sidecar(
4243
4344
Args:
4445
app_id: Dapr application ID.
45-
grpc_port: Sidecar gRPC port (must match DAPR_GRPC_PORT setting).
46-
http_port: Sidecar HTTP port (must match DAPR_HTTP_PORT setting for
47-
the SDK health check).
46+
grpc_port: Sidecar gRPC port.
47+
http_port: Sidecar HTTP port (also used for the SDK health check).
4848
app_port: Port the app listens on (implies ``--app-protocol grpc``).
4949
app_cmd: Shell command to start alongside the sidecar.
5050
components: Path to component YAML directory. Defaults to
@@ -85,9 +85,12 @@ def start_sidecar(
8585
# check starts hitting the HTTP endpoint.
8686
time.sleep(wait)
8787

88-
# DaprClient constructor calls DaprHealth.wait_for_sidecar(), which
89-
# polls http://localhost:{DAPR_HTTP_PORT}/v1.0/healthz/outbound until
90-
# the sidecar is ready (up to DAPR_HEALTH_TIMEOUT seconds).
88+
# Point the SDK health check at the actual sidecar HTTP port.
89+
# DaprHealth.wait_for_sidecar() reads settings.DAPR_HTTP_PORT, which
90+
# is initialized once at import time and won't reflect a non-default
91+
# http_port unless we update it here.
92+
settings.DAPR_HTTP_PORT = http_port
93+
9194
client = DaprClient(address=f'127.0.0.1:{grpc_port}')
9295
self._clients.append(client)
9396
return client

tests/integration/test_configuration.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,7 @@ def _redis_set(key: str, value: str, version: int = 1) -> None:
1616
Dapr's Redis configuration store encodes values as ``value||version``.
1717
"""
1818
subprocess.run(
19-
f'docker exec {REDIS_CONTAINER} redis-cli SET {key} "{value}||{version}"',
20-
shell=True,
19+
args=('docker', 'exec', REDIS_CONTAINER, 'redis-cli', 'SET', key, f'"{value}||{version}"'),
2120
check=True,
2221
capture_output=True,
2322
)

0 commit comments

Comments
 (0)