157 lines
6.0 KiB
Go
157 lines
6.0 KiB
Go
package client
|
|||
|
|
|
||
|
|
import (
|
||
|
|
"context"
|
||
|
|
"errors"
|
||
|
|
"net/http"
|
||
|
|
"net/http/httptest"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
"time"
|
||
|
|
|
||
|
|
api "code.zhecent.com/open/shop-crm-agent/internal/api"
|
||
|
|
|
||
|
|
"google.golang.org/protobuf/proto"
|
||
|
|
)
|
||
|
|
|
||
|
|
func TestClientSendsBearerProtobufAndChecksBusinessHeader(t *testing.T) {
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||
|
|
if request.URL.Path != apiPath+"/projects/get" {
|
||
|
|
t.Errorf("path = %s", request.URL.Path)
|
||
|
|
}
|
||
|
|
if request.Header.Get("Authorization") != "Bearer test-token" {
|
||
|
|
t.Errorf("authorization = %q", request.Header.Get("Authorization"))
|
||
|
|
}
|
||
|
|
if request.Header.Get("Content-Type") != "application/x-protobuf" {
|
||
|
|
t.Errorf("content type = %q", request.Header.Get("Content-Type"))
|
||
|
|
}
|
||
|
|
data, _ := proto.Marshal(&api.ProjectResponse{Header: &api.ResponseHeader{Code: 200, Message: "success"}, Project: &api.Project{Id: 9}})
|
||
|
|
writer.Header().Set("Content-Type", "application/x-protobuf")
|
||
|
|
_, _ = writer.Write(data)
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
client, err := New(server.URL, "test-token", server.Client())
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("new client: %v", err)
|
||
|
|
}
|
||
|
|
response, err := client.ProjectGet(context.Background(), &api.ProjectGetRequest{ProjectId: 9})
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("project get: %v", err)
|
||
|
|
}
|
||
|
|
if response.Project.GetId() != 9 {
|
||
|
|
t.Fatalf("project id = %d", response.Project.GetId())
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestClientReportsHTTPProtocolAndSizeFailures(t *testing.T) {
|
||
|
|
t.Run("http error preserves protobuf header", func(t *testing.T) {
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||
|
|
writer.WriteHeader(http.StatusUnauthorized)
|
||
|
|
data, _ := proto.Marshal(&api.ProjectResponse{Header: &api.ResponseHeader{Code: 401, Message: "unauthorized"}})
|
||
|
|
_, _ = writer.Write(data)
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
client, _ := New(server.URL, "token", server.Client())
|
||
|
|
response, err := client.ProjectGet(context.Background(), &api.ProjectGetRequest{})
|
||
|
|
if err == nil || !strings.Contains(err.Error(), "HTTP 401: unauthorized") {
|
||
|
|
t.Fatalf("error = %v", err)
|
||
|
|
}
|
||
|
|
if response.GetHeader().GetCode() != 401 {
|
||
|
|
t.Fatalf("header = %#v", response.GetHeader())
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("malformed protobuf", func(t *testing.T) {
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||
|
|
_, _ = writer.Write([]byte{0xff})
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
client, _ := New(server.URL, "token", server.Client())
|
||
|
|
if _, err := client.ProjectGet(context.Background(), &api.ProjectGetRequest{}); err == nil || !strings.Contains(err.Error(), "解析 Protobuf 响应失败") {
|
||
|
|
t.Fatalf("error = %v", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
|
||
|
|
t.Run("response size limit", func(t *testing.T) {
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||
|
|
_, _ = writer.Write(make([]byte, maxResponseSize+1))
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
client, _ := New(server.URL, "token", server.Client())
|
||
|
|
if _, err := client.ProjectGet(context.Background(), &api.ProjectGetRequest{}); err == nil || !strings.Contains(err.Error(), "16 MiB") {
|
||
|
|
t.Fatalf("error = %v", err)
|
||
|
|
}
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestClientHonorsHTTPClientTimeout(t *testing.T) {
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||
|
|
time.Sleep(50 * time.Millisecond)
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
httpClient := server.Client()
|
||
|
|
httpClient.Timeout = time.Millisecond
|
||
|
|
client, _ := New(server.URL, "token", httpClient)
|
||
|
|
_, err := client.ProjectGet(context.Background(), &api.ProjectGetRequest{})
|
||
|
|
if err == nil || !errors.Is(err, context.DeadlineExceeded) {
|
||
|
|
t.Fatalf("error = %v", err)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestClientRepresentativeOperationRoutes(t *testing.T) {
|
||
|
|
var paths []string
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||
|
|
paths = append(paths, request.URL.Path)
|
||
|
|
var response proto.Message
|
||
|
|
switch request.URL.Path {
|
||
|
|
case apiPath + "/projects/create":
|
||
|
|
response = &api.ProjectResponse{Header: &api.ResponseHeader{Code: 200}}
|
||
|
|
case apiPath + "/quotations/delete/preview":
|
||
|
|
response = &api.DeletePreviewResponse{Header: &api.ResponseHeader{Code: 200}}
|
||
|
|
case apiPath + "/quotation/edit/command":
|
||
|
|
response = &api.QuotationEditCommandResponse{Header: &api.ResponseHeader{Code: 200}}
|
||
|
|
default:
|
||
|
|
t.Fatalf("unexpected path %q", request.URL.Path)
|
||
|
|
}
|
||
|
|
data, _ := proto.Marshal(response)
|
||
|
|
_, _ = writer.Write(data)
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
client, _ := New(server.URL, "token", server.Client())
|
||
|
|
if _, err := client.ProjectCreate(context.Background(), &api.ProjectCreateRequest{}); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if _, err := client.QuotationDeletePreview(context.Background(), &api.QuotationDeletePreviewRequest{}); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
if _, err := client.EditCommand(context.Background(), &api.QuotationEditCommandRequest{}); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
want := []string{apiPath + "/projects/create", apiPath + "/quotations/delete/preview", apiPath + "/quotation/edit/command"}
|
||
|
|
if strings.Join(paths, "|") != strings.Join(want, "|") {
|
||
|
|
t.Fatalf("paths = %v", paths)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
func TestClientRejectsMissingConfigurationAndBusinessError(t *testing.T) {
|
||
|
|
if _, err := New("", "token", nil); err == nil {
|
||
|
|
t.Fatal("empty base URL accepted")
|
||
|
|
}
|
||
|
|
if _, err := New("https://example.com", "", nil); err == nil {
|
||
|
|
t.Fatal("empty token accepted")
|
||
|
|
}
|
||
|
|
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
||
|
|
data, _ := proto.Marshal(&api.CommonResponse{Header: &api.ResponseHeader{Code: 400, Message: "bad request", RequestId: "request-123", Replayed: true}})
|
||
|
|
_, _ = writer.Write(data)
|
||
|
|
}))
|
||
|
|
defer server.Close()
|
||
|
|
client, _ := New(server.URL, "token", server.Client())
|
||
|
|
response, err := client.ProjectGet(context.Background(), &api.ProjectGetRequest{})
|
||
|
|
if err == nil {
|
||
|
|
t.Fatal("business error was accepted")
|
||
|
|
}
|
||
|
|
if response.GetHeader() == nil || !response.GetHeader().Replayed || response.GetHeader().RequestId != "request-123" {
|
||
|
|
t.Fatalf("business error response = %#v", response.GetHeader())
|
||
|
|
}
|
||
|
|
}
|