socialseed-e2e 0.1.0__py3-none-any.whl → 0.1.2__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.
- socialseed_e2e/__init__.py +184 -20
- socialseed_e2e/__version__.py +2 -2
- socialseed_e2e/cli.py +353 -190
- socialseed_e2e/core/base_page.py +368 -49
- socialseed_e2e/core/config_loader.py +15 -3
- socialseed_e2e/core/headers.py +11 -4
- socialseed_e2e/core/loaders.py +6 -4
- socialseed_e2e/core/test_orchestrator.py +2 -0
- socialseed_e2e/core/test_runner.py +487 -0
- socialseed_e2e/templates/agent_docs/AGENT_GUIDE.md.template +412 -0
- socialseed_e2e/templates/agent_docs/EXAMPLE_TEST.md.template +152 -0
- socialseed_e2e/templates/agent_docs/FRAMEWORK_CONTEXT.md.template +55 -0
- socialseed_e2e/templates/agent_docs/WORKFLOW_GENERATION.md.template +182 -0
- socialseed_e2e/templates/data_schema.py.template +111 -70
- socialseed_e2e/templates/e2e.conf.template +19 -0
- socialseed_e2e/templates/service_page.py.template +82 -27
- socialseed_e2e/templates/test_module.py.template +21 -7
- socialseed_e2e/templates/verify_installation.py +192 -0
- socialseed_e2e/utils/__init__.py +29 -0
- socialseed_e2e/utils/ai_generator.py +463 -0
- socialseed_e2e/utils/pydantic_helpers.py +392 -0
- socialseed_e2e/utils/state_management.py +312 -0
- {socialseed_e2e-0.1.0.dist-info → socialseed_e2e-0.1.2.dist-info}/METADATA +64 -27
- socialseed_e2e-0.1.2.dist-info/RECORD +38 -0
- socialseed_e2e-0.1.0.dist-info/RECORD +0 -29
- {socialseed_e2e-0.1.0.dist-info → socialseed_e2e-0.1.2.dist-info}/WHEEL +0 -0
- {socialseed_e2e-0.1.0.dist-info → socialseed_e2e-0.1.2.dist-info}/entry_points.txt +0 -0
- {socialseed_e2e-0.1.0.dist-info → socialseed_e2e-0.1.2.dist-info}/licenses/LICENSE +0 -0
- {socialseed_e2e-0.1.0.dist-info → socialseed_e2e-0.1.2.dist-info}/top_level.txt +0 -0
socialseed_e2e/cli.py
CHANGED
|
@@ -5,7 +5,6 @@ This module provides the command-line interface for the E2E testing framework,
|
|
|
5
5
|
enabling developers and AI agents to create, manage, and run API tests.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
import os
|
|
9
8
|
import subprocess
|
|
10
9
|
import sys
|
|
11
10
|
from pathlib import Path
|
|
@@ -15,7 +14,6 @@ import click
|
|
|
15
14
|
from rich.console import Console
|
|
16
15
|
from rich.panel import Panel
|
|
17
16
|
from rich.table import Table
|
|
18
|
-
from rich.text import Text
|
|
19
17
|
|
|
20
18
|
from socialseed_e2e import __version__
|
|
21
19
|
from socialseed_e2e.core.config_loader import ApiConfigLoader, ConfigError
|
|
@@ -25,33 +23,33 @@ console = Console()
|
|
|
25
23
|
|
|
26
24
|
|
|
27
25
|
@click.group()
|
|
28
|
-
@click.version_option(version=__version__, prog_name="socialseed-e2e")
|
|
26
|
+
@click.version_option(version=str(__version__), prog_name="socialseed-e2e")
|
|
29
27
|
def cli():
|
|
30
|
-
"""socialseed-e2e: Framework
|
|
28
|
+
"""socialseed-e2e: E2E Framework for REST APIs.
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
A service-agnostic framework for End-to-End testing of REST APIs,
|
|
31
|
+
designed for developers and AI agents.
|
|
34
32
|
"""
|
|
35
33
|
pass
|
|
36
34
|
|
|
37
35
|
|
|
38
36
|
@cli.command()
|
|
39
37
|
@click.argument("directory", default=".", required=False)
|
|
40
|
-
@click.option("--force", is_flag=True, help="
|
|
38
|
+
@click.option("--force", is_flag=True, help="Overwrite existing files")
|
|
41
39
|
def init(directory: str, force: bool):
|
|
42
|
-
"""
|
|
40
|
+
"""Initialize a new E2E project.
|
|
43
41
|
|
|
44
|
-
|
|
42
|
+
Creates the initial directory structure and configuration files.
|
|
45
43
|
|
|
46
44
|
Args:
|
|
47
|
-
directory:
|
|
48
|
-
force:
|
|
45
|
+
directory: Directory to create the project (default: current directory)
|
|
46
|
+
force: If True, overwrites existing files
|
|
49
47
|
"""
|
|
50
48
|
target_path = Path(directory).resolve()
|
|
51
49
|
|
|
52
|
-
console.print(f"\n🌱 [bold green]
|
|
50
|
+
console.print(f"\n🌱 [bold green]Initializing E2E project at:[/bold green] {target_path}\n")
|
|
53
51
|
|
|
54
|
-
#
|
|
52
|
+
# Create directory structure
|
|
55
53
|
dirs_to_create = [
|
|
56
54
|
target_path / "services",
|
|
57
55
|
target_path / "tests",
|
|
@@ -67,11 +65,13 @@ def init(directory: str, force: bool):
|
|
|
67
65
|
if dir_path.parent == target_path
|
|
68
66
|
else str(dir_path.relative_to(target_path))
|
|
69
67
|
)
|
|
70
|
-
console.print(f" [green]✓[/green]
|
|
68
|
+
console.print(f" [green]✓[/green] Created: {dir_path.relative_to(target_path)}")
|
|
71
69
|
else:
|
|
72
|
-
console.print(
|
|
70
|
+
console.print(
|
|
71
|
+
f" [yellow]⚠[/yellow] Already exists: {dir_path.relative_to(target_path)}"
|
|
72
|
+
)
|
|
73
73
|
|
|
74
|
-
#
|
|
74
|
+
# Create configuration file
|
|
75
75
|
config_path = target_path / "e2e.conf"
|
|
76
76
|
if not config_path.exists() or force:
|
|
77
77
|
engine = TemplateEngine()
|
|
@@ -87,11 +87,11 @@ def init(directory: str, force: bool):
|
|
|
87
87
|
str(config_path),
|
|
88
88
|
overwrite=force,
|
|
89
89
|
)
|
|
90
|
-
console.print(
|
|
90
|
+
console.print(" [green]✓[/green] Created: e2e.conf")
|
|
91
91
|
else:
|
|
92
|
-
console.print(
|
|
92
|
+
console.print(" [yellow]⚠[/yellow] Already exists: e2e.conf (use --force to overwrite)")
|
|
93
93
|
|
|
94
|
-
#
|
|
94
|
+
# Create .gitignore
|
|
95
95
|
gitignore_path = target_path / ".gitignore"
|
|
96
96
|
if not gitignore_path.exists() or force:
|
|
97
97
|
gitignore_content = """# Python
|
|
@@ -121,70 +121,207 @@ test-results/
|
|
|
121
121
|
htmlcov/
|
|
122
122
|
"""
|
|
123
123
|
gitignore_path.write_text(gitignore_content)
|
|
124
|
-
console.print(
|
|
124
|
+
console.print(" [green]✓[/green] Created: .gitignore")
|
|
125
|
+
else:
|
|
126
|
+
console.print(" [yellow]⚠[/yellow] Already exists: .gitignore")
|
|
127
|
+
|
|
128
|
+
# Create requirements.txt
|
|
129
|
+
requirements_path = target_path / "requirements.txt"
|
|
130
|
+
if not requirements_path.exists() or force:
|
|
131
|
+
requirements_content = """pydantic>=2.0.0
|
|
132
|
+
email-validator>=2.0.0
|
|
133
|
+
"""
|
|
134
|
+
requirements_path.write_text(requirements_content)
|
|
135
|
+
console.print(" [green]✓[/green] Created: requirements.txt")
|
|
125
136
|
else:
|
|
126
|
-
console.print(
|
|
137
|
+
console.print(" [yellow]⚠[/yellow] Already exists: requirements.txt")
|
|
138
|
+
|
|
139
|
+
# Show success message
|
|
140
|
+
console.print("\n[bold green]✅ Project initialized successfully![/bold green]\n")
|
|
141
|
+
|
|
142
|
+
# Create .agent folder for AI documentation
|
|
143
|
+
agent_docs_path = target_path / ".agent"
|
|
144
|
+
if not agent_docs_path.exists() or force:
|
|
145
|
+
if not agent_docs_path.exists():
|
|
146
|
+
agent_docs_path.mkdir()
|
|
147
|
+
|
|
148
|
+
# Instantiate engine if it doesn't exist
|
|
149
|
+
engine = TemplateEngine()
|
|
150
|
+
|
|
151
|
+
# List of documentation templates for the agent
|
|
152
|
+
agent_templates = [
|
|
153
|
+
("agent_docs/FRAMEWORK_CONTEXT.md.template", "FRAMEWORK_CONTEXT.md"),
|
|
154
|
+
("agent_docs/WORKFLOW_GENERATION.md.template", "WORKFLOW_GENERATION.md"),
|
|
155
|
+
("agent_docs/EXAMPLE_TEST.md.template", "EXAMPLE_TEST.md"),
|
|
156
|
+
("agent_docs/AGENT_GUIDE.md.template", "AGENT_GUIDE.md"),
|
|
157
|
+
]
|
|
158
|
+
|
|
159
|
+
for template_name, output_name in agent_templates:
|
|
160
|
+
engine.render_to_file(
|
|
161
|
+
template_name,
|
|
162
|
+
{}, # No variables to replace in these MD
|
|
163
|
+
str(agent_docs_path / output_name),
|
|
164
|
+
overwrite=force,
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
console.print(" [green]✓[/green] Created: .agent/ (AI Documentation)")
|
|
168
|
+
|
|
169
|
+
# Copy verification script
|
|
170
|
+
verify_script_path = target_path / "verify_installation.py"
|
|
171
|
+
if not verify_script_path.exists() or force:
|
|
172
|
+
try:
|
|
173
|
+
import shutil
|
|
174
|
+
|
|
175
|
+
from socialseed_e2e.templates import __file__ as templates_init
|
|
127
176
|
|
|
128
|
-
|
|
129
|
-
|
|
177
|
+
templates_dir = Path(templates_init).parent
|
|
178
|
+
source_script = templates_dir / "verify_installation.py"
|
|
179
|
+
if source_script.exists():
|
|
180
|
+
shutil.copy(str(source_script), str(verify_script_path))
|
|
181
|
+
console.print(" [green]✓[/green] Created: verify_installation.py")
|
|
182
|
+
except Exception:
|
|
183
|
+
# If it fails, it's not critical
|
|
184
|
+
pass
|
|
130
185
|
|
|
131
186
|
console.print(
|
|
132
187
|
Panel(
|
|
133
|
-
"[bold]
|
|
134
|
-
"1.
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
"
|
|
138
|
-
title="🚀
|
|
188
|
+
"[bold]Next steps:[/bold]\n\n"
|
|
189
|
+
"1. Edit [cyan]e2e.conf[/cyan] to configure your API\n"
|
|
190
|
+
'2. Ask your AI Agent: [italic]"Read the AGENT_GUIDE.md and '
|
|
191
|
+
'generate tests for my API"[/italic]\n'
|
|
192
|
+
"3. Or do it manually: [cyan]e2e new-service <name>[/cyan]",
|
|
193
|
+
title="🚀 Getting Started",
|
|
139
194
|
border_style="green",
|
|
140
195
|
)
|
|
141
196
|
)
|
|
142
197
|
|
|
198
|
+
console.print(
|
|
199
|
+
Panel(
|
|
200
|
+
"[bold]⚠️ Important for AI Agents:[/bold]\n\n"
|
|
201
|
+
"• Use [cyan]absolute imports[/cyan] (not relative) in tests\n"
|
|
202
|
+
"• Remember to use [cyan]model_dump(by_alias=True)[/cyan] to serialize DTOs\n"
|
|
203
|
+
"• Review [cyan]AGENT_GUIDE.md[/cyan] for correct patterns and conventions",
|
|
204
|
+
title="🤖 Development Guide",
|
|
205
|
+
border_style="yellow",
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
# 1. Auto-install dependencies (if requirements.txt was created or force=True)
|
|
210
|
+
console.print("\n📦 Installing dependencies...")
|
|
211
|
+
try:
|
|
212
|
+
result = subprocess.run(
|
|
213
|
+
[sys.executable, "-m", "pip", "install", "-r", "requirements.txt"],
|
|
214
|
+
cwd=str(target_path),
|
|
215
|
+
capture_output=True,
|
|
216
|
+
text=True,
|
|
217
|
+
timeout=120,
|
|
218
|
+
)
|
|
219
|
+
if result.returncode == 0:
|
|
220
|
+
console.print(" [green]✓[/green] Dependencies installed")
|
|
221
|
+
else:
|
|
222
|
+
console.print(" [yellow]⚠ Warning:[/yellow] Some dependencies could not be installed")
|
|
223
|
+
if result.stderr:
|
|
224
|
+
console.print(f" [dim]{result.stderr[:200]}...[/dim]")
|
|
225
|
+
except subprocess.TimeoutExpired:
|
|
226
|
+
console.print(" [yellow]⚠ Warning:[/yellow] Installation took too long")
|
|
227
|
+
except Exception as e:
|
|
228
|
+
console.print(f" [yellow]⚠ Warning:[/yellow] Could not install dependencies: {e}")
|
|
229
|
+
|
|
230
|
+
# 2. Run verification (always)
|
|
231
|
+
console.print("\n🔍 Verifying installation...")
|
|
232
|
+
all_checks_passed = False
|
|
233
|
+
try:
|
|
234
|
+
# Try to import and run verification
|
|
235
|
+
import importlib.util
|
|
236
|
+
|
|
237
|
+
verify_script_path = target_path / "verify_installation.py"
|
|
238
|
+
if verify_script_path.exists():
|
|
239
|
+
spec = importlib.util.spec_from_file_location(
|
|
240
|
+
"verify_installation", str(verify_script_path)
|
|
241
|
+
)
|
|
242
|
+
verify_module = None
|
|
243
|
+
if spec and spec.loader:
|
|
244
|
+
verify_module = importlib.util.module_from_spec(spec)
|
|
245
|
+
spec.loader.exec_module(verify_module)
|
|
246
|
+
|
|
247
|
+
# Run the verification function if it exists
|
|
248
|
+
if verify_module and hasattr(verify_module, "run_verification"):
|
|
249
|
+
all_checks_passed = verify_module.run_verification(str(target_path))
|
|
250
|
+
else:
|
|
251
|
+
# Fallback: run via subprocess
|
|
252
|
+
result = subprocess.run(
|
|
253
|
+
[sys.executable, str(verify_script_path), str(target_path)],
|
|
254
|
+
cwd=str(target_path),
|
|
255
|
+
capture_output=True,
|
|
256
|
+
text=True,
|
|
257
|
+
timeout=30,
|
|
258
|
+
)
|
|
259
|
+
all_checks_passed = result.returncode == 0
|
|
260
|
+
if result.stdout:
|
|
261
|
+
console.print(result.stdout)
|
|
262
|
+
else:
|
|
263
|
+
console.print(" [yellow]⚠[/yellow] Verification script not found")
|
|
264
|
+
except Exception as e:
|
|
265
|
+
console.print(f" [yellow]⚠[/yellow] Could not run verification: {e}")
|
|
266
|
+
all_checks_passed = True # Consider successful if verification couldn't run
|
|
267
|
+
|
|
268
|
+
# 3. Final success panel (if all checks pass)
|
|
269
|
+
if all_checks_passed:
|
|
270
|
+
console.print(
|
|
271
|
+
Panel(
|
|
272
|
+
"[bold green]✅ ALL READY![/bold green] Your project is configured and verified.\n\n"
|
|
273
|
+
"🤖 You can ask your AI Agent to read [cyan].agent/AGENT_GUIDE.md[/cyan]\n"
|
|
274
|
+
"🚀 And generate E2E tests automatically",
|
|
275
|
+
title="🎉 Success",
|
|
276
|
+
border_style="green",
|
|
277
|
+
)
|
|
278
|
+
)
|
|
279
|
+
|
|
143
280
|
|
|
144
281
|
@cli.command()
|
|
145
282
|
@click.argument("name")
|
|
146
|
-
@click.option("--base-url", default="http://localhost:8080", help="
|
|
147
|
-
@click.option("--health-endpoint", default="/health", help="
|
|
283
|
+
@click.option("--base-url", default="http://localhost:8080", help="Service base URL")
|
|
284
|
+
@click.option("--health-endpoint", default="/health", help="Health check endpoint")
|
|
148
285
|
def new_service(name: str, base_url: str, health_endpoint: str):
|
|
149
|
-
"""
|
|
286
|
+
"""Create a new service with scaffolding.
|
|
150
287
|
|
|
151
288
|
Args:
|
|
152
|
-
name:
|
|
153
|
-
base_url:
|
|
154
|
-
health_endpoint:
|
|
289
|
+
name: Service name (e.g.: users-api)
|
|
290
|
+
base_url: Service base URL
|
|
291
|
+
health_endpoint: Health check endpoint
|
|
155
292
|
"""
|
|
156
|
-
console.print(f"\n🔧 [bold blue]
|
|
293
|
+
console.print(f"\n🔧 [bold blue]Creating service:[/bold blue] {name}\n")
|
|
157
294
|
|
|
158
|
-
#
|
|
295
|
+
# Verify we are in an E2E project
|
|
159
296
|
if not _is_e2e_project():
|
|
160
|
-
console.print("[red]❌ Error:[/red]
|
|
161
|
-
console.print("
|
|
297
|
+
console.print("[red]❌ Error:[/red] e2e.conf not found. Are you in an E2E project?")
|
|
298
|
+
console.print(" Run: [cyan]e2e init[/cyan] first")
|
|
162
299
|
sys.exit(1)
|
|
163
300
|
|
|
164
|
-
#
|
|
301
|
+
# Create service structure
|
|
165
302
|
service_path = Path("services") / name
|
|
166
303
|
modules_path = service_path / "modules"
|
|
167
304
|
|
|
168
305
|
try:
|
|
169
306
|
service_path.mkdir(parents=True)
|
|
170
307
|
modules_path.mkdir()
|
|
171
|
-
console.print(f" [green]✓[/green]
|
|
172
|
-
console.print(f" [green]✓[/green]
|
|
308
|
+
console.print(f" [green]✓[/green] Created: services/{name}/")
|
|
309
|
+
console.print(f" [green]✓[/green] Created: services/{name}/modules/")
|
|
173
310
|
except FileExistsError:
|
|
174
|
-
console.print(f" [yellow]⚠[/yellow]
|
|
175
|
-
if not click.confirm("
|
|
311
|
+
console.print(f" [yellow]⚠[/yellow] Service '{name}' already exists")
|
|
312
|
+
if not click.confirm("Do you want to continue and overwrite files?"):
|
|
176
313
|
return
|
|
177
314
|
|
|
178
|
-
#
|
|
179
|
-
_create_file(service_path / "__init__.py", f'"""
|
|
180
|
-
_create_file(modules_path / "__init__.py", f'"""
|
|
181
|
-
console.print(f" [green]✓[/green]
|
|
182
|
-
console.print(f" [green]✓[/green]
|
|
315
|
+
# Create __init__.py
|
|
316
|
+
_create_file(service_path / "__init__.py", f'"""Service {name}."""\n')
|
|
317
|
+
_create_file(modules_path / "__init__.py", f'"""Test modules for {name}."""\n')
|
|
318
|
+
console.print(f" [green]✓[/green] Created: services/{name}/__init__.py")
|
|
319
|
+
console.print(f" [green]✓[/green] Created: services/{name}/modules/__init__.py")
|
|
183
320
|
|
|
184
|
-
#
|
|
321
|
+
# Initialize TemplateEngine
|
|
185
322
|
engine = TemplateEngine()
|
|
186
323
|
|
|
187
|
-
# Variables
|
|
324
|
+
# Variables for templates
|
|
188
325
|
class_name = _to_class_name(name)
|
|
189
326
|
snake_case_name = to_snake_case(name)
|
|
190
327
|
template_vars = {
|
|
@@ -194,42 +331,45 @@ def new_service(name: str, base_url: str, health_endpoint: str):
|
|
|
194
331
|
"endpoint_prefix": "entities",
|
|
195
332
|
}
|
|
196
333
|
|
|
197
|
-
#
|
|
334
|
+
# Create service page
|
|
198
335
|
engine.render_to_file(
|
|
199
336
|
"service_page.py.template",
|
|
200
337
|
template_vars,
|
|
201
338
|
str(service_path / f"{snake_case_name}_page.py"),
|
|
202
339
|
overwrite=False,
|
|
203
340
|
)
|
|
204
|
-
console.print(f" [green]✓[/green]
|
|
341
|
+
console.print(f" [green]✓[/green] Created: services/{name}/{snake_case_name}_page.py")
|
|
205
342
|
|
|
206
|
-
#
|
|
343
|
+
# Create configuration file
|
|
207
344
|
engine.render_to_file(
|
|
208
|
-
"config.py.template",
|
|
345
|
+
"config.py.template",
|
|
346
|
+
template_vars,
|
|
347
|
+
str(service_path / "config.py"),
|
|
348
|
+
overwrite=False,
|
|
209
349
|
)
|
|
210
|
-
console.print(f" [green]✓[/green]
|
|
350
|
+
console.print(f" [green]✓[/green] Created: services/{name}/config.py")
|
|
211
351
|
|
|
212
|
-
#
|
|
352
|
+
# Create data_schema.py
|
|
213
353
|
engine.render_to_file(
|
|
214
354
|
"data_schema.py.template",
|
|
215
355
|
template_vars,
|
|
216
356
|
str(service_path / "data_schema.py"),
|
|
217
357
|
overwrite=False,
|
|
218
358
|
)
|
|
219
|
-
console.print(f" [green]✓[/green]
|
|
359
|
+
console.print(f" [green]✓[/green] Created: services/{name}/data_schema.py")
|
|
220
360
|
|
|
221
|
-
#
|
|
361
|
+
# Update e2e.conf
|
|
222
362
|
_update_e2e_conf(name, base_url, health_endpoint)
|
|
223
363
|
|
|
224
|
-
console.print(f"\n[bold green]✅
|
|
364
|
+
console.print(f"\n[bold green]✅ Service '{name}' created successfully![/bold green]\n")
|
|
225
365
|
|
|
226
366
|
console.print(
|
|
227
367
|
Panel(
|
|
228
|
-
f"[bold]
|
|
229
|
-
f"1.
|
|
230
|
-
f"2.
|
|
231
|
-
f"3.
|
|
232
|
-
title="🚀
|
|
368
|
+
f"[bold]Next steps:[/bold]\n\n"
|
|
369
|
+
f"1. Edit [cyan]services/{name}/data_schema.py[/cyan] to define your DTOs\n"
|
|
370
|
+
f"2. Run: [cyan]e2e new-test <name> --service {name}[/cyan]\n"
|
|
371
|
+
f"3. Run: [cyan]e2e run --service {name}[/cyan]",
|
|
372
|
+
title="🚀 Continue",
|
|
233
373
|
border_style="blue",
|
|
234
374
|
)
|
|
235
375
|
)
|
|
@@ -237,36 +377,36 @@ def new_service(name: str, base_url: str, health_endpoint: str):
|
|
|
237
377
|
|
|
238
378
|
@cli.command()
|
|
239
379
|
@click.argument("name")
|
|
240
|
-
@click.option("--service", "-s", required=True, help="
|
|
241
|
-
@click.option("--description", "-d", default="", help="
|
|
380
|
+
@click.option("--service", "-s", required=True, help="Service name")
|
|
381
|
+
@click.option("--description", "-d", default="", help="Test description")
|
|
242
382
|
def new_test(name: str, service: str, description: str):
|
|
243
|
-
"""
|
|
383
|
+
"""Create a new test module.
|
|
244
384
|
|
|
245
385
|
Args:
|
|
246
|
-
name:
|
|
247
|
-
service:
|
|
248
|
-
description:
|
|
386
|
+
name: Test name (e.g.: login, create-user)
|
|
387
|
+
service: Service to which the test belongs
|
|
388
|
+
description: Optional test description
|
|
249
389
|
"""
|
|
250
|
-
console.print(f"\n📝 [bold cyan]
|
|
390
|
+
console.print(f"\n📝 [bold cyan]Creating test:[/bold cyan] {name}\n")
|
|
251
391
|
|
|
252
|
-
#
|
|
392
|
+
# Verify we are in an E2E project
|
|
253
393
|
if not _is_e2e_project():
|
|
254
|
-
console.print("[red]❌ Error:[/red]
|
|
394
|
+
console.print("[red]❌ Error:[/red] e2e.conf not found. Are you in an E2E project?")
|
|
255
395
|
sys.exit(1)
|
|
256
396
|
|
|
257
|
-
#
|
|
397
|
+
# Verify that the service exists
|
|
258
398
|
service_path = Path("services") / service
|
|
259
399
|
modules_path = service_path / "modules"
|
|
260
400
|
|
|
261
401
|
if not service_path.exists():
|
|
262
|
-
console.print(f"[red]❌ Error:[/red]
|
|
263
|
-
console.print(f"
|
|
402
|
+
console.print(f"[red]❌ Error:[/red] Service '{service}' does not exist.")
|
|
403
|
+
console.print(f" Create the service first: [cyan]e2e new-service {service}[/cyan]")
|
|
264
404
|
sys.exit(1)
|
|
265
405
|
|
|
266
406
|
if not modules_path.exists():
|
|
267
407
|
modules_path.mkdir(parents=True)
|
|
268
408
|
|
|
269
|
-
#
|
|
409
|
+
# Find next available number
|
|
270
410
|
existing_tests = sorted(modules_path.glob("[0-9][0-9]_*.py"))
|
|
271
411
|
if existing_tests:
|
|
272
412
|
last_num = int(existing_tests[-1].name[:2])
|
|
@@ -277,16 +417,16 @@ def new_test(name: str, service: str, description: str):
|
|
|
277
417
|
test_filename = f"{next_num:02d}_{name}_flow.py"
|
|
278
418
|
test_path = modules_path / test_filename
|
|
279
419
|
|
|
280
|
-
#
|
|
420
|
+
# Check if it already exists
|
|
281
421
|
if test_path.exists():
|
|
282
|
-
console.print(f"[yellow]⚠[/yellow]
|
|
283
|
-
if not click.confirm("
|
|
422
|
+
console.print(f"[yellow]⚠[/yellow] Test '{name}' already exists.")
|
|
423
|
+
if not click.confirm("Do you want to overwrite it?"):
|
|
284
424
|
return
|
|
285
425
|
|
|
286
|
-
#
|
|
426
|
+
# Initialize TemplateEngine
|
|
287
427
|
engine = TemplateEngine()
|
|
288
428
|
|
|
289
|
-
# Variables
|
|
429
|
+
# Variables for template
|
|
290
430
|
class_name = _to_class_name(service)
|
|
291
431
|
snake_case_name = to_snake_case(service)
|
|
292
432
|
test_description = description or f"Test flow for {name}"
|
|
@@ -299,169 +439,189 @@ def new_test(name: str, service: str, description: str):
|
|
|
299
439
|
"test_description": test_description,
|
|
300
440
|
}
|
|
301
441
|
|
|
302
|
-
#
|
|
442
|
+
# Create test using template
|
|
303
443
|
engine.render_to_file("test_module.py.template", template_vars, str(test_path), overwrite=False)
|
|
304
|
-
console.print(f" [green]✓[/green]
|
|
444
|
+
console.print(f" [green]✓[/green] Created: services/{service}/modules/{test_filename}")
|
|
305
445
|
|
|
306
|
-
console.print(f"\n[bold green]✅ Test '{name}'
|
|
446
|
+
console.print(f"\n[bold green]✅ Test '{name}' created successfully![/bold green]\n")
|
|
307
447
|
|
|
308
448
|
console.print(
|
|
309
449
|
Panel(
|
|
310
|
-
f"[bold]
|
|
311
|
-
f"1.
|
|
312
|
-
f"2.
|
|
313
|
-
f"3.
|
|
314
|
-
title="🚀
|
|
450
|
+
f"[bold]Next steps:[/bold]\n\n"
|
|
451
|
+
f"1. Edit [cyan]services/{service}/modules/{test_filename}[/cyan]\n"
|
|
452
|
+
f"2. Implement the test logic\n"
|
|
453
|
+
f"3. Run: [cyan]e2e run --service {service}[/cyan]",
|
|
454
|
+
title="🚀 Implement",
|
|
315
455
|
border_style="cyan",
|
|
316
456
|
)
|
|
317
457
|
)
|
|
318
458
|
|
|
319
459
|
|
|
320
460
|
@cli.command()
|
|
321
|
-
@click.option("--service", "-s", help="
|
|
322
|
-
@click.option("--module", "-m", help="
|
|
323
|
-
@click.option("--
|
|
461
|
+
@click.option("--service", "-s", help="Filter by specific service")
|
|
462
|
+
@click.option("--module", "-m", help="Filter by specific module")
|
|
463
|
+
@click.option("--config", "-c", help="Path to configuration file (e2e.conf)")
|
|
464
|
+
@click.option("--verbose", "-v", is_flag=True, help="Verbose mode")
|
|
324
465
|
@click.option(
|
|
325
|
-
"--output",
|
|
466
|
+
"--output",
|
|
467
|
+
"-o",
|
|
468
|
+
type=click.Choice(["text", "json"]),
|
|
469
|
+
default="text",
|
|
470
|
+
help="Output format",
|
|
326
471
|
)
|
|
327
|
-
def run(
|
|
328
|
-
|
|
472
|
+
def run(
|
|
473
|
+
service: Optional[str],
|
|
474
|
+
module: Optional[str],
|
|
475
|
+
config: Optional[str],
|
|
476
|
+
verbose: bool,
|
|
477
|
+
output: str,
|
|
478
|
+
):
|
|
479
|
+
"""Execute E2E tests.
|
|
329
480
|
|
|
330
|
-
|
|
481
|
+
Discovers and automatically executes all available tests.
|
|
331
482
|
|
|
332
483
|
Args:
|
|
333
|
-
service:
|
|
334
|
-
module:
|
|
335
|
-
|
|
336
|
-
|
|
484
|
+
service: If specified, only run tests for this service
|
|
485
|
+
module: If specified, only run this test module
|
|
486
|
+
config: Path to the e2e.conf file
|
|
487
|
+
verbose: If True, shows detailed information
|
|
488
|
+
output: Output format (text or json)
|
|
337
489
|
"""
|
|
338
|
-
from .core.test_orchestrator import TestOrchestrator
|
|
490
|
+
# from .core.test_orchestrator import TestOrchestrator
|
|
339
491
|
|
|
340
492
|
console.print(f"\n🚀 [bold green]socialseed-e2e v{__version__}[/bold green]")
|
|
341
493
|
console.print("═" * 50)
|
|
342
494
|
console.print()
|
|
343
495
|
|
|
344
|
-
#
|
|
496
|
+
# Verify configuration
|
|
345
497
|
try:
|
|
346
498
|
loader = ApiConfigLoader()
|
|
347
|
-
|
|
348
|
-
console.print(f"📋 [cyan]
|
|
349
|
-
console.print(f"🌍 [cyan]Environment:[/cyan] {
|
|
499
|
+
app_config = loader.load(config) # Pass the config path if provided
|
|
500
|
+
console.print(f"📋 [cyan]Configuration:[/cyan] {loader._config_path}")
|
|
501
|
+
console.print(f"🌍 [cyan]Environment:[/cyan] {app_config.environment}")
|
|
350
502
|
console.print()
|
|
351
503
|
except ConfigError as e:
|
|
352
|
-
console.print(f"[red]❌
|
|
353
|
-
console.print("
|
|
504
|
+
console.print(f"[red]❌ Configuration error:[/red] {e}")
|
|
505
|
+
console.print(" Run: [cyan]e2e init[/cyan] to create a project")
|
|
354
506
|
sys.exit(1)
|
|
507
|
+
except Exception as e:
|
|
508
|
+
console.print(f"[red]❌ Unexpected error:[/red] {e}")
|
|
509
|
+
sys.exit(1)
|
|
510
|
+
|
|
511
|
+
# Import test runner
|
|
512
|
+
from .core.test_runner import print_summary, run_all_tests
|
|
355
513
|
|
|
356
|
-
#
|
|
357
|
-
#
|
|
514
|
+
# Determine services path
|
|
515
|
+
# If a config file was explicitly provided, we prioritize the 'services' folder next to it.
|
|
516
|
+
services_path = Path("services")
|
|
517
|
+
if loader._config_path:
|
|
518
|
+
alt_path = loader._config_path.parent / "services"
|
|
519
|
+
if alt_path.exists():
|
|
520
|
+
services_path = alt_path
|
|
521
|
+
elif not services_path.exists():
|
|
522
|
+
# Fallback if neither works
|
|
523
|
+
pass
|
|
358
524
|
|
|
359
525
|
if service:
|
|
360
|
-
console.print(f"🔍 [yellow]
|
|
526
|
+
console.print(f"🔍 [yellow]Filtering by service:[/yellow] {service}")
|
|
361
527
|
if module:
|
|
362
|
-
console.print(f"🔍 [yellow]
|
|
528
|
+
console.print(f"🔍 [yellow]Filtering by module:[/yellow] {module}")
|
|
363
529
|
if verbose:
|
|
364
|
-
console.print(
|
|
530
|
+
console.print("📢 [yellow]Verbose mode activated[/yellow]")
|
|
365
531
|
|
|
366
532
|
console.print()
|
|
367
|
-
console.print("[yellow]⚠ Nota:[/yellow] La ejecución de tests aún no está implementada")
|
|
368
|
-
console.print(" Este es un placeholder para la versión 0.1.0")
|
|
369
|
-
console.print()
|
|
370
533
|
|
|
371
|
-
#
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
534
|
+
# Execute tests
|
|
535
|
+
try:
|
|
536
|
+
results = run_all_tests(
|
|
537
|
+
services_path=services_path,
|
|
538
|
+
specific_service=service,
|
|
539
|
+
specific_module=module,
|
|
540
|
+
verbose=verbose,
|
|
541
|
+
)
|
|
377
542
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
table.add_column("Servicio", style="cyan")
|
|
381
|
-
table.add_column("Tests", style="green")
|
|
382
|
-
table.add_column("Estado", style="yellow")
|
|
543
|
+
# Print summary
|
|
544
|
+
all_passed = print_summary(results)
|
|
383
545
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
if modules_path.exists():
|
|
387
|
-
test_count = len(list(modules_path.glob("[0-9][0-9]_*.py")))
|
|
388
|
-
table.add_row(svc, str(test_count), "Ready" if test_count > 0 else "Empty")
|
|
389
|
-
else:
|
|
390
|
-
table.add_row(svc, "0", "No modules")
|
|
546
|
+
# Exit with appropriate code
|
|
547
|
+
sys.exit(0 if all_passed else 1)
|
|
391
548
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
else:
|
|
397
|
-
console.print("[red]❌ No se encontró el directorio 'services/'[/red]")
|
|
549
|
+
except Exception as e:
|
|
550
|
+
console.print(f"[red]❌ Error executing tests:[/red] {e}")
|
|
551
|
+
if verbose:
|
|
552
|
+
import traceback
|
|
398
553
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
console.print("[bold]Para implementar la ejecución real, contribuye en:[/bold]")
|
|
402
|
-
console.print("[cyan]https://github.com/daironpf/socialseed-e2e[/cyan]")
|
|
554
|
+
console.print(traceback.format_exc())
|
|
555
|
+
sys.exit(1)
|
|
403
556
|
|
|
404
557
|
|
|
405
558
|
@cli.command()
|
|
406
559
|
def doctor():
|
|
407
|
-
"""
|
|
560
|
+
"""Verify installation and dependencies.
|
|
408
561
|
|
|
409
|
-
|
|
562
|
+
Checks that everything is properly configured to use the framework.
|
|
410
563
|
"""
|
|
411
564
|
console.print("\n🏥 [bold green]socialseed-e2e Doctor[/bold green]\n")
|
|
412
565
|
|
|
413
566
|
checks = []
|
|
414
567
|
|
|
415
|
-
#
|
|
568
|
+
# Check Python
|
|
416
569
|
python_version = f"{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}"
|
|
417
570
|
checks.append(("Python", python_version, sys.version_info >= (3, 9)))
|
|
418
571
|
|
|
419
|
-
#
|
|
572
|
+
# Check Playwright
|
|
420
573
|
try:
|
|
421
574
|
from importlib.metadata import version
|
|
422
575
|
|
|
423
576
|
pw_version = version("playwright")
|
|
424
577
|
checks.append(("Playwright", pw_version, True))
|
|
425
578
|
except Exception:
|
|
426
|
-
checks.append(("Playwright", "
|
|
579
|
+
checks.append(("Playwright", "Not installed", False))
|
|
427
580
|
|
|
428
|
-
#
|
|
581
|
+
# Check Playwright browsers
|
|
429
582
|
try:
|
|
430
583
|
result = subprocess.run(
|
|
431
|
-
["playwright", "install", "--help"],
|
|
584
|
+
["playwright", "install", "--help"],
|
|
585
|
+
capture_output=True,
|
|
586
|
+
text=True,
|
|
587
|
+
timeout=5,
|
|
432
588
|
)
|
|
433
589
|
browsers_installed = result.returncode == 0
|
|
434
|
-
checks.append(("Playwright CLI", "
|
|
590
|
+
checks.append(("Playwright CLI", "Available", browsers_installed))
|
|
435
591
|
except (subprocess.TimeoutExpired, FileNotFoundError):
|
|
436
|
-
checks.append(("Playwright CLI", "
|
|
592
|
+
checks.append(("Playwright CLI", "Not available", False))
|
|
437
593
|
|
|
438
|
-
#
|
|
594
|
+
# Check Pydantic
|
|
439
595
|
try:
|
|
440
596
|
import pydantic
|
|
441
597
|
|
|
442
598
|
checks.append(("Pydantic", pydantic.__version__, True))
|
|
443
599
|
except ImportError:
|
|
444
|
-
checks.append(("Pydantic", "
|
|
600
|
+
checks.append(("Pydantic", "Not installed", False))
|
|
445
601
|
|
|
446
|
-
#
|
|
602
|
+
# Check e2e.conf
|
|
447
603
|
if _is_e2e_project():
|
|
448
|
-
checks.append(("
|
|
604
|
+
checks.append(("Configuration", "e2e.conf found", True))
|
|
449
605
|
else:
|
|
450
|
-
checks.append(("
|
|
606
|
+
checks.append(("Configuration", "e2e.conf not found", False))
|
|
451
607
|
|
|
452
|
-
#
|
|
608
|
+
# Check directory structure
|
|
453
609
|
services_exists = Path("services").exists()
|
|
454
610
|
tests_exists = Path("tests").exists()
|
|
455
611
|
checks.append(
|
|
456
|
-
(
|
|
612
|
+
(
|
|
613
|
+
"services/ directory",
|
|
614
|
+
"OK" if services_exists else "Not found",
|
|
615
|
+
services_exists,
|
|
616
|
+
)
|
|
457
617
|
)
|
|
458
|
-
checks.append(("
|
|
618
|
+
checks.append(("tests/ directory", "OK" if tests_exists else "Not found", tests_exists))
|
|
459
619
|
|
|
460
|
-
#
|
|
461
|
-
table = Table(title="
|
|
462
|
-
table.add_column("
|
|
463
|
-
table.add_column("
|
|
464
|
-
table.add_column("
|
|
620
|
+
# Show results
|
|
621
|
+
table = Table(title="System Verification")
|
|
622
|
+
table.add_column("Component", style="cyan")
|
|
623
|
+
table.add_column("Version/Status", style="white")
|
|
624
|
+
table.add_column("Status", style="bold")
|
|
465
625
|
|
|
466
626
|
all_ok = True
|
|
467
627
|
for name, value, ok in checks:
|
|
@@ -474,67 +634,70 @@ def doctor():
|
|
|
474
634
|
|
|
475
635
|
console.print()
|
|
476
636
|
if all_ok:
|
|
477
|
-
console.print("[bold green]✅
|
|
637
|
+
console.print("[bold green]✅ Everything is configured correctly![/bold green]")
|
|
478
638
|
else:
|
|
479
|
-
console.print("[bold yellow]⚠
|
|
639
|
+
console.print("[bold yellow]⚠ Some issues were found[/bold yellow]")
|
|
480
640
|
console.print()
|
|
481
|
-
console.print("[cyan]
|
|
641
|
+
console.print("[cyan]Suggested solutions:[/cyan]")
|
|
482
642
|
|
|
483
643
|
if not any(name == "Playwright" and ok for name, _, ok in checks):
|
|
484
|
-
console.print(" •
|
|
644
|
+
console.print(" • Install Playwright: [white]pip install playwright[/white]")
|
|
485
645
|
if not any(name == "Playwright CLI" and ok for name, _, ok in checks):
|
|
486
|
-
console.print(" •
|
|
646
|
+
console.print(" • Install browsers: [white]playwright install chromium[/white]")
|
|
487
647
|
if not any(name == "Pydantic" and ok for name, _, ok in checks):
|
|
488
|
-
console.print(" •
|
|
648
|
+
console.print(" • Install dependencies: [white]pip install socialseed-e2e[/white]")
|
|
489
649
|
if not _is_e2e_project():
|
|
490
|
-
console.print(" •
|
|
650
|
+
console.print(" • Initialize project: [white]e2e init[/white]")
|
|
491
651
|
|
|
492
652
|
console.print()
|
|
493
653
|
|
|
494
654
|
|
|
495
655
|
@cli.command()
|
|
496
656
|
def config():
|
|
497
|
-
"""
|
|
657
|
+
"""Show and validate current configuration.
|
|
498
658
|
|
|
499
|
-
|
|
659
|
+
Shows the configuration loaded from e2e.conf and validates its syntax.
|
|
500
660
|
"""
|
|
501
|
-
console.print("\n⚙️ [bold blue]
|
|
661
|
+
console.print("\n⚙️ [bold blue]E2E Configuration[/bold blue]\n")
|
|
502
662
|
|
|
503
663
|
try:
|
|
504
664
|
loader = ApiConfigLoader()
|
|
505
665
|
config = loader.load()
|
|
506
666
|
|
|
507
|
-
console.print(f"📋 [cyan]
|
|
667
|
+
console.print(f"📋 [cyan]Configuration:[/cyan] {loader._config_path}")
|
|
508
668
|
console.print(f"🌍 [cyan]Environment:[/cyan] {config.environment}")
|
|
509
669
|
console.print(f"[cyan]Timeout:[/cyan] {config.timeout}ms")
|
|
510
670
|
console.print(f"[cyan]Verbose:[/cyan] {config.verbose}")
|
|
511
671
|
console.print()
|
|
512
672
|
|
|
513
673
|
if config.services:
|
|
514
|
-
table = Table(title="
|
|
515
|
-
table.add_column("
|
|
674
|
+
table = Table(title="Configured Services")
|
|
675
|
+
table.add_column("Name", style="cyan")
|
|
516
676
|
table.add_column("Base URL", style="green")
|
|
517
677
|
table.add_column("Health", style="yellow")
|
|
518
|
-
table.add_column("
|
|
678
|
+
table.add_column("Required", style="white")
|
|
519
679
|
|
|
520
680
|
for name, svc in config.services.items():
|
|
521
681
|
table.add_row(
|
|
522
|
-
name,
|
|
682
|
+
name,
|
|
683
|
+
svc.base_url,
|
|
684
|
+
svc.health_endpoint or "N/A",
|
|
685
|
+
"✓" if svc.required else "✗",
|
|
523
686
|
)
|
|
524
687
|
|
|
525
688
|
console.print(table)
|
|
526
689
|
else:
|
|
527
|
-
console.print("[yellow]⚠ No
|
|
528
|
-
console.print("
|
|
690
|
+
console.print("[yellow]⚠ No services configured[/yellow]")
|
|
691
|
+
console.print(" Use: [cyan]e2e new-service <name>[/cyan]")
|
|
529
692
|
|
|
530
693
|
console.print()
|
|
531
|
-
console.print("[bold green]✅
|
|
694
|
+
console.print("[bold green]✅ Valid configuration[/bold green]")
|
|
532
695
|
|
|
533
696
|
except ConfigError as e:
|
|
534
|
-
console.print(f"[red]❌
|
|
697
|
+
console.print(f"[red]❌ Configuration error:[/red] {e}")
|
|
535
698
|
sys.exit(1)
|
|
536
699
|
except Exception as e:
|
|
537
|
-
console.print(f"[red]❌
|
|
700
|
+
console.print(f"[red]❌ Unexpected error:[/red] {e}")
|
|
538
701
|
sys.exit(1)
|
|
539
702
|
|
|
540
703
|
|
|
@@ -583,11 +746,11 @@ def _update_e2e_conf(service_name: str, base_url: str, health_endpoint: str) ->
|
|
|
583
746
|
|
|
584
747
|
content = config_path.read_text()
|
|
585
748
|
|
|
586
|
-
#
|
|
749
|
+
# Check if services section already exists
|
|
587
750
|
if "services:" not in content:
|
|
588
751
|
content += "\nservices:\n"
|
|
589
752
|
|
|
590
|
-
#
|
|
753
|
+
# Add service configuration
|
|
591
754
|
service_config = f""" {service_name}:
|
|
592
755
|
name: {service_name}-service
|
|
593
756
|
base_url: {base_url}
|
|
@@ -599,7 +762,7 @@ def _update_e2e_conf(service_name: str, base_url: str, health_endpoint: str) ->
|
|
|
599
762
|
|
|
600
763
|
content += service_config
|
|
601
764
|
config_path.write_text(content)
|
|
602
|
-
console.print(
|
|
765
|
+
console.print(" [green]✓[/green] Updated: e2e.conf")
|
|
603
766
|
|
|
604
767
|
|
|
605
768
|
def main():
|