Files
zhaojiaxing-coding 0f35676a28 feat(drive): extend permission shortcuts for Miaoda (#2070)
* feat(drive): support Miaoda apps in permission shortcuts

Extend Drive permission shortcuts to accept Miaoda page URLs and the apps resource type while keeping each endpoint's accepted resource contract explicit.

Key features:

- Infer apps from /page/ URLs and accept explicit --type=apps in +apply-permission, +member-add, +member-list, and +permission-get-setting

- Decouple secure-label target parsing so expanding apply-permission does not widen secure-label support

- Align skill guidance and unit/dry-run coverage with the new resource type

* test(drive): cover apps permission target validation

Add focused coverage for Miaoda apps target handling across apply-permission and secure-label boundaries.

Exercise malformed page URLs, explicit apps bare tokens, typed validation errors, and command-level rejection so future resource-type changes cannot silently widen unsupported secure-label behavior.

* fix(drive): parse permission markers from URL paths

Keep drive +apply-permission resource inference aligned with URL component boundaries. Parse and validate URL inputs before extracting tokens so query strings and fragments cannot redirect permission requests to a different resource.

Key fixes:

- Match document and apps markers only against the parsed URL path

- Reject malformed URLs with a typed --token validation error

- Cover /page/ markers found only in query strings or fragments

* docs(skills): redact Miaoda page token example

Replace the concrete Miaoda page token with a representative pagcn placeholder. This keeps the token shape recognizable while avoiding exposure of a real resource identifier in the skill documentation.

* fix(drive): harden permission target resolution

Make Drive shortcut targets unambiguous before they reach read or write API paths. URL inputs now bind to a recognized root path and a single validated token segment, preventing encoded separators, dot segments, and type conflicts from silently changing the addressed resource.

Key fixes:

- Reject non-root URLs, dot/traversal tokens, and URL/type conflicts for secure-label and permission-apply writes

- Keep permission-setting URL parsing and pretty output reversible for every supported command-local resource kind

- Add unit and dry-run E2E regressions plus aligned permission-apply guidance
2026-07-31 12:16:22 +08:00

20 KiB

Drive CLI E2E Coverage

Metrics

  • Denominator: 40 leaf commands
  • Covered: 22
  • Coverage: 55.0%

Summary

  • TestDrive_FilesCreateFolderWorkflow: proves drive files create_folder in create_folder as bot; helper asserts the returned folder token and registers best-effort cleanup via drive files delete.
  • TestDrive_StatusWorkflow: proves drive +status against a real Drive folder. Seeds the remote side via drive +upload (unchanged.txt, modified.txt, remote-only.txt), seeds local files with the matching/diverging contents, and asserts every output bucket (unchanged, modified, new_local, new_remote) holds exactly the expected rel_path and file_token. Cleans up uploaded files and the parent folder via best-effort cleanup hooks.
  • TestDrive_UploadWorkflow: proves drive +upload against the real backend in both create and overwrite modes. First uploads a fresh file into a temporary Drive folder, then re-uploads new bytes with --file-token against the returned token, asserts the overwrite keeps the token stable, downloads the file with explicit --output to confirm the remote content changed, and downloads again without --output to prove default filename resolution saves the remote name with matching bytes.
  • TestDriveDownloadDryRun_DefaultNamePlansMetadataBeforeDownload / TestDriveDownloadDryRun_ExplicitOutputSkipsMetadata: dry-run coverage for drive +download; asserts omitted --output plans metadata before download for default naming, while explicit --output preserves the direct download-only path.
  • TestDrive_DuplicateRemoteWorkflow: proves the duplicate-remote workflows against the real backend. One subtest uploads two same-name files into the same Drive folder and asserts drive +status and default drive +pull both fail with a typed validation error for the duplicate rel_path, while drive +pull --on-duplicate-remote=rename succeeds, downloads both files, and writes a hashed renamed sibling locally. The other subtest uploads duplicate remote files, runs drive +push --on-duplicate-remote=newest --if-exists=overwrite --delete-remote --yes, and then re-runs drive +status to prove the mirror converged to a single unchanged dup.txt.
  • TestDrive_ApplyPermissionDryRun / TestDrive_ApplyPermissionDryRunRejectsFullAccess / TestDrive_ApplyPermissionDryRunRejectsUnsafeTargets: dry-run coverage for drive +apply-permission; asserts URL→type inference for docx/sheet/slides, bare-token + explicit --type path, request method/URL/type-query/perm/remark body shape, optional remark omission when unset, and client-side rejection of invalid permissions, unsafe tokens, non-root URL markers, encoded path separators, and conflicting URL/--type inputs. Runs without hitting the live API.
  • TestDriveAddCommentDryRun_File / TestDriveAddCommentDryRun_Base: dry-run coverage for drive +add-comment on supported Drive file and Base targets; pins the metas.batch_query -> files/:token/new_comments file chain, Base file_type=bitable, and Base anchor fields.
  • TestDriveListCommentsDryRun_DocxDefaults / TestDriveListCommentsDryRun_AppsPageURL / TestDriveListCommentsDryRun_WikiToken: dry-run coverage for drive +list-comments; asserts URL parsing to files/:token/comments, apps /page/<token> URL parsing with file_type=apps, default is_solved=false, default omitted is_whole and user_id_type, and Wiki token orchestration (get_node -> comments.list) without live API calls.
  • TestDrive_CommentOpsDryRun: dry-run coverage for drive +batch-query-comments, drive +resolve-comment, drive +restore-comment, drive +add-reply, drive +list-replies, drive +update-reply, drive +delete-reply, and drive +react-reply; asserts URL→type inference (incl. Miaoda apps /page/<token> → file_type=apps and Base /base/ → file_type=bitable), file_type/page_size/need_reaction/need_relation (docx-gated) query wiring, comment_ids / is_solved / reply content.elements[] (text_run) / reaction action+reaction_type+reply_id body shapes, resolved :comment_id/:reply_id path segments, the Wiki get_node -> batch_query / get_node -> replies list / get_node -> v2 reaction orchestration plans, and the batch_query wiki dry-run surfacing need_relation as the <sent only when obj_type is docx> placeholder, without live API calls. All eight verified manually against live documents (list → batch-query → add-reply → list-replies → update-reply → react add/delete → resolve/restore → delete-reply round trip; root-reply update rewriting the comment body, the 1069303 forbidden cross-identity update rejection, the server persisting arbitrary reaction_type strings, and count=0 reaction tombstones were probed live as well).
  • TestDriveAddCommentMarkdownFileWorkflow: opt-in live workflow skeleton for comment write/read, gated by LARK_DRIVE_MD_COMMENT_E2E=1; creates a Markdown file, adds a file comment, lists it back through drive +list-comments, and cleans up.
  • TestDriveCommentOpsWorkflow: opt-in self-contained live workflow for the comment operation shortcuts, gated by LARK_DRIVE_MD_COMMENT_E2E=1; creates a Markdown file + file comment fixture, then +batch-query-comments finds it by ID, +add-reply attaches a reply, +list-replies surfaces it, +update-reply rewrites its content (confirmed by polling +list-replies until the new text lands), +react-reply attaches then removes a THUMBSUP reaction (confirmed by polling +list-replies --need-reaction, judging presence by count>0 because deleted reactions linger as count=0 entries), +resolve-comment marks it solved and +restore-comment reopens it (with polling reads between state flips to absorb rate limiting), +delete-reply --yes removes the created reply, and cleanup deletes the file.
  • TestDrive_SecureLabelDryRun: dry-run coverage for drive +secure-label-list and drive +secure-label-update; asserts label-list query params and update URL→type inference, request method/URL/type query, and label-id body shape. Runs without hitting live APIs because update can trigger document-level security approval flows.
  • TestDriveExportDryRun_FileNameMetadata / TestDriveExportDryRun_WikiURLPlansResolveBeforeExportTask / TestDriveExportDryRun_WikiTokenTypePlansResolveBeforeExportTask / TestDriveExportDryRun_MarkdownFetchAPI / TestDriveExportDryRun_BitableBaseOnlySchema: dry-run coverage for drive +export; asserts export task request shape, Wiki URL and --doc-type wiki token get_node -> export_tasks planning, markdown fetch request shape without docs fetch extra_param, local --file-name / --output-dir metadata, and bitable .base only_schema request body without calling live APIs.
  • TestDriveDeleteDryRunAsyncParams / TestDrive_DeleteAsyncWorkflow: dry-run coverage for drive +delete pins DELETE /drive/v1/files/:file_token params with type plus async=true and the follow-up task_check plan; live workflow creates and deletes a docx, an empty folder, and a non-empty folder, converging every delete outcome to the resource-gone terminal state: async deletes (non-empty task_id) are verified via drive +task_result --scenario task_check, sync deletes (empty task_id) assert deleted=true, and the one verified backend transient (server_error: "drive task failed") passes once the target is confirmed gone (retried up to 3 times otherwise); any other delete failure stays fatal.
  • TestDrive_PullDryRun / TestDrive_PullDryRunAcceptsDuplicateRemoteStrategies: dry-run coverage for drive +pull; asserts the list-files request shape, Validate-stage safety guards, and acceptance of --on-duplicate-remote=rename|newest|oldest by the real CLI binary.
  • TestDrive_PushDryRun / TestDrive_PushDryRunAcceptsDuplicateRemoteStrategies: dry-run coverage for drive +push; asserts the list-files request shape, Validate-stage safety guards, conditional delete preflight, and acceptance of --on-duplicate-remote=newest|oldest by the real CLI binary.
  • Cleanup note: drive files delete is only exercised in cleanup and is intentionally left uncovered.
  • Blocked area: live export, permission, subscription, reply, and file comment API flows still need deterministic remote fixtures and filesystem setup.
  • Dry-run note: drive_upload_dryrun_test.go::TestDriveUploadDryRun_WikiTarget and TestDriveUploadDryRun_WithFileToken cover the wiki-target and overwrite request shapes for drive +upload; live upload/status/duplicate workflows also use real +upload against the backend.

Command Table

Status Cmd Type Testcase Key parameter shapes Notes / uncovered reason
✓ drive +add-comment shortcut drive_add_comment_dryrun_test.go::TestDriveAddCommentDryRun_File; drive_add_comment_dryrun_test.go::TestDriveAddCommentDryRun_Base --doc file URL vs bare token + --type file; supported-extension metadata gate; placeholder anchor.block_id; Base URL with --block-id <table-id>!<record-id>!<view-id> dry-run coverage in place; opt-in live file workflow exists behind LARK_DRIVE_MD_COMMENT_E2E=1
✓ drive +list-comments shortcut drive_list_comments_dryrun_test.go::TestDriveListCommentsDryRun_DocxDefaults; drive_list_comments_dryrun_test.go::TestDriveListCommentsDryRun_AppsPageURL; drive_list_comments_dryrun_test.go::TestDriveListCommentsDryRun_WikiToken; drive_add_comment_workflow_test.go::TestDriveAddCommentMarkdownFileWorkflow --url; apps /page/<token> URL; --token + --type wiki; --solved-status=false|all; --comment-scope=all|partial; --need-relation; --page-size dry-run locks URL/token parsing, apps file_type=apps, default unresolved filter, omitted all-scope filter, omitted user_id_type, and Wiki unwrap request shape; opt-in live workflow verifies a created file comment can be listed back
✓ drive +batch-query-comments shortcut drive_comment_ops_dryrun_test.go::TestDrive_CommentOpsDryRun; drive_comment_ops_workflow_test.go::TestDriveCommentOpsWorkflow --url docx; base /base/ URL (file_type=bitable); apps /page/<token> URL (file_type=apps); --token + --type wiki; --comment-ids CSV; --need-reaction body flag; --need-relation body flag (docx only, omitted otherwise) dry-run pins request shape and wiki resolve plan; opt-in live workflow covers the full comment-ops round trip; need_relation is metadata-absent but live-verified (returns relation with block position)
✓ drive +resolve-comment shortcut drive_comment_ops_dryrun_test.go::TestDrive_CommentOpsDryRun; drive_comment_ops_workflow_test.go::TestDriveCommentOpsWorkflow --url sheet; --comment-id path segment; fixed is_solved=true body dry-run pins request shape; opt-in live workflow marks the fixture comment solved against the real backend
✓ drive +restore-comment shortcut drive_comment_ops_dryrun_test.go::TestDrive_CommentOpsDryRun; drive_comment_ops_workflow_test.go::TestDriveCommentOpsWorkflow --url docx; --comment-id path segment; fixed is_solved=false body (same patch endpoint as +resolve-comment) dry-run pins request shape; opt-in live workflow reopens the solved fixture comment against the real backend
✓ drive +add-reply shortcut drive_comment_ops_dryrun_test.go::TestDrive_CommentOpsDryRun; drive_comment_ops_workflow_test.go::TestDriveCommentOpsWorkflow --url docx; --comment-id path segment (.../comments/:comment_id/replies); --content simplified elements → content.elements[] text_run dry-run pins reply body mapping; opt-in live workflow attaches and verifies a real reply (body-comment_id create variant does NOT reply — creates a standalone comment)
✓ drive +list-replies shortcut drive_comment_ops_dryrun_test.go::TestDrive_CommentOpsDryRun; drive_comment_ops_workflow_test.go::TestDriveCommentOpsWorkflow --url docx; --token + --type wiki resolve plan; --comment-id path segment (.../comments/:comment_id/replies GET); --page-size/--page-token/--need-reaction query dry-run pins request shape and wiki resolve plan; opt-in live workflow lists the created reply back (the first-page items[0] is the root reply carrying the comment body)
✓ drive +update-reply shortcut drive_comment_ops_dryrun_test.go::TestDrive_CommentOpsDryRun; drive_comment_ops_workflow_test.go::TestDriveCommentOpsWorkflow base /base/ URL (file_type=bitable); --comment-id + --reply-id path segments (PUT); --content simplified elements → content.elements[] text_run dry-run pins request/body shape; opt-in live workflow rewrites the created reply and polls +list-replies until the new text is visible; only the creator identity may update (live-probed 1069303 forbidden)
✓ drive +delete-reply shortcut drive_comment_ops_dryrun_test.go::TestDrive_CommentOpsDryRun; drive_comment_ops_workflow_test.go::TestDriveCommentOpsWorkflow --url docx; --comment-id + --reply-id path segments; --dry-run bypasses the --yes gate dry-run pins request shape; opt-in live workflow deletes the created reply and verifies the count returns to baseline
✓ drive +react-reply shortcut drive_comment_ops_dryrun_test.go::TestDrive_CommentOpsDryRun; drive_comment_ops_workflow_test.go::TestDriveCommentOpsWorkflow --url docx; --token + --type wiki resolve plan; v2 .../comments/reaction POST; --reply-id/--emoji/--action add|delete → body reply_id/reaction_type/action dry-run pins request/body shape and wiki resolve plan; opt-in live workflow adds then removes a reaction, polling +list-replies --need-reaction with count>0 presence checks; local --emoji enum validation guards the unvalidated server field
✓ drive +apply-permission shortcut drive_apply_permission_dryrun_test.go::TestDrive_ApplyPermissionDryRun --token URL vs bare; --type (enum) with URL inference; --perm view|edit; --remark optional dry-run only; no live-apply E2E because a real request pushes a card to the owner
✓ drive +delete shortcut drive_delete_dryrun_test.go::TestDriveDeleteDryRunAsyncParams + drive_delete_workflow_test.go::TestDrive_DeleteAsyncWorkflow --file-token; --type; fixed query async=true; task_check follow-up dry-run locks async request shape; live workflow covers docx, empty folder, and non-empty folder deletion with async/sync/transient-failure convergence
✓ drive +download shortcut drive_download_dryrun_test.go::TestDriveDownloadDryRun_DefaultNamePlansMetadataBeforeDownload; drive_download_dryrun_test.go::TestDriveDownloadDryRun_ExplicitOutputSkipsMetadata; drive_upload_workflow_test.go::TestDrive_UploadWorkflow omitted --output plans metas.batch_query before file download; explicit --output skips metadata; live workflow downloads an uploaded file both with explicit output and with default remote-name output dry-run plus live fixture coverage
✓ drive +export shortcut drive_export_dryrun_test.go::TestDriveExportDryRun_FileNameMetadata + TestDriveExportDryRun_WikiURLPlansResolveBeforeExportTask + TestDriveExportDryRun_WikiTokenTypePlansResolveBeforeExportTask + TestDriveExportDryRun_MarkdownFetchAPI + TestDriveExportDryRun_BitableBaseOnlySchema --url; --token; --doc-type; --file-extension; --file-name; --output-dir; --only-schema; Wiki URL / --doc-type wiki resolve step; markdown fetch omits docs fetch extra_param dry-run only; no live export workflow yet
✕ drive +export-download shortcut none no export-download workflow yet
✕ drive +import shortcut none no import workflow yet
✕ drive +move shortcut none no move workflow yet
✓ drive +pull shortcut drive_pull_dryrun_test.go::TestDrive_PullDryRun + drive_duplicate_sync_workflow_test.go::TestDrive_DuplicateRemoteWorkflow --local-dir; --folder-token; --on-duplicate-remote=rename|newest|oldest; --delete-local --yes guard dry-run locks flag/validate shape; live workflow proves duplicate fail-fast and rename recovery
✓ drive +push shortcut drive_push_dryrun_test.go::TestDrive_PushDryRun + drive_duplicate_sync_workflow_test.go::TestDrive_DuplicateRemoteWorkflow --local-dir; --folder-token; --if-exists; --on-duplicate-remote=newest|oldest; --delete-remote --yes dry-run locks flag/validate shape; live workflow proves overwrite + duplicate cleanup converges status
✓ drive +secure-label-list shortcut drive_secure_label_dryrun_test.go::TestDrive_SecureLabelDryRun --page-size; --page-token; --lang dry-run only; live label availability depends on tenant security-label configuration
✓ drive +secure-label-update shortcut drive_secure_label_dryrun_test.go::TestDrive_SecureLabelDryRun --token URL inference; --type; --label-id body dry-run only; live update can require document-level approval or mutate a fixture document's security level
✓ drive +status shortcut drive_status_workflow_test.go::TestDrive_StatusWorkflow + drive_status_dryrun_test.go::TestDrive_StatusDryRun + drive_duplicate_sync_workflow_test.go::TestDrive_DuplicateRemoteWorkflow --local-dir; --folder-token; bucketed new_local / new_remote / modified / unchanged outputs dry-run pins request shape; live workflows cover both normal hashing buckets and duplicate-remote failure
✓ drive +sync shortcut drive_sync_dryrun_test.go::TestDrive_SyncDryRun + drive_sync_workflow_test.go::TestDrive_SyncWorkflow + drive_sync_workflow_test.go::TestDrive_SyncEmptyDirWorkflow --local-dir; --folder-token; --on-conflict=remote-wins|local-wins|keep-both|ask; --on-duplicate-remote=fail|newest|oldest; --quick dry-run validates request shape, flag acceptance, and path safety guards; live workflow proves new_remote→pull, new_local→push, remote-wins/local-wins/keep-both conflict resolution, empty directory creation, and post-sync convergence
✓ drive +task_result shortcut drive_delete_workflow_test.go::TestDrive_DeleteAsyncWorkflow --scenario task_check; --task-id live delete workflow verifies task polling command can read returned delete tasks
✓ drive +upload shortcut drive_upload_dryrun_test.go::TestDriveUploadDryRun_WikiTarget + drive_upload_dryrun_test.go::TestDriveUploadDryRun_WithFileToken + drive_upload_workflow_test.go::TestDrive_UploadWorkflow + drive_status_workflow_test.go::TestDrive_StatusWorkflow + drive_duplicate_sync_workflow_test.go::TestDrive_DuplicateRemoteWorkflow --wiki-token; --file-token; parent_type=wiki; parent_node; named uploads into Drive folders; in-place overwrite uploads dry-run covers wiki-target and overwrite request shapes; live workflows assert returned file tokens, token-stable overwrite behavior, and that uploaded fixtures are consumable by downstream commands
✕ drive file.comment.replys create api none no reply workflow yet
✕ drive file.comment.replys delete api none no reply workflow yet
✕ drive file.comment.replys list api none no reply workflow yet
✕ drive file.comment.replys update api none no reply workflow yet
✕ drive file.comments create_v2 api none no file comment workflow yet
✕ drive file.comments list api none no file comment workflow yet
✕ drive file.comments patch api none no file comment workflow yet
✕ drive file.statistics get api none no statistics workflow yet
✕ drive file.view_records list api none no view-record workflow yet
✕ drive files copy api none no file copy workflow yet
✓ drive files create_folder api drive_files_workflow_test.go::TestDrive_FilesCreateFolderWorkflow/create_folder as bot name; empty folder_token in --data
✕ drive files list api none no list workflow yet
✕ drive metas batch_query api none no metadata workflow yet
✕ drive permission.members auth api none permission workflows not covered
✕ drive permission.members create api none permission workflows not covered
✕ drive permission.members transfer_owner api none permission workflows not covered
✕ drive user remove_subscription api none subscription workflows not covered
✕ drive user subscription api none subscription workflows not covered
✕ drive user subscription_status api none subscription workflows not covered