test: implement test_validate_transaction_exception_handling (#35)
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m6s
All checks were successful
Build Docker Image / build (pull_request) Successful in 1m6s
Removes the @pytest.mark.skip and implements the test using a patched RosterValidation that raises on the first call, triggering the except clause in validate_transaction. Verifies the method returns is_legal=False with a descriptive error message. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
f7a65706a1
commit
ebec9a720b
@ -3,6 +3,7 @@ Tests for TransactionService
|
|||||||
|
|
||||||
Validates transaction service functionality, API interaction, and business logic.
|
Validates transaction service functionality, API interaction, and business logic.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from unittest.mock import AsyncMock, patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
@ -13,117 +14,131 @@ from exceptions import APIException
|
|||||||
|
|
||||||
class TestTransactionService:
|
class TestTransactionService:
|
||||||
"""Test TransactionService functionality."""
|
"""Test TransactionService functionality."""
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def service(self):
|
def service(self):
|
||||||
"""Create a fresh TransactionService instance for testing."""
|
"""Create a fresh TransactionService instance for testing."""
|
||||||
return TransactionService()
|
return TransactionService()
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_transaction_data(self):
|
def mock_transaction_data(self):
|
||||||
"""Create mock transaction data for testing."""
|
"""Create mock transaction data for testing."""
|
||||||
return {
|
return {
|
||||||
'id': 27787,
|
"id": 27787,
|
||||||
'week': 10,
|
"week": 10,
|
||||||
'season': 12,
|
"season": 12,
|
||||||
'moveid': 'Season-012-Week-10-19-13:04:41',
|
"moveid": "Season-012-Week-10-19-13:04:41",
|
||||||
'player': {
|
"player": {
|
||||||
'id': 12472,
|
"id": 12472,
|
||||||
'name': 'Test Player',
|
"name": "Test Player",
|
||||||
'wara': 2.47,
|
"wara": 2.47,
|
||||||
'season': 12,
|
"season": 12,
|
||||||
'pos_1': 'LF'
|
"pos_1": "LF",
|
||||||
},
|
},
|
||||||
'oldteam': {
|
"oldteam": {
|
||||||
'id': 508,
|
"id": 508,
|
||||||
'abbrev': 'NYD',
|
"abbrev": "NYD",
|
||||||
'sname': 'Diamonds',
|
"sname": "Diamonds",
|
||||||
'lname': 'New York Diamonds',
|
"lname": "New York Diamonds",
|
||||||
'season': 12
|
"season": 12,
|
||||||
},
|
},
|
||||||
'newteam': {
|
"newteam": {
|
||||||
'id': 499,
|
"id": 499,
|
||||||
'abbrev': 'WV',
|
"abbrev": "WV",
|
||||||
'sname': 'Black Bears',
|
"sname": "Black Bears",
|
||||||
'lname': 'West Virginia Black Bears',
|
"lname": "West Virginia Black Bears",
|
||||||
'season': 12
|
"season": 12,
|
||||||
},
|
},
|
||||||
'cancelled': False,
|
"cancelled": False,
|
||||||
'frozen': False
|
"frozen": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_api_response(self, mock_transaction_data):
|
def mock_api_response(self, mock_transaction_data):
|
||||||
"""Create mock API response with multiple transactions."""
|
"""Create mock API response with multiple transactions."""
|
||||||
return {
|
return {
|
||||||
'count': 3,
|
"count": 3,
|
||||||
'transactions': [
|
"transactions": [
|
||||||
mock_transaction_data,
|
mock_transaction_data,
|
||||||
{**mock_transaction_data, 'id': 27788, 'frozen': True},
|
{**mock_transaction_data, "id": 27788, "frozen": True},
|
||||||
{**mock_transaction_data, 'id': 27789, 'cancelled': True}
|
{**mock_transaction_data, "id": 27789, "cancelled": True},
|
||||||
]
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_service_initialization(self, service):
|
async def test_service_initialization(self, service):
|
||||||
"""Test service initialization."""
|
"""Test service initialization."""
|
||||||
assert service.model_class == Transaction
|
assert service.model_class == Transaction
|
||||||
assert service.endpoint == 'transactions'
|
assert service.endpoint == "transactions"
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_team_transactions_basic(self, service, mock_api_response):
|
async def test_get_team_transactions_basic(self, service, mock_api_response):
|
||||||
"""Test getting team transactions with basic parameters."""
|
"""Test getting team transactions with basic parameters."""
|
||||||
with patch.object(service, 'get_all_items', new_callable=AsyncMock) as mock_get:
|
with patch.object(service, "get_all_items", new_callable=AsyncMock) as mock_get:
|
||||||
mock_get.return_value = [
|
mock_get.return_value = [
|
||||||
Transaction.from_api_data(tx) for tx in mock_api_response['transactions']
|
Transaction.from_api_data(tx)
|
||||||
|
for tx in mock_api_response["transactions"]
|
||||||
]
|
]
|
||||||
|
|
||||||
result = await service.get_team_transactions('WV', 12)
|
result = await service.get_team_transactions("WV", 12)
|
||||||
|
|
||||||
assert len(result) == 3
|
assert len(result) == 3
|
||||||
assert all(isinstance(tx, Transaction) for tx in result)
|
assert all(isinstance(tx, Transaction) for tx in result)
|
||||||
|
|
||||||
# Verify API call was made
|
# Verify API call was made
|
||||||
mock_get.assert_called_once()
|
mock_get.assert_called_once()
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_team_transactions_with_filters(self, service, mock_api_response):
|
async def test_get_team_transactions_with_filters(self, service, mock_api_response):
|
||||||
"""Test getting team transactions with status filters."""
|
"""Test getting team transactions with status filters."""
|
||||||
with patch.object(service, 'get_all_items', new_callable=AsyncMock) as mock_get:
|
with patch.object(service, "get_all_items", new_callable=AsyncMock) as mock_get:
|
||||||
mock_get.return_value = []
|
mock_get.return_value = []
|
||||||
|
|
||||||
await service.get_team_transactions(
|
await service.get_team_transactions(
|
||||||
'WV', 12,
|
"WV", 12, cancelled=True, frozen=False, week_start=5, week_end=15
|
||||||
cancelled=True,
|
|
||||||
frozen=False,
|
|
||||||
week_start=5,
|
|
||||||
week_end=15
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# Verify API call was made
|
# Verify API call was made
|
||||||
mock_get.assert_called_once()
|
mock_get.assert_called_once()
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_team_transactions_sorting(self, service, mock_transaction_data):
|
async def test_get_team_transactions_sorting(self, service, mock_transaction_data):
|
||||||
"""Test transaction sorting by week and moveid."""
|
"""Test transaction sorting by week and moveid."""
|
||||||
# Create transactions with different weeks and moveids
|
# Create transactions with different weeks and moveids
|
||||||
transactions_data = [
|
transactions_data = [
|
||||||
{**mock_transaction_data, 'id': 1, 'week': 10, 'moveid': 'Season-012-Week-10-19-13:04:41'},
|
{
|
||||||
{**mock_transaction_data, 'id': 2, 'week': 8, 'moveid': 'Season-012-Week-08-12-10:30:15'},
|
**mock_transaction_data,
|
||||||
{**mock_transaction_data, 'id': 3, 'week': 10, 'moveid': 'Season-012-Week-10-15-09:22:33'},
|
"id": 1,
|
||||||
|
"week": 10,
|
||||||
|
"moveid": "Season-012-Week-10-19-13:04:41",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
**mock_transaction_data,
|
||||||
|
"id": 2,
|
||||||
|
"week": 8,
|
||||||
|
"moveid": "Season-012-Week-08-12-10:30:15",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
**mock_transaction_data,
|
||||||
|
"id": 3,
|
||||||
|
"week": 10,
|
||||||
|
"moveid": "Season-012-Week-10-15-09:22:33",
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch.object(service, 'get_all_items', new_callable=AsyncMock) as mock_get:
|
with patch.object(service, "get_all_items", new_callable=AsyncMock) as mock_get:
|
||||||
mock_get.return_value = [Transaction.from_api_data(tx) for tx in transactions_data]
|
mock_get.return_value = [
|
||||||
|
Transaction.from_api_data(tx) for tx in transactions_data
|
||||||
result = await service.get_team_transactions('WV', 12)
|
]
|
||||||
|
|
||||||
|
result = await service.get_team_transactions("WV", 12)
|
||||||
|
|
||||||
# Verify sorting: week 8 first, then week 10 sorted by moveid
|
# Verify sorting: week 8 first, then week 10 sorted by moveid
|
||||||
assert result[0].week == 8
|
assert result[0].week == 8
|
||||||
assert result[1].week == 10
|
assert result[1].week == 10
|
||||||
assert result[2].week == 10
|
assert result[2].week == 10
|
||||||
assert result[1].moveid < result[2].moveid # Alphabetical order
|
assert result[1].moveid < result[2].moveid # Alphabetical order
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_pending_transactions(self, service):
|
async def test_get_pending_transactions(self, service):
|
||||||
"""Test getting pending transactions.
|
"""Test getting pending transactions.
|
||||||
@ -131,115 +146,166 @@ class TestTransactionService:
|
|||||||
The method first fetches the current week, then calls get_team_transactions
|
The method first fetches the current week, then calls get_team_transactions
|
||||||
with week_start set to the current week.
|
with week_start set to the current week.
|
||||||
"""
|
"""
|
||||||
with patch.object(service, 'get_client', new_callable=AsyncMock) as mock_get_client:
|
with patch.object(
|
||||||
|
service, "get_client", new_callable=AsyncMock
|
||||||
|
) as mock_get_client:
|
||||||
mock_client = AsyncMock()
|
mock_client = AsyncMock()
|
||||||
mock_client.get.return_value = {'week': 17} # Simulate current week
|
mock_client.get.return_value = {"week": 17} # Simulate current week
|
||||||
mock_get_client.return_value = mock_client
|
mock_get_client.return_value = mock_client
|
||||||
|
|
||||||
with patch.object(service, 'get_team_transactions', new_callable=AsyncMock) as mock_get:
|
with patch.object(
|
||||||
|
service, "get_team_transactions", new_callable=AsyncMock
|
||||||
|
) as mock_get:
|
||||||
mock_get.return_value = []
|
mock_get.return_value = []
|
||||||
|
|
||||||
await service.get_pending_transactions('WV', 12)
|
await service.get_pending_transactions("WV", 12)
|
||||||
|
|
||||||
|
mock_get.assert_called_once_with(
|
||||||
|
"WV", 12, cancelled=False, frozen=False, week_start=17
|
||||||
|
)
|
||||||
|
|
||||||
mock_get.assert_called_once_with('WV', 12, cancelled=False, frozen=False, week_start=17)
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_frozen_transactions(self, service):
|
async def test_get_frozen_transactions(self, service):
|
||||||
"""Test getting frozen transactions."""
|
"""Test getting frozen transactions."""
|
||||||
with patch.object(service, 'get_team_transactions', new_callable=AsyncMock) as mock_get:
|
with patch.object(
|
||||||
|
service, "get_team_transactions", new_callable=AsyncMock
|
||||||
|
) as mock_get:
|
||||||
mock_get.return_value = []
|
mock_get.return_value = []
|
||||||
|
|
||||||
await service.get_frozen_transactions('WV', 12)
|
await service.get_frozen_transactions("WV", 12)
|
||||||
|
|
||||||
mock_get.assert_called_once_with('WV', 12, frozen=True)
|
mock_get.assert_called_once_with("WV", 12, frozen=True)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_processed_transactions_success(self, service, mock_transaction_data):
|
async def test_get_processed_transactions_success(
|
||||||
|
self, service, mock_transaction_data
|
||||||
|
):
|
||||||
"""Test getting processed transactions with current week lookup."""
|
"""Test getting processed transactions with current week lookup."""
|
||||||
# Mock current week response
|
# Mock current week response
|
||||||
current_response = {'week': 12}
|
current_response = {"week": 12}
|
||||||
|
|
||||||
# Create test transactions with different statuses
|
# Create test transactions with different statuses
|
||||||
all_transactions = [
|
all_transactions = [
|
||||||
Transaction.from_api_data({**mock_transaction_data, 'id': 1, 'cancelled': False, 'frozen': False}), # pending
|
Transaction.from_api_data(
|
||||||
Transaction.from_api_data({**mock_transaction_data, 'id': 2, 'cancelled': False, 'frozen': True}), # frozen
|
{**mock_transaction_data, "id": 1, "cancelled": False, "frozen": False}
|
||||||
Transaction.from_api_data({**mock_transaction_data, 'id': 3, 'cancelled': True, 'frozen': False}), # cancelled
|
), # pending
|
||||||
Transaction.from_api_data({**mock_transaction_data, 'id': 4, 'cancelled': False, 'frozen': False}), # pending
|
Transaction.from_api_data(
|
||||||
|
{**mock_transaction_data, "id": 2, "cancelled": False, "frozen": True}
|
||||||
|
), # frozen
|
||||||
|
Transaction.from_api_data(
|
||||||
|
{**mock_transaction_data, "id": 3, "cancelled": True, "frozen": False}
|
||||||
|
), # cancelled
|
||||||
|
Transaction.from_api_data(
|
||||||
|
{**mock_transaction_data, "id": 4, "cancelled": False, "frozen": False}
|
||||||
|
), # pending
|
||||||
]
|
]
|
||||||
|
|
||||||
# Mock the service methods
|
# Mock the service methods
|
||||||
with patch.object(service, 'get_client', new_callable=AsyncMock) as mock_client:
|
with patch.object(service, "get_client", new_callable=AsyncMock) as mock_client:
|
||||||
mock_api_client = AsyncMock()
|
mock_api_client = AsyncMock()
|
||||||
mock_api_client.get.return_value = current_response
|
mock_api_client.get.return_value = current_response
|
||||||
mock_client.return_value = mock_api_client
|
mock_client.return_value = mock_api_client
|
||||||
|
|
||||||
with patch.object(service, 'get_team_transactions', new_callable=AsyncMock) as mock_get_team:
|
with patch.object(
|
||||||
|
service, "get_team_transactions", new_callable=AsyncMock
|
||||||
|
) as mock_get_team:
|
||||||
mock_get_team.return_value = all_transactions
|
mock_get_team.return_value = all_transactions
|
||||||
|
|
||||||
result = await service.get_processed_transactions('WV', 12)
|
result = await service.get_processed_transactions("WV", 12)
|
||||||
|
|
||||||
# Should return empty list since all test transactions are either pending, frozen, or cancelled
|
# Should return empty list since all test transactions are either pending, frozen, or cancelled
|
||||||
# (none are processed - not pending, not frozen, not cancelled)
|
# (none are processed - not pending, not frozen, not cancelled)
|
||||||
assert len(result) == 0
|
assert len(result) == 0
|
||||||
|
|
||||||
# Verify current week API call
|
# Verify current week API call
|
||||||
mock_api_client.get.assert_called_once_with('current')
|
mock_api_client.get.assert_called_once_with("current")
|
||||||
|
|
||||||
# Verify team transactions call with week range
|
# Verify team transactions call with week range
|
||||||
mock_get_team.assert_called_once_with('WV', 12, week_start=8) # 12 - 4 = 8
|
mock_get_team.assert_called_once_with(
|
||||||
|
"WV", 12, week_start=8
|
||||||
|
) # 12 - 4 = 8
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_processed_transactions_fallback(self, service):
|
async def test_get_processed_transactions_fallback(self, service):
|
||||||
"""Test processed transactions fallback when current week fails."""
|
"""Test processed transactions fallback when current week fails."""
|
||||||
with patch.object(service, 'get_client', new_callable=AsyncMock) as mock_client:
|
with patch.object(service, "get_client", new_callable=AsyncMock) as mock_client:
|
||||||
# Mock client to raise exception
|
# Mock client to raise exception
|
||||||
mock_client.side_effect = Exception("API Error")
|
mock_client.side_effect = Exception("API Error")
|
||||||
|
|
||||||
with patch.object(service, 'get_team_transactions', new_callable=AsyncMock) as mock_get_team:
|
with patch.object(
|
||||||
|
service, "get_team_transactions", new_callable=AsyncMock
|
||||||
|
) as mock_get_team:
|
||||||
mock_get_team.return_value = []
|
mock_get_team.return_value = []
|
||||||
|
|
||||||
result = await service.get_processed_transactions('WV', 12)
|
result = await service.get_processed_transactions("WV", 12)
|
||||||
|
|
||||||
assert result == []
|
assert result == []
|
||||||
# Verify fallback call without week range
|
# Verify fallback call without week range
|
||||||
mock_get_team.assert_called_with('WV', 12)
|
mock_get_team.assert_called_with("WV", 12)
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_validate_transaction_success(self, service, mock_transaction_data):
|
async def test_validate_transaction_success(self, service, mock_transaction_data):
|
||||||
"""Test successful transaction validation."""
|
"""Test successful transaction validation."""
|
||||||
transaction = Transaction.from_api_data(mock_transaction_data)
|
transaction = Transaction.from_api_data(mock_transaction_data)
|
||||||
|
|
||||||
result = await service.validate_transaction(transaction)
|
result = await service.validate_transaction(transaction)
|
||||||
|
|
||||||
assert isinstance(result, RosterValidation)
|
assert isinstance(result, RosterValidation)
|
||||||
assert result.is_legal is True
|
assert result.is_legal is True
|
||||||
assert len(result.errors) == 0
|
assert len(result.errors) == 0
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_validate_transaction_no_moves(self, service, mock_transaction_data):
|
async def test_validate_transaction_no_moves(self, service, mock_transaction_data):
|
||||||
"""Test transaction validation with no moves (edge case)."""
|
"""Test transaction validation with no moves (edge case)."""
|
||||||
# For single-move transactions, this test simulates validation logic
|
# For single-move transactions, this test simulates validation logic
|
||||||
transaction = Transaction.from_api_data(mock_transaction_data)
|
transaction = Transaction.from_api_data(mock_transaction_data)
|
||||||
|
|
||||||
# Mock validation that would fail for complex business rules
|
# Mock validation that would fail for complex business rules
|
||||||
with patch.object(service, 'validate_transaction') as mock_validate:
|
with patch.object(service, "validate_transaction") as mock_validate:
|
||||||
validation_result = RosterValidation(
|
validation_result = RosterValidation(
|
||||||
is_legal=False,
|
is_legal=False, errors=["Transaction validation failed"]
|
||||||
errors=['Transaction validation failed']
|
|
||||||
)
|
)
|
||||||
mock_validate.return_value = validation_result
|
mock_validate.return_value = validation_result
|
||||||
|
|
||||||
result = await service.validate_transaction(transaction)
|
result = await service.validate_transaction(transaction)
|
||||||
|
|
||||||
assert result.is_legal is False
|
assert result.is_legal is False
|
||||||
assert 'Transaction validation failed' in result.errors
|
assert "Transaction validation failed" in result.errors
|
||||||
|
|
||||||
@pytest.mark.skip(reason="Exception handling test needs refactoring for new patterns")
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_validate_transaction_exception_handling(self, service, mock_transaction_data):
|
async def test_validate_transaction_exception_handling(
|
||||||
"""Test transaction validation exception handling."""
|
self, service, mock_transaction_data
|
||||||
pass
|
):
|
||||||
|
"""Test transaction validation exception handling.
|
||||||
|
|
||||||
|
When an unexpected exception occurs inside validate_transaction (e.g., the
|
||||||
|
RosterValidation constructor raises), the method's except clause catches it
|
||||||
|
and returns a failed RosterValidation containing the error message rather
|
||||||
|
than propagating the exception to the caller.
|
||||||
|
|
||||||
|
Covers the critical except path at services/transaction_service.py:187-192.
|
||||||
|
"""
|
||||||
|
transaction = Transaction.from_api_data(mock_transaction_data)
|
||||||
|
|
||||||
|
_real = RosterValidation
|
||||||
|
call_count = [0]
|
||||||
|
|
||||||
|
def patched_rv(*args, **kwargs):
|
||||||
|
call_count[0] += 1
|
||||||
|
if call_count[0] == 1:
|
||||||
|
raise RuntimeError("Simulated validation failure")
|
||||||
|
return _real(*args, **kwargs)
|
||||||
|
|
||||||
|
with patch(
|
||||||
|
"services.transaction_service.RosterValidation", side_effect=patched_rv
|
||||||
|
):
|
||||||
|
result = await service.validate_transaction(transaction)
|
||||||
|
|
||||||
|
assert isinstance(result, RosterValidation)
|
||||||
|
assert result.is_legal is False
|
||||||
|
assert len(result.errors) == 1
|
||||||
|
assert result.errors[0] == "Validation error: Simulated validation failure"
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_cancel_transaction_success(self, service, mock_transaction_data):
|
async def test_cancel_transaction_success(self, service, mock_transaction_data):
|
||||||
"""Test successful transaction cancellation.
|
"""Test successful transaction cancellation.
|
||||||
@ -248,51 +314,57 @@ class TestTransactionService:
|
|||||||
returning a success message for bulk updates. We mock the client.patch()
|
returning a success message for bulk updates. We mock the client.patch()
|
||||||
method to simulate successful cancellation.
|
method to simulate successful cancellation.
|
||||||
"""
|
"""
|
||||||
with patch.object(service, 'get_client', new_callable=AsyncMock) as mock_get_client:
|
with patch.object(
|
||||||
|
service, "get_client", new_callable=AsyncMock
|
||||||
|
) as mock_get_client:
|
||||||
mock_client = AsyncMock()
|
mock_client = AsyncMock()
|
||||||
# cancel_transaction expects a string response for success
|
# cancel_transaction expects a string response for success
|
||||||
mock_client.patch.return_value = "Updated 1 transactions"
|
mock_client.patch.return_value = "Updated 1 transactions"
|
||||||
mock_get_client.return_value = mock_client
|
mock_get_client.return_value = mock_client
|
||||||
|
|
||||||
result = await service.cancel_transaction('27787')
|
result = await service.cancel_transaction("27787")
|
||||||
|
|
||||||
assert result is True
|
assert result is True
|
||||||
mock_client.patch.assert_called_once()
|
mock_client.patch.assert_called_once()
|
||||||
call_args = mock_client.patch.call_args
|
call_args = mock_client.patch.call_args
|
||||||
assert call_args[0][0] == 'transactions' # endpoint
|
assert call_args[0][0] == "transactions" # endpoint
|
||||||
assert 'cancelled' in call_args[0][1] # update_data contains 'cancelled'
|
assert "cancelled" in call_args[0][1] # update_data contains 'cancelled'
|
||||||
assert call_args[1]['object_id'] == '27787' # transaction_id
|
assert call_args[1]["object_id"] == "27787" # transaction_id
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_cancel_transaction_not_found(self, service):
|
async def test_cancel_transaction_not_found(self, service):
|
||||||
"""Test cancelling non-existent transaction.
|
"""Test cancelling non-existent transaction.
|
||||||
|
|
||||||
When the API returns None (no response), cancel_transaction returns False.
|
When the API returns None (no response), cancel_transaction returns False.
|
||||||
"""
|
"""
|
||||||
with patch.object(service, 'get_client', new_callable=AsyncMock) as mock_get_client:
|
with patch.object(
|
||||||
|
service, "get_client", new_callable=AsyncMock
|
||||||
|
) as mock_get_client:
|
||||||
mock_client = AsyncMock()
|
mock_client = AsyncMock()
|
||||||
mock_client.patch.return_value = None # No response = failure
|
mock_client.patch.return_value = None # No response = failure
|
||||||
mock_get_client.return_value = mock_client
|
mock_get_client.return_value = mock_client
|
||||||
|
|
||||||
result = await service.cancel_transaction('99999')
|
result = await service.cancel_transaction("99999")
|
||||||
|
|
||||||
assert result is False
|
assert result is False
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_cancel_transaction_not_pending(self, service, mock_transaction_data):
|
async def test_cancel_transaction_not_pending(self, service, mock_transaction_data):
|
||||||
"""Test cancelling already processed transaction.
|
"""Test cancelling already processed transaction.
|
||||||
|
|
||||||
The API handles validation - we just need to simulate a failure response.
|
The API handles validation - we just need to simulate a failure response.
|
||||||
"""
|
"""
|
||||||
with patch.object(service, 'get_client', new_callable=AsyncMock) as mock_get_client:
|
with patch.object(
|
||||||
|
service, "get_client", new_callable=AsyncMock
|
||||||
|
) as mock_get_client:
|
||||||
mock_client = AsyncMock()
|
mock_client = AsyncMock()
|
||||||
mock_client.patch.return_value = None # API returns None on failure
|
mock_client.patch.return_value = None # API returns None on failure
|
||||||
mock_get_client.return_value = mock_client
|
mock_get_client.return_value = mock_client
|
||||||
|
|
||||||
result = await service.cancel_transaction('27787')
|
result = await service.cancel_transaction("27787")
|
||||||
|
|
||||||
assert result is False
|
assert result is False
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_cancel_transaction_exception_handling(self, service):
|
async def test_cancel_transaction_exception_handling(self, service):
|
||||||
"""Test transaction cancellation exception handling.
|
"""Test transaction cancellation exception handling.
|
||||||
@ -300,106 +372,134 @@ class TestTransactionService:
|
|||||||
When the API call raises an exception, cancel_transaction catches it
|
When the API call raises an exception, cancel_transaction catches it
|
||||||
and returns False.
|
and returns False.
|
||||||
"""
|
"""
|
||||||
with patch.object(service, 'get_client', new_callable=AsyncMock) as mock_get_client:
|
with patch.object(
|
||||||
|
service, "get_client", new_callable=AsyncMock
|
||||||
|
) as mock_get_client:
|
||||||
mock_client = AsyncMock()
|
mock_client = AsyncMock()
|
||||||
mock_client.patch.side_effect = Exception("Database error")
|
mock_client.patch.side_effect = Exception("Database error")
|
||||||
mock_get_client.return_value = mock_client
|
mock_get_client.return_value = mock_client
|
||||||
|
|
||||||
result = await service.cancel_transaction('27787')
|
result = await service.cancel_transaction("27787")
|
||||||
|
|
||||||
assert result is False
|
assert result is False
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_get_contested_transactions(self, service, mock_transaction_data):
|
async def test_get_contested_transactions(self, service, mock_transaction_data):
|
||||||
"""Test getting contested transactions."""
|
"""Test getting contested transactions."""
|
||||||
# Create transactions where multiple teams want the same player
|
# Create transactions where multiple teams want the same player
|
||||||
contested_data = [
|
contested_data = [
|
||||||
{**mock_transaction_data, 'id': 1, 'newteam': {'id': 499, 'abbrev': 'WV', 'sname': 'Black Bears', 'lname': 'West Virginia Black Bears', 'season': 12}},
|
{
|
||||||
{**mock_transaction_data, 'id': 2, 'newteam': {'id': 502, 'abbrev': 'LAA', 'sname': 'Angels', 'lname': 'Los Angeles Angels', 'season': 12}}, # Same player, different team
|
**mock_transaction_data,
|
||||||
|
"id": 1,
|
||||||
|
"newteam": {
|
||||||
|
"id": 499,
|
||||||
|
"abbrev": "WV",
|
||||||
|
"sname": "Black Bears",
|
||||||
|
"lname": "West Virginia Black Bears",
|
||||||
|
"season": 12,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
**mock_transaction_data,
|
||||||
|
"id": 2,
|
||||||
|
"newteam": {
|
||||||
|
"id": 502,
|
||||||
|
"abbrev": "LAA",
|
||||||
|
"sname": "Angels",
|
||||||
|
"lname": "Los Angeles Angels",
|
||||||
|
"season": 12,
|
||||||
|
},
|
||||||
|
}, # Same player, different team
|
||||||
]
|
]
|
||||||
|
|
||||||
with patch.object(service, 'get_all_items', new_callable=AsyncMock) as mock_get:
|
with patch.object(service, "get_all_items", new_callable=AsyncMock) as mock_get:
|
||||||
mock_get.return_value = [Transaction.from_api_data(tx) for tx in contested_data]
|
mock_get.return_value = [
|
||||||
|
Transaction.from_api_data(tx) for tx in contested_data
|
||||||
|
]
|
||||||
|
|
||||||
result = await service.get_contested_transactions(12, 10)
|
result = await service.get_contested_transactions(12, 10)
|
||||||
|
|
||||||
# Should return both transactions since they're for the same player
|
# Should return both transactions since they're for the same player
|
||||||
assert len(result) == 2
|
assert len(result) == 2
|
||||||
|
|
||||||
# Verify API call was made
|
# Verify API call was made
|
||||||
mock_get.assert_called_once()
|
mock_get.assert_called_once()
|
||||||
# Note: This test might need adjustment based on actual contested transaction logic
|
# Note: This test might need adjustment based on actual contested transaction logic
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_api_exception_handling(self, service):
|
async def test_api_exception_handling(self, service):
|
||||||
"""Test API exception handling in service methods."""
|
"""Test API exception handling in service methods."""
|
||||||
with patch.object(service, 'get_all_items', new_callable=AsyncMock) as mock_get:
|
with patch.object(service, "get_all_items", new_callable=AsyncMock) as mock_get:
|
||||||
mock_get.side_effect = APIException("API unavailable")
|
mock_get.side_effect = APIException("API unavailable")
|
||||||
|
|
||||||
with pytest.raises(APIException):
|
with pytest.raises(APIException):
|
||||||
await service.get_team_transactions('WV', 12)
|
await service.get_team_transactions("WV", 12)
|
||||||
|
|
||||||
def test_global_service_instance(self):
|
def test_global_service_instance(self):
|
||||||
"""Test that global service instance is properly initialized."""
|
"""Test that global service instance is properly initialized."""
|
||||||
assert isinstance(transaction_service, TransactionService)
|
assert isinstance(transaction_service, TransactionService)
|
||||||
assert transaction_service.model_class == Transaction
|
assert transaction_service.model_class == Transaction
|
||||||
assert transaction_service.endpoint == 'transactions'
|
assert transaction_service.endpoint == "transactions"
|
||||||
|
|
||||||
|
|
||||||
class TestTransactionServiceIntegration:
|
class TestTransactionServiceIntegration:
|
||||||
"""Integration tests for TransactionService with real-like scenarios."""
|
"""Integration tests for TransactionService with real-like scenarios."""
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_full_transaction_workflow(self):
|
async def test_full_transaction_workflow(self):
|
||||||
"""Test complete transaction workflow simulation."""
|
"""Test complete transaction workflow simulation."""
|
||||||
service = TransactionService()
|
service = TransactionService()
|
||||||
|
|
||||||
# Mock data for a complete workflow
|
# Mock data for a complete workflow
|
||||||
mock_data = {
|
mock_data = {
|
||||||
'id': 27787,
|
"id": 27787,
|
||||||
'week': 10,
|
"week": 10,
|
||||||
'season': 12,
|
"season": 12,
|
||||||
'moveid': 'Season-012-Week-10-19-13:04:41',
|
"moveid": "Season-012-Week-10-19-13:04:41",
|
||||||
'player': {
|
"player": {
|
||||||
'id': 12472,
|
"id": 12472,
|
||||||
'name': 'Test Player',
|
"name": "Test Player",
|
||||||
'wara': 2.47,
|
"wara": 2.47,
|
||||||
'season': 12,
|
"season": 12,
|
||||||
'pos_1': 'LF'
|
"pos_1": "LF",
|
||||||
},
|
},
|
||||||
'oldteam': {
|
"oldteam": {
|
||||||
'id': 508,
|
"id": 508,
|
||||||
'abbrev': 'NYD',
|
"abbrev": "NYD",
|
||||||
'sname': 'Diamonds',
|
"sname": "Diamonds",
|
||||||
'lname': 'New York Diamonds',
|
"lname": "New York Diamonds",
|
||||||
'season': 12
|
"season": 12,
|
||||||
},
|
},
|
||||||
'newteam': {
|
"newteam": {
|
||||||
'id': 499,
|
"id": 499,
|
||||||
'abbrev': 'WV',
|
"abbrev": "WV",
|
||||||
'sname': 'Black Bears',
|
"sname": "Black Bears",
|
||||||
'lname': 'West Virginia Black Bears',
|
"lname": "West Virginia Black Bears",
|
||||||
'season': 12
|
"season": 12,
|
||||||
},
|
},
|
||||||
'cancelled': False,
|
"cancelled": False,
|
||||||
'frozen': False
|
"frozen": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Mock the full workflow properly
|
# Mock the full workflow properly
|
||||||
transaction = Transaction.from_api_data(mock_data)
|
transaction = Transaction.from_api_data(mock_data)
|
||||||
|
|
||||||
# Mock get_pending_transactions to return our test transaction
|
# Mock get_pending_transactions to return our test transaction
|
||||||
with patch.object(service, 'get_pending_transactions', new_callable=AsyncMock) as mock_get_pending:
|
with patch.object(
|
||||||
|
service, "get_pending_transactions", new_callable=AsyncMock
|
||||||
|
) as mock_get_pending:
|
||||||
mock_get_pending.return_value = [transaction]
|
mock_get_pending.return_value = [transaction]
|
||||||
|
|
||||||
# Mock cancel_transaction
|
# Mock cancel_transaction
|
||||||
with patch.object(service, 'get_client', new_callable=AsyncMock) as mock_get_client:
|
with patch.object(
|
||||||
|
service, "get_client", new_callable=AsyncMock
|
||||||
|
) as mock_get_client:
|
||||||
mock_client = AsyncMock()
|
mock_client = AsyncMock()
|
||||||
mock_client.patch.return_value = "Updated 1 transactions"
|
mock_client.patch.return_value = "Updated 1 transactions"
|
||||||
mock_get_client.return_value = mock_client
|
mock_get_client.return_value = mock_client
|
||||||
|
|
||||||
# Test workflow: get pending -> validate -> cancel
|
# Test workflow: get pending -> validate -> cancel
|
||||||
pending = await service.get_pending_transactions('WV', 12)
|
pending = await service.get_pending_transactions("WV", 12)
|
||||||
assert len(pending) == 1
|
assert len(pending) == 1
|
||||||
|
|
||||||
validation = await service.validate_transaction(pending[0])
|
validation = await service.validate_transaction(pending[0])
|
||||||
@ -407,61 +507,62 @@ class TestTransactionServiceIntegration:
|
|||||||
|
|
||||||
cancelled = await service.cancel_transaction(str(pending[0].id))
|
cancelled = await service.cancel_transaction(str(pending[0].id))
|
||||||
assert cancelled is True
|
assert cancelled is True
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_performance_with_large_dataset(self):
|
async def test_performance_with_large_dataset(self):
|
||||||
"""Test service performance with large transaction dataset."""
|
"""Test service performance with large transaction dataset."""
|
||||||
service = TransactionService()
|
service = TransactionService()
|
||||||
|
|
||||||
# Create 100 mock transactions
|
# Create 100 mock transactions
|
||||||
large_dataset = []
|
large_dataset = []
|
||||||
for i in range(100):
|
for i in range(100):
|
||||||
tx_data = {
|
tx_data = {
|
||||||
'id': i,
|
"id": i,
|
||||||
'week': (i % 18) + 1, # Weeks 1-18
|
"week": (i % 18) + 1, # Weeks 1-18
|
||||||
'season': 12,
|
"season": 12,
|
||||||
'moveid': f'Season-012-Week-{(i % 18) + 1:02d}-{i}',
|
"moveid": f"Season-012-Week-{(i % 18) + 1:02d}-{i}",
|
||||||
'player': {
|
"player": {
|
||||||
'id': i + 1000,
|
"id": i + 1000,
|
||||||
'name': f'Player {i}',
|
"name": f"Player {i}",
|
||||||
'wara': round(1.0 + (i % 50) * 0.1, 2),
|
"wara": round(1.0 + (i % 50) * 0.1, 2),
|
||||||
'season': 12,
|
"season": 12,
|
||||||
'pos_1': 'LF'
|
"pos_1": "LF",
|
||||||
},
|
},
|
||||||
'oldteam': {
|
"oldteam": {
|
||||||
'id': 508,
|
"id": 508,
|
||||||
'abbrev': 'NYD',
|
"abbrev": "NYD",
|
||||||
'sname': 'Diamonds',
|
"sname": "Diamonds",
|
||||||
'lname': 'New York Diamonds',
|
"lname": "New York Diamonds",
|
||||||
'season': 12
|
"season": 12,
|
||||||
},
|
},
|
||||||
'newteam': {
|
"newteam": {
|
||||||
'id': 499,
|
"id": 499,
|
||||||
'abbrev': 'WV',
|
"abbrev": "WV",
|
||||||
'sname': 'Black Bears',
|
"sname": "Black Bears",
|
||||||
'lname': 'West Virginia Black Bears',
|
"lname": "West Virginia Black Bears",
|
||||||
'season': 12
|
"season": 12,
|
||||||
},
|
},
|
||||||
'cancelled': i % 10 == 0, # Every 10th transaction is cancelled
|
"cancelled": i % 10 == 0, # Every 10th transaction is cancelled
|
||||||
'frozen': i % 7 == 0 # Every 7th transaction is frozen
|
"frozen": i % 7 == 0, # Every 7th transaction is frozen
|
||||||
}
|
}
|
||||||
large_dataset.append(Transaction.from_api_data(tx_data))
|
large_dataset.append(Transaction.from_api_data(tx_data))
|
||||||
|
|
||||||
with patch.object(service, 'get_all_items', new_callable=AsyncMock) as mock_get:
|
with patch.object(service, "get_all_items", new_callable=AsyncMock) as mock_get:
|
||||||
mock_get.return_value = large_dataset
|
mock_get.return_value = large_dataset
|
||||||
|
|
||||||
# Test that service handles large datasets efficiently
|
# Test that service handles large datasets efficiently
|
||||||
import time
|
import time
|
||||||
|
|
||||||
start_time = time.time()
|
start_time = time.time()
|
||||||
|
|
||||||
result = await service.get_team_transactions('WV', 12)
|
result = await service.get_team_transactions("WV", 12)
|
||||||
|
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
processing_time = end_time - start_time
|
processing_time = end_time - start_time
|
||||||
|
|
||||||
assert len(result) == 100
|
assert len(result) == 100
|
||||||
assert processing_time < 1.0 # Should process quickly
|
assert processing_time < 1.0 # Should process quickly
|
||||||
|
|
||||||
# Verify sorting worked correctly
|
# Verify sorting worked correctly
|
||||||
for i in range(len(result) - 1):
|
for i in range(len(result) - 1):
|
||||||
assert result[i].week <= result[i + 1].week
|
assert result[i].week <= result[i + 1].week
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user