socialseed-e2e 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,24 @@
1
+ from typing import Any, Dict, Optional, Protocol, runtime_checkable
2
+
3
+ from pydantic import BaseModel, Field
4
+
5
+
6
+ class ServiceConfig(BaseModel):
7
+ """Configuration for a specific service."""
8
+
9
+ name: str
10
+ base_url: str
11
+ default_headers: Dict[str, str] = Field(default_factory=dict)
12
+ timeout: int = 30000
13
+ extra: Dict[str, Any] = Field(default_factory=dict)
14
+
15
+
16
+ class TestContext(BaseModel):
17
+ """Generic context for test execution."""
18
+
19
+ env: str = "dev"
20
+ services: Dict[str, ServiceConfig] = Field(default_factory=dict)
21
+ metadata: Dict[str, Any] = Field(default_factory=dict)
22
+
23
+ def get_service(self, name: str) -> Optional[ServiceConfig]:
24
+ return self.services.get(name)
@@ -0,0 +1,84 @@
1
+ import importlib.util
2
+ import os
3
+ import sys
4
+ from pathlib import Path
5
+ from typing import Any, Callable, Dict, List, Optional
6
+
7
+ try:
8
+ import pytest
9
+ except ImportError:
10
+ pytest = None
11
+
12
+
13
+ from socialseed_e2e.core.loaders import ModuleLoader
14
+
15
+
16
+ class TestOrchestrator:
17
+ """
18
+ Orchestrates dynamic loading and execution of test modules.
19
+ Auto-discovers modules in services/*/modules/ and runs them.
20
+ """
21
+
22
+ def __init__(
23
+ self, root_dir: Optional[str] = None, services_path: str = "verify_services/e2e/services"
24
+ ):
25
+ # Default to the directory containing the 'verify_services' folder or current working directory
26
+ self.root_dir = Path(root_dir) if root_dir else Path(os.getcwd())
27
+ self.services_dir = self.root_dir / services_path
28
+ self.loader = ModuleLoader()
29
+ self.modules: Dict[str, List[Callable]] = {}
30
+
31
+ def discover_modules(self):
32
+ """Discover all test modules in services directories using dynamic file loading."""
33
+ if not self.services_dir.exists():
34
+ print(f"Warning: Services directory not found at {self.services_dir}")
35
+ return
36
+
37
+ for service_dir in self.services_dir.iterdir():
38
+ if service_dir.is_dir():
39
+ modules_dir = service_dir / "modules"
40
+ if modules_dir.exists():
41
+ service_name = service_dir.name
42
+ self.modules[service_name] = self.loader.discover_runnables(modules_dir)
43
+
44
+ def run_service_tests(self, service_name: str, context: Any):
45
+ """Run all modules for a specific service."""
46
+ if service_name not in self.modules:
47
+ raise ValueError(
48
+ f"Service {service_name} not found or has no modules discovered at {self.services_dir}"
49
+ )
50
+ for run_func in self.modules[service_name]:
51
+ run_func(context)
52
+
53
+ def run_all_tests(self, context_factory: Callable[[], Any]):
54
+ """Run all discovered tests with a context factory."""
55
+ for service_name, modules in self.modules.items():
56
+ context = context_factory()
57
+ try:
58
+ for run_func in modules:
59
+ run_func(context)
60
+ print(f"✓ {service_name} tests passed")
61
+ except Exception as e:
62
+ print(f"✗ {service_name} tests failed: {e}")
63
+ raise
64
+ finally:
65
+ if hasattr(context, "teardown"):
66
+ context.teardown()
67
+
68
+
69
+ # Pytest integration
70
+ def pytest_configure(config):
71
+ """Hook to set up orchestrator for pytest."""
72
+ if pytest:
73
+ # Configuration can be passed via environment variables
74
+ root = os.getenv("E2E_ROOT_DIR")
75
+ spath = os.getenv("E2E_SERVICES_PATH", "verify_services/e2e/services")
76
+ config.orchestrator = TestOrchestrator(root_dir=root, services_path=spath)
77
+
78
+
79
+ def pytest_collection_modifyitems(config, items):
80
+ """Modify test collection to include dynamic modules."""
81
+ if pytest:
82
+ orchestrator = getattr(config, "orchestrator", None)
83
+ if orchestrator:
84
+ orchestrator.discover_modules()
@@ -0,0 +1,9 @@
1
+ # E2E Testing Framework - Services
2
+ # ==================================
3
+ #
4
+ # This package contains service-specific test implementations.
5
+ # Each service has its own directory with:
6
+ # - config.py: Service configuration
7
+ # - data_schema.py: Pydantic models
8
+ # - {service}_page.py: Page object
9
+ # - modules/: Test modules
@@ -0,0 +1,32 @@
1
+ """Templates for code scaffolding.
2
+
3
+ This package contains template files used by the TemplateEngine
4
+ to generate code files and project scaffolding.
5
+
6
+ Available Templates:
7
+ - e2e.conf.template: Default configuration file
8
+ - service_page.py.template: Service page class
9
+ - test_module.py.template: Test module structure
10
+ - data_schema.py.template: Data transfer objects
11
+ - config.py.template: Service configuration loader
12
+
13
+ Usage:
14
+ Templates are loaded and rendered by the TemplateEngine class
15
+ in socialseed_e2e.utils.template_engine.
16
+
17
+ Example:
18
+ from socialseed_e2e.utils import TemplateEngine
19
+
20
+ engine = TemplateEngine()
21
+ content = engine.render('service_page.py', {
22
+ 'service_name': 'users-api',
23
+ 'class_name': 'UsersApi'
24
+ })
25
+ """
26
+
27
+ from pathlib import Path
28
+
29
+ # Get the templates directory
30
+ TEMPLATES_DIR = Path(__file__).parent
31
+
32
+ __all__ = ["TEMPLATES_DIR"]
@@ -0,0 +1,20 @@
1
+ """Configuration loader for ${service_name}.
2
+
3
+ This module provides configuration loading specific to the ${service_name} service.
4
+ """
5
+
6
+ from socialseed_e2e.core.config_loader import ApiConfigLoader, ServiceConfig
7
+
8
+
9
+ def get_${snake_case_name}_config() -> ServiceConfig:
10
+ """Get configuration for ${service_name} service.
11
+
12
+ Returns:
13
+ ServiceConfig: Configuration object for the service
14
+
15
+ Raises:
16
+ ConfigError: If configuration cannot be loaded
17
+ """
18
+ loader = ApiConfigLoader()
19
+ config = loader.load()
20
+ return config.services["${service_name}"]
@@ -0,0 +1,116 @@
1
+ """Data schema and DTOs for ${service_name}.
2
+
3
+ This module defines data transfer objects, validators, and constants
4
+ for the ${service_name} service.
5
+ """
6
+
7
+ from typing import Optional, List
8
+ from pydantic import BaseModel, Field, validator
9
+
10
+
11
+ class ${class_name}DTO(BaseModel):
12
+ """Data Transfer Object for ${service_name} entities.
13
+
14
+ Attributes:
15
+ id: Unique identifier
16
+ name: Entity name
17
+ description: Entity description
18
+ created_at: Creation timestamp
19
+ updated_at: Last update timestamp
20
+ """
21
+
22
+ id: Optional[str] = Field(None, description="Unique identifier")
23
+ name: str = Field(..., description="Entity name", min_length=1, max_length=100)
24
+ description: Optional[str] = Field(None, description="Entity description", max_length=500)
25
+ created_at: Optional[str] = Field(None, description="Creation timestamp (ISO format)")
26
+ updated_at: Optional[str] = Field(None, description="Last update timestamp (ISO format)")
27
+
28
+ @validator('name')
29
+ def name_must_not_be_empty(cls, v):
30
+ """Validate that name is not empty or whitespace."""
31
+ if not v or not v.strip():
32
+ raise ValueError('Name cannot be empty')
33
+ return v.strip()
34
+
35
+ class Config:
36
+ """Pydantic configuration."""
37
+ json_schema_extra = {
38
+ "example": {
39
+ "id": "123e4567-e89b-12d3-a456-426614174000",
40
+ "name": "Example Entity",
41
+ "description": "This is an example entity",
42
+ "created_at": "2025-01-31T12:00:00Z",
43
+ "updated_at": "2025-01-31T12:00:00Z"
44
+ }
45
+ }
46
+
47
+
48
+ class ${class_name}CreateDTO(BaseModel):
49
+ """DTO for creating new ${service_name} entities.
50
+
51
+ Attributes:
52
+ name: Entity name (required)
53
+ description: Entity description (optional)
54
+ """
55
+
56
+ name: str = Field(..., description="Entity name", min_length=1, max_length=100)
57
+ description: Optional[str] = Field(None, description="Entity description", max_length=500)
58
+
59
+ @validator('name')
60
+ def name_must_not_be_empty(cls, v):
61
+ """Validate that name is not empty or whitespace."""
62
+ if not v or not v.strip():
63
+ raise ValueError('Name cannot be empty')
64
+ return v.strip()
65
+
66
+
67
+ class ${class_name}UpdateDTO(BaseModel):
68
+ """DTO for updating existing ${service_name} entities.
69
+
70
+ Attributes:
71
+ name: Entity name (optional)
72
+ description: Entity description (optional)
73
+ """
74
+
75
+ name: Optional[str] = Field(None, description="Entity name", min_length=1, max_length=100)
76
+ description: Optional[str] = Field(None, description="Entity description", max_length=500)
77
+
78
+ @validator('name')
79
+ def name_must_not_be_empty_if_provided(cls, v):
80
+ """Validate that name is not empty if provided."""
81
+ if v is not None and not v.strip():
82
+ raise ValueError('Name cannot be empty')
83
+ return v.strip() if v else v
84
+
85
+
86
+ class ${class_name}ListResponse(BaseModel):
87
+ """DTO for paginated list response.
88
+
89
+ Attributes:
90
+ items: List of entities
91
+ total: Total number of entities
92
+ page: Current page number
93
+ page_size: Number of items per page
94
+ """
95
+
96
+ items: List[${class_name}DTO] = Field(default_factory=list)
97
+ total: int = Field(0, ge=0)
98
+ page: int = Field(1, ge=1)
99
+ page_size: int = Field(10, ge=1, le=100)
100
+
101
+
102
+ # Constants for ${service_name} service
103
+ ${constants}
104
+
105
+ # Validation patterns
106
+ VALIDATION_PATTERNS = {
107
+ "uuid": r'^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$',
108
+ "iso_timestamp": r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-]\d{2}:\d{2})?$',
109
+ }
110
+
111
+ # Default values
112
+ DEFAULTS = {
113
+ "page_size": 10,
114
+ "timeout": 5000,
115
+ "max_retries": 3,
116
+ }
@@ -0,0 +1,20 @@
1
+ # Configuración E2E para socialseed-e2e
2
+ # Documentación: https://github.com/daironpf/socialseed-e2e
3
+
4
+ general:
5
+ # Entorno de ejecución (dev, staging, production)
6
+ environment: ${environment}
7
+
8
+ # Timeout global para requests en milisegundos
9
+ timeout: ${timeout}
10
+
11
+ # User agent para requests HTTP
12
+ user_agent: "${user_agent}"
13
+
14
+ # Modo verbose para debugging
15
+ verbose: ${verbose}
16
+
17
+ # Configuración de servicios
18
+ # Define cada servicio a testear
19
+ services:
20
+ ${services_config}
@@ -0,0 +1,83 @@
1
+ """Página de servicio para ${service_name}.
2
+
3
+ Este módulo define la clase de página para interactuar con el servicio ${service_name}.
4
+ """
5
+
6
+ from typing import Optional
7
+ from playwright.sync_api import APIResponse
8
+
9
+ from socialseed_e2e.core.base_page import BasePage
10
+
11
+
12
+ class ${class_name}Page(BasePage):
13
+ """Página de servicio para ${service_name}.
14
+
15
+ Gestiona el estado compartido entre módulos de test.
16
+
17
+ Attributes:
18
+ current_entity: Entidad actualmente seleccionada
19
+ auth_token: Token de autenticación
20
+ """
21
+
22
+ def __init__(self, playwright=None, base_url: Optional[str] = None):
23
+ """Inicializa la página de servicio.
24
+
25
+ Args:
26
+ playwright: Instancia de Playwright
27
+ base_url: URL base del servicio (opcional)
28
+ """
29
+ # Cargar configuración desde e2e.conf
30
+ from .config import get_${snake_case_name}_config
31
+ config = get_${snake_case_name}_config()
32
+
33
+ url = base_url or config.base_url
34
+ super().__init__(url, playwright, default_headers=config.default_headers)
35
+
36
+ # Estado compartido entre módulos
37
+ self.current_entity: Optional[dict] = None
38
+ self.auth_token: Optional[str] = None
39
+
40
+ def get_entity(self, entity_id: str) -> APIResponse:
41
+ """Obtener entidad por ID.
42
+
43
+ Args:
44
+ entity_id: ID de la entidad
45
+
46
+ Returns:
47
+ APIResponse: Respuesta HTTP
48
+ """
49
+ return self.get(f"/${endpoint_prefix}/{entity_id}")
50
+
51
+ def create_entity(self, data: dict) -> APIResponse:
52
+ """Crear nueva entidad.
53
+
54
+ Args:
55
+ data: Datos de la entidad a crear
56
+
57
+ Returns:
58
+ APIResponse: Respuesta HTTP
59
+ """
60
+ return self.post("/${endpoint_prefix}", json=data)
61
+
62
+ def update_entity(self, entity_id: str, data: dict) -> APIResponse:
63
+ """Actualizar entidad existente.
64
+
65
+ Args:
66
+ entity_id: ID de la entidad
67
+ data: Datos a actualizar
68
+
69
+ Returns:
70
+ APIResponse: Respuesta HTTP
71
+ """
72
+ return self.put(f"/${endpoint_prefix}/{entity_id}", json=data)
73
+
74
+ def delete_entity(self, entity_id: str) -> APIResponse:
75
+ """Eliminar entidad.
76
+
77
+ Args:
78
+ entity_id: ID de la entidad
79
+
80
+ Returns:
81
+ APIResponse: Respuesta HTTP
82
+ """
83
+ return self.delete(f"/${endpoint_prefix}/{entity_id}")
@@ -0,0 +1,46 @@
1
+ """Test module for ${test_name} flow.
2
+
3
+ This module implements the test flow for ${test_description}.
4
+ """
5
+
6
+ from playwright.sync_api import APIResponse
7
+ from typing import TYPE_CHECKING
8
+
9
+ if TYPE_CHECKING:
10
+ from ..${snake_case_service}_page import ${class_name}Page
11
+
12
+
13
+ def run(${snake_case_service}: '${class_name}Page') -> APIResponse:
14
+ """Execute ${test_name} test flow.
15
+
16
+ This test validates the ${test_description} functionality.
17
+
18
+ Args:
19
+ ${snake_case_service}: Instance of ${class_name}Page
20
+
21
+ Returns:
22
+ APIResponse: HTTP response from the API
23
+
24
+ Raises:
25
+ AssertionError: If test assertions fail
26
+ """
27
+ print(f"Running ${test_name} test...")
28
+
29
+ # Arrange: Setup test data and preconditions
30
+ # TODO: Add test data setup here
31
+
32
+ # Act: Execute the operation being tested
33
+ # TODO: Add API call here
34
+ # response = ${snake_case_service}.get("/${endpoint}")
35
+
36
+ # Assert: Verify the results
37
+ # TODO: Add assertions here
38
+ # assert response.ok, f"Expected success, got {response.status}"
39
+ # data = response.json()
40
+ # assert "expected_field" in data
41
+
42
+ # Store state for subsequent tests (optional)
43
+ # ${snake_case_service}.current_entity = data
44
+
45
+ print(f"✓ ${test_name} test completed successfully")
46
+ return response
@@ -0,0 +1,44 @@
1
+ """Utility functions for socialseed-e2e."""
2
+
3
+ from .template_engine import TemplateEngine, to_camel_case, to_class_name, to_snake_case
4
+ from .validators import (
5
+ ValidationError,
6
+ validate_base_url,
7
+ validate_dict,
8
+ validate_email,
9
+ validate_integer,
10
+ validate_json_response,
11
+ validate_list,
12
+ validate_pagination_response,
13
+ validate_port,
14
+ validate_service_name,
15
+ validate_status_code,
16
+ validate_string,
17
+ validate_timeout,
18
+ validate_url,
19
+ validate_uuid,
20
+ )
21
+
22
+ __all__ = [
23
+ # Template engine
24
+ "TemplateEngine",
25
+ "to_class_name",
26
+ "to_snake_case",
27
+ "to_camel_case",
28
+ # Validators
29
+ "ValidationError",
30
+ "validate_url",
31
+ "validate_base_url",
32
+ "validate_port",
33
+ "validate_timeout",
34
+ "validate_service_name",
35
+ "validate_string",
36
+ "validate_integer",
37
+ "validate_email",
38
+ "validate_uuid",
39
+ "validate_status_code",
40
+ "validate_json_response",
41
+ "validate_pagination_response",
42
+ "validate_list",
43
+ "validate_dict",
44
+ ]