Skip to content

Commit 239ef17

Browse files
Some tests modules added
1 parent 6ec1773 commit 239ef17

6 files changed

Lines changed: 189 additions & 0 deletions

File tree

.github/workflows/ci.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Python Application CI
2+
3+
on:
4+
push:
5+
branches: [ "main" ]
6+
pull_request:
7+
branches: [ "main" ]
8+
9+
permissions:
10+
contents: read
11+
12+
jobs:
13+
build:
14+
15+
runs-on: ubuntu-latest
16+
17+
steps:
18+
- uses: actions/checkout@v4
19+
- name: Set up Python 3.12
20+
uses: actions/setup-python@v5
21+
with:
22+
python-version: "3.12"
23+
- name: Install dependencies
24+
run: |
25+
python -m pip install --upgrade pip
26+
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
27+
- name: Test with pytest
28+
run: |
29+
export PYTHONPATH=.
30+
pytest tests

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ watchdog
44
# Added for fetching stock prices
55
yfinance
66
plotly
7+
pytest

tests/run_tests.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
PYTHONPATH=. pytest tests

tests/test_indicators.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import pandas as pd
2+
import numpy as np
3+
import pytest
4+
from indicators import add_moving_average, add_52w_high_low, calculate_rsi
5+
6+
@pytest.fixture
7+
def sample_price_df():
8+
# Create a simple DataFrame for testing
9+
dates = pd.date_range(start='2023-01-01', periods=100)
10+
data = {
11+
'AAPL': np.arange(100, 200), # Linear increase
12+
'GOOG': np.random.rand(100) * 100
13+
}
14+
return pd.DataFrame(data, index=dates)
15+
16+
def test_add_moving_average(sample_price_df):
17+
df = sample_price_df.copy()
18+
df = add_moving_average(df, ma_periods=[10, 20])
19+
20+
assert 'AAPL_MA10' in df.columns
21+
assert 'AAPL_MA20' in df.columns
22+
assert 'GOOG_MA10' in df.columns
23+
24+
# Check simple calculation for linear increase (avg of last 10 of 100..109 is roughly 104.5)
25+
# The first 9 values should be NaN if min_periods wasn't 1, but logic uses min_periods=1
26+
assert not df['AAPL_MA10'].isnull().all()
27+
28+
# Verify values for a known sequence
29+
# AAPL is 100, 101, 102...
30+
# At index 0 (value 100), MA10 (min_periods=1) should be 100
31+
assert df['AAPL_MA10'].iloc[0] == 100.0
32+
33+
# At index 9 (value 109), MA10 is avg(100...109) = 104.5
34+
assert df['AAPL_MA10'].iloc[9] == 104.5
35+
36+
def test_add_52w_high_low(sample_price_df):
37+
df = sample_price_df.copy()
38+
# Ensure enough data or check min_periods behavior
39+
# Code uses min_periods=1
40+
df = add_52w_high_low(df)
41+
42+
assert 'AAPL_52w_high' in df.columns
43+
assert 'AAPL_52w_low' in df.columns
44+
45+
# For monotonically increasing AAPL (100 to 199)
46+
# The 52w high at the last point should be the current price
47+
assert df['AAPL_52w_high'].iloc[-1] == 199
48+
# The 52w low at the last point should be the price 52 weeks ago (or start of data)
49+
# Since we only have 100 days, it's the min of the whole window so far, which is 100
50+
assert df['AAPL_52w_low'].iloc[-1] == 100
51+
52+
def test_calculate_rsi(sample_price_df):
53+
df = sample_price_df.copy()
54+
rsi_df = calculate_rsi(df)
55+
56+
assert 'AAPL' in rsi_df.columns
57+
assert 'GOOG' in rsi_df.columns
58+
59+
# RSI should be between 0 and 100
60+
assert rsi_df.max().max() <= 100
61+
assert rsi_df.min().min() >= 0
62+
63+
# For a constantly increasing stock (AAPL), RSI should be high (near 100)
64+
# Use a slice to avoid initial warmup period
65+
assert rsi_df['AAPL'].iloc[-1] > 70

tests/test_news.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import pytest
2+
from unittest.mock import MagicMock, patch
3+
from news import get_news
4+
5+
@patch('news.yf.Ticker')
6+
def test_get_news(mock_ticker):
7+
# Mock data structure matching yfinance news format
8+
mock_news_data = [
9+
{
10+
'content': {
11+
'title': 'Stock goes up',
12+
'canonicalUrl': {'url': 'http://example.com/news1'}
13+
},
14+
'publisher': 'The News Publisher'
15+
},
16+
{
17+
'content': {
18+
'title': 'Market crash?',
19+
# Missing url test
20+
},
21+
# Missing publisher test
22+
}
23+
]
24+
25+
mock_instance = MagicMock()
26+
mock_instance.news = mock_news_data
27+
mock_ticker.return_value = mock_instance
28+
29+
news_list = get_news('TEST')
30+
31+
assert len(news_list) == 2
32+
assert news_list[0]['headline'] == 'Stock goes up'
33+
assert news_list[0]['link'] == 'http://example.com/news1'
34+
assert news_list[0]['publisher'] == 'The News Publisher'
35+
36+
# Check defaults
37+
assert news_list[1]['headline'] == 'Market crash?'
38+
assert news_list[1]['link'] == 'N/A - No Link Found'
39+
assert news_list[1]['publisher'] == 'N/A - No Publisher'
40+
41+
@patch('news.yf.Ticker')
42+
def test_get_news_empty(mock_ticker):
43+
mock_instance = MagicMock()
44+
mock_instance.news = []
45+
mock_ticker.return_value = mock_instance
46+
47+
news_list = get_news('EMPTY')
48+
assert news_list == []

tests/test_utils.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import pytest
2+
import pandas as pd
3+
from unittest.mock import MagicMock, patch
4+
from utils import get_price_data
5+
6+
@patch('utils.yf.Ticker')
7+
def test_get_price_data(mock_ticker):
8+
# Setup mock return data
9+
mock_hist = pd.DataFrame({
10+
'Open': [100, 102],
11+
'High': [105, 106],
12+
'Low': [99, 101],
13+
'Close': [101, 105],
14+
}, index=pd.to_datetime(['2023-01-01', '2023-01-02']))
15+
mock_hist.index.name = 'Date'
16+
17+
mock_instance = MagicMock()
18+
mock_instance.history.return_value = mock_hist
19+
mock_ticker.return_value = mock_instance
20+
21+
start = '2023-01-01'
22+
end = '2023-01-03'
23+
tickers = ['TEST']
24+
25+
df = get_price_data(tickers, start, end)
26+
27+
assert not df.empty
28+
assert 'Ticker' in df.columns
29+
assert 'Price' in df.columns
30+
assert df['Ticker'].iloc[0] == 'TEST'
31+
assert df['Price'].iloc[0] == 101 # Close price renamed to Price
32+
33+
@patch('utils.yf.Ticker')
34+
def test_get_price_data_empty(mock_ticker):
35+
# Setup mock to return empty
36+
mock_instance = MagicMock()
37+
mock_instance.history.return_value = pd.DataFrame()
38+
mock_ticker.return_value = mock_instance
39+
40+
df = get_price_data(['EMPTY'], '2023-01-01', '2023-01-02')
41+
42+
assert df.empty
43+
assert 'Ticker' in df.columns
44+
assert 'Price' in df.columns

0 commit comments

Comments
 (0)