Loading...
Loading...
Fix ServiceResult pattern mistakes causing test failures and type errors. Use when seeing "dict object has no attribute success", "mock not returning ServiceResult", mock assertions failing, or type errors with ServiceResult. Analyzes .py test files and service implementations. Covers async/await patterns, monad operations (map, bind, flatmap), and proper mock configuration.
npx skill4agent add dawiddutoit/custom-claude util-resolve-serviceresult-errors# Test fails with: 'dict' object has no attribute 'success'
❌ WRONG:
mock_service.get_data = AsyncMock(return_value={"items": []})
✅ CORRECT:
from project_watch_mcp.domain.common import ServiceResult
mock_service.get_data = AsyncMock(return_value=ServiceResult.ok({"items": []}))
Result: Test passes, ServiceResult pattern enforced'dict' object has no attribute 'success'coroutine object has no attribute 'success'Incompatible types in assignmentCannot unwrap None data from successful resultuv run pytest tests/path/to/failing_test.py -v'dict' object has no attribute 'success'return_value = {"key": "value"}return_value = ServiceResult.ok({"key": "value"})coroutine object has no attribute 'success'AsyncMock(return_value=ServiceResult.ok(...))Incompatible types in assignmentCannot unwrap None data from successful resultServiceResult.ok(None).unwrap()result.data is not Nonemock_service.get_data = AsyncMock(return_value={"items": []})
# Causes: 'dict' object has no attribute 'success'from project_watch_mcp.domain.common import ServiceResult
mock_service.get_data = AsyncMock(
return_value=ServiceResult.ok({"items": []})
)# Fix all mocks in test file at once
mock_repo.file_exists = AsyncMock(return_value=ServiceResult.ok(False))
mock_repo.store_file = AsyncMock(return_value=ServiceResult.ok())
mock_service.create_chunks = AsyncMock(return_value=ServiceResult.ok(chunks))# ❌ WRONG - No success check
result = service.get_data()
data = result.data # Could be None on failure!
# ✅ CORRECT - Check success first
result = service.get_data()
if result.success:
data = result.data
else:
return ServiceResult.fail(result.error)# ❌ WRONG - Manual chaining
result1 = service1.operation()
if result1.success:
result2 = service2.operation(result1.data)
if result2.success:
return result2
return result1 if not result1.success else result2
# ✅ CORRECT - Use composition utilities
from project_watch_mcp.domain.common.service_result_utils import compose_results
result = service1.operation()
result = compose_results(lambda d: service2.operation(d), result)
return result# ✅ CORRECT - Async ServiceResult pattern
async def process_file(file_path: Path) -> ServiceResult[dict]:
result = await self.reader.read_file(file_path)
if result.is_failure:
return ServiceResult.fail(result.error)
# result.success guarantees result.data is not None
content = result.data
return ServiceResult.ok({"content": content})from project_watch_mcp.domain.common.service_result_utils import (
compose_results, # Monadic bind (flatMap)
map_result, # Functor map
chain_results, # Chain multiple results
collect_results, # Collect list of results
flatten_results, # Flatten nested results
unwrap_or_fail, # Convert to exception
)# Map over successful result
result = ServiceResult.ok([1, 2, 3])
doubled = map_result(lambda items: [x * 2 for x in items], result)
# Result: ServiceResult.ok([2, 4, 6])
# Compose operations (flatMap)
def validate(data: dict) -> ServiceResult[dict]:
if "required_field" in data:
return ServiceResult.ok(data)
return ServiceResult.fail("Missing required field")
result = ServiceResult.ok({"required_field": "value"})
validated = compose_results(validate, result)
# Chain multiple results
result1 = ServiceResult.ok(10)
result2 = ServiceResult.ok(20)
result3 = ServiceResult.ok(30)
combined = chain_results(result1, result2, result3)
# Result: ServiceResult.ok([10, 20, 30])# ❌ WRONG - Type mismatch
def process() -> ServiceResult[list[str]]:
result: ServiceResult[dict] = get_data()
return result # Type error!
# ✅ CORRECT - Proper type transformation
def process() -> ServiceResult[list[str]]:
result: ServiceResult[dict] = get_data()
if result.is_failure:
return ServiceResult.fail(result.error)
items = list(result.data.keys())
return ServiceResult.ok(items)# ❌ WRONG - Not handling None
result = ServiceResult.ok(None)
value = result.unwrap() # Raises ValueError!
# ✅ CORRECT - Use unwrap_or for optional data
result = ServiceResult.ok(None)
value = result.unwrap_or([]) # Returns default on None/failure# Run the specific test
uv run pytest tests/path/to/test_file.py::test_name -v
# Run all related tests
uv run pytest tests/unit/application/ -v -k "service"
# Verify type safety
uv run pyright src/project_watch_mcp/'dict' object has no attribute 'success'@pytest.fixture
def mock_repository():
repo = MagicMock(spec=CodeRepository)
# ❌ WRONG - Returns dict, not ServiceResult
repo.get_files = AsyncMock(return_value={"files": []})
return repo
async def test_get_files(mock_repository):
handler = FileHandler(mock_repository)
result = await handler.process()
# Fails: 'dict' object has no attribute 'success'
assert result.success is Truefrom project_watch_mcp.domain.common import ServiceResult
@pytest.fixture
def mock_repository():
repo = MagicMock(spec=CodeRepository)
# ✅ CORRECT - Returns ServiceResult
repo.get_files = AsyncMock(
return_value=ServiceResult.ok({"files": []})
)
return repo
async def test_get_files(mock_repository):
handler = FileHandler(mock_repository)
result = await handler.process()
# Now works correctly
assert result.success is True
assert result.data == {"files": []}from project_watch_mcp.domain.common import ServiceResult
# ❌ WRONG - AsyncMock without proper return_value
service.embed_text = AsyncMock()
# ✅ CORRECT - AsyncMock with ServiceResult return_value
service.embed_text = AsyncMock(
return_value=ServiceResult.ok([0.1, 0.2, 0.3])
)if result.success:from project_watch_mcp.domain.common import ServiceResultfrom project_watch_mcp.domain.common.service_result_utils import <utility>from unittest.mock import AsyncMock'dict' object has no attribute 'success'ServiceResult.ok(data)datacoroutine object has no attribute 'success'return_valueunwrap_or(default)return_value = datareturn_value = ServiceResult.ok(data)data = result.dataif result.success: data = result.datavalue = result.unwrap()value = result.unwrap_or(default)MagicMock(return_value=ServiceResult.ok(...))AsyncMock(return_value=ServiceResult.ok(...))if result.is_failure: return result# Fix all test mocks automatically
python scripts/fix_serviceresult_mocks.py --all tests/
# Find all violations in codebase
python scripts/validate_serviceresult_usage.py src/
# Get refactoring suggestions
python scripts/find_serviceresult_chains.py --suggest-refactor src/