Skip to content

feat(docs): add opt-in --emulate fallback for block_move_after/block_copy_insert_after - #1427

Open
leeguooooo wants to merge 5 commits into
larksuite:mainfrom
leeguooooo:feat/emulate-block-move
Open

feat(docs): add opt-in --emulate fallback for block_move_after/block_copy_insert_after#1427
leeguooooo wants to merge 5 commits into
larksuite:mainfrom
leeguooooo:feat/emulate-block-move

Conversation

@leeguooooo

@leeguooooo leeguooooo commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Summary

On some server deployments (reproduced on a JP-tenant Lark international workspace, see #758), docs +update --command block_move_after / block_copy_insert_after fail with command 'block_move_after' requires content or src_block_ids but both were empty even though the CLI sends src_block_ids correctly — the server side does not read the field. This PR adds an opt-in --emulate flag that reproduces move/copy semantics client-side with the primitives that do work, so agents are not blocked while the server bug persists. Default behavior is completely unchanged; without --emulate both commands still send the same single request as today.

Changes

  • Add --emulate (bool, default false) to docs +update, valid only with block_move_after / block_copy_insert_after. When set, the command runs a client-side orchestration: fetch source blocks (full detail) → rebuild them after --block-id via block_insert_after → re-fetch to verify the rebuilt blocks exist → block_delete the originals (move only). Insert always precedes delete and any failure stops immediately with the inserted/deleted state reported on stderr, so no path loses source content (shortcuts/doc/docs_update_emulate.go).
  • Supported block types: p, h1h9, ul, ol, li (re-wrapped in its original list type), blockquote, callout (rgb colors dropped, emoji kept), pre, img (rebuilt from href, effectively a re-upload). Block types carrying server-side tokens or state (whiteboard, sheet, bitable, table, synced_reference, …) are rejected with a typed failed_precondition error before any write.
  • The emulation is not semantically identical to the server-side command and says so explicitly — in the flag help, in a stderr warning on every run, and in a semantic_differences field of the success envelope: rebuilt blocks get new block ids, comments and #block_id anchors on the originals are not migrated, and image file tokens change.
  • --emulate rejects a non-default --revision-id (the orchestration issues multiple writes against the latest revision) and duplicate ids in --src-block-ids.
  • --dry-run --emulate prints the multi-call plan (fetch → insert → verify-fetch → delete for move; three calls for copy).
  • Unit tests for the XML block indexer / rebuild rules and httpmock-based orchestration tests (move, copy keeps originals, unsupported type rejected before any write, verification failure keeps originals); dry-run E2E cases for both commands.
  • Note the fallback (and its semantic differences) in the lark-doc-update.md skill reference so agents discover it next to the move/copy documentation.

Test Plan

  • make unit-test (full -race suite green)
  • go vet ./...
  • gofmt -l . (no output)
  • go mod tidy (no go.mod/go.sum changes)
  • go run github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.1.6 run --new-from-rev=origin/main (0 issues)
  • LARK_CLI_BIN=$PWD/lark-cli go test ./tests/cli_e2e/docs/ -run TestDocs_DryRunDefaultsToV2OpenAPI
  • Live verification on a scratch docx in the affected JP tenant:

Related Issues

Summary by CodeRabbit

  • New Features

    • Added an --emulate flag for block_move_after and block_copy_insert_after as a client-side fallback when servers reject requests due to missing src_block_ids, including dry-run and execute support.
    • Emulation rebuilds eligible blocks, inserts after the anchor, verifies the insertion is visible/contiguous, and deletes originals only for move (conservatively aborts on verification failures).
  • Documentation

    • Updated docs +update guidance with behavior notes and limitations (block-id changes, link/comment behavior, image handling, supported block types, stop-on-failure).
  • Tests

    • Added extensive unit, dry-run, and end-to-end coverage, including validation, error paths, and concurrency scenarios.

…copy_insert_after

Some server deployments do not read src_block_ids and reject both
commands (larksuite#758). --emulate opts into a client-side fallback: fetch the
source blocks (full detail), rebuild them after --block-id via
block_insert_after, re-fetch to verify the rebuilt blocks exist, then
block_delete the originals (move only). Insert always precedes delete
and any failure stops with the inserted/deleted state on stderr, so no
path loses source content.

The emulation is not semantically identical and says so in the flag
help, a stderr warning, and a semantic_differences field: rebuilt
blocks get new ids, comments and #block_id anchors are not migrated,
and images are re-uploaded from their href. Block types carrying
server-side tokens or state (whiteboard, sheet, bitable, table, ...)
are rejected before any write. Default behavior without --emulate is
unchanged.
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 94ca5ba1-9004-417e-a3e5-45f3b00a43fc

📥 Commits

Reviewing files that changed from the base of the PR and between cc4e47c and 28326ec.

📒 Files selected for processing (1)
  • shortcuts/doc/docs_update_emulate_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • shortcuts/doc/docs_update_emulate_test.go

📝 Walkthrough

Walkthrough

Adds a client-side --emulate fallback for block_move_after and block_copy_insert_after: fetch full document XML, index and sanitize emulable source blocks, rebuild insert payload, insert after anchor, verify contiguous insertion, and optionally delete originals for moves. Includes dry-run planning, validation, tests, and docs.

Changes

Block move/copy emulation feature

Layer / File(s) Summary
Flag registration and validation
shortcuts/doc/docs_update_v2.go, shortcuts/doc/docs_update_test.go
Adds --emulate boolean flag; validates it is only used with block_move_after/block_copy_insert_after, forbids --revision-id, requires non-empty and unique --src-block-ids.
Block indexing and XML parsing
shortcuts/doc/docs_update_emulate.go
Define emulable tag allowlist, XML tokenization regexes, block-id extraction, implement indexEmulatedBlocks to capture id-bearing XML subtrees with ancestor chains, and indexTopLevelBlocks to collect outermost blocks with descendant id sets for post-insert verification.
XML sanitization and content rebuild
shortcuts/doc/docs_update_emulate.go
rebuildEmulatedImgTag reconstructs img tags from href/url; sanitizeEmulatedBlockXML strips id attributes and drops RGB callout colors; buildEmulatedBlockContent validates emulable types, sanitizes fetched XML, and wraps li in nearest ul/ol ancestor.
API builders and flow routing
shortcuts/doc/docs_update_emulate.go, shortcuts/doc/docs_update_v2.go
Implement docs_ai fetch/update request/response helpers, and wire dry-run/execute branching to delegate to dryRunUpdateEmulated / executeUpdateEmulated when --emulate is set.
Post-insert verification
shortcuts/doc/docs_update_emulate.go
isNewTopLevelBlock classifies post-insert blocks as new vs pre-existing; anchorRunStart locates rebuilt run after anchor; verifyEmulatedInsert performs strict anchor-scoped contiguous verification against expected tag sequence, signaling originals not deleted on failure.
Execution orchestration and dry-run planning
shortcuts/doc/docs_update_emulate.go
executeUpdateEmulated orchestrates fetch→rebuild→insert→verify→optional-delete for moves, returning OutRaw with emulation metadata; dryRunUpdateEmulated emits multi-call plan without mutations.
Comprehensive test suite
shortcuts/doc/docs_update_emulate_test.go
Test module structure and indexing validation with tag/parent chain assertions, content rebuild for supported types with entity/list/image/callout handling, error rejection for unsupported blocks and malformed images, HTTP stubs for docs_ai endpoints, end-to-end move vs copy flows with block ID discovery and deletion semantics, flag/command/ID validation table tests, dry-run plan assertions, anchor-scoped verification edge cases, and concurrency regression tests.
Documentation and E2E dry-run cases
skills/lark-doc/references/lark-doc-update.md, tests/cli_e2e/docs/docs_update_dryrun_test.go
Document --emulate semantics and semantic differences (block_id changes, comment/anchor link non-migration, image rebuild equivalence); extend E2E dry-run tests with move/copy emulate scenarios.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • larksuite/cli#638: Prior v2 docs +update work that this --emulate fallback builds upon.
  • larksuite/cli#1291: Both PRs refactor v2 update validation and flag handling in shortcuts/doc/docs_update_v2.go; this PR's --emulate flag and validation extend the retrieved PR's v2-only structure.

Suggested labels

size/XL

Suggested reviewers

  • SunPeiYang996
  • fangshuyu-768

Poem

🐰 I hop through XML, tidy and light,
strip ids and rebuild imgs right.
Fetch, insert, verify—then prune with care,
a rabbit's emulation, tidy and fair. ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: introducing an opt-in --emulate fallback for block_move_after and block_copy_insert_after commands.
Description check ✅ Passed The description comprehensively covers all template sections: Summary explains the server bug and client-side workaround, Changes lists implementation details with file references, Test Plan has all checkboxes completed and verified, and Related Issues links to #758.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added domain/ccm PR touches the ccm domain size/L Large or sensitive change across domains or core paths labels Jun 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@shortcuts/doc/docs_update_emulate_test.go`:
- Around line 92-125: Update the two tests to assert typed error metadata
instead of relying on err.Error() text: in
TestBuildEmulatedBlockContentRejectsUnsupportedType, after calling
buildEmulatedBlockContent use errs.ProblemOf(err) to assert category/subtype
(already done) and replace the string substring checks with assertions on the
error’s params/cause by using errors.As to extract *errs.ValidationError (or the
concrete typed error) and check its Param or other fields; likewise in
TestRebuildEmulatedImgTagRequiresHref, after calling rebuildEmulatedImgTag use
errs.ProblemOf to check SubtypeFailedPrecondition and use errors.As to get the
validation error and assert the Param (or relevant field) is "href"/missing,
removing reliance on message text. Ensure you reference
buildEmulatedBlockContent and rebuildEmulatedImgTag when updating the
assertions.

In `@shortcuts/doc/docs_update_emulate.go`:
- Around line 375-398: The verification currently treats any newly appearing
block id as proof of a successful rebuild; change the newIDs collection to be
anchor-scoped so only blocks that sit inside the expected rebuilt subtree are
considered. Specifically, when iterating afterBlocks (the code that calls
indexEmulatedBlocks and builds newIDs), compute the anchor extent (e.g., derive
minStart/maxEnd or anchorID from the original srcList entries) and filter so you
only append b.id when afterBlocks[b.id] lies within that anchor extent or has
the expected parent/ancestor matching the anchor; keep the subsequent sort and
emptiness check but base them on this filtered set, and only call
emulateUpdateDoc("block_delete", ...) when the filtered newIDs prove the rebuilt
subtree exists at the anchor. Ensure you reference afterBlocks,
indexEmulatedBlocks, newIDs and srcList when making the change.
- Around line 344-346: The error returned from buildEmulatedBlockContent can be
a typed validation error missing the CLI param metadata; before returning err
from the caller in docs_update_emulate.go, detect when err is a validation/typed
error and set its param to "--src-block-ids" (or wrap it so the typed error's
Param field is populated) so rebuild failures surface the correct CLI metadata;
update the return path that currently does "piece, err :=
buildEmulatedBlockContent(b); if err != nil { return err }" to attach or wrap
the error with Param="--src-block-ids" using the project's typed error helper or
setter on the validation error type.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d1c51b66-fcfe-4fb2-a19e-1262630a5a22

📥 Commits

Reviewing files that changed from the base of the PR and between 76ba6fa and 4bc02c5.

📒 Files selected for processing (6)
  • shortcuts/doc/docs_update_emulate.go
  • shortcuts/doc/docs_update_emulate_test.go
  • shortcuts/doc/docs_update_test.go
  • shortcuts/doc/docs_update_v2.go
  • skills/lark-doc/references/lark-doc-update.md
  • tests/cli_e2e/docs/docs_update_dryrun_test.go

Comment thread shortcuts/doc/docs_update_emulate_test.go
Comment thread shortcuts/doc/docs_update_emulate.go Outdated
Comment thread shortcuts/doc/docs_update_emulate.go Outdated
Address review feedback on the --emulate fallback:

- Verification was "any new block id appeared after the insert", which in
  a shared document would treat a concurrent collaborator's edit as proof
  of our own insert and delete the originals — data loss. Replace it with
  anchor-scoped verification: confirm the rebuilt blocks landed as a
  contiguous run immediately after the anchor (document start for a page-id
  anchor, the final slots for -1) with the expected count and tag sequence.
  Anything that does not line up fails conservatively and keeps the
  originals, leaving a recoverable duplicate rather than risking loss. The
  top-level walker matches Lark's flat DocxXML export (sibling blocks, no
  wrapping page element).

- buildEmulatedBlockContent now tags unsupported-type and missing-href
  rejections with Param --src-block-ids so the command layer knows which
  flag carried the offending source block.

- Error-path tests assert the typed envelope via the shared
  assertValidationContract helper (category/subtype/param) instead of
  err.Error() substrings; add coverage for the concurrent-edit guard and
  the document-start/-end anchor cases.
@leeguooooo

Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai — addressed all three in 43b811e:

1. Anchor-scoped verification (the data-loss one) — fixed. You're right that "any new block id appeared" would, in a shared doc, treat a concurrent collaborator's edit as proof of our own insert and then delete the originals. Verification is now anchor-scoped: it confirms the rebuilt blocks landed as a contiguous run immediately after the anchor (document start for a page-id anchor, the final slots for -1) with the expected count and top-level tag sequence. Anything that doesn't line up fails conservatively and keeps the originals (recoverable duplicate over data loss). "Insert → verify → delete, stop on any failure" is preserved. The top-level walker matches Lark's flat DocxXML export (sibling blocks, no wrapping <page>); I caught and fixed a fixture/-1-anchor mismatch here via live testing on a scratch doc.

2. Tag the offending --src-block-ids — fixed. buildEmulatedBlockContent now attaches Param: --src-block-ids to both the unsupported-type rejection and the missing-href img rejection, so the command layer can surface which source block failed.

3. Typed-error assertions — fixed. The two error-path tests now assert the typed envelope (category/subtype/param) via the package's shared assertValidationContract helper instead of err.Error() substrings. Added coverage for the concurrent-edit guard and the document-start/-1 anchor cases.

Re-verified after the changes: full -race unit suite, go vet, gofmt, golangci-lint --new-from-rev (0 issues), dry-run E2E, and the live six-case matrix (text move, multi-block move, img move to -1, copy, unsupported-type rejection, no---emulate passthrough unchanged) all green.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

@leeguooooo Excellent work on all three — the anchor-scoped verification fix is particularly thorough. Using a contiguous-run check (correct count + tag sequence immediately after the anchor, conservative failure to keep originals) is exactly the right trade-off: a recoverable duplicate beats silent data loss in any shared-doc scenario. The -1 / document-start edge cases and the live scratch-doc validation are a nice bonus. The typed-error propagation (Param: --src-block-ids) and the assertValidationContract-based test assertions round it out cleanly.

Triggering a fresh review pass to pick up the 43b811e changes.

ʕ •ᴥ•ʔ 🎉

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
shortcuts/doc/docs_update_emulate_test.go (1)

352-403: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add typed error metadata assertions to validation test cases.

Each case should assert the typed error envelope (category/subtype/param) instead of relying solely on message substring matching. Per context snippet 1, validateUpdateV2 returns errs.NewValidationError with SubtypeInvalidArgument and .WithParam(...) for each rejection. Use errors.As(err, &ve) to extract *errs.ValidationError and assert ve.Param matches the expected flag (e.g., "--emulate", "--revision-id", "--src-block-ids"), and use errs.ProblemOf(err) to assert Subtype. As per coding guidelines, error-path tests must assert typed metadata via errs.ProblemOf (category/subtype/param), not message substrings alone.

♻️ Example refactor for one test case
 		{
 			name:     "rejects non move/copy command",
 			setFlags: map[string]string{"command": "append", "content": "<p>x</p>", "emulate": "true"},
 			want:     "--emulate only applies to block_move_after and block_copy_insert_after",
 		},

In the test loop body:

 		err := validateUpdateV2(context.Background(), runtime)
 		if err == nil {
 			t.Fatal("expected validation error")
 		}
+		// Assert typed metadata
+		problem, ok := errs.ProblemOf(err)
+		if !ok || problem.Subtype != errs.SubtypeInvalidArgument {
+			t.Fatalf("expected SubtypeInvalidArgument, got %v", err)
+		}
+		var ve *errs.ValidationError
+		if !errors.As(err, &ve) {
+			t.Fatalf("expected *errs.ValidationError, got %T", err)
+		}
+		// Each case should assert the expected param, e.g.:
+		// if ve.Param != "--emulate" { ... }
+		
 		if !strings.Contains(err.Error(), tt.want) {
 			t.Fatalf("error missing %q: %v", tt.want, err)
 		}

Repeat for all four test cases, asserting the appropriate ve.Param value for each.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shortcuts/doc/docs_update_emulate_test.go` around lines 352 - 403, The test
TestDocsUpdateEmulateValidation currently only checks error message substrings;
update each subtest to extract the typed validation error returned by
validateUpdateV2 using errors.As into a *errs.ValidationError (ve) and assert
ve.Param equals the expected flag (e.g., "--emulate", "--revision-id",
"--src-block-ids") and that errs.ProblemOf(err).Subtype (or the full ProblemOf
result) matches SubtypeInvalidArgument; remove or keep substring checks as a
secondary assertion but ensure the primary assertions use errors.As(., &ve) and
errs.ProblemOf to validate category/subtype/param for each case. Use the test
names and setFlags to map expected params: first two -> "--emulate", second ->
"--revision-id", third -> "--src-block-ids", fourth ->
"--src-block-ids"/contains-no-ids as appropriate, and update the loop body
accordingly.

Source: Coding guidelines

🧹 Nitpick comments (1)
shortcuts/doc/docs_update_emulate.go (1)

226-233: 💤 Low value

Consider caching compiled regexes for attribute lookups.

xmlAttrValue compiles a new regex on each call. In rebuildEmulatedImgTag, this results in up to 6 compilations per img tag. Since this is the emulation fallback path (not the hot path), the impact is likely minimal, but a cached approach would improve performance for documents with many images.

+var xmlAttrValueCache = make(map[string]*regexp.Regexp)
+var xmlAttrValueMu sync.Mutex
+
 func xmlAttrValue(attrs, name string) (string, bool) {
-	re := regexp.MustCompile(`(?:^|\s)` + regexp.QuoteMeta(name) + `="([^"]*)"`)
+	xmlAttrValueMu.Lock()
+	re, ok := xmlAttrValueCache[name]
+	if !ok {
+		re = regexp.MustCompile(`(?:^|\s)` + regexp.QuoteMeta(name) + `="([^"]*)"`)
+		xmlAttrValueCache[name] = re
+	}
+	xmlAttrValueMu.Unlock()
 	m := re.FindStringSubmatch(attrs)

Alternatively, pre-compile regexes for the known attribute names used in rebuildEmulatedImgTag.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@shortcuts/doc/docs_update_emulate.go` around lines 226 - 233, xmlAttrValue
currently compiles a new regexp on every call (causing multiple compilations per
img tag in rebuildEmulatedImgTag); change it to reuse compiled regexes by
introducing a package-level cache (e.g., map[string]*regexp.Regexp guarded by
sync.RWMutex) or precompile regexes for the known attribute names used by
rebuildEmulatedImgTag, and update xmlAttrValue to fetch the regexp from that
cache (or the precompiled set) before calling FindStringSubmatch.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@shortcuts/doc/docs_update_emulate_test.go`:
- Around line 352-403: The test TestDocsUpdateEmulateValidation currently only
checks error message substrings; update each subtest to extract the typed
validation error returned by validateUpdateV2 using errors.As into a
*errs.ValidationError (ve) and assert ve.Param equals the expected flag (e.g.,
"--emulate", "--revision-id", "--src-block-ids") and that
errs.ProblemOf(err).Subtype (or the full ProblemOf result) matches
SubtypeInvalidArgument; remove or keep substring checks as a secondary assertion
but ensure the primary assertions use errors.As(., &ve) and errs.ProblemOf to
validate category/subtype/param for each case. Use the test names and setFlags
to map expected params: first two -> "--emulate", second -> "--revision-id",
third -> "--src-block-ids", fourth -> "--src-block-ids"/contains-no-ids as
appropriate, and update the loop body accordingly.

---

Nitpick comments:
In `@shortcuts/doc/docs_update_emulate.go`:
- Around line 226-233: xmlAttrValue currently compiles a new regexp on every
call (causing multiple compilations per img tag in rebuildEmulatedImgTag);
change it to reuse compiled regexes by introducing a package-level cache (e.g.,
map[string]*regexp.Regexp guarded by sync.RWMutex) or precompile regexes for the
known attribute names used by rebuildEmulatedImgTag, and update xmlAttrValue to
fetch the regexp from that cache (or the precompiled set) before calling
FindStringSubmatch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bf83c279-ba50-46ec-ad2a-c939b2196c35

📥 Commits

Reviewing files that changed from the base of the PR and between 4bc02c5 and 43b811e.

📒 Files selected for processing (2)
  • shortcuts/doc/docs_update_emulate.go
  • shortcuts/doc/docs_update_emulate_test.go

Address CodeRabbit review on PR larksuite#1427:
- TestDocsUpdateEmulateValidation now asserts the typed error envelope
  (errs.ProblemOf subtype + *errs.ValidationError.Param) per case instead
  of relying on message substrings alone; substring check kept as secondary.
- xmlAttrValue memoizes its per-attribute-name regex via sync.Map so
  re-inserting image-heavy docs no longer recompiles the same pattern on
  every call.
Add doc comments to the remaining undocumented functions in the emulate
fallback and its tests, raising docstring coverage from 42% to 100% to
satisfy the CodeRabbit threshold. Comments only; no behavior change.
… test

Replace the inline ProblemOf/errors.As block in TestDocsUpdateEmulateValidation
with the package's assertValidationContract helper (already used elsewhere in
this file), matching the established Doc error-contract test idiom and also
pinning Category/Params. No behavior change.
@leeguooooo

Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai — picked up both items from the 43b811e pass.

1. Typed-error assertions in TestDocsUpdateEmulateValidation — fixed (c09ad57, refined in 28326ec). The four validation cases now pin the typed envelope instead of err.Error() substrings, routed through the package's shared assertValidationContract helper (same one already used by the unsupported-type / missing-href cases in this file), so each case asserts CategoryValidation + SubtypeInvalidArgument + the offending Param (--emulate, --revision-id, --src-block-ids, --src-block-ids). The message-substring check is kept as a secondary assertion so the human-facing wording stays covered.

2. xmlAttrValue regex recompilation — fixed (c09ad57). It now memoizes the per-attribute-name regex via a package-level sync.Map, so re-inserting an image-heavy document no longer recompiles the same pattern on every lookup. Kept it on the cold emulation path only; no change to the default (non---emulate) flow.

Also raised docstring coverage on both touched files from 42% to 100% (cc4e47c) to clear the coverage gate — comments only, no behavior change.

Re-verified: full -race unit suite (217 green), go vet, gofmt -l (no output) all clean.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

@leeguooooo Both fixes look solid:

  1. Typed-error assertions — routing all four TestDocsUpdateEmulateValidation cases through assertValidationContract (with the message-substring check retained as a secondary layer) gives consistent, complete coverage of the typed envelope across the whole test file. Clean.

  2. xmlAttrValue regex memoization — the sync.Map cache eliminates the per-call recompilation on the emulation path without touching the default flow at all. Correct scope for an opt-in flag.

The docstring sweep to 100% is a nice finishing touch. All green across -race, vet, and gofmt — this one looks ready to land. 🎉

ʕ •ᴥ•ʔ ✨

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain/ccm PR touches the ccm domain size/L Large or sensitive change across domains or core paths

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant