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,484 @@
1
+ """
2
+ Test suite for RelayClient class
3
+ Tests cover initialization, URL/secret/timeout resolution, JSON serialization
4
+ """
5
+
6
+ import json
7
+ from typing import Any
8
+
9
+ import pytest
10
+
11
+ from xfintech.data.relay.client import RelayClient
12
+
13
+ # ============================================================================
14
+ # Helper Classes for Testing
15
+ # ============================================================================
16
+
17
+
18
+ class ConcreteRelayClient(RelayClient):
19
+ """Concrete implementation for testing"""
20
+
21
+ def call(self) -> Any:
22
+ return {"status": "success"}
23
+
24
+
25
+ class CustomRelayClient(RelayClient):
26
+ """Custom client with overridden call"""
27
+
28
+ def __init__(self, url: str, secret: str, timeout: int = None, **kwargs):
29
+ super().__init__(url, secret, timeout, **kwargs)
30
+ self.custom_attr = kwargs.get("custom_attr", "default")
31
+
32
+ def call(self) -> dict:
33
+ return {"url": self.url, "timeout": self.timeout, "custom": self.custom_attr}
34
+
35
+
36
+ # ============================================================================
37
+ # Initialization Tests
38
+ # ============================================================================
39
+
40
+
41
+ def test_relayclient_init_basic():
42
+ """Test RelayClient basic initialization"""
43
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="test-secret")
44
+
45
+ assert client.url == "https://relay.example.com"
46
+ assert client.secret == "test-secret"
47
+ assert client.timeout == RelayClient.DEFAULT_TIMEOUT
48
+
49
+
50
+ def test_relayclient_init_with_timeout():
51
+ """Test RelayClient initialization with custom timeout"""
52
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="test-secret", timeout=120)
53
+
54
+ assert client.timeout == 120
55
+
56
+
57
+ def test_relayclient_init_with_kwargs():
58
+ """Test RelayClient initialization accepts kwargs"""
59
+ client = CustomRelayClient(url="https://relay.example.com", secret="test-secret", custom_attr="custom_value")
60
+
61
+ assert client.custom_attr == "custom_value"
62
+
63
+
64
+ def test_relayclient_init_strips_trailing_slash():
65
+ """Test URL trailing slash is removed"""
66
+ client = ConcreteRelayClient(url="https://relay.example.com/", secret="test-secret")
67
+
68
+ assert client.url == "https://relay.example.com"
69
+
70
+
71
+ def test_relayclient_init_multiple_trailing_slashes():
72
+ """Test multiple trailing slashes are removed"""
73
+ client = ConcreteRelayClient(url="https://relay.example.com///", secret="test-secret")
74
+
75
+ assert client.url == "https://relay.example.com"
76
+
77
+
78
+ def test_relayclient_init_strips_secret_whitespace():
79
+ """Test secret whitespace is stripped"""
80
+ client = ConcreteRelayClient(url="https://relay.example.com", secret=" test-secret ")
81
+
82
+ assert client.secret == "test-secret"
83
+
84
+
85
+ def test_relayclient_default_timeout_constant():
86
+ """Test DEFAULT_TIMEOUT constant value"""
87
+ assert RelayClient.DEFAULT_TIMEOUT == 180
88
+
89
+
90
+ # ============================================================================
91
+ # URL Resolution Tests
92
+ # ============================================================================
93
+
94
+
95
+ def test_resolve_url_valid():
96
+ """Test _resolve_url with valid URL"""
97
+ client = ConcreteRelayClient(url="https://api.example.com", secret="secret")
98
+
99
+ assert client.url == "https://api.example.com"
100
+
101
+
102
+ def test_resolve_url_empty_string_raises_error():
103
+ """Test _resolve_url raises error for empty string"""
104
+ with pytest.raises(ValueError, match="Relay URL must be provided"):
105
+ ConcreteRelayClient(url="", secret="secret")
106
+
107
+
108
+ def test_resolve_url_with_path():
109
+ """Test _resolve_url preserves path"""
110
+ client = ConcreteRelayClient(url="https://relay.example.com/api/v1", secret="secret")
111
+
112
+ assert client.url == "https://relay.example.com/api/v1"
113
+
114
+
115
+ def test_resolve_url_with_path_and_trailing_slash():
116
+ """Test _resolve_url preserves path but removes trailing slash"""
117
+ client = ConcreteRelayClient(url="https://relay.example.com/api/v1/", secret="secret")
118
+
119
+ assert client.url == "https://relay.example.com/api/v1"
120
+
121
+
122
+ def test_resolve_url_localhost():
123
+ """Test _resolve_url with localhost"""
124
+ client = ConcreteRelayClient(url="http://localhost:8080", secret="secret")
125
+
126
+ assert client.url == "http://localhost:8080"
127
+
128
+
129
+ # ============================================================================
130
+ # Secret Resolution Tests
131
+ # ============================================================================
132
+
133
+
134
+ def test_resolve_secret_valid():
135
+ """Test _resolve_secret with valid secret"""
136
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="my-secret-key")
137
+
138
+ assert client.secret == "my-secret-key"
139
+
140
+
141
+ def test_resolve_secret_empty_string_raises_error():
142
+ """Test _resolve_secret raises error for empty string"""
143
+ with pytest.raises(ValueError, match="Relay secret must be provided"):
144
+ ConcreteRelayClient(url="https://relay.example.com", secret="")
145
+
146
+
147
+ def test_resolve_secret_whitespace_only_raises_error():
148
+ """Test _resolve_secret raises error for whitespace-only string"""
149
+ with pytest.raises(ValueError, match="Relay secret must be provided"):
150
+ ConcreteRelayClient(url="https://relay.example.com", secret=" ")
151
+
152
+
153
+ def test_resolve_secret_with_leading_whitespace():
154
+ """Test _resolve_secret strips leading whitespace"""
155
+ client = ConcreteRelayClient(url="https://relay.example.com", secret=" my-secret")
156
+
157
+ assert client.secret == "my-secret"
158
+
159
+
160
+ def test_resolve_secret_with_trailing_whitespace():
161
+ """Test _resolve_secret strips trailing whitespace"""
162
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="my-secret ")
163
+
164
+ assert client.secret == "my-secret"
165
+
166
+
167
+ def test_resolve_secret_with_internal_whitespace():
168
+ """Test _resolve_secret preserves internal whitespace"""
169
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="my secret key")
170
+
171
+ assert client.secret == "my secret key"
172
+
173
+
174
+ def test_resolve_secret_special_characters():
175
+ """Test _resolve_secret accepts special characters"""
176
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret!@#$%^&*()_+-=[]{}|")
177
+
178
+ assert client.secret == "secret!@#$%^&*()_+-=[]{}|"
179
+
180
+
181
+ # ============================================================================
182
+ # Timeout Resolution Tests
183
+ # ============================================================================
184
+
185
+
186
+ def test_resolve_timeout_none_uses_default():
187
+ """Test _resolve_timeout uses default when None"""
188
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret", timeout=None)
189
+
190
+ assert client.timeout == RelayClient.DEFAULT_TIMEOUT
191
+
192
+
193
+ def test_resolve_timeout_valid_integer():
194
+ """Test _resolve_timeout with valid integer"""
195
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret", timeout=60)
196
+
197
+ assert client.timeout == 60
198
+
199
+
200
+ def test_resolve_timeout_zero_uses_default():
201
+ """Test _resolve_timeout uses default for zero"""
202
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret", timeout=0)
203
+
204
+ assert client.timeout == RelayClient.DEFAULT_TIMEOUT
205
+
206
+
207
+ def test_resolve_timeout_negative_uses_default():
208
+ """Test _resolve_timeout uses default for negative"""
209
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret", timeout=-10)
210
+
211
+ assert client.timeout == RelayClient.DEFAULT_TIMEOUT
212
+
213
+
214
+ def test_resolve_timeout_string_raises_error():
215
+ """Test _resolve_timeout raises error for string"""
216
+ with pytest.raises(ValueError, match="Timeout must be an integer"):
217
+ ConcreteRelayClient(url="https://relay.example.com", secret="secret", timeout="60")
218
+
219
+
220
+ def test_resolve_timeout_float_raises_error():
221
+ """Test _resolve_timeout raises error for float"""
222
+ with pytest.raises(ValueError, match="Timeout must be an integer"):
223
+ ConcreteRelayClient(url="https://relay.example.com", secret="secret", timeout=60.5)
224
+
225
+
226
+ def test_resolve_timeout_large_value():
227
+ """Test _resolve_timeout accepts large values"""
228
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret", timeout=3600)
229
+
230
+ assert client.timeout == 3600
231
+
232
+
233
+ # ============================================================================
234
+ # canonical_json Method Tests
235
+ # ============================================================================
236
+
237
+
238
+ def test_canonical_json_simple_dict():
239
+ """Test canonical_json with simple dictionary"""
240
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret")
241
+
242
+ data = {"name": "test", "value": 42}
243
+ result = client.canonical_json(data)
244
+
245
+ assert isinstance(result, bytes)
246
+ assert result == b'{"name":"test","value":42}'
247
+
248
+
249
+ def test_canonical_json_sorted_keys():
250
+ """Test canonical_json sorts keys"""
251
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret")
252
+
253
+ data = {"z": 1, "a": 2, "m": 3}
254
+ result = client.canonical_json(data)
255
+
256
+ assert result == b'{"a":2,"m":3,"z":1}'
257
+
258
+
259
+ def test_canonical_json_no_spaces():
260
+ """Test canonical_json has no spaces"""
261
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret")
262
+
263
+ data = {"key1": "value1", "key2": "value2"}
264
+ result = client.canonical_json(data)
265
+
266
+ assert b" " not in result
267
+ assert result == b'{"key1":"value1","key2":"value2"}'
268
+
269
+
270
+ def test_canonical_json_nested_dict():
271
+ """Test canonical_json with nested dictionary"""
272
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret")
273
+
274
+ data = {"outer": {"inner": "value"}}
275
+ result = client.canonical_json(data)
276
+
277
+ assert result == b'{"outer":{"inner":"value"}}'
278
+
279
+
280
+ def test_canonical_json_list():
281
+ """Test canonical_json with list"""
282
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret")
283
+
284
+ data = [1, 2, 3]
285
+ result = client.canonical_json(data)
286
+
287
+ assert result == b"[1,2,3]"
288
+
289
+
290
+ def test_canonical_json_mixed_types():
291
+ """Test canonical_json with mixed types"""
292
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret")
293
+
294
+ data = {"string": "text", "number": 123, "float": 45.67, "bool": True, "null": None, "list": [1, 2, 3]}
295
+ result = client.canonical_json(data)
296
+
297
+ assert isinstance(result, bytes)
298
+ # Verify it's valid JSON
299
+ parsed = json.loads(result)
300
+ assert parsed["string"] == "text"
301
+ assert parsed["number"] == 123
302
+ assert parsed["bool"] is True
303
+ assert parsed["null"] is None
304
+
305
+
306
+ def test_canonical_json_unicode():
307
+ """Test canonical_json handles unicode"""
308
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret")
309
+
310
+ data = {"name": "测试", "symbol": "€"}
311
+ result = client.canonical_json(data)
312
+
313
+ assert isinstance(result, bytes)
314
+ # Should preserve unicode, not escape it
315
+ assert "测试".encode("utf-8") in result
316
+ assert "€".encode("utf-8") in result
317
+
318
+
319
+ def test_canonical_json_empty_dict():
320
+ """Test canonical_json with empty dictionary"""
321
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret")
322
+
323
+ data = {}
324
+ result = client.canonical_json(data)
325
+
326
+ assert result == b"{}"
327
+
328
+
329
+ def test_canonical_json_empty_list():
330
+ """Test canonical_json with empty list"""
331
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret")
332
+
333
+ data = []
334
+ result = client.canonical_json(data)
335
+
336
+ assert result == b"[]"
337
+
338
+
339
+ def test_canonical_json_returns_bytes():
340
+ """Test canonical_json always returns bytes"""
341
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret")
342
+
343
+ result = client.canonical_json({"test": "data"})
344
+
345
+ assert type(result) is bytes
346
+
347
+
348
+ # ============================================================================
349
+ # call Method Tests
350
+ # ============================================================================
351
+
352
+
353
+ def test_call_not_implemented():
354
+ """Test call raises NotImplementedError in base class"""
355
+ client = RelayClient(url="https://relay.example.com", secret="secret")
356
+
357
+ with pytest.raises(NotImplementedError):
358
+ client.call()
359
+
360
+
361
+ def test_call_implemented_in_subclass():
362
+ """Test call can be implemented in subclass"""
363
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret")
364
+
365
+ result = client.call()
366
+
367
+ assert result == {"status": "success"}
368
+
369
+
370
+ def test_call_custom_implementation():
371
+ """Test call with custom implementation"""
372
+ client = CustomRelayClient(url="https://relay.example.com", secret="secret", timeout=100, custom_attr="test")
373
+
374
+ result = client.call()
375
+
376
+ assert result["url"] == "https://relay.example.com"
377
+ assert result["timeout"] == 100
378
+ assert result["custom"] == "test"
379
+
380
+
381
+ # ============================================================================
382
+ # Integration Tests
383
+ # ============================================================================
384
+
385
+
386
+ def test_relayclient_full_initialization():
387
+ """Test complete RelayClient initialization workflow"""
388
+ client = ConcreteRelayClient(url="https://relay.example.com/api/v1/", secret=" my-secret-key ", timeout=300)
389
+
390
+ assert client.url == "https://relay.example.com/api/v1"
391
+ assert client.secret == "my-secret-key"
392
+ assert client.timeout == 300
393
+
394
+
395
+ def test_relayclient_json_serialization_workflow():
396
+ """Test JSON serialization workflow"""
397
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret")
398
+
399
+ data = {"endpoint": client.url, "timeout": client.timeout, "data": {"key": "value"}}
400
+
401
+ json_bytes = client.canonical_json(data)
402
+
403
+ # Verify it can be deserialized
404
+ parsed = json.loads(json_bytes)
405
+ assert parsed["endpoint"] == client.url
406
+ assert parsed["timeout"] == 180
407
+
408
+
409
+ def test_relayclient_multiple_instances_independent():
410
+ """Test multiple RelayClient instances are independent"""
411
+ client1 = ConcreteRelayClient(url="https://relay1.example.com", secret="secret1", timeout=60)
412
+
413
+ client2 = ConcreteRelayClient(url="https://relay2.example.com", secret="secret2", timeout=120)
414
+
415
+ assert client1.url != client2.url
416
+ assert client1.secret != client2.secret
417
+ assert client1.timeout != client2.timeout
418
+
419
+
420
+ def test_relayclient_protocol_compliance():
421
+ """Test RelayClient implements RelayClientLike protocol"""
422
+ from xfintech.data.relay.clientlike import RelayClientLike
423
+
424
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret")
425
+
426
+ # Should have all required attributes
427
+ assert hasattr(client, "url")
428
+ assert hasattr(client, "secret")
429
+ assert hasattr(client, "timeout")
430
+ assert hasattr(client, "call")
431
+ assert callable(client.call)
432
+
433
+ # Should be recognized as RelayClientLike
434
+ assert isinstance(client, RelayClientLike)
435
+
436
+
437
+ # ============================================================================
438
+ # Edge Case Tests
439
+ # ============================================================================
440
+
441
+
442
+ def test_relayclient_url_with_query_params():
443
+ """Test URL with query parameters"""
444
+ client = ConcreteRelayClient(url="https://relay.example.com/api?key=value", secret="secret")
445
+
446
+ assert client.url == "https://relay.example.com/api?key=value"
447
+
448
+
449
+ def test_relayclient_url_with_fragment():
450
+ """Test URL with fragment"""
451
+ client = ConcreteRelayClient(url="https://relay.example.com/api#section", secret="secret")
452
+
453
+ assert client.url == "https://relay.example.com/api#section"
454
+
455
+
456
+ def test_relayclient_very_long_secret():
457
+ """Test with very long secret"""
458
+ long_secret = "x" * 1000
459
+ client = ConcreteRelayClient(url="https://relay.example.com", secret=long_secret)
460
+
461
+ assert len(client.secret) == 1000
462
+
463
+
464
+ def test_canonical_json_large_data():
465
+ """Test canonical_json with large data structure"""
466
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret")
467
+
468
+ large_data = {f"key_{i}": f"value_{i}" for i in range(100)}
469
+ result = client.canonical_json(large_data)
470
+
471
+ assert isinstance(result, bytes)
472
+ parsed = json.loads(result)
473
+ assert len(parsed) == 100
474
+
475
+
476
+ def test_canonical_json_deeply_nested():
477
+ """Test canonical_json with deeply nested structure"""
478
+ client = ConcreteRelayClient(url="https://relay.example.com", secret="secret")
479
+
480
+ data = {"level1": {"level2": {"level3": {"level4": "deep"}}}}
481
+ result = client.canonical_json(data)
482
+
483
+ parsed = json.loads(result)
484
+ assert parsed["level1"]["level2"]["level3"]["level4"] == "deep"