6ede33ccdb
Build and Push Docker Images / create_manifest (web, surfsense-web, , cpu) (push) Has been cancelled
Build and Push Docker Images / finalize_release (push) Has been cancelled
Obsidian Plugin Lint / lint (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-24.04-arm, linux/arm64, arm64, , runner, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_web, cpu, ./surfsense_web/Dockerfile, web, surfsense-web, ubuntu-latest, linux/amd64, amd64, , runner, false, cpu) (push) Has been cancelled
Build and Push Docker Images / compute_version (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, , production, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cpu, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, , production, false, cpu) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda126, production, true, cuda126) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu126, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda126, production, true, cuda126) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-24.04-arm, linux/arm64, arm64, -cuda, production, true, cuda) (push) Has been cancelled
Build and Push Docker Images / build (./surfsense_backend, cu128, ./surfsense_backend/Dockerfile, backend, surfsense-backend, ubuntu-latest, linux/amd64, amd64, -cuda, production, true, cuda) (push) Has been cancelled
Build and Push Docker Images / verify_digests (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, , cpu) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda, cuda) (push) Has been cancelled
Build and Push Docker Images / create_manifest (backend, surfsense-backend, -cuda126, cuda126) (push) Has been cancelled
188 lines
5.2 KiB
Python
188 lines
5.2 KiB
Python
"""Unit tests for the first-class memory service."""
|
|
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from app.services.memory import (
|
|
MemoryScope,
|
|
reset_memory,
|
|
save_memory,
|
|
)
|
|
|
|
pytestmark = pytest.mark.unit
|
|
|
|
|
|
class _FakeSession:
|
|
def __init__(self) -> None:
|
|
self.commit_calls = 0
|
|
self.rollback_calls = 0
|
|
self.added = []
|
|
|
|
def add(self, obj) -> None:
|
|
self.added.append(obj)
|
|
|
|
async def commit(self) -> None:
|
|
self.commit_calls += 1
|
|
|
|
async def rollback(self) -> None:
|
|
self.rollback_calls += 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_save_memory_saves_heading_based_memory(monkeypatch) -> None:
|
|
target = SimpleNamespace(memory_md="")
|
|
session = _FakeSession()
|
|
|
|
async def fake_load_target(**_kwargs):
|
|
return target
|
|
|
|
monkeypatch.setattr("app.services.memory.service._load_target", fake_load_target)
|
|
|
|
result = await save_memory(
|
|
scope=MemoryScope.USER,
|
|
target_id="00000000-0000-0000-0000-000000000000",
|
|
content="## Facts\n- 2026-05-19: Anish works on SurfSense\n",
|
|
session=session,
|
|
)
|
|
|
|
assert result.status == "saved"
|
|
assert target.memory_md.startswith("## Facts")
|
|
assert session.commit_calls == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_save_memory_accepts_legacy_marker_payload(monkeypatch) -> None:
|
|
target = SimpleNamespace(memory_md="")
|
|
session = _FakeSession()
|
|
|
|
async def fake_load_target(**_kwargs):
|
|
return target
|
|
|
|
monkeypatch.setattr("app.services.memory.service._load_target", fake_load_target)
|
|
|
|
result = await save_memory(
|
|
scope=MemoryScope.USER,
|
|
target_id="00000000-0000-0000-0000-000000000000",
|
|
content="- (2026-05-19) [fact] Legacy marker memory\n",
|
|
session=session,
|
|
)
|
|
|
|
assert result.status == "saved"
|
|
assert target.memory_md == "## Memory\n- 2026-05-19: Legacy marker memory"
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_save_memory_rejects_long_no_heading_payload(monkeypatch) -> None:
|
|
target = SimpleNamespace(memory_md="## Facts\n- 2026-05-19: Existing\n")
|
|
session = _FakeSession()
|
|
|
|
async def fake_load_target(**_kwargs):
|
|
return target
|
|
|
|
monkeypatch.setattr("app.services.memory.service._load_target", fake_load_target)
|
|
|
|
result = await save_memory(
|
|
scope=MemoryScope.USER,
|
|
target_id="00000000-0000-0000-0000-000000000000",
|
|
content="reasoning text before NO_UPDATE should not become saved memory",
|
|
session=session,
|
|
)
|
|
|
|
assert result.status == "error"
|
|
assert session.commit_calls == 0
|
|
assert target.memory_md.startswith("## Facts")
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_save_memory_no_update_sentinel_is_no_op(monkeypatch) -> None:
|
|
existing = "## Preferences\n- 2026-05-20: Existing preference\n"
|
|
target = SimpleNamespace(memory_md=existing)
|
|
session = _FakeSession()
|
|
|
|
async def fake_load_target(**_kwargs):
|
|
return target
|
|
|
|
monkeypatch.setattr("app.services.memory.service._load_target", fake_load_target)
|
|
|
|
result = await save_memory(
|
|
scope=MemoryScope.USER,
|
|
target_id="00000000-0000-0000-0000-000000000000",
|
|
content="NO_UPDATE",
|
|
session=session,
|
|
)
|
|
|
|
assert result.status == "no_op"
|
|
assert result.memory_md == existing
|
|
assert target.memory_md == existing
|
|
assert session.commit_calls == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_save_memory_no_update_sentinel_is_case_insensitive(monkeypatch) -> None:
|
|
existing = "## Preferences\n- 2026-05-20: Existing preference\n"
|
|
target = SimpleNamespace(memory_md=existing)
|
|
session = _FakeSession()
|
|
|
|
async def fake_load_target(**_kwargs):
|
|
return target
|
|
|
|
monkeypatch.setattr("app.services.memory.service._load_target", fake_load_target)
|
|
|
|
result = await save_memory(
|
|
scope=MemoryScope.USER,
|
|
target_id="00000000-0000-0000-0000-000000000000",
|
|
content=" no update ",
|
|
session=session,
|
|
)
|
|
|
|
assert result.status == "no_op"
|
|
assert result.memory_md == existing
|
|
assert target.memory_md == existing
|
|
assert session.commit_calls == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_save_memory_grandfathers_existing_team_personal_heading(
|
|
monkeypatch,
|
|
) -> None:
|
|
content = "## Preferences\n- 2026-05-19: Existing legacy heading\n"
|
|
target = SimpleNamespace(shared_memory_md=content)
|
|
session = _FakeSession()
|
|
|
|
async def fake_load_target(**_kwargs):
|
|
return target
|
|
|
|
monkeypatch.setattr("app.services.memory.service._load_target", fake_load_target)
|
|
|
|
result = await save_memory(
|
|
scope=MemoryScope.TEAM,
|
|
target_id=1,
|
|
content=content,
|
|
session=session,
|
|
)
|
|
|
|
assert result.status == "saved"
|
|
assert result.warnings
|
|
assert session.commit_calls == 1
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_reset_memory_clears_memory(monkeypatch) -> None:
|
|
target = SimpleNamespace(memory_md="## Facts\n- 2026-05-19: Existing\n")
|
|
session = _FakeSession()
|
|
|
|
async def fake_load_target(**_kwargs):
|
|
return target
|
|
|
|
monkeypatch.setattr("app.services.memory.service._load_target", fake_load_target)
|
|
|
|
result = await reset_memory(
|
|
scope=MemoryScope.USER,
|
|
target_id="00000000-0000-0000-0000-000000000000",
|
|
session=session,
|
|
)
|
|
|
|
assert result.status == "saved"
|
|
assert target.memory_md == ""
|