Files
larksuite__cli/tests/cli_e2e/base/coverage.md
huarenmin13 d66b1cac2e fix(base): improve search recovery and form deletion safety (#2422)
- guide record-search callers to the correct flag or command path without reporting recovery-only flags as invalid input
- reject blank form question IDs before destructive deletion and preserve keep-field request semantics
- cover typed validation and dry-run request contracts, then refresh Base E2E coverage

Co-authored-by: BASE Infra Harness <ai@base-infra-harness.noreply.local>
2026-09-02 14:15:28 +08:00

138 lines
21 KiB
Markdown

# Base CLI E2E Coverage
## Metrics
- Denominator: 99 leaf commands
- Covered: 49
- Coverage: 49.5%
## Summary
- TestBase_BasicWorkflow: proves `+base-create`, `+base-get`, `+table-create`, `+table-get`, and `+table-list`; key `t.Run(...)` proof points are `get base as bot`, `get table as bot`, and `list tables and find created table as bot`.
- TestBaseBlockDryRun: proves the five `+base-block-*` shortcuts request shapes without touching live data.
- TestBaseAppWorkflow: live user workflow in an isolated fixture Workspace covering Workspace entity listing, BaseApp create/get, Page create/list/get/update/delete, text Block create/list/get/update, and App cleanup through Drive delete (`type=bitable`). Set `LARK_CLI_E2E_BASEAPP_WORKSPACE_TOKEN` to enable it.
- TestBaseDashboardBlockCreateDryRun_PositionAndNumberFormat / TestBaseDashboardBlockUpdateDryRun_Position: prove `+dashboard-block-create` / `+dashboard-block-update` dry-run request shape carries the optional top-level `position` sibling and statistics `data_config.number_format`; TestBaseDashboardBlockCreateDryRun_InvalidNumberFormat proves the number_format enum validation rejects a bad formatName before any request.
- TestBaseFieldCreateDryRunArrayCompat: proves `+field-create` dry-run request shape for the internal JSON-array compatibility path.
- TestBaseFieldExtensionDryRun: proves `+field-extension-get`, `+field-extension-update`, and `+field-extension-update-cells` request shapes, including row-vs-column update bodies; TestBaseFieldExtensionUpdateCellsDryRunRejectsRowWithoutRecordID proves row updates require an explicit record.
- TestBaseFormQuestionsCreateDryRun: proves `+form-questions-create` preserves its POST body and renders the existing-question guard in command help.
- TestBaseFormDetailDryRun / TestBaseFormSubmitDryRun: prove shared-form detail and submission request shapes.
- TestBaseDashboardBlockGetDataDryRun: proves dashboard block data request shapes and identifier handling.
- TestBaseDashboardBlockLayoutPrecisionWorkflow: creates a temporary Base/table/dashboard, creates a statistics block with `position` and omitted `number_format`, asserts the server default, updates to a custom format, then verifies a precision-only update preserves `formatName`, and cleans up the block/dashboard/base. `+dashboard-create`, `+dashboard-delete`, `+dashboard-block-get` and `+dashboard-block-delete` have no dry-run coverage and rest on this test alone. This workflow was executed successfully against a live tenant on 2026-08-20 while validating PR #2118.
- TestBaseDashboardBlockRankingCreateDryRun / TestBaseDashboardBlockRankingUpdateDryRunPreservesPatch / TestBaseDashboardBlockRankingDryRunRejectsInvalidConfig: prove ranking create defaults, top-level patch preservation, and typed validation failures for unsupported fields and malformed filters.
- TestBaseShareDryRun: proves dashboard/form share GET and PATCH routes, one-field update requests, explicit false preservation, and nested form settings without touching live data.
- TestBaseShareWorkflow: deployment-gated by `LARK_CLI_E2E_BASE_SHARE_READY=1`; creates a Base, table, form, and dashboard, updates each share field in a separate request, verifies get round trips for both resources, disables sharing, and cleans up the Base.
- TestBaseRecordBatchUpdatePerRecordDryRun: proves `+record-batch-update` preserves the per-record `update_records` request shape.
- TestBaseRecordBatchUpdatePerRecordWorkflow: creates two records, generates their share links using mixed `--record-id` / `--record-ids` input, updates different field types in one request, asserts the minimal response contract, reads both records back, verifies a missing record ID is not prevalidated, and cleans up the temporary Base.
- TestBaseRecordShareLinkCreateDryRunAcceptsSingularAndPluralRecordIDFlags: proves singular and plural record ID flags compose into one deduplicated share-link request.
- TestBaseRecordHistoryListDryRunUsesExplicitRecordID / TestBaseRecordHistoryListDryRunRejectsNonPositiveMaxVersion: prove the history request keeps the explicit record ID and rejects explicitly non-positive cursors with a typed validation error.
- TestBase_RoleWorkflow: proves `+advperm-enable`, `+role-create`, `+role-list`, `+role-get`, and `+role-update`; key `t.Run(...)` proof points are `list as bot`, `get as bot`, and `update as bot`.
- TestBaseFormListDryRun_UsesBaseAndTableIdentifiers: proves `+form-list` dry-run request shape uses Base and table identifiers in the endpoint.
- TestBaseFormQuestionsCreateVisibleRuleDryRun / TestBaseFormQuestionsUpdateVisibleRuleDryRun: prove `+form-questions-create` / `+form-questions-update` dry-run request shape and that the optional `visible_rule` display condition is transcribed verbatim into the request body.
- TestBaseTableCopyDryRun: proves `+table-copy` and `+table-copy-status` request shapes, including the schema-safe default, table-name path escaping, explicit all+wait orchestration, Cobra duration parsing, and the opaque task ID body.
- TestBaseTableCopyWorkflow: feature-gated by `LARK_CLI_E2E_BASE_TABLE_COPY_READY=1` until the OpenAPI is deployed; creates a source table and record, proves schema-only copy, all no-wait plus status, all wait, record inclusion, and cleanup.
- TestBaseTemplateCenterDryRun: proves `+template-categories`, `+template-list`, and `+template-search` request shapes; the list case covers category, limit, and offset parameters.
- Cleanup note: `+table-delete` and `+role-delete` only run in cleanup and are intentionally left uncovered.
- Blocked area: table-copy live integration remains deployment-gated; remaining dashboard, field, most record operations, most form operations, view, and workflow operations still lack deterministic create/read/update workflows in this suite.
## Command Table
| Status | Cmd | Type | Testcase | Key parameter shapes | Notes / uncovered reason |
| --- | --- | --- | --- | --- | --- |
| ✕ | base +advperm-disable | shortcut | | none | no disable workflow yet |
| ✓ | base +advperm-enable | shortcut | base_role_workflow_test.go::TestBase_RoleWorkflow | `--base-token` | |
| ✕ | base +base-copy | shortcut | | none | no copy workflow yet |
| ✓ | base +base-create | shortcut | base/helpers_test.go::createBaseWithRetry | `--name`; `--time-zone` | helper asserts created base token |
| ✓ | base +base-get | shortcut | base_basic_workflow_test.go::TestBase_BasicWorkflow/get base as bot | `--base-token` | |
| ✓ | base +base-block-create | shortcut | base_block_dryrun_test.go::TestBaseBlockDryRun/create | `--base-token`; `--type`; `--name`; `--parent-id`; dry-run only | request shape only |
| ✓ | base +base-block-delete | shortcut | base_block_dryrun_test.go::TestBaseBlockDryRun/delete | `--base-token`; `--block-id`; dry-run only | request shape only |
| ✓ | base +base-block-list | shortcut | base_block_dryrun_test.go::TestBaseBlockDryRun/list all,list folder | `--base-token`; optional `--parent-id`; optional `--type`; dry-run only | request shape only |
| ✓ | base +base-block-move | shortcut | base_block_dryrun_test.go::TestBaseBlockDryRun/move root,move after | `--base-token`; `--block-id`; optional `--parent-id`; `--after-id`; dry-run only | request shape only |
| ✓ | base +base-block-rename | shortcut | base_block_dryrun_test.go::TestBaseBlockDryRun/rename | `--base-token`; `--block-id`; `--name`; dry-run only | request shape only |
| ✓ | base +template-categories | shortcut | base_template_center_dryrun_test.go::TestBaseTemplateCenterDryRun/categories | dry-run only | request shape only |
| ✓ | base +template-list | shortcut | base_template_center_dryrun_test.go::TestBaseTemplateCenterDryRun/list | `--category-key`; `--limit`/`--page-size`; `--offset`; dry-run only | request shape only |
| ✓ | base +template-search | shortcut | base_template_center_dryrun_test.go::TestBaseTemplateCenterDryRun/search | `--keyword`; `--limit`; dry-run only | request shape only; blank keyword validation covered |
| ✕ | base +dashboard-arrange | shortcut | | none | dashboard workflows not covered |
| ✓ | base +dashboard-block-create | shortcut | base_dashboard_block_layout_precision_dryrun_test.go::TestBaseDashboardBlockCreateDryRun_PositionAndNumberFormat; base_dashboard_block_layout_precision_workflow_test.go::TestBaseDashboardBlockLayoutPrecisionWorkflow; base_dashboard_block_ranking_dryrun_test.go::TestBaseDashboardBlockRankingCreateDryRun; TestBaseDashboardBlockRankingDryRunRejectsInvalidConfig | `--position` top-level; `--data-config.number_format`; ranking defaults and invalid child/top-level/filter fields; dry-run + live | request shape, typed validation envelope, plus live statistics block creation |
| ✓ | base +dashboard-block-delete | shortcut | base_dashboard_block_layout_precision_workflow_test.go::TestBaseDashboardBlockLayoutPrecisionWorkflow (cleanup) | `--base-token`; `--dashboard-id`; `--block-id`; `--yes`; live cleanup | deletes the temporary block |
| ✓ | base +dashboard-block-get | shortcut | base_dashboard_block_layout_precision_workflow_test.go::TestBaseDashboardBlockLayoutPrecisionWorkflow | `--base-token`; `--dashboard-id`; `--block-id`; live | reads back number_format; coordinates are not asserted (get is not contracted to echo position this iteration) |
| ✓ | base +dashboard-block-get-data | shortcut | base_dashboard_block_get_data_dryrun_test.go | `--base-token`; `--dashboard-id`; `--block-id`; dry-run only | request shape and identifier handling |
| ✕ | base +dashboard-block-list | shortcut | | none | dashboard workflows not covered |
| ✓ | base +dashboard-block-update | shortcut | base_dashboard_block_layout_precision_dryrun_test.go::TestBaseDashboardBlockUpdateDryRun_Position; base_dashboard_block_layout_precision_workflow_test.go::TestBaseDashboardBlockLayoutPrecisionWorkflow; base_dashboard_block_ranking_dryrun_test.go::TestBaseDashboardBlockRankingUpdateDryRunPreservesPatch | `--position` top-level dry-run; `--data-config.number_format` live; ranking `limit_size` patch dry-run | request shape for position and ranking patch; live workflow verifies number_format update and read-back, while position read-back remains unverified |
| ✓ | base +dashboard-create | shortcut | base_dashboard_block_layout_precision_workflow_test.go::TestBaseDashboardBlockLayoutPrecisionWorkflow | `--base-token`; `--name`; live | creates the temporary dashboard |
| ✓ | base +dashboard-delete | shortcut | base_dashboard_block_layout_precision_workflow_test.go::TestBaseDashboardBlockLayoutPrecisionWorkflow (cleanup) | `--base-token`; `--dashboard-id`; `--yes`; live cleanup | deletes the temporary dashboard |
| ✕ | base +dashboard-get | shortcut | | none | dashboard workflows not covered |
| ✕ | base +dashboard-list | shortcut | | none | dashboard workflows not covered |
| ✓ | base +dashboard-share-get | shortcut | base_share_dryrun_test.go::TestBaseShareDryRun/dashboard get; base_share_workflow_test.go::TestBaseShareWorkflow/dashboard share update and get | `--base-token`; `--dashboard-id`; dry-run + deployment-gated live | live requires `LARK_CLI_E2E_BASE_SHARE_READY=1` |
| ✓ | base +dashboard-share-update | shortcut | base_share_dryrun_test.go::TestBaseShareDryRun/dashboard partial update, dashboard auto analysis is not exposed; base_share_workflow_test.go::TestBaseShareWorkflow/dashboard share update and get | one of `--enabled`; `--access-scope=invite`; `--show-source` per request; unsupported auto-analysis flag | single-field updates, explicit false, invite-only scope, live read-back, and backend-gated setting exclusion covered |
| ✕ | base +dashboard-update | shortcut | | none | dashboard workflows not covered |
| ✕ | base +data-query | shortcut | | none | no data-query assertions yet |
| ✓ | base +field-create | shortcut | base_field_dryrun_test.go::TestBaseFieldCreateDryRunArrayCompat | `--base-token`; `--table-id`; `--json`; dry-run only | request shape only |
| ✕ | base +field-delete | shortcut | | none | field workflows not covered |
| ✓ | base +field-extension-get | shortcut | base_field_extension_dryrun_test.go::TestBaseFieldExtensionDryRun/get | `--base-token`; `--table-id`; `--field-id`; dry-run only | request shape only |
| ✓ | base +field-extension-update | shortcut | base_field_extension_dryrun_test.go::TestBaseFieldExtensionDryRun/update | `--json.extension_id`; `--json.inputs.prompt`; `--yes`; dry-run only | request shape only |
| ✓ | base +field-extension-update-cells | shortcut | base_field_extension_dryrun_test.go::TestBaseFieldExtensionDryRun/update cells row,update cells column; TestBaseFieldExtensionUpdateCellsDryRunRejectsRowWithoutRecordID | `--type=row` + repeated `--record-id`; `--type=column` + optional `--view-id`; `--yes`; dry-run only | request shape and row validation |
| ✕ | base +field-get | shortcut | | none | field workflows not covered |
| ✕ | base +field-list | shortcut | | none | field workflows not covered |
| ✕ | base +field-search-options | shortcut | | none | field workflows not covered |
| ✕ | base +field-update | shortcut | | none | field workflows not covered |
| ✕ | base +form-create | shortcut | | none | form workflows not covered |
| ✕ | base +form-delete | shortcut | | none | form workflows not covered |
| ✓ | base +form-detail | shortcut | base_form_detail_dryrun_test.go::TestBaseFormDetailDryRun | `--share-token`; dry-run only | shared-form request shape |
| ✕ | base +form-get | shortcut | | none | form workflows not covered |
| ✓ | base +form-list | shortcut | base_form_detail_dryrun_test.go::TestBaseFormListDryRun_UsesBaseAndTableIdentifiers | `--base-token`; `--table-id`; dry-run only | request shape only |
| ✓ | base +form-share-get | shortcut | base_share_dryrun_test.go::TestBaseShareDryRun/form get; base_share_workflow_test.go::TestBaseShareWorkflow/form share update and get | `--base-token`; `--table-id`; `--form-id`; dry-run + deployment-gated live | live requires `LARK_CLI_E2E_BASE_SHARE_READY=1` |
| ✓ | base +form-share-update | shortcut | base_share_dryrun_test.go::TestBaseShareDryRun/form settings update; base_share_workflow_test.go::TestBaseShareWorkflow/form share update and get | one of share enablement; `access-scope=invite`; anonymous/login settings per request | single-field updates, login-plus-anonymous across separate requests, explicit false, and live read-back covered |
| ✓ | base +form-questions-create | shortcut | TestBaseFormQuestionsCreateVisibleRuleDryRun; base_form_questions_create_dryrun_test.go | questions[].visible_rule; dry-run | request body, visible_rule passthrough, and help guard covered |
| ✓ | base +form-questions-delete | shortcut | base_form_questions_dryrun_test.go::TestBaseFormQuestionsDeleteDefaultDryRun; TestBaseFormQuestionsDeleteKeepFieldDryRun | `question_ids`; optional `keep_field=true` | default destructive body and opt-in field-preserving body covered |
| ✕ | base +form-questions-list | shortcut | | none | form workflows not covered |
| ✓ | base +form-questions-update | shortcut | TestBaseFormQuestionsUpdateVisibleRuleDryRun | questions[].visible_rule | dry-run: request shape + visible_rule body passthrough |
| ✓ | base +form-submit | shortcut | base_form_submit_dryrun_test.go::TestBaseFormSubmitDryRun | `--share-token`; `--json`; dry-run only | submission request shape |
| ✕ | base +form-update | shortcut | | none | form workflows not covered |
| ✓ | base +record-batch-create | shortcut | base_record_batch_update_workflow_test.go::TestBaseRecordBatchUpdatePerRecordWorkflow | `--base-token`; `--table-id`; `--json.create_records` | seeds heterogeneous live workflow records |
| ✓ | base +record-batch-update | shortcut | base_record_batch_update_dryrun_test.go::TestBaseRecordBatchUpdatePerRecordDryRun; base_record_batch_update_workflow_test.go::TestBaseRecordBatchUpdatePerRecordWorkflow | `--base-token`; `--table-id`; `--json.update_records`; dry-run + live | heterogeneous select/number update with write-back verification |
| ✕ | base +record-delete | shortcut | | none | record workflows not covered |
| ✓ | base +record-get | shortcut | base_record_batch_update_workflow_test.go::TestBaseRecordBatchUpdatePerRecordWorkflow | `--record-id`; repeated `--field-id`; `--format json` | reads back select and number values after batch update |
| ✓ | base +record-history-list | shortcut | base_record_history_dryrun_test.go::TestBaseRecordHistoryListDryRunUsesExplicitRecordID; TestBaseRecordHistoryListDryRunRejectsNonPositiveMaxVersion | `--base-token`; `--table-id`; `--record-id`; `--page-size`; `--max-version`; dry-run | request shape and typed cursor validation |
| ✕ | base +record-list | shortcut | | none | record workflows not covered |
| ✕ | base +record-search | shortcut | | none | record workflows not covered |
| ✓ | base +record-share-link-create | shortcut | base_record_share_link_dryrun_test.go::TestBaseRecordShareLinkCreateDryRunAcceptsSingularAndPluralRecordIDFlags; base_record_batch_update_workflow_test.go::TestBaseRecordBatchUpdatePerRecordWorkflow | canonical `--record-id`; hidden compatibility alias `--record-ids`; dry-run + live | singular and plural flags compose into one request |
| ✓ | base +record-upload-attachment | shortcut | base_attachment_dryrun_test.go::TestBase_AttachmentDryRun/upload | dry-run only | request shape only |
| ✓ | base +record-download-attachment | shortcut | base_attachment_dryrun_test.go::TestBase_AttachmentDryRun/download | dry-run only | request shape only |
| ✓ | base +record-remove-attachment | shortcut | base_attachment_dryrun_test.go::TestBase_AttachmentDryRun/remove | dry-run only | request shape only |
| ✕ | base +record-upsert | shortcut | | none | record workflows not covered |
| ✓ | base +role-create | shortcut | base/helpers_test.go::createRole | `--base-token`; `--json` | helper asserts created role id |
| ✕ | base +role-delete | shortcut | | none | cleanup only |
| ✓ | base +role-get | shortcut | base_role_workflow_test.go::TestBase_RoleWorkflow/get as bot | `--base-token`; `--role-id` | |
| ✓ | base +role-list | shortcut | base_role_workflow_test.go::TestBase_RoleWorkflow/list as bot | `--base-token` | |
| ✓ | base +role-update | shortcut | base_role_workflow_test.go::TestBase_RoleWorkflow/update as bot | `--base-token`; `--role-id`; `--json` | |
| ✓ | base +table-create | shortcut | base/helpers_test.go::createTableWithRetry | `--base-token`; `--name`; `--fields`; optional `--view` | helper asserts table id |
| ✓ | base +table-copy | shortcut | base_table_copy_dryrun_test.go::TestBaseTableCopyDryRun; base_table_copy_workflow_test.go::TestBaseTableCopyWorkflow | `--table-id` ID/name; default `--range schema`; explicit `--range all --wait --timeout` | dry-run covered; live workflow is deployment-gated and not yet verified |
| ✓ | base +table-copy-status | shortcut | base_table_copy_dryrun_test.go::TestBaseTableCopyDryRun/status; base_table_copy_workflow_test.go::TestBaseTableCopyWorkflow/all no-wait and status | opaque `--task-id` | dry-run covered; live polling is deployment-gated and not yet verified |
| ✕ | base +table-delete | shortcut | | none | cleanup only |
| ✓ | base +table-get | shortcut | base_basic_workflow_test.go::TestBase_BasicWorkflow/get table as bot | `--base-token`; `--table-id` | |
| ✓ | base +table-list | shortcut | base_basic_workflow_test.go::TestBase_BasicWorkflow/list tables and find created table as bot | `--base-token` | |
| ✕ | base +table-update | shortcut | | none | no rename workflow yet |
| ✕ | base +title-resolve | shortcut | | none | resolver workflow not covered |
| ✕ | base +url-resolve | shortcut | | none | resolver workflow not covered |
| ✕ | base +view-create | shortcut | | none | view workflows not covered |
| ✕ | base +view-delete | shortcut | | none | view workflows not covered |
| ✕ | base +view-get | shortcut | | none | view workflows not covered |
| ✕ | base +view-get-card | shortcut | | none | view workflows not covered |
| ✕ | base +view-get-filter | shortcut | | none | view workflows not covered |
| ✕ | base +view-get-group | shortcut | | none | view workflows not covered |
| ✕ | base +view-get-sort | shortcut | | none | view workflows not covered |
| ✕ | base +view-get-timebar | shortcut | | none | view workflows not covered |
| ✕ | base +view-get-visible-fields | shortcut | | none | view workflows not covered |
| ✕ | base +view-list | shortcut | | none | view workflows not covered |
| ✕ | base +view-rename | shortcut | | none | view workflows not covered |
| ✕ | base +view-set-card | shortcut | | none | view workflows not covered |
| ✕ | base +view-set-filter | shortcut | | none | view workflows not covered |
| ✕ | base +view-set-group | shortcut | | none | view workflows not covered |
| ✕ | base +view-set-sort | shortcut | | none | view workflows not covered |
| ✕ | base +view-set-timebar | shortcut | | none | view workflows not covered |
| ✕ | base +view-set-visible-fields | shortcut | | none | view workflows not covered |
| ✕ | base +workflow-create | shortcut | | none | workflow CRUD not covered |
| ✕ | base +workflow-disable | shortcut | | none | workflow CRUD not covered |
| ✕ | base +workflow-enable | shortcut | | none | workflow CRUD not covered |
| ✕ | base +workflow-get | shortcut | | none | workflow CRUD not covered |
| ✕ | base +workflow-list | shortcut | | none | workflow CRUD not covered |
| ✕ | base +workflow-update | shortcut | | none | workflow CRUD not covered |