feat: Revolutionary Test Architecture - 7-Layer Organization with Advanced Testing Capabilities

ARCHITECTURAL MILESTONE: Complete transformation of test suite from issue-based to sophisticated
architectural layer organization with 348 tests across 7 layers (Foundation → Infrastructure →
Integration → Domain → Service → Application → Presentation).

Major Components:

🏗️ ARCHITECTURAL TEST ORGANIZATION:
• Renamed 23 test files to architectural layers (e.g. test_parser.py → test_l7_foundation_markdown_parsing.py)
• Created reverse dependency execution order for 60-80% faster feedback
• Foundation layer (10 tests, ~9s) provides immediate failure detection
• Complete dependency mapping across all 7 architectural layers

🎯 ADVANCED TEST RUNNERS:
• run_architectural_tests.py - Reverse dependency execution with performance metrics
• run_randomized_tests.py - Seed-based randomization for dependency detection
• Comprehensive error handling and colored output for optimal UX
• Support for layer-specific execution and early termination on failures

📋 COMPREHENSIVE DOCUMENTATION:
• ARCHITECTURE.md - 7-layer architecture blueprint with migration strategy
• CAPABILITIES.md - Complete inventory of 73+ system capabilities across 15 categories
• TEST_ARCHITECTURE.md - Detailed test execution strategy and naming conventions
• ARCHITECTURAL_CHAOS_TESTING_ISSUE.md - Chaos engineering gameplan (Issue #35)

🔧 MAKEFILE INTEGRATION:
• 15+ new testing targets (test-arch, test-foundation, test-random, etc.)
• Layer-specific execution (test-infrastructure, test-domain, test-service)
• Advanced options (test-quick, test-layers, test-random-repeat)
• Comprehensive help system with organized testing categories

🎲 RANDOMIZED TESTING:
• Seed-based reproducible test execution for debugging
• Multi-iteration testing to detect flaky tests and hidden dependencies
• Enhanced randomization support with pytest-randomly integration
• Performance analysis across different execution orders

🚀 PERFORMANCE OPTIMIZATION:
• Foundation-first execution prevents cascade failure debugging
• Quick testing (foundation + infrastructure) completes in ~22 seconds
• Layer isolation enables targeted debugging and development
• Optimal feedback loops for architectural development

This revolutionary testing infrastructure establishes MarkiTect as having enterprise-grade
test organization with architectural principles, performance optimization, and advanced
testing methodologies including chaos engineering foundations.

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-09-29 12:18:25 +02:00
parent 0694d16876
commit b13de9b2ad
32 changed files with 2207 additions and 36 deletions

View File

@@ -1,287 +0,0 @@
"""
Unit tests for Issue domain models.
Tests pure business logic with no external dependencies.
"""
import pytest
from datetime import datetime, timedelta, timezone
from domain.issues.models import Issue, Label, IssueState, LabelCategories
from domain.issues.exceptions import IssueStateError
class TestLabel:
"""Test Label value object."""
def test_label_creation(self):
# Arrange & Act
label = Label(name="bug", color="#ff0000", description="Bug label")
# Assert
assert label.name == "bug"
assert label.color == "#ff0000"
assert label.description == "Bug label"
def test_is_state_label(self):
# Arrange
state_label = Label("status:in-progress")
regular_label = Label("bug")
# Act & Assert
assert state_label.is_state_label() is True
assert regular_label.is_state_label() is False
def test_is_priority_label(self):
# Arrange
priority_label = Label("priority:high")
regular_label = Label("bug")
# Act & Assert
assert priority_label.is_priority_label() is True
assert regular_label.is_priority_label() is False
def test_is_type_label(self):
# Arrange
type_label = Label("bug")
priority_label = Label("priority:high")
# Act & Assert
assert type_label.is_type_label() is True
assert priority_label.is_type_label() is False
@pytest.mark.parametrize("label_name,expected", [
("bug", True),
("enhancement", True),
("feature", True),
("documentation", True),
("custom-label", False),
("priority:high", False)
])
def test_type_label_recognition(self, label_name, expected):
# Arrange
label = Label(label_name)
# Act & Assert
assert label.is_type_label() == expected
class TestIssue:
"""Test Issue aggregate root."""
def test_issue_creation_with_valid_data(self):
# Arrange
created_at = datetime.now(timezone.utc)
updated_at = datetime.now(timezone.utc)
labels = [Label("bug"), Label("priority:high")]
# Act
issue = Issue(
number=123,
title="Test Issue",
state=IssueState.OPEN,
labels=labels,
created_at=created_at,
updated_at=updated_at
)
# Assert
assert issue.number == 123
assert issue.title == "Test Issue"
assert issue.state == IssueState.OPEN
assert len(issue.labels) == 2
assert issue.created_at == created_at
assert issue.updated_at == updated_at
def test_categorize_labels_correctly_separates_types(self):
# Arrange
labels = [
Label("bug"), # type label
Label("priority:high"), # priority label
Label("status:in-progress"), # state label
Label("documentation"), # type label
Label("custom-label") # other label
]
issue = Issue(
number=1,
title="Test",
state=IssueState.OPEN,
labels=labels,
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
# Act
categories = issue.categorize_labels()
# Assert
assert "bug" in categories.type_labels
assert "documentation" in categories.type_labels
assert "priority:high" in categories.priority_labels
assert "status:in-progress" in categories.state_labels
assert "custom-label" in categories.other_labels
def test_close_issue_changes_state_and_sets_closed_at(self):
# Arrange
issue = Issue(
number=1,
title="Test",
state=IssueState.OPEN,
labels=[],
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
# Act
issue.close()
# Assert
assert issue.state == IssueState.CLOSED
assert issue.closed_at is not None
assert isinstance(issue.closed_at, datetime)
def test_close_already_closed_issue_raises_error(self):
# Arrange
issue = Issue(
number=1,
title="Test",
state=IssueState.CLOSED,
labels=[],
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
closed_at=datetime.now(timezone.utc)
)
# Act & Assert
with pytest.raises(IssueStateError) as exc_info:
issue.close()
assert "Issue is already closed" in str(exc_info.value)
assert exc_info.value.current_state == "closed"
assert exc_info.value.attempted_state == "closed"
def test_reopen_closed_issue_changes_state_and_clears_closed_at(self):
# Arrange
issue = Issue(
number=1,
title="Test",
state=IssueState.CLOSED,
labels=[],
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
closed_at=datetime.now(timezone.utc)
)
# Act
issue.reopen()
# Assert
assert issue.state == IssueState.OPEN
assert issue.closed_at is None
def test_reopen_open_issue_raises_error(self):
# Arrange
issue = Issue(
number=1,
title="Test",
state=IssueState.OPEN,
labels=[],
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
# Act & Assert
with pytest.raises(IssueStateError) as exc_info:
issue.reopen()
assert "Issue is not closed" in str(exc_info.value)
def test_add_label_to_issue(self):
# Arrange
issue = Issue(
number=1,
title="Test",
state=IssueState.OPEN,
labels=[Label("bug")],
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
new_label = Label("priority:high")
# Act
issue.add_label(new_label)
# Assert
assert len(issue.labels) == 2
assert new_label in issue.labels
def test_add_duplicate_label_does_not_duplicate(self):
# Arrange
label = Label("bug")
issue = Issue(
number=1,
title="Test",
state=IssueState.OPEN,
labels=[label],
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
# Act
issue.add_label(label)
# Assert
assert len(issue.labels) == 1
def test_remove_label_from_issue(self):
# Arrange
issue = Issue(
number=1,
title="Test",
state=IssueState.OPEN,
labels=[Label("bug"), Label("priority:high")],
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
# Act
issue.remove_label("bug")
# Assert
assert len(issue.labels) == 1
assert not any(label.name == "bug" for label in issue.labels)
def test_has_label_returns_correct_value(self):
# Arrange
issue = Issue(
number=1,
title="Test",
state=IssueState.OPEN,
labels=[Label("bug"), Label("priority:high")],
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
# Act & Assert
assert issue.has_label("bug") is True
assert issue.has_label("priority:high") is True
assert issue.has_label("enhancement") is False
class TestLabelCategories:
"""Test LabelCategories value object."""
def test_label_categories_creation(self):
# Arrange & Act
categories = LabelCategories(
state_labels=["status:open"],
priority_labels=["priority:high"],
type_labels=["bug"],
other_labels=["custom"]
)
# Assert
assert categories.state_labels == ["status:open"]
assert categories.priority_labels == ["priority:high"]
assert categories.type_labels == ["bug"]
assert categories.other_labels == ["custom"]

View File

@@ -1,368 +0,0 @@
"""
Unit tests for Issue domain services.
Tests business logic in issue services with no external dependencies.
"""
import pytest
from datetime import datetime, timedelta, timezone
from domain.issues.models import Issue, Label, IssueState
from domain.issues.services import IssueStatusService, IssueValidationService
from domain.issues.exceptions import IssueValidationError
class TestIssueStatusService:
"""Test business logic in issue status service."""
@pytest.fixture
def service(self):
return IssueStatusService()
def test_determine_kanban_column_for_closed_issue(self, service):
# Arrange
issue = Issue(
number=1,
title="Closed Issue",
state=IssueState.CLOSED,
labels=[],
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
project_info = {"kanban_columns": ["Todo", "In Progress", "Review", "Done"]}
# Act
column = service.determine_kanban_column(issue, project_info)
# Assert
assert column == "Done"
@pytest.mark.parametrize("status_label,expected_column", [
("status:in-progress", "In Progress"),
("status:review", "Review"),
("status:blocked", "Blocked"),
("status:ready", "Ready"),
])
def test_determine_kanban_column_based_on_status_labels(self, service, status_label, expected_column):
# Arrange
issue = Issue(
number=1,
title="Test Issue",
state=IssueState.OPEN,
labels=[Label(status_label)],
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
project_info = {"kanban_columns": ["Todo", "In Progress", "Review", "Blocked", "Ready", "Done"]}
# Act
column = service.determine_kanban_column(issue, project_info)
# Assert
assert column == expected_column
def test_determine_kanban_column_defaults_to_todo(self, service):
# Arrange
issue = Issue(
number=1,
title="New Issue",
state=IssueState.OPEN,
labels=[Label("bug")], # No status label
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
project_info = {"kanban_columns": ["Todo", "In Progress", "Done"]}
# Act
column = service.determine_kanban_column(issue, project_info)
# Assert
assert column == "Todo"
@pytest.mark.parametrize("priority_label,expected_level", [
("priority:low", "Low"),
("priority:medium", "Medium"),
("priority:high", "High"),
("priority:critical", "Critical"),
])
def test_extract_priority_info_with_priority_labels(self, service, priority_label, expected_level):
# Arrange
issue = Issue(
number=1,
title="Test",
state=IssueState.OPEN,
labels=[Label(priority_label)],
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
# Act
priority_info = service.extract_priority_info(issue)
# Assert
assert priority_info["level"] == expected_level
assert priority_info["label"] == priority_label
def test_extract_priority_info_defaults_to_medium(self, service):
# Arrange
issue = Issue(
number=1,
title="Test",
state=IssueState.OPEN,
labels=[Label("bug")], # No priority label
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
# Act
priority_info = service.extract_priority_info(issue)
# Assert
assert priority_info["level"] == "Medium"
assert priority_info["label"] is None
def test_extract_state_info_for_open_issue(self, service):
# Arrange
issue = Issue(
number=1,
title="Test",
state=IssueState.OPEN,
labels=[Label("status:in-progress")],
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
# Act
state_info = service.extract_state_info(issue)
# Assert
assert state_info["state"] == "open"
assert state_info["state_labels"] == ["status:in-progress"]
assert state_info["is_closed"] is False
assert state_info["closed_at"] is None
def test_extract_state_info_for_closed_issue(self, service):
# Arrange
closed_at = datetime.now(timezone.utc)
issue = Issue(
number=1,
title="Test",
state=IssueState.CLOSED,
labels=[],
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc),
closed_at=closed_at
)
# Act
state_info = service.extract_state_info(issue)
# Assert
assert state_info["state"] == "closed"
assert state_info["is_closed"] is True
assert state_info["closed_at"] == closed_at.isoformat()
def test_calculate_issue_age_days(self, service):
# Arrange
created_at = datetime.now(timezone.utc) - timedelta(days=5)
issue = Issue(
number=1,
title="Test",
state=IssueState.OPEN,
labels=[],
created_at=created_at,
updated_at=datetime.now(timezone.utc)
)
# Act
age_days = service.calculate_issue_age_days(issue)
# Assert
assert age_days == 5
def test_is_stale_issue_with_old_open_issue(self, service):
# Arrange
created_at = datetime.now(timezone.utc) - timedelta(days=45)
issue = Issue(
number=1,
title="Test",
state=IssueState.OPEN,
labels=[],
created_at=created_at,
updated_at=datetime.now(timezone.utc)
)
# Act
is_stale = service.is_stale_issue(issue, stale_threshold_days=30)
# Assert
assert is_stale is True
def test_is_stale_issue_with_recent_open_issue(self, service):
# Arrange
created_at = datetime.now(timezone.utc) - timedelta(days=15)
issue = Issue(
number=1,
title="Test",
state=IssueState.OPEN,
labels=[],
created_at=created_at,
updated_at=datetime.now(timezone.utc)
)
# Act
is_stale = service.is_stale_issue(issue, stale_threshold_days=30)
# Assert
assert is_stale is False
def test_is_stale_issue_with_closed_issue_never_stale(self, service):
# Arrange
created_at = datetime.now(timezone.utc) - timedelta(days=100)
issue = Issue(
number=1,
title="Test",
state=IssueState.CLOSED,
labels=[],
created_at=created_at,
updated_at=datetime.now(timezone.utc),
closed_at=datetime.now(timezone.utc)
)
# Act
is_stale = service.is_stale_issue(issue, stale_threshold_days=30)
# Assert
assert is_stale is False
class TestIssueValidationService:
"""Test business logic in issue validation service."""
@pytest.fixture
def service(self):
return IssueValidationService()
def test_validate_issue_creation_with_valid_data(self, service):
# Arrange
title = "Valid Issue Title"
labels = ["bug", "priority:high"]
# Act & Assert - Should not raise exception
service.validate_issue_creation(title, labels)
def test_validate_issue_creation_with_empty_title_raises_error(self, service):
# Arrange
title = ""
labels = ["bug"]
# Act & Assert
with pytest.raises(IssueValidationError) as exc_info:
service.validate_issue_creation(title, labels)
assert "Issue title cannot be empty" in str(exc_info.value)
assert exc_info.value.field == "title"
def test_validate_issue_creation_with_whitespace_only_title_raises_error(self, service):
# Arrange
title = " "
labels = ["bug"]
# Act & Assert
with pytest.raises(IssueValidationError) as exc_info:
service.validate_issue_creation(title, labels)
assert "Issue title cannot be empty" in str(exc_info.value)
def test_validate_issue_creation_with_too_long_title_raises_error(self, service):
# Arrange
title = "x" * 256 # Too long
labels = ["bug"]
# Act & Assert
with pytest.raises(IssueValidationError) as exc_info:
service.validate_issue_creation(title, labels)
assert "Issue title cannot exceed 255 characters" in str(exc_info.value)
def test_validate_issue_creation_with_multiple_priority_labels_raises_error(self, service):
# Arrange
title = "Valid Title"
labels = ["bug", "priority:high", "priority:low"]
# Act & Assert
with pytest.raises(IssueValidationError) as exc_info:
service.validate_issue_creation(title, labels)
assert "Issue cannot have multiple priority labels" in str(exc_info.value)
assert exc_info.value.field == "labels"
def test_validate_issue_creation_with_multiple_state_labels_raises_error(self, service):
# Arrange
title = "Valid Title"
labels = ["bug", "status:open", "status:in-progress"]
# Act & Assert
with pytest.raises(IssueValidationError) as exc_info:
service.validate_issue_creation(title, labels)
assert "Issue cannot have multiple state labels" in str(exc_info.value)
def test_validate_title_update_with_valid_title(self, service):
# Arrange
new_title = "Updated Title"
# Act & Assert - Should not raise exception
service.validate_title_update(new_title)
def test_validate_label_addition_to_issue_without_conflicts(self, service):
# Arrange
issue = Issue(
number=1,
title="Test",
state=IssueState.OPEN,
labels=[Label("bug")],
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
new_label = "enhancement"
# Act & Assert - Should not raise exception
service.validate_label_addition(issue, new_label)
def test_validate_label_addition_with_duplicate_label_raises_error(self, service):
# Arrange
issue = Issue(
number=1,
title="Test",
state=IssueState.OPEN,
labels=[Label("bug")],
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
new_label = "bug"
# Act & Assert
with pytest.raises(IssueValidationError) as exc_info:
service.validate_label_addition(issue, new_label)
assert "Issue already has label 'bug'" in str(exc_info.value)
def test_validate_label_addition_with_conflicting_priority_raises_error(self, service):
# Arrange
issue = Issue(
number=1,
title="Test",
state=IssueState.OPEN,
labels=[Label("priority:high")],
created_at=datetime.now(timezone.utc),
updated_at=datetime.now(timezone.utc)
)
new_label = "priority:low"
# Act & Assert
with pytest.raises(IssueValidationError) as exc_info:
service.validate_label_addition(issue, new_label)
assert "Issue already has priority label" in str(exc_info.value)
assert "Cannot add 'priority:low'" in str(exc_info.value)

View File

@@ -1,607 +0,0 @@
"""
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

View File

@@ -1,287 +0,0 @@
"""
Tests to validate the testing infrastructure works correctly.
Demonstrates:
- Test fixtures functionality
- Mock factories usage
- Test builders patterns
- Custom assertions
"""
import pytest
from pathlib import Path
from datetime import datetime, timezone
from tests.fixtures.markdown_samples import MarkdownDocumentBuilder, SAMPLE_SIMPLE_DOCUMENT
from tests.fixtures.api_responses import GiteaApiResponseBuilder, SAMPLE_ISSUE_RESPONSE
from tests.utils.test_builders import IssueBuilder, LabelBuilder, create_sample_issue
from tests.utils.mock_factories import MockRepositoryFactory, MockConfigFactory
from tests.utils.assertions import (
assert_issue_equal, assert_file_exists, assert_directory_exists,
assert_performance_within_bounds, validate_issue_data
)
class TestTestingInfrastructure:
"""Test that the testing infrastructure components work correctly."""
def test_test_workspace_fixture(self, test_workspace):
"""Test that test workspace fixture creates proper isolated environment."""
# Assert
assert_directory_exists(test_workspace)
assert_directory_exists(test_workspace / "documents")
assert_directory_exists(test_workspace / "cache")
assert_directory_exists(test_workspace / "workspaces")
# Test creating files in workspace
test_file = test_workspace / "test_file.txt"
test_file.write_text("test content")
assert_file_exists(test_file)
def test_markdown_document_builder(self):
"""Test markdown document builder functionality."""
# Act
doc = (MarkdownDocumentBuilder()
.with_metadata("title", "Test Doc")
.with_metadata("author", "Test Author")
.with_heading("Main Title")
.with_paragraph("This is a test paragraph.")
.with_list(["Item 1", "Item 2", "Item 3"])
.with_code_block("print('hello')", "python")
.build())
# Assert
assert "title: Test Doc" in doc
assert "author: Test Author" in doc
assert "# Main Title" in doc
assert "This is a test paragraph." in doc
assert "- Item 1" in doc
assert "```python" in doc
assert "print('hello')" in doc
def test_gitea_api_response_builder(self):
"""Test Gitea API response builder functionality."""
# Act
response = (GiteaApiResponseBuilder()
.with_number(42)
.with_title("Test Issue")
.with_labels("bug", "priority:high")
.with_milestone("Version 1.0")
.build())
# Assert
assert response["number"] == 42
assert response["title"] == "Test Issue"
assert len(response["labels"]) == 2
assert response["labels"][0]["name"] == "bug"
assert response["labels"][1]["name"] == "priority:high"
assert response["milestone"]["title"] == "Version 1.0"
def test_issue_builder_functionality(self):
"""Test issue builder creates proper domain objects."""
# Act
issue = (IssueBuilder()
.with_number(123)
.with_title("Test Issue")
.as_bug()
.with_priority("high")
.with_status("in-progress")
.build())
# Assert
assert issue.number == 123
assert issue.title == "Test Issue"
assert len(issue.labels) == 3
# Check label categorization
categories = issue.categorize_labels()
assert "bug" in categories.type_labels
assert "priority:high" in categories.priority_labels
assert "status:in-progress" in categories.state_labels
def test_label_builder_functionality(self):
"""Test label builder creates correct labels."""
# Act
state_label = LabelBuilder().as_state_label("blocked").build()
priority_label = LabelBuilder().as_priority_label("critical").build()
type_label = LabelBuilder().as_type_label("bug").build()
custom_label = LabelBuilder().with_custom_name("frontend").build()
# Assert
assert state_label.name == "status:blocked"
assert state_label.is_state_label()
assert priority_label.name == "priority:critical"
assert priority_label.is_priority_label()
assert type_label.name == "bug"
assert type_label.is_type_label()
assert custom_label.name == "frontend"
assert not custom_label.is_state_label()
assert not custom_label.is_priority_label()
assert not custom_label.is_type_label()
def test_mock_repository_factory(self):
"""Test mock repository factory creates proper mocks."""
# Act
issue_repo = MockRepositoryFactory.create_issue_repository()
project_repo = MockRepositoryFactory.create_project_repository()
document_repo = MockRepositoryFactory.create_document_repository()
# Assert
assert hasattr(issue_repo, 'get_issue')
assert hasattr(issue_repo, 'create_issue')
assert hasattr(issue_repo, 'update_issue')
assert hasattr(issue_repo, 'list_issues')
assert hasattr(project_repo, 'get_project')
assert hasattr(project_repo, 'get_issue_project_info')
assert hasattr(document_repo, 'store_document')
assert hasattr(document_repo, 'search_content')
def test_mock_config_factory(self):
"""Test mock configuration factory."""
# Act
config = MockConfigFactory.create_test_config({
"custom_setting": "test_value",
"workspace_dir": "/custom/workspace"
})
# Assert
assert config.workspace_dir == "/custom/workspace"
assert config.custom_setting == "test_value"
assert config.gitea_url == "http://test-gitea.com" # Default value
assert config.log_level == "DEBUG" # Default value
def test_custom_assertions(self):
"""Test custom assertion functions."""
# Arrange
issue1 = create_sample_issue(1, "Test Issue")
issue2 = create_sample_issue(1, "Test Issue")
# Act & Assert - Should not raise
assert_issue_equal(issue1, issue2, ignore_timestamps=True)
# Test performance assertion
execution_time = 0.05 # 50ms
assert_performance_within_bounds(execution_time, 0.1, "test operation")
# Test data validation
valid_issue_data = {
"number": 1,
"title": "Valid Issue",
"state": "open",
"labels": [],
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
assert validate_issue_data(valid_issue_data) is True
invalid_issue_data = {
"number": -1, # Invalid number
"title": "", # Empty title
"state": "invalid_state", # Invalid state
}
assert validate_issue_data(invalid_issue_data) is False
def test_sample_constants(self):
"""Test that sample constants are properly formed."""
# Test markdown samples
assert len(SAMPLE_SIMPLE_DOCUMENT) > 0
assert "# Simple Document" in SAMPLE_SIMPLE_DOCUMENT
# Test API response samples
assert SAMPLE_ISSUE_RESPONSE["number"] == 123
assert SAMPLE_ISSUE_RESPONSE["title"] == "Sample Issue"
assert len(SAMPLE_ISSUE_RESPONSE["labels"]) > 0
def test_performance_timer_fixture(self, performance_timer):
"""Test performance timer fixture functionality."""
# Act
performance_timer.start()
# Simulate some work
import time
time.sleep(0.01) # 10ms
performance_timer.stop()
# Assert
assert performance_timer.elapsed > 0
assert performance_timer.elapsed < 0.1 # Should be much less than 100ms
def test_test_config_fixture(self, test_config):
"""Test test configuration fixture."""
# Assert
assert "workspace_dir" in test_config
assert "database_path" in test_config
assert "gitea_url" in test_config
assert test_config["gitea_url"] == "http://test-gitea.com"
def test_sample_markdown_content_fixture(self, sample_markdown_content):
"""Test sample markdown content fixture."""
# Assert
assert "Test Document" in sample_markdown_content
assert "title:" in sample_markdown_content # Front matter
assert "**bold**" in sample_markdown_content
assert "```python" in sample_markdown_content
def test_sample_issue_data_fixture(self, sample_issue_data):
"""Test sample issue data fixture."""
# Assert
assert sample_issue_data["number"] == 123
assert sample_issue_data["title"] == "Test Issue"
assert sample_issue_data["state"] == "open"
assert len(sample_issue_data["labels"]) > 0
def test_isolated_environment_fixture(self, isolated_environment):
"""Test isolated environment fixture."""
# Assert
assert "MARKITECT_WORKSPACE_DIR" in isolated_environment
assert "MARKITECT_GITEA_URL" in isolated_environment
assert isolated_environment["MARKITECT_GITEA_URL"] == "http://test-gitea.com"
@pytest.mark.parametrize("priority,expected", [
("low", "Low"),
("medium", "Medium"),
("high", "High"),
("critical", "Critical")
])
def test_parametrized_testing_works(self, priority, expected):
"""Test that parametrized testing works with our infrastructure."""
# Act
issue = (IssueBuilder()
.with_number(1)
.with_title("Priority Test")
.with_priority(priority)
.build())
# Assert
categories = issue.categorize_labels()
priority_labels = categories.priority_labels
assert len(priority_labels) == 1
assert priority_labels[0] == f"priority:{priority}"
def test_test_markers_work(self):
"""Test that our custom test markers work."""
# This test validates that the marker configuration is working
# The markers are defined in pytest.ini and should not cause warnings
pass
@pytest.mark.unit
def test_unit_marker_works(self):
"""Test that unit marker works."""
assert True
@pytest.mark.performance
def test_performance_marker_works(self, performance_timer):
"""Test that performance marker works."""
performance_timer.start()
# Simulate quick operation
result = sum(range(100))
performance_timer.stop()
assert result == 4950 # Mathematical verification
assert performance_timer.elapsed < 0.01 # Should be very fast