From dff41e068aeafd41640a956146d2766f08437f22 Mon Sep 17 00:00:00 2001 From: Felipe Fidelix Date: Fri, 28 Aug 2026 09:36:14 -0300 Subject: [PATCH] fix(documents): pass description_html when constructing CreatePage plane-sdk requires description_html on CreatePage with no default, so CreatePage(name=title) raised ValidationError before any HTTP call. Co-authored-by: Cursor --- CHANGELOG.md | 3 ++ src/planecli/commands/documents.py | 5 +- tests/test_commands/test_documents.py | 70 +++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 3 deletions(-) create mode 100644 tests/test_commands/test_documents.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 66ffbab..5e3652f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/). ### Added - `planecli intake` command group: `ls`, `create`, `accept`, `decline`, `delete`, `enabled` for project intake queues. Mutations take the work item UUID shown in the `Issue ID` column of `intake ls`. `accept`/`decline` require the project Admin role (the API silently ignores the change for lower roles, so the CLI verifies it and fails loudly). `delete` also permanently deletes the underlying work item for any status other than `accepted` +### Fixed +- `document create` no longer crashes before the API call: `CreatePage` now receives the required `description_html` at construction (`

` when `--content` is omitted) + ## [0.5.1] - 2026-07-03 ### Added diff --git a/src/planecli/commands/documents.py b/src/planecli/commands/documents.py index 1ceaa53..4ef19c2 100644 --- a/src/planecli/commands/documents.py +++ b/src/planecli/commands/documents.py @@ -165,9 +165,8 @@ async def create( client = get_client() workspace = get_workspace() - page_data = CreatePage(name=title) - if content: - page_data.description_html = f"

{content}

" + description_html = f"

{content}

" if content else "

" + page_data = CreatePage(name=title, description_html=description_html) if project: proj = await resolve_project_async(project, client, workspace) diff --git a/tests/test_commands/test_documents.py b/tests/test_commands/test_documents.py new file mode 100644 index 0000000..118627c --- /dev/null +++ b/tests/test_commands/test_documents.py @@ -0,0 +1,70 @@ +"""Tests for document / page commands.""" + +from __future__ import annotations + +from unittest.mock import AsyncMock, MagicMock, patch + +from plane.models.pages import CreatePage + +from planecli.commands.documents import DOC_FIELDS, create + + +def _page_model(**overrides) -> MagicMock: + """A fake Page: only `.model_dump()` is used by the create command.""" + payload = { + "id": "page-1", + "name": "Test doc", + "description_html": "

", + "created_at": "2026-08-28T10:00:00Z", + "updated_at": "2026-08-28T10:00:00Z", + } + payload.update(overrides) + m = MagicMock() + m.model_dump.return_value = payload + return m + + +@patch("planecli.commands.documents.output_single") +@patch("planecli.commands.documents.run_sdk", new_callable=AsyncMock) +@patch("planecli.commands.documents.resolve_project_async", new_callable=AsyncMock) +@patch("planecli.commands.documents.get_workspace", return_value="ws") +@patch("planecli.commands.documents.get_client") +async def test_create_passes_description_html_when_content_provided( + mock_client, mock_ws, mock_resolve, mock_run_sdk, mock_output_single +): + """CreatePage is constructed with wrapped --content, not assigned after the fact.""" + mock_resolve.return_value = {"id": "p1", "identifier": "ABC"} + mock_run_sdk.return_value = _page_model(name="Test doc", description_html="

hello

") + + await create(title="Test doc", content="hello", project="ABC") + + args = mock_run_sdk.call_args[0] + assert args[0] is mock_client.return_value.pages.create_project_page + assert args[1:3] == ("ws", "p1") + payload = args[3] + assert isinstance(payload, CreatePage) + assert payload.name == "Test doc" + assert payload.description_html == "

hello

" + data, fields = mock_output_single.call_args[0] + assert fields is DOC_FIELDS + assert data["name"] == "Test doc" + + +@patch("planecli.commands.documents.output_single") +@patch("planecli.commands.documents.run_sdk", new_callable=AsyncMock) +@patch("planecli.commands.documents.resolve_project_async", new_callable=AsyncMock) +@patch("planecli.commands.documents.get_workspace", return_value="ws") +@patch("planecli.commands.documents.get_client") +async def test_create_passes_empty_html_body_when_content_omitted( + mock_client, mock_ws, mock_resolve, mock_run_sdk, mock_output_single +): + """Omitting --content still supplies a valid empty HTML body at construction.""" + mock_resolve.return_value = {"id": "p1"} + mock_run_sdk.return_value = _page_model() + + await create(title="Test doc", project="ABC") + + payload = mock_run_sdk.call_args[0][3] + assert isinstance(payload, CreatePage) + assert payload.name == "Test doc" + assert payload.description_html == "

"