Skip to content

Commit 1f14147

Browse files
committed
Merge branch 'main' into hybim-717
2 parents 1ed427c + 9fe5710 commit 1f14147

10 files changed

Lines changed: 98 additions & 20 deletions

File tree

galileo-a2a/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ python_files = ["test_*.py"]
6969
python_classes = ["Test*"]
7070
python_functions = ["test_*"]
7171
env = [
72-
"GALILEO_CONSOLE_URL=http://localtest:8088",
72+
"GALILEO_CONSOLE_URL=http://fake.test:8088",
7373
"GALILEO_API_KEY=api-1234567890",
7474
"GALILEO_PROJECT=test-project",
7575
"GALILEO_LOG_STREAM=test-log-stream",

galileo-a2a/tests/conftest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# 3. Security - prevents real API keys from leaking into test logs
1010
import os
1111

12-
os.environ["GALILEO_CONSOLE_URL"] = "http://localtest:8088"
12+
os.environ["GALILEO_CONSOLE_URL"] = "http://fake.test:8088"
1313
os.environ["GALILEO_API_KEY"] = "api-1234567890"
1414
os.environ["GALILEO_PROJECT"] = "test-project"
1515
os.environ["GALILEO_LOG_STREAM"] = "test-log-stream"

galileo-adk/pyproject.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,16 @@ packages = ["src/galileo_adk"]
3434

3535
[tool.pytest.ini_options]
3636
testpaths = ["tests"]
37-
pythonpath = ["src"]
37+
# ".." puts the monorepo root on sys.path so tests can import the shared
38+
# `test_support` helper package (see test_support/config.py).
39+
pythonpath = ["src", ".."]
3840
asyncio_default_fixture_loop_scope = "function"
3941
asyncio_mode = "auto"
4042
python_files = ["test_*.py"]
4143
python_classes = ["Test*"]
4244
python_functions = ["test_*"]
4345
env = [
44-
"GALILEO_CONSOLE_URL=http://localtest:8088",
46+
"GALILEO_CONSOLE_URL=http://fake.test:8088",
4547
"GALILEO_API_KEY=api-1234567890",
4648
"GALILEO_PROJECT=test-project",
4749
"GALILEO_LOG_STREAM=test-log-stream",

galileo-adk/tests/conftest.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from galileo_core.constants.routes import Routes as CoreRoutes
99
from galileo_core.schemas.core.user import User
1010
from galileo_core.schemas.core.user_role import UserRole
11+
from test_support.config import fast_config_validation
1112
from splunk_ao.config import GalileoPythonConfig
1213
from splunk_ao.utils.singleton import GalileoLoggerSingleton
1314

@@ -167,7 +168,11 @@ def set_validated_config(
167168
# Reset any cached loggers from previous tests
168169
GalileoLoggerSingleton().reset_all()
169170

170-
config = GalileoPythonConfig.get(console_url="http://localtest:8088", api_key="api-1234567890")
171+
# Bypass the slow async validation round-trips for the build only; the
172+
# endpoints are already mocked above, so this only removes event-loop cost
173+
# (notably the ~11x slower Windows IOCP poll on Python 3.11+).
174+
with fast_config_validation():
175+
config = GalileoPythonConfig.get(console_url="http://fake.test:8088", api_key="api-1234567890")
171176
yield
172177
# Clean up after test
173178
GalileoLoggerSingleton().reset_all()

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ pythonpath = ["./src/"]
8989
# Note: Some env vars are also set in conftest.py for pytest-xdist compatibility
9090
# on Python 3.14+. This section remains for documentation and older Python support.
9191
env = [
92-
"GALILEO_CONSOLE_URL=http://localtest:8088",
92+
"GALILEO_CONSOLE_URL=http://fake.test:8088",
9393
"GALILEO_API_KEY=api-1234567890",
9494
"GALILEO_PROJECT=test-project",
9595
"GALILEO_LOG_STREAM=test-log-stream",

test_support/__init__.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
"""Repo-level test-support helpers shared across the main package and the
2+
sibling integration packages (galileo-adk, galileo-a2a).
3+
4+
This package is intentionally NOT shipped (it lives outside ``src/``) and is
5+
not a pytest test package — it only holds importable helpers. It sits at the
6+
repo root, rather than under ``tests/``, so the sibling packages (which have
7+
their own top-level ``tests`` package) can import it without a package-name
8+
collision.
9+
"""

test_support/config.py

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Fast GalileoPythonConfig validation for tests.
2+
3+
Building ``GalileoPythonConfig`` runs 3 async validation requests
4+
(healthcheck/login/current_user) through galileo_core's ``async_run`` /
5+
``EventLoopThreadPool``, whose Windows IOCP poll is ~11x slower on Python
6+
3.11+. In tests these endpoints are already mocked, so they add no coverage —
7+
only event-loop cost. ``fast_config_validation`` replaces them with canned,
8+
await-free results so the per-test config build is trivial.
9+
10+
Scope the context manager to the per-test config build only; test bodies still
11+
exercise the real validation/connect code with their own mocks.
12+
13+
Shared by the autouse ``set_validated_config`` fixtures in the main package and
14+
in galileo-adk.
15+
"""
16+
17+
from collections.abc import Generator
18+
from contextlib import contextmanager
19+
from typing import Any
20+
from unittest.mock import patch
21+
from uuid import uuid4
22+
23+
from galileo_core.helpers.api_client import ApiClient
24+
from galileo_core.schemas.core.user import User
25+
from galileo_core.schemas.core.user_role import UserRole
26+
27+
28+
def fast_validation_payload(endpoint: Any) -> dict:
29+
"""Canned response for the 3 config-validation endpoints."""
30+
ep = str(endpoint)
31+
if "login" in ep or "token" in ep:
32+
return {"access_token": "secret_jwt_token"}
33+
if "current_user" in ep:
34+
return User.model_validate({"id": uuid4(), "email": "user@example.com", "role": UserRole.user}).model_dump(
35+
mode="json"
36+
)
37+
return {"status": "ok"}
38+
39+
40+
@contextmanager
41+
def fast_config_validation() -> Generator[None, None, None]:
42+
"""Stub the async config-validation round-trips with canned, await-free
43+
results so the per-test ``GalileoPythonConfig`` build is cheap.
44+
45+
Scoped to the config build only; test bodies still exercise the real
46+
validation/connect code.
47+
"""
48+
49+
async def _stub_make_request(request_method: Any, base_url: str, endpoint: Any, **kwargs: Any) -> dict:
50+
return fast_validation_payload(endpoint)
51+
52+
def _stub_request(self: Any, request_method: Any, path: Any = None, **kwargs: Any) -> dict:
53+
return fast_validation_payload(path)
54+
55+
with (
56+
patch.object(ApiClient, "make_request", staticmethod(_stub_make_request)),
57+
patch.object(ApiClient, "request", _stub_request),
58+
):
59+
yield

tests/conftest.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
)
2323
from openai.types.responses.response_usage import InputTokensDetails, OutputTokensDetails
2424

25-
_os.environ["SPLUNK_AO_CONSOLE_URL"] = "http://localtest:8088"
25+
_os.environ["SPLUNK_AO_CONSOLE_URL"] = "http://fake.test:8088"
2626
_os.environ["SPLUNK_AO_API_KEY"] = "api-1234567890"
2727
_os.environ["SPLUNK_AO_PROJECT"] = "test-project"
2828
_os.environ["SPLUNK_AO_LOG_STREAM"] = "test-log-stream"
@@ -50,6 +50,7 @@
5050

5151
from httpx import Request # noqa: E402
5252
from httpx import Response as HttpxResponse # noqa: E402
53+
from test_support.config import fast_config_validation # noqa: E402
5354

5455
from galileo.resources.models import DatasetContent, DatasetRow, DatasetRowValuesDict # noqa: E402
5556
from galileo.resources.models.messages_list_item import MessagesListItem # noqa: E402
@@ -125,8 +126,10 @@ def set_validated_config(
125126
if GalileoPythonConfig._instance is not None:
126127
GalileoPythonConfig._instance.reset()
127128
# Initialize config with EXPLICIT values to avoid env var timing issues with pytest-xdist
128-
# This ensures correct config even if env vars weren't set before module imports
129-
config = GalileoPythonConfig.get(console_url="http://localtest:8088", api_key="api-1234567890")
129+
# This ensures correct config even if env vars weren't set before module imports.
130+
# Bypass the slow async validation round-trips for the build only.
131+
with fast_config_validation():
132+
config = GalileoPythonConfig.get(console_url="http://fake.test:8088", api_key="api-1234567890")
130133
yield
131134
config.reset()
132135

tests/test_experiments.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,7 @@ def test_run_experiment_without_metrics(
674674
prompt_settings=ANY,
675675
)
676676

677-
@pytest.mark.parametrize("console_url", ["http://localtest:8088", "http://localtest:8088/"])
677+
@pytest.mark.parametrize("console_url", ["http://fake.test:8088", "http://fake.test:8088/"])
678678
@travel(datetime(2012, 1, 1), tick=False)
679679
@patch.object(splunk_ao.datasets.Datasets, "get")
680680
@patch.object(splunk_ao.jobs.Jobs, "create")

tests/test_prompts_global.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ class TestGlobalPromptTemplates:
6262
def test_create_global_prompt(self, respx_mock: MockRouter, prompt_template_response):
6363
"""Test creating a global prompt template."""
6464
# Mock the query API (for uniqueness check)
65-
query_route = respx_mock.post("http://localtest:8088/templates/query").mock(
65+
query_route = respx_mock.post("http://fake.test:8088/templates/query").mock(
6666
return_value=httpx.Response(200, json={"templates": []})
6767
)
6868

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

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

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

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

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

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

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

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

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

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

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

0 commit comments

Comments
 (0)