mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-07-06 22:12:12 +02:00
test: rename SearchSpace -> Workspace across tests + fixtures (Phase 2 Wave F)
Apply the same rename to surfsense_backend/tests: workspace_id fields/vars, Workspace* classes/schemas, table name searchspaces -> workspaces in raw SQL, and the API URL spellings -> /workspaces. Preserves the carve-out wire literals tests assert (Celery task names, OTel key "search_space.id").
This commit is contained in:
parent
56826a63bc
commit
ca9bd28934
124 changed files with 1269 additions and 1269 deletions
|
|
@ -94,7 +94,7 @@ def fake_session_factory():
|
|||
class TestActionLogMiddlewareDisabled:
|
||||
@pytest.mark.asyncio
|
||||
async def test_no_op_when_flag_off(self, patch_get_flags) -> None:
|
||||
mw = ActionLogMiddleware(thread_id=1, search_space_id=1, user_id=None)
|
||||
mw = ActionLogMiddleware(thread_id=1, workspace_id=1, user_id=None)
|
||||
request = _FakeRequest(
|
||||
tool_call={
|
||||
"name": "make_widget",
|
||||
|
|
@ -110,7 +110,7 @@ class TestActionLogMiddlewareDisabled:
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_no_op_when_thread_id_none(self, patch_get_flags) -> None:
|
||||
mw = ActionLogMiddleware(thread_id=None, search_space_id=1, user_id=None)
|
||||
mw = ActionLogMiddleware(thread_id=None, workspace_id=1, user_id=None)
|
||||
request = _FakeRequest(
|
||||
tool_call={"name": "make_widget", "args": {}, "id": "tc1"}
|
||||
)
|
||||
|
|
@ -126,7 +126,7 @@ class TestActionLogMiddlewarePersistence:
|
|||
self, patch_get_flags, fake_session_factory
|
||||
) -> None:
|
||||
captured, factory = fake_session_factory
|
||||
mw = ActionLogMiddleware(thread_id=42, search_space_id=7, user_id="u1")
|
||||
mw = ActionLogMiddleware(thread_id=42, workspace_id=7, user_id="u1")
|
||||
request = _FakeRequest(
|
||||
tool_call={
|
||||
"name": "make_widget",
|
||||
|
|
@ -150,7 +150,7 @@ class TestActionLogMiddlewarePersistence:
|
|||
assert len(captured["rows"]) == 1
|
||||
row = captured["rows"][0]
|
||||
assert row.thread_id == 42
|
||||
assert row.search_space_id == 7
|
||||
assert row.workspace_id == 7
|
||||
assert row.user_id == "u1"
|
||||
assert row.tool_name == "make_widget"
|
||||
assert row.args == {"color": "red", "size": 3}
|
||||
|
|
@ -170,7 +170,7 @@ class TestActionLogMiddlewarePersistence:
|
|||
) -> None:
|
||||
"""``chat_turn_id`` falls back to NULL when ``runtime.config`` is absent."""
|
||||
captured, factory = fake_session_factory
|
||||
mw = ActionLogMiddleware(thread_id=1, search_space_id=1, user_id=None)
|
||||
mw = ActionLogMiddleware(thread_id=1, workspace_id=1, user_id=None)
|
||||
request = _FakeRequest(
|
||||
tool_call={"name": "make_widget", "args": {}, "id": "tc-1"},
|
||||
runtime=None,
|
||||
|
|
@ -190,7 +190,7 @@ class TestActionLogMiddlewarePersistence:
|
|||
self, patch_get_flags, fake_session_factory
|
||||
) -> None:
|
||||
captured, factory = fake_session_factory
|
||||
mw = ActionLogMiddleware(thread_id=42, search_space_id=7, user_id="u1")
|
||||
mw = ActionLogMiddleware(thread_id=42, workspace_id=7, user_id="u1")
|
||||
request = _FakeRequest(
|
||||
tool_call={"name": "make_widget", "args": {"color": "red"}, "id": "tc1"}
|
||||
)
|
||||
|
|
@ -214,7 +214,7 @@ class TestActionLogMiddlewarePersistence:
|
|||
self, patch_get_flags
|
||||
) -> None:
|
||||
"""Even if the DB write blows up, the tool's result must reach the model."""
|
||||
mw = ActionLogMiddleware(thread_id=1, search_space_id=1, user_id=None)
|
||||
mw = ActionLogMiddleware(thread_id=1, workspace_id=1, user_id=None)
|
||||
request = _FakeRequest(
|
||||
tool_call={"name": "make_widget", "args": {}, "id": "tc1"}
|
||||
)
|
||||
|
|
@ -250,7 +250,7 @@ class TestReverseDescriptor:
|
|||
)
|
||||
mw = ActionLogMiddleware(
|
||||
thread_id=1,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
user_id="u",
|
||||
tool_definitions={"make_widget": tool_def},
|
||||
)
|
||||
|
|
@ -296,7 +296,7 @@ class TestReverseDescriptor:
|
|||
)
|
||||
mw = ActionLogMiddleware(
|
||||
thread_id=1,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
user_id=None,
|
||||
tool_definitions={"make_widget": tool_def},
|
||||
)
|
||||
|
|
@ -321,7 +321,7 @@ class TestReverseDescriptor:
|
|||
self, patch_get_flags, fake_session_factory
|
||||
) -> None:
|
||||
captured, factory = fake_session_factory
|
||||
mw = ActionLogMiddleware(thread_id=1, search_space_id=1, user_id=None)
|
||||
mw = ActionLogMiddleware(thread_id=1, workspace_id=1, user_id=None)
|
||||
request = _FakeRequest(
|
||||
tool_call={"name": "unknown_tool", "args": {}, "id": "tc1"}
|
||||
)
|
||||
|
|
@ -343,7 +343,7 @@ class TestActionLogDispatch:
|
|||
self, patch_get_flags, fake_session_factory
|
||||
) -> None:
|
||||
_captured, factory = fake_session_factory
|
||||
mw = ActionLogMiddleware(thread_id=42, search_space_id=7, user_id="u1")
|
||||
mw = ActionLogMiddleware(thread_id=42, workspace_id=7, user_id="u1")
|
||||
request = _FakeRequest(
|
||||
tool_call={
|
||||
"name": "make_widget",
|
||||
|
|
@ -383,7 +383,7 @@ class TestActionLogDispatch:
|
|||
@pytest.mark.asyncio
|
||||
async def test_no_dispatch_when_persistence_fails(self, patch_get_flags) -> None:
|
||||
"""If commit fails the dispatch is suppressed (no row to surface)."""
|
||||
mw = ActionLogMiddleware(thread_id=1, search_space_id=1, user_id=None)
|
||||
mw = ActionLogMiddleware(thread_id=1, workspace_id=1, user_id=None)
|
||||
request = _FakeRequest(
|
||||
tool_call={"name": "make_widget", "args": {}, "id": "tc1"}
|
||||
)
|
||||
|
|
@ -411,7 +411,7 @@ class TestArgsTruncation:
|
|||
self, patch_get_flags, fake_session_factory
|
||||
) -> None:
|
||||
captured, factory = fake_session_factory
|
||||
mw = ActionLogMiddleware(thread_id=1, search_space_id=1, user_id=None)
|
||||
mw = ActionLogMiddleware(thread_id=1, workspace_id=1, user_id=None)
|
||||
# Build a > 32KB string so the persisted payload triggers the truncation path.
|
||||
huge = "x" * (40 * 1024)
|
||||
request = _FakeRequest(
|
||||
|
|
|
|||
|
|
@ -99,7 +99,7 @@ class TestResolveMentions:
|
|||
session.execute = AsyncMock()
|
||||
result = await resolve_mentions(
|
||||
session,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
mentioned_documents=None,
|
||||
)
|
||||
assert isinstance(result, ResolvedMentionSet)
|
||||
|
|
@ -134,7 +134,7 @@ class TestResolveMentions:
|
|||
|
||||
out = await resolve_mentions(
|
||||
session,
|
||||
search_space_id=5,
|
||||
workspace_id=5,
|
||||
mentioned_documents=[chip],
|
||||
)
|
||||
assert len(out.mentions) == 1
|
||||
|
|
@ -170,7 +170,7 @@ class TestResolveMentions:
|
|||
|
||||
out = await resolve_mentions(
|
||||
session,
|
||||
search_space_id=3,
|
||||
workspace_id=3,
|
||||
mentioned_documents=[chip],
|
||||
)
|
||||
assert len(out.mentions) == 1
|
||||
|
|
@ -201,7 +201,7 @@ class TestResolveMentions:
|
|||
|
||||
out = await resolve_mentions(
|
||||
session,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
mentioned_documents=[chip],
|
||||
)
|
||||
assert out.mentions == []
|
||||
|
|
@ -238,7 +238,7 @@ class TestResolveMentions:
|
|||
|
||||
out = await resolve_mentions(
|
||||
session,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
mentioned_documents=[chip_short, chip_long],
|
||||
)
|
||||
tokens = [tok for tok, _ in out.token_to_path]
|
||||
|
|
@ -265,7 +265,7 @@ class TestResolveMentions:
|
|||
|
||||
out = await resolve_mentions(
|
||||
session,
|
||||
search_space_id=2,
|
||||
workspace_id=2,
|
||||
mentioned_documents=None,
|
||||
mentioned_document_ids=[7],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ class TestVirtualPathToDoc:
|
|||
|
||||
document = await virtual_path_to_doc(
|
||||
session,
|
||||
search_space_id=5,
|
||||
workspace_id=5,
|
||||
virtual_path=f"{DOCUMENTS_ROOT}/{encoded_basename}",
|
||||
)
|
||||
assert document is target_doc
|
||||
|
|
@ -169,7 +169,7 @@ class TestVirtualPathToDoc:
|
|||
|
||||
document = await virtual_path_to_doc(
|
||||
session,
|
||||
search_space_id=5,
|
||||
workspace_id=5,
|
||||
virtual_path=f"{DOCUMENTS_ROOT}/Calendar_ Happy birthday!.xml",
|
||||
)
|
||||
assert document is None
|
||||
|
|
@ -191,7 +191,7 @@ class TestVirtualPathToDoc:
|
|||
|
||||
document = await virtual_path_to_doc(
|
||||
session,
|
||||
search_space_id=5,
|
||||
workspace_id=5,
|
||||
virtual_path=f"{DOCUMENTS_ROOT}/Plain Note.xml",
|
||||
)
|
||||
assert document is target_doc
|
||||
|
|
@ -217,7 +217,7 @@ class TestVirtualPathToDoc:
|
|||
|
||||
document = await virtual_path_to_doc(
|
||||
session,
|
||||
search_space_id=5,
|
||||
workspace_id=5,
|
||||
virtual_path=f"{DOCUMENTS_ROOT}/2025-W2.pdf.xml",
|
||||
)
|
||||
assert document is target_doc
|
||||
|
|
@ -239,7 +239,7 @@ class TestVirtualPathToDoc:
|
|||
|
||||
document = await virtual_path_to_doc(
|
||||
session,
|
||||
search_space_id=5,
|
||||
workspace_id=5,
|
||||
virtual_path=f"{DOCUMENTS_ROOT}/2025-W2.pdf",
|
||||
)
|
||||
assert document is target_doc
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ class _DummyMiddleware(AgentMiddleware):
|
|||
|
||||
def _ctx() -> PluginContext:
|
||||
return PluginContext.build(
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
user_id="u",
|
||||
thread_visibility="PRIVATE", # type: ignore[arg-type]
|
||||
llm=MagicMock(),
|
||||
|
|
@ -165,12 +165,12 @@ class TestPluginContext:
|
|||
def test_build_includes_required_fields(self) -> None:
|
||||
llm = MagicMock()
|
||||
ctx = PluginContext.build(
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
user_id="user-1",
|
||||
thread_visibility="PRIVATE", # type: ignore[arg-type]
|
||||
llm=llm,
|
||||
)
|
||||
assert ctx["search_space_id"] == 42
|
||||
assert ctx["workspace_id"] == 42
|
||||
assert ctx["user_id"] == "user-1"
|
||||
assert ctx["llm"] is llm
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from app.agents.chat.multi_agent_chat.main_agent.skills.backends import (
|
|||
SKILLS_BUILTIN_PREFIX,
|
||||
SKILLS_SPACE_PREFIX,
|
||||
BuiltinSkillsBackend,
|
||||
SearchSpaceSkillsBackend,
|
||||
WorkspaceSkillsBackend,
|
||||
build_skills_backend_factory,
|
||||
default_skills_sources,
|
||||
)
|
||||
|
|
@ -176,7 +176,7 @@ class _FakeKBBackend:
|
|||
return out
|
||||
|
||||
|
||||
class TestSearchSpaceSkillsBackend:
|
||||
class TestWorkspaceSkillsBackend:
|
||||
def test_remaps_paths_when_listing(self) -> None:
|
||||
listing = [
|
||||
{"path": "/documents/_skills/policy", "is_dir": True},
|
||||
|
|
@ -184,7 +184,7 @@ class TestSearchSpaceSkillsBackend:
|
|||
{"path": "/documents/other-folder/x.md", "is_dir": False},
|
||||
]
|
||||
kb = _FakeKBBackend(listing=listing, file_contents={})
|
||||
backend = SearchSpaceSkillsBackend(kb)
|
||||
backend = WorkspaceSkillsBackend(kb)
|
||||
infos = asyncio.run(backend.als_info("/"))
|
||||
assert kb.last_ls_path == "/documents/_skills"
|
||||
paths = [info["path"] for info in infos]
|
||||
|
|
@ -200,7 +200,7 @@ class TestSearchSpaceSkillsBackend:
|
|||
"/documents/_skills/policy/SKILL.md": b"---\nname: policy\n---\n",
|
||||
},
|
||||
)
|
||||
backend = SearchSpaceSkillsBackend(kb)
|
||||
backend = WorkspaceSkillsBackend(kb)
|
||||
responses = asyncio.run(backend.adownload_files(["/policy/SKILL.md"]))
|
||||
assert kb.last_download_paths == ["/documents/_skills/policy/SKILL.md"]
|
||||
assert responses[0].path == "/policy/SKILL.md"
|
||||
|
|
@ -208,7 +208,7 @@ class TestSearchSpaceSkillsBackend:
|
|||
assert responses[0].content is not None
|
||||
|
||||
def test_sync_methods_raise_not_implemented(self) -> None:
|
||||
backend = SearchSpaceSkillsBackend(_FakeKBBackend([], {}))
|
||||
backend = WorkspaceSkillsBackend(_FakeKBBackend([], {}))
|
||||
with pytest.raises(NotImplementedError):
|
||||
backend.ls_info("/")
|
||||
with pytest.raises(NotImplementedError):
|
||||
|
|
@ -221,7 +221,7 @@ class TestSearchSpaceSkillsBackend:
|
|||
],
|
||||
file_contents={},
|
||||
)
|
||||
backend = SearchSpaceSkillsBackend(kb, kb_root="/skills_admin")
|
||||
backend = WorkspaceSkillsBackend(kb, kb_root="/skills_admin")
|
||||
infos = asyncio.run(backend.als_info("/"))
|
||||
assert kb.last_ls_path == "/skills_admin"
|
||||
assert infos[0]["path"] == "/x"
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ async def test_generate_resume_defaults_to_one_page_target(monkeypatch) -> None:
|
|||
monkeypatch.setattr(resume_tool, "_compile_typst", lambda _source: b"pdf")
|
||||
monkeypatch.setattr(resume_tool, "_count_pdf_pages", lambda _pdf: 1)
|
||||
|
||||
tool = resume_tool.create_generate_resume_tool(search_space_id=1, thread_id=1)
|
||||
tool = resume_tool.create_generate_resume_tool(workspace_id=1, thread_id=1)
|
||||
result = await _invoke(tool, {"user_info": "Jane Doe experience"})
|
||||
|
||||
assert result["status"] == "ready"
|
||||
|
|
@ -178,7 +178,7 @@ async def test_generate_resume_compresses_when_over_limit(monkeypatch) -> None:
|
|||
page_counts = iter([2, 1])
|
||||
monkeypatch.setattr(resume_tool, "_count_pdf_pages", lambda _pdf: next(page_counts))
|
||||
|
||||
tool = resume_tool.create_generate_resume_tool(search_space_id=1, thread_id=1)
|
||||
tool = resume_tool.create_generate_resume_tool(workspace_id=1, thread_id=1)
|
||||
result = await _invoke(tool, {"user_info": "Jane Doe experience", "max_pages": 1})
|
||||
|
||||
assert result["status"] == "ready"
|
||||
|
|
@ -213,7 +213,7 @@ async def test_generate_resume_returns_ready_when_target_not_met(monkeypatch) ->
|
|||
page_counts = iter([3, 3, 2])
|
||||
monkeypatch.setattr(resume_tool, "_count_pdf_pages", lambda _pdf: next(page_counts))
|
||||
|
||||
tool = resume_tool.create_generate_resume_tool(search_space_id=1, thread_id=1)
|
||||
tool = resume_tool.create_generate_resume_tool(workspace_id=1, thread_id=1)
|
||||
result = await _invoke(tool, {"user_info": "Jane Doe experience", "max_pages": 1})
|
||||
|
||||
assert result["status"] == "ready"
|
||||
|
|
@ -246,7 +246,7 @@ async def test_generate_resume_fails_when_hard_limit_exceeded(monkeypatch) -> No
|
|||
page_counts = iter([7, 6, 6])
|
||||
monkeypatch.setattr(resume_tool, "_count_pdf_pages", lambda _pdf: next(page_counts))
|
||||
|
||||
tool = resume_tool.create_generate_resume_tool(search_space_id=1, thread_id=1)
|
||||
tool = resume_tool.create_generate_resume_tool(workspace_id=1, thread_id=1)
|
||||
result = await _invoke(tool, {"user_info": "Jane Doe experience", "max_pages": 1})
|
||||
|
||||
assert result["status"] == "failed"
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
"""Lock the runtime model-policy backstop in ``build_dependencies``.
|
||||
|
||||
Automations resolve their LLM from the *captured* ``chat_model_id`` snapshot (so
|
||||
runs are insulated from later chat/search-space model changes), and the model
|
||||
runs are insulated from later chat/workspace model changes), and the model
|
||||
policy is re-checked at run time so a captured model that is no longer billable
|
||||
fails the run clearly. When no snapshot is present, resolution falls back to the
|
||||
live search space.
|
||||
live workspace.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -25,20 +25,20 @@ pytestmark = pytest.mark.unit
|
|||
|
||||
|
||||
class _FakeSession:
|
||||
"""Minimal async session whose ``get`` returns a preset search space."""
|
||||
"""Minimal async session whose ``get`` returns a preset workspace."""
|
||||
|
||||
def __init__(self, search_space: Any) -> None:
|
||||
self._search_space = search_space
|
||||
def __init__(self, workspace: Any) -> None:
|
||||
self._workspace = workspace
|
||||
|
||||
async def get(self, _model: Any, _pk: int) -> Any:
|
||||
return self._search_space
|
||||
return self._workspace
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patched_side_effects(monkeypatch: pytest.MonkeyPatch):
|
||||
"""Stub the connector setup + checkpointer so only policy/LLM logic runs."""
|
||||
|
||||
async def _fake_setup(_session, *, search_space_id):
|
||||
async def _fake_setup(_session, *, workspace_id):
|
||||
return (SimpleNamespace(name="connector"), "fc-key")
|
||||
|
||||
monkeypatch.setattr(deps_mod, "setup_connector_and_firecrawl", _fake_setup)
|
||||
|
|
@ -48,35 +48,35 @@ def patched_side_effects(monkeypatch: pytest.MonkeyPatch):
|
|||
async def test_build_dependencies_resolves_captured_chat_model_id(
|
||||
monkeypatch: pytest.MonkeyPatch, patched_side_effects
|
||||
) -> None:
|
||||
"""The bundle loads with the *captured* ``chat_model_id``, not the live search space."""
|
||||
"""The bundle loads with the *captured* ``chat_model_id``, not the live workspace."""
|
||||
captured: dict[str, Any] = {}
|
||||
|
||||
async def _fake_load(_session, *, config_id, search_space_id):
|
||||
async def _fake_load(_session, *, config_id, workspace_id):
|
||||
captured["config_id"] = config_id
|
||||
captured["search_space_id"] = search_space_id
|
||||
captured["workspace_id"] = workspace_id
|
||||
return (SimpleNamespace(name="llm"), SimpleNamespace(name="agent_config"), None)
|
||||
|
||||
monkeypatch.setattr(deps_mod, "load_llm_bundle", _fake_load)
|
||||
# Captured path validates the explicit ids; passes for this test.
|
||||
monkeypatch.setattr(deps_mod, "assert_models_billable", lambda **_kw: None)
|
||||
# A different value on the live search space proves we ignore it when a
|
||||
# A different value on the live workspace proves we ignore it when a
|
||||
# snapshot is supplied.
|
||||
monkeypatch.setattr(
|
||||
deps_mod,
|
||||
"assert_automation_models_billable",
|
||||
lambda _ss: pytest.fail("search-space policy should not run on captured path"),
|
||||
lambda _ss: pytest.fail("workspace policy should not run on captured path"),
|
||||
)
|
||||
|
||||
search_space = SimpleNamespace(chat_model_id=-99)
|
||||
workspace = SimpleNamespace(chat_model_id=-99)
|
||||
result = await build_dependencies(
|
||||
session=_FakeSession(search_space),
|
||||
search_space_id=42,
|
||||
session=_FakeSession(workspace),
|
||||
workspace_id=42,
|
||||
chat_model_id=-7,
|
||||
image_gen_model_id=5,
|
||||
vision_model_id=-1,
|
||||
)
|
||||
|
||||
assert captured == {"config_id": -7, "search_space_id": 42}
|
||||
assert captured == {"config_id": -7, "workspace_id": 42}
|
||||
assert result.llm.name == "llm"
|
||||
assert result.firecrawl_api_key == "fc-key"
|
||||
|
||||
|
|
@ -84,7 +84,7 @@ async def test_build_dependencies_resolves_captured_chat_model_id(
|
|||
async def test_build_dependencies_validates_captured_ids(
|
||||
monkeypatch: pytest.MonkeyPatch, patched_side_effects
|
||||
) -> None:
|
||||
"""The captured ids (not the search space) are what gets policy-checked."""
|
||||
"""The captured ids (not the workspace) are what gets policy-checked."""
|
||||
seen: dict[str, Any] = {}
|
||||
|
||||
def _capture(**kwargs):
|
||||
|
|
@ -92,14 +92,14 @@ async def test_build_dependencies_validates_captured_ids(
|
|||
|
||||
monkeypatch.setattr(deps_mod, "assert_models_billable", _capture)
|
||||
|
||||
async def _fake_load(_session, *, config_id, search_space_id):
|
||||
async def _fake_load(_session, *, config_id, workspace_id):
|
||||
return (SimpleNamespace(name="llm"), SimpleNamespace(name="agent_config"), None)
|
||||
|
||||
monkeypatch.setattr(deps_mod, "load_llm_bundle", _fake_load)
|
||||
|
||||
await build_dependencies(
|
||||
session=_FakeSession(SimpleNamespace(chat_model_id=0)),
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
chat_model_id=-7,
|
||||
image_gen_model_id=5,
|
||||
vision_model_id=-1,
|
||||
|
|
@ -132,20 +132,20 @@ async def test_build_dependencies_raises_on_captured_policy_violation(
|
|||
with pytest.raises(DependencyError):
|
||||
await build_dependencies(
|
||||
session=_FakeSession(SimpleNamespace(chat_model_id=-7)),
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
chat_model_id=-7,
|
||||
image_gen_model_id=-2,
|
||||
vision_model_id=-1,
|
||||
)
|
||||
|
||||
|
||||
async def test_build_dependencies_falls_back_to_search_space(
|
||||
async def test_build_dependencies_falls_back_to_workspace(
|
||||
monkeypatch: pytest.MonkeyPatch, patched_side_effects
|
||||
) -> None:
|
||||
"""With no captured snapshot, resolve + validate the live search space."""
|
||||
"""With no captured snapshot, resolve + validate the live workspace."""
|
||||
captured: dict[str, Any] = {}
|
||||
|
||||
async def _fake_load(_session, *, config_id, search_space_id):
|
||||
async def _fake_load(_session, *, config_id, workspace_id):
|
||||
captured["config_id"] = config_id
|
||||
return (SimpleNamespace(name="llm"), SimpleNamespace(name="agent_config"), None)
|
||||
|
||||
|
|
@ -157,18 +157,18 @@ async def test_build_dependencies_falls_back_to_search_space(
|
|||
lambda **_kw: pytest.fail("captured policy should not run on fallback path"),
|
||||
)
|
||||
|
||||
search_space = SimpleNamespace(chat_model_id=-7)
|
||||
workspace = SimpleNamespace(chat_model_id=-7)
|
||||
result = await build_dependencies(
|
||||
session=_FakeSession(search_space), search_space_id=42
|
||||
session=_FakeSession(workspace), workspace_id=42
|
||||
)
|
||||
|
||||
assert captured == {"config_id": -7}
|
||||
assert result.llm.name == "llm"
|
||||
|
||||
|
||||
async def test_build_dependencies_raises_when_search_space_missing(
|
||||
async def test_build_dependencies_raises_when_workspace_missing(
|
||||
patched_side_effects,
|
||||
) -> None:
|
||||
"""A missing search space (fallback path) surfaces as a ``DependencyError``."""
|
||||
"""A missing workspace (fallback path) surfaces as a ``DependencyError``."""
|
||||
with pytest.raises(DependencyError):
|
||||
await build_dependencies(session=_FakeSession(None), search_space_id=999)
|
||||
await build_dependencies(session=_FakeSession(None), workspace_id=999)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ def _action_context() -> ActionContext:
|
|||
session=cast(AsyncSession, None),
|
||||
run_id=1,
|
||||
step_id="s1",
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
creator_user_id=None,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""Lock that the executor propagates the captured model snapshot into the
|
||||
``ActionContext``, so runs resolve their own model (insulated from chat /
|
||||
search-space changes) and not the live search space.
|
||||
workspace changes) and not the live workspace.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -21,7 +21,7 @@ pytestmark = pytest.mark.unit
|
|||
def _run() -> SimpleNamespace:
|
||||
return SimpleNamespace(
|
||||
id=1,
|
||||
automation=SimpleNamespace(search_space_id=42, created_by_user_id="u-1"),
|
||||
automation=SimpleNamespace(workspace_id=42, created_by_user_id="u-1"),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ def test_build_action_ctx_propagates_captured_models() -> None:
|
|||
models,
|
||||
)
|
||||
|
||||
assert ctx.search_space_id == 42
|
||||
assert ctx.workspace_id == 42
|
||||
assert ctx.chat_model_id == -1
|
||||
assert ctx.image_gen_model_id == 5
|
||||
assert ctx.vision_model_id == -1
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@ _VALID_DEFINITION = {
|
|||
|
||||
|
||||
def test_automation_create_accepts_valid_minimal_payload() -> None:
|
||||
"""Happy path: just search_space_id, name, and a valid definition.
|
||||
"""Happy path: just workspace_id, name, and a valid definition.
|
||||
Triggers default to ``[]`` so users can attach them later."""
|
||||
payload = AutomationCreate.model_validate(
|
||||
{
|
||||
"search_space_id": 1,
|
||||
"workspace_id": 1,
|
||||
"name": "Daily digest",
|
||||
"definition": _VALID_DEFINITION,
|
||||
}
|
||||
|
|
@ -39,7 +39,7 @@ def test_automation_create_cascades_validation_into_nested_definition() -> None:
|
|||
with pytest.raises(ValidationError):
|
||||
AutomationCreate.model_validate(
|
||||
{
|
||||
"search_space_id": 1,
|
||||
"workspace_id": 1,
|
||||
"name": "Bad",
|
||||
"definition": {"name": "X", "plan": []}, # empty plan
|
||||
}
|
||||
|
|
@ -51,7 +51,7 @@ def test_automation_create_rejects_unknown_top_level_field() -> None:
|
|||
with pytest.raises(ValidationError):
|
||||
AutomationCreate.model_validate(
|
||||
{
|
||||
"search_space_id": 1,
|
||||
"workspace_id": 1,
|
||||
"name": "X",
|
||||
"definition": _VALID_DEFINITION,
|
||||
"owner": "tg", # not allowed
|
||||
|
|
@ -64,7 +64,7 @@ def test_automation_create_rejects_empty_name() -> None:
|
|||
with pytest.raises(ValidationError):
|
||||
AutomationCreate.model_validate(
|
||||
{
|
||||
"search_space_id": 1,
|
||||
"workspace_id": 1,
|
||||
"name": "",
|
||||
"definition": _VALID_DEFINITION,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""Lock creation-time model-policy enforcement in ``AutomationService``.
|
||||
|
||||
Creation (REST + manual builder) rejects search spaces whose models aren't
|
||||
Creation (REST + manual builder) rejects workspaces whose models aren't
|
||||
billable for automations with HTTP 422, mirroring the runtime backstop. These
|
||||
tests isolate the new ``_assert_models_billable`` / ``model_eligibility`` paths
|
||||
without touching the DB commit.
|
||||
|
|
@ -29,13 +29,13 @@ pytestmark = pytest.mark.unit
|
|||
|
||||
|
||||
class _FakeSession:
|
||||
def __init__(self, search_space: Any) -> None:
|
||||
self._search_space = search_space
|
||||
def __init__(self, workspace: Any) -> None:
|
||||
self._workspace = workspace
|
||||
self.added: list[Any] = []
|
||||
self.commits = 0
|
||||
|
||||
async def get(self, _model: Any, _pk: int) -> Any:
|
||||
return self._search_space
|
||||
return self._workspace
|
||||
|
||||
def add(self, obj: Any) -> None:
|
||||
self.added.append(obj)
|
||||
|
|
@ -44,9 +44,9 @@ class _FakeSession:
|
|||
self.commits += 1
|
||||
|
||||
|
||||
def _service(search_space: Any) -> AutomationService:
|
||||
def _service(workspace: Any) -> AutomationService:
|
||||
return AutomationService(
|
||||
session=_FakeSession(search_space),
|
||||
session=_FakeSession(workspace),
|
||||
auth=AuthContext.session(SimpleNamespace(id="u-1")),
|
||||
)
|
||||
|
||||
|
|
@ -81,7 +81,7 @@ async def test_assert_models_billable_raises_422_on_violation(
|
|||
async def test_assert_models_billable_raises_404_when_missing(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""A missing search space is a 404, not a policy error."""
|
||||
"""A missing workspace is a 404, not a policy error."""
|
||||
monkeypatch.setattr(
|
||||
automation_mod, "assert_automation_models_billable", lambda _ss: None
|
||||
)
|
||||
|
|
@ -93,23 +93,23 @@ async def test_assert_models_billable_raises_404_when_missing(
|
|||
assert exc_info.value.status_code == 404
|
||||
|
||||
|
||||
async def test_assert_models_billable_returns_search_space_when_ok(
|
||||
async def test_assert_models_billable_returns_workspace_when_ok(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""When the policy accepts, the loaded search space is returned for reuse."""
|
||||
"""When the policy accepts, the loaded workspace is returned for reuse."""
|
||||
monkeypatch.setattr(
|
||||
automation_mod, "assert_automation_models_billable", lambda _ss: None
|
||||
)
|
||||
|
||||
search_space = SimpleNamespace(chat_model_id=-1)
|
||||
service = _service(search_space)
|
||||
assert await service._assert_models_billable(1) is search_space
|
||||
workspace = SimpleNamespace(chat_model_id=-1)
|
||||
service = _service(workspace)
|
||||
assert await service._assert_models_billable(1) is workspace
|
||||
|
||||
|
||||
async def test_create_injects_captured_models_from_search_space(
|
||||
async def test_create_injects_captured_models_from_workspace(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""create() snapshots the search space's model prefs onto the definition."""
|
||||
"""create() snapshots the workspace's model prefs onto the definition."""
|
||||
monkeypatch.setattr(
|
||||
automation_mod, "assert_automation_models_billable", lambda _ss: None
|
||||
)
|
||||
|
|
@ -124,14 +124,14 @@ async def test_create_injects_captured_models_from_search_space(
|
|||
|
||||
monkeypatch.setattr(AutomationService, "_get_with_triggers_or_raise", _return_added)
|
||||
|
||||
search_space = SimpleNamespace(
|
||||
workspace = SimpleNamespace(
|
||||
chat_model_id=-1,
|
||||
image_gen_model_id=5,
|
||||
vision_model_id=-1,
|
||||
)
|
||||
service = _service(search_space)
|
||||
service = _service(workspace)
|
||||
payload = AutomationCreate(
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
name="A",
|
||||
definition=_definition(),
|
||||
)
|
||||
|
|
@ -148,7 +148,7 @@ async def test_create_injects_captured_models_from_search_space(
|
|||
async def test_create_treats_unset_prefs_as_auto_zero(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""``None`` search-space prefs are captured as ``0`` (Auto) ids."""
|
||||
"""``None`` workspace prefs are captured as ``0`` (Auto) ids."""
|
||||
monkeypatch.setattr(
|
||||
automation_mod, "assert_automation_models_billable", lambda _ss: None
|
||||
)
|
||||
|
|
@ -163,13 +163,13 @@ async def test_create_treats_unset_prefs_as_auto_zero(
|
|||
|
||||
monkeypatch.setattr(AutomationService, "_get_with_triggers_or_raise", _return_added)
|
||||
|
||||
search_space = SimpleNamespace(
|
||||
workspace = SimpleNamespace(
|
||||
chat_model_id=None,
|
||||
image_gen_model_id=None,
|
||||
vision_model_id=None,
|
||||
)
|
||||
service = _service(search_space)
|
||||
payload = AutomationCreate(search_space_id=1, name="A", definition=_definition())
|
||||
service = _service(workspace)
|
||||
payload = AutomationCreate(workspace_id=1, name="A", definition=_definition())
|
||||
|
||||
automation = await service.create(payload)
|
||||
|
||||
|
|
@ -185,7 +185,7 @@ async def test_create_honors_selected_models_when_provided(
|
|||
) -> None:
|
||||
"""When the payload carries ``definition.models`` they are validated + kept.
|
||||
|
||||
The search-space snapshot path is bypassed entirely (no
|
||||
The workspace snapshot path is bypassed entirely (no
|
||||
``assert_automation_models_billable`` call).
|
||||
"""
|
||||
|
||||
|
|
@ -217,7 +217,7 @@ async def test_create_honors_selected_models_when_provided(
|
|||
|
||||
service = _service(SimpleNamespace(chat_model_id=-99))
|
||||
payload = AutomationCreate(
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
name="A",
|
||||
definition=_definition(
|
||||
models=AutomationModels(
|
||||
|
|
@ -257,7 +257,7 @@ async def test_create_rejects_unbillable_selected_models(
|
|||
|
||||
service = _service(SimpleNamespace(chat_model_id=-3))
|
||||
payload = AutomationCreate(
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
name="A",
|
||||
definition=_definition(
|
||||
models=AutomationModels(
|
||||
|
|
@ -284,7 +284,7 @@ async def test_update_preserves_captured_models(
|
|||
"vision_model_id": -1,
|
||||
}
|
||||
existing = SimpleNamespace(
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
definition={"name": "A", "plan": [], "models": captured},
|
||||
version=3,
|
||||
)
|
||||
|
|
@ -315,7 +315,7 @@ async def test_update_honors_changed_models_when_valid(
|
|||
) -> None:
|
||||
"""A definition edit with a *changed* models block validates + keeps it."""
|
||||
existing = SimpleNamespace(
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
definition={
|
||||
"name": "A",
|
||||
"plan": [],
|
||||
|
|
@ -376,7 +376,7 @@ async def test_update_rejects_changed_unbillable_models(
|
|||
) -> None:
|
||||
"""A *changed* non-billable models block is rejected with HTTP 422."""
|
||||
existing = SimpleNamespace(
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
definition={
|
||||
"name": "A",
|
||||
"plan": [],
|
||||
|
|
@ -438,7 +438,7 @@ async def test_update_keeps_unchanged_models_without_revalidation(
|
|||
"vision_model_id": -1,
|
||||
}
|
||||
existing = SimpleNamespace(
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
definition={"name": "A", "plan": [], "models": captured},
|
||||
version=3,
|
||||
)
|
||||
|
|
@ -477,7 +477,7 @@ async def test_model_eligibility_authorizes_and_returns_payload(
|
|||
authorized: dict[str, Any] = {}
|
||||
|
||||
async def _fake_check_permission(_session, _user, ss_id, permission, _msg):
|
||||
authorized["search_space_id"] = ss_id
|
||||
authorized["workspace_id"] = ss_id
|
||||
authorized["permission"] = permission
|
||||
|
||||
monkeypatch.setattr(automation_mod, "check_permission", _fake_check_permission)
|
||||
|
|
@ -488,8 +488,8 @@ async def test_model_eligibility_authorizes_and_returns_payload(
|
|||
)
|
||||
|
||||
service = _service(SimpleNamespace(chat_model_id=-2))
|
||||
result = await service.model_eligibility(search_space_id=5)
|
||||
result = await service.model_eligibility(workspace_id=5)
|
||||
|
||||
assert result == {"allowed": False, "violations": [{"kind": "image"}]}
|
||||
assert authorized["search_space_id"] == 5
|
||||
assert authorized["workspace_id"] == 5
|
||||
assert authorized["permission"] == "automations:read"
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ from app.automations.services.model_policy import (
|
|||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
def _search_space(*, llm: int | None, image: int | None, vision: int | None):
|
||||
"""Minimal stand-in for the ``SearchSpace`` ORM row the policy reads."""
|
||||
def _workspace(*, llm: int | None, image: int | None, vision: int | None):
|
||||
"""Minimal stand-in for the ``Workspace`` ORM row the policy reads."""
|
||||
return SimpleNamespace(
|
||||
chat_model_id=llm,
|
||||
image_gen_model_id=image,
|
||||
|
|
@ -95,15 +95,15 @@ def test_unknown_global_id_is_blocked(kind: str, patched_globals) -> None:
|
|||
|
||||
def test_eligibility_all_billable(patched_globals) -> None:
|
||||
"""Premium LLM + BYOK image + premium vision → allowed, no violations."""
|
||||
search_space = _search_space(llm=-1, image=5, vision=-1)
|
||||
result = get_automation_model_eligibility(search_space)
|
||||
workspace = _workspace(llm=-1, image=5, vision=-1)
|
||||
result = get_automation_model_eligibility(workspace)
|
||||
assert result == {"allowed": True, "violations": []}
|
||||
|
||||
|
||||
def test_eligibility_reports_each_violation(patched_globals) -> None:
|
||||
"""A free LLM, Auto image, and free vision each produce a violation."""
|
||||
search_space = _search_space(llm=-2, image=0, vision=-2)
|
||||
result = get_automation_model_eligibility(search_space)
|
||||
workspace = _workspace(llm=-2, image=0, vision=-2)
|
||||
result = get_automation_model_eligibility(workspace)
|
||||
|
||||
assert result["allowed"] is False
|
||||
kinds = {v["kind"] for v in result["violations"]}
|
||||
|
|
@ -115,9 +115,9 @@ def test_eligibility_reports_each_violation(patched_globals) -> None:
|
|||
|
||||
def test_assert_raises_with_violations(patched_globals) -> None:
|
||||
"""``assert_automation_models_billable`` raises when any slot is blocked."""
|
||||
search_space = _search_space(llm=0, image=5, vision=-1)
|
||||
workspace = _workspace(llm=0, image=5, vision=-1)
|
||||
with pytest.raises(AutomationModelPolicyError) as exc_info:
|
||||
assert_automation_models_billable(search_space)
|
||||
assert_automation_models_billable(workspace)
|
||||
|
||||
assert len(exc_info.value.violations) == 1
|
||||
assert exc_info.value.violations[0]["kind"] == "chat"
|
||||
|
|
@ -125,8 +125,8 @@ def test_assert_raises_with_violations(patched_globals) -> None:
|
|||
|
||||
def test_assert_passes_when_all_billable(patched_globals) -> None:
|
||||
"""No exception when every slot is premium or BYOK."""
|
||||
search_space = _search_space(llm=3, image=-1, vision=4)
|
||||
assert assert_automation_models_billable(search_space) is None
|
||||
workspace = _workspace(llm=3, image=-1, vision=4)
|
||||
assert assert_automation_models_billable(workspace) is None
|
||||
|
||||
|
||||
# --- ID-based core (used by the runtime backstop against captured snapshots) ---
|
||||
|
|
@ -170,9 +170,9 @@ def test_assert_models_billable_passes(patched_globals) -> None:
|
|||
)
|
||||
|
||||
|
||||
def test_search_space_wrapper_delegates_to_core(patched_globals) -> None:
|
||||
"""The search-space wrapper produces the same result as the ID core."""
|
||||
search_space = _search_space(llm=-2, image=0, vision=-2)
|
||||
assert get_automation_model_eligibility(search_space) == get_model_eligibility(
|
||||
def test_workspace_wrapper_delegates_to_core(patched_globals) -> None:
|
||||
"""The workspace wrapper produces the same result as the ID core."""
|
||||
workspace = _workspace(llm=-2, image=0, vision=-2)
|
||||
assert get_automation_model_eligibility(workspace) == get_model_eligibility(
|
||||
chat_model_id=-2, image_gen_model_id=0, vision_model_id=-2
|
||||
)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ def test_build_run_context_exposes_run_inputs_and_steps_namespaces() -> None:
|
|||
automation_id=7,
|
||||
automation_name="Weekly digest",
|
||||
automation_version=3,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
creator_id=creator,
|
||||
trigger_id=11,
|
||||
trigger_type="schedule",
|
||||
|
|
@ -41,7 +41,7 @@ def test_build_run_context_exposes_run_inputs_and_steps_namespaces() -> None:
|
|||
"automation_id": 7,
|
||||
"automation_name": "Weekly digest",
|
||||
"automation_version": 3,
|
||||
"search_space_id": 1,
|
||||
"workspace_id": 1,
|
||||
"creator_id": creator,
|
||||
"trigger_id": 11,
|
||||
"trigger_type": "schedule",
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ def test_scalar_value_is_implicit_equality() -> None:
|
|||
|
||||
|
||||
def test_multiple_fields_are_anded() -> None:
|
||||
flt = {"document_type": "FILE", "search_space_id": 7}
|
||||
assert matches(flt, {"document_type": "FILE", "search_space_id": 7}) is True
|
||||
assert matches(flt, {"document_type": "FILE", "search_space_id": 9}) is False
|
||||
flt = {"document_type": "FILE", "workspace_id": 7}
|
||||
assert matches(flt, {"document_type": "FILE", "workspace_id": 7}) is True
|
||||
assert matches(flt, {"document_type": "FILE", "workspace_id": 9}) is False
|
||||
|
||||
|
||||
def test_gt_operator_compares_greater_than() -> None:
|
||||
|
|
@ -97,12 +97,12 @@ def test_missing_field_never_matches_and_never_raises() -> None:
|
|||
|
||||
def test_logical_operators_compose_with_fields() -> None:
|
||||
flt = {
|
||||
"search_space_id": 7,
|
||||
"workspace_id": 7,
|
||||
"$or": [{"document_type": "FILE"}, {"document_type": "WEBPAGE"}],
|
||||
}
|
||||
assert matches(flt, {"search_space_id": 7, "document_type": "FILE"}) is True
|
||||
assert matches(flt, {"search_space_id": 9, "document_type": "FILE"}) is False
|
||||
assert matches(flt, {"search_space_id": 7, "document_type": "SLACK"}) is False
|
||||
assert matches(flt, {"workspace_id": 7, "document_type": "FILE"}) is True
|
||||
assert matches(flt, {"workspace_id": 9, "document_type": "FILE"}) is False
|
||||
assert matches(flt, {"workspace_id": 7, "document_type": "SLACK"}) is False
|
||||
|
||||
|
||||
def test_unknown_field_operator_raises_filter_error() -> None:
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ def test_runtime_inputs_flatten_payload_with_event_metadata() -> None:
|
|||
event = Event(
|
||||
event_type="document.indexed",
|
||||
payload={"document_id": 42, "document_type": "FILE"},
|
||||
search_space_id=7,
|
||||
workspace_id=7,
|
||||
)
|
||||
|
||||
inputs = event_runtime_inputs(event)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ pytestmark = pytest.mark.unit
|
|||
|
||||
|
||||
def _event(event_type: str = "document.indexed", **payload) -> Event:
|
||||
return Event(event_type=event_type, payload=payload, search_space_id=7)
|
||||
return Event(event_type=event_type, payload=payload, workspace_id=7)
|
||||
|
||||
|
||||
def test_matches_when_event_type_equal_and_filter_passes() -> None:
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ async def test_build_connector_doc_produces_correct_fields():
|
|||
page,
|
||||
markdown,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -77,7 +77,7 @@ async def test_build_connector_doc_produces_correct_fields():
|
|||
assert doc.unique_id == "abc-123"
|
||||
assert doc.document_type == DocumentType.CONFLUENCE_CONNECTOR
|
||||
assert doc.source_markdown == markdown
|
||||
assert doc.search_space_id == _SEARCH_SPACE_ID
|
||||
assert doc.workspace_id == _SEARCH_SPACE_ID
|
||||
assert doc.connector_id == _CONNECTOR_ID
|
||||
assert doc.created_by_id == _USER_ID
|
||||
assert doc.metadata["page_id"] == "abc-123"
|
||||
|
|
@ -181,7 +181,7 @@ async def _run_index(mocks, **overrides):
|
|||
return await index_confluence_pages(
|
||||
session=mocks["session"],
|
||||
connector_id=overrides.get("connector_id", _CONNECTOR_ID),
|
||||
search_space_id=overrides.get("search_space_id", _SEARCH_SPACE_ID),
|
||||
workspace_id=overrides.get("workspace_id", _SEARCH_SPACE_ID),
|
||||
user_id=overrides.get("user_id", _USER_ID),
|
||||
start_date=overrides.get("start_date", "2025-01-01"),
|
||||
end_date=overrides.get("end_date", "2025-12-31"),
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ async def test_single_file_returns_one_connector_document(
|
|||
mock_dropbox_client,
|
||||
[_make_file_dict("f1", "test.txt")],
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ async def test_multiple_files_all_produce_documents(
|
|||
mock_dropbox_client,
|
||||
files,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -121,7 +121,7 @@ async def test_one_download_exception_does_not_block_others(
|
|||
mock_dropbox_client,
|
||||
files,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -147,7 +147,7 @@ async def test_etl_error_counts_as_download_failure(
|
|||
mock_dropbox_client,
|
||||
files,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -185,7 +185,7 @@ async def test_concurrency_bounded_by_semaphore(
|
|||
mock_dropbox_client,
|
||||
files,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
max_concurrency=2,
|
||||
)
|
||||
|
|
@ -224,7 +224,7 @@ async def test_heartbeat_fires_during_parallel_downloads(
|
|||
mock_dropbox_client,
|
||||
files,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
on_heartbeat=_on_heartbeat,
|
||||
)
|
||||
|
|
@ -257,7 +257,7 @@ def full_scan_mocks(mock_dropbox_client, monkeypatch):
|
|||
|
||||
monkeypatch.setattr("app.config.config.ETL_SERVICE", "LLAMACLOUD")
|
||||
|
||||
async def _fake_skip(session, file, search_space_id):
|
||||
async def _fake_skip(session, file, workspace_id):
|
||||
from app.connectors.dropbox.file_types import should_skip_file as _skip
|
||||
|
||||
item_skip, unsup_ext = _skip(file)
|
||||
|
|
@ -389,7 +389,7 @@ def selected_files_mocks(mock_dropbox_client, monkeypatch):
|
|||
|
||||
skip_results: dict[str, tuple[bool, str | None]] = {}
|
||||
|
||||
async def _fake_skip(session, file, search_space_id):
|
||||
async def _fake_skip(session, file, workspace_id):
|
||||
return skip_results.get(file["id"], (False, None))
|
||||
|
||||
monkeypatch.setattr(_mod, "_should_skip_file", _fake_skip)
|
||||
|
|
@ -430,7 +430,7 @@ async def _run_selected(mocks, file_tuples):
|
|||
mocks["session"],
|
||||
file_tuples,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -547,7 +547,7 @@ async def test_delta_sync_deletions_call_remove_document(monkeypatch):
|
|||
|
||||
remove_calls: list[str] = []
|
||||
|
||||
async def _fake_remove(session, file_id, search_space_id):
|
||||
async def _fake_remove(session, file_id, workspace_id):
|
||||
remove_calls.append(file_id)
|
||||
|
||||
monkeypatch.setattr(_mod, "_remove_document", _fake_remove)
|
||||
|
|
@ -641,7 +641,7 @@ async def test_delta_sync_mix_deletions_and_upserts(monkeypatch):
|
|||
|
||||
remove_calls: list[str] = []
|
||||
|
||||
async def _fake_remove(session, file_id, search_space_id):
|
||||
async def _fake_remove(session, file_id, workspace_id):
|
||||
remove_calls.append(file_id)
|
||||
|
||||
monkeypatch.setattr(_mod, "_remove_document", _fake_remove)
|
||||
|
|
|
|||
|
|
@ -201,7 +201,7 @@ async def _run_gdrive_selected(mocks, file_ids):
|
|||
mocks["session"],
|
||||
file_ids,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -542,7 +542,7 @@ async def _run_onedrive_selected(mocks, file_ids):
|
|||
mocks["session"],
|
||||
file_ids,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -641,7 +641,7 @@ async def _run_dropbox_selected(mocks, file_paths):
|
|||
mocks["session"],
|
||||
file_paths,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ async def test_single_file_returns_one_connector_document(
|
|||
mock_drive_client,
|
||||
[_make_file_dict("f1", "test.txt")],
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ async def test_multiple_files_all_produce_documents(
|
|||
mock_drive_client,
|
||||
files,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ async def test_one_download_exception_does_not_block_others(
|
|||
mock_drive_client,
|
||||
files,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ async def test_etl_error_counts_as_download_failure(
|
|||
mock_drive_client,
|
||||
files,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ async def test_concurrency_bounded_by_semaphore(
|
|||
mock_drive_client,
|
||||
files,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
max_concurrency=2,
|
||||
)
|
||||
|
|
@ -219,7 +219,7 @@ async def test_heartbeat_fires_during_parallel_downloads(
|
|||
mock_drive_client,
|
||||
files,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
on_heartbeat=_on_heartbeat,
|
||||
)
|
||||
|
|
@ -284,7 +284,7 @@ def full_scan_mocks(mock_drive_client, monkeypatch):
|
|||
|
||||
skip_results: dict[str, tuple[bool, str | None]] = {}
|
||||
|
||||
async def _fake_skip(session, file, search_space_id):
|
||||
async def _fake_skip(session, file, workspace_id):
|
||||
return skip_results.get(file["id"], (False, None))
|
||||
|
||||
monkeypatch.setattr(_mod, "_should_skip_file", _fake_skip)
|
||||
|
|
@ -458,7 +458,7 @@ async def test_delta_sync_removals_serial_rest_parallel(monkeypatch):
|
|||
|
||||
remove_calls: list[str] = []
|
||||
|
||||
async def _fake_remove(session, file_id, search_space_id):
|
||||
async def _fake_remove(session, file_id, workspace_id):
|
||||
remove_calls.append(file_id)
|
||||
|
||||
monkeypatch.setattr(_mod, "_remove_document", _fake_remove)
|
||||
|
|
@ -532,7 +532,7 @@ def selected_files_mocks(mock_drive_client, monkeypatch):
|
|||
|
||||
skip_results: dict[str, tuple[bool, str | None]] = {}
|
||||
|
||||
async def _fake_skip(session, file, search_space_id):
|
||||
async def _fake_skip(session, file, workspace_id):
|
||||
return skip_results.get(file["id"], (False, None))
|
||||
|
||||
monkeypatch.setattr(_mod, "_should_skip_file", _fake_skip)
|
||||
|
|
@ -563,7 +563,7 @@ async def _run_selected(mocks, file_ids):
|
|||
mocks["session"],
|
||||
file_ids,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ async def test_build_connector_doc_produces_correct_fields():
|
|||
formatted,
|
||||
markdown,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -76,7 +76,7 @@ async def test_build_connector_doc_produces_correct_fields():
|
|||
assert doc.unique_id == "abc-123"
|
||||
assert doc.document_type == DocumentType.LINEAR_CONNECTOR
|
||||
assert doc.source_markdown == markdown
|
||||
assert doc.search_space_id == _SEARCH_SPACE_ID
|
||||
assert doc.workspace_id == _SEARCH_SPACE_ID
|
||||
assert doc.connector_id == _CONNECTOR_ID
|
||||
assert doc.created_by_id == _USER_ID
|
||||
assert doc.metadata["issue_id"] == "abc-123"
|
||||
|
|
@ -196,7 +196,7 @@ async def _run_index(mocks, **overrides):
|
|||
return await index_linear_issues(
|
||||
session=mocks["session"],
|
||||
connector_id=overrides.get("connector_id", _CONNECTOR_ID),
|
||||
search_space_id=overrides.get("search_space_id", _SEARCH_SPACE_ID),
|
||||
workspace_id=overrides.get("workspace_id", _SEARCH_SPACE_ID),
|
||||
user_id=overrides.get("user_id", _USER_ID),
|
||||
start_date=overrides.get("start_date", "2025-01-01"),
|
||||
end_date=overrides.get("end_date", "2025-12-31"),
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ async def test_build_connector_doc_produces_correct_fields():
|
|||
page,
|
||||
markdown,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ async def test_build_connector_doc_produces_correct_fields():
|
|||
assert doc.unique_id == "abc-123"
|
||||
assert doc.document_type == DocumentType.NOTION_CONNECTOR
|
||||
assert doc.source_markdown == markdown
|
||||
assert doc.search_space_id == _SEARCH_SPACE_ID
|
||||
assert doc.workspace_id == _SEARCH_SPACE_ID
|
||||
assert doc.connector_id == _CONNECTOR_ID
|
||||
assert doc.created_by_id == _USER_ID
|
||||
assert doc.metadata["page_title"] == "My Notion Page"
|
||||
|
|
@ -159,7 +159,7 @@ async def _run_index(mocks, **overrides):
|
|||
return await index_notion_pages(
|
||||
session=mocks["session"],
|
||||
connector_id=overrides.get("connector_id", _CONNECTOR_ID),
|
||||
search_space_id=overrides.get("search_space_id", _SEARCH_SPACE_ID),
|
||||
workspace_id=overrides.get("workspace_id", _SEARCH_SPACE_ID),
|
||||
user_id=overrides.get("user_id", _USER_ID),
|
||||
start_date=overrides.get("start_date", "2025-01-01"),
|
||||
end_date=overrides.get("end_date", "2025-12-31"),
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ async def test_single_file_returns_one_connector_document(
|
|||
mock_onedrive_client,
|
||||
[_make_file_dict("f1", "test.txt")],
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -88,7 +88,7 @@ async def test_multiple_files_all_produce_documents(
|
|||
mock_onedrive_client,
|
||||
files,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ async def test_one_download_exception_does_not_block_others(
|
|||
mock_onedrive_client,
|
||||
files,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -141,7 +141,7 @@ async def test_etl_error_counts_as_download_failure(
|
|||
mock_onedrive_client,
|
||||
files,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
)
|
||||
|
||||
|
|
@ -179,7 +179,7 @@ async def test_concurrency_bounded_by_semaphore(
|
|||
mock_onedrive_client,
|
||||
files,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
max_concurrency=2,
|
||||
)
|
||||
|
|
@ -218,7 +218,7 @@ async def test_heartbeat_fires_during_parallel_downloads(
|
|||
mock_onedrive_client,
|
||||
files,
|
||||
connector_id=_CONNECTOR_ID,
|
||||
search_space_id=_SEARCH_SPACE_ID,
|
||||
workspace_id=_SEARCH_SPACE_ID,
|
||||
user_id=_USER_ID,
|
||||
on_heartbeat=_on_heartbeat,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ pytestmark = pytest.mark.unit
|
|||
|
||||
|
||||
def _event() -> Event:
|
||||
return Event(event_type="x.happened", payload={"k": "v"}, search_space_id=1)
|
||||
return Event(event_type="x.happened", payload={"k": "v"}, workspace_id=1)
|
||||
|
||||
|
||||
async def _noop(_event: Event) -> None:
|
||||
|
|
@ -152,13 +152,13 @@ async def test_publish_builds_a_stamped_event_and_fans_it_out() -> None:
|
|||
received.append(event)
|
||||
|
||||
bus.subscribe(handler)
|
||||
await bus.publish("document.indexed", {"document_id": 42}, search_space_id=7)
|
||||
await bus.publish("document.indexed", {"document_id": 42}, workspace_id=7)
|
||||
|
||||
assert len(received) == 1
|
||||
event = received[0]
|
||||
assert event.event_type == "document.indexed"
|
||||
assert event.payload == {"document_id": 42}
|
||||
assert event.search_space_id == 7
|
||||
assert event.workspace_id == 7
|
||||
# Engine-stamped identity/time on the way through.
|
||||
assert event.event_id
|
||||
assert event.occurred_at
|
||||
|
|
@ -172,10 +172,10 @@ async def test_publish_defaults_payload_to_empty_dict() -> None:
|
|||
received.append(event)
|
||||
|
||||
bus.subscribe(handler)
|
||||
await bus.publish("x.happened", search_space_id=1)
|
||||
await bus.publish("x.happened", workspace_id=1)
|
||||
|
||||
assert received[0].payload == {}
|
||||
|
||||
|
||||
async def test_publish_with_no_subscribers_is_a_noop() -> None:
|
||||
await EventBus().publish("x.happened", search_space_id=1) # must not raise
|
||||
await EventBus().publish("x.happened", workspace_id=1) # must not raise
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ pytestmark = pytest.mark.unit
|
|||
def _call(**overrides: Any) -> dict[str, Any] | None:
|
||||
defaults: dict[str, Any] = {
|
||||
"document_id": 1,
|
||||
"search_space_id": 10,
|
||||
"workspace_id": 10,
|
||||
"new_folder_id": 7,
|
||||
"previous_folder_id": None,
|
||||
"folder_id_changed": True,
|
||||
|
|
@ -33,7 +33,7 @@ def test_folder_set_ready_fires() -> None:
|
|||
|
||||
assert result is not None
|
||||
assert result["event_type"] == "document.entered_folder"
|
||||
assert result["search_space_id"] == 10
|
||||
assert result["workspace_id"] == 10
|
||||
assert result["payload"]["folder_id"] == 7
|
||||
assert result["payload"]["previous_folder_id"] is None
|
||||
|
||||
|
|
|
|||
|
|
@ -16,17 +16,17 @@ def test_event_carries_caller_supplied_facts() -> None:
|
|||
event = Event(
|
||||
event_type="document.indexed",
|
||||
payload={"document_id": 42, "content_type": "pdf"},
|
||||
search_space_id=7,
|
||||
workspace_id=7,
|
||||
)
|
||||
|
||||
assert event.event_type == "document.indexed"
|
||||
assert event.payload == {"document_id": 42, "content_type": "pdf"}
|
||||
assert event.search_space_id == 7
|
||||
assert event.workspace_id == 7
|
||||
|
||||
|
||||
def test_event_stamps_identity_and_time_when_not_supplied() -> None:
|
||||
"""Engine stamps id + time so subscribers can dedup/order."""
|
||||
event = Event(event_type="x.happened", payload={}, search_space_id=1)
|
||||
event = Event(event_type="x.happened", payload={}, workspace_id=1)
|
||||
|
||||
assert event.event_id
|
||||
assert isinstance(event.occurred_at, datetime)
|
||||
|
|
@ -34,8 +34,8 @@ def test_event_stamps_identity_and_time_when_not_supplied() -> None:
|
|||
|
||||
def test_event_ids_are_unique_per_instance() -> None:
|
||||
"""Two events published with identical content are still distinct facts."""
|
||||
first = Event(event_type="x.happened", payload={}, search_space_id=1)
|
||||
second = Event(event_type="x.happened", payload={}, search_space_id=1)
|
||||
first = Event(event_type="x.happened", payload={}, workspace_id=1)
|
||||
second = Event(event_type="x.happened", payload={}, workspace_id=1)
|
||||
|
||||
assert first.event_id != second.event_id
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ def test_event_survives_json_round_trip() -> None:
|
|||
original = Event(
|
||||
event_type="podcast.generated",
|
||||
payload={"podcast_id": 9, "duration_s": 123.5},
|
||||
search_space_id=3,
|
||||
workspace_id=3,
|
||||
)
|
||||
|
||||
restored = Event.model_validate_json(original.model_dump_json())
|
||||
|
|
|
|||
|
|
@ -330,10 +330,10 @@ async def test_discord_gateway_install_returns_oauth_url(monkeypatch, mocker):
|
|||
"http://localhost:8000/api/v1/gateway/discord/callback",
|
||||
)
|
||||
monkeypatch.setattr(routes.config, "SECRET_KEY", "test-secret")
|
||||
monkeypatch.setattr(routes, "check_search_space_access", mocker.AsyncMock())
|
||||
monkeypatch.setattr(routes, "check_workspace_access", mocker.AsyncMock())
|
||||
|
||||
response = await routes.install_discord_gateway(
|
||||
search_space_id=123,
|
||||
workspace_id=123,
|
||||
auth=AuthContext.session(
|
||||
SimpleNamespace(id="00000000-0000-0000-0000-000000000001")
|
||||
),
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ def test_valid_document_created_with_required_fields():
|
|||
source_markdown="## Task\n\nSome content.",
|
||||
unique_id="task-1",
|
||||
document_type=DocumentType.CLICKUP_CONNECTOR,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
connector_id=42,
|
||||
created_by_id="00000000-0000-0000-0000-000000000001",
|
||||
)
|
||||
|
|
@ -32,7 +32,7 @@ def test_omitting_created_by_id_raises():
|
|||
source_markdown="## Content",
|
||||
unique_id="task-1",
|
||||
document_type=DocumentType.CLICKUP_CONNECTOR,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
connector_id=42,
|
||||
)
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ def test_empty_source_markdown_raises():
|
|||
source_markdown="",
|
||||
unique_id="task-1",
|
||||
document_type=DocumentType.CLICKUP_CONNECTOR,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ def test_whitespace_only_source_markdown_raises():
|
|||
source_markdown=" \n\t ",
|
||||
unique_id="task-1",
|
||||
document_type=DocumentType.CLICKUP_CONNECTOR,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ def test_empty_title_raises():
|
|||
source_markdown="## Content",
|
||||
unique_id="task-1",
|
||||
document_type=DocumentType.CLICKUP_CONNECTOR,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -81,21 +81,21 @@ def test_empty_created_by_id_raises():
|
|||
source_markdown="## Content",
|
||||
unique_id="task-1",
|
||||
document_type=DocumentType.CLICKUP_CONNECTOR,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
connector_id=42,
|
||||
created_by_id="",
|
||||
)
|
||||
|
||||
|
||||
def test_zero_search_space_id_raises():
|
||||
"""search_space_id of zero raises a validation error."""
|
||||
def test_zero_workspace_id_raises():
|
||||
"""workspace_id of zero raises a validation error."""
|
||||
with pytest.raises(ValidationError):
|
||||
ConnectorDocument(
|
||||
title="Task",
|
||||
source_markdown="## Content",
|
||||
unique_id="task-1",
|
||||
document_type=DocumentType.CLICKUP_CONNECTOR,
|
||||
search_space_id=0,
|
||||
workspace_id=0,
|
||||
connector_id=42,
|
||||
created_by_id="00000000-0000-0000-0000-000000000001",
|
||||
)
|
||||
|
|
@ -109,5 +109,5 @@ def test_empty_unique_id_raises():
|
|||
source_markdown="## Content",
|
||||
unique_id="",
|
||||
document_type=DocumentType.CLICKUP_CONNECTOR,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ def _make_placeholder(**overrides) -> PlaceholderInfo:
|
|||
"title": "Test Doc",
|
||||
"document_type": DocumentType.GOOGLE_DRIVE_FILE,
|
||||
"unique_id": "file-001",
|
||||
"search_space_id": 1,
|
||||
"workspace_id": 1,
|
||||
"connector_id": 42,
|
||||
"created_by_id": "00000000-0000-0000-0000-000000000001",
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ def _make_placeholder(**overrides) -> PlaceholderInfo:
|
|||
|
||||
def _uid_hash(p: PlaceholderInfo) -> str:
|
||||
return compute_identifier_hash(
|
||||
p.document_type.value, p.unique_id, p.search_space_id
|
||||
p.document_type.value, p.unique_id, p.workspace_id
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -82,7 +82,7 @@ async def test_creates_documents_with_pending_status_and_commits():
|
|||
assert doc.document_type == DocumentType.GOOGLE_DRIVE_FILE
|
||||
assert doc.content == "Pending..."
|
||||
assert DocumentStatus.is_state(doc.status, DocumentStatus.PENDING)
|
||||
assert doc.search_space_id == 1
|
||||
assert doc.workspace_id == 1
|
||||
assert doc.connector_id == 42
|
||||
|
||||
session.commit.assert_awaited_once()
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ def test_different_unique_id_produces_different_hash(make_connector_document):
|
|||
)
|
||||
|
||||
|
||||
def test_different_search_space_produces_different_identifier_hash(
|
||||
def test_different_workspace_produces_different_identifier_hash(
|
||||
make_connector_document,
|
||||
):
|
||||
"""Same document in different search spaces produces different identifier hashes."""
|
||||
doc_a = make_connector_document(search_space_id=1)
|
||||
doc_b = make_connector_document(search_space_id=2)
|
||||
"""Same document in different workspaces produces different identifier hashes."""
|
||||
doc_a = make_connector_document(workspace_id=1)
|
||||
doc_b = make_connector_document(workspace_id=2)
|
||||
assert compute_unique_identifier_hash(doc_a) != compute_unique_identifier_hash(
|
||||
doc_b
|
||||
)
|
||||
|
|
@ -42,18 +42,18 @@ def test_different_document_type_produces_different_identifier_hash(
|
|||
|
||||
|
||||
def test_same_content_same_space_produces_same_content_hash(make_connector_document):
|
||||
"""Identical content in the same search space always produces the same content hash."""
|
||||
doc_a = make_connector_document(source_markdown="Hello world", search_space_id=1)
|
||||
doc_b = make_connector_document(source_markdown="Hello world", search_space_id=1)
|
||||
"""Identical content in the same workspace always produces the same content hash."""
|
||||
doc_a = make_connector_document(source_markdown="Hello world", workspace_id=1)
|
||||
doc_b = make_connector_document(source_markdown="Hello world", workspace_id=1)
|
||||
assert compute_content_hash(doc_a) == compute_content_hash(doc_b)
|
||||
|
||||
|
||||
def test_same_content_different_space_produces_different_content_hash(
|
||||
make_connector_document,
|
||||
):
|
||||
"""Identical content in different search spaces produces different content hashes."""
|
||||
doc_a = make_connector_document(source_markdown="Hello world", search_space_id=1)
|
||||
doc_b = make_connector_document(source_markdown="Hello world", search_space_id=2)
|
||||
"""Identical content in different workspaces produces different content hashes."""
|
||||
doc_a = make_connector_document(source_markdown="Hello world", workspace_id=1)
|
||||
doc_b = make_connector_document(source_markdown="Hello world", workspace_id=2)
|
||||
assert compute_content_hash(doc_a) != compute_content_hash(doc_b)
|
||||
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ def test_compute_identifier_hash_matches_connector_doc_hash(make_connector_docum
|
|||
doc = make_connector_document(
|
||||
document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR,
|
||||
unique_id="msg-123",
|
||||
search_space_id=5,
|
||||
workspace_id=5,
|
||||
)
|
||||
raw_hash = compute_identifier_hash("GOOGLE_GMAIL_CONNECTOR", "msg-123", 5)
|
||||
assert raw_hash == compute_unique_identifier_hash(doc)
|
||||
|
|
|
|||
|
|
@ -24,12 +24,12 @@ async def test_calls_prepare_then_index_per_document(pipeline, make_connector_do
|
|||
doc1 = make_connector_document(
|
||||
document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR,
|
||||
unique_id="msg-1",
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
)
|
||||
doc2 = make_connector_document(
|
||||
document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR,
|
||||
unique_id="msg-2",
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
)
|
||||
|
||||
orm1 = MagicMock(spec=Document)
|
||||
|
|
@ -63,7 +63,7 @@ async def test_skips_document_without_matching_connector_doc(
|
|||
doc1 = make_connector_document(
|
||||
document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR,
|
||||
unique_id="msg-1",
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
)
|
||||
|
||||
orphan_orm = MagicMock(spec=Document)
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ async def test_index_calls_embed_and_chunk_via_to_thread(
|
|||
connector_doc = make_connector_document(
|
||||
document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR,
|
||||
unique_id="msg-1",
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
)
|
||||
document = MagicMock(spec=Document)
|
||||
document.id = 1
|
||||
|
|
@ -140,7 +140,7 @@ async def test_non_code_documents_use_hybrid_chunker(
|
|||
connector_doc = make_connector_document(
|
||||
document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR,
|
||||
unique_id="msg-1",
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
should_use_code_chunker=False,
|
||||
)
|
||||
document = MagicMock(spec=Document)
|
||||
|
|
@ -184,7 +184,7 @@ async def test_batch_parallel_indexes_all_documents(
|
|||
make_connector_document(
|
||||
document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR,
|
||||
unique_id=f"msg-{i}",
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
)
|
||||
for i in range(3)
|
||||
]
|
||||
|
|
@ -222,7 +222,7 @@ async def test_batch_parallel_one_failure_does_not_affect_others(
|
|||
make_connector_document(
|
||||
document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR,
|
||||
unique_id=f"msg-{i}",
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
)
|
||||
for i in range(3)
|
||||
]
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ async def test_updates_hash_and_type_for_legacy_document(
|
|||
doc = make_connector_document(
|
||||
document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR,
|
||||
unique_id="msg-abc",
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
)
|
||||
|
||||
legacy_hash = compute_identifier_hash("COMPOSIO_GMAIL_CONNECTOR", "msg-abc", 1)
|
||||
|
|
@ -70,7 +70,7 @@ async def test_noop_when_no_legacy_document_exists(
|
|||
doc = make_connector_document(
|
||||
document_type=DocumentType.GOOGLE_GMAIL_CONNECTOR,
|
||||
unique_id="msg-xyz",
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
)
|
||||
|
||||
result_mock = MagicMock()
|
||||
|
|
@ -89,7 +89,7 @@ async def test_skips_non_google_doc_types(
|
|||
doc = make_connector_document(
|
||||
document_type=DocumentType.SLACK_CONNECTOR,
|
||||
unique_id="slack-123",
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
)
|
||||
|
||||
await pipeline.migrate_legacy_docs([doc])
|
||||
|
|
@ -111,7 +111,7 @@ async def test_handles_all_three_google_types(
|
|||
doc = make_connector_document(
|
||||
document_type=native_type,
|
||||
unique_id="id-1",
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
)
|
||||
|
||||
existing = MagicMock(spec=Document)
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ def _make_connector_doc(**overrides) -> ConnectorDocument:
|
|||
"source_markdown": "## Some new content",
|
||||
"unique_id": "file-001",
|
||||
"document_type": DocumentType.GOOGLE_DRIVE_FILE,
|
||||
"search_space_id": 1,
|
||||
"workspace_id": 1,
|
||||
"connector_id": 42,
|
||||
"created_by_id": "00000000-0000-0000-0000-000000000001",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,11 +39,11 @@ pytestmark = pytest.mark.unit
|
|||
|
||||
def _make_middleware(mode: FilesystemMode = FilesystemMode.CLOUD):
|
||||
selection = FilesystemSelection(mode=mode)
|
||||
resolver = build_backend_resolver(selection, search_space_id=1)
|
||||
resolver = build_backend_resolver(selection, workspace_id=1)
|
||||
return build_filesystem_mw(
|
||||
backend_resolver=resolver,
|
||||
filesystem_mode=mode,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
thread_id=1,
|
||||
)
|
||||
|
|
@ -290,7 +290,7 @@ class TestKBPostgresBackendDeleteFilter:
|
|||
|
||||
def _make_backend(self, state: dict[str, Any]) -> KBPostgresBackend:
|
||||
runtime = SimpleNamespace(state=state)
|
||||
return KBPostgresBackend(search_space_id=1, runtime=runtime)
|
||||
return KBPostgresBackend(workspace_id=1, runtime=runtime)
|
||||
|
||||
def test_pending_filesystem_view_returns_deleted_paths(self):
|
||||
backend = self._make_backend(
|
||||
|
|
|
|||
|
|
@ -38,16 +38,16 @@ def test_backend_resolver_returns_multi_root_backend_for_single_root(tmp_path: P
|
|||
def test_backend_resolver_uses_cloud_mode_by_default():
|
||||
resolver = build_backend_resolver(FilesystemSelection())
|
||||
backend = resolver(_RuntimeStub())
|
||||
# When no search_space_id is provided we fall back to StateBackend so
|
||||
# When no workspace_id is provided we fall back to StateBackend so
|
||||
# sub-agents / tests without DB access still work.
|
||||
assert backend.__class__.__name__ == "StateBackend"
|
||||
|
||||
|
||||
def test_backend_resolver_uses_kb_postgres_in_cloud_with_search_space():
|
||||
resolver = build_backend_resolver(FilesystemSelection(), search_space_id=42)
|
||||
def test_backend_resolver_uses_kb_postgres_in_cloud_with_workspace():
|
||||
resolver = build_backend_resolver(FilesystemSelection(), workspace_id=42)
|
||||
backend = resolver(_RuntimeStub())
|
||||
assert backend.__class__.__name__ == "KBPostgresBackend"
|
||||
assert backend.search_space_id == 42
|
||||
assert backend.workspace_id == 42
|
||||
|
||||
|
||||
def test_backend_resolver_returns_multi_root_backend_for_multiple_roots(tmp_path: Path):
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ async def test_create_document_allows_identical_content_at_different_paths() ->
|
|||
session, # type: ignore[arg-type]
|
||||
virtual_path="/documents/a/notes.md",
|
||||
content=content,
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
created_by_id="user-1",
|
||||
)
|
||||
assert isinstance(first, Document)
|
||||
|
|
@ -104,7 +104,7 @@ async def test_create_document_allows_identical_content_at_different_paths() ->
|
|||
session, # type: ignore[arg-type]
|
||||
virtual_path="/documents/b/notes-copy.md",
|
||||
content=content,
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
created_by_id="user-1",
|
||||
)
|
||||
assert isinstance(second, Document)
|
||||
|
|
@ -121,7 +121,7 @@ async def test_create_document_still_rejects_path_collision() -> None:
|
|||
"""Path uniqueness remains the hard invariant.
|
||||
|
||||
If ``unique_identifier_hash`` already points at an existing row in
|
||||
the same search space, the create call must raise ``ValueError``
|
||||
the same workspace, the create call must raise ``ValueError``
|
||||
with a clear message — matching the behavior the commit loop relies
|
||||
on to upsert via the existing-row code path.
|
||||
"""
|
||||
|
|
@ -137,7 +137,7 @@ async def test_create_document_still_rejects_path_collision() -> None:
|
|||
session, # type: ignore[arg-type]
|
||||
virtual_path="/documents/notes.md",
|
||||
content="anything",
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
created_by_id="user-1",
|
||||
)
|
||||
|
||||
|
|
@ -160,7 +160,7 @@ async def test_create_document_does_not_query_for_content_hash_collision(
|
|||
session, # type: ignore[arg-type]
|
||||
virtual_path="/documents/notes.md",
|
||||
content="hello",
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
created_by_id="user-1",
|
||||
)
|
||||
# Path-collision SELECT only. No content_hash SELECT.
|
||||
|
|
|
|||
|
|
@ -217,7 +217,7 @@ async def test_pre_write_snapshot_defers_dispatch_when_list_provided(
|
|||
session, # type: ignore[arg-type]
|
||||
doc=doc,
|
||||
action_id=42,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
turn_id="t-1",
|
||||
deferred_dispatches=deferred,
|
||||
)
|
||||
|
|
@ -262,7 +262,7 @@ async def test_pre_write_snapshot_dispatches_inline_when_list_omitted(
|
|||
session, # type: ignore[arg-type]
|
||||
doc=doc,
|
||||
action_id=88,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
turn_id="t-1",
|
||||
# No deferred_dispatches arg — fall back to inline dispatch.
|
||||
)
|
||||
|
|
@ -302,7 +302,7 @@ async def test_pre_mkdir_snapshot_defers_dispatch_when_list_provided(
|
|||
session, # type: ignore[arg-type]
|
||||
folder=folder,
|
||||
action_id=55,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
turn_id="t-1",
|
||||
deferred_dispatches=deferred,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ pytestmark = pytest.mark.unit
|
|||
|
||||
|
||||
def _backend(state: dict) -> KBPostgresBackend:
|
||||
return KBPostgresBackend(search_space_id=1, runtime=SimpleNamespace(state=state))
|
||||
return KBPostgresBackend(workspace_id=1, runtime=SimpleNamespace(state=state))
|
||||
|
||||
|
||||
def test_render_full_document_uses_full_view_and_registers() -> None:
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ class TestFormatTreeRendering:
|
|||
docs = [_Row(**spec) for spec in doc_specs]
|
||||
|
||||
mw = KnowledgeTreeMiddleware(
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
filesystem_mode=None, # type: ignore[arg-type]
|
||||
)
|
||||
return mw._format_tree(index, docs)
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ def _notification(**overrides) -> Notification:
|
|||
defaults = {
|
||||
"id": 1,
|
||||
"user_id": uuid.uuid4(),
|
||||
"search_space_id": 3,
|
||||
"workspace_id": 3,
|
||||
"type": "document_processing",
|
||||
"title": "Title",
|
||||
"message": "Message",
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ pytestmark = pytest.mark.unit
|
|||
|
||||
|
||||
def test_operation_id_encodes_type_and_space():
|
||||
"""The operation id embeds the document type and search space id."""
|
||||
"""The operation id embeds the document type and workspace id."""
|
||||
op = msg.operation_id("FILE", "report.pdf", 9)
|
||||
assert op.startswith("doc_FILE_9_")
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ from app.notifications.service.messages import insufficient_credits as msg
|
|||
pytestmark = pytest.mark.unit
|
||||
|
||||
|
||||
def test_operation_id_encodes_search_space():
|
||||
"""The operation id embeds the search space id."""
|
||||
def test_operation_id_encodes_workspace():
|
||||
"""The operation id embeds the workspace id."""
|
||||
assert msg.operation_id("doc.pdf", 9).startswith("insufficient_credits_9_")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ class TestMetricHelpers:
|
|||
metrics.record_tool_call_error(tool_name="web_search")
|
||||
metrics.record_kb_search_duration(
|
||||
4.0,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
surface="documents",
|
||||
)
|
||||
metrics.record_compaction_run(reason="auto")
|
||||
|
|
@ -250,7 +250,7 @@ class TestNoopSpansWhenDisabled:
|
|||
helpers = [
|
||||
otel.tool_call_span("write_file", input_size=42),
|
||||
otel.model_call_span(model_id="openai:gpt-4o", provider="openai"),
|
||||
otel.kb_search_span(search_space_id=1, query_chars=99),
|
||||
otel.kb_search_span(workspace_id=1, query_chars=99),
|
||||
otel.kb_persist_span(document_type="NOTE", document_id=7),
|
||||
otel.compaction_span(reason="overflow", messages_in=120),
|
||||
otel.interrupt_span(interrupt_type="permission_ask"),
|
||||
|
|
|
|||
|
|
@ -48,14 +48,14 @@ async def test_retriever_wrapper_records_one_span_and_metric(monkeypatch) -> Non
|
|||
self,
|
||||
query_text: str,
|
||||
top_k: int,
|
||||
search_space_id: int,
|
||||
workspace_id: int,
|
||||
) -> list[str]:
|
||||
del query_text, top_k, search_space_id
|
||||
del query_text, top_k, workspace_id
|
||||
return ["doc-1", "doc-2"]
|
||||
|
||||
result = await Retriever().search("hello", 3, 42)
|
||||
|
||||
assert result == ["doc-1", "doc-2"]
|
||||
assert len(calls) == 1
|
||||
assert calls[0]["search_space_id"] == 42
|
||||
assert calls[0]["workspace_id"] == 42
|
||||
assert calls[0]["surface"] == "documents"
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ def _podcast(*, status: PodcastStatus = PodcastStatus.PENDING, **columns) -> Pod
|
|||
"""A persisted-looking row: the id and created_at a saved podcast would carry."""
|
||||
podcast = Podcast(
|
||||
title="Episode",
|
||||
search_space_id=3,
|
||||
workspace_id=3,
|
||||
status=status,
|
||||
spec_version=1,
|
||||
**columns,
|
||||
|
|
|
|||
|
|
@ -36,11 +36,11 @@ async def test_resolve_billing_for_auto_mode(monkeypatch):
|
|||
_no_auto_candidates,
|
||||
)
|
||||
|
||||
search_space = SimpleNamespace(id=1, user_id=None, image_gen_model_id=None)
|
||||
workspace = SimpleNamespace(id=1, user_id=None, image_gen_model_id=None)
|
||||
tier, model, reserve = await image_generation_routes._resolve_billing_for_image_gen(
|
||||
session=None,
|
||||
config_id=0, # IMAGE_GEN_AUTO_MODE_ID
|
||||
search_space=search_space,
|
||||
workspace=workspace,
|
||||
)
|
||||
assert tier == "free"
|
||||
assert model == "auto"
|
||||
|
|
@ -95,11 +95,11 @@ async def test_resolve_billing_for_premium_global_config(monkeypatch):
|
|||
raising=False,
|
||||
)
|
||||
|
||||
search_space = SimpleNamespace(id=1, user_id=None, image_gen_model_id=None)
|
||||
workspace = SimpleNamespace(id=1, user_id=None, image_gen_model_id=None)
|
||||
|
||||
# Premium with override.
|
||||
tier, model, reserve = await image_generation_routes._resolve_billing_for_image_gen(
|
||||
session=None, config_id=-1, search_space=search_space
|
||||
session=None, config_id=-1, workspace=workspace
|
||||
)
|
||||
assert tier == "premium"
|
||||
assert model == "openai/gpt-image-1"
|
||||
|
|
@ -109,7 +109,7 @@ async def test_resolve_billing_for_premium_global_config(monkeypatch):
|
|||
from app.services.billable_calls import DEFAULT_IMAGE_RESERVE_MICROS
|
||||
|
||||
tier, model, reserve = await image_generation_routes._resolve_billing_for_image_gen(
|
||||
session=None, config_id=-2, search_space=search_space
|
||||
session=None, config_id=-2, workspace=workspace
|
||||
)
|
||||
assert tier == "free"
|
||||
# Provider-prefixed model string for OpenRouter.
|
||||
|
|
@ -125,9 +125,9 @@ async def test_resolve_billing_for_user_owned_byok_is_free():
|
|||
from app.routes import image_generation_routes
|
||||
from app.services.billable_calls import DEFAULT_IMAGE_RESERVE_MICROS
|
||||
|
||||
search_space = SimpleNamespace(id=1, user_id=None, image_gen_model_id=None)
|
||||
workspace = SimpleNamespace(id=1, user_id=None, image_gen_model_id=None)
|
||||
tier, model, reserve = await image_generation_routes._resolve_billing_for_image_gen(
|
||||
session=None, config_id=42, search_space=search_space
|
||||
session=None, config_id=42, workspace=workspace
|
||||
)
|
||||
assert tier == "free"
|
||||
assert model == "user_byok"
|
||||
|
|
@ -135,9 +135,9 @@ async def test_resolve_billing_for_user_owned_byok_is_free():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_resolve_billing_falls_back_to_search_space_default(monkeypatch):
|
||||
async def test_resolve_billing_falls_back_to_workspace_default(monkeypatch):
|
||||
"""When the request omits ``image_gen_model_id``, the helper
|
||||
must consult the search space's default — so a search space pinned
|
||||
must consult the workspace's default — so a workspace pinned
|
||||
to a premium global config still gates new requests by quota.
|
||||
"""
|
||||
from app.config import config
|
||||
|
|
@ -172,13 +172,13 @@ async def test_resolve_billing_falls_back_to_search_space_default(monkeypatch):
|
|||
raising=False,
|
||||
)
|
||||
|
||||
search_space = SimpleNamespace(id=1, user_id=None, image_gen_model_id=-7)
|
||||
workspace = SimpleNamespace(id=1, user_id=None, image_gen_model_id=-7)
|
||||
(
|
||||
tier,
|
||||
model,
|
||||
_reserve,
|
||||
) = await image_generation_routes._resolve_billing_for_image_gen(
|
||||
session=None, config_id=None, search_space=search_space
|
||||
session=None, config_id=None, workspace=workspace
|
||||
)
|
||||
assert tier == "premium"
|
||||
assert model == "openai/gpt-image-1"
|
||||
|
|
|
|||
|
|
@ -117,7 +117,7 @@ class TestRegenerateRequestValidation:
|
|||
def test_revert_actions_requires_from_message_id(self) -> None:
|
||||
with pytest.raises(Exception) as exc:
|
||||
RegenerateRequest(
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
user_query="hi",
|
||||
revert_actions=True,
|
||||
)
|
||||
|
|
@ -126,7 +126,7 @@ class TestRegenerateRequestValidation:
|
|||
|
||||
def test_from_message_id_without_revert_is_allowed(self) -> None:
|
||||
req = RegenerateRequest(
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
user_query="hi",
|
||||
from_message_id=42,
|
||||
)
|
||||
|
|
@ -135,7 +135,7 @@ class TestRegenerateRequestValidation:
|
|||
|
||||
def test_revert_actions_with_from_message_id_passes(self) -> None:
|
||||
req = RegenerateRequest(
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
user_query="hi",
|
||||
from_message_id=42,
|
||||
revert_actions=True,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Unit tests for ``_resolve_agent_billing_for_search_space``."""
|
||||
"""Unit tests for ``_resolve_agent_billing_for_workspace``."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -39,7 +39,7 @@ class _FakePinResolution:
|
|||
from_existing_pin: bool = False
|
||||
|
||||
|
||||
def _make_search_space(*, chat_model_id: int | None, user_id: UUID) -> SimpleNamespace:
|
||||
def _make_workspace(*, chat_model_id: int | None, user_id: UUID) -> SimpleNamespace:
|
||||
return SimpleNamespace(id=42, chat_model_id=chat_model_id, user_id=user_id)
|
||||
|
||||
|
||||
|
|
@ -50,16 +50,16 @@ def _make_byok_model(
|
|||
id=id_,
|
||||
model_id=model_id,
|
||||
catalog={"base_model": base_model} if base_model else {},
|
||||
connection=SimpleNamespace(enabled=True, search_space_id=42, user_id=None),
|
||||
connection=SimpleNamespace(enabled=True, workspace_id=42, user_id=None),
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_auto_mode_with_thread_id_resolves_to_premium_global(monkeypatch):
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_search_space
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_workspace
|
||||
|
||||
user_id = uuid4()
|
||||
session = _FakeSession([_make_search_space(chat_model_id=0, user_id=user_id)])
|
||||
session = _FakeSession([_make_workspace(chat_model_id=0, user_id=user_id)])
|
||||
|
||||
async def _fake_resolve_pin(*_args, **kwargs):
|
||||
assert kwargs["selected_llm_config_id"] == 0
|
||||
|
|
@ -84,8 +84,8 @@ async def test_auto_mode_with_thread_id_resolves_to_premium_global(monkeypatch):
|
|||
)
|
||||
monkeypatch.setattr(llm_module, "get_global_llm_config", _fake_get_global)
|
||||
|
||||
owner, tier, base_model = await _resolve_agent_billing_for_search_space(
|
||||
session, search_space_id=42, thread_id=99
|
||||
owner, tier, base_model = await _resolve_agent_billing_for_workspace(
|
||||
session, workspace_id=42, thread_id=99
|
||||
)
|
||||
|
||||
assert owner == user_id
|
||||
|
|
@ -95,14 +95,14 @@ async def test_auto_mode_with_thread_id_resolves_to_premium_global(monkeypatch):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_auto_mode_with_thread_id_resolves_to_byok_is_free(monkeypatch):
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_search_space
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_workspace
|
||||
|
||||
user_id = uuid4()
|
||||
search_space = _make_search_space(chat_model_id=0, user_id=user_id)
|
||||
workspace = _make_workspace(chat_model_id=0, user_id=user_id)
|
||||
byok_model = _make_byok_model(
|
||||
id_=17, base_model="anthropic/claude-3-haiku", model_id="my-claude"
|
||||
)
|
||||
session = _FakeSession([search_space, byok_model])
|
||||
session = _FakeSession([workspace, byok_model])
|
||||
|
||||
async def _fake_resolve_pin(*_args, **_kwargs):
|
||||
return _FakePinResolution(resolved_llm_config_id=17, resolved_tier="free")
|
||||
|
|
@ -113,8 +113,8 @@ async def test_auto_mode_with_thread_id_resolves_to_byok_is_free(monkeypatch):
|
|||
pin_module, "resolve_or_get_pinned_llm_config_id", _fake_resolve_pin
|
||||
)
|
||||
|
||||
owner, tier, base_model = await _resolve_agent_billing_for_search_space(
|
||||
session, search_space_id=42, thread_id=99
|
||||
owner, tier, base_model = await _resolve_agent_billing_for_workspace(
|
||||
session, workspace_id=42, thread_id=99
|
||||
)
|
||||
|
||||
assert owner == user_id
|
||||
|
|
@ -124,13 +124,13 @@ async def test_auto_mode_with_thread_id_resolves_to_byok_is_free(monkeypatch):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_auto_mode_without_thread_id_falls_back_to_free():
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_search_space
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_workspace
|
||||
|
||||
user_id = uuid4()
|
||||
session = _FakeSession([_make_search_space(chat_model_id=0, user_id=user_id)])
|
||||
session = _FakeSession([_make_workspace(chat_model_id=0, user_id=user_id)])
|
||||
|
||||
owner, tier, base_model = await _resolve_agent_billing_for_search_space(
|
||||
session, search_space_id=42, thread_id=None
|
||||
owner, tier, base_model = await _resolve_agent_billing_for_workspace(
|
||||
session, workspace_id=42, thread_id=None
|
||||
)
|
||||
|
||||
assert owner == user_id
|
||||
|
|
@ -140,10 +140,10 @@ async def test_auto_mode_without_thread_id_falls_back_to_free():
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_auto_mode_pin_failure_falls_back_to_free(monkeypatch):
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_search_space
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_workspace
|
||||
|
||||
user_id = uuid4()
|
||||
session = _FakeSession([_make_search_space(chat_model_id=0, user_id=user_id)])
|
||||
session = _FakeSession([_make_workspace(chat_model_id=0, user_id=user_id)])
|
||||
|
||||
async def _fake_resolve_pin(*args, **kwargs):
|
||||
raise ValueError("thread missing")
|
||||
|
|
@ -154,8 +154,8 @@ async def test_auto_mode_pin_failure_falls_back_to_free(monkeypatch):
|
|||
pin_module, "resolve_or_get_pinned_llm_config_id", _fake_resolve_pin
|
||||
)
|
||||
|
||||
owner, tier, base_model = await _resolve_agent_billing_for_search_space(
|
||||
session, search_space_id=42, thread_id=99
|
||||
owner, tier, base_model = await _resolve_agent_billing_for_workspace(
|
||||
session, workspace_id=42, thread_id=99
|
||||
)
|
||||
|
||||
assert owner == user_id
|
||||
|
|
@ -165,10 +165,10 @@ async def test_auto_mode_pin_failure_falls_back_to_free(monkeypatch):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_negative_id_premium_global_returns_premium(monkeypatch):
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_search_space
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_workspace
|
||||
|
||||
user_id = uuid4()
|
||||
session = _FakeSession([_make_search_space(chat_model_id=-1, user_id=user_id)])
|
||||
session = _FakeSession([_make_workspace(chat_model_id=-1, user_id=user_id)])
|
||||
|
||||
def _fake_get_global(cfg_id):
|
||||
return {
|
||||
|
|
@ -182,8 +182,8 @@ async def test_negative_id_premium_global_returns_premium(monkeypatch):
|
|||
|
||||
monkeypatch.setattr(llm_module, "get_global_llm_config", _fake_get_global)
|
||||
|
||||
owner, tier, base_model = await _resolve_agent_billing_for_search_space(
|
||||
session, search_space_id=42, thread_id=99
|
||||
owner, tier, base_model = await _resolve_agent_billing_for_workspace(
|
||||
session, workspace_id=42, thread_id=99
|
||||
)
|
||||
|
||||
assert owner == user_id
|
||||
|
|
@ -193,10 +193,10 @@ async def test_negative_id_premium_global_returns_premium(monkeypatch):
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_negative_id_missing_base_model_falls_back_to_model_name(monkeypatch):
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_search_space
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_workspace
|
||||
|
||||
user_id = uuid4()
|
||||
session = _FakeSession([_make_search_space(chat_model_id=-5, user_id=user_id)])
|
||||
session = _FakeSession([_make_workspace(chat_model_id=-5, user_id=user_id)])
|
||||
|
||||
def _fake_get_global(cfg_id):
|
||||
return {"id": cfg_id, "model_name": "fallback-model", "billing_tier": "premium"}
|
||||
|
|
@ -205,8 +205,8 @@ async def test_negative_id_missing_base_model_falls_back_to_model_name(monkeypat
|
|||
|
||||
monkeypatch.setattr(llm_module, "get_global_llm_config", _fake_get_global)
|
||||
|
||||
_, tier, base_model = await _resolve_agent_billing_for_search_space(
|
||||
session, search_space_id=42
|
||||
_, tier, base_model = await _resolve_agent_billing_for_workspace(
|
||||
session, workspace_id=42
|
||||
)
|
||||
|
||||
assert tier == "premium"
|
||||
|
|
@ -215,15 +215,15 @@ async def test_negative_id_missing_base_model_falls_back_to_model_name(monkeypat
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_positive_id_byok_is_always_free():
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_search_space
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_workspace
|
||||
|
||||
user_id = uuid4()
|
||||
search_space = _make_search_space(chat_model_id=23, user_id=user_id)
|
||||
workspace = _make_workspace(chat_model_id=23, user_id=user_id)
|
||||
byok_model = _make_byok_model(id_=23, base_model="anthropic/claude-3.5-sonnet")
|
||||
session = _FakeSession([search_space, byok_model])
|
||||
session = _FakeSession([workspace, byok_model])
|
||||
|
||||
owner, tier, base_model = await _resolve_agent_billing_for_search_space(
|
||||
session, search_space_id=42
|
||||
owner, tier, base_model = await _resolve_agent_billing_for_workspace(
|
||||
session, workspace_id=42
|
||||
)
|
||||
|
||||
assert owner == user_id
|
||||
|
|
@ -233,13 +233,13 @@ async def test_positive_id_byok_is_always_free():
|
|||
|
||||
@pytest.mark.asyncio
|
||||
async def test_positive_id_byok_missing_returns_free_with_empty_base_model():
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_search_space
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_workspace
|
||||
|
||||
user_id = uuid4()
|
||||
session = _FakeSession([_make_search_space(chat_model_id=99, user_id=user_id)])
|
||||
session = _FakeSession([_make_workspace(chat_model_id=99, user_id=user_id)])
|
||||
|
||||
owner, tier, base_model = await _resolve_agent_billing_for_search_space(
|
||||
session, search_space_id=42
|
||||
owner, tier, base_model = await _resolve_agent_billing_for_workspace(
|
||||
session, workspace_id=42
|
||||
)
|
||||
|
||||
assert owner == user_id
|
||||
|
|
@ -248,21 +248,21 @@ async def test_positive_id_byok_missing_returns_free_with_empty_base_model():
|
|||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_space_not_found_raises_value_error():
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_search_space
|
||||
async def test_workspace_not_found_raises_value_error():
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_workspace
|
||||
|
||||
with pytest.raises(ValueError, match="Search space"):
|
||||
await _resolve_agent_billing_for_search_space(
|
||||
_FakeSession([None]), search_space_id=999
|
||||
with pytest.raises(ValueError, match="Workspace"):
|
||||
await _resolve_agent_billing_for_workspace(
|
||||
_FakeSession([None]), workspace_id=999
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_chat_model_id_none_raises_value_error():
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_search_space
|
||||
from app.services.billable_calls import _resolve_agent_billing_for_workspace
|
||||
|
||||
user_id = uuid4()
|
||||
session = _FakeSession([_make_search_space(chat_model_id=None, user_id=user_id)])
|
||||
session = _FakeSession([_make_workspace(chat_model_id=None, user_id=user_id)])
|
||||
|
||||
with pytest.raises(ValueError, match="chat_model_id"):
|
||||
await _resolve_agent_billing_for_search_space(session, search_space_id=42)
|
||||
await _resolve_agent_billing_for_workspace(session, workspace_id=42)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ def test_lock_key_format():
|
|||
from app.tasks.celery_tasks.document_tasks import _ai_sort_lock_key
|
||||
|
||||
key = _ai_sort_lock_key(42)
|
||||
assert key == "ai_sort:search_space:42:lock"
|
||||
assert key == "ai_sort:workspace:42:lock"
|
||||
|
||||
|
||||
def test_lock_prevents_duplicate_run():
|
||||
|
|
@ -31,11 +31,11 @@ def test_lock_prevents_duplicate_run():
|
|||
):
|
||||
import asyncio
|
||||
|
||||
from app.tasks.celery_tasks.document_tasks import _ai_sort_search_space_async
|
||||
from app.tasks.celery_tasks.document_tasks import _ai_sort_workspace_async
|
||||
|
||||
loop = asyncio.new_event_loop()
|
||||
try:
|
||||
loop.run_until_complete(_ai_sort_search_space_async(1, "user-123"))
|
||||
loop.run_until_complete(_ai_sort_workspace_async(1, "user-123"))
|
||||
finally:
|
||||
loop.close()
|
||||
|
||||
|
|
|
|||
|
|
@ -140,12 +140,12 @@ def _set_global_llm_configs(monkeypatch, config, configs: list[dict]):
|
|||
|
||||
def _thread(
|
||||
*,
|
||||
search_space_id: int = 10,
|
||||
workspace_id: int = 10,
|
||||
pinned_llm_config_id: int | None = None,
|
||||
):
|
||||
return SimpleNamespace(
|
||||
id=1,
|
||||
search_space_id=search_space_id,
|
||||
workspace_id=workspace_id,
|
||||
pinned_llm_config_id=pinned_llm_config_id,
|
||||
)
|
||||
|
||||
|
|
@ -186,7 +186,7 @@ async def test_auto_first_turn_pins_one_model(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -234,7 +234,7 @@ async def test_premium_eligible_auto_prefers_premium_over_free(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -321,7 +321,7 @@ async def test_premium_eligible_auto_uses_quality_pool_not_single_preferred_mode
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -361,7 +361,7 @@ async def test_next_turn_reuses_existing_pin(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -400,7 +400,7 @@ async def test_premium_eligible_auto_can_pin_premium(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -445,7 +445,7 @@ async def test_premium_ineligible_auto_pins_free_only(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -490,7 +490,7 @@ async def test_pinned_premium_stays_premium_after_quota_exhaustion(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -535,7 +535,7 @@ async def test_force_repin_free_switches_auto_premium_pin_to_free(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
force_repin_free=True,
|
||||
|
|
@ -567,7 +567,7 @@ async def test_explicit_user_model_change_clears_pin(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=7,
|
||||
)
|
||||
|
|
@ -605,7 +605,7 @@ async def test_invalid_pinned_config_repairs_with_new_pin(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -664,7 +664,7 @@ async def test_health_gated_config_is_excluded_from_selection(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -716,7 +716,7 @@ async def test_tier_a_locks_first_premium_user_skips_or(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -768,7 +768,7 @@ async def test_tier_a_falls_through_to_or_when_a_pool_empty_for_user(monkeypatch
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -825,7 +825,7 @@ async def test_top_k_picks_only_high_score_models(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=thread_id,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -889,7 +889,7 @@ async def test_pin_reuse_survives_health_gating_for_existing_pin(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -941,7 +941,7 @@ async def test_pin_reuse_regression_existing_healthy_pin(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -1001,7 +1001,7 @@ async def test_runtime_cooled_down_pin_is_not_reused(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -1069,7 +1069,7 @@ async def test_shared_runtime_cooldown_blocks_pin_across_workers(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -1113,7 +1113,7 @@ async def test_clearing_runtime_cooldown_restores_pin_reuse(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -1165,7 +1165,7 @@ async def test_auto_pin_repin_excludes_previous_config_on_runtime_retry(monkeypa
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id="00000000-0000-0000-0000-000000000001",
|
||||
selected_llm_config_id=0,
|
||||
exclude_config_ids={-1},
|
||||
|
|
|
|||
|
|
@ -76,7 +76,7 @@ class _FakeSession:
|
|||
|
||||
|
||||
def _thread(*, pinned: int | None = None):
|
||||
return SimpleNamespace(id=1, search_space_id=10, pinned_llm_config_id=pinned)
|
||||
return SimpleNamespace(id=1, workspace_id=10, pinned_llm_config_id=pinned)
|
||||
|
||||
|
||||
def _set_global_llm_configs(monkeypatch, config, configs: list[dict]):
|
||||
|
|
@ -179,7 +179,7 @@ async def test_image_turn_filters_out_text_only_candidates(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id=None,
|
||||
selected_llm_config_id=0,
|
||||
requires_image_input=True,
|
||||
|
|
@ -207,7 +207,7 @@ async def test_image_turn_force_repins_stale_text_only_pin(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id=None,
|
||||
selected_llm_config_id=0,
|
||||
requires_image_input=True,
|
||||
|
|
@ -239,7 +239,7 @@ async def test_image_turn_reuses_existing_vision_pin(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id=None,
|
||||
selected_llm_config_id=0,
|
||||
requires_image_input=True,
|
||||
|
|
@ -269,7 +269,7 @@ async def test_image_turn_with_no_vision_candidates_raises(monkeypatch):
|
|||
await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id=None,
|
||||
selected_llm_config_id=0,
|
||||
requires_image_input=True,
|
||||
|
|
@ -292,7 +292,7 @@ async def test_non_image_turn_keeps_text_only_in_pool(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id=None,
|
||||
selected_llm_config_id=0,
|
||||
)
|
||||
|
|
@ -326,7 +326,7 @@ async def test_image_turn_unannotated_cfg_resolves_via_helper(monkeypatch):
|
|||
result = await resolve_or_get_pinned_llm_config_id(
|
||||
session,
|
||||
thread_id=1,
|
||||
search_space_id=10,
|
||||
workspace_id=10,
|
||||
user_id=None,
|
||||
selected_llm_config_id=0,
|
||||
requires_image_input=True,
|
||||
|
|
|
|||
|
|
@ -169,7 +169,7 @@ async def test_free_path_skips_reserve_but_writes_audit_row(monkeypatch):
|
|||
|
||||
async with billable_call(
|
||||
user_id=user_id,
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
billing_tier="free",
|
||||
base_model="openai/gpt-image-1",
|
||||
usage_type="image_generation",
|
||||
|
|
@ -210,7 +210,7 @@ async def test_premium_reserve_denied_raises_quota_insufficient(monkeypatch):
|
|||
with pytest.raises(QuotaInsufficientError) as exc_info:
|
||||
async with billable_call(
|
||||
user_id=user_id,
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
billing_tier="premium",
|
||||
base_model="openai/gpt-image-1",
|
||||
quota_reserve_micros_override=50_000,
|
||||
|
|
@ -242,7 +242,7 @@ async def test_premium_success_finalizes_with_actual_cost(monkeypatch):
|
|||
|
||||
async with billable_call(
|
||||
user_id=user_id,
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
billing_tier="premium",
|
||||
base_model="openai/gpt-image-1",
|
||||
quota_reserve_micros_override=50_000,
|
||||
|
|
@ -292,7 +292,7 @@ async def test_premium_failure_releases_reservation(monkeypatch):
|
|||
with pytest.raises(_ProviderError):
|
||||
async with billable_call(
|
||||
user_id=user_id,
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
billing_tier="premium",
|
||||
base_model="openai/gpt-image-1",
|
||||
quota_reserve_micros_override=50_000,
|
||||
|
|
@ -336,7 +336,7 @@ async def test_premium_uses_estimator_when_no_micros_override(monkeypatch):
|
|||
user_id = uuid4()
|
||||
async with billable_call(
|
||||
user_id=user_id,
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
billing_tier="premium",
|
||||
base_model="openai/gpt-4o",
|
||||
quota_reserve_tokens=4000,
|
||||
|
|
@ -367,7 +367,7 @@ async def test_premium_finalize_failure_propagates_and_releases(monkeypatch):
|
|||
with pytest.raises(BillingSettlementError):
|
||||
async with billable_call(
|
||||
user_id=user_id,
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
billing_tier="premium",
|
||||
base_model="openai/gpt-image-1",
|
||||
quota_reserve_micros_override=50_000,
|
||||
|
|
@ -408,7 +408,7 @@ async def test_premium_audit_commit_hang_times_out_after_finalize(monkeypatch):
|
|||
|
||||
async with billable_call(
|
||||
user_id=user_id,
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
billing_tier="premium",
|
||||
base_model="openai/gpt-image-1",
|
||||
quota_reserve_micros_override=50_000,
|
||||
|
|
@ -450,7 +450,7 @@ async def test_free_audit_failure_is_best_effort(monkeypatch):
|
|||
|
||||
async with billable_call(
|
||||
user_id=uuid4(),
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
billing_tier="free",
|
||||
base_model="openai/gpt-image-1",
|
||||
usage_type="image_generation",
|
||||
|
|
@ -488,7 +488,7 @@ async def test_free_podcast_path_audits_with_podcast_usage_type(monkeypatch):
|
|||
|
||||
async with billable_call(
|
||||
user_id=user_id,
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
billing_tier="free",
|
||||
base_model="openrouter/some-free-model",
|
||||
quota_reserve_micros_override=200_000,
|
||||
|
|
@ -514,7 +514,7 @@ async def test_free_podcast_path_audits_with_podcast_usage_type(monkeypatch):
|
|||
row = spies["record"][0]
|
||||
assert row["usage_type"] == "podcast_generation"
|
||||
assert row["thread_id"] is None
|
||||
assert row["search_space_id"] == 42
|
||||
assert row["workspace_id"] == 42
|
||||
assert row["call_details"] == {"podcast_id": 7, "title": "Test Podcast"}
|
||||
|
||||
|
||||
|
|
@ -539,7 +539,7 @@ async def test_premium_video_denial_raises_quota_insufficient(monkeypatch):
|
|||
with pytest.raises(QuotaInsufficientError) as exc_info:
|
||||
async with billable_call(
|
||||
user_id=user_id,
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
billing_tier="premium",
|
||||
base_model="gpt-5.4",
|
||||
quota_reserve_micros_override=1_000_000,
|
||||
|
|
|
|||
|
|
@ -46,8 +46,8 @@ async def test_global_openrouter_image_gen_sets_explicit_api_base():
|
|||
image_gen.response_format = None
|
||||
image_gen.model = None
|
||||
|
||||
search_space = MagicMock()
|
||||
search_space.image_gen_model_id = global_model["id"]
|
||||
workspace = MagicMock()
|
||||
workspace.image_gen_model_id = global_model["id"]
|
||||
session = MagicMock()
|
||||
|
||||
with (
|
||||
|
|
@ -68,7 +68,7 @@ async def test_global_openrouter_image_gen_sets_explicit_api_base():
|
|||
),
|
||||
):
|
||||
await image_generation_routes._execute_image_generation(
|
||||
session=session, image_gen=image_gen, search_space=search_space
|
||||
session=session, image_gen=image_gen, workspace=workspace
|
||||
)
|
||||
|
||||
assert captured.get("api_base") == "https://openrouter.ai/api/v1"
|
||||
|
|
@ -109,16 +109,16 @@ async def test_generate_image_tool_global_sets_explicit_api_base():
|
|||
response._hidden_params = {"model": "openrouter/openai/gpt-image-1"}
|
||||
return response
|
||||
|
||||
search_space = MagicMock()
|
||||
search_space.id = 1
|
||||
search_space.image_gen_model_id = global_model["id"]
|
||||
workspace = MagicMock()
|
||||
workspace.id = 1
|
||||
workspace.image_gen_model_id = global_model["id"]
|
||||
|
||||
session_cm = AsyncMock()
|
||||
session = AsyncMock()
|
||||
session_cm.__aenter__.return_value = session
|
||||
|
||||
scalars = MagicMock()
|
||||
scalars.first.return_value = search_space
|
||||
scalars.first.return_value = workspace
|
||||
exec_result = MagicMock()
|
||||
exec_result.scalars.return_value = scalars
|
||||
session.execute.return_value = exec_result
|
||||
|
|
@ -146,7 +146,7 @@ async def test_generate_image_tool_global_sets_explicit_api_base():
|
|||
),
|
||||
):
|
||||
tool = gi_module.create_generate_image_tool(
|
||||
search_space_id=1, db_session=MagicMock()
|
||||
workspace_id=1, db_session=MagicMock()
|
||||
)
|
||||
# The live tool takes an injected ToolRuntime and returns a Command;
|
||||
# drive the raw coroutine with a minimal runtime (the tool only reads
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ async def test_ainvoke_routes_through_billable_call(monkeypatch):
|
|||
wrapper = QuotaCheckedVisionLLM(
|
||||
inner,
|
||||
user_id=user_id,
|
||||
search_space_id=99,
|
||||
workspace_id=99,
|
||||
billing_tier="premium",
|
||||
base_model="openai/gpt-4o",
|
||||
quota_reserve_tokens=4000,
|
||||
|
|
@ -89,7 +89,7 @@ async def test_ainvoke_routes_through_billable_call(monkeypatch):
|
|||
assert len(captured_kwargs) == 1
|
||||
bc_kwargs = captured_kwargs[0]
|
||||
assert bc_kwargs["user_id"] == user_id
|
||||
assert bc_kwargs["search_space_id"] == 99
|
||||
assert bc_kwargs["workspace_id"] == 99
|
||||
assert bc_kwargs["billing_tier"] == "premium"
|
||||
assert bc_kwargs["base_model"] == "openai/gpt-4o"
|
||||
assert bc_kwargs["quota_reserve_tokens"] == 4000
|
||||
|
|
@ -120,7 +120,7 @@ async def test_ainvoke_propagates_quota_insufficient_error(monkeypatch):
|
|||
wrapper = QuotaCheckedVisionLLM(
|
||||
inner,
|
||||
user_id=uuid4(),
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
billing_tier="premium",
|
||||
base_model="openai/gpt-4o",
|
||||
quota_reserve_tokens=4000,
|
||||
|
|
@ -146,7 +146,7 @@ async def test_proxies_non_overridden_attributes_to_inner():
|
|||
wrapper = QuotaCheckedVisionLLM(
|
||||
inner,
|
||||
user_id=uuid4(),
|
||||
search_space_id=1,
|
||||
workspace_id=1,
|
||||
billing_tier="premium",
|
||||
base_model="openai/gpt-4o",
|
||||
quota_reserve_tokens=4000,
|
||||
|
|
|
|||
|
|
@ -93,7 +93,7 @@ def _action(*, tool_name: str, action_id: int = 7):
|
|||
id=action_id,
|
||||
tool_name=tool_name,
|
||||
thread_id=1,
|
||||
search_space_id=2,
|
||||
workspace_id=2,
|
||||
user_id="user-1",
|
||||
reverse_descriptor=None,
|
||||
)
|
||||
|
|
@ -111,7 +111,7 @@ def _doc_revision(
|
|||
revision = MagicMock()
|
||||
revision.id = 100
|
||||
revision.document_id = document_id
|
||||
revision.search_space_id = 2
|
||||
revision.workspace_id = 2
|
||||
revision.content_before = content_before
|
||||
revision.title_before = title_before
|
||||
revision.folder_id_before = folder_id_before
|
||||
|
|
@ -130,7 +130,7 @@ def _folder_revision(
|
|||
revision = MagicMock()
|
||||
revision.id = 200
|
||||
revision.folder_id = folder_id
|
||||
revision.search_space_id = 2
|
||||
revision.workspace_id = 2
|
||||
revision.name_before = name_before
|
||||
revision.parent_id_before = parent_id_before
|
||||
revision.position_before = position_before
|
||||
|
|
|
|||
|
|
@ -32,12 +32,12 @@ def _patch_common_bundle_dependencies(monkeypatch: pytest.MonkeyPatch):
|
|||
|
||||
_CapturedChatLiteLLM.calls = []
|
||||
|
||||
async def _fake_search_space(
|
||||
_session: Any, _search_space_id: int
|
||||
async def _fake_workspace(
|
||||
_session: Any, _workspace_id: int
|
||||
) -> SimpleNamespace:
|
||||
return SimpleNamespace(id=42, user_id="user-1")
|
||||
|
||||
monkeypatch.setattr(llm_bundle, "_load_search_space", _fake_search_space)
|
||||
monkeypatch.setattr(llm_bundle, "_load_workspace", _fake_workspace)
|
||||
monkeypatch.setattr(llm_bundle, "SanitizedChatLiteLLM", _CapturedChatLiteLLM)
|
||||
monkeypatch.setattr(llm_bundle, "register_model_usage_metadata", lambda **_kw: None)
|
||||
monkeypatch.setattr(
|
||||
|
|
@ -65,9 +65,9 @@ async def test_load_llm_bundle_enables_streaming_for_db_models(
|
|||
connection=connection,
|
||||
)
|
||||
|
||||
async def _fake_db_model(_session: Any, *, model_id: int, search_space: Any) -> Any:
|
||||
async def _fake_db_model(_session: Any, *, model_id: int, workspace: Any) -> Any:
|
||||
assert model_id == 7
|
||||
assert search_space.id == 42
|
||||
assert workspace.id == 42
|
||||
return model
|
||||
|
||||
monkeypatch.setattr(llm_bundle, "_load_db_model", _fake_db_model)
|
||||
|
|
@ -83,7 +83,7 @@ async def test_load_llm_bundle_enables_streaming_for_db_models(
|
|||
llm, agent_config, error = await llm_bundle.load_llm_bundle(
|
||||
object(),
|
||||
config_id=7,
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
)
|
||||
|
||||
assert error is None
|
||||
|
|
@ -140,7 +140,7 @@ async def test_load_llm_bundle_enables_streaming_for_global_models(
|
|||
llm, agent_config, error = await llm_bundle.load_llm_bundle(
|
||||
object(),
|
||||
config_id=-11,
|
||||
search_space_id=42,
|
||||
workspace_id=42,
|
||||
)
|
||||
|
||||
assert error is None
|
||||
|
|
|
|||
|
|
@ -145,14 +145,14 @@ async def test_billable_call_invoked_with_correct_kwargs_for_free_config(monkeyp
|
|||
|
||||
user_id = uuid4()
|
||||
|
||||
async def _fake_resolver(sess, search_space_id, *, thread_id=None):
|
||||
assert search_space_id == 777
|
||||
async def _fake_resolver(sess, workspace_id, *, thread_id=None):
|
||||
assert workspace_id == 777
|
||||
assert thread_id == 99
|
||||
return user_id, "free", "openrouter/some-free-model"
|
||||
|
||||
monkeypatch.setattr(
|
||||
video_presentation_tasks,
|
||||
"_resolve_agent_billing_for_search_space",
|
||||
"_resolve_agent_billing_for_workspace",
|
||||
_fake_resolver,
|
||||
)
|
||||
monkeypatch.setattr(video_presentation_tasks, "billable_call", _ok_billable_call)
|
||||
|
|
@ -169,7 +169,7 @@ async def test_billable_call_invoked_with_correct_kwargs_for_free_config(monkeyp
|
|||
result = await video_presentation_tasks._generate_video_presentation(
|
||||
video_presentation_id=11,
|
||||
source_content="content",
|
||||
search_space_id=777,
|
||||
workspace_id=777,
|
||||
user_prompt=None,
|
||||
)
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ async def test_billable_call_invoked_with_correct_kwargs_for_free_config(monkeyp
|
|||
assert len(_CALL_LOG) == 1
|
||||
call = _CALL_LOG[0]
|
||||
assert call["user_id"] == user_id
|
||||
assert call["search_space_id"] == 777
|
||||
assert call["workspace_id"] == 777
|
||||
assert call["billing_tier"] == "free"
|
||||
assert call["base_model"] == "openrouter/some-free-model"
|
||||
assert call["usage_type"] == "video_presentation_generation"
|
||||
|
|
@ -213,12 +213,12 @@ async def test_billable_call_invoked_with_premium_tier(monkeypatch):
|
|||
|
||||
user_id = uuid4()
|
||||
|
||||
async def _fake_resolver(sess, search_space_id, *, thread_id=None):
|
||||
async def _fake_resolver(sess, workspace_id, *, thread_id=None):
|
||||
return user_id, "premium", "gpt-5.4"
|
||||
|
||||
monkeypatch.setattr(
|
||||
video_presentation_tasks,
|
||||
"_resolve_agent_billing_for_search_space",
|
||||
"_resolve_agent_billing_for_workspace",
|
||||
_fake_resolver,
|
||||
)
|
||||
monkeypatch.setattr(video_presentation_tasks, "billable_call", _ok_billable_call)
|
||||
|
|
@ -235,7 +235,7 @@ async def test_billable_call_invoked_with_premium_tier(monkeypatch):
|
|||
await video_presentation_tasks._generate_video_presentation(
|
||||
video_presentation_id=11,
|
||||
source_content="content",
|
||||
search_space_id=777,
|
||||
workspace_id=777,
|
||||
user_prompt=None,
|
||||
)
|
||||
|
||||
|
|
@ -256,12 +256,12 @@ async def test_quota_insufficient_marks_video_failed_and_skips_graph(monkeypatch
|
|||
lambda: _FakeSessionMaker(session),
|
||||
)
|
||||
|
||||
async def _fake_resolver(sess, search_space_id, *, thread_id=None):
|
||||
async def _fake_resolver(sess, workspace_id, *, thread_id=None):
|
||||
return uuid4(), "premium", "gpt-5.4"
|
||||
|
||||
monkeypatch.setattr(
|
||||
video_presentation_tasks,
|
||||
"_resolve_agent_billing_for_search_space",
|
||||
"_resolve_agent_billing_for_workspace",
|
||||
_fake_resolver,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
|
|
@ -283,7 +283,7 @@ async def test_quota_insufficient_marks_video_failed_and_skips_graph(monkeypatch
|
|||
result = await video_presentation_tasks._generate_video_presentation(
|
||||
video_presentation_id=12,
|
||||
source_content="content",
|
||||
search_space_id=777,
|
||||
workspace_id=777,
|
||||
user_prompt=None,
|
||||
)
|
||||
|
||||
|
|
@ -309,12 +309,12 @@ async def test_billing_settlement_failure_marks_video_failed(monkeypatch):
|
|||
lambda: _FakeSessionMaker(session),
|
||||
)
|
||||
|
||||
async def _fake_resolver(sess, search_space_id, *, thread_id=None):
|
||||
async def _fake_resolver(sess, workspace_id, *, thread_id=None):
|
||||
return uuid4(), "premium", "gpt-5.4"
|
||||
|
||||
monkeypatch.setattr(
|
||||
video_presentation_tasks,
|
||||
"_resolve_agent_billing_for_search_space",
|
||||
"_resolve_agent_billing_for_workspace",
|
||||
_fake_resolver,
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
|
|
@ -335,7 +335,7 @@ async def test_billing_settlement_failure_marks_video_failed(monkeypatch):
|
|||
result = await video_presentation_tasks._generate_video_presentation(
|
||||
video_presentation_id=14,
|
||||
source_content="content",
|
||||
search_space_id=777,
|
||||
workspace_id=777,
|
||||
user_prompt=None,
|
||||
)
|
||||
|
||||
|
|
@ -360,12 +360,12 @@ async def test_resolver_failure_marks_video_failed(monkeypatch):
|
|||
lambda: _FakeSessionMaker(session),
|
||||
)
|
||||
|
||||
async def _failing_resolver(sess, search_space_id, *, thread_id=None):
|
||||
raise ValueError("Search space 777 not found")
|
||||
async def _failing_resolver(sess, workspace_id, *, thread_id=None):
|
||||
raise ValueError("Workspace 777 not found")
|
||||
|
||||
monkeypatch.setattr(
|
||||
video_presentation_tasks,
|
||||
"_resolve_agent_billing_for_search_space",
|
||||
"_resolve_agent_billing_for_workspace",
|
||||
_failing_resolver,
|
||||
)
|
||||
|
||||
|
|
@ -384,7 +384,7 @@ async def test_resolver_failure_marks_video_failed(monkeypatch):
|
|||
result = await video_presentation_tasks._generate_video_presentation(
|
||||
video_presentation_id=13,
|
||||
source_content="content",
|
||||
search_space_id=777,
|
||||
workspace_id=777,
|
||||
user_prompt=None,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ ALLOW_ANY_EXPECTED = {
|
|||
"routes/obsidian_plugin_routes.py": (
|
||||
"_auth: AuthContext = Depends(allow_any_principal)"
|
||||
),
|
||||
"routes/search_spaces_routes.py": (
|
||||
"routes/workspaces_routes.py": (
|
||||
"auth: AuthContext = Depends(allow_any_principal)"
|
||||
),
|
||||
}
|
||||
|
|
@ -60,12 +60,12 @@ def test_allow_any_principal_is_only_used_by_bootstrap_allowlist() -> None:
|
|||
assert expected_snippet in text
|
||||
|
||||
|
||||
def test_connector_listers_route_pat_through_search_space_gate() -> None:
|
||||
def test_connector_listers_route_pat_through_workspace_gate() -> None:
|
||||
for rel_path in CONNECTOR_LISTERS:
|
||||
text = (APP_ROOT / rel_path).read_text()
|
||||
assert "auth: AuthContext = Depends(get_auth_context)" in text, rel_path
|
||||
assert (
|
||||
"await check_search_space_access(session, auth, connector.search_space_id)"
|
||||
"await check_workspace_access(session, auth, connector.workspace_id)"
|
||||
in text
|
||||
), rel_path
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from app.utils.validators import (
|
|||
validate_messages,
|
||||
validate_research_mode,
|
||||
validate_search_mode,
|
||||
validate_search_space_id,
|
||||
validate_workspace_id,
|
||||
validate_top_k,
|
||||
validate_url,
|
||||
validate_uuid,
|
||||
|
|
@ -34,8 +34,8 @@ pytestmark = pytest.mark.unit
|
|||
(" 42 ", 42),
|
||||
],
|
||||
)
|
||||
def test_validate_search_space_id_valid(valid_input, expected):
|
||||
assert validate_search_space_id(valid_input) == expected
|
||||
def test_validate_workspace_id_valid(valid_input, expected):
|
||||
assert validate_workspace_id(valid_input) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
@ -54,9 +54,9 @@ def test_validate_search_space_id_valid(valid_input, expected):
|
|||
"-5",
|
||||
],
|
||||
)
|
||||
def test_validate_search_space_id_invalid(invalid_input):
|
||||
def test_validate_workspace_id_invalid(invalid_input):
|
||||
with pytest.raises(HTTPException) as excinfo:
|
||||
validate_search_space_id(invalid_input)
|
||||
validate_workspace_id(invalid_input)
|
||||
assert excinfo.value.status_code == 400
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue