Files
threshold/internal/iam/repository_role_test.go
T
Jim 8d6dee09a9 refact: Add db.DB wrapper and refact all test fixtures to use it. (#1535)
* refact: Changes required for gorm v2

* refact: Add db.DB wrapper and refact all test fixtures to use it

Stop leaking the gorm abstraction within our unit tests.

* refact: Migrate direct calls to pq to pgx driver

The refactor will remove the remaining direct dependencies on
the pq driver which has been deprecated in favor of the pgx
driver.

It includes the addition of common.SqlOpen which is a wrapper around
sql.Open, which is needed to deal with the driver name changing from
"postgres" to "pgx" but in our code base the dialect and driver are
synonyms.
2021-09-30 09:17:27 -04:00

669 lines
18 KiB
Go

package iam
import (
"context"
"fmt"
"testing"
"time"
"github.com/hashicorp/boundary/internal/db"
dbassert "github.com/hashicorp/boundary/internal/db/assert"
"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/boundary/internal/iam/store"
"github.com/hashicorp/boundary/internal/oplog"
"github.com/hashicorp/go-uuid"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestRepository_CreateRole(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
id := testId(t)
org, proj := TestScopes(t, repo)
type args struct {
role *Role
opt []Option
}
tests := []struct {
name string
args args
wantDup bool
wantErr bool
wantErrMsg string
wantIsError errors.Code
}{
{
name: "valid-org",
args: args{
role: func() *Role {
r, err := NewRole(org.PublicId, WithName("valid-org"+id), WithDescription(id))
assert.NoError(t, err)
return r
}(),
},
wantErr: false,
},
{
name: "valid-proj",
args: args{
role: func() *Role {
r, err := NewRole(proj.PublicId, WithName("valid-proj"+id), WithDescription(id))
assert.NoError(t, err)
return r
}(),
},
wantErr: false,
},
{
name: "bad-public-id",
args: args{
role: func() *Role {
r, err := NewRole(proj.PublicId, WithName("valid-proj"+id), WithDescription(id))
assert.NoError(t, err)
r.PublicId = id
return r
}(),
},
wantErrMsg: "am.(Repository).CreateRole: public id not empty: parameter violation: error #100",
wantIsError: errors.InvalidParameter,
wantErr: true,
},
{
name: "nil-role",
args: args{
role: nil,
},
wantErr: true,
wantErrMsg: "iam.(Repository).CreateRole: missing role: parameter violation: error #100",
wantIsError: errors.InvalidParameter,
},
{
name: "nil-store",
args: args{
role: func() *Role {
return &Role{
Role: nil,
}
}(),
},
wantErr: true,
wantErrMsg: "iam.(Repository).CreateRole: missing role store: parameter violation: error #100",
wantIsError: errors.InvalidParameter,
},
{
name: "bad-scope-id",
args: args{
role: func() *Role {
r, err := NewRole(id)
assert.NoError(t, err)
return r
}(),
},
wantErr: true,
wantErrMsg: "iam.(Repository).create: error getting metadata: iam.(Repository).stdMetadata: unable to get scope: iam.LookupScope: db.LookupWhere: record not found, search issue: error #1100",
wantIsError: errors.RecordNotFound,
},
{
name: "dup-name",
args: args{
role: func() *Role {
r, err := NewRole(org.PublicId, WithName("dup-name"+id), WithDescription(id))
assert.NoError(t, err)
return r
}(),
opt: []Option{WithName("dup-name" + id)},
},
wantDup: true,
wantErr: true,
wantErrMsg: "already exists in scope ",
wantIsError: errors.NotUnique,
},
{
name: "dup-name-but-diff-scope",
args: args{
role: func() *Role {
r, err := NewRole(proj.PublicId, WithName("dup-name-but-diff-scope"+id), WithDescription(id))
assert.NoError(t, err)
return r
}(),
opt: []Option{WithName("dup-name-but-diff-scope" + id)},
},
wantDup: true,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert := assert.New(t)
if tt.wantDup {
dup, err := NewRole(org.PublicId, tt.args.opt...)
assert.NoError(err)
dup, err = repo.CreateRole(context.Background(), dup, tt.args.opt...)
assert.NoError(err)
assert.NotNil(dup)
}
grp, err := repo.CreateRole(context.Background(), tt.args.role, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Nil(grp)
assert.Contains(err.Error(), tt.wantErrMsg)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
return
}
assert.NoError(err)
assert.NotNil(grp.CreateTime)
assert.NotNil(grp.UpdateTime)
foundGrp, _, _, err := repo.LookupRole(context.Background(), grp.PublicId)
assert.NoError(err)
assert.True(proto.Equal(foundGrp, grp))
err = db.TestVerifyOplog(t, rw, grp.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_CREATE), db.WithCreateNotBefore(10*time.Second))
assert.NoError(err)
})
}
}
func TestRepository_UpdateRole(t *testing.T) {
t.Parallel()
ctx := context.Background()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
id, err := uuid.GenerateUUID()
require.NoError(t, err)
org, proj := TestScopes(t, repo)
u := TestUser(t, repo, org.GetPublicId())
pubId := func(s string) *string { return &s }
type args struct {
name string
description string
fieldMaskPaths []string
opt []Option
ScopeId string
PublicId *string
}
tests := []struct {
name string
newScopeId string
newRoleOpts []Option
args args
wantRowsUpdate int
wantErr bool
wantErrMsg string
wantIsError errors.Code
wantDup bool
}{
{
name: "valid",
args: args{
name: "valid" + id,
fieldMaskPaths: []string{"Name"},
ScopeId: org.PublicId,
},
newScopeId: org.PublicId,
wantErr: false,
wantRowsUpdate: 1,
},
{
name: "valid-no-op",
args: args{
name: "valid-no-op" + id,
fieldMaskPaths: []string{"Name"},
ScopeId: org.PublicId,
},
newScopeId: org.PublicId,
newRoleOpts: []Option{WithName("valid-no-op" + id)},
wantErr: false,
wantRowsUpdate: 1,
},
{
name: "not-found",
args: args{
name: "not-found" + id,
fieldMaskPaths: []string{"Name"},
ScopeId: org.PublicId,
PublicId: func() *string { s := "1"; return &s }(),
},
newScopeId: org.PublicId,
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "db.DoTx: iam.(Repository).UpdateRole: iam.(Repository).update: db.DoTx: iam.(Repository).update: db.Update: db.lookupAfterWrite: db.LookupById: record not found, search issue: error #1100",
wantIsError: errors.RecordNotFound,
},
{
name: "null-name",
args: args{
name: "",
fieldMaskPaths: []string{"Name"},
ScopeId: org.PublicId,
},
newScopeId: org.PublicId,
newRoleOpts: []Option{WithName("null-name" + id)},
wantErr: false,
wantRowsUpdate: 1,
},
{
name: "null-description",
args: args{
name: "",
fieldMaskPaths: []string{"Description"},
ScopeId: org.PublicId,
},
newScopeId: org.PublicId,
newRoleOpts: []Option{WithDescription("null-description" + id)},
wantErr: false,
wantRowsUpdate: 1,
},
{
name: "empty-field-mask",
args: args{
name: "valid" + id,
fieldMaskPaths: []string{},
ScopeId: org.PublicId,
},
newScopeId: org.PublicId,
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "iam.(Repository).UpdateRole: empty field mask, parameter violation: error #104",
wantIsError: errors.EmptyFieldMask,
},
{
name: "nil-fieldmask",
args: args{
name: "valid" + id,
fieldMaskPaths: nil,
ScopeId: org.PublicId,
},
newScopeId: org.PublicId,
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "iam.(Repository).UpdateRole: empty field mask, parameter violation: error #104",
wantIsError: errors.EmptyFieldMask,
},
{
name: "read-only-fields",
args: args{
name: "valid" + id,
fieldMaskPaths: []string{"CreateTime"},
ScopeId: org.PublicId,
},
newScopeId: org.PublicId,
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "iam.(Repository).UpdateRole: invalid field mask: CreateTime: parameter violation: error #103",
wantIsError: errors.InvalidFieldMask,
},
{
name: "unknown-fields",
args: args{
name: "valid" + id,
fieldMaskPaths: []string{"Alice"},
ScopeId: org.PublicId,
},
newScopeId: org.PublicId,
wantErr: true,
wantRowsUpdate: 0,
wantErrMsg: "iam.(Repository).UpdateRole: invalid field mask: Alice: parameter violation: error #103",
wantIsError: errors.InvalidFieldMask,
},
{
name: "no-public-id",
args: args{
name: "valid" + id,
fieldMaskPaths: []string{"Name"},
ScopeId: org.PublicId,
PublicId: pubId(""),
},
newScopeId: org.PublicId,
wantErr: true,
wantErrMsg: "iam.(Repository).UpdateRole: missing public id: parameter violation: error #100",
wantIsError: errors.InvalidParameter,
wantRowsUpdate: 0,
},
{
name: "proj-scope-id-no-mask",
args: args{
name: "proj-scope-id" + id,
ScopeId: proj.PublicId,
},
newScopeId: org.PublicId,
wantErr: true,
wantErrMsg: "iam.(Repository).UpdateRole: empty field mask, parameter violation: error #104",
wantIsError: errors.EmptyFieldMask,
},
{
name: "empty-scope-id-with-name-mask",
args: args{
name: "empty-scope-id" + id,
fieldMaskPaths: []string{"Name"},
ScopeId: "",
},
newScopeId: org.PublicId,
wantErr: false,
wantRowsUpdate: 1,
},
{
name: "dup-name-in-diff-scope",
args: args{
name: "dup-name-in-diff-scope" + id,
fieldMaskPaths: []string{"Name"},
ScopeId: proj.PublicId,
},
newScopeId: proj.PublicId,
newRoleOpts: []Option{WithName("dup-name-in-diff-scope-pre-update" + id)},
wantErr: false,
wantRowsUpdate: 1,
wantDup: true,
},
{
name: "dup-name",
args: args{
name: "dup-name" + id,
fieldMaskPaths: []string{"Name"},
ScopeId: org.PublicId,
},
newScopeId: org.PublicId,
wantErr: true,
wantDup: true,
wantErrMsg: " already exists in org " + org.PublicId,
wantIsError: errors.NotUnique,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
require, assert := require.New(t), assert.New(t)
if tt.wantDup {
r := TestRole(t, conn, org.PublicId)
_ = TestUserRole(t, conn, r.GetPublicId(), u.GetPublicId())
_ = TestRoleGrant(t, conn, r.GetPublicId(), "id=*;type=*;actions=*")
r.Name = tt.args.name
_, _, _, _, err := repo.UpdateRole(context.Background(), r, r.Version, tt.args.fieldMaskPaths, tt.args.opt...)
assert.NoError(err)
}
r := TestRole(t, conn, tt.newScopeId, tt.newRoleOpts...)
ur := TestUserRole(t, conn, r.GetPublicId(), u.GetPublicId())
princRole := PrincipalRole{PrincipalRoleView: &store.PrincipalRoleView{
Type: UserRoleType.String(),
CreateTime: ur.CreateTime,
RoleId: ur.RoleId,
PrincipalId: ur.PrincipalId,
PrincipalScopeId: org.GetPublicId(),
ScopedPrincipalId: ur.PrincipalId,
RoleScopeId: org.GetPublicId(),
}}
if tt.newScopeId != org.GetPublicId() {
// If the project is in a different scope from the created user we need to update the
// scope specific fields.
princRole.RoleScopeId = tt.newScopeId
princRole.ScopedPrincipalId = fmt.Sprintf("%s:%s", org.PublicId, ur.PrincipalId)
}
rGrant := TestRoleGrant(t, conn, r.GetPublicId(), "id=*;type=*;actions=*")
updateRole := allocRole()
updateRole.PublicId = r.PublicId
if tt.args.PublicId != nil {
updateRole.PublicId = *tt.args.PublicId
}
updateRole.ScopeId = tt.args.ScopeId
updateRole.Name = tt.args.name
updateRole.Description = tt.args.description
roleAfterUpdate, principals, grants, updatedRows, err := repo.UpdateRole(context.Background(), &updateRole, r.Version, tt.args.fieldMaskPaths, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.True(errors.Match(errors.T(tt.wantIsError), err))
assert.Nil(roleAfterUpdate)
assert.Equal(0, updatedRows)
assert.Contains(err.Error(), tt.wantErrMsg)
err = db.TestVerifyOplog(t, rw, r.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
require.NoError(err)
require.NotNil(roleAfterUpdate)
assert.Equal(tt.wantRowsUpdate, updatedRows)
assert.Equal([]PrincipalRole{princRole}, principals)
assert.Equal([]*RoleGrant{rGrant}, grants)
switch tt.name {
case "valid-no-op":
assert.Equal(r.UpdateTime, roleAfterUpdate.UpdateTime)
default:
assert.NotEqual(r.UpdateTime, roleAfterUpdate.UpdateTime)
}
foundRole, _, _, err := repo.LookupRole(context.Background(), r.PublicId)
assert.NoError(err)
assert.True(proto.Equal(roleAfterUpdate, foundRole))
underlyingDB, err := conn.SqlDB(ctx)
require.NoError(err)
dbassert := dbassert.New(t, underlyingDB)
if tt.args.name == "" {
assert.Equal(foundRole.Name, "")
dbassert.IsNull(foundRole, "name")
}
if tt.args.description == "" {
assert.Equal(foundRole.Description, "")
dbassert.IsNull(foundRole, "description")
}
err = db.TestVerifyOplog(t, rw, r.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_UPDATE), db.WithCreateNotBefore(10*time.Second))
assert.NoError(err)
})
}
}
func TestRepository_DeleteRole(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
rw := db.New(conn)
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
org, _ := TestScopes(t, repo)
roleId, err := newRoleId()
require.NoError(t, err)
type args struct {
role *Role
opt []Option
}
tests := []struct {
name string
args args
wantRowsDeleted int
wantErr bool
wantErrMsg string
}{
{
name: "valid",
args: args{
role: TestRole(t, conn, org.PublicId),
},
wantRowsDeleted: 1,
wantErr: false,
},
{
name: "no-public-id",
args: args{
role: func() *Role {
r := allocRole()
return &r
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantErrMsg: "iam.(Repository).DeleteRole: missing public id: parameter violation: error #100",
},
{
name: "not-found",
args: args{
role: func() *Role {
r, err := NewRole(org.PublicId)
r.PublicId = roleId
require.NoError(t, err)
return r
}(),
},
wantRowsDeleted: 0,
wantErr: true,
wantErrMsg: "iam.(Repository).DeleteRole: failed for " + roleId + ": db.LookupById: record not found, search issue: error #1100",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert := assert.New(t)
deletedRows, err := repo.DeleteRole(context.Background(), tt.args.role.PublicId, tt.args.opt...)
if tt.wantErr {
assert.Error(err)
assert.Equal(0, deletedRows)
assert.Contains(err.Error(), tt.wantErrMsg)
err = db.TestVerifyOplog(t, rw, tt.args.role.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.Error(err)
assert.True(errors.IsNotFoundError(err))
return
}
assert.NoError(err)
assert.Equal(tt.wantRowsDeleted, deletedRows)
foundRole, _, _, err := repo.LookupRole(context.Background(), tt.args.role.PublicId)
assert.NoError(err)
assert.Nil(foundRole)
err = db.TestVerifyOplog(t, rw, tt.args.role.PublicId, db.WithOperation(oplog.OpType_OP_TYPE_DELETE), db.WithCreateNotBefore(10*time.Second))
assert.NoError(err)
})
}
}
func TestRepository_ListRoles(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
const testLimit = 10
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper, WithLimit(testLimit))
org, proj := TestScopes(t, repo)
type args struct {
withScopeId string
opt []Option
}
tests := []struct {
name string
createCnt int
createScopeId string
args args
wantCnt int
wantErr bool
}{
{
name: "no-limit",
createCnt: repo.defaultLimit + 1,
createScopeId: org.PublicId,
args: args{
withScopeId: org.PublicId,
opt: []Option{WithLimit(-1)},
},
wantCnt: repo.defaultLimit + 1,
wantErr: false,
},
{
name: "no-limit-proj-role",
createCnt: repo.defaultLimit + 1,
createScopeId: proj.PublicId,
args: args{
withScopeId: proj.PublicId,
opt: []Option{WithLimit(-1)},
},
wantCnt: repo.defaultLimit + 1,
wantErr: false,
},
{
name: "default-limit",
createCnt: repo.defaultLimit + 1,
createScopeId: org.PublicId,
args: args{
withScopeId: org.PublicId,
},
wantCnt: repo.defaultLimit,
wantErr: false,
},
{
name: "custom-limit",
createCnt: repo.defaultLimit + 1,
createScopeId: org.PublicId,
args: args{
withScopeId: org.PublicId,
opt: []Option{WithLimit(3)},
},
wantCnt: 3,
wantErr: false,
},
{
name: "bad-org",
createCnt: 1,
createScopeId: org.PublicId,
args: args{
withScopeId: "bad-id",
},
wantCnt: 0,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert, require := assert.New(t), require.New(t)
require.NoError(conn.Where("1=1").Delete(allocRole()).Error)
testRoles := []*Role{}
for i := 0; i < tt.createCnt; i++ {
testRoles = append(testRoles, TestRole(t, conn, tt.createScopeId))
}
assert.Equal(tt.createCnt, len(testRoles))
got, err := repo.ListRoles(context.Background(), []string{tt.args.withScopeId}, tt.args.opt...)
if tt.wantErr {
require.Error(err)
return
}
require.NoError(err)
assert.Equal(tt.wantCnt, len(got))
})
}
}
func TestRepository_ListRoles_Multiple_Scopes(t *testing.T) {
t.Parallel()
conn, _ := db.TestSetup(t, "postgres")
wrapper := db.TestWrapper(t)
repo := TestRepo(t, conn, wrapper)
org, proj := TestScopes(t, repo)
require.NoError(t, conn.Where("1=1").Delete(allocRole()).Error)
const numPerScope = 10
var total int
for i := 0; i < numPerScope; i++ {
TestRole(t, conn, "global")
total++
TestRole(t, conn, org.GetPublicId())
total++
TestRole(t, conn, proj.GetPublicId())
total++
}
got, err := repo.ListRoles(context.Background(), []string{"global", org.GetPublicId(), proj.GetPublicId()})
require.NoError(t, err)
assert.Equal(t, total, len(got))
}