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,829 @@
1
+ """
2
+ Test suite for Company class
3
+ Tests cover initialization, data fetching, transformation, and utility methods
4
+ """
5
+
6
+ from unittest.mock import MagicMock, patch
7
+
8
+ import pandas as pd
9
+ import pytest
10
+
11
+ from xfintech.data.common.cache import Cache
12
+ from xfintech.data.common.coolant import Coolant
13
+ from xfintech.data.common.paginate import Paginate
14
+ from xfintech.data.common.params import Params
15
+ from xfintech.data.common.retry import Retry
16
+ from xfintech.data.source.tushare.session.session import Session
17
+ from xfintech.data.source.tushare.stock.company.company import Company
18
+ from xfintech.data.source.tushare.stock.company.constant import (
19
+ EXCHANGES,
20
+ KEY,
21
+ NAME,
22
+ PAGINATE,
23
+ SOURCE,
24
+ TARGET,
25
+ )
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 (which is returned by ts.pro_api())
44
+ mock_connection = MagicMock()
45
+ mock_connection.stock_company = 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
+ "ts_code": ["000001.SZ", "000002.SZ", "600000.SH"],
57
+ "com_name": ["平安银行股份有限公司", "万科企业股份有限公司", "浦发银行"],
58
+ "com_id": ["91440300192448726P", "91440300192452569K", "9131000010000093XR"],
59
+ "exchange": ["SZSE", "SZSE", "SSE"],
60
+ "short_name": ["平安银行", "万科A", "浦发银行"],
61
+ "chairman": ["谢永林", "郁亮", "郑杨"],
62
+ "manager": ["谢永林", "祝九胜", "潘卫东"],
63
+ "secretary": ["周强", "朱旭", "谢伟"],
64
+ "reg_capital": [19405918.198, 1118226.3929, 29352374.0519],
65
+ "setup_date": ["19871212", "19840530", "19920928"],
66
+ "province": ["广东", "广东", "上海"],
67
+ "city": ["深圳", "深圳", "上海"],
68
+ "introduction": ["商业银行", "房地产开发", "商业银行"],
69
+ "website": ["http://bank.pingan.com", "http://www.vanke.com", "http://www.spdb.com.cn"],
70
+ "email": ["ir@pingan.com", "ir@vanke.com", "spdb@spdb.com.cn"],
71
+ "office": ["深圳市", "深圳市", "上海市"],
72
+ "employees": [35000, 50000, 45000],
73
+ "main_business": ["银行业务", "房地产业务", "银行业务"],
74
+ "business_scope": ["吸收存款", "房地产开发", "吸收存款"],
75
+ }
76
+ )
77
+
78
+
79
+ @pytest.fixture
80
+ def expected_transformed_data():
81
+ """Create expected transformed data"""
82
+ return pd.DataFrame(
83
+ {
84
+ "stockcode": ["000001.SZ", "000002.SZ", "600000.SH"],
85
+ "company_name": ["平安银行股份有限公司", "万科企业股份有限公司", "浦发银行"],
86
+ "company_id": ["91440300192448726P", "91440300192452569K", "9131000010000093XR"],
87
+ "exchange": ["SZSE", "SZSE", "SSE"],
88
+ "chairman": ["谢永林", "郁亮", "郑杨"],
89
+ "manager": ["谢永林", "祝九胜", "潘卫东"],
90
+ "secretary": ["周强", "朱旭", "谢伟"],
91
+ "reg_capital": [19405918.198, 1118226.3929, 29352374.0519],
92
+ "setup_date": ["1987-12-12", "1984-05-30", "1992-09-28"],
93
+ "province": ["广东", "广东", "上海"],
94
+ "city": ["深圳", "深圳", "上海"],
95
+ "introduction": ["商业银行", "房地产开发", "商业银行"],
96
+ "website": ["http://bank.pingan.com", "http://www.vanke.com", "http://www.spdb.com.cn"],
97
+ "email": ["ir@pingan.com", "ir@vanke.com", "spdb@spdb.com.cn"],
98
+ "office": ["深圳市", "深圳市", "上海市"],
99
+ "employees": [35000, 50000, 45000],
100
+ "main_business": ["银行业务", "房地产业务", "银行业务"],
101
+ "business_scope": ["吸收存款", "房地产开发", "吸收存款"],
102
+ }
103
+ )
104
+
105
+
106
+ # ============================================================================
107
+ # Initialization Tests
108
+ # ============================================================================
109
+
110
+
111
+ def test_company_init_basic(mock_session):
112
+ """Test Company initialization with minimal parameters"""
113
+ company = Company(session=mock_session)
114
+
115
+ assert company.name == NAME
116
+ assert company.key == KEY
117
+ assert company.source == SOURCE
118
+ assert company.target == TARGET
119
+ assert isinstance(company.params, Params)
120
+ assert isinstance(company.coolant, Coolant)
121
+ assert isinstance(company.paginate, Paginate)
122
+ assert isinstance(company.retry, Retry)
123
+ assert company.paginate.pagesize == PAGINATE.pagesize
124
+ assert company.paginate.pagelimit == PAGINATE.pagelimit
125
+
126
+
127
+ def test_company_init_with_params_dict(mock_session):
128
+ """Test Company initialization with params as dict"""
129
+ params = {"exchange": "SSE", "ts_code": "600000.SH"}
130
+ company = Company(session=mock_session, params=params)
131
+
132
+ assert company.params.exchange == "SSE"
133
+ assert company.params.ts_code == "600000.SH"
134
+
135
+
136
+ def test_company_init_with_params_object(mock_session):
137
+ """Test Company initialization with Params object"""
138
+ params = Params(exchange="SZSE", ts_code="000001.SZ")
139
+ company = Company(session=mock_session, params=params)
140
+
141
+ assert company.params.exchange == "SZSE"
142
+ assert company.params.ts_code == "000001.SZ"
143
+
144
+
145
+ def test_company_init_with_cache_bool_true(mock_session):
146
+ """Test Company initialization with cache=True"""
147
+ company = Company(session=mock_session, cache=True)
148
+
149
+ assert company.cache is not None
150
+ assert isinstance(company.cache, Cache)
151
+
152
+
153
+ def test_company_init_with_cache_bool_false(mock_session):
154
+ """Test Company initialization with cache=False"""
155
+ company = Company(session=mock_session, cache=False)
156
+
157
+ assert company.cache is None
158
+
159
+
160
+ def test_company_init_with_cache_dict(mock_session):
161
+ """Test Company initialization with cache as dict"""
162
+ cache_config = {"directory": "/tmp/cache"}
163
+ company = Company(session=mock_session, cache=cache_config)
164
+
165
+ assert company.cache is not None
166
+ assert isinstance(company.cache, Cache)
167
+
168
+
169
+ def test_company_init_with_all_params(mock_session):
170
+ """Test Company initialization with all parameters"""
171
+ company = Company(
172
+ session=mock_session,
173
+ params={"exchange": "SSE"},
174
+ coolant={"interval": 1.0},
175
+ retry={"max_retries": 3},
176
+ cache=True,
177
+ )
178
+
179
+ assert company.name == NAME
180
+ assert company.params.exchange == "SSE"
181
+ assert company.cache is not None
182
+ assert company.paginate.pagesize == PAGINATE.pagesize
183
+ assert company.paginate.pagelimit == PAGINATE.pagelimit
184
+
185
+
186
+ def test_company_constants():
187
+ """Test that constants are properly defined"""
188
+ assert NAME == "company"
189
+ assert KEY == "/tushare/stockcompany"
190
+ assert EXCHANGES == ["SSE", "SZSE", "BSE"]
191
+ assert SOURCE is not None
192
+ assert TARGET is not None
193
+
194
+
195
+ # ============================================================================
196
+ # Transform Method Tests
197
+ # ============================================================================
198
+
199
+
200
+ def test_company_transform_basic(mock_session, sample_source_data):
201
+ """Test basic data transformation"""
202
+ company = Company(session=mock_session)
203
+ result = company.transform(sample_source_data)
204
+
205
+ assert not result.empty
206
+ assert len(result) == 3
207
+ assert "stockcode" in result.columns
208
+ assert "company_name" in result.columns
209
+ assert "setup_date" in result.columns
210
+
211
+
212
+ def test_company_transform_stockcode_mapping(mock_session, sample_source_data):
213
+ """Test that ts_code is mapped to stockcode"""
214
+ company = Company(session=mock_session)
215
+ result = company.transform(sample_source_data)
216
+
217
+ assert result["stockcode"].tolist() == ["000001.SZ", "000002.SZ", "600000.SH"]
218
+
219
+
220
+ def test_company_transform_company_name_mapping(mock_session, sample_source_data):
221
+ """Test that com_name is mapped to company_name"""
222
+ company = Company(session=mock_session)
223
+ result = company.transform(sample_source_data)
224
+
225
+ assert "平安银行股份有限公司" in result["company_name"].values
226
+
227
+
228
+ def test_company_transform_date_format(mock_session, sample_source_data):
229
+ """Test that setup_date is converted from YYYYMMDD to YYYY-MM-DD"""
230
+ company = Company(session=mock_session)
231
+ result = company.transform(sample_source_data)
232
+
233
+ assert result["setup_date"].tolist() == ["1987-12-12", "1984-05-30", "1992-09-28"]
234
+
235
+
236
+ def test_company_transform_numeric_conversion(mock_session, sample_source_data):
237
+ """Test that reg_capital is converted to numeric"""
238
+ company = Company(session=mock_session)
239
+ result = company.transform(sample_source_data)
240
+
241
+ assert result["reg_capital"].dtype in [float, "float64"]
242
+ assert result["reg_capital"].iloc[0] == 19405918.198
243
+
244
+
245
+ def test_company_transform_employees_integer(mock_session, sample_source_data):
246
+ """Test that employees is converted to Int64"""
247
+ company = Company(session=mock_session)
248
+ result = company.transform(sample_source_data)
249
+
250
+ assert result["employees"].dtype == "Int64"
251
+ assert result["employees"].iloc[0] == 35000
252
+
253
+
254
+ def test_company_transform_empty_dataframe(mock_session):
255
+ """Test transform with empty DataFrame"""
256
+ company = Company(session=mock_session)
257
+ empty_df = pd.DataFrame()
258
+ result = company.transform(empty_df)
259
+
260
+ assert result.empty
261
+ assert list(result.columns) == TARGET.list_column_names()
262
+
263
+
264
+ def test_company_transform_none_input(mock_session):
265
+ """Test transform with None input"""
266
+ company = Company(session=mock_session)
267
+ result = company.transform(None)
268
+
269
+ assert result.empty
270
+ assert list(result.columns) == TARGET.list_column_names()
271
+
272
+
273
+ def test_company_transform_handles_invalid_dates(mock_session):
274
+ """Test transform handles invalid date formats"""
275
+ company = Company(session=mock_session)
276
+ data = pd.DataFrame(
277
+ {
278
+ "ts_code": ["000001.SZ"],
279
+ "com_name": ["Test Company"],
280
+ "com_id": ["12345"],
281
+ "exchange": ["SZSE"],
282
+ "short_name": ["Test"],
283
+ "chairman": ["John"],
284
+ "manager": ["Jane"],
285
+ "secretary": ["Bob"],
286
+ "reg_capital": [1000],
287
+ "setup_date": ["invalid"], # Invalid date
288
+ "province": ["Test"],
289
+ "city": ["Test"],
290
+ "introduction": ["Test"],
291
+ "website": ["http://test.com"],
292
+ "email": ["test@test.com"],
293
+ "office": ["Test"],
294
+ "employees": [100],
295
+ "main_business": ["Test"],
296
+ "business_scope": ["Test"],
297
+ }
298
+ )
299
+
300
+ result = company.transform(data)
301
+ # Should handle error with coerce
302
+ assert pd.isna(result["setup_date"].iloc[0]) or result["setup_date"].iloc[0] == "NaT"
303
+
304
+
305
+ def test_company_transform_handles_invalid_numeric(mock_session):
306
+ """Test transform handles invalid numeric values"""
307
+ company = Company(session=mock_session)
308
+ data = pd.DataFrame(
309
+ {
310
+ "ts_code": ["000001.SZ"],
311
+ "com_name": ["Test Company"],
312
+ "com_id": ["12345"],
313
+ "exchange": ["SZSE"],
314
+ "short_name": ["Test"],
315
+ "chairman": ["John"],
316
+ "manager": ["Jane"],
317
+ "secretary": ["Bob"],
318
+ "reg_capital": ["invalid"], # Invalid number
319
+ "setup_date": ["20200101"],
320
+ "province": ["Test"],
321
+ "city": ["Test"],
322
+ "introduction": ["Test"],
323
+ "website": ["http://test.com"],
324
+ "email": ["test@test.com"],
325
+ "office": ["Test"],
326
+ "employees": ["invalid"], # Invalid number
327
+ "main_business": ["Test"],
328
+ "business_scope": ["Test"],
329
+ }
330
+ )
331
+
332
+ result = company.transform(data)
333
+ # Should handle error with coerce
334
+ assert pd.isna(result["reg_capital"].iloc[0])
335
+ assert pd.isna(result["employees"].iloc[0])
336
+
337
+
338
+ def test_company_transform_removes_duplicates(mock_session):
339
+ """Test that transform removes duplicate rows"""
340
+ company = Company(session=mock_session)
341
+ data = pd.DataFrame(
342
+ {
343
+ "ts_code": ["000001.SZ", "000001.SZ"], # Duplicate
344
+ "com_name": ["Test", "Test"],
345
+ "com_id": ["123", "123"],
346
+ "exchange": ["SZSE", "SZSE"],
347
+ "short_name": ["Test", "Test"],
348
+ "chairman": ["A", "A"],
349
+ "manager": ["B", "B"],
350
+ "secretary": ["C", "C"],
351
+ "reg_capital": [1000, 1000],
352
+ "setup_date": ["20200101", "20200101"],
353
+ "province": ["Test", "Test"],
354
+ "city": ["Test", "Test"],
355
+ "introduction": ["Test", "Test"],
356
+ "website": ["http://test.com", "http://test.com"],
357
+ "email": ["test@test.com", "test@test.com"],
358
+ "office": ["Test", "Test"],
359
+ "employees": [100, 100],
360
+ "main_business": ["Test", "Test"],
361
+ "business_scope": ["Test", "Test"],
362
+ }
363
+ )
364
+
365
+ result = company.transform(data)
366
+ assert len(result) == 1
367
+
368
+
369
+ def test_company_transform_sorts_by_stockcode(mock_session, sample_source_data):
370
+ """Test that result is sorted by stockcode"""
371
+ company = Company(session=mock_session)
372
+ # Shuffle the data
373
+ shuffled = sample_source_data.sample(frac=1).reset_index(drop=True)
374
+ result = company.transform(shuffled)
375
+
376
+ # Should be sorted
377
+ assert result["stockcode"].tolist() == sorted(result["stockcode"].tolist())
378
+
379
+
380
+ def test_company_transform_resets_index(mock_session, sample_source_data):
381
+ """Test that result has reset index"""
382
+ company = Company(session=mock_session)
383
+ result = company.transform(sample_source_data)
384
+
385
+ assert result.index.tolist() == list(range(len(result)))
386
+
387
+
388
+ def test_company_transform_only_target_columns(mock_session, sample_source_data):
389
+ """Test that only target columns are in result"""
390
+ company = Company(session=mock_session)
391
+ result = company.transform(sample_source_data)
392
+
393
+ expected_cols = set(TARGET.list_column_names())
394
+ actual_cols = set(result.columns)
395
+ assert actual_cols == expected_cols
396
+
397
+
398
+ # ============================================================================
399
+ # _run Method Tests
400
+ # ============================================================================
401
+
402
+
403
+ def test_company_run_with_cache_hit(mock_session):
404
+ """Test _run returns cached data when available"""
405
+ company = Company(session=mock_session, cache=True)
406
+
407
+ # Set up cached data
408
+ cached_df = pd.DataFrame({"stockcode": ["000001.SZ"]})
409
+ company.cache.set(company.params.identifier, cached_df)
410
+
411
+ result = company._run()
412
+
413
+ # Should return cached data without calling API
414
+ assert result.equals(cached_df)
415
+
416
+
417
+ def test_company_run_without_exchange_param(mock_session, sample_source_data):
418
+ """Test _run queries all exchanges when exchange not specified"""
419
+ company = Company(session=mock_session)
420
+
421
+ # Mock _fetchall to return sample data
422
+ with patch.object(company, "_fetchall", return_value=sample_source_data):
423
+ with patch.object(company, "transform", return_value=sample_source_data):
424
+ company._run()
425
+
426
+ # Should call _fetchall for each exchange
427
+ assert company._fetchall.call_count == len(EXCHANGES)
428
+
429
+
430
+ def test_company_run_with_exchange_param(mock_session, sample_source_data):
431
+ """Test _run queries specific exchange when provided"""
432
+ company = Company(session=mock_session, params={"exchange": "SSE"})
433
+
434
+ # Mock _fetchall
435
+ with patch.object(company, "_fetchall", return_value=sample_source_data):
436
+ with patch.object(company, "transform", return_value=sample_source_data):
437
+ company._run()
438
+
439
+ # Should call _fetchall only once
440
+ assert company._fetchall.call_count == 1
441
+
442
+
443
+ def test_company_run_adds_fields_param(mock_session, sample_source_data):
444
+ """Test _run adds fields parameter if not provided"""
445
+ company = Company(session=mock_session)
446
+
447
+ with patch.object(company, "_fetchall", return_value=sample_source_data):
448
+ with patch.object(company, "transform", return_value=sample_source_data):
449
+ company._run()
450
+
451
+ # Check that fields were added to params
452
+ call_args = company._fetchall.call_args
453
+ assert "fields" in call_args[1]
454
+
455
+
456
+ def test_company_run_preserves_fields_param(mock_session, sample_source_data):
457
+ """Test _run preserves existing fields parameter"""
458
+ custom_fields = "ts_code,com_name,exchange"
459
+ company = Company(session=mock_session, params={"fields": custom_fields})
460
+
461
+ with patch.object(company, "_fetchall", return_value=sample_source_data):
462
+ with patch.object(company, "transform", return_value=sample_source_data):
463
+ company._run()
464
+
465
+ # Should use provided fields
466
+ assert company.params.fields == custom_fields
467
+
468
+
469
+ def test_company_run_sets_cache(mock_session, sample_source_data):
470
+ """Test _run saves result to cache"""
471
+ company = Company(session=mock_session, cache=True)
472
+
473
+ with patch.object(company, "_fetchall", return_value=sample_source_data):
474
+ with patch.object(company, "transform", return_value=sample_source_data):
475
+ company._run()
476
+
477
+ # Check cache was set
478
+ cached = company.cache.get(company.params.identifier)
479
+ assert cached is not None
480
+
481
+
482
+ def test_company_run_calls_transform(mock_session, sample_source_data):
483
+ """Test _run calls transform method"""
484
+ company = Company(session=mock_session)
485
+
486
+ with patch.object(company, "_fetchall", return_value=sample_source_data):
487
+ with patch.object(company, "transform", return_value=sample_source_data) as mock_transform:
488
+ company._run()
489
+
490
+ # Transform should be called
491
+ assert mock_transform.called
492
+
493
+
494
+ def test_company_run_concatenates_multiple_exchanges(mock_session, sample_source_data):
495
+ """Test _run concatenates data from multiple exchanges"""
496
+ company = Company(session=mock_session)
497
+
498
+ # Create different data for each exchange
499
+ sse_data = sample_source_data[sample_source_data["exchange"] == "SSE"]
500
+ szse_data = sample_source_data[sample_source_data["exchange"] == "SZSE"]
501
+
502
+ call_count = [0]
503
+
504
+ def mock_fetchall(*args, **kwargs):
505
+ call_count[0] += 1
506
+ if call_count[0] == 1:
507
+ return sse_data
508
+ elif call_count[0] == 2:
509
+ return szse_data
510
+ else:
511
+ return pd.DataFrame()
512
+
513
+ with patch.object(company, "_fetchall", side_effect=mock_fetchall):
514
+ with patch.object(company, "transform", side_effect=lambda x: x):
515
+ result = company._run()
516
+
517
+ # Should have data from multiple exchanges
518
+ assert len(result) >= 1
519
+
520
+
521
+ # ============================================================================
522
+ # list_codes Method Tests
523
+ # ============================================================================
524
+
525
+
526
+ def test_company_list_codes_basic(mock_session, sample_source_data):
527
+ """Test list_codes returns list of stock codes"""
528
+ company = Company(session=mock_session, cache=True)
529
+
530
+ # Mock the run to return sample data
531
+ transformed = company.transform(sample_source_data)
532
+ company.cache.set(company.params.identifier, transformed)
533
+
534
+ codes = company.list_codes()
535
+
536
+ assert isinstance(codes, list)
537
+ assert len(codes) == 3
538
+ assert "000001.SZ" in codes
539
+
540
+
541
+ def test_company_list_codes_unique(mock_session):
542
+ """Test list_codes returns unique codes"""
543
+ company = Company(session=mock_session, cache=True)
544
+
545
+ # Create data with duplicates
546
+ df = pd.DataFrame(
547
+ {
548
+ "stockcode": ["000001.SZ", "000001.SZ", "000002.SZ"],
549
+ }
550
+ )
551
+ company.cache.set(company.params.identifier, df)
552
+
553
+ codes = company.list_codes()
554
+
555
+ assert len(codes) == 2 # Only unique codes
556
+
557
+
558
+ def test_company_list_codes_calls_run_when_not_cached(mock_session, sample_source_data):
559
+ """Test list_codes calls run() when data not in cache"""
560
+ company = Company(session=mock_session)
561
+
562
+ with patch.object(company, "run", return_value=company.transform(sample_source_data)):
563
+ codes = company.list_codes()
564
+
565
+ # run should have been called
566
+ company.run.assert_called_once()
567
+ assert len(codes) == 3
568
+
569
+
570
+ def test_company_list_codes_uses_cache(mock_session, sample_source_data):
571
+ """Test list_codes uses cached data when available"""
572
+ company = Company(session=mock_session, cache=True)
573
+
574
+ transformed = company.transform(sample_source_data)
575
+ company.cache.set(company.params.identifier, transformed)
576
+
577
+ # Mock _fetchall to verify it's not called when cache exists
578
+ with patch.object(company, "_fetchall") as mock_fetch:
579
+ codes = company.list_codes()
580
+
581
+ # _fetchall should NOT be called when cache exists
582
+ mock_fetch.assert_not_called()
583
+ assert len(codes) == 3
584
+
585
+
586
+ # ============================================================================
587
+ # list_names Method Tests
588
+ # ============================================================================
589
+
590
+
591
+ def test_company_list_names_basic(mock_session, sample_source_data):
592
+ """Test list_names returns list of company names"""
593
+ company = Company(session=mock_session, cache=True)
594
+
595
+ transformed = company.transform(sample_source_data)
596
+ company.cache.set(company.params.identifier, transformed)
597
+
598
+ names = company.list_names()
599
+
600
+ assert isinstance(names, list)
601
+ assert len(names) == 3
602
+ assert "平安银行股份有限公司" in names
603
+
604
+
605
+ def test_company_list_names_sorted(mock_session, sample_source_data):
606
+ """Test list_names returns sorted list"""
607
+ company = Company(session=mock_session, cache=True)
608
+
609
+ transformed = company.transform(sample_source_data)
610
+ company.cache.set(company.params.identifier, transformed)
611
+
612
+ names = company.list_names()
613
+
614
+ assert names == sorted(names)
615
+
616
+
617
+ def test_company_list_names_unique(mock_session):
618
+ """Test list_names returns unique names"""
619
+ company = Company(session=mock_session, cache=True)
620
+
621
+ df = pd.DataFrame(
622
+ {
623
+ "company_name": ["平安银行", "平安银行", "万科A"],
624
+ }
625
+ )
626
+ company.cache.set(company.params.identifier, df)
627
+
628
+ names = company.list_names()
629
+
630
+ assert len(names) == 2 # Only unique names
631
+
632
+
633
+ def test_company_list_names_calls_run_when_not_cached(mock_session, sample_source_data):
634
+ """Test list_names calls run() when data not in cache"""
635
+ company = Company(session=mock_session)
636
+
637
+ with patch.object(company, "run", return_value=company.transform(sample_source_data)):
638
+ names = company.list_names()
639
+
640
+ company.run.assert_called_once()
641
+ assert len(names) == 3
642
+
643
+
644
+ def test_company_list_names_uses_cache(mock_session, sample_source_data):
645
+ """Test list_names uses cached data when available"""
646
+ company = Company(session=mock_session, cache=True)
647
+
648
+ transformed = company.transform(sample_source_data)
649
+ company.cache.set(company.params.identifier, transformed)
650
+
651
+ # Mock _fetchall to verify it's not called when cache exists
652
+ with patch.object(company, "_fetchall") as mock_fetch:
653
+ names = company.list_names()
654
+
655
+ # _fetchall should NOT be called when cache exists
656
+ mock_fetch.assert_not_called()
657
+ assert len(names) == 3
658
+
659
+
660
+ # ============================================================================
661
+ # Integration Tests
662
+ # ============================================================================
663
+
664
+
665
+ def test_company_full_workflow(mock_session, sample_source_data):
666
+ """Test complete workflow from initialization to data retrieval"""
667
+ company = Company(
668
+ session=mock_session,
669
+ params={"exchange": "SZSE"},
670
+ cache=True,
671
+ )
672
+
673
+ with patch.object(company, "_fetchall", return_value=sample_source_data):
674
+ # Run the job
675
+ result = company.run()
676
+
677
+ assert not result.empty
678
+ assert "stockcode" in result.columns
679
+
680
+ # Get codes and names
681
+ codes = company.list_codes()
682
+ names = company.list_names()
683
+
684
+ assert len(codes) > 0
685
+ assert len(names) > 0
686
+
687
+
688
+ def test_company_multiple_exchanges_integration(mock_session, sample_source_data):
689
+ """Test fetching data from multiple exchanges"""
690
+ company = Company(session=mock_session, cache=True)
691
+
692
+ with patch.object(company, "_fetchall", return_value=sample_source_data):
693
+ result = company.run()
694
+
695
+ # Should have data from multiple exchanges
696
+ unique_exchanges = result["exchange"].unique()
697
+ assert len(unique_exchanges) > 1
698
+
699
+
700
+ def test_company_cache_persistence(mock_session, sample_source_data):
701
+ """Test that cache persists across method calls"""
702
+ company = Company(session=mock_session, cache=True)
703
+
704
+ with patch.object(company, "_load_cache", return_value=None) as mock_load:
705
+ with patch.object(company, "_fetchall", return_value=sample_source_data) as mock_fetch:
706
+ # First call - fetches data and caches it
707
+ result1 = company.run()
708
+ assert mock_fetch.call_count == len(EXCHANGES) # Once per exchange
709
+ assert mock_load.call_count == 1
710
+
711
+ # Second call - _load_cache still returns None, so _fetchall called again
712
+ result2 = company.run()
713
+ assert mock_fetch.call_count == len(EXCHANGES) * 2 # Called again for each exchange
714
+ assert mock_load.call_count == 2
715
+
716
+ pd.testing.assert_frame_equal(result1, result2)
717
+
718
+
719
+ def test_company_params_identifier_uniqueness(mock_session):
720
+ """Test that different params produce different cache keys"""
721
+ company1 = Company(session=mock_session, params={"exchange": "SSE"}, cache=True)
722
+ company2 = Company(session=mock_session, params={"exchange": "SZSE"}, cache=True)
723
+
724
+ assert company1.params.identifier != company2.params.identifier
725
+
726
+
727
+ # ============================================================================
728
+ # Edge Case Tests
729
+ # ============================================================================
730
+
731
+
732
+ def test_company_empty_result_handling(mock_session):
733
+ """Test handling of empty API results"""
734
+ company = Company(session=mock_session)
735
+
736
+ empty_df = pd.DataFrame()
737
+ with patch.object(company, "_fetchall", return_value=empty_df):
738
+ result = company._run()
739
+
740
+ assert result.empty
741
+ assert list(result.columns) == TARGET.list_column_names()
742
+
743
+
744
+ def test_company_large_dataset_handling(mock_session):
745
+ """Test handling of large datasets"""
746
+ company = Company(session=mock_session)
747
+
748
+ # Create large dataset
749
+ large_data = pd.DataFrame(
750
+ {
751
+ "ts_code": [f"{i:06d}.SZ" for i in range(5000)],
752
+ "com_name": [f"Company {i}" for i in range(5000)],
753
+ "com_id": [f"ID{i}" for i in range(5000)],
754
+ "exchange": ["SZSE"] * 5000,
755
+ "short_name": [f"C{i}" for i in range(5000)],
756
+ "chairman": ["A"] * 5000,
757
+ "manager": ["B"] * 5000,
758
+ "secretary": ["C"] * 5000,
759
+ "reg_capital": [1000.0] * 5000,
760
+ "setup_date": ["20200101"] * 5000,
761
+ "province": ["Test"] * 5000,
762
+ "city": ["Test"] * 5000,
763
+ "introduction": ["Test"] * 5000,
764
+ "website": ["http://test.com"] * 5000,
765
+ "email": ["test@test.com"] * 5000,
766
+ "office": ["Test"] * 5000,
767
+ "employees": [100] * 5000,
768
+ "main_business": ["Test"] * 5000,
769
+ "business_scope": ["Test"] * 5000,
770
+ }
771
+ )
772
+
773
+ result = company.transform(large_data)
774
+
775
+ assert len(result) == 5000
776
+ assert not result.empty
777
+
778
+
779
+ def test_company_special_characters_in_data(mock_session):
780
+ """Test handling of special characters in company data"""
781
+ company = Company(session=mock_session)
782
+
783
+ data = pd.DataFrame(
784
+ {
785
+ "ts_code": ["000001.SZ"],
786
+ "com_name": ["公司名称(中文)& Special <Chars>"],
787
+ "com_id": ["123-456"],
788
+ "exchange": ["SZSE"],
789
+ "short_name": ["特殊@字符"],
790
+ "chairman": ["董事长"],
791
+ "manager": ["总经理"],
792
+ "secretary": ["董秘"],
793
+ "reg_capital": [1000.0],
794
+ "setup_date": ["20200101"],
795
+ "province": ["广东"],
796
+ "city": ["深圳"],
797
+ "introduction": ["公司介绍 & info"],
798
+ "website": ["http://www.测试.com"],
799
+ "email": ["test@test.com"],
800
+ "office": ["办公室地址"],
801
+ "employees": [100],
802
+ "main_business": ["主要业务"],
803
+ "business_scope": ["经营范围"],
804
+ }
805
+ )
806
+
807
+ result = company.transform(data)
808
+
809
+ assert len(result) == 1
810
+ assert "特殊@字符" in result["chairman"].values[0] or "董事长" in result["chairman"].values[0]
811
+
812
+
813
+ def test_company_without_cache(mock_session, sample_source_data):
814
+ """Test Company works correctly without cache"""
815
+ company = Company(session=mock_session, cache=False)
816
+
817
+ assert company.cache is None
818
+
819
+ with patch.object(company, "_fetchall", return_value=sample_source_data):
820
+ result = company.run()
821
+
822
+ assert not result.empty
823
+
824
+ # list_codes and list_names should still work
825
+ codes = company.list_codes()
826
+ names = company.list_names()
827
+
828
+ assert len(codes) > 0
829
+ assert len(names) > 0