1303 lines
32 KiB
Go
1303 lines
32 KiB
Go
package builder_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/proto"
|
|
|
|
"go.unistack.org/micro-client-http/v4/builder"
|
|
pb "go.unistack.org/micro-client-http/v4/builder/proto"
|
|
)
|
|
|
|
func TestNewRequestBuilder(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
method string
|
|
bodyOpt string
|
|
msg proto.Message
|
|
wantError bool
|
|
}{
|
|
{
|
|
name: "empty path",
|
|
path: "",
|
|
method: "GET",
|
|
bodyOpt: "",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "invalid method",
|
|
path: "/v1/users",
|
|
method: "INVALID",
|
|
bodyOpt: "",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "nil msg",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "*",
|
|
msg: nil,
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "GET without body",
|
|
path: "/v1/users",
|
|
method: "GET",
|
|
bodyOpt: "",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "GET with body",
|
|
path: "/v1/users",
|
|
method: "GET",
|
|
bodyOpt: "*",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "DELETE without body",
|
|
path: "/v1/users/42",
|
|
method: "DELETE",
|
|
bodyOpt: "",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "DELETE with body",
|
|
path: "/v1/users/42",
|
|
method: "DELETE",
|
|
bodyOpt: "*",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "POST with body",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "*",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "POST without body",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "PUT with body",
|
|
path: "/v1/users/42",
|
|
method: "PUT",
|
|
bodyOpt: "*",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "PUT without body",
|
|
path: "/v1/users/42",
|
|
method: "PUT",
|
|
bodyOpt: "",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "PATCH with body",
|
|
path: "/v1/users/42",
|
|
method: "PATCH",
|
|
bodyOpt: "*",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "PATCH without body",
|
|
path: "/v1/users/42",
|
|
method: "PATCH",
|
|
bodyOpt: "",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "HEAD without body",
|
|
path: "/v1/users/42",
|
|
method: "HEAD",
|
|
bodyOpt: "",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "HEAD with body",
|
|
path: "/v1/users/42",
|
|
method: "HEAD",
|
|
bodyOpt: "*",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "OPTIONS without body",
|
|
path: "/v1/users/42",
|
|
method: "OPTIONS",
|
|
bodyOpt: "",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "OPTIONS with body",
|
|
path: "/v1/users/42",
|
|
method: "OPTIONS",
|
|
bodyOpt: "*",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "lowercase method still valid",
|
|
path: "/v1/users",
|
|
method: "post",
|
|
bodyOpt: "*",
|
|
msg: &pb.TestRequestBuilder{},
|
|
wantError: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rb, err := builder.NewRequestBuilder(tt.path, tt.method, tt.bodyOpt, tt.msg)
|
|
|
|
if tt.wantError {
|
|
require.Error(t, err)
|
|
require.Nil(t, rb)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.NotNil(t, rb)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRequestBuilder_PathOnly(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
method string
|
|
msg func() proto.Message
|
|
expectedPath string
|
|
expectedMsg func() proto.Message
|
|
wantError bool
|
|
}{
|
|
{
|
|
name: "primitive case",
|
|
path: "/v1/users/{user_id}/orders/{order_id}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PrimitiveCase
|
|
return &Msg{
|
|
UserId: "42",
|
|
OrderId: 123,
|
|
}
|
|
},
|
|
expectedPath: "/v1/users/42/orders/123",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PrimitiveCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "nested case",
|
|
path: "/v1/users/{user.id}/orders/{order.id}/products/{order.product.id}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_NestedCase
|
|
type User = pb.Test_PathOnly_NestedCase_User
|
|
type Order = pb.Test_PathOnly_NestedCase_Order
|
|
type Product = pb.Test_PathOnly_NestedCase_Order_Product
|
|
return &Msg{
|
|
User: &User{Id: "42"},
|
|
Order: &Order{
|
|
Id: 123,
|
|
Product: &Product{Id: 456},
|
|
},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users/42/orders/123/products/456",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_NestedCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "multiply case",
|
|
path: "/v1/users/{user_id}/orders/{order.id}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_MultipleCase
|
|
type Order = pb.Test_PathOnly_MultipleCase_Order
|
|
return &Msg{
|
|
UserId: "42",
|
|
Order: &Order{
|
|
Id: "123",
|
|
},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users/42/orders/123",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_MultipleCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "not found case",
|
|
path: "/v1/users/{userId}/orders/{order_id}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PrimitiveCase
|
|
return &Msg{
|
|
UserId: "42",
|
|
OrderId: 123,
|
|
}
|
|
},
|
|
expectedPath: "",
|
|
expectedMsg: func() proto.Message {
|
|
return nil
|
|
},
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "zero-value case",
|
|
path: "/v1/users/{user_id}/orders/{order_id}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PrimitiveCase
|
|
return &Msg{UserId: "42"}
|
|
},
|
|
expectedPath: "",
|
|
expectedMsg: func() proto.Message {
|
|
return nil
|
|
},
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "repeated case",
|
|
path: "/v1/users/{user_id}/orders/{order_id}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_RepeatedCase
|
|
return &Msg{
|
|
UserId: []string{"42"},
|
|
OrderId: 123,
|
|
}
|
|
},
|
|
expectedPath: "",
|
|
expectedMsg: func() proto.Message {
|
|
return nil
|
|
},
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "non-primitive message case",
|
|
path: "/v1/users/{user_id}/orders/{order_id}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_NonPrimitiveMessageCase
|
|
type User = pb.Test_PathOnly_NonPrimitiveMessageCase_User
|
|
return &Msg{
|
|
UserId: &User{Id: "42"},
|
|
OrderId: 123,
|
|
}
|
|
},
|
|
expectedPath: "",
|
|
expectedMsg: func() proto.Message {
|
|
return nil
|
|
},
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "non-primitive map case",
|
|
path: "/v1/users/{user_id}/orders/{order_id}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_NonPrimitiveMapCase
|
|
return &Msg{
|
|
UserId: map[string]string{"": ""},
|
|
OrderId: 123,
|
|
}
|
|
},
|
|
expectedPath: "",
|
|
expectedMsg: func() proto.Message {
|
|
return nil
|
|
},
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "custom verb case",
|
|
path: "/v1/users/{user_id}:get",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PrimitiveCase
|
|
return &Msg{UserId: "42"}
|
|
},
|
|
expectedPath: "/v1/users/42:get",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PrimitiveCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
// pattern cases
|
|
{
|
|
name: "pattern case -> *",
|
|
path: "/v1/users/{pattern=*}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PatternCase
|
|
return &Msg{Pattern: "42"}
|
|
},
|
|
expectedPath: "/v1/users/42",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PatternCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "pattern case -> * (invalid value)",
|
|
path: "/v1/users/{pattern=*}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PatternCase
|
|
return &Msg{Pattern: "a/b/c"}
|
|
},
|
|
expectedPath: "",
|
|
expectedMsg: func() proto.Message {
|
|
return nil
|
|
},
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "pattern case -> * (empty value)",
|
|
path: "/v1/users/{pattern=*}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PatternCase
|
|
return &Msg{}
|
|
},
|
|
expectedPath: "",
|
|
expectedMsg: func() proto.Message {
|
|
return nil
|
|
},
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "pattern case -> **",
|
|
path: "/v1/users/{pattern=**}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PatternCase
|
|
return &Msg{Pattern: "a/b/c"}
|
|
},
|
|
expectedPath: "/v1/users/a/b/c",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PatternCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "pattern case -> ** (empty value)",
|
|
path: "/v1/users/{pattern=**}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PatternCase
|
|
return &Msg{}
|
|
},
|
|
expectedPath: "/v1/users/",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PatternCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "pattern case -> composite pattern",
|
|
path: "/v1/users/{pattern=*/orders/*}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PatternCase
|
|
return &Msg{Pattern: "42/orders/123"}
|
|
},
|
|
expectedPath: "/v1/users/42/orders/123",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PatternCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "pattern case -> composite pattern (with extra segment)",
|
|
path: "/v1/users/{pattern=*/orders/*}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_PatternCase
|
|
return &Msg{Pattern: "42/orders/123/456"}
|
|
},
|
|
expectedPath: "",
|
|
expectedMsg: func() proto.Message {
|
|
return nil
|
|
},
|
|
wantError: true,
|
|
},
|
|
{
|
|
name: "pattern case -> ** (composite segments)",
|
|
path: "/v1/users/{pattern=**}/orders/{order_id}/products/{product_id}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_CompositePatternCase
|
|
return &Msg{
|
|
Pattern: "a/b/c",
|
|
OrderId: "123",
|
|
ProductId: "456",
|
|
}
|
|
},
|
|
expectedPath: "/v1/users/a/b/c/orders/123/products/456",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_CompositePatternCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "pattern case -> ** (with empty value and composite segments)",
|
|
path: "/v1/users/{pattern=**}/orders/{order_id}/products/{product_id}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_CompositePatternCase
|
|
return &Msg{
|
|
Pattern: "",
|
|
OrderId: "123",
|
|
ProductId: "456",
|
|
}
|
|
},
|
|
expectedPath: "/v1/users//orders/123/products/456",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_CompositePatternCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "pattern case -> composite pattern (multiple consecutive variables)",
|
|
path: "/v1/{pattern}/{order_id}/{product_id}",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_CompositePatternCase
|
|
return &Msg{
|
|
Pattern: "123",
|
|
OrderId: "456",
|
|
ProductId: "789",
|
|
}
|
|
},
|
|
expectedPath: "/v1/123/456/789",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_PathOnly_CompositePatternCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rb, err := builder.NewRequestBuilder(tt.path, tt.method, "", tt.msg())
|
|
require.NoError(t, err)
|
|
|
|
path, newMsg, err := rb.Build()
|
|
if tt.wantError {
|
|
require.Error(t, err)
|
|
require.Empty(t, path)
|
|
require.Nil(t, newMsg)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expectedPath, path)
|
|
require.True(t, proto.Equal(tt.expectedMsg(), newMsg))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRequestBuilder_QueryOnly(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
method string
|
|
msg func() proto.Message
|
|
expectedPath string
|
|
expectedMsg func() proto.Message
|
|
wantError bool
|
|
}{
|
|
{
|
|
name: "primitive case",
|
|
path: "/v1/users",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_QueryOnly_PrimitiveCase
|
|
return &Msg{
|
|
UserId: "42",
|
|
OrderId: 123,
|
|
Flag: true,
|
|
}
|
|
},
|
|
expectedPath: "/v1/users?flag=true&order_id=123&user_id=42",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_QueryOnly_PrimitiveCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "primitive case (with empty fields)",
|
|
path: "/v1/users",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_QueryOnly_PrimitiveCase
|
|
return &Msg{UserId: "42"}
|
|
},
|
|
expectedPath: "/v1/users?user_id=42",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_QueryOnly_PrimitiveCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "repeated case",
|
|
path: "/v1/users",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_QueryOnly_RepeatedCase
|
|
return &Msg{
|
|
Strings: []string{"foo", "bar"},
|
|
Integers: []int64{1, 2, 3},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users?integers=1&integers=2&integers=3&strings=foo&strings=bar",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_QueryOnly_RepeatedCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "nested message case",
|
|
path: "/v1/users",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_QueryOnly_NestedMessageCase
|
|
type Filter = pb.Test_QueryOnly_NestedMessageCase_Filter
|
|
type SubFilter = pb.Test_QueryOnly_NestedMessageCase_Filter_SubFilter
|
|
return &Msg{
|
|
UserId: "42",
|
|
Filter: &Filter{
|
|
Age: 30,
|
|
Name: "Alice",
|
|
SubFilter: &SubFilter{
|
|
SubAge: 20,
|
|
SubName: "John",
|
|
},
|
|
},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users?filter.age=30&filter.name=Alice&filter.sub_filter.sub_age=20&filter.sub_filter.sub_name=John&user_id=42",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_QueryOnly_NestedMessageCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "nested map case",
|
|
path: "/v1/users",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_QueryOnly_NestedMapCase
|
|
type SubFilter = pb.Test_QueryOnly_NestedMapCase_SubFilter
|
|
return &Msg{
|
|
UserId: "42",
|
|
FirstFilter: map[string]string{"age": "30", "name": "Alice"},
|
|
SecondFilter: map[string]*SubFilter{
|
|
"filter1": {SubAge: 20, SubName: "John"},
|
|
"filter2": {SubAge: 40, SubName: "Travolta"},
|
|
},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users?first_filter.age=30&first_filter.name=Alice&second_filter.filter1.sub_age=20&second_filter.filter1.sub_name=John&second_filter.filter2.sub_age=40&second_filter.filter2.sub_name=Travolta&user_id=42",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_QueryOnly_NestedMapCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "multiple case",
|
|
path: "/v1/users",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_QueryOnly_MultipleCase
|
|
type Filter = pb.Test_QueryOnly_MultipleCase_Filter
|
|
type SubFilter = pb.Test_QueryOnly_MultipleCase_SubFilter
|
|
return &Msg{
|
|
UserId: "42",
|
|
Strings: []string{"foo", "bar"},
|
|
FirstFilter: &Filter{
|
|
Age: 30,
|
|
SubFilter: &SubFilter{
|
|
SubAge: 20,
|
|
},
|
|
},
|
|
SecondFilter: map[string]*SubFilter{
|
|
"filter1": {SubAge: 20},
|
|
"filter2": {SubAge: 40},
|
|
},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users?first_filter.age=30&first_filter.sub_filter.sub_age=20&second_filter.filter1.sub_age=20&second_filter.filter2.sub_age=40&strings=foo&strings=bar&user_id=42",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_QueryOnly_MultipleCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "repeated message case",
|
|
path: "/v1/users",
|
|
method: "GET",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_QueryOnly_RepeatedMessageCase
|
|
type Filter = pb.Test_QueryOnly_RepeatedMessageCase_Filter
|
|
return &Msg{Filters: []*Filter{{Age: 20}, {Age: 30}, {Age: 40}}}
|
|
},
|
|
expectedPath: "",
|
|
expectedMsg: nil,
|
|
wantError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rb, err := builder.NewRequestBuilder(tt.path, tt.method, "", tt.msg())
|
|
require.NoError(t, err)
|
|
|
|
path, newMsg, err := rb.Build()
|
|
if tt.wantError {
|
|
require.Error(t, err)
|
|
require.Empty(t, path)
|
|
require.Nil(t, newMsg)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expectedPath, path)
|
|
require.True(t, proto.Equal(tt.expectedMsg(), newMsg))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRequestBuilder_BodyOnly(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
method string
|
|
bodyOpt string
|
|
msg func() proto.Message
|
|
expectedPath string
|
|
expectedMsg func() proto.Message
|
|
wantError bool
|
|
}{
|
|
{
|
|
name: "primitive case: full body",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "*",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_PrimitiveCase
|
|
type Product = pb.Test_BodyOnly_PrimitiveCase_Product
|
|
return &Msg{
|
|
UserId: "42",
|
|
OrderId: 123,
|
|
Flag: true,
|
|
Strings: []string{"foo", "bar"},
|
|
Product: &Product{
|
|
Id: "product_id",
|
|
Name: "product_name",
|
|
},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_PrimitiveCase
|
|
type Product = pb.Test_BodyOnly_PrimitiveCase_Product
|
|
return &Msg{
|
|
UserId: "42",
|
|
OrderId: 123,
|
|
Flag: true,
|
|
Strings: []string{"foo", "bar"},
|
|
Product: &Product{
|
|
Id: "product_id",
|
|
Name: "product_name",
|
|
},
|
|
}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "primitive case: specified primitive field",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "user_id",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_PrimitiveCase
|
|
type Product = pb.Test_BodyOnly_PrimitiveCase_Product
|
|
return &Msg{
|
|
UserId: "42",
|
|
OrderId: 123,
|
|
Flag: true,
|
|
Strings: []string{"foo", "bar"},
|
|
Product: &Product{
|
|
Id: "product_id",
|
|
Name: "product_name",
|
|
},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users?flag=true&order_id=123&product.id=product_id&product.name=product_name&strings=foo&strings=bar",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_PrimitiveCase
|
|
return &Msg{
|
|
UserId: "42",
|
|
}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "primitive case: specified non-primitive field",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "product",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_PrimitiveCase
|
|
type Product = pb.Test_BodyOnly_PrimitiveCase_Product
|
|
return &Msg{
|
|
UserId: "42",
|
|
OrderId: 123,
|
|
Flag: true,
|
|
Strings: []string{"foo", "bar"},
|
|
Product: &Product{
|
|
Id: "product_id",
|
|
Name: "product_name",
|
|
},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users?flag=true&order_id=123&strings=foo&strings=bar&user_id=42",
|
|
expectedMsg: func() proto.Message {
|
|
type Product = pb.Test_BodyOnly_PrimitiveCase_Product
|
|
return &Product{
|
|
Id: "product_id",
|
|
Name: "product_name",
|
|
}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "primitive case: empty fields",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "*",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_PrimitiveCase
|
|
return &Msg{
|
|
UserId: "42",
|
|
Flag: true,
|
|
}
|
|
},
|
|
expectedPath: "/v1/users",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_PrimitiveCase
|
|
type Product = pb.Test_BodyOnly_PrimitiveCase_Product
|
|
return &Msg{
|
|
UserId: "42",
|
|
OrderId: 0,
|
|
Flag: true,
|
|
Strings: []string{},
|
|
Product: &Product{},
|
|
}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "primitive case: empty all fields",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "*",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_PrimitiveCase
|
|
return &Msg{}
|
|
},
|
|
expectedPath: "/v1/users",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_PrimitiveCase
|
|
type Product = pb.Test_BodyOnly_PrimitiveCase_Product
|
|
return &Msg{
|
|
UserId: "",
|
|
OrderId: 0,
|
|
Flag: false,
|
|
Strings: []string{},
|
|
Product: &Product{},
|
|
}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "nested case: full body",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "*",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_NestedCase
|
|
type Filter = pb.Test_BodyOnly_NestedCase_Filter
|
|
type SubFilter = pb.Test_BodyOnly_NestedCase_Filter_SubFilter
|
|
return &Msg{
|
|
UserId: "42",
|
|
FirstFilter: &Filter{
|
|
Age: 30,
|
|
Name: "Alice",
|
|
SubFilter: &SubFilter{
|
|
SubAge: 40,
|
|
SubName: "John",
|
|
},
|
|
},
|
|
SecondFilter: &Filter{
|
|
Age: 50,
|
|
Name: "Alex",
|
|
SubFilter: &SubFilter{
|
|
SubAge: 60,
|
|
SubName: "Mike",
|
|
},
|
|
},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_NestedCase
|
|
type Filter = pb.Test_BodyOnly_NestedCase_Filter
|
|
type SubFilter = pb.Test_BodyOnly_NestedCase_Filter_SubFilter
|
|
return &Msg{
|
|
UserId: "42",
|
|
FirstFilter: &Filter{
|
|
Age: 30,
|
|
Name: "Alice",
|
|
SubFilter: &SubFilter{
|
|
SubAge: 40,
|
|
SubName: "John",
|
|
},
|
|
},
|
|
SecondFilter: &Filter{
|
|
Age: 50,
|
|
Name: "Alex",
|
|
SubFilter: &SubFilter{
|
|
SubAge: 60,
|
|
SubName: "Mike",
|
|
},
|
|
},
|
|
}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "nested case: specified non-primitive field",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "second_filter",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_NestedCase
|
|
type Filter = pb.Test_BodyOnly_NestedCase_Filter
|
|
type SubFilter = pb.Test_BodyOnly_NestedCase_Filter_SubFilter
|
|
return &Msg{
|
|
UserId: "42",
|
|
FirstFilter: &Filter{
|
|
Age: 30,
|
|
Name: "Alice",
|
|
SubFilter: &SubFilter{
|
|
SubAge: 40,
|
|
SubName: "John",
|
|
},
|
|
},
|
|
SecondFilter: &Filter{
|
|
Age: 50,
|
|
Name: "Alex",
|
|
SubFilter: &SubFilter{
|
|
SubAge: 60,
|
|
SubName: "Mike",
|
|
},
|
|
},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users?first_filter.age=30&first_filter.name=Alice&first_filter.sub_filter.sub_age=40&first_filter.sub_filter.sub_name=John&user_id=42",
|
|
expectedMsg: func() proto.Message {
|
|
type Filter = pb.Test_BodyOnly_NestedCase_Filter
|
|
type SubFilter = pb.Test_BodyOnly_NestedCase_Filter_SubFilter
|
|
return &Filter{
|
|
Age: 50,
|
|
Name: "Alex",
|
|
SubFilter: &SubFilter{
|
|
SubAge: 60,
|
|
SubName: "Mike",
|
|
},
|
|
}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "repeated message case",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "*",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_RepeatedMessageCase
|
|
type Product = pb.Test_BodyOnly_RepeatedMessageCase_Product
|
|
return &Msg{
|
|
UserId: "42",
|
|
Products: []*Product{
|
|
{Id: "product_id_1", Name: "product_name_1"},
|
|
{Id: "product_id_2", Name: "product_name_2"},
|
|
},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_RepeatedMessageCase
|
|
type Product = pb.Test_BodyOnly_RepeatedMessageCase_Product
|
|
return &Msg{
|
|
UserId: "42",
|
|
Products: []*Product{
|
|
{Id: "product_id_1", Name: "product_name_1"},
|
|
{Id: "product_id_2", Name: "product_name_2"},
|
|
},
|
|
}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "repeated message case (empty)",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "*",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_RepeatedMessageCase
|
|
type Product = pb.Test_BodyOnly_RepeatedMessageCase_Product
|
|
return &Msg{
|
|
UserId: "42",
|
|
Products: []*Product{},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_RepeatedMessageCase
|
|
type Product = pb.Test_BodyOnly_RepeatedMessageCase_Product
|
|
return &Msg{
|
|
UserId: "42",
|
|
Products: []*Product{},
|
|
}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "primitive and non-primitive map case",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "*",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_MapCase
|
|
type SubFilter = pb.Test_BodyOnly_MapCase_SubFilter
|
|
return &Msg{
|
|
FirstFilter: map[string]string{"age": "50", "name": "Alex"},
|
|
SecondFilter: map[string]*SubFilter{
|
|
"second_filter_1": {SubAge: 30, SubName: "Alice"},
|
|
"second_filter_2": {SubAge: 40, SubName: "John"},
|
|
},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_MapCase
|
|
type SubFilter = pb.Test_BodyOnly_MapCase_SubFilter
|
|
return &Msg{
|
|
FirstFilter: map[string]string{"age": "50", "name": "Alex"},
|
|
SecondFilter: map[string]*SubFilter{
|
|
"second_filter_1": {SubAge: 30, SubName: "Alice"},
|
|
"second_filter_2": {SubAge: 40, SubName: "John"},
|
|
},
|
|
}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "primitive and non-primitive map case (empty)",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "*",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_MapCase
|
|
return &Msg{}
|
|
},
|
|
expectedPath: "/v1/users",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_MapCase
|
|
type SubFilter = pb.Test_BodyOnly_MapCase_SubFilter
|
|
return &Msg{
|
|
FirstFilter: map[string]string{},
|
|
SecondFilter: map[string]*SubFilter{},
|
|
}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "multiple case",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "*",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_MultipleCase
|
|
type SubFilter = pb.Test_BodyOnly_MultipleCase_SubFilter
|
|
return &Msg{
|
|
UserId: "42",
|
|
FirstFilter: []*SubFilter{
|
|
{SubAge: 30, SubName: "Alice"},
|
|
{SubAge: 40, SubName: "John"},
|
|
},
|
|
SecondFilter: map[string]*SubFilter{
|
|
"second_filter_1": {SubAge: 50, SubName: "Alex"},
|
|
"second_filter_2": {SubAge: 60, SubName: "Max"},
|
|
},
|
|
ThirdFilter: &SubFilter{SubAge: 70, SubName: "Ricardo"},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_MultipleCase
|
|
type SubFilter = pb.Test_BodyOnly_MultipleCase_SubFilter
|
|
return &Msg{
|
|
UserId: "42",
|
|
FirstFilter: []*SubFilter{
|
|
{SubAge: 30, SubName: "Alice"},
|
|
{SubAge: 40, SubName: "John"},
|
|
},
|
|
SecondFilter: map[string]*SubFilter{
|
|
"second_filter_1": {SubAge: 50, SubName: "Alex"},
|
|
"second_filter_2": {SubAge: 60, SubName: "Max"},
|
|
},
|
|
ThirdFilter: &SubFilter{SubAge: 70, SubName: "Ricardo"},
|
|
}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "multiple case (empty)",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "*",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_MultipleCase
|
|
return &Msg{}
|
|
},
|
|
expectedPath: "/v1/users",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_MultipleCase
|
|
type SubFilter = pb.Test_BodyOnly_MultipleCase_SubFilter
|
|
return &Msg{
|
|
UserId: "",
|
|
FirstFilter: []*SubFilter{},
|
|
SecondFilter: map[string]*SubFilter{},
|
|
ThirdFilter: &SubFilter{},
|
|
}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "nonexistent body field",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "nonexistent_field",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_BodyOnly_PrimitiveCase
|
|
return &Msg{}
|
|
},
|
|
expectedPath: "",
|
|
expectedMsg: nil,
|
|
wantError: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rb, err := builder.NewRequestBuilder(tt.path, tt.method, tt.bodyOpt, tt.msg())
|
|
require.NoError(t, err)
|
|
|
|
path, newMsg, err := rb.Build()
|
|
if tt.wantError {
|
|
require.Error(t, err)
|
|
require.Empty(t, path)
|
|
require.Nil(t, newMsg)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expectedPath, path)
|
|
require.True(t, proto.Equal(tt.expectedMsg(), newMsg))
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRequestBuilder_Mixed(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
path string
|
|
method string
|
|
bodyOpt string
|
|
msg func() proto.Message
|
|
expectedPath string
|
|
expectedMsg func() proto.Message
|
|
wantError bool
|
|
}{
|
|
{
|
|
name: "path + query",
|
|
path: "/v1/users/{user_id}/orders/{order_id}",
|
|
method: "POST",
|
|
bodyOpt: "",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_Mixed_PrimitiveCase
|
|
type Product = pb.Test_Mixed_PrimitiveCase_Product
|
|
return &Msg{
|
|
UserId: "42",
|
|
OrderId: 123,
|
|
Product: &Product{
|
|
Id: "product_id",
|
|
Name: "product_name",
|
|
},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users/42/orders/123?product.id=product_id&product.name=product_name",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_Mixed_PrimitiveCase
|
|
return &Msg{}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "path + body",
|
|
path: "/v1/users/{user_id}/orders/{order_id}",
|
|
method: "POST",
|
|
bodyOpt: "*",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_Mixed_PrimitiveCase
|
|
type Product = pb.Test_Mixed_PrimitiveCase_Product
|
|
return &Msg{
|
|
UserId: "42",
|
|
OrderId: 123,
|
|
Product: &Product{
|
|
Id: "product_id",
|
|
Name: "product_name",
|
|
},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users/42/orders/123",
|
|
expectedMsg: func() proto.Message {
|
|
type Msg = pb.Test_Mixed_PrimitiveCase
|
|
type Product = pb.Test_Mixed_PrimitiveCase_Product
|
|
return &Msg{
|
|
Product: &Product{
|
|
Id: "product_id",
|
|
Name: "product_name",
|
|
},
|
|
}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "path + body + query",
|
|
path: "/v1/users/{user_id}",
|
|
method: "POST",
|
|
bodyOpt: "product",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_Mixed_PrimitiveCase
|
|
type Product = pb.Test_Mixed_PrimitiveCase_Product
|
|
return &Msg{
|
|
UserId: "42",
|
|
OrderId: 123,
|
|
Product: &Product{
|
|
Id: "product_id",
|
|
Name: "product_name",
|
|
},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users/42?order_id=123",
|
|
expectedMsg: func() proto.Message {
|
|
type Product = pb.Test_Mixed_PrimitiveCase_Product
|
|
return &Product{
|
|
Id: "product_id",
|
|
Name: "product_name",
|
|
}
|
|
},
|
|
wantError: false,
|
|
},
|
|
{
|
|
name: "query + body",
|
|
path: "/v1/users",
|
|
method: "POST",
|
|
bodyOpt: "product",
|
|
msg: func() proto.Message {
|
|
type Msg = pb.Test_Mixed_PrimitiveCase
|
|
type Product = pb.Test_Mixed_PrimitiveCase_Product
|
|
return &Msg{
|
|
UserId: "42",
|
|
OrderId: 123,
|
|
Product: &Product{
|
|
Id: "product_id",
|
|
Name: "product_name",
|
|
},
|
|
}
|
|
},
|
|
expectedPath: "/v1/users?order_id=123&user_id=42",
|
|
expectedMsg: func() proto.Message {
|
|
type Product = pb.Test_Mixed_PrimitiveCase_Product
|
|
return &Product{
|
|
Id: "product_id",
|
|
Name: "product_name",
|
|
}
|
|
},
|
|
wantError: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
rb, err := builder.NewRequestBuilder(tt.path, tt.method, tt.bodyOpt, tt.msg())
|
|
require.NoError(t, err)
|
|
|
|
path, newMsg, err := rb.Build()
|
|
if tt.wantError {
|
|
require.Error(t, err)
|
|
require.Empty(t, path)
|
|
require.Nil(t, newMsg)
|
|
} else {
|
|
require.NoError(t, err)
|
|
require.Equal(t, tt.expectedPath, path)
|
|
require.True(t, proto.Equal(tt.expectedMsg(), newMsg))
|
|
}
|
|
})
|
|
}
|
|
}
|