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