mirror of
https://github.com/langgenius/dify.git
synced 2026-05-27 04:16:16 +08:00
240 lines
8.0 KiB
Python
240 lines
8.0 KiB
Python
import importlib.util
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from tests.pytest_dify import (
|
|
DEFAULT_LOG_FORMAT,
|
|
DockerComposeStack,
|
|
build_middleware_stack,
|
|
build_vdb_stack,
|
|
ensure_backend_test_environment,
|
|
ensure_compose_env_files,
|
|
parse_services,
|
|
)
|
|
|
|
|
|
def _load_api_conftest():
|
|
api_conftest_path = Path(__file__).resolve().parents[2] / "conftest.py"
|
|
spec = importlib.util.spec_from_file_location("api_root_conftest_for_tests", api_conftest_path)
|
|
assert spec is not None
|
|
assert spec.loader is not None
|
|
api_conftest = importlib.util.module_from_spec(spec)
|
|
sys.modules[spec.name] = api_conftest
|
|
spec.loader.exec_module(api_conftest)
|
|
return api_conftest
|
|
|
|
|
|
def test_ensure_backend_test_environment_uses_example_env_and_stable_logging(
|
|
tmp_path: Path,
|
|
monkeypatch,
|
|
):
|
|
repo_root = tmp_path
|
|
integration_tests_dir = repo_root / "api" / "tests" / "integration_tests"
|
|
integration_tests_dir.mkdir(parents=True)
|
|
env_example = integration_tests_dir / ".env.example"
|
|
env_example.write_text("LOG_LEVEL=INFO\n")
|
|
storage_root = repo_root / "storage"
|
|
|
|
monkeypatch.setenv("LOG_FORMAT", "json")
|
|
monkeypatch.delenv("LOG_OUTPUT_FORMAT", raising=False)
|
|
monkeypatch.delenv("DIFY_TEST_ENV_FILE", raising=False)
|
|
monkeypatch.delenv("DIFY_VDB_TEST_ENV_FILE", raising=False)
|
|
monkeypatch.setenv("OPENDAL_FS_ROOT", str(storage_root))
|
|
|
|
ensure_backend_test_environment(repo_root)
|
|
|
|
assert os.environ["DIFY_TEST_ENV_FILE"] == str(env_example)
|
|
assert "DIFY_VDB_TEST_ENV_FILE" not in os.environ
|
|
assert os.environ["LOG_OUTPUT_FORMAT"] == "text"
|
|
assert os.environ["LOG_FORMAT"] == DEFAULT_LOG_FORMAT
|
|
assert os.environ["STORAGE_TYPE"] == "opendal"
|
|
assert os.environ["OPENDAL_SCHEME"] == "fs"
|
|
assert storage_root.is_dir()
|
|
|
|
|
|
def test_ensure_compose_env_files_copies_missing_env_files(tmp_path: Path):
|
|
docker_dir = tmp_path / "docker"
|
|
envs_dir = docker_dir / "envs"
|
|
envs_dir.mkdir(parents=True)
|
|
(docker_dir / ".env.example").write_text("APP_WEB_URL=http://localhost\n")
|
|
(envs_dir / "middleware.env.example").write_text("DB_PASSWORD=difyai123456\n")
|
|
|
|
ensure_compose_env_files(tmp_path)
|
|
|
|
assert (docker_dir / ".env").read_text() == "APP_WEB_URL=http://localhost\n"
|
|
assert (docker_dir / "middleware.env").read_text() == "DB_PASSWORD=difyai123456\n"
|
|
|
|
|
|
def test_parse_services_discards_empty_items():
|
|
assert parse_services(" db_postgres, redis,, sandbox ") == ["db_postgres", "redis", "sandbox"]
|
|
|
|
|
|
def test_stack_up_uses_waiting_compose_command(monkeypatch, tmp_path: Path):
|
|
calls: list[list[str]] = []
|
|
|
|
def fake_run(args, **kwargs):
|
|
calls.append(args)
|
|
if args == ["docker", "compose", "up", "--help"]:
|
|
return subprocess.CompletedProcess(args=args, returncode=0, stdout="--wait\n--wait-timeout\n")
|
|
return subprocess.CompletedProcess(args=args, returncode=0)
|
|
|
|
monkeypatch.setattr(subprocess, "run", fake_run)
|
|
monkeypatch.setattr("time.sleep", lambda _: None)
|
|
|
|
stack = DockerComposeStack(
|
|
name="middleware",
|
|
project_name="dify-pytest-middleware",
|
|
repo_root=tmp_path,
|
|
compose_files=(tmp_path / "docker-compose.yaml",),
|
|
env_file=tmp_path / "middleware.env",
|
|
services=("db_postgres", "redis"),
|
|
)
|
|
|
|
stack.up()
|
|
|
|
assert calls == [
|
|
["docker", "compose", "up", "--help"],
|
|
[
|
|
"docker",
|
|
"compose",
|
|
"--project-name",
|
|
"dify-pytest-middleware",
|
|
"--env-file",
|
|
str(tmp_path / "middleware.env"),
|
|
"-f",
|
|
str(tmp_path / "docker-compose.yaml"),
|
|
"up",
|
|
"-d",
|
|
"--wait",
|
|
"--wait-timeout",
|
|
"180",
|
|
"db_postgres",
|
|
"redis",
|
|
],
|
|
]
|
|
|
|
|
|
def test_stack_up_skips_wait_flags_when_compose_help_omits_them(monkeypatch, tmp_path: Path):
|
|
calls: list[list[str]] = []
|
|
|
|
def fake_run(args, **kwargs):
|
|
calls.append(args)
|
|
if args == ["docker", "compose", "up", "--help"]:
|
|
return subprocess.CompletedProcess(args=args, returncode=0, stdout="Usage: docker compose up\n")
|
|
return subprocess.CompletedProcess(args=args, returncode=0)
|
|
|
|
monkeypatch.setattr(subprocess, "run", fake_run)
|
|
monkeypatch.setattr("time.sleep", lambda _: None)
|
|
|
|
stack = DockerComposeStack(
|
|
name="middleware",
|
|
project_name="dify-pytest-middleware",
|
|
repo_root=tmp_path,
|
|
compose_files=(tmp_path / "docker-compose.yaml",),
|
|
env_file=tmp_path / "middleware.env",
|
|
services=("db_postgres",),
|
|
)
|
|
|
|
stack.up()
|
|
|
|
assert calls[1][-3:] == ["up", "-d", "db_postgres"]
|
|
assert "--wait" not in calls[1]
|
|
assert "--wait-timeout" not in calls[1]
|
|
|
|
|
|
def test_builders_use_expected_compose_files(tmp_path: Path):
|
|
middleware = build_middleware_stack(tmp_path, ["db_postgres"])
|
|
vdb = build_vdb_stack(tmp_path, ["weaviate", "qdrant"])
|
|
|
|
assert middleware.compose_files == (tmp_path / "docker" / "docker-compose.middleware.yaml",)
|
|
assert middleware.env_file == tmp_path / "docker" / "middleware.env"
|
|
assert middleware.ready_delay_seconds == 5.0
|
|
assert vdb.compose_files == (
|
|
tmp_path / "docker" / "docker-compose.yaml",
|
|
tmp_path / "docker" / "docker-compose.pytest.ports.yaml",
|
|
)
|
|
assert vdb.env_file == tmp_path / "docker" / ".env"
|
|
assert vdb.profiles == ("weaviate", "qdrant")
|
|
|
|
|
|
def test_pytest_sessionstart_cleans_started_stacks_when_later_stack_fails(monkeypatch):
|
|
api_conftest = _load_api_conftest()
|
|
|
|
events: list[str] = []
|
|
|
|
class FakeStack:
|
|
def __init__(self, name: str, fail: bool = False) -> None:
|
|
self.name = name
|
|
self.fail = fail
|
|
|
|
def up(self) -> None:
|
|
events.append(f"{self.name}:up")
|
|
if self.fail:
|
|
raise RuntimeError(f"{self.name} failed")
|
|
|
|
def down(self) -> None:
|
|
events.append(f"{self.name}:down")
|
|
|
|
class FakeConfig:
|
|
stash: dict[object, list[FakeStack]]
|
|
|
|
def __init__(self) -> None:
|
|
self.stash = {}
|
|
|
|
def getoption(self, name: str) -> bool | str:
|
|
options: dict[str, bool | str] = {
|
|
"start_middleware": True,
|
|
"middleware_services": "db_postgres",
|
|
"start_vdb": True,
|
|
"vdb_services": "qdrant",
|
|
}
|
|
return options[name]
|
|
|
|
middleware_stack = FakeStack("middleware")
|
|
vdb_stack = FakeStack("vdb", fail=True)
|
|
monkeypatch.setattr(api_conftest, "ensure_compose_env_files", lambda _repo_root: None)
|
|
monkeypatch.setattr(api_conftest, "build_middleware_stack", lambda *_args: middleware_stack)
|
|
monkeypatch.setattr(api_conftest, "build_vdb_stack", lambda *_args: vdb_stack)
|
|
|
|
config = FakeConfig()
|
|
session = SimpleNamespace(config=config)
|
|
|
|
with pytest.raises(RuntimeError, match="vdb failed"):
|
|
api_conftest.pytest_sessionstart(session)
|
|
|
|
assert events == ["middleware:up", "vdb:up", "vdb:down", "middleware:down"]
|
|
assert config.stash[api_conftest._DIFY_COMPOSE_STACKS_KEY] == []
|
|
|
|
|
|
def test_stop_stacks_attempts_all_stacks_before_reporting_errors():
|
|
api_conftest = _load_api_conftest()
|
|
events: list[str] = []
|
|
|
|
class FakeStack:
|
|
def __init__(self, name: str, fail: bool = False) -> None:
|
|
self.name = name
|
|
self.fail = fail
|
|
|
|
def down(self) -> None:
|
|
events.append(f"{self.name}:down")
|
|
if self.fail:
|
|
raise RuntimeError(f"{self.name} failed")
|
|
|
|
with pytest.raises(BaseExceptionGroup) as exc_info:
|
|
api_conftest._stop_stacks(
|
|
[
|
|
FakeStack("middleware"),
|
|
FakeStack("vdb", fail=True),
|
|
FakeStack("extra"),
|
|
]
|
|
)
|
|
|
|
assert events == ["extra:down", "vdb:down", "middleware:down"]
|
|
assert len(exc_info.value.exceptions) == 1
|
|
assert "vdb failed" in str(exc_info.value.exceptions[0])
|