feat: persist heaven readings in unified dialog
This commit is contained in:
@@ -144,6 +144,19 @@ async function mockApplication(page, authSession = session()) {
|
||||
}],
|
||||
summary: { total: 1, observed: 1, t1_win_rate: 100, t5_win_rate: null, average_t5: null },
|
||||
};
|
||||
} else if (url.pathname === "/api/heaven/readings") {
|
||||
payload = {
|
||||
mode: url.searchParams.get("mode") || "fortune",
|
||||
items: [{
|
||||
id: 31,
|
||||
mode: "fortune",
|
||||
context_date: "20260723",
|
||||
subject: "2026-07-23 观气",
|
||||
subject_detail: "丙午年 · 乙未月 · 己丑日 · 土气偏显",
|
||||
answer: "三层气机已经合参,今日宜先定节奏,再看行动。",
|
||||
created_at: "2026-07-23T09:12:00+08:00",
|
||||
}],
|
||||
};
|
||||
} else if (url.pathname === "/api/mentors/setup") payload = { trade_date: "20260722", mentors: [] };
|
||||
else if (url.pathname === "/api/heaven/setup") {
|
||||
await route.fulfill({ status: 503, contentType: "application/json", body: JSON.stringify({ error: "测试环境不加载问天数据" }) });
|
||||
@@ -214,6 +227,37 @@ test("stock hover preview ignores the selected historical date", async ({ page }
|
||||
await expect(page.locator("#stockPreviewName")).toHaveText("Test Stock");
|
||||
});
|
||||
|
||||
test("saved daily fortune opens in the reading dialog without regenerating", async ({ page }) => {
|
||||
await mockApplication(page, session("user", true));
|
||||
await page.goto("/index.html");
|
||||
await page.locator('[data-view="heavenView"]').first().click();
|
||||
await page.locator('[data-heaven-panel="fortune"]').click();
|
||||
await page.evaluate(() => {
|
||||
state.heavenSetup = { field: {}, chart: { available: true } };
|
||||
state.heavenInterpretations.fortune = {
|
||||
id: 31,
|
||||
mode: "fortune",
|
||||
context_date: "20260723",
|
||||
subject: "2026-07-23 观气",
|
||||
subject_detail: "丙午年 · 乙未月 · 己丑日 · 土气偏显",
|
||||
answer: "三层气机已经合参,今日宜先定节奏,再看行动。",
|
||||
created_at: "2026-07-23T09:12:00+08:00",
|
||||
};
|
||||
updateHeavenInterpretationControls();
|
||||
});
|
||||
await expect(page.locator("#interpretFortuneButton")).toHaveText("已解运");
|
||||
const interpretRequests = [];
|
||||
page.on("request", (request) => {
|
||||
if (request.url().includes("/api/heaven/interpret")) interpretRequests.push(request.url());
|
||||
});
|
||||
await page.locator("#interpretFortuneButton").click();
|
||||
await expect(page.locator("#heavenReadingDialog")).toBeVisible();
|
||||
await expect(page.locator("#heavenReadingAnswer")).toContainText("今日宜先定节奏");
|
||||
expect(interpretRequests).toHaveLength(0);
|
||||
await page.locator('[data-heaven-reading-tab="history"]').click();
|
||||
await expect(page.locator("#heavenReadingHistoryList .heaven-reading-history-item")).toHaveCount(1);
|
||||
});
|
||||
|
||||
test("mobile shell stays within the viewport", async ({ page }) => {
|
||||
await page.setViewportSize({ width: 375, height: 812 });
|
||||
await mockApplication(page, session("user", true));
|
||||
@@ -280,6 +324,21 @@ for (const viewport of [
|
||||
const dialogWidth = await page.locator("#tradeLogDialog").evaluate((dialog) => dialog.getBoundingClientRect().width);
|
||||
expect(dialogWidth).toBeLessThanOrEqual(viewport.width);
|
||||
await page.locator("#closeTradeLogDialog").click();
|
||||
await page.evaluate(() => {
|
||||
state.heavenInterpretations.fortune = {
|
||||
id: 31,
|
||||
subject: "2026-07-23 观气",
|
||||
subject_detail: "丙午年 · 乙未月 · 己丑日",
|
||||
answer: "当日解运结果",
|
||||
context_date: "20260723",
|
||||
created_at: "2026-07-23T09:12:00+08:00",
|
||||
};
|
||||
openHeavenReading("fortune", { loading: false });
|
||||
});
|
||||
await expect(page.locator("#heavenReadingDialog")).toBeVisible();
|
||||
const readingWidth = await page.locator("#heavenReadingDialog").evaluate((dialog) => dialog.getBoundingClientRect().width);
|
||||
expect(readingWidth).toBeLessThanOrEqual(viewport.width);
|
||||
await page.locator("#closeHeavenReadingDialog").click();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ class ApiAccessPolicyTests(unittest.TestCase):
|
||||
("GET", "/api/screener/tracking"): "member",
|
||||
("GET", "/api/mentors/messages"): "member",
|
||||
("GET", "/api/heaven/setup"): "member",
|
||||
("GET", "/api/heaven/readings"): "member",
|
||||
("GET", "/api/assistant/messages"): "member",
|
||||
("POST", "/api/screener/run"): "member",
|
||||
("POST", "/api/screener/tracking/refresh"): "member",
|
||||
@@ -21,6 +22,7 @@ class ApiAccessPolicyTests(unittest.TestCase):
|
||||
("DELETE", "/api/screener/strategies/42"): "member",
|
||||
("DELETE", "/api/mentors/messages"): "member",
|
||||
("DELETE", "/api/assistant/messages"): "member",
|
||||
("DELETE", "/api/heaven/readings/42"): "member",
|
||||
}
|
||||
for (method, path), role in cases.items():
|
||||
with self.subTest(method=method, path=path):
|
||||
|
||||
@@ -99,6 +99,15 @@ class FrontendContractTests(unittest.TestCase):
|
||||
self.assertIn('elements.tradeLogDialog.showModal()', self.script)
|
||||
self.assertIn('renderTradeLog();\n closeTradeLogDialog();', self.script)
|
||||
|
||||
def test_heaven_interpretations_use_one_dialog_and_history_tabs(self):
|
||||
self.assertIn('id="heavenReadingDialog"', self.html)
|
||||
self.assertIn('data-heaven-reading-tab="current"', self.html)
|
||||
self.assertIn('data-heaven-reading-tab="history"', self.html)
|
||||
for button_id in ("historyTrendButton", "historyFortuneButton", "historyHeartButton"):
|
||||
self.assertIn(f'id="{button_id}"', self.html)
|
||||
self.assertIn('state.heavenInterpretations.fortune = payload.daily_fortune_reading || "";', self.script)
|
||||
self.assertIn('if (existing) {\n openHeavenReading(mode, { loading: false });', self.script)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
import threading
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
from database import ReviewDatabase
|
||||
from server import DashboardService
|
||||
|
||||
|
||||
class HeavenReadingTests(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.database = ReviewDatabase(Path(self.temp.name) / "review.db")
|
||||
self.owner = self.database.create_user("heaven_owner", "salt", "hash")
|
||||
self.other = self.database.create_user("heaven_other", "salt", "hash")
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.temp.cleanup()
|
||||
|
||||
def save(self, user_id: int, dedupe_key: str = "fortune:20260723") -> dict:
|
||||
return self.database.save_heaven_reading(
|
||||
user_id,
|
||||
"fortune",
|
||||
"20260723",
|
||||
"2026-07-23 观气",
|
||||
"丙午年 · 乙未月 · 己丑日",
|
||||
"当日解运结果",
|
||||
{"calendar_date": "20260723"},
|
||||
dedupe_key,
|
||||
)
|
||||
|
||||
def test_history_is_private_and_delete_requires_ownership(self):
|
||||
reading = self.save(self.owner["id"])
|
||||
self.assertEqual(
|
||||
self.database.list_heaven_readings(self.owner["id"], "fortune")[0]["answer"],
|
||||
"当日解运结果",
|
||||
)
|
||||
self.assertEqual(
|
||||
self.database.list_heaven_readings(self.other["id"], "fortune"), []
|
||||
)
|
||||
self.assertFalse(
|
||||
self.database.delete_heaven_reading(self.other["id"], reading["id"])
|
||||
)
|
||||
self.assertTrue(
|
||||
self.database.delete_heaven_reading(self.owner["id"], reading["id"])
|
||||
)
|
||||
|
||||
def test_fortune_dedupe_keeps_the_first_successful_result(self):
|
||||
first = self.save(self.owner["id"])
|
||||
second = self.database.save_heaven_reading(
|
||||
self.owner["id"],
|
||||
"fortune",
|
||||
"20260723",
|
||||
"replacement",
|
||||
"replacement",
|
||||
"不应覆盖",
|
||||
{},
|
||||
"fortune:20260723",
|
||||
)
|
||||
self.assertEqual(second["id"], first["id"])
|
||||
self.assertEqual(second["answer"], "当日解运结果")
|
||||
|
||||
def test_fortune_interpretation_reuses_saved_result_before_llm(self):
|
||||
existing = self.save(self.owner["id"])
|
||||
service = DashboardService.__new__(DashboardService)
|
||||
service.database = self.database
|
||||
service._request_context = threading.local()
|
||||
service._request_context.user_id = self.owner["id"]
|
||||
|
||||
with patch.object(service, "heaven_setup") as setup, patch.object(
|
||||
service, "_call_heaven_agent"
|
||||
) as call_agent:
|
||||
result = service.heaven_interpret(
|
||||
{"mode": "fortune", "trade_date": "2026-07-23"}
|
||||
)
|
||||
|
||||
setup.assert_not_called()
|
||||
call_agent.assert_not_called()
|
||||
self.assertTrue(result["reused"])
|
||||
self.assertEqual(result["reading"]["id"], existing["id"])
|
||||
self.assertEqual(result["answer"], "当日解运结果")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user