This repository was archived by the owner on Jul 12, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathtest_scheduler.py
More file actions
239 lines (194 loc) · 8.09 KB
/
Copy pathtest_scheduler.py
File metadata and controls
239 lines (194 loc) · 8.09 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
"""Tests for the scheduler module."""
import pytest
from unittest.mock import Mock, AsyncMock, patch
from datetime import datetime
from pathlib import Path
from quantcoder.scheduler.notion_client import NotionClient, StrategyArticle
from quantcoder.scheduler.article_generator import ArticleGenerator, StrategyReport
from quantcoder.scheduler.runner import ScheduledRunner, ScheduleConfig, ScheduleInterval
class TestNotionClient:
"""Tests for NotionClient."""
def test_is_configured_without_credentials(self):
"""Test is_configured returns False without credentials."""
client = NotionClient(api_key=None, database_id=None)
assert not client.is_configured()
def test_is_configured_with_credentials(self):
"""Test is_configured returns True with credentials."""
client = NotionClient(api_key="test_key", database_id="test_db")
assert client.is_configured()
@patch('requests.get')
def test_test_connection_success(self, mock_get):
"""Test successful connection test."""
mock_get.return_value.status_code = 200
client = NotionClient(api_key="test_key", database_id="test_db")
assert client.test_connection()
@patch('requests.get')
def test_test_connection_failure(self, mock_get):
"""Test failed connection test."""
mock_get.return_value.status_code = 401
client = NotionClient(api_key="invalid_key", database_id="test_db")
assert not client.test_connection()
class TestStrategyArticle:
"""Tests for StrategyArticle."""
def test_to_notion_blocks(self):
"""Test conversion to Notion blocks."""
article = StrategyArticle(
title="Test Strategy",
paper_title="Test Paper",
paper_url="https://example.com/paper",
paper_authors=["Author 1", "Author 2"],
strategy_summary="This is a test strategy.",
strategy_type="momentum",
backtest_results={
"sharpe_ratio": 1.5,
"total_return": 0.25,
"max_drawdown": -0.10,
},
tags=["momentum", "high sharpe"],
)
blocks = article.to_notion_blocks()
assert len(blocks) > 0
# Check for callout block with paper info
assert any(b.get("type") == "callout" for b in blocks)
# Check for heading blocks
assert any(b.get("type") == "heading_2" for b in blocks)
class TestArticleGenerator:
"""Tests for ArticleGenerator."""
@pytest.fixture
def sample_report(self):
"""Create a sample strategy report."""
return StrategyReport(
strategy_name="MomentumStrategy_20240101",
paper_title="A Study of Momentum Trading",
paper_url="https://arxiv.org/abs/1234.5678",
paper_authors=["John Doe", "Jane Smith"],
paper_abstract="This paper studies momentum trading strategies...",
strategy_type="momentum",
strategy_summary="",
code_files={
"Main.py": "class MomentumAlgorithm(QCAlgorithm):\n pass",
"Alpha.py": "class MomentumAlpha:\n pass",
},
backtest_results={
"sharpe_ratio": 1.2,
"total_return": 0.35,
"max_drawdown": -0.15,
},
)
def test_generate_title(self, sample_report):
"""Test title generation."""
generator = ArticleGenerator()
title = generator.generate_title(sample_report)
assert "Momentum" in title
assert sample_report.strategy_name in title
def test_generate_template_summary(self, sample_report):
"""Test template-based summary generation."""
generator = ArticleGenerator()
summary = generator._generate_template_summary(sample_report)
assert len(summary) > 0
assert "momentum" in summary.lower()
assert "1.2" in summary or "Sharpe" in summary
def test_generate_notion_article(self, sample_report):
"""Test Notion article generation."""
generator = ArticleGenerator()
article = generator.generate_notion_article(sample_report)
assert isinstance(article, StrategyArticle)
assert article.paper_title == sample_report.paper_title
assert article.strategy_type == "momentum"
assert len(article.tags) > 0
def test_generate_markdown(self, sample_report):
"""Test markdown generation."""
generator = ArticleGenerator()
markdown = generator.generate_markdown(sample_report)
assert "# " in markdown
assert sample_report.paper_title in markdown
assert "```python" in markdown
assert "Sharpe Ratio" in markdown
class TestScheduleConfig:
"""Tests for ScheduleConfig."""
def test_daily_trigger(self):
"""Test daily schedule trigger creation."""
config = ScheduleConfig(
interval=ScheduleInterval.DAILY,
hour=6,
minute=0,
)
trigger = config.to_trigger()
assert trigger is not None
def test_weekly_trigger(self):
"""Test weekly schedule trigger creation."""
config = ScheduleConfig(
interval=ScheduleInterval.WEEKLY,
hour=9,
day_of_week="mon",
)
trigger = config.to_trigger()
assert trigger is not None
def test_hourly_trigger(self):
"""Test hourly schedule trigger creation."""
config = ScheduleConfig(interval=ScheduleInterval.HOURLY)
trigger = config.to_trigger()
assert trigger is not None
class TestScheduledRunner:
"""Tests for ScheduledRunner."""
@pytest.fixture
def mock_pipeline(self):
"""Create a mock pipeline function."""
async def pipeline():
return {"strategies_generated": 2, "strategies_published": 1}
return pipeline
def test_runner_initialization(self, mock_pipeline, tmp_path):
"""Test runner initialization."""
runner = ScheduledRunner(
pipeline_func=mock_pipeline,
state_file=tmp_path / "test_state.json"
)
assert runner.stats.total_runs == 0
assert not runner.running
def test_get_status(self, mock_pipeline, tmp_path):
"""Test status retrieval."""
runner = ScheduledRunner(
pipeline_func=mock_pipeline,
state_file=tmp_path / "test_state.json"
)
status = runner.get_status()
assert "running" in status
assert "stats" in status
assert status["running"] is False
@pytest.mark.asyncio
async def test_run_once(self, mock_pipeline, tmp_path):
"""Test single run execution."""
runner = ScheduledRunner(
pipeline_func=mock_pipeline,
state_file=tmp_path / "test_state.json"
)
await runner.run_once()
assert runner.stats.total_runs == 1
assert runner.stats.successful_runs == 1
assert runner.stats.strategies_generated == 2
assert runner.stats.strategies_published == 1
@pytest.mark.asyncio
async def test_run_with_error(self, tmp_path):
"""Test run with pipeline error."""
async def failing_pipeline():
raise ValueError("Test error")
# Use a separate state file to avoid test pollution
runner = ScheduledRunner(
pipeline_func=failing_pipeline,
state_file=tmp_path / "test_state.json"
)
await runner.run_once()
assert runner.stats.total_runs == 1
assert runner.stats.failed_runs == 1
assert runner.stats.successful_runs == 0
assert len(runner.stats.errors) == 1
class TestPipelineConfig:
"""Tests for PipelineConfig."""
def test_default_config(self):
"""Test default configuration values."""
from quantcoder.scheduler.automated_pipeline import PipelineConfig
config = PipelineConfig()
assert len(config.search_queries) > 0
assert config.min_sharpe_ratio == 0.5 # Acceptance criteria
assert config.max_strategies_per_run == 10 # Batch limit
assert config.publish_to_notion is True