"""Tests for Handlers.""" import json import re from contextlib import nullcontext as does_not_raise from unittest.mock import MagicMock, NonCallableMagicMock, call, patch import pytest import sentry_sdk import werkzeug from flask import g from owsresponse import response from product_review import api, handlers from product_review.handlers import canned_response_logic, review_queue_logic from product_review.logic import account_note as account_note_logic from product_review.logic import approval as approval_logic from product_review.logic import rejection as rejection_logic from product_review.logic import score_history as score_history_logic from product_review.testing_accommodations import art_relations from product_review.util import exception as exception_util from tests.utils.db_utils import create_test_review_queue_item, create_test_unsubmit mock_exception_handler_500 = werkzeug.exceptions.InternalServerError("this is a 500") mock_exception_handler_400 = werkzeug.exceptions.BadRequest("this is a 400") test_exception_handler_non_json_exception = Exception("oops") test_exception_handler_json_exception_404 = Exception( json.dumps( { "status": 404, "code": "applesauce_not_found", "message": "Applesauce Not Found", } ) ) test_exception_handler_json_exception_409 = Exception( json.dumps( { "status": 409, "code": "exists", "message": "Applesauce Already Exists", } ) ) test_exception_handler_no_status_json_exception = Exception( json.dumps( { "code": "applesauce_not_found", "message": "Applesauce Not Found", } ) ) test_exception_handler_none_status_json_exception = Exception( json.dumps( { "status": None, "code": "applesauce_not_found", "message": "Applesauce Not Found", } ) ) @pytest.mark.parametrize( ( "test_description", "exception", "code_key", "expected_code", "message_key", "expected_message", "expected_g_log_exception_calls", "expected_g_log_warn_calls", ), [ ( "500 error", mock_exception_handler_500, "code", 500, "description", "this is a 500", [call(mock_exception_handler_500)], [], ), ( "400 error", mock_exception_handler_400, "code", 400, "description", "this is a 400", [call(mock_exception_handler_400)], [], ), ( "Non JSON Exception", test_exception_handler_non_json_exception, "status_code", 500, "json", {"code": "bad_request", "message": "oops"}, [call(test_exception_handler_non_json_exception)], [], ), ( "JSON Exception", test_exception_handler_json_exception_404, "status_code", 404, "json", {"code": "applesauce_not_found", "message": "Applesauce Not Found"}, [], [call(test_exception_handler_json_exception_404)], ), ( "JSON Exception", test_exception_handler_json_exception_409, "status_code", 409, "json", {"code": "exists", "message": "Applesauce Already Exists"}, [], [call(test_exception_handler_json_exception_409)], ), ( "JSON Exception with No Status Specified", test_exception_handler_no_status_json_exception, "status_code", 500, "json", {"code": "applesauce_not_found", "message": "Applesauce Not Found"}, [call(test_exception_handler_no_status_json_exception)], [], ), ( "JSON Exception with None Status Specified", test_exception_handler_none_status_json_exception, "status_code", 500, "json", {"code": "applesauce_not_found", "message": "Applesauce Not Found"}, [call(test_exception_handler_none_status_json_exception)], [], ), ], ) def test_exception_handler( mocker, test_description, exception, code_key, expected_code, message_key, expected_message, expected_g_log_exception_calls, expected_g_log_warn_calls, ): """Test exception_handler.""" mocker.patch.object(sentry_sdk, "capture_exception") mock_logger = NonCallableMagicMock( error=MagicMock(), warn=MagicMock(), ) with api.app.test_request_context(): g.log = mock_logger result = handlers.exception_handler(exception) assert mock_logger.exception.mock_calls == expected_g_log_exception_calls assert mock_logger.warning.mock_calls == expected_g_log_warn_calls assert getattr(result, code_key) == expected_code assert getattr(result, message_key) == expected_message @patch("product_review.handlers.review_queue_logic") def test_get_review_queue(mock_logic, fixture_client): """Test get_review_queue.""" mock_logic.get_review_queue_items.return_value = response.Response("okie") result = fixture_client.get( "/review-queue?page_offset=11&page_limit=12&status=some-status" ) mock_logic.get_review_queue_items.assert_called_once_with( status="some-status", page_limit="12", page_offset="11", review_queue_id=None, product_id=None, ) assert result.status_code == 200 def test_get_review_queue_items_dataloader(mocker, fixture_client): """Test get_review_queue_items_dataloader.""" data = ["1", "2"] mocker.patch.object( review_queue_logic, "get_review_queue_items_by_id", autospec=True, ) fixture_client.post("/review-queue/dataloader", json=data) review_queue_logic.get_review_queue_items_by_id.assert_called_once() def test_get_target_group_by_item_ids_dataloader(mocker, fixture_client): """Test get_review_queue_items_dataloader.""" data = ["1", "2"] mocker.patch.object( review_queue_logic, "get_target_group_by_item_id", autospec=True, ) fixture_client.post("/review-queue/target-group/dataloader", json=data) review_queue_logic.get_target_group_by_item_id.assert_called_once() def test_get_review_queue_item(mocker, fixture_client): """Test get_review_queue_item.""" test_response = response.Response("okie") mocker.patch.object( review_queue_logic, "get_review_queue_items", return_value=test_response, autospec=True, ) result = fixture_client.get("/review-queue?review_queue_id=12321") review_queue_logic.get_review_queue_items.assert_called_once_with( status=None, page_limit=None, page_offset=None, review_queue_id="12321", product_id=None, ) assert result.status_code == 200 def test_lock_review_queue_item(mocker, fixture_client): """Test locking a review queue item.""" test_response = {"okie": "cool"} mocker.patch.object( review_queue_logic, "lock", return_value=test_response, autospec=True, ) result = fixture_client.patch( "/review-queue/12321/lock", headers={"Orchard-Identity-Id": "abc-123-identity-id"}, ) review_queue_logic.lock.assert_called_once_with(12321) assert result.status_code == 200 assert result.json == test_response def test_unlock_review_queue_item(mocker, fixture_client): """Test unlocking a review queue item.""" test_response = {"okie": "cool"} mocker.patch.object( review_queue_logic, "unlock", return_value=test_response, autospec=True, ) result = fixture_client.patch( "/review-queue/12321/unlock", headers={"Orchard-Identity-Id": "abc-123-identity-id"}, ) review_queue_logic.unlock.assert_called_once_with(12321) assert result.status_code == 200 assert result.json == test_response def test_move_review_queue_item_to_queue(mocker, fixture_client): """Test move_review_queue_item_to_queue success.""" test_response = {"okie": "cool"} mocker.patch.object( handlers, "validate_request_info", ) mocker.spy(handlers, "_ensure_lower_snake_cased_alphanumeric") mocker.patch.object( review_queue_logic, "move_review_queue_item_to_queue", return_value=test_response, autospec=True, ) result = fixture_client.patch( "/review-queue/12321/queue", headers={ "Orchard-Identity-Id": "abc-123-identity-id", "Content-Type": "application/json", }, data=json.dumps({"queue_name": "under_investigation"}), ) handlers.validate_request_info.assert_called_once_with( params=["queue_name"], headers=["Orchard-Identity-Id"], ) handlers._ensure_lower_snake_cased_alphanumeric.assert_called_once_with( "under_investigation" ) review_queue_logic.move_review_queue_item_to_queue.assert_called_once_with( review_queue_id=12321, queue_name="under_investigation", move_note=None, moved_to_target_id=None, ) assert result.status_code == 200 assert result.json == test_response def test_get_review_queue_item_queue_move_history(mocker, fixture_client): """Test get_review_queue_item_queue_move_history.""" test_response = {"okie": "cool"} mocker.patch.object( review_queue_logic, "get_queue_move_history", return_value=test_response, autospec=True, ) result = fixture_client.get("/review-queue/12321/queue/move-history") review_queue_logic.get_queue_move_history.assert_called_once_with(12321) assert result.status_code == 200 assert result.json == {"okie": "cool"} @pytest.mark.parametrize( ( "value", "expected_raise", ), [ ("initial", does_not_raise()), ( "Initial", pytest.raises( Exception, match="'Initial' must be a lower snake cased alphanumeric string.", ), ), ("under_investigation", does_not_raise()), ( "under-investigation", pytest.raises( Exception, match="'under-investigation' must be a lower snake cased alphanumeric string.", # noqa: E501 ), ), ("123", does_not_raise()), ( 123, pytest.raises( Exception, match="'123' must be a lower snake cased alphanumeric string.", ), ), ("abc_123", does_not_raise()), ("123_abc", does_not_raise()), ("abc", does_not_raise()), ], ) def test__ensure_lower_snake_cased_alphanumeric(value, expected_raise): """Test _ensure_lower_snake_cased_alphanumeric.""" with expected_raise: handlers._ensure_lower_snake_cased_alphanumeric(value) @pytest.mark.parametrize( ( "test_description", "test_request_context", "request_context", "expected_raise", "expected_raise_message", "expected_wrapped_function_calls", ), [ ( "success", api.app.test_request_context(json={"review_queue_id": 123}), NonCallableMagicMock(identity_id="abc-123-identity-id"), does_not_raise(), "None", [call(1, 2, 3, four=4, five=5)], ), ( "no identity_id", api.app.test_request_context(json={"review_queue_id": 123}), NonCallableMagicMock(identity_id=None), pytest.raises(Exception), json.dumps( { "status": 400, "code": None, "message": "[Orchard-Identity-Id] missing from request headers", # noqa } ), [], ), ], ) def test_require_header_orchard_identity_id( test_description, test_request_context, request_context, expected_raise, expected_raise_message, expected_wrapped_function_calls, ): """Test require_header_orchard_identity_id.""" with test_request_context, expected_raise as er: g.request_context = request_context wrapped_function = MagicMock() handlers.require_header_orchard_identity_id(wrapped_function)( 1, 2, 3, four=4, five=5 ) assert str(getattr(er, "value", None)) == expected_raise_message assert wrapped_function.mock_calls == expected_wrapped_function_calls @pytest.mark.parametrize( ( "access_rule_decision", "pdp_enabled", "pdp_authorized", "expected_status_code", "should_call_pdp", "should_call_handler_function", "error_response_calls", "flaskify_calls" ), [ pytest.param( True, # access_rule_decision False, # pdp_enabled False, # pdp_authorized (doesn't matter) 200, # expected_status_code False, # should_call_pdp True, # should_call_handler_function [], # error_response_calls [], # flaskify_calls id="Case 1: Rules access granted (PDP disabled)" ), pytest.param( False, # access_rule_decision False, # pdp_enabled False, # pdp_authorized (doesn't matter) 401, # expected_status_code False, # should_call_pdp False, # should_call_handler_function [ call(code='authorization_error', message='Unauthorized', status=401) ], # error_response_calls [call('error_response')], # flaskify calls id="Case 2: Rules access denied and PDP disabled" ), pytest.param( False, # access_rule_decision True, # pdp_enabled True, # pdp_authorized 200, # expected_status_code True, # should_call_pdp True, # should_call_handler_function [], # error_response_calls [], # flaskify calls id="Case 3: PDP check enabled, rules access denied, but PDP authorizes" ), pytest.param( False, # access_rule_decision True, # pdp_enabled False, # pdp_authorized 403, # expected_status_code True, # should_call_pdp False, # should_call_handler_function [ call(code='forbidden', message='User is forbidden', status=403) ], # error_response_calls [call('error_response')], # flaskify calls id="Case 4: PDP check enabled, rules access denied, and PDP denies" ), ] ) def test_require_standalone_authorization( access_rule_decision, pdp_enabled, pdp_authorized, expected_status_code, should_call_pdp, should_call_handler_function, error_response_calls, flaskify_calls, mocker ): """Test the require_standalone_authorization decorator with various scenarios.""" # Create test function to decorate mock_handler_function = mocker.Mock( return_value=MagicMock(status_code=200, json={"message": "success"}) ) # Required for functools.wraps mock_handler_function.__name__ = "mock_handler_function" # Apply decorator with test action and resource type mock_action = "view" mock_resource_type = "mock_resource" mock_resource_id = 0 decorated_function = handlers.require_standalone_authorization( action=mock_action, resource_type=mock_resource_type, resource_id=mock_resource_id )(mock_handler_function) # Setup mocks mocker.patch( "product_review.models.splitio.is_enabled_pdp_auth_check", return_value=pdp_enabled ) mock_verify_rules = mocker.patch( "product_review.handlers.flask_request.verify_rules_access_standalone", return_value=access_rule_decision ) mock_pdp_authorize = mocker.patch( "product_review.handlers.authorization.pdp_authorize_resource_without_attributes", return_value=pdp_authorized ) mock_flaskify = mocker.patch( "product_review.handlers.flaskify", return_value=MagicMock(status_code=expected_status_code) ) mock_error_response = mocker.patch( "product_review.handlers.response.create_error_response", return_value="error_response" ) handler_function_mock_calls = [call( 1, 2, test_param="test" )] if should_call_handler_function else [] mock_pdp_calls = [call( action=mock_action, resource_id=mock_resource_id, resource_type=mock_resource_type )] if should_call_pdp else [] # Call decorated function within request context with api.app.test_request_context(): result = decorated_function(1, 2, test_param="test") # Verify core interactions mock_verify_rules.assert_called_once() assert mock_pdp_authorize.mock_calls == mock_pdp_calls # Verify response flow assert mock_error_response.mock_calls == error_response_calls assert mock_flaskify.mock_calls == flaskify_calls assert mock_handler_function.mock_calls == handler_function_mock_calls assert result.status_code == expected_status_code @pytest.mark.parametrize( ( "test_description", "access_granted", "args", "kwargs", "function_return_value", "expected_result", "mock_function_calls", "error_response_calls", "flaskify_calls" ), [ ( "Access denied", False, [1, 2, 3], {"key": "value"}, "function_result", "unauthorized_response", [], [call(code='authorization_error', message='Unauthorized', status=401)], [call("error_response")] ), ( "Access granted", True, [4, 5, 6], {"test": "data"}, "success_result", "success_result", [call(4, 5, 6, test="data")], [], [] ), ( "Access granted with empty args", True, [], {}, "empty_args_result", "empty_args_result", [call()], [], [] ), ], ) def test_require_auth_rules_access_standalone( mocker, test_description, access_granted, args, kwargs, function_return_value, expected_result, mock_function_calls, error_response_calls, flaskify_calls ): """Test the require_auth_rules_access_standalone authorization handler decorator. This test verifies that the decorator: 1. Checks access rules via flask_request.verify_rules_access_standalone 2. Returns 401 when access is denied 3. Calls the wrapped function when access is granted """ # Setup mock function to be decorated mock_function = MagicMock(return_value=function_return_value) # Apply decorator to mock function decorated_function = handlers.require_auth_rules_access_standalone(mock_function) # Setup mocks mocker.patch('product_review.handlers.flask_request.verify_rules_access_standalone', return_value=access_granted) mocker.patch('product_review.handlers.flaskify', return_value="unauthorized_response") mocker.patch('product_review.handlers.response.create_error_response', return_value="error_response") # Execute the decorated function result = decorated_function(*args, **kwargs) # Assertions handlers.flask_request.verify_rules_access_standalone.assert_called_once() assert mock_function.mock_calls == mock_function_calls assert handlers.response.create_error_response.mock_calls == error_response_calls assert handlers.flaskify.mock_calls == flaskify_calls assert result == expected_result @pytest.mark.parametrize( ( "test_description", "test_request_context", "request_context", "expected_raise", ), [ ( "success", api.app.test_request_context(json={"something": 123}), NonCallableMagicMock( identity_id="abc-123-identity-id", profile_type="ProfileType", profile_id="profile-id", ), does_not_raise(), ), ( "no json", api.app.test_request_context(json={}), NonCallableMagicMock( identity_id="abc-123-identity-id", profile_type="ProfileType", profile_id="profile-id", ), pytest.raises( Exception, match=re.escape( json.dumps( { "status": 400, "code": None, "message": "[something] missing from POST json", } ) ), ), ), ( "missing all required headers", api.app.test_request_context(json={"something": 123}), NonCallableMagicMock(identity_id=None, profile_type=None, profile_id=None), pytest.raises( Exception, match=re.escape( json.dumps( { "status": 400, "code": None, "message": ", ".join( [ "[Orchard-Identity-Id] missing from request headers", # noqa: E501 "[Orchard-Profile-Type] missing from request headers", # noqa: E501 "[Orchard-Profile-Id] missing from request headers", ] ), } ) ), ), ), ], ) def test_validate_request_info( test_description, test_request_context, request_context, expected_raise, ): """Test validate_request_info.""" with test_request_context, expected_raise: g.request_context = request_context handlers.validate_request_info( params=["something"], headers=[ "Orchard-Identity-Id", "Orchard-Profile-Type", "Orchard-Profile-Id", ], ) test_approval_validate_approval_request_exception_json: exception_util.ExceptionDictOwsResponse = { # noqa "status": 400, "code": None, "message": "oopsies", } test_approval_validate_approval_request_exception = Exception( json.dumps(test_approval_validate_approval_request_exception_json) ) @pytest.mark.parametrize( ( "test_description", "request_data", "expected_approve_mock_calls", ), [ ( "no note", None, [call(222, "peter-123", None, None, None, [])], ), ( "note and canned response id", json.dumps({"note": "because", "canned_response_id": 66}), [call(222, "peter-123", "because", 66, None, [])], ), ( "note and tracks", json.dumps( { "note": "because", "tracks": [{"track_id": 1, "canned_response_id": 1}]} ), [call( 222, "peter-123", "because", None, [{"track_id": 1, "canned_response_id": 1}], [] )], ), ( "note and canned response ids", json.dumps({"note": "because", "canned_response_ids": [66, 77]}), [call(222, "peter-123", "because", None, None, [66, 77])], ), ], ) def test_approval_by_id( test_description, request_data, expected_approve_mock_calls, mocker, fixture_client ): """Test approval.""" mocker.patch.object(approval_logic, "approve_via_workflow", autospec=True) mocker.patch.object(handlers, "validate_request_info", autospec=True) result = fixture_client.post( "/approve/222", headers={ "Orchard-Identity-Id": "peter-123", "Orchard-Profile-Type": "ProfileType", "Orchard-Profile-Id": "profile-id", "Content-Type": "application/json", }, data=request_data, ) assert handlers.validate_request_info.mock_calls == [ call( headers=[ "Orchard-Identity-Id", "Orchard-Profile-Type", "Orchard-Profile-Id", ] ) ] assert approval_logic.approve_via_workflow.mock_calls == expected_approve_mock_calls assert result.status_code == 200 assert result.json is None test_rejection_validate_rejection_request_exception_json: exception_util.ExceptionDictOwsResponse = { # noqa "status": 400, "code": None, "message": "oopsies", } test_rejection_validate_rejection_request_exception = Exception( json.dumps(test_rejection_validate_rejection_request_exception_json) ) def test_rejection_by_id(mocker, fixture_client): """Test rejection.""" mocker.patch.object(rejection_logic, "reject_via_workflow", autospec=True) mocker.patch.object(handlers, "validate_request_info", autospec=True) result = fixture_client.post( "/reject/222", headers={ "Orchard-Identity-Id": "peter-123", "Orchard-Profile-Type": "ContentProfile", "Orchard-Profile-Id": "foo", "Content-Type": "application/json", }, data=json.dumps({"note": "not good", "canned_response_ids": [10, 12]}), ) assert handlers.validate_request_info.mock_calls == [ call( params=["note"], headers=[ "Orchard-Identity-Id", "Orchard-Profile-Type", "Orchard-Profile-Id", ], ) ] assert rejection_logic.reject_via_workflow.mock_calls == [ call(222, "peter-123", "not good", [10, 12], False)] assert result.status_code == 200 assert result.json is None def test_create_review_queue_item_success(mocker, fixture_client): """Test create_review_queue_item.""" mocker.patch.object( review_queue_logic, "insert_review_queue_item", return_value=response.Response({}, status=201), autospec=True, ) result = fixture_client.post("/review-queue/product/8") assert result.status_code == 201 def test_create_review_queue_item_success_with_previous_entry(mocker, fixture_client): """Test create_review_queue_item when there are previous entries in DB.""" test_item = create_test_review_queue_item(product_id=8, status="complete") mocker.patch.object( review_queue_logic, "get_error_correction_id", return_value=None, autospec=True, ) create_test_unsubmit(review_queue_id=test_item.review_queue_id) result = fixture_client.post("/review-queue/product/8") assert result.status_code == 201 def test_create_review_queue_item_failure(fixture_client): """Test create_review_queue_item.""" create_test_review_queue_item(product_id=8, status="new") result = fixture_client.post("/review-queue/product/8") assert result.status_code == 409 def test_get_review_history(mocker, fixture_client): """Test get_review_history.""" test_response = response.Response({"items": []}) mocker.patch.object( review_queue_logic, "get_product_review_history", return_value=test_response, autospec=True, ) result = fixture_client.get("/review-history/123") review_queue_logic.get_product_review_history.assert_called_once_with( page_limit=None, page_offset=None, product_id=123 ) assert result.status_code == 200 assert result.json == {"items": []} def test_send_email_notification(mocker, fixture_client): """Test send_email_notification.""" test_response = response.Response( message={"notification_type": "rejection"}, status=201 ) mocker.patch.object( review_queue_logic, "send_email_notification", return_value=test_response, autospec=True, ) result = fixture_client.post("/review/1/email-notification") assert result.status_code == 201 assert result.json == {"notification_type": "rejection"} review_queue_logic.send_email_notification.assert_called_once_with(1) def test_apply_revisions_if_applicable(mocker, fixture_client): """Test apply_revisions_if_applicable.""" test_response = {"applied_in_legacy": True} mocker.patch.object( approval_logic, "apply_revisions_if_applicable", return_value=test_response, autospec=True, ) result = fixture_client.post("/review/1/apply-revisions-legacy") approval_logic.apply_revisions_if_applicable.assert_called_once_with(1) assert result.json == test_response assert result.status_code == 200 def test_get_all_target_groups(mocker, fixture_client): """Test get_all_target_groups.""" test_response = [{"foo": "bar"}, {"foo": "baz"}] mocker.patch.object( review_queue_logic, "get_all_target_groups", return_value=test_response, autospec=True, ) result = fixture_client.get("/target-groups") review_queue_logic.get_all_target_groups.assert_called_once() assert result.json == test_response assert result.status_code == 200 def test_get_canned_response_items(mocker, fixture_client): """Test get_canned_response_items.""" mocker.patch.object( canned_response_logic, "get_canned_response_items", return_value=response.Response({"foo": "bar"}), autospec=True, ) result = fixture_client.get( "/canned-responses?page_limit=15&page_offset=1&category_id=4" ) canned_response_logic.get_canned_response_items.assert_called_once_with( page_limit="15", page_offset="1", category_id="4", modified_by_user_id=None, review_context=None, language_code=None, ) assert result.status_code == 200 def test_get_canned_response_categories(mocker, fixture_client): """Test get_canned_response_categories.""" mocker.patch.object( canned_response_logic, "get_canned_response_categories", return_value=response.Response({"foo": "bar"}), autospec=True, ) result = fixture_client.get("/canned-response-categories") canned_response_logic.get_canned_response_categories.assert_called_once() assert result.status_code == 200 def test_get_canned_response_item(mocker, fixture_client): """Test get_canned_response_item.""" mocker.patch.object( canned_response_logic, "get_canned_response", return_value=response.Response({"foo": "bar"}), autospec=True, ) result = fixture_client.get("/canned-response/10") canned_response_logic.get_canned_response.assert_called_once_with(10, None) assert result.status_code == 200 def test_dataload_canned_response_notes(mocker, fixture_client): """Test dataload_canned_response_notes.""" mocker.patch.object( canned_response_logic, "dataload_canned_response_notes", return_value=[{"foo": "bar"}], autospec=True, ) result = fixture_client.post( "/canned-response-note/dataloader", json=[{"canned_response_id": 1, "language_code": "en"}], ) canned_response_logic.dataload_canned_response_notes.assert_called_once_with( [{"canned_response_id": 1, "language_code": "en"}] ) assert result.status_code == 200 def test_get_account_note(mocker, fixture_client): """Test get_account_note.""" mocker.patch.object( account_note_logic, "get_account_note", return_value=response.Response({"foo": "bar"}), autospec=True, ) result = fixture_client.get("/account-note/vendor/1") assert result.status_code == 200 account_note_logic.get_account_note.assert_called_once_with(1) def test_get_subaccount_note(mocker, fixture_client): """Test get_subaccount_note.""" mocker.patch.object( account_note_logic, "get_subaccount_note", return_value=response.Response({"foo": "bar"}), autospec=True, ) result = fixture_client.get("/subaccount-note/subaccount/1") assert result.status_code == 200 account_note_logic.get_subaccount_note.assert_called_once_with(1) def test_add_account_note(mocker, fixture_client): """Test add_account_note.""" mocker.patch.object( account_note_logic, "add_account_note", return_value=response.Response("foo bar"), autospec=True, ) result = fixture_client.put( "/account-note/vendor/1", json={"note": "random"}, headers={"Orchard-Identity-Id": "abc-123-identity-id"}, ) assert result.status_code == 200 account_note_logic.add_account_note.assert_called_once_with( 1, "random", "abc-123-identity-id" ) def test_add_subaccount_note(mocker, fixture_client): """Test add_subaccount_note.""" mocker.patch.object( account_note_logic, "add_subaccount_note", return_value=response.Response("foo bar"), autospec=True, ) result = fixture_client.put( "/subaccount-note/subaccount/1", json={"note": "random"}, headers={"Orchard-Identity-Id": "abc-123-identity-id"}, ) assert result.status_code == 200 account_note_logic.add_subaccount_note.assert_called_once_with( 1, "random", "abc-123-identity-id" ) def test_sync_db(mocker, fixture_client): """Test sync_db.""" mocker.patch.object( art_relations, "sync_db", autospec=True, ) result = fixture_client.get("/dbsync") assert result.status_code == 200 assert result.json is None art_relations.sync_db.assert_called_once() @pytest.mark.parametrize( ( "test_description", "review_queue_id", "payload", "expected_logic_call", ), [ ( "basic score with PASS validation", 123, { "validation_result": "PASS", "failed_validations": None, "confidence_score": 95, "score_reasoning": {"reason": "High quality content"}, "auto_approved": None, "score_version": 1 }, (123, "PASS", None, 95, {"reason": "High quality content"}, None, 1), ), ( "score with FAIL validation and failures", 456, { "validation_result": "FAIL", "failed_validations": ["validation_1", "validation_2"], "confidence_score": 30, "score_reasoning": {"details": "Multiple validation failures"}, "auto_approved": None, "score_version": 2 }, (456, "FAIL", ["validation_1", "validation_2"], 30, {"details": "Multiple validation failures"}, None, 2), ), ( "score with auto_approved", 789, { "validation_result": "PASS", "confidence_score": 100, "score_reasoning": {"reason": "Auto approved"}, "auto_approved": True, "score_version": 1.5 }, (789, "PASS", None, 100, {"reason": "Auto approved"}, True, 1.5), ), ], ) def test_add_score( test_description, review_queue_id, payload, expected_logic_call, mocker, fixture_client ): """Test add_score handler with various payloads.""" mocker.patch.object( score_history_logic, "score_review_queue_item", return_value=response.Response("Successfully added score history record."), autospec=True, ) result = fixture_client.post( f"/score/{review_queue_id}", json=payload ) assert result.status_code == 200 score_history_logic.score_review_queue_item.assert_called_once_with( *expected_logic_call ) def test_get_score(mocker, fixture_client): """Test get_score.""" mocker.patch.object( score_history_logic, "get_score", return_value=response.Response({"foo": "bar"}), autospec=True, ) result = fixture_client.get("/score/1") assert result.status_code == 200 score_history_logic.get_score.assert_called_once_with(1)