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.
- lib_x17_fintech-2.1.3.dist-info/METADATA +633 -0
- lib_x17_fintech-2.1.3.dist-info/RECORD +282 -0
- lib_x17_fintech-2.1.3.dist-info/WHEEL +5 -0
- lib_x17_fintech-2.1.3.dist-info/licenses/LICENSE +1 -0
- lib_x17_fintech-2.1.3.dist-info/top_level.txt +1 -0
- xfintech/__init__.py +0 -0
- xfintech/connect/__init__.py +18 -0
- xfintech/connect/artifact/__init__.py +5 -0
- xfintech/connect/artifact/artifact.py +168 -0
- xfintech/connect/artifact/tests/__init__.py +3 -0
- xfintech/connect/artifact/tests/test_class_artifact_all.py +564 -0
- xfintech/connect/common/__init__.py +12 -0
- xfintech/connect/common/connect.py +49 -0
- xfintech/connect/common/connectref.py +119 -0
- xfintech/connect/common/error.py +62 -0
- xfintech/connect/common/tests/__init__.py +1 -0
- xfintech/connect/common/tests/test_class_connectlike_all.py +544 -0
- xfintech/connect/common/tests/test_class_connectref_all.py +586 -0
- xfintech/connect/common/tests/test_class_errors_all.py +524 -0
- xfintech/connect/instance/__init__.py +7 -0
- xfintech/connect/instance/macos.py +121 -0
- xfintech/connect/instance/s3.py +176 -0
- xfintech/connect/instance/tests/__init__.py +1 -0
- xfintech/connect/instance/tests/test_class_macosconnect_all.py +692 -0
- xfintech/connect/instance/tests/test_class_s3connect_all.py +603 -0
- xfintech/data/__init__.py +20 -0
- xfintech/data/common/__init__.py +15 -0
- xfintech/data/common/cache.py +186 -0
- xfintech/data/common/coolant.py +171 -0
- xfintech/data/common/metric.py +138 -0
- xfintech/data/common/paginate.py +132 -0
- xfintech/data/common/params.py +162 -0
- xfintech/data/common/retry.py +201 -0
- xfintech/data/common/tests/__init__.py +1 -0
- xfintech/data/common/tests/test_class_cache_all.py +681 -0
- xfintech/data/common/tests/test_class_coolant_all.py +534 -0
- xfintech/data/common/tests/test_class_metric_all.py +705 -0
- xfintech/data/common/tests/test_class_paginate_all.py +508 -0
- xfintech/data/common/tests/test_class_params_all.py +891 -0
- xfintech/data/common/tests/test_class_retry_all.py +714 -0
- xfintech/data/job/__init__.py +17 -0
- xfintech/data/job/errors.py +112 -0
- xfintech/data/job/house.py +156 -0
- xfintech/data/job/job.py +247 -0
- xfintech/data/job/joblike.py +47 -0
- xfintech/data/job/tests/__init__.py +1 -0
- xfintech/data/job/tests/test_class_errors_all.py +275 -0
- xfintech/data/job/tests/test_class_house_all.py +801 -0
- xfintech/data/job/tests/test_class_job_all.py +684 -0
- xfintech/data/job/tests/test_class_joblike_all.py +482 -0
- xfintech/data/relay/__init__.py +7 -0
- xfintech/data/relay/client.py +114 -0
- xfintech/data/relay/clientlike.py +45 -0
- xfintech/data/relay/tests/test_class_relayclient_all.py +484 -0
- xfintech/data/relay/tests/test_class_relayclientlike_all.py +500 -0
- xfintech/data/source/__init__.py +7 -0
- xfintech/data/source/baostock/__init__.py +21 -0
- xfintech/data/source/baostock/job/__init__.py +5 -0
- xfintech/data/source/baostock/job/job.py +217 -0
- xfintech/data/source/baostock/job/tests/__init__.py +0 -0
- xfintech/data/source/baostock/job/tests/test_class_baostockjob_all.py +547 -0
- xfintech/data/source/baostock/session/__init__.py +8 -0
- xfintech/data/source/baostock/session/relay.py +223 -0
- xfintech/data/source/baostock/session/session.py +241 -0
- xfintech/data/source/baostock/session/tests/__init__.py +0 -0
- xfintech/data/source/baostock/session/tests/test_class_relay_all.py +694 -0
- xfintech/data/source/baostock/session/tests/test_class_session_all.py +505 -0
- xfintech/data/source/baostock/stock/__init__.py +0 -0
- xfintech/data/source/baostock/stock/hs300stock/__init__.py +3 -0
- xfintech/data/source/baostock/stock/hs300stock/constant.py +49 -0
- xfintech/data/source/baostock/stock/hs300stock/hs300stock.py +133 -0
- xfintech/data/source/baostock/stock/hs300stock/tests/__init__.py +1 -0
- xfintech/data/source/baostock/stock/hs300stock/tests/test_class_hs300index_all.py +413 -0
- xfintech/data/source/baostock/stock/minuteline/__init__.py +19 -0
- xfintech/data/source/baostock/stock/minuteline/constant.py +89 -0
- xfintech/data/source/baostock/stock/minuteline/minuteline.py +163 -0
- xfintech/data/source/baostock/stock/minuteline/tests/__init__.py +0 -0
- xfintech/data/source/baostock/stock/minuteline/tests/test_class_minuteline_all.py +582 -0
- xfintech/data/source/baostock/stock/stock/__init__.py +19 -0
- xfintech/data/source/baostock/stock/stock/constant.py +55 -0
- xfintech/data/source/baostock/stock/stock/stock.py +149 -0
- xfintech/data/source/baostock/stock/stock/tests/__init__.py +0 -0
- xfintech/data/source/baostock/stock/stock/tests/test_class_stock_all.py +508 -0
- xfintech/data/source/baostock/stock/stockinfo/__init__.py +5 -0
- xfintech/data/source/baostock/stock/stockinfo/constant.py +66 -0
- xfintech/data/source/baostock/stock/stockinfo/stockinfo.py +176 -0
- xfintech/data/source/baostock/stock/stockinfo/tests/__init__.py +1 -0
- xfintech/data/source/baostock/stock/stockinfo/tests/test_class_stockinfo_all.py +617 -0
- xfintech/data/source/baostock/stock/sz50stock/__init__.py +3 -0
- xfintech/data/source/baostock/stock/sz50stock/constant.py +49 -0
- xfintech/data/source/baostock/stock/sz50stock/sz50stock.py +133 -0
- xfintech/data/source/baostock/stock/sz50stock/tests/__init__.py +1 -0
- xfintech/data/source/baostock/stock/sz50stock/tests/test_class_sz50stock_all.py +397 -0
- xfintech/data/source/baostock/stock/tradedate/__init__.py +19 -0
- xfintech/data/source/baostock/stock/tradedate/constant.py +72 -0
- xfintech/data/source/baostock/stock/tradedate/tests/__init__.py +0 -0
- xfintech/data/source/baostock/stock/tradedate/tests/test_class_tradedate_all.py +695 -0
- xfintech/data/source/baostock/stock/tradedate/tradedate.py +208 -0
- xfintech/data/source/baostock/stock/zz500stock/__init__.py +3 -0
- xfintech/data/source/baostock/stock/zz500stock/constant.py +55 -0
- xfintech/data/source/baostock/stock/zz500stock/tests/__init__.py +1 -0
- xfintech/data/source/baostock/stock/zz500stock/tests/test_class_zz500stock_all.py +421 -0
- xfintech/data/source/baostock/stock/zz500stock/zz500stock.py +133 -0
- xfintech/data/source/tushare/__init__.py +61 -0
- xfintech/data/source/tushare/job/__init__.py +5 -0
- xfintech/data/source/tushare/job/job.py +257 -0
- xfintech/data/source/tushare/job/tests/test_class_tusharejob_all.py +589 -0
- xfintech/data/source/tushare/session/__init__.py +5 -0
- xfintech/data/source/tushare/session/relay.py +231 -0
- xfintech/data/source/tushare/session/session.py +239 -0
- xfintech/data/source/tushare/session/tests/test_class_relay_all.py +719 -0
- xfintech/data/source/tushare/session/tests/test_class_session_all.py +705 -0
- xfintech/data/source/tushare/stock/__init__.py +55 -0
- xfintech/data/source/tushare/stock/adjfactor/__init__.py +19 -0
- xfintech/data/source/tushare/stock/adjfactor/adjfactor.py +150 -0
- xfintech/data/source/tushare/stock/adjfactor/constant.py +71 -0
- xfintech/data/source/tushare/stock/adjfactor/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/adjfactor/tests/test_class_adjfactor_all.py +372 -0
- xfintech/data/source/tushare/stock/capflow/__init__.py +19 -0
- xfintech/data/source/tushare/stock/capflow/capflow.py +171 -0
- xfintech/data/source/tushare/stock/capflow/constant.py +105 -0
- xfintech/data/source/tushare/stock/capflow/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/capflow/tests/test_class_capflow_all.py +589 -0
- xfintech/data/source/tushare/stock/capflowdc/__init__.py +19 -0
- xfintech/data/source/tushare/stock/capflowdc/capflowdc.py +167 -0
- xfintech/data/source/tushare/stock/capflowdc/constant.py +95 -0
- xfintech/data/source/tushare/stock/capflowdc/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/capflowdc/tests/test_class_capflowdc_all.py +814 -0
- xfintech/data/source/tushare/stock/capflowths/__init__.py +19 -0
- xfintech/data/source/tushare/stock/capflowths/capflowths.py +173 -0
- xfintech/data/source/tushare/stock/capflowths/constant.py +92 -0
- xfintech/data/source/tushare/stock/capflowths/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/capflowths/tests/test_class_capflowths_all.py +551 -0
- xfintech/data/source/tushare/stock/company/__init__.py +19 -0
- xfintech/data/source/tushare/stock/company/company.py +188 -0
- xfintech/data/source/tushare/stock/company/constant.py +92 -0
- xfintech/data/source/tushare/stock/company/tests/__init__.py +1 -0
- xfintech/data/source/tushare/stock/company/tests/test_class_company_all.py +829 -0
- xfintech/data/source/tushare/stock/companybusiness/__init__.py +21 -0
- xfintech/data/source/tushare/stock/companybusiness/companybusiness.py +183 -0
- xfintech/data/source/tushare/stock/companybusiness/constant.py +91 -0
- xfintech/data/source/tushare/stock/companybusiness/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companybusiness/tests/test_class_companybusiness_all.py +633 -0
- xfintech/data/source/tushare/stock/companycashflow/__init__.py +21 -0
- xfintech/data/source/tushare/stock/companycashflow/companycashflow.py +277 -0
- xfintech/data/source/tushare/stock/companycashflow/constant.py +293 -0
- xfintech/data/source/tushare/stock/companycashflow/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companycashflow/tests/test_class_companycashflow_all.py +619 -0
- xfintech/data/source/tushare/stock/companydebt/__init__.py +19 -0
- xfintech/data/source/tushare/stock/companydebt/companydebt.py +339 -0
- xfintech/data/source/tushare/stock/companydebt/constant.py +403 -0
- xfintech/data/source/tushare/stock/companydebt/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companydebt/tests/test_class_companydebt_all.py +655 -0
- xfintech/data/source/tushare/stock/companyoverview/__init__.py +21 -0
- xfintech/data/source/tushare/stock/companyoverview/companyoverview.py +214 -0
- xfintech/data/source/tushare/stock/companyoverview/constant.py +152 -0
- xfintech/data/source/tushare/stock/companyoverview/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companyoverview/tests/test_class_companyoverview_all.py +647 -0
- xfintech/data/source/tushare/stock/companyprofit/__init__.py +21 -0
- xfintech/data/source/tushare/stock/companyprofit/companyprofit.py +272 -0
- xfintech/data/source/tushare/stock/companyprofit/constant.py +259 -0
- xfintech/data/source/tushare/stock/companyprofit/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/companyprofit/tests/test_class_companyprofit_all.py +635 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/__init__.py +21 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/conceptcapflowdc.py +175 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/constant.py +106 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/conceptcapflowdc/tests/test_class_conceptcapflowdc_all.py +568 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/__init__.py +21 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/conceptcapflowths.py +188 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/constant.py +89 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/conceptcapflowths/tests/test_class_conceptcapflowths_all.py +516 -0
- xfintech/data/source/tushare/stock/dayline/__init__.py +19 -0
- xfintech/data/source/tushare/stock/dayline/constant.py +87 -0
- xfintech/data/source/tushare/stock/dayline/dayline.py +177 -0
- xfintech/data/source/tushare/stock/dayline/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/dayline/tests/test_class_dayline_all.py +585 -0
- xfintech/data/source/tushare/stock/industrycapflowths/__init__.py +21 -0
- xfintech/data/source/tushare/stock/industrycapflowths/constant.py +89 -0
- xfintech/data/source/tushare/stock/industrycapflowths/industrycapflowths.py +192 -0
- xfintech/data/source/tushare/stock/industrycapflowths/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/industrycapflowths/tests/test_class_industrycapflowths_all.py +683 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/__init__.py +21 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/constant.py +90 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/marketindexcapflowdc.py +173 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/marketindexcapflowdc/tests/test_class_marketindexcapflowdc_all.py +793 -0
- xfintech/data/source/tushare/stock/monthline/__init__.py +19 -0
- xfintech/data/source/tushare/stock/monthline/constant.py +87 -0
- xfintech/data/source/tushare/stock/monthline/monthline.py +180 -0
- xfintech/data/source/tushare/stock/monthline/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/monthline/tests/test_class_monthline_all.py +574 -0
- xfintech/data/source/tushare/stock/stock/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stock/constant.py +105 -0
- xfintech/data/source/tushare/stock/stock/stock.py +193 -0
- xfintech/data/source/tushare/stock/stock/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stock/tests/test_class_stock_all.py +788 -0
- xfintech/data/source/tushare/stock/stockdividend/__init__.py +21 -0
- xfintech/data/source/tushare/stock/stockdividend/constant.py +111 -0
- xfintech/data/source/tushare/stock/stockdividend/stockdividend.py +180 -0
- xfintech/data/source/tushare/stock/stockdividend/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockdividend/tests/test_class_stockdividend_all.py +725 -0
- xfintech/data/source/tushare/stock/stockinfo/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stockinfo/constant.py +104 -0
- xfintech/data/source/tushare/stock/stockinfo/stockinfo.py +208 -0
- xfintech/data/source/tushare/stock/stockinfo/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockinfo/tests/test_class_stockinfo_all.py +881 -0
- xfintech/data/source/tushare/stock/stockipo/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stockipo/constant.py +90 -0
- xfintech/data/source/tushare/stock/stockipo/stockipo.py +234 -0
- xfintech/data/source/tushare/stock/stockipo/tests/__init__.py +1 -0
- xfintech/data/source/tushare/stock/stockipo/tests/test_class_stockipo_all.py +750 -0
- xfintech/data/source/tushare/stock/stockpledge/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stockpledge/constant.py +72 -0
- xfintech/data/source/tushare/stock/stockpledge/stockpledge.py +158 -0
- xfintech/data/source/tushare/stock/stockpledge/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockpledge/tests/test_class_stockpledge_all.py +664 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/__init__.py +21 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/constant.py +85 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/stockpledgedetail.py +171 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockpledgedetail/tests/test_class_stockpledgedetail_all.py +112 -0
- xfintech/data/source/tushare/stock/stockst/__init__.py +19 -0
- xfintech/data/source/tushare/stock/stockst/constant.py +80 -0
- xfintech/data/source/tushare/stock/stockst/stockst.py +189 -0
- xfintech/data/source/tushare/stock/stockst/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stockst/tests/test_class_stockst_all.py +693 -0
- xfintech/data/source/tushare/stock/stocksuspend/__init__.py +21 -0
- xfintech/data/source/tushare/stock/stocksuspend/constant.py +75 -0
- xfintech/data/source/tushare/stock/stocksuspend/stocksuspend.py +151 -0
- xfintech/data/source/tushare/stock/stocksuspend/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/stocksuspend/tests/test_class_stocksuspend_all.py +626 -0
- xfintech/data/source/tushare/stock/techindex/__init__.py +19 -0
- xfintech/data/source/tushare/stock/techindex/constant.py +600 -0
- xfintech/data/source/tushare/stock/techindex/techindex.py +314 -0
- xfintech/data/source/tushare/stock/techindex/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/techindex/tests/test_class_techindex_all.py +576 -0
- xfintech/data/source/tushare/stock/tradedate/__init__.py +19 -0
- xfintech/data/source/tushare/stock/tradedate/constant.py +93 -0
- xfintech/data/source/tushare/stock/tradedate/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/tradedate/tests/test_class_tradedate_all.py +947 -0
- xfintech/data/source/tushare/stock/tradedate/tradedate.py +234 -0
- xfintech/data/source/tushare/stock/weekline/__init__.py +19 -0
- xfintech/data/source/tushare/stock/weekline/constant.py +87 -0
- xfintech/data/source/tushare/stock/weekline/tests/__init__.py +0 -0
- xfintech/data/source/tushare/stock/weekline/tests/test_class_weekline_all.py +575 -0
- xfintech/data/source/tushare/stock/weekline/weekline.py +182 -0
- xfintech/fabric/__init__.py +18 -0
- xfintech/fabric/column/__init__.py +7 -0
- xfintech/fabric/column/info.py +202 -0
- xfintech/fabric/column/kind.py +102 -0
- xfintech/fabric/column/tests/__init__.py +0 -0
- xfintech/fabric/column/tests/test_class_info_all.py +207 -0
- xfintech/fabric/column/tests/test_class_kind_all.py +80 -0
- xfintech/fabric/table/__init__.py +5 -0
- xfintech/fabric/table/info.py +263 -0
- xfintech/fabric/table/tests/__init__.py +0 -0
- xfintech/fabric/table/tests/test_class_info_all.py +547 -0
- xfintech/serde/__init__.py +35 -0
- xfintech/serde/common/__init__.py +9 -0
- xfintech/serde/common/dataformat.py +78 -0
- xfintech/serde/common/deserialiserlike.py +38 -0
- xfintech/serde/common/error.py +182 -0
- xfintech/serde/common/serialiserlike.py +38 -0
- xfintech/serde/common/tests/__init__.py +1 -0
- xfintech/serde/common/tests/test_class_dataformat_all.py +694 -0
- xfintech/serde/common/tests/test_class_deserialiserlike_all.py +500 -0
- xfintech/serde/common/tests/test_class_errors_all.py +518 -0
- xfintech/serde/common/tests/test_class_serialiserlike_all.py +401 -0
- xfintech/serde/deserialiser/__init__.py +7 -0
- xfintech/serde/deserialiser/pandas.py +113 -0
- xfintech/serde/deserialiser/python.py +68 -0
- xfintech/serde/deserialiser/tests/__init__.py +1 -0
- xfintech/serde/deserialiser/tests/test_class_pandasdeserialiser_all.py +503 -0
- xfintech/serde/deserialiser/tests/test_class_pythondeserialiser_all.py +570 -0
- xfintech/serde/serialiser/__init__.py +7 -0
- xfintech/serde/serialiser/pandas.py +116 -0
- xfintech/serde/serialiser/python.py +71 -0
- xfintech/serde/serialiser/tests/__init__.py +1 -0
- xfintech/serde/serialiser/tests/test_class_pandasserialiser_all.py +474 -0
- xfintech/serde/serialiser/tests/test_class_pythonserialiser_all.py +508 -0
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test suite for TushareRelayClient and RelayConnection classes
|
|
3
|
+
Tests cover initialization, authentication, API calls, and health checks
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import gzip
|
|
7
|
+
import json
|
|
8
|
+
from unittest.mock import Mock, patch
|
|
9
|
+
|
|
10
|
+
import pandas as pd
|
|
11
|
+
import pytest
|
|
12
|
+
import requests
|
|
13
|
+
|
|
14
|
+
from xfintech.data.source.tushare.session.relay import RelayConnection, TushareRelayClient
|
|
15
|
+
|
|
16
|
+
# ============================================================================
|
|
17
|
+
# TushareRelayClient Initialization Tests
|
|
18
|
+
# ============================================================================
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_relay_client_init_basic():
|
|
22
|
+
"""Test TushareRelayClient initialization with basic parameters"""
|
|
23
|
+
client = TushareRelayClient(
|
|
24
|
+
url="https://relay.example.com",
|
|
25
|
+
secret="test-secret",
|
|
26
|
+
)
|
|
27
|
+
assert client.url == "https://relay.example.com"
|
|
28
|
+
assert client.secret == "test-secret"
|
|
29
|
+
assert client.timeout == 180
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_relay_client_init_custom_timeout():
|
|
33
|
+
"""Test TushareRelayClient initialization with custom timeout"""
|
|
34
|
+
client = TushareRelayClient(
|
|
35
|
+
url="https://relay.example.com",
|
|
36
|
+
secret="test-secret",
|
|
37
|
+
timeout=300,
|
|
38
|
+
)
|
|
39
|
+
assert client.timeout == 300
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_relay_client_init_strips_trailing_slash():
|
|
43
|
+
"""Test TushareRelayClient strips trailing slash from URL"""
|
|
44
|
+
client = TushareRelayClient(
|
|
45
|
+
url="https://relay.example.com/",
|
|
46
|
+
secret="test-secret",
|
|
47
|
+
)
|
|
48
|
+
assert client.url == "https://relay.example.com"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_relay_client_init_multiple_trailing_slashes():
|
|
52
|
+
"""Test TushareRelayClient strips multiple trailing slashes"""
|
|
53
|
+
client = TushareRelayClient(
|
|
54
|
+
url="https://relay.example.com///",
|
|
55
|
+
secret="test-secret",
|
|
56
|
+
)
|
|
57
|
+
assert client.url == "https://relay.example.com"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_relay_client_init_empty_url():
|
|
61
|
+
"""Test TushareRelayClient raises error with empty URL"""
|
|
62
|
+
with pytest.raises(ValueError, match="Relay URL must be provided"):
|
|
63
|
+
TushareRelayClient(
|
|
64
|
+
url="",
|
|
65
|
+
secret="test-secret",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_relay_client_init_none_url():
|
|
70
|
+
"""Test TushareRelayClient raises error with None URL"""
|
|
71
|
+
with pytest.raises(ValueError, match="Relay URL must be provided"):
|
|
72
|
+
TushareRelayClient(
|
|
73
|
+
url=None,
|
|
74
|
+
secret="test-secret",
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_relay_client_init_empty_secret():
|
|
79
|
+
"""Test TushareRelayClient raises error with empty secret"""
|
|
80
|
+
with pytest.raises(ValueError, match="Relay secret must be provided"):
|
|
81
|
+
TushareRelayClient(url="https://relay.example.com", secret="")
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_relay_client_init_none_secret():
|
|
85
|
+
"""Test TushareRelayClient raises error with None secret"""
|
|
86
|
+
with pytest.raises(ValueError, match="Relay secret must be provided"):
|
|
87
|
+
TushareRelayClient(url="https://relay.example.com", secret=None)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_relay_client_init_strips_secret_whitespace():
|
|
91
|
+
"""Test TushareRelayClient strips whitespace from secret"""
|
|
92
|
+
client = TushareRelayClient(
|
|
93
|
+
url="https://relay.example.com",
|
|
94
|
+
secret=" test-secret ",
|
|
95
|
+
)
|
|
96
|
+
assert client.secret == "test-secret"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# ============================================================================
|
|
100
|
+
# TushareRelayClient Resolve Methods Tests
|
|
101
|
+
# ============================================================================
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def test_relay_client_resolve_timeout_default():
|
|
105
|
+
"""Test _resolve_timeout returns default when None"""
|
|
106
|
+
client = TushareRelayClient(
|
|
107
|
+
url="https://relay.example.com",
|
|
108
|
+
secret="test-secret",
|
|
109
|
+
)
|
|
110
|
+
assert client._resolve_timeout(None) == 180
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def test_relay_client_resolve_timeout_valid():
|
|
114
|
+
"""Test _resolve_timeout returns valid timeout"""
|
|
115
|
+
client = TushareRelayClient(
|
|
116
|
+
url="https://relay.example.com",
|
|
117
|
+
secret="test-secret",
|
|
118
|
+
)
|
|
119
|
+
assert client._resolve_timeout(300) == 300
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def test_relay_client_resolve_timeout_invalid_type():
|
|
123
|
+
"""Test _resolve_timeout raises error with invalid type"""
|
|
124
|
+
client = TushareRelayClient(
|
|
125
|
+
url="https://relay.example.com",
|
|
126
|
+
secret="test-secret",
|
|
127
|
+
)
|
|
128
|
+
with pytest.raises(ValueError, match="Timeout must be an integer"):
|
|
129
|
+
client._resolve_timeout("invalid")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_relay_client_resolve_timeout_zero():
|
|
133
|
+
"""Test _resolve_timeout returns default when zero"""
|
|
134
|
+
client = TushareRelayClient(
|
|
135
|
+
url="https://relay.example.com",
|
|
136
|
+
secret="test-secret",
|
|
137
|
+
)
|
|
138
|
+
assert client._resolve_timeout(0) == 180
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_relay_client_resolve_timeout_negative():
|
|
142
|
+
"""Test _resolve_timeout returns default when negative"""
|
|
143
|
+
client = TushareRelayClient(
|
|
144
|
+
url="https://relay.example.com",
|
|
145
|
+
secret="test-secret",
|
|
146
|
+
)
|
|
147
|
+
assert client._resolve_timeout(-100) == 180
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
# ============================================================================
|
|
151
|
+
# TushareRelayClient Canonical JSON Tests
|
|
152
|
+
# ============================================================================
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def test_relay_client_canonical_json_simple():
|
|
156
|
+
"""Test canonical_json with simple data"""
|
|
157
|
+
client = TushareRelayClient(
|
|
158
|
+
url="https://relay.example.com",
|
|
159
|
+
secret="test-secret",
|
|
160
|
+
)
|
|
161
|
+
result = client.canonical_json({"key": "value"})
|
|
162
|
+
assert isinstance(result, bytes)
|
|
163
|
+
assert result == b'{"key":"value"}'
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def test_relay_client_canonical_json_sorted_keys():
|
|
167
|
+
"""Test canonical_json sorts keys"""
|
|
168
|
+
client = TushareRelayClient(
|
|
169
|
+
url="https://relay.example.com",
|
|
170
|
+
secret="test-secret",
|
|
171
|
+
)
|
|
172
|
+
result = client.canonical_json({"z": 1, "a": 2, "m": 3})
|
|
173
|
+
assert result == b'{"a":2,"m":3,"z":1}'
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_relay_client_canonical_json_no_spaces():
|
|
177
|
+
"""Test canonical_json has no spaces"""
|
|
178
|
+
client = TushareRelayClient(
|
|
179
|
+
url="https://relay.example.com",
|
|
180
|
+
secret="test-secret",
|
|
181
|
+
)
|
|
182
|
+
result = client.canonical_json({"key": "value", "num": 42})
|
|
183
|
+
assert b" " not in result
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def test_relay_client_canonical_json_unicode():
|
|
187
|
+
"""Test canonical_json handles unicode"""
|
|
188
|
+
client = TushareRelayClient(
|
|
189
|
+
url="https://relay.example.com",
|
|
190
|
+
secret="test-secret",
|
|
191
|
+
)
|
|
192
|
+
result = client.canonical_json({"中文": "测试"})
|
|
193
|
+
assert "中文" in result.decode("utf-8")
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def test_relay_client_canonical_json_nested():
|
|
197
|
+
"""Test canonical_json with nested structure"""
|
|
198
|
+
client = TushareRelayClient(
|
|
199
|
+
url="https://relay.example.com",
|
|
200
|
+
secret="test-secret",
|
|
201
|
+
)
|
|
202
|
+
data = {"outer": {"inner": {"deep": "value"}}}
|
|
203
|
+
result = client.canonical_json(data)
|
|
204
|
+
assert result == b'{"outer":{"inner":{"deep":"value"}}}'
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# ============================================================================
|
|
208
|
+
# TushareRelayClient Call Method Tests
|
|
209
|
+
# ============================================================================
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
213
|
+
@patch("xfintech.data.source.tushare.session.relay.pd.read_parquet")
|
|
214
|
+
def test_relay_client_call_basic(mock_read_parquet, mock_post):
|
|
215
|
+
"""Test call method with basic parameters"""
|
|
216
|
+
# Setup mocks
|
|
217
|
+
mock_df = pd.DataFrame({"col1": [1, 2], "col2": ["a", "b"]})
|
|
218
|
+
mock_read_parquet.return_value = mock_df
|
|
219
|
+
|
|
220
|
+
mock_response = Mock()
|
|
221
|
+
mock_response.content = gzip.compress(b"parquet_data")
|
|
222
|
+
mock_post.return_value = mock_response
|
|
223
|
+
|
|
224
|
+
# Create client and call
|
|
225
|
+
client = TushareRelayClient(
|
|
226
|
+
url="https://relay.example.com",
|
|
227
|
+
secret="test-secret",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
result = client.call(
|
|
231
|
+
method="daily",
|
|
232
|
+
limit=100,
|
|
233
|
+
offset=0,
|
|
234
|
+
params={"ts_code": "000001.SZ"},
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Verify
|
|
238
|
+
assert isinstance(result, pd.DataFrame)
|
|
239
|
+
assert mock_post.called
|
|
240
|
+
assert mock_read_parquet.called
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
244
|
+
def test_relay_client_call_correct_url(mock_post):
|
|
245
|
+
"""Test call method uses correct URL"""
|
|
246
|
+
mock_response = Mock()
|
|
247
|
+
mock_response.content = gzip.compress(b"parquet_data")
|
|
248
|
+
mock_post.return_value = mock_response
|
|
249
|
+
|
|
250
|
+
with patch("xfintech.data.source.tushare.session.relay.pd.read_parquet"):
|
|
251
|
+
client = TushareRelayClient(
|
|
252
|
+
url="https://relay.example.com",
|
|
253
|
+
secret="test-secret",
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
client.call(method="daily", limit=100, offset=0, params={})
|
|
257
|
+
|
|
258
|
+
call_args = mock_post.call_args
|
|
259
|
+
assert call_args[0][0] == "https://relay.example.com/v2/tushare/call"
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
263
|
+
def test_relay_client_call_correct_headers(mock_post):
|
|
264
|
+
"""Test call method sends correct headers"""
|
|
265
|
+
mock_response = Mock()
|
|
266
|
+
mock_response.content = gzip.compress(b"parquet_data")
|
|
267
|
+
mock_post.return_value = mock_response
|
|
268
|
+
|
|
269
|
+
with patch("xfintech.data.source.tushare.session.relay.pd.read_parquet"):
|
|
270
|
+
client = TushareRelayClient(
|
|
271
|
+
url="https://relay.example.com",
|
|
272
|
+
secret="test-secret",
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
client.call(method="daily", limit=100, offset=0, params={})
|
|
276
|
+
|
|
277
|
+
call_kwargs = mock_post.call_args[1]
|
|
278
|
+
headers = call_kwargs["headers"]
|
|
279
|
+
|
|
280
|
+
assert headers["Content-Type"] == "application/json"
|
|
281
|
+
assert "X-YNONCE" in headers
|
|
282
|
+
assert "X-YTS" in headers
|
|
283
|
+
assert "X-YSIGN" in headers
|
|
284
|
+
assert headers["X-Format"] == "parquet"
|
|
285
|
+
assert headers["X-Compression"] == "zstd+gzip"
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
289
|
+
def test_relay_client_call_with_none_limit_offset(mock_post):
|
|
290
|
+
"""Test call method with None limit and offset"""
|
|
291
|
+
mock_response = Mock()
|
|
292
|
+
mock_response.content = gzip.compress(b"parquet_data")
|
|
293
|
+
mock_post.return_value = mock_response
|
|
294
|
+
|
|
295
|
+
with patch("xfintech.data.source.tushare.session.relay.pd.read_parquet"):
|
|
296
|
+
client = TushareRelayClient(
|
|
297
|
+
url="https://relay.example.com",
|
|
298
|
+
secret="test-secret",
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
client.call(method="daily", limit=None, offset=None, params={})
|
|
302
|
+
|
|
303
|
+
call_kwargs = mock_post.call_args[1]
|
|
304
|
+
body = json.loads(call_kwargs["data"])
|
|
305
|
+
assert body["limit"] is None
|
|
306
|
+
assert body["offset"] is None
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
310
|
+
def test_relay_client_call_timeout(mock_post):
|
|
311
|
+
"""Test call method uses correct timeout"""
|
|
312
|
+
mock_response = Mock()
|
|
313
|
+
mock_response.content = gzip.compress(b"parquet_data")
|
|
314
|
+
mock_post.return_value = mock_response
|
|
315
|
+
|
|
316
|
+
with patch("xfintech.data.source.tushare.session.relay.pd.read_parquet"):
|
|
317
|
+
client = TushareRelayClient(
|
|
318
|
+
url="https://relay.example.com",
|
|
319
|
+
secret="test-secret",
|
|
320
|
+
timeout=300,
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
client.call(method="daily", limit=100, offset=0, params={})
|
|
324
|
+
|
|
325
|
+
call_kwargs = mock_post.call_args[1]
|
|
326
|
+
assert call_kwargs["timeout"] == 300
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
330
|
+
def test_relay_client_call_http_error(mock_post):
|
|
331
|
+
"""Test call method raises error on HTTP error"""
|
|
332
|
+
mock_response = Mock()
|
|
333
|
+
mock_response.raise_for_status.side_effect = requests.HTTPError("404 Not Found")
|
|
334
|
+
mock_post.return_value = mock_response
|
|
335
|
+
|
|
336
|
+
client = TushareRelayClient(
|
|
337
|
+
url="https://relay.example.com",
|
|
338
|
+
secret="test-secret",
|
|
339
|
+
)
|
|
340
|
+
|
|
341
|
+
with pytest.raises(requests.HTTPError):
|
|
342
|
+
client.call(method="daily", limit=100, offset=0, params={})
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
# ============================================================================
|
|
346
|
+
# TushareRelayClient Check Health Tests
|
|
347
|
+
# ============================================================================
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.get")
|
|
351
|
+
def test_relay_client_check_health_ok(mock_get):
|
|
352
|
+
"""Test check_health returns True when status is ok"""
|
|
353
|
+
mock_response = Mock()
|
|
354
|
+
mock_response.json.return_value = {"status": "ok"}
|
|
355
|
+
mock_get.return_value = mock_response
|
|
356
|
+
|
|
357
|
+
client = TushareRelayClient(
|
|
358
|
+
url="https://relay.example.com",
|
|
359
|
+
secret="test-secret",
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
result = client.check_health()
|
|
363
|
+
assert result is True
|
|
364
|
+
mock_get.assert_called_once_with("https://relay.example.com/health", timeout=180)
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.get")
|
|
368
|
+
def test_relay_client_check_health_not_ok(mock_get):
|
|
369
|
+
"""Test check_health raises error when status is not ok"""
|
|
370
|
+
mock_response = Mock()
|
|
371
|
+
mock_response.json.return_value = {"status": "error"}
|
|
372
|
+
mock_get.return_value = mock_response
|
|
373
|
+
|
|
374
|
+
client = TushareRelayClient(
|
|
375
|
+
url="https://relay.example.com",
|
|
376
|
+
secret="test-secret",
|
|
377
|
+
)
|
|
378
|
+
|
|
379
|
+
with pytest.raises(RuntimeError, match="Health check returned non-ok status"):
|
|
380
|
+
client.check_health()
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.get")
|
|
384
|
+
def test_relay_client_check_health_connection_error(mock_get):
|
|
385
|
+
"""Test check_health raises error on connection error"""
|
|
386
|
+
mock_get.side_effect = requests.ConnectionError("Connection failed")
|
|
387
|
+
|
|
388
|
+
client = TushareRelayClient(
|
|
389
|
+
url="https://relay.example.com",
|
|
390
|
+
secret="test-secret",
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
with pytest.raises(RuntimeError, match="Health check failed"):
|
|
394
|
+
client.check_health()
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.get")
|
|
398
|
+
def test_relay_client_check_health_timeout_error(mock_get):
|
|
399
|
+
"""Test check_health raises error on timeout"""
|
|
400
|
+
mock_get.side_effect = requests.Timeout("Request timed out")
|
|
401
|
+
|
|
402
|
+
client = TushareRelayClient(
|
|
403
|
+
url="https://relay.example.com",
|
|
404
|
+
secret="test-secret",
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
with pytest.raises(RuntimeError, match="Health check failed"):
|
|
408
|
+
client.check_health()
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.get")
|
|
412
|
+
def test_relay_client_check_health_custom_timeout(mock_get):
|
|
413
|
+
"""Test check_health uses custom timeout"""
|
|
414
|
+
mock_response = Mock()
|
|
415
|
+
mock_response.json.return_value = {"status": "ok"}
|
|
416
|
+
mock_get.return_value = mock_response
|
|
417
|
+
|
|
418
|
+
client = TushareRelayClient(
|
|
419
|
+
url="https://relay.example.com",
|
|
420
|
+
secret="test-secret",
|
|
421
|
+
timeout=60,
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
client.check_health()
|
|
425
|
+
|
|
426
|
+
call_kwargs = mock_get.call_args[1]
|
|
427
|
+
assert call_kwargs["timeout"] == 60
|
|
428
|
+
|
|
429
|
+
|
|
430
|
+
# ============================================================================
|
|
431
|
+
# RelayConnection Initialization Tests
|
|
432
|
+
# ============================================================================
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def test_relay_connection_init():
|
|
436
|
+
"""Test RelayConnection initialization"""
|
|
437
|
+
client = TushareRelayClient(
|
|
438
|
+
url="https://relay.example.com",
|
|
439
|
+
secret="test-secret",
|
|
440
|
+
)
|
|
441
|
+
connection = RelayConnection(client=client)
|
|
442
|
+
assert connection.client is client
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
# ============================================================================
|
|
446
|
+
# RelayConnection Dynamic Method Tests
|
|
447
|
+
# ============================================================================
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
451
|
+
@patch("xfintech.data.source.tushare.session.relay.pd.read_parquet")
|
|
452
|
+
def test_relay_connection_dynamic_method_call(mock_read_parquet, mock_post):
|
|
453
|
+
"""Test RelayConnection dynamic method call"""
|
|
454
|
+
mock_df = pd.DataFrame({"col1": [1, 2]})
|
|
455
|
+
mock_read_parquet.return_value = mock_df
|
|
456
|
+
|
|
457
|
+
mock_response = Mock()
|
|
458
|
+
mock_response.content = gzip.compress(b"parquet_data")
|
|
459
|
+
mock_post.return_value = mock_response
|
|
460
|
+
|
|
461
|
+
client = TushareRelayClient(
|
|
462
|
+
url="https://relay.example.com",
|
|
463
|
+
secret="test-secret",
|
|
464
|
+
)
|
|
465
|
+
connection = RelayConnection(client=client)
|
|
466
|
+
|
|
467
|
+
# Call dynamic method
|
|
468
|
+
result = connection.daily(ts_code="000001.SZ", limit=100, offset=0)
|
|
469
|
+
|
|
470
|
+
assert isinstance(result, pd.DataFrame)
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
474
|
+
@patch("xfintech.data.source.tushare.session.relay.pd.read_parquet")
|
|
475
|
+
def test_relay_connection_multiple_methods(mock_read_parquet, mock_post):
|
|
476
|
+
"""Test RelayConnection can call multiple different methods"""
|
|
477
|
+
mock_df = pd.DataFrame({"col1": [1, 2]})
|
|
478
|
+
mock_read_parquet.return_value = mock_df
|
|
479
|
+
|
|
480
|
+
mock_response = Mock()
|
|
481
|
+
mock_response.content = gzip.compress(b"parquet_data")
|
|
482
|
+
mock_post.return_value = mock_response
|
|
483
|
+
|
|
484
|
+
client = TushareRelayClient(
|
|
485
|
+
url="https://relay.example.com",
|
|
486
|
+
secret="test-secret",
|
|
487
|
+
)
|
|
488
|
+
connection = RelayConnection(client=client)
|
|
489
|
+
|
|
490
|
+
# Call different methods
|
|
491
|
+
connection.daily(ts_code="000001.SZ")
|
|
492
|
+
connection.stock_basic(exchange="SSE")
|
|
493
|
+
connection.trade_cal(exchange="SSE")
|
|
494
|
+
|
|
495
|
+
assert mock_post.call_count == 3
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
499
|
+
@patch("xfintech.data.source.tushare.session.relay.pd.read_parquet")
|
|
500
|
+
def test_relay_connection_keyword_only_params(mock_read_parquet, mock_post):
|
|
501
|
+
"""Test RelayConnection enforces keyword-only parameters"""
|
|
502
|
+
mock_df = pd.DataFrame({"col1": [1, 2]})
|
|
503
|
+
mock_read_parquet.return_value = mock_df
|
|
504
|
+
|
|
505
|
+
mock_response = Mock()
|
|
506
|
+
mock_response.content = gzip.compress(b"parquet_data")
|
|
507
|
+
mock_post.return_value = mock_response
|
|
508
|
+
|
|
509
|
+
client = TushareRelayClient(
|
|
510
|
+
url="https://relay.example.com",
|
|
511
|
+
secret="test-secret",
|
|
512
|
+
)
|
|
513
|
+
connection = RelayConnection(client=client)
|
|
514
|
+
|
|
515
|
+
# Should work with keywords
|
|
516
|
+
result = connection.daily(ts_code="000001.SZ", limit=100)
|
|
517
|
+
assert isinstance(result, pd.DataFrame)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
# ============================================================================
|
|
521
|
+
# Integration Tests
|
|
522
|
+
# ============================================================================
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
526
|
+
@patch("xfintech.data.source.tushare.session.relay.pd.read_parquet")
|
|
527
|
+
def test_relay_full_workflow(mock_read_parquet, mock_post):
|
|
528
|
+
"""Test complete relay workflow"""
|
|
529
|
+
mock_df = pd.DataFrame(
|
|
530
|
+
{
|
|
531
|
+
"ts_code": ["000001.SZ", "000002.SZ"],
|
|
532
|
+
"trade_date": ["20240101", "20240101"],
|
|
533
|
+
"close": [10.5, 20.3],
|
|
534
|
+
}
|
|
535
|
+
)
|
|
536
|
+
mock_read_parquet.return_value = mock_df
|
|
537
|
+
|
|
538
|
+
mock_response = Mock()
|
|
539
|
+
mock_response.content = gzip.compress(b"parquet_data")
|
|
540
|
+
mock_post.return_value = mock_response
|
|
541
|
+
|
|
542
|
+
# Create client and connection
|
|
543
|
+
client = TushareRelayClient(
|
|
544
|
+
url="https://relay.example.com",
|
|
545
|
+
secret="test-secret",
|
|
546
|
+
timeout=120,
|
|
547
|
+
)
|
|
548
|
+
connection = RelayConnection(client=client)
|
|
549
|
+
|
|
550
|
+
# Fetch data
|
|
551
|
+
result = connection.daily(
|
|
552
|
+
ts_code="000001.SZ",
|
|
553
|
+
start_date="20240101",
|
|
554
|
+
end_date="20240131",
|
|
555
|
+
limit=1000,
|
|
556
|
+
offset=0,
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
assert isinstance(result, pd.DataFrame)
|
|
560
|
+
assert len(result) == 2
|
|
561
|
+
assert "ts_code" in result.columns
|
|
562
|
+
|
|
563
|
+
|
|
564
|
+
def test_relay_client_constants():
|
|
565
|
+
"""Test TushareRelayClient class constants"""
|
|
566
|
+
assert TushareRelayClient.DEFAULT_TIMEOUT == 180
|
|
567
|
+
|
|
568
|
+
|
|
569
|
+
# ============================================================================
|
|
570
|
+
# TushareRelayClient Refresh Tests
|
|
571
|
+
# ============================================================================
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
575
|
+
def test_relay_client_refresh_success(mock_post):
|
|
576
|
+
"""Test refresh method with successful response"""
|
|
577
|
+
mock_response = Mock()
|
|
578
|
+
mock_response.json.return_value = {"status": "ok", "message": "Refreshed"}
|
|
579
|
+
mock_post.return_value = mock_response
|
|
580
|
+
|
|
581
|
+
client = TushareRelayClient(
|
|
582
|
+
url="https://relay.example.com",
|
|
583
|
+
secret="test-secret",
|
|
584
|
+
)
|
|
585
|
+
result = client.refresh()
|
|
586
|
+
|
|
587
|
+
assert result is True
|
|
588
|
+
mock_post.assert_called_once()
|
|
589
|
+
|
|
590
|
+
# Verify the endpoint
|
|
591
|
+
call_args = mock_post.call_args
|
|
592
|
+
assert call_args[0][0] == "https://relay.example.com/v2/tushare/refresh"
|
|
593
|
+
|
|
594
|
+
# Verify headers
|
|
595
|
+
headers = call_args[1]["headers"]
|
|
596
|
+
assert "X-YNONCE" in headers
|
|
597
|
+
assert "X-YTS" in headers
|
|
598
|
+
assert "X-YSIGN" in headers
|
|
599
|
+
assert headers["Content-Type"] == "application/json"
|
|
600
|
+
|
|
601
|
+
|
|
602
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
603
|
+
def test_relay_client_refresh_non_ok_status(mock_post):
|
|
604
|
+
"""Test refresh method raises error on non-ok status"""
|
|
605
|
+
mock_response = Mock()
|
|
606
|
+
mock_response.json.return_value = {"status": "error", "message": "Failed"}
|
|
607
|
+
mock_post.return_value = mock_response
|
|
608
|
+
|
|
609
|
+
client = TushareRelayClient(
|
|
610
|
+
url="https://relay.example.com",
|
|
611
|
+
secret="test-secret",
|
|
612
|
+
)
|
|
613
|
+
|
|
614
|
+
with pytest.raises(RuntimeError, match="Refresh returned non-ok status"):
|
|
615
|
+
client.refresh()
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
619
|
+
def test_relay_client_refresh_http_error(mock_post):
|
|
620
|
+
"""Test refresh method handles HTTP errors"""
|
|
621
|
+
mock_post.side_effect = requests.exceptions.HTTPError("500 Server Error")
|
|
622
|
+
|
|
623
|
+
client = TushareRelayClient(
|
|
624
|
+
url="https://relay.example.com",
|
|
625
|
+
secret="test-secret",
|
|
626
|
+
)
|
|
627
|
+
|
|
628
|
+
with pytest.raises(RuntimeError, match="Tushare refresh failed"):
|
|
629
|
+
client.refresh()
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
633
|
+
def test_relay_client_refresh_timeout(mock_post):
|
|
634
|
+
"""Test refresh method handles timeout errors"""
|
|
635
|
+
mock_post.side_effect = requests.exceptions.Timeout("Request timeout")
|
|
636
|
+
|
|
637
|
+
client = TushareRelayClient(
|
|
638
|
+
url="https://relay.example.com",
|
|
639
|
+
secret="test-secret",
|
|
640
|
+
timeout=5,
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
with pytest.raises(RuntimeError, match="Tushare refresh failed"):
|
|
644
|
+
client.refresh()
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
648
|
+
def test_relay_client_refresh_uses_correct_timeout(mock_post):
|
|
649
|
+
"""Test refresh method uses client timeout setting"""
|
|
650
|
+
mock_response = Mock()
|
|
651
|
+
mock_response.json.return_value = {"status": "ok"}
|
|
652
|
+
mock_post.return_value = mock_response
|
|
653
|
+
|
|
654
|
+
client = TushareRelayClient(
|
|
655
|
+
url="https://relay.example.com",
|
|
656
|
+
secret="test-secret",
|
|
657
|
+
timeout=300,
|
|
658
|
+
)
|
|
659
|
+
client.refresh()
|
|
660
|
+
|
|
661
|
+
call_args = mock_post.call_args
|
|
662
|
+
assert call_args[1]["timeout"] == 300
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
666
|
+
def test_relay_client_refresh_authentication(mock_post):
|
|
667
|
+
"""Test refresh method sends proper authentication"""
|
|
668
|
+
mock_response = Mock()
|
|
669
|
+
mock_response.json.return_value = {"status": "ok"}
|
|
670
|
+
mock_post.return_value = mock_response
|
|
671
|
+
|
|
672
|
+
client = TushareRelayClient(
|
|
673
|
+
url="https://relay.example.com",
|
|
674
|
+
secret="my-secret-key",
|
|
675
|
+
)
|
|
676
|
+
client.refresh()
|
|
677
|
+
|
|
678
|
+
# Verify authentication headers are present
|
|
679
|
+
call_args = mock_post.call_args
|
|
680
|
+
headers = call_args[1]["headers"]
|
|
681
|
+
|
|
682
|
+
assert len(headers["X-YNONCE"]) == 32 # 16 bytes hex = 32 chars
|
|
683
|
+
assert headers["X-YTS"].isdigit()
|
|
684
|
+
assert len(headers["X-YSIGN"]) == 64 # SHA256 hex = 64 chars
|
|
685
|
+
|
|
686
|
+
|
|
687
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
688
|
+
def test_relay_client_refresh_empty_payload(mock_post):
|
|
689
|
+
"""Test refresh method sends empty payload"""
|
|
690
|
+
mock_response = Mock()
|
|
691
|
+
mock_response.json.return_value = {"status": "ok"}
|
|
692
|
+
mock_post.return_value = mock_response
|
|
693
|
+
|
|
694
|
+
client = TushareRelayClient(
|
|
695
|
+
url="https://relay.example.com",
|
|
696
|
+
secret="test-secret",
|
|
697
|
+
)
|
|
698
|
+
client.refresh()
|
|
699
|
+
|
|
700
|
+
# Verify empty JSON payload
|
|
701
|
+
call_args = mock_post.call_args
|
|
702
|
+
data = call_args[1]["data"]
|
|
703
|
+
assert data == b"{}"
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
@patch("xfintech.data.source.tushare.session.relay.requests.post")
|
|
707
|
+
def test_relay_client_refresh_json_decode_error(mock_post):
|
|
708
|
+
"""Test refresh method handles JSON decode errors"""
|
|
709
|
+
mock_response = Mock()
|
|
710
|
+
mock_response.json.side_effect = ValueError("Invalid JSON")
|
|
711
|
+
mock_post.return_value = mock_response
|
|
712
|
+
|
|
713
|
+
client = TushareRelayClient(
|
|
714
|
+
url="https://relay.example.com",
|
|
715
|
+
secret="test-secret",
|
|
716
|
+
)
|
|
717
|
+
|
|
718
|
+
with pytest.raises(RuntimeError, match="Tushare refresh failed"):
|
|
719
|
+
client.refresh()
|