Skip to content

Commit e02c442

Browse files
authored
feat: support event share link and error details (#583)
* feat: support event share info Change-Id: I4876df38effe44de04e587ac18ace7e230c9fa3a * fix: return detail err info for calendar
1 parent fbed6be commit e02c442

6 files changed

Lines changed: 407 additions & 0 deletions

File tree

shortcuts/calendar/calendar_agenda.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func fetchInstanceViewRange(ctx context.Context, runtime *common.RuntimeContext,
5454
"start_time": fmt.Sprintf("%d", startTime),
5555
"end_time": fmt.Sprintf("%d", endTime),
5656
}, nil)
57+
err = wrapPredefinedError(err)
5758
if err != nil {
5859
return nil, output.Errorf(output.ExitAPI, "api_error", "API call failed: %s", err)
5960
}

shortcuts/calendar/calendar_create.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,7 @@ var CalendarCreate = common.Shortcut{
194194
data, err := runtime.CallAPI("POST",
195195
fmt.Sprintf("/open-apis/calendar/v4/calendars/%s/events", validate.EncodePathSegment(calendarId)),
196196
nil, eventData)
197+
err = wrapPredefinedError(err)
197198
if err != nil {
198199
return err
199200
}
@@ -221,11 +222,13 @@ var CalendarCreate = common.Shortcut{
221222
"attendees": attendees,
222223
"need_notification": true,
223224
})
225+
err = wrapPredefinedError(err)
224226
if err != nil {
225227
// Rollback: delete the event
226228
_, rollbackErr := runtime.RawAPI("DELETE",
227229
fmt.Sprintf("/open-apis/calendar/v4/calendars/%s/events/%s", validate.EncodePathSegment(calendarId), validate.EncodePathSegment(eventId)),
228230
map[string]interface{}{"need_notification": false}, nil)
231+
rollbackErr = wrapPredefinedError(rollbackErr)
229232
if rollbackErr != nil {
230233
return output.Errorf(output.ExitAPI, "api_error", "failed to add attendees: %v; rollback also failed, orphan event_id=%s needs manual cleanup", rollbackErr, eventId)
231234
}

shortcuts/calendar/calendar_freebusy.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ var CalendarFreebusy = common.Shortcut{
102102
"user_id": userId,
103103
"need_rsvp_status": true,
104104
})
105+
err = wrapPredefinedError(err)
105106
if err != nil {
106107
return err
107108
}

shortcuts/calendar/calendar_test.go

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,238 @@ func TestCreate_NoEventIdReturned(t *testing.T) {
375375
}
376376
}
377377

378+
func TestCreate_CreateEvent_InvalidParamsWithDetail(t *testing.T) {
379+
f, _, _, reg := cmdutil.TestFactory(t, defaultConfig())
380+
381+
reg.Register(&httpmock.Stub{
382+
Method: "POST",
383+
URL: "/open-apis/calendar/v4/calendars/cal_test123/events",
384+
Body: map[string]interface{}{
385+
"code": errCodeInvalidParamsWithDetail,
386+
"msg": "invalid params",
387+
"error": map[string]interface{}{
388+
"details": []interface{}{
389+
map[string]interface{}{"value": "end_time should be later than start_time"},
390+
},
391+
},
392+
},
393+
})
394+
395+
err := mountAndRun(t, CalendarCreate, []string{
396+
"+create",
397+
"--summary", "Bad Time",
398+
"--start", "2025-03-21T10:00:00+08:00",
399+
"--end", "2025-03-21T11:00:00+08:00",
400+
"--calendar-id", "cal_test123",
401+
"--as", "bot",
402+
}, f, nil)
403+
404+
if err == nil {
405+
t.Fatal("expected error for 190014, got nil")
406+
}
407+
var exitErr *output.ExitError
408+
if !errors.As(err, &exitErr) {
409+
t.Fatalf("expected *output.ExitError, got %T", err)
410+
}
411+
if exitErr.Detail.Code != errCodeInvalidParamsWithDetail {
412+
t.Errorf("expected code %d, got %d", errCodeInvalidParamsWithDetail, exitErr.Detail.Code)
413+
}
414+
if !strings.Contains(exitErr.Detail.Message, "end_time should be later than start_time") {
415+
t.Errorf("expected detail value in message, got %q", exitErr.Detail.Message)
416+
}
417+
}
418+
419+
func TestCreate_CreateEvent_InvalidParamsWithoutDetailValue(t *testing.T) {
420+
f, _, _, reg := cmdutil.TestFactory(t, defaultConfig())
421+
422+
reg.Register(&httpmock.Stub{
423+
Method: "POST",
424+
URL: "/open-apis/calendar/v4/calendars/cal_test123/events",
425+
Body: map[string]interface{}{
426+
"code": errCodeInvalidParamsWithDetail,
427+
"msg": "invalid params",
428+
},
429+
})
430+
431+
err := mountAndRun(t, CalendarCreate, []string{
432+
"+create",
433+
"--summary", "Bad Time",
434+
"--start", "2025-03-21T10:00:00+08:00",
435+
"--end", "2025-03-21T11:00:00+08:00",
436+
"--calendar-id", "cal_test123",
437+
"--as", "bot",
438+
}, f, nil)
439+
440+
if err == nil {
441+
t.Fatal("expected error for 190014, got nil")
442+
}
443+
var exitErr *output.ExitError
444+
if !errors.As(err, &exitErr) {
445+
t.Fatalf("expected *output.ExitError, got %T", err)
446+
}
447+
if exitErr.Detail.Code != errCodeInvalidParamsWithDetail {
448+
t.Errorf("expected code %d, got %d", errCodeInvalidParamsWithDetail, exitErr.Detail.Code)
449+
}
450+
}
451+
452+
func TestCreate_CreateEvent_InvalidParams_ErrorNotMap(t *testing.T) {
453+
f, _, _, reg := cmdutil.TestFactory(t, defaultConfig())
454+
455+
reg.Register(&httpmock.Stub{
456+
Method: "POST",
457+
URL: "/open-apis/calendar/v4/calendars/cal_test123/events",
458+
RawBody: []byte(`{"code":190014,"msg":"invalid params","error":"just a string"}`),
459+
ContentType: "text/plain",
460+
})
461+
462+
err := mountAndRun(t, CalendarCreate, []string{
463+
"+create",
464+
"--summary", "Bad Time",
465+
"--start", "2025-03-21T10:00:00+08:00",
466+
"--end", "2025-03-21T11:00:00+08:00",
467+
"--calendar-id", "cal_test123",
468+
"--as", "bot",
469+
}, f, nil)
470+
471+
if err == nil {
472+
t.Fatal("expected error for 190014, got nil")
473+
}
474+
var exitErr *output.ExitError
475+
if !errors.As(err, &exitErr) {
476+
t.Fatalf("expected *output.ExitError, got %T", err)
477+
}
478+
if exitErr.Detail.Code != errCodeInvalidParamsWithDetail {
479+
t.Errorf("expected code %d, got %d", errCodeInvalidParamsWithDetail, exitErr.Detail.Code)
480+
}
481+
}
482+
483+
func TestCreate_CreateEvent_InvalidParams_NoDetailsKey(t *testing.T) {
484+
f, _, _, reg := cmdutil.TestFactory(t, defaultConfig())
485+
486+
reg.Register(&httpmock.Stub{
487+
Method: "POST",
488+
URL: "/open-apis/calendar/v4/calendars/cal_test123/events",
489+
Body: map[string]interface{}{
490+
"code": errCodeInvalidParamsWithDetail,
491+
"msg": "invalid params",
492+
"error": map[string]interface{}{
493+
"other_key": "no details here",
494+
},
495+
},
496+
})
497+
498+
err := mountAndRun(t, CalendarCreate, []string{
499+
"+create",
500+
"--summary", "Bad Time",
501+
"--start", "2025-03-21T10:00:00+08:00",
502+
"--end", "2025-03-21T11:00:00+08:00",
503+
"--calendar-id", "cal_test123",
504+
"--as", "bot",
505+
}, f, nil)
506+
507+
if err == nil {
508+
t.Fatal("expected error for 190014, got nil")
509+
}
510+
var exitErr *output.ExitError
511+
if !errors.As(err, &exitErr) {
512+
t.Fatalf("expected *output.ExitError, got %T", err)
513+
}
514+
if exitErr.Detail.Code != errCodeInvalidParamsWithDetail {
515+
t.Errorf("expected code %d, got %d", errCodeInvalidParamsWithDetail, exitErr.Detail.Code)
516+
}
517+
}
518+
519+
func TestCreate_CreateEvent_InvalidParams_DetailItemNotMap(t *testing.T) {
520+
f, _, _, reg := cmdutil.TestFactory(t, defaultConfig())
521+
522+
reg.Register(&httpmock.Stub{
523+
Method: "POST",
524+
URL: "/open-apis/calendar/v4/calendars/cal_test123/events",
525+
Body: map[string]interface{}{
526+
"code": errCodeInvalidParamsWithDetail,
527+
"msg": "invalid params",
528+
"error": map[string]interface{}{
529+
"details": []interface{}{nil},
530+
},
531+
},
532+
})
533+
534+
err := mountAndRun(t, CalendarCreate, []string{
535+
"+create",
536+
"--summary", "Bad Time",
537+
"--start", "2025-03-21T10:00:00+08:00",
538+
"--end", "2025-03-21T11:00:00+08:00",
539+
"--calendar-id", "cal_test123",
540+
"--as", "bot",
541+
}, f, nil)
542+
543+
if err == nil {
544+
t.Fatal("expected error for 190014, got nil")
545+
}
546+
var exitErr *output.ExitError
547+
if !errors.As(err, &exitErr) {
548+
t.Fatalf("expected *output.ExitError, got %T", err)
549+
}
550+
if exitErr.Detail.Code != errCodeInvalidParamsWithDetail {
551+
t.Errorf("expected code %d, got %d", errCodeInvalidParamsWithDetail, exitErr.Detail.Code)
552+
}
553+
}
554+
555+
func TestCreate_WithAttendees_InvalidParamsWithDetail_RollsBack(t *testing.T) {
556+
f, _, _, reg := cmdutil.TestFactory(t, defaultConfig())
557+
558+
reg.Register(&httpmock.Stub{
559+
Method: "POST",
560+
URL: "/open-apis/calendar/v4/calendars/cal_test123/events",
561+
Body: map[string]interface{}{
562+
"code": 0, "msg": "ok",
563+
"data": map[string]interface{}{
564+
"event": map[string]interface{}{
565+
"event_id": "evt_190014",
566+
"summary": "Bad Attendees",
567+
"start_time": map[string]interface{}{"timestamp": "1742515200"},
568+
"end_time": map[string]interface{}{"timestamp": "1742518800"},
569+
},
570+
},
571+
},
572+
})
573+
reg.Register(&httpmock.Stub{
574+
Method: "POST",
575+
URL: "/events/evt_190014/attendees",
576+
Body: map[string]interface{}{
577+
"code": errCodeInvalidParamsWithDetail,
578+
"msg": "invalid params",
579+
"error": map[string]interface{}{
580+
"details": []interface{}{
581+
map[string]interface{}{"value": "invalid attendee open_id"},
582+
},
583+
},
584+
},
585+
})
586+
reg.Register(&httpmock.Stub{
587+
Method: "DELETE",
588+
URL: "/events/evt_190014",
589+
Body: map[string]interface{}{"code": 0, "msg": "ok"},
590+
})
591+
592+
err := mountAndRun(t, CalendarCreate, []string{
593+
"+create",
594+
"--summary", "Bad Attendees",
595+
"--start", "2025-03-21T00:00:00+08:00",
596+
"--end", "2025-03-21T01:00:00+08:00",
597+
"--calendar-id", "cal_test123",
598+
"--attendee-ids", "ou_invalid",
599+
"--as", "bot",
600+
}, f, nil)
601+
602+
if err == nil {
603+
t.Fatal("expected error for invalid attendees with 190014, got nil")
604+
}
605+
if !strings.Contains(err.Error(), "invalid attendee open_id") {
606+
t.Errorf("expected detail value in error, got: %v", err)
607+
}
608+
}
609+
378610
// ---------------------------------------------------------------------------
379611
// CalendarAgenda tests
380612
// ---------------------------------------------------------------------------
@@ -645,6 +877,67 @@ func TestAgenda_ExplicitCalendarId(t *testing.T) {
645877
}
646878
}
647879

880+
func TestAgenda_InvalidParamsWithDetail(t *testing.T) {
881+
f, _, _, reg := cmdutil.TestFactory(t, defaultConfig())
882+
883+
reg.Register(&httpmock.Stub{
884+
Method: "GET",
885+
URL: "/events/instance_view",
886+
Body: map[string]interface{}{
887+
"code": errCodeInvalidParamsWithDetail,
888+
"msg": "invalid params",
889+
"error": map[string]interface{}{
890+
"details": []interface{}{
891+
map[string]interface{}{"value": "start_time is required"},
892+
},
893+
},
894+
},
895+
})
896+
897+
err := mountAndRun(t, CalendarAgenda, []string{
898+
"+agenda",
899+
"--start", "2025-03-21",
900+
"--end", "2025-03-21",
901+
"--as", "bot",
902+
}, f, nil)
903+
904+
if err == nil {
905+
t.Fatal("expected error for 190014, got nil")
906+
}
907+
var exitErr *output.ExitError
908+
if !errors.As(err, &exitErr) {
909+
t.Fatalf("expected *output.ExitError, got %T", err)
910+
}
911+
if exitErr.Detail.Code != errCodeInvalidParamsWithDetail {
912+
t.Errorf("expected code %d, got %d", errCodeInvalidParamsWithDetail, exitErr.Detail.Code)
913+
}
914+
}
915+
916+
func TestAgenda_NonExitError_Passthrough(t *testing.T) {
917+
f, _, _, reg := cmdutil.TestFactory(t, defaultConfig())
918+
919+
reg.Register(&httpmock.Stub{
920+
Method: "GET",
921+
URL: "/events/instance_view",
922+
RawBody: []byte("this is not json"),
923+
})
924+
925+
err := mountAndRun(t, CalendarAgenda, []string{
926+
"+agenda",
927+
"--start", "2025-03-21",
928+
"--end", "2025-03-21",
929+
"--as", "bot",
930+
}, f, nil)
931+
932+
if err == nil {
933+
t.Fatal("expected error for non-JSON response, got nil")
934+
}
935+
var exitErr *output.ExitError
936+
if errors.As(err, &exitErr) && exitErr.Detail != nil && exitErr.Detail.Code != 0 {
937+
t.Fatalf("expected non-API error passthrough, got API error code %d", exitErr.Detail.Code)
938+
}
939+
}
940+
648941
// ---------------------------------------------------------------------------
649942
// CalendarFreebusy tests
650943
// ---------------------------------------------------------------------------
@@ -725,6 +1018,46 @@ func TestFreebusy_APIError(t *testing.T) {
7251018
}
7261019
}
7271020

1021+
func TestFreebusy_InvalidParamsWithDetail(t *testing.T) {
1022+
f, _, _, reg := cmdutil.TestFactory(t, defaultConfig())
1023+
1024+
reg.Register(&httpmock.Stub{
1025+
Method: "POST",
1026+
URL: "/open-apis/calendar/v4/freebusy/list",
1027+
Body: map[string]interface{}{
1028+
"code": errCodeInvalidParamsWithDetail,
1029+
"msg": "invalid params",
1030+
"error": map[string]interface{}{
1031+
"details": []interface{}{
1032+
map[string]interface{}{"value": "user_id is invalid"},
1033+
},
1034+
},
1035+
},
1036+
})
1037+
1038+
err := mountAndRun(t, CalendarFreebusy, []string{
1039+
"+freebusy",
1040+
"--start", "2025-03-21",
1041+
"--end", "2025-03-21",
1042+
"--user-id", "ou_someone",
1043+
"--as", "bot",
1044+
}, f, nil)
1045+
1046+
if err == nil {
1047+
t.Fatal("expected error for 190014, got nil")
1048+
}
1049+
var exitErr *output.ExitError
1050+
if !errors.As(err, &exitErr) {
1051+
t.Fatalf("expected *output.ExitError, got %T", err)
1052+
}
1053+
if exitErr.Detail.Code != errCodeInvalidParamsWithDetail {
1054+
t.Errorf("expected code %d, got %d", errCodeInvalidParamsWithDetail, exitErr.Detail.Code)
1055+
}
1056+
if !strings.Contains(exitErr.Detail.Message, "user_id is invalid") {
1057+
t.Errorf("expected detail value in message, got %q", exitErr.Detail.Message)
1058+
}
1059+
}
1060+
7281061
// ---------------------------------------------------------------------------
7291062
// CalendarSuggestion tests
7301063
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)