influxdb/testing/user_service.go

1159 lines
23 KiB
Go

package testing
import (
"bytes"
"context"
"sort"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/influxdata/influxdb/v2"
"github.com/influxdata/influxdb/v2/kit/platform"
"github.com/influxdata/influxdb/v2/kit/platform/errors"
"github.com/influxdata/influxdb/v2/mock"
)
const (
userOneID = "020f755c3c082000"
userTwoID = "020f755c3c082001"
userThreeID = "020f755c3c082002"
)
var userCmpOptions = cmp.Options{
cmp.Comparer(func(x, y []byte) bool {
return bytes.Equal(x, y)
}),
cmp.Comparer(func(x, y *influxdb.User) bool {
if x == nil && y == nil {
return true
}
if x != nil && y == nil || y != nil && x == nil {
return false
}
return x.Name == y.Name && x.OAuthID == y.OAuthID && x.Status == y.Status
}),
cmp.Transformer("Sort", func(in []*influxdb.User) []*influxdb.User {
out := append([]*influxdb.User(nil), in...) // Copy input to avoid mutating it
sort.Slice(out, func(i, j int) bool {
return out[i].ID.String() > out[j].ID.String()
})
return out
}),
}
// UserFields will include the IDGenerator, and users
type UserFields struct {
IDGenerator platform.IDGenerator
Users []*influxdb.User
}
// UserService tests all the service functions.
func UserService(
init func(UserFields, *testing.T) (influxdb.UserService, string, func()), t *testing.T,
) {
tests := []struct {
name string
fn func(init func(UserFields, *testing.T) (influxdb.UserService, string, func()),
t *testing.T)
}{
{
name: "CreateUser",
fn: CreateUser,
},
{
name: "FindUserByID",
fn: FindUserByID,
},
{
name: "FindUsers",
fn: FindUsers,
},
{
name: "DeleteUser",
fn: DeleteUser,
},
{
name: "FindUser",
fn: FindUser,
},
{
name: "UpdateUser",
fn: UpdateUser,
},
{
name: "UpdateUser_IndexHygiene",
fn: UpdateUser_IndexHygiene,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt := tt
t.Parallel()
tt.fn(init, t)
})
}
}
// CreateUser testing
func CreateUser(
init func(UserFields, *testing.T) (influxdb.UserService, string, func()),
t *testing.T,
) {
type args struct {
user *influxdb.User
}
type wants struct {
err error
users []*influxdb.User
}
tests := []struct {
name string
fields UserFields
args args
wants wants
}{
{
name: "create users with empty set",
fields: UserFields{
IDGenerator: mock.NewIDGenerator(userOneID, t),
Users: []*influxdb.User{},
},
args: args{
user: &influxdb.User{
Name: "name1",
Status: influxdb.Active,
},
},
wants: wants{
users: []*influxdb.User{
{
Name: "name1",
ID: MustIDBase16(userOneID),
Status: influxdb.Active,
},
},
},
},
{
name: "basic create user",
fields: UserFields{
IDGenerator: mock.NewIDGenerator(userTwoID, t),
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "user1",
Status: influxdb.Active,
},
},
},
args: args{
user: &influxdb.User{
Name: "user2",
Status: influxdb.Active,
},
},
wants: wants{
users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "user1",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userTwoID),
Name: "user2",
Status: influxdb.Active,
},
},
},
},
{
name: "names should be unique",
fields: UserFields{
IDGenerator: &mock.IDGenerator{
IDFn: func() platform.ID {
return MustIDBase16(userOneID)
},
},
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "user1",
Status: influxdb.Active,
},
},
},
args: args{
user: &influxdb.User{
Name: "user1",
},
},
wants: wants{
users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "user1",
Status: influxdb.Active,
},
},
err: &errors.Error{
Code: errors.EConflict,
Op: influxdb.OpCreateUser,
Msg: "user with name user1 already exists",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, opPrefix, done := init(tt.fields, t)
defer done()
ctx := context.Background()
err := s.CreateUser(ctx, tt.args.user)
diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t)
// Delete only created users - ie., having a not nil ID
if tt.args.user.ID.Valid() {
defer s.DeleteUser(ctx, tt.args.user.ID)
}
users, _, err := s.FindUsers(ctx, influxdb.UserFilter{})
if err != nil {
t.Fatalf("failed to retrieve users: %v", err)
}
if diff := cmp.Diff(users, tt.wants.users, userCmpOptions...); diff != "" {
t.Errorf("users are different -got/+want\ndiff %s", diff)
}
})
}
}
// FindUserByID testing
func FindUserByID(
init func(UserFields, *testing.T) (influxdb.UserService, string, func()),
t *testing.T,
) {
type args struct {
id platform.ID
}
type wants struct {
err error
user *influxdb.User
}
tests := []struct {
name string
fields UserFields
args args
wants wants
}{
{
name: "basic find user by id",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "user1",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userTwoID),
Name: "user2",
Status: influxdb.Active,
},
},
},
args: args{
id: MustIDBase16(userTwoID),
},
wants: wants{
user: &influxdb.User{
ID: MustIDBase16(userTwoID),
Name: "user2",
Status: influxdb.Active,
},
},
},
{
name: "find user by id not exists",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "user1",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userTwoID),
Name: "user2",
Status: influxdb.Active,
},
},
},
args: args{
id: MustIDBase16(threeID),
},
wants: wants{
err: &errors.Error{
Code: errors.ENotFound,
Op: influxdb.OpFindUserByID,
Msg: "user not found",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, opPrefix, done := init(tt.fields, t)
defer done()
ctx := context.Background()
user, err := s.FindUserByID(ctx, tt.args.id)
diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t)
if diff := cmp.Diff(user, tt.wants.user, userCmpOptions...); diff != "" {
t.Errorf("user is different -got/+want\ndiff %s", diff)
}
})
}
}
// FindUsers testing
func FindUsers(
init func(UserFields, *testing.T) (influxdb.UserService, string, func()),
t *testing.T,
) {
type args struct {
ID platform.ID
name string
findOptions influxdb.FindOptions
}
type wants struct {
users []*influxdb.User
err error
}
tests := []struct {
name string
fields UserFields
args args
wants wants
}{
{
name: "find all users",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "abc",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userTwoID),
Name: "xyz",
Status: influxdb.Active,
},
},
},
args: args{},
wants: wants{
users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "abc",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userTwoID),
Name: "xyz",
Status: influxdb.Active,
},
},
},
},
{
name: "find user by id",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "abc",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userTwoID),
Name: "xyz",
Status: influxdb.Active,
},
},
},
args: args{
ID: MustIDBase16(userTwoID),
},
wants: wants{
users: []*influxdb.User{
{
ID: MustIDBase16(userTwoID),
Name: "xyz",
Status: influxdb.Active,
},
},
},
},
{
name: "find user by name",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "abc",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userTwoID),
Name: "xyz",
Status: influxdb.Active,
},
},
},
args: args{
name: "xyz",
},
wants: wants{
users: []*influxdb.User{
{
ID: MustIDBase16(userTwoID),
Name: "xyz",
Status: influxdb.Active,
},
},
},
},
{
name: "find user by id not exists",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "abc",
},
{
ID: MustIDBase16(userTwoID),
Name: "xyz",
},
},
},
args: args{
ID: MustIDBase16(threeID),
},
wants: wants{
err: &errors.Error{
Code: errors.ENotFound,
Op: influxdb.OpFindUsers,
Msg: "user not found",
},
},
},
{
name: "find user by name not exists",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "abc",
},
{
ID: MustIDBase16(userTwoID),
Name: "xyz",
},
},
},
args: args{
name: "no_exist",
},
wants: wants{
err: &errors.Error{
Code: errors.ENotFound,
Op: influxdb.OpFindUsers,
Msg: "user not found",
},
},
},
{
name: "find all users by offset and limit",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "abc",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userTwoID),
Name: "def",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userThreeID),
Name: "xyz",
Status: influxdb.Active,
},
},
},
args: args{
findOptions: influxdb.FindOptions{
Offset: 1,
Limit: 1,
},
},
wants: wants{
users: []*influxdb.User{
{
ID: MustIDBase16(userTwoID),
Name: "def",
Status: influxdb.Active,
},
},
},
},
{
name: "find all users by after and limit",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "abc",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userTwoID),
Name: "def",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userThreeID),
Name: "xyz",
Status: influxdb.Active,
},
},
},
args: args{
findOptions: influxdb.FindOptions{
After: MustIDBase16Ptr(userOneID),
Limit: 2,
},
},
wants: wants{
users: []*influxdb.User{
{
ID: MustIDBase16(userTwoID),
Name: "def",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userThreeID),
Name: "xyz",
Status: influxdb.Active,
},
},
},
},
{
name: "find all users by descending",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "abc",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userTwoID),
Name: "def",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userThreeID),
Name: "xyz",
Status: influxdb.Active,
},
},
},
args: args{
findOptions: influxdb.FindOptions{
Offset: 1,
Descending: true,
},
},
wants: wants{
users: []*influxdb.User{
{
ID: MustIDBase16(userTwoID),
Name: "def",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userOneID),
Name: "abc",
Status: influxdb.Active,
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, opPrefix, done := init(tt.fields, t)
defer done()
ctx := context.Background()
filter := influxdb.UserFilter{}
if tt.args.ID.Valid() {
filter.ID = &tt.args.ID
}
if tt.args.name != "" {
filter.Name = &tt.args.name
}
users, _, err := s.FindUsers(ctx, filter, tt.args.findOptions)
diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t)
if diff := cmp.Diff(users, tt.wants.users, userCmpOptions...); diff != "" {
t.Errorf("users are different -got/+want\ndiff %s", diff)
}
})
}
}
// DeleteUser testing
func DeleteUser(
init func(UserFields, *testing.T) (influxdb.UserService, string, func()),
t *testing.T,
) {
type args struct {
ID platform.ID
}
type wants struct {
err error
users []*influxdb.User
}
tests := []struct {
name string
fields UserFields
args args
wants wants
}{
{
name: "delete users using exist id",
fields: UserFields{
Users: []*influxdb.User{
{
Name: "orgA",
ID: MustIDBase16(userOneID),
Status: influxdb.Active,
},
{
Name: "orgB",
ID: MustIDBase16(userTwoID),
Status: influxdb.Active,
},
},
},
args: args{
ID: MustIDBase16(userOneID),
},
wants: wants{
users: []*influxdb.User{
{
Name: "orgB",
ID: MustIDBase16(userTwoID),
Status: influxdb.Active,
},
},
},
},
{
name: "delete users using id that does not exist",
fields: UserFields{
Users: []*influxdb.User{
{
Name: "orgA",
ID: MustIDBase16(userOneID),
Status: influxdb.Active,
},
{
Name: "orgB",
ID: MustIDBase16(userTwoID),
Status: influxdb.Active,
},
},
},
args: args{
ID: MustIDBase16(userThreeID),
},
wants: wants{
err: &errors.Error{
Code: errors.ENotFound,
Op: influxdb.OpDeleteUser,
Msg: "user not found",
},
users: []*influxdb.User{
{
Name: "orgA",
ID: MustIDBase16(userOneID),
Status: influxdb.Active,
},
{
Name: "orgB",
ID: MustIDBase16(userTwoID),
Status: influxdb.Active,
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, opPrefix, done := init(tt.fields, t)
defer done()
ctx := context.Background()
err := s.DeleteUser(ctx, tt.args.ID)
diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t)
filter := influxdb.UserFilter{}
users, _, err := s.FindUsers(ctx, filter)
if err != nil {
t.Fatalf("failed to retrieve users: %v", err)
}
if diff := cmp.Diff(users, tt.wants.users, userCmpOptions...); diff != "" {
t.Errorf("users are different -got/+want\ndiff %s", diff)
}
})
}
}
// FindUser testing
func FindUser(
init func(UserFields, *testing.T) (influxdb.UserService, string, func()),
t *testing.T,
) {
type args struct {
filter influxdb.UserFilter
}
type wants struct {
user *influxdb.User
err error
}
tests := []struct {
name string
fields UserFields
args args
wants wants
}{
{
name: "find user by name",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "abc",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userTwoID),
Name: "xyz",
Status: influxdb.Active,
},
},
},
args: args{
filter: influxdb.UserFilter{
Name: func(s string) *string { return &s }("abc"),
},
},
wants: wants{
user: &influxdb.User{
ID: MustIDBase16(userOneID),
Name: "abc",
Status: influxdb.Active,
},
},
},
{
name: "find existing user by its id",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "abc",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userTwoID),
Name: "xyz",
Status: influxdb.Active,
},
},
},
args: args{
filter: influxdb.UserFilter{
ID: func(id platform.ID) *platform.ID { return &id }(MustIDBase16(userOneID)),
},
},
wants: wants{
user: &influxdb.User{
ID: MustIDBase16(userOneID),
Name: "abc",
Status: influxdb.Active,
},
},
},
{
name: "user with name does not exist",
fields: UserFields{
Users: []*influxdb.User{},
},
args: args{
filter: influxdb.UserFilter{
Name: func(s string) *string { return &s }("abc"),
},
},
wants: wants{
err: &errors.Error{
Code: errors.ENotFound,
Msg: "user not found",
Op: influxdb.OpFindUser,
},
},
},
{
name: "user with id does not exist",
fields: UserFields{
Users: []*influxdb.User{},
},
args: args{
filter: influxdb.UserFilter{
ID: func(id platform.ID) *platform.ID { return &id }(MustIDBase16(userOneID)),
},
},
wants: wants{
err: &errors.Error{
Code: errors.ENotFound,
Msg: "user not found",
Op: influxdb.OpFindUser,
},
},
},
{
name: "filter with both name and ID prefers ID",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "abc",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userTwoID),
Name: "xyz",
Status: influxdb.Active,
},
},
},
args: args{
filter: influxdb.UserFilter{
ID: func(id platform.ID) *platform.ID { return &id }(MustIDBase16(userOneID)),
Name: func(s string) *string { return &s }("xyz"),
},
},
wants: wants{
user: &influxdb.User{
ID: MustIDBase16(userOneID),
Name: "abc",
Status: influxdb.Active,
},
},
},
{
name: "filter with no name nor id returns error",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userTwoID),
Name: "xyz",
},
},
},
args: args{
filter: influxdb.UserFilter{},
},
wants: wants{
err: &errors.Error{
Code: errors.ENotFound,
Msg: "user not found",
Op: influxdb.OpFindUser,
},
},
},
{
name: "filter both name and non-existent id returns no user",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userTwoID),
Name: "xyz",
},
},
},
args: args{
filter: influxdb.UserFilter{
ID: func(id platform.ID) *platform.ID { return &id }(MustIDBase16(userOneID)),
Name: func(s string) *string { return &s }("xyz"),
},
},
wants: wants{
err: &errors.Error{
Code: errors.ENotFound,
Msg: "user not found",
Op: influxdb.OpFindUser,
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, opPrefix, done := init(tt.fields, t)
defer done()
ctx := context.Background()
user, err := s.FindUser(ctx, tt.args.filter)
diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t)
if diff := cmp.Diff(user, tt.wants.user, userCmpOptions...); diff != "" {
t.Errorf("users are different -got/+want\ndiff %s", diff)
}
})
}
}
// UpdateUser testing
func UpdateUser(
init func(UserFields, *testing.T) (influxdb.UserService, string, func()),
t *testing.T,
) {
type args struct {
name string
id platform.ID
status string
}
type wants struct {
err error
user *influxdb.User
}
tests := []struct {
name string
fields UserFields
args args
wants wants
}{
{
name: "update name",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "user1",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userTwoID),
Name: "user2",
Status: influxdb.Active,
},
},
},
args: args{
id: MustIDBase16(userOneID),
name: "changed",
},
wants: wants{
user: &influxdb.User{
ID: MustIDBase16(userOneID),
Name: "changed",
Status: influxdb.Active,
},
},
},
{
name: "update name to same name",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "user1",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userTwoID),
Name: "user2",
Status: influxdb.Active,
},
},
},
args: args{
id: MustIDBase16(userOneID),
name: "user1",
},
wants: wants{
user: &influxdb.User{
ID: MustIDBase16(userOneID),
Name: "user1",
Status: influxdb.Active,
},
},
},
{
name: "update status",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "user1",
Status: influxdb.Active,
},
{
ID: MustIDBase16(userTwoID),
Name: "user2",
Status: influxdb.Active,
},
},
},
args: args{
id: MustIDBase16(userOneID),
status: "inactive",
},
wants: wants{
user: &influxdb.User{
ID: MustIDBase16(userOneID),
Name: "user1",
Status: "inactive",
},
},
},
{
name: "update name with id not exists",
fields: UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: "user1",
},
{
ID: MustIDBase16(userTwoID),
Name: "user2",
},
},
},
args: args{
id: MustIDBase16(threeID),
name: "changed",
},
wants: wants{
err: &errors.Error{
Code: errors.ENotFound,
Op: influxdb.OpUpdateUser,
Msg: "user not found",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
s, opPrefix, done := init(tt.fields, t)
defer done()
ctx := context.Background()
upd := influxdb.UserUpdate{}
if tt.args.name != "" {
upd.Name = &tt.args.name
}
switch tt.args.status {
case "inactive":
status := influxdb.Inactive
upd.Status = &status
case "active":
status := influxdb.Inactive
upd.Status = &status
}
user, err := s.UpdateUser(ctx, tt.args.id, upd)
diffPlatformErrors(tt.name, err, tt.wants.err, opPrefix, t)
if diff := cmp.Diff(user, tt.wants.user, userCmpOptions...); diff != "" {
t.Errorf("user is different -got/+want\ndiff %s", diff)
}
})
}
}
func UpdateUser_IndexHygiene(
init func(UserFields, *testing.T) (influxdb.UserService, string, func()),
t *testing.T,
) {
oldUserName := "user1"
users := UserFields{
Users: []*influxdb.User{
{
ID: MustIDBase16(userOneID),
Name: oldUserName,
Status: "active",
},
},
}
s, _, done := init(users, t)
defer done()
newUserName := "user1Updated"
upd := influxdb.UserUpdate{
Name: &newUserName,
}
ctx := context.Background()
_, err := s.UpdateUser(ctx, MustIDBase16(userOneID), upd)
if err != nil {
t.Error(err)
}
// Ensure we can find the user with the new name.
_, nerr := s.FindUser(ctx, influxdb.UserFilter{
Name: &newUserName,
})
if nerr != nil {
t.Error("unexpected error when finding user by name", nerr)
}
// Ensure we cannot find a user with the old name. The index used when
// searching by name should have been cleared out by the UpdateUser
// operation.
_, oerr := s.FindUser(ctx, influxdb.UserFilter{
Name: &oldUserName,
})
ErrorsEqual(t, oerr, &errors.Error{
Code: errors.ENotFound,
Op: influxdb.OpFindUser,
Msg: "user not found",
})
}