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,683 @@
1
+ from datetime import 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 import Session
11
+ from xfintech.data.source.tushare.stock.industrycapflowths import (
12
+ KEY,
13
+ NAME,
14
+ SOURCE,
15
+ TARGET,
16
+ IndustryCapflowTHS,
17
+ )
18
+
19
+ # Fixtures
20
+
21
+
22
+ @pytest.fixture
23
+ def mock_session():
24
+ """Create a mock Tushare session"""
25
+ session = MagicMock(spec=Session)
26
+ session._credential = "test_token"
27
+ session.id = "test1234"
28
+ session.mode = "direct"
29
+ session.relay_url = None
30
+ session.relay_secret = None
31
+ session.connected = True
32
+
33
+ # Mock the connection object
34
+ mock_connection = MagicMock()
35
+ mock_connection.moneyflow_ind_ths = MagicMock()
36
+ session.connection = mock_connection
37
+
38
+ return session
39
+
40
+
41
+ # Test Class 1: Initialization Tests
42
+
43
+
44
+ class TestIndustryCapflowTHSInitialization:
45
+ """Test IndustryCapflowTHS class initialization and configuration."""
46
+
47
+ def test_name_constant(self):
48
+ """Test NAME constant is set correctly."""
49
+ assert NAME == "industrycapflowths"
50
+
51
+ def test_key_constant(self):
52
+ """Test KEY constant is set correctly."""
53
+ assert KEY == "/tushare/industrycapflowths"
54
+
55
+ def test_source_table_info(self):
56
+ """Test SOURCE TableInfo configuration."""
57
+ assert SOURCE.desc == "同花顺行业资金流向数据(tushare格式)"
58
+ assert SOURCE.meta["provider"] == "tushare"
59
+ assert SOURCE.meta["source"] == "moneyflow_ind_ths"
60
+ assert SOURCE.meta["type"] == "partitioned"
61
+ assert SOURCE.meta["scale"] == "crosssection"
62
+ # Verify key columns exist
63
+ column_names = SOURCE.list_column_names()
64
+ assert "trade_date" in column_names
65
+ assert "ts_code" in column_names
66
+ assert "industry" in column_names
67
+ assert "net_amount" in column_names
68
+
69
+ def test_target_table_info(self):
70
+ """Test TARGET TableInfo configuration."""
71
+ assert TARGET.desc == "同花顺行业资金流向数据(xfinbatch标准格式)"
72
+ assert TARGET.meta["key"] == KEY
73
+ assert TARGET.meta["name"] == NAME
74
+ assert TARGET.meta["type"] == "partitioned"
75
+ assert TARGET.meta["scale"] == "crosssection"
76
+ # Verify transformed columns exist
77
+ column_names = TARGET.list_column_names()
78
+ assert "code" in column_names
79
+ assert "date" in column_names
80
+ assert "datecode" in column_names
81
+ assert "percent_change" in column_names
82
+
83
+ def test_initialization_with_params(self, mock_session):
84
+ """Test IndustryCapflowTHS initialization with parameters."""
85
+ params = {"ts_code": "881267.TI", "start_date": "20240901", "end_date": "20240930"}
86
+ job = IndustryCapflowTHS(session=mock_session, params=params)
87
+
88
+ assert job.name == NAME
89
+ assert job.key == KEY
90
+ assert job.params.get("ts_code") == "881267.TI"
91
+
92
+ def test_initialization_with_optional_components(self, mock_session):
93
+ """Test initialization with optional coolant, retry, and cache."""
94
+ coolant = Coolant(interval=1.0)
95
+ retry = Retry(retry=3)
96
+ cache = Cache(path="/tmp/test_cache")
97
+
98
+ job = IndustryCapflowTHS(
99
+ session=mock_session,
100
+ params={"trade_date": "20240927"},
101
+ coolant=coolant,
102
+ retry=retry,
103
+ cache=cache,
104
+ )
105
+
106
+ assert job.coolant.interval == 1.0
107
+ assert job.retry.retry == 3
108
+ assert job.cache is not None
109
+ assert isinstance(job.cache, Cache)
110
+
111
+ def test_initialization_minimal(self, mock_session):
112
+ """Test minimal initialization with only required session."""
113
+ job = IndustryCapflowTHS(session=mock_session)
114
+
115
+ assert job.name == NAME
116
+ assert job.key == KEY
117
+
118
+
119
+ # Test Class 2: Transform Tests
120
+
121
+
122
+ class TestIndustryCapflowTHSTransform:
123
+ """Test the transform method that converts Tushare format to xfinbatch format."""
124
+
125
+ def test_transform_empty_dataframe(self, mock_session):
126
+ """Test transform with empty DataFrame."""
127
+ job = IndustryCapflowTHS(session=mock_session)
128
+
129
+ empty_df = pd.DataFrame()
130
+ result = job.transform(empty_df)
131
+
132
+ assert result.empty
133
+ assert list(result.columns) == TARGET.list_column_names()
134
+
135
+ def test_transform_none_input(self, mock_session):
136
+ """Test transform with None input."""
137
+ job = IndustryCapflowTHS(session=mock_session)
138
+
139
+ result = job.transform(None)
140
+
141
+ assert result.empty
142
+ assert list(result.columns) == TARGET.list_column_names()
143
+
144
+ def test_transform_basic_data(self, mock_session):
145
+ """Test transform with basic data."""
146
+ job = IndustryCapflowTHS(session=mock_session)
147
+
148
+ source_data = pd.DataFrame(
149
+ {
150
+ "trade_date": ["20240927"],
151
+ "ts_code": ["881267.TI"],
152
+ "industry": ["能源金属"],
153
+ "lead_stock": ["某股票"],
154
+ "close": [15021.70],
155
+ "pct_change": [2.5],
156
+ "company_num": [16],
157
+ "pct_change_stock": [3.2],
158
+ "close_price": [45.6],
159
+ "net_buy_amount": [490.00],
160
+ "net_sell_amount": [46.00],
161
+ "net_amount": [3.00],
162
+ }
163
+ )
164
+
165
+ result = job.transform(source_data)
166
+
167
+ assert not result.empty
168
+ assert len(result) == 1
169
+ assert result.iloc[0]["code"] == "881267.TI"
170
+ assert result.iloc[0]["date"] == "2024-09-27"
171
+ assert result.iloc[0]["datecode"] == "20240927"
172
+ assert result.iloc[0]["industry"] == "能源金属"
173
+ assert result.iloc[0]["percent_change"] == pytest.approx(2.5)
174
+ assert result.iloc[0]["company_num"] == 16
175
+
176
+ def test_transform_multiple_industries(self, mock_session):
177
+ """Test transform with multiple industries."""
178
+ job = IndustryCapflowTHS(session=mock_session)
179
+
180
+ source_data = pd.DataFrame(
181
+ {
182
+ "trade_date": ["20240927", "20240927", "20240926"],
183
+ "ts_code": ["881267.TI", "881273.TI", "881267.TI"],
184
+ "industry": ["能源金属", "白酒", "能源金属"],
185
+ "lead_stock": ["股票A", "股票B", "股票A"],
186
+ "close": [15021.70, 3251.85, 15000.50],
187
+ "pct_change": [2.5, 1.2, 1.8],
188
+ "company_num": [16, 20, 16],
189
+ "pct_change_stock": [3.2, 2.1, 2.5],
190
+ "close_price": [45.6, 123.4, 44.8],
191
+ "net_buy_amount": [490.00, 1890.00, 450.00],
192
+ "net_sell_amount": [46.00, 179.00, 40.00],
193
+ "net_amount": [3.00, 10.00, 5.00],
194
+ }
195
+ )
196
+
197
+ result = job.transform(source_data)
198
+
199
+ assert len(result) == 3
200
+ assert set(result["code"].unique()) == {"881267.TI", "881273.TI"}
201
+ # Verify sorted by code then date
202
+ assert result.iloc[0]["code"] == "881267.TI"
203
+ assert result.iloc[1]["code"] == "881267.TI"
204
+ assert result.iloc[2]["code"] == "881273.TI"
205
+
206
+ def test_transform_numeric_conversion(self, mock_session):
207
+ """Test that numeric fields are properly converted."""
208
+ job = IndustryCapflowTHS(session=mock_session)
209
+
210
+ source_data = pd.DataFrame(
211
+ {
212
+ "trade_date": ["20240927"],
213
+ "ts_code": ["881267.TI"],
214
+ "industry": ["能源金属"],
215
+ "lead_stock": ["某股票"],
216
+ "close": ["15021.70"], # String
217
+ "pct_change": ["2.5"], # String
218
+ "company_num": ["16"], # String
219
+ "pct_change_stock": ["3.2"], # String
220
+ "close_price": ["45.6"], # String
221
+ "net_buy_amount": ["490.00"], # String
222
+ "net_sell_amount": ["46.00"], # String
223
+ "net_amount": ["3.00"], # String
224
+ }
225
+ )
226
+
227
+ result = job.transform(source_data)
228
+
229
+ # Verify all numeric fields are actually numeric
230
+ assert isinstance(result.iloc[0]["percent_change"], (int, float))
231
+ assert isinstance(result.iloc[0]["close"], (int, float))
232
+ assert isinstance(result.iloc[0]["net_amount"], (int, float))
233
+ # company_num is numpy int64, verify it's numeric
234
+ assert pd.api.types.is_numeric_dtype(result["company_num"])
235
+ assert result.iloc[0]["percent_change"] == pytest.approx(2.5)
236
+ assert result.iloc[0]["company_num"] == 16
237
+
238
+ def test_transform_handles_invalid_values(self, mock_session):
239
+ """Test transform handles invalid numeric values gracefully."""
240
+ job = IndustryCapflowTHS(session=mock_session)
241
+
242
+ source_data = pd.DataFrame(
243
+ {
244
+ "trade_date": ["20240927"],
245
+ "ts_code": ["881267.TI"],
246
+ "industry": ["能源金属"],
247
+ "lead_stock": ["某股票"],
248
+ "close": ["invalid"],
249
+ "pct_change": ["N/A"],
250
+ "company_num": ["N/A"],
251
+ "pct_change_stock": ["3.2"],
252
+ "close_price": ["45.6"],
253
+ "net_buy_amount": ["490.00"],
254
+ "net_sell_amount": ["46.00"],
255
+ "net_amount": ["3.00"],
256
+ }
257
+ )
258
+
259
+ result = job.transform(source_data)
260
+
261
+ # Should handle gracefully with NaN
262
+ assert pd.isna(result.iloc[0]["percent_change"])
263
+ assert pd.isna(result.iloc[0]["close"])
264
+ assert result.iloc[0]["company_num"] == 0 # fillna(0) for integers
265
+
266
+ def test_transform_removes_duplicates(self, mock_session):
267
+ """Test that transform removes duplicate rows."""
268
+ job = IndustryCapflowTHS(session=mock_session)
269
+
270
+ # Create data with duplicates
271
+ source_data = pd.DataFrame(
272
+ {
273
+ "trade_date": ["20240927", "20240927"],
274
+ "ts_code": ["881267.TI", "881267.TI"],
275
+ "industry": ["能源金属", "能源金属"],
276
+ "lead_stock": ["某股票", "某股票"],
277
+ "close": [15021.70, 15021.70],
278
+ "pct_change": [2.5, 2.5],
279
+ "company_num": [16, 16],
280
+ "pct_change_stock": [3.2, 3.2],
281
+ "close_price": [45.6, 45.6],
282
+ "net_buy_amount": [490.00, 490.00],
283
+ "net_sell_amount": [46.00, 46.00],
284
+ "net_amount": [3.00, 3.00],
285
+ }
286
+ )
287
+
288
+ result = job.transform(source_data)
289
+
290
+ # Should only have 1 row after deduplication
291
+ assert len(result) == 1
292
+
293
+ def test_transform_date_formatting(self, mock_session):
294
+ """Test that dates are formatted correctly."""
295
+ job = IndustryCapflowTHS(session=mock_session)
296
+
297
+ source_data = pd.DataFrame(
298
+ {
299
+ "trade_date": ["20240927", "20240926"],
300
+ "ts_code": ["881267.TI", "881273.TI"],
301
+ "industry": ["能源金属", "白酒"],
302
+ "lead_stock": ["股票A", "股票B"],
303
+ "close": [15021.70, 3251.85],
304
+ "pct_change": [2.5, 1.2],
305
+ "company_num": [16, 20],
306
+ "pct_change_stock": [3.2, 2.1],
307
+ "close_price": [45.6, 123.4],
308
+ "net_buy_amount": [490.00, 1890.00],
309
+ "net_sell_amount": [46.00, 179.00],
310
+ "net_amount": [3.00, 10.00],
311
+ }
312
+ )
313
+
314
+ result = job.transform(source_data)
315
+
316
+ # Result is sorted by code, then date
317
+ assert result.iloc[0]["code"] == "881267.TI"
318
+ assert result.iloc[0]["date"] == "2024-09-27"
319
+ assert result.iloc[0]["datecode"] == "20240927"
320
+ assert result.iloc[1]["code"] == "881273.TI"
321
+ assert result.iloc[1]["date"] == "2024-09-26"
322
+ assert result.iloc[1]["datecode"] == "20240926"
323
+
324
+ def test_transform_all_target_columns_present(self, mock_session):
325
+ """Test that all target columns are present in transformed data."""
326
+ job = IndustryCapflowTHS(session=mock_session)
327
+
328
+ source_data = pd.DataFrame(
329
+ {
330
+ "trade_date": ["20240927"],
331
+ "ts_code": ["881267.TI"],
332
+ "industry": ["能源金属"],
333
+ "lead_stock": ["某股票"],
334
+ "close": [15021.70],
335
+ "pct_change": [2.5],
336
+ "company_num": [16],
337
+ "pct_change_stock": [3.2],
338
+ "close_price": [45.6],
339
+ "net_buy_amount": [490.00],
340
+ "net_sell_amount": [46.00],
341
+ "net_amount": [3.00],
342
+ }
343
+ )
344
+
345
+ result = job.transform(source_data)
346
+
347
+ expected_columns = TARGET.list_column_names()
348
+ assert list(result.columns) == expected_columns
349
+
350
+
351
+ # Test Class 3: Run Method Tests
352
+
353
+
354
+ class TestIndustryCapflowTHSRun:
355
+ """Test the _run method and parameter handling."""
356
+
357
+ def test_run_with_string_date_params(self, mock_session):
358
+ """Test _run with string date parameters."""
359
+ # Mock the API response
360
+ mock_session.connection.moneyflow_ind_ths.return_value = pd.DataFrame(
361
+ {
362
+ "trade_date": ["20240927"],
363
+ "ts_code": ["881267.TI"],
364
+ "industry": ["能源金属"],
365
+ "lead_stock": ["某股票"],
366
+ "close": [15021.70],
367
+ "pct_change": [2.5],
368
+ "company_num": [16],
369
+ "pct_change_stock": [3.2],
370
+ "close_price": [45.6],
371
+ "net_buy_amount": [490.00],
372
+ "net_sell_amount": [46.00],
373
+ "net_amount": [3.00],
374
+ }
375
+ )
376
+
377
+ job = IndustryCapflowTHS(
378
+ session=mock_session,
379
+ params={"ts_code": "881267.TI", "start_date": "20240901", "end_date": "20240930"},
380
+ cache=False,
381
+ )
382
+
383
+ result = job._run()
384
+
385
+ assert not result.empty
386
+ mock_session.connection.moneyflow_ind_ths.assert_called_once()
387
+ call_kwargs = mock_session.connection.moneyflow_ind_ths.call_args[1]
388
+ assert call_kwargs["ts_code"] == "881267.TI"
389
+ assert call_kwargs["start_date"] == "20240901"
390
+ assert call_kwargs["end_date"] == "20240930"
391
+
392
+ def test_run_with_datetime_params(self, mock_session):
393
+ """Test _run converts datetime parameters correctly."""
394
+ mock_session.connection.moneyflow_ind_ths.return_value = pd.DataFrame(
395
+ {
396
+ "trade_date": ["20240927"],
397
+ "ts_code": ["881267.TI"],
398
+ "industry": ["能源金属"],
399
+ "lead_stock": ["某股票"],
400
+ "close": [15021.70],
401
+ "pct_change": [2.5],
402
+ "company_num": [16],
403
+ "pct_change_stock": [3.2],
404
+ "close_price": [45.6],
405
+ "net_buy_amount": [490.00],
406
+ "net_sell_amount": [46.00],
407
+ "net_amount": [3.00],
408
+ }
409
+ )
410
+
411
+ job = IndustryCapflowTHS(
412
+ session=mock_session,
413
+ params={"trade_date": datetime(2024, 9, 27)},
414
+ cache=False,
415
+ )
416
+ result = job._run()
417
+ assert not result.empty
418
+
419
+ def test_run_with_trade_date(self, mock_session):
420
+ """Test _run with single trade_date parameter."""
421
+ mock_session.connection.moneyflow_ind_ths.return_value = pd.DataFrame(
422
+ {
423
+ "trade_date": ["20240927"],
424
+ "ts_code": ["881267.TI"],
425
+ "industry": ["能源金属"],
426
+ "lead_stock": ["某股票"],
427
+ "close": [15021.70],
428
+ "pct_change": [2.5],
429
+ "company_num": [16],
430
+ "pct_change_stock": [3.2],
431
+ "close_price": [45.6],
432
+ "net_buy_amount": [490.00],
433
+ "net_sell_amount": [46.00],
434
+ "net_amount": [3.00],
435
+ }
436
+ )
437
+
438
+ job = IndustryCapflowTHS(session=mock_session, params={"trade_date": "20240927"}, cache=False)
439
+
440
+ result = job._run()
441
+
442
+ assert not result.empty
443
+ call_kwargs = mock_session.connection.moneyflow_ind_ths.call_args[1]
444
+ assert call_kwargs["trade_date"] == "20240927"
445
+
446
+ def test_run_with_ts_code(self, mock_session):
447
+ """Test _run with ts_code parameter."""
448
+ mock_session.connection.moneyflow_ind_ths.return_value = pd.DataFrame(
449
+ {
450
+ "trade_date": ["20240927"],
451
+ "ts_code": ["881267.TI"],
452
+ "industry": ["能源金属"],
453
+ "lead_stock": ["某股票"],
454
+ "close": [15021.70],
455
+ "pct_change": [2.5],
456
+ "company_num": [16],
457
+ "pct_change_stock": [3.2],
458
+ "close_price": [45.6],
459
+ "net_buy_amount": [490.00],
460
+ "net_sell_amount": [46.00],
461
+ "net_amount": [3.00],
462
+ }
463
+ )
464
+
465
+ job = IndustryCapflowTHS(
466
+ session=mock_session,
467
+ params={"ts_code": "881267.TI", "start_date": "20240901", "end_date": "20240930"},
468
+ cache=False,
469
+ )
470
+
471
+ result = job._run()
472
+
473
+ assert not result.empty
474
+ assert result.iloc[0]["code"] == "881267.TI"
475
+
476
+ def test_run_empty_result(self, mock_session):
477
+ """Test _run handles empty API result."""
478
+ mock_session.connection.moneyflow_ind_ths.return_value = pd.DataFrame()
479
+
480
+ job = IndustryCapflowTHS(session=mock_session, params={"trade_date": "20240101"}, cache=False)
481
+
482
+ result = job._run()
483
+
484
+ assert result.empty
485
+ assert list(result.columns) == TARGET.list_column_names()
486
+
487
+ def test_run_api_called_with_correct_method(self, mock_session):
488
+ """Test that _run calls moneyflow_ind_ths API method."""
489
+ mock_session.connection.moneyflow_ind_ths.return_value = pd.DataFrame(
490
+ {
491
+ "trade_date": ["20240927"],
492
+ "ts_code": ["881267.TI"],
493
+ "industry": ["能源金属"],
494
+ "lead_stock": ["某股票"],
495
+ "close": [15021.70],
496
+ "pct_change": [2.5],
497
+ "company_num": [16],
498
+ "pct_change_stock": [3.2],
499
+ "close_price": [45.6],
500
+ "net_buy_amount": [490.00],
501
+ "net_sell_amount": [46.00],
502
+ "net_amount": [3.00],
503
+ }
504
+ )
505
+
506
+ job = IndustryCapflowTHS(session=mock_session, params={"trade_date": "20240927"}, cache=False)
507
+
508
+ job._run()
509
+
510
+ # Verify the correct API method was called
511
+ mock_session.connection.moneyflow_ind_ths.assert_called_once()
512
+
513
+
514
+ # Test Class 4: Cache Tests
515
+
516
+
517
+ class TestIndustryCapflowTHSCache:
518
+ """Test caching functionality."""
519
+
520
+ def test_cache_enabled(self, mock_session):
521
+ """Test that cache is used when enabled."""
522
+ cached_data = pd.DataFrame(
523
+ {
524
+ "code": ["881267.TI"],
525
+ "date": ["2024-09-27"],
526
+ "datecode": ["20240927"],
527
+ "industry": ["能源金属"],
528
+ "lead_stock": ["某股票"],
529
+ "close": [15021.70],
530
+ "percent_change": [2.5],
531
+ "company_num": [16],
532
+ "percent_change_stock": [3.2],
533
+ "close_price": [45.6],
534
+ "net_buy_amount": [490.00],
535
+ "net_sell_amount": [46.00],
536
+ "net_amount": [3.00],
537
+ }
538
+ )
539
+
540
+ job = IndustryCapflowTHS(session=mock_session, params={"trade_date": "20240927"}, cache=True)
541
+
542
+ # Mock _load_cache to return cached data
543
+ with patch.object(job, "_load_cache", return_value=cached_data):
544
+ result = job._run()
545
+
546
+ # Should return cached data without calling API
547
+ assert not result.empty
548
+ mock_session.connection.moneyflow_ind_ths.assert_not_called()
549
+
550
+ def test_cache_disabled(self, mock_session):
551
+ """Test that API is called when cache is disabled."""
552
+ mock_session.connection.moneyflow_ind_ths.return_value = pd.DataFrame(
553
+ {
554
+ "trade_date": ["20240927"],
555
+ "ts_code": ["881267.TI"],
556
+ "industry": ["能源金属"],
557
+ "lead_stock": ["某股票"],
558
+ "close": [15021.70],
559
+ "pct_change": [2.5],
560
+ "company_num": [16],
561
+ "pct_change_stock": [3.2],
562
+ "close_price": [45.6],
563
+ "net_buy_amount": [490.00],
564
+ "net_sell_amount": [46.00],
565
+ "net_amount": [3.00],
566
+ }
567
+ )
568
+
569
+ job = IndustryCapflowTHS(session=mock_session, params={"trade_date": "20240927"}, cache=False)
570
+
571
+ result = job._run()
572
+
573
+ # Should call API
574
+ assert not result.empty
575
+ mock_session.connection.moneyflow_ind_ths.assert_called_once()
576
+
577
+ def test_cache_save_called(self, mock_session):
578
+ """Test that cache save is called after successful run."""
579
+ mock_session.connection.moneyflow_ind_ths.return_value = pd.DataFrame(
580
+ {
581
+ "trade_date": ["20240927"],
582
+ "ts_code": ["881267.TI"],
583
+ "industry": ["能源金属"],
584
+ "lead_stock": ["某股票"],
585
+ "close": [15021.70],
586
+ "pct_change": [2.5],
587
+ "company_num": [16],
588
+ "pct_change_stock": [3.2],
589
+ "close_price": [45.6],
590
+ "net_buy_amount": [490.00],
591
+ "net_sell_amount": [46.00],
592
+ "net_amount": [3.00],
593
+ }
594
+ )
595
+
596
+ job = IndustryCapflowTHS(session=mock_session, params={"trade_date": "20240927"}, cache=True)
597
+
598
+ with patch.object(job, "_save_cache") as mock_save:
599
+ job._run()
600
+ # Should call save cache
601
+ mock_save.assert_called_once()
602
+
603
+
604
+ # Test Class 5: Integration Tests
605
+
606
+
607
+ class TestIndustryCapflowTHSIntegration:
608
+ """Integration tests combining multiple features."""
609
+
610
+ def test_full_workflow_single_industry(self, mock_session):
611
+ """Test complete workflow for single industry query."""
612
+ mock_session.connection.moneyflow_ind_ths.return_value = pd.DataFrame(
613
+ {
614
+ "trade_date": ["20240927", "20240926", "20240925"],
615
+ "ts_code": ["881267.TI", "881267.TI", "881267.TI"],
616
+ "industry": ["能源金属", "能源金属", "能源金属"],
617
+ "lead_stock": ["股票A", "股票A", "股票A"],
618
+ "close": [15021.70, 15000.50, 14980.30],
619
+ "pct_change": [2.5, 1.8, -0.5],
620
+ "company_num": [16, 16, 16],
621
+ "pct_change_stock": [3.2, 2.5, -0.8],
622
+ "close_price": [45.6, 44.8, 44.2],
623
+ "net_buy_amount": [490.00, 450.00, 380.00],
624
+ "net_sell_amount": [46.00, 40.00, 45.00],
625
+ "net_amount": [3.00, 5.00, -2.00],
626
+ }
627
+ )
628
+
629
+ job = IndustryCapflowTHS(
630
+ session=mock_session,
631
+ params={"ts_code": "881267.TI", "start_date": "20240925", "end_date": "20240927"},
632
+ cache=False,
633
+ )
634
+
635
+ result = job.run()
636
+
637
+ assert len(result) == 3
638
+ assert all(result["code"] == "881267.TI")
639
+ assert all(result["industry"] == "能源金属")
640
+ # Verify dates are in ascending order
641
+ assert result.iloc[0]["date"] == "2024-09-25"
642
+ assert result.iloc[1]["date"] == "2024-09-26"
643
+ assert result.iloc[2]["date"] == "2024-09-27"
644
+
645
+ def test_full_workflow_market_wide(self, mock_session):
646
+ """Test complete workflow for market-wide query."""
647
+ mock_session.connection.moneyflow_ind_ths.return_value = pd.DataFrame(
648
+ {
649
+ "trade_date": ["20240927", "20240927", "20240927"],
650
+ "ts_code": ["881267.TI", "881273.TI", "881279.TI"],
651
+ "industry": ["能源金属", "白酒", "光伏设备"],
652
+ "lead_stock": ["股票A", "股票B", "股票C"],
653
+ "close": [15021.70, 3251.85, 5940.19],
654
+ "pct_change": [2.5, 1.2, 3.5],
655
+ "company_num": [16, 20, 70],
656
+ "pct_change_stock": [3.2, 2.1, 4.2],
657
+ "close_price": [45.6, 123.4, 67.8],
658
+ "net_buy_amount": [490.00, 1890.00, 1120.00],
659
+ "net_sell_amount": [46.00, 179.00, 94.00],
660
+ "net_amount": [3.00, 10.00, 17.00],
661
+ }
662
+ )
663
+
664
+ job = IndustryCapflowTHS(session=mock_session, params={"trade_date": "20240927"}, cache=False)
665
+
666
+ result = job.run()
667
+
668
+ assert len(result) == 3
669
+ assert set(result["code"].unique()) == {"881267.TI", "881273.TI", "881279.TI"}
670
+ assert all(result["date"] == "2024-09-27")
671
+
672
+ def test_error_handling_integration(self, mock_session):
673
+ """Test error handling in full workflow."""
674
+ # Simulate API returning None or error
675
+ mock_session.connection.moneyflow_ind_ths.return_value = None
676
+
677
+ job = IndustryCapflowTHS(session=mock_session, params={"trade_date": "20240927"}, cache=False)
678
+
679
+ result = job._run()
680
+
681
+ # Should handle gracefully with empty DataFrame
682
+ assert result.empty
683
+ assert list(result.columns) == TARGET.list_column_names()