def test_fail_duplicate(app, tender, plan): response = app.post_json( "/tenders/{}/plans?acc_token={}".format(tender["data"]["id"], tender["access"]["token"]), {"data": {"id": plan["data"]["id"]}}, ) assert response.status == "200 OK" # the same response = app.post_json( "/tenders/{}/plans?acc_token={}".format(tender["data"]["id"], tender["access"]["token"]), {"data": {"id": plan["data"]["id"]}}, status=422 ) assert response.json == {'status': 'error', 'errors': [ {'description': "Can't update plan in 'complete' status", 'location': 'body', 'name': 'status'}]} # what if plan hasn't been updated for an unknown reason plan_obj = app.app.registry.databases.plans.get(plan["data"]["id"]) del plan_obj["tender_id"] plan_obj["status"] = "scheduled" app.app.registry.databases.plans.save(plan_obj) response = app.post_json( "/tenders/{}/plans?acc_token={}".format(tender["data"]["id"], tender["access"]["token"]), {"data": {"id": plan["data"]["id"]}}, status=422 ) assert response.json == {'status': 'error', 'errors': [ {'description': ['The list should not contain duplicates'], 'location': 'body', 'name': 'plans'}]}
def test_post_tender_plan_success(app, tender, plan): response = app.post_json( "/tenders/{}/plans?acc_token={}".format(tender["data"]["id"], tender["access"]["token"]), {"data": { "id": plan["data"]["id"] }}, ) assert response.json["data"] == [{'id': plan["data"]["id"]}] response = app.get("/tenders/{}".format(tender["data"]["id"])) assert response.json["data"]["dateModified"] > tender["data"][ "dateModified"] response = app.get("/plans/{}".format(plan["data"]["id"])) assert response.json["data"]["tender_id"] == tender["data"]["id"] assert response.json["data"]["dateModified"] > plan["data"]["dateModified"] # second plan response = app.post_json("/plans", {"data": deepcopy(test_plan_data)}) another_plan = response.json response = app.post_json( "/tenders/{}/plans?acc_token={}".format(tender["data"]["id"], tender["access"]["token"]), {"data": { "id": another_plan["data"]["id"] }}, ) assert response.json["data"] == [{ 'id': plan["data"]["id"] }, { 'id': another_plan["data"]["id"] }]
def test_fail_non_central(app, plan): app.authorization = ("Basic", ("broker", "broker")) test_data = deepcopy(test_tender_data) test_data["procuringEntity"]["kind"] = "general" response = app.post_json("/tenders", dict(data=test_data)) assert response.status == "201 Created" tender = response.json response = app.post_json("/tenders/{}/plans?acc_token={}".format( tender["data"]["id"], tender["access"]["token"]), {"data": { "id": plan["data"]["id"] }}, status=403) assert response.json == { "status": "error", "errors": [{ "location": "body", "name": "data", "description": "Only allowed for procurementEntity.kind = 'central'" }] }
def test_fail_not_draft(app, plan): app.authorization = ("Basic", ("broker", "broker")) test_data = deepcopy(test_tender_data) del test_data["status"] response = app.post_json("/tenders", dict(data=test_data)) assert response.status == "201 Created" tender = response.json response = app.post_json( "/tenders/{}/plans?acc_token={}".format(tender["data"]["id"], tender["access"]["token"]), {"data": {"id": plan["data"]["id"]}}, status=403 ) assert response.json == {"status": "error", "errors": [ {"location": "body", "name": "data", "description": "Only allowed in draft tender status"}]}
def test_fail_saving_plan(app, tender, plan): plan_obj = app.app.registry.db.get(plan["data"]["id"]) plan_obj["status"] = "will cause a data validation error" app.app.registry.db.save(plan_obj) # got an error response = app.post_json("/tenders/{}/plans?acc_token={}".format( tender["data"]["id"], tender["access"]["token"]), {"data": { "id": plan["data"]["id"] }}, status=422) assert response.json == { "status": "error", "errors": [{ "location": "body", "name": "status", "description": [ "Value must be one of ['draft', 'scheduled', 'cancelled', 'complete']." ] }] } # check that the tender hasn't been changed tender_obj = app.app.registry.db.get(tender["data"]["id"]) assert tender_obj.get("plans") is None
def test_post_cancellation(app, tender_data): """ posting an active cancellation should trigger the validation """ tender, tender_token = post_tender(app, tender_data) def mock_validate(request, cancellation=None): raise_operation_error(request, "hello") if get_now() < RELEASE_2020_04_19: with mock.patch( "openprocurement.tender.core.utils.validate_absence_of_pending_accepted_satisfied_complaints", mock_validate): cancellation = dict(**test_cancellation) cancellation.update({"status": "active"}) response = app.post_json( "/tenders/{}/cancellations?acc_token={}".format( tender["id"], tender_token), {"data": cancellation}, status=403) assert response.json == { u'status': u'error', u'errors': [{ u'description': u'hello', u'location': u'body', u'name': u'data' }] }
def test_post_cancellation(app, tender_data): """ posting an active cancellation should trigger the validation """ tender, tender_token = post_tender(app, tender_data) def mock_validate(request): raise_operation_error(request, "hello") with mock.patch( "openprocurement.tender.core.views.cancellation.validate_absence_of_pending_accepted_satisfied_complaints", mock_validate): response = app.post_json( "/tenders/{}/cancellations?acc_token={}".format( tender["id"], tender_token), {"data": { "reason": "cancellation reason", "status": "active", }}, status=403) assert response.json == { u'status': u'error', u'errors': [{ u'description': u'hello', u'location': u'body', u'name': u'data' }] }
def test_post_tender_plan_data_empty(app, tender): response = app.post_json( "/tenders/{}/plans?acc_token={}".format(tender["data"]["id"], tender["access"]["token"]), {"data": {}}, status=422 ) assert response.json == {'status': 'error', 'errors': [ {'description': ['This field is required.'], 'location': 'body', 'name': 'id'}]}
def test_post_tender_plan_empty(app, tender): response = app.post_json( "/tenders/{}/plans?acc_token={}".format(tender["data"]["id"], tender["access"]["token"]), {}, status=422 ) assert response.json == {"status": "error", "errors": [ {"location": "body", "name": "data", "description": "Data not available"}]}
def test_post_tender_plan_404(app, tender): response = app.post_json( "/tenders/{}/plans?acc_token={}".format(tender["data"]["id"], tender["access"]["token"]), {"data": {"id": tender["data"]["id"]}}, status=404 ) assert response.json == {'status': 'error', 'errors': [ {'description': 'Not Found', 'location': 'url', 'name': 'plan_id'}]}
def post_tender(app, data): if data["procurementMethodType"] in (STAGE_2_EU_TYPE, STAGE_2_UA_TYPE): app.authorization = ("Basic", ("competitive_dialogue", "")) else: app.authorization = ("Basic", ("broker", "broker")) test_data = deepcopy(data) response = app.post_json("/tenders", dict(data=test_data)) assert response.status == "201 Created" return response.json["data"], response.json["access"]["token"]
def test_complaint_value_with_lots(app): """ Value should be based on a lot value if lots are present """ test_data = deepcopy(test_tender_data) test_data["lots"] = deepcopy(test_lots) test_data["lots"].append(deepcopy(test_lots[0])) test_data["lots"][0]["value"]["amount"] = 500 test_data["lots"][1]["value"]["amount"] = 99999999999999 tender = create_tender(app, test_data) req_data = deepcopy(complaint_data) # a chip complaint req_data["relatedLot"] = tender["data"]["lots"][0]["id"] with patch("openprocurement.tender.core.models.RELEASE_2020_04_19", get_now() - timedelta(days=1)): response = app.post_json( "/tenders/{}/complaints".format(tender["data"]["id"]), {"data": req_data}, ) response_data = response.json["data"] assert "value" in response_data assert response_data["value"] == { "currency": "UAH", "amount": COMPLAINT_MIN_AMOUNT } # an expensive one req_data["relatedLot"] = tender["data"]["lots"][1]["id"] with patch("openprocurement.tender.core.models.RELEASE_2020_04_19", get_now() - timedelta(days=1)): response = app.post_json( "/tenders/{}/complaints".format(tender["data"]["id"]), {"data": req_data}, ) response_data = response.json["data"] assert "value" in response_data assert response_data["value"] == { "currency": "UAH", "amount": COMPLAINT_MAX_AMOUNT }
def test_patch_cancellation(app, tender_data): """ only patching to active should trigger the validation """ tender, tender_token = post_tender(app, tender_data) def mock_validate(request, cancellation=None): raise_operation_error(request, "hello") def patch(data): return "openprocurement.tender.core.utils.validate_absence_of_pending_accepted_satisfied_complaints" with mock.patch(patch(tender_data), mock_validate): if get_now() < RELEASE_2020_04_19: response = app.post_json( "/tenders/{}/cancellations?acc_token={}".format( tender["id"], tender_token), {"data": test_cancellation}, ) assert response.status_code == 201 cancellation = response.json["data"] response = app.patch_json( "/tenders/{}/cancellations/{}?acc_token={}".format( tender["id"], cancellation["id"], tender_token), {"data": { "reason": "another reason", }}, ) assert response.status_code == 200 response = app.patch_json( "/tenders/{}/cancellations/{}?acc_token={}".format( tender["id"], cancellation["id"], tender_token), {"data": { "status": "active", }}, status=403) assert response.json == { u'status': u'error', u'errors': [{ u'description': u'hello', u'location': u'body', u'name': u'data' }] }
def post_tender(app, data): if data["procurementMethodType"] == "aboveThresholdUA.defense": release_simpledef_date = get_now() + timedelta(days=1) else: release_simpledef_date = get_now() - timedelta(days=1) release_simpledef_patcher = mock.patch( "openprocurement.tender.core.validation.RELEASE_SIMPLE_DEFENSE_FROM", release_simpledef_date) release_simpledef_patcher.start() if data["procurementMethodType"] in (STAGE_2_EU_TYPE, STAGE_2_UA_TYPE): app.authorization = ("Basic", ("competitive_dialogue", "")) else: app.authorization = ("Basic", ("broker", "broker")) test_data = deepcopy(data) response = app.post_json("/tenders", dict(data=test_data)) release_simpledef_patcher.stop() assert response.status == "201 Created" return response.json["data"], response.json["access"]["token"]
def test_patch_cancellation(app, tender_data): """ only patching to active should trigger the validation """ tender, tender_token = post_tender(app, tender_data) def mock_validate(request): raise_operation_error(request, "hello") def patch(data): excl_procedures = ["belowThreshold", "reporting", "closeFrameworkAgreementSelectionUA"] if data["procurementMethodType"] not in excl_procedures: return "openprocurement.tender.core.views.cancellation.validate_absence_of_pending_accepted_satisfied_complaints" else: return "openprocurement.tender.belowthreshold.views.cancellation.validate_absence_of_pending_accepted_satisfied_complaints" with mock.patch(patch(tender_data), mock_validate): if get_now() < RELEASE_2020_04_19: response = app.post_json( "/tenders/{}/cancellations?acc_token={}".format(tender["id"], tender_token), {"data": test_cancellation}, ) assert response.status_code == 201 cancellation = response.json["data"] response = app.patch_json( "/tenders/{}/cancellations/{}?acc_token={}".format(tender["id"], cancellation["id"], tender_token), {"data": { "reason": "another reason", }}, ) assert response.status_code == 200 response = app.patch_json( "/tenders/{}/cancellations/{}?acc_token={}".format(tender["id"], cancellation["id"], tender_token), {"data": { "status": "active", }}, status=403 ) assert response.json == {u'status': u'error', u'errors': [ {u'description': u'hello', u'location': u'body', u'name': u'data'}]}
def test_complaint_value_change(app): """ value should be calculated only once for a complaint """ test_tender_data["value"]["amount"] = 1000 # we want minimum complaint value tender = create_tender(app, test_tender_data) with patch("openprocurement.tender.core.models.RELEASE_2020_04_19", get_now() - timedelta(days=1)): response = app.post_json( "/tenders/{}/complaints".format(tender["data"]["id"]), {"data": complaint_data}, ) response_data = response.json["data"] assert "value" in response_data expected_value = {"currency": "UAH", "amount": COMPLAINT_MIN_AMOUNT} assert response_data["value"] == expected_value # if we deploy new constant values the value shouldn't change with patch("openprocurement.tender.core.models.COMPLAINT_MIN_AMOUNT", 40): response = app.get("/tenders/{}".format(tender["data"]["id"])) complaint = response.json["data"].get("complaints")[0] assert complaint["value"] == expected_value
def plan(app): app.authorization = ("Basic", ("broker", "broker")) response = app.post_json("/plans", {"data": deepcopy(test_plan_data)}) return response.json
def test_post_cancellation_openeu(app): """ test without mocking (just in case) """ tender, tender_token = post_tender(app, eu_tender_data) tender_data = app.app.registry.db.get(tender["id"]) # award complaint complaint = deepcopy(test_complaint) complaint.update( resolutionType="resolved", cancellationReason="whatever", ) tender_data["awards"] = [{ "id": "0" * 32, "bid_id": "0" * 32, "suppliers": [test_organization], "complaints": [complaint] }] app.app.registry.db.save(tender_data) cancellation = dict(**test_cancellation) cancellation.update({"status": "active"}) if get_now() < RELEASE_2020_04_19: with mock.patch( "openprocurement.tender.core.validation.RELEASE_2020_04_19", get_now() - timedelta(1)): response = app.post_json( "/tenders/{}/cancellations?acc_token={}".format( tender["id"], tender_token), {"data": cancellation}, status=403) assert response.json == { u'status': u'error', u'errors': [{ u'description': u"Can't perform operation for there is an award complaint in pending status", u'location': u'body', u'name': u'data' }] } # qualification complaints complaint = deepcopy(test_complaint) complaint.update( status="accepted", resolutionType="resolved", cancellationReason="whatever", ) tender_data["qualifications"] = [{ "id": "0" * 32, "complaints": [complaint] }] app.app.registry.db.save(tender_data) cancellation = dict(**test_cancellation) cancellation.update({"status": "active"}) with mock.patch( "openprocurement.tender.core.validation.RELEASE_2020_04_19", get_now() - timedelta(1)): response = app.post_json( "/tenders/{}/cancellations?acc_token={}".format( tender["id"], tender_token), {"data": cancellation}, status=403) assert response.json == { u'status': u'error', u'errors': [{ u'description': u"Can't perform operation for there is a qualification complaint in accepted status", u'location': u'body', u'name': u'data' }] } # tender complaint complaint = deepcopy(test_complaint) complaint.update( status="satisfied", resolutionType="resolved", cancellationReason="whatever", ) tender_data["complaints"] = [complaint] app.app.registry.db.save(tender_data) cancellation = dict(**test_cancellation) cancellation.update({"status": "active"}) with mock.patch( "openprocurement.tender.core.validation.RELEASE_2020_04_19", get_now() - timedelta(1)): response = app.post_json( "/tenders/{}/cancellations?acc_token={}".format( tender["id"], tender_token), {"data": cancellation}, status=403) assert response.json == { u'status': u'error', u'errors': [{ u'description': u"Can't perform operation for there is a tender complaint in satisfied status", u'location': u'body', u'name': u'data' }] }
def test_post_cancellation_openeu(app): """ test without mocking (just in case) """ tender, tender_token = post_tender(app, eu_tender_data) tender_data = app.app.registry.db.get(tender["id"]) # award complaint tender_data["awards"] = [{ "id": "0" * 32, "bid_id": "0" * 32, "suppliers": [test_organization], "complaints": [{ "status": "pending", "title": "complaint title", "description": "complaint description", "author": test_author, "resolutionType": "resolved", "cancellationReason": "whatever", }] }] app.app.registry.db.save(tender_data) with mock.patch( "openprocurement.tender.core.validation.RELEASE_2020_04_19", get_now() - timedelta(1)): response = app.post_json( "/tenders/{}/cancellations?acc_token={}".format( tender["id"], tender_token), {"data": { "reason": "cancellation reason", "status": "active", }}, status=403) assert response.json == { u'status': u'error', u'errors': [{ u'description': u"Can't perform operation for there is an award complaint in pending status", u'location': u'body', u'name': u'data' }] } # qualification complaints tender_data["qualifications"] = [{ "id": "0" * 32, "complaints": [{ "status": "accepted", "title": "complaint title", "description": "complaint description", "author": test_author, "resolutionType": "resolved", "cancellationReason": "whatever", }] }] app.app.registry.db.save(tender_data) with mock.patch( "openprocurement.tender.core.validation.RELEASE_2020_04_19", get_now() - timedelta(1)): response = app.post_json( "/tenders/{}/cancellations?acc_token={}".format( tender["id"], tender_token), {"data": { "reason": "cancellation reason", "status": "active", }}, status=403) assert response.json == { u'status': u'error', u'errors': [{ u'description': u"Can't perform operation for there is a qualification complaint in accepted status", u'location': u'body', u'name': u'data' }] } # tender complaint tender_data["complaints"] = [{ "status": "satisfied", "title": "complaint title", "description": "complaint description", "author": test_author, "resolutionType": "resolved", "cancellationReason": "whatever", }] app.app.registry.db.save(tender_data) with mock.patch( "openprocurement.tender.core.validation.RELEASE_2020_04_19", get_now() - timedelta(1)): response = app.post_json( "/tenders/{}/cancellations?acc_token={}".format( tender["id"], tender_token), {"data": { "reason": "cancellation reason", "status": "active", }}, status=403) assert response.json == { u'status': u'error', u'errors': [{ u'description': u"Can't perform operation for there is a tender complaint in satisfied status", u'location': u'body', u'name': u'data' }] }
def test_post_tender_plan_403(app, tender): app.post_json("/tenders/{}/plans".format(tender["data"]["id"]), status=403)
def create_tender(app, tender_data): app.authorization = ("Basic", ("broker", "broker")) response = app.post_json("/tenders", dict(data=tender_data)) assert response.status == "201 Created" return response.json