Skip to content

Commit 0c1e5cf

Browse files
test(feishuadapter): raise adapter coverage above 90
Generated with [codeagent](https://github.com/qbox/codeagent) Co-authored-by: Cai-Tang-www <106404101+Cai-Tang-www@users.noreply.github.com>
1 parent d1e1043 commit 0c1e5cf

2 files changed

Lines changed: 219 additions & 0 deletions

File tree

internal/feishuadapter/adapter_test.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2596,6 +2596,14 @@ func TestBuildTaskNameTruncatesLongFirstLine(t *testing.T) {
25962596
}
25972597

25982598
func TestExtractHookNotificationSummaryAndHintFallbacks(t *testing.T) {
2599+
if summary := extractHookNotificationSummary(nil); summary != "" {
2600+
t.Fatalf("summary = %q, want empty", summary)
2601+
}
2602+
if summary := extractHookNotificationSummary(map[string]any{
2603+
"payload": map[string]any{"summary": "summary"},
2604+
}); summary != "summary" {
2605+
t.Fatalf("summary = %q, want summary", summary)
2606+
}
25992607
if summary := extractHookNotificationSummary(map[string]any{
26002608
"payload": map[string]any{"notification": "notify"},
26012609
}); summary != "notify" {
@@ -2606,11 +2614,19 @@ func TestExtractHookNotificationSummaryAndHintFallbacks(t *testing.T) {
26062614
}); summary != "message" {
26072615
t.Fatalf("summary = %q, want message", summary)
26082616
}
2617+
if hint := extractHookNotificationHint(map[string]any{
2618+
"payload": map[string]any{"reason": "retry"},
2619+
}); hint != "retry" {
2620+
t.Fatalf("hint = %q, want retry", hint)
2621+
}
26092622
if hint := extractHookNotificationHint(map[string]any{
26102623
"payload": map[string]any{"status": "async"},
26112624
}); hint != "async" {
26122625
t.Fatalf("hint = %q, want async", hint)
26132626
}
2627+
if hint := extractHookNotificationHint(nil); hint != "" {
2628+
t.Fatalf("hint = %q, want empty", hint)
2629+
}
26142630
}
26152631

26162632
func TestDeriveRunStatusAdditionalBranches(t *testing.T) {
@@ -2659,6 +2675,22 @@ func TestExtractUserVisibleDoneTextHandlesTextFieldAndTypedParts(t *testing.T) {
26592675
}); text != "keep" {
26602676
t.Fatalf("parts text = %q, want keep", text)
26612677
}
2678+
if text := extractUserVisibleDoneText(map[string]any{
2679+
"payload": map[string]any{
2680+
"parts": []any{
2681+
map[string]any{"type": "text", "text": "line one"},
2682+
map[string]any{"type": "", "content": "line two"},
2683+
"ignored",
2684+
},
2685+
},
2686+
}); text != "line one\nline two" {
2687+
t.Fatalf("parts text = %q, want joined lines", text)
2688+
}
2689+
if text := extractUserVisibleDoneText(map[string]any{
2690+
"payload": map[string]any{"parts": []any{}},
2691+
}); text != "" {
2692+
t.Fatalf("done text = %q, want empty", text)
2693+
}
26622694
}
26632695

26642696
func TestConsumeGatewayEventsIgnoresNonGatewayNotifications(t *testing.T) {
@@ -2746,6 +2778,21 @@ func TestHelperFunctionsCoverFallbackBranches(t *testing.T) {
27462778
}); text != "本机 Runner 未连接,请在电脑上启动 `neocode runner`" {
27472779
t.Fatalf("runner error text = %q", text)
27482780
}
2781+
if text := extractUserVisibleErrorText(map[string]any{
2782+
"payload": map[string]any{"message": "capability_denied"},
2783+
}); text != "权限不足:当前能力令牌不允许此操作" {
2784+
t.Fatalf("capability error text = %q", text)
2785+
}
2786+
if text := extractUserVisibleErrorText(map[string]any{
2787+
"payload": map[string]any{"message": "tool_execution_failed: bash"},
2788+
}); text != "工具执行失败:tool_execution_failed: bash" {
2789+
t.Fatalf("tool execution error text = %q", text)
2790+
}
2791+
if text := extractUserVisibleErrorText(map[string]any{
2792+
"message": "timed out waiting for runner",
2793+
}); text != "本机 Runner 响应超时,请检查网络连接和 Runner 状态" {
2794+
t.Fatalf("timeout error text = %q", text)
2795+
}
27492796
if text := extractUserVisibleErrorText(nil); text != "" {
27502797
t.Fatalf("error text = %q, want empty", text)
27512798
}
@@ -2790,9 +2837,15 @@ func TestHelperFunctionsCoverFallbackBranches(t *testing.T) {
27902837
if status := terminalStatusFromResult("failure"); status != "failure" {
27912838
t.Fatalf("terminal status = %q, want failure", status)
27922839
}
2840+
if status := terminalStatusFromResult("interrupted"); status != "interrupted" {
2841+
t.Fatalf("terminal status = %q, want interrupted", status)
2842+
}
27932843
if status := terminalStatusFromResult("unknown"); status != "running" {
27942844
t.Fatalf("terminal status = %q, want running fallback", status)
27952845
}
2846+
if text := buildTerminalFallbackText("success", ""); text != "任务已完成。" {
2847+
t.Fatalf("terminal fallback text = %q, want success default", text)
2848+
}
27962849
if text := buildTerminalFallbackText("success", "执行完成"); text != "任务已完成:\n执行完成" {
27972850
t.Fatalf("terminal fallback text = %q, want success summary", text)
27982851
}
@@ -3508,12 +3561,21 @@ func TestExtractUserQuestionRequestAndApprovalDecisionHelpers(t *testing.T) {
35083561
if !isPermissionRequestNotFoundError(fmt.Errorf("permission request abc not found")) {
35093562
t.Fatal("expected not-found error to be detected")
35103563
}
3564+
if isPermissionRequestNotFoundError(nil) {
3565+
t.Fatal("expected nil error to not match")
3566+
}
35113567
if isPermissionRequestNotFoundError(fmt.Errorf("other error")) {
35123568
t.Fatal("expected unrelated error to not match")
35133569
}
3570+
if readBool(nil, "ok") {
3571+
t.Fatal("expected nil map bool lookup to fall back to false")
3572+
}
35143573
if !readBool(map[string]any{"ok": true}, "ok") {
35153574
t.Fatal("expected bool field to be read")
35163575
}
3576+
if readBool(map[string]any{}, "ok") {
3577+
t.Fatal("expected missing bool field to fall back to false")
3578+
}
35173579
if readBool(map[string]any{"ok": "true"}, "ok") {
35183580
t.Fatal("expected non-bool field to fall back to false")
35193581
}

internal/feishuadapter/webhook_ingress_test.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,26 @@ package feishuadapter
33
import (
44
"context"
55
"errors"
6+
"net/http"
7+
"net/http/httptest"
8+
"strings"
69
"testing"
710
"time"
811
)
912

13+
type failingIngressHandler struct {
14+
messageErr error
15+
cardErr error
16+
}
17+
18+
func (f *failingIngressHandler) HandleMessage(_ context.Context, _ FeishuMessageEvent) error {
19+
return f.messageErr
20+
}
21+
22+
func (f *failingIngressHandler) HandleCardAction(_ context.Context, _ FeishuCardActionEvent) error {
23+
return f.cardErr
24+
}
25+
1026
func TestNewWebhookIngressAndRunContextCancel(t *testing.T) {
1127
ingress, ok := NewWebhookIngress(Config{
1228
ListenAddress: "127.0.0.1:0",
@@ -57,3 +73,144 @@ func TestNewWebhookIngressUsesProvidedClockAndRunReturnsListenError(t *testing.T
5773
t.Fatal("expected listen error for invalid address")
5874
}
5975
}
76+
77+
func TestWebhookIngressHandleFeishuEventVerificationIgnoreAndHandlerError(t *testing.T) {
78+
ingress, ok := NewWebhookIngress(Config{
79+
VerifyToken: "verify",
80+
SigningSecret: "sign-secret",
81+
}, nil).(*WebhookIngress)
82+
if !ok {
83+
t.Fatal("expected webhook ingress instance")
84+
}
85+
86+
t.Run("url verification", func(t *testing.T) {
87+
request := signedRequest(t, ingress.cfg.SigningSecret, `{"type":"url_verification","challenge":"hello","token":"verify"}`)
88+
recorder := httptest.NewRecorder()
89+
90+
ingress.handleFeishuEvent(&captureIngressHandler{}).ServeHTTP(recorder, request)
91+
92+
if recorder.Code != http.StatusOK {
93+
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusOK)
94+
}
95+
if !strings.Contains(recorder.Body.String(), `"challenge":"hello"`) {
96+
t.Fatalf("response = %s, want challenge body", recorder.Body.String())
97+
}
98+
})
99+
100+
t.Run("unsupported event ignored before token check", func(t *testing.T) {
101+
request := signedRequest(t, ingress.cfg.SigningSecret, `{"header":{"event_type":"other"}}`)
102+
recorder := httptest.NewRecorder()
103+
104+
ingress.handleFeishuEvent(&captureIngressHandler{}).ServeHTTP(recorder, request)
105+
106+
if recorder.Code != http.StatusOK {
107+
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusOK)
108+
}
109+
if !strings.Contains(recorder.Body.String(), `"message":"ignored"`) {
110+
t.Fatalf("response = %s, want ignored", recorder.Body.String())
111+
}
112+
})
113+
114+
t.Run("handler error returns retryable response", func(t *testing.T) {
115+
body := `{"header":{"event_id":"evt-1","event_type":"im.message.receive_v1","token":"verify"},"event":{"message":{"message_id":"msg-1","chat_id":"chat-1","content":"{\"text\":\"hello\"}"}}}`
116+
request := signedRequest(t, ingress.cfg.SigningSecret, body)
117+
recorder := httptest.NewRecorder()
118+
119+
ingress.handleFeishuEvent(&failingIngressHandler{messageErr: errors.New("boom")}).ServeHTTP(recorder, request)
120+
121+
if recorder.Code != http.StatusInternalServerError {
122+
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusInternalServerError)
123+
}
124+
if !strings.Contains(recorder.Body.String(), "retryable_error") {
125+
t.Fatalf("response = %s, want retryable_error", recorder.Body.String())
126+
}
127+
})
128+
}
129+
130+
func TestWebhookIngressHandleCardCallbackActionResponses(t *testing.T) {
131+
ingress, ok := NewWebhookIngress(Config{
132+
VerifyToken: "verify",
133+
SigningSecret: "sign-secret",
134+
}, nil).(*WebhookIngress)
135+
if !ok {
136+
t.Fatal("expected webhook ingress instance")
137+
}
138+
139+
t.Run("url verification", func(t *testing.T) {
140+
request := signedRequest(t, ingress.cfg.SigningSecret, `{"type":"url_verification","challenge":"card-ok","token":"verify","header":{"token":"verify"}}`)
141+
recorder := httptest.NewRecorder()
142+
143+
ingress.handleCardCallback(&captureIngressHandler{}).ServeHTTP(recorder, request)
144+
145+
if recorder.Code != http.StatusOK {
146+
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusOK)
147+
}
148+
if !strings.Contains(recorder.Body.String(), `"challenge":"card-ok"`) {
149+
t.Fatalf("response = %s, want challenge body", recorder.Body.String())
150+
}
151+
})
152+
153+
t.Run("invalid callback returns ready toast", func(t *testing.T) {
154+
request := signedRequest(t, ingress.cfg.SigningSecret, `{"action":{"value":{"action_type":"permission","request_id":"perm-1","decision":"allow_all"}},"token":"verify","header":{"token":"verify"}}`)
155+
recorder := httptest.NewRecorder()
156+
157+
ingress.handleCardCallback(&captureIngressHandler{}).ServeHTTP(recorder, request)
158+
159+
if recorder.Code != http.StatusOK {
160+
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusOK)
161+
}
162+
if !strings.Contains(recorder.Body.String(), "callback ready") {
163+
t.Fatalf("response = %s, want callback ready", recorder.Body.String())
164+
}
165+
})
166+
167+
t.Run("permission success toast", func(t *testing.T) {
168+
request := signedRequest(t, ingress.cfg.SigningSecret, `{"action":{"value":{"request_id":"perm-2","decision":"allow_once"}},"token":"verify","header":{"event_id":"evt-perm","token":"verify"}}`)
169+
recorder := httptest.NewRecorder()
170+
handler := &captureIngressHandler{}
171+
172+
ingress.handleCardCallback(handler).ServeHTTP(recorder, request)
173+
174+
if recorder.Code != http.StatusOK {
175+
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusOK)
176+
}
177+
if len(handler.cards) != 1 || handler.cards[0].Decision != "allow_once" {
178+
t.Fatalf("unexpected cards: %#v", handler.cards)
179+
}
180+
if !strings.Contains(recorder.Body.String(), "审批已提交") {
181+
t.Fatalf("response = %s, want permission toast", recorder.Body.String())
182+
}
183+
})
184+
185+
t.Run("user question success toast", func(t *testing.T) {
186+
request := signedRequest(t, ingress.cfg.SigningSecret, `{"action":{"value":{"action_type":"user_question","request_id":"ask-1","status":"answered","value":"A"}},"open_message_id":"card-1","token":"verify","header":{"event_id":"evt-ask","token":"verify"}}`)
187+
recorder := httptest.NewRecorder()
188+
handler := &captureIngressHandler{}
189+
190+
ingress.handleCardCallback(handler).ServeHTTP(recorder, request)
191+
192+
if recorder.Code != http.StatusOK {
193+
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusOK)
194+
}
195+
if len(handler.cards) != 1 || handler.cards[0].ActionType != "user_question" {
196+
t.Fatalf("unexpected cards: %#v", handler.cards)
197+
}
198+
if !strings.Contains(recorder.Body.String(), "回答已提交") {
199+
t.Fatalf("response = %s, want user question toast", recorder.Body.String())
200+
}
201+
})
202+
203+
t.Run("handler error returns server error", func(t *testing.T) {
204+
request := signedRequest(t, ingress.cfg.SigningSecret, `{"action":{"value":{"request_id":"perm-3","decision":"reject"}},"token":"verify","header":{"token":"verify"}}`)
205+
recorder := httptest.NewRecorder()
206+
207+
ingress.handleCardCallback(&failingIngressHandler{cardErr: errors.New("boom")}).ServeHTTP(recorder, request)
208+
209+
if recorder.Code != http.StatusInternalServerError {
210+
t.Fatalf("status = %d, want %d", recorder.Code, http.StatusInternalServerError)
211+
}
212+
if !strings.Contains(recorder.Body.String(), "card action failed") {
213+
t.Fatalf("response = %s, want card action failed", recorder.Body.String())
214+
}
215+
})
216+
}

0 commit comments

Comments
 (0)