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,695 @@
1
+ from datetime import date, datetime
2
+ from unittest.mock import MagicMock, patch
3
+
4
+ import pandas as pd
5
+ import pytest
6
+
7
+ from xfintech.data.common.cache import Cache
8
+ from xfintech.data.common.coolant import Coolant
9
+ from xfintech.data.common.retry import Retry
10
+ from xfintech.data.source.baostock.session.session import Session
11
+ from xfintech.data.source.baostock.stock.tradedate.constant import (
12
+ KEY,
13
+ NAME,
14
+ SOURCE,
15
+ TARGET,
16
+ )
17
+ from xfintech.data.source.baostock.stock.tradedate.tradedate import TradeDate
18
+
19
+ # ============================================================================
20
+ # Fixtures
21
+ # ============================================================================
22
+
23
+
24
+ @pytest.fixture
25
+ def mock_session():
26
+ """Create a mock Baostock session"""
27
+ session = MagicMock(spec=Session)
28
+ session._credential = None
29
+ session.id = "test1234"
30
+ session.mode = "direct"
31
+ session.relay_url = None
32
+ session.relay_secret = None
33
+ session.connected = True
34
+
35
+ # Mock the connection object
36
+ mock_connection = MagicMock()
37
+ mock_connection.query_trade_dates = MagicMock()
38
+ session.connection = mock_connection
39
+
40
+ return session
41
+
42
+
43
+ @pytest.fixture
44
+ def sample_source_data():
45
+ """Create sample source data in Baostock format"""
46
+ return pd.DataFrame(
47
+ {
48
+ "calendar_date": [
49
+ "2026-01-01",
50
+ "2026-01-02",
51
+ "2026-01-05",
52
+ "2026-01-06",
53
+ "2026-01-07",
54
+ ],
55
+ "is_trading_day": ["0", "0", "1", "1", "1"],
56
+ }
57
+ )
58
+
59
+
60
+ # ============================================================================
61
+ # Initialization Tests
62
+ # ============================================================================
63
+
64
+
65
+ def test_tradedate_initialization_basic(mock_session):
66
+ """Test basic initialization"""
67
+ job = TradeDate(session=mock_session)
68
+
69
+ assert job.name == NAME
70
+ assert job.key == KEY
71
+ assert job.source == SOURCE
72
+ assert job.target == TARGET
73
+
74
+
75
+ def test_tradedate_initialization_with_params(mock_session):
76
+ """Test initialization with params"""
77
+ params = {"year": "2026"}
78
+ job = TradeDate(session=mock_session, params=params)
79
+
80
+ assert job.params.year == "2026"
81
+
82
+
83
+ def test_tradedate_initialization_with_all_components(mock_session):
84
+ """Test initialization with all components"""
85
+ params = {"start_date": "20260101", "end_date": "20260131"}
86
+ coolant = Coolant(interval=0.2)
87
+ retry = Retry(retry=3)
88
+ cache = Cache(path="/tmp/test_cache")
89
+
90
+ job = TradeDate(
91
+ session=mock_session,
92
+ params=params,
93
+ coolant=coolant,
94
+ retry=retry,
95
+ cache=cache,
96
+ )
97
+
98
+ assert job.params.start_date == "20260101"
99
+ assert job.params.end_date == "20260131"
100
+ assert job.coolant.interval == 0.2
101
+ assert job.retry.retry == 3
102
+ assert job.cache is not None
103
+ assert isinstance(job.cache, Cache)
104
+
105
+
106
+ def test_tradedate_name_and_key():
107
+ """Test name and key constants"""
108
+ assert NAME == "tradedate"
109
+ assert KEY == "/baostock/tradedate"
110
+
111
+
112
+ def test_tradedate_source_schema():
113
+ """Test source schema has all required columns"""
114
+ assert SOURCE is not None
115
+ assert "交易日历数据" in SOURCE.desc
116
+
117
+ column_names = SOURCE.columns
118
+ assert "calendar_date" in column_names
119
+ assert "is_trading_day" in column_names
120
+
121
+
122
+ def test_tradedate_target_schema():
123
+ """Test target schema has all required columns"""
124
+ assert TARGET is not None
125
+ assert "交易日历数据" in TARGET.desc
126
+
127
+ column_names = TARGET.columns
128
+ assert "datecode" in column_names
129
+ assert "date" in column_names
130
+ assert "exchange" in column_names
131
+ assert "is_open" in column_names
132
+ assert "year" in column_names
133
+ assert "month" in column_names
134
+ assert "day" in column_names
135
+ assert "week" in column_names
136
+ assert "weekday" in column_names
137
+ assert "quarter" in column_names
138
+
139
+
140
+ # ============================================================================
141
+ # Transform Tests
142
+ # ============================================================================
143
+
144
+
145
+ def test_tradedate_transform_basic(mock_session, sample_source_data):
146
+ """Test basic data transformation"""
147
+ job = TradeDate(session=mock_session)
148
+ result = job.transform(sample_source_data)
149
+
150
+ assert len(result) == 5
151
+ assert "date" in result.columns
152
+ assert "datecode" in result.columns
153
+ assert "is_open" in result.columns
154
+ assert result.iloc[0]["date"] == "2026-01-01"
155
+ assert result.iloc[0]["datecode"] == "20260101"
156
+
157
+
158
+ def test_tradedate_transform_field_types(mock_session, sample_source_data):
159
+ """Test field type conversions"""
160
+ job = TradeDate(session=mock_session)
161
+ result = job.transform(sample_source_data)
162
+
163
+ # Check string fields
164
+ assert isinstance(result.iloc[0]["date"], str)
165
+ assert isinstance(result.iloc[0]["datecode"], str)
166
+ assert isinstance(result.iloc[0]["exchange"], str)
167
+ assert isinstance(result.iloc[0]["weekday"], str)
168
+
169
+ # Check boolean field (pandas returns numpy.bool_)
170
+ assert pd.api.types.is_bool_dtype(result["is_open"])
171
+
172
+ # Check integer fields
173
+ assert pd.api.types.is_integer_dtype(result["year"])
174
+ assert pd.api.types.is_integer_dtype(result["month"])
175
+ assert pd.api.types.is_integer_dtype(result["day"])
176
+ assert pd.api.types.is_integer_dtype(result["week"])
177
+ assert pd.api.types.is_integer_dtype(result["quarter"])
178
+
179
+
180
+ def test_tradedate_transform_date_parsing(mock_session, sample_source_data):
181
+ """Test date parsing and transformation"""
182
+ job = TradeDate(session=mock_session)
183
+ result = job.transform(sample_source_data)
184
+
185
+ # Check date format conversion
186
+ assert result.iloc[0]["date"] == "2026-01-01"
187
+ assert result.iloc[0]["datecode"] == "20260101"
188
+ assert result.iloc[2]["date"] == "2026-01-05"
189
+ assert result.iloc[2]["datecode"] == "20260105"
190
+
191
+
192
+ def test_tradedate_transform_is_open_conversion(mock_session, sample_source_data):
193
+ """Test is_open field conversion"""
194
+ job = TradeDate(session=mock_session)
195
+ result = job.transform(sample_source_data)
196
+
197
+ # "0" -> False, "1" -> True
198
+ assert not result.iloc[0]["is_open"] # 2026-01-01 休市
199
+ assert not result.iloc[1]["is_open"] # 2026-01-02 休市
200
+ assert result.iloc[2]["is_open"] # 2026-01-05 交易日
201
+ assert result.iloc[3]["is_open"] # 2026-01-06 交易日
202
+
203
+
204
+ def test_tradedate_transform_date_components(mock_session, sample_source_data):
205
+ """Test date component extraction"""
206
+ job = TradeDate(session=mock_session)
207
+ result = job.transform(sample_source_data)
208
+
209
+ # Check first row (2026-01-01)
210
+ assert result.iloc[0]["year"] == 2026
211
+ assert result.iloc[0]["month"] == 1
212
+ assert result.iloc[0]["day"] == 1
213
+ assert result.iloc[0]["quarter"] == 1
214
+
215
+
216
+ def test_tradedate_transform_weekday(mock_session, sample_source_data):
217
+ """Test weekday extraction"""
218
+ job = TradeDate(session=mock_session)
219
+ result = job.transform(sample_source_data)
220
+
221
+ # Check that weekday is 3-letter abbreviation
222
+ weekday = result.iloc[0]["weekday"]
223
+ assert isinstance(weekday, str)
224
+ assert len(weekday) == 3
225
+
226
+
227
+ def test_tradedate_transform_empty_data(mock_session):
228
+ """Test transform with empty data"""
229
+ job = TradeDate(session=mock_session)
230
+
231
+ # Test with None
232
+ result = job.transform(None)
233
+ assert result.empty
234
+ assert len(result.columns) == len(TARGET.columns)
235
+
236
+ # Test with empty DataFrame
237
+ empty_df = pd.DataFrame()
238
+ result = job.transform(empty_df)
239
+ assert result.empty
240
+ assert len(result.columns) == len(TARGET.columns)
241
+
242
+
243
+ def test_tradedate_transform_duplicate_removal(mock_session):
244
+ """Test that duplicates are removed"""
245
+ data = pd.DataFrame(
246
+ {
247
+ "calendar_date": ["2026-01-05", "2026-01-05", "2026-01-06"],
248
+ "is_trading_day": ["1", "1", "1"],
249
+ }
250
+ )
251
+ job = TradeDate(session=mock_session)
252
+ result = job.transform(data)
253
+
254
+ # Note: previous field may differ, so check unique dates
255
+ assert len(result["datecode"].unique()) == 2
256
+
257
+
258
+ def test_tradedate_transform_sorting(mock_session):
259
+ """Test that result is sorted by datecode"""
260
+ data = pd.DataFrame(
261
+ {
262
+ "calendar_date": ["2026-01-07", "2026-01-05", "2026-01-06"],
263
+ "is_trading_day": ["1", "1", "1"],
264
+ }
265
+ )
266
+ job = TradeDate(session=mock_session)
267
+ result = job.transform(data)
268
+
269
+ # Should be sorted by datecode
270
+ assert result.iloc[0]["datecode"] == "20260105"
271
+ assert result.iloc[1]["datecode"] == "20260106"
272
+ assert result.iloc[2]["datecode"] == "20260107"
273
+
274
+
275
+ def test_tradedate_transform_exchange_field(mock_session, sample_source_data):
276
+ """Test that exchange field is set to ALL"""
277
+ job = TradeDate(session=mock_session)
278
+ result = job.transform(sample_source_data)
279
+
280
+ # All records should have exchange = "ALL"
281
+ assert all(result["exchange"] == "ALL")
282
+
283
+
284
+ # ============================================================================
285
+ # Run Tests
286
+ # ============================================================================
287
+
288
+
289
+ def test_tradedate_run_basic(mock_session, sample_source_data):
290
+ """Test basic run method"""
291
+ job = TradeDate(session=mock_session)
292
+
293
+ with patch.object(job, "_fetchall", return_value=sample_source_data):
294
+ result = job.run()
295
+
296
+ assert isinstance(result, pd.DataFrame)
297
+ assert len(result) == 5
298
+ assert "date" in result.columns
299
+ assert "datecode" in result.columns
300
+ assert "is_open" in result.columns
301
+
302
+
303
+ def test_tradedate_run_with_year(mock_session, sample_source_data):
304
+ """Test run with year parameter"""
305
+ job = TradeDate(session=mock_session, params={"year": "2026"})
306
+
307
+ with patch.object(job, "_fetchall", return_value=sample_source_data) as mock_fetchall:
308
+ job.run()
309
+
310
+ # Verify _fetchall was called (year processing happens in _parse_year_params)
311
+ assert mock_fetchall.call_count == 1
312
+
313
+
314
+ def test_tradedate_run_with_date_range(mock_session, sample_source_data):
315
+ """Test run with start_date and end_date"""
316
+ job = TradeDate(
317
+ session=mock_session,
318
+ params={"start_date": "20260101", "end_date": "20260131"},
319
+ )
320
+
321
+ with patch.object(job, "_fetchall", return_value=sample_source_data) as mock_fetchall:
322
+ job.run()
323
+
324
+ call_kwargs = mock_fetchall.call_args[1]
325
+ assert call_kwargs["start_date"] == "2026-01-01"
326
+ assert call_kwargs["end_date"] == "2026-01-31"
327
+
328
+
329
+ def test_tradedate_run_calls_query_trade_dates(mock_session, sample_source_data):
330
+ """Test that run calls query_trade_dates API"""
331
+ job = TradeDate(session=mock_session)
332
+
333
+ with patch.object(job, "_fetchall", return_value=sample_source_data) as mock_fetchall:
334
+ job.run()
335
+
336
+ # Verify that _fetchall was called with the correct API
337
+ assert mock_fetchall.call_count == 1
338
+ call_args = mock_fetchall.call_args
339
+ assert call_args[1]["api"] == job.connection.query_trade_dates
340
+
341
+
342
+ def test_tradedate_run_calls_transform(mock_session, sample_source_data):
343
+ """Test that run calls transform"""
344
+ job = TradeDate(session=mock_session)
345
+
346
+ with patch.object(job, "_fetchall", return_value=sample_source_data):
347
+ with patch.object(job, "transform", wraps=job.transform) as mock_transform:
348
+ job.run()
349
+
350
+ mock_transform.assert_called_once()
351
+
352
+
353
+ # ============================================================================
354
+ # Cache Tests
355
+ # ============================================================================
356
+
357
+
358
+ def test_tradedate_cache_persistence(mock_session, sample_source_data):
359
+ """Test that cache persists across runs"""
360
+ job = TradeDate(session=mock_session, cache=True)
361
+
362
+ with patch.object(job, "_load_cache", return_value=None) as mock_load:
363
+ with patch.object(job, "_fetchall", return_value=sample_source_data) as mock_fetchall:
364
+ # First run - fetches data and caches it
365
+ result1 = job.run()
366
+ assert mock_fetchall.call_count == 1
367
+ assert mock_load.call_count == 1
368
+
369
+ # Second run - _load_cache still returns None, so _fetchall called again
370
+ result2 = job.run()
371
+ assert mock_fetchall.call_count == 2
372
+ assert mock_load.call_count == 2
373
+
374
+ pd.testing.assert_frame_equal(result1, result2)
375
+
376
+
377
+ def test_tradedate_params_identifier_uniqueness(mock_session):
378
+ """Test that different params create different cache keys"""
379
+ job1 = TradeDate(session=mock_session, params={"year": "2026"}, cache=True)
380
+ job2 = TradeDate(session=mock_session, params={"year": "2025"}, cache=True)
381
+
382
+ assert job1.params.identifier != job2.params.identifier
383
+
384
+
385
+ def test_tradedate_without_cache(mock_session, sample_source_data):
386
+ """Test that tradedate works correctly without cache"""
387
+ job = TradeDate(session=mock_session, cache=False)
388
+
389
+ with patch.object(job, "_fetchall", return_value=sample_source_data) as mock_fetchall:
390
+ job.run()
391
+ job.run()
392
+
393
+ # Should fetch twice (no caching)
394
+ assert mock_fetchall.call_count == 2
395
+
396
+
397
+ # ============================================================================
398
+ # Date Parsing Tests
399
+ # ============================================================================
400
+
401
+
402
+ def test_tradedate_date_parsing_yyyymmdd(mock_session, sample_source_data):
403
+ """Test date parsing from YYYYMMDD format"""
404
+ job = TradeDate(
405
+ session=mock_session,
406
+ params={"start_date": "20260101", "end_date": "20260131"},
407
+ )
408
+
409
+ with patch.object(job, "_fetchall", return_value=sample_source_data) as mock_fetchall:
410
+ job.run()
411
+
412
+ call_kwargs = mock_fetchall.call_args[1]
413
+ # Should convert YYYYMMDD to YYYY-MM-DD
414
+ assert call_kwargs["start_date"] == "2026-01-01"
415
+ assert call_kwargs["end_date"] == "2026-01-31"
416
+
417
+
418
+ def test_tradedate_date_parsing_hyphen_format(mock_session, sample_source_data):
419
+ """Test date parsing preserves YYYY-MM-DD format"""
420
+ job = TradeDate(
421
+ session=mock_session,
422
+ params={"start_date": "2026-01-01", "end_date": "2026-01-31"},
423
+ )
424
+
425
+ with patch.object(job, "_fetchall", return_value=sample_source_data) as mock_fetchall:
426
+ job.run()
427
+
428
+ call_kwargs = mock_fetchall.call_args[1]
429
+ assert call_kwargs["start_date"] == "2026-01-01"
430
+ assert call_kwargs["end_date"] == "2026-01-31"
431
+
432
+
433
+ # ============================================================================
434
+ # List Methods Tests
435
+ # ============================================================================
436
+
437
+
438
+ def test_tradedate_list_dates(mock_session, sample_source_data):
439
+ """Test list_dates method"""
440
+ job = TradeDate(session=mock_session)
441
+
442
+ with patch.object(job, "_fetchall", return_value=sample_source_data):
443
+ dates = job.list_dates()
444
+
445
+ assert isinstance(dates, list)
446
+ assert len(dates) == 5
447
+ assert "2026-01-01" in dates
448
+ assert "2026-01-05" in dates
449
+ # Should be sorted
450
+ assert dates == sorted(dates)
451
+
452
+
453
+ def test_tradedate_list_datecodes(mock_session, sample_source_data):
454
+ """Test list_datecodes method"""
455
+ job = TradeDate(session=mock_session)
456
+
457
+ with patch.object(job, "_fetchall", return_value=sample_source_data):
458
+ datecodes = job.list_datecodes()
459
+
460
+ assert isinstance(datecodes, list)
461
+ assert len(datecodes) == 5
462
+ assert "20260101" in datecodes
463
+ assert "20260105" in datecodes
464
+ # Should be sorted
465
+ assert datecodes == sorted(datecodes)
466
+
467
+
468
+ def test_tradedate_list_open_dates(mock_session, sample_source_data):
469
+ """Test list_open_dates method returns only trading days"""
470
+ job = TradeDate(session=mock_session)
471
+
472
+ with patch.object(job, "_fetchall", return_value=sample_source_data):
473
+ open_dates = job.list_open_dates()
474
+
475
+ assert isinstance(open_dates, list)
476
+ assert len(open_dates) == 3 # Only trading days
477
+ assert "2026-01-01" not in open_dates # Non-trading day
478
+ assert "2026-01-02" not in open_dates # Non-trading day
479
+ assert "2026-01-05" in open_dates # Trading day
480
+ assert "2026-01-06" in open_dates # Trading day
481
+ assert "2026-01-07" in open_dates # Trading day
482
+
483
+
484
+ def test_tradedate_list_open_datecodes(mock_session, sample_source_data):
485
+ """Test list_open_datecodes method returns only trading days"""
486
+ job = TradeDate(session=mock_session)
487
+
488
+ with patch.object(job, "_fetchall", return_value=sample_source_data):
489
+ open_datecodes = job.list_open_datecodes()
490
+
491
+ assert isinstance(open_datecodes, list)
492
+ assert len(open_datecodes) == 3 # Only trading days
493
+ assert "20260101" not in open_datecodes # Non-trading day
494
+ assert "20260102" not in open_datecodes # Non-trading day
495
+ assert "20260105" in open_datecodes # Trading day
496
+ assert "20260106" in open_datecodes # Trading day
497
+ assert "20260107" in open_datecodes # Trading day
498
+
499
+
500
+ # ============================================================================
501
+ # Check Method Tests
502
+ # ============================================================================
503
+
504
+
505
+ def test_tradedate_check_with_string_hyphen(mock_session):
506
+ """Test check method with string date in YYYY-MM-DD format"""
507
+ trading_data = pd.DataFrame(
508
+ {
509
+ "calendar_date": ["2026-01-10"],
510
+ "is_trading_day": ["1"],
511
+ }
512
+ )
513
+
514
+ with patch.object(TradeDate, "_fetchall", return_value=trading_data):
515
+ result = TradeDate.check(mock_session, "2026-01-10")
516
+ assert result
517
+
518
+
519
+ def test_tradedate_check_with_string_yyyymmdd(mock_session):
520
+ """Test check method with string date in YYYYMMDD format"""
521
+ trading_data = pd.DataFrame(
522
+ {
523
+ "calendar_date": ["2026-01-10"],
524
+ "is_trading_day": ["1"],
525
+ }
526
+ )
527
+
528
+ with patch.object(TradeDate, "_fetchall", return_value=trading_data):
529
+ result = TradeDate.check(mock_session, "20260110")
530
+ assert result
531
+
532
+
533
+ def test_tradedate_check_with_date_object(mock_session):
534
+ """Test check method with date object"""
535
+ trading_data = pd.DataFrame(
536
+ {
537
+ "calendar_date": ["2026-01-10"],
538
+ "is_trading_day": ["1"],
539
+ }
540
+ )
541
+
542
+ with patch.object(TradeDate, "_fetchall", return_value=trading_data):
543
+ result = TradeDate.check(mock_session, date(2026, 1, 10))
544
+ assert result
545
+
546
+
547
+ def test_tradedate_check_with_datetime_object(mock_session):
548
+ """Test check method with datetime object"""
549
+ trading_data = pd.DataFrame(
550
+ {
551
+ "calendar_date": ["2026-01-10"],
552
+ "is_trading_day": ["1"],
553
+ }
554
+ )
555
+
556
+ with patch.object(TradeDate, "_fetchall", return_value=trading_data):
557
+ result = TradeDate.check(mock_session, datetime(2026, 1, 10, 9, 30, 0))
558
+ assert result
559
+
560
+
561
+ def test_tradedate_check_non_trading_day(mock_session):
562
+ """Test check method returns False for non-trading day"""
563
+ empty_data = pd.DataFrame(columns=["calendar_date", "is_trading_day"])
564
+
565
+ with patch.object(TradeDate, "_fetchall", return_value=empty_data):
566
+ result = TradeDate.check(mock_session, "2026-01-01")
567
+ assert result is False
568
+
569
+
570
+ # ============================================================================
571
+ # Integration Tests
572
+ # ============================================================================
573
+
574
+
575
+ def test_tradedate_full_workflow(mock_session, sample_source_data):
576
+ """Test complete workflow from initialization to data retrieval"""
577
+ job = TradeDate(
578
+ session=mock_session,
579
+ params={"start_date": "20260101", "end_date": "20260107"},
580
+ )
581
+
582
+ with patch.object(job, "_fetchall", return_value=sample_source_data):
583
+ result = job.run()
584
+
585
+ assert not result.empty
586
+ assert len(result) == 5
587
+ assert "date" in result.columns
588
+ assert "datecode" in result.columns
589
+ assert "is_open" in result.columns
590
+ assert "year" in result.columns
591
+
592
+
593
+ def test_tradedate_with_year_query(mock_session):
594
+ """Test querying by year"""
595
+ year_data = pd.DataFrame(
596
+ {
597
+ "calendar_date": [f"2026-01-{str(i).zfill(2)}" for i in range(1, 11)],
598
+ "is_trading_day": ["0", "0", "0", "0", "1", "1", "1", "1", "1", "0"],
599
+ }
600
+ )
601
+
602
+ job = TradeDate(session=mock_session, params={"year": 2026})
603
+
604
+ with patch.object(job, "_fetchall", return_value=year_data):
605
+ result = job.run()
606
+
607
+ assert len(result) == 10
608
+ assert all(result["year"] == 2026)
609
+
610
+
611
+ def test_tradedate_filtering_trading_days(mock_session, sample_source_data):
612
+ """Test filtering for only trading days"""
613
+ job = TradeDate(session=mock_session)
614
+
615
+ with patch.object(job, "_fetchall", return_value=sample_source_data):
616
+ result = job.run()
617
+
618
+ # Filter for trading days
619
+ trading_days = result[result["is_open"]]
620
+ assert len(trading_days) == 3
621
+ assert all(trading_days["is_open"])
622
+
623
+
624
+ def test_tradedate_with_large_dataset(mock_session):
625
+ """Test handling of large dataset"""
626
+ # Create a large dataset (365 days)
627
+ dates = pd.date_range(start="2026-01-01", end="2026-12-31", freq="D")
628
+ large_data = pd.DataFrame(
629
+ {
630
+ "calendar_date": dates.strftime("%Y-%m-%d"),
631
+ "is_trading_day": ["1"] * len(dates), # All trading days for simplicity
632
+ }
633
+ )
634
+
635
+ job = TradeDate(session=mock_session)
636
+
637
+ with patch.object(job, "_fetchall", return_value=large_data):
638
+ result = job.run()
639
+
640
+ assert len(result) == 365
641
+ assert result.iloc[0]["datecode"] == "20260101"
642
+ assert result.iloc[-1]["datecode"] == "20261231"
643
+
644
+
645
+ def test_tradedate_with_empty_result(mock_session):
646
+ """Test handling of empty result from API"""
647
+ empty_data = pd.DataFrame(columns=["calendar_date", "is_trading_day"])
648
+
649
+ job = TradeDate(session=mock_session)
650
+
651
+ with patch.object(job, "_fetchall", return_value=empty_data):
652
+ result = job.run()
653
+
654
+ assert result.empty
655
+ assert list(result.columns) == TARGET.list_column_names()
656
+
657
+
658
+ def test_tradedate_quarter_calculation(mock_session):
659
+ """Test quarter calculation for different months"""
660
+ data = pd.DataFrame(
661
+ {
662
+ "calendar_date": ["2026-01-15", "2026-04-15", "2026-07-15", "2026-10-15"],
663
+ "is_trading_day": ["1", "1", "1", "1"],
664
+ }
665
+ )
666
+
667
+ job = TradeDate(session=mock_session)
668
+
669
+ with patch.object(job, "_fetchall", return_value=data):
670
+ result = job.run()
671
+
672
+ assert result.iloc[0]["quarter"] == 1
673
+ assert result.iloc[1]["quarter"] == 2
674
+ assert result.iloc[2]["quarter"] == 3
675
+ assert result.iloc[3]["quarter"] == 4
676
+
677
+
678
+ def test_tradedate_week_calculation(mock_session):
679
+ """Test ISO week number calculation"""
680
+ data = pd.DataFrame(
681
+ {
682
+ "calendar_date": ["2026-01-05", "2026-01-12"],
683
+ "is_trading_day": ["1", "1"],
684
+ }
685
+ )
686
+
687
+ job = TradeDate(session=mock_session)
688
+
689
+ with patch.object(job, "_fetchall", return_value=data):
690
+ result = job.run()
691
+
692
+ # Check that week numbers are calculated
693
+ assert pd.api.types.is_integer_dtype(result["week"])
694
+ assert result.iloc[0]["week"] > 0
695
+ assert result.iloc[1]["week"] > 0