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
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>
338 lines
11 KiB
Python
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()) |