Skip to content

feat: add IM read status shortcuts - #2318

Open
os-zzw wants to merge 8 commits into
larksuite:mainfrom
os-zzw:feat/im-read-status-shortcuts-pr
Open

feat: add IM read status shortcuts#2318
os-zzw wants to merge 8 commits into
larksuite:mainfrom
os-zzw:feat/im-read-status-shortcuts-pr

Conversation

@os-zzw

@os-zzw os-zzw commented Aug 12, 2026

Copy link
Copy Markdown

Summary

Add two focused IM shortcuts for message read-status APIs that support user access tokens: querying whether the current user read messages, and listing users who read one message. Read-users additionally supports tenant access tokens.

Changes

  • Add im +messages-read-status for the new POST /open-apis/im/v1/messages/read_status API.
  • Preserve the separate existing batch_get_read_status API; this PR does not call or rename it.
  • Use the allowlisted im:message:get_as_user scope and the new items plus invalid_message_ids response contract.
  • Add im +message-read-users for user and bot identities with identity-specific scopes and bounded automatic pagination.
  • Add unit contracts, CLI dry-run E2E coverage, coverage inventory entries, and Lark IM skill guidance.

Test Plan

  • Required GitHub Actions CI checks pass.
  • gofmt reports no changed Go files.
  • git diff --check passes.
  • Dry-run contract pins POST /open-apis/im/v1/messages/read_status and the message_ids body.
  • Manual dry-run confirms +messages-read-status --as bot returns a typed identity validation error with empty stdout.
  • User-access-token smoke for +messages-read-status after Registry publication.
  • User-access-token and tenant-access-token smoke for +message-read-users after Registry publication.

Local Go tests were intentionally not run; repository CI is the executable Go test gate for this branch.

Related

Merge Order

  1. Merge and publish Registry MR !128.
  2. Complete CI and online UAT evidence for this PR.
  3. Merge this PR.

Summary by CodeRabbit

  • New Features

    • Added commands to list users who read a message, with pagination and user or bot authentication.
    • Added batch read-status queries for 1–50 messages.
    • Added input validation, dry-run previews, progress details, and pagination metadata.
    • Improved permission error guidance for user-authenticated requests.
  • Documentation

    • Documented usage, permissions, identity restrictions, response fields, and troubleshooting guidance.
  • Tests

    • Added coverage for validation, request generation, pagination, authentication, and dry-run behavior.

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c801fd8e-d6e3-4908-aa65-7c040c2bd216

📥 Commits

Reviewing files that changed from the base of the PR and between f2609ea and afa1fd4.

📒 Files selected for processing (4)
  • shortcuts/im/im_messages_read_status.go
  • skills/lark-im/SKILL.md
  • skills/lark-im/references/lark-im-message-read-status.md
  • tests/cli_e2e/im/message_read_status_dryrun_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
  • shortcuts/im/im_messages_read_status.go
  • skills/lark-im/references/lark-im-message-read-status.md
  • skills/lark-im/SKILL.md
  • tests/cli_e2e/im/message_read_status_dryrun_test.go

📝 Walkthrough

Walkthrough

The PR adds IM shortcuts for paginated message readers and batch message read-status queries. It adds identity-specific validation, pagination safeguards, user-scope error normalization, registration, documentation, and dry-run coverage.

Changes

IM read-status operations

Layer / File(s) Summary
Paginated read-users shortcut
shortcuts/im/im_message_read_users.go, shortcuts/im/im_message_read_users_test.go
Adds +message-read-users with user and bot identity support, validation, automatic pagination, result aggregation, error normalization, and pagination metadata.
Batch read-status shortcut
shortcuts/im/im_messages_read_status.go, shortcuts/im/im_messages_read_status_test.go, shortcuts/im/im_allowlisted_scope.go
Adds +messages-read-status with validation for 1–50 message IDs, a batch POST request, and normalized user-scope errors.
Registration, documentation, and dry-run coverage
shortcuts/im/shortcuts.go, skills/lark-im/*, tests/cli_e2e/im/*
Registers both shortcuts and documents their scopes, restrictions, usage, pagination, API behavior, and dry-run coverage.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant CLI
  participant IMShortcut
  participant LarkIMAPI
  CLI->>IMShortcut: Validate flags and build request
  IMShortcut->>LarkIMAPI: Query read users or batch read status
  LarkIMAPI-->>IMShortcut: Return results and pagination data
  IMShortcut-->>CLI: Output results and metadata
Loading

Possibly related PRs

Suggested labels: feature

Suggested reviewers: liangshuo-1

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description check ✅ Passed The description covers the summary, changes, testing status, related work, and merge order; it is mostly complete despite using a different heading for related issues.
Title check ✅ Passed The title clearly identifies the main change: new IM message read-status shortcuts.
✨ 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.

@github-actions github-actions Bot added domain/im PR touches the im domain size/L Large or sensitive change across domains or core paths labels Aug 12, 2026
@os-zzw
os-zzw marked this pull request as ready for review August 12, 2026 07:44

@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: 7

🤖 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/im/im_message_read_users_test.go`:
- Around line 49-59: Add live and dry-run E2E tests for ImMessageReadUsers that
exercise read-user retrieval with both user and bot identities against the API,
rather than only validating static scopes. Make the tests self-contained by
creating required resources, invoking the shortcut, asserting results for each
identity and dry-run behavior, and cleaning up all resources afterward.
- Around line 140-144: Update the validation test table and its assertions
around validateMessageReadUsers to include the expected parameter name for each
case. Replace errs.ProblemOf-based checking with errors.As into
*errs.ValidationError, then assert Category, Subtype, and Param for every
validation error; do not add cause assertions for these direct branches.
- Around line 80-121: Extend TestFetchMessageReadUsersAggregatesPages with
regression coverage for both pagination termination guards in
fetchMessageReadUsers: verify a positive page limit stops requests at the
configured cap, and verify an unchanged page token stops pagination without
repeated requests. Add explicit request-count assertions for each scenario so
reverting either guard fails the tests.

In `@shortcuts/im/im_message_read_users.go`:
- Around line 120-127: Update the API response handling in the read-users
shortcut to decode the result into a typed page structure containing typed
read-user items and pagination metadata before aggregation. Replace direct map
access to data["items"] and common.PaginationMeta(data) with the typed fields,
and return decoding/type errors instead of silently treating malformed responses
as empty.

In `@shortcuts/im/im_messages_read_status_test.go`:
- Around line 48-50: Add a table-driven case in the read-status validation tests
alongside “more than fifty” that supplies exactly 50 IDs from
makeReadStatusMessageIDs and expects acceptance. Keep the existing 51-ID
rejection case so the boundary between accepted and rejected counts is covered.
- Around line 43-64: Strengthen
TestBuildMessagesReadStatusBodyRejectsInvalidInputs by asserting
problem.Category is errs.CategoryValidation, then use errors.As to extract an
*errs.ValidationError and verify its Param identifies --message-ids. Keep the
existing invalid-subtype assertion and apply these checks to each validation
error.

In `@tests/cli_e2e/im/message_read_status_dryrun_test.go`:
- Around line 17-84: The dry-run-only coverage for
TestIMMessagesReadStatusDryRun and TestIMMessageReadUsersDryRun is insufficient:
add self-contained live E2E workflows using UAT for +messages-read-status and
all supported identities for +message-read-users, including resource creation,
cleanup, and assertions on actual response fields. In
tests/cli_e2e/im/message_read_status_dryrun_test.go lines 17-84, add the live
tests; in tests/cli_e2e/im/coverage.md lines 31-33, keep these commands marked
uncovered or blocked until live coverage is implemented.
🪄 Autofix

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 Plus

Run ID: 1ff454dc-058e-45ae-8667-fd32f389ffac

📥 Commits

Reviewing files that changed from the base of the PR and between 1e87f67 and 6c29bd3.

📒 Files selected for processing (9)
  • shortcuts/im/im_message_read_users.go
  • shortcuts/im/im_message_read_users_test.go
  • shortcuts/im/im_messages_read_status.go
  • shortcuts/im/im_messages_read_status_test.go
  • shortcuts/im/shortcuts.go
  • skills/lark-im/SKILL.md
  • skills/lark-im/references/lark-im-message-read-status.md
  • tests/cli_e2e/im/coverage.md
  • tests/cli_e2e/im/message_read_status_dryrun_test.go

Comment on lines +49 to +59
func TestMessageReadUsersScopesByIdentity(t *testing.T) {
if !reflect.DeepEqual(ImMessageReadUsers.ScopesForIdentity("user"), []string{"im:message:get_as_user"}) {
t.Fatalf("user scopes = %v", ImMessageReadUsers.ScopesForIdentity("user"))
}
if !reflect.DeepEqual(ImMessageReadUsers.ScopesForIdentity("bot"), []string{"im:message:readonly"}) {
t.Fatalf("bot scopes = %v", ImMessageReadUsers.ScopesForIdentity("bot"))
}
if !reflect.DeepEqual(ImMessageReadUsers.AuthTypes, []string{"user", "bot"}) {
t.Fatalf("AuthTypes = %v", ImMessageReadUsers.AuthTypes)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Add live E2E coverage for this new shortcut.

These tests verify static scope configuration only. They do not verify read-user retrieval with user and bot identities against the API.

Add self-contained live E2E coverage that creates, uses, and cleans up its required resources.

As per coding guidelines, “new shortcuts require live E2E coverage.” Based on learnings, shortcut changes also require dry-run E2E coverage.

🤖 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/im/im_message_read_users_test.go` around lines 49 - 59, Add live
and dry-run E2E tests for ImMessageReadUsers that exercise read-user retrieval
with both user and bot identities against the API, rather than only validating
static scopes. Make the tests self-contained by creating required resources,
invoking the shortcut, asserting results for each identity and dry-run behavior,
and cleaning up all resources afterward.

Sources: Coding guidelines, Learnings

Comment on lines +80 to +121
func TestFetchMessageReadUsersAggregatesPages(t *testing.T) {
calls := 0
transport := shortcutRoundTripFunc(func(req *http.Request) (*http.Response, error) {
if req.URL.Path != "/open-apis/im/v1/messages/om_test/read_users" {
return nil, fmt.Errorf("unexpected path: %s", req.URL.Path)
}
calls++
if req.URL.Query().Get("page_token") == "" {
return shortcutJSONResponse(200, map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"items": []interface{}{map[string]interface{}{"user_id": "ou_one", "timestamp": "1"}},
"has_more": true,
"page_token": "next",
},
}), nil
}
return shortcutJSONResponse(200, map[string]interface{}{
"code": 0,
"data": map[string]interface{}{
"items": []interface{}{map[string]interface{}{"user_id": "ou_two", "tenant_key": "tenant"}},
"has_more": false,
},
}), nil
})
runtime := newMessageReadUsersTestRuntime(t, transport, map[string]string{"message-id": "om_test"}, map[string]bool{"page-all": true}, map[string]int{"page-limit": 0})

got, err := fetchMessageReadUsers(context.Background(), runtime)
if err != nil {
t.Fatalf("fetchMessageReadUsers() error = %v", err)
}
if calls != 2 {
t.Fatalf("calls = %d, want 2", calls)
}
items, _ := got["items"].([]interface{})
if len(items) != 2 {
t.Fatalf("len(items) = %d, want 2", len(items))
}
if got["has_more"] != false || got["page_token"] != "" || got["total"] != 2 {
t.Fatalf("result metadata = %#v", got)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Cover pagination stop conditions.

This test uses --page-limit=0 and an advancing token. It does not verify the positive page-limit cap or the non-advancing-token guard.

Add request-count assertions for both conditions. Removing either termination guard at Lines 131-137 would currently leave the suite passing.

As per coding guidelines, “Every behavior change requires a nearby regression test that fails when the implementation is reverted.”

🤖 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/im/im_message_read_users_test.go` around lines 80 - 121, Extend
TestFetchMessageReadUsersAggregatesPages with regression coverage for both
pagination termination guards in fetchMessageReadUsers: verify a positive page
limit stops requests at the configured cap, and verify an unchanged page token
stops pagination without repeated requests. Add explicit request-count
assertions for each scenario so reverting either guard fails the tests.

Source: Coding guidelines

Comment on lines +140 to +144
err := validateMessageReadUsers(runtime)
problem, ok := errs.ProblemOf(err)
if !ok || problem.Subtype != errs.SubtypeInvalidArgument {
t.Fatalf("problem = %#v, err = %v", problem, err)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert complete validation metadata.

Each case asserts only Subtype. Add expected parameter names to the table. Use errors.As with *errs.ValidationError to assert Category, Subtype, and Param.

No wrapped cause exists in these direct validation branches, so cause preservation is not applicable here.

As per coding guidelines, “Error tests must assert typed metadata and cause preservation rather than message text alone.” Based on learnings, errs.ProblemOf does not expose ValidationError.Param.

🤖 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/im/im_message_read_users_test.go` around lines 140 - 144, Update
the validation test table and its assertions around validateMessageReadUsers to
include the expected parameter name for each case. Replace errs.ProblemOf-based
checking with errors.As into *errs.ValidationError, then assert Category,
Subtype, and Param for every validation error; do not add cause assertions for
these direct branches.

Sources: Coding guidelines, Learnings

Comment on lines +120 to +127
data, err := runtime.CallAPITyped(http.MethodGet, apiPath, params, nil)
if err != nil {
return nil, err
}
if pageItems, ok := data["items"].([]interface{}); ok {
items = append(items, pageItems...)
}
hasMore, nextToken = common.PaginationMeta(data)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Project the API response into typed structs.

Lines 120-127 consume API fields through map[string]interface{}. An unexpected field type silently produces an empty item list or pagination metadata.

Decode the response at this shortcut boundary into a typed page struct and typed read-user item struct before aggregation.

As per coding guidelines, “project loose-map fields into typed structs at new API boundaries.”

🤖 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/im/im_message_read_users.go` around lines 120 - 127, Update the API
response handling in the read-users shortcut to decode the result into a typed
page structure containing typed read-user items and pagination metadata before
aggregation. Replace direct map access to data["items"] and
common.PaginationMeta(data) with the typed fields, and return decoding/type
errors instead of silently treating malformed responses as empty.

Source: Coding guidelines

Comment on lines +43 to +64
func TestBuildMessagesReadStatusBodyRejectsInvalidInputs(t *testing.T) {
tests := []struct {
name string
messageIDs string
}{
{name: "empty", messageIDs: ""},
{name: "invalid prefix", messageIDs: "oc_not_message"},
{name: "more than fifty", messageIDs: strings.Join(makeReadStatusMessageIDs(51), ",")},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
runtime := newMessagesReadStatusTestRuntime(t, tt.messageIDs)
_, err := buildMessagesReadStatusBody(runtime)
problem, ok := errs.ProblemOf(err)
if !ok {
t.Fatalf("errs.ProblemOf() ok = false, err = %v", err)
}
if problem.Subtype != errs.SubtypeInvalidArgument {
t.Fatalf("problem.Subtype = %q, want %q", problem.Subtype, errs.SubtypeInvalidArgument)
}
})

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert the validation category and parameter.

The test accepts an error that loses the --message-ids parameter metadata. Assert CategoryValidation and use errors.As with *errs.ValidationError to assert Param.

Proposed test update
 import (
+	"errors"
 	"fmt"
 	"reflect"
@@
 			if problem.Subtype != errs.SubtypeInvalidArgument {
 				t.Fatalf("problem.Subtype = %q, want %q", problem.Subtype, errs.SubtypeInvalidArgument)
 			}
+			if problem.Category != errs.CategoryValidation {
+				t.Fatalf("problem.Category = %q, want %q", problem.Category, errs.CategoryValidation)
+			}
+			var validationErr *errs.ValidationError
+			if !errors.As(err, &validationErr) {
+				t.Fatalf("errors.As(*errs.ValidationError) = false, err = %v", err)
+			}
+			if validationErr.Param != "--message-ids" {
+				t.Fatalf("validationErr.Param = %q, want --message-ids", validationErr.Param)
+			}

As per coding guidelines, “Error tests must assert typed metadata and cause preservation rather than message text alone.” Based on learnings, use errors.As on *errs.ValidationError to inspect Param.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func TestBuildMessagesReadStatusBodyRejectsInvalidInputs(t *testing.T) {
tests := []struct {
name string
messageIDs string
}{
{name: "empty", messageIDs: ""},
{name: "invalid prefix", messageIDs: "oc_not_message"},
{name: "more than fifty", messageIDs: strings.Join(makeReadStatusMessageIDs(51), ",")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
runtime := newMessagesReadStatusTestRuntime(t, tt.messageIDs)
_, err := buildMessagesReadStatusBody(runtime)
problem, ok := errs.ProblemOf(err)
if !ok {
t.Fatalf("errs.ProblemOf() ok = false, err = %v", err)
}
if problem.Subtype != errs.SubtypeInvalidArgument {
t.Fatalf("problem.Subtype = %q, want %q", problem.Subtype, errs.SubtypeInvalidArgument)
}
})
func TestBuildMessagesReadStatusBodyRejectsInvalidInputs(t *testing.T) {
tests := []struct {
name string
messageIDs string
}{
{name: "empty", messageIDs: ""},
{name: "invalid prefix", messageIDs: "oc_not_message"},
{name: "more than fifty", messageIDs: strings.Join(makeReadStatusMessageIDs(51), ",")},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
runtime := newMessagesReadStatusTestRuntime(t, tt.messageIDs)
_, err := buildMessagesReadStatusBody(runtime)
problem, ok := errs.ProblemOf(err)
if !ok {
t.Fatalf("errs.ProblemOf() ok = false, err = %v", err)
}
if problem.Subtype != errs.SubtypeInvalidArgument {
t.Fatalf("problem.Subtype = %q, want %q", problem.Subtype, errs.SubtypeInvalidArgument)
}
if problem.Category != errs.CategoryValidation {
t.Fatalf("problem.Category = %q, want %q", problem.Category, errs.CategoryValidation)
}
var validationErr *errs.ValidationError
if !errors.As(err, &validationErr) {
t.Fatalf("errors.As(*errs.ValidationError) = false, err = %v", err)
}
if validationErr.Param != "--message-ids" {
t.Fatalf("validationErr.Param = %q, want --message-ids", validationErr.Param)
}
})
}
}
🤖 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/im/im_messages_read_status_test.go` around lines 43 - 64,
Strengthen TestBuildMessagesReadStatusBodyRejectsInvalidInputs by asserting
problem.Category is errs.CategoryValidation, then use errors.As to extract an
*errs.ValidationError and verify its Param identifies --message-ids. Keep the
existing invalid-subtype assertion and apply these checks to each validation
error.

Sources: Coding guidelines, Learnings

Comment on lines +48 to +50
{name: "empty", messageIDs: ""},
{name: "invalid prefix", messageIDs: "oc_not_message"},
{name: "more than fifty", messageIDs: strings.Join(makeReadStatusMessageIDs(51), ",")},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add an exact maximum-boundary acceptance test.

The suite rejects 51 IDs but does not accept 50 IDs. A regression from len(ids) > 50 to len(ids) >= 50 would pass these tests.

Proposed test
+func TestBuildMessagesReadStatusBodyAllowsMaximum(t *testing.T) {
+	runtime := newMessagesReadStatusTestRuntime(t, strings.Join(makeReadStatusMessageIDs(50), ","))
+
+	if _, err := buildMessagesReadStatusBody(runtime); err != nil {
+		t.Fatalf("buildMessagesReadStatusBody() error = %v", err)
+	}
+}
+
 func TestBuildMessagesReadStatusBodyRejectsInvalidInputs(t *testing.T) {

As per coding guidelines, every behavior change requires a nearby regression test that fails when the implementation is reverted.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{name: "empty", messageIDs: ""},
{name: "invalid prefix", messageIDs: "oc_not_message"},
{name: "more than fifty", messageIDs: strings.Join(makeReadStatusMessageIDs(51), ",")},
func TestBuildMessagesReadStatusBodyAllowsMaximum(t *testing.T) {
runtime := newMessagesReadStatusTestRuntime(t, strings.Join(makeReadStatusMessageIDs(50), ","))
if _, err := buildMessagesReadStatusBody(runtime); err != nil {
t.Fatalf("buildMessagesReadStatusBody() error = %v", err)
}
}
func TestBuildMessagesReadStatusBodyRejectsInvalidInputs(t *testing.T) {
{name: "empty", messageIDs: ""},
{name: "invalid prefix", messageIDs: "oc_not_message"},
{name: "more than fifty", messageIDs: strings.Join(makeReadStatusMessageIDs(51), ",")},
🤖 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/im/im_messages_read_status_test.go` around lines 48 - 50, Add a
table-driven case in the read-status validation tests alongside “more than
fifty” that supplies exactly 50 IDs from makeReadStatusMessageIDs and expects
acceptance. Keep the existing 51-ID rejection case so the boundary between
accepted and rejected counts is covered.

Source: Coding guidelines

Comment on lines +17 to +84
func TestIMMessagesReadStatusDryRun(t *testing.T) {
setMessageReadStatusDryRunEnv(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
t.Cleanup(cancel)

result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{
"im", "+messages-read-status",
"--message-ids", "om_one,om_two",
"--dry-run",
},
DefaultAs: "user",
})
require.NoError(t, err)
result.AssertExitCode(t, 0)
require.Equal(t, "user", clie2e.DryRunGet(result.Stdout, "identity").String())
require.Equal(t, http.MethodPost, clie2e.DryRunGet(result.Stdout, "api.0.method").String())
require.Equal(t, "/open-apis/im/v1/messages/batch_query_read_status", clie2e.DryRunGet(result.Stdout, "api.0.url").String())
require.Equal(t, "om_one", clie2e.DryRunGet(result.Stdout, "api.0.body.message_ids.0").String())
require.Equal(t, "om_two", clie2e.DryRunGet(result.Stdout, "api.0.body.message_ids.1").String())
}

func TestIMMessagesReadStatusRejectsBotIdentity(t *testing.T) {
setMessageReadStatusDryRunEnv(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
t.Cleanup(cancel)

result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{
"im", "+messages-read-status",
"--message-ids", "om_one",
"--dry-run",
},
DefaultAs: "bot",
})
require.NoError(t, err)
result.AssertExitCode(t, 2)
require.Empty(t, result.Stdout)
require.Equal(t, "validation", gjson.Get(result.Stderr, "error.type").String())
require.Equal(t, "invalid_argument", gjson.Get(result.Stderr, "error.subtype").String())
require.Equal(t, "--as", gjson.Get(result.Stderr, "error.param").String())
}

func TestIMMessageReadUsersDryRunSupportsUserAndBot(t *testing.T) {
setMessageReadStatusDryRunEnv(t)
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
t.Cleanup(cancel)

for _, identity := range []string{"user", "bot"} {
t.Run(identity, func(t *testing.T) {
result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{
"im", "+message-read-users",
"--message-id", "om_test",
"--dry-run",
},
DefaultAs: identity,
})
require.NoError(t, err)
result.AssertExitCode(t, 0)
require.Equal(t, identity, clie2e.DryRunGet(result.Stdout, "identity").String())
require.Equal(t, http.MethodGet, clie2e.DryRunGet(result.Stdout, "api.0.method").String())
require.Equal(t, "/open-apis/im/v1/messages/om_test/read_users", clie2e.DryRunGet(result.Stdout, "api.0.url").String())
require.Equal(t, "open_id", clie2e.DryRunGet(result.Stdout, "api.0.params.user_id_type").String())
require.Equal(t, int64(100), clie2e.DryRunGet(result.Stdout, "api.0.params.page_size").Int())
})
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Add live E2E coverage before marking the new shortcuts as covered.

Dry-run tests do not verify access-token authentication, scope enforcement, or API response handling. The PR objective also states that access-token smoke tests remain pending.

  • tests/cli_e2e/im/message_read_status_dryrun_test.go#L17-L84: add self-contained live workflows for +messages-read-status with UAT and +message-read-users with its supported identities. Create, use, and clean up test resources. Assert actual response fields.
  • tests/cli_e2e/im/coverage.md#L31-L33: mark these commands uncovered or blocked until the live workflows exist.

As per coding guidelines, “new shortcuts require live E2E coverage.” Based on learnings, changed shortcut flags and request parameters also require live coverage.

📍 Affects 2 files
  • tests/cli_e2e/im/message_read_status_dryrun_test.go#L17-L84 (this comment)
  • tests/cli_e2e/im/coverage.md#L31-L33
🤖 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 `@tests/cli_e2e/im/message_read_status_dryrun_test.go` around lines 17 - 84,
The dry-run-only coverage for TestIMMessagesReadStatusDryRun and
TestIMMessageReadUsersDryRun is insufficient: add self-contained live E2E
workflows using UAT for +messages-read-status and all supported identities for
+message-read-users, including resource creation, cleanup, and assertions on
actual response fields. In tests/cli_e2e/im/message_read_status_dryrun_test.go
lines 17-84, add the live tests; in tests/cli_e2e/im/coverage.md lines 31-33,
keep these commands marked uncovered or blocked until live coverage is
implemented.

Sources: Coding guidelines, Learnings

@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: 1

🤖 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/im/im_messages_read_status_test.go`:
- Around line 30-51: Add a sentinel cause to the PermissionError constructed as
source in TestNormalizeAllowlistedUserScopeErrorRemovesOAuthRecovery, then
assert errors.Is(got, cause) after normalization. Keep the existing typed
metadata assertions unchanged so the test verifies both normalized fields and
cause preservation.
🪄 Autofix

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 Plus

Run ID: 5dbeddd1-44c8-4964-ba1d-5a3844614b34

📥 Commits

Reviewing files that changed from the base of the PR and between 6c29bd3 and dfc0e81.

📒 Files selected for processing (6)
  • shortcuts/im/im_allowlisted_scope.go
  • shortcuts/im/im_message_read_users.go
  • shortcuts/im/im_message_read_users_test.go
  • shortcuts/im/im_messages_read_status.go
  • shortcuts/im/im_messages_read_status_test.go
  • skills/lark-im/references/lark-im-message-read-status.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • skills/lark-im/references/lark-im-message-read-status.md
  • shortcuts/im/im_messages_read_status.go
  • shortcuts/im/im_message_read_users.go
  • shortcuts/im/im_message_read_users_test.go

Comment on lines +30 to +51
func TestNormalizeAllowlistedUserScopeErrorRemovesOAuthRecovery(t *testing.T) {
source := errs.NewPermissionError(errs.SubtypeMissingScope, "missing allowlisted scope").
WithCode(99991679).
WithLogID("log-id").
WithMissingScopes("im:message.read_status:readonly").
WithHint("run auth login")

got := normalizeAllowlistedUserScopeError(source, core.AsUser, "im:message.read_status:readonly")
var permissionErr *errs.PermissionError
if !errors.As(got, &permissionErr) {
t.Fatalf("errors.As() = false, err = %v", got)
}
if len(permissionErr.MissingScopes) != 0 {
t.Fatalf("MissingScopes = %v, want none", permissionErr.MissingScopes)
}
if strings.Contains(permissionErr.Hint, "auth login") || !strings.Contains(permissionErr.Hint, "Scope platform") {
t.Fatalf("Hint = %q", permissionErr.Hint)
}
if permissionErr.Code != 99991679 || permissionErr.LogID != "log-id" {
t.Fatalf("server evidence was not preserved: %#v", permissionErr)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert cause preservation for the normalized error.

Add a sentinel cause to source. Assert errors.Is(got, cause) after normalization. The current test cannot detect a change that rebuilds the permission error and drops its cause.

As per coding guidelines, “Error tests must assert typed metadata and cause preservation rather than message text alone.” Based on learnings, verify preserved causes with errors.Is.

🤖 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/im/im_messages_read_status_test.go` around lines 30 - 51, Add a
sentinel cause to the PermissionError constructed as source in
TestNormalizeAllowlistedUserScopeErrorRemovesOAuthRecovery, then assert
errors.Is(got, cause) after normalization. Keep the existing typed metadata
assertions unchanged so the test verifies both normalized fields and cause
preservation.

Sources: Coding guidelines, Learnings

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

Labels

domain/im PR touches the im 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.

2 participants