mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-06 15:02:20 +02: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()
|
||||
|
||||
if is_bound:
|
||||
yield
|
||||
return
|
||||
raise RuntimeError("port 7011 is already in use; browser tests require this worktree's static server")
|
||||
|
||||
root_dir = Path(__file__).resolve().parent.parent
|
||||
|
||||
@@ -134,12 +133,11 @@ def _serve_test_static():
|
||||
class _Server(socketserver.TCPServer):
|
||||
allow_reuse_address = True
|
||||
|
||||
server = _Server(("127.0.0.1", 7011), _Handler)
|
||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
try:
|
||||
server = _Server(("127.0.0.1", 7011), _Handler)
|
||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
yield
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
except Exception:
|
||||
yield
|
||||
|
||||
@@ -26,7 +26,7 @@ export async function loadMarkdown() {
|
||||
globalThis.MutationObserver = class { observe() {} };
|
||||
|
||||
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(
|
||||
/import \{ splitTableRow \} from ['"]\.\/markdown\/tableRow\.js['"];/,
|
||||
() => `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.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):
|
||||
|
||||
@@ -58,7 +58,7 @@ def _extract_thinking_blocks(text: str) -> dict:
|
||||
|
||||
let source = fs.readFileSync('./static/js/markdown.js', 'utf8');
|
||||
source = source.replace(
|
||||
/import uiModule from ['"]\.\/ui\.js(?:\?[^'"]*)?['"];?/,
|
||||
/import uiModule from ['"]\.\/ui\.js(?:[?#][^'"]*)?['"];?/,
|
||||
''
|
||||
);
|
||||
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
|
||||
language hint, not a "python" tool call with content "3\n...".
|
||||
"""
|
||||
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_inline_args_on_tag_line_parse():
|
||||
# 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
|
||||
to the code executor as if it were python/bash.
|
||||
"""
|
||||
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 # 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
|
||||
|
||||
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```'
|
||||
|
||||
@@ -105,6 +105,7 @@ async def test_api_call_root_path_has_no_trailing_slash():
|
||||
|
||||
with (
|
||||
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),
|
||||
):
|
||||
result = await integrations.execute_api_call(
|
||||
|
||||
@@ -102,7 +102,7 @@ globalThis.document = {
|
||||
globalThis.MutationObserver = class { observe() {} };
|
||||
|
||||
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(
|
||||
/import \{ splitTableRow \} from ['"]\.\/markdown\/tableRow\.js['"];/,
|
||||
`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');
|
||||
source = source.replace(
|
||||
/import uiModule from ['"]\.\/ui\.js(?:\?[^'"]*)?['"];?/,
|
||||
/import uiModule from ['"]\.\/ui\.js(?:[?#][^'"]*)?['"];?/,
|
||||
''
|
||||
);
|
||||
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):
|
||||
import importlib
|
||||
from sqlalchemy import create_engine, event
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
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)
|
||||
factory = sessionmaker(bind=engine)
|
||||
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 = []
|
||||
def inspect_new_rows(session):
|
||||
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")
|
||||
versioned_rewrites = (
|
||||
(
|
||||
r"import Storage from './storage\.js(?:\?[^']+)?';",
|
||||
r"import Storage from './storage\.js(?:[?#][^']*)?';",
|
||||
"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';",
|
||||
),
|
||||
(
|
||||
r"import chatRenderer from './chatRenderer\.js(?:\?[^']+)?';",
|
||||
r"import chatRenderer from './chatRenderer\.js(?:[?#][^']*)?';",
|
||||
"import chatRenderer from './chatRenderer.mjs';",
|
||||
),
|
||||
(
|
||||
r"import \{ providerLogo \} from './providers\.js(?:\?[^']+)?';",
|
||||
r"import \{ providerLogo \} from './providers\.js(?:[?#][^']*)?';",
|
||||
"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';",
|
||||
),
|
||||
(
|
||||
r"import themeModule from './theme\.js(?:\?[^']+)?';",
|
||||
r"import themeModule from './theme\.js(?:[?#][^']*)?';",
|
||||
"import themeModule from './theme.mjs';",
|
||||
),
|
||||
(
|
||||
r"import spinnerModule from './spinner\.js(?:\?[^']+)?';",
|
||||
r"import spinnerModule from './spinner\.js(?:[?#][^']*)?';",
|
||||
"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';",
|
||||
),
|
||||
(
|
||||
r"import \{ registerEscapeLayer, bindMenuDismiss \} from './escMenuStack\.js(?:\?[^']+)?';",
|
||||
r"import \{ registerEscapeLayer, bindMenuDismiss \} from './escMenuStack\.js(?:[?#][^']*)?';",
|
||||
"import { registerEscapeLayer, bindMenuDismiss } from './escMenuStack.mjs';",
|
||||
),
|
||||
)
|
||||
|
||||
@@ -20,11 +20,12 @@ def _run_tile_case():
|
||||
innerWidth: 1200,
|
||||
innerHeight: 800,
|
||||
addEventListener() {{}},
|
||||
getComputedStyle() {{ return {{ display: 'block' }}; }},
|
||||
}};
|
||||
let sidebarVisible = false;
|
||||
const sidebar = {{
|
||||
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 = {{
|
||||
readyState: 'loading',
|
||||
|
||||
@@ -191,7 +191,7 @@ async def test_chat_image_upload_is_added_to_gallery(tmp_path, monkeypatch):
|
||||
up.setup_upload_routes(h)
|
||||
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]
|
||||
|
||||
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.
|
||||
"""
|
||||
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():
|
||||
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
|
||||
bare string (back-compat).
|
||||
"""
|
||||
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 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
|
||||
import json
|
||||
|
||||
from src.tool_schemas import function_call_to_tool_block
|
||||
|
||||
def test_time_filter_is_preserved_as_json():
|
||||
block = function_call_to_tool_block(
|
||||
|
||||
Reference in New Issue
Block a user