// Copyright 2022 Harness Inc. All rights reserved. // Use of this source code is governed by the Polyform Free Trial License // that can be found in the LICENSE.md file for this repository. package database import ( "context" "fmt" "strings" "testing" "time" "github.com/harness/gitness/internal/store" "github.com/harness/gitness/types" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/jmoiron/sqlx" "github.com/pkg/errors" ) // user fields to ignore in test comparisons. var userIgnore = cmpopts.IgnoreFields(types.User{}, "ID", "Salt", "Created", "Updated") func TestUser(t *testing.T) { db, err := connect() if err != nil { t.Error(err) return } defer func(db *sqlx.DB) { _ = db.Close() }(db) if err = seed(db); err != nil { t.Error(err) return } principalStore := NewPrincipalStore(db, store.ToLowerPrincipalUIDTransformation) t.Run("create", testUserCreate(principalStore)) t.Run("duplicate", testUserDuplicate(principalStore)) t.Run("count", testUserCount(principalStore)) t.Run("find", testUserFind(principalStore)) t.Run("list", testUserList(principalStore)) t.Run("update", testUserUpdate(principalStore)) t.Run("delete", testUserDelete(principalStore)) } // this test creates entries in the database and confirms // the primary keys were auto-incremented. func testUserCreate(store store.PrincipalStore) func(t *testing.T) { return func(t *testing.T) { ctx := context.Background() vv := []*types.User{} if err := unmarshal("testdata/users.json", &vv); err != nil { t.Error(err) return } // create row 1 v := vv[0] // generate a deterministic token for each // entry based on the hash of the email. v.Salt = fmt.Sprintf("%x", v.Email) if err := store.CreateUser(ctx, v); err != nil { t.Error(err) return } if v.ID == 0 { t.Errorf("Want autoincremented primary key") } // create row 2 v = vv[1] v.Salt = fmt.Sprintf("%x", v.Email) if err := store.CreateUser(ctx, v); err != nil { t.Error(err) return } if v.ID == 0 { t.Errorf("Want autoincremented primary key") } } } // this test attempts to create an entry in the database using // a duplicate email to verify that unique email constraints are // being enforced. func testUserDuplicate(store store.PrincipalStore) func(t *testing.T) { return func(t *testing.T) { vv := []*types.User{} if err := unmarshal("testdata/users.json", &vv); err != nil { t.Error(err) return } if err := store.CreateUser(context.Background(), vv[0]); err == nil { t.Errorf("Expect unique index violation") } } } // this test counts the number of users in the database // and compares to the expected count. func testUserCount(store store.PrincipalStore) func(t *testing.T) { return func(t *testing.T) { got, err := store.CountUsers(context.Background()) if err != nil { t.Error(err) return } if want := int64(2); got != want { t.Errorf("Want user count %d, got %d", want, got) } } } // this test fetches users from the database by id and key // and compares to the expected results (sourced from a json file) // to ensure all columns are correctly mapped. // //nolint:gocognit // test method, keep for now func testUserFind(store store.PrincipalStore) func(t *testing.T) { return func(t *testing.T) { ctx := context.Background() vv := []*types.User{} if err := unmarshal("testdata/users.json", &vv); err != nil { t.Error(err) return } want := vv[0] t.Run("id", func(t *testing.T) { got, err := store.FindUser(ctx, 1) if err != nil { t.Error(err) return } if diff := cmp.Diff(got, want, userIgnore); len(diff) != 0 { t.Errorf(diff) return } }) t.Run("uid", func(t *testing.T) { got, err := store.FindUserByUID(ctx, "jane21") if err != nil { t.Error(err) return } if diff := cmp.Diff(got, want, userIgnore); len(diff) != 0 { t.Errorf(diff) return } }) t.Run("email", func(t *testing.T) { got, err := store.FindUserByEmail(ctx, want.Email) if err != nil { t.Error(err) return } if diff := cmp.Diff(got, want, userIgnore); len(diff) != 0 { t.Errorf(diff) return } }) t.Run("email/nocase", func(t *testing.T) { got, err := store.FindUserByEmail(ctx, strings.ToUpper(want.Email)) if err != nil { t.Error(err) return } if diff := cmp.Diff(got, want, userIgnore); len(diff) != 0 { t.Errorf(diff) return } }) } } // this test fetches a list of users from the database // and compares to the expected results (sourced from a json file) // to ensure all columns are correctly mapped. func testUserList(store store.PrincipalStore) func(t *testing.T) { return func(t *testing.T) { want := []*types.User{} if err := unmarshal("testdata/users.json", &want); err != nil { t.Error(err) return } got, err := store.ListUsers(context.Background(), &types.UserFilter{Page: 0, Size: 100}) if err != nil { t.Error(err) return } if diff := cmp.Diff(got, want, userIgnore); len(diff) != 0 { t.Errorf(diff) return } } } // this test updates an user in the database and then fetches // the user and confirms the column was updated as expected. func testUserUpdate(store store.PrincipalStore) func(t *testing.T) { return func(t *testing.T) { ctx := context.Background() before, err := store.FindUser(ctx, 1) if err != nil { t.Error(err) return } before.Updated = time.Now().Unix() if err = store.UpdateUser(ctx, before); err != nil { t.Error(err) return } after, err := store.FindUser(ctx, 1) if err != nil { t.Error(err) return } if diff := cmp.Diff(before, after, userIgnore); len(diff) != 0 { t.Errorf(diff) return } } } // this test deletes an user from the database and then confirms // subsequent attempts to fetch the deleted user result in // a sql.ErrNoRows error. func testUserDelete(s store.PrincipalStore) func(t *testing.T) { return func(t *testing.T) { ctx := context.Background() _, err := s.Find(ctx, 1) if err != nil { t.Error(err) return } if err = s.DeleteUser(ctx, 1); err != nil { t.Error(err) return } if _, err = s.Find(ctx, 1); !errors.Is(err, store.ErrResourceNotFound) { t.Errorf("Expected store.ErrResourceNotFound got %s", err) } } }