1285 lines
36 KiB
Go
1285 lines
36 KiB
Go
package http_test
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
jsoncodec "go.unistack.org/micro-codec-json/v4"
|
|
"go.unistack.org/micro/v4/client"
|
|
"go.unistack.org/micro/v4/metadata"
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
httpcli "go.unistack.org/micro-client-http/v4"
|
|
pb "go.unistack.org/micro-client-http/v4/builder/proto"
|
|
)
|
|
|
|
func TestClient_Call_Get(t *testing.T) {
|
|
type (
|
|
request = pb.Test_Client_Call_Request
|
|
response = pb.Test_Client_Call_Response
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
method string
|
|
path string
|
|
req *request
|
|
options []client.CallOption
|
|
wantPath string
|
|
wantReqBody []byte
|
|
wantRsp *response
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "GET request (query)",
|
|
method: http.MethodGet,
|
|
path: "/user/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
wantPath: "/user/products?order_id=456&user_id=123",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{Id: "product-id", Name: "product-name"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "GET request (path)",
|
|
method: http.MethodGet,
|
|
path: "/user/{user_id}/order/{order_id}/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
wantPath: "/user/123/order/456/products",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{Id: "product-id", Name: "product-name"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "GET request (path + query)",
|
|
method: http.MethodGet,
|
|
path: "/user/{user_id}/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
wantPath: "/user/123/products?order_id=456",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{Id: "product-id", Name: "product-name"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "GET request (zero-value query)",
|
|
method: http.MethodGet,
|
|
path: "/user/products",
|
|
req: &request{},
|
|
wantPath: "/user/products",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{Id: "product-id", Name: "product-name"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "GET request (zero-value path)",
|
|
method: http.MethodGet,
|
|
path: "/user/{user_id}/products",
|
|
req: &request{OrderId: 456},
|
|
wantRsp: nil,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "GET request (with body)",
|
|
method: http.MethodGet,
|
|
path: "/user/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
options: []client.CallOption{httpcli.Body("*")},
|
|
wantRsp: nil,
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
require.Equal(t, tt.method, r.Method)
|
|
require.Equal(t, tt.wantPath, r.URL.RequestURI())
|
|
|
|
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
|
require.Equal(t, "Bearer token", r.Header.Get("Authorization"))
|
|
require.Equal(t, "My-Header-Value", r.Header.Get("My-Header"))
|
|
|
|
buf, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
defer r.Body.Close()
|
|
require.Equal(t, tt.wantReqBody, buf)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("My-Header", "My-Header-Value")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
if tt.wantRsp != nil {
|
|
c := jsoncodec.NewCodec()
|
|
buf, err = c.Marshal(tt.wantRsp)
|
|
require.NoError(t, err)
|
|
_, err = w.Write(buf)
|
|
require.NoError(t, err)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
httpClient := httpcli.NewClient(
|
|
client.Codec("application/json", jsoncodec.NewCodec()),
|
|
)
|
|
|
|
var (
|
|
ctx = metadata.NewOutgoingContext(
|
|
context.Background(),
|
|
metadata.Pairs("Authorization", "Bearer token", "My-Header", "My-Header-Value"),
|
|
)
|
|
rsp = &response{}
|
|
respMetadata = metadata.Metadata{}
|
|
)
|
|
|
|
opts := []client.CallOption{
|
|
client.WithAddress(server.URL),
|
|
client.WithResponseMetadata(&respMetadata),
|
|
httpcli.Method(tt.method),
|
|
httpcli.Path(tt.path),
|
|
}
|
|
|
|
if len(tt.options) > 0 {
|
|
opts = append(opts, tt.options...)
|
|
}
|
|
|
|
err := httpClient.Call(
|
|
ctx,
|
|
httpClient.NewRequest("test.service", "Test.Call", tt.req),
|
|
rsp,
|
|
opts...,
|
|
)
|
|
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
require.Empty(t, rsp)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.True(t, proto.Equal(tt.wantRsp, rsp))
|
|
require.Equal(t, "application/json", respMetadata.GetJoined("Content-Type"))
|
|
require.Equal(t, "My-Header-Value", respMetadata.GetJoined("My-Header"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_Call_Head(t *testing.T) {
|
|
type (
|
|
request = pb.Test_Client_Call_Request
|
|
response = pb.Test_Client_Call_Response
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
method string
|
|
path string
|
|
req *request
|
|
options []client.CallOption
|
|
wantPath string
|
|
wantReqBody []byte
|
|
wantRsp *response
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "HEAD request (query)",
|
|
method: http.MethodHead,
|
|
path: "/user/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
wantPath: "/user/products?order_id=456&user_id=123",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "HEAD request (path)",
|
|
method: http.MethodHead,
|
|
path: "/user/{user_id}/order/{order_id}/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
wantPath: "/user/123/order/456/products",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "HEAD request (path + query)",
|
|
method: http.MethodHead,
|
|
path: "/user/{user_id}/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
wantPath: "/user/123/products?order_id=456",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "HEAD request (zero-value query)",
|
|
method: http.MethodHead,
|
|
path: "/user/products",
|
|
req: &request{},
|
|
wantPath: "/user/products",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "HEAD request (zero-value path)",
|
|
method: http.MethodHead,
|
|
path: "/user/{user_id}/products",
|
|
req: &request{OrderId: 456},
|
|
wantRsp: nil,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "HEAD request (with body)",
|
|
method: http.MethodHead,
|
|
path: "/user/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
options: []client.CallOption{httpcli.Body("*")},
|
|
wantRsp: nil,
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
require.Equal(t, tt.method, r.Method)
|
|
require.Equal(t, tt.wantPath, r.URL.RequestURI())
|
|
|
|
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
|
require.Equal(t, "Bearer token", r.Header.Get("Authorization"))
|
|
require.Equal(t, "My-Header-Value", r.Header.Get("My-Header"))
|
|
|
|
buf, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
defer r.Body.Close()
|
|
require.Equal(t, tt.wantReqBody, buf)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("My-Header", "My-Header-Value")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
// used to verify that the HTTP client skips the response body for HEAD method
|
|
c := jsoncodec.NewCodec()
|
|
buf, err = c.Marshal(map[string]any{"id": "product-id", "name": "product-name"})
|
|
require.NoError(t, err)
|
|
_, err = w.Write(buf)
|
|
require.NoError(t, err)
|
|
}))
|
|
defer server.Close()
|
|
|
|
httpClient := httpcli.NewClient(
|
|
client.Codec("application/json", jsoncodec.NewCodec()),
|
|
)
|
|
|
|
var (
|
|
ctx = metadata.NewOutgoingContext(
|
|
context.Background(),
|
|
metadata.Pairs("Authorization", "Bearer token", "My-Header", "My-Header-Value"),
|
|
)
|
|
rsp = &response{}
|
|
respMetadata = metadata.Metadata{}
|
|
)
|
|
|
|
opts := []client.CallOption{
|
|
client.WithAddress(server.URL),
|
|
client.WithResponseMetadata(&respMetadata),
|
|
httpcli.Method(tt.method),
|
|
httpcli.Path(tt.path),
|
|
}
|
|
|
|
if len(tt.options) > 0 {
|
|
opts = append(opts, tt.options...)
|
|
}
|
|
|
|
err := httpClient.Call(
|
|
ctx,
|
|
httpClient.NewRequest("test.service", "Test.Call", tt.req),
|
|
rsp,
|
|
opts...,
|
|
)
|
|
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
require.Empty(t, rsp)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.True(t, proto.Equal(tt.wantRsp, rsp))
|
|
require.Equal(t, "application/json", respMetadata.GetJoined("Content-Type"))
|
|
require.Equal(t, "My-Header-Value", respMetadata.GetJoined("My-Header"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_Call_Post(t *testing.T) {
|
|
type (
|
|
request = pb.Test_Client_Call_Request
|
|
response = pb.Test_Client_Call_Response
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
method string
|
|
path string
|
|
req *request
|
|
options []client.CallOption
|
|
wantPath string
|
|
wantReqBody []byte
|
|
wantRsp *response
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "POST request (query)",
|
|
method: http.MethodPost,
|
|
path: "/user/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
wantPath: "/user/products?order_id=456&user_id=123",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{Id: "product-id", Name: "product-name"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "POST request (path)",
|
|
method: http.MethodPost,
|
|
path: "/user/{user_id}/order/{order_id}/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
wantPath: "/user/123/order/456/products",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{Id: "product-id", Name: "product-name"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "POST request (body)",
|
|
method: http.MethodPost,
|
|
path: "/user/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
options: []client.CallOption{httpcli.Body("*")},
|
|
wantPath: "/user/products",
|
|
wantReqBody: []byte(`{"userId":"123","orderId":456}`),
|
|
wantRsp: &response{Id: "product-id", Name: "product-name"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "POST request (path + query)",
|
|
method: http.MethodPost,
|
|
path: "/user/{user_id}/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
wantPath: "/user/123/products?order_id=456",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{Id: "product-id", Name: "product-name"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "POST request (path + body)",
|
|
method: http.MethodPost,
|
|
path: "/user/{user_id}/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
options: []client.CallOption{httpcli.Body("*")},
|
|
wantPath: "/user/123/products",
|
|
wantReqBody: []byte(`{"orderId":456}`),
|
|
wantRsp: &response{Id: "product-id", Name: "product-name"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "POST request (query + body)",
|
|
method: http.MethodPost,
|
|
path: "/user/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
options: []client.CallOption{httpcli.Body("order_id")},
|
|
wantPath: "/user/products?user_id=123",
|
|
wantReqBody: []byte(`{"orderId":456}`),
|
|
wantRsp: &response{Id: "product-id", Name: "product-name"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "POST request (zero-value query)",
|
|
method: http.MethodPost,
|
|
path: "/user/products",
|
|
req: &request{},
|
|
wantPath: "/user/products",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{Id: "product-id", Name: "product-name"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "POST request (zero-value body)",
|
|
method: http.MethodPost,
|
|
path: "/user/products",
|
|
req: &request{},
|
|
options: []client.CallOption{httpcli.Body("*")},
|
|
wantPath: "/user/products",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{Id: "product-id", Name: "product-name"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "POST request (zero-value path)",
|
|
method: http.MethodPost,
|
|
path: "/user/{user_id}/products",
|
|
req: &request{OrderId: 456},
|
|
wantRsp: nil,
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
require.Equal(t, tt.method, r.Method)
|
|
require.Equal(t, tt.wantPath, r.URL.RequestURI())
|
|
|
|
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
|
require.Equal(t, "Bearer token", r.Header.Get("Authorization"))
|
|
require.Equal(t, "My-Header-Value", r.Header.Get("My-Header"))
|
|
|
|
buf, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
defer r.Body.Close()
|
|
require.Equal(t, tt.wantReqBody, buf)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("My-Header", "My-Header-Value")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
if tt.wantRsp != nil {
|
|
c := jsoncodec.NewCodec()
|
|
buf, err = c.Marshal(tt.wantRsp)
|
|
require.NoError(t, err)
|
|
_, err = w.Write(buf)
|
|
require.NoError(t, err)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
httpClient := httpcli.NewClient(
|
|
client.Codec("application/json", jsoncodec.NewCodec()),
|
|
)
|
|
|
|
var (
|
|
ctx = metadata.NewOutgoingContext(
|
|
context.Background(),
|
|
metadata.Pairs("Authorization", "Bearer token", "My-Header", "My-Header-Value"),
|
|
)
|
|
rsp = &response{}
|
|
respMetadata = metadata.Metadata{}
|
|
)
|
|
|
|
opts := []client.CallOption{
|
|
client.WithAddress(server.URL),
|
|
client.WithResponseMetadata(&respMetadata),
|
|
httpcli.Method(tt.method),
|
|
httpcli.Path(tt.path),
|
|
}
|
|
|
|
if len(tt.options) > 0 {
|
|
opts = append(opts, tt.options...)
|
|
}
|
|
|
|
err := httpClient.Call(
|
|
ctx,
|
|
httpClient.NewRequest("test.service", "Test.Call", tt.req),
|
|
rsp,
|
|
opts...,
|
|
)
|
|
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
require.Empty(t, rsp)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.True(t, proto.Equal(tt.wantRsp, rsp))
|
|
require.Equal(t, "application/json", respMetadata.GetJoined("Content-Type"))
|
|
require.Equal(t, "My-Header-Value", respMetadata.GetJoined("My-Header"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_Call_Delete(t *testing.T) {
|
|
type (
|
|
request = pb.Test_Client_Call_Request
|
|
response = pb.Test_Client_Call_Response
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
method string
|
|
path string
|
|
req *request
|
|
options []client.CallOption
|
|
wantPath string
|
|
wantReqBody []byte
|
|
wantRsp *response
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "DELETE request (query)",
|
|
method: http.MethodDelete,
|
|
path: "/user/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
wantPath: "/user/products?order_id=456&user_id=123",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{Id: "product-id", Name: "product-name"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "DELETE request (path)",
|
|
method: http.MethodDelete,
|
|
path: "/user/{user_id}/order/{order_id}/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
wantPath: "/user/123/order/456/products",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{Id: "product-id", Name: "product-name"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "DELETE request (path + query)",
|
|
method: http.MethodDelete,
|
|
path: "/user/{user_id}/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
wantPath: "/user/123/products?order_id=456",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{Id: "product-id", Name: "product-name"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "DELETE request (zero-value query)",
|
|
method: http.MethodDelete,
|
|
path: "/user/products",
|
|
req: &request{},
|
|
wantPath: "/user/products",
|
|
wantReqBody: []byte{},
|
|
wantRsp: &response{Id: "product-id", Name: "product-name"},
|
|
wantErr: false,
|
|
},
|
|
{
|
|
name: "DELETE request (zero-value path)",
|
|
method: http.MethodDelete,
|
|
path: "/user/{user_id}/products",
|
|
req: &request{OrderId: 456},
|
|
wantRsp: nil,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "DELETE request (with body)",
|
|
method: http.MethodDelete,
|
|
path: "/user/products",
|
|
req: &request{UserId: "123", OrderId: 456},
|
|
options: []client.CallOption{httpcli.Body("*")},
|
|
wantRsp: nil,
|
|
wantErr: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
require.Equal(t, tt.method, r.Method)
|
|
require.Equal(t, tt.wantPath, r.URL.RequestURI())
|
|
|
|
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
|
require.Equal(t, "Bearer token", r.Header.Get("Authorization"))
|
|
require.Equal(t, "My-Header-Value", r.Header.Get("My-Header"))
|
|
|
|
buf, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
defer r.Body.Close()
|
|
require.Equal(t, tt.wantReqBody, buf)
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("My-Header", "My-Header-Value")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
if tt.wantRsp != nil {
|
|
c := jsoncodec.NewCodec()
|
|
buf, err = c.Marshal(tt.wantRsp)
|
|
require.NoError(t, err)
|
|
_, err = w.Write(buf)
|
|
require.NoError(t, err)
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
httpClient := httpcli.NewClient(
|
|
client.Codec("application/json", jsoncodec.NewCodec()),
|
|
)
|
|
|
|
var (
|
|
ctx = metadata.NewOutgoingContext(
|
|
context.Background(),
|
|
metadata.Pairs("Authorization", "Bearer token", "My-Header", "My-Header-Value"),
|
|
)
|
|
rsp = &response{}
|
|
respMetadata = metadata.Metadata{}
|
|
)
|
|
|
|
opts := []client.CallOption{
|
|
client.WithAddress(server.URL),
|
|
client.WithResponseMetadata(&respMetadata),
|
|
httpcli.Method(tt.method),
|
|
httpcli.Path(tt.path),
|
|
}
|
|
|
|
if len(tt.options) > 0 {
|
|
opts = append(opts, tt.options...)
|
|
}
|
|
|
|
err := httpClient.Call(
|
|
ctx,
|
|
httpClient.NewRequest("test.service", "Test.Call", tt.req),
|
|
rsp,
|
|
opts...,
|
|
)
|
|
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
require.Empty(t, rsp)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.True(t, proto.Equal(tt.wantRsp, rsp))
|
|
require.Equal(t, "application/json", respMetadata.GetJoined("Content-Type"))
|
|
require.Equal(t, "My-Header-Value", respMetadata.GetJoined("My-Header"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_Call_ErrorsMap(t *testing.T) {
|
|
type (
|
|
request = pb.Test_Client_Call_Request
|
|
response = pb.Test_Client_Call_Response
|
|
defaultError = pb.Test_Client_Call_DefaultError
|
|
specialError = pb.Test_Client_Call_SpecialError
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
serverMock func() *httptest.Server
|
|
expectedErr error
|
|
}{
|
|
{
|
|
name: "default error",
|
|
serverMock: func() *httptest.Server {
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Validate request
|
|
require.Equal(t, "POST", r.Method)
|
|
require.Equal(t, "/user/products", r.URL.RequestURI())
|
|
|
|
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
|
require.Equal(t, "Bearer token", r.Header.Get("Authorization"))
|
|
require.Equal(t, "My-Header-Value", r.Header.Get("My-Header"))
|
|
|
|
buf, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
defer r.Body.Close()
|
|
|
|
c := jsoncodec.NewCodec()
|
|
|
|
req := &request{}
|
|
err = c.Unmarshal(buf, req)
|
|
require.NoError(t, err)
|
|
require.True(t, proto.Equal(&request{UserId: "123", OrderId: 456}, req))
|
|
|
|
// Return response
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("My-Header", "My-Header-Value")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
resp := map[string]interface{}{
|
|
"code": "default-error-code",
|
|
"msg": "default-error-message",
|
|
}
|
|
buf, err = c.Marshal(resp)
|
|
require.NoError(t, err)
|
|
_, err = w.Write(buf)
|
|
require.NoError(t, err)
|
|
}))
|
|
},
|
|
expectedErr: &defaultError{Code: "default-error-code", Msg: "default-error-message"},
|
|
},
|
|
{
|
|
name: "special error",
|
|
serverMock: func() *httptest.Server {
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Validate request
|
|
require.Equal(t, "POST", r.Method)
|
|
require.Equal(t, "/user/products", r.URL.RequestURI())
|
|
|
|
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
|
require.Equal(t, "Bearer token", r.Header.Get("Authorization"))
|
|
require.Equal(t, "My-Header-Value", r.Header.Get("My-Header"))
|
|
|
|
buf, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
defer r.Body.Close()
|
|
|
|
c := jsoncodec.NewCodec()
|
|
|
|
req := &request{}
|
|
err = c.Unmarshal(buf, req)
|
|
require.NoError(t, err)
|
|
require.True(t, proto.Equal(&request{UserId: "123", OrderId: 456}, req))
|
|
|
|
// Return response
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("My-Header", "My-Header-Value")
|
|
w.WriteHeader(http.StatusForbidden)
|
|
|
|
resp := map[string]interface{}{
|
|
"code": "special-error-code",
|
|
"msg": "special-error-message",
|
|
"warning": "special-error-warning",
|
|
}
|
|
buf, err = c.Marshal(resp)
|
|
require.NoError(t, err)
|
|
_, err = w.Write(buf)
|
|
require.NoError(t, err)
|
|
}))
|
|
},
|
|
expectedErr: &specialError{Code: "special-error-code", Msg: "special-error-message", Warning: "special-error-warning"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
server := tt.serverMock()
|
|
defer server.Close()
|
|
|
|
httpClient := httpcli.NewClient(
|
|
client.Codec("application/json", jsoncodec.NewCodec()),
|
|
)
|
|
|
|
var (
|
|
ctx = metadata.NewOutgoingContext(
|
|
context.Background(),
|
|
metadata.Pairs("Authorization", "Bearer token", "My-Header", "My-Header-Value"),
|
|
)
|
|
req = &request{UserId: "123", OrderId: 456}
|
|
rsp = &response{}
|
|
|
|
respMetadata = metadata.Metadata{}
|
|
)
|
|
|
|
opts := []client.CallOption{
|
|
client.WithAddress(server.URL),
|
|
client.WithResponseMetadata(&respMetadata),
|
|
httpcli.Method(http.MethodPost),
|
|
httpcli.Path("/user/products"),
|
|
httpcli.Body("*"),
|
|
httpcli.ErrorMap(map[string]any{
|
|
"default": &defaultError{},
|
|
"403": &specialError{},
|
|
}),
|
|
}
|
|
|
|
err := httpClient.Call(
|
|
ctx,
|
|
httpClient.NewRequest("test.service", "Test.Call", req),
|
|
rsp,
|
|
opts...,
|
|
)
|
|
|
|
require.Equal(t, tt.expectedErr.Error(), err.Error())
|
|
require.Empty(t, rsp)
|
|
|
|
require.Equal(t, "application/json", respMetadata.GetJoined("Content-Type"))
|
|
require.Equal(t, "My-Header-Value", respMetadata.GetJoined("My-Header"))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_Call_HeadersAndCookies(t *testing.T) {
|
|
type (
|
|
request = pb.Test_Client_Call_Request
|
|
response = pb.Test_Client_Call_Response
|
|
)
|
|
|
|
tests := []struct {
|
|
name string
|
|
serverMock func() *httptest.Server
|
|
prepareMetadata func() metadata.Metadata
|
|
headerOption client.CallOption
|
|
cookieOption client.CallOption
|
|
expectedRsp *response
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "with required headers",
|
|
serverMock: func() *httptest.Server {
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Validate request
|
|
require.Equal(t, "POST", r.Method)
|
|
require.Equal(t, "/user/products", r.URL.RequestURI())
|
|
|
|
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
|
require.Equal(t, "Bearer token", r.Header.Get("Authorization"))
|
|
require.Equal(t, "My-Header-Value", r.Header.Get("My-Header"))
|
|
|
|
buf, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
defer r.Body.Close()
|
|
|
|
c := jsoncodec.NewCodec()
|
|
|
|
req := &request{}
|
|
err = c.Unmarshal(buf, req)
|
|
require.NoError(t, err)
|
|
require.True(t, proto.Equal(&request{UserId: "123", OrderId: 456}, req))
|
|
|
|
// Return response
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("My-Header", "My-Header-Value")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
resp := map[string]interface{}{
|
|
"id": "product-id",
|
|
"name": "product-name",
|
|
}
|
|
buf, err = c.Marshal(resp)
|
|
require.NoError(t, err)
|
|
_, err = w.Write(buf)
|
|
require.NoError(t, err)
|
|
}))
|
|
},
|
|
prepareMetadata: func() metadata.Metadata {
|
|
return metadata.Pairs("Authorization", "Bearer token", "My-Header", "My-Header-Value")
|
|
},
|
|
headerOption: httpcli.Header("Authorization", "true", "My-Header", "true"),
|
|
expectedRsp: &response{Id: "product-id", Name: "product-name"},
|
|
},
|
|
{
|
|
name: "without required headers",
|
|
serverMock: func() *httptest.Server {
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Validate request
|
|
require.Equal(t, "POST", r.Method)
|
|
require.Equal(t, "/user/products", r.URL.RequestURI())
|
|
|
|
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
|
require.Equal(t, "Bearer token", r.Header.Get("Authorization"))
|
|
require.Equal(t, "My-Header-Value", r.Header.Get("My-Header"))
|
|
|
|
buf, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
defer r.Body.Close()
|
|
|
|
c := jsoncodec.NewCodec()
|
|
|
|
req := &request{}
|
|
err = c.Unmarshal(buf, req)
|
|
require.NoError(t, err)
|
|
require.True(t, proto.Equal(&request{UserId: "123", OrderId: 456}, req))
|
|
|
|
// Return response
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("My-Header", "My-Header-Value")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
resp := map[string]interface{}{
|
|
"id": "product-id",
|
|
"name": "product-name",
|
|
}
|
|
buf, err = c.Marshal(resp)
|
|
require.NoError(t, err)
|
|
_, err = w.Write(buf)
|
|
require.NoError(t, err)
|
|
}))
|
|
},
|
|
prepareMetadata: func() metadata.Metadata {
|
|
return metadata.Pairs("Authorization", "Bearer token")
|
|
},
|
|
headerOption: httpcli.Header("Authorization", "true", "My-Header", "true"),
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "with required cookies",
|
|
serverMock: func() *httptest.Server {
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Validate request
|
|
require.Equal(t, "POST", r.Method)
|
|
require.Equal(t, "/user/products", r.URL.RequestURI())
|
|
|
|
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
|
require.Equal(t, "session_id=abc123; theme=dark", r.Header.Get("Cookie"))
|
|
|
|
buf, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
defer r.Body.Close()
|
|
|
|
c := jsoncodec.NewCodec()
|
|
|
|
req := &request{}
|
|
err = c.Unmarshal(buf, req)
|
|
require.NoError(t, err)
|
|
require.True(t, proto.Equal(&request{UserId: "123", OrderId: 456}, req))
|
|
|
|
// Return response
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("My-Header", "My-Header-Value")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
resp := map[string]interface{}{
|
|
"id": "product-id",
|
|
"name": "product-name",
|
|
}
|
|
buf, err = c.Marshal(resp)
|
|
require.NoError(t, err)
|
|
_, err = w.Write(buf)
|
|
require.NoError(t, err)
|
|
}))
|
|
},
|
|
prepareMetadata: func() metadata.Metadata {
|
|
return metadata.Pairs("Cookie", "session_id=abc123; theme=dark")
|
|
},
|
|
cookieOption: httpcli.Cookie("session_id", "true", "theme", "true"),
|
|
expectedRsp: &response{Id: "product-id", Name: "product-name"},
|
|
},
|
|
{
|
|
name: "without required cookies",
|
|
serverMock: func() *httptest.Server {
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Validate request
|
|
require.Equal(t, "POST", r.Method)
|
|
require.Equal(t, "/user/products", r.URL.RequestURI())
|
|
|
|
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
|
require.Equal(t, "session_id=abc123; theme=dark", r.Header.Get("Cookie"))
|
|
|
|
buf, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
defer r.Body.Close()
|
|
|
|
c := jsoncodec.NewCodec()
|
|
|
|
req := &request{}
|
|
err = c.Unmarshal(buf, req)
|
|
require.NoError(t, err)
|
|
require.True(t, proto.Equal(&request{UserId: "123", OrderId: 456}, req))
|
|
|
|
// Return response
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("My-Header", "My-Header-Value")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
resp := map[string]interface{}{
|
|
"id": "product-id",
|
|
"name": "product-name",
|
|
}
|
|
buf, err = c.Marshal(resp)
|
|
require.NoError(t, err)
|
|
_, err = w.Write(buf)
|
|
require.NoError(t, err)
|
|
}))
|
|
},
|
|
prepareMetadata: func() metadata.Metadata {
|
|
return metadata.Pairs("Cookie", "session_id=abc123")
|
|
},
|
|
cookieOption: httpcli.Cookie("session_id", "true", "theme", "true"),
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "with headers and cookies",
|
|
serverMock: func() *httptest.Server {
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Validate request
|
|
require.Equal(t, "POST", r.Method)
|
|
require.Equal(t, "/user/products", r.URL.RequestURI())
|
|
|
|
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
|
require.Equal(t, "Bearer token", r.Header.Get("Authorization"))
|
|
require.Equal(t, "My-Header-Value", r.Header.Get("My-Header"))
|
|
require.Equal(t, "session_id=abc123; theme=dark", r.Header.Get("Cookie"))
|
|
|
|
buf, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
defer r.Body.Close()
|
|
|
|
c := jsoncodec.NewCodec()
|
|
|
|
req := &request{}
|
|
err = c.Unmarshal(buf, req)
|
|
require.NoError(t, err)
|
|
require.True(t, proto.Equal(&request{UserId: "123", OrderId: 456}, req))
|
|
|
|
// Return response
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("My-Header", "My-Header-Value")
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
resp := map[string]interface{}{
|
|
"id": "product-id",
|
|
"name": "product-name",
|
|
}
|
|
buf, err = c.Marshal(resp)
|
|
require.NoError(t, err)
|
|
_, err = w.Write(buf)
|
|
require.NoError(t, err)
|
|
}))
|
|
},
|
|
prepareMetadata: func() metadata.Metadata {
|
|
return metadata.Pairs(
|
|
"Authorization", "Bearer token",
|
|
"My-Header", "My-Header-Value",
|
|
"Cookie", "session_id=abc123; theme=dark",
|
|
)
|
|
},
|
|
headerOption: httpcli.Header("Authorization", "true", "My-Header", "true"),
|
|
cookieOption: httpcli.Cookie("session_id", "true", "theme", "true"),
|
|
expectedRsp: &response{Id: "product-id", Name: "product-name"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
server := tt.serverMock()
|
|
defer server.Close()
|
|
|
|
httpClient := httpcli.NewClient(
|
|
client.Codec("application/json", jsoncodec.NewCodec()),
|
|
)
|
|
|
|
var (
|
|
ctx = metadata.NewOutgoingContext(context.Background(), tt.prepareMetadata())
|
|
req = &request{UserId: "123", OrderId: 456}
|
|
rsp = &response{}
|
|
|
|
respMetadata = metadata.Metadata{}
|
|
)
|
|
|
|
opts := []client.CallOption{
|
|
client.WithAddress(server.URL),
|
|
client.WithResponseMetadata(&respMetadata),
|
|
httpcli.Method(http.MethodPost),
|
|
httpcli.Path("/user/products"),
|
|
httpcli.Body("*"),
|
|
}
|
|
if tt.headerOption != nil {
|
|
opts = append(opts, tt.headerOption)
|
|
}
|
|
if tt.cookieOption != nil {
|
|
opts = append(opts, tt.cookieOption)
|
|
}
|
|
|
|
err := httpClient.Call(
|
|
ctx,
|
|
httpClient.NewRequest("test.service", "Test.Call", req),
|
|
rsp,
|
|
opts...,
|
|
)
|
|
|
|
if tt.wantErr {
|
|
require.Error(t, err)
|
|
require.Empty(t, rsp)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.True(t, proto.Equal(tt.expectedRsp, rsp))
|
|
require.Equal(t, "application/json", respMetadata.GetJoined("Content-Type"))
|
|
require.Equal(t, "My-Header-Value", respMetadata.GetJoined("My-Header"))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_Call_NoContent(t *testing.T) {
|
|
type (
|
|
request = pb.Test_Client_Call_Request
|
|
response = pb.Test_Client_Call_Response
|
|
)
|
|
|
|
serverMock := func() *httptest.Server {
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// Validate request
|
|
require.Equal(t, "POST", r.Method)
|
|
require.Equal(t, "/user/products", r.URL.RequestURI())
|
|
|
|
require.Equal(t, "application/json", r.Header.Get("Content-Type"))
|
|
require.Equal(t, "Bearer token", r.Header.Get("Authorization"))
|
|
require.Equal(t, "My-Header-Value", r.Header.Get("My-Header"))
|
|
|
|
buf, err := io.ReadAll(r.Body)
|
|
require.NoError(t, err)
|
|
defer r.Body.Close()
|
|
|
|
c := jsoncodec.NewCodec()
|
|
|
|
req := &request{}
|
|
err = c.Unmarshal(buf, req)
|
|
require.NoError(t, err)
|
|
require.True(t, proto.Equal(&request{UserId: "123", OrderId: 456}, req))
|
|
|
|
// Return response
|
|
w.Header().Set("Content-Type", "application/json")
|
|
w.Header().Set("My-Header", "My-Header-Value")
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}))
|
|
}
|
|
|
|
server := serverMock()
|
|
defer server.Close()
|
|
|
|
httpClient := httpcli.NewClient(
|
|
client.Codec("application/json", jsoncodec.NewCodec()),
|
|
)
|
|
|
|
var (
|
|
ctx = metadata.NewOutgoingContext(
|
|
context.Background(),
|
|
metadata.Pairs("Authorization", "Bearer token", "My-Header", "My-Header-Value"),
|
|
)
|
|
req = &request{UserId: "123", OrderId: 456}
|
|
rsp = &response{}
|
|
|
|
respMetadata = metadata.Metadata{}
|
|
)
|
|
|
|
opts := []client.CallOption{
|
|
client.WithAddress(server.URL),
|
|
client.WithResponseMetadata(&respMetadata),
|
|
httpcli.Method(http.MethodPost),
|
|
httpcli.Path("/user/products"),
|
|
httpcli.Body("*"),
|
|
}
|
|
|
|
err := httpClient.Call(
|
|
ctx,
|
|
httpClient.NewRequest("test.service", "Test.Call", req),
|
|
rsp,
|
|
opts...,
|
|
)
|
|
require.NoError(t, err)
|
|
require.Empty(t, rsp)
|
|
|
|
require.Equal(t, "application/json", respMetadata.GetJoined("Content-Type"))
|
|
require.Equal(t, "My-Header-Value", respMetadata.GetJoined("My-Header"))
|
|
}
|
|
|
|
func TestClient_Call_RequestTimeoutError(t *testing.T) {
|
|
type (
|
|
request = pb.Test_Client_Call_Request
|
|
response = pb.Test_Client_Call_Response
|
|
)
|
|
|
|
serverMock := func() *httptest.Server {
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
time.Sleep(2 * time.Millisecond)
|
|
}))
|
|
}
|
|
|
|
server := serverMock()
|
|
defer server.Close()
|
|
|
|
httpClient := httpcli.NewClient(
|
|
client.Codec("application/json", jsoncodec.NewCodec()),
|
|
)
|
|
|
|
var (
|
|
ctx = context.Background()
|
|
req = &request{UserId: "123", OrderId: 456}
|
|
rsp = &response{}
|
|
)
|
|
|
|
opts := []client.CallOption{
|
|
client.WithAddress(server.URL),
|
|
client.WithRequestTimeout(time.Millisecond),
|
|
httpcli.Method(http.MethodPost),
|
|
httpcli.Path("/user/products"),
|
|
httpcli.Body("*"),
|
|
}
|
|
|
|
err := httpClient.Call(
|
|
ctx,
|
|
httpClient.NewRequest("test.service", "Test.Call", req),
|
|
rsp,
|
|
opts...,
|
|
)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestClient_Call_ContextDeadlineError(t *testing.T) {
|
|
type (
|
|
request = pb.Test_Client_Call_Request
|
|
response = pb.Test_Client_Call_Response
|
|
)
|
|
|
|
serverMock := func() *httptest.Server {
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
time.Sleep(2 * time.Millisecond)
|
|
}))
|
|
}
|
|
|
|
server := serverMock()
|
|
defer server.Close()
|
|
|
|
httpClient := httpcli.NewClient(
|
|
client.Codec("application/json", jsoncodec.NewCodec()),
|
|
)
|
|
|
|
var (
|
|
ctx, cancel = context.WithDeadline(context.Background(), time.Now().Add(time.Millisecond))
|
|
req = &request{UserId: "123", OrderId: 456}
|
|
rsp = &response{}
|
|
)
|
|
defer cancel()
|
|
|
|
opts := []client.CallOption{
|
|
client.WithAddress(server.URL),
|
|
httpcli.Method(http.MethodPost),
|
|
httpcli.Path("/user/products"),
|
|
httpcli.Body("*"),
|
|
}
|
|
|
|
err := httpClient.Call(
|
|
ctx,
|
|
httpClient.NewRequest("test.service", "Test.Call", req),
|
|
rsp,
|
|
opts...,
|
|
)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestClient_Call_ContextCanceled(t *testing.T) {
|
|
type (
|
|
request = pb.Test_Client_Call_Request
|
|
response = pb.Test_Client_Call_Response
|
|
)
|
|
|
|
serverMock := func() *httptest.Server {
|
|
return httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
time.Sleep(2 * time.Millisecond)
|
|
}))
|
|
}
|
|
|
|
server := serverMock()
|
|
defer server.Close()
|
|
|
|
httpClient := httpcli.NewClient(
|
|
client.Codec("application/json", jsoncodec.NewCodec()),
|
|
)
|
|
|
|
var (
|
|
ctx, cancel = context.WithCancel(context.Background())
|
|
req = &request{UserId: "123", OrderId: 456}
|
|
rsp = &response{}
|
|
)
|
|
cancel()
|
|
|
|
opts := []client.CallOption{
|
|
client.WithAddress(server.URL),
|
|
httpcli.Method(http.MethodPost),
|
|
httpcli.Path("/user/products"),
|
|
httpcli.Body("*"),
|
|
}
|
|
|
|
err := httpClient.Call(
|
|
ctx,
|
|
httpClient.NewRequest("test.service", "Test.Call", req),
|
|
rsp,
|
|
opts...,
|
|
)
|
|
require.Error(t, err)
|
|
}
|