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
- 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>
607 lines
17 KiB
Python
607 lines
17 KiB
Python
"""
|
|
Unit tests for Project domain models.
|
|
|
|
Tests pure business logic with no external dependencies.
|
|
"""
|
|
|
|
import pytest
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
from domain.projects.models import Project, Milestone, ProjectState
|
|
from domain.projects.exceptions import MilestoneError
|
|
|
|
|
|
class TestMilestone:
|
|
"""Test Milestone entity."""
|
|
|
|
def test_milestone_creation(self):
|
|
# Arrange
|
|
due_date = datetime.now(timezone.utc) + timedelta(days=30)
|
|
|
|
# Act
|
|
milestone = Milestone(
|
|
id=1,
|
|
title="Version 1.0",
|
|
description="First release",
|
|
due_date=due_date,
|
|
state="open",
|
|
open_issues=5,
|
|
closed_issues=3
|
|
)
|
|
|
|
# Assert
|
|
assert milestone.id == 1
|
|
assert milestone.title == "Version 1.0"
|
|
assert milestone.description == "First release"
|
|
assert milestone.due_date == due_date
|
|
assert milestone.state == "open"
|
|
assert milestone.open_issues == 5
|
|
assert milestone.closed_issues == 3
|
|
|
|
def test_completion_percentage_calculation(self):
|
|
# Arrange
|
|
milestone = Milestone(
|
|
id=1,
|
|
title="Test",
|
|
description=None,
|
|
due_date=None,
|
|
state="open",
|
|
open_issues=2,
|
|
closed_issues=8
|
|
)
|
|
|
|
# Act
|
|
percentage = milestone.completion_percentage
|
|
|
|
# Assert
|
|
assert percentage == 80.0 # 8/(2+8) * 100
|
|
|
|
def test_completion_percentage_with_no_issues(self):
|
|
# Arrange
|
|
milestone = Milestone(
|
|
id=1,
|
|
title="Test",
|
|
description=None,
|
|
due_date=None,
|
|
state="open",
|
|
open_issues=0,
|
|
closed_issues=0
|
|
)
|
|
|
|
# Act
|
|
percentage = milestone.completion_percentage
|
|
|
|
# Assert
|
|
assert percentage == 0.0
|
|
|
|
def test_total_issues_property(self):
|
|
# Arrange
|
|
milestone = Milestone(
|
|
id=1,
|
|
title="Test",
|
|
description=None,
|
|
due_date=None,
|
|
state="open",
|
|
open_issues=3,
|
|
closed_issues=7
|
|
)
|
|
|
|
# Act & Assert
|
|
assert milestone.total_issues == 10
|
|
|
|
def test_is_overdue_with_past_due_date(self):
|
|
# Arrange
|
|
past_date = datetime.now(timezone.utc) - timedelta(days=1)
|
|
milestone = Milestone(
|
|
id=1,
|
|
title="Test",
|
|
description=None,
|
|
due_date=past_date,
|
|
state="open",
|
|
open_issues=1,
|
|
closed_issues=0
|
|
)
|
|
|
|
# Act & Assert
|
|
assert milestone.is_overdue() is True
|
|
|
|
def test_is_overdue_with_future_due_date(self):
|
|
# Arrange
|
|
future_date = datetime.now(timezone.utc) + timedelta(days=1)
|
|
milestone = Milestone(
|
|
id=1,
|
|
title="Test",
|
|
description=None,
|
|
due_date=future_date,
|
|
state="open",
|
|
open_issues=1,
|
|
closed_issues=0
|
|
)
|
|
|
|
# Act & Assert
|
|
assert milestone.is_overdue() is False
|
|
|
|
def test_is_overdue_with_no_due_date(self):
|
|
# Arrange
|
|
milestone = Milestone(
|
|
id=1,
|
|
title="Test",
|
|
description=None,
|
|
due_date=None,
|
|
state="open",
|
|
open_issues=1,
|
|
closed_issues=0
|
|
)
|
|
|
|
# Act & Assert
|
|
assert milestone.is_overdue() is False
|
|
|
|
def test_is_overdue_with_closed_milestone(self):
|
|
# Arrange
|
|
past_date = datetime.now(timezone.utc) - timedelta(days=1)
|
|
milestone = Milestone(
|
|
id=1,
|
|
title="Test",
|
|
description=None,
|
|
due_date=past_date,
|
|
state="closed",
|
|
open_issues=0,
|
|
closed_issues=5
|
|
)
|
|
|
|
# Act & Assert
|
|
assert milestone.is_overdue() is False
|
|
|
|
def test_is_completed_with_closed_state(self):
|
|
# Arrange
|
|
milestone = Milestone(
|
|
id=1,
|
|
title="Test",
|
|
description=None,
|
|
due_date=None,
|
|
state="closed",
|
|
open_issues=0,
|
|
closed_issues=5
|
|
)
|
|
|
|
# Act & Assert
|
|
assert milestone.is_completed() is True
|
|
|
|
def test_is_completed_with_100_percent_completion(self):
|
|
# Arrange
|
|
milestone = Milestone(
|
|
id=1,
|
|
title="Test",
|
|
description=None,
|
|
due_date=None,
|
|
state="open",
|
|
open_issues=0,
|
|
closed_issues=5
|
|
)
|
|
|
|
# Act & Assert
|
|
assert milestone.is_completed() is True
|
|
|
|
def test_is_completed_with_partial_completion(self):
|
|
# Arrange
|
|
milestone = Milestone(
|
|
id=1,
|
|
title="Test",
|
|
description=None,
|
|
due_date=None,
|
|
state="open",
|
|
open_issues=2,
|
|
closed_issues=3
|
|
)
|
|
|
|
# Act & Assert
|
|
assert milestone.is_completed() is False
|
|
|
|
def test_add_issue_increments_open_count(self):
|
|
# Arrange
|
|
milestone = Milestone(
|
|
id=1,
|
|
title="Test",
|
|
description=None,
|
|
due_date=None,
|
|
state="open",
|
|
open_issues=3,
|
|
closed_issues=2
|
|
)
|
|
|
|
# Act
|
|
milestone.add_issue()
|
|
|
|
# Assert
|
|
assert milestone.open_issues == 4
|
|
assert milestone.closed_issues == 2
|
|
|
|
def test_close_issue_moves_from_open_to_closed(self):
|
|
# Arrange
|
|
milestone = Milestone(
|
|
id=1,
|
|
title="Test",
|
|
description=None,
|
|
due_date=None,
|
|
state="open",
|
|
open_issues=3,
|
|
closed_issues=2
|
|
)
|
|
|
|
# Act
|
|
milestone.close_issue()
|
|
|
|
# Assert
|
|
assert milestone.open_issues == 2
|
|
assert milestone.closed_issues == 3
|
|
|
|
def test_close_issue_with_no_open_issues_raises_error(self):
|
|
# Arrange
|
|
milestone = Milestone(
|
|
id=1,
|
|
title="Test",
|
|
description=None,
|
|
due_date=None,
|
|
state="open",
|
|
open_issues=0,
|
|
closed_issues=5
|
|
)
|
|
|
|
# Act & Assert
|
|
with pytest.raises(MilestoneError) as exc_info:
|
|
milestone.close_issue()
|
|
|
|
assert "no open issues" in str(exc_info.value)
|
|
assert exc_info.value.milestone_id == 1
|
|
|
|
def test_reopen_issue_moves_from_closed_to_open(self):
|
|
# Arrange
|
|
milestone = Milestone(
|
|
id=1,
|
|
title="Test",
|
|
description=None,
|
|
due_date=None,
|
|
state="open",
|
|
open_issues=2,
|
|
closed_issues=3
|
|
)
|
|
|
|
# Act
|
|
milestone.reopen_issue()
|
|
|
|
# Assert
|
|
assert milestone.open_issues == 3
|
|
assert milestone.closed_issues == 2
|
|
|
|
def test_reopen_issue_with_no_closed_issues_raises_error(self):
|
|
# Arrange
|
|
milestone = Milestone(
|
|
id=1,
|
|
title="Test",
|
|
description=None,
|
|
due_date=None,
|
|
state="open",
|
|
open_issues=5,
|
|
closed_issues=0
|
|
)
|
|
|
|
# Act & Assert
|
|
with pytest.raises(MilestoneError) as exc_info:
|
|
milestone.reopen_issue()
|
|
|
|
assert "no closed issues" in str(exc_info.value)
|
|
assert exc_info.value.milestone_id == 1
|
|
|
|
|
|
class TestProject:
|
|
"""Test Project aggregate root."""
|
|
|
|
def test_project_creation(self):
|
|
# Arrange
|
|
created_at = datetime.now(timezone.utc)
|
|
updated_at = datetime.now(timezone.utc)
|
|
milestones = [
|
|
Milestone(1, "M1", None, None, "open", 2, 1),
|
|
Milestone(2, "M2", None, None, "closed", 0, 3)
|
|
]
|
|
|
|
# Act
|
|
project = Project(
|
|
name="Test Project",
|
|
description="A test project",
|
|
state=ProjectState.ACTIVE,
|
|
milestones=milestones,
|
|
kanban_columns=["Todo", "In Progress", "Done"],
|
|
created_at=created_at,
|
|
updated_at=updated_at
|
|
)
|
|
|
|
# Assert
|
|
assert project.name == "Test Project"
|
|
assert project.description == "A test project"
|
|
assert project.state == ProjectState.ACTIVE
|
|
assert len(project.milestones) == 2
|
|
assert project.kanban_columns == ["Todo", "In Progress", "Done"]
|
|
|
|
def test_get_active_milestones(self):
|
|
# Arrange
|
|
milestones = [
|
|
Milestone(1, "M1", None, None, "open", 2, 1),
|
|
Milestone(2, "M2", None, None, "closed", 0, 3),
|
|
Milestone(3, "M3", None, None, "open", 1, 0)
|
|
]
|
|
project = Project(
|
|
name="Test",
|
|
description="",
|
|
state=ProjectState.ACTIVE,
|
|
milestones=milestones,
|
|
kanban_columns=[],
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc)
|
|
)
|
|
|
|
# Act
|
|
active_milestones = project.get_active_milestones()
|
|
|
|
# Assert
|
|
assert len(active_milestones) == 2
|
|
assert all(m.state == "open" for m in active_milestones)
|
|
|
|
def test_get_completed_milestones(self):
|
|
# Arrange
|
|
milestones = [
|
|
Milestone(1, "M1", None, None, "open", 2, 1), # Not completed
|
|
Milestone(2, "M2", None, None, "closed", 0, 3), # Completed (closed)
|
|
Milestone(3, "M3", None, None, "open", 0, 5) # Completed (100%)
|
|
]
|
|
project = Project(
|
|
name="Test",
|
|
description="",
|
|
state=ProjectState.ACTIVE,
|
|
milestones=milestones,
|
|
kanban_columns=[],
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc)
|
|
)
|
|
|
|
# Act
|
|
completed_milestones = project.get_completed_milestones()
|
|
|
|
# Assert
|
|
assert len(completed_milestones) == 2
|
|
|
|
def test_get_overdue_milestones(self):
|
|
# Arrange
|
|
past_date = datetime.now(timezone.utc) - timedelta(days=1)
|
|
future_date = datetime.now(timezone.utc) + timedelta(days=1)
|
|
milestones = [
|
|
Milestone(1, "M1", None, past_date, "open", 2, 1), # Overdue
|
|
Milestone(2, "M2", None, future_date, "open", 1, 0), # Not overdue
|
|
Milestone(3, "M3", None, None, "open", 1, 0) # No due date
|
|
]
|
|
project = Project(
|
|
name="Test",
|
|
description="",
|
|
state=ProjectState.ACTIVE,
|
|
milestones=milestones,
|
|
kanban_columns=[],
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc)
|
|
)
|
|
|
|
# Act
|
|
overdue_milestones = project.get_overdue_milestones()
|
|
|
|
# Assert
|
|
assert len(overdue_milestones) == 1
|
|
assert overdue_milestones[0].id == 1
|
|
|
|
def test_calculate_overall_progress(self):
|
|
# Arrange
|
|
milestones = [
|
|
Milestone(1, "M1", None, None, "open", 1, 4), # 80% complete
|
|
Milestone(2, "M2", None, None, "open", 3, 2) # 40% complete
|
|
]
|
|
project = Project(
|
|
name="Test",
|
|
description="",
|
|
state=ProjectState.ACTIVE,
|
|
milestones=milestones,
|
|
kanban_columns=[],
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc)
|
|
)
|
|
|
|
# Act
|
|
progress = project.calculate_overall_progress()
|
|
|
|
# Assert
|
|
assert progress == 60.0 # (80 + 40) / 2
|
|
|
|
def test_calculate_overall_progress_with_no_milestones(self):
|
|
# Arrange
|
|
project = Project(
|
|
name="Test",
|
|
description="",
|
|
state=ProjectState.ACTIVE,
|
|
milestones=[],
|
|
kanban_columns=[],
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc)
|
|
)
|
|
|
|
# Act
|
|
progress = project.calculate_overall_progress()
|
|
|
|
# Assert
|
|
assert progress == 0.0
|
|
|
|
def test_get_total_issues(self):
|
|
# Arrange
|
|
milestones = [
|
|
Milestone(1, "M1", None, None, "open", 2, 3), # 5 total
|
|
Milestone(2, "M2", None, None, "open", 1, 4) # 5 total
|
|
]
|
|
project = Project(
|
|
name="Test",
|
|
description="",
|
|
state=ProjectState.ACTIVE,
|
|
milestones=milestones,
|
|
kanban_columns=[],
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc)
|
|
)
|
|
|
|
# Act & Assert
|
|
assert project.get_total_issues() == 10
|
|
assert project.get_total_open_issues() == 3
|
|
assert project.get_total_closed_issues() == 7
|
|
|
|
def test_archive_project(self):
|
|
# Arrange
|
|
project = Project(
|
|
name="Test",
|
|
description="",
|
|
state=ProjectState.ACTIVE,
|
|
milestones=[],
|
|
kanban_columns=[],
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc)
|
|
)
|
|
|
|
# Act
|
|
project.archive()
|
|
|
|
# Assert
|
|
assert project.state == ProjectState.ARCHIVED
|
|
assert project.archived_at is not None
|
|
|
|
def test_activate_project(self):
|
|
# Arrange
|
|
project = Project(
|
|
name="Test",
|
|
description="",
|
|
state=ProjectState.ARCHIVED,
|
|
milestones=[],
|
|
kanban_columns=[],
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc),
|
|
archived_at=datetime.now(timezone.utc)
|
|
)
|
|
|
|
# Act
|
|
project.activate()
|
|
|
|
# Assert
|
|
assert project.state == ProjectState.ACTIVE
|
|
assert project.archived_at is None
|
|
|
|
def test_add_milestone(self):
|
|
# Arrange
|
|
project = Project(
|
|
name="Test",
|
|
description="",
|
|
state=ProjectState.ACTIVE,
|
|
milestones=[],
|
|
kanban_columns=[],
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc)
|
|
)
|
|
milestone = Milestone(1, "New Milestone", None, None, "open", 0, 0)
|
|
|
|
# Act
|
|
project.add_milestone(milestone)
|
|
|
|
# Assert
|
|
assert len(project.milestones) == 1
|
|
assert project.milestones[0] == milestone
|
|
|
|
def test_add_duplicate_milestone_raises_error(self):
|
|
# Arrange
|
|
milestone1 = Milestone(1, "Milestone 1", None, None, "open", 0, 0)
|
|
milestone2 = Milestone(1, "Milestone 2", None, None, "open", 0, 0) # Same ID
|
|
project = Project(
|
|
name="Test",
|
|
description="",
|
|
state=ProjectState.ACTIVE,
|
|
milestones=[milestone1],
|
|
kanban_columns=[],
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc)
|
|
)
|
|
|
|
# Act & Assert
|
|
with pytest.raises(ValueError, match="Milestone with ID 1 already exists"):
|
|
project.add_milestone(milestone2)
|
|
|
|
def test_remove_milestone(self):
|
|
# Arrange
|
|
milestone = Milestone(1, "Milestone", None, None, "open", 0, 0)
|
|
project = Project(
|
|
name="Test",
|
|
description="",
|
|
state=ProjectState.ACTIVE,
|
|
milestones=[milestone],
|
|
kanban_columns=[],
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc)
|
|
)
|
|
|
|
# Act
|
|
project.remove_milestone(1)
|
|
|
|
# Assert
|
|
assert len(project.milestones) == 0
|
|
|
|
def test_remove_nonexistent_milestone_raises_error(self):
|
|
# Arrange
|
|
project = Project(
|
|
name="Test",
|
|
description="",
|
|
state=ProjectState.ACTIVE,
|
|
milestones=[],
|
|
kanban_columns=[],
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc)
|
|
)
|
|
|
|
# Act & Assert
|
|
with pytest.raises(ValueError, match="Milestone with ID 999 not found"):
|
|
project.remove_milestone(999)
|
|
|
|
def test_get_milestone(self):
|
|
# Arrange
|
|
milestone = Milestone(1, "Milestone", None, None, "open", 0, 0)
|
|
project = Project(
|
|
name="Test",
|
|
description="",
|
|
state=ProjectState.ACTIVE,
|
|
milestones=[milestone],
|
|
kanban_columns=[],
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc)
|
|
)
|
|
|
|
# Act
|
|
found_milestone = project.get_milestone(1)
|
|
|
|
# Assert
|
|
assert found_milestone == milestone
|
|
|
|
def test_get_nonexistent_milestone_returns_none(self):
|
|
# Arrange
|
|
project = Project(
|
|
name="Test",
|
|
description="",
|
|
state=ProjectState.ACTIVE,
|
|
milestones=[],
|
|
kanban_columns=[],
|
|
created_at=datetime.now(timezone.utc),
|
|
updated_at=datetime.now(timezone.utc)
|
|
)
|
|
|
|
# Act
|
|
found_milestone = project.get_milestone(999)
|
|
|
|
# Assert
|
|
assert found_milestone is None |