Files
markitect-main/domain/issues/services.py
tegwick 1fa0f1e84a
Some checks failed
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
fix: Eliminate all 111 test warnings by fixing root causes
- Replace deprecated datetime.utcnow() with datetime.now(timezone.utc)
  across all domain models, services, infrastructure, and test files
- Add missing timezone imports to all affected files
- Fix pytest.ini configuration format from [tool:pytest] to [pytest]
- Remove warning suppressions to expose actual issues
- Ensure proper pytest marker registration for smoke tests

Results:
- 305 passed, 2 skipped, 0 warnings (down from 111 warnings)
- All functionality preserved with modern datetime API usage
- Improved code quality by addressing root causes vs suppression

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-27 20:14:22 +02:00

174 lines
6.3 KiB
Python

"""
Issue domain services.
Contains business logic for issue-related operations.
"""
from typing import Dict, Any, List
from datetime import datetime, timezone
from .models import Issue, IssueState, LabelCategories
from .exceptions import IssueValidationError
class IssueStatusService:
"""Domain service for issue status-related business logic."""
def determine_kanban_column(self, issue: Issue, project_info: Dict[str, Any]) -> str:
"""Determine kanban column based on issue state and labels."""
# Pure business logic - no infrastructure dependencies
label_categories = issue.categorize_labels()
# Business rules for kanban column determination
if issue.state == IssueState.CLOSED:
return "Done"
# Check for explicit status labels
for state_label in label_categories.state_labels:
if state_label == "status:in-progress":
return "In Progress"
elif state_label == "status:review":
return "Review"
elif state_label == "status:blocked":
return "Blocked"
elif state_label == "status:ready":
return "Ready"
# Default for open issues without explicit status
return "Todo"
def extract_priority_info(self, issue: Issue) -> Dict[str, Any]:
"""Extract priority information from issue labels."""
label_categories = issue.categorize_labels()
priority_mapping = {
"priority:low": "Low",
"priority:medium": "Medium",
"priority:high": "High",
"priority:critical": "Critical"
}
for priority_label in label_categories.priority_labels:
if priority_label in priority_mapping:
return {
"level": priority_mapping[priority_label],
"label": priority_label
}
# Default priority
return {"level": "Medium", "label": None}
def extract_state_info(self, issue: Issue) -> Dict[str, Any]:
"""Extract state information from issue labels and state."""
label_categories = issue.categorize_labels()
return {
"state": issue.state.value,
"state_labels": label_categories.state_labels,
"is_closed": issue.state == IssueState.CLOSED,
"closed_at": issue.closed_at.isoformat() if issue.closed_at else None
}
def calculate_issue_age_days(self, issue: Issue) -> int:
"""Calculate issue age in days."""
from datetime import datetime
return (datetime.now(timezone.utc) - issue.created_at).days
def is_stale_issue(self, issue: Issue, stale_threshold_days: int = 30) -> bool:
"""Determine if issue is considered stale based on business rules."""
if issue.state == IssueState.CLOSED:
return False
age_days = self.calculate_issue_age_days(issue)
return age_days > stale_threshold_days
class IssueValidationService:
"""Domain service for issue validation business rules."""
def validate_issue_creation(self, title: str, labels: List[str]) -> None:
"""Validate issue creation according to business rules."""
if not title or not title.strip():
raise IssueValidationError(
"Issue title cannot be empty",
field="title",
value=title
)
if len(title) > 255:
raise IssueValidationError(
"Issue title cannot exceed 255 characters",
field="title",
value=title
)
# Business rule: Cannot have conflicting priority labels
priority_labels = [label for label in labels if label.startswith("priority:")]
if len(priority_labels) > 1:
raise IssueValidationError(
"Issue cannot have multiple priority labels",
field="labels",
value=priority_labels
)
# Business rule: Cannot have conflicting state labels
state_labels = [label for label in labels if label.startswith("status:")]
if len(state_labels) > 1:
raise IssueValidationError(
"Issue cannot have multiple state labels",
field="labels",
value=state_labels
)
def validate_title_update(self, new_title: str) -> None:
"""Validate issue title update."""
if not new_title or not new_title.strip():
raise IssueValidationError(
"Issue title cannot be empty",
field="title",
value=new_title
)
if len(new_title) > 255:
raise IssueValidationError(
"Issue title cannot exceed 255 characters",
field="title",
value=new_title
)
def validate_label_addition(self, issue: Issue, new_label: str) -> None:
"""Validate adding a label to an issue."""
# Business rule: Cannot add duplicate labels
if issue.has_label(new_label):
raise IssueValidationError(
f"Issue already has label '{new_label}'",
field="labels",
value=new_label
)
# Business rule: Cannot add conflicting priority labels
if new_label.startswith("priority:"):
existing_priority_labels = [
label.name for label in issue.labels
if label.is_priority_label()
]
if existing_priority_labels:
raise IssueValidationError(
f"Issue already has priority label '{existing_priority_labels[0]}'. "
f"Cannot add '{new_label}'",
field="labels",
value=new_label
)
# Business rule: Cannot add conflicting state labels
if new_label.startswith("status:"):
existing_state_labels = [
label.name for label in issue.labels
if label.is_state_label()
]
if existing_state_labels:
raise IssueValidationError(
f"Issue already has state label '{existing_state_labels[0]}'. "
f"Cannot add '{new_label}'",
field="labels",
value=new_label
)