Files
Octop/tests/unit/agents/test_memory_backend.py
347dee56f4 refactor(infra): group agents modules and clarify package ownership (#1164)
* refactor(agents): group thin modules into domain subpackages

Move plugin helpers, memory, settings stores, persona/MBTI, and avatar
bootstrap code under plugins/, memory/, settings/, persona/, and experts/
while keeping top-level import shims so existing call sites stay stable.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(agents): drop compatibility shims after subpackage move

Call sites already import the new package paths; remove the top-level
re-export stubs so the agents tree only shows real modules.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(agents): move thread helpers into threads/

Group artifact shaping, fork, and context breakdown under
agents/threads so top-level only keeps lifecycle/config adapters.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(agents): fold profile/runtime/tool catalog into settings/

Move the remaining config-surface helpers under settings/ and drop the
skill_package id-list aliases in favor of dump/parse_id_list.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(agents): group workspace_dir and execute_env under workspace/

Keep path resolution and harness execute-env injection together as one
workspace package; leave only manager and conversation_mode at top level.

Co-authored-by: Cursor <cursoragent@cursor.com>

* refactor(infra): fix history/SkillHub ownership and drop thin providers/

Move history_projection under history/, share SkillHub host/limits and
route expert skill downloads through skills.skillhub_market, and fold
Codex OAuth helpers into agents.providers to remove the tiny providers/
package.

Co-authored-by: Cursor <cursoragent@cursor.com>

* docs: sync AGENTS.md and guides with infra/agents layout

Reflect subpackages (settings/workspace/threads/persona, history/, skills/)
and drop stale runtime.py / mbti_profiles paths after the agents reorg.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: jubaoliang <jubaoliang@tencent.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-25 22:32:41 +08:00

150 lines
5.0 KiB
Python

from __future__ import annotations
from pathlib import Path
import pytest
from octop.config import DatabaseConfig, OctopConfig
from octop.infra.agents.memory.backend import memory_backend_from_agent_config, open_memory_kwargs
from octop.infra.errors import OctopError
def test_default_memory_backend_empty_on_sqlite_control_plane() -> None:
assert memory_backend_from_agent_config({}, octop_config=OctopConfig()) == {}
def test_default_memory_backend_follows_postgresql_control_plane() -> None:
cfg = OctopConfig(
database=DatabaseConfig(
driver="postgresql",
host="127.0.0.1",
database="octop",
user="octop",
password="x",
)
)
out = memory_backend_from_agent_config({}, octop_config=cfg)
assert out["memory_backend"]["type"] == "postgres"
assert "127.0.0.1" in out["memory_backend"]["dsn"]
assert (
out["memory_backend"]["dsn"].endswith("/octop") or "/octop" in out["memory_backend"]["dsn"]
)
def test_explicit_sqlite_overrides_postgresql_control_plane() -> None:
cfg = OctopConfig(
database=DatabaseConfig(
driver="postgresql",
host="127.0.0.1",
database="octop",
user="octop",
password="x",
)
)
out = memory_backend_from_agent_config(
{"memory": {"backend": {"type": "sqlite"}}},
octop_config=cfg,
workspace_dir=Path("/tmp/ws"),
)
assert out["memory_backend"]["type"] == "sqlite"
# Compare as Path so the assertion is separator-agnostic (Windows renders
# the db_path with backslashes; the code uses pathlib, not literal "/").
assert Path(out["memory_backend"]["db_path"]) == Path("/tmp/ws") / "memory.sqlite"
def test_sqlite_explicit_uses_system_files_path(tmp_path: Path) -> None:
out = memory_backend_from_agent_config(
{"memory": {"backend": {"type": "sqlite"}}, "system_files_path": ".octop"},
octop_config=OctopConfig(),
workspace_dir=tmp_path,
)
assert Path(out["memory_backend"]["db_path"]) == tmp_path / ".octop" / "memory.sqlite"
def test_postgres_explicit_dsn() -> None:
out = memory_backend_from_agent_config(
{"memory": {"backend": {"type": "postgres", "dsn": "postgresql://a@b/c"}}},
octop_config=OctopConfig(),
)
assert out["memory_backend"] == {"type": "postgres", "dsn": "postgresql://a@b/c"}
def test_postgres_use_control_plane_dsn() -> None:
cfg = OctopConfig(
database=DatabaseConfig(
driver="postgresql",
host="127.0.0.1",
database="octop",
user="octop",
password="x",
url="postgresql://octop:x@127.0.0.1:5432/octop?sslmode=require",
)
)
out = memory_backend_from_agent_config(
{"memory": {"backend": {"type": "postgres", "use_control_plane_dsn": True}}},
octop_config=cfg,
)
assert out["memory_backend"]["type"] == "postgres"
assert "sslmode=require" in out["memory_backend"]["dsn"]
def test_use_control_plane_dsn_requires_postgresql() -> None:
with pytest.raises(OctopError, match="control plane"):
memory_backend_from_agent_config(
{"memory": {"backend": {"type": "postgres", "use_control_plane_dsn": True}}},
octop_config=OctopConfig(),
)
def test_open_memory_kwargs_follows_postgresql_control_plane(tmp_path: Path) -> None:
cfg = OctopConfig(
database=DatabaseConfig(
driver="postgresql",
host="127.0.0.1",
database="octop",
user="octop",
password="x",
)
)
ns, backend, backend_config = open_memory_kwargs(
agent_id="a1",
cfg={},
octop_config=cfg,
workspace_dir=tmp_path,
)
assert ns == "agent_a1"
assert backend == "postgres"
assert backend_config is not None
assert "dsn" in backend_config
def test_memory_db_path_prefers_existing_nested(tmp_path: Path) -> None:
from octop.api.common.memory_client import memory_db_path
nested = tmp_path / ".octop" / "memory.sqlite"
nested.parent.mkdir(parents=True)
nested.write_text("", encoding="utf-8")
assert memory_db_path(tmp_path) == nested
def test_memory_db_path_legacy_when_only_root_exists(tmp_path: Path) -> None:
from octop.api.common.memory_client import memory_db_path
root = tmp_path / "memory.sqlite"
root.write_text("", encoding="utf-8")
assert memory_db_path(tmp_path) == root
def test_memory_db_path_new_layout_signal_without_sqlite_yet(tmp_path: Path) -> None:
from octop.api.common.memory_client import memory_db_path
(tmp_path / ".octop" / "_builtin_skills").mkdir(parents=True)
assert memory_db_path(tmp_path) == tmp_path / ".octop" / "memory.sqlite"
def test_memory_db_path_empty_octop_dir_stays_legacy(tmp_path: Path) -> None:
from octop.api.common.memory_client import memory_db_path
(tmp_path / ".octop").mkdir()
assert memory_db_path(tmp_path) == tmp_path / "memory.sqlite"