Files
wehub-resource-sync 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
chore: import upstream snapshot with attribution
2026-07-13 13:30:13 +08:00

598 lines
22 KiB
Python

"""Tests for share CLI commands."""
from datetime import datetime
from unittest.mock import AsyncMock, patch
import notebooklm.auth as auth_module
from notebooklm.notebooklm_cli import cli
from notebooklm.types import (
Notebook,
ShareAccess,
SharedUser,
SharePermission,
ShareStatus,
ShareViewLevel,
)
from .conftest import create_mock_client, inject_client
def create_mock_share_status(
notebook_id: str = "nb_123",
is_public: bool = False,
shared_users: list | None = None,
) -> ShareStatus:
"""Create a mock ShareStatus for testing."""
return ShareStatus(
notebook_id=notebook_id,
is_public=is_public,
access=ShareAccess.ANYONE_WITH_LINK if is_public else ShareAccess.RESTRICTED,
view_level=ShareViewLevel.FULL_NOTEBOOK,
shared_users=shared_users or [],
share_url=f"https://notebooklm.google.com/notebook/{notebook_id}" if is_public else None,
)
# =============================================================================
# SHARE STATUS TESTS
# =============================================================================
class TestShareStatus:
def test_share_status_private(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
mock_client.sharing.get_status = AsyncMock(
return_value=create_mock_share_status(is_public=False)
)
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli, ["share", "status", "-n", "nb_123"], obj=inject_client(mock_client)
)
assert result.exit_code == 0
assert "Private" in result.output
mock_client.sharing.get_status.assert_called_once_with("nb_123")
def test_share_status_public(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
mock_client.sharing.get_status = AsyncMock(
return_value=create_mock_share_status(is_public=True)
)
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli, ["share", "status", "-n", "nb_123"], obj=inject_client(mock_client)
)
assert result.exit_code == 0
assert "Public" in result.output
assert "Share URL" in result.output
# =============================================================================
# SHARE PUBLIC TESTS
# =============================================================================
class TestSharePublic:
def test_share_public_enable(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
mock_client.sharing.set_public = AsyncMock(
return_value=create_mock_share_status(is_public=True)
)
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli, ["share", "public", "-n", "nb_123", "--enable"], obj=inject_client(mock_client)
)
assert result.exit_code == 0
assert "Public sharing enabled" in result.output
assert "Share URL" in result.output
mock_client.sharing.set_public.assert_called_once_with("nb_123", True)
def test_share_public_disable(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
mock_client.sharing.set_public = AsyncMock(
return_value=create_mock_share_status(is_public=False)
)
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli,
["share", "public", "-n", "nb_123", "--disable"],
obj=inject_client(mock_client),
)
assert result.exit_code == 0
assert "Public sharing disabled" in result.output
mock_client.sharing.set_public.assert_called_once_with("nb_123", False)
# =============================================================================
# SHARE ADD USER TESTS
# =============================================================================
class TestShareAdd:
def test_share_add_user_viewer(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
mock_client.sharing.add_user = AsyncMock(return_value=create_mock_share_status())
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli,
["share", "add", "user@example.com", "-n", "nb_123"],
obj=inject_client(mock_client),
)
assert result.exit_code == 0
assert "Shared with user@example.com" in result.output
assert "Viewer" in result.output
mock_client.sharing.add_user.assert_called_once_with(
"nb_123",
"user@example.com",
permission=SharePermission.VIEWER,
notify=True,
welcome_message="",
)
def test_share_add_user_editor(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
mock_client.sharing.add_user = AsyncMock(return_value=create_mock_share_status())
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli,
["share", "add", "user@example.com", "-n", "nb_123", "-p", "editor"],
obj=inject_client(mock_client),
)
assert result.exit_code == 0
assert "Editor" in result.output
mock_client.sharing.add_user.assert_called_once_with(
"nb_123",
"user@example.com",
permission=SharePermission.EDITOR,
notify=True,
welcome_message="",
)
# =============================================================================
# SHARE REMOVE USER TESTS
# =============================================================================
class TestShareRemove:
def test_share_remove_user(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
mock_client.sharing.remove_user = AsyncMock(return_value=create_mock_share_status())
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli,
["share", "remove", "user@example.com", "-n", "nb_123", "-y"],
obj=inject_client(mock_client),
)
assert result.exit_code == 0
assert "Removed access for user@example.com" in result.output
mock_client.sharing.remove_user.assert_called_once_with("nb_123", "user@example.com")
def test_share_remove_cancelled(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
mock_client.sharing.remove_user = AsyncMock(return_value=create_mock_share_status())
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli,
["share", "remove", "user@example.com", "-n", "nb_123"],
input="n\n",
obj=inject_client(mock_client),
)
assert result.exit_code == 0
assert "Remove access for user@example.com?" in result.output
mock_client.sharing.remove_user.assert_not_called()
def test_share_remove_json(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
mock_client.sharing.remove_user = AsyncMock(return_value=create_mock_share_status())
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli,
["share", "remove", "user@example.com", "-n", "nb_123", "--json"],
obj=inject_client(mock_client),
)
assert result.exit_code == 0
assert '"removed_user": "user@example.com"' in result.output
def test_share_remove_json_without_yes_does_not_prompt(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
mock_client.sharing.remove_user = AsyncMock(return_value=create_mock_share_status())
with (
patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch,
patch("click.confirm") as mock_confirm,
):
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli,
["share", "remove", "user@example.com", "-n", "nb_123", "--json"],
obj=inject_client(mock_client),
)
assert result.exit_code == 0
assert '"removed_user": "user@example.com"' in result.output
mock_confirm.assert_not_called()
mock_client.sharing.remove_user.assert_called_once_with("nb_123", "user@example.com")
# =============================================================================
# SHARE VIEW-LEVEL TESTS
# =============================================================================
class TestShareViewLevel:
def test_share_view_level_full(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
# set_view_level now returns ShareStatus with the view_level that was set
mock_status = ShareStatus(
notebook_id="nb_123",
is_public=False,
access=ShareAccess.RESTRICTED,
view_level=ShareViewLevel.FULL_NOTEBOOK,
shared_users=[],
share_url=None,
)
mock_client.sharing.set_view_level = AsyncMock(return_value=mock_status)
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli, ["share", "view-level", "full", "-n", "nb_123"], obj=inject_client(mock_client)
)
assert result.exit_code == 0
assert "Full Notebook" in result.output
mock_client.sharing.set_view_level.assert_called_once_with(
"nb_123", ShareViewLevel.FULL_NOTEBOOK
)
def test_share_view_level_chat(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
# set_view_level now returns ShareStatus with the view_level that was set
mock_status = ShareStatus(
notebook_id="nb_123",
is_public=False,
access=ShareAccess.RESTRICTED,
view_level=ShareViewLevel.CHAT_ONLY,
shared_users=[],
share_url=None,
)
mock_client.sharing.set_view_level = AsyncMock(return_value=mock_status)
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli, ["share", "view-level", "chat", "-n", "nb_123"], obj=inject_client(mock_client)
)
assert result.exit_code == 0
assert "Chat Only" in result.output
mock_client.sharing.set_view_level.assert_called_once_with(
"nb_123", ShareViewLevel.CHAT_ONLY
)
def test_share_view_level_json(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
# set_view_level now returns ShareStatus with the view_level that was set
mock_status = ShareStatus(
notebook_id="nb_123",
is_public=False,
access=ShareAccess.RESTRICTED,
view_level=ShareViewLevel.FULL_NOTEBOOK,
shared_users=[],
share_url=None,
)
mock_client.sharing.set_view_level = AsyncMock(return_value=mock_status)
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli,
["share", "view-level", "full", "-n", "nb_123", "--json"],
obj=inject_client(mock_client),
)
assert result.exit_code == 0
assert '"view_level": "full_notebook"' in result.output
# =============================================================================
# SHARE UPDATE TESTS
# =============================================================================
class TestShareUpdate:
def test_share_update_to_editor(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
mock_client.sharing.update_user = AsyncMock(return_value=create_mock_share_status())
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli,
["share", "update", "user@example.com", "-n", "nb_123", "-p", "editor"],
obj=inject_client(mock_client),
)
assert result.exit_code == 0
assert "Updated user@example.com" in result.output
assert "Editor" in result.output
mock_client.sharing.update_user.assert_called_once_with(
"nb_123", "user@example.com", SharePermission.EDITOR
)
def test_share_update_json(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
mock_client.sharing.update_user = AsyncMock(return_value=create_mock_share_status())
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli,
[
"share",
"update",
"user@example.com",
"-n",
"nb_123",
"-p",
"viewer",
"--json",
],
obj=inject_client(mock_client),
)
assert result.exit_code == 0
assert '"updated_user": "user@example.com"' in result.output
assert '"permission": "viewer"' in result.output
# =============================================================================
# JSON OUTPUT TESTS
# =============================================================================
class TestShareJsonOutput:
def test_share_status_json(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
mock_client.sharing.get_status = AsyncMock(
return_value=create_mock_share_status(is_public=True)
)
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli, ["share", "status", "-n", "nb_123", "--json"], obj=inject_client(mock_client)
)
assert result.exit_code == 0
assert '"notebook_id": "nb_123"' in result.output
assert '"is_public": true' in result.output
def test_share_public_json(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
mock_client.sharing.set_public = AsyncMock(
return_value=create_mock_share_status(is_public=True)
)
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli, ["share", "public", "-n", "nb_123", "--json"], obj=inject_client(mock_client)
)
assert result.exit_code == 0
assert '"is_public": true' in result.output
assert '"share_url"' in result.output
def test_share_add_json(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
mock_client.sharing.add_user = AsyncMock(return_value=create_mock_share_status())
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli,
["share", "add", "user@example.com", "-n", "nb_123", "--json"],
obj=inject_client(mock_client),
)
assert result.exit_code == 0
assert '"added_user": "user@example.com"' in result.output
assert '"permission": "viewer"' in result.output
# =============================================================================
# SHARED USERS DISPLAY TESTS
# =============================================================================
class TestShareStatusWithUsers:
def test_share_status_with_shared_users(self, runner, mock_auth):
mock_client = create_mock_client()
mock_client.notebooks.list = AsyncMock(
return_value=[
Notebook(id="nb_123", title="Test", created_at=datetime(2024, 1, 1)),
]
)
shared_users = [
SharedUser(
email="editor@example.com",
permission=SharePermission.EDITOR,
display_name="Editor User",
),
SharedUser(
email="viewer@example.com",
permission=SharePermission.VIEWER,
display_name=None,
),
]
mock_client.sharing.get_status = AsyncMock(
return_value=create_mock_share_status(is_public=False, shared_users=shared_users)
)
with patch.object(
auth_module, "fetch_tokens_with_domains", new_callable=AsyncMock
) as mock_fetch:
mock_fetch.return_value = ("csrf", "session")
result = runner.invoke(
cli, ["share", "status", "-n", "nb_123"], obj=inject_client(mock_client)
)
assert result.exit_code == 0
assert "Shared Users" in result.output
assert "editor@example.com" in result.output
assert "viewer@example.com" in result.output