chore: import upstream snapshot with attribution
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
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
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
"""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
|
||||
Reference in New Issue
Block a user