from __future__ import annotations import tempfile import unittest from pathlib import Path from database import ReviewDatabase from trade_journal import TradeJournalService class TradeJournalTests(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("trade_owner", "salt", "hash") self.other = self.database.create_user("trade_other", "salt", "hash") self.service = TradeJournalService(self.database) def tearDown(self) -> None: self.temp.cleanup() @staticmethod def payload(**overrides): data = { "trade_date": "2026-07-22", "code": "002141", "name": "贤丰控股", "action": "buy", "price": 10.25, "quantity": 1000, "position_pct": 20, "pnl_amount": "", "pnl_pct": "", "thesis": "修复期低吸", "execution": "按计划成交", "emotion": "calm", "tags": "计划内,低吸", } data.update(overrides) return data def test_unrealized_entry_does_not_enter_win_rate(self): self.service.save(self.owner["id"], self.payload()) result = self.service.list_entries(self.owner["id"]) self.assertEqual(result["summary"]["total"], 1) self.assertEqual(result["summary"]["realized"], 0) self.assertIsNone(result["summary"]["win_rate"]) self.assertEqual(result["items"][0]["tags"], ["计划内", "低吸"]) def test_realized_entries_build_summary(self): self.service.save( self.owner["id"], self.payload(action="sell", pnl_amount=500, pnl_pct=5) ) self.service.save( self.owner["id"], self.payload(code="600000", name="浦发银行", action="sell", pnl_amount=-200, pnl_pct=-2, position_pct=40), ) result = self.service.list_entries(self.owner["id"]) self.assertEqual(result["summary"]["realized"], 2) self.assertEqual(result["summary"]["win_rate"], 50.0) self.assertEqual(result["summary"]["pnl_amount"], 300.0) self.assertEqual(result["summary"]["average_position"], 30.0) def test_update_and_delete_require_ownership(self): trade_id = self.service.save(self.owner["id"], self.payload()) with self.assertRaises(ValueError): self.service.save( self.other["id"], self.payload(id=trade_id, thesis="越权修改") ) self.assertFalse(self.database.delete_trade_entry(self.other["id"], trade_id)) self.service.save( self.owner["id"], self.payload(id=trade_id, thesis="复盘后修订") ) self.assertEqual( self.service.list_entries(self.owner["id"])["items"][0]["thesis"], "复盘后修订", ) self.assertTrue(self.database.delete_trade_entry(self.owner["id"], trade_id)) self.assertEqual(self.service.list_entries(self.owner["id"])["summary"]["total"], 0) def test_entries_are_isolated_between_accounts(self): self.service.save(self.owner["id"], self.payload()) self.assertEqual(self.service.list_entries(self.other["id"])["items"], []) if __name__ == "__main__": unittest.main()