mirror of
https://github.com/Skyvern-AI/skyvern.git
synced 2026-04-28 11:40:32 +00:00
140 lines
4.3 KiB
Python
140 lines
4.3 KiB
Python
"""Regression tests for the embedded mode bootstrap boundary.
|
|
|
|
These tests verify hermetic isolation between embedded mode and cloud modules.
|
|
They do NOT require an LLM key or Playwright — they test infrastructure only.
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
def test_no_cloud_in_sys_modules() -> None:
|
|
"""Importing api_app must not trigger cloud/__init__.py in a clean process.
|
|
|
|
In a mixed test suite where scenario conftest already imported cloud,
|
|
this verifies the import itself doesn't RE-trigger cloud. In a clean
|
|
process (SDK user), this verifies cloud is never loaded.
|
|
"""
|
|
import importlib
|
|
|
|
cloud_before = "cloud" in sys.modules
|
|
if "skyvern.forge.api_app" in sys.modules:
|
|
importlib.reload(sys.modules["skyvern.forge.api_app"])
|
|
else:
|
|
import skyvern.forge.api_app # noqa: F401
|
|
|
|
cloud_after = "cloud" in sys.modules
|
|
if not cloud_before:
|
|
assert not cloud_after, (
|
|
f"cloud was loaded by importing api_app. Modules: {[k for k in sys.modules if k.startswith('cloud')]}"
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_bootstrap_creates_db() -> None:
|
|
"""Embedded bootstrap creates SQLite tables, org, and token."""
|
|
from skyvern import Skyvern
|
|
|
|
skyvern = Skyvern.local(use_in_memory_db=True)
|
|
try:
|
|
workflows = await skyvern.get_workflows()
|
|
assert workflows is not None
|
|
finally:
|
|
await skyvern.aclose()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_artifact_tempdir_is_live() -> None:
|
|
"""StorageFactory points to an existing temp directory after bootstrap."""
|
|
from skyvern import Skyvern
|
|
from skyvern.library.embedded_server_factory import EmbeddedClient
|
|
|
|
skyvern = Skyvern.local(use_in_memory_db=True)
|
|
try:
|
|
await skyvern.get_workflows()
|
|
client = getattr(skyvern, "_embedded_client", None)
|
|
assert isinstance(client, EmbeddedClient)
|
|
artifact_dir = client.embedded_transport._artifact_dir
|
|
assert artifact_dir is not None
|
|
assert Path(artifact_dir).exists()
|
|
assert "skyvern-artifacts-" in artifact_dir
|
|
finally:
|
|
await skyvern.aclose()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_unique_llm_key_no_collision() -> None:
|
|
"""Sequential clients (create -> close -> create again) use different LLM keys."""
|
|
from skyvern import Skyvern
|
|
from skyvern.forge.sdk.api.llm.models import LLMConfig
|
|
|
|
config = LLMConfig(
|
|
model_name="gpt-4o-mini",
|
|
required_env_vars=[],
|
|
supports_vision=True,
|
|
add_assistant_prefix=False,
|
|
)
|
|
|
|
skyvern1 = Skyvern.local(use_in_memory_db=True, llm_config=config)
|
|
try:
|
|
await skyvern1.get_workflows()
|
|
finally:
|
|
await skyvern1.aclose()
|
|
|
|
# Second client should NOT raise DuplicateLLMConfigError
|
|
skyvern2 = Skyvern.local(use_in_memory_db=True, llm_config=config)
|
|
try:
|
|
await skyvern2.get_workflows()
|
|
finally:
|
|
await skyvern2.aclose()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_second_client_fails_fast() -> None:
|
|
"""Creating a second embedded client WHILE the first is still open raises."""
|
|
from skyvern import Skyvern
|
|
|
|
skyvern1 = Skyvern.local(use_in_memory_db=True)
|
|
try:
|
|
with pytest.raises(RuntimeError, match="already active"):
|
|
Skyvern.local(use_in_memory_db=True)
|
|
finally:
|
|
await skyvern1.aclose()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_close_without_bootstrap() -> None:
|
|
"""aclose() on a client that never made a request is a no-op."""
|
|
from skyvern import Skyvern
|
|
|
|
skyvern = Skyvern.local(use_in_memory_db=True)
|
|
await skyvern.aclose()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_double_close_idempotent() -> None:
|
|
"""Calling aclose() twice does not raise."""
|
|
from skyvern import Skyvern
|
|
|
|
skyvern = Skyvern.local(use_in_memory_db=True)
|
|
try:
|
|
await skyvern.get_workflows()
|
|
finally:
|
|
await skyvern.aclose()
|
|
await skyvern.aclose()
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_blocked_settings_rejected() -> None:
|
|
"""Attempting to override OTEL_ENABLED or ENABLE_CLEANUP_CRON raises ValueError."""
|
|
from skyvern import Skyvern
|
|
|
|
skyvern = Skyvern.local(use_in_memory_db=True, settings={"OTEL_ENABLED": True})
|
|
try:
|
|
# Validation happens on first request (lazy bootstrap)
|
|
with pytest.raises(ValueError, match="Cannot override"):
|
|
await skyvern.get_workflows()
|
|
finally:
|
|
await skyvern.aclose()
|