- api/report/ 包:query(连库+SQL)/ views(2 视图)/ serializers(OpenAPI)/ tests(17 单测) - urls.py 注册 news/reports/、news/events/;settings.py SPECTACULAR 加「日报」tag - .env.example 补 NEWS_DB_* 占位配置;README/continuation.md 更新 - docs/news_report_api.md 使用手册;CLAUDE*.md 修正 CLI 路径为 finance/ 前缀
150 lines
5.8 KiB
Python
150 lines
5.8 KiB
Python
"""
|
|
日报查询 API 测试(GET /api/news/reports/ 与 /api/news/events/)。
|
|
|
|
通过 mock 数据库查询层(api.report.query.*),只验证视图层
|
|
参数解析 / 默认窗口 / 响应结构 / 错误处理,不依赖真实 MySQL。
|
|
"""
|
|
|
|
from datetime import timedelta
|
|
from unittest.mock import patch
|
|
|
|
from django.test import TestCase
|
|
from django.utils import timezone
|
|
from rest_framework.test import APIClient
|
|
|
|
|
|
class NewsReportsAPITest(TestCase):
|
|
"""GET /api/news/reports/ 日报查询"""
|
|
|
|
def setUp(self):
|
|
self.client = APIClient()
|
|
self.url = '/api/news/reports/'
|
|
|
|
@patch('api.report.query.fetch_reports', return_value=[])
|
|
def test_default_window_last_24h(self, mock_fetch):
|
|
resp = self.client.get(self.url)
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEqual(resp.json(), [])
|
|
kwargs = mock_fetch.call_args.kwargs
|
|
now = timezone.now()
|
|
self.assertEqual(kwargs['start_date'], (now - timedelta(hours=24)).date())
|
|
self.assertEqual(kwargs['end_date'], now.date())
|
|
self.assertIsNone(kwargs['report_type'])
|
|
self.assertIsNone(kwargs['report_id'])
|
|
|
|
@patch('api.report.query.fetch_reports',
|
|
return_value=[{'id': 1, 'report_date': '2026-08-01', 'report_type': 'finance'}])
|
|
def test_report_type_and_date_range(self, mock_fetch):
|
|
resp = self.client.get(self.url, {
|
|
'report_type': 'finance',
|
|
'start_date': '2026-07-01',
|
|
'end_date': '2026-08-03',
|
|
})
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEqual(len(resp.json()), 1)
|
|
kwargs = mock_fetch.call_args.kwargs
|
|
self.assertEqual(kwargs['report_type'], 'finance')
|
|
self.assertEqual(str(kwargs['start_date']), '2026-07-01')
|
|
self.assertEqual(str(kwargs['end_date']), '2026-08-03')
|
|
|
|
@patch('api.report.query.fetch_reports')
|
|
def test_detail_by_id(self, mock_fetch):
|
|
mock_fetch.return_value = {
|
|
'id': 10, 'report_date': '2026-08-03', 'report_type': 'finance',
|
|
'events': [{'id': 1, 'title': '事件一'}],
|
|
}
|
|
resp = self.client.get(self.url, {'id': '10'})
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEqual(resp.json()['id'], 10)
|
|
self.assertEqual(mock_fetch.call_args.kwargs['report_id'], 10)
|
|
|
|
@patch('api.report.query.fetch_reports', return_value=None)
|
|
def test_detail_not_found(self, mock_fetch):
|
|
resp = self.client.get(self.url, {'id': '99999'})
|
|
self.assertEqual(resp.status_code, 404)
|
|
self.assertIn('error', resp.json())
|
|
|
|
def test_invalid_report_type(self):
|
|
resp = self.client.get(self.url, {'report_type': 'xxx'})
|
|
self.assertEqual(resp.status_code, 400)
|
|
|
|
def test_invalid_date_format(self):
|
|
resp = self.client.get(self.url, {'start_date': '2026/07/01'})
|
|
self.assertEqual(resp.status_code, 400)
|
|
|
|
def test_invalid_id(self):
|
|
resp = self.client.get(self.url, {'id': 'abc'})
|
|
self.assertEqual(resp.status_code, 400)
|
|
|
|
def test_non_positive_id(self):
|
|
resp = self.client.get(self.url, {'id': '0'})
|
|
self.assertEqual(resp.status_code, 400)
|
|
|
|
@patch('api.report.query.fetch_reports', side_effect=RuntimeError('db down'))
|
|
def test_query_error_500(self, mock_fetch):
|
|
resp = self.client.get(self.url)
|
|
self.assertEqual(resp.status_code, 500)
|
|
self.assertIn('error', resp.json())
|
|
|
|
|
|
class NewsEventsAPITest(TestCase):
|
|
"""GET /api/news/events/ 重要事件聚合"""
|
|
|
|
def setUp(self):
|
|
self.client = APIClient()
|
|
self.url = '/api/news/events/'
|
|
|
|
@patch('api.report.query.fetch_important_events', return_value=[])
|
|
def test_defaults(self, mock_fetch):
|
|
resp = self.client.get(self.url)
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEqual(resp.json(), [])
|
|
kwargs = mock_fetch.call_args.kwargs
|
|
self.assertEqual(kwargs['days'], 7)
|
|
self.assertEqual(kwargs['importance'], 4)
|
|
self.assertEqual(kwargs['limit'], 100)
|
|
self.assertIsNone(kwargs['report_type'])
|
|
self.assertIsNone(kwargs['section'])
|
|
|
|
@patch('api.report.query.fetch_important_events',
|
|
return_value=[{'id': 1, 'title': '重要事件', 'importance': 5}])
|
|
def test_filters(self, mock_fetch):
|
|
resp = self.client.get(self.url, {
|
|
'days': '3', 'importance': '5',
|
|
'report_type': 'intl', 'section': 'intl', 'limit': '10',
|
|
})
|
|
self.assertEqual(resp.status_code, 200)
|
|
self.assertEqual(len(resp.json()), 1)
|
|
kwargs = mock_fetch.call_args.kwargs
|
|
self.assertEqual(kwargs['days'], 3)
|
|
self.assertEqual(kwargs['importance'], 5)
|
|
self.assertEqual(kwargs['limit'], 10)
|
|
self.assertEqual(kwargs['report_type'], 'intl')
|
|
self.assertEqual(kwargs['section'], 'intl')
|
|
|
|
def test_invalid_days(self):
|
|
resp = self.client.get(self.url, {'days': 'abc'})
|
|
self.assertEqual(resp.status_code, 400)
|
|
|
|
def test_days_out_of_range(self):
|
|
resp = self.client.get(self.url, {'days': '0'})
|
|
self.assertEqual(resp.status_code, 400)
|
|
|
|
def test_invalid_importance(self):
|
|
resp = self.client.get(self.url, {'importance': '9'})
|
|
self.assertEqual(resp.status_code, 400)
|
|
|
|
def test_invalid_section(self):
|
|
resp = self.client.get(self.url, {'section': 'foo'})
|
|
self.assertEqual(resp.status_code, 400)
|
|
|
|
def test_invalid_report_type(self):
|
|
resp = self.client.get(self.url, {'report_type': 'xxx'})
|
|
self.assertEqual(resp.status_code, 400)
|
|
|
|
@patch('api.report.query.fetch_important_events', side_effect=RuntimeError('db down'))
|
|
def test_query_error_500(self, mock_fetch):
|
|
resp = self.client.get(self.url)
|
|
self.assertEqual(resp.status_code, 500)
|
|
self.assertIn('error', resp.json())
|