chore: establish stable application baseline

This commit is contained in:
leefer
2026-07-22 23:16:27 +08:00
commit ca7e752def
114 changed files with 51252 additions and 0 deletions
+209
View File
@@ -0,0 +1,209 @@
from __future__ import annotations
import tempfile
import unittest
import sqlite3
from pathlib import Path
from database import ReviewDatabase
from security import hash_password, verify_password
class AccountAccessTests(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory()
self.database = ReviewDatabase(Path(self.temp.name) / "review.db")
def tearDown(self):
self.temp.cleanup()
def test_first_user_is_admin_and_following_users_are_regular(self):
first = self.database.create_user("admin_user", "salt", "hash")
second = self.database.create_user("member_user", "salt", "hash")
self.assertEqual(first["role"], "admin")
self.assertEqual(second["role"], "user")
self.assertEqual(self.database.user_access(first["id"])["role"], "admin")
self.assertEqual(self.database.user_access(second["id"])["role"], "user")
def test_admin_role_can_also_hold_an_explicit_membership(self):
admin = self.database.create_user("admin_member", "salt", "hash")
updated = self.database.update_membership(
admin["id"],
"active",
"内部会员",
"2026-07-22T00:00:00+00:00",
"2026-08-23T00:00:00+00:00",
)
access = self.database.user_access(admin["id"])
self.assertTrue(updated)
self.assertEqual(access["role"], "admin")
self.assertEqual(access["membership_status"], "active")
def test_membership_mode_system_settings_and_usage_are_persistent(self):
user = self.database.create_user("member_user", "salt", "hash")
self.database.update_user_llm_mode(user["id"], "platform")
updated = self.database.update_membership(
user["id"],
"active",
"内部会员",
"2026-07-22T00:00:00+00:00",
"2026-08-23T00:00:00+00:00",
)
self.database.save_system_setting("credentials", "encrypted")
self.database.record_llm_usage(
user["id"], "mentor", "platform", "model", "success", 1200
)
access = self.database.user_access(user["id"])
self.assertTrue(updated)
self.assertEqual(access["llm_mode"], "platform")
self.assertEqual(access["membership_status"], "active")
self.assertEqual(access["membership_plan"], "内部会员")
self.assertEqual(self.database.get_system_setting("credentials"), "encrypted")
self.assertEqual(
self.database.count_llm_usage_since(
user["id"], "platform", "2026-01-01T00:00:00+00:00"
),
1,
)
def test_password_can_be_rotated_without_changing_account_access(self):
old_salt, old_hash = hash_password("OldPassword123")
user = self.database.create_user("password_user", old_salt, old_hash)
new_salt, new_hash = hash_password("NewPassword456")
self.assertTrue(
self.database.update_user_password(user["id"], new_salt, new_hash)
)
stored = self.database.user_password(user["id"])
self.assertFalse(
verify_password("OldPassword123", stored["password_salt"], stored["password_hash"])
)
self.assertTrue(
verify_password("NewPassword456", stored["password_salt"], stored["password_hash"])
)
self.assertEqual(self.database.user_access(user["id"])["role"], "admin")
def test_latest_real_snapshot_skips_demo_and_supports_strict_previous_date(self):
self.database.save_snapshot(
"20260720", "tushare", {"meta": {"trade_date": "2026-07-20", "source": "tushare"}}
)
self.database.save_snapshot(
"20260721", "demo", {"meta": {"trade_date": "2026-07-21", "source": "demo"}}
)
latest = self.database.get_latest_real_snapshot("20260722")
previous = self.database.get_latest_real_snapshot("20260721", strictly_before=True)
self.assertEqual(latest["meta"]["trade_date"], "2026-07-20")
self.assertEqual(previous["meta"]["trade_date"], "2026-07-20")
def test_stock_master_search_supports_exact_name_and_code(self):
self.database.upsert_stock_master(
[
{
"ts_code": "002141.SZ",
"name": "贤丰控股",
"industry": "元件",
"market": "主板",
"list_date": "20071228",
}
]
)
self.assertEqual(self.database.search_stock_master("贤丰控股")[0]["code"], "002141")
self.assertEqual(self.database.search_stock_master("002141")[0]["name"], "贤丰控股")
def test_review_notes_are_scoped_to_their_owner(self):
first = self.database.create_user("note_owner", "salt", "hash")
second = self.database.create_user("other_reader", "salt", "hash")
note_id = self.database.save_note(
first["id"], "002141", "贤丰控股", "20260721", "只属于甲", "明日观察"
)
self.assertEqual(len(self.database.list_notes(first["id"], code="002141")), 1)
self.assertEqual(self.database.list_notes(second["id"], code="002141"), [])
with self.assertRaises(ValueError):
self.database.save_note(
second["id"],
"002141",
"贤丰控股",
"20260721",
"越权修改",
"",
note_id,
)
self.assertFalse(self.database.delete_note(second["id"], note_id))
self.assertTrue(self.database.delete_note(first["id"], note_id))
def test_watchlist_is_scoped_to_its_owner(self):
first = self.database.create_user("watch_owner", "salt", "hash")
second = self.database.create_user("other_watcher", "salt", "hash")
self.database.save_watchlist(first["id"], "002141", "贤丰控股", "元件", "red")
self.database.save_watchlist(second["id"], "002141", "贤丰控股", "元件", "blue")
self.assertEqual(self.database.list_watchlist(first["id"])[0]["color"], "red")
self.assertEqual(self.database.list_watchlist(second["id"])[0]["color"], "blue")
self.assertFalse(self.database.delete_watchlist(second["id"], "000001"))
self.assertTrue(self.database.delete_watchlist(first["id"], "002141"))
self.assertEqual(self.database.list_watchlist(first["id"]), [])
self.assertEqual(len(self.database.list_watchlist(second["id"])), 1)
def test_legacy_review_notes_are_assigned_to_first_account(self):
legacy_path = Path(self.temp.name) / "legacy.db"
connection = sqlite3.connect(legacy_path)
try:
connection.executescript(
"""
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password_salt TEXT NOT NULL,
password_hash TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
INSERT INTO users
(username, password_salt, password_hash, created_at, updated_at)
VALUES ('legacy_admin', 'salt', 'hash', '2026-01-01', '2026-01-01');
CREATE TABLE review_notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT NOT NULL DEFAULT '',
stock_name TEXT NOT NULL DEFAULT '',
trade_date TEXT NOT NULL,
content TEXT NOT NULL DEFAULT '',
plan TEXT NOT NULL DEFAULT '',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
INSERT INTO review_notes
(code, stock_name, trade_date, content, plan, created_at, updated_at)
VALUES ('', '', '20260721', '旧复盘', '', '2026-07-21', '2026-07-21');
CREATE TABLE watchlist (
code TEXT PRIMARY KEY,
name TEXT NOT NULL,
sector TEXT NOT NULL DEFAULT '',
color TEXT NOT NULL DEFAULT 'red',
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
INSERT INTO watchlist
(code, name, sector, color, created_at, updated_at)
VALUES ('002141', '贤丰控股', '元件', 'red', '2026-07-21', '2026-07-21');
"""
)
connection.commit()
finally:
connection.close()
migrated = ReviewDatabase(legacy_path)
notes = migrated.list_notes(1)
self.assertEqual(len(notes), 1)
self.assertEqual(notes[0]["content"], "旧复盘")
self.assertEqual(migrated.list_watchlist(1)[0]["code"], "002141")
if __name__ == "__main__":
unittest.main()
+89
View File
@@ -0,0 +1,89 @@
from __future__ import annotations
import unittest
from heaven_engine import build_five_phase_field
class FivePhaseFrameworkTests(unittest.TestCase):
def test_public_field_uses_year_current_qi_day_contract(self):
field = build_five_phase_field("2026-06-15")
self.assertEqual(
field["framework"]["weights"],
{
"year_movement": 30,
"sitian_zaiquan": 20,
"sitian": 15,
"zaiquan": 5,
"host_qi": 20,
"guest_qi": 25,
"day": 5,
},
)
self.assertEqual(
[(layer["id"], layer["weight"]) for layer in field["framework"]["layers"]],
[("year", 50), ("current", 45), ("day", 5)],
)
self.assertEqual(sum(item["score"] for item in field["balance"]), 100)
def test_sitian_and_zaiquan_follow_half_year_dominance(self):
first_half = build_five_phase_field("2026-06-15")
second_half = build_five_phase_field("2026-08-20")
self.assertEqual(first_half["framework"]["weights"]["sitian"], 15)
self.assertEqual(first_half["framework"]["weights"]["zaiquan"], 5)
self.assertEqual(first_half["six_qi"]["ruling"], "司天")
self.assertEqual(second_half["framework"]["weights"]["sitian"], 5)
self.assertEqual(second_half["framework"]["weights"]["zaiquan"], 15)
self.assertEqual(second_half["six_qi"]["ruling"], "在泉")
def test_guest_host_relation_and_anchor_alignment_are_explicit(self):
third_qi = build_five_phase_field("2026-06-15")
final_qi = build_five_phase_field("2026-12-10")
controlled = build_five_phase_field("2025-02-10")
self.assertEqual(third_qi["framework"]["relations"]["guest_host"]["label"], "客主同气")
self.assertEqual(third_qi["six_qi"]["alignment"], "司天同位")
self.assertEqual(final_qi["framework"]["relations"]["guest_host"]["label"], "客生主")
self.assertEqual(final_qi["six_qi"]["alignment"], "在泉同位")
self.assertEqual(controlled["framework"]["relations"]["guest_host"]["order"], "客胜为从")
def test_tianfu_and_suihui_use_traditional_year_positions(self):
taiyi = build_five_phase_field("2038-06-15")
non_suihui = build_five_phase_field("2022-06-15")
self.assertEqual(
taiyi["framework"]["relations"]["annual_pattern"]["primary"],
"太乙天符",
)
self.assertFalse(
non_suihui["framework"]["relations"]["annual_pattern"]["is_suihui"]
)
def test_public_field_has_no_observation_hour(self):
field = build_five_phase_field("2026-07-18")
self.assertNotIn("time", field["pillars"])
self.assertNotIn("observation_time", field)
def test_sector_catalog_lists_all_rules_and_applies_manual_overrides(self):
field = build_five_phase_field(
"2026-07-18",
{"电力": "", "低空经济": ""},
)
groups = {item["element"]: item["industries"] for item in field["sector_catalog"]}
names = {
element: {item["name"]: item["classification_source"] for item in items}
for element, items in groups.items()
}
self.assertEqual(set(groups), {"", "", "", "", ""})
self.assertNotIn("电力", names[""])
self.assertEqual(names[""]["电力"], "manual")
self.assertEqual(names[""]["低空经济"], "manual")
self.assertEqual(sum(len(items) for items in groups.values()), 144)
if __name__ == "__main__":
unittest.main()
+96
View File
@@ -0,0 +1,96 @@
from __future__ import annotations
import unittest
from pathlib import Path
from server import DashboardService
class SearchDatabaseStub:
def __init__(self) -> None:
self.directory = {
"schema_version": 2,
"items": [
{
"id": "881107.TI",
"code": "881107.TI",
"name": "油气开采及服务",
"type": "sector",
"subtitle": "行业板块",
"member_count": 19,
},
{
"id": "885728.TI",
"code": "885728.TI",
"name": "人工智能",
"type": "theme",
"subtitle": "概念题材",
"member_count": 1079,
},
],
}
def get_data_snapshot(self, kind: str, cache_key: str):
if (kind, cache_key) == ("search_directory", "ths"):
return self.directory
return None
@staticmethod
def search_stock_master(query: str, limit: int = 12):
if query in {"002141", "贤丰控股"}:
return [
{
"ts_code": "002141.SZ",
"code": "002141",
"name": "贤丰控股",
"industry": "元件",
"market": "主板",
"list_date": "20071228",
}
]
return []
class GlobalSearchTests(unittest.TestCase):
def setUp(self) -> None:
self.service = DashboardService.__new__(DashboardService)
self.service.database = SearchDatabaseStub()
self.service._system_credentials = {"tushare_token": ""}
def test_search_groups_stock_sector_theme_and_index(self):
stock = self.service.search_entities("002141", "2026-07-22")
sector = self.service.search_entities("油气", "2026-07-22")
theme = self.service.search_entities("人工智能", "2026-07-22")
index = self.service.search_entities("上证指数", "2026-07-22")
self.assertEqual(stock["groups"]["stocks"][0]["name"], "贤丰控股")
self.assertEqual(stock["groups"]["stocks"][0]["industry"], "元件")
self.assertEqual(sector["groups"]["sectors"][0]["code"], "881107.TI")
self.assertEqual(theme["groups"]["themes"][0]["code"], "885728.TI")
self.assertEqual(index["groups"]["indices"][0]["code"], "000001.SH")
def test_empty_query_returns_all_groups_without_remote_lookup(self):
result = self.service.search_entities("", "2026-07-22")
self.assertEqual(
result["groups"],
{"stocks": [], "sectors": [], "themes": [], "indices": []},
)
def test_frontend_reuses_full_stock_detail_and_renders_market_daily_k(self):
static_dir = Path(__file__).resolve().parents[1] / "static"
html = (static_dir / "index.html").read_text(encoding="utf-8")
script = (static_dir / "app.js").read_text(encoding="utf-8")
self.assertIn('id="globalSearchButton"', html)
self.assertIn('id="globalSearchDialog"', html)
self.assertIn('id="entityDetailDialog"', html)
self.assertIn("日 K 与成交量", html)
self.assertIn('event.key.toLowerCase() !== "k"', script)
self.assertIn('openStock(item.id, { code: item.code', script)
self.assertNotIn('include_notes', script)
self.assertIn('const candles = (series || [])', script)
self.assertIn('renderStockNotes(payload.notes || [])', script)
if __name__ == "__main__":
unittest.main()
+309
View File
@@ -0,0 +1,309 @@
from __future__ import annotations
import http.client
import json
import unittest
from unittest.mock import MagicMock, patch
from heaven_engine import _market_line_scores, build_manual_market_hexagram
from realtime_aggregator import WebRealtimeAggregator
from server import DashboardService
from tushare_client import TushareClient
class HeavenMarketLineTests(unittest.TestCase):
def test_index_external_uses_only_current_index_change(self):
dashboard = {
"overview": {
"up_count": 1740,
"down_count": 3710,
"amount_billion": 27180.8,
"sentiment_score": 27,
"seal_rate": 57.3,
"limit_up_count": 55,
"limit_down_count": 267,
},
"sectors": [],
"sector_rotation": [],
}
index_context = {
"aggregate": {
"average_pct_chg": 0.187,
"average_return_5d": -5.606,
}
}
scores = _market_line_scores(
dashboard,
[{"amount_billion": 25000}],
index_context,
{},
{},
[],
)
self.assertAlmostEqual(scores[5]["score"], 0.187 / 3)
self.assertGreater(scores[5]["score"], 0)
self.assertIn("不参与外显阴阳", scores[5]["evidence"][1])
def test_manual_calibration_preserves_six_lines_and_marks_user_evidence(self):
chart = build_manual_market_hexagram(
[8, 7, 6, 9, 8, 7],
"20260722",
{"name": "元件", "taxonomy": "sw_l2"},
{"code": "002141", "name": "贤丰控股"},
{},
"人工核对",
)
self.assertTrue(chart["manual_calibration"])
self.assertEqual([line["value"] for line in chart["hexagram"]["lines"]], [8, 7, 6, 9, 8, 7])
self.assertEqual(chart["hexagram"]["moving_lines"], [3, 4])
self.assertIn("用户手动校准", chart["hexagram"]["lines"][0]["evidence"][0])
def test_quantitative_supplement_repairs_only_failed_lines_and_recalculates(self):
trade_date = "20260722"
dashboard = {
"overview": {
"sentiment_score": 32,
"seal_rate": 48,
"amount_billion": 16500,
"up_count": 1800,
"down_count": 3500,
"limit_up_count": 35,
"limit_down_count": 192,
},
"limits": [{"amount_billion": 12}, {"amount_billion": 25}],
"sectors": [],
"sector_rotation": [],
}
history = [{"amount_billion": 16000}, {"amount_billion": 15800}]
index_context = {
"trade_date": trade_date,
"source": "tushare",
"realtime": False,
"precise": False,
"indices": [
{"ts_code": code, "trade_date": "20260721", "pct_chg": 0.1}
for code in ("000001.SH", "399001.SZ", "399006.SZ")
],
}
sector = {"taxonomy": "sw_l2", "precise": False, "error": "行业日线尚未返回"}
stock = {
"code": "002141", "name": "贤丰控股", "trade_date": trade_date,
"data_source": "tushare", "realtime": False, "precise": True,
"amount_billion": 20, "turnover_rate": 8.5, "seal_amount_million": 0,
"open_times": 0, "change": 2.4, "streak": 0, "status": "普通",
}
automatic = DashboardService._heaven_line_checks(
trade_date, dashboard, history, index_context, sector, stock, "closed", {}
)
self.assertEqual(
[item["line"] for item in automatic if not item["passed"]], [3, 4, 6]
)
manual = {
"sector_name": "元件", "sector_up_count": 18, "sector_down_count": 42,
"sector_coverage": 96, "sector_member_equal_change": -2.2,
"sector_change": -2.6, "sector_leading_pct": 3.1,
"index_sh_change": -0.9, "index_sz_change": -1.4, "index_cy_change": -1.8,
}
merged = DashboardService._apply_heaven_manual_data(
dashboard, index_context, sector, stock, manual, "closed", trade_date, "002141"
)
repaired = DashboardService._heaven_line_checks(
trade_date, merged[0], history, merged[1], merged[2], merged[3], "closed", manual
)
self.assertTrue(all(item["passed"] for item in repaired))
self.assertEqual(
[item["line"] for item in repaired if item["status"] == "manual"], [3, 4, 6]
)
self.assertEqual(repaired[5]["line_value"], 8)
self.assertAlmostEqual(repaired[5]["score"], (-0.9 - 1.4 - 1.8) / 3 / 3, places=3)
def test_sector_inner_and_outer_have_independent_quality_gates(self):
trade_date = "20260722"
dashboard = {
"overview": {
"sentiment_score": 30, "seal_rate": 50, "amount_billion": 15000,
"up_count": 2000, "down_count": 3000,
"limit_up_count": 40, "limit_down_count": 80,
},
"limits": [], "sectors": [], "sector_rotation": [],
}
history = [{"amount_billion": 14800}, {"amount_billion": 14900}]
indices = {
"trade_date": trade_date, "source": "tushare", "realtime": False,
"precise": True,
"indices": [
{"ts_code": code, "trade_date": trade_date, "pct_chg": -1}
for code in ("000001.SH", "399001.SZ", "399006.SZ")
],
"aggregate": {"average_pct_chg": -1},
}
sector = {
"name": "元件", "code": "801083.SI", "taxonomy": "sw_l2",
"trade_date": trade_date, "realtime": False, "finalized": True,
"inner_precise": True, "outer_precise": False, "precise": False,
"coverage": 98, "up_count": 19, "down_count": 46,
"member_equal_change": -2.15, "leading_pct": 5.2,
"outer_error": "申万日线尚未发布", "source": "tushare_member_daily",
}
stock = {
"code": "002141", "trade_date": trade_date, "precise": True,
"realtime": False, "data_source": "tushare", "amount_billion": 10,
"turnover_rate": 5, "seal_amount_million": 0, "open_times": 0,
"change": 2, "streak": 0, "status": "普通",
}
checks = DashboardService._heaven_line_checks(
trade_date, dashboard, history, indices, sector, stock, "closed", {}
)
self.assertTrue(checks[2]["passed"])
self.assertFalse(checks[3]["passed"])
self.assertIn("申万日线尚未发布", checks[3]["reasons"])
manual = {"sector_change": -3.85}
merged = DashboardService._apply_heaven_manual_data(
dashboard, indices, sector, stock, manual, "closed", trade_date, "002141"
)
repaired = DashboardService._heaven_line_checks(
trade_date, merged[0], history, merged[1], merged[2], merged[3], "closed", manual
)
self.assertTrue(repaired[3]["passed"])
self.assertEqual(repaired[3]["status"], "manual")
self.assertEqual(
[field["key"] for field in repaired[3]["fields"] if field["manual"]],
["sector_change"],
)
missing_leader_sector = {**merged[2], "leading_pct": None}
still_blocked = DashboardService._heaven_line_checks(
trade_date,
merged[0],
history,
merged[1],
missing_leader_sector,
merged[3],
"closed",
manual,
)
self.assertFalse(still_blocked[3]["passed"])
self.assertIn("需补充:行业领涨股涨跌幅", still_blocked[3]["reasons"])
class ShenwanMembershipTests(unittest.TestCase):
@patch.object(TushareClient, "query")
def test_latest_effective_membership_wins_over_stale_is_new_row(self, query: MagicMock):
stale_y = {
"l1_code": "801010.SI", "l1_name": "农林牧渔",
"l2_code": "801018.SI", "l2_name": "动物保健Ⅱ",
"l3_code": "850181.SI", "l3_name": "动物保健Ⅲ",
"ts_code": "002141.SZ", "in_date": "20240730", "out_date": None, "is_new": "Y",
}
current_y = {
"l1_code": "801080.SI", "l1_name": "电子",
"l2_code": "801083.SI", "l2_name": "元件",
"l3_code": "850822.SI", "l3_name": "印制电路板",
"ts_code": "002141.SZ", "in_date": "20260701", "out_date": None, "is_new": "Y",
}
closed_n = {**stale_y, "out_date": "20260630", "is_new": "N"}
query.side_effect = lambda _api, params, _fields: (
[stale_y, current_y] if params["is_new"] == "Y" else [closed_n]
)
industry = TushareClient("token").sw_stock_industry("002141.SZ", "20260722")
self.assertEqual(industry["l2_code"], "801083.SI")
self.assertEqual(industry["l2_name"], "元件")
class RealtimeAggregatorTests(unittest.TestCase):
def setUp(self):
WebRealtimeAggregator._response_cache.clear()
@staticmethod
def _response(payload: dict) -> MagicMock:
response = MagicMock()
response.headers.get.return_value = "application/json"
response.read.return_value = json.dumps(payload).encode("utf-8")
context = MagicMock()
context.__enter__.return_value = response
return context
@patch("realtime_aggregator.urllib.request.urlopen")
def test_transport_failure_is_retried(self, urlopen: MagicMock):
urlopen.side_effect = [
http.client.RemoteDisconnected("temporary disconnect"),
self._response({"rc": 0, "data": {"diff": []}}),
]
aggregator = WebRealtimeAggregator(retry_delay_seconds=0)
payload = aggregator._get_json("https://example.test", {}, "https://example.test")
self.assertEqual(payload["rc"], 0)
self.assertEqual(urlopen.call_count, 2)
@patch("realtime_aggregator.urllib.request.urlopen")
def test_recent_success_is_used_after_retries_fail(self, urlopen: MagicMock):
aggregator = WebRealtimeAggregator(retry_delay_seconds=0)
urlopen.return_value = self._response({"rc": 0, "data": {"diff": []}})
aggregator._get_json("https://example.test", {}, "https://example.test")
urlopen.side_effect = http.client.RemoteDisconnected("temporary disconnect")
payload = aggregator._get_json("https://example.test", {}, "https://example.test")
self.assertIn("_aggregate_cache", payload)
self.assertEqual(urlopen.call_count, 4)
@patch.object(WebRealtimeAggregator, "_get_text")
def test_tencent_indices_include_verifiable_quote_times(self, get_text: MagicMock):
def quote_line(
symbol: str,
name: str,
code: str,
price: str,
previous_close: str,
quote_time: str,
change_amount: str,
change: str,
amount: str,
) -> str:
fields = [""] * 38
fields[1] = name
fields[2] = code
fields[3] = price
fields[4] = previous_close
fields[5] = price
fields[30] = quote_time
fields[31] = change_amount
fields[32] = change
fields[33] = price
fields[34] = price
fields[37] = amount
return f'v_{symbol}="{"~".join(fields)}";'
get_text.return_value = (
'\n'.join(
[
quote_line("sh000001", "上证指数", "000001", "3796.28", "3764.15", "20260720155402", "32.13", "0.85", "129465190"),
quote_line("sz399001", "深证成指", "399001", "13610.23", "13706.88", "20260720155330", "-96.65", "-0.71", "140747525"),
quote_line("sz399006", "创业板指", "399006", "3443.10", "3428.63", "20260720155345", "14.47", "0.42", "67120487"),
]
),
0,
)
rows = WebRealtimeAggregator().tencent_indices()
self.assertEqual(len(rows), 3)
self.assertEqual(rows[0]["source"], "tencent_qt")
self.assertEqual(rows[0]["quote_time"][:10], "2026-07-20")
self.assertAlmostEqual(rows[0]["amount_billion"], 12946.52)
if __name__ == "__main__":
unittest.main()
+222
View File
@@ -0,0 +1,222 @@
from __future__ import annotations
import ast
import unittest
from datetime import datetime, timedelta, timezone
from pathlib import Path
def load_method(name: str):
source = Path("server.py").read_text(encoding="utf-8")
tree = ast.parse(source)
dashboard_service = next(
node for node in tree.body
if isinstance(node, ast.ClassDef) and node.name == "DashboardService"
)
method = next(
node for node in dashboard_service.body
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))
and node.name == name
)
module = ast.Module(body=[method], type_ignores=[])
namespace = {"datetime": datetime, "Any": object}
exec(compile(ast.fix_missing_locations(module), "server.py", "exec"), namespace)
return namespace[name]
MARKET_MODE = load_method("_heaven_market_mode")
QUALITY_ISSUES = load_method("_heaven_trend_quality_issues")
TZ = timezone(timedelta(hours=8))
class MarketModeTests(unittest.TestCase):
def test_closed_rt_snapshot_is_closed_not_intraday(self):
dashboard = {"meta": {"realtime": True, "market_status": "closed"}}
now = datetime(2026, 7, 20, 16, 27, tzinfo=TZ)
self.assertEqual(MARKET_MODE("20260720", dashboard, now), "closed")
def test_trading_snapshot_is_intraday(self):
dashboard = {"meta": {"realtime": True, "market_status": "trading"}}
now = datetime(2026, 7, 20, 10, 30, tzinfo=TZ)
self.assertEqual(MARKET_MODE("20260720", dashboard, now), "intraday")
def test_historical_date_is_always_historical(self):
dashboard = {"meta": {"realtime": True, "market_status": "trading"}}
now = datetime(2026, 7, 20, 10, 30, tzinfo=TZ)
self.assertEqual(MARKET_MODE("20260717", dashboard, now), "historical")
@staticmethod
def intraday_layers(trade_date: str):
index_context = {
"trade_date": trade_date,
"precise": True,
"realtime": True,
"indices": [{"trade_date": trade_date}] * 3,
}
sector = {
"trade_date": trade_date,
"precise": True,
"realtime": True,
"taxonomy": "sw_l2",
"schema_version": 3,
"coverage": 100,
"relative_turnover": 1.2,
}
stock = {
"trade_date": trade_date,
"code": "002141",
"precise": True,
"realtime": True,
"turnover_source": "float_share",
"activity_source": "historical_progress",
}
return index_context, sector, stock
@staticmethod
def historical_layers(trade_date: str):
index_context = {
"trade_date": trade_date,
"precise": True,
"realtime": False,
"source": "tushare",
"indices": [{"trade_date": trade_date}] * 3,
}
sector = {
"trade_date": trade_date,
"precise": True,
"realtime": False,
"taxonomy": "sw_l2",
"schema_version": 3,
"source": "tushare_sw_daily+member_daily",
"coverage": 97,
}
stock = {
"trade_date": trade_date,
"code": "002141",
"precise": True,
"realtime": False,
"data_source": "tushare",
}
return index_context, sector, stock
def test_intraday_accepts_verified_realtime_layers(self):
trade_date = "20260720"
index_context, sector, stock = self.intraday_layers(trade_date)
dashboard = {
"meta": {
"realtime": True,
"market_status": "closed",
"updated_at": datetime.now(TZ).isoformat(),
}
}
issues = QUALITY_ISSUES(
trade_date, dashboard, index_context, sector, stock, "intraday"
)
self.assertEqual(issues, [])
def test_intraday_sector_coverage_90_passes_89_blocks(self):
trade_date = "20260720"
index_context, sector, stock = self.intraday_layers(trade_date)
dashboard = {"meta": {"realtime": True, "market_status": "closed"}}
sector["coverage"] = 90
self.assertEqual(
QUALITY_ISSUES(trade_date, dashboard, index_context, sector, stock, "intraday"),
[],
)
sector["coverage"] = 89
issues = QUALITY_ISSUES(
trade_date, dashboard, index_context, sector, stock, "intraday"
)
self.assertTrue(any("覆盖率" in issue for issue in issues))
def test_intraday_sector_requires_relative_turnover(self):
trade_date = "20260720"
index_context, sector, stock = self.intraday_layers(trade_date)
sector["relative_turnover"] = 0
issues = QUALITY_ISSUES(
trade_date, {"meta": {}}, index_context, sector, stock, "intraday"
)
self.assertTrue(any("相对全市场换手" in issue for issue in issues))
def test_sector_must_use_shenwan_l2_taxonomy(self):
trade_date = "20260720"
index_context, sector, stock = self.intraday_layers(trade_date)
sector["taxonomy"] = "ths"
issues = QUALITY_ISSUES(
trade_date, {"meta": {}}, index_context, sector, stock, "intraday"
)
self.assertTrue(any("申万二级" in issue for issue in issues))
def test_historical_accepts_official_daily_layers(self):
trade_date = "20260717"
index_context, sector, stock = self.historical_layers(trade_date)
issues = QUALITY_ISSUES(
trade_date, {"meta": {}}, index_context, sector, stock, "historical"
)
self.assertEqual(issues, [])
def test_historical_rejects_realtime_index_layer(self):
trade_date = "20260717"
index_context, sector, stock = self.historical_layers(trade_date)
index_context["realtime"] = True
issues = QUALITY_ISSUES(
trade_date, {"meta": {}}, index_context, sector, stock, "historical"
)
self.assertTrue(any("指数层" in issue for issue in issues))
def test_historical_rejects_realtime_sector_layer(self):
trade_date = "20260717"
index_context, sector, stock = self.historical_layers(trade_date)
sector["realtime"] = True
issues = QUALITY_ISSUES(
trade_date, {"meta": {}}, index_context, sector, stock, "historical"
)
self.assertTrue(any("行业层" in issue for issue in issues))
def test_historical_rejects_non_tushare_stock(self):
trade_date = "20260717"
index_context, sector, stock = self.historical_layers(trade_date)
stock["data_source"] = "dashboard"
issues = QUALITY_ISSUES(
trade_date, {"meta": {}}, index_context, sector, stock, "historical"
)
self.assertTrue(any("个股层" in issue for issue in issues))
def test_stock_must_be_precise(self):
trade_date = "20260717"
index_context, sector, stock = self.historical_layers(trade_date)
stock["precise"] = False
issues = QUALITY_ISSUES(
trade_date, {"meta": {}}, index_context, sector, stock, "historical"
)
self.assertTrue(any("个股层" in issue for issue in issues))
def test_closed_mode_ignores_nonessential_dashboard_status(self):
trade_date = "20260720"
index_context, sector, stock = self.historical_layers(trade_date)
dashboard = {"meta": {"realtime": True, "market_status": "trading"}}
issues = QUALITY_ISSUES(
trade_date, dashboard, index_context, sector, stock, "closed"
)
self.assertEqual(issues, [])
def test_closed_mode_accepts_finalized_realtime_shenwan_snapshot(self):
trade_date = "20260720"
index_context, sector, stock = self.historical_layers(trade_date)
sector.update({
"realtime": True,
"finalized": True,
"inner_precise": True,
"outer_precise": True,
"relative_turnover": 1.2,
})
issues = QUALITY_ISSUES(
trade_date, {"meta": {}}, index_context, sector, stock, "closed"
)
self.assertEqual(issues, [])
if __name__ == "__main__":
unittest.main()
+116
View File
@@ -0,0 +1,116 @@
from __future__ import annotations
import unittest
from tushare_client import TushareClient
class FakeRealtimeClient(TushareClient):
def query(self, api_name, params=None, fields=""):
params = params or {}
if api_name == "trade_cal":
return [
{
"cal_date": "20260720",
"is_open": 1,
"pretrade_date": "20260717",
}
]
if api_name == "stock_basic":
return [
{"ts_code": "000001.SZ", "name": "", "industry": "银行"},
{"ts_code": "000002.SZ", "name": "", "industry": "地产"},
{"ts_code": "000003.SZ", "name": "", "industry": "元器件"},
]
if api_name == "stk_limit":
return [
{"ts_code": "000001.SZ", "up_limit": 11.0, "down_limit": 9.0},
{"ts_code": "000002.SZ", "up_limit": 22.0, "down_limit": 18.0},
{"ts_code": "000003.SZ", "up_limit": 33.0, "down_limit": 27.0},
]
if api_name == "daily_basic":
requested_code = str(params.get("ts_code") or "")
rows = [
{"ts_code": "000001.SZ", "trade_date": "20260717", "float_share": 1000},
{"ts_code": "000002.SZ", "trade_date": "20260717", "float_share": 2000},
{"ts_code": "000003.SZ", "trade_date": "20260717", "float_share": 3000},
]
return [row for row in rows if not requested_code or row["ts_code"] == requested_code]
if api_name == "daily":
return [
{"ts_code": params.get("ts_code"), "trade_date": "20260713", "vol": 1000, "amount": 1},
{"ts_code": params.get("ts_code"), "trade_date": "20260714", "vol": 1000, "amount": 1},
{"ts_code": params.get("ts_code"), "trade_date": "20260715", "vol": 1000, "amount": 1},
{"ts_code": params.get("ts_code"), "trade_date": "20260716", "vol": 1000, "amount": 1},
{"ts_code": params.get("ts_code"), "trade_date": "20260717", "vol": 1000, "amount": 1},
]
if api_name == "limit_list_d":
return [
{
"ts_code": "000001.SZ",
"name": "",
"industry": "银行",
"close": 10.0,
"pct_chg": 10.0,
"amount": 100000000,
"limit_times": 2,
}
]
if api_name == "rt_k":
rows = [
{
"ts_code": "000001.SZ", "name": "", "pre_close": 10.0,
"open": 10.1, "high": 11.0, "low": 10.0, "close": 11.0,
"vol": 1000, "amount": 100000000, "num": 10,
},
{
"ts_code": "000002.SZ", "name": "", "pre_close": 20.0,
"open": 19.5, "high": 20.0, "low": 18.0, "close": 18.0,
"vol": 2000, "amount": 200000000, "num": 20,
},
{
"ts_code": "000003.SZ", "name": "", "pre_close": 30.0,
"open": 31.0, "high": 33.0, "low": 30.0, "close": 32.0,
"vol": 3000, "amount": 300000000, "num": 30,
},
]
requested = {
code for code in str(params.get("ts_code") or "").split(",") if code
}
return [row for row in rows if row["ts_code"] in requested]
raise AssertionError(f"Unexpected API call: {api_name} {params}")
class RealtimeDashboardTests(unittest.TestCase):
def setUp(self):
TushareClient._realtime_reference_cache.clear()
TushareClient._capital_cache.clear()
TushareClient._latest_realtime_market.clear()
TushareClient._stock_activity_cache.clear()
self.client = FakeRealtimeClient("test-token")
def test_realtime_dashboard_classifies_pools_and_units(self):
dashboard = self.client._realtime_dashboard("20260720", "20260720", "20260717")
self.assertTrue(dashboard["meta"]["realtime"])
self.assertEqual(dashboard["meta"]["quote_count"], 3)
self.assertEqual(dashboard["overview"]["limit_up_count"], 1)
self.assertEqual(dashboard["overview"]["limit_down_count"], 1)
self.assertEqual(dashboard["overview"]["broken_count"], 1)
self.assertEqual(dashboard["overview"]["amount_billion"], 6.0)
self.assertEqual(dashboard["limits"][0]["streak"], 3)
self.assertEqual(dashboard["limits"][0]["amount_billion"], 1.0)
def test_realtime_stock_quote_uses_cached_industry(self):
self.client._load_realtime_reference("20260720", "20260717")
quote = self.client.realtime_stock_quote("000003.SZ")
self.assertEqual(quote["name"], "")
self.assertEqual(quote["sector"], "元器件")
self.assertAlmostEqual(quote["change"], 6.6667)
self.assertEqual(quote["amount_billion"], 3.0)
self.assertAlmostEqual(quote["turnover_rate"], 0.01)
if __name__ == "__main__":
unittest.main()