"""Test data factory for Behave tests using Faker for realistic data generation. This module provides factory functions and classes for generating realistic test data for Behave scenarios. It uses Faker to generate dynamic, varied test data instead of hardcoded values, improving test realism and maintainability. Usage: from features.test_data_factory import ContextFragmentFactory, TestDataGenerator # Generate a single context fragment with realistic data fragment = ContextFragmentFactory.create() # Generate multiple fragments with varied data fragments = ContextFragmentFactory.create_batch(5) # Generate realistic project names project_name = TestDataGenerator.project_name() # Generate realistic Python code snippets code = TestDataGenerator.python_code() """ from __future__ import annotations import sys from pathlib import Path from typing import Any from faker import Faker # Add src to path for imports _SRC = str(Path(__file__).resolve().parents[1] / "src") if _SRC not in sys.path: sys.path.insert(0, _SRC) from cleveragents.domain.models.core.context_fragment import ( # noqa: E402 ContextBudget, ContextFragment, FragmentProvenance, ) # Initialize Faker for consistent, reproducible test data _faker = Faker() class TestDataGenerator: """Generate realistic test data for various domain objects.""" @staticmethod def project_name() -> str: """Generate a realistic project name.""" return _faker.word() + "-" + _faker.word() @staticmethod def skill_name() -> str: """Generate a realistic skill name.""" return _faker.word() + "_" + _faker.word() @staticmethod def tool_name() -> str: """Generate a realistic tool name.""" return _faker.word() + "-tool" @staticmethod def actor_name() -> str: """Generate a realistic actor name.""" return _faker.first_name() + _faker.last_name() @staticmethod def python_code() -> str: """Generate a realistic Python code snippet.""" var_name = _faker.word() value = _faker.random_int(min=1, max=1000) return f"{var_name} = {value}" @staticmethod def python_prose() -> str: """Generate realistic Python-like prose.""" return _faker.sentence(nb_words=10) @staticmethod def file_path() -> str: """Generate a realistic file path.""" return f"src/{_faker.word()}/{_faker.word()}.py" @staticmethod def uko_node_uri() -> str: """Generate a realistic UKO node URI.""" return f"p://{_faker.word()}/{_faker.word()}.py" @staticmethod def resource_uri() -> str: """Generate a realistic resource URI.""" return f"test://{_faker.word()}-{_faker.word()}" @staticmethod def namespaced_name() -> tuple[str, str]: """Generate a realistic namespaced name (namespace, name).""" namespace = _faker.word() name = _faker.word() + "-" + _faker.word() return (namespace, name) @staticmethod def ulid() -> str: """Generate a valid ULID-like string (26 chars, base32).""" # Generate a valid Crockford base32 ULID (26 characters) # Using only valid characters: 0-9, A-Z (excluding I, L, O, U) valid_chars = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" return "".join(_faker.random.choice(valid_chars) for _ in range(26)) @staticmethod def relevance_score() -> float: """Generate a realistic relevance score (0.0 to 1.0).""" return round(_faker.pyfloat(min_value=0.0, max_value=1.0, right_digits=2), 2) @staticmethod def token_count() -> int: """Generate a realistic token count.""" return _faker.random_int(min=10, max=500) @staticmethod def detail_depth() -> int: """Generate a realistic detail depth level.""" return _faker.random_int(min=1, max=10) class ContextFragmentFactory: """Factory for creating realistic ContextFragment test objects.""" @staticmethod def create(**overrides: Any) -> ContextFragment: """Create a single ContextFragment with realistic default data. Args: **overrides: Override any default values Returns: A ContextFragment instance with realistic data """ defaults = { "uko_node": TestDataGenerator.uko_node_uri(), "content": TestDataGenerator.python_code(), "token_count": TestDataGenerator.token_count(), "relevance_score": TestDataGenerator.relevance_score(), "detail_depth": TestDataGenerator.detail_depth(), "provenance": FragmentProvenance( resource_uri=TestDataGenerator.resource_uri() ), } defaults.update(overrides) return ContextFragment(**defaults) @staticmethod def create_batch(count: int, **overrides: Any) -> list[ContextFragment]: """Create multiple ContextFragments with varied realistic data. Args: count: Number of fragments to create **overrides: Override any default values (applied to all) Returns: A list of ContextFragment instances """ return [ContextFragmentFactory.create(**overrides) for _ in range(count)] @staticmethod def create_with_content(content: str, **overrides: Any) -> ContextFragment: """Create a ContextFragment with specific content. Args: content: The content to use **overrides: Override any other default values Returns: A ContextFragment instance with the specified content """ overrides["content"] = content return ContextFragmentFactory.create(**overrides) @staticmethod def create_duplicate_pair( content: str = "duplicate_content", **overrides: Any ) -> tuple[ContextFragment, ContextFragment]: """Create a pair of duplicate ContextFragments for deduplication testing. Args: content: The shared content **overrides: Override any other default values Returns: A tuple of two ContextFragments with identical content """ frag1 = ContextFragmentFactory.create_with_content(content, **overrides) frag2 = ContextFragmentFactory.create_with_content( content, uko_node=frag1.uko_node, relevance_score=round(frag1.relevance_score - 0.1, 2), **overrides, ) return (frag1, frag2) class ContextBudgetFactory: """Factory for creating realistic ContextBudget test objects.""" @staticmethod def create( max_tokens: int | None = None, reserved_tokens: int | None = None, ) -> ContextBudget: """Create a ContextBudget with realistic default data. Args: max_tokens: Maximum tokens (default: random 100-1000) reserved_tokens: Reserved tokens (default: 0) Returns: A ContextBudget instance """ if max_tokens is None: max_tokens = _faker.random_int(min=100, max=1000) if reserved_tokens is None: reserved_tokens = 0 return ContextBudget(max_tokens=max_tokens, reserved_tokens=reserved_tokens) @staticmethod def create_tight() -> ContextBudget: """Create a ContextBudget with tight constraints (100-200 tokens).""" max_tokens = _faker.random_int(min=100, max=200) return ContextBudgetFactory.create(max_tokens=max_tokens) @staticmethod def create_generous() -> ContextBudget: """Create a ContextBudget with generous constraints (500-2000 tokens).""" max_tokens = _faker.random_int(min=500, max=2000) return ContextBudgetFactory.create(max_tokens=max_tokens)