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,589 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test suite for TushareJob class
|
|
3
|
+
Tests cover initialization, connection resolution, pagination, and data fetching
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from unittest.mock import Mock, 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.job.job import TushareJob
|
|
17
|
+
from xfintech.data.source.tushare.session.session import Session
|
|
18
|
+
|
|
19
|
+
# ============================================================================
|
|
20
|
+
# Helper Classes for Testing
|
|
21
|
+
# ============================================================================
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ConcreteTushareJob(TushareJob):
|
|
25
|
+
"""Concrete implementation for testing"""
|
|
26
|
+
|
|
27
|
+
def _run(self) -> pd.DataFrame:
|
|
28
|
+
return pd.DataFrame({"test": [1, 2, 3]})
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class FetchAllJob(TushareJob):
|
|
32
|
+
"""Job that uses _fetchall in _run"""
|
|
33
|
+
|
|
34
|
+
def _run(self) -> pd.DataFrame:
|
|
35
|
+
api = Mock(return_value=pd.DataFrame({"data": [1, 2, 3]}))
|
|
36
|
+
return self._fetchall(api, symbol="TEST")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# ============================================================================
|
|
40
|
+
# Initialization Tests
|
|
41
|
+
# ============================================================================
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_tusharejob_init_basic():
|
|
45
|
+
"""Test TushareJob basic initialization"""
|
|
46
|
+
mock_session = Mock(spec=Session)
|
|
47
|
+
mock_session.connection = Mock()
|
|
48
|
+
|
|
49
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session)
|
|
50
|
+
|
|
51
|
+
assert job.name == "test_job"
|
|
52
|
+
assert job.key == "test_key"
|
|
53
|
+
assert job.connection is not None
|
|
54
|
+
assert isinstance(job.params, Params)
|
|
55
|
+
assert isinstance(job.coolant, Coolant)
|
|
56
|
+
assert isinstance(job.paginate, Paginate)
|
|
57
|
+
assert isinstance(job.retry, Retry)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def test_tusharejob_init_with_params():
|
|
61
|
+
"""Test TushareJob initialization with params dict"""
|
|
62
|
+
mock_session = Mock(spec=Session)
|
|
63
|
+
mock_session.connection = Mock()
|
|
64
|
+
|
|
65
|
+
job = ConcreteTushareJob(
|
|
66
|
+
name="test_job", key="test_key", session=mock_session, params={"symbol": "AAPL", "date": "20240101"}
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
assert job.params.to_dict()["symbol"] == "AAPL"
|
|
70
|
+
assert job.params.to_dict()["date"] == "20240101"
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def test_tusharejob_init_with_params_object():
|
|
74
|
+
"""Test TushareJob initialization with Params object"""
|
|
75
|
+
mock_session = Mock(spec=Session)
|
|
76
|
+
mock_session.connection = Mock()
|
|
77
|
+
params = Params(symbol="TSLA", exchange="NASDAQ")
|
|
78
|
+
|
|
79
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session, params=params)
|
|
80
|
+
|
|
81
|
+
assert job.params is params
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_tusharejob_init_with_coolant_dict():
|
|
85
|
+
"""Test TushareJob initialization with coolant dict"""
|
|
86
|
+
mock_session = Mock(spec=Session)
|
|
87
|
+
mock_session.connection = Mock()
|
|
88
|
+
|
|
89
|
+
job = ConcreteTushareJob(
|
|
90
|
+
name="test_job", key="test_key", session=mock_session, coolant={"interval": 2, "use_jitter": True}
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
assert job.coolant.interval == 2
|
|
94
|
+
assert job.coolant.use_jitter is True
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_tusharejob_init_with_paginate_dict():
|
|
98
|
+
"""Test TushareJob initialization with paginate dict"""
|
|
99
|
+
mock_session = Mock(spec=Session)
|
|
100
|
+
mock_session.connection = Mock()
|
|
101
|
+
|
|
102
|
+
job = ConcreteTushareJob(
|
|
103
|
+
name="test_job", key="test_key", session=mock_session, paginate={"pagesize": 500, "pagelimit": 5}
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
assert job.paginate.pagesize == 500
|
|
107
|
+
assert job.paginate.pagelimit == 5
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def test_tusharejob_init_with_retry_dict():
|
|
111
|
+
"""Test TushareJob initialization with retry dict"""
|
|
112
|
+
mock_session = Mock(spec=Session)
|
|
113
|
+
mock_session.connection = Mock()
|
|
114
|
+
|
|
115
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session, retry={"retry": 5, "wait": 2})
|
|
116
|
+
|
|
117
|
+
assert job.retry.retry == 5
|
|
118
|
+
assert job.retry.wait == 2
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def test_tusharejob_init_with_cache_true():
|
|
122
|
+
"""Test TushareJob initialization with cache enabled"""
|
|
123
|
+
mock_session = Mock(spec=Session)
|
|
124
|
+
mock_session.connection = Mock()
|
|
125
|
+
|
|
126
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session, cache=True)
|
|
127
|
+
|
|
128
|
+
assert job.cache is not None
|
|
129
|
+
assert isinstance(job.cache, Cache)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def test_tusharejob_init_with_cache_false():
|
|
133
|
+
"""Test TushareJob initialization with cache disabled"""
|
|
134
|
+
mock_session = Mock(spec=Session)
|
|
135
|
+
mock_session.connection = Mock()
|
|
136
|
+
|
|
137
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session, cache=False)
|
|
138
|
+
|
|
139
|
+
assert job.cache is None
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_tusharejob_init_marks_checkpoints():
|
|
143
|
+
"""Test TushareJob marks initialization checkpoints"""
|
|
144
|
+
mock_session = Mock(spec=Session)
|
|
145
|
+
mock_session.connection = Mock()
|
|
146
|
+
|
|
147
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session)
|
|
148
|
+
|
|
149
|
+
# Should have marked both init[OK] and resolve_connection
|
|
150
|
+
marks = job.metric.marks
|
|
151
|
+
assert "init[OK]" in marks
|
|
152
|
+
assert "_resolve_connection[OK]" in marks
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# ============================================================================
|
|
156
|
+
# Connection Resolution Tests
|
|
157
|
+
# ============================================================================
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def test_resolve_connection_success():
|
|
161
|
+
"""Test _resolve_connection with valid session"""
|
|
162
|
+
mock_session = Mock(spec=Session)
|
|
163
|
+
mock_connection = Mock()
|
|
164
|
+
mock_session.connection = mock_connection
|
|
165
|
+
|
|
166
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session)
|
|
167
|
+
|
|
168
|
+
assert job.connection is mock_connection
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_resolve_connection_no_connection_raises_error():
|
|
172
|
+
"""Test _resolve_connection raises error when session has no connection"""
|
|
173
|
+
mock_session = Mock(spec=Session)
|
|
174
|
+
mock_session.connection = None
|
|
175
|
+
|
|
176
|
+
with pytest.raises(ConnectionError, match="No active connection found in session"):
|
|
177
|
+
ConcreteTushareJob(name="test_job", key="test_key", session=mock_session)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_resolve_connection_missing_attribute():
|
|
181
|
+
"""Test _resolve_connection handles missing connection attribute"""
|
|
182
|
+
mock_session = Mock(spec=Session)
|
|
183
|
+
delattr(mock_session, "connection")
|
|
184
|
+
|
|
185
|
+
with pytest.raises(ConnectionError):
|
|
186
|
+
ConcreteTushareJob(name="test_job", key="test_key", session=mock_session)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def test_resolve_connection_marks_checkpoint():
|
|
190
|
+
"""Test _resolve_connection marks checkpoint on success"""
|
|
191
|
+
mock_session = Mock(spec=Session)
|
|
192
|
+
mock_session.connection = Mock()
|
|
193
|
+
|
|
194
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session)
|
|
195
|
+
|
|
196
|
+
assert "_resolve_connection[OK]" in job.metric.marks
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# ============================================================================
|
|
200
|
+
# _fetchall Method Tests
|
|
201
|
+
# ============================================================================
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def test_fetchall_single_page():
|
|
205
|
+
"""Test _fetchall with data fitting in single page"""
|
|
206
|
+
mock_session = Mock(spec=Session)
|
|
207
|
+
mock_session.connection = Mock()
|
|
208
|
+
|
|
209
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session, paginate={"pagesize": 100})
|
|
210
|
+
|
|
211
|
+
# Mock API that returns less than pagesize
|
|
212
|
+
mock_api = Mock(return_value=pd.DataFrame({"col": [1, 2, 3]}))
|
|
213
|
+
|
|
214
|
+
result = job._fetchall(mock_api, symbol="TEST")
|
|
215
|
+
|
|
216
|
+
assert isinstance(result, pd.DataFrame)
|
|
217
|
+
assert len(result) == 3
|
|
218
|
+
assert mock_api.call_count == 1
|
|
219
|
+
mock_api.assert_called_with(limit=100, offset=0, symbol="TEST")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def test_fetchall_multiple_pages():
|
|
223
|
+
"""Test _fetchall with data spanning multiple pages"""
|
|
224
|
+
mock_session = Mock(spec=Session)
|
|
225
|
+
mock_session.connection = Mock()
|
|
226
|
+
|
|
227
|
+
job = ConcreteTushareJob(
|
|
228
|
+
name="test_job", key="test_key", session=mock_session, paginate={"pagesize": 10, "pagelimit": 5}
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
# Mock API that returns full pages then partial
|
|
232
|
+
mock_api = Mock(
|
|
233
|
+
side_effect=[
|
|
234
|
+
pd.DataFrame({"col": range(10)}), # Full page 1
|
|
235
|
+
pd.DataFrame({"col": range(10, 20)}), # Full page 2
|
|
236
|
+
pd.DataFrame({"col": range(20, 25)}), # Partial page 3
|
|
237
|
+
]
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
result = job._fetchall(mock_api, symbol="TEST")
|
|
241
|
+
|
|
242
|
+
assert isinstance(result, pd.DataFrame)
|
|
243
|
+
assert len(result) == 25
|
|
244
|
+
assert mock_api.call_count == 3
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def test_fetchall_empty_result():
|
|
248
|
+
"""Test _fetchall with empty result"""
|
|
249
|
+
mock_session = Mock(spec=Session)
|
|
250
|
+
mock_session.connection = Mock()
|
|
251
|
+
|
|
252
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session)
|
|
253
|
+
|
|
254
|
+
mock_api = Mock(return_value=pd.DataFrame())
|
|
255
|
+
|
|
256
|
+
result = job._fetchall(mock_api, symbol="TEST")
|
|
257
|
+
|
|
258
|
+
assert isinstance(result, pd.DataFrame)
|
|
259
|
+
assert len(result) == 0
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def test_fetchall_none_result():
|
|
263
|
+
"""Test _fetchall handles None result"""
|
|
264
|
+
mock_session = Mock(spec=Session)
|
|
265
|
+
mock_session.connection = Mock()
|
|
266
|
+
|
|
267
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session)
|
|
268
|
+
|
|
269
|
+
mock_api = Mock(return_value=None)
|
|
270
|
+
|
|
271
|
+
result = job._fetchall(mock_api, symbol="TEST")
|
|
272
|
+
|
|
273
|
+
assert isinstance(result, pd.DataFrame)
|
|
274
|
+
assert len(result) == 0
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
def test_fetchall_respects_pagelimit():
|
|
278
|
+
"""Test _fetchall respects pagelimit"""
|
|
279
|
+
mock_session = Mock(spec=Session)
|
|
280
|
+
mock_session.connection = Mock()
|
|
281
|
+
|
|
282
|
+
job = ConcreteTushareJob(
|
|
283
|
+
name="test_job", key="test_key", session=mock_session, paginate={"pagesize": 10, "pagelimit": 3}
|
|
284
|
+
)
|
|
285
|
+
|
|
286
|
+
# Mock API that always returns full pages
|
|
287
|
+
mock_api = Mock(return_value=pd.DataFrame({"col": range(10)}))
|
|
288
|
+
|
|
289
|
+
result = job._fetchall(mock_api)
|
|
290
|
+
|
|
291
|
+
# Should stop at pagelimit even though pages are full
|
|
292
|
+
assert mock_api.call_count == 3
|
|
293
|
+
assert len(result) == 30
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def test_fetchall_calls_coolant():
|
|
297
|
+
"""Test _fetchall calls coolant between pages"""
|
|
298
|
+
mock_session = Mock(spec=Session)
|
|
299
|
+
mock_session.connection = Mock()
|
|
300
|
+
|
|
301
|
+
job = ConcreteTushareJob(
|
|
302
|
+
name="test_job", key="test_key", session=mock_session, paginate={"pagesize": 10}, coolant={"interval": 1}
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
with patch.object(job.coolant, "cool") as mock_cool:
|
|
306
|
+
mock_api = Mock(
|
|
307
|
+
side_effect=[
|
|
308
|
+
pd.DataFrame({"col": range(10)}),
|
|
309
|
+
pd.DataFrame({"col": range(10, 20)}),
|
|
310
|
+
pd.DataFrame({"col": range(20, 25)}),
|
|
311
|
+
]
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
job._fetchall(mock_api)
|
|
315
|
+
|
|
316
|
+
# cool() should be called between pages (2 times for 3 pages)
|
|
317
|
+
assert mock_cool.call_count == 2
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def test_fetchall_updates_pagination_offset():
|
|
321
|
+
"""Test _fetchall updates pagination offset correctly"""
|
|
322
|
+
mock_session = Mock(spec=Session)
|
|
323
|
+
mock_session.connection = Mock()
|
|
324
|
+
|
|
325
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session, paginate={"pagesize": 100})
|
|
326
|
+
|
|
327
|
+
mock_api = Mock(
|
|
328
|
+
side_effect=[
|
|
329
|
+
pd.DataFrame({"col": range(100)}),
|
|
330
|
+
pd.DataFrame({"col": range(100, 200)}),
|
|
331
|
+
pd.DataFrame({"col": range(200, 250)}),
|
|
332
|
+
]
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
job._fetchall(mock_api)
|
|
336
|
+
|
|
337
|
+
# Check that offset was updated in calls
|
|
338
|
+
calls = mock_api.call_args_list
|
|
339
|
+
assert calls[0][1]["offset"] == 0
|
|
340
|
+
assert calls[1][1]["offset"] == 100
|
|
341
|
+
assert calls[2][1]["offset"] == 200
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def test_fetchall_marks_checkpoints():
|
|
345
|
+
"""Test _fetchall marks checkpoints for each page"""
|
|
346
|
+
mock_session = Mock(spec=Session)
|
|
347
|
+
mock_session.connection = Mock()
|
|
348
|
+
|
|
349
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session, paginate={"pagesize": 10})
|
|
350
|
+
|
|
351
|
+
mock_api = Mock(
|
|
352
|
+
side_effect=[
|
|
353
|
+
pd.DataFrame({"col": range(10)}),
|
|
354
|
+
pd.DataFrame({"col": range(10, 15)}),
|
|
355
|
+
]
|
|
356
|
+
)
|
|
357
|
+
|
|
358
|
+
job._fetchall(mock_api)
|
|
359
|
+
|
|
360
|
+
marks = job.metric.marks
|
|
361
|
+
assert "_fetchall[pagenum=0, OK]" in marks
|
|
362
|
+
assert "_fetchall[pagenum=1, OK]" in marks
|
|
363
|
+
assert "_fetchall[OK]" in marks
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def test_fetchall_passes_params_to_api():
|
|
367
|
+
"""Test _fetchall passes all params to API"""
|
|
368
|
+
mock_session = Mock(spec=Session)
|
|
369
|
+
mock_session.connection = Mock()
|
|
370
|
+
|
|
371
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session)
|
|
372
|
+
|
|
373
|
+
mock_api = Mock(return_value=pd.DataFrame({"col": [1, 2, 3]}))
|
|
374
|
+
|
|
375
|
+
job._fetchall(mock_api, symbol="AAPL", exchange="NASDAQ", date="20240101")
|
|
376
|
+
|
|
377
|
+
mock_api.assert_called_once_with(
|
|
378
|
+
limit=job.paginate.pagesize, offset=0, symbol="AAPL", exchange="NASDAQ", date="20240101"
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
def test_fetchall_concatenates_correctly():
|
|
383
|
+
"""Test _fetchall concatenates DataFrames correctly"""
|
|
384
|
+
mock_session = Mock(spec=Session)
|
|
385
|
+
mock_session.connection = Mock()
|
|
386
|
+
|
|
387
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session, paginate={"pagesize": 5})
|
|
388
|
+
|
|
389
|
+
# Full pages then partial to trigger multiple pages
|
|
390
|
+
mock_api = Mock(
|
|
391
|
+
side_effect=[
|
|
392
|
+
pd.DataFrame({"col1": [1, 2, 3, 4, 5], "col2": ["a", "b", "c", "d", "e"]}),
|
|
393
|
+
pd.DataFrame({"col1": [6, 7, 8, 9, 10], "col2": ["f", "g", "h", "i", "j"]}),
|
|
394
|
+
pd.DataFrame({"col1": [11, 12], "col2": ["k", "l"]}),
|
|
395
|
+
]
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
result = job._fetchall(mock_api)
|
|
399
|
+
|
|
400
|
+
assert len(result) == 12
|
|
401
|
+
assert list(result.columns) == ["col1", "col2"]
|
|
402
|
+
assert result["col1"].tolist() == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def test_fetchall_resets_index():
|
|
406
|
+
"""Test _fetchall resets index in concatenated result"""
|
|
407
|
+
mock_session = Mock(spec=Session)
|
|
408
|
+
mock_session.connection = Mock()
|
|
409
|
+
|
|
410
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session, paginate={"pagesize": 3})
|
|
411
|
+
|
|
412
|
+
# DataFrames with different indices, both full pages
|
|
413
|
+
df1 = pd.DataFrame({"col": [1, 2, 3]}, index=[10, 20, 30])
|
|
414
|
+
df2 = pd.DataFrame({"col": [4, 5]}, index=[40, 50])
|
|
415
|
+
|
|
416
|
+
mock_api = Mock(side_effect=[df1, df2])
|
|
417
|
+
|
|
418
|
+
result = job._fetchall(mock_api)
|
|
419
|
+
|
|
420
|
+
# Index should be reset to 0, 1, 2, 3, 4
|
|
421
|
+
assert result.index.tolist() == [0, 1, 2, 3, 4]
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
# ============================================================================
|
|
425
|
+
# Integration Tests
|
|
426
|
+
# ============================================================================
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
def test_tusharejob_full_workflow():
|
|
430
|
+
"""Test complete TushareJob workflow"""
|
|
431
|
+
mock_session = Mock(spec=Session)
|
|
432
|
+
mock_api_method = Mock(
|
|
433
|
+
side_effect=[
|
|
434
|
+
pd.DataFrame({"symbol": ["AAPL", "GOOGL"], "price": [150.0, 2800.0]}),
|
|
435
|
+
pd.DataFrame({"symbol": ["MSFT"], "price": [300.0]}),
|
|
436
|
+
]
|
|
437
|
+
)
|
|
438
|
+
mock_session.connection = Mock(stock_basic=mock_api_method)
|
|
439
|
+
|
|
440
|
+
class StockJob(TushareJob):
|
|
441
|
+
def _run(self):
|
|
442
|
+
return self._fetchall(api=self.connection.stock_basic, exchange="NASDAQ")
|
|
443
|
+
|
|
444
|
+
job = StockJob(
|
|
445
|
+
name="stock_job",
|
|
446
|
+
key="stock_001",
|
|
447
|
+
session=mock_session,
|
|
448
|
+
params={"exchange": "NASDAQ"},
|
|
449
|
+
paginate={"pagesize": 2, "pagelimit": 10},
|
|
450
|
+
retry={"retry": 2, "wait": 0},
|
|
451
|
+
)
|
|
452
|
+
|
|
453
|
+
result = job.run()
|
|
454
|
+
|
|
455
|
+
assert isinstance(result, pd.DataFrame)
|
|
456
|
+
assert len(result) == 3
|
|
457
|
+
assert "symbol" in result.columns
|
|
458
|
+
assert "price" in result.columns
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def test_tusharejob_with_cache():
|
|
462
|
+
"""Test TushareJob with caching enabled"""
|
|
463
|
+
mock_session = Mock(spec=Session)
|
|
464
|
+
mock_session.connection = Mock()
|
|
465
|
+
|
|
466
|
+
job = ConcreteTushareJob(name="cached_job", key="cached_001", session=mock_session, cache=True)
|
|
467
|
+
|
|
468
|
+
# Test cache operations
|
|
469
|
+
job.set_cache("test_key", {"data": "test_value"})
|
|
470
|
+
cached_value = job.get_cache("test_key")
|
|
471
|
+
|
|
472
|
+
assert cached_value == {"data": "test_value"}
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def test_tusharejob_inherits_job_methods():
|
|
476
|
+
"""Test TushareJob inherits all Job methods"""
|
|
477
|
+
mock_session = Mock(spec=Session)
|
|
478
|
+
mock_session.connection = Mock()
|
|
479
|
+
|
|
480
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session, params={"symbol": "AAPL"})
|
|
481
|
+
|
|
482
|
+
# Test inherited methods
|
|
483
|
+
assert hasattr(job, "run")
|
|
484
|
+
assert hasattr(job, "markpoint")
|
|
485
|
+
assert hasattr(job, "cool")
|
|
486
|
+
assert hasattr(job, "get_params")
|
|
487
|
+
assert hasattr(job, "reset")
|
|
488
|
+
assert hasattr(job, "describe")
|
|
489
|
+
assert hasattr(job, "to_dict")
|
|
490
|
+
|
|
491
|
+
# Test get_params works
|
|
492
|
+
params = job.get_params()
|
|
493
|
+
assert params["symbol"] == "AAPL"
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def test_tusharejob_metric_tracking():
|
|
497
|
+
"""Test TushareJob tracks metrics correctly"""
|
|
498
|
+
mock_session = Mock(spec=Session)
|
|
499
|
+
mock_session.connection = Mock()
|
|
500
|
+
|
|
501
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session)
|
|
502
|
+
|
|
503
|
+
# Marks are created during __init__
|
|
504
|
+
assert "init[OK]" in job.metric.marks
|
|
505
|
+
assert "_resolve_connection[OK]" in job.metric.marks
|
|
506
|
+
|
|
507
|
+
# Run job - note that metric context manager resets marks
|
|
508
|
+
job.run()
|
|
509
|
+
|
|
510
|
+
# After run, metric tracks duration
|
|
511
|
+
assert job.metric.duration is not None
|
|
512
|
+
assert job.metric.start_at is not None
|
|
513
|
+
assert job.metric.finish_at is not None
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
def test_tusharejob_multiple_instances_independent():
|
|
517
|
+
"""Test multiple TushareJob instances are independent"""
|
|
518
|
+
mock_session1 = Mock(spec=Session)
|
|
519
|
+
mock_session1.connection = Mock(name="connection1")
|
|
520
|
+
|
|
521
|
+
mock_session2 = Mock(spec=Session)
|
|
522
|
+
mock_session2.connection = Mock(name="connection2")
|
|
523
|
+
|
|
524
|
+
job1 = ConcreteTushareJob(name="job1", key="key1", session=mock_session1, params={"symbol": "AAPL"})
|
|
525
|
+
|
|
526
|
+
job2 = ConcreteTushareJob(name="job2", key="key2", session=mock_session2, params={"symbol": "GOOGL"})
|
|
527
|
+
|
|
528
|
+
assert job1.name != job2.name
|
|
529
|
+
assert job1.connection != job2.connection
|
|
530
|
+
assert job1.get_params() != job2.get_params()
|
|
531
|
+
|
|
532
|
+
|
|
533
|
+
# ============================================================================
|
|
534
|
+
# Edge Case Tests
|
|
535
|
+
# ============================================================================
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def test_fetchall_with_zero_pagelimit():
|
|
539
|
+
"""Test _fetchall with pagelimit=0 iterates zero times"""
|
|
540
|
+
mock_session = Mock(spec=Session)
|
|
541
|
+
mock_session.connection = Mock()
|
|
542
|
+
|
|
543
|
+
job = ConcreteTushareJob(
|
|
544
|
+
name="test_job", key="test_key", session=mock_session, paginate={"pagesize": 100, "pagelimit": 1}
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
# API returns less than pagesize, should stop after one page
|
|
548
|
+
mock_api = Mock(return_value=pd.DataFrame({"col": [1, 2, 3]}))
|
|
549
|
+
|
|
550
|
+
result = job._fetchall(mock_api)
|
|
551
|
+
|
|
552
|
+
assert isinstance(result, pd.DataFrame)
|
|
553
|
+
assert len(result) == 3
|
|
554
|
+
assert mock_api.call_count == 1
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def test_fetchall_api_raises_exception():
|
|
558
|
+
"""Test _fetchall handles API exceptions"""
|
|
559
|
+
mock_session = Mock(spec=Session)
|
|
560
|
+
mock_session.connection = Mock()
|
|
561
|
+
|
|
562
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session)
|
|
563
|
+
|
|
564
|
+
mock_api = Mock(side_effect=RuntimeError("API Error"))
|
|
565
|
+
|
|
566
|
+
with pytest.raises(RuntimeError, match="API Error"):
|
|
567
|
+
job._fetchall(mock_api)
|
|
568
|
+
|
|
569
|
+
|
|
570
|
+
def test_fetchall_with_varying_column_names():
|
|
571
|
+
"""Test _fetchall handles DataFrames with different columns"""
|
|
572
|
+
mock_session = Mock(spec=Session)
|
|
573
|
+
mock_session.connection = Mock()
|
|
574
|
+
|
|
575
|
+
job = ConcreteTushareJob(name="test_job", key="test_key", session=mock_session, paginate={"pagesize": 10})
|
|
576
|
+
|
|
577
|
+
# First page has 2 rows (< pagesize), so loop stops after first page
|
|
578
|
+
mock_api = Mock(
|
|
579
|
+
side_effect=[
|
|
580
|
+
pd.DataFrame({"col1": [1, 2]}),
|
|
581
|
+
pd.DataFrame({"col2": [3, 4]}), # Won't be reached
|
|
582
|
+
]
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
result = job._fetchall(mock_api)
|
|
586
|
+
|
|
587
|
+
assert isinstance(result, pd.DataFrame)
|
|
588
|
+
assert len(result) == 2
|
|
589
|
+
assert mock_api.call_count == 1
|