mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-11-04 04:02:21 +01:00
test(harness): isolate imports and stabilize browser and DNS fixtures
This commit is contained in:
+5
-7
@@ -112,8 +112,7 @@ def _serve_test_static():
|
|||||||
sock.close()
|
sock.close()
|
||||||
|
|
||||||
if is_bound:
|
if is_bound:
|
||||||
yield
|
raise RuntimeError("port 7011 is already in use; browser tests require this worktree's static server")
|
||||||
return
|
|
||||||
|
|
||||||
root_dir = Path(__file__).resolve().parent.parent
|
root_dir = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
@@ -134,12 +133,11 @@ def _serve_test_static():
|
|||||||
class _Server(socketserver.TCPServer):
|
class _Server(socketserver.TCPServer):
|
||||||
allow_reuse_address = True
|
allow_reuse_address = True
|
||||||
|
|
||||||
|
server = _Server(("127.0.0.1", 7011), _Handler)
|
||||||
|
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||||
|
thread.start()
|
||||||
try:
|
try:
|
||||||
server = _Server(("127.0.0.1", 7011), _Handler)
|
|
||||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
||||||
thread.start()
|
|
||||||
yield
|
yield
|
||||||
|
finally:
|
||||||
server.shutdown()
|
server.shutdown()
|
||||||
server.server_close()
|
server.server_close()
|
||||||
except Exception:
|
|
||||||
yield
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ export async function loadMarkdown() {
|
|||||||
globalThis.MutationObserver = class { observe() {} };
|
globalThis.MutationObserver = class { observe() {} };
|
||||||
|
|
||||||
let src = fs.readFileSync(path.join(REPO, 'static/js/markdown.js'), 'utf8');
|
let src = fs.readFileSync(path.join(REPO, 'static/js/markdown.js'), 'utf8');
|
||||||
src = src.replace(/import uiModule from ['"]\.\/ui\.js(?:\?[^'"]*)?['"];?/, '');
|
src = src.replace(/import uiModule from ['"]\.\/ui\.js(?:[?#][^'"]*)?['"];?/, '');
|
||||||
src = src.replace(
|
src = src.replace(
|
||||||
/import \{ splitTableRow \} from ['"]\.\/markdown\/tableRow\.js['"];/,
|
/import \{ splitTableRow \} from ['"]\.\/markdown\/tableRow\.js['"];/,
|
||||||
() => `function splitTableRow(row){return (row||'').replace(/^\\s*\\|/,'').replace(/\\|\\s*$/,'').split('|').map((c)=>c.trim());}`,
|
() => `function splitTableRow(row){return (row||'').replace(/^\\s*\\|/,'').replace(/\\|\\s*$/,'').split('|').map((c)=>c.trim());}`,
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ def test_omitted_memory_survives_only_explicit_drop(monkeypatch):
|
|||||||
monkeypatch.setattr(src.memory, "MemoryManager", _FakeMM)
|
monkeypatch.setattr(src.memory, "MemoryManager", _FakeMM)
|
||||||
monkeypatch.setattr(
|
monkeypatch.setattr(
|
||||||
src.task_endpoint, "resolve_task_candidates",
|
src.task_endpoint, "resolve_task_candidates",
|
||||||
lambda owner=None: [("http://x/v1", "model", {})],
|
lambda owner=None, **kwargs: [("http://x/v1", "model", {})],
|
||||||
)
|
)
|
||||||
|
|
||||||
async def fake_llm(_candidates, **kwargs):
|
async def fake_llm(_candidates, **kwargs):
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ def _extract_thinking_blocks(text: str) -> dict:
|
|||||||
|
|
||||||
let source = fs.readFileSync('./static/js/markdown.js', 'utf8');
|
let source = fs.readFileSync('./static/js/markdown.js', 'utf8');
|
||||||
source = source.replace(
|
source = source.replace(
|
||||||
/import uiModule from ['"]\.\/ui\.js(?:\?[^'"]*)?['"];?/,
|
/import uiModule from ['"]\.\/ui\.js(?:[?#][^'"]*)?['"];?/,
|
||||||
''
|
''
|
||||||
);
|
);
|
||||||
source = source.replace(
|
source = source.replace(
|
||||||
|
|||||||
@@ -5,41 +5,7 @@ with the args on the same line as the tag; the parser must execute those. The
|
|||||||
relaxed tag pattern must NOT prefix-match longer fence tags: ```python3 is a
|
relaxed tag pattern must NOT prefix-match longer fence tags: ```python3 is a
|
||||||
language hint, not a "python" tool call with content "3\n...".
|
language hint, not a "python" tool call with content "3\n...".
|
||||||
"""
|
"""
|
||||||
import sys
|
from src.tool_parsing import parse_tool_blocks, strip_tool_blocks
|
||||||
from unittest.mock import MagicMock
|
|
||||||
|
|
||||||
# This module needs the real agent-tool stack; importing it pulls in heavy
|
|
||||||
# DB/auth deps, so we stub those just long enough to import, then restore them.
|
|
||||||
# We deliberately do NOT pop src.tool_execution: popping and re-importing it
|
|
||||||
# rebinds the `src` package's `tool_execution` attribute, so a later
|
|
||||||
# `import src.tool_execution as te` resolves to a different module object than
|
|
||||||
# the one its functions live in - which silently breaks tests that monkeypatch
|
|
||||||
# it (e.g. test_edit_file's admin gate) and breaks request-scoped ContextVars.
|
|
||||||
_ABSENT = object()
|
|
||||||
_AGENT_MODULES = ["src.agent_tools", "src.tool_parsing", "src.tool_schemas"]
|
|
||||||
_STUBBED = [
|
|
||||||
"sqlalchemy", "sqlalchemy.orm", "sqlalchemy.ext", "sqlalchemy.ext.declarative",
|
|
||||||
"sqlalchemy.ext.hybrid", "sqlalchemy.sql", "sqlalchemy.sql.expression",
|
|
||||||
"src.database", "core.models", "core.database", "core.auth",
|
|
||||||
]
|
|
||||||
_saved_stubs = {name: sys.modules.get(name, _ABSENT) for name in _STUBBED}
|
|
||||||
|
|
||||||
for _mod in _AGENT_MODULES:
|
|
||||||
sys.modules.pop(_mod, None)
|
|
||||||
for _mod in _STUBBED:
|
|
||||||
if _mod not in sys.modules:
|
|
||||||
sys.modules[_mod] = MagicMock()
|
|
||||||
|
|
||||||
import src.agent_tools # noqa: E402, F401
|
|
||||||
from src.tool_parsing import parse_tool_blocks, strip_tool_blocks # noqa: E402
|
|
||||||
|
|
||||||
# Drop the stubs we installed so they do not leak into later tests.
|
|
||||||
for _name, _original in _saved_stubs.items():
|
|
||||||
if _original is _ABSENT:
|
|
||||||
sys.modules.pop(_name, None)
|
|
||||||
else:
|
|
||||||
sys.modules[_name] = _original
|
|
||||||
|
|
||||||
|
|
||||||
def test_inline_args_on_tag_line_parse():
|
def test_inline_args_on_tag_line_parse():
|
||||||
# The original bug: ```list_email_accounts {} (args on the tag line)
|
# The original bug: ```list_email_accounts {} (args on the tag line)
|
||||||
|
|||||||
@@ -3,41 +3,7 @@ can't be converted (e.g. a hyphenated/namespaced tool name that _XML_INVOKE_RE's
|
|||||||
\\w+ won't match, or an unknown tool) must NOT fall through and ship the raw XML
|
\\w+ won't match, or an unknown tool) must NOT fall through and ship the raw XML
|
||||||
to the code executor as if it were python/bash.
|
to the code executor as if it were python/bash.
|
||||||
"""
|
"""
|
||||||
import sys
|
from src.tool_parsing import parse_tool_blocks
|
||||||
from unittest.mock import MagicMock
|
|
||||||
|
|
||||||
# This module needs the real agent-tool stack; importing it pulls in heavy
|
|
||||||
# DB/auth deps, so we stub those just long enough to import, then restore them.
|
|
||||||
# We deliberately do NOT pop src.tool_execution: popping and re-importing it
|
|
||||||
# rebinds the `src` package's `tool_execution` attribute, so a later
|
|
||||||
# `import src.tool_execution as te` resolves to a different module object than
|
|
||||||
# the one its functions live in - which silently breaks tests that monkeypatch
|
|
||||||
# it (e.g. test_edit_file's admin gate) and breaks request-scoped ContextVars.
|
|
||||||
_ABSENT = object()
|
|
||||||
_AGENT_MODULES = ["src.agent_tools", "src.tool_parsing", "src.tool_schemas"]
|
|
||||||
_STUBBED = [
|
|
||||||
"sqlalchemy", "sqlalchemy.orm", "sqlalchemy.ext", "sqlalchemy.ext.declarative",
|
|
||||||
"sqlalchemy.ext.hybrid", "sqlalchemy.sql", "sqlalchemy.sql.expression",
|
|
||||||
"src.database", "core.models", "core.database", "core.auth",
|
|
||||||
]
|
|
||||||
_saved_stubs = {name: sys.modules.get(name, _ABSENT) for name in _STUBBED}
|
|
||||||
|
|
||||||
for _mod in _AGENT_MODULES:
|
|
||||||
sys.modules.pop(_mod, None)
|
|
||||||
for _mod in _STUBBED:
|
|
||||||
if _mod not in sys.modules:
|
|
||||||
sys.modules[_mod] = MagicMock()
|
|
||||||
|
|
||||||
import src.agent_tools # noqa: E402, F401
|
|
||||||
from src.tool_parsing import parse_tool_blocks # noqa: E402
|
|
||||||
|
|
||||||
# Drop the stubs we installed so they do not leak into later tests.
|
|
||||||
for _name, _original in _saved_stubs.items():
|
|
||||||
if _original is _ABSENT:
|
|
||||||
sys.modules.pop(_name, None)
|
|
||||||
else:
|
|
||||||
sys.modules[_name] = _original
|
|
||||||
|
|
||||||
|
|
||||||
def test_unconvertible_invoke_in_fence_is_not_executed_as_code():
|
def test_unconvertible_invoke_in_fence_is_not_executed_as_code():
|
||||||
text = '```python\n<invoke name="foo-bar">\n<parameter name="x">1</parameter>\n</invoke>\n```'
|
text = '```python\n<invoke name="foo-bar">\n<parameter name="x">1</parameter>\n</invoke>\n```'
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ async def test_api_call_root_path_has_no_trailing_slash():
|
|||||||
|
|
||||||
with (
|
with (
|
||||||
patch.object(integrations, "_find_integration", return_value=DISCORD_INTEGRATION),
|
patch.object(integrations, "_find_integration", return_value=DISCORD_INTEGRATION),
|
||||||
|
patch("src.url_safety._default_resolver", return_value=["93.184.216.34"]),
|
||||||
patch("httpx.AsyncClient", return_value=mock_client),
|
patch("httpx.AsyncClient", return_value=mock_client),
|
||||||
):
|
):
|
||||||
result = await integrations.execute_api_call(
|
result = await integrations.execute_api_call(
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ globalThis.document = {
|
|||||||
globalThis.MutationObserver = class { observe() {} };
|
globalThis.MutationObserver = class { observe() {} };
|
||||||
|
|
||||||
let source = fs.readFileSync('./static/js/markdown.js', 'utf8');
|
let source = fs.readFileSync('./static/js/markdown.js', 'utf8');
|
||||||
source = source.replace(/import uiModule from ['"]\.\/ui\.js(?:\?[^'"]*)?['"];?/, '');
|
source = source.replace(/import uiModule from ['"]\.\/ui\.js(?:[?#][^'"]*)?['"];?/, '');
|
||||||
source = source.replace(
|
source = source.replace(
|
||||||
/import \{ splitTableRow \} from ['"]\.\/markdown\/tableRow\.js['"];/,
|
/import \{ splitTableRow \} from ['"]\.\/markdown\/tableRow\.js['"];/,
|
||||||
`function splitTableRow(row) {
|
`function splitTableRow(row) {
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ def _run_markdown_case(markdown: str, render_expr: str = "mod.mdToHtml(input)",
|
|||||||
|
|
||||||
let source = fs.readFileSync('./static/js/markdown.js', 'utf8');
|
let source = fs.readFileSync('./static/js/markdown.js', 'utf8');
|
||||||
source = source.replace(
|
source = source.replace(
|
||||||
/import uiModule from ['"]\.\/ui\.js(?:\?[^'"]*)?['"];?/,
|
/import uiModule from ['"]\.\/ui\.js(?:[?#][^'"]*)?['"];?/,
|
||||||
''
|
''
|
||||||
);
|
);
|
||||||
source = source.replace(
|
source = source.replace(
|
||||||
|
|||||||
@@ -114,6 +114,7 @@ async def test_docx_preview_runs_off_loop_and_checks_owner(monkeypatch, conversi
|
|||||||
|
|
||||||
|
|
||||||
def test_imported_office_document_is_owned_at_first_commit(monkeypatch, tmp_path):
|
def test_imported_office_document_is_owned_at_first_commit(monkeypatch, tmp_path):
|
||||||
|
import importlib
|
||||||
from sqlalchemy import create_engine, event
|
from sqlalchemy import create_engine, event
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
import src.database as database
|
import src.database as database
|
||||||
@@ -122,7 +123,8 @@ def test_imported_office_document_is_owned_at_first_commit(monkeypatch, tmp_path
|
|||||||
database.Base.metadata.create_all(engine)
|
database.Base.metadata.create_all(engine)
|
||||||
factory = sessionmaker(bind=engine)
|
factory = sessionmaker(bind=engine)
|
||||||
monkeypatch.setattr(database, "SessionLocal", factory)
|
monkeypatch.setattr(database, "SessionLocal", factory)
|
||||||
monkeypatch.setattr("src.agent_tools.document_tools.set_active_document", lambda doc_id: None)
|
document_tools = importlib.import_module("src.agent_tools.document_tools")
|
||||||
|
monkeypatch.setattr(document_tools, "set_active_document", lambda doc_id: None)
|
||||||
owners_at_commit = []
|
owners_at_commit = []
|
||||||
def inspect_new_rows(session):
|
def inspect_new_rows(session):
|
||||||
owners_at_commit.extend(row.owner for row in session.new if isinstance(row, database.Document))
|
owners_at_commit.extend(row.owner for row in session.new if isinstance(row, database.Document))
|
||||||
|
|||||||
@@ -302,39 +302,39 @@ def results(tmp_path_factory):
|
|||||||
source = _SESSIONS.read_text(encoding="utf-8")
|
source = _SESSIONS.read_text(encoding="utf-8")
|
||||||
versioned_rewrites = (
|
versioned_rewrites = (
|
||||||
(
|
(
|
||||||
r"import Storage from './storage\.js(?:\?[^']+)?';",
|
r"import Storage from './storage\.js(?:[?#][^']*)?';",
|
||||||
"import Storage from './storage.mjs';",
|
"import Storage from './storage.mjs';",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"import uiModule, \{ autoResize, styledPrompt \} from './ui\.js(?:\?[^']+)?';",
|
r"import uiModule, \{ autoResize, styledPrompt \} from './ui\.js(?:[?#][^']*)?';",
|
||||||
"import uiModule, { autoResize, styledPrompt } from './ui.mjs';",
|
"import uiModule, { autoResize, styledPrompt } from './ui.mjs';",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"import chatRenderer from './chatRenderer\.js(?:\?[^']+)?';",
|
r"import chatRenderer from './chatRenderer\.js(?:[?#][^']*)?';",
|
||||||
"import chatRenderer from './chatRenderer.mjs';",
|
"import chatRenderer from './chatRenderer.mjs';",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"import \{ providerLogo \} from './providers\.js(?:\?[^']+)?';",
|
r"import \{ providerLogo \} from './providers\.js(?:[?#][^']*)?';",
|
||||||
"import { providerLogo } from './providers.mjs';",
|
"import { providerLogo } from './providers.mjs';",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"import \{ initModelPicker, updateModelPicker \} from './modelPicker\.js(?:\?[^']+)?';",
|
r"import \{ initModelPicker, updateModelPicker \} from './modelPicker\.js(?:[?#][^']*)?';",
|
||||||
"import { initModelPicker, updateModelPicker } from './modelPicker.mjs';",
|
"import { initModelPicker, updateModelPicker } from './modelPicker.mjs';",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"import themeModule from './theme\.js(?:\?[^']+)?';",
|
r"import themeModule from './theme\.js(?:[?#][^']*)?';",
|
||||||
"import themeModule from './theme.mjs';",
|
"import themeModule from './theme.mjs';",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"import spinnerModule from './spinner\.js(?:\?[^']+)?';",
|
r"import spinnerModule from './spinner\.js(?:[?#][^']*)?';",
|
||||||
"import spinnerModule from './spinner.mjs';",
|
"import spinnerModule from './spinner.mjs';",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"import \{ actionMenuRank, orderActionMenuItems, SELECT_MENU_ICON \} from './actionMenuOrder\.js(?:\?[^']+)?';",
|
r"import \{ actionMenuRank, orderActionMenuItems, SELECT_MENU_ICON \} from './actionMenuOrder\.js(?:[?#][^']*)?';",
|
||||||
"import { actionMenuRank, orderActionMenuItems, SELECT_MENU_ICON } from './actionMenuOrder.mjs';",
|
"import { actionMenuRank, orderActionMenuItems, SELECT_MENU_ICON } from './actionMenuOrder.mjs';",
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
r"import \{ registerEscapeLayer, bindMenuDismiss \} from './escMenuStack\.js(?:\?[^']+)?';",
|
r"import \{ registerEscapeLayer, bindMenuDismiss \} from './escMenuStack\.js(?:[?#][^']*)?';",
|
||||||
"import { registerEscapeLayer, bindMenuDismiss } from './escMenuStack.mjs';",
|
"import { registerEscapeLayer, bindMenuDismiss } from './escMenuStack.mjs';",
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,11 +20,12 @@ def _run_tile_case():
|
|||||||
innerWidth: 1200,
|
innerWidth: 1200,
|
||||||
innerHeight: 800,
|
innerHeight: 800,
|
||||||
addEventListener() {{}},
|
addEventListener() {{}},
|
||||||
|
getComputedStyle() {{ return {{ display: 'block' }}; }},
|
||||||
}};
|
}};
|
||||||
let sidebarVisible = false;
|
let sidebarVisible = false;
|
||||||
const sidebar = {{
|
const sidebar = {{
|
||||||
classList: {{ contains(name) {{ return name === 'hidden' ? !sidebarVisible : false; }} }},
|
classList: {{ contains(name) {{ return name === 'hidden' ? !sidebarVisible : false; }} }},
|
||||||
getBoundingClientRect() {{ return {{ right: 240 }}; }},
|
getBoundingClientRect() {{ return {{ left: 0, right: 240, width: 240, top: 0, bottom: 800 }}; }},
|
||||||
}};
|
}};
|
||||||
globalThis.document = {{
|
globalThis.document = {{
|
||||||
readyState: 'loading',
|
readyState: 'loading',
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ async def test_chat_image_upload_is_added_to_gallery(tmp_path, monkeypatch):
|
|||||||
up.setup_upload_routes(h)
|
up.setup_upload_routes(h)
|
||||||
endpoint = _endpoint(up.router)
|
endpoint = _endpoint(up.router)
|
||||||
|
|
||||||
result = await endpoint(_request(user="alice"), [_image_upload()])
|
result = await endpoint(_request(user="alice"), [_image_upload()], gallery_id=None)
|
||||||
uploaded = result["files"][0]
|
uploaded = result["files"][0]
|
||||||
|
|
||||||
assert uploaded["gallery_id"]
|
assert uploaded["gallery_id"]
|
||||||
|
|||||||
@@ -9,41 +9,8 @@ That is an intended tool call in non-native/textual tool mode, but older parsing
|
|||||||
only recognized fenced blocks, [TOOL_CALL], XML invoke, and tool_code markup.
|
only recognized fenced blocks, [TOOL_CALL], XML invoke, and tool_code markup.
|
||||||
"""
|
"""
|
||||||
import json
|
import json
|
||||||
import sys
|
|
||||||
from unittest.mock import MagicMock
|
|
||||||
|
|
||||||
# This module needs the real agent-tool stack; importing it pulls in heavy
|
|
||||||
# DB/auth deps, so we stub those just long enough to import, then restore them.
|
|
||||||
# We deliberately do NOT pop src.tool_execution: popping and re-importing it
|
|
||||||
# rebinds the `src` package's `tool_execution` attribute, so a later
|
|
||||||
# `import src.tool_execution as te` resolves to a different module object than
|
|
||||||
# the one its functions live in - which silently breaks tests that monkeypatch
|
|
||||||
# it (e.g. test_edit_file's admin gate) and breaks request-scoped ContextVars.
|
|
||||||
_ABSENT = object()
|
|
||||||
_AGENT_MODULES = ["src.agent_tools", "src.tool_parsing", "src.tool_schemas"]
|
|
||||||
_STUBBED = [
|
|
||||||
"sqlalchemy", "sqlalchemy.orm", "sqlalchemy.ext", "sqlalchemy.ext.declarative",
|
|
||||||
"sqlalchemy.ext.hybrid", "sqlalchemy.sql", "sqlalchemy.sql.expression",
|
|
||||||
"src.database", "core.models", "core.database", "core.auth",
|
|
||||||
]
|
|
||||||
_saved_stubs = {name: sys.modules.get(name, _ABSENT) for name in _STUBBED}
|
|
||||||
|
|
||||||
for _mod in _AGENT_MODULES:
|
|
||||||
sys.modules.pop(_mod, None)
|
|
||||||
for _mod in _STUBBED:
|
|
||||||
if _mod not in sys.modules:
|
|
||||||
sys.modules[_mod] = MagicMock()
|
|
||||||
|
|
||||||
import src.agent_tools # noqa: E402, F401
|
|
||||||
from src.tool_parsing import parse_tool_blocks, strip_tool_blocks # noqa: E402
|
|
||||||
|
|
||||||
# Drop the stubs we installed so they do not leak into later tests.
|
|
||||||
for _name, _original in _saved_stubs.items():
|
|
||||||
if _original is _ABSENT:
|
|
||||||
sys.modules.pop(_name, None)
|
|
||||||
else:
|
|
||||||
sys.modules[_name] = _original
|
|
||||||
|
|
||||||
|
from src.tool_parsing import parse_tool_blocks, strip_tool_blocks
|
||||||
|
|
||||||
def test_raw_json_after_web_search_phrase_runs_as_web_search():
|
def test_raw_json_after_web_search_phrase_runs_as_web_search():
|
||||||
text = (
|
text = (
|
||||||
|
|||||||
@@ -6,43 +6,9 @@ web_search branch emitted a bare query string and dropped time_filter. These pin
|
|||||||
that a valid filter is passed through as JSON, while plain/invalid cases stay a
|
that a valid filter is passed through as JSON, while plain/invalid cases stay a
|
||||||
bare string (back-compat).
|
bare string (back-compat).
|
||||||
"""
|
"""
|
||||||
import sys
|
import json
|
||||||
from unittest.mock import MagicMock
|
|
||||||
|
|
||||||
# This module needs the real agent-tool stack; importing it pulls in heavy
|
|
||||||
# DB/auth deps, so we stub those just long enough to import, then restore them.
|
|
||||||
# We deliberately do NOT pop src.tool_execution: popping and re-importing it
|
|
||||||
# rebinds the `src` package's `tool_execution` attribute, so a later
|
|
||||||
# `import src.tool_execution as te` resolves to a different module object than
|
|
||||||
# the one its functions live in - which silently breaks tests that monkeypatch
|
|
||||||
# it (e.g. test_edit_file's admin gate) and breaks request-scoped ContextVars.
|
|
||||||
_ABSENT = object()
|
|
||||||
_AGENT_MODULES = ["src.agent_tools", "src.tool_parsing", "src.tool_schemas"]
|
|
||||||
_STUBBED = [
|
|
||||||
"sqlalchemy", "sqlalchemy.orm", "sqlalchemy.ext", "sqlalchemy.ext.declarative",
|
|
||||||
"sqlalchemy.ext.hybrid", "sqlalchemy.sql", "sqlalchemy.sql.expression",
|
|
||||||
"src.database", "core.models", "core.database", "core.auth",
|
|
||||||
]
|
|
||||||
_saved_stubs = {name: sys.modules.get(name, _ABSENT) for name in _STUBBED}
|
|
||||||
|
|
||||||
for _mod in _AGENT_MODULES:
|
|
||||||
sys.modules.pop(_mod, None)
|
|
||||||
for _mod in _STUBBED:
|
|
||||||
if _mod not in sys.modules:
|
|
||||||
sys.modules[_mod] = MagicMock()
|
|
||||||
|
|
||||||
import json # noqa: E402
|
|
||||||
|
|
||||||
import src.agent_tools # noqa: E402, F401
|
|
||||||
from src.tool_schemas import function_call_to_tool_block # noqa: E402
|
|
||||||
|
|
||||||
# Drop the stubs we installed so they do not leak into later tests.
|
|
||||||
for _name, _original in _saved_stubs.items():
|
|
||||||
if _original is _ABSENT:
|
|
||||||
sys.modules.pop(_name, None)
|
|
||||||
else:
|
|
||||||
sys.modules[_name] = _original
|
|
||||||
|
|
||||||
|
from src.tool_schemas import function_call_to_tool_block
|
||||||
|
|
||||||
def test_time_filter_is_preserved_as_json():
|
def test_time_filter_is_preserved_as_json():
|
||||||
block = function_call_to_tool_block(
|
block = function_call_to_tool_block(
|
||||||
|
|||||||
Reference in New Issue
Block a user