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,881 @@
1
+ """
2
+ Test suite for StockInfo class
3
+ Tests cover initialization, data fetching, transformation, date handling, and utility methods
4
+ """
5
+
6
+ from datetime import date, datetime
7
+ from unittest.mock import MagicMock, patch
8
+
9
+ import pandas as pd
10
+ import pytest
11
+
12
+ from xfintech.data.common.cache import Cache
13
+ from xfintech.data.common.coolant import Coolant
14
+ from xfintech.data.common.paginate import Paginate
15
+ from xfintech.data.common.params import Params
16
+ from xfintech.data.common.retry import Retry
17
+ from xfintech.data.source.tushare.session.session import Session
18
+ from xfintech.data.source.tushare.stock.stockinfo.constant import (
19
+ KEY,
20
+ NAME,
21
+ PAGINATE,
22
+ SOURCE,
23
+ TARGET,
24
+ )
25
+ from xfintech.data.source.tushare.stock.stockinfo.stockinfo import StockInfo
26
+
27
+ # ============================================================================
28
+ # Fixtures
29
+ # ============================================================================
30
+
31
+
32
+ @pytest.fixture
33
+ def mock_session():
34
+ """Create a mock Tushare session"""
35
+ session = MagicMock(spec=Session)
36
+ session._credential = "test_token"
37
+ session.id = "test1234"
38
+ session.mode = "direct"
39
+ session.relay_url = None
40
+ session.relay_secret = None
41
+ session.connected = True
42
+
43
+ # Mock the connection object
44
+ mock_connection = MagicMock()
45
+ mock_connection.bak_basic = MagicMock()
46
+ session.connection = mock_connection
47
+
48
+ return session
49
+
50
+
51
+ @pytest.fixture
52
+ def sample_source_data():
53
+ """Create sample source data in Tushare format"""
54
+ return pd.DataFrame(
55
+ {
56
+ "trade_date": ["20230101", "20230101", "20230102"],
57
+ "ts_code": ["000001.SZ", "000002.SZ", "600000.SH"],
58
+ "name": ["平安银行", "万科A", "浦发银行"],
59
+ "industry": ["银行", "房地产", "银行"],
60
+ "area": ["深圳", "深圳", "上海"],
61
+ "pe": [5.5, 10.2, 6.3],
62
+ "float_share": [100.5, 50.3, 80.2],
63
+ "total_share": [150.2, 80.5, 120.3],
64
+ "total_assets": [1000.5, 500.3, 800.2],
65
+ "liquid_assets": [500.2, 250.1, 400.1],
66
+ "fixed_assets": [300.1, 150.2, 250.3],
67
+ "reserved": [100.5, 50.3, 80.2],
68
+ "reserved_pershare": [0.67, 0.63, 0.67],
69
+ "eps": [1.2, 0.8, 1.1],
70
+ "bvps": [8.5, 6.3, 7.8],
71
+ "pb": [1.5, 1.8, 1.6],
72
+ "list_date": ["19910403", "19910129", "19991110"],
73
+ "undp": [50.2, 25.1, 40.1],
74
+ "per_undp": [0.33, 0.31, 0.33],
75
+ "rev_yoy": [5.5, 10.2, 6.3],
76
+ "profit_yoy": [8.5, 12.3, 9.1],
77
+ "gpr": [30.5, 25.3, 28.2],
78
+ "npr": [20.1, 18.5, 19.3],
79
+ "holder_num": [50000, 30000, 40000],
80
+ }
81
+ )
82
+
83
+
84
+ @pytest.fixture
85
+ def expected_transformed_data():
86
+ """Create expected transformed data"""
87
+ return pd.DataFrame(
88
+ {
89
+ "code": ["000001.SZ", "000002.SZ", "600000.SH"],
90
+ "name": ["平安银行", "万科A", "浦发银行"],
91
+ "date": ["2023-01-01", "2023-01-01", "2023-01-02"],
92
+ "datecode": ["20230101", "20230101", "20230102"],
93
+ "list_date": ["1991-04-03", "1991-01-29", "1999-11-10"],
94
+ "list_datecode": ["19910403", "19910129", "19991110"],
95
+ "industry": ["银行", "房地产", "银行"],
96
+ "area": ["深圳", "深圳", "上海"],
97
+ "pe": [5.5, 10.2, 6.3],
98
+ "float_share": [100.5, 50.3, 80.2],
99
+ "total_share": [150.2, 80.5, 120.3],
100
+ "total_assets": [1000.5, 500.3, 800.2],
101
+ "liquid_assets": [500.2, 250.1, 400.1],
102
+ "fixed_assets": [300.1, 150.2, 250.3],
103
+ "reserved": [100.5, 50.3, 80.2],
104
+ "reserved_pershare": [0.67, 0.63, 0.67],
105
+ "eps": [1.2, 0.8, 1.1],
106
+ "bvps": [8.5, 6.3, 7.8],
107
+ "pb": [1.5, 1.8, 1.6],
108
+ "undp": [50.2, 25.1, 40.1],
109
+ "per_undp": [0.33, 0.31, 0.33],
110
+ "rev_yoy": [5.5, 10.2, 6.3],
111
+ "profit_yoy": [8.5, 12.3, 9.1],
112
+ "gpr": [30.5, 25.3, 28.2],
113
+ "npr": [20.1, 18.5, 19.3],
114
+ "holder_num": [50000, 30000, 40000],
115
+ }
116
+ )
117
+
118
+
119
+ # ============================================================================
120
+ # Initialization Tests
121
+ # ============================================================================
122
+
123
+
124
+ def test_stockinfo_init_basic(mock_session):
125
+ """Test StockInfo initialization with minimal parameters"""
126
+ stockinfo = StockInfo(session=mock_session)
127
+
128
+ assert stockinfo.name == NAME
129
+ assert stockinfo.key == KEY
130
+ assert stockinfo.source == SOURCE
131
+ assert stockinfo.target == TARGET
132
+ assert isinstance(stockinfo.params, Params)
133
+ assert isinstance(stockinfo.coolant, Coolant)
134
+ assert isinstance(stockinfo.paginate, Paginate)
135
+ assert isinstance(stockinfo.retry, Retry)
136
+ assert stockinfo.paginate.pagesize == PAGINATE.pagesize
137
+ assert stockinfo.paginate.pagelimit == PAGINATE.pagelimit
138
+
139
+
140
+ def test_stockinfo_init_with_params_dict(mock_session):
141
+ """Test StockInfo initialization with params as dict"""
142
+ params = {"ts_code": "000001.SZ"}
143
+ stockinfo = StockInfo(session=mock_session, params=params)
144
+
145
+ assert isinstance(stockinfo.params, Params)
146
+ assert stockinfo.params.to_dict()["ts_code"] == "000001.SZ"
147
+
148
+
149
+ def test_stockinfo_init_with_params_object(mock_session):
150
+ """Test StockInfo initialization with params as Params object"""
151
+ params = Params(ts_code="000001.SZ")
152
+ stockinfo = StockInfo(session=mock_session, params=params)
153
+
154
+ assert isinstance(stockinfo.params, Params)
155
+ assert stockinfo.params.to_dict()["ts_code"] == "000001.SZ"
156
+
157
+
158
+ def test_stockinfo_init_with_trade_date_param(mock_session):
159
+ """Test StockInfo initialization with trade_date param"""
160
+ params = {"trade_date": "20230101"}
161
+ stockinfo = StockInfo(session=mock_session, params=params)
162
+
163
+ assert stockinfo.params.to_dict()["trade_date"] == "20230101"
164
+
165
+
166
+ def test_stockinfo_init_with_ts_code_param(mock_session):
167
+ """Test StockInfo initialization with ts_code param"""
168
+ params = {"ts_code": "000001.SZ"}
169
+ stockinfo = StockInfo(session=mock_session, params=params)
170
+
171
+ assert stockinfo.params.to_dict()["ts_code"] == "000001.SZ"
172
+
173
+
174
+ def test_stockinfo_init_with_cache_bool_true(mock_session):
175
+ """Test StockInfo initialization with cache as bool True"""
176
+ stockinfo = StockInfo(session=mock_session, cache=True)
177
+
178
+ assert isinstance(stockinfo.cache, Cache)
179
+
180
+
181
+ def test_stockinfo_init_with_cache_bool_false(mock_session):
182
+ """Test StockInfo initialization with cache as bool False"""
183
+ stockinfo = StockInfo(session=mock_session, cache=False)
184
+
185
+ assert stockinfo.cache is None
186
+
187
+
188
+ def test_stockinfo_init_with_cache_dict(mock_session):
189
+ """Test StockInfo initialization with cache as dict"""
190
+ cache_config = {"enabled": True, "ttl": 3600}
191
+ stockinfo = StockInfo(session=mock_session, cache=cache_config)
192
+
193
+ assert isinstance(stockinfo.cache, Cache)
194
+
195
+
196
+ def test_stockinfo_init_with_all_params(mock_session):
197
+ """Test StockInfo initialization with all parameters"""
198
+ params = {"ts_code": "000001.SZ", "trade_date": "20230101"}
199
+ coolant = {"enabled": True, "interval": 1.0}
200
+ retry = {"max_attempts": 3, "backoff": 2.0}
201
+ cache = {"enabled": True, "ttl": 3600}
202
+
203
+ stockinfo = StockInfo(
204
+ session=mock_session,
205
+ params=params,
206
+ coolant=coolant,
207
+ retry=retry,
208
+ cache=cache,
209
+ )
210
+
211
+ assert stockinfo.params.to_dict()["ts_code"] == "000001.SZ"
212
+ assert isinstance(stockinfo.coolant, Coolant)
213
+ assert isinstance(stockinfo.retry, Retry)
214
+ assert isinstance(stockinfo.cache, Cache)
215
+
216
+
217
+ def test_stockinfo_constants():
218
+ """Test that constants are properly defined"""
219
+ assert NAME == "stockinfo"
220
+ assert KEY == "/tushare/stockinfo"
221
+ assert PAGINATE.pagesize == 7000
222
+ assert SOURCE is not None
223
+ assert TARGET is not None
224
+
225
+
226
+ # ============================================================================
227
+ # Transform Tests
228
+ # ============================================================================
229
+
230
+
231
+ def test_stockinfo_transform_basic(mock_session, sample_source_data):
232
+ """Test basic transform functionality"""
233
+ stockinfo = StockInfo(session=mock_session)
234
+ result = stockinfo.transform(sample_source_data)
235
+
236
+ assert isinstance(result, pd.DataFrame)
237
+ assert len(result) == 3
238
+ assert list(result.columns) == stockinfo.target.list_column_names()
239
+
240
+
241
+ def test_stockinfo_transform_code_mapping(mock_session, sample_source_data):
242
+ """Test that ts_code is mapped to code"""
243
+ stockinfo = StockInfo(session=mock_session)
244
+ result = stockinfo.transform(sample_source_data)
245
+
246
+ assert "code" in result.columns
247
+ assert result["code"].tolist() == ["000001.SZ", "000002.SZ", "600000.SH"]
248
+
249
+
250
+ def test_stockinfo_transform_name_mapping(mock_session, sample_source_data):
251
+ """Test that name is preserved"""
252
+ stockinfo = StockInfo(session=mock_session)
253
+ result = stockinfo.transform(sample_source_data)
254
+
255
+ assert "name" in result.columns
256
+ assert result["name"].tolist() == ["平安银行", "万科A", "浦发银行"]
257
+
258
+
259
+ def test_stockinfo_transform_date_format(mock_session, sample_source_data):
260
+ """Test that trade_date is formatted correctly"""
261
+ stockinfo = StockInfo(session=mock_session)
262
+ result = stockinfo.transform(sample_source_data)
263
+
264
+ assert "date" in result.columns
265
+ assert result["date"].dtype == object
266
+ assert result["date"].iloc[0] == "2023-01-01"
267
+ assert result["date"].iloc[2] == "2023-01-02"
268
+
269
+
270
+ def test_stockinfo_transform_datecode_preserved(mock_session, sample_source_data):
271
+ """Test that datecode preserves original format"""
272
+ stockinfo = StockInfo(session=mock_session)
273
+ result = stockinfo.transform(sample_source_data)
274
+
275
+ assert "datecode" in result.columns
276
+ assert result["datecode"].tolist() == ["20230101", "20230101", "20230102"]
277
+
278
+
279
+ def test_stockinfo_transform_list_date_format(mock_session, sample_source_data):
280
+ """Test that list_date is formatted correctly"""
281
+ stockinfo = StockInfo(session=mock_session)
282
+ result = stockinfo.transform(sample_source_data)
283
+
284
+ assert "list_date" in result.columns
285
+ assert result["list_date"].dtype == object
286
+ assert result["list_date"].iloc[0] == "1991-04-03"
287
+
288
+
289
+ def test_stockinfo_transform_list_datecode_preserved(mock_session, sample_source_data):
290
+ """Test that list_datecode preserves original format"""
291
+ stockinfo = StockInfo(session=mock_session)
292
+ result = stockinfo.transform(sample_source_data)
293
+
294
+ assert "list_datecode" in result.columns
295
+ assert result["list_datecode"].tolist() == ["19910403", "19910129", "19991110"]
296
+
297
+
298
+ def test_stockinfo_transform_numeric_conversions(mock_session, sample_source_data):
299
+ """Test that numeric fields are converted correctly"""
300
+ stockinfo = StockInfo(session=mock_session)
301
+ result = stockinfo.transform(sample_source_data)
302
+
303
+ # Check numeric fields
304
+ numeric_fields = ["pe", "float_share", "total_share", "eps", "bvps", "pb"]
305
+ for field in numeric_fields:
306
+ assert field in result.columns
307
+ assert pd.api.types.is_numeric_dtype(result[field])
308
+
309
+
310
+ def test_stockinfo_transform_string_fields(mock_session, sample_source_data):
311
+ """Test that string fields are converted correctly"""
312
+ stockinfo = StockInfo(session=mock_session)
313
+ result = stockinfo.transform(sample_source_data)
314
+
315
+ assert result["industry"].tolist() == ["银行", "房地产", "银行"]
316
+ assert result["area"].tolist() == ["深圳", "深圳", "上海"]
317
+
318
+
319
+ def test_stockinfo_transform_empty_dataframe(mock_session):
320
+ """Test transform with empty DataFrame"""
321
+ stockinfo = StockInfo(session=mock_session)
322
+ empty_df = pd.DataFrame()
323
+ result = stockinfo.transform(empty_df)
324
+
325
+ assert isinstance(result, pd.DataFrame)
326
+ assert len(result) == 0
327
+ assert list(result.columns) == stockinfo.target.list_column_names()
328
+
329
+
330
+ def test_stockinfo_transform_none_input(mock_session):
331
+ """Test transform with None input"""
332
+ stockinfo = StockInfo(session=mock_session)
333
+ result = stockinfo.transform(None)
334
+
335
+ assert isinstance(result, pd.DataFrame)
336
+ assert len(result) == 0
337
+ assert list(result.columns) == stockinfo.target.list_column_names()
338
+
339
+
340
+ def test_stockinfo_transform_handles_invalid_dates(mock_session):
341
+ """Test that transform handles invalid dates gracefully"""
342
+ stockinfo = StockInfo(session=mock_session)
343
+ data = pd.DataFrame(
344
+ {
345
+ "trade_date": ["invalid", "20230101"],
346
+ "ts_code": ["000001.SZ", "000002.SZ"],
347
+ "name": ["平安银行", "万科A"],
348
+ "industry": ["银行", "房地产"],
349
+ "area": ["深圳", "深圳"],
350
+ "pe": [5.5, 10.2],
351
+ "float_share": [100.5, 50.3],
352
+ "total_share": [150.2, 80.5],
353
+ "total_assets": [1000.5, 500.3],
354
+ "liquid_assets": [500.2, 250.1],
355
+ "fixed_assets": [300.1, 150.2],
356
+ "reserved": [100.5, 50.3],
357
+ "reserved_pershare": [0.67, 0.63],
358
+ "eps": [1.2, 0.8],
359
+ "bvps": [8.5, 6.3],
360
+ "pb": [1.5, 1.8],
361
+ "list_date": ["19910403", "19910129"],
362
+ "undp": [50.2, 25.1],
363
+ "per_undp": [0.33, 0.31],
364
+ "rev_yoy": [5.5, 10.2],
365
+ "profit_yoy": [8.5, 12.3],
366
+ "gpr": [30.5, 25.3],
367
+ "npr": [20.1, 18.5],
368
+ "holder_num": [50000, 30000],
369
+ }
370
+ )
371
+ result = stockinfo.transform(data)
372
+
373
+ assert len(result) == 2
374
+ assert pd.isna(result.loc[result["code"] == "000001.SZ", "date"].iloc[0])
375
+
376
+
377
+ def test_stockinfo_transform_removes_duplicates(mock_session, sample_source_data):
378
+ """Test that transform removes duplicate rows"""
379
+ stockinfo = StockInfo(session=mock_session)
380
+ # Add duplicate rows
381
+ duplicated_data = pd.concat([sample_source_data, sample_source_data.iloc[[0]]])
382
+ result = stockinfo.transform(duplicated_data)
383
+
384
+ assert len(result) == 3 # Original length without duplicates
385
+
386
+
387
+ def test_stockinfo_transform_sorts_by_code(mock_session):
388
+ """Test that transform sorts by code"""
389
+ stockinfo = StockInfo(session=mock_session)
390
+ data = pd.DataFrame(
391
+ {
392
+ "trade_date": ["20230101", "20230101", "20230101"],
393
+ "ts_code": ["600000.SH", "000001.SZ", "000002.SZ"],
394
+ "name": ["浦发银行", "平安银行", "万科A"],
395
+ "industry": ["银行", "银行", "房地产"],
396
+ "area": ["上海", "深圳", "深圳"],
397
+ "pe": [6.3, 5.5, 10.2],
398
+ "float_share": [80.2, 100.5, 50.3],
399
+ "total_share": [120.3, 150.2, 80.5],
400
+ "total_assets": [800.2, 1000.5, 500.3],
401
+ "liquid_assets": [400.1, 500.2, 250.1],
402
+ "fixed_assets": [250.3, 300.1, 150.2],
403
+ "reserved": [80.2, 100.5, 50.3],
404
+ "reserved_pershare": [0.67, 0.67, 0.63],
405
+ "eps": [1.1, 1.2, 0.8],
406
+ "bvps": [7.8, 8.5, 6.3],
407
+ "pb": [1.6, 1.5, 1.8],
408
+ "list_date": ["19991110", "19910403", "19910129"],
409
+ "undp": [40.1, 50.2, 25.1],
410
+ "per_undp": [0.33, 0.33, 0.31],
411
+ "rev_yoy": [6.3, 5.5, 10.2],
412
+ "profit_yoy": [9.1, 8.5, 12.3],
413
+ "gpr": [28.2, 30.5, 25.3],
414
+ "npr": [19.3, 20.1, 18.5],
415
+ "holder_num": [40000, 50000, 30000],
416
+ }
417
+ )
418
+ result = stockinfo.transform(data)
419
+
420
+ # Should be sorted by code
421
+ assert result["code"].tolist() == ["000001.SZ", "000002.SZ", "600000.SH"]
422
+
423
+
424
+ def test_stockinfo_transform_resets_index(mock_session, sample_source_data):
425
+ """Test that transform resets the index"""
426
+ stockinfo = StockInfo(session=mock_session)
427
+ result = stockinfo.transform(sample_source_data)
428
+
429
+ assert result.index.tolist() == [0, 1, 2]
430
+
431
+
432
+ def test_stockinfo_transform_only_target_columns(mock_session, sample_source_data):
433
+ """Test that transform only includes target columns"""
434
+ stockinfo = StockInfo(session=mock_session)
435
+ result = stockinfo.transform(sample_source_data)
436
+
437
+ expected_columns = stockinfo.target.list_column_names()
438
+ assert list(result.columns) == expected_columns
439
+
440
+
441
+ # ============================================================================
442
+ # Run Tests
443
+ # ============================================================================
444
+
445
+
446
+ def test_stockinfo_run_with_cache_hit(mock_session, sample_source_data):
447
+ """Test run method with cache hit"""
448
+ stockinfo = StockInfo(session=mock_session, cache=True)
449
+
450
+ # Pre-populate cache
451
+ cached_data = stockinfo.transform(sample_source_data)
452
+ stockinfo.cache.set(stockinfo.params.identifier, cached_data)
453
+
454
+ # Run should return cached data
455
+ with patch.object(stockinfo, "_fetchall") as mock_fetchall:
456
+ result = stockinfo.run()
457
+ mock_fetchall.assert_not_called()
458
+
459
+ assert len(result) == 3
460
+
461
+
462
+ def test_stockinfo_run_basic_date(mock_session, sample_source_data):
463
+ """Test run with basic trade_date parameter"""
464
+ stockinfo = StockInfo(session=mock_session, params={"trade_date": "20230101"})
465
+
466
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
467
+ result = stockinfo.run()
468
+
469
+ assert len(result) == 3
470
+ assert isinstance(result, pd.DataFrame)
471
+
472
+
473
+ def test_stockinfo_run_with_trade_date_string(mock_session, sample_source_data):
474
+ """Test run with trade_date as string"""
475
+ stockinfo = StockInfo(session=mock_session, params={"trade_date": "20230101"})
476
+
477
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data) as mock_fetchall:
478
+ stockinfo.run()
479
+
480
+ # Check that trade_date is preserved as string
481
+ call_kwargs = mock_fetchall.call_args[1]
482
+ assert call_kwargs["trade_date"] == "20230101"
483
+
484
+
485
+ def test_stockinfo_run_with_trade_date_datetime(mock_session, sample_source_data):
486
+ """Test run with trade_date as datetime object"""
487
+ trade_date = datetime(2023, 1, 1)
488
+ stockinfo = StockInfo(session=mock_session, params={"trade_date": trade_date})
489
+
490
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data) as mock_fetchall:
491
+ stockinfo.run()
492
+
493
+ # Check that datetime was converted to string format YYYYMMDD
494
+ call_kwargs = mock_fetchall.call_args[1]
495
+ assert call_kwargs["trade_date"] == "20230101"
496
+
497
+
498
+ def test_stockinfo_run_with_ts_code_param(mock_session, sample_source_data):
499
+ """Test run with ts_code parameter"""
500
+ stockinfo = StockInfo(session=mock_session, params={"ts_code": "000001.SZ"})
501
+
502
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data) as mock_fetchall:
503
+ stockinfo.run()
504
+
505
+ call_kwargs = mock_fetchall.call_args[1]
506
+ assert call_kwargs["ts_code"] == "000001.SZ"
507
+
508
+
509
+ def test_stockinfo_run_adds_fields_param(mock_session, sample_source_data):
510
+ """Test that run adds fields parameter if not present"""
511
+ stockinfo = StockInfo(session=mock_session)
512
+
513
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data) as mock_fetchall:
514
+ stockinfo.run()
515
+
516
+ call_kwargs = mock_fetchall.call_args[1]
517
+ assert "fields" in call_kwargs
518
+ assert isinstance(call_kwargs["fields"], str)
519
+
520
+
521
+ def test_stockinfo_run_sets_cache(mock_session, sample_source_data):
522
+ """Test that run sets cache after fetching"""
523
+ stockinfo = StockInfo(session=mock_session, cache=True)
524
+
525
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
526
+ result = stockinfo.run()
527
+
528
+ cached = stockinfo.cache.get(stockinfo.params.identifier)
529
+ assert cached is not None
530
+ assert len(cached) == len(result)
531
+
532
+
533
+ def test_stockinfo_run_calls_transform(mock_session, sample_source_data):
534
+ """Test that run calls transform method"""
535
+ stockinfo = StockInfo(session=mock_session)
536
+
537
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
538
+ with patch.object(stockinfo, "transform", wraps=stockinfo.transform) as mock_transform:
539
+ stockinfo.run()
540
+ mock_transform.assert_called_once()
541
+
542
+
543
+ def test_stockinfo_run_with_trade_date_as_date(mock_session, sample_source_data):
544
+ """Test run with trade_date as date object (not datetime)"""
545
+ trade_date = date(2023, 1, 1)
546
+ stockinfo = StockInfo(session=mock_session, params={"trade_date": trade_date})
547
+
548
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data) as mock_fetchall:
549
+ stockinfo.run()
550
+
551
+ # Check that date was converted to string format YYYYMMDD
552
+ call_kwargs = mock_fetchall.call_args[1]
553
+ assert call_kwargs["trade_date"] == "20230101"
554
+
555
+
556
+ # ============================================================================
557
+ # List Methods Tests
558
+ # ============================================================================
559
+
560
+
561
+ def test_stockinfo_list_codes_basic(mock_session, sample_source_data):
562
+ """Test list_codes returns list of stock codes"""
563
+ stockinfo = StockInfo(session=mock_session, cache=True)
564
+
565
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
566
+ codes = stockinfo.list_codes()
567
+
568
+ assert isinstance(codes, list)
569
+ assert len(codes) == 3
570
+ assert "000001.SZ" in codes
571
+ assert "000002.SZ" in codes
572
+ assert "600000.SH" in codes
573
+
574
+
575
+ def test_stockinfo_list_codes_unique(mock_session):
576
+ """Test list_codes returns unique codes"""
577
+ stockinfo = StockInfo(session=mock_session, cache=False) # Disable cache
578
+
579
+ # Create data with duplicate codes
580
+ data = pd.DataFrame(
581
+ {
582
+ "trade_date": ["20230101", "20230102", "20230101"],
583
+ "ts_code": ["000001.SZ", "000001.SZ", "000002.SZ"],
584
+ "name": ["平安银行", "平安银行", "万科A"],
585
+ "industry": ["银行", "银行", "房地产"],
586
+ "area": ["深圳", "深圳", "深圳"],
587
+ "pe": [5.5, 5.6, 10.2],
588
+ "float_share": [100.5, 100.5, 50.3],
589
+ "total_share": [150.2, 150.2, 80.5],
590
+ "total_assets": [1000.5, 1000.5, 500.3],
591
+ "liquid_assets": [500.2, 500.2, 250.1],
592
+ "fixed_assets": [300.1, 300.1, 150.2],
593
+ "reserved": [100.5, 100.5, 50.3],
594
+ "reserved_pershare": [0.67, 0.67, 0.63],
595
+ "eps": [1.2, 1.2, 0.8],
596
+ "bvps": [8.5, 8.5, 6.3],
597
+ "pb": [1.5, 1.5, 1.8],
598
+ "list_date": ["19910403", "19910403", "19910129"],
599
+ "undp": [50.2, 50.2, 25.1],
600
+ "per_undp": [0.33, 0.33, 0.31],
601
+ "rev_yoy": [5.5, 5.5, 10.2],
602
+ "profit_yoy": [8.5, 8.5, 12.3],
603
+ "gpr": [30.5, 30.5, 25.3],
604
+ "npr": [20.1, 20.1, 18.5],
605
+ "holder_num": [50000, 50000, 30000],
606
+ }
607
+ )
608
+
609
+ with patch.object(stockinfo, "_fetchall", return_value=data):
610
+ codes = stockinfo.list_codes()
611
+
612
+ assert len(codes) == 2 # Only unique codes
613
+ assert codes == ["000001.SZ", "000002.SZ"]
614
+
615
+
616
+ def test_stockinfo_list_codes_sorted(mock_session, sample_source_data):
617
+ """Test list_codes returns sorted codes"""
618
+ stockinfo = StockInfo(session=mock_session, cache=True)
619
+
620
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
621
+ codes = stockinfo.list_codes()
622
+
623
+ assert codes == sorted(codes)
624
+
625
+
626
+ def test_stockinfo_list_codes_calls_run_when_not_cached(mock_session, sample_source_data):
627
+ """Test list_codes calls run when cache is empty"""
628
+ stockinfo = StockInfo(session=mock_session, cache=True)
629
+
630
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
631
+ codes = stockinfo.list_codes()
632
+
633
+ assert len(codes) > 0
634
+
635
+
636
+ def test_stockinfo_list_names_basic(mock_session, sample_source_data):
637
+ """Test list_names returns list of stock names"""
638
+ stockinfo = StockInfo(session=mock_session, cache=True)
639
+
640
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
641
+ names = stockinfo.list_names()
642
+
643
+ assert isinstance(names, list)
644
+ assert len(names) == 3
645
+ assert "平安银行" in names
646
+ assert "万科A" in names
647
+ assert "浦发银行" in names
648
+
649
+
650
+ def test_stockinfo_list_names_sorted(mock_session, sample_source_data):
651
+ """Test list_names returns sorted names"""
652
+ stockinfo = StockInfo(session=mock_session, cache=True)
653
+
654
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
655
+ names = stockinfo.list_names()
656
+
657
+ assert names == sorted(names)
658
+
659
+
660
+ def test_stockinfo_list_names_unique(mock_session):
661
+ """Test list_names returns unique names"""
662
+ stockinfo = StockInfo(session=mock_session, cache=False) # Disable cache for this test
663
+
664
+ # Create data with duplicate names
665
+ data = pd.DataFrame(
666
+ {
667
+ "trade_date": ["20230101", "20230102", "20230101"],
668
+ "ts_code": ["000001.SZ", "000001.SZ", "000002.SZ"],
669
+ "name": ["平安银行", "平安银行", "万科A"],
670
+ "industry": ["银行", "银行", "房地产"],
671
+ "area": ["深圳", "深圳", "深圳"],
672
+ "pe": [5.5, 5.6, 10.2],
673
+ "float_share": [100.5, 100.5, 50.3],
674
+ "total_share": [150.2, 150.2, 80.5],
675
+ "total_assets": [1000.5, 1000.5, 500.3],
676
+ "liquid_assets": [500.2, 500.2, 250.1],
677
+ "fixed_assets": [300.1, 300.1, 150.2],
678
+ "reserved": [100.5, 100.5, 50.3],
679
+ "reserved_pershare": [0.67, 0.67, 0.63],
680
+ "eps": [1.2, 1.2, 0.8],
681
+ "bvps": [8.5, 8.5, 6.3],
682
+ "pb": [1.5, 1.5, 1.8],
683
+ "list_date": ["19910403", "19910403", "19910129"],
684
+ "undp": [50.2, 50.2, 25.1],
685
+ "per_undp": [0.33, 0.33, 0.31],
686
+ "rev_yoy": [5.5, 5.5, 10.2],
687
+ "profit_yoy": [8.5, 8.5, 12.3],
688
+ "gpr": [30.5, 30.5, 25.3],
689
+ "npr": [20.1, 20.1, 18.5],
690
+ "holder_num": [50000, 50000, 30000],
691
+ }
692
+ )
693
+
694
+ with patch.object(stockinfo, "_fetchall", return_value=data):
695
+ names = stockinfo.list_names()
696
+
697
+ assert len(names) == 2 # Only unique names
698
+
699
+
700
+ def test_stockinfo_list_names_calls_run_when_not_cached(mock_session, sample_source_data):
701
+ """Test list_names calls run when cache is empty"""
702
+ stockinfo = StockInfo(session=mock_session, cache=True)
703
+
704
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
705
+ names = stockinfo.list_names()
706
+
707
+ assert len(names) > 0
708
+
709
+
710
+ # ============================================================================
711
+ # Integration Tests
712
+ # ============================================================================
713
+
714
+
715
+ def test_stockinfo_full_workflow(mock_session, sample_source_data):
716
+ """Test complete workflow from initialization to data retrieval"""
717
+ stockinfo = StockInfo(
718
+ session=mock_session,
719
+ params={"trade_date": "20230101"},
720
+ cache=True,
721
+ )
722
+
723
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data):
724
+ # First run - should fetch from source
725
+ result1 = stockinfo.run()
726
+ assert len(result1) == 3
727
+
728
+ # Second run - should use cache
729
+ result2 = stockinfo.run()
730
+ assert len(result2) == 3
731
+ assert result1.equals(result2)
732
+
733
+ # List methods should work with cached data
734
+ codes = stockinfo.list_codes()
735
+ names = stockinfo.list_names()
736
+ assert len(codes) == 3
737
+ assert len(names) == 3
738
+
739
+
740
+ def test_stockinfo_cache_persistence(mock_session, sample_source_data):
741
+ """Test that cache persists across runs"""
742
+ stockinfo = StockInfo(session=mock_session, cache=True)
743
+
744
+ with patch.object(stockinfo, "_load_cache", return_value=None) as mock_load:
745
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data) as mock_fetchall:
746
+ # First run - fetches data and caches it
747
+ result1 = stockinfo.run()
748
+ assert mock_fetchall.call_count == 1
749
+ assert mock_load.call_count == 1
750
+
751
+ # Second run - _load_cache still returns None because we're mocking it
752
+ result2 = stockinfo.run()
753
+ assert mock_fetchall.call_count == 2 # Called again since cache mock returns None
754
+ assert mock_load.call_count == 2
755
+
756
+ pd.testing.assert_frame_equal(result1, result2)
757
+
758
+
759
+ def test_stockinfo_params_identifier_uniqueness(mock_session):
760
+ """Test that different params create different cache keys"""
761
+ stockinfo1 = StockInfo(session=mock_session, params={"trade_date": "20230101"}, cache=True)
762
+ stockinfo2 = StockInfo(session=mock_session, params={"trade_date": "20230102"}, cache=True)
763
+
764
+ assert stockinfo1.params.identifier != stockinfo2.params.identifier
765
+
766
+
767
+ def test_stockinfo_different_date_params(mock_session, sample_source_data):
768
+ """Test handling of different date parameters"""
769
+ stockinfo1 = StockInfo(session=mock_session, params={"trade_date": "20230101"})
770
+ stockinfo2 = StockInfo(session=mock_session, params={"trade_date": datetime(2023, 1, 2)})
771
+
772
+ with patch.object(stockinfo1, "_fetchall", return_value=sample_source_data):
773
+ result1 = stockinfo1.run()
774
+
775
+ with patch.object(stockinfo2, "_fetchall", return_value=sample_source_data):
776
+ result2 = stockinfo2.run()
777
+
778
+ assert len(result1) == 3
779
+ assert len(result2) == 3
780
+
781
+
782
+ def test_stockinfo_empty_result_handling(mock_session):
783
+ """Test handling of empty results from API"""
784
+ stockinfo = StockInfo(session=mock_session)
785
+ empty_df = pd.DataFrame()
786
+
787
+ with patch.object(stockinfo, "_fetchall", return_value=empty_df):
788
+ result = stockinfo.run()
789
+
790
+ assert len(result) == 0
791
+ assert list(result.columns) == stockinfo.target.list_column_names()
792
+
793
+
794
+ def test_stockinfo_large_dataset_handling(mock_session):
795
+ """Test handling of large datasets"""
796
+ stockinfo = StockInfo(session=mock_session)
797
+
798
+ # Create a large dataset
799
+ large_data = pd.DataFrame(
800
+ {
801
+ "trade_date": ["20230101"] * 1000,
802
+ "ts_code": [f"{str(i).zfill(6)}.SZ" for i in range(1000)],
803
+ "name": [f"股票{i}" for i in range(1000)],
804
+ "industry": ["银行"] * 1000,
805
+ "area": ["深圳"] * 1000,
806
+ "pe": [5.5] * 1000,
807
+ "float_share": [100.5] * 1000,
808
+ "total_share": [150.2] * 1000,
809
+ "total_assets": [1000.5] * 1000,
810
+ "liquid_assets": [500.2] * 1000,
811
+ "fixed_assets": [300.1] * 1000,
812
+ "reserved": [100.5] * 1000,
813
+ "reserved_pershare": [0.67] * 1000,
814
+ "eps": [1.2] * 1000,
815
+ "bvps": [8.5] * 1000,
816
+ "pb": [1.5] * 1000,
817
+ "list_date": ["19910403"] * 1000,
818
+ "undp": [50.2] * 1000,
819
+ "per_undp": [0.33] * 1000,
820
+ "rev_yoy": [5.5] * 1000,
821
+ "profit_yoy": [8.5] * 1000,
822
+ "gpr": [30.5] * 1000,
823
+ "npr": [20.1] * 1000,
824
+ "holder_num": [50000] * 1000,
825
+ }
826
+ )
827
+
828
+ with patch.object(stockinfo, "_fetchall", return_value=large_data):
829
+ result = stockinfo.run()
830
+
831
+ assert len(result) == 1000
832
+
833
+
834
+ def test_stockinfo_without_cache(mock_session, sample_source_data):
835
+ """Test that stockinfo works correctly without cache"""
836
+ stockinfo = StockInfo(session=mock_session, cache=False)
837
+
838
+ with patch.object(stockinfo, "_fetchall", return_value=sample_source_data) as mock_fetchall:
839
+ stockinfo.run()
840
+ stockinfo.run()
841
+
842
+ # Should fetch twice (no caching)
843
+ assert mock_fetchall.call_count == 2
844
+
845
+
846
+ def test_stockinfo_handles_missing_numeric_fields(mock_session):
847
+ """Test handling of data with missing numeric fields"""
848
+ stockinfo = StockInfo(session=mock_session)
849
+
850
+ # Create data with some missing numeric fields
851
+ data = pd.DataFrame(
852
+ {
853
+ "trade_date": ["20230101"],
854
+ "ts_code": ["000001.SZ"],
855
+ "name": ["平安银行"],
856
+ "industry": ["银行"],
857
+ "area": ["深圳"],
858
+ "pe": [None],
859
+ "float_share": [100.5],
860
+ "total_share": [150.2],
861
+ "total_assets": [1000.5],
862
+ "liquid_assets": [500.2],
863
+ "fixed_assets": [300.1],
864
+ "reserved": [100.5],
865
+ "reserved_pershare": [0.67],
866
+ "eps": [1.2],
867
+ "bvps": [8.5],
868
+ "pb": [1.5],
869
+ "list_date": ["19910403"],
870
+ "undp": [50.2],
871
+ "per_undp": [0.33],
872
+ "rev_yoy": [5.5],
873
+ "profit_yoy": [8.5],
874
+ "gpr": [30.5],
875
+ "npr": [20.1],
876
+ "holder_num": [50000],
877
+ }
878
+ )
879
+
880
+ result = stockinfo.transform(data)
881
+ assert pd.isna(result["pe"].iloc[0])