Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion shortcuts/calendar/calendar_search_event.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ var CalendarSearchEvent = common.Shortcut{
Description: "Search calendar events by keyword, time range, and attendees",
Risk: "read",
Scopes: []string{"calendar:calendar.event:read"},
AuthTypes: []string{"user"},
AuthTypes: []string{"user", "bot"},
HasFormat: true,
Flags: []common.Flag{
{Name: "calendar-id", Desc: "calendar ID (default: primary)"},
Expand Down
2 changes: 2 additions & 0 deletions skills/lark-calendar/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ lark-cli calendar +get --calendar-id <calendar_id> --event-id <event_id>

仅返回基础字段(`event_id`/`summary`/`start`/`end` 等),需要详情请走 `+get`。

身份范围:用户身份搜索当前用户有权访问的日历;应用身份搜索应用自身有权访问的日历(例如应用主日历及应用创建的日程),不会自动获得用户个人日历的访问权限。

```bash
# query 按关键词 可选
# start/end 按时间范围(ISO 8601 或 YYYY-MM-DD)可选
Expand Down
130 changes: 130 additions & 0 deletions tests/cli_e2e/calendar/calendar_search_event_workflow_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright (c) 2026 Lark Technologies Pte. Ltd.
// SPDX-License-Identifier: MIT

package calendar

import (
"context"
"fmt"
"testing"
"time"

clie2e "github.com/larksuite/cli/tests/cli_e2e"
"github.com/stretchr/testify/require"
"github.com/tidwall/gjson"
)

func TestCalendar_SearchEventDryRunAsBot(t *testing.T) {
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir())
t.Setenv("LARKSUITE_CLI_APP_ID", "app")
t.Setenv("LARKSUITE_CLI_APP_SECRET", "secret")
t.Setenv("LARKSUITE_CLI_BRAND", "feishu")

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
t.Cleanup(cancel)

result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{
"calendar", "+search-event",
"--calendar-id", "cal_dry",
"--query", "calendar-e2e-search",
"--start", "2026-07-22T10:00:00+08:00",
"--end", "2026-07-22T11:00:00+08:00",
"--dry-run",
},
DefaultAs: "bot",
})
require.NoError(t, err)
result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, true)

require.Equal(t, "POST", clie2e.DryRunGet(result.Stdout, "api.0.method").String(), "stdout:\n%s", result.Stdout)
require.Equal(t, "/open-apis/calendar/v4/calendars/cal_dry/events/search_event", clie2e.DryRunGet(result.Stdout, "api.0.url").String(), "stdout:\n%s", result.Stdout)
require.Equal(t, "cal_dry", clie2e.DryRunGet(result.Stdout, "calendar_id").String(), "stdout:\n%s", result.Stdout)
}

func TestCalendar_SearchEventWorkflowAsBot(t *testing.T) {
clie2e.SkipWithoutTenantAccessToken(t)
parentT := t
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
t.Cleanup(cancel)

calendarID := getPrimaryCalendarID(t, ctx)
eventSummary := "calendar-e2e-search-" + clie2e.GenerateSuffix()
startAt := time.Now().UTC().Add(time.Hour).Truncate(time.Minute)
endAt := startAt.Add(30 * time.Minute)

var eventID string
t.Run("create searchable event as bot", func(t *testing.T) {
result, err := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{
"calendar", "+create",
"--calendar-id", calendarID,
"--summary", eventSummary,
"--start", startAt.Format(time.RFC3339),
"--end", endAt.Format(time.RFC3339),
},
DefaultAs: "bot",
})
require.NoError(t, err)
result.AssertExitCode(t, 0)
result.AssertStdoutStatus(t, true)

eventID = gjson.Get(result.Stdout, "data.event_id").String()
require.NotEmpty(t, eventID, "stdout:\n%s", result.Stdout)

parentT.Cleanup(func() {
if eventID == "" {
return
}

cleanupCtx, cleanupCancel := clie2e.CleanupContext()
defer cleanupCancel()
deleteResult, deleteErr := clie2e.RunCmd(cleanupCtx, clie2e.Request{
Args: []string{"calendar", "events", "delete"},
DefaultAs: "bot",
Params: map[string]any{
"calendar_id": calendarID,
"event_id": eventID,
},
})
clie2e.ReportCleanupFailure(parentT, "delete event "+eventID, deleteResult, deleteErr)
})
})

t.Run("find created event with shortcut as bot", func(t *testing.T) {
require.NotEmpty(t, eventID)

var lastResult *clie2e.Result
err := clie2e.WaitForCondition(ctx, clie2e.WaitOptions{
Timeout: 30 * time.Second,
Interval: 2 * time.Second,
TimeoutError: func() error {
if lastResult == nil {
return fmt.Errorf("created event was not searchable before timeout")
}
return fmt.Errorf("created event was not searchable before timeout; stdout=%s stderr=%s", lastResult.Stdout, lastResult.Stderr)
},
}, func() (bool, error) {
result, runErr := clie2e.RunCmd(ctx, clie2e.Request{
Args: []string{
"calendar", "+search-event",
"--calendar-id", calendarID,
"--query", eventSummary,
"--start", startAt.Add(-time.Minute).Format(time.RFC3339),
"--end", endAt.Add(time.Minute).Format(time.RFC3339),
},
DefaultAs: "bot",
})
if runErr != nil {
return false, runErr
}
lastResult = result
if result.ExitCode != 0 {
return false, fmt.Errorf("search command failed: exit=%d stderr=%s", result.ExitCode, result.Stderr)
}
return gjson.Get(result.Stdout, `data.items.#(event_id=="`+eventID+`").event_id`).String() == eventID, nil
})
require.NoError(t, err)
})
}
18 changes: 13 additions & 5 deletions tests/cli_e2e/calendar/coverage.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
# Calendar CLI E2E Coverage

## Metrics
- Denominator: 23 leaf commands
- Covered: 11
- Coverage: 47.8%
- Denominator: 28 leaf commands
- Covered: 13
- Coverage: 46.4%

## Summary
- TestCalendar_ViewAgenda: proves the user shortcut `calendar +agenda`; key `t.Run(...)` proof points are `view today agenda as user`, `view agenda with date range as user`, and `view agenda with pretty format as user`.
- TestCalendar_PersonalEventWorkflowAsUser: proves a self-contained user event workflow across `calendar calendars primary`, `calendar +create`, `calendar events get`, and `calendar +agenda`; key `t.Run(...)` proof points are `get primary calendar as user`, `create personal event with shortcut as user`, `get created event as user`, and `find created event in agenda as user`.
- TestCalendar_RSVPWorkflowAsUser: proves the user shortcuts `calendar +freebusy` and `calendar +rsvp`; key `t.Run(...)` proof points are `query freebusy as user`, `reply tentative as user`, `verify tentative freebusy as user`, `reply accept as user`, and `verify accepted freebusy as user`.
- TestCalendar_SearchEventDryRunAsBot: proves `calendar +search-event` accepts bot identity and produces the expected `search_event` request path without live credentials.
- TestCalendar_SearchEventWorkflowAsBot: proves a self-contained bot workflow across `calendar +create`, `calendar +search-event`, and `calendar events delete`; key `t.Run(...)` proof points are `create searchable event as bot` and `find created event with shortcut as bot`.
- TestCalendar_UpdateDryRun: proves `calendar +update` request construction as bot, including event mutation and attendee add/remove calls.
- TestCalendar_CreateEvent: proves `calendar +create`, `calendar events get`, and `calendar events delete`; key `t.Run(...)` proof points are `create event with shortcut as bot`, `verify event created as bot`, and `delete event as bot`.
- TestCalendar_ManageCalendar: proves `calendar calendars primary`, `calendar calendars create`, `calendar calendars get`, and `calendar calendars patch`; key `t.Run(...)` proof points are `get primary calendar as bot`, `create calendar as bot`, `get created calendar as bot`, and `update calendar as bot`.
- Cleanup note: `calendar calendars delete` is part of the calendar lifecycle workflow and is counted as covered because the workflow proves the full shared-calendar lifecycle.
- Blocked area: direct `event.attendees *` APIs, `calendar calendars search`, `calendar events create|instance_view|patch|search`, `calendar freebusys list`, and planning shortcuts `calendar +room-find` / `calendar +suggestion` still need deterministic workflows; the planning shortcuts currently depend on live tenant availability and room inventory, so they remain uncovered.
- Blocked area: direct `event.attendees *` APIs, `calendar calendars search`, `calendar events create|instance_view|patch|search_event|share_info`, `calendar freebusys list`, and shortcuts `calendar +get` / `calendar +meeting` / `calendar +room-find` / `calendar +suggestion` still need deterministic workflows; the planning shortcuts currently depend on live tenant availability and room inventory, so they remain uncovered.

## Command Table

Expand All @@ -21,9 +24,13 @@
| ✓ | calendar +agenda | shortcut | calendar_view_agenda_test.go::TestCalendar_ViewAgenda; calendar_personal_event_workflow_test.go::TestCalendar_PersonalEventWorkflowAsUser/find created event in agenda as user | default today; `--start`; `--end`; `--format pretty` | user identity readback plus general agenda view |
| ✓ | calendar +create | shortcut | calendar_create_event_test.go::TestCalendar_CreateEvent/create event with shortcut as bot; calendar_personal_event_workflow_test.go::TestCalendar_PersonalEventWorkflowAsUser/create personal event with shortcut as user | `--summary`; `--start`; `--end`; `--calendar-id`; `--description` | bot and user workflow coverage |
| ✓ | calendar +freebusy | shortcut | calendar_rsvp_workflow_test.go::TestCalendar_RSVPWorkflowAsUser/query freebusy as user; calendar_rsvp_workflow_test.go::TestCalendar_RSVPWorkflowAsUser/verify tentative freebusy as user; calendar_rsvp_workflow_test.go::TestCalendar_RSVPWorkflowAsUser/verify accepted freebusy as user | default current user; `--start`; `--end` | user identity flow |
| ✕ | calendar +get | shortcut | | none | no direct shortcut workflow yet |
| ✕ | calendar +meeting | shortcut | | none | requires an event with linked meeting artifacts |
| ✕ | calendar +room-find | shortcut | | none | no deterministic self-contained workflow yet; output depends on live room inventory |
| ✓ | calendar +rsvp | shortcut | calendar_rsvp_workflow_test.go::TestCalendar_RSVPWorkflowAsUser/reply tentative as user; calendar_rsvp_workflow_test.go::TestCalendar_RSVPWorkflowAsUser/reply accept as user | `--calendar-id`; `--event-id`; `--rsvp-status` | user reply flow |
| ✓ | calendar +search-event | shortcut | calendar_search_event_workflow_test.go::TestCalendar_SearchEventDryRunAsBot; calendar_search_event_workflow_test.go::TestCalendar_SearchEventWorkflowAsBot/find created event with shortcut as bot | bot identity; `--calendar-id`; `--query`; `--start`; `--end`; `--dry-run` | bot-owned event search plus dry-run request contract |
| ✕ | calendar +suggestion | shortcut | | none | no deterministic self-contained workflow yet; output depends on live availability suggestions |
| ✓ | calendar +update | shortcut | calendar_update_dryrun_test.go::TestCalendar_UpdateDryRun | event fields plus attendee add/remove under bot identity | dry-run request contract |
| ✓ | calendar calendars create | api | calendar_manage_calendar_test.go::TestCalendar_ManageCalendar/create calendar as bot | `summary`; `description` in `--data` | |
| ✓ | calendar calendars delete | api | calendar_manage_calendar_test.go::TestCalendar_ManageCalendar/delete calendar as bot | `calendar_id` in `--params` | |
| ✓ | calendar calendars get | api | calendar_manage_calendar_test.go::TestCalendar_ManageCalendar/get created calendar as bot; calendar_manage_calendar_test.go::TestCalendar_ManageCalendar/verify updated calendar as bot | `calendar_id` in `--params` | |
Expand All @@ -36,7 +43,8 @@
| ✓ | calendar events get | api | calendar_create_event_test.go::TestCalendar_CreateEvent/verify event created as bot; calendar_personal_event_workflow_test.go::TestCalendar_PersonalEventWorkflowAsUser/get created event as user | `calendar_id`; `event_id` in `--params` | bot and user read-after-write coverage |
| ✕ | calendar events instance_view | api | | none | `+agenda` is indirect orchestration, not direct API coverage |
| ✕ | calendar events patch | api | | none | no direct event-update workflow yet |
| ✕ | calendar events search | api | | none | no search workflow yet |
| ✕ | calendar events search_event | api | | none | only covered indirectly through `calendar +search-event` |
| ✕ | calendar events share_info | api | | none | no event sharing workflow yet |
| ✕ | calendar freebusys list | api | | none | no direct freebusy API workflow yet |
| ✕ | calendar event.attendees batch_delete | api | | none | requires an isolated attendee lifecycle workflow |
| ✕ | calendar event.attendees create | api | | none | requires an isolated attendee lifecycle workflow |
Expand Down
Loading