e4dcfc49aa
Tests / Import Check (Python 3.13) (push) Has been cancelled
Tests / Import Check (Python 3.14) (push) Has been cancelled
Tests / Python Tests (Python 3.11) (push) Has been cancelled
Tests / Python Tests (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.14) (push) Has been cancelled
Tests / Test Summary (push) Has been cancelled
Tests / Lint and Format (push) Has been cancelled
Tests / Web Node Tests (push) Has been cancelled
Tests / Import Check (Python 3.11) (push) Has been cancelled
Tests / Import Check (Python 3.12) (push) Has been cancelled
Tests / Python Tests (Python 3.13) (push) Has been cancelled
237 lines
7.1 KiB
Python
237 lines
7.1 KiB
Python
"""CLI smoke tests for ``deeptutor notebook`` commands."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from typer.testing import CliRunner
|
|
|
|
from deeptutor_cli.main import app
|
|
|
|
runner = CliRunner()
|
|
|
|
|
|
class FakeNotebookManager:
|
|
"""Fake NotebookManager for CLI testing."""
|
|
|
|
def __init__(self) -> None:
|
|
self.notebooks: dict[str, dict] = {}
|
|
|
|
def create_notebook(self, name: str, description: str = "") -> dict:
|
|
import time
|
|
import uuid
|
|
|
|
nb = {
|
|
"id": str(uuid.uuid4())[:8],
|
|
"name": name,
|
|
"description": description,
|
|
"created_at": time.time(),
|
|
"updated_at": time.time(),
|
|
"records": [],
|
|
}
|
|
self.notebooks[nb["id"]] = nb
|
|
return nb
|
|
|
|
def get_notebook(self, notebook_id: str) -> dict | None:
|
|
return self.notebooks.get(notebook_id)
|
|
|
|
def add_record(
|
|
self,
|
|
notebook_ids: list[str],
|
|
record_type: str,
|
|
title: str,
|
|
user_query: str,
|
|
output: str,
|
|
summary: str = "",
|
|
metadata: dict | None = None,
|
|
kb_name: str | None = None,
|
|
) -> dict:
|
|
import time
|
|
import uuid
|
|
|
|
record = {
|
|
"id": str(uuid.uuid4())[:8],
|
|
"type": record_type,
|
|
"title": title,
|
|
"summary": summary,
|
|
"user_query": user_query,
|
|
"output": output,
|
|
"metadata": metadata or {},
|
|
"created_at": time.time(),
|
|
"kb_name": kb_name,
|
|
}
|
|
for nb_id in notebook_ids:
|
|
if nb_id in self.notebooks:
|
|
self.notebooks[nb_id]["records"].append(record)
|
|
return {"record": record, "added_to_notebooks": notebook_ids}
|
|
|
|
def update_record(self, notebook_id: str, record_id: str, **kwargs) -> dict | None:
|
|
nb = self.notebooks.get(notebook_id)
|
|
if not nb:
|
|
return None
|
|
for rec in nb["records"]:
|
|
if rec["id"] == record_id:
|
|
rec.update(kwargs)
|
|
return rec
|
|
return None
|
|
|
|
def remove_record(self, notebook_id: str, record_id: str) -> bool:
|
|
nb = self.notebooks.get(notebook_id)
|
|
if not nb:
|
|
return False
|
|
for i, rec in enumerate(nb["records"]):
|
|
if rec["id"] == record_id:
|
|
nb["records"].pop(i)
|
|
return True
|
|
return False
|
|
|
|
|
|
_fake_manager = FakeNotebookManager()
|
|
|
|
|
|
def _patch_facade(monkeypatch) -> None:
|
|
"""Patch DeepTutorApp methods to use the shared fake manager."""
|
|
|
|
def _create_notebook(self, **kwargs):
|
|
return _fake_manager.create_notebook(**kwargs)
|
|
|
|
def _get_notebook(self, notebook_id):
|
|
return _fake_manager.get_notebook(notebook_id)
|
|
|
|
def _add_record(self, **kwargs):
|
|
return _fake_manager.add_record(**kwargs)
|
|
|
|
def _update_record(self, notebook_id, record_id, **kwargs):
|
|
return _fake_manager.update_record(notebook_id, record_id, **kwargs)
|
|
|
|
def _remove_record(self, notebook_id, record_id) -> bool:
|
|
return _fake_manager.remove_record(notebook_id, record_id)
|
|
|
|
from deeptutor.app import facade
|
|
|
|
monkeypatch.setattr(facade.DeepTutorApp, "create_notebook", _create_notebook)
|
|
monkeypatch.setattr(facade.DeepTutorApp, "get_notebook", _get_notebook)
|
|
monkeypatch.setattr(facade.DeepTutorApp, "add_record", _add_record)
|
|
monkeypatch.setattr(facade.DeepTutorApp, "update_record", _update_record)
|
|
monkeypatch.setattr(facade.DeepTutorApp, "remove_record", _remove_record)
|
|
|
|
|
|
def test_notebook_add_md_creates_record(monkeypatch, tmp_path: Path) -> None:
|
|
"""add-md should read a markdown file and add it as a record to a notebook."""
|
|
_patch_facade(monkeypatch)
|
|
_fake_manager.notebooks.clear()
|
|
|
|
notebook = _fake_manager.create_notebook("Test Notebook")
|
|
|
|
md_file = tmp_path / "sample.md"
|
|
md_file.write_text("# Hello\n\nThis is a test.", encoding="utf-8")
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["notebook", "add-md", notebook["id"], str(md_file)],
|
|
)
|
|
|
|
assert result.exit_code == 0, result.output
|
|
assert "Added record" in result.output
|
|
|
|
stored = _fake_manager.get_notebook(notebook["id"])
|
|
assert stored is not None
|
|
assert len(stored["records"]) == 1
|
|
assert stored["records"][0]["title"] == "sample"
|
|
assert stored["records"][0]["type"] == "chat"
|
|
assert stored["records"][0]["output"] == "# Hello\n\nThis is a test."
|
|
|
|
|
|
def test_notebook_add_md_custom_title_and_type(monkeypatch, tmp_path: Path) -> None:
|
|
"""add-md should respect --title and --type options."""
|
|
_patch_facade(monkeypatch)
|
|
_fake_manager.notebooks.clear()
|
|
|
|
notebook = _fake_manager.create_notebook("Test Notebook")
|
|
|
|
md_file = tmp_path / "notes.md"
|
|
md_file.write_text("## Notes", encoding="utf-8")
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
[
|
|
"notebook",
|
|
"add-md",
|
|
notebook["id"],
|
|
str(md_file),
|
|
"--title",
|
|
"My Custom Title",
|
|
"--type",
|
|
"research",
|
|
],
|
|
)
|
|
|
|
assert result.exit_code == 0, result.output
|
|
|
|
stored = _fake_manager.get_notebook(notebook["id"])
|
|
assert stored["records"][0]["title"] == "My Custom Title"
|
|
assert stored["records"][0]["type"] == "research"
|
|
|
|
|
|
def test_notebook_add_md_file_not_found(monkeypatch) -> None:
|
|
"""add-md should exit with an error when the file does not exist."""
|
|
_patch_facade(monkeypatch)
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["notebook", "add-md", "any-nb", "/path/to/missing.md"],
|
|
)
|
|
|
|
assert result.exit_code == 1
|
|
assert "File not found" in result.output
|
|
|
|
|
|
def test_notebook_replace_md_updates_record(monkeypatch, tmp_path: Path) -> None:
|
|
"""replace-md should replace the output content of an existing record."""
|
|
_patch_facade(monkeypatch)
|
|
_fake_manager.notebooks.clear()
|
|
|
|
notebook = _fake_manager.create_notebook("Test Notebook")
|
|
add_result = _fake_manager.add_record(
|
|
notebook_ids=[notebook["id"]],
|
|
record_type="chat",
|
|
title="Original",
|
|
user_query="",
|
|
output="Old content",
|
|
)
|
|
record_id = add_result["record"]["id"]
|
|
|
|
new_md = tmp_path / "updated.md"
|
|
new_md.write_text("# Updated\n\nNew content here.", encoding="utf-8")
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["notebook", "replace-md", notebook["id"], record_id, str(new_md)],
|
|
)
|
|
|
|
assert result.exit_code == 0, result.output
|
|
assert "Updated record" in result.output
|
|
|
|
stored = _fake_manager.get_notebook(notebook["id"])
|
|
assert len(stored["records"]) == 1
|
|
assert stored["records"][0]["output"] == "# Updated\n\nNew content here."
|
|
|
|
|
|
def test_notebook_replace_md_record_not_found(monkeypatch, tmp_path: Path) -> None:
|
|
"""replace-md should exit with an error when the record does not exist."""
|
|
_patch_facade(monkeypatch)
|
|
_fake_manager.notebooks.clear()
|
|
|
|
notebook = _fake_manager.create_notebook("Test Notebook")
|
|
|
|
md_file = tmp_path / "any.md"
|
|
md_file.write_text("content", encoding="utf-8")
|
|
|
|
result = runner.invoke(
|
|
app,
|
|
["notebook", "replace-md", notebook["id"], "nonexistent-record", str(md_file)],
|
|
)
|
|
|
|
assert result.exit_code == 1
|
|
assert "Record not found" in result.output
|