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,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"
|