Files
yxy-bd db102ab314 feat (apps): add miaoda app collaborator management (#2230)
* feat: add miaoda app collaborator management

* refactor: remove collaborator list pagination

* fix: make external invite setting read-only

* fix: make Miaoda copy setting read-only

* docs: record BOE collaborator verification

* fix: remove unsupported Miaoda setting flags
2026-08-07 16:20:02 +08:00

13 KiB

Apps CLI E2E Coverage

Metrics

  • Denominator: 25 leaf commands in the selected apps E2E coverage set (not all 88 apps shortcuts)
  • Selected command coverage: 100% (25/25)
  • API dry-run coverage: 100% (23/23 API-backed commands)
  • Local E2E coverage: 100% (2/2 local-only commands)
  • Live coverage: file and role workflows are fixture-gated and skipped by default CI. File upload covers absolute-path upload, metadata readback, and cleanup; role workflows cover role lifecycle and member mutations with cleanup. All six Miaoda collaborator commands have manual Feishu BOE evidence: user/department/chat CRUD was verified with cleanup on 2026-08-04, and unpaginated list plus settings get/set, read-only errors, combined writes, readback, and rollback were reverified on 2026-08-06.

Summary

  • TestAppsCreateDryRun: happy path with --app-type html, all-fields shape, rejection paths (missing name, missing app-type, invalid app-type, legacy uppercase HTML). --app-type is a strict lowercase enum (html/frontend/full_stack); the CLI does not normalize case — legacy uppercase compatibility is a server concern.
  • TestAppsUpdateDryRun: partial-field PATCH semantics; --app-id and at-least-one-field validation.
  • TestAppsListDryRun: default page_size=20; empty --page-token omitted; negative size passed through to server (no client-side bound check); --keyword/--ownership/--app-type pass-through + empty-omission; invalid --ownership and legacy uppercase --app-type enum rejection.
  • TestAppsAccessScopeSetDryRun: CLI input specific/public/tenant -> server enum Range/All/Tenant; apply_config.approvers shape; four mutex rejection paths.
  • TestAppsAccessScopeGetDryRun: URL shape; no body/params on GET; --app-id required.
  • TestAppsHTMLPublishDryRun: walker manifest for directory + single file; hidden files intentionally included (design decision); empty dir / missing index.html produce envelope validation_error field (dry-run exits 0 advisory, not blocking); both required-flag rejections.
  • TestAppsFileUploadDryRun_AcceptsAbsoluteHostPath: dry-run validates an absolute local file and derives its basename without uploading it; a missing source path is rejected before preview.
  • TestAppsFileUploadLiveWorkflow: fixture-gated absolute-path upload, +file-get readback, and +file-delete cleanup in a dedicated app.
  • TestAppsGitCredentialInitDryRun: URL shape for issuing an app Git PAT; no body; app_id query metadata included.
  • TestAppsGitCredentialListLocalE2E: local-only command scans every app storage directory and reports repository URL and status without exposing PAT or expiry details.
  • TestAppsGitCredentialRemoveLocalE2E: local cleanup command removes app-scoped metadata under an isolated config dir.
  • TestAppsRoleManagementDryRun_RequestShapes: role CRUD/member/match request shapes for all 9 role shortcuts. Request/response fields follow the API contract: name, role_id, users, departments, chats, target_user_id, and roles.
  • TestAppsRoleManagementValidation: deterministic typed validation for invalid role ID, page bounds/token, missing update fields, missing member input, --all conflicts, and missing --user-id.
  • TestAppsRoleManagementLiveWorkflow: fixture-gated live role/member workflow against the role provided by LARK_CLI_E2E_APPS_ROLE_ID. It refuses to run when the selected member already exists, mutates only that member, and removes only that member during cleanup; it never changes a shared role definition or clears unrelated members.
  • TestAppsRoleLifecycleLiveWorkflow: creates a uniquely named transient role, independently reads it back, updates and re-reads it, adds a fixture member, clears all members and proves the role still exists, then deletes it and verifies the target role_id is absent. Cleanup is armed before creation and uses only environment-provided test identifiers.
  • TestAppsRoleMatchListLiveWorkflow: separately fixture-gated live +role-match-list proof against the same isolated fixture role. It also requires the selected user to be absent at baseline and removes only the user it added.
  • TestAppsMemberDryRun: request shapes for all six Creative collaborator commands, including typed external ID projection (openid -> user_open_id, openchat -> chat_id, opendepartmentid -> department_id), filters, unpaginated member lists, and partial settings PATCH semantics.
  • TestAppsMemberHighRiskWritesRequireYesButDryRunDoesNot: all four collaborator/settings writes require explicit confirmation for live execution while remaining directly inspectable with --dry-run.
  • TestAppsMemberValidationFailuresAreStructured: deterministic validation envelopes for mismatched typed IDs, invalid settings enums, and empty settings updates.
  • TestAppsMemberUnsupportedWriteFlagsAreNotRegistered: external_invite and copy_download_by remain readable, while their unsupported setter flags are absent from the real CLI command and rejected as unknown flags.

Blocked: General app create live E2E is intentionally not implemented yet. Apps has no +delete endpoint, so a create-and-cleanup workflow would leak tenant state. File upload and selected role live workflows remain fixture-gated; each uses dedicated fixtures and cleans up the resources it mutates. Miaoda collaborator live coverage is currently manual rather than default-CI automation because its write cases require isolated user, department, and chat fixtures; the BOE contract and cleanup workflows have been exercised end to end.

Command Table

Status Cmd Type Testcase Key parameter shapes Notes / uncovered reason
✓ apps +create shortcut apps_create_dryrun_test.go::TestAppsCreateDryRun --name, --app-type (required, case-sensitive, html/frontend/full_stack), --description, --icon-url live blocked: no +delete to clean up
✓ apps +update shortcut apps_update_dryrun_test.go::TestAppsUpdateDryRun --app-id; at least one of --name/--description live blocked: no +delete
✓ apps +list shortcut apps_list_dryrun_test.go::TestAppsListDryRun --keyword; --ownership (enum all/mine/shared); --app-type (enum html/frontend/full_stack); --page-size default 20; --page-token cursor live blocked: needs tenant fixtures
✓ apps +access-scope-set shortcut apps_access_scope_set_dryrun_test.go::TestAppsAccessScopeSetDryRun --scope specific/public/tenant; --targets JSON; --apply-enabled --approver; --require-login live blocked: needs real open_ids
✓ apps +access-scope-get shortcut apps_access_scope_get_dryrun_test.go::TestAppsAccessScopeGetDryRun --app-id live blocked: depends on +access-scope-set state
✓ apps +html-publish shortcut apps_html_publish_dryrun_test.go::TestAppsHTMLPublishDryRun --app-id, --path (file or directory containing index.html) live blocked: real upload has side effects; no rollback API
✓ apps +file-upload shortcut apps_file_upload_dryrun_test.go::TestAppsFileUploadDryRun_AcceptsAbsoluteHostPath; apps_file_upload_dryrun_test.go::TestAppsFileUploadDryRun_RejectsMissingHostPath; apps_file_upload_live_test.go::TestAppsFileUploadLiveWorkflow --app-id, --file (absolute or relative local path) live workflow uses LARK_CLI_E2E_APPS_FILE_APP_ID, reads metadata back, and deletes the uploaded file
✓ apps +git-credential-init shortcut apps_git_credential_dryrun_test.go::TestAppsGitCredentialInitDryRun --app-id; dry-run GET /open-apis/spark/v1/apps/{app_id}/git_info live blocked: issues short-lived repository PAT
✓ apps +git-credential-list shortcut apps_git_credential_local_test.go::TestAppsGitCredentialListLocalE2E no --app-id; scans all local app storage directories and reports app_id, repository URL, and status without PAT or expiry local E2E only: no dry-run API because command is local read only
✓ apps +git-credential-remove shortcut apps_git_credential_local_test.go::TestAppsGitCredentialRemoveLocalE2E --app-id; deletes local metadata, keychain PAT, and Git config local E2E only: no dry-run API because command is local cleanup only
✓ apps +role-list shortcut apps_role_management_test.go::TestAppsRoleManagementDryRun_RequestShapes --app-id; --name -> name; --page-size -> limit; --page-token -> offset live depends on a role-capable app fixture
✓ apps +role-get shortcut apps_role_management_test.go::TestAppsRoleManagementDryRun_RequestShapes GET /roles/:role_id; no body/query live covered by fixture-gated role workflow
✓ apps +role-create shortcut apps_role_management_test.go::TestAppsRoleManagementDryRun_RequestShapes; apps_role_management_test.go::TestAppsRoleLifecycleLiveWorkflow POST /roles; required name; optional role_id transient live role is independently read back
✓ apps +role-update shortcut apps_role_management_test.go::TestAppsRoleManagementDryRun_RequestShapes; apps_role_management_test.go::TestAppsRoleLifecycleLiveWorkflow PATCH /roles/:role_id; only changed fields transient live role update is independently read back
✓ apps +role-delete shortcut apps_role_management_test.go::TestAppsRoleManagementDryRun_RequestShapes; apps_role_management_test.go::TestAppsRoleLifecycleLiveWorkflow DELETE /roles/:role_id; high-risk confirmation live flow verifies the target role_id is absent after deletion
✓ apps +role-member-list shortcut apps_role_management_test.go::TestAppsRoleManagementDryRun_RequestShapes GET /member_list; optional member_type=user/department/chat; no pagination live covered by fixture-gated role workflow for a provided chat member when available, otherwise a user member
✓ apps +role-member-add shortcut apps_role_management_test.go::TestAppsRoleManagementDryRun_RequestShapes POST /member_add; body users/departments/chats open_id arrays live covered by fixture-gated role workflow for a provided chat member when available, otherwise a user member
✓ apps +role-member-remove shortcut apps_role_management_test.go::TestAppsRoleManagementDryRun_RequestShapes; apps_role_management_test.go::TestAppsRoleManagementLiveWorkflow; apps_role_management_test.go::TestAppsRoleLifecycleLiveWorkflow POST /member_remove; body users/departments/chats open_id arrays or all=true; high-risk confirmation explicit removal and --all both have fixture-gated live readback coverage
✓ apps +role-match-list shortcut apps_role_management_test.go::TestAppsRoleManagementDryRun_RequestShapes; apps_role_management_test.go::TestAppsRoleMatchListLiveWorkflow POST /user_role_list; body target_user_id; no role_id; response field is roles per the API contract automated live runs only when LARK_CLI_E2E_APPS_ROLE_MATCH_READY=1; it reuses the role provided by LARK_CLI_E2E_APPS_ROLE_ID instead of creating a transient role
✓ apps +member-list shortcut ../dryrun/apps_member_dryrun_test.go::TestAppsMemberDryRun --app-id; optional --role view/edit/full_access; --member-type user/department/chat; returns all direct collaborators without pagination BOE live verified: user/department/chat only, role/type filters, no app member, no pagination
✓ apps +member-add shortcut ../dryrun/apps_member_dryrun_test.go::TestAppsMemberDryRun; ../dryrun/apps_member_dryrun_test.go::TestAppsMemberHighRiskWritesRequireYesButDryRunDoesNot typed external ID via --member-type openid/openchat/opendepartmentid + --member-id; --perm; optional notification BOE live verified for user/department/chat with isolated cleanup; high-risk write remains excluded from default CI
✓ apps +member-update shortcut ../dryrun/apps_member_dryrun_test.go::TestAppsMemberDryRun; ../dryrun/apps_member_dryrun_test.go::TestAppsMemberHighRiskWritesRequireYesButDryRunDoesNot typed external ID plus required --perm; PATCH only the selected collaborator BOE live verified for user/department/chat with readback and cleanup; high-risk write remains excluded from default CI
✓ apps +member-remove shortcut ../dryrun/apps_member_dryrun_test.go::TestAppsMemberDryRun; ../dryrun/apps_member_dryrun_test.go::TestAppsMemberHighRiskWritesRequireYesButDryRunDoesNot typed external ID; POST /members/remove; no raw/internal numeric IDs BOE live verified for user/department/chat with absence readback; high-risk write remains excluded from default CI
✓ apps +member-settings-get shortcut ../dryrun/apps_member_dryrun_test.go::TestAppsMemberDryRun --app-id; GET without body BOE live verified with all six returned settings
✓ apps +member-settings-set shortcut ../dryrun/apps_member_dryrun_test.go::TestAppsMemberDryRun; ../dryrun/apps_member_dryrun_test.go::TestAppsMemberHighRiskWritesRequireYesButDryRunDoesNot; ../dryrun/apps_member_dryrun_test.go::TestAppsMemberUnsupportedWriteFlagsAreNotRegistered partial PATCH of external access, link sharing, collaborator management, and comments; unsupported --external-invite and --copy-download-by flags are not registered BOE live verified for four-field combined write/readback/rollback, no-op, invalid enum, and server-side read-only errors