mirror of
https://github.com/larksuite/cli.git
synced 2026-09-14 18:42:53 +08:00
0f35676a28
* 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
20 KiB
20 KiB
Drive CLI E2E Coverage
Metrics
- Denominator: 40 leaf commands
- Covered: 22
- Coverage: 55.0%
Summary
- TestDrive_FilesCreateFolderWorkflow: proves
drive files create_folderincreate_folder as bot; helper asserts the returned folder token and registers best-effort cleanup viadrive files delete. - TestDrive_StatusWorkflow: proves
drive +statusagainst a real Drive folder. Seeds the remote side viadrive +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 expectedrel_pathandfile_token. Cleans up uploaded files and the parent folder via best-effort cleanup hooks. - TestDrive_UploadWorkflow: proves
drive +uploadagainst 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-tokenagainst the returned token, asserts the overwrite keeps the token stable, downloads the file with explicit--outputto confirm the remote content changed, and downloads again without--outputto prove default filename resolution saves the remote name with matching bytes. - TestDriveDownloadDryRun_DefaultNamePlansMetadataBeforeDownload / TestDriveDownloadDryRun_ExplicitOutputSkipsMetadata: dry-run coverage for
drive +download; asserts omitted--outputplans metadata before download for default naming, while explicit--outputpreserves 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 +statusand defaultdrive +pullboth fail with a typed validation error for the duplicate rel_path, whiledrive +pull --on-duplicate-remote=renamesucceeds, downloads both files, and writes a hashed renamed sibling locally. The other subtest uploads duplicate remote files, runsdrive +push --on-duplicate-remote=newest --if-exists=overwrite --delete-remote --yes, and then re-runsdrive +statusto prove the mirror converged to a single unchangeddup.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--typepath, request method/URL/type-query/perm/remark body shape, optionalremarkomission when unset, and client-side rejection of invalid permissions, unsafe tokens, non-root URL markers, encoded path separators, and conflicting URL/--typeinputs. Runs without hitting the live API. - TestDriveAddCommentDryRun_File / TestDriveAddCommentDryRun_Base: dry-run coverage for
drive +add-commenton supported Drive file and Base targets; pins themetas.batch_query -> files/:token/new_commentsfile chain, Basefile_type=bitable, and Base anchor fields. - TestDriveListCommentsDryRun_DocxDefaults / TestDriveListCommentsDryRun_AppsPageURL / TestDriveListCommentsDryRun_WikiToken: dry-run coverage for
drive +list-comments; asserts URL parsing tofiles/:token/comments, apps/page/<token>URL parsing withfile_type=apps, defaultis_solved=false, default omittedis_wholeanduser_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, anddrive +react-reply; asserts URL→type inference (incl. Miaoda apps/page/<token>→file_type=appsand Base/base/→file_type=bitable),file_type/page_size/need_reaction/need_relation(docx-gated) query wiring,comment_ids/is_solved/ replycontent.elements[](text_run) / reactionaction+reaction_type+reply_idbody shapes, resolved:comment_id/:reply_idpath segments, the Wikiget_node -> batch_query/get_node -> replies list/get_node -> v2 reactionorchestration plans, and the batch_query wiki dry-run surfacingneed_relationas 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, the1069303 forbiddencross-identity update rejection, the server persisting arbitraryreaction_typestrings, 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 throughdrive +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-commentsfinds it by ID,+add-replyattaches a reply,+list-repliessurfaces it,+update-replyrewrites its content (confirmed by polling+list-repliesuntil the new text lands),+react-replyattaches 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-commentmarks it solved and+restore-commentreopens it (with polling reads between state flips to absorb rate limiting),+delete-reply --yesremoves the created reply, and cleanup deletes the file. - TestDrive_SecureLabelDryRun: dry-run coverage for
drive +secure-label-listanddrive +secure-label-update; asserts label-list query params and update URL→type inference, request method/URL/type query, andlabel-idbody 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 wikitokenget_node -> export_tasksplanning, markdown fetch request shape without docs fetchextra_param, local--file-name/--output-dirmetadata, andbitable.baseonly_schemarequest body without calling live APIs. - TestDriveDeleteDryRunAsyncParams / TestDrive_DeleteAsyncWorkflow: dry-run coverage for
drive +deletepinsDELETE /drive/v1/files/:file_tokenparams withtypeplusasync=trueand the follow-uptask_checkplan; 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-emptytask_id) are verified viadrive +task_result --scenario task_check, sync deletes (emptytask_id) assertdeleted=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|oldestby 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|oldestby the real CLI binary. - Cleanup note:
drive files deleteis 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_WikiTargetandTestDriveUploadDryRun_WithFileTokencover the wiki-target and overwrite request shapes fordrive +upload; live upload/status/duplicate workflows also use real+uploadagainst 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 |