scriptgini 1.6.1__tar.gz → 1.6.2__tar.gz
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.
- {scriptgini-1.6.1 → scriptgini-1.6.2}/PKG-INFO +2 -2
- {scriptgini-1.6.1 → scriptgini-1.6.2}/README.md +1 -1
- scriptgini-1.6.2/app/__init__.py +3 -0
- scriptgini-1.6.2/app/celery_app.py +98 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/tasks.py +53 -1
- {scriptgini-1.6.1 → scriptgini-1.6.2}/pyproject.toml +1 -1
- {scriptgini-1.6.1 → scriptgini-1.6.2}/scriptgini.egg-info/PKG-INFO +2 -2
- {scriptgini-1.6.1 → scriptgini-1.6.2}/scriptgini.egg-info/SOURCES.txt +1 -0
- scriptgini-1.6.2/tests/test_api_contracts.py +251 -0
- scriptgini-1.6.1/app/__init__.py +0 -3
- scriptgini-1.6.1/app/celery_app.py +0 -30
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/agents/__init__.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/agents/prompts.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/agents/script_gini_agent.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/cache.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/config.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/database.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/llm/__init__.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/llm/provider.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/main.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/models/__init__.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/models/api_key.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/models/bulk_job.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/models/execution_job.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/models/generated_script.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/models/membership.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/models/organization.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/models/project.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/models/script_revision.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/models/script_run.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/models/test_case.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/models/user.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/routers/__init__.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/routers/analytics.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/routers/api_key.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/routers/auth.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/routers/bulk_jobs.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/routers/demo.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/routers/execution.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/routers/organizations.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/routers/projects.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/routers/reports.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/routers/scripts.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/routers/test_cases.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/schemas/__init__.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/schemas/analytics.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/schemas/api_key.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/schemas/auth.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/schemas/bulk_job.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/schemas/execution.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/schemas/generated_script.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/schemas/membership.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/schemas/organization.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/schemas/project.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/schemas/reports.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/schemas/script_revision.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/schemas/test_case.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/services/api_key.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/services/auth.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/services/auth_dependencies.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/services/git_export.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/services/rate_limit.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/app/services/rbac.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/scriptgini.egg-info/dependency_links.txt +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/scriptgini.egg-info/top_level.txt +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/setup.cfg +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/tests/test_api.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/tests/test_auth.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/tests/test_coverage.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/tests/test_infra_services_coverage.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/tests/test_sprint2_rbac.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/tests/test_sprint3_execution.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/tests/test_sprint5_reporting_analytics.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/tests/test_sprint6_coverage_lifecycle.py +0 -0
- {scriptgini-1.6.1 → scriptgini-1.6.2}/tests/test_sprint7_rate_limit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scriptgini
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.2
|
|
4
4
|
Summary: Agentic AI system that converts functional test cases into automation test scripts.
|
|
5
5
|
Author: ScriptGini Team
|
|
6
6
|
License: Proprietary
|
|
@@ -16,7 +16,7 @@ Description-Content-Type: text/markdown
|
|
|
16
16
|
|
|
17
17
|
> **Enterprise-grade Agentic AI system that converts functional test cases into high-quality, review-ready automation test scripts.**
|
|
18
18
|
|
|
19
|
-
Current release: v1.
|
|
19
|
+
Current release: v1.6.2 (Patch release - API contract guardrails, Celery test-environment fallback, 273/273 tests passing, 100% statement coverage)
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **Enterprise-grade Agentic AI system that converts functional test cases into high-quality, review-ready automation test scripts.**
|
|
4
4
|
|
|
5
|
-
Current release: v1.
|
|
5
|
+
Current release: v1.6.2 (Patch release - API contract guardrails, Celery test-environment fallback, 273/273 tests passing, 100% statement coverage)
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
try:
|
|
2
|
+
from celery import Celery
|
|
3
|
+
except ModuleNotFoundError: # pragma: no cover - fallback for test environments without celery installed
|
|
4
|
+
from types import SimpleNamespace
|
|
5
|
+
|
|
6
|
+
class _TaskWrapper:
|
|
7
|
+
def __init__(self, func, *, bind: bool = False, max_retries: int | None = None):
|
|
8
|
+
self._func = func
|
|
9
|
+
self.bind = bind
|
|
10
|
+
self.max_retries = max_retries if max_retries is not None else 0
|
|
11
|
+
|
|
12
|
+
def _self_proxy(self):
|
|
13
|
+
return SimpleNamespace(
|
|
14
|
+
request=SimpleNamespace(retries=0),
|
|
15
|
+
max_retries=self.max_retries,
|
|
16
|
+
retry=self._retry,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def _retry(self, exc=None, **kwargs):
|
|
20
|
+
if exc is not None:
|
|
21
|
+
raise exc
|
|
22
|
+
raise RuntimeError("Celery retry requested")
|
|
23
|
+
|
|
24
|
+
def run(self, *args, **kwargs):
|
|
25
|
+
if self.bind:
|
|
26
|
+
return self._func(self._self_proxy(), *args, **kwargs)
|
|
27
|
+
return self._func(*args, **kwargs)
|
|
28
|
+
|
|
29
|
+
def delay(self, *args, **kwargs):
|
|
30
|
+
return SimpleNamespace(id="stub-task", result=self.run(*args, **kwargs))
|
|
31
|
+
|
|
32
|
+
def apply(self, args=None, kwargs=None):
|
|
33
|
+
args = args or ()
|
|
34
|
+
kwargs = kwargs or {}
|
|
35
|
+
return SimpleNamespace(result=self.run(*args, **kwargs))
|
|
36
|
+
|
|
37
|
+
def __call__(self, *args, **kwargs):
|
|
38
|
+
return self.run(*args, **kwargs)
|
|
39
|
+
|
|
40
|
+
class Celery: # type: ignore[override]
|
|
41
|
+
def __init__(self, *args, **kwargs):
|
|
42
|
+
self.conf = self._Conf()
|
|
43
|
+
self.control = SimpleNamespace(revoke=lambda *a, **k: None)
|
|
44
|
+
self.AsyncResult = lambda task_id: SimpleNamespace(id=task_id, state="PENDING", result=None, info=None)
|
|
45
|
+
|
|
46
|
+
class _Conf:
|
|
47
|
+
def update(self, *args, **kwargs):
|
|
48
|
+
for mapping in args:
|
|
49
|
+
if isinstance(mapping, dict):
|
|
50
|
+
for key, value in mapping.items():
|
|
51
|
+
setattr(self, key, value)
|
|
52
|
+
for key, value in kwargs.items():
|
|
53
|
+
setattr(self, key, value)
|
|
54
|
+
return None
|
|
55
|
+
|
|
56
|
+
def task(self, *decorator_args, **decorator_kwargs):
|
|
57
|
+
def decorator(func):
|
|
58
|
+
wrapper = _TaskWrapper(
|
|
59
|
+
func,
|
|
60
|
+
bind=bool(decorator_kwargs.get("bind", False)),
|
|
61
|
+
max_retries=decorator_kwargs.get("max_retries"),
|
|
62
|
+
)
|
|
63
|
+
wrapper.__name__ = getattr(func, "__name__", "task")
|
|
64
|
+
wrapper.__doc__ = getattr(func, "__doc__", None)
|
|
65
|
+
return wrapper
|
|
66
|
+
|
|
67
|
+
if decorator_args and callable(decorator_args[0]) and len(decorator_args) == 1 and not decorator_kwargs:
|
|
68
|
+
return decorator(decorator_args[0])
|
|
69
|
+
return decorator
|
|
70
|
+
from app.config import settings
|
|
71
|
+
|
|
72
|
+
# Initialize Celery app
|
|
73
|
+
celery_app = Celery(
|
|
74
|
+
settings.APP_NAME,
|
|
75
|
+
broker=settings.CELERY_BROKER_URL,
|
|
76
|
+
backend=settings.CELERY_RESULT_BACKEND,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Configure Celery
|
|
80
|
+
celery_app.conf.update(
|
|
81
|
+
task_serializer="json",
|
|
82
|
+
accept_content=["json"],
|
|
83
|
+
result_serializer="json",
|
|
84
|
+
timezone="UTC",
|
|
85
|
+
enable_utc=True,
|
|
86
|
+
task_track_started=True,
|
|
87
|
+
task_time_limit=settings.CELERY_TASK_HARD_TIMEOUT,
|
|
88
|
+
task_soft_time_limit=settings.CELERY_TASK_TIMEOUT,
|
|
89
|
+
worker_prefetch_multiplier=4,
|
|
90
|
+
worker_max_tasks_per_child=1000,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@celery_app.task(bind=True, max_retries=settings.CELERY_MAX_RETRIES)
|
|
95
|
+
def debug_task(self):
|
|
96
|
+
"""Test task to verify Celery is working."""
|
|
97
|
+
print(f"Request: {self.request!r}")
|
|
98
|
+
return "Celery is working!"
|
|
@@ -8,7 +8,59 @@ Examples: Script generation, test execution, file processing, etc.
|
|
|
8
8
|
from datetime import datetime, timedelta, timezone
|
|
9
9
|
import logging
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
try:
|
|
12
|
+
from celery import shared_task
|
|
13
|
+
except ModuleNotFoundError: # pragma: no cover - fallback for test environments without celery installed
|
|
14
|
+
from types import SimpleNamespace
|
|
15
|
+
|
|
16
|
+
class _TaskWrapper:
|
|
17
|
+
def __init__(self, func, *, bind: bool = False, max_retries: int | None = None):
|
|
18
|
+
self._func = func
|
|
19
|
+
self.bind = bind
|
|
20
|
+
self.max_retries = max_retries if max_retries is not None else 0
|
|
21
|
+
|
|
22
|
+
def _self_proxy(self):
|
|
23
|
+
return SimpleNamespace(
|
|
24
|
+
request=SimpleNamespace(retries=0),
|
|
25
|
+
max_retries=self.max_retries,
|
|
26
|
+
retry=self._retry,
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
def _retry(self, exc=None, **kwargs):
|
|
30
|
+
if exc is not None:
|
|
31
|
+
raise exc
|
|
32
|
+
raise RuntimeError("Celery retry requested")
|
|
33
|
+
|
|
34
|
+
def run(self, *args, **kwargs):
|
|
35
|
+
if self.bind:
|
|
36
|
+
return self._func(self._self_proxy(), *args, **kwargs)
|
|
37
|
+
return self._func(*args, **kwargs)
|
|
38
|
+
|
|
39
|
+
def delay(self, *args, **kwargs):
|
|
40
|
+
return SimpleNamespace(id="stub-task", result=self.run(*args, **kwargs))
|
|
41
|
+
|
|
42
|
+
def apply(self, args=None, kwargs=None):
|
|
43
|
+
args = args or ()
|
|
44
|
+
kwargs = kwargs or {}
|
|
45
|
+
return SimpleNamespace(result=self.run(*args, **kwargs))
|
|
46
|
+
|
|
47
|
+
def __call__(self, *args, **kwargs):
|
|
48
|
+
return self.run(*args, **kwargs)
|
|
49
|
+
|
|
50
|
+
def shared_task(*decorator_args, **decorator_kwargs):
|
|
51
|
+
def decorator(func):
|
|
52
|
+
wrapper = _TaskWrapper(
|
|
53
|
+
func,
|
|
54
|
+
bind=bool(decorator_kwargs.get("bind", False)),
|
|
55
|
+
max_retries=decorator_kwargs.get("max_retries"),
|
|
56
|
+
)
|
|
57
|
+
wrapper.__name__ = getattr(func, "__name__", "task")
|
|
58
|
+
wrapper.__doc__ = getattr(func, "__doc__", None)
|
|
59
|
+
return wrapper
|
|
60
|
+
|
|
61
|
+
if decorator_args and callable(decorator_args[0]) and len(decorator_args) == 1 and not decorator_kwargs:
|
|
62
|
+
return decorator(decorator_args[0])
|
|
63
|
+
return decorator
|
|
12
64
|
from app.config import settings
|
|
13
65
|
from app.database import SessionLocal
|
|
14
66
|
from app.models.execution_job import ExecutionJob, ExecutionJobStatus, can_transition
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scriptgini"
|
|
7
|
-
version = "1.6.
|
|
7
|
+
version = "1.6.2"
|
|
8
8
|
description = "Agentic AI system that converts functional test cases into automation test scripts."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scriptgini
|
|
3
|
-
Version: 1.6.
|
|
3
|
+
Version: 1.6.2
|
|
4
4
|
Summary: Agentic AI system that converts functional test cases into automation test scripts.
|
|
5
5
|
Author: ScriptGini Team
|
|
6
6
|
License: Proprietary
|
|
@@ -16,7 +16,7 @@ Description-Content-Type: text/markdown
|
|
|
16
16
|
|
|
17
17
|
> **Enterprise-grade Agentic AI system that converts functional test cases into high-quality, review-ready automation test scripts.**
|
|
18
18
|
|
|
19
|
-
Current release: v1.
|
|
19
|
+
Current release: v1.6.2 (Patch release - API contract guardrails, Celery test-environment fallback, 273/273 tests passing, 100% statement coverage)
|
|
20
20
|
|
|
21
21
|
---
|
|
22
22
|
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
"""Contract tests for the stable project, test-case, script, and bulk API surface."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import types
|
|
5
|
+
|
|
6
|
+
import pytest
|
|
7
|
+
from fastapi.testclient import TestClient
|
|
8
|
+
from sqlalchemy import create_engine
|
|
9
|
+
from sqlalchemy.orm import sessionmaker
|
|
10
|
+
from sqlalchemy.pool import StaticPool
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _install_celery_stub() -> None:
|
|
14
|
+
if "celery" in sys.modules:
|
|
15
|
+
return
|
|
16
|
+
|
|
17
|
+
celery_module = types.ModuleType("celery")
|
|
18
|
+
|
|
19
|
+
class _Conf:
|
|
20
|
+
def update(self, *args, **kwargs):
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
class Celery:
|
|
24
|
+
def __init__(self, *args, **kwargs):
|
|
25
|
+
self.conf = _Conf()
|
|
26
|
+
|
|
27
|
+
def task(self, *decorator_args, **decorator_kwargs):
|
|
28
|
+
def decorator(func):
|
|
29
|
+
def delay(*args, **kwargs):
|
|
30
|
+
return None
|
|
31
|
+
|
|
32
|
+
func.delay = delay
|
|
33
|
+
return func
|
|
34
|
+
|
|
35
|
+
if decorator_args and callable(decorator_args[0]) and len(decorator_args) == 1 and not decorator_kwargs:
|
|
36
|
+
return decorator(decorator_args[0])
|
|
37
|
+
return decorator
|
|
38
|
+
|
|
39
|
+
def shared_task(*decorator_args, **decorator_kwargs):
|
|
40
|
+
def decorator(func):
|
|
41
|
+
def delay(*args, **kwargs):
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
func.delay = delay
|
|
45
|
+
return func
|
|
46
|
+
|
|
47
|
+
if decorator_args and callable(decorator_args[0]) and len(decorator_args) == 1 and not decorator_kwargs:
|
|
48
|
+
return decorator(decorator_args[0])
|
|
49
|
+
return decorator
|
|
50
|
+
|
|
51
|
+
celery_module.Celery = Celery
|
|
52
|
+
celery_module.shared_task = shared_task
|
|
53
|
+
sys.modules["celery"] = celery_module
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
_install_celery_stub()
|
|
57
|
+
|
|
58
|
+
import app.models.api_key # noqa: F401
|
|
59
|
+
import app.models.bulk_job # noqa: F401
|
|
60
|
+
import app.models.generated_script # noqa: F401
|
|
61
|
+
import app.models.membership # noqa: F401
|
|
62
|
+
import app.models.project # noqa: F401
|
|
63
|
+
import app.models.script_run # noqa: F401
|
|
64
|
+
import app.models.test_case # noqa: F401
|
|
65
|
+
import app.models.user # noqa: F401
|
|
66
|
+
|
|
67
|
+
from app.database import Base, get_db
|
|
68
|
+
from app.main import app as fastapi_app
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
TEST_DATABASE_URL = "sqlite:///:memory:"
|
|
72
|
+
|
|
73
|
+
engine = create_engine(
|
|
74
|
+
TEST_DATABASE_URL,
|
|
75
|
+
connect_args={"check_same_thread": False},
|
|
76
|
+
poolclass=StaticPool,
|
|
77
|
+
)
|
|
78
|
+
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def override_get_db():
|
|
82
|
+
db = TestingSessionLocal()
|
|
83
|
+
try:
|
|
84
|
+
yield db
|
|
85
|
+
finally:
|
|
86
|
+
db.close()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@pytest.fixture(autouse=True)
|
|
90
|
+
def setup_db():
|
|
91
|
+
Base.metadata.create_all(bind=engine)
|
|
92
|
+
fastapi_app.dependency_overrides[get_db] = override_get_db
|
|
93
|
+
try:
|
|
94
|
+
yield
|
|
95
|
+
finally:
|
|
96
|
+
fastapi_app.dependency_overrides.clear()
|
|
97
|
+
Base.metadata.drop_all(bind=engine)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
@pytest.fixture()
|
|
101
|
+
def client():
|
|
102
|
+
return TestClient(fastapi_app)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _register_and_login(client: TestClient, email: str, password: str = "TestPassword123!") -> dict[str, str]:
|
|
106
|
+
register_resp = client.post("/api/v1/auth/register", json={"email": email, "password": password})
|
|
107
|
+
assert register_resp.status_code == 201
|
|
108
|
+
|
|
109
|
+
login_resp = client.post("/api/v1/auth/login", json={"email": email, "password": password})
|
|
110
|
+
assert login_resp.status_code == 200
|
|
111
|
+
return {"Authorization": f"Bearer {login_resp.json()['access_token']}"}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _create_project(client: TestClient, headers: dict[str, str]) -> dict:
|
|
115
|
+
resp = client.post(
|
|
116
|
+
"/api/v1/projects/",
|
|
117
|
+
headers=headers,
|
|
118
|
+
json={
|
|
119
|
+
"name": "Contract Project",
|
|
120
|
+
"aut_base_url": "https://example.com",
|
|
121
|
+
"default_framework": "playwright_python",
|
|
122
|
+
"selector_preference": "role",
|
|
123
|
+
},
|
|
124
|
+
)
|
|
125
|
+
assert resp.status_code == 201
|
|
126
|
+
return resp.json()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _create_test_case(client: TestClient, project_id: int, headers: dict[str, str]) -> dict:
|
|
130
|
+
resp = client.post(
|
|
131
|
+
f"/api/v1/projects/{project_id}/test-cases/",
|
|
132
|
+
headers=headers,
|
|
133
|
+
json={
|
|
134
|
+
"title": "Contract TC",
|
|
135
|
+
"format": "step_based",
|
|
136
|
+
"content": "Step 1: open the app",
|
|
137
|
+
},
|
|
138
|
+
)
|
|
139
|
+
assert resp.status_code == 201
|
|
140
|
+
return resp.json()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def test_openapi_documents_stable_project_test_case_script_and_bulk_surface():
|
|
144
|
+
openapi = fastapi_app.openapi()
|
|
145
|
+
paths = openapi["paths"]
|
|
146
|
+
|
|
147
|
+
expected_paths = [
|
|
148
|
+
"/api/v1/projects/",
|
|
149
|
+
"/api/v1/projects/{project_id}",
|
|
150
|
+
"/api/v1/projects/{project_id}/test-cases/",
|
|
151
|
+
"/api/v1/projects/{project_id}/test-cases/{tc_id}/scripts/",
|
|
152
|
+
"/api/v1/projects/{project_id}/test-cases/{tc_id}/scripts/generate",
|
|
153
|
+
"/api/v1/projects/{project_id}/test-cases/{tc_id}/scripts/{script_id}",
|
|
154
|
+
"/api/v1/projects/{project_id}/test-cases/{tc_id}/scripts/{script_id}/runs",
|
|
155
|
+
"/api/v1/projects/{project_id}/test-cases/{tc_id}/scripts/{script_id}/refactor",
|
|
156
|
+
"/api/v1/projects/{project_id}/test-cases/{tc_id}/scripts/{script_id}/version-history",
|
|
157
|
+
"/api/v1/projects/{project_id}/scripts/bulk-generate",
|
|
158
|
+
"/api/v1/projects/{project_id}/scripts/bulk-run",
|
|
159
|
+
"/api/v1/projects/{project_id}/scripts/bulk-jobs/{job_id}",
|
|
160
|
+
]
|
|
161
|
+
|
|
162
|
+
for path in expected_paths:
|
|
163
|
+
assert path in paths
|
|
164
|
+
|
|
165
|
+
assert paths["/api/v1/projects/"]["get"]["responses"]["200"]
|
|
166
|
+
assert paths["/api/v1/projects/"]["post"]["responses"]["201"]
|
|
167
|
+
assert paths["/api/v1/projects/{project_id}/test-cases/"]["post"]["responses"]["201"]
|
|
168
|
+
assert paths["/api/v1/projects/{project_id}/test-cases/{tc_id}/scripts/generate"]["post"]["responses"]["202"]
|
|
169
|
+
assert paths["/api/v1/projects/{project_id}/scripts/bulk-generate"]["post"]["responses"]["202"]
|
|
170
|
+
assert paths["/api/v1/projects/{project_id}/scripts/bulk-run"]["post"]["responses"]["202"]
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def test_stable_project_test_case_script_and_bulk_contract(client: TestClient, monkeypatch):
|
|
174
|
+
headers = _register_and_login(client, "contract-owner@example.com")
|
|
175
|
+
project = _create_project(client, headers)
|
|
176
|
+
test_case = _create_test_case(client, project["id"], headers)
|
|
177
|
+
|
|
178
|
+
monkeypatch.setattr("app.routers.scripts.process_script_generation_job.delay", lambda *args, **kwargs: None)
|
|
179
|
+
monkeypatch.setattr("app.routers.bulk_jobs.process_bulk_generation_job.delay", lambda *args, **kwargs: None)
|
|
180
|
+
monkeypatch.setattr("app.routers.bulk_jobs.process_bulk_execution_job.delay", lambda *args, **kwargs: None)
|
|
181
|
+
|
|
182
|
+
project_list = client.get("/api/v1/projects/")
|
|
183
|
+
assert project_list.status_code == 200
|
|
184
|
+
assert project_list.json()[0]["id"] == project["id"]
|
|
185
|
+
assert project_list.json()[0]["name"] == "Contract Project"
|
|
186
|
+
|
|
187
|
+
tc_list = client.get(f"/api/v1/projects/{project['id']}/test-cases/", headers=headers)
|
|
188
|
+
assert tc_list.status_code == 200
|
|
189
|
+
assert tc_list.json()[0]["id"] == test_case["id"]
|
|
190
|
+
assert tc_list.json()[0]["title"] == "Contract TC"
|
|
191
|
+
|
|
192
|
+
script_generate = client.post(
|
|
193
|
+
f"/api/v1/projects/{project['id']}/test-cases/{test_case['id']}/scripts/generate",
|
|
194
|
+
headers=headers,
|
|
195
|
+
json={"llm_provider": "openai", "framework": "playwright_python"},
|
|
196
|
+
)
|
|
197
|
+
assert script_generate.status_code == 202
|
|
198
|
+
generated = script_generate.json()
|
|
199
|
+
assert generated["project_id"] == project["id"]
|
|
200
|
+
assert generated["test_case_id"] == test_case["id"]
|
|
201
|
+
assert generated["status"] == "pending"
|
|
202
|
+
|
|
203
|
+
script_list = client.get(
|
|
204
|
+
f"/api/v1/projects/{project['id']}/test-cases/{test_case['id']}/scripts/",
|
|
205
|
+
headers=headers,
|
|
206
|
+
)
|
|
207
|
+
assert script_list.status_code == 200
|
|
208
|
+
assert len(script_list.json()) == 1
|
|
209
|
+
assert script_list.json()[0]["id"] == generated["id"]
|
|
210
|
+
assert script_list.json()[0]["status"] == "pending"
|
|
211
|
+
|
|
212
|
+
script_detail = client.get(
|
|
213
|
+
f"/api/v1/projects/{project['id']}/test-cases/{test_case['id']}/scripts/{generated['id']}",
|
|
214
|
+
headers=headers,
|
|
215
|
+
)
|
|
216
|
+
assert script_detail.status_code == 200
|
|
217
|
+
assert script_detail.json()["id"] == generated["id"]
|
|
218
|
+
assert script_detail.json()["project_id"] == project["id"]
|
|
219
|
+
|
|
220
|
+
bulk_generate = client.post(
|
|
221
|
+
f"/api/v1/projects/{project['id']}/scripts/bulk-generate",
|
|
222
|
+
headers=headers,
|
|
223
|
+
json={"test_case_ids": [test_case["id"]], "framework": "playwright_python", "llm_provider": "openai"},
|
|
224
|
+
)
|
|
225
|
+
assert bulk_generate.status_code == 202
|
|
226
|
+
bulk_generate_payload = bulk_generate.json()
|
|
227
|
+
assert bulk_generate_payload["project_id"] == project["id"]
|
|
228
|
+
assert bulk_generate_payload["kind"] == "generate"
|
|
229
|
+
assert bulk_generate_payload["status"] == "pending"
|
|
230
|
+
assert len(bulk_generate_payload["items"]) == 1
|
|
231
|
+
assert bulk_generate_payload["items"][0]["test_case_id"] == test_case["id"]
|
|
232
|
+
|
|
233
|
+
bulk_job = client.get(
|
|
234
|
+
f"/api/v1/projects/{project['id']}/scripts/bulk-jobs/{bulk_generate_payload['id']}",
|
|
235
|
+
headers=headers,
|
|
236
|
+
)
|
|
237
|
+
assert bulk_job.status_code == 200
|
|
238
|
+
assert bulk_job.json()["id"] == bulk_generate_payload["id"]
|
|
239
|
+
assert bulk_job.json()["kind"] == "generate"
|
|
240
|
+
|
|
241
|
+
bulk_run = client.post(
|
|
242
|
+
f"/api/v1/projects/{project['id']}/scripts/bulk-run",
|
|
243
|
+
headers=headers,
|
|
244
|
+
json={"test_case_ids": [test_case["id"]]},
|
|
245
|
+
)
|
|
246
|
+
assert bulk_run.status_code == 202
|
|
247
|
+
bulk_run_payload = bulk_run.json()
|
|
248
|
+
assert bulk_run_payload["project_id"] == project["id"]
|
|
249
|
+
assert bulk_run_payload["kind"] == "run"
|
|
250
|
+
assert bulk_run_payload["status"] == "pending"
|
|
251
|
+
assert bulk_run_payload["items"][0]["test_case_id"] == test_case["id"]
|
scriptgini-1.6.1/app/__init__.py
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
from celery import Celery
|
|
2
|
-
from app.config import settings
|
|
3
|
-
|
|
4
|
-
# Initialize Celery app
|
|
5
|
-
celery_app = Celery(
|
|
6
|
-
settings.APP_NAME,
|
|
7
|
-
broker=settings.CELERY_BROKER_URL,
|
|
8
|
-
backend=settings.CELERY_RESULT_BACKEND,
|
|
9
|
-
)
|
|
10
|
-
|
|
11
|
-
# Configure Celery
|
|
12
|
-
celery_app.conf.update(
|
|
13
|
-
task_serializer="json",
|
|
14
|
-
accept_content=["json"],
|
|
15
|
-
result_serializer="json",
|
|
16
|
-
timezone="UTC",
|
|
17
|
-
enable_utc=True,
|
|
18
|
-
task_track_started=True,
|
|
19
|
-
task_time_limit=settings.CELERY_TASK_HARD_TIMEOUT,
|
|
20
|
-
task_soft_time_limit=settings.CELERY_TASK_TIMEOUT,
|
|
21
|
-
worker_prefetch_multiplier=4,
|
|
22
|
-
worker_max_tasks_per_child=1000,
|
|
23
|
-
)
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
@celery_app.task(bind=True, max_retries=settings.CELERY_MAX_RETRIES)
|
|
27
|
-
def debug_task(self):
|
|
28
|
-
"""Test task to verify Celery is working."""
|
|
29
|
-
print(f"Request: {self.request!r}")
|
|
30
|
-
return "Celery is working!"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|