-
Notifications
You must be signed in to change notification settings - Fork 1
tests: add Python test infrastructure with 30 tests #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,4 +5,7 @@ src/tls.cert | |
| src/modules/test.ts | ||
| dist/ | ||
| debug/ | ||
| AGENTS.md | ||
| AGENTS.md.venv/ | ||
| __pycache__/ | ||
| *.pyc | ||
| .pytest_cache/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| [pytest] | ||
| testpaths = tests | ||
| python_files = test_*.py | ||
| python_classes = Test* | ||
| python_functions = test_* | ||
| addopts = -v --tb=short |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| # tests package |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,80 @@ | ||
| """Shared fixtures for Botillo tests.""" | ||
| import json | ||
| import os | ||
| import tempfile | ||
| import pytest | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def tmp_db_dir(): | ||
| """Create a temporary directory for test JSON files.""" | ||
| with tempfile.TemporaryDirectory() as d: | ||
| yield d | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def bitcoin_file(tmp_db_dir): | ||
| """Path to a test bitcoin.json file.""" | ||
| return os.path.join(tmp_db_dir, "bitcoin.json") | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def prodillos_file(tmp_db_dir): | ||
| """Path to a test prodillos.json file.""" | ||
| return os.path.join(tmp_db_dir, "prodillos.json") | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def trofeillos_file(tmp_db_dir): | ||
| """Path to a test trofeillos.json file.""" | ||
| return os.path.join(tmp_db_dir, "trofeillos.json") | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def sample_bitcoin_data(): | ||
| """Sample bitcoin price tracker data.""" | ||
| return { | ||
| "bitcoinATH": 109000, | ||
| "dailyMax": 108500, | ||
| "dailyMin": 107000, | ||
| "bitcoinMax": 108800, | ||
| "bitcoinMaxBlock": 890000, | ||
| } | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def sample_prodillos(): | ||
| """Sample prodillos prediction data.""" | ||
| return { | ||
| "users": { | ||
| "123": {"user": "Alice", "predict": 110000}, | ||
| "456": {"user": "Bob", "predict": 105000}, | ||
| "789": {"user": "Charlie", "predict": 108500}, | ||
| }, | ||
| "treasury": 1000, | ||
| } | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def sample_trofeillos(): | ||
| """Sample trofeillos champion data.""" | ||
| return { | ||
| "currentChampion": "Alice", | ||
| "currentChampionId": "123", | ||
| "123": { | ||
| "champion": "Alice", | ||
| "trofeillos profesionales": ["🏆 [890000]"], | ||
| }, | ||
| } | ||
|
|
||
|
|
||
| def write_json(path, data): | ||
| """Helper to write JSON to a file.""" | ||
| with open(path, "w") as f: | ||
| json.dump(data, f, indent=2) | ||
|
|
||
|
|
||
| def read_json(path): | ||
| """Helper to read JSON from a file.""" | ||
| with open(path) as f: | ||
| return json.load(f) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,185 @@ | ||
| """Tests for Bitcoin price tracking logic. | ||
|
|
||
| Tests the price tracking, ATH detection, and daily max/min logic. | ||
| External API calls are mocked. | ||
| """ | ||
| import json | ||
| import pytest | ||
| from tests.conftest import write_json, read_json | ||
|
|
||
|
|
||
| class TestPriceTracking: | ||
| """Test price comparison and tracking logic.""" | ||
|
|
||
| def test_new_ath_detected(self, bitcoin_file, sample_bitcoin_data): | ||
| """When price > ATH, update ATH.""" | ||
| write_json(bitcoin_file, sample_bitcoin_data) | ||
| data = read_json(bitcoin_file) | ||
|
|
||
| new_price = 110000 | ||
| assert new_price > data["bitcoinATH"] | ||
|
|
||
| data["bitcoinATH"] = new_price | ||
| write_json(bitcoin_file, data) | ||
|
|
||
| updated = read_json(bitcoin_file) | ||
| assert updated["bitcoinATH"] == 110000 | ||
|
|
||
| def test_new_daily_max(self, bitcoin_file, sample_bitcoin_data): | ||
| """When price > dailyMax but < ATH, update dailyMax.""" | ||
| write_json(bitcoin_file, sample_bitcoin_data) | ||
| data = read_json(bitcoin_file) | ||
|
|
||
| new_price = 108700 # > dailyMax (108500), < ATH (109000) | ||
| assert new_price > data["dailyMax"] | ||
| assert new_price < data["bitcoinATH"] | ||
|
|
||
| data["dailyMax"] = new_price | ||
| write_json(bitcoin_file, data) | ||
|
|
||
| updated = read_json(bitcoin_file) | ||
| assert updated["dailyMax"] == 108700 | ||
|
|
||
|
Comment on lines
+28
to
+42
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check field names in TypeScript types and implementation
echo "=== BitcoinPriceTracker interface ==="
rg -n "dailyMax|dailyMin|lastReportedMax|lastReportedMin" --type=ts -C2
echo ""
echo "=== Sample fixture in conftest.py ==="
rg -n "dailyMax|dailyMin|lastReportedMax|lastReportedMin" tests/conftest.pyRepository: Fierillo/botillo Length of output: 3705 🏁 Script executed: #!/bin/bash
# Check all occurrences of field names in test files
echo "=== All field usages in test_bitcoin_prices.py ==="
cat -n tests/test_bitcoin_prices.py | grep -E "dailyMax|dailyMin|lastReportedMax|lastReportedMin"
echo ""
echo "=== Check if there are other test files referencing these fields ==="
find tests -name "*.py" -type f | xargs grep -l "dailyMax\|dailyMin\|lastReportedMax\|lastReportedMin"Repository: Fierillo/botillo Length of output: 1417 🏁 Script executed: #!/bin/bash
# Check the imports and test structure in test_bitcoin_prices.py
echo "=== Test file imports and fixtures ==="
head -30 tests/test_bitcoin_prices.py
echo ""
echo "=== Check if tests use actual functions or mocks ==="
rg -n "def test_|@pytest|mock|Mock" tests/test_bitcoin_prices.py | head -20Repository: Fierillo/botillo Length of output: 2239 🏁 Script executed: #!/bin/bash
# Check conftest.py to see the fixture definitions
echo "=== conftest.py content ==="
cat tests/conftest.pyRepository: Fierillo/botillo Length of output: 1931 Field name mismatch between tests and production code. Tests use 🤖 Prompt for AI Agents |
||
| def test_new_daily_min(self, bitcoin_file, sample_bitcoin_data): | ||
| """When price < dailyMin, update dailyMin.""" | ||
| write_json(bitcoin_file, sample_bitcoin_data) | ||
| data = read_json(bitcoin_file) | ||
|
|
||
| new_price = 106000 # < dailyMin (107000) | ||
| assert new_price < data["dailyMin"] | ||
|
|
||
| data["dailyMin"] = new_price | ||
| write_json(bitcoin_file, data) | ||
|
|
||
| updated = read_json(bitcoin_file) | ||
| assert updated["dailyMin"] == 106000 | ||
|
|
||
| def test_price_between_daily_bounds(self, bitcoin_file, sample_bitcoin_data): | ||
| """Price between daily min/max shouldn't trigger updates.""" | ||
| write_json(bitcoin_file, sample_bitcoin_data) | ||
| data = read_json(bitcoin_file) | ||
|
|
||
| new_price = 107500 # between 107000 and 108500 | ||
| assert new_price > data["dailyMin"] | ||
| assert new_price < data["dailyMax"] | ||
| assert new_price < data["bitcoinATH"] | ||
|
|
||
| # No updates should happen | ||
| assert data["dailyMin"] == 107000 | ||
| assert data["dailyMax"] == 108500 | ||
|
|
||
| def test_bitcoin_max_for_prodillo(self, bitcoin_file, sample_bitcoin_data): | ||
| """bitcoinMax tracks the all-time high for the current prodillo round.""" | ||
| write_json(bitcoin_file, sample_bitcoin_data) | ||
| data = read_json(bitcoin_file) | ||
|
|
||
| new_price = 109500 | ||
| if new_price > data["bitcoinMax"]: | ||
| data["bitcoinMax"] = new_price | ||
| data["bitcoinMaxBlock"] = 890100 | ||
|
|
||
| write_json(bitcoin_file, data) | ||
| updated = read_json(bitcoin_file) | ||
| assert updated["bitcoinMax"] == 109500 | ||
| assert updated["bitcoinMaxBlock"] == 890100 | ||
|
|
||
|
|
||
| class TestBitstampResponse: | ||
| """Test parsing of Bitstamp API responses.""" | ||
|
|
||
| def test_parse_valid_response(self): | ||
| """Valid Bitstamp response parsed correctly.""" | ||
| mock_response = { | ||
| "last": "108500", | ||
| "low": "107000", | ||
| "high": "109000", | ||
| } | ||
| price = int(mock_response["last"]) | ||
| low = int(mock_response["low"]) | ||
| high = int(mock_response["high"]) | ||
|
|
||
| assert price == 108500 | ||
| assert low == 107000 | ||
| assert high == 109000 | ||
|
|
||
| def test_parse_decimal_response(self): | ||
| """Response with decimals handled by parseInt.""" | ||
| mock_response = {"last": "108500.50"} | ||
| price = int(mock_response["last"].split(".")[0]) | ||
| assert price == 108500 | ||
|
|
||
|
|
||
| class TestDailyReset: | ||
| """Test daily max/min reset logic.""" | ||
|
|
||
| def test_daily_reset_preserves_ath(self, bitcoin_file): | ||
| """Daily reset should keep ATH and bitcoinMax.""" | ||
| data = { | ||
| "bitcoinATH": 109000, | ||
| "dailyMax": 0, | ||
| "dailyMin": float('inf'), | ||
| "bitcoinMax": 108800, | ||
| "bitcoinMaxBlock": 890000, | ||
| } | ||
| # Simulate reset | ||
| data["dailyMax"] = 0 | ||
| data["dailyMin"] = float('inf') | ||
|
|
||
| # These should be preserved | ||
| assert data["bitcoinATH"] == 109000 | ||
| assert data["bitcoinMax"] == 108800 | ||
| assert data["bitcoinMaxBlock"] == 890000 | ||
| assert data["dailyMax"] == 0 | ||
| assert data["dailyMin"] == float('inf') | ||
|
|
||
| def test_round_reset_clears_bitcoin_max(self, bitcoin_file, sample_bitcoin_data): | ||
| """After prodillo round ends, bitcoinMax resets to 0.""" | ||
| write_json(bitcoin_file, sample_bitcoin_data) | ||
| data = read_json(bitcoin_file) | ||
|
|
||
| # Round ends | ||
| data["bitcoinMax"] = 0 | ||
| data["bitcoinMaxBlock"] = 0 | ||
| write_json(bitcoin_file, data) | ||
|
|
||
| updated = read_json(bitcoin_file) | ||
| assert updated["bitcoinMax"] == 0 | ||
| assert updated["bitcoinMaxBlock"] == 0 | ||
| # ATH and daily values preserved | ||
| assert updated["bitcoinATH"] == 109000 | ||
|
|
||
|
|
||
| class TestNotificationLogic: | ||
| """Test when notifications should be sent.""" | ||
|
|
||
| def test_ath_notification(self): | ||
| """ATH notification when price > all previous.""" | ||
| ath = 109000 | ||
| new_price = 110000 | ||
| should_notify = new_price > ath | ||
| assert should_notify is True | ||
|
|
||
| def test_daily_max_notification(self): | ||
| """Daily max notification when price > dailyMax but < ATH.""" | ||
| ath = 109000 | ||
| daily_max = 108500 | ||
| new_price = 108700 | ||
| should_notify = new_price > daily_max and new_price < ath | ||
| assert should_notify is True | ||
|
|
||
| def test_daily_min_notification(self): | ||
| """Daily min notification when price < dailyMin.""" | ||
| daily_min = 107000 | ||
| new_price = 106000 | ||
| should_notify = new_price < daily_min | ||
| assert should_notify is True | ||
|
|
||
| def test_no_notification_in_range(self): | ||
| """No notification when price is within daily bounds.""" | ||
| daily_max = 108500 | ||
| daily_min = 107000 | ||
| new_price = 107500 | ||
| should_notify = ( | ||
| new_price > daily_max or new_price < daily_min | ||
| ) | ||
| assert should_notify is False | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Malformed gitignore entry - likely a merge artifact.
AGENTS.md.venv/appears to be two entries accidentally merged together. Based on the PR description mentioning.venvshould be ignored, this should be split into separate entries.🔧 Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents