Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion galileo-a2a/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
env = [
"GALILEO_CONSOLE_URL=http://localtest:8088",
"GALILEO_CONSOLE_URL=http://fake.test:8088",
"GALILEO_API_KEY=api-1234567890",
"GALILEO_PROJECT=test-project",
"GALILEO_LOG_STREAM=test-log-stream",
Expand Down
2 changes: 1 addition & 1 deletion galileo-a2a/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# 3. Security - prevents real API keys from leaking into test logs
import os

os.environ["GALILEO_CONSOLE_URL"] = "http://localtest:8088"
os.environ["GALILEO_CONSOLE_URL"] = "http://fake.test:8088"
os.environ["GALILEO_API_KEY"] = "api-1234567890"
os.environ["GALILEO_PROJECT"] = "test-project"
os.environ["GALILEO_LOG_STREAM"] = "test-log-stream"
Expand Down
6 changes: 4 additions & 2 deletions galileo-adk/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,16 @@ packages = ["src/galileo_adk"]

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]
# ".." puts the monorepo root on sys.path so tests can import the shared
# `test_support` helper package (see test_support/config.py).
pythonpath = ["src", ".."]
asyncio_default_fixture_loop_scope = "function"
asyncio_mode = "auto"
python_files = ["test_*.py"]
python_classes = ["Test*"]
python_functions = ["test_*"]
env = [
"GALILEO_CONSOLE_URL=http://localtest:8088",
"GALILEO_CONSOLE_URL=http://fake.test:8088",
"GALILEO_API_KEY=api-1234567890",
"GALILEO_PROJECT=test-project",
"GALILEO_LOG_STREAM=test-log-stream",
Expand Down
7 changes: 6 additions & 1 deletion galileo-adk/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from galileo_core.constants.routes import Routes as CoreRoutes
from galileo_core.schemas.core.user import User
from galileo_core.schemas.core.user_role import UserRole
from test_support.config import fast_config_validation

from galileo.config import GalileoPythonConfig
from galileo.utils.singleton import GalileoLoggerSingleton
Expand Down Expand Up @@ -168,7 +169,11 @@ def set_validated_config(
# Reset any cached loggers from previous tests
GalileoLoggerSingleton().reset_all()

config = GalileoPythonConfig.get(console_url="http://localtest:8088", api_key="api-1234567890")
# Bypass the slow async validation round-trips for the build only; the
# endpoints are already mocked above, so this only removes event-loop cost
# (notably the ~11x slower Windows IOCP poll on Python 3.11+).
with fast_config_validation():
config = GalileoPythonConfig.get(console_url="http://fake.test:8088", api_key="api-1234567890")
yield
# Clean up after test
GalileoLoggerSingleton().reset_all()
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ pythonpath = ["./src/"]
# Note: Some env vars are also set in conftest.py for pytest-xdist compatibility
# on Python 3.14+. This section remains for documentation and older Python support.
env = [
"GALILEO_CONSOLE_URL=http://localtest:8088",
"GALILEO_CONSOLE_URL=http://fake.test:8088",
"GALILEO_API_KEY=api-1234567890",
"GALILEO_PROJECT=test-project",
"GALILEO_LOG_STREAM=test-log-stream",
Expand Down
9 changes: 9 additions & 0 deletions test_support/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Repo-level test-support helpers shared across the main package and the
sibling integration packages (galileo-adk, galileo-a2a).

This package is intentionally NOT shipped (it lives outside ``src/``) and is
not a pytest test package — it only holds importable helpers. It sits at the
repo root, rather than under ``tests/``, so the sibling packages (which have
their own top-level ``tests`` package) can import it without a package-name
collision.
"""
59 changes: 59 additions & 0 deletions test_support/config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
"""Fast GalileoPythonConfig validation for tests.

Building ``GalileoPythonConfig`` runs 3 async validation requests
(healthcheck/login/current_user) through galileo_core's ``async_run`` /
``EventLoopThreadPool``, whose Windows IOCP poll is ~11x slower on Python
3.11+. In tests these endpoints are already mocked, so they add no coverage —
only event-loop cost. ``fast_config_validation`` replaces them with canned,
await-free results so the per-test config build is trivial.

Scope the context manager to the per-test config build only; test bodies still
exercise the real validation/connect code with their own mocks.

Shared by the autouse ``set_validated_config`` fixtures in the main package and
in galileo-adk.
"""

from collections.abc import Generator
from contextlib import contextmanager
from typing import Any
from unittest.mock import patch
from uuid import uuid4

from galileo_core.helpers.api_client import ApiClient
from galileo_core.schemas.core.user import User
from galileo_core.schemas.core.user_role import UserRole


def fast_validation_payload(endpoint: Any) -> dict:
"""Canned response for the 3 config-validation endpoints."""
ep = str(endpoint)
if "login" in ep or "token" in ep:
return {"access_token": "secret_jwt_token"}
if "current_user" in ep:
return User.model_validate({"id": uuid4(), "email": "user@example.com", "role": UserRole.user}).model_dump(
mode="json"
)
return {"status": "ok"}


@contextmanager
def fast_config_validation() -> Generator[None, None, None]:
"""Stub the async config-validation round-trips with canned, await-free
results so the per-test ``GalileoPythonConfig`` build is cheap.

Scoped to the config build only; test bodies still exercise the real
validation/connect code.
"""

async def _stub_make_request(request_method: Any, base_url: str, endpoint: Any, **kwargs: Any) -> dict:
return fast_validation_payload(endpoint)

def _stub_request(self: Any, request_method: Any, path: Any = None, **kwargs: Any) -> dict:
return fast_validation_payload(path)

with (
patch.object(ApiClient, "make_request", staticmethod(_stub_make_request)),
patch.object(ApiClient, "request", _stub_request),
):
yield
9 changes: 6 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
)
from openai.types.responses.response_usage import InputTokensDetails, OutputTokensDetails

_os.environ["SPLUNK_AO_CONSOLE_URL"] = "http://localtest:8088"
_os.environ["SPLUNK_AO_CONSOLE_URL"] = "http://fake.test:8088"
_os.environ["SPLUNK_AO_API_KEY"] = "api-1234567890"
_os.environ["SPLUNK_AO_PROJECT"] = "test-project"
_os.environ["SPLUNK_AO_LOG_STREAM"] = "test-log-stream"
Expand Down Expand Up @@ -50,6 +50,7 @@

from httpx import Request # noqa: E402
from httpx import Response as HttpxResponse # noqa: E402
from test_support.config import fast_config_validation # noqa: E402

from galileo.collaborator import CollaboratorRole # noqa: E402
from galileo.config import GalileoPythonConfig # noqa: E402
Expand Down Expand Up @@ -125,8 +126,10 @@ def set_validated_config(
if GalileoPythonConfig._instance is not None:
GalileoPythonConfig._instance.reset()
# Initialize config with EXPLICIT values to avoid env var timing issues with pytest-xdist
# This ensures correct config even if env vars weren't set before module imports
config = GalileoPythonConfig.get(console_url="http://localtest:8088", api_key="api-1234567890")
# This ensures correct config even if env vars weren't set before module imports.
# Bypass the slow async validation round-trips for the build only.
with fast_config_validation():
config = GalileoPythonConfig.get(console_url="http://fake.test:8088", api_key="api-1234567890")
yield
config.reset()

Expand Down
2 changes: 1 addition & 1 deletion tests/test_experiments.py
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ def test_run_experiment_without_metrics(
prompt_settings=ANY,
)

@pytest.mark.parametrize("console_url", ["http://localtest:8088", "http://localtest:8088/"])
@pytest.mark.parametrize("console_url", ["http://fake.test:8088", "http://fake.test:8088/"])
@travel(datetime(2012, 1, 1), tick=False)
@patch.object(galileo.datasets.Datasets, "get")
@patch.object(galileo.jobs.Jobs, "create")
Expand Down
20 changes: 10 additions & 10 deletions tests/test_prompts_global.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,12 @@ class TestGlobalPromptTemplates:
def test_create_global_prompt(self, respx_mock: MockRouter, prompt_template_response):
"""Test creating a global prompt template."""
# Mock the query API (for uniqueness check)
query_route = respx_mock.post("http://localtest:8088/templates/query").mock(
query_route = respx_mock.post("http://fake.test:8088/templates/query").mock(
return_value=httpx.Response(200, json={"templates": []})
)

# Mock the create API
create_route = respx_mock.post("http://localtest:8088/templates").mock(
create_route = respx_mock.post("http://fake.test:8088/templates").mock(
return_value=httpx.Response(200, json=prompt_template_response)
)

Expand All @@ -80,7 +80,7 @@ def test_create_global_prompt(self, respx_mock: MockRouter, prompt_template_resp

def test_get_global_prompt_by_id(self, respx_mock: MockRouter, prompt_template_response):
"""Test retrieving a global prompt template by ID."""
get_route = respx_mock.get(f"http://localtest:8088/templates/{prompt_template_response['id']}").mock(
get_route = respx_mock.get(f"http://fake.test:8088/templates/{prompt_template_response['id']}").mock(
return_value=httpx.Response(200, json=prompt_template_response)
)

Expand All @@ -92,7 +92,7 @@ def test_get_global_prompt_by_id(self, respx_mock: MockRouter, prompt_template_r

def test_get_global_prompt_by_name(self, respx_mock: MockRouter, prompt_template_response):
"""Test retrieving a global prompt template by name."""
query_route = respx_mock.post("http://localtest:8088/templates/query").mock(
query_route = respx_mock.post("http://fake.test:8088/templates/query").mock(
return_value=httpx.Response(
200, json={"templates": [prompt_template_response], "next_starting_token": None}
)
Expand All @@ -106,7 +106,7 @@ def test_get_global_prompt_by_name(self, respx_mock: MockRouter, prompt_template

def test_list_global_prompts(self, respx_mock: MockRouter, prompt_template_response):
"""Test listing global prompt templates."""
query_route = respx_mock.post("http://localtest:8088/templates/query").mock(
query_route = respx_mock.post("http://fake.test:8088/templates/query").mock(
return_value=httpx.Response(
200, json={"templates": [prompt_template_response], "next_starting_token": None}
)
Expand All @@ -120,7 +120,7 @@ def test_list_global_prompts(self, respx_mock: MockRouter, prompt_template_respo

def test_delete_global_prompt_by_id(self, respx_mock: MockRouter):
"""Test deleting a global prompt template by ID."""
delete_route = respx_mock.delete("http://localtest:8088/templates/template-id-123").mock(
delete_route = respx_mock.delete("http://fake.test:8088/templates/template-id-123").mock(
return_value=httpx.Response(200, json={"message": "Template deleted successfully"})
)

Expand All @@ -131,14 +131,14 @@ def test_delete_global_prompt_by_id(self, respx_mock: MockRouter):
def test_delete_global_prompt_by_name(self, respx_mock: MockRouter, prompt_template_response):
"""Test deleting a global prompt template by name."""
# Mock query to find template by name
query_route = respx_mock.post("http://localtest:8088/templates/query").mock(
query_route = respx_mock.post("http://fake.test:8088/templates/query").mock(
return_value=httpx.Response(
200, json={"templates": [prompt_template_response], "next_starting_token": None}
)
)

# Mock delete
delete_route = respx_mock.delete(f"http://localtest:8088/templates/{prompt_template_response['id']}").mock(
delete_route = respx_mock.delete(f"http://fake.test:8088/templates/{prompt_template_response['id']}").mock(
return_value=httpx.Response(200, json={"message": "Template deleted successfully"})
)

Expand All @@ -151,13 +151,13 @@ def test_create_prompt_with_unique_name(self, respx_mock: MockRouter, prompt_tem
"""Test that duplicate names get auto-incremented."""
# Mock query to find existing template
existing_template = {**prompt_template_response, "name": "test-template"}
query_route = respx_mock.post("http://localtest:8088/templates/query").mock(
query_route = respx_mock.post("http://fake.test:8088/templates/query").mock(
return_value=httpx.Response(200, json={"templates": [existing_template], "next_starting_token": None})
)

# Mock create with new unique name
new_template = {**prompt_template_response, "name": "test-template (1)"}
create_route = respx_mock.post("http://localtest:8088/templates").mock(
create_route = respx_mock.post("http://fake.test:8088/templates").mock(
return_value=httpx.Response(200, json=new_template)
)

Expand Down
Loading