lib-x17-fintech 2.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. lib_x17_fintech-2.1.3.dist-info/METADATA +633 -0
  2. lib_x17_fintech-2.1.3.dist-info/RECORD +282 -0
  3. lib_x17_fintech-2.1.3.dist-info/WHEEL +5 -0
  4. lib_x17_fintech-2.1.3.dist-info/licenses/LICENSE +1 -0
  5. lib_x17_fintech-2.1.3.dist-info/top_level.txt +1 -0
  6. xfintech/__init__.py +0 -0
  7. xfintech/connect/__init__.py +18 -0
  8. xfintech/connect/artifact/__init__.py +5 -0
  9. xfintech/connect/artifact/artifact.py +168 -0
  10. xfintech/connect/artifact/tests/__init__.py +3 -0
  11. xfintech/connect/artifact/tests/test_class_artifact_all.py +564 -0
  12. xfintech/connect/common/__init__.py +12 -0
  13. xfintech/connect/common/connect.py +49 -0
  14. xfintech/connect/common/connectref.py +119 -0
  15. xfintech/connect/common/error.py +62 -0
  16. xfintech/connect/common/tests/__init__.py +1 -0
  17. xfintech/connect/common/tests/test_class_connectlike_all.py +544 -0
  18. xfintech/connect/common/tests/test_class_connectref_all.py +586 -0
  19. xfintech/connect/common/tests/test_class_errors_all.py +524 -0
  20. xfintech/connect/instance/__init__.py +7 -0
  21. xfintech/connect/instance/macos.py +121 -0
  22. xfintech/connect/instance/s3.py +176 -0
  23. xfintech/connect/instance/tests/__init__.py +1 -0
  24. xfintech/connect/instance/tests/test_class_macosconnect_all.py +692 -0
  25. xfintech/connect/instance/tests/test_class_s3connect_all.py +603 -0
  26. xfintech/data/__init__.py +20 -0
  27. xfintech/data/common/__init__.py +15 -0
  28. xfintech/data/common/cache.py +186 -0
  29. xfintech/data/common/coolant.py +171 -0
  30. xfintech/data/common/metric.py +138 -0
  31. xfintech/data/common/paginate.py +132 -0
  32. xfintech/data/common/params.py +162 -0
  33. xfintech/data/common/retry.py +201 -0
  34. xfintech/data/common/tests/__init__.py +1 -0
  35. xfintech/data/common/tests/test_class_cache_all.py +681 -0
  36. xfintech/data/common/tests/test_class_coolant_all.py +534 -0
  37. xfintech/data/common/tests/test_class_metric_all.py +705 -0
  38. xfintech/data/common/tests/test_class_paginate_all.py +508 -0
  39. xfintech/data/common/tests/test_class_params_all.py +891 -0
  40. xfintech/data/common/tests/test_class_retry_all.py +714 -0
  41. xfintech/data/job/__init__.py +17 -0
  42. xfintech/data/job/errors.py +112 -0
  43. xfintech/data/job/house.py +156 -0
  44. xfintech/data/job/job.py +247 -0
  45. xfintech/data/job/joblike.py +47 -0
  46. xfintech/data/job/tests/__init__.py +1 -0
  47. xfintech/data/job/tests/test_class_errors_all.py +275 -0
  48. xfintech/data/job/tests/test_class_house_all.py +801 -0
  49. xfintech/data/job/tests/test_class_job_all.py +684 -0
  50. xfintech/data/job/tests/test_class_joblike_all.py +482 -0
  51. xfintech/data/relay/__init__.py +7 -0
  52. xfintech/data/relay/client.py +114 -0
  53. xfintech/data/relay/clientlike.py +45 -0
  54. xfintech/data/relay/tests/test_class_relayclient_all.py +484 -0
  55. xfintech/data/relay/tests/test_class_relayclientlike_all.py +500 -0
  56. xfintech/data/source/__init__.py +7 -0
  57. xfintech/data/source/baostock/__init__.py +21 -0
  58. xfintech/data/source/baostock/job/__init__.py +5 -0
  59. xfintech/data/source/baostock/job/job.py +217 -0
  60. xfintech/data/source/baostock/job/tests/__init__.py +0 -0
  61. xfintech/data/source/baostock/job/tests/test_class_baostockjob_all.py +547 -0
  62. xfintech/data/source/baostock/session/__init__.py +8 -0
  63. xfintech/data/source/baostock/session/relay.py +223 -0
  64. xfintech/data/source/baostock/session/session.py +241 -0
  65. xfintech/data/source/baostock/session/tests/__init__.py +0 -0
  66. xfintech/data/source/baostock/session/tests/test_class_relay_all.py +694 -0
  67. xfintech/data/source/baostock/session/tests/test_class_session_all.py +505 -0
  68. xfintech/data/source/baostock/stock/__init__.py +0 -0
  69. xfintech/data/source/baostock/stock/hs300stock/__init__.py +3 -0
  70. xfintech/data/source/baostock/stock/hs300stock/constant.py +49 -0
  71. xfintech/data/source/baostock/stock/hs300stock/hs300stock.py +133 -0
  72. xfintech/data/source/baostock/stock/hs300stock/tests/__init__.py +1 -0
  73. xfintech/data/source/baostock/stock/hs300stock/tests/test_class_hs300index_all.py +413 -0
  74. xfintech/data/source/baostock/stock/minuteline/__init__.py +19 -0
  75. xfintech/data/source/baostock/stock/minuteline/constant.py +89 -0
  76. xfintech/data/source/baostock/stock/minuteline/minuteline.py +163 -0
  77. xfintech/data/source/baostock/stock/minuteline/tests/__init__.py +0 -0
  78. xfintech/data/source/baostock/stock/minuteline/tests/test_class_minuteline_all.py +582 -0
  79. xfintech/data/source/baostock/stock/stock/__init__.py +19 -0
  80. xfintech/data/source/baostock/stock/stock/constant.py +55 -0
  81. xfintech/data/source/baostock/stock/stock/stock.py +149 -0
  82. xfintech/data/source/baostock/stock/stock/tests/__init__.py +0 -0
  83. xfintech/data/source/baostock/stock/stock/tests/test_class_stock_all.py +508 -0
  84. xfintech/data/source/baostock/stock/stockinfo/__init__.py +5 -0
  85. xfintech/data/source/baostock/stock/stockinfo/constant.py +66 -0
  86. xfintech/data/source/baostock/stock/stockinfo/stockinfo.py +176 -0
  87. xfintech/data/source/baostock/stock/stockinfo/tests/__init__.py +1 -0
  88. xfintech/data/source/baostock/stock/stockinfo/tests/test_class_stockinfo_all.py +617 -0
  89. xfintech/data/source/baostock/stock/sz50stock/__init__.py +3 -0
  90. xfintech/data/source/baostock/stock/sz50stock/constant.py +49 -0
  91. xfintech/data/source/baostock/stock/sz50stock/sz50stock.py +133 -0
  92. xfintech/data/source/baostock/stock/sz50stock/tests/__init__.py +1 -0
  93. xfintech/data/source/baostock/stock/sz50stock/tests/test_class_sz50stock_all.py +397 -0
  94. xfintech/data/source/baostock/stock/tradedate/__init__.py +19 -0
  95. xfintech/data/source/baostock/stock/tradedate/constant.py +72 -0
  96. xfintech/data/source/baostock/stock/tradedate/tests/__init__.py +0 -0
  97. xfintech/data/source/baostock/stock/tradedate/tests/test_class_tradedate_all.py +695 -0
  98. xfintech/data/source/baostock/stock/tradedate/tradedate.py +208 -0
  99. xfintech/data/source/baostock/stock/zz500stock/__init__.py +3 -0
  100. xfintech/data/source/baostock/stock/zz500stock/constant.py +55 -0
  101. xfintech/data/source/baostock/stock/zz500stock/tests/__init__.py +1 -0
  102. xfintech/data/source/baostock/stock/zz500stock/tests/test_class_zz500stock_all.py +421 -0
  103. xfintech/data/source/baostock/stock/zz500stock/zz500stock.py +133 -0
  104. xfintech/data/source/tushare/__init__.py +61 -0
  105. xfintech/data/source/tushare/job/__init__.py +5 -0
  106. xfintech/data/source/tushare/job/job.py +257 -0
  107. xfintech/data/source/tushare/job/tests/test_class_tusharejob_all.py +589 -0
  108. xfintech/data/source/tushare/session/__init__.py +5 -0
  109. xfintech/data/source/tushare/session/relay.py +231 -0
  110. xfintech/data/source/tushare/session/session.py +239 -0
  111. xfintech/data/source/tushare/session/tests/test_class_relay_all.py +719 -0
  112. xfintech/data/source/tushare/session/tests/test_class_session_all.py +705 -0
  113. xfintech/data/source/tushare/stock/__init__.py +55 -0
  114. xfintech/data/source/tushare/stock/adjfactor/__init__.py +19 -0
  115. xfintech/data/source/tushare/stock/adjfactor/adjfactor.py +150 -0
  116. xfintech/data/source/tushare/stock/adjfactor/constant.py +71 -0
  117. xfintech/data/source/tushare/stock/adjfactor/tests/__init__.py +0 -0
  118. xfintech/data/source/tushare/stock/adjfactor/tests/test_class_adjfactor_all.py +372 -0
  119. xfintech/data/source/tushare/stock/capflow/__init__.py +19 -0
  120. xfintech/data/source/tushare/stock/capflow/capflow.py +171 -0
  121. xfintech/data/source/tushare/stock/capflow/constant.py +105 -0
  122. xfintech/data/source/tushare/stock/capflow/tests/__init__.py +0 -0
  123. xfintech/data/source/tushare/stock/capflow/tests/test_class_capflow_all.py +589 -0
  124. xfintech/data/source/tushare/stock/capflowdc/__init__.py +19 -0
  125. xfintech/data/source/tushare/stock/capflowdc/capflowdc.py +167 -0
  126. xfintech/data/source/tushare/stock/capflowdc/constant.py +95 -0
  127. xfintech/data/source/tushare/stock/capflowdc/tests/__init__.py +0 -0
  128. xfintech/data/source/tushare/stock/capflowdc/tests/test_class_capflowdc_all.py +814 -0
  129. xfintech/data/source/tushare/stock/capflowths/__init__.py +19 -0
  130. xfintech/data/source/tushare/stock/capflowths/capflowths.py +173 -0
  131. xfintech/data/source/tushare/stock/capflowths/constant.py +92 -0
  132. xfintech/data/source/tushare/stock/capflowths/tests/__init__.py +0 -0
  133. xfintech/data/source/tushare/stock/capflowths/tests/test_class_capflowths_all.py +551 -0
  134. xfintech/data/source/tushare/stock/company/__init__.py +19 -0
  135. xfintech/data/source/tushare/stock/company/company.py +188 -0
  136. xfintech/data/source/tushare/stock/company/constant.py +92 -0
  137. xfintech/data/source/tushare/stock/company/tests/__init__.py +1 -0
  138. xfintech/data/source/tushare/stock/company/tests/test_class_company_all.py +829 -0
  139. xfintech/data/source/tushare/stock/companybusiness/__init__.py +21 -0
  140. xfintech/data/source/tushare/stock/companybusiness/companybusiness.py +183 -0
  141. xfintech/data/source/tushare/stock/companybusiness/constant.py +91 -0
  142. xfintech/data/source/tushare/stock/companybusiness/tests/__init__.py +0 -0
  143. xfintech/data/source/tushare/stock/companybusiness/tests/test_class_companybusiness_all.py +633 -0
  144. xfintech/data/source/tushare/stock/companycashflow/__init__.py +21 -0
  145. xfintech/data/source/tushare/stock/companycashflow/companycashflow.py +277 -0
  146. xfintech/data/source/tushare/stock/companycashflow/constant.py +293 -0
  147. xfintech/data/source/tushare/stock/companycashflow/tests/__init__.py +0 -0
  148. xfintech/data/source/tushare/stock/companycashflow/tests/test_class_companycashflow_all.py +619 -0
  149. xfintech/data/source/tushare/stock/companydebt/__init__.py +19 -0
  150. xfintech/data/source/tushare/stock/companydebt/companydebt.py +339 -0
  151. xfintech/data/source/tushare/stock/companydebt/constant.py +403 -0
  152. xfintech/data/source/tushare/stock/companydebt/tests/__init__.py +0 -0
  153. xfintech/data/source/tushare/stock/companydebt/tests/test_class_companydebt_all.py +655 -0
  154. xfintech/data/source/tushare/stock/companyoverview/__init__.py +21 -0
  155. xfintech/data/source/tushare/stock/companyoverview/companyoverview.py +214 -0
  156. xfintech/data/source/tushare/stock/companyoverview/constant.py +152 -0
  157. xfintech/data/source/tushare/stock/companyoverview/tests/__init__.py +0 -0
  158. xfintech/data/source/tushare/stock/companyoverview/tests/test_class_companyoverview_all.py +647 -0
  159. xfintech/data/source/tushare/stock/companyprofit/__init__.py +21 -0
  160. xfintech/data/source/tushare/stock/companyprofit/companyprofit.py +272 -0
  161. xfintech/data/source/tushare/stock/companyprofit/constant.py +259 -0
  162. xfintech/data/source/tushare/stock/companyprofit/tests/__init__.py +0 -0
  163. xfintech/data/source/tushare/stock/companyprofit/tests/test_class_companyprofit_all.py +635 -0
  164. xfintech/data/source/tushare/stock/conceptcapflowdc/__init__.py +21 -0
  165. xfintech/data/source/tushare/stock/conceptcapflowdc/conceptcapflowdc.py +175 -0
  166. xfintech/data/source/tushare/stock/conceptcapflowdc/constant.py +106 -0
  167. xfintech/data/source/tushare/stock/conceptcapflowdc/tests/__init__.py +0 -0
  168. xfintech/data/source/tushare/stock/conceptcapflowdc/tests/test_class_conceptcapflowdc_all.py +568 -0
  169. xfintech/data/source/tushare/stock/conceptcapflowths/__init__.py +21 -0
  170. xfintech/data/source/tushare/stock/conceptcapflowths/conceptcapflowths.py +188 -0
  171. xfintech/data/source/tushare/stock/conceptcapflowths/constant.py +89 -0
  172. xfintech/data/source/tushare/stock/conceptcapflowths/tests/__init__.py +0 -0
  173. xfintech/data/source/tushare/stock/conceptcapflowths/tests/test_class_conceptcapflowths_all.py +516 -0
  174. xfintech/data/source/tushare/stock/dayline/__init__.py +19 -0
  175. xfintech/data/source/tushare/stock/dayline/constant.py +87 -0
  176. xfintech/data/source/tushare/stock/dayline/dayline.py +177 -0
  177. xfintech/data/source/tushare/stock/dayline/tests/__init__.py +0 -0
  178. xfintech/data/source/tushare/stock/dayline/tests/test_class_dayline_all.py +585 -0
  179. xfintech/data/source/tushare/stock/industrycapflowths/__init__.py +21 -0
  180. xfintech/data/source/tushare/stock/industrycapflowths/constant.py +89 -0
  181. xfintech/data/source/tushare/stock/industrycapflowths/industrycapflowths.py +192 -0
  182. xfintech/data/source/tushare/stock/industrycapflowths/tests/__init__.py +0 -0
  183. xfintech/data/source/tushare/stock/industrycapflowths/tests/test_class_industrycapflowths_all.py +683 -0
  184. xfintech/data/source/tushare/stock/marketindexcapflowdc/__init__.py +21 -0
  185. xfintech/data/source/tushare/stock/marketindexcapflowdc/constant.py +90 -0
  186. xfintech/data/source/tushare/stock/marketindexcapflowdc/marketindexcapflowdc.py +173 -0
  187. xfintech/data/source/tushare/stock/marketindexcapflowdc/tests/__init__.py +0 -0
  188. xfintech/data/source/tushare/stock/marketindexcapflowdc/tests/test_class_marketindexcapflowdc_all.py +793 -0
  189. xfintech/data/source/tushare/stock/monthline/__init__.py +19 -0
  190. xfintech/data/source/tushare/stock/monthline/constant.py +87 -0
  191. xfintech/data/source/tushare/stock/monthline/monthline.py +180 -0
  192. xfintech/data/source/tushare/stock/monthline/tests/__init__.py +0 -0
  193. xfintech/data/source/tushare/stock/monthline/tests/test_class_monthline_all.py +574 -0
  194. xfintech/data/source/tushare/stock/stock/__init__.py +19 -0
  195. xfintech/data/source/tushare/stock/stock/constant.py +105 -0
  196. xfintech/data/source/tushare/stock/stock/stock.py +193 -0
  197. xfintech/data/source/tushare/stock/stock/tests/__init__.py +0 -0
  198. xfintech/data/source/tushare/stock/stock/tests/test_class_stock_all.py +788 -0
  199. xfintech/data/source/tushare/stock/stockdividend/__init__.py +21 -0
  200. xfintech/data/source/tushare/stock/stockdividend/constant.py +111 -0
  201. xfintech/data/source/tushare/stock/stockdividend/stockdividend.py +180 -0
  202. xfintech/data/source/tushare/stock/stockdividend/tests/__init__.py +0 -0
  203. xfintech/data/source/tushare/stock/stockdividend/tests/test_class_stockdividend_all.py +725 -0
  204. xfintech/data/source/tushare/stock/stockinfo/__init__.py +19 -0
  205. xfintech/data/source/tushare/stock/stockinfo/constant.py +104 -0
  206. xfintech/data/source/tushare/stock/stockinfo/stockinfo.py +208 -0
  207. xfintech/data/source/tushare/stock/stockinfo/tests/__init__.py +0 -0
  208. xfintech/data/source/tushare/stock/stockinfo/tests/test_class_stockinfo_all.py +881 -0
  209. xfintech/data/source/tushare/stock/stockipo/__init__.py +19 -0
  210. xfintech/data/source/tushare/stock/stockipo/constant.py +90 -0
  211. xfintech/data/source/tushare/stock/stockipo/stockipo.py +234 -0
  212. xfintech/data/source/tushare/stock/stockipo/tests/__init__.py +1 -0
  213. xfintech/data/source/tushare/stock/stockipo/tests/test_class_stockipo_all.py +750 -0
  214. xfintech/data/source/tushare/stock/stockpledge/__init__.py +19 -0
  215. xfintech/data/source/tushare/stock/stockpledge/constant.py +72 -0
  216. xfintech/data/source/tushare/stock/stockpledge/stockpledge.py +158 -0
  217. xfintech/data/source/tushare/stock/stockpledge/tests/__init__.py +0 -0
  218. xfintech/data/source/tushare/stock/stockpledge/tests/test_class_stockpledge_all.py +664 -0
  219. xfintech/data/source/tushare/stock/stockpledgedetail/__init__.py +21 -0
  220. xfintech/data/source/tushare/stock/stockpledgedetail/constant.py +85 -0
  221. xfintech/data/source/tushare/stock/stockpledgedetail/stockpledgedetail.py +171 -0
  222. xfintech/data/source/tushare/stock/stockpledgedetail/tests/__init__.py +0 -0
  223. xfintech/data/source/tushare/stock/stockpledgedetail/tests/test_class_stockpledgedetail_all.py +112 -0
  224. xfintech/data/source/tushare/stock/stockst/__init__.py +19 -0
  225. xfintech/data/source/tushare/stock/stockst/constant.py +80 -0
  226. xfintech/data/source/tushare/stock/stockst/stockst.py +189 -0
  227. xfintech/data/source/tushare/stock/stockst/tests/__init__.py +0 -0
  228. xfintech/data/source/tushare/stock/stockst/tests/test_class_stockst_all.py +693 -0
  229. xfintech/data/source/tushare/stock/stocksuspend/__init__.py +21 -0
  230. xfintech/data/source/tushare/stock/stocksuspend/constant.py +75 -0
  231. xfintech/data/source/tushare/stock/stocksuspend/stocksuspend.py +151 -0
  232. xfintech/data/source/tushare/stock/stocksuspend/tests/__init__.py +0 -0
  233. xfintech/data/source/tushare/stock/stocksuspend/tests/test_class_stocksuspend_all.py +626 -0
  234. xfintech/data/source/tushare/stock/techindex/__init__.py +19 -0
  235. xfintech/data/source/tushare/stock/techindex/constant.py +600 -0
  236. xfintech/data/source/tushare/stock/techindex/techindex.py +314 -0
  237. xfintech/data/source/tushare/stock/techindex/tests/__init__.py +0 -0
  238. xfintech/data/source/tushare/stock/techindex/tests/test_class_techindex_all.py +576 -0
  239. xfintech/data/source/tushare/stock/tradedate/__init__.py +19 -0
  240. xfintech/data/source/tushare/stock/tradedate/constant.py +93 -0
  241. xfintech/data/source/tushare/stock/tradedate/tests/__init__.py +0 -0
  242. xfintech/data/source/tushare/stock/tradedate/tests/test_class_tradedate_all.py +947 -0
  243. xfintech/data/source/tushare/stock/tradedate/tradedate.py +234 -0
  244. xfintech/data/source/tushare/stock/weekline/__init__.py +19 -0
  245. xfintech/data/source/tushare/stock/weekline/constant.py +87 -0
  246. xfintech/data/source/tushare/stock/weekline/tests/__init__.py +0 -0
  247. xfintech/data/source/tushare/stock/weekline/tests/test_class_weekline_all.py +575 -0
  248. xfintech/data/source/tushare/stock/weekline/weekline.py +182 -0
  249. xfintech/fabric/__init__.py +18 -0
  250. xfintech/fabric/column/__init__.py +7 -0
  251. xfintech/fabric/column/info.py +202 -0
  252. xfintech/fabric/column/kind.py +102 -0
  253. xfintech/fabric/column/tests/__init__.py +0 -0
  254. xfintech/fabric/column/tests/test_class_info_all.py +207 -0
  255. xfintech/fabric/column/tests/test_class_kind_all.py +80 -0
  256. xfintech/fabric/table/__init__.py +5 -0
  257. xfintech/fabric/table/info.py +263 -0
  258. xfintech/fabric/table/tests/__init__.py +0 -0
  259. xfintech/fabric/table/tests/test_class_info_all.py +547 -0
  260. xfintech/serde/__init__.py +35 -0
  261. xfintech/serde/common/__init__.py +9 -0
  262. xfintech/serde/common/dataformat.py +78 -0
  263. xfintech/serde/common/deserialiserlike.py +38 -0
  264. xfintech/serde/common/error.py +182 -0
  265. xfintech/serde/common/serialiserlike.py +38 -0
  266. xfintech/serde/common/tests/__init__.py +1 -0
  267. xfintech/serde/common/tests/test_class_dataformat_all.py +694 -0
  268. xfintech/serde/common/tests/test_class_deserialiserlike_all.py +500 -0
  269. xfintech/serde/common/tests/test_class_errors_all.py +518 -0
  270. xfintech/serde/common/tests/test_class_serialiserlike_all.py +401 -0
  271. xfintech/serde/deserialiser/__init__.py +7 -0
  272. xfintech/serde/deserialiser/pandas.py +113 -0
  273. xfintech/serde/deserialiser/python.py +68 -0
  274. xfintech/serde/deserialiser/tests/__init__.py +1 -0
  275. xfintech/serde/deserialiser/tests/test_class_pandasdeserialiser_all.py +503 -0
  276. xfintech/serde/deserialiser/tests/test_class_pythondeserialiser_all.py +570 -0
  277. xfintech/serde/serialiser/__init__.py +7 -0
  278. xfintech/serde/serialiser/pandas.py +116 -0
  279. xfintech/serde/serialiser/python.py +71 -0
  280. xfintech/serde/serialiser/tests/__init__.py +1 -0
  281. xfintech/serde/serialiser/tests/test_class_pandasserialiser_all.py +474 -0
  282. xfintech/serde/serialiser/tests/test_class_pythonserialiser_all.py +508 -0
@@ -0,0 +1,814 @@
1
+ from datetime import date, datetime
2
+ from unittest.mock import MagicMock, patch
3
+
4
+ import pandas as pd
5
+ import pytest
6
+
7
+ from xfintech.data.common.cache import Cache
8
+ from xfintech.data.common.coolant import Coolant
9
+ from xfintech.data.common.retry import Retry
10
+ from xfintech.data.source.tushare.session.session import Session
11
+ from xfintech.data.source.tushare.stock.capflowdc.capflowdc import CapflowDC
12
+ from xfintech.data.source.tushare.stock.capflowdc.constant import (
13
+ KEY,
14
+ NAME,
15
+ PAGINATE,
16
+ SOURCE,
17
+ TARGET,
18
+ )
19
+
20
+ # Fixtures
21
+
22
+
23
+ @pytest.fixture
24
+ def mock_session():
25
+ """Create a mock Tushare session"""
26
+ session = MagicMock(spec=Session)
27
+ session._credential = "test_token"
28
+ session.id = "test1234"
29
+ session.mode = "direct"
30
+ session.relay_url = None
31
+ session.relay_secret = None
32
+ session.connected = True
33
+
34
+ # Mock the connection object
35
+ mock_connection = MagicMock()
36
+ mock_connection.moneyflow_dc = MagicMock()
37
+ session.connection = mock_connection
38
+
39
+ return session
40
+
41
+
42
+ # Test Class 1: Initialization Tests
43
+ class TestCapflowDCInitialization:
44
+ """Test CapflowDC class initialization and configuration."""
45
+
46
+ def test_name_constant(self):
47
+ """Test NAME constant is set correctly."""
48
+ assert NAME == "capflowdc"
49
+
50
+ def test_key_constant(self):
51
+ """Test KEY constant is set correctly."""
52
+ assert KEY == "/tushare/capflowdc"
53
+
54
+ def test_paginate_settings(self):
55
+ """Test pagination settings are correct."""
56
+ assert PAGINATE.pagesize == 6000
57
+ assert PAGINATE.pagelimit == 1000
58
+
59
+ def test_source_table_info(self):
60
+ """Test SOURCE TableInfo configuration."""
61
+ assert SOURCE.desc == "东方财富个股资金流向数据(tushare格式)"
62
+ assert SOURCE.meta["provider"] == "tushare"
63
+ assert SOURCE.meta["source"] == "moneyflow_dc"
64
+ assert SOURCE.meta["type"] == "partitioned"
65
+ assert SOURCE.meta["scale"] == "crosssection"
66
+ # Verify key columns exist
67
+ column_names = SOURCE.list_column_names()
68
+ assert "trade_date" in column_names
69
+ assert "ts_code" in column_names
70
+ assert "name" in column_names
71
+ assert "net_amount" in column_names
72
+
73
+ def test_target_table_info(self):
74
+ """Test TARGET TableInfo configuration."""
75
+ assert TARGET.desc == "东方财富个股资金流向数据(xfinbatch标准格式)"
76
+ assert TARGET.meta["key"] == KEY
77
+ assert TARGET.meta["name"] == NAME
78
+ assert TARGET.meta["type"] == "partitioned"
79
+ assert TARGET.meta["scale"] == "crosssection"
80
+ # Verify transformed columns exist
81
+ column_names = TARGET.list_column_names()
82
+ assert "code" in column_names
83
+ assert "date" in column_names
84
+ assert "datecode" in column_names
85
+ assert "percent_change" in column_names
86
+
87
+ def test_initialization_with_params(self, mock_session):
88
+ """Test CapflowDC initialization with parameters."""
89
+ params = {"ts_code": "002149.SZ", "start_date": "20240901", "end_date": "20240913"}
90
+ job = CapflowDC(session=mock_session, params=params)
91
+
92
+ assert job.name == NAME
93
+ assert job.key == KEY
94
+ assert job.params.get("ts_code") == "002149.SZ"
95
+
96
+ def test_initialization_with_optional_components(self, mock_session):
97
+ """Test initialization with optional coolant, retry, and cache."""
98
+ coolant = Coolant(interval=1.0)
99
+ retry = Retry(retry=3)
100
+ cache = Cache(path="/tmp/test_cache")
101
+
102
+ job = CapflowDC(
103
+ session=mock_session,
104
+ params={"trade_date": "20241011"},
105
+ coolant=coolant,
106
+ retry=retry,
107
+ cache=cache,
108
+ )
109
+
110
+ assert job.coolant.interval == 1.0
111
+ assert job.retry.retry == 3
112
+ assert job.cache is not None
113
+ assert isinstance(job.cache, Cache)
114
+
115
+ def test_initialization_minimal(self, mock_session):
116
+ """Test minimal initialization with only required session."""
117
+ job = CapflowDC(session=mock_session)
118
+
119
+ assert job.name == NAME
120
+ assert job.key == KEY
121
+
122
+
123
+ # Test Class 2: Transform Tests
124
+
125
+
126
+ class TestCapflowDCTransform:
127
+ """Test the transform method that converts Tushare format to xfinbatch format."""
128
+
129
+ def test_transform_empty_dataframe(self, mock_session):
130
+ """Test transform with empty DataFrame."""
131
+ job = CapflowDC(session=mock_session)
132
+
133
+ empty_df = pd.DataFrame()
134
+ result = job.transform(empty_df)
135
+
136
+ assert result.empty
137
+ assert list(result.columns) == TARGET.list_column_names()
138
+
139
+ def test_transform_none_input(self, mock_session):
140
+ """Test transform with None input."""
141
+ job = CapflowDC(session=mock_session)
142
+
143
+ result = job.transform(None)
144
+
145
+ assert result.empty
146
+ assert list(result.columns) == TARGET.list_column_names()
147
+
148
+ def test_transform_basic_data(self, mock_session):
149
+ """Test transform with basic data."""
150
+ job = CapflowDC(session=mock_session)
151
+
152
+ source_data = pd.DataFrame(
153
+ {
154
+ "trade_date": ["20240913"],
155
+ "ts_code": ["002149.SZ"],
156
+ "name": ["西部材料"],
157
+ "pct_change": [-1.34],
158
+ "close": [15.25],
159
+ "net_amount": [-245.67],
160
+ "net_amount_rate": [-6.77],
161
+ "buy_elg_amount": [-183.55],
162
+ "buy_elg_amount_rate": [-5.06],
163
+ "buy_lg_amount": [-62.12],
164
+ "buy_lg_amount_rate": [-1.71],
165
+ "buy_md_amount": [-12.65],
166
+ "buy_md_amount_rate": [-0.35],
167
+ "buy_sm_amount": [-62.43],
168
+ "buy_sm_amount_rate": [-1.72],
169
+ }
170
+ )
171
+
172
+ result = job.transform(source_data)
173
+
174
+ assert not result.empty
175
+ assert len(result) == 1
176
+ assert result.iloc[0]["code"] == "002149.SZ"
177
+ assert result.iloc[0]["date"] == "2024-09-13"
178
+ assert result.iloc[0]["datecode"] == "20240913"
179
+ assert result.iloc[0]["name"] == "西部材料"
180
+ assert result.iloc[0]["percent_change"] == pytest.approx(-1.34)
181
+ assert result.iloc[0]["close"] == pytest.approx(15.25)
182
+
183
+ def test_transform_multiple_stocks(self, mock_session):
184
+ """Test transform with multiple stocks."""
185
+ job = CapflowDC(session=mock_session)
186
+
187
+ source_data = pd.DataFrame(
188
+ {
189
+ "trade_date": ["20240913", "20240913", "20240912"],
190
+ "ts_code": ["002149.SZ", "000001.SZ", "002149.SZ"],
191
+ "name": ["西部材料", "平安银行", "西部材料"],
192
+ "pct_change": [-1.34, 0.52, 1.43],
193
+ "close": [15.25, 12.30, 15.46],
194
+ "net_amount": [-245.67, 123.45, 456.78],
195
+ "net_amount_rate": [-6.77, 3.21, 10.89],
196
+ "buy_elg_amount": [-183.55, 89.12, 234.56],
197
+ "buy_elg_amount_rate": [-5.06, 2.31, 5.59],
198
+ "buy_lg_amount": [-62.12, 34.33, 222.22],
199
+ "buy_lg_amount_rate": [-1.71, 0.89, 5.30],
200
+ "buy_md_amount": [-12.65, 23.45, 13.71],
201
+ "buy_md_amount_rate": [-0.35, 0.61, 0.33],
202
+ "buy_sm_amount": [-62.43, -23.45, -388.43],
203
+ "buy_sm_amount_rate": [-1.72, -0.61, -9.25],
204
+ }
205
+ )
206
+
207
+ result = job.transform(source_data)
208
+
209
+ assert len(result) == 3
210
+ assert set(result["code"].unique()) == {"002149.SZ", "000001.SZ"}
211
+ # Verify sorted by code then date
212
+ assert result.iloc[0]["code"] == "000001.SZ"
213
+ assert result.iloc[1]["code"] == "002149.SZ"
214
+ assert result.iloc[2]["code"] == "002149.SZ"
215
+
216
+ def test_transform_numeric_conversion(self, mock_session):
217
+ """Test that numeric fields are properly converted."""
218
+ job = CapflowDC(session=mock_session)
219
+
220
+ source_data = pd.DataFrame(
221
+ {
222
+ "trade_date": ["20240913"],
223
+ "ts_code": ["002149.SZ"],
224
+ "name": ["西部材料"],
225
+ "pct_change": ["-1.34"], # String
226
+ "close": ["15.25"], # String
227
+ "net_amount": ["-245.67"], # String
228
+ "net_amount_rate": ["-6.77"], # String
229
+ "buy_elg_amount": ["-183.55"], # String
230
+ "buy_elg_amount_rate": ["-5.06"], # String
231
+ "buy_lg_amount": ["-62.12"], # String
232
+ "buy_lg_amount_rate": ["-1.71"], # String
233
+ "buy_md_amount": ["-12.65"], # String
234
+ "buy_md_amount_rate": ["-0.35"], # String
235
+ "buy_sm_amount": ["-62.43"], # String
236
+ "buy_sm_amount_rate": ["-1.72"], # String
237
+ }
238
+ )
239
+
240
+ result = job.transform(source_data)
241
+
242
+ # Verify all numeric fields are actually numeric
243
+ assert isinstance(result.iloc[0]["percent_change"], (int, float))
244
+ assert isinstance(result.iloc[0]["close"], (int, float))
245
+ assert isinstance(result.iloc[0]["net_amount"], (int, float))
246
+ assert result.iloc[0]["percent_change"] == pytest.approx(-1.34)
247
+
248
+ def test_transform_handles_invalid_values(self, mock_session):
249
+ """Test transform handles invalid numeric values gracefully."""
250
+ job = CapflowDC(session=mock_session)
251
+
252
+ source_data = pd.DataFrame(
253
+ {
254
+ "trade_date": ["20240913"],
255
+ "ts_code": ["002149.SZ"],
256
+ "name": ["西部材料"],
257
+ "pct_change": ["invalid"],
258
+ "close": ["N/A"],
259
+ "net_amount": ["-245.67"],
260
+ "net_amount_rate": ["-6.77"],
261
+ "buy_elg_amount": ["-183.55"],
262
+ "buy_elg_amount_rate": ["-5.06"],
263
+ "buy_lg_amount": ["-62.12"],
264
+ "buy_lg_amount_rate": ["-1.71"],
265
+ "buy_md_amount": ["-12.65"],
266
+ "buy_md_amount_rate": ["-0.35"],
267
+ "buy_sm_amount": ["-62.43"],
268
+ "buy_sm_amount_rate": ["-1.72"],
269
+ }
270
+ )
271
+
272
+ result = job.transform(source_data)
273
+
274
+ # Should handle gracefully with NaN
275
+ assert pd.isna(result.iloc[0]["percent_change"])
276
+ assert pd.isna(result.iloc[0]["close"])
277
+
278
+ def test_transform_removes_duplicates(self, mock_session):
279
+ """Test that transform removes duplicate rows."""
280
+ job = CapflowDC(session=mock_session)
281
+
282
+ # Create data with duplicates
283
+ source_data = pd.DataFrame(
284
+ {
285
+ "trade_date": ["20240913", "20240913"],
286
+ "ts_code": ["002149.SZ", "002149.SZ"],
287
+ "name": ["西部材料", "西部材料"],
288
+ "pct_change": [-1.34, -1.34],
289
+ "close": [15.25, 15.25],
290
+ "net_amount": [-245.67, -245.67],
291
+ "net_amount_rate": [-6.77, -6.77],
292
+ "buy_elg_amount": [-183.55, -183.55],
293
+ "buy_elg_amount_rate": [-5.06, -5.06],
294
+ "buy_lg_amount": [-62.12, -62.12],
295
+ "buy_lg_amount_rate": [-1.71, -1.71],
296
+ "buy_md_amount": [-12.65, -12.65],
297
+ "buy_md_amount_rate": [-0.35, -0.35],
298
+ "buy_sm_amount": [-62.43, -62.43],
299
+ "buy_sm_amount_rate": [-1.72, -1.72],
300
+ }
301
+ )
302
+
303
+ result = job.transform(source_data)
304
+
305
+ # Should only have 1 row after deduplication
306
+ assert len(result) == 1
307
+
308
+ def test_transform_date_formatting(self, mock_session):
309
+ """Test that dates are formatted correctly."""
310
+ job = CapflowDC(session=mock_session)
311
+
312
+ source_data = pd.DataFrame(
313
+ {
314
+ "trade_date": ["20240913", "20241011"],
315
+ "ts_code": ["002149.SZ", "000001.SZ"],
316
+ "name": ["西部材料", "平安银行"],
317
+ "pct_change": [-1.34, 0.52],
318
+ "close": [15.25, 12.30],
319
+ "net_amount": [-245.67, 123.45],
320
+ "net_amount_rate": [-6.77, 3.21],
321
+ "buy_elg_amount": [-183.55, 89.12],
322
+ "buy_elg_amount_rate": [-5.06, 2.31],
323
+ "buy_lg_amount": [-62.12, 34.33],
324
+ "buy_lg_amount_rate": [-1.71, 0.89],
325
+ "buy_md_amount": [-12.65, 23.45],
326
+ "buy_md_amount_rate": [-0.35, 0.61],
327
+ "buy_sm_amount": [-62.43, -23.45],
328
+ "buy_sm_amount_rate": [-1.72, -0.61],
329
+ }
330
+ )
331
+
332
+ result = job.transform(source_data)
333
+
334
+ # Result is sorted by code, then date
335
+ # 000001.SZ comes before 002149.SZ
336
+ assert result.iloc[0]["code"] == "000001.SZ"
337
+ assert result.iloc[0]["date"] == "2024-10-11"
338
+ assert result.iloc[0]["datecode"] == "20241011"
339
+ assert result.iloc[1]["code"] == "002149.SZ"
340
+ assert result.iloc[1]["date"] == "2024-09-13"
341
+ assert result.iloc[1]["datecode"] == "20240913"
342
+
343
+ def test_transform_all_target_columns_present(self, mock_session):
344
+ """Test that all target columns are present in transformed data."""
345
+ job = CapflowDC(session=mock_session)
346
+
347
+ source_data = pd.DataFrame(
348
+ {
349
+ "trade_date": ["20240913"],
350
+ "ts_code": ["002149.SZ"],
351
+ "name": ["西部材料"],
352
+ "pct_change": [-1.34],
353
+ "close": [15.25],
354
+ "net_amount": [-245.67],
355
+ "net_amount_rate": [-6.77],
356
+ "buy_elg_amount": [-183.55],
357
+ "buy_elg_amount_rate": [-5.06],
358
+ "buy_lg_amount": [-62.12],
359
+ "buy_lg_amount_rate": [-1.71],
360
+ "buy_md_amount": [-12.65],
361
+ "buy_md_amount_rate": [-0.35],
362
+ "buy_sm_amount": [-62.43],
363
+ "buy_sm_amount_rate": [-1.72],
364
+ }
365
+ )
366
+
367
+ result = job.transform(source_data)
368
+
369
+ expected_columns = TARGET.list_column_names()
370
+ assert list(result.columns) == expected_columns
371
+
372
+
373
+ # Test Class 3: Run Method Tests
374
+
375
+
376
+ class TestCapflowDCRun:
377
+ """Test the _run method and parameter handling."""
378
+
379
+ def test_run_with_string_date_params(self, mock_session):
380
+ """Test _run with string date parameters."""
381
+ # Mock the API response
382
+ mock_session.connection.moneyflow_dc.return_value = pd.DataFrame(
383
+ {
384
+ "trade_date": ["20240913"],
385
+ "ts_code": ["002149.SZ"],
386
+ "name": ["西部材料"],
387
+ "pct_change": [-1.34],
388
+ "close": [15.25],
389
+ "net_amount": [-245.67],
390
+ "net_amount_rate": [-6.77],
391
+ "buy_elg_amount": [-183.55],
392
+ "buy_elg_amount_rate": [-5.06],
393
+ "buy_lg_amount": [-62.12],
394
+ "buy_lg_amount_rate": [-1.71],
395
+ "buy_md_amount": [-12.65],
396
+ "buy_md_amount_rate": [-0.35],
397
+ "buy_sm_amount": [-62.43],
398
+ "buy_sm_amount_rate": [-1.72],
399
+ }
400
+ )
401
+
402
+ job = CapflowDC(
403
+ session=mock_session,
404
+ params={"ts_code": "002149.SZ", "start_date": "20240901", "end_date": "20240913"},
405
+ cache=False,
406
+ )
407
+
408
+ result = job._run()
409
+
410
+ assert not result.empty
411
+ mock_session.connection.moneyflow_dc.assert_called_once()
412
+ call_kwargs = mock_session.connection.moneyflow_dc.call_args[1]
413
+ assert call_kwargs["ts_code"] == "002149.SZ"
414
+ assert call_kwargs["start_date"] == "20240901"
415
+ assert call_kwargs["end_date"] == "20240913"
416
+
417
+ def test_run_with_datetime_params(self, mock_session):
418
+ """Test _run converts datetime parameters correctly."""
419
+ mock_session.connection.moneyflow_dc.return_value = pd.DataFrame(
420
+ {
421
+ "trade_date": ["20240913"],
422
+ "ts_code": ["002149.SZ"],
423
+ "name": ["西部材料"],
424
+ "pct_change": [-1.34],
425
+ "close": [15.25],
426
+ "net_amount": [-245.67],
427
+ "net_amount_rate": [-6.77],
428
+ "buy_elg_amount": [-183.55],
429
+ "buy_elg_amount_rate": [-5.06],
430
+ "buy_lg_amount": [-62.12],
431
+ "buy_lg_amount_rate": [-1.71],
432
+ "buy_md_amount": [-12.65],
433
+ "buy_md_amount_rate": [-0.35],
434
+ "buy_sm_amount": [-62.43],
435
+ "buy_sm_amount_rate": [-1.72],
436
+ }
437
+ )
438
+
439
+ job = CapflowDC(
440
+ session=mock_session,
441
+ params={"trade_date": datetime(2024, 9, 13)},
442
+ cache=False,
443
+ )
444
+
445
+ result = job._run()
446
+
447
+ assert not result.empty
448
+ call_kwargs = mock_session.connection.moneyflow_dc.call_args[1]
449
+ assert call_kwargs["trade_date"] == "20240913"
450
+
451
+ def test_run_with_date_params(self, mock_session):
452
+ """Test _run converts date parameters correctly."""
453
+ mock_session.connection.moneyflow_dc.return_value = pd.DataFrame(
454
+ {
455
+ "trade_date": ["20240913"],
456
+ "ts_code": ["002149.SZ"],
457
+ "name": ["西部材料"],
458
+ "pct_change": [-1.34],
459
+ "close": [15.25],
460
+ "net_amount": [-245.67],
461
+ "net_amount_rate": [-6.77],
462
+ "buy_elg_amount": [-183.55],
463
+ "buy_elg_amount_rate": [-5.06],
464
+ "buy_lg_amount": [-62.12],
465
+ "buy_lg_amount_rate": [-1.71],
466
+ "buy_md_amount": [-12.65],
467
+ "buy_md_amount_rate": [-0.35],
468
+ "buy_sm_amount": [-62.43],
469
+ "buy_sm_amount_rate": [-1.72],
470
+ }
471
+ )
472
+
473
+ job = CapflowDC(
474
+ session=mock_session,
475
+ params={"start_date": date(2024, 9, 1), "end_date": date(2024, 9, 13)},
476
+ cache=False,
477
+ )
478
+
479
+ result = job._run()
480
+
481
+ assert not result.empty
482
+ call_kwargs = mock_session.connection.moneyflow_dc.call_args[1]
483
+ assert call_kwargs["start_date"] == "20240901"
484
+ assert call_kwargs["end_date"] == "20240913"
485
+
486
+ def test_run_with_trade_date(self, mock_session):
487
+ """Test _run with single trade_date parameter."""
488
+ mock_session.connection.moneyflow_dc.return_value = pd.DataFrame(
489
+ {
490
+ "trade_date": ["20241011"],
491
+ "ts_code": ["002149.SZ"],
492
+ "name": ["西部材料"],
493
+ "pct_change": [-1.34],
494
+ "close": [15.25],
495
+ "net_amount": [-245.67],
496
+ "net_amount_rate": [-6.77],
497
+ "buy_elg_amount": [-183.55],
498
+ "buy_elg_amount_rate": [-5.06],
499
+ "buy_lg_amount": [-62.12],
500
+ "buy_lg_amount_rate": [-1.71],
501
+ "buy_md_amount": [-12.65],
502
+ "buy_md_amount_rate": [-0.35],
503
+ "buy_sm_amount": [-62.43],
504
+ "buy_sm_amount_rate": [-1.72],
505
+ }
506
+ )
507
+
508
+ job = CapflowDC(session=mock_session, params={"trade_date": "20241011"}, cache=False)
509
+
510
+ result = job._run()
511
+
512
+ assert not result.empty
513
+ call_kwargs = mock_session.connection.moneyflow_dc.call_args[1]
514
+ assert call_kwargs["trade_date"] == "20241011"
515
+
516
+ def test_run_with_ts_code(self, mock_session):
517
+ """Test _run with ts_code parameter."""
518
+ mock_session.connection.moneyflow_dc.return_value = pd.DataFrame(
519
+ {
520
+ "trade_date": ["20240913"],
521
+ "ts_code": ["002149.SZ"],
522
+ "name": ["西部材料"],
523
+ "pct_change": [-1.34],
524
+ "close": [15.25],
525
+ "net_amount": [-245.67],
526
+ "net_amount_rate": [-6.77],
527
+ "buy_elg_amount": [-183.55],
528
+ "buy_elg_amount_rate": [-5.06],
529
+ "buy_lg_amount": [-62.12],
530
+ "buy_lg_amount_rate": [-1.71],
531
+ "buy_md_amount": [-12.65],
532
+ "buy_md_amount_rate": [-0.35],
533
+ "buy_sm_amount": [-62.43],
534
+ "buy_sm_amount_rate": [-1.72],
535
+ }
536
+ )
537
+
538
+ job = CapflowDC(
539
+ session=mock_session,
540
+ params={"ts_code": "002149.SZ", "start_date": "20240901", "end_date": "20240913"},
541
+ cache=False,
542
+ )
543
+
544
+ result = job._run()
545
+
546
+ assert not result.empty
547
+ assert result.iloc[0]["code"] == "002149.SZ"
548
+
549
+ def test_run_empty_result(self, mock_session):
550
+ """Test _run handles empty API result."""
551
+ mock_session.connection.moneyflow_dc.return_value = pd.DataFrame()
552
+
553
+ job = CapflowDC(session=mock_session, params={"trade_date": "20240101"}, cache=False)
554
+
555
+ result = job._run()
556
+
557
+ assert result.empty
558
+ assert list(result.columns) == TARGET.list_column_names()
559
+
560
+ def test_run_with_multiple_params(self, mock_session):
561
+ """Test _run with multiple parameter types."""
562
+ mock_session.connection.moneyflow_dc.return_value = pd.DataFrame(
563
+ {
564
+ "trade_date": ["20240913"],
565
+ "ts_code": ["002149.SZ"],
566
+ "name": ["西部材料"],
567
+ "pct_change": [-1.34],
568
+ "close": [15.25],
569
+ "net_amount": [-245.67],
570
+ "net_amount_rate": [-6.77],
571
+ "buy_elg_amount": [-183.55],
572
+ "buy_elg_amount_rate": [-5.06],
573
+ "buy_lg_amount": [-62.12],
574
+ "buy_lg_amount_rate": [-1.71],
575
+ "buy_md_amount": [-12.65],
576
+ "buy_md_amount_rate": [-0.35],
577
+ "buy_sm_amount": [-62.43],
578
+ "buy_sm_amount_rate": [-1.72],
579
+ }
580
+ )
581
+
582
+ job = CapflowDC(
583
+ session=mock_session,
584
+ params={
585
+ "ts_code": "002149.SZ",
586
+ "start_date": date(2024, 9, 1),
587
+ "end_date": datetime(2024, 9, 13),
588
+ },
589
+ cache=False,
590
+ )
591
+
592
+ job._run()
593
+
594
+ call_kwargs = mock_session.connection.moneyflow_dc.call_args[1]
595
+ assert call_kwargs["ts_code"] == "002149.SZ"
596
+ assert call_kwargs["start_date"] == "20240901"
597
+ assert call_kwargs["end_date"] == "20240913"
598
+
599
+ def test_run_api_called_with_correct_method(self, mock_session):
600
+ """Test that _run calls moneyflow_dc API method."""
601
+ mock_session.connection.moneyflow_dc.return_value = pd.DataFrame(
602
+ {
603
+ "trade_date": ["20241011"],
604
+ "ts_code": ["002149.SZ"],
605
+ "name": ["西部材料"],
606
+ "pct_change": [-1.34],
607
+ "close": [15.25],
608
+ "net_amount": [-245.67],
609
+ "net_amount_rate": [-6.77],
610
+ "buy_elg_amount": [-183.55],
611
+ "buy_elg_amount_rate": [-5.06],
612
+ "buy_lg_amount": [-62.12],
613
+ "buy_lg_amount_rate": [-1.71],
614
+ "buy_md_amount": [-12.65],
615
+ "buy_md_amount_rate": [-0.35],
616
+ "buy_sm_amount": [-62.43],
617
+ "buy_sm_amount_rate": [-1.72],
618
+ }
619
+ )
620
+
621
+ job = CapflowDC(session=mock_session, params={"trade_date": "20241011"}, cache=False)
622
+
623
+ job._run()
624
+
625
+ # Verify the correct API method was called
626
+ mock_session.connection.moneyflow_dc.assert_called_once()
627
+
628
+
629
+ # Test Class 4: Cache Tests
630
+
631
+
632
+ class TestCapflowDCCache:
633
+ """Test caching functionality."""
634
+
635
+ def test_cache_enabled(self, mock_session):
636
+ """Test that cache is used when enabled."""
637
+ cached_data = pd.DataFrame(
638
+ {
639
+ "code": ["002149.SZ"],
640
+ "date": ["2024-09-13"],
641
+ "datecode": ["20240913"],
642
+ "name": ["西部材料"],
643
+ "percent_change": [-1.34],
644
+ "close": [15.25],
645
+ "net_amount": [-245.67],
646
+ "net_amount_rate": [-6.77],
647
+ "buy_elg_amount": [-183.55],
648
+ "buy_elg_amount_rate": [-5.06],
649
+ "buy_lg_amount": [-62.12],
650
+ "buy_lg_amount_rate": [-1.71],
651
+ "buy_md_amount": [-12.65],
652
+ "buy_md_amount_rate": [-0.35],
653
+ "buy_sm_amount": [-62.43],
654
+ "buy_sm_amount_rate": [-1.72],
655
+ }
656
+ )
657
+
658
+ job = CapflowDC(session=mock_session, params={"trade_date": "20240913"}, cache=True)
659
+
660
+ # Mock _load_cache to return cached data
661
+ with patch.object(job, "_load_cache", return_value=cached_data):
662
+ result = job._run()
663
+
664
+ # Should return cached data without calling API
665
+ assert not result.empty
666
+ mock_session.connection.moneyflow_dc.assert_not_called()
667
+
668
+ def test_cache_disabled(self, mock_session):
669
+ """Test that API is called when cache is disabled."""
670
+ mock_session.connection.moneyflow_dc.return_value = pd.DataFrame(
671
+ {
672
+ "trade_date": ["20240913"],
673
+ "ts_code": ["002149.SZ"],
674
+ "name": ["西部材料"],
675
+ "pct_change": [-1.34],
676
+ "close": [15.25],
677
+ "net_amount": [-245.67],
678
+ "net_amount_rate": [-6.77],
679
+ "buy_elg_amount": [-183.55],
680
+ "buy_elg_amount_rate": [-5.06],
681
+ "buy_lg_amount": [-62.12],
682
+ "buy_lg_amount_rate": [-1.71],
683
+ "buy_md_amount": [-12.65],
684
+ "buy_md_amount_rate": [-0.35],
685
+ "buy_sm_amount": [-62.43],
686
+ "buy_sm_amount_rate": [-1.72],
687
+ }
688
+ )
689
+
690
+ job = CapflowDC(session=mock_session, params={"trade_date": "20240913"}, cache=False)
691
+
692
+ result = job._run()
693
+
694
+ # Should call API
695
+ assert not result.empty
696
+ mock_session.connection.moneyflow_dc.assert_called_once()
697
+
698
+ def test_cache_save_called(self, mock_session):
699
+ """Test that cache save is called after successful run."""
700
+ mock_session.connection.moneyflow_dc.return_value = pd.DataFrame(
701
+ {
702
+ "trade_date": ["20240913"],
703
+ "ts_code": ["002149.SZ"],
704
+ "name": ["西部材料"],
705
+ "pct_change": [-1.34],
706
+ "close": [15.25],
707
+ "net_amount": [-245.67],
708
+ "net_amount_rate": [-6.77],
709
+ "buy_elg_amount": [-183.55],
710
+ "buy_elg_amount_rate": [-5.06],
711
+ "buy_lg_amount": [-62.12],
712
+ "buy_lg_amount_rate": [-1.71],
713
+ "buy_md_amount": [-12.65],
714
+ "buy_md_amount_rate": [-0.35],
715
+ "buy_sm_amount": [-62.43],
716
+ "buy_sm_amount_rate": [-1.72],
717
+ }
718
+ )
719
+
720
+ job = CapflowDC(session=mock_session, params={"trade_date": "20240913"}, cache=True)
721
+
722
+ with patch.object(job, "_save_cache") as mock_save:
723
+ job._run()
724
+
725
+ # Should call save cache
726
+ mock_save.assert_called_once()
727
+
728
+
729
+ # Test Class 5: Integration Tests
730
+
731
+
732
+ class TestCapflowDCIntegration:
733
+ """Integration tests combining multiple features."""
734
+
735
+ def test_full_workflow_single_stock(self, mock_session):
736
+ """Test complete workflow for single stock query."""
737
+ mock_session.connection.moneyflow_dc.return_value = pd.DataFrame(
738
+ {
739
+ "trade_date": ["20240913", "20240912", "20240911"],
740
+ "ts_code": ["002149.SZ", "002149.SZ", "002149.SZ"],
741
+ "name": ["西部材料", "西部材料", "西部材料"],
742
+ "pct_change": [-1.34, 1.43, -0.79],
743
+ "close": [15.25, 15.46, 15.24],
744
+ "net_amount": [-245.67, 456.78, -123.45],
745
+ "net_amount_rate": [-6.77, 10.89, -7.94],
746
+ "buy_elg_amount": [-183.55, 234.56, -89.12],
747
+ "buy_elg_amount_rate": [-5.06, 5.59, -5.73],
748
+ "buy_lg_amount": [-62.12, 222.22, -34.33],
749
+ "buy_lg_amount_rate": [-1.71, 5.30, -2.21],
750
+ "buy_md_amount": [-12.65, 13.71, -26.10],
751
+ "buy_md_amount_rate": [-0.35, 0.33, -1.68],
752
+ "buy_sm_amount": [-62.43, -388.43, 95.69],
753
+ "buy_sm_amount_rate": [-1.72, -9.25, 6.15],
754
+ }
755
+ )
756
+
757
+ job = CapflowDC(
758
+ session=mock_session,
759
+ params={"ts_code": "002149.SZ", "start_date": "20240911", "end_date": "20240913"},
760
+ cache=False,
761
+ )
762
+
763
+ result = job.run()
764
+
765
+ assert len(result) == 3
766
+ assert all(result["code"] == "002149.SZ")
767
+ assert all(result["name"] == "西部材料")
768
+ # Verify dates are in ascending order
769
+ assert result.iloc[0]["date"] == "2024-09-11"
770
+ assert result.iloc[1]["date"] == "2024-09-12"
771
+ assert result.iloc[2]["date"] == "2024-09-13"
772
+
773
+ def test_full_workflow_market_wide(self, mock_session):
774
+ """Test complete workflow for market-wide query."""
775
+ mock_session.connection.moneyflow_dc.return_value = pd.DataFrame(
776
+ {
777
+ "trade_date": ["20241011", "20241011", "20241011"],
778
+ "ts_code": ["002149.SZ", "000001.SZ", "600000.SH"],
779
+ "name": ["西部材料", "平安银行", "浦发银行"],
780
+ "pct_change": [-1.34, 0.52, 1.23],
781
+ "close": [15.25, 12.30, 8.45],
782
+ "net_amount": [-245.67, 123.45, 678.90],
783
+ "net_amount_rate": [-6.77, 3.21, 15.32],
784
+ "buy_elg_amount": [-183.55, 89.12, 456.78],
785
+ "buy_elg_amount_rate": [-5.06, 2.31, 10.31],
786
+ "buy_lg_amount": [-62.12, 34.33, 222.12],
787
+ "buy_lg_amount_rate": [-1.71, 0.89, 5.01],
788
+ "buy_md_amount": [-12.65, 23.45, 45.67],
789
+ "buy_md_amount_rate": [-0.35, 0.61, 1.03],
790
+ "buy_sm_amount": [-62.43, -23.45, -45.67],
791
+ "buy_sm_amount_rate": [-1.72, -0.61, -1.03],
792
+ }
793
+ )
794
+
795
+ job = CapflowDC(session=mock_session, params={"trade_date": "20241011"}, cache=False)
796
+
797
+ result = job.run()
798
+
799
+ assert len(result) == 3
800
+ assert set(result["code"].unique()) == {"002149.SZ", "000001.SZ", "600000.SH"}
801
+ assert all(result["date"] == "2024-10-11")
802
+
803
+ def test_error_handling_integration(self, mock_session):
804
+ """Test error handling in full workflow."""
805
+ # Simulate API returning None or error
806
+ mock_session.connection.moneyflow_dc.return_value = None
807
+
808
+ job = CapflowDC(session=mock_session, params={"trade_date": "20240913"}, cache=False)
809
+
810
+ result = job._run()
811
+
812
+ # Should handle gracefully with empty DataFrame
813
+ assert result.empty
814
+ assert list(result.columns) == TARGET.list_column_names()