Files
markitect-main/tests/utils/test_builders.py
tegwick 21a5d1d734
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
feat: Implement comprehensive Testing Architecture Enhancement
Establishes robust testing framework with clean architecture patterns:

## Phase 1: Test Infrastructure Foundation
- Global test configuration with pytest.ini and conftest.py
- Isolated test workspaces and environment management
- Comprehensive fixture library for all test types
- Test requirements and dependency management

## Phase 2: Advanced Testing Patterns
- Test builders using builder pattern for domain objects
- Mock factories for repositories, services, and configs
- API response builders for external system simulation
- Enhanced unit tests with proper mocking and isolation

## Phase 3: Test Performance and Quality
- Performance testing framework with benchmarks
- Memory usage monitoring and leak detection
- Custom assertions for domain-specific validation
- Parametrized testing for comprehensive coverage

## Phase 4: CI/CD Integration
- GitHub Actions workflow for automated testing
- Multi-stage testing: unit → integration → e2e → performance
- Code quality checks with flake8, mypy, black, isort
- Security scanning with safety and bandit

## Testing Architecture Benefits
 100+ new test infrastructure components
 Standardized test organization (unit/integration/e2e)
 Mock-based testing with no external dependencies
 Performance regression detection
 Comprehensive fixture library
 CI/CD pipeline with quality gates

The testing framework supports the domain logic separation and provides
a solid foundation for maintaining high code quality as the system evolves.

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-26 22:36:35 +02:00

338 lines
11 KiB
Python

"""
Test data builders using the builder pattern for creating domain objects.
"""
from datetime import datetime, timezone
from typing import List, Optional, Dict, Any
from domain.issues.models import Issue, Label, IssueState, LabelCategories
from domain.projects.models import Project, Milestone, ProjectState
class IssueBuilder:
"""Builder for creating Issue domain objects for testing."""
def __init__(self):
self.number = 1
self.title = "Test Issue"
self.state = IssueState.OPEN
self.labels: List[Label] = []
self.created_at = datetime.now(timezone.utc)
self.updated_at = datetime.now(timezone.utc)
self.closed_at: Optional[datetime] = None
def with_number(self, number: int) -> "IssueBuilder":
"""Set issue number."""
self.number = number
return self
def with_title(self, title: str) -> "IssueBuilder":
"""Set issue title."""
self.title = title
return self
def with_state(self, state: IssueState) -> "IssueBuilder":
"""Set issue state."""
self.state = state
if state == IssueState.CLOSED and self.closed_at is None:
self.closed_at = datetime.now(timezone.utc)
return self
def with_labels(self, *label_names: str) -> "IssueBuilder":
"""Add labels to the issue."""
self.labels = [Label(name) for name in label_names]
return self
def with_label_objects(self, *labels: Label) -> "IssueBuilder":
"""Add label objects to the issue."""
self.labels = list(labels)
return self
def with_timestamps(self, created_at: datetime, updated_at: datetime, closed_at: Optional[datetime] = None) -> "IssueBuilder":
"""Set issue timestamps."""
self.created_at = created_at
self.updated_at = updated_at
self.closed_at = closed_at
return self
def as_closed(self, closed_at: Optional[datetime] = None) -> "IssueBuilder":
"""Mark issue as closed."""
self.state = IssueState.CLOSED
self.closed_at = closed_at or datetime.now(timezone.utc)
return self
def as_bug(self) -> "IssueBuilder":
"""Add bug label."""
self.labels.append(Label("bug"))
return self
def as_enhancement(self) -> "IssueBuilder":
"""Add enhancement label."""
self.labels.append(Label("enhancement"))
return self
def with_priority(self, priority: str) -> "IssueBuilder":
"""Add priority label."""
if priority not in ["low", "medium", "high", "critical"]:
raise ValueError("Priority must be one of: low, medium, high, critical")
self.labels.append(Label(f"priority:{priority}"))
return self
def with_status(self, status: str) -> "IssueBuilder":
"""Add status label."""
self.labels.append(Label(f"status:{status}"))
return self
def build(self) -> Issue:
"""Build the Issue object."""
return Issue(
number=self.number,
title=self.title,
state=self.state,
labels=self.labels,
created_at=self.created_at,
updated_at=self.updated_at,
closed_at=self.closed_at
)
class LabelBuilder:
"""Builder for creating Label objects for testing."""
def __init__(self, name: str = "test-label"):
self.name = name
def as_state_label(self, status: str) -> "LabelBuilder":
"""Create a state label."""
self.name = f"status:{status}"
return self
def as_priority_label(self, priority: str) -> "LabelBuilder":
"""Create a priority label."""
if priority not in ["low", "medium", "high", "critical"]:
raise ValueError("Priority must be one of: low, medium, high, critical")
self.name = f"priority:{priority}"
return self
def as_type_label(self, type_name: str) -> "LabelBuilder":
"""Create a type label."""
if type_name not in ["bug", "enhancement", "feature", "documentation"]:
raise ValueError("Type must be one of: bug, enhancement, feature, documentation")
self.name = type_name
return self
def with_custom_name(self, name: str) -> "LabelBuilder":
"""Set custom label name."""
self.name = name
return self
def build(self) -> Label:
"""Build the Label object."""
return Label(self.name)
class MilestoneBuilder:
"""Builder for creating Milestone objects for testing."""
def __init__(self):
self.id = 1
self.title = "Test Milestone"
self.description: Optional[str] = None
self.due_date: Optional[datetime] = None
self.state = "open"
self.open_issues = 0
self.closed_issues = 0
def with_id(self, id: int) -> "MilestoneBuilder":
"""Set milestone ID."""
self.id = id
return self
def with_title(self, title: str) -> "MilestoneBuilder":
"""Set milestone title."""
self.title = title
return self
def with_description(self, description: str) -> "MilestoneBuilder":
"""Set milestone description."""
self.description = description
return self
def with_due_date(self, due_date: datetime) -> "MilestoneBuilder":
"""Set milestone due date."""
self.due_date = due_date
return self
def with_state(self, state: str) -> "MilestoneBuilder":
"""Set milestone state."""
if state not in ["open", "closed"]:
raise ValueError("State must be 'open' or 'closed'")
self.state = state
return self
def with_issue_counts(self, open_issues: int, closed_issues: int) -> "MilestoneBuilder":
"""Set issue counts."""
self.open_issues = open_issues
self.closed_issues = closed_issues
return self
def as_overdue(self) -> "MilestoneBuilder":
"""Make milestone overdue."""
from datetime import timedelta
self.due_date = datetime.now(timezone.utc) - timedelta(days=1)
return self
def as_completed(self) -> "MilestoneBuilder":
"""Mark milestone as completed."""
self.state = "closed"
return self
def build(self) -> Milestone:
"""Build the Milestone object."""
return Milestone(
id=self.id,
title=self.title,
description=self.description,
due_date=self.due_date,
state=self.state,
open_issues=self.open_issues,
closed_issues=self.closed_issues
)
class ProjectBuilder:
"""Builder for creating Project objects for testing."""
def __init__(self):
self.name = "Test Project"
self.description: Optional[str] = None
self.state = ProjectState.ACTIVE
self.milestones: List[Milestone] = []
self.kanban_columns = ["Todo", "In Progress", "Done"]
self.created_at = datetime.now(timezone.utc)
self.updated_at = datetime.now(timezone.utc)
self.archived_at: Optional[datetime] = None
def with_name(self, name: str) -> "ProjectBuilder":
"""Set project name."""
self.name = name
return self
def with_description(self, description: str) -> "ProjectBuilder":
"""Set project description."""
self.description = description
return self
def with_state(self, state: ProjectState) -> "ProjectBuilder":
"""Set project state."""
self.state = state
if state == ProjectState.ARCHIVED and self.archived_at is None:
self.archived_at = datetime.now(timezone.utc)
return self
def with_milestones(self, *milestones: Milestone) -> "ProjectBuilder":
"""Add milestones to the project."""
self.milestones = list(milestones)
return self
def with_kanban_columns(self, *columns: str) -> "ProjectBuilder":
"""Set kanban columns."""
self.kanban_columns = list(columns)
return self
def with_timestamps(self, created_at: datetime, updated_at: datetime, archived_at: Optional[datetime] = None) -> "ProjectBuilder":
"""Set project timestamps."""
self.created_at = created_at
self.updated_at = updated_at
self.archived_at = archived_at
return self
def as_archived(self, archived_at: Optional[datetime] = None) -> "ProjectBuilder":
"""Mark project as archived."""
self.state = ProjectState.ARCHIVED
self.archived_at = archived_at or datetime.now(timezone.utc)
return self
def build(self) -> Project:
"""Build the Project object."""
return Project(
name=self.name,
description=self.description,
state=self.state,
milestones=self.milestones,
kanban_columns=self.kanban_columns,
created_at=self.created_at,
updated_at=self.updated_at,
archived_at=self.archived_at
)
# Convenience functions for common test scenarios
def create_sample_issue(number: int = 1, title: str = "Sample Issue") -> Issue:
"""Create a basic sample issue for testing."""
return (IssueBuilder()
.with_number(number)
.with_title(title)
.as_bug()
.with_priority("medium")
.with_status("new")
.build())
def create_in_progress_issue(number: int = 1) -> Issue:
"""Create an in-progress issue for testing."""
return (IssueBuilder()
.with_number(number)
.with_title("In Progress Issue")
.as_enhancement()
.with_priority("high")
.with_status("in-progress")
.build())
def create_closed_issue(number: int = 1) -> Issue:
"""Create a closed issue for testing."""
return (IssueBuilder()
.with_number(number)
.with_title("Closed Issue")
.as_bug()
.with_priority("low")
.as_closed()
.build())
def create_sample_milestone(id: int = 1, title: str = "Sample Milestone") -> Milestone:
"""Create a basic sample milestone for testing."""
return (MilestoneBuilder()
.with_id(id)
.with_title(title)
.with_description(f"Description for {title}")
.with_issue_counts(3, 7)
.build())
def create_sample_project(name: str = "Sample Project") -> Project:
"""Create a basic sample project for testing."""
milestone1 = create_sample_milestone(1, "Version 1.0")
milestone2 = create_sample_milestone(2, "Version 2.0")
return (ProjectBuilder()
.with_name(name)
.with_description(f"Description for {name}")
.with_milestones(milestone1, milestone2)
.build())
def create_complex_issue_with_labels() -> Issue:
"""Create an issue with various types of labels for testing categorization."""
return (IssueBuilder()
.with_number(42)
.with_title("Complex Issue with Multiple Labels")
.with_labels(
"bug", # type label
"priority:critical", # priority label
"status:blocked", # state label
"frontend", # other label
"needs-testing", # other label
"enhancement" # type label (multiple types allowed)
)
.build())