Skip to content

Commit bf508b0

Browse files
Refactor duplicate use Stackone API url and update tests
1 parent 6017b44 commit bf508b0

File tree

9 files changed

+60
-44
lines changed

9 files changed

+60
-44
lines changed

stackone_ai/constants.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# StackOne API base URL
2+
DEFAULT_BASE_URL: str = "https://api.stackone.com"
3+
14
# Hybrid search default weight for BM25 vs TF-IDF
25
# alpha=0.2 means: 20% BM25 + 80% TF-IDF
36
# This value was optimized through validation testing and provides

stackone_ai/feedback/tool.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@
66

77
from pydantic import BaseModel, Field, field_validator
88

9-
from ..models import (
9+
from stackone_ai.models import (
1010
ExecuteConfig,
1111
JsonDict,
1212
ParameterLocation,
1313
StackOneError,
1414
StackOneTool,
1515
ToolParameters,
1616
)
17+
from stackone_ai.constants import DEFAULT_BASE_URL
1718

1819

1920
class FeedbackInput(BaseModel):
@@ -147,7 +148,7 @@ def execute(
147148
def create_feedback_tool(
148149
api_key: str,
149150
account_id: str | None = None,
150-
base_url: str = "https://api.stackone.com",
151+
base_url: str = DEFAULT_BASE_URL,
151152
) -> FeedbackTool:
152153
"""
153154
Create a feedback collection tool.

stackone_ai/semantic_search.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@
5959
import httpx
6060
from pydantic import BaseModel
6161

62+
from stackone_ai.constants import DEFAULT_BASE_URL
63+
6264

6365
class SemanticSearchError(Exception):
6466
"""Raised when semantic search fails."""
@@ -103,7 +105,7 @@ class SemanticSearchClient:
103105
def __init__(
104106
self,
105107
api_key: str,
106-
base_url: str = "https://api.stackone.com",
108+
base_url: str = DEFAULT_BASE_URL,
107109
timeout: float = 30.0,
108110
) -> None:
109111
"""Initialize the semantic search client.

stackone_ai/toolset.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
SemanticSearchError,
2626
SemanticSearchResult,
2727
)
28+
from stackone_ai.constants import DEFAULT_BASE_URL
2829
from stackone_ai.utils.normalize import _normalize_action_name
2930

3031
logger = logging.getLogger("stackone.tools")
@@ -35,8 +36,6 @@
3536
_SDK_VERSION = metadata.version("stackone-ai")
3637
except metadata.PackageNotFoundError: # pragma: no cover - best-effort fallback when running from source
3738
_SDK_VERSION = "dev"
38-
39-
DEFAULT_BASE_URL = "https://api.stackone.com"
4039
_RPC_PARAMETER_LOCATIONS = {
4140
"action": ParameterLocation.BODY,
4241
"body": ParameterLocation.BODY,

tests/conftest.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
from __future__ import annotations
44

5+
# Test base URL - used instead of production URLs in all test mocks.
6+
# Since respx intercepts at the HTTP client level before DNS resolution,
7+
# any URL string works for matching; http://localhost avoids exposing
8+
# real infrastructure URLs.
9+
TEST_BASE_URL = "http://localhost"
10+
511
import os
612
import socket
713
import subprocess

tests/test_feedback.py

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212
from hypothesis import given, settings
1313
from hypothesis import strategies as st
1414

15+
from stackone_ai.constants import DEFAULT_BASE_URL
1516
from stackone_ai.feedback import create_feedback_tool
17+
from tests.conftest import TEST_BASE_URL
1618
from stackone_ai.models import StackOneError
1719

1820
# Hypothesis strategies for PBT
@@ -48,7 +50,7 @@ class TestFeedbackToolValidation:
4850

4951
def test_missing_required_fields(self) -> None:
5052
"""Test validation errors for missing required fields."""
51-
tool = create_feedback_tool(api_key="test_key")
53+
tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
5254

5355
with pytest.raises(StackOneError, match="account_id"):
5456
tool.execute({"feedback": "Great tools!", "tool_names": ["test_tool"]})
@@ -61,7 +63,7 @@ def test_missing_required_fields(self) -> None:
6163

6264
def test_empty_and_whitespace_validation(self) -> None:
6365
"""Test validation for empty and whitespace-only strings."""
64-
tool = create_feedback_tool(api_key="test_key")
66+
tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
6567

6668
with pytest.raises(StackOneError, match="non-empty"):
6769
tool.execute({"feedback": " ", "account_id": "acc_123456", "tool_names": ["test_tool"]})
@@ -77,7 +79,7 @@ def test_empty_and_whitespace_validation(self) -> None:
7779

7880
def test_multiple_account_ids_validation(self) -> None:
7981
"""Test validation with multiple account IDs."""
80-
tool = create_feedback_tool(api_key="test_key")
82+
tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
8183

8284
with pytest.raises(StackOneError, match="At least one account ID is required"):
8385
tool.execute({"feedback": "Great tools!", "account_id": [], "tool_names": ["test_tool"]})
@@ -87,7 +89,7 @@ def test_multiple_account_ids_validation(self) -> None:
8789

8890
def test_invalid_account_id_type(self) -> None:
8991
"""Test validation with invalid account ID type (not string or list)."""
90-
tool = create_feedback_tool(api_key="test_key")
92+
tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
9193

9294
# Pydantic validates input types before our custom validator runs
9395
with pytest.raises(StackOneError, match="(account_id|Input should be a valid)"):
@@ -100,7 +102,7 @@ def test_invalid_account_id_type(self) -> None:
100102

101103
def test_invalid_json_input(self) -> None:
102104
"""Test that invalid JSON input raises appropriate error."""
103-
tool = create_feedback_tool(api_key="test_key")
105+
tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
104106

105107
with pytest.raises(StackOneError, match="Invalid JSON"):
106108
tool.execute("not valid json {}")
@@ -112,7 +114,7 @@ def test_invalid_json_input(self) -> None:
112114
@settings(max_examples=50)
113115
def test_whitespace_feedback_validation_pbt(self, whitespace: str) -> None:
114116
"""PBT: Test validation for various whitespace patterns in feedback."""
115-
tool = create_feedback_tool(api_key="test_key")
117+
tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
116118

117119
with pytest.raises(StackOneError, match="non-empty"):
118120
tool.execute({"feedback": whitespace, "account_id": "acc_123456", "tool_names": ["test_tool"]})
@@ -121,7 +123,7 @@ def test_whitespace_feedback_validation_pbt(self, whitespace: str) -> None:
121123
@settings(max_examples=50)
122124
def test_whitespace_account_id_validation_pbt(self, whitespace: str) -> None:
123125
"""PBT: Test validation for various whitespace patterns in account_id."""
124-
tool = create_feedback_tool(api_key="test_key")
126+
tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
125127

126128
with pytest.raises(StackOneError, match="non-empty"):
127129
tool.execute({"feedback": "Great!", "account_id": whitespace, "tool_names": ["test_tool"]})
@@ -130,7 +132,7 @@ def test_whitespace_account_id_validation_pbt(self, whitespace: str) -> None:
130132
@settings(max_examples=50)
131133
def test_whitespace_tool_names_validation_pbt(self, whitespace_list: list[str]) -> None:
132134
"""PBT: Test validation for lists containing only whitespace tool names."""
133-
tool = create_feedback_tool(api_key="test_key")
135+
tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
134136

135137
with pytest.raises(StackOneError, match="At least one tool name"):
136138
tool.execute({"feedback": "Great!", "account_id": "acc_123456", "tool_names": whitespace_list})
@@ -141,7 +143,7 @@ def test_whitespace_tool_names_validation_pbt(self, whitespace_list: list[str])
141143
@settings(max_examples=50)
142144
def test_whitespace_account_ids_list_validation_pbt(self, whitespace_list: list[str]) -> None:
143145
"""PBT: Test validation for lists containing only whitespace account IDs."""
144-
tool = create_feedback_tool(api_key="test_key")
146+
tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
145147

146148
with pytest.raises(StackOneError, match="At least one valid account ID is required"):
147149
tool.execute(
@@ -156,17 +158,17 @@ def test_whitespace_account_ids_list_validation_pbt(self, whitespace_list: list[
156158
@settings(max_examples=50)
157159
def test_invalid_json_input_pbt(self, invalid_json: str) -> None:
158160
"""PBT: Test that various invalid JSON inputs raise appropriate error."""
159-
tool = create_feedback_tool(api_key="test_key")
161+
tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
160162

161163
with pytest.raises(StackOneError, match="Invalid JSON"):
162164
tool.execute(invalid_json)
163165

164166
@respx.mock
165167
def test_json_string_input(self) -> None:
166168
"""Test that JSON string input is properly parsed."""
167-
tool = create_feedback_tool(api_key="test_key")
169+
tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
168170

169-
route = respx.post("https://api.stackone.com/ai/tool-feedback").mock(
171+
route = respx.post(f"{TEST_BASE_URL}/ai/tool-feedback").mock(
170172
return_value=httpx.Response(200, json={"message": "Success"})
171173
)
172174

@@ -185,10 +187,10 @@ class TestFeedbackToolExecution:
185187
@respx.mock
186188
def test_single_account_execution(self) -> None:
187189
"""Test execution with single account ID."""
188-
tool = create_feedback_tool(api_key="test_key")
190+
tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
189191
api_response = {"message": "Feedback successfully stored", "trace_id": "test-trace-id"}
190192

191-
route = respx.post("https://api.stackone.com/ai/tool-feedback").mock(
193+
route = respx.post(f"{TEST_BASE_URL}/ai/tool-feedback").mock(
192194
return_value=httpx.Response(200, json=api_response)
193195
)
194196

@@ -213,10 +215,10 @@ def test_single_account_execution(self) -> None:
213215
@respx.mock
214216
def test_call_method_interface(self) -> None:
215217
"""Test that the .call() method works correctly."""
216-
tool = create_feedback_tool(api_key="test_key")
218+
tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
217219
api_response = {"message": "Success", "trace_id": "test-trace-id"}
218220

219-
route = respx.post("https://api.stackone.com/ai/tool-feedback").mock(
221+
route = respx.post(f"{TEST_BASE_URL}/ai/tool-feedback").mock(
220222
return_value=httpx.Response(200, json=api_response)
221223
)
222224

@@ -234,9 +236,9 @@ def test_call_method_interface(self) -> None:
234236
@respx.mock
235237
def test_api_error_handling(self) -> None:
236238
"""Test that API errors are handled properly."""
237-
tool = create_feedback_tool(api_key="test_key")
239+
tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
238240

239-
route = respx.post("https://api.stackone.com/ai/tool-feedback").mock(
241+
route = respx.post(f"{TEST_BASE_URL}/ai/tool-feedback").mock(
240242
return_value=httpx.Response(401, json={"error": "Unauthorized"})
241243
)
242244

@@ -255,11 +257,11 @@ def test_api_error_handling(self) -> None:
255257
@respx.mock
256258
def test_multiple_account_ids_execution(self) -> None:
257259
"""Test execution with multiple account IDs - both success and mixed scenarios."""
258-
tool = create_feedback_tool(api_key="test_key")
260+
tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
259261
api_response = {"message": "Feedback successfully stored", "trace_id": "test-trace-id"}
260262

261263
# Test all successful case
262-
route = respx.post("https://api.stackone.com/ai/tool-feedback").mock(
264+
route = respx.post(f"{TEST_BASE_URL}/ai/tool-feedback").mock(
263265
return_value=httpx.Response(200, json=api_response)
264266
)
265267

@@ -302,7 +304,7 @@ def test_multiple_account_ids_execution(self) -> None:
302304
@respx.mock
303305
def test_multiple_account_ids_mixed_success(self) -> None:
304306
"""Test execution with multiple account IDs - mixed success and error."""
305-
tool = create_feedback_tool(api_key="test_key")
307+
tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
306308

307309
def custom_side_effect(request: httpx.Request) -> httpx.Response:
308310
body = json.loads(request.content)
@@ -312,7 +314,7 @@ def custom_side_effect(request: httpx.Request) -> httpx.Response:
312314
else:
313315
return httpx.Response(401, json={"error": "Unauthorized"})
314316

315-
route = respx.post("https://api.stackone.com/ai/tool-feedback").mock(side_effect=custom_side_effect)
317+
route = respx.post(f"{TEST_BASE_URL}/ai/tool-feedback").mock(side_effect=custom_side_effect)
316318

317319
result = tool.execute(
318320
{
@@ -338,7 +340,7 @@ def custom_side_effect(request: httpx.Request) -> httpx.Response:
338340
"status": "error",
339341
"error": (
340342
"Client error '401 Unauthorized' for url "
341-
"'https://api.stackone.com/ai/tool-feedback'\n"
343+
f"'{TEST_BASE_URL}/ai/tool-feedback'\n"
342344
"For more information check: "
343345
"https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401"
344346
),
@@ -351,7 +353,7 @@ def custom_side_effect(request: httpx.Request) -> httpx.Response:
351353

352354
def test_tool_integration(self) -> None:
353355
"""Test that feedback tool integrates properly with toolset."""
354-
feedback_tool = create_feedback_tool(api_key="test_key")
356+
feedback_tool = create_feedback_tool(api_key="test_key", base_url=TEST_BASE_URL)
355357

356358
assert feedback_tool is not None
357359
assert feedback_tool.name == "tool_feedback"
@@ -376,7 +378,7 @@ def test_live_feedback_submission() -> None:
376378
if not api_key:
377379
pytest.skip("STACKONE_API_KEY env var required for live feedback test")
378380

379-
base_url = os.getenv("STACKONE_BASE_URL", "https://api.stackone.com")
381+
base_url = os.getenv("STACKONE_BASE_URL", DEFAULT_BASE_URL)
380382

381383
feedback_tool = create_feedback_tool(api_key=api_key, base_url=base_url)
382384
assert feedback_tool is not None, "Feedback tool must be available"

tests/test_semantic_search.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import httpx
88
import pytest
99

10+
from stackone_ai.constants import DEFAULT_BASE_URL
1011
from stackone_ai.semantic_search import (
1112
SemanticSearchClient,
1213
SemanticSearchError,
@@ -75,7 +76,7 @@ def test_init(self) -> None:
7576
client = SemanticSearchClient(api_key="test-key")
7677

7778
assert client.api_key == "test-key"
78-
assert client.base_url == "https://api.stackone.com"
79+
assert client.base_url == DEFAULT_BASE_URL
7980
assert client.timeout == 30.0
8081

8182
def test_init_custom_base_url(self) -> None:

0 commit comments

Comments
 (0)