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,684 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test suite for Job class
|
|
3
|
+
Tests cover initialization, lifecycle management, cache operations, and protocol compliance
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, Dict
|
|
7
|
+
from unittest.mock import patch
|
|
8
|
+
|
|
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.metric import Metric
|
|
14
|
+
from xfintech.data.common.paginate import Paginate
|
|
15
|
+
from xfintech.data.common.params import Params
|
|
16
|
+
from xfintech.data.common.retry import Retry
|
|
17
|
+
from xfintech.data.job.job import Job
|
|
18
|
+
from xfintech.data.job.joblike import JobLike
|
|
19
|
+
|
|
20
|
+
# ============================================================================
|
|
21
|
+
# Helper Classes for Testing
|
|
22
|
+
# ============================================================================
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ConcreteJob(Job):
|
|
26
|
+
"""Concrete implementation for testing Job"""
|
|
27
|
+
|
|
28
|
+
def _run(self) -> Dict[str, Any]:
|
|
29
|
+
return {"status": "success", "data": [1, 2, 3]}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class FailingJob(Job):
|
|
33
|
+
"""Job that fails for testing retry logic"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, *args, fail_count=2, **kwargs):
|
|
36
|
+
super().__init__(*args, **kwargs)
|
|
37
|
+
self.attempts = 0
|
|
38
|
+
self.fail_count = fail_count
|
|
39
|
+
|
|
40
|
+
def _run(self) -> Dict[str, Any]:
|
|
41
|
+
self.attempts += 1
|
|
42
|
+
if self.attempts <= self.fail_count:
|
|
43
|
+
raise RuntimeError(f"Attempt {self.attempts} failed")
|
|
44
|
+
return {"status": "success", "attempts": self.attempts}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class CachedJob(Job):
|
|
48
|
+
"""Job that uses cache"""
|
|
49
|
+
|
|
50
|
+
def _run(self) -> Dict[str, Any]:
|
|
51
|
+
# Check cache first
|
|
52
|
+
cached = self.get_cache("result")
|
|
53
|
+
if cached:
|
|
54
|
+
return {"status": "from_cache", "data": cached}
|
|
55
|
+
|
|
56
|
+
# Compute result
|
|
57
|
+
result = {"computed": True, "value": 42}
|
|
58
|
+
self.set_cache("result", result)
|
|
59
|
+
return {"status": "computed", "data": result}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ============================================================================
|
|
63
|
+
# Job Initialization Tests
|
|
64
|
+
# ============================================================================
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_job_init_basic():
|
|
68
|
+
"""Test Job initialization with minimal parameters"""
|
|
69
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
70
|
+
|
|
71
|
+
assert job.name == "test_job"
|
|
72
|
+
assert job.key == "test_key"
|
|
73
|
+
assert isinstance(job.params, Params)
|
|
74
|
+
assert isinstance(job.coolant, Coolant)
|
|
75
|
+
assert isinstance(job.paginate, Paginate)
|
|
76
|
+
assert isinstance(job.retry, Retry)
|
|
77
|
+
assert isinstance(job.metric, Metric)
|
|
78
|
+
assert job.cache is None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_job_init_with_params_dict():
|
|
82
|
+
"""Test Job initialization with params as dict"""
|
|
83
|
+
job = ConcreteJob(name="test_job", key="test_key", params={"symbol": "AAPL", "limit": 100})
|
|
84
|
+
|
|
85
|
+
assert isinstance(job.params, Params)
|
|
86
|
+
assert job.params.symbol == "AAPL"
|
|
87
|
+
assert job.params.limit == 100
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_job_init_with_params_object():
|
|
91
|
+
"""Test Job initialization with Params object"""
|
|
92
|
+
params = Params(symbol="TSLA", date="20240115")
|
|
93
|
+
job = ConcreteJob(name="test_job", key="test_key", params=params)
|
|
94
|
+
|
|
95
|
+
assert job.params is params
|
|
96
|
+
assert job.params.symbol == "TSLA"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def test_job_init_with_coolant_dict():
|
|
100
|
+
"""Test Job initialization with coolant as dict"""
|
|
101
|
+
job = ConcreteJob(name="test_job", key="test_key", coolant={"interval": 2, "use_jitter": True})
|
|
102
|
+
|
|
103
|
+
assert isinstance(job.coolant, Coolant)
|
|
104
|
+
assert job.coolant.interval == 2
|
|
105
|
+
assert job.coolant.use_jitter is True
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def test_job_init_with_coolant_object():
|
|
109
|
+
"""Test Job initialization with Coolant object"""
|
|
110
|
+
coolant = Coolant(interval=5, use_jitter=False)
|
|
111
|
+
job = ConcreteJob(name="test_job", key="test_key", coolant=coolant)
|
|
112
|
+
|
|
113
|
+
assert job.coolant is coolant
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_job_init_with_paginate_dict():
|
|
117
|
+
"""Test Job initialization with paginate as dict"""
|
|
118
|
+
job = ConcreteJob(name="test_job", key="test_key", paginate={"pagesize": 1000, "pagelimit": 5000})
|
|
119
|
+
|
|
120
|
+
assert isinstance(job.paginate, Paginate)
|
|
121
|
+
assert job.paginate.pagesize == 1000
|
|
122
|
+
assert job.paginate.pagelimit == 5000
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_job_init_with_paginate_object():
|
|
126
|
+
"""Test Job initialization with Paginate object"""
|
|
127
|
+
paginate = Paginate(pagesize=2000, pagelimit=10000)
|
|
128
|
+
job = ConcreteJob(name="test_job", key="test_key", paginate=paginate)
|
|
129
|
+
|
|
130
|
+
assert job.paginate is paginate
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_job_init_with_retry_dict():
|
|
134
|
+
"""Test Job initialization with retry as dict"""
|
|
135
|
+
job = ConcreteJob(name="test_job", key="test_key", retry={"retry": 5, "wait": 10})
|
|
136
|
+
|
|
137
|
+
assert isinstance(job.retry, Retry)
|
|
138
|
+
assert job.retry.retry == 5
|
|
139
|
+
assert job.retry.wait == 10
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_job_init_with_retry_object():
|
|
143
|
+
"""Test Job initialization with Retry object"""
|
|
144
|
+
retry = Retry(retry=3, wait=5)
|
|
145
|
+
job = ConcreteJob(name="test_job", key="test_key", retry=retry)
|
|
146
|
+
|
|
147
|
+
assert job.retry is retry
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_job_init_with_cache_true():
|
|
151
|
+
"""Test Job initialization with cache=True"""
|
|
152
|
+
job = ConcreteJob(name="test_job", key="test_key", cache=True)
|
|
153
|
+
|
|
154
|
+
assert isinstance(job.cache, Cache)
|
|
155
|
+
assert job.cache is not None
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def test_job_init_with_cache_false():
|
|
159
|
+
"""Test Job initialization with cache=False"""
|
|
160
|
+
job = ConcreteJob(name="test_job", key="test_key", cache=False)
|
|
161
|
+
|
|
162
|
+
assert job.cache is None
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def test_job_init_with_cache_dict():
|
|
166
|
+
"""Test Job initialization with cache as dict"""
|
|
167
|
+
job = ConcreteJob(name="test_job", key="test_key", cache={"name": "custom_cache"})
|
|
168
|
+
|
|
169
|
+
assert isinstance(job.cache, Cache)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def test_job_init_with_cache_object():
|
|
173
|
+
"""Test Job initialization with Cache object"""
|
|
174
|
+
cache = Cache()
|
|
175
|
+
job = ConcreteJob(name="test_job", key="test_key", cache=cache)
|
|
176
|
+
|
|
177
|
+
assert job.cache is cache
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def test_job_init_marks_init_point():
|
|
181
|
+
"""Test Job initialization marks 'init' checkpoint"""
|
|
182
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
183
|
+
|
|
184
|
+
assert "init[OK]" in job.metric.marks
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ============================================================================
|
|
188
|
+
# Job Resolve Methods Tests
|
|
189
|
+
# ============================================================================
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_job_resolve_params_none():
|
|
193
|
+
"""Test _resolve_params with None returns default Params"""
|
|
194
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
195
|
+
result = job._resolve_params(None)
|
|
196
|
+
|
|
197
|
+
assert isinstance(result, Params)
|
|
198
|
+
assert result.to_dict() == {}
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def test_job_resolve_params_dict():
|
|
202
|
+
"""Test _resolve_params converts dict to Params"""
|
|
203
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
204
|
+
result = job._resolve_params({"key": "value"})
|
|
205
|
+
|
|
206
|
+
assert isinstance(result, Params)
|
|
207
|
+
assert result.key == "value"
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def test_job_resolve_params_object():
|
|
211
|
+
"""Test _resolve_params returns Params object as-is"""
|
|
212
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
213
|
+
params = Params(test="data")
|
|
214
|
+
result = job._resolve_params(params)
|
|
215
|
+
|
|
216
|
+
assert result is params
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def test_job_resolve_coolant_none():
|
|
220
|
+
"""Test _resolve_coolant with None returns default Coolant"""
|
|
221
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
222
|
+
result = job._resolve_coolant(None)
|
|
223
|
+
|
|
224
|
+
assert isinstance(result, Coolant)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def test_job_resolve_paginate_none():
|
|
228
|
+
"""Test _resolve_paginate with None returns default Paginate"""
|
|
229
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
230
|
+
result = job._resolve_paginate(None)
|
|
231
|
+
|
|
232
|
+
assert isinstance(result, Paginate)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def test_job_resolve_retry_none():
|
|
236
|
+
"""Test _resolve_retry with None returns default Retry"""
|
|
237
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
238
|
+
result = job._resolve_retry(None)
|
|
239
|
+
|
|
240
|
+
assert isinstance(result, Retry)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def test_job_resolve_cache_none():
|
|
244
|
+
"""Test _resolve_cache with None returns None"""
|
|
245
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
246
|
+
result = job._resolve_cache(None)
|
|
247
|
+
|
|
248
|
+
assert result is None
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def test_job_resolve_cache_true():
|
|
252
|
+
"""Test _resolve_cache with True creates Cache"""
|
|
253
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
254
|
+
result = job._resolve_cache(True)
|
|
255
|
+
|
|
256
|
+
assert isinstance(result, Cache)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
def test_job_resolve_cache_false():
|
|
260
|
+
"""Test _resolve_cache with False returns None"""
|
|
261
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
262
|
+
result = job._resolve_cache(False)
|
|
263
|
+
|
|
264
|
+
assert result is None
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# ============================================================================
|
|
268
|
+
# Job Run Method Tests
|
|
269
|
+
# ============================================================================
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
def test_job_run_basic():
|
|
273
|
+
"""Test Job run method executes successfully"""
|
|
274
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
275
|
+
result = job.run()
|
|
276
|
+
|
|
277
|
+
assert result["status"] == "success"
|
|
278
|
+
assert result["data"] == [1, 2, 3]
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def test_job_run_tracks_duration():
|
|
282
|
+
"""Test Job run tracks execution duration"""
|
|
283
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
284
|
+
job.run()
|
|
285
|
+
|
|
286
|
+
assert job.metric.duration > 0
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def test_job_run_not_implemented():
|
|
290
|
+
"""Test base Job raises NotImplementedError if _run not implemented"""
|
|
291
|
+
job = Job(name="test_job", key="test_key")
|
|
292
|
+
|
|
293
|
+
with pytest.raises(NotImplementedError, match="Subclasses must implement"):
|
|
294
|
+
job.run()
|
|
295
|
+
|
|
296
|
+
|
|
297
|
+
def test_job_run_with_retry_on_failure():
|
|
298
|
+
"""Test Job retries on failure"""
|
|
299
|
+
job = FailingJob(name="test_job", key="test_key", retry={"retry": 5, "wait": 0}, fail_count=2)
|
|
300
|
+
|
|
301
|
+
result = job.run()
|
|
302
|
+
|
|
303
|
+
assert result["status"] == "success"
|
|
304
|
+
assert job.attempts == 3 # Failed 2 times, succeeded on 3rd
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
def test_job_run_exhausts_retries():
|
|
308
|
+
"""Test Job exhausts retries and raises exception"""
|
|
309
|
+
job = FailingJob(
|
|
310
|
+
name="test_job",
|
|
311
|
+
key="test_key",
|
|
312
|
+
retry={"max_retries": 2, "interval": 0},
|
|
313
|
+
fail_count=5, # Will keep failing
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
with pytest.raises(RuntimeError):
|
|
317
|
+
job.run()
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def test_job_run_captures_error_in_metric():
|
|
321
|
+
"""Test Job captures error in metric when run fails"""
|
|
322
|
+
job = FailingJob(name="test_job", key="test_key", retry={"retry": 1, "wait": 0}, fail_count=5)
|
|
323
|
+
|
|
324
|
+
try:
|
|
325
|
+
job.run()
|
|
326
|
+
except RuntimeError:
|
|
327
|
+
pass
|
|
328
|
+
|
|
329
|
+
assert len(job.metric.errors) > 0
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
# ============================================================================
|
|
333
|
+
# Job Markpoint Tests
|
|
334
|
+
# ============================================================================
|
|
335
|
+
|
|
336
|
+
|
|
337
|
+
def test_job_markpoint():
|
|
338
|
+
"""Test Job markpoint adds checkpoint to metric"""
|
|
339
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
340
|
+
job.markpoint("checkpoint1")
|
|
341
|
+
job.markpoint("checkpoint2")
|
|
342
|
+
|
|
343
|
+
assert "checkpoint1" in job.metric.marks
|
|
344
|
+
assert "checkpoint2" in job.metric.marks
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
# ============================================================================
|
|
348
|
+
# Job Cool Tests
|
|
349
|
+
# ============================================================================
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
@patch("time.sleep")
|
|
353
|
+
def test_job_cool(mock_sleep):
|
|
354
|
+
"""Test Job cool method calls coolant"""
|
|
355
|
+
job = ConcreteJob(name="test_job", key="test_key", coolant={"interval": 5})
|
|
356
|
+
|
|
357
|
+
job.cool()
|
|
358
|
+
|
|
359
|
+
mock_sleep.assert_called_once()
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
# ============================================================================
|
|
363
|
+
# Job Get Params Tests
|
|
364
|
+
# ============================================================================
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def test_job_get_params():
|
|
368
|
+
"""Test Job get_params returns params dict"""
|
|
369
|
+
job = ConcreteJob(name="test_job", key="test_key", params={"symbol": "AAPL", "limit": 100})
|
|
370
|
+
|
|
371
|
+
result = job.get_params()
|
|
372
|
+
|
|
373
|
+
assert result["symbol"] == "AAPL"
|
|
374
|
+
assert result["limit"] == 100
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
def test_job_get_params_empty():
|
|
378
|
+
"""Test Job get_params with no params"""
|
|
379
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
380
|
+
result = job.get_params()
|
|
381
|
+
|
|
382
|
+
assert result == {}
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
# ============================================================================
|
|
386
|
+
# Job Cache Operations Tests
|
|
387
|
+
# ============================================================================
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def test_job_get_cache_without_cache():
|
|
391
|
+
"""Test get_cache returns default when cache is disabled"""
|
|
392
|
+
job = ConcreteJob(name="test_job", key="test_key", cache=False)
|
|
393
|
+
result = job.get_cache("key", default="default_value")
|
|
394
|
+
|
|
395
|
+
assert result == "default_value"
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def test_job_get_cache_with_cache():
|
|
399
|
+
"""Test get_cache retrieves data from cache"""
|
|
400
|
+
job = ConcreteJob(name="test_job", key="test_key", cache=True)
|
|
401
|
+
job.set_cache("test_key", {"data": "value"})
|
|
402
|
+
|
|
403
|
+
result = job.get_cache("test_key")
|
|
404
|
+
|
|
405
|
+
assert result == {"data": "value"}
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def test_job_get_cache_nonexistent_key():
|
|
409
|
+
"""Test get_cache returns default for nonexistent key"""
|
|
410
|
+
job = ConcreteJob(name="test_job", key="test_key", cache=True)
|
|
411
|
+
result = job.get_cache("nonexistent", default="not_found")
|
|
412
|
+
|
|
413
|
+
assert result == "not_found"
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def test_job_set_cache_without_cache():
|
|
417
|
+
"""Test set_cache does nothing when cache is disabled"""
|
|
418
|
+
job = ConcreteJob(name="test_job", key="test_key", cache=False)
|
|
419
|
+
job.set_cache("key", "value") # Should not raise error
|
|
420
|
+
|
|
421
|
+
result = job.get_cache("key", default="default")
|
|
422
|
+
assert result == "default"
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def test_job_set_cache_with_cache():
|
|
426
|
+
"""Test set_cache stores data in cache"""
|
|
427
|
+
job = ConcreteJob(name="test_job", key="test_key", cache=True)
|
|
428
|
+
job.set_cache("test_key", [1, 2, 3])
|
|
429
|
+
|
|
430
|
+
result = job.get_cache("test_key")
|
|
431
|
+
|
|
432
|
+
assert result == [1, 2, 3]
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def test_job_cache_workflow():
|
|
436
|
+
"""Test complete cache workflow in CachedJob"""
|
|
437
|
+
job = CachedJob(name="test_job", key="test_key", cache=True)
|
|
438
|
+
|
|
439
|
+
# First run computes result
|
|
440
|
+
result1 = job.run()
|
|
441
|
+
assert result1["status"] == "computed"
|
|
442
|
+
|
|
443
|
+
# Second run uses cache
|
|
444
|
+
result2 = job.run()
|
|
445
|
+
assert result2["status"] == "from_cache"
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
# ============================================================================
|
|
449
|
+
# Job Reset Tests
|
|
450
|
+
# ============================================================================
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def test_job_reset_metric():
|
|
454
|
+
"""Test Job reset clears metric"""
|
|
455
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
456
|
+
job.run()
|
|
457
|
+
|
|
458
|
+
assert job.metric.duration > 0
|
|
459
|
+
|
|
460
|
+
job.reset()
|
|
461
|
+
|
|
462
|
+
assert job.metric.duration == 0
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def test_job_reset_cache():
|
|
466
|
+
"""Test Job reset clears cache"""
|
|
467
|
+
job = ConcreteJob(name="test_job", key="test_key", cache=True)
|
|
468
|
+
job.set_cache("key", "value")
|
|
469
|
+
|
|
470
|
+
assert job.get_cache("key") == "value"
|
|
471
|
+
|
|
472
|
+
job.reset()
|
|
473
|
+
|
|
474
|
+
assert job.get_cache("key") is None
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
def test_job_reset_without_cache():
|
|
478
|
+
"""Test Job reset works when cache is disabled"""
|
|
479
|
+
job = ConcreteJob(name="test_job", key="test_key", cache=False)
|
|
480
|
+
job.run()
|
|
481
|
+
|
|
482
|
+
job.reset() # Should not raise error
|
|
483
|
+
|
|
484
|
+
assert job.metric.duration == 0
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
# ============================================================================
|
|
488
|
+
# Job Describe Tests
|
|
489
|
+
# ============================================================================
|
|
490
|
+
|
|
491
|
+
|
|
492
|
+
def test_job_describe_basic():
|
|
493
|
+
"""Test Job describe returns basic info"""
|
|
494
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
495
|
+
result = job.describe()
|
|
496
|
+
|
|
497
|
+
assert result["name"] == "test_job"
|
|
498
|
+
assert result["key"] == "test_key"
|
|
499
|
+
assert "params" in result
|
|
500
|
+
assert "coolant" in result
|
|
501
|
+
assert "paginate" in result
|
|
502
|
+
assert "retry" in result
|
|
503
|
+
assert "metric" in result
|
|
504
|
+
|
|
505
|
+
|
|
506
|
+
def test_job_describe_with_cache():
|
|
507
|
+
"""Test Job describe includes cache when enabled"""
|
|
508
|
+
job = ConcreteJob(name="test_job", key="test_key", cache=True)
|
|
509
|
+
result = job.describe()
|
|
510
|
+
|
|
511
|
+
assert "cache" in result
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
def test_job_describe_without_cache():
|
|
515
|
+
"""Test Job describe excludes cache when disabled"""
|
|
516
|
+
job = ConcreteJob(name="test_job", key="test_key", cache=False)
|
|
517
|
+
result = job.describe()
|
|
518
|
+
|
|
519
|
+
assert "cache" not in result
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
def test_job_describe_with_custom_params():
|
|
523
|
+
"""Test Job describe includes custom params"""
|
|
524
|
+
job = ConcreteJob(name="test_job", key="test_key", params={"symbol": "AAPL", "date": "20240115"})
|
|
525
|
+
result = job.describe()
|
|
526
|
+
|
|
527
|
+
assert result["params"]["symbol"] == "AAPL"
|
|
528
|
+
assert result["params"]["date"] == "20240115"
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
# ============================================================================
|
|
532
|
+
# Job To Dict Tests
|
|
533
|
+
# ============================================================================
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
def test_job_to_dict_basic():
|
|
537
|
+
"""Test Job to_dict returns complete structure"""
|
|
538
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
539
|
+
result = job.to_dict()
|
|
540
|
+
|
|
541
|
+
assert result["name"] == "test_job"
|
|
542
|
+
assert result["key"] == "test_key"
|
|
543
|
+
assert "params" in result
|
|
544
|
+
assert "coolant" in result
|
|
545
|
+
assert "paginate" in result
|
|
546
|
+
assert "retry" in result
|
|
547
|
+
assert "metric" in result
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def test_job_to_dict_with_cache():
|
|
551
|
+
"""Test Job to_dict includes cache"""
|
|
552
|
+
job = ConcreteJob(name="test_job", key="test_key", cache=True)
|
|
553
|
+
result = job.to_dict()
|
|
554
|
+
|
|
555
|
+
assert result["cache"] is not None
|
|
556
|
+
assert isinstance(result["cache"], dict)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
def test_job_to_dict_structure():
|
|
560
|
+
"""Test Job to_dict has expected structure"""
|
|
561
|
+
job = ConcreteJob(
|
|
562
|
+
name="test_job",
|
|
563
|
+
key="test_key",
|
|
564
|
+
params={"symbol": "AAPL"},
|
|
565
|
+
coolant={"interval": 5},
|
|
566
|
+
paginate={"pagesize": 1000},
|
|
567
|
+
retry={"max_retries": 3},
|
|
568
|
+
cache=True,
|
|
569
|
+
)
|
|
570
|
+
result = job.to_dict()
|
|
571
|
+
|
|
572
|
+
assert isinstance(result["params"], dict)
|
|
573
|
+
assert isinstance(result["coolant"], dict)
|
|
574
|
+
assert isinstance(result["paginate"], dict)
|
|
575
|
+
assert isinstance(result["retry"], dict)
|
|
576
|
+
assert isinstance(result["cache"], dict)
|
|
577
|
+
assert isinstance(result["metric"], dict)
|
|
578
|
+
|
|
579
|
+
|
|
580
|
+
# ============================================================================
|
|
581
|
+
# JobLike Protocol Compliance Tests
|
|
582
|
+
# ============================================================================
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
def test_job_implements_joblike_protocol():
|
|
586
|
+
"""Test Job implements JobLike protocol"""
|
|
587
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
588
|
+
|
|
589
|
+
assert isinstance(job, JobLike)
|
|
590
|
+
|
|
591
|
+
|
|
592
|
+
def test_job_has_required_methods():
|
|
593
|
+
"""Test Job has all required protocol methods"""
|
|
594
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
595
|
+
|
|
596
|
+
assert hasattr(job, "run")
|
|
597
|
+
assert callable(job.run)
|
|
598
|
+
assert hasattr(job, "_run")
|
|
599
|
+
assert callable(job._run)
|
|
600
|
+
assert hasattr(job, "describe")
|
|
601
|
+
assert callable(job.describe)
|
|
602
|
+
assert hasattr(job, "to_dict")
|
|
603
|
+
assert callable(job.to_dict)
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
# ============================================================================
|
|
607
|
+
# Integration Tests
|
|
608
|
+
# ============================================================================
|
|
609
|
+
|
|
610
|
+
|
|
611
|
+
def test_job_full_lifecycle():
|
|
612
|
+
"""Test complete Job lifecycle"""
|
|
613
|
+
job = ConcreteJob(
|
|
614
|
+
name="test_job", key="test_key", params={"symbol": "AAPL"}, retry={"retry": 2, "wait": 0}, cache=True
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
# Run job
|
|
618
|
+
result = job.run()
|
|
619
|
+
assert result["status"] == "success"
|
|
620
|
+
|
|
621
|
+
# Check metrics
|
|
622
|
+
assert job.metric.duration > 0
|
|
623
|
+
|
|
624
|
+
# Check params
|
|
625
|
+
params = job.get_params()
|
|
626
|
+
assert params["symbol"] == "AAPL"
|
|
627
|
+
|
|
628
|
+
# Get description
|
|
629
|
+
desc = job.describe()
|
|
630
|
+
assert desc["name"] == "test_job"
|
|
631
|
+
|
|
632
|
+
# Reset
|
|
633
|
+
job.reset()
|
|
634
|
+
assert job.metric.duration == 0
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def test_job_multiple_runs():
|
|
638
|
+
"""Test Job can be run multiple times"""
|
|
639
|
+
job = ConcreteJob(name="test_job", key="test_key")
|
|
640
|
+
|
|
641
|
+
result1 = job.run()
|
|
642
|
+
result2 = job.run()
|
|
643
|
+
|
|
644
|
+
assert result1 == result2
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def test_job_with_all_features():
|
|
648
|
+
"""Test Job with all features enabled"""
|
|
649
|
+
job = CachedJob(
|
|
650
|
+
name="complex_job",
|
|
651
|
+
key="complex_key",
|
|
652
|
+
params={"symbol": "AAPL", "date": "20240115"},
|
|
653
|
+
coolant={"interval": 1, "use_jitter": True},
|
|
654
|
+
paginate={"pagesize": 1000, "pagelimit": 5000},
|
|
655
|
+
retry={"retry": 3, "wait": 1},
|
|
656
|
+
cache=True,
|
|
657
|
+
)
|
|
658
|
+
|
|
659
|
+
# First run
|
|
660
|
+
job.run()
|
|
661
|
+
result2 = job.run()
|
|
662
|
+
assert result2["status"] == "from_cache"
|
|
663
|
+
|
|
664
|
+
# Verify all components
|
|
665
|
+
assert isinstance(job.params, Params)
|
|
666
|
+
assert isinstance(job.coolant, Coolant)
|
|
667
|
+
assert isinstance(job.paginate, Paginate)
|
|
668
|
+
assert isinstance(job.retry, Retry)
|
|
669
|
+
assert isinstance(job.cache, Cache)
|
|
670
|
+
assert isinstance(job.metric, Metric)
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
def test_job_different_instances_independent():
|
|
674
|
+
"""Test different Job instances are independent"""
|
|
675
|
+
job1 = ConcreteJob(name="job1", key="key1", params={"value": 1})
|
|
676
|
+
job2 = ConcreteJob(name="job2", key="key2", params={"value": 2})
|
|
677
|
+
|
|
678
|
+
assert job1.name != job2.name
|
|
679
|
+
assert job1.params.value != job2.params.value
|
|
680
|
+
|
|
681
|
+
job1.run()
|
|
682
|
+
|
|
683
|
+
assert job1.metric.duration > 0
|
|
684
|
+
assert job2.metric.duration == 0
|