diff --git a/.travis.yml b/.travis.yml index 3900fe3..13e6967 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,13 +16,13 @@ cache: directories: - eggs before_install: - - pip install setuptools==36.5.0 python-coveralls + - pip install python-coveralls - python2 bootstrap.py - mv openprocurement/auction/worker/tests/data/auction_worker_travis.yaml openprocurement/auction/worker/tests/data/auction_worker_defaults.yaml install: - bin/buildout -N - curl -X PUT 0.0.0.0:5984/auctions script: - - bin/pytest openprocurement/auction/worker/tests + - bin/pytest openprocurement/auction/worker/tests/unit after_success: - coveralls diff --git a/buildout.cfg b/buildout.cfg index da84f55..5684b62 100644 --- a/buildout.cfg +++ b/buildout.cfg @@ -22,14 +22,6 @@ eggs = WTForms-JSON -[versions] -pbr = 1.8.0 -oslo.middleware = 2.8.0 -stevedore = 1.5.0 -oslo.i18n = 2.6.0 -oslo.context = 0.6.0 -oslo.config = 2.3.0 - [sources] chromedriver = git https://github.com/enkidulan/chromedriver.git openprocurement.auction = git https://github.com/openprocurement/openprocurement.auction.git branch=eauctions-sandbox diff --git a/openprocurement/auction/worker/auction.py b/openprocurement/auction/worker/auction.py index 1e9ec20..62e77bc 100644 --- a/openprocurement/auction/worker/auction.py +++ b/openprocurement/auction/worker/auction.py @@ -1,9 +1,7 @@ import logging from copy import deepcopy -from urlparse import urljoin from datetime import datetime -from couchdb import Database, Session from gevent import sleep from gevent.event import Event @@ -12,7 +10,6 @@ from yaml import safe_dump as yaml_dump from requests import Session as RequestsSession from dateutil.tz import tzlocal -from dateutil import parser from barbecue import cooking from apscheduler.schedulers.gevent import GeventScheduler @@ -29,24 +26,27 @@ ) from openprocurement.auction.worker.server import run_server from openprocurement.auction.executor import AuctionsExecutor -from openprocurement.auction.worker_core.constants import TIMEZONE -from openprocurement.auction.worker.constants import ROUNDS -from openprocurement.auction.worker.mixins import\ - DBServiceMixin,\ - BiddersServiceMixin, PostAuctionServiceMixin,\ +from openprocurement.auction.worker.mixins import ( + DBServiceMixin, BiddersServiceMixin, PostAuctionServiceMixin, StagesServiceMixin, WorkerAuditServiceMixin +) from openprocurement.auction.worker_core.mixins import ( RequestIDServiceMixin, - DateTimeServiceMixin + DateTimeServiceMixin, + InitializeServiceMixin ) +from openprocurement.auction.worker_core.constants import TIMEZONE +from openprocurement.auction.worker.constants import ROUNDS + from openprocurement.auction.worker.utils import \ prepare_initial_bid_stage, prepare_results_stage -from openprocurement.auction.worker.auctions import\ - simple -from openprocurement.auction.utils import\ - get_latest_bid_for_bidder, sorting_by_amount,\ +from openprocurement.auction.utils import ( + get_latest_bid_for_bidder, sorting_by_amount, check, sorting_start_bids_by_amount, delete_mapping, get_tender_data +) +logging.addLevelName(25, 'CHECK') +logging.Logger.check = check LOGGER = logging.getLogger('Auction Worker') SCHEDULER = GeventScheduler(job_defaults={"misfire_grace_time": 100}, @@ -57,6 +57,7 @@ class Auction(DBServiceMixin, RequestIDServiceMixin, + InitializeServiceMixin, WorkerAuditServiceMixin, BiddersServiceMixin, DateTimeServiceMixin, @@ -76,29 +77,18 @@ def __init__(self, tender_id, self.auction_doc_id = tender_id + "_" + lot_id else: self.auction_doc_id = tender_id - self.tender_url = urljoin( - worker_defaults["resource_api_server"], - '/api/{0}/{1}/{2}'.format( - worker_defaults["resource_api_version"], - worker_defaults["resource_name"], - tender_id - ) - ) if auction_data: self.debug = True LOGGER.setLevel(logging.DEBUG) self._auction_data = auction_data else: self.debug = False + self.worker_defaults = worker_defaults + self.init_services() self._end_auction_event = Event() self.bids_actions = BoundedSemaphore() self.session = RequestsSession() - self.worker_defaults = worker_defaults - if self.worker_defaults.get('with_document_service', False): - self.session_ds = RequestsSession() self._bids_data = {} - self.db = Database(str(self.worker_defaults["COUCH_DATABASE"]), - session=Session(retry_delays=range(10))) self.audit = {} self.retries = 10 self.bidders_count = 0 @@ -380,4 +370,4 @@ def post_audit(self): if self.worker_defaults.get('with_document_service', False): self.upload_audit_file_with_document_service() else: - self.upload_audit_file_without_document_service() \ No newline at end of file + self.upload_audit_file_without_document_service() diff --git a/openprocurement/auction/worker/cli.py b/openprocurement/auction/worker/cli.py index 731c721..bb1b9d0 100644 --- a/openprocurement/auction/worker/cli.py +++ b/openprocurement/auction/worker/cli.py @@ -56,6 +56,8 @@ def main(): worker_defaults=worker_defaults, auction_data=auction_data, lot_id=args.lot) + if args.cmd == 'check': + sys.exit() if args.cmd == 'run': SCHEDULER.start() auction.schedule_auction() diff --git a/openprocurement/auction/worker/mixins.py b/openprocurement/auction/worker/mixins.py index 63cbe2d..3985063 100644 --- a/openprocurement/auction/worker/mixins.py +++ b/openprocurement/auction/worker/mixins.py @@ -1,12 +1,12 @@ -import logging import json -import iso8601 -from datetime import datetime, timedelta +import logging from copy import deepcopy -from dateutil.tz import tzlocal -from yaml import safe_dump as yaml_dump -from couchdb.http import HTTPError, RETRYABLE_ERRORS +from datetime import datetime, timedelta from fractions import Fraction + +from couchdb.http import HTTPError, RETRYABLE_ERRORS +from dateutil.tz import tzlocal + from barbecue import cooking from openprocurement.auction.worker_core.utils import prepare_service_stage diff --git a/openprocurement/auction/worker/tests/unit/conftest.py b/openprocurement/auction/worker/tests/unit/conftest.py index 602d9f0..d8bfad5 100644 --- a/openprocurement/auction/worker/tests/unit/conftest.py +++ b/openprocurement/auction/worker/tests/unit/conftest.py @@ -21,7 +21,6 @@ from openprocurement.auction.worker.server import ( app as worker_app, BidsForm ) -# from openprocurement.auction.tests.functional.main import update_auctionPeriod def update_auctionPeriod(data): @@ -35,8 +34,14 @@ def update_auctionPeriod(data): PWD = os.path.dirname(os.path.realpath(__file__)) worker_defaults_file_path = os.path.join(PWD, "../data/auction_worker_defaults.yaml") -with open(worker_defaults_file_path) as stream: - worker_defaults = yaml.load(stream) + + +@pytest.yield_fixture(scope='function') +def worker_config(): + update_auctionPeriod(tender_data) + with open(worker_defaults_file_path) as stream: + worker_defaults = yaml.load(stream) + return worker_defaults @pytest.yield_fixture( @@ -57,13 +62,13 @@ def universal_auction(request): lot_id=request.param['lot_id'] ) + @pytest.yield_fixture(scope="function") -def auction(): - update_auctionPeriod(tender_data) +def auction(worker_config): yield Auction( tender_id=tender_data['data']['auctionID'], - worker_defaults=yaml.load(open(worker_defaults_file_path)), + worker_defaults=worker_config, auction_data=tender_data, lot_id=False ) @@ -90,9 +95,9 @@ def features_auction(): @pytest.fixture(scope='function') -def db(request): - server = couchdb.Server("http://" + worker_defaults['COUCH_DATABASE'].split('/')[2]) - name = worker_defaults['COUCH_DATABASE'].split('/')[3] +def db(request, worker_config): + server = couchdb.Server("http://" + worker_config['COUCH_DATABASE'].split('/')[2]) + name = worker_config['COUCH_DATABASE'].split('/')[3] def delete(): del server[name] diff --git a/openprocurement/auction/worker/tests/unit/test_initialization.py b/openprocurement/auction/worker/tests/unit/test_initialization.py new file mode 100644 index 0000000..bd49865 --- /dev/null +++ b/openprocurement/auction/worker/tests/unit/test_initialization.py @@ -0,0 +1,55 @@ +from copy import deepcopy +from uuid import uuid4 + +import pytest +from requests import exceptions + +from openprocurement.auction.worker.auction import Auction +from openprocurement.auction.worker.mixins import Server + + +def test_init_services(worker_config, logger, mocker): + + test_config = deepcopy(worker_config) + test_config['with_document_service'] = True + test_config['DOCUMENT_SERVICE']['url'] = "http://1.2.3.4:6543/" + test_config['resource_api_server'] = "http://1.2.3.4:6543/" + + tender_id = uuid4().hex # random tender id + + mock_make_request = mocker.MagicMock() + mock_make_request.side_effect = Exception("API can't be reached") + mocker.patch('openprocurement.auction.worker.mixins.make_request', mock_make_request) + + with pytest.raises(Exception) as e: + Auction(tender_id, test_config) + log_strings = logger.log_capture_string.getvalue().split('\n') + assert e.value.message == "API can't be reached" + + assert log_strings.count("API can't be reached") == 1 + assert "ConnectTimeout" in log_strings[-2] + + with pytest.raises(exceptions.RequestException) as e: + Auction(tender_id, test_config, {'test_auction_data': True}) + log_strings = logger.log_capture_string.getvalue().split('\n') + assert "ConnectTimeout" in str(e.value.message) + + assert log_strings.count("API can't be reached") == 1 + assert "ConnectTimeout" in log_strings[-2] + + test_config['with_document_service'] = False + auction = Auction(tender_id, test_config, {'test_auction_data': True}) + + assert auction.__getattribute__("tender_url") is not None + assert auction.__getattribute__("db") is not None + + mock_server_connect = mocker.patch.object(Server, "__init__", autospec=True) + mock_server_connect.side_effect = Exception("Connection refused") + + with pytest.raises(Exception) as e: + Auction(tender_id, test_config, {'test_auction_data': True}) + log_strings = logger.log_capture_string.getvalue().split('\n') + assert e.value.message == "Connection refused" + + assert log_strings.count("API can't be reached") == 1 + assert log_strings.count("Connection refused") == 1