e4dcfc49aa
Tests / Lint and Format (push) Waiting to run
Tests / Web Node Tests (push) Waiting to run
Tests / Import Check (Python 3.11) (push) Waiting to run
Tests / Import Check (Python 3.12) (push) Waiting to run
Tests / Import Check (Python 3.13) (push) Waiting to run
Tests / Import Check (Python 3.14) (push) Waiting to run
Tests / Python Tests (Python 3.11) (push) Blocked by required conditions
Tests / Python Tests (Python 3.12) (push) Blocked by required conditions
Tests / Python Tests (Python 3.13) (push) Blocked by required conditions
Tests / Python Tests (Python 3.14) (push) Blocked by required conditions
Tests / Test Summary (push) Blocked by required conditions
319 lines
9.9 KiB
Python
319 lines
9.9 KiB
Python
"""Unit tests for the ``write_note`` tool (append + edit modes)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from deeptutor.tools.write_note import (
|
|
ALL_TURNS_SENTINEL,
|
|
DEFAULT_TURNS_TO_INCLUDE,
|
|
MAX_NOTE_CHARS,
|
|
MAX_TITLE_CHARS,
|
|
write_note,
|
|
)
|
|
|
|
|
|
class _FakeManager:
|
|
"""Notebook manager stand-in. Tracks last add / update for assertions."""
|
|
|
|
def __init__(
|
|
self,
|
|
notebooks=(),
|
|
records_by_nb=None,
|
|
raise_on_add: bool = False,
|
|
raise_on_update: bool = False,
|
|
):
|
|
self._notebooks = list(notebooks)
|
|
self._records = dict(records_by_nb or {})
|
|
self._raise_on_add = raise_on_add
|
|
self._raise_on_update = raise_on_update
|
|
self.last_add = None
|
|
self.last_update = None
|
|
|
|
def list_notebooks(self):
|
|
return list(self._notebooks)
|
|
|
|
def get_record(self, notebook_id, record_id):
|
|
for rec in self._records.get(notebook_id, []):
|
|
if str(rec.get("id")) == record_id:
|
|
return rec
|
|
return None
|
|
|
|
def add_record(self, **kwargs):
|
|
if self._raise_on_add:
|
|
raise RuntimeError("disk full")
|
|
self.last_add = kwargs
|
|
return {"record": {"id": "rec-123"}, "added_to_notebooks": kwargs["notebook_ids"]}
|
|
|
|
def update_record(self, notebook_id, record_id, **kwargs):
|
|
if self._raise_on_update:
|
|
raise RuntimeError("io error")
|
|
target = self.get_record(notebook_id, record_id)
|
|
if target is None:
|
|
return None
|
|
target.update(kwargs)
|
|
self.last_update = {"notebook_id": notebook_id, "record_id": record_id, **kwargs}
|
|
return target
|
|
|
|
|
|
_SAMPLE_HISTORY = [
|
|
{"role": "user", "content": "What is a vector?"},
|
|
{"role": "assistant", "content": "A vector is..."},
|
|
{"role": "user", "content": "And a tensor?"},
|
|
{"role": "assistant", "content": "A tensor generalises..."},
|
|
]
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Mode validation + shared error paths
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_rejects_unknown_mode() -> None:
|
|
outcome = write_note(
|
|
mode="rewrite",
|
|
notebook_id="nb-1",
|
|
notebook_manager=_FakeManager(notebooks=[{"id": "nb-1", "name": "N"}]),
|
|
)
|
|
assert outcome.ok is False
|
|
assert "Unknown mode" in outcome.error
|
|
|
|
|
|
def test_rejects_unknown_notebook() -> None:
|
|
outcome = write_note(
|
|
mode="append",
|
|
notebook_id="bogus",
|
|
title="t",
|
|
conversation_history=_SAMPLE_HISTORY,
|
|
notebook_manager=_FakeManager(notebooks=[{"id": "nb-1", "name": "N"}]),
|
|
)
|
|
assert outcome.ok is False
|
|
assert "Unknown notebook_id" in outcome.error
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Append mode
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_append_writes_real_transcript_by_default() -> None:
|
|
manager = _FakeManager(notebooks=[{"id": "nb-1", "name": "Math"}])
|
|
outcome = write_note(
|
|
mode="append",
|
|
notebook_id="nb-1",
|
|
title="Vectors",
|
|
turns_to_include=2,
|
|
note="Bookmark for review.",
|
|
conversation_history=_SAMPLE_HISTORY,
|
|
current_user_message="",
|
|
notebook_manager=manager,
|
|
)
|
|
assert outcome.ok is True
|
|
assert outcome.mode == "append"
|
|
body = manager.last_add["output"]
|
|
assert "### User" in body and "### Assistant" in body
|
|
assert "What is a vector?" in body
|
|
assert "A tensor generalises..." in body
|
|
# Note prefix appears above the transcript.
|
|
assert "Bookmark for review." in body
|
|
assert body.index("Bookmark") < body.index("### User")
|
|
assert manager.last_add["metadata"]["mode"] == "append"
|
|
assert manager.last_add["metadata"]["explicit_content"] is False
|
|
|
|
|
|
def test_append_with_explicit_content_skips_auto_transcript() -> None:
|
|
"""When the agent passes `content`, it's saved verbatim — supports
|
|
the 'save an agent-authored summary' use case."""
|
|
manager = _FakeManager(notebooks=[{"id": "nb-1", "name": "N"}])
|
|
outcome = write_note(
|
|
mode="append",
|
|
notebook_id="nb-1",
|
|
title="Summary",
|
|
content="### My summary\n\nThings to remember.",
|
|
conversation_history=_SAMPLE_HISTORY,
|
|
notebook_manager=manager,
|
|
)
|
|
assert outcome.ok is True
|
|
body = manager.last_add["output"]
|
|
assert "Things to remember." in body
|
|
# The default Q&A transcript headings should NOT appear when
|
|
# explicit content was provided.
|
|
assert "### User" not in body
|
|
assert manager.last_add["metadata"]["explicit_content"] is True
|
|
|
|
|
|
def test_append_accepts_all_sentinel_for_turns() -> None:
|
|
manager = _FakeManager(notebooks=[{"id": "nb-1", "name": "N"}])
|
|
outcome = write_note(
|
|
mode="append",
|
|
notebook_id="nb-1",
|
|
title="Everything",
|
|
turns_to_include=ALL_TURNS_SENTINEL,
|
|
conversation_history=_SAMPLE_HISTORY,
|
|
current_user_message="latest question",
|
|
notebook_manager=manager,
|
|
)
|
|
assert outcome.ok is True
|
|
body = manager.last_add["output"]
|
|
# All four history pairs + the current question should be in the body.
|
|
assert "What is a vector?" in body
|
|
assert "And a tensor?" in body
|
|
assert "latest question" in body
|
|
|
|
|
|
def test_append_requires_title() -> None:
|
|
manager = _FakeManager(notebooks=[{"id": "nb-1", "name": "N"}])
|
|
outcome = write_note(
|
|
mode="append",
|
|
notebook_id="nb-1",
|
|
title=" ",
|
|
conversation_history=_SAMPLE_HISTORY,
|
|
notebook_manager=manager,
|
|
)
|
|
assert outcome.ok is False
|
|
assert "title" in outcome.error.lower()
|
|
|
|
|
|
def test_append_refuses_empty_history_without_content() -> None:
|
|
manager = _FakeManager(notebooks=[{"id": "nb-1", "name": "N"}])
|
|
outcome = write_note(
|
|
mode="append",
|
|
notebook_id="nb-1",
|
|
title="t",
|
|
conversation_history=[],
|
|
current_user_message="",
|
|
notebook_manager=manager,
|
|
)
|
|
assert outcome.ok is False
|
|
assert "nothing to save" in outcome.error.lower()
|
|
|
|
|
|
def test_append_surfaces_manager_errors_as_outcome() -> None:
|
|
manager = _FakeManager(notebooks=[{"id": "nb-1", "name": "N"}], raise_on_add=True)
|
|
outcome = write_note(
|
|
mode="append",
|
|
notebook_id="nb-1",
|
|
title="t",
|
|
conversation_history=_SAMPLE_HISTORY,
|
|
notebook_manager=manager,
|
|
)
|
|
assert outcome.ok is False
|
|
assert "Save failed" in outcome.error
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Edit mode
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_edit_requires_record_id() -> None:
|
|
manager = _FakeManager(
|
|
notebooks=[{"id": "nb-1", "name": "N"}],
|
|
records_by_nb={"nb-1": [{"id": "r1", "title": "old"}]},
|
|
)
|
|
outcome = write_note(
|
|
mode="edit",
|
|
notebook_id="nb-1",
|
|
title="new",
|
|
notebook_manager=manager,
|
|
)
|
|
assert outcome.ok is False
|
|
assert "record_id" in outcome.error.lower()
|
|
|
|
|
|
def test_edit_rejects_unknown_record() -> None:
|
|
manager = _FakeManager(
|
|
notebooks=[{"id": "nb-1", "name": "N"}],
|
|
records_by_nb={"nb-1": [{"id": "r1", "title": "old"}]},
|
|
)
|
|
outcome = write_note(
|
|
mode="edit",
|
|
notebook_id="nb-1",
|
|
record_id="r-missing",
|
|
title="new",
|
|
notebook_manager=manager,
|
|
)
|
|
assert outcome.ok is False
|
|
assert "not found" in outcome.error.lower()
|
|
assert "list_notebook" in outcome.error # tells the LLM where to discover ids
|
|
|
|
|
|
def test_edit_requires_at_least_one_field_changed() -> None:
|
|
manager = _FakeManager(
|
|
notebooks=[{"id": "nb-1", "name": "N"}],
|
|
records_by_nb={"nb-1": [{"id": "r1", "title": "old"}]},
|
|
)
|
|
outcome = write_note(
|
|
mode="edit",
|
|
notebook_id="nb-1",
|
|
record_id="r1",
|
|
notebook_manager=manager,
|
|
)
|
|
assert outcome.ok is False
|
|
assert "at least one" in outcome.error.lower()
|
|
|
|
|
|
def test_edit_patches_title_and_content() -> None:
|
|
manager = _FakeManager(
|
|
notebooks=[{"id": "nb-1", "name": "N"}],
|
|
records_by_nb={"nb-1": [{"id": "r1", "title": "old", "output": "old body"}]},
|
|
)
|
|
outcome = write_note(
|
|
mode="edit",
|
|
notebook_id="nb-1",
|
|
record_id="r1",
|
|
title="new title",
|
|
content="new body",
|
|
notebook_manager=manager,
|
|
)
|
|
assert outcome.ok is True
|
|
assert outcome.mode == "edit"
|
|
assert outcome.record_id == "r1"
|
|
assert manager.last_update == {
|
|
"notebook_id": "nb-1",
|
|
"record_id": "r1",
|
|
"title": "new title",
|
|
"output": "new body",
|
|
}
|
|
|
|
|
|
def test_edit_surfaces_manager_errors_as_outcome() -> None:
|
|
manager = _FakeManager(
|
|
notebooks=[{"id": "nb-1", "name": "N"}],
|
|
records_by_nb={"nb-1": [{"id": "r1", "title": "old"}]},
|
|
raise_on_update=True,
|
|
)
|
|
outcome = write_note(
|
|
mode="edit",
|
|
notebook_id="nb-1",
|
|
record_id="r1",
|
|
title="new",
|
|
notebook_manager=manager,
|
|
)
|
|
assert outcome.ok is False
|
|
assert "Edit failed" in outcome.error
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Constants / defaults
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_default_turns_to_include_is_three() -> None:
|
|
assert DEFAULT_TURNS_TO_INCLUDE == 3
|
|
|
|
|
|
def test_clips_oversized_title_and_note() -> None:
|
|
manager = _FakeManager(notebooks=[{"id": "nb-1", "name": "N"}])
|
|
outcome = write_note(
|
|
mode="append",
|
|
notebook_id="nb-1",
|
|
title="x" * (MAX_TITLE_CHARS + 50),
|
|
note="y" * (MAX_NOTE_CHARS + 100),
|
|
conversation_history=_SAMPLE_HISTORY,
|
|
notebook_manager=manager,
|
|
)
|
|
assert outcome.ok is True
|
|
assert manager.last_add["title"].endswith("…")
|
|
assert len(manager.last_add["title"]) <= MAX_TITLE_CHARS + 1
|
|
# The clipped note appears in the body with an ellipsis.
|
|
assert "…" in manager.last_add["output"]
|