refac: project dir struct + tests

This commit is contained in:
Florian Sylvain
2025-03-28 01:56:15 +01:00
parent fa1ef33d2f
commit 6982a5b97e
12 changed files with 197 additions and 7 deletions
+4
View File
@@ -173,3 +173,7 @@ cython_debug/
# PyPI configuration file # PyPI configuration file
.pypirc .pypirc
# IDE
.vscode/
.idea/
+20 -2
View File
@@ -33,7 +33,7 @@ pip install -r requirements.txt
Run the main script to generate lottery draws: Run the main script to generate lottery draws:
```bash ```bash
python src/main.py python main.py
``` ```
The program will: The program will:
@@ -60,9 +60,27 @@ The application uses a combination of entropy sources to generate truly random l
All sources are combined, hashed, and processed to create seeds for the random number generator that selects the lottery numbers. All sources are combined, hashed, and processed to create seeds for the random number generator that selects the lottery numbers.
## Testing
Unit tests are included to verify the functionality of all major components.
### Running Tests
Run the test suite with pytest:
```bash
python -m pytest test/
```
For a coverage report:
```bash
python -m pytest --cov-report term --cov=./fdj_slayer test/
```
## Configuration ## Configuration
Edit the constants in ``scr/constants.py`` to modify: Edit the constants in ``src/constants.py`` to modify:
| Parameter | Description | | Parameter | Description |
|------------------------|-------------------------------------------------------| |------------------------|-------------------------------------------------------|
+7
View File
@@ -0,0 +1,7 @@
"""
Add the parent directory to the sys.path so the tests can import the modules from the parent dir.
"""
import os
import sys
sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
+1
View File
@@ -0,0 +1 @@
# Empty file to make directory a package
+1 -1
View File
@@ -16,7 +16,7 @@ import multiprocessing
import psutil import psutil
from progress.bar import Bar from progress.bar import Bar
from constants import MAX_NUMBER, MAX_STAR, NUMBER_OF_NUMBERS, NUMBER_OF_STARS from .constants import MAX_NUMBER, MAX_STAR, NUMBER_OF_NUMBERS, NUMBER_OF_STARS
class Draw: class Draw:
+1 -1
View File
@@ -9,7 +9,7 @@ import openmeteo_requests
import requests_cache import requests_cache
from retry_requests import retry from retry_requests import retry
from constants import OPENMETEO_API_URL, OPENMETEO_HOURLY_PARAMS from .constants import OPENMETEO_API_URL, OPENMETEO_HOURLY_PARAMS
class Weather: class Weather:
+3 -3
View File
@@ -5,9 +5,9 @@ This script generates random draws for EuroMillions using various entropy source
import random import random
import numpy as _ import numpy as _
from draw import Draw from fdj_slayer.draw import Draw
from weather import Weather from fdj_slayer.weather import Weather
from constants import NUMBER_OF_DRAWS from fdj_slayer.constants import NUMBER_OF_DRAWS
def main(): def main():
+3
View File
@@ -5,3 +5,6 @@ psutil==7.0.0
requests-cache==1.2.1 requests-cache==1.2.1
retry-requests==2.0.0 retry-requests==2.0.0
numpy==2.2.4 numpy==2.2.4
pytest==8.3.5
pytest-cov==6.0.0
pytest-mock==3.14.0
+1
View File
@@ -0,0 +1 @@
# Empty file to make directory a package
+119
View File
@@ -0,0 +1,119 @@
"""
Unit tests for the Draw class.
"""
import unittest
from unittest.mock import patch, MagicMock
from io import StringIO
import sys
from fdj_slayer.draw import Draw
from fdj_slayer.constants import NUMBER_OF_NUMBERS, NUMBER_OF_STARS
class TestDraw(unittest.TestCase):
"""Test suite for the Draw class"""
def setUp(self):
"""Set up test fixtures"""
self.mock_weather = MagicMock()
self.mock_weather.get_weather_entropy.return_value = "a" * 64
self.draw = Draw(self.mock_weather)
def test_get_static_entropy_pool(self):
"""Test the static entropy pool generation"""
pool = self.draw.get_static_entropy_pool()
self.assertIsInstance(pool, list)
self.assertIn("a" * 64, pool)
self.assertEqual(len(pool), 9)
def test_get_dynamic_entropy_pool(self):
"""Test that dynamic entropy pool returns different values"""
pool1 = self.draw.get_dynamic_entropy_pool()
pool2 = self.draw.get_dynamic_entropy_pool()
self.assertIsInstance(pool1, list)
self.assertIsInstance(pool2, list)
self.assertEqual(len(pool1), len(pool2))
self.assertNotEqual(pool1, pool2)
def test_generate_seed(self):
"""Test that seed generation produces an integer from entropy pool"""
base_pool = ["test1", "test2"]
seed = self.draw.generate_seed(base_pool)
self.assertIsInstance(seed, int)
@patch('fdj_slayer.draw.random')
def test_make_draw(self, mock_random):
"""Test the draw creation with specified numbers and stars"""
mock_random.seed.return_value = None
mock_random.sample.side_effect = [
[10, 20, 30, 40, 50],
[5, 10]
]
draw = self.draw.make_draw(["test_entropy"])
self.assertIn("seed", draw)
self.assertIn("numbers", draw)
self.assertIn("stars", draw)
self.assertEqual(len(draw["numbers"]), NUMBER_OF_NUMBERS)
self.assertEqual(len(draw["stars"]), NUMBER_OF_STARS)
self.assertEqual(draw["numbers"], [10, 20, 30, 40, 50])
self.assertEqual(draw["stars"], [5, 10])
@patch('fdj_slayer.draw.Bar')
def test_generate_draws(self, mock_bar):
"""Test generation of multiple draws"""
mock_bar_instance = mock_bar.return_value
self.draw.make_draw = MagicMock(
side_effect=[{"numbers": [1, 2, 3, 4, 5],
"stars": [1, 2], "seed": i} for i in range(3)]
)
draws = self.draw.generate_draws(3)
self.assertEqual(len(draws), 3)
mock_bar_instance.next.assert_called()
mock_bar_instance.finish.assert_called_once()
def test_display_draw(self):
"""Test draw display formatting"""
captured_output = StringIO()
sys.stdout = captured_output
try:
draw = {"numbers": [1, 2, 3, 4, 5], "stars": [1, 2], "seed": 12345}
self.draw.display_draw(draw, index=0, title="TEST")
output = captured_output.getvalue()
self.assertIn("TEST", output)
self.assertIn("[1, 2, 3, 4, 5]", output)
self.assertIn("[1, 2]", output)
finally:
sys.stdout = sys.__stdout__
@patch('builtins.input')
def test_display_additional_draws(self, mock_input):
"""Test the additional draws display functionality"""
mock_input.side_effect = ['y', 'n']
self.draw.display_draw = MagicMock()
draws = [
{"numbers": [1, 2, 3, 4, 5], "stars": [1, 2], "seed": 1},
{"numbers": [6, 7, 8, 9, 10], "stars": [3, 4], "seed": 2},
{"numbers": [11, 12, 13, 14, 15], "stars": [5, 6], "seed": 3}
]
displayed_draws = {0}
self.draw.display_additional_draws(draws, displayed_draws)
self.assertEqual(len(displayed_draws), 2)
self.draw.display_draw.assert_called_once()
if __name__ == '__main__':
unittest.main()
+37
View File
@@ -0,0 +1,37 @@
"""
Unit tests for the Weather class.
"""
import unittest
from unittest.mock import patch
from fdj_slayer.weather import Weather
class TestWeather(unittest.TestCase):
"""Test suite for the Weather class"""
def setUp(self):
self.weather = Weather()
@patch('fdj_slayer.weather.Weather._get_weather_data')
def test_get_weather_entropy_success(self, mock_get_data):
"""Test the get_weather_entropy method"""
expected_hash = "a" * 64
mock_get_data.return_value = expected_hash
result = self.weather.get_weather_entropy()
self.assertEqual(result, expected_hash)
@patch('fdj_slayer.weather.Weather._get_weather_data')
def test_get_weather_entropy_fallback(self, mock_get_data):
"""Test the get_weather_entropy method when the API fails"""
mock_get_data.side_effect = Exception("API Error")
result = self.weather.get_weather_entropy()
self.assertEqual(len(result), 64)
self.assertTrue(all(c in '0123456789abcdef' for c in result))
if __name__ == '__main__':
unittest.main()