Skip to content

Commit d429caa

Browse files
committed
feat(harvesters): export new harvesters and add tests
- Export WeatherHarvester and RadioactiveHarvester - Add unit tests for both new harvesters - Total: 37 harvester tests passing
1 parent 79117a3 commit d429caa

2 files changed

Lines changed: 93 additions & 0 deletions

File tree

src/trueentropy/harvesters/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
# - NetworkHarvester: Network latency measurements
1111
# - SystemHarvester: System state (RAM, processes, etc.)
1212
# - ExternalHarvester: External APIs (earthquakes, crypto prices)
13+
# - WeatherHarvester: Weather data (temperature, humidity, pressure)
14+
# - RadioactiveHarvester: random.org / ANU QRNG (quantum randomness)
1315
#
1416
# =============================================================================
1517

@@ -25,11 +27,15 @@
2527
from trueentropy.harvesters.network import NetworkHarvester
2628
from trueentropy.harvesters.system import SystemHarvester
2729
from trueentropy.harvesters.external import ExternalHarvester
30+
from trueentropy.harvesters.weather import WeatherHarvester
31+
from trueentropy.harvesters.radioactive import RadioactiveHarvester
2832

2933
__all__ = [
3034
"BaseHarvester",
3135
"TimingHarvester",
3236
"NetworkHarvester",
3337
"SystemHarvester",
3438
"ExternalHarvester",
39+
"WeatherHarvester",
40+
"RadioactiveHarvester",
3541
]

tests/test_harvesters.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
# - Network harvester
99
# - System harvester
1010
# - External harvester
11+
# - Weather harvester
12+
# - Radioactive harvester
1113
# - Base harvester interface
1214
#
1315
# =============================================================================
@@ -21,6 +23,8 @@
2123
from trueentropy.harvesters.network import NetworkHarvester
2224
from trueentropy.harvesters.system import SystemHarvester
2325
from trueentropy.harvesters.external import ExternalHarvester
26+
from trueentropy.harvesters.weather import WeatherHarvester
27+
from trueentropy.harvesters.radioactive import RadioactiveHarvester
2428

2529

2630
class TestHarvestResult:
@@ -274,8 +278,91 @@ def test_repr(self) -> None:
274278
SystemHarvester(),
275279
NetworkHarvester(),
276280
ExternalHarvester(),
281+
WeatherHarvester(),
282+
RadioactiveHarvester(),
277283
]
278284

279285
for h in harvesters:
280286
repr_str = repr(h)
281287
assert h.name in repr_str
288+
289+
290+
class TestWeatherHarvester:
291+
"""Test WeatherHarvester."""
292+
293+
def test_name(self) -> None:
294+
"""Harvester should have correct name."""
295+
harvester = WeatherHarvester()
296+
assert harvester.name == "weather"
297+
298+
def test_collect_returns_result(self) -> None:
299+
"""collect() should return HarvestResult."""
300+
harvester = WeatherHarvester()
301+
result = harvester.collect()
302+
303+
assert isinstance(result, HarvestResult)
304+
305+
def test_timeout_configuration(self) -> None:
306+
"""Harvester should respect timeout config."""
307+
harvester = WeatherHarvester(timeout=3.0)
308+
assert harvester.timeout == 3.0
309+
310+
def test_api_key_masking(self) -> None:
311+
"""API key should be masked when accessed."""
312+
harvester = WeatherHarvester(api_key="my_secret_api_key_12345")
313+
masked = harvester.api_key
314+
315+
# Should be partially masked
316+
assert masked is not None
317+
assert "..." in masked
318+
assert "my_secret_api_key_12345" != masked
319+
320+
def test_no_api_key_returns_none(self) -> None:
321+
"""api_key property should return None when not set."""
322+
harvester = WeatherHarvester()
323+
assert harvester.api_key is None
324+
325+
326+
class TestRadioactiveHarvester:
327+
"""Test RadioactiveHarvester."""
328+
329+
def test_name(self) -> None:
330+
"""Harvester should have correct name."""
331+
harvester = RadioactiveHarvester()
332+
assert harvester.name == "radioactive"
333+
334+
def test_collect_returns_result(self) -> None:
335+
"""collect() should return HarvestResult."""
336+
harvester = RadioactiveHarvester()
337+
result = harvester.collect()
338+
339+
assert isinstance(result, HarvestResult)
340+
341+
def test_timeout_configuration(self) -> None:
342+
"""Harvester should respect timeout config."""
343+
harvester = RadioactiveHarvester(timeout=15.0)
344+
assert harvester.timeout == 15.0
345+
346+
def test_num_integers_configuration(self) -> None:
347+
"""Harvester should respect num_integers config."""
348+
harvester = RadioactiveHarvester(num_integers=20)
349+
assert harvester.num_integers == 20
350+
351+
def test_num_integers_validation(self) -> None:
352+
"""num_integers should have valid range."""
353+
harvester = RadioactiveHarvester()
354+
355+
with pytest.raises(ValueError):
356+
harvester.num_integers = 0
357+
358+
with pytest.raises(ValueError):
359+
harvester.num_integers = 1001
360+
361+
def test_api_key_masking(self) -> None:
362+
"""API key should be masked when accessed."""
363+
harvester = RadioactiveHarvester(api_key="12345678-abcd-efgh-ijkl")
364+
masked = harvester.api_key
365+
366+
assert masked is not None
367+
assert "..." in masked
368+

0 commit comments

Comments
 (0)