mirror of https://github.com/hashicorp/boundary
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
488 lines
12 KiB
488 lines
12 KiB
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package handlers
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
|
|
pbs "github.com/hashicorp/boundary/internal/gen/controller/api/services"
|
|
pb "github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/users"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/protobuf/types/known/fieldmaskpb"
|
|
"google.golang.org/protobuf/types/known/timestamppb"
|
|
"google.golang.org/protobuf/types/known/wrapperspb"
|
|
)
|
|
|
|
func errorIncludesFields(t *testing.T, err error, wantFields []string) {
|
|
t.Helper()
|
|
var apiErr *ApiError
|
|
require.True(t, errors.As(err, &apiErr))
|
|
var gotFields []string
|
|
for _, d := range apiErr.Inner.GetDetails().GetRequestFields() {
|
|
gotFields = append(gotFields, d.GetName())
|
|
}
|
|
assert.ElementsMatch(t, gotFields, wantFields)
|
|
}
|
|
|
|
// Throughout this test we will use the User requests, but we could use any request
|
|
// message. User was picked arbitrarily.
|
|
|
|
func TestValidId(t *testing.T) {
|
|
assert.True(t, ValidId(Id("prefix_somerandomid"), "prefix"))
|
|
assert.True(t, ValidId(Id("prefix_somerandomid"), "notprefix", "prefix"))
|
|
assert.True(t, ValidId(Id("prefix_short"), "prefix"))
|
|
assert.True(t, ValidId(Id("prefix_thisisalongidentifierwhichstillworks"), "prefix"))
|
|
|
|
assert.False(t, ValidId(Id("prefixsomerandomid"), "prefix"))
|
|
assert.False(t, ValidId(Id("prefixsomerandomid"), "prefix", "alsobadprefix"))
|
|
assert.False(t, ValidId(Id("prefix_this has spaces"), "prefix"))
|
|
assert.False(t, ValidId(Id("prefix_includes-dash"), "prefix"))
|
|
assert.False(t, ValidId(Id("prefix_other@strange!characters"), "prefix"))
|
|
assert.False(t, ValidId(Id("prefix_short"), "short"))
|
|
}
|
|
|
|
func TestValidDescription(t *testing.T) {
|
|
assert.True(t, ValidDescription("foobar"))
|
|
assert.True(t, ValidDescription("this is\n a long description"))
|
|
assert.False(t, ValidDescription("foo\u200Bbar"))
|
|
}
|
|
|
|
func TestValidName(t *testing.T) {
|
|
assert.True(t, ValidName("foobar"))
|
|
assert.False(t, ValidName("this is\n a long description"))
|
|
assert.False(t, ValidName("foo\u200Bbar"))
|
|
}
|
|
|
|
func TestValidateGetRequest(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
prefix string
|
|
req GetRequest
|
|
valFn CustomValidatorFunc
|
|
badFields []string
|
|
}{
|
|
{
|
|
name: "noopvalidator no error",
|
|
prefix: "prefix",
|
|
req: &pbs.GetUserRequest{
|
|
Id: "prefix_someidentifier",
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
},
|
|
{
|
|
name: "noopvalidator bad prefix",
|
|
prefix: "bad",
|
|
req: &pbs.GetUserRequest{
|
|
Id: "prefix_someidentifier",
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"id"},
|
|
},
|
|
{
|
|
name: "custom field error",
|
|
prefix: "prefix",
|
|
req: &pbs.GetUserRequest{
|
|
Id: "prefix_someidentifier",
|
|
},
|
|
valFn: func() map[string]string {
|
|
return map[string]string{"test": "test"}
|
|
},
|
|
badFields: []string{"test"},
|
|
},
|
|
{
|
|
name: "both custom error and prefix",
|
|
prefix: "bad",
|
|
req: &pbs.GetUserRequest{
|
|
Id: "prefix_someidentifier",
|
|
},
|
|
valFn: func() map[string]string {
|
|
return map[string]string{"test": "test"}
|
|
},
|
|
badFields: []string{"id", "test"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := ValidateGetRequest(tc.valFn, tc.req, tc.prefix)
|
|
if len(tc.badFields) == 0 {
|
|
if !assert.NoError(t, err) {
|
|
errorIncludesFields(t, err, []string{})
|
|
}
|
|
return
|
|
}
|
|
errorIncludesFields(t, err, tc.badFields)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateDeleteRequest(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
prefix string
|
|
req DeleteRequest
|
|
valFn CustomValidatorFunc
|
|
badFields []string
|
|
}{
|
|
{
|
|
name: "noopvalidator no error",
|
|
prefix: "prefix",
|
|
req: &pbs.DeleteUserRequest{
|
|
Id: "prefix_someidentifier",
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
},
|
|
{
|
|
name: "noopvalidator bad prefix",
|
|
prefix: "bad",
|
|
req: &pbs.DeleteUserRequest{
|
|
Id: "prefix_someidentifier",
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"id"},
|
|
},
|
|
{
|
|
name: "custom field error",
|
|
prefix: "prefix",
|
|
req: &pbs.DeleteUserRequest{
|
|
Id: "prefix_someidentifier",
|
|
},
|
|
valFn: func() map[string]string {
|
|
return map[string]string{"test": "test"}
|
|
},
|
|
badFields: []string{"test"},
|
|
},
|
|
{
|
|
name: "both custom error and prefix",
|
|
prefix: "bad",
|
|
req: &pbs.DeleteUserRequest{
|
|
Id: "prefix_someidentifier",
|
|
},
|
|
valFn: func() map[string]string {
|
|
return map[string]string{"test": "test"}
|
|
},
|
|
badFields: []string{"id", "test"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := ValidateDeleteRequest(tc.valFn, tc.req, tc.prefix)
|
|
if len(tc.badFields) == 0 {
|
|
if !assert.NoError(t, err) {
|
|
errorIncludesFields(t, err, []string{})
|
|
}
|
|
return
|
|
}
|
|
errorIncludesFields(t, err, tc.badFields)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateCreateRequest(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
item ApiResource
|
|
valFn CustomValidatorFunc
|
|
badFields []string
|
|
}{
|
|
{
|
|
name: "valid",
|
|
item: &pb.User{},
|
|
valFn: NoopValidatorFn,
|
|
},
|
|
{
|
|
name: "disallow set id",
|
|
item: &pb.User{
|
|
Id: "anything",
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"id"},
|
|
},
|
|
{
|
|
name: "disallow set created",
|
|
item: &pb.User{
|
|
CreatedTime: timestamppb.Now(),
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"created_time"},
|
|
},
|
|
{
|
|
name: "disallow set updated",
|
|
item: &pb.User{
|
|
UpdatedTime: timestamppb.Now(),
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"updated_time"},
|
|
},
|
|
{
|
|
name: "disallow set version",
|
|
item: &pb.User{
|
|
Version: 4,
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"version"},
|
|
},
|
|
{
|
|
name: "custom validator error",
|
|
item: &pb.User{},
|
|
valFn: func() map[string]string {
|
|
return map[string]string{"test": "test"}
|
|
},
|
|
badFields: []string{"test"},
|
|
},
|
|
{
|
|
name: "disallow several fields",
|
|
item: &pb.User{
|
|
Id: "anything",
|
|
CreatedTime: timestamppb.Now(),
|
|
Version: 4,
|
|
},
|
|
valFn: func() map[string]string {
|
|
return map[string]string{"test": "test"}
|
|
},
|
|
badFields: []string{"id", "created_time", "version", "test"},
|
|
},
|
|
{
|
|
name: "good name",
|
|
item: &pb.User{
|
|
Name: wrapperspb.String("foobar"),
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
},
|
|
{
|
|
name: "bad name",
|
|
item: &pb.User{
|
|
Name: wrapperspb.String("foo\u200Bbar"),
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"name"},
|
|
},
|
|
{
|
|
name: "good description",
|
|
item: &pb.User{
|
|
Description: wrapperspb.String("foobar"),
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
},
|
|
{
|
|
name: "bad description",
|
|
item: &pb.User{
|
|
Description: wrapperspb.String("foo\u200Bbar"),
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"description"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := ValidateCreateRequest(tc.item, tc.valFn)
|
|
if len(tc.badFields) == 0 {
|
|
if !assert.NoError(t, err) {
|
|
errorIncludesFields(t, err, []string{})
|
|
}
|
|
return
|
|
}
|
|
errorIncludesFields(t, err, tc.badFields)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateUpdateRequest(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
prefix string
|
|
req UpdateRequest
|
|
item ApiResource
|
|
valFn CustomValidatorFunc
|
|
badFields []string
|
|
}{
|
|
{
|
|
name: "valid",
|
|
prefix: "prefix",
|
|
req: &pbs.UpdateUserRequest{
|
|
Id: "prefix_something",
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
|
|
},
|
|
item: &pb.User{
|
|
Version: 1,
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
},
|
|
{
|
|
name: "missing mask",
|
|
prefix: "prefix",
|
|
req: &pbs.UpdateUserRequest{
|
|
Id: "prefix_something",
|
|
},
|
|
item: &pb.User{
|
|
Version: 1,
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"update_mask"},
|
|
},
|
|
{
|
|
name: "bad id",
|
|
prefix: "prefix",
|
|
req: &pbs.UpdateUserRequest{
|
|
Id: "mismatched_prefix",
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
|
|
},
|
|
item: &pb.User{
|
|
Version: 1,
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"id"},
|
|
},
|
|
{
|
|
name: "good name",
|
|
prefix: "prefix",
|
|
req: &pbs.UpdateUserRequest{
|
|
Id: "prefix_something",
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
|
|
},
|
|
item: &pb.User{
|
|
Version: 1,
|
|
Name: wrapperspb.String("foobar"),
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
},
|
|
{
|
|
name: "bad name",
|
|
prefix: "prefix",
|
|
req: &pbs.UpdateUserRequest{
|
|
Id: "prefix_something",
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
|
|
},
|
|
item: &pb.User{
|
|
Version: 1,
|
|
Name: wrapperspb.String("foo\u200Bbar"),
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"name"},
|
|
},
|
|
{
|
|
name: "good description",
|
|
prefix: "prefix",
|
|
req: &pbs.UpdateUserRequest{
|
|
Id: "prefix_something",
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
|
|
},
|
|
item: &pb.User{
|
|
Version: 1,
|
|
Description: wrapperspb.String("foobar"),
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
},
|
|
{
|
|
name: "bad description",
|
|
prefix: "prefix",
|
|
req: &pbs.UpdateUserRequest{
|
|
Id: "prefix_something",
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
|
|
},
|
|
item: &pb.User{
|
|
Version: 1,
|
|
Description: wrapperspb.String("foo\u200Bbar"),
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"description"},
|
|
},
|
|
{
|
|
name: "missing version",
|
|
prefix: "prefix",
|
|
req: &pbs.UpdateUserRequest{
|
|
Id: "prefix_something",
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
|
|
},
|
|
item: &pb.User{},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"version"},
|
|
},
|
|
{
|
|
name: "bad create time",
|
|
prefix: "prefix",
|
|
req: &pbs.UpdateUserRequest{
|
|
Id: "prefix_something",
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
|
|
},
|
|
item: &pb.User{
|
|
Version: 1,
|
|
CreatedTime: timestamppb.Now(),
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"created_time"},
|
|
},
|
|
{
|
|
name: "bad updated time",
|
|
prefix: "prefix",
|
|
req: &pbs.UpdateUserRequest{
|
|
Id: "prefix_something",
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
|
|
},
|
|
item: &pb.User{
|
|
Version: 1,
|
|
UpdatedTime: timestamppb.Now(),
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"updated_time"},
|
|
},
|
|
{
|
|
name: "bad defined id on item",
|
|
prefix: "prefix",
|
|
req: &pbs.UpdateUserRequest{
|
|
Id: "prefix_something",
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
|
|
},
|
|
item: &pb.User{
|
|
Id: "prefix_something",
|
|
Version: 1,
|
|
},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"id"},
|
|
},
|
|
{
|
|
name: "custom validator error",
|
|
prefix: "prefix",
|
|
req: &pbs.UpdateUserRequest{
|
|
Id: "prefix_something",
|
|
UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"anything"}},
|
|
},
|
|
item: &pb.User{
|
|
Version: 1,
|
|
},
|
|
valFn: func() map[string]string {
|
|
return map[string]string{
|
|
"test": "test",
|
|
}
|
|
},
|
|
badFields: []string{"test"},
|
|
},
|
|
{
|
|
name: "multiple",
|
|
prefix: "prefix",
|
|
req: &pbs.UpdateUserRequest{},
|
|
item: &pb.User{},
|
|
valFn: NoopValidatorFn,
|
|
badFields: []string{"id", "update_mask", "version"},
|
|
},
|
|
}
|
|
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
err := ValidateUpdateRequest(tc.req, tc.item, tc.valFn, tc.prefix)
|
|
if len(tc.badFields) == 0 {
|
|
if !assert.NoError(t, err) {
|
|
errorIncludesFields(t, err, []string{})
|
|
}
|
|
return
|
|
}
|
|
errorIncludesFields(t, err, tc.badFields)
|
|
})
|
|
}
|
|
}
|