Skip to content

Commit 86dd42c

Browse files
committed
test: add comprehensive test suite (98 tests)
- test_pool.py: EntropyPool unit tests - test_tap.py: EntropyTap with distribution checks - test_harvesters.py: all harvester implementations - test_integration.py: public API and end-to-end tests
1 parent ca9dee0 commit 86dd42c

6 files changed

Lines changed: 1312 additions & 0 deletions

File tree

tests/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# TrueEntropy Tests

tests/conftest.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# =============================================================================
2+
# TrueEntropy - Test Configuration
3+
# =============================================================================
4+
5+
"""Pytest configuration and fixtures for TrueEntropy tests."""
6+
7+
import pytest
8+
9+
from trueentropy.pool import EntropyPool
10+
from trueentropy.tap import EntropyTap
11+
12+
13+
@pytest.fixture
14+
def pool() -> EntropyPool:
15+
"""Create a fresh EntropyPool for testing."""
16+
return EntropyPool()
17+
18+
19+
@pytest.fixture
20+
def seeded_pool() -> EntropyPool:
21+
"""Create an EntropyPool with fixed seed for deterministic tests."""
22+
return EntropyPool(seed=b"test_seed_for_deterministic_tests")
23+
24+
25+
@pytest.fixture
26+
def tap(pool: EntropyPool) -> EntropyTap:
27+
"""Create an EntropyTap with a fresh pool."""
28+
return EntropyTap(pool)
29+
30+
31+
@pytest.fixture
32+
def seeded_tap(seeded_pool: EntropyPool) -> EntropyTap:
33+
"""Create an EntropyTap with seeded pool for deterministic tests."""
34+
return EntropyTap(seeded_pool)

tests/test_harvesters.py

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
# =============================================================================
2+
# TrueEntropy - Harvesters Tests
3+
# =============================================================================
4+
#
5+
# Unit tests for all harvester classes.
6+
# Tests cover:
7+
# - Timing harvester
8+
# - Network harvester
9+
# - System harvester
10+
# - External harvester
11+
# - Base harvester interface
12+
#
13+
# =============================================================================
14+
15+
"""Tests for the entropy harvesters."""
16+
17+
import pytest
18+
19+
from trueentropy.harvesters.base import BaseHarvester, HarvestResult
20+
from trueentropy.harvesters.timing import TimingHarvester
21+
from trueentropy.harvesters.network import NetworkHarvester
22+
from trueentropy.harvesters.system import SystemHarvester
23+
from trueentropy.harvesters.external import ExternalHarvester
24+
25+
26+
class TestHarvestResult:
27+
"""Test HarvestResult dataclass."""
28+
29+
def test_success_result(self) -> None:
30+
"""Test creating a successful result."""
31+
result = HarvestResult(
32+
data=b"test_data",
33+
entropy_bits=16,
34+
source="test",
35+
success=True
36+
)
37+
38+
assert result.data == b"test_data"
39+
assert result.entropy_bits == 16
40+
assert result.source == "test"
41+
assert result.success is True
42+
assert result.error is None
43+
44+
def test_failure_result(self) -> None:
45+
"""Test creating a failed result."""
46+
result = HarvestResult(
47+
data=b"",
48+
entropy_bits=0,
49+
source="test",
50+
success=False,
51+
error="Something went wrong"
52+
)
53+
54+
assert result.data == b""
55+
assert result.entropy_bits == 0
56+
assert result.success is False
57+
assert result.error == "Something went wrong"
58+
59+
60+
class TestTimingHarvester:
61+
"""Test TimingHarvester."""
62+
63+
def test_name(self) -> None:
64+
"""Harvester should have correct name."""
65+
harvester = TimingHarvester()
66+
assert harvester.name == "timing"
67+
68+
def test_collect_returns_result(self) -> None:
69+
"""collect() should return HarvestResult."""
70+
harvester = TimingHarvester()
71+
result = harvester.collect()
72+
73+
assert isinstance(result, HarvestResult)
74+
75+
def test_collect_succeeds(self) -> None:
76+
"""collect() should succeed (timing is always available)."""
77+
harvester = TimingHarvester()
78+
result = harvester.collect()
79+
80+
assert result.success is True
81+
assert result.data != b""
82+
assert result.entropy_bits > 0
83+
84+
def test_collect_returns_bytes(self) -> None:
85+
"""collect() should return bytes data."""
86+
harvester = TimingHarvester()
87+
result = harvester.collect()
88+
89+
assert isinstance(result.data, bytes)
90+
assert len(result.data) > 0
91+
92+
def test_num_samples_configuration(self) -> None:
93+
"""Harvester should respect num_samples config."""
94+
harvester = TimingHarvester(num_samples=32)
95+
assert harvester.num_samples == 32
96+
97+
result = harvester.collect()
98+
# 32 samples * 8 bytes per sample
99+
assert len(result.data) == 32 * 8
100+
101+
def test_different_collections_different_data(self) -> None:
102+
"""Successive collections should return different data."""
103+
harvester = TimingHarvester()
104+
105+
results = [harvester.collect().data for _ in range(10)]
106+
107+
# All should be unique
108+
assert len(set(results)) == 10
109+
110+
def test_safe_collect(self) -> None:
111+
"""safe_collect() should never raise."""
112+
harvester = TimingHarvester()
113+
114+
# Should not raise
115+
result = harvester.safe_collect()
116+
117+
assert isinstance(result, HarvestResult)
118+
119+
120+
class TestSystemHarvester:
121+
"""Test SystemHarvester."""
122+
123+
def test_name(self) -> None:
124+
"""Harvester should have correct name."""
125+
harvester = SystemHarvester()
126+
assert harvester.name == "system"
127+
128+
def test_collect_returns_result(self) -> None:
129+
"""collect() should return HarvestResult."""
130+
harvester = SystemHarvester()
131+
result = harvester.collect()
132+
133+
assert isinstance(result, HarvestResult)
134+
135+
def test_collect_returns_data(self) -> None:
136+
"""collect() should return non-empty data."""
137+
harvester = SystemHarvester()
138+
result = harvester.collect()
139+
140+
# Should succeed if psutil is available
141+
if result.success:
142+
assert result.data != b""
143+
assert result.entropy_bits > 0
144+
145+
def test_list_available_metrics(self) -> None:
146+
"""list_available_metrics() should return list."""
147+
harvester = SystemHarvester()
148+
metrics = harvester.list_available_metrics()
149+
150+
assert isinstance(metrics, list)
151+
# At minimum, timestamp should be available
152+
assert "timestamp_ns" in metrics
153+
154+
155+
class TestNetworkHarvester:
156+
"""Test NetworkHarvester."""
157+
158+
def test_name(self) -> None:
159+
"""Harvester should have correct name."""
160+
harvester = NetworkHarvester()
161+
assert harvester.name == "network"
162+
163+
def test_default_targets(self) -> None:
164+
"""Harvester should have default targets."""
165+
harvester = NetworkHarvester()
166+
167+
assert len(harvester.targets) > 0
168+
assert all(t.startswith("http") for t in harvester.targets)
169+
170+
def test_custom_targets(self) -> None:
171+
"""Harvester should accept custom targets."""
172+
targets = ["https://example.com", "https://test.com"]
173+
harvester = NetworkHarvester(targets=targets)
174+
175+
assert harvester.targets == targets
176+
177+
def test_timeout_configuration(self) -> None:
178+
"""Harvester should respect timeout config."""
179+
harvester = NetworkHarvester(timeout=1.0)
180+
assert harvester.timeout == 1.0
181+
182+
def test_collect_returns_result(self) -> None:
183+
"""collect() should return HarvestResult."""
184+
harvester = NetworkHarvester()
185+
result = harvester.collect()
186+
187+
assert isinstance(result, HarvestResult)
188+
189+
# Note: We don't test actual network calls here to avoid
190+
# network-dependent test failures. Integration tests would
191+
# cover that.
192+
193+
194+
class TestExternalHarvester:
195+
"""Test ExternalHarvester."""
196+
197+
def test_name(self) -> None:
198+
"""Harvester should have correct name."""
199+
harvester = ExternalHarvester()
200+
assert harvester.name == "external"
201+
202+
def test_enable_flags(self) -> None:
203+
"""Harvester should respect enable flags."""
204+
harvester = ExternalHarvester(
205+
enable_earthquake=False,
206+
enable_crypto=True
207+
)
208+
209+
assert harvester.enable_earthquake is False
210+
assert harvester.enable_crypto is True
211+
212+
def test_timeout_configuration(self) -> None:
213+
"""Harvester should respect timeout config."""
214+
harvester = ExternalHarvester(timeout=3.0)
215+
assert harvester.timeout == 3.0
216+
217+
def test_collect_returns_result(self) -> None:
218+
"""collect() should return HarvestResult."""
219+
harvester = ExternalHarvester()
220+
result = harvester.collect()
221+
222+
assert isinstance(result, HarvestResult)
223+
224+
# Note: We don't test actual API calls here to avoid
225+
# network-dependent test failures and rate limiting.
226+
227+
228+
class TestBaseHarvesterInterface:
229+
"""Test that harvesters implement the interface correctly."""
230+
231+
def test_all_harvesters_have_name(self) -> None:
232+
"""All harvesters should implement name property."""
233+
harvesters = [
234+
TimingHarvester(),
235+
SystemHarvester(),
236+
NetworkHarvester(),
237+
ExternalHarvester(),
238+
]
239+
240+
for h in harvesters:
241+
assert isinstance(h.name, str)
242+
assert len(h.name) > 0
243+
244+
def test_all_harvesters_have_collect(self) -> None:
245+
"""All harvesters should implement collect method."""
246+
harvesters = [
247+
TimingHarvester(),
248+
SystemHarvester(),
249+
NetworkHarvester(),
250+
ExternalHarvester(),
251+
]
252+
253+
for h in harvesters:
254+
result = h.collect()
255+
assert isinstance(result, HarvestResult)
256+
257+
def test_all_harvesters_have_safe_collect(self) -> None:
258+
"""All harvesters should have safe_collect from base class."""
259+
harvesters = [
260+
TimingHarvester(),
261+
SystemHarvester(),
262+
NetworkHarvester(),
263+
ExternalHarvester(),
264+
]
265+
266+
for h in harvesters:
267+
result = h.safe_collect()
268+
assert isinstance(result, HarvestResult)
269+
270+
def test_repr(self) -> None:
271+
"""All harvesters should have useful repr."""
272+
harvesters = [
273+
TimingHarvester(),
274+
SystemHarvester(),
275+
NetworkHarvester(),
276+
ExternalHarvester(),
277+
]
278+
279+
for h in harvesters:
280+
repr_str = repr(h)
281+
assert h.name in repr_str

0 commit comments

Comments
 (0)