09e9f3545f
Test / Code Quality (push) Has been cancelled
Test / Test (macos-latest, Python 3.10) (push) Has been cancelled
Test / Test (macos-latest, Python 3.11) (push) Has been cancelled
Test / Test (macos-latest, Python 3.12) (push) Has been cancelled
Test / Test (macos-latest, Python 3.13) (push) Has been cancelled
Test / Test (macos-latest, Python 3.14) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.10) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.11) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.12) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.13) (push) Has been cancelled
Test / Test (ubuntu-latest, Python 3.14) (push) Has been cancelled
Test / Test (windows-latest, Python 3.10) (push) Has been cancelled
Test / Test (windows-latest, Python 3.11) (push) Has been cancelled
Test / Test (windows-latest, Python 3.12) (push) Has been cancelled
Test / Test (windows-latest, Python 3.13) (push) Has been cancelled
Test / Test (windows-latest, Python 3.14) (push) Has been cancelled
CodeQL / Analyze (push) Has been cancelled
dependency-audit / pip-audit (push) Has been cancelled
166 lines
5.6 KiB
Python
166 lines
5.6 KiB
Python
"""E2E tests for NotesAPI."""
|
|
|
|
import pytest
|
|
|
|
from notebooklm import NoteNotFoundError
|
|
|
|
from .conftest import requires_auth
|
|
|
|
|
|
@requires_auth
|
|
class TestNotesList:
|
|
"""Test listing notes."""
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.readonly
|
|
async def test_list_notes(self, client, read_only_notebook_id):
|
|
"""List notes in test notebook - read-only."""
|
|
notes = await client.notes.list(read_only_notebook_id)
|
|
assert isinstance(notes, list)
|
|
|
|
|
|
@requires_auth
|
|
class TestNotesGet:
|
|
"""Test getting individual notes."""
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.readonly
|
|
async def test_get_note(self, client, read_only_notebook_id):
|
|
"""Get a specific note from test notebook - read-only."""
|
|
notes = await client.notes.list(read_only_notebook_id)
|
|
if not notes:
|
|
pytest.skip("No notes available in test notebook")
|
|
|
|
note = await client.notes.get(read_only_notebook_id, notes[0].id)
|
|
assert note is not None
|
|
assert note.id == notes[0].id
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_note_not_found(self, client, read_only_notebook_id):
|
|
"""Test getting a non-existent note raises NoteNotFoundError."""
|
|
# v0.8.0: a miss now raises NoteNotFoundError (issue #1247).
|
|
with pytest.raises(NoteNotFoundError):
|
|
await client.notes.get(read_only_notebook_id, "nonexistent_note_id")
|
|
|
|
|
|
@requires_auth
|
|
class TestNotesCRUD:
|
|
"""Test note CRUD operations - uses temp notebook."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_and_delete_note(self, client, temp_notebook):
|
|
"""Create and delete a note in temp notebook."""
|
|
# Create
|
|
note = await client.notes.create(
|
|
temp_notebook.id,
|
|
title="Test Note",
|
|
content="Test content for E2E",
|
|
)
|
|
assert note is not None
|
|
assert note.id != ""
|
|
assert note.title == "Test Note"
|
|
assert note.content == "Test content for E2E"
|
|
|
|
# Verify it appears in list
|
|
notes = await client.notes.list(temp_notebook.id)
|
|
note_ids = [n.id for n in notes]
|
|
assert note.id in note_ids
|
|
|
|
# Delete (v0.7.0: returns None, idempotent — issue #1211)
|
|
result = await client.notes.delete(temp_notebook.id, note.id)
|
|
assert result is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_note(self, client, temp_notebook):
|
|
"""Update a note's content and title."""
|
|
# Create
|
|
note = await client.notes.create(
|
|
temp_notebook.id,
|
|
title="Original Title",
|
|
content="Original content",
|
|
)
|
|
|
|
# Update
|
|
await client.notes.update(
|
|
temp_notebook.id,
|
|
note.id,
|
|
content="Updated content",
|
|
title="Updated Title",
|
|
)
|
|
|
|
# Verify
|
|
updated = await client.notes.get(temp_notebook.id, note.id)
|
|
assert updated is not None
|
|
assert updated.title == "Updated Title"
|
|
assert updated.content == "Updated content"
|
|
|
|
# Cleanup
|
|
await client.notes.delete(temp_notebook.id, note.id)
|
|
|
|
|
|
@requires_auth
|
|
class TestMindMaps:
|
|
"""Test mind map operations."""
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.readonly
|
|
async def test_list_mind_maps(self, client, read_only_notebook_id):
|
|
"""List mind maps in test notebook - read-only."""
|
|
mind_maps = await client.notes.list_mind_maps(read_only_notebook_id)
|
|
assert isinstance(mind_maps, list)
|
|
|
|
|
|
@pytest.mark.e2e
|
|
@requires_auth
|
|
class TestSaveAnswerAsNote:
|
|
"""Test saving chat answers as citation-rich notes (issue #660).
|
|
|
|
These tests exercise the full ask → save_answer_as_note path against
|
|
the live NotebookLM service. The note's creation success is
|
|
automated; the per-citation hover-anchor rendering is a MANUAL
|
|
verification step — open the resulting note in the web UI, hover a
|
|
``[N]`` marker, and confirm a passage popup appears. The test
|
|
prints instructions to the log to make this step actionable.
|
|
"""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_save_answer_as_note_preserves_citations(self, client, temp_notebook):
|
|
"""Ask a citation-bearing question, save as note, verify creation.
|
|
|
|
Uses ``temp_notebook`` so the test cleans up after itself even
|
|
when a downstream assertion fails. The actual hover-anchor
|
|
check is manual — see class docstring.
|
|
"""
|
|
result = await client.chat.ask(
|
|
temp_notebook.id,
|
|
"Summarize the key points from the sources in one sentence.",
|
|
)
|
|
if not result.references:
|
|
pytest.skip(
|
|
"Live answer had no citations; save_answer_as_note requires "
|
|
"non-empty references. Temp notebook may need sources."
|
|
)
|
|
|
|
note = await client.chat.save_answer_as_note(
|
|
temp_notebook.id,
|
|
result,
|
|
title=f"E2E #660 hover-anchor test {result.conversation_id[:8]}",
|
|
)
|
|
try:
|
|
assert note.id != ""
|
|
# The server may apply smart-title generation for [2]-mode
|
|
# notes — assert non-empty but don't pin the value.
|
|
assert note.title
|
|
|
|
listed = await client.notes.list(temp_notebook.id)
|
|
assert note.id in {n.id for n in listed}
|
|
|
|
print(
|
|
f"\n[Manual hover-anchor check] Open "
|
|
f"https://notebooklm.google.com/notebook/{temp_notebook.id}, "
|
|
f"find note '{note.title}' (id={note.id[:8]}...), hover any "
|
|
f"[N] marker, confirm the popup shows the cited passage."
|
|
)
|
|
finally:
|
|
await client.notes.delete(temp_notebook.id, note.id)
|