Skip to content

Commit ba69c84

Browse files
committed
Add desktop tests for Datadog client
1 parent 5b8058a commit ba69c84

2 files changed

Lines changed: 191 additions & 0 deletions

File tree

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ circuitpython-datadog/
2626
|-- datadog.py
2727
|-- examples/
2828
| `-- basic_metrics.py
29+
|-- tests/
30+
| `-- test_datadog.py
2931
|-- README.md
3032
`-- LICENSE
3133
```
@@ -107,6 +109,19 @@ loops.
107109

108110
Each request response is closed with `response.close()`.
109111

112+
## Testing
113+
114+
The core client can be tested on desktop Python because network access is
115+
injected through the `session` object:
116+
117+
```sh
118+
python3 -B -m unittest discover -s tests
119+
```
120+
121+
The tests use fake sessions and responses. They validate payload shape, retry
122+
behavior, response cleanup, URL construction, and buffer clearing without making
123+
real network requests.
124+
110125
## Supported Datadog Sites
111126

112127
Pass the site hostname for your Datadog account:

tests/test_datadog.py

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
import os
2+
import sys
3+
import unittest
4+
5+
6+
sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))
7+
8+
import datadog
9+
10+
11+
class FakeResponse:
12+
def __init__(self, status_code, close_raises=False):
13+
self.status_code = status_code
14+
self.closed = False
15+
self.close_raises = close_raises
16+
17+
def close(self):
18+
self.closed = True
19+
if self.close_raises:
20+
raise RuntimeError("close failed")
21+
22+
23+
class FakeSession:
24+
def __init__(self, events=None):
25+
self.events = events or []
26+
self.posts = []
27+
self.responses = []
28+
29+
def post(self, url, headers=None, json=None):
30+
self.posts.append({
31+
"url": url,
32+
"headers": headers,
33+
"json": json,
34+
})
35+
36+
event = self.events.pop(0)
37+
if isinstance(event, Exception):
38+
raise event
39+
if isinstance(event, FakeResponse):
40+
response = event
41+
else:
42+
response = FakeResponse(event)
43+
44+
self.responses.append(response)
45+
return response
46+
47+
48+
class DatadogClientTest(unittest.TestCase):
49+
def setUp(self):
50+
self._old_time = datadog.time.time
51+
self._old_sleep = datadog.time.sleep
52+
self.sleeps = []
53+
datadog.time.time = lambda: 1710000000
54+
datadog.time.sleep = self.sleeps.append
55+
56+
def tearDown(self):
57+
datadog.time.time = self._old_time
58+
datadog.time.sleep = self._old_sleep
59+
60+
def test_builds_metric_payloads_with_types_and_tags(self):
61+
session = FakeSession([202])
62+
client = datadog.DatadogClient(
63+
session,
64+
"api-key",
65+
default_tags=["env:test"],
66+
)
67+
68+
client.gauge("sensor.temperature", 22.5, tags=["room:lab"])
69+
client.count("button.press", 1)
70+
client.rate("sensor.samples_per_second", 2.0)
71+
72+
self.assertTrue(client.flush())
73+
series = session.posts[0]["json"]["series"]
74+
75+
self.assertEqual(series[0]["metric"], "sensor.temperature")
76+
self.assertEqual(series[0]["type"], 3)
77+
self.assertEqual(series[0]["points"], [{
78+
"timestamp": 1710000000,
79+
"value": 22.5,
80+
}])
81+
self.assertEqual(series[0]["tags"], ["env:test", "room:lab"])
82+
83+
self.assertEqual(series[1]["metric"], "button.press")
84+
self.assertEqual(series[1]["type"], 1)
85+
self.assertEqual(series[1]["tags"], ["env:test"])
86+
87+
self.assertEqual(series[2]["metric"], "sensor.samples_per_second")
88+
self.assertEqual(series[2]["type"], 2)
89+
self.assertEqual(series[2]["tags"], ["env:test"])
90+
91+
def test_constructs_datadog_site_urls(self):
92+
cases = (
93+
("datadoghq.com", "https://api.datadoghq.com/api/v2/series"),
94+
("datadoghq.eu", "https://api.datadoghq.eu/api/v2/series"),
95+
("us3.datadoghq.com", "https://api.us3.datadoghq.com/api/v2/series"),
96+
(
97+
"https://api.us5.datadoghq.com/",
98+
"https://api.us5.datadoghq.com/api/v2/series",
99+
),
100+
)
101+
102+
for site, expected_url in cases:
103+
client = datadog.DatadogClient(FakeSession(), "api-key", site=site)
104+
self.assertEqual(client.url, expected_url)
105+
106+
def test_flush_success_closes_response_and_clears_buffer(self):
107+
session = FakeSession([202])
108+
client = datadog.DatadogClient(session, "api-key")
109+
client.gauge("sensor.temperature", 22.5)
110+
111+
self.assertTrue(client.flush())
112+
self.assertEqual(client._buffer, [])
113+
self.assertEqual(len(session.posts), 1)
114+
self.assertTrue(session.responses[0].closed)
115+
self.assertEqual(session.posts[0]["headers"]["DD-API-KEY"], "api-key")
116+
117+
def test_flush_http_errors_retry_then_clear_buffer(self):
118+
session = FakeSession([500, 503, 400])
119+
client = datadog.DatadogClient(
120+
session,
121+
"api-key",
122+
max_retries=2,
123+
retry_delay=5,
124+
)
125+
client.count("button.press", 1)
126+
127+
self.assertFalse(client.flush())
128+
self.assertEqual(len(session.posts), 3)
129+
self.assertEqual(self.sleeps, [5, 5])
130+
self.assertEqual(client._buffer, [])
131+
for response in session.responses:
132+
self.assertTrue(response.closed)
133+
134+
def test_flush_network_error_retries_and_can_succeed(self):
135+
session = FakeSession([OSError("socket down"), 202])
136+
client = datadog.DatadogClient(
137+
session,
138+
"api-key",
139+
max_retries=1,
140+
retry_delay=2,
141+
)
142+
client.gauge("sensor.temperature", 22.5)
143+
144+
self.assertTrue(client.flush())
145+
self.assertEqual(len(session.posts), 2)
146+
self.assertEqual(self.sleeps, [2])
147+
self.assertEqual(client._buffer, [])
148+
self.assertTrue(session.responses[0].closed)
149+
150+
def test_flush_never_raises_on_unexpected_request_failure(self):
151+
session = FakeSession([RuntimeError("request failed")])
152+
client = datadog.DatadogClient(session, "api-key", max_retries=0)
153+
client.gauge("sensor.temperature", 22.5)
154+
155+
self.assertFalse(client.flush())
156+
self.assertEqual(client._buffer, [])
157+
158+
def test_flush_never_raises_when_response_close_fails(self):
159+
session = FakeSession([FakeResponse(500, close_raises=True)])
160+
client = datadog.DatadogClient(session, "api-key", max_retries=0)
161+
client.gauge("sensor.temperature", 22.5)
162+
163+
self.assertFalse(client.flush())
164+
self.assertEqual(client._buffer, [])
165+
self.assertTrue(session.responses[0].closed)
166+
167+
def test_flush_empty_buffer_returns_true_without_posting(self):
168+
session = FakeSession()
169+
client = datadog.DatadogClient(session, "api-key")
170+
171+
self.assertTrue(client.flush())
172+
self.assertEqual(session.posts, [])
173+
174+
175+
if __name__ == "__main__":
176+
unittest.main()

0 commit comments

Comments
 (0)