package push import ( "context" "encoding/json" "io" "net/http" "net/http/httptest" "strings" "sync" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // fakeServer 接收推送请求并保存;返回 status 由调用方控制。 type fakeServer struct { mu sync.Mutex requests []Payload status int } func (f *fakeServer) handler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(r.Body) _ = r.Body.Close() var p Payload _ = json.Unmarshal(body, &p) f.mu.Lock() f.requests = append(f.requests, p) status := f.status f.mu.Unlock() w.WriteHeader(status) _, _ = w.Write([]byte(`{"errcode":0}`)) } } func (f *fakeServer) last() Payload { f.mu.Lock() defer f.mu.Unlock() if len(f.requests) == 0 { return Payload{} } return f.requests[len(f.requests)-1] } func (f *fakeServer) count() int { f.mu.Lock() defer f.mu.Unlock() return len(f.requests) } // TestUniPushClient_SendHappy 验证:成功路径下请求体含正确字段。 func TestUniPushClient_SendHappy(t *testing.T) { srv := &fakeServer{status: http.StatusOK} ts := httptest.NewServer(srv.handler()) defer ts.Close() c := NewUniPushClient(ts.URL, 2*time.Second, nil) err := c.Send(context.Background(), Payload{ CIDs: []string{"cid-a", "cid-b"}, Title: "你好", Content: "有新消息", Data: map[string]interface{}{"notification_id": int64(42), "type": "like"}, }) require.NoError(t, err) assert.Equal(t, 1, srv.count()) got := srv.last() assert.ElementsMatch(t, []string{"cid-a", "cid-b"}, got.CIDs) assert.Equal(t, "你好", got.Title) assert.Equal(t, "有新消息", got.Content) assert.NotEmpty(t, got.RequestID, "request_id 应自动生成") assert.EqualValues(t, 42, got.Data["notification_id"]) assert.Equal(t, "like", got.Data["type"]) } // TestUniPushClient_SendNonSuccess 验证:非 2xx 返回 error。 func TestUniPushClient_SendNonSuccess(t *testing.T) { srv := &fakeServer{status: http.StatusBadRequest} ts := httptest.NewServer(srv.handler()) defer ts.Close() c := NewUniPushClient(ts.URL, 2*time.Second, nil) err := c.Send(context.Background(), Payload{ CIDs: []string{"cid-a"}, Title: "x", Content: "y", }) require.Error(t, err) assert.Contains(t, err.Error(), "status=400") } // TestUniPushClient_EmptyCIDs 验证:cids 为空直接跳过(不发送 HTTP 请求)。 func TestUniPushClient_EmptyCIDs(t *testing.T) { srv := &fakeServer{status: http.StatusOK} ts := httptest.NewServer(srv.handler()) defer ts.Close() c := NewUniPushClient(ts.URL, 2*time.Second, nil) err := c.Send(context.Background(), Payload{Title: "x", Content: "y"}) require.NoError(t, err) assert.Equal(t, 0, srv.count(), "cids 为空时不应发请求") } // TestUniPushClient_EmptyURL 验证:URL 为空时返回 error。 func TestUniPushClient_EmptyURL(t *testing.T) { c := NewUniPushClient("", 2*time.Second, nil) err := c.Send(context.Background(), Payload{CIDs: []string{"cid-a"}, Title: "x", Content: "y"}) require.Error(t, err) assert.Contains(t, err.Error(), "not initialized") } // TestUniPushClient_Timeout 验证:超时场景下返回 error。 func TestUniPushClient_Timeout(t *testing.T) { slow := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { time.Sleep(300 * time.Millisecond) w.WriteHeader(http.StatusOK) })) defer slow.Close() c := NewUniPushClient(slow.URL, 50*time.Millisecond, nil) err := c.Send(context.Background(), Payload{CIDs: []string{"cid-a"}, Title: "x", Content: "y"}) require.Error(t, err) assert.True(t, strings.Contains(err.Error(), "timeout") || strings.Contains(err.Error(), "context deadline") || strings.Contains(err.Error(), "Client.Timeout"), "expected timeout error, got: %v", err) } // TestUniPushClient_RequestIDGenerated 验证:未传 request_id 时自动生成。 func TestUniPushClient_RequestIDGenerated(t *testing.T) { srv := &fakeServer{status: http.StatusOK} ts := httptest.NewServer(srv.handler()) defer ts.Close() c := NewUniPushClient(ts.URL, 2*time.Second, nil) _ = c.Send(context.Background(), Payload{CIDs: []string{"x"}, Title: "t", Content: "c"}) got := srv.last() assert.True(t, strings.HasPrefix(got.RequestID, ""), "request_id 应存在") assert.Greater(t, len(got.RequestID), 5) } // TestNoopPusher 验证:NoopPusher.Send 不报错也不发请求。 func TestNoopPusher(t *testing.T) { var p Pusher = NoopPusher{} assert.NoError(t, p.Send(context.Background(), Payload{CIDs: []string{"x"}})) }