This is an automated email from the ASF dual-hosted git repository.

zrhoffman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new 95ee202ca3 Refactor Users tests (#6999)
95ee202ca3 is described below

commit 95ee202ca38555c2fa6299f8c0a02d3efad2cb24
Author: Eric Holguin <[email protected]>
AuthorDate: Thu Aug 18 13:52:42 2022 -0600

    Refactor Users tests (#6999)
    
    * Refactor users tests
    
    * Move user id function
    
    * remove call to error
    
    * Change client session being used
    
    * fix users register test
    
    * Fix user response
    
    * fix broken tests
    
    * fix user registers validation
    
    * Remove unneeded validation
    
    * fix typo
    
    * Fix query
    
    * Remove error call
    
    * Renamed user test to match route name
    
    * Fix go fmt errors
---
 .../testing/api/v3/federation_users_test.go        |  10 -
 traffic_ops/testing/api/v3/user_current_test.go    | 176 +++++++
 traffic_ops/testing/api/v3/user_test.go            | 536 -------------------
 traffic_ops/testing/api/v3/users_register_test.go  |  93 ++++
 traffic_ops/testing/api/v3/users_test.go           | 367 +++++++++++++
 .../testing/api/v4/federation_users_test.go        |  12 -
 traffic_ops/testing/api/v4/user_current_test.go    | 115 +++++
 traffic_ops/testing/api/v4/user_test.go            | 573 ---------------------
 traffic_ops/testing/api/v4/users_register_test.go  |  93 ++++
 traffic_ops/testing/api/v4/users_test.go           | 375 ++++++++++++++
 10 files changed, 1219 insertions(+), 1131 deletions(-)

diff --git a/traffic_ops/testing/api/v3/federation_users_test.go 
b/traffic_ops/testing/api/v3/federation_users_test.go
index c328ef5e8e..15720626a6 100644
--- a/traffic_ops/testing/api/v3/federation_users_test.go
+++ b/traffic_ops/testing/api/v3/federation_users_test.go
@@ -125,16 +125,6 @@ func TestFederationUsers(t *testing.T) {
        })
 }
 
-func GetUserID(t *testing.T, username string) func() int {
-       return func() int {
-               users, _, err := TOSession.GetUserByUsernameWithHdr(username, 
nil)
-               assert.RequireNoError(t, err, "Get Users Request failed with 
error:", err)
-               assert.RequireEqual(t, 1, len(users), "Expected response object 
length 1, but got %d", len(users))
-               assert.RequireNotNil(t, users[0].ID, "Expected ID to not be 
nil.")
-               return *users[0].ID
-       }
-}
-
 func CreateTestFederationUsers(t *testing.T) {
        // Prerequisite Federation Users
        federationUsers := map[string]tc.FederationUserPost{
diff --git a/traffic_ops/testing/api/v3/user_current_test.go 
b/traffic_ops/testing/api/v3/user_current_test.go
new file mode 100644
index 0000000000..32a9179067
--- /dev/null
+++ b/traffic_ops/testing/api/v3/user_current_test.go
@@ -0,0 +1,176 @@
+package v3
+
+/*
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+import (
+       "encoding/json"
+       "net/http"
+       "testing"
+
+       "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert"
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils"
+       "github.com/apache/trafficcontrol/traffic_ops/toclientlib"
+)
+
+func TestUserCurrent(t *testing.T) {
+       WithObjs(t, []TCObj{Tenants, Parameters, Users}, func() {
+
+               opsUserSession := utils.CreateV3Session(t, 
Config.TrafficOps.URL, "opsuser", "pa$$word", 
Config.Default.Session.TimeoutInSecs)
+
+               methodTests := utils.V3TestCase{
+                       "GET": {
+                               "OK when VALID request": {
+                                       ClientSession: TOSession,
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateUserCurrentFields(map[string]interface{}{"Username": "admin"})),
+                               },
+                       },
+                       "PUT": {
+                               "OK when VALID request": {
+                                       ClientSession: opsUserSession,
+                                       RequestBody: map[string]interface{}{
+                                               "addressLine1":       "address 
of ops",
+                                               "addressLine2":       "place",
+                                               "city":               
"somewhere",
+                                               "company":            "else",
+                                               "country":            "UK",
+                                               "email":              
"[email protected]",
+                                               "fullName":           
"Operations User Updated",
+                                               "id":                 
GetUserID(t, "opsuser")(),
+                                               "localPasswd":        
"pa$$word",
+                                               "confirmLocalPasswd": 
"pa$$word",
+                                               "role":               3,
+                                               "tenant":             "root",
+                                               "tenantId":           
GetTenantID(t, "root")(),
+                                               "username":           "opsuser",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateUsersUpdateCreateFields(map[string]interface{}{"Email": 
"[email protected]", "FullName": "Operations User Updated"})),
+                               },
+                               "BAD REQUEST when EMPTY EMAIL field": {
+                                       ClientSession: opsUserSession,
+                                       RequestBody: map[string]interface{}{
+                                               "addressLine1":       "address 
of ops",
+                                               "addressLine2":       "place",
+                                               "city":               
"somewhere",
+                                               "company":            "else",
+                                               "country":            "UK",
+                                               "email":              "",
+                                               "fullName":           
"Operations User Updated",
+                                               "id":                 
GetUserID(t, "opsuser")(),
+                                               "localPasswd":        
"pa$$word",
+                                               "confirmLocalPasswd": 
"pa$$word",
+                                               "role":               3,
+                                               "tenant":             "root",
+                                               "tenantId":           
GetTenantID(t, "root")(),
+                                               "username":           "opsuser",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                       },
+               }
+
+               for method, testCases := range methodTests {
+                       t.Run(method, func(t *testing.T) {
+                               for name, testCase := range testCases {
+                                       user := tc.User{}
+
+                                       if testCase.RequestBody != nil {
+                                               dat, err := 
json.Marshal(testCase.RequestBody)
+                                               assert.NoError(t, err, "Error 
occurred when marshalling request body: %v", err)
+                                               err = json.Unmarshal(dat, &user)
+                                               assert.NoError(t, err, "Error 
occurred when unmarshalling request body: %v", err)
+                                       }
+
+                                       switch method {
+                                       case "GET":
+                                               t.Run(name, func(t *testing.T) {
+                                                       resp, reqInf, err := 
testCase.ClientSession.GetUserCurrentWithHdr(testCase.RequestHeaders)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp, tc.Alerts{}, err)
+                                                       }
+                                               })
+                                       case "PUT":
+                                               t.Run(name, func(t *testing.T) {
+                                                       resp, reqInf, err := 
testCase.ClientSession.UpdateCurrentUser(user)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                                       }
+                                               })
+                                       }
+                               }
+                       })
+               }
+       })
+}
+
+func validateUserCurrentFields(expectedResp map[string]interface{}) 
utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected Users response to not 
be nil.")
+               user := resp.(*tc.UserCurrent)
+               for field, expected := range expectedResp {
+                       switch field {
+                       case "AddressLine1":
+                               assert.RequireNotNil(t, user.AddressLine1, 
"Expected AddressLine1 to not be nil.")
+                               assert.Equal(t, expected, *user.AddressLine1, 
"Expected AddressLine1 to be %v, but got %s", expected, *user.AddressLine1)
+                       case "AddressLine2":
+                               assert.RequireNotNil(t, user.AddressLine2, 
"Expected AddressLine2 to not be nil.")
+                               assert.Equal(t, expected, *user.AddressLine2, 
"Expected AddressLine2 to be %v, but got %s", expected, *user.AddressLine2)
+                       case "City":
+                               assert.RequireNotNil(t, user.City, "Expected 
City to not be nil.")
+                               assert.Equal(t, expected, *user.City, "Expected 
City to be %v, but got %s", expected, *user.City)
+                       case "Company":
+                               assert.RequireNotNil(t, user.Company, "Expected 
Company to not be nil.")
+                               assert.Equal(t, expected, *user.Company, 
"Expected Company to be %v, but got %s", expected, *user.Company)
+                       case "Country":
+                               assert.RequireNotNil(t, user.Country, "Expected 
Country to not be nil.")
+                               assert.Equal(t, expected, *user.Country, 
"Expected Country to be %v, but got %s", expected, *user.Country)
+                       case "Email":
+                               assert.RequireNotNil(t, user.Email, "Expected 
Email to not be nil.")
+                               assert.Equal(t, expected, *user.Email, 
"Expected Email to be %v, but got %s", expected, *user.Email)
+                       case "FullName":
+                               assert.RequireNotNil(t, user.FullName, 
"Expected FullName to not be nil.")
+                               assert.Equal(t, expected, *user.FullName, 
"Expected FullName to be %v, but got %s", expected, *user.FullName)
+                       case "ID":
+                               assert.RequireNotNil(t, user.ID, "Expected ID 
to not be nil.")
+                               assert.Equal(t, expected, *user.ID, "Expected 
ID to be %v, but got %d", expected, user.ID)
+                       case "PhoneNumber":
+                               assert.RequireNotNil(t, user.PhoneNumber, 
"Expected PhoneNumber to not be nil.")
+                               assert.Equal(t, expected, *user.PhoneNumber, 
"Expected PhoneNumber to be %v, but got %s", expected, *user.PhoneNumber)
+                       case "PostalCode":
+                               assert.RequireNotNil(t, user.PostalCode, 
"Expected PostalCode to not be nil.")
+                               assert.Equal(t, expected, *user.PostalCode, 
"Expected PostalCode to be %v, but got %s", expected, *user.PostalCode)
+                       case "Role":
+                               assert.RequireNotNil(t, user.Role, "Expected 
Role to not be nil.")
+                               assert.Equal(t, expected, *user.Role, "Expected 
Role to be %v, but got %s", expected, *user.Role)
+                       case "StateOrProvince":
+                               assert.RequireNotNil(t, user.StateOrProvince, 
"Expected StateOrProvince to not be nil.")
+                               assert.Equal(t, expected, 
*user.StateOrProvince, "Expected StateOrProvince to be %v, but got %s", 
expected, *user.StateOrProvince)
+                       case "Tenant":
+                               assert.RequireNotNil(t, user.Tenant, "Expected 
Tenant to not be nil.")
+                               assert.Equal(t, expected, *user.Tenant, 
"Expected Tenant to be %v, but got %s", expected, *user.Tenant)
+                       case "TenantID":
+                               assert.RequireNotNil(t, user.TenantID, 
"Expected Tenant to not be nil.")
+                               assert.Equal(t, expected, *user.TenantID, 
"Expected TenantID to be %v, but got %d", expected, *user.TenantID)
+                       case "Username":
+                               assert.RequireNotNil(t, user.UserName, 
"Expected Username to not be nil.")
+                               assert.Equal(t, expected, *user.UserName, 
"Expected Username to be %v, but got %s", expected, *user.UserName)
+                       default:
+                               t.Errorf("Expected field: %v, does not exist in 
response", field)
+                       }
+               }
+       }
+}
diff --git a/traffic_ops/testing/api/v3/user_test.go 
b/traffic_ops/testing/api/v3/user_test.go
deleted file mode 100644
index b9093d9c07..0000000000
--- a/traffic_ops/testing/api/v3/user_test.go
+++ /dev/null
@@ -1,536 +0,0 @@
-package v3
-
-/*
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-*/
-
-import (
-       "bytes"
-       "fmt"
-       "net/http"
-       "net/mail"
-       "sort"
-       "strings"
-       "testing"
-       "time"
-
-       "github.com/apache/trafficcontrol/lib/go-rfc"
-       "github.com/apache/trafficcontrol/lib/go-tc"
-       "github.com/apache/trafficcontrol/lib/go-util"
-       toclient "github.com/apache/trafficcontrol/traffic_ops/v3-client"
-)
-
-func TestUsers(t *testing.T) {
-       WithObjs(t, []TCObj{Tenants, Parameters, Users}, func() {
-               GetTestUsersIMS(t)
-               currentTime := time.Now().UTC().Add(-5 * time.Second)
-               time := currentTime.Format(time.RFC1123)
-               var header http.Header
-               header = make(map[string][]string)
-               header.Set(rfc.IfModifiedSince, time)
-               SortTestUsers(t)
-               UpdateTestUsers(t)
-               GetTestUsersIMSAfterChange(t, header)
-               RolenameCapitalizationTest(t)
-               OpsUpdateAdminTest(t)
-               UserSelfUpdateTest(t)
-               UserUpdateOwnRoleTest(t)
-               GetTestUsers(t)
-               GetTestUserCurrent(t)
-               UserTenancyTest(t)
-               if includeSystemTests {
-                       // UserRegistrationTest deletes test users before 
registering new users, so it must come after the other user tests.
-                       UserRegistrationTest(t)
-               }
-       })
-}
-
-func GetTestUsersIMSAfterChange(t *testing.T, header http.Header) {
-       _, reqInf, err := TOSession.GetUsersWithHdr(header)
-       if err != nil {
-               t.Fatalf("Expected no error, but got %v", err.Error())
-       }
-       if reqInf.StatusCode != http.StatusOK {
-               t.Fatalf("Expected 200 status code, got %v", reqInf.StatusCode)
-       }
-       currentTime := time.Now().UTC()
-       currentTime = currentTime.Add(1 * time.Second)
-       timeStr := currentTime.Format(time.RFC1123)
-       header.Set(rfc.IfModifiedSince, timeStr)
-       _, reqInf, err = TOSession.GetUsersWithHdr(header)
-       if err != nil {
-               t.Fatalf("Expected no error, but got %v", err.Error())
-       }
-       if reqInf.StatusCode != http.StatusNotModified {
-               t.Fatalf("Expected 304 status code, got %v", reqInf.StatusCode)
-       }
-}
-
-const SessionUserName = "admin" // TODO make dynamic?
-
-func GetTestUsersIMS(t *testing.T) {
-       var header http.Header
-       header = make(map[string][]string)
-       futureTime := time.Now().AddDate(0, 0, 1)
-       time := futureTime.Format(time.RFC1123)
-       header.Set(rfc.IfModifiedSince, time)
-       _, reqInf, err := TOSession.GetUsersWithHdr(header)
-       if err != nil {
-               t.Fatalf("Expected no error, but got %v", err.Error())
-       }
-       if reqInf.StatusCode != http.StatusNotModified {
-               t.Fatalf("Expected 304 status code, got %v", reqInf.StatusCode)
-       }
-}
-
-func CreateTestUsers(t *testing.T) {
-       for _, user := range testData.Users {
-
-               resp, _, err := TOSession.CreateUser(&user)
-               if err != nil {
-                       t.Errorf("could not CREATE user: %v", err)
-               }
-               t.Log("Response: ", resp.Alerts)
-       }
-}
-
-func RolenameCapitalizationTest(t *testing.T) {
-
-       roles, _, _, err := TOSession.GetRoles()
-       if err != nil {
-               t.Errorf("could not get roles: %v", err)
-       }
-       if len(roles) == 0 {
-               t.Fatal("there should be at least one role to test the user")
-       }
-
-       tenants, _, err := TOSession.Tenants()
-       if err != nil {
-               t.Errorf("could not get tenants: %v", err)
-       }
-       if len(tenants) == 0 {
-               t.Fatal("there should be at least one tenant to test the user")
-       }
-
-       // this user never does anything, so the role and tenant aren't 
important
-       blob := fmt.Sprintf(`
-       {
-               "username": "test_user",
-               "email": "[email protected]",
-               "fullName": "full name is required",
-               "localPasswd": "better_twelve",
-               "confirmLocalPasswd": "better_twelve",
-               "role": %d,
-               "tenantId": %d
-       }`, *roles[0].ID, tenants[0].ID)
-
-       reader := strings.NewReader(blob)
-       request, err := http.NewRequest("POST", fmt.Sprintf("%v%s/users", 
TOSession.URL, TestAPIBase), reader)
-       if err != nil {
-               t.Errorf("could not make new request: %v", err)
-       }
-       resp, err := TOSession.Client.Do(request)
-       if err != nil {
-               t.Errorf("could not do request: %v", err)
-       }
-
-       buf := new(bytes.Buffer)
-       buf.ReadFrom(resp.Body)
-       strResp := buf.String()
-       if !strings.Contains(strResp, "roleName") {
-               t.Error("incorrect json was returned for POST")
-       }
-
-       request, err = http.NewRequest("GET", 
fmt.Sprintf("%v%s/users?username=test_user", TOSession.URL, TestAPIBase), nil)
-       resp, err = TOSession.Client.Do(request)
-
-       buf = new(bytes.Buffer)
-       buf.ReadFrom(resp.Body)
-       strResp = buf.String()
-       if !strings.Contains(strResp, "rolename") {
-               t.Error("incorrect json was returned for GET")
-       }
-
-}
-
-func OpsUpdateAdminTest(t *testing.T) {
-       toReqTimeout := time.Second * 
time.Duration(Config.Default.Session.TimeoutInSecs)
-       opsTOClient, _, err := toclient.LoginWithAgent(TOSession.URL, 
"opsuser", "pa$$word", true, "to-api-v3-client-tests/opsuser", true, 
toReqTimeout)
-       if err != nil {
-               t.Fatalf("failed to get log in with opsuser: %v", err.Error())
-       }
-
-       resp, _, err := TOSession.GetUserByUsername("admin")
-       if err != nil {
-               t.Errorf("cannot GET user by name: 'admin', %v", err)
-       }
-       user := resp[0]
-
-       fullName := "oops"
-       email := "[email protected]"
-       user.FullName = &fullName
-       user.Email = &email
-
-       _, _, err = opsTOClient.UpdateUserByID(*user.ID, &user)
-       if err == nil {
-               t.Error("ops user incorrectly updated an admin")
-       }
-}
-
-func SortTestUsers(t *testing.T) {
-       var header http.Header
-       var sortedList []string
-       resp, _, err := TOSession.GetUsersWithHdr(header)
-       if err != nil {
-               t.Fatalf("Expected no error, but got %v", err.Error())
-       }
-       for i, _ := range resp {
-               sortedList = append(sortedList, *resp[i].Username)
-       }
-
-       res := sort.SliceIsSorted(sortedList, func(p, q int) bool {
-               return sortedList[p] < sortedList[q]
-       })
-       if res != true {
-               t.Errorf("list is not sorted by their names: %v", sortedList)
-       }
-}
-
-func UserRegistrationTest(t *testing.T) {
-       ForceDeleteTestUsers(t)
-       var emails []string
-       for _, user := range testData.Users {
-               tenant, _, err := TOSession.TenantByName(*user.Tenant)
-               if err != nil {
-                       t.Fatalf("could not get tenant %v: %v", *user.Tenant, 
err)
-               }
-               resp, _, err := TOSession.RegisterNewUser(uint(tenant.ID), 
uint(*user.Role), rfc.EmailAddress{Address: mail.Address{Address: *user.Email}})
-               if err != nil {
-                       t.Fatalf("could not register user: %v", err)
-               }
-               t.Log("Response: ", resp.Alerts)
-               emails = append(emails, fmt.Sprintf(`'%v'`, *user.Email))
-       }
-
-       db, err := OpenConnection()
-       if err != nil {
-               t.Error("cannot open db")
-       }
-       defer db.Close()
-       q := `DELETE FROM tm_user WHERE email IN (` + strings.Join(emails, ",") 
+ `)`
-       if err := execSQL(db, q); err != nil {
-               t.Errorf("cannot execute SQL to delete registered users: %s; 
SQL is %s", err.Error(), q)
-       }
-}
-
-func UserSelfUpdateTest(t *testing.T) {
-       toReqTimeout := time.Second * 
time.Duration(Config.Default.Session.TimeoutInSecs)
-       opsTOClient, _, err := toclient.LoginWithAgent(TOSession.URL, 
"opsuser", "pa$$word", true, "to-api-v3-client-tests/opsuser", true, 
toReqTimeout)
-       if err != nil {
-               t.Fatalf("failed to get log in with opsuser: %v", err.Error())
-       }
-
-       resp, _, err := TOSession.GetUserByUsername("opsuser")
-       if err != nil {
-               t.Fatalf("cannot GET user by name: 'opsuser', %v\n", err)
-       }
-       if len(resp) < 1 {
-               t.Fatalf("no users returned when requesting user 'opsuser'")
-       }
-       user := resp[0]
-
-       if user.ID == nil {
-               t.Fatalf("user 'opsuser' has a null or missing ID - cannot 
proceed")
-       }
-
-       user.FullName = util.StrPtr("Oops-man")
-       user.Email = util.StrPtr("[email protected]")
-
-       var updateResp *tc.UpdateUserResponse
-       updateResp, _, err = opsTOClient.UpdateUserByID(*user.ID, &user)
-       if err != nil {
-               t.Fatalf("cannot UPDATE user by id: %v - %v\n", err, updateResp)
-       }
-
-       // Make sure it got updated
-       resp2, _, err := TOSession.GetUserByID(*user.ID)
-       if err != nil {
-               t.Fatalf("cannot GET user by id: '%d', %v\n", *user.ID, err)
-       }
-       if len(resp2) < 1 {
-               t.Fatalf("no results returned when requesting user #%d", 
*user.ID)
-       }
-       updatedUser := resp2[0]
-
-       if updatedUser.FullName == nil {
-               t.Errorf("user was not correctly updated, FullName is null or 
missing")
-       } else if *updatedUser.FullName != "Oops-man" {
-               t.Errorf("results do not match actual: '%s', expected: 
'Oops-man'\n", *updatedUser.FullName)
-       }
-
-       if updatedUser.Email == nil {
-               t.Errorf("user was not correctly updated, Email is null or 
missing")
-       } else if *updatedUser.Email != "[email protected]" {
-               t.Errorf("results do not match actual: '%s', expected: 
'[email protected]'\n", *updatedUser.Email)
-       }
-
-       // Same thing using /user/current
-       user.FullName = util.StrPtr("ops-man")
-       user.Email = util.StrPtr("[email protected]")
-       updateResp, _, err = opsTOClient.UpdateCurrentUser(user)
-       if err != nil {
-               t.Fatalf("error updating current user: %v - %v", err, 
updateResp)
-       }
-
-       // Make sure it got updated
-       resp2, _, err = TOSession.GetUserByID(*user.ID)
-       if err != nil {
-               t.Fatalf("error getting user #%d: %v", *user.ID, err)
-       }
-
-       if len(resp2) < 1 {
-               t.Fatalf("no user returned when requesting user #%d", *user.ID)
-       }
-
-       if resp2[0].FullName == nil {
-               t.Errorf("FullName missing or null after update")
-       } else if *resp2[0].FullName != "ops-man" {
-               t.Errorf("Expected FullName to be 'ops-man', but it was '%s'", 
*resp2[0].FullName)
-       }
-
-       if resp2[0].Email == nil {
-               t.Errorf("Email missing or null after update")
-       } else if *resp2[0].Email != "[email protected]" {
-               t.Errorf("Expected Email to be restored to 
'[email protected]', but it was '%s'", *resp2[0].Email)
-       }
-
-       // now test using an invalid email address
-       currentEmail := *user.Email
-       user.Email = new(string)
-       updateResp, _, err = TOSession.UpdateCurrentUser(user)
-       if err == nil {
-               t.Fatal("error was expected updating user with email: '' - got 
none")
-       }
-
-       // Ensure it wasn't actually updated
-       resp2, _, err = TOSession.GetUserByID(*user.ID)
-       if err != nil {
-               t.Fatalf("error getting user #%d: %v", *user.ID, err)
-       }
-
-       if len(resp2) < 1 {
-               t.Fatalf("no user returned when requesting user #%d", *user.ID)
-       }
-
-       if resp2[0].Email == nil {
-               t.Errorf("Email missing or null after update")
-       } else if *resp2[0].Email != currentEmail {
-               t.Errorf("Expected Email to still be '%s', but it was '%s'", 
currentEmail, *resp2[0].Email)
-       }
-}
-
-func UserUpdateOwnRoleTest(t *testing.T) {
-       resp, _, err := TOSession.GetUserByUsername(SessionUserName)
-       if err != nil {
-               t.Errorf("cannot GET user by name: '%s', %v", SessionUserName, 
err)
-       }
-       user := resp[0]
-
-       *user.Role = *user.Role + 1
-       _, _, err = TOSession.UpdateUserByID(*user.ID, &user)
-       if err == nil {
-               t.Error("user incorrectly updated their role")
-       }
-}
-
-func UpdateTestUsers(t *testing.T) {
-       firstUsername := *testData.Users[0].Username
-       resp, _, err := TOSession.GetUserByUsername(firstUsername)
-       if err != nil {
-               t.Errorf("cannot GET user by name: '%s', %v", firstUsername, 
err)
-       }
-       user := resp[0]
-       newCity := "kidz kable kown"
-       *user.City = newCity
-
-       var updateResp *tc.UpdateUserResponse
-       updateResp, _, err = TOSession.UpdateUserByID(*user.ID, &user)
-       if err != nil {
-               t.Errorf("cannot UPDATE user by id: %v - %v", err, 
updateResp.Alerts)
-       }
-
-       // Make sure it got updated
-       resp2, _, err := TOSession.GetUserByID(*user.ID)
-       if err != nil {
-               t.Errorf("cannot GET user by id: '%d', %v", *user.ID, err)
-       }
-       updatedUser := resp2[0]
-       if *updatedUser.City != newCity {
-               t.Errorf("results do not match actual: %s, expected: %s", 
*updatedUser.City, newCity)
-       }
-       if resp[0].RegistrationSent != resp2[0].RegistrationSent {
-               t.Errorf("registration_sent value shouldn't have been updated, 
expectd: %s, got: %s", resp[0].RegistrationSent, resp2[0].RegistrationSent)
-       }
-
-}
-
-func GetTestUsers(t *testing.T) {
-       _, _, err := TOSession.GetUsers()
-       if err != nil {
-               t.Errorf("cannot GET users: %v", err)
-       }
-}
-
-func GetTestUserCurrent(t *testing.T) {
-       user, _, err := TOSession.GetUserCurrent()
-       if err != nil {
-               t.Errorf("cannot GET current user: %v", err)
-       }
-       if user.UserName == nil {
-               t.Errorf("current user expected: %v actual: %v", 
SessionUserName, nil)
-       }
-       if *user.UserName != SessionUserName {
-               t.Errorf("current user expected: %v actual: %v", 
SessionUserName, *user.UserName)
-       }
-}
-
-func UserTenancyTest(t *testing.T) {
-       users, _, err := TOSession.GetUsers()
-       if err != nil {
-               t.Errorf("cannot GET users: %v", err)
-       }
-       tenant3Found := false
-       tenant4Found := false
-       tenant3Username := "tenant3user"
-       tenant4Username := "tenant4user"
-       tenant3User := tc.User{}
-
-       // assert admin user can view tenant3user and tenant4user
-       for _, user := range users {
-               if *user.Username == tenant3Username {
-                       tenant3Found = true
-                       tenant3User = user
-               } else if *user.Username == tenant4Username {
-                       tenant4Found = true
-               }
-               if tenant3Found && tenant4Found {
-                       break
-               }
-       }
-       if !tenant3Found || !tenant4Found {
-               t.Error("expected admin to be able to view tenants: tenant3 and 
tenant4")
-       }
-
-       toReqTimeout := time.Second * 
time.Duration(Config.Default.Session.TimeoutInSecs)
-       tenant4TOClient, _, err := toclient.LoginWithAgent(TOSession.URL, 
"tenant4user", "pa$$word", true, "to-api-v3-client-tests/tenant4user", true, 
toReqTimeout)
-       if err != nil {
-               t.Fatalf("failed to log in with tenant4user: %v", err.Error())
-       }
-
-       usersReadableByTenant4, _, err := tenant4TOClient.GetUsers()
-       if err != nil {
-               t.Error("tenant4user cannot GET users")
-       }
-
-       tenant4canReadItself := false
-       for _, user := range usersReadableByTenant4 {
-               // assert that tenant4user cannot read tenant3user
-               if *user.Username == tenant3Username {
-                       t.Error("expected tenant4user to be unable to read 
tenant3user")
-               }
-               // assert that tenant4user can read itself
-               if *user.Username == tenant4Username {
-                       tenant4canReadItself = true
-               }
-       }
-       if !tenant4canReadItself {
-               t.Error("expected tenant4user to be able to read itself")
-       }
-
-       // assert that tenant4user cannot update tenant3user
-       if _, _, err = tenant4TOClient.UpdateUserByID(*tenant3User.ID, 
&tenant3User); err == nil {
-               t.Error("expected tenant4user to be unable to update 
tenant4user")
-       }
-
-       // assert that tenant4user cannot create a user outside of its tenant
-       rootTenant, _, err := TOSession.TenantByName("root")
-       if err != nil {
-               t.Error("expected to be able to GET the root tenant")
-       }
-       newUser := testData.Users[0]
-       newUser.Email = util.StrPtr("[email protected]")
-       newUser.Username = util.StrPtr("testusertenancy")
-       newUser.TenantID = &rootTenant.ID
-       if _, _, err = tenant4TOClient.CreateUser(&newUser); err == nil {
-               t.Error("expected tenant4user to be unable to create a new user 
in the root tenant")
-       }
-}
-
-// ForceDeleteTestUsers forcibly deletes the users from the db.
-func ForceDeleteTestUsers(t *testing.T) {
-
-       // NOTE: Special circumstances!  This should *NOT* be done without a 
really good reason!
-       //  Connects directly to the DB to remove users rather than going thru 
the client.
-       //  This is required here because the DeleteUser action does not really 
delete users,  but disables them.
-       db, err := OpenConnection()
-       if err != nil {
-               t.Error("cannot open db")
-       }
-       defer db.Close()
-
-       var usernames []string
-       for _, user := range testData.Users {
-               usernames = append(usernames, `'`+*user.Username+`'`)
-       }
-
-       // there is a constraint that prevents users from being deleted when 
they have a log
-       q := `DELETE FROM log WHERE NOT tm_user = (SELECT id FROM tm_user WHERE 
username = 'admin')`
-       err = execSQL(db, q)
-       if err != nil {
-               t.Errorf("cannot execute SQL: %s; SQL is %s", err.Error(), q)
-       }
-
-       q = `DELETE FROM tm_user WHERE username IN (` + strings.Join(usernames, 
",") + `)`
-       err = execSQL(db, q)
-       if err != nil {
-               t.Errorf("cannot execute SQL: %s; SQL is %s", err.Error(), q)
-       }
-}
-
-func DeleteTestUsers(t *testing.T) {
-       for _, user := range testData.Users {
-
-               resp, _, err := TOSession.GetUserByUsername(*user.Username)
-               if err != nil {
-                       t.Errorf("cannot GET user by name: %v - %v", 
*user.Username, err)
-               }
-
-               if resp != nil {
-                       respUser := resp[0]
-
-                       _, _, err := TOSession.DeleteUserByID(*respUser.ID)
-                       if err != nil {
-                               t.Errorf("cannot DELETE user by name: '%s' %v", 
*respUser.Username, err)
-                       }
-
-                       // Make sure it got deleted
-                       resp, _, err := 
TOSession.GetUserByUsername(*user.Username)
-                       if err != nil {
-                               t.Errorf("error deleting user by name: %s", 
err.Error())
-                       }
-                       if len(resp) > 0 {
-                               t.Errorf("expected user: %s to be deleted", 
*user.Username)
-                       }
-               }
-       }
-}
diff --git a/traffic_ops/testing/api/v3/users_register_test.go 
b/traffic_ops/testing/api/v3/users_register_test.go
new file mode 100644
index 0000000000..050aae9b7b
--- /dev/null
+++ b/traffic_ops/testing/api/v3/users_register_test.go
@@ -0,0 +1,93 @@
+package v3
+
+/*
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+import (
+       "encoding/json"
+       "net/http"
+       "testing"
+
+       "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert"
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils"
+       "github.com/apache/trafficcontrol/traffic_ops/toclientlib"
+)
+
+func TestUsersRegister(t *testing.T) {
+       if includeSystemTests {
+               WithObjs(t, []TCObj{Tenants, Parameters}, func() {
+
+                       methodTests := utils.V3TestCase{
+                               "POST": {
+                                       "OK when VALID request": {
+                                               ClientSession: TOSession,
+                                               RequestBody: 
map[string]interface{}{
+                                                       "addressLine1":       
"address of ops",
+                                                       "addressLine2":       
"place",
+                                                       "city":               
"somewhere",
+                                                       "company":            
"else",
+                                                       "country":            
"UK",
+                                                       "email":              
"[email protected]",
+                                                       "fullName":           
"Operations User Updated",
+                                                       "localPasswd":        
"pa$$word",
+                                                       "confirmLocalPasswd": 
"pa$$word",
+                                                       "role":               3,
+                                                       "tenant":             
"root",
+                                                       "tenantId":           
GetTenantID(t, "root")(),
+                                                       "username":           
"opsuser",
+                                               },
+                                               Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
validateDeletion("[email protected]")),
+                                       },
+                               },
+                       }
+
+                       for method, testCases := range methodTests {
+                               t.Run(method, func(t *testing.T) {
+                                       for name, testCase := range testCases {
+                                               userRegistration := 
tc.UserRegistrationRequest{}
+
+                                               if testCase.RequestBody != nil {
+                                                       dat, err := 
json.Marshal(testCase.RequestBody)
+                                                       assert.NoError(t, err, 
"Error occurred when marshalling request body: %v", err)
+                                                       err = 
json.Unmarshal(dat, &userRegistration)
+                                                       assert.NoError(t, err, 
"Error occurred when unmarshalling request body: %v", err)
+                                               }
+
+                                               switch method {
+                                               case "POST":
+                                                       t.Run(name, func(t 
*testing.T) {
+                                                               alerts, reqInf, 
err := testCase.ClientSession.RegisterNewUser(userRegistration.TenantID, 
userRegistration.Role, userRegistration.Email)
+                                                               for _, check := 
range testCase.Expectations {
+                                                                       
check(t, reqInf, nil, alerts, err)
+                                                               }
+                                                       })
+                                               }
+                                       }
+                               })
+                       }
+               })
+       }
+}
+
+func validateDeletion(email string) utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               db, err := OpenConnection()
+               assert.RequireNoError(t, err, "Cannot open db")
+               defer db.Close()
+               q := `DELETE FROM tm_user WHERE email = '` + email + `'`
+               err = execSQL(db, q)
+               assert.NoError(t, err, "Cannot execute SQL to delete registered 
users: %s; SQL is %s", err, q)
+       }
+}
diff --git a/traffic_ops/testing/api/v3/users_test.go 
b/traffic_ops/testing/api/v3/users_test.go
new file mode 100644
index 0000000000..3d6f6d92f8
--- /dev/null
+++ b/traffic_ops/testing/api/v3/users_test.go
@@ -0,0 +1,367 @@
+package v3
+
+/*
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+import (
+       "encoding/json"
+       "net/http"
+       "sort"
+       "strings"
+       "testing"
+       "time"
+
+       "github.com/apache/trafficcontrol/lib/go-rfc"
+       "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert"
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils"
+       "github.com/apache/trafficcontrol/traffic_ops/toclientlib"
+)
+
+func TestUsers(t *testing.T) {
+       WithObjs(t, []TCObj{Tenants, Parameters, Users}, func() {
+
+               opsUserSession := utils.CreateV3Session(t, 
Config.TrafficOps.URL, "opsuser", "pa$$word", 
Config.Default.Session.TimeoutInSecs)
+               tenant4UserSession := utils.CreateV3Session(t, 
Config.TrafficOps.URL, "tenant4user", "pa$$word", 
Config.Default.Session.TimeoutInSecs)
+
+               currentTime := time.Now().UTC().Add(-15 * time.Second)
+               currentTimeRFC := currentTime.Format(time.RFC1123)
+               tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123)
+
+               methodTests := utils.V3TestCase{
+                       "GET": {
+                               "NOT MODIFIED when NO CHANGES made": {
+                                       ClientSession:  TOSession,
+                                       RequestHeaders: 
http.Header{rfc.IfModifiedSince: {tomorrow}},
+                                       Expectations:   
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)),
+                               },
+                               "OK when CHANGES made": {
+                                       ClientSession:  TOSession,
+                                       RequestHeaders: 
http.Header{rfc.IfModifiedSince: {currentTimeRFC}},
+                                       Expectations:   
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                               },
+                               "OK when VALID request": {
+                                       ClientSession: TOSession,
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseLengthGreaterOrEqual(1),
+                                               validateUsersSort()),
+                               },
+                               "ADMIN can view CHILD TENANTS": {
+                                       ClientSession: TOSession,
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseLengthGreaterOrEqual(1),
+                                               
validateTenants(map[string]bool{"tenant3": true, "tenant4": true})),
+                               },
+                               "CHILD TENANT should NOT read PARENT TENANT": {
+                                       ClientSession: tenant4UserSession,
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseHasLength(1),
+                                               
validateTenants(map[string]bool{"tenant3": false, "tenant4": true})),
+                               },
+                       },
+                       "POST": {
+                               "FORBIDDEN when CHILD TENANT creates USER with 
PARENT TENANCY": {
+                                       ClientSession: tenant4UserSession,
+                                       RequestBody: map[string]interface{}{
+                                               "email":              
"[email protected]",
+                                               "fullName":           "Outside 
Tenancy",
+                                               "localPasswd":        
"pa$$word",
+                                               "confirmLocalPasswd": 
"pa$$word",
+                                               "role":               3,
+                                               "tenantId":           
GetTenantID(t, "tenant3")(),
+                                               "username":           
"outsideTenantUser",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)),
+                               },
+                       },
+                       "PUT": {
+                               "OK when VALID request": {
+                                       EndpointId:    GetUserID(t, "steering"),
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "addressLine1":       "updated 
line 1",
+                                               "addressLine2":       "updated 
line 2",
+                                               "city":               "updated 
city name",
+                                               "company":            "new 
company",
+                                               "country":            "US",
+                                               "email":              
"[email protected]",
+                                               "fullName":           "Steering 
User Updated",
+                                               "localPasswd":        
"pa$$word",
+                                               "confirmLocalPasswd": 
"pa$$word",
+                                               "newUser":            false,
+                                               "role":               6,
+                                               "tenant":             "root",
+                                               "tenantId":           
GetTenantID(t, "root")(),
+                                               "username":           
"steering",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateUsersUpdateCreateFields(map[string]interface{}{"AddressLine1": "updated 
line 1",
+                                                       "AddressLine2": 
"updated line 2", "City": "updated city name", "Company": "new company",
+                                                       "Country": "US", 
"Email": "[email protected]", "FullName": "Steering User Updated"})),
+                               },
+                               "OK when UPDATING SELF": {
+                                       EndpointId:    GetUserID(t, "opsuser"),
+                                       ClientSession: opsUserSession,
+                                       RequestBody: map[string]interface{}{
+                                               "addressLine1":       "address 
of ops",
+                                               "addressLine2":       "place",
+                                               "city":               
"somewhere",
+                                               "company":            "else",
+                                               "country":            "UK",
+                                               "email":              
"[email protected]",
+                                               "fullName":           
"Operations User Updated",
+                                               "localPasswd":        
"pa$$word",
+                                               "confirmLocalPasswd": 
"pa$$word",
+                                               "role":               3,
+                                               "tenant":             "root",
+                                               "tenantId":           
GetTenantID(t, "root")(),
+                                               "username":           "opsuser",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateUsersUpdateCreateFields(map[string]interface{}{"Email": 
"[email protected]", "FullName": "Operations User Updated"})),
+                               },
+                               "BAD REQUEST when updating OWN ROLE": {
+                                       EndpointId:    GetUserID(t, "opsuser"),
+                                       ClientSession: opsUserSession,
+                                       RequestBody: map[string]interface{}{
+                                               "addressLine1":       "address 
of ops",
+                                               "addressLine2":       "place",
+                                               "city":               
"somewhere",
+                                               "company":            "else",
+                                               "country":            "UK",
+                                               "email":              
"[email protected]",
+                                               "fullName":           
"Operations User Updated",
+                                               "localPasswd":        
"pa$$word",
+                                               "confirmLocalPasswd": 
"pa$$word",
+                                               "role":               9999,
+                                               "tenant":             "root",
+                                               "tenantId":           
GetTenantID(t, "root")(),
+                                               "username":           "opsuser",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "FORBIDDEN when OPERATIONS USER updates ADMIN 
USER": {
+                                       EndpointId:    GetUserID(t, "admin"),
+                                       ClientSession: opsUserSession,
+                                       RequestBody: map[string]interface{}{
+                                               "email":              
"[email protected]",
+                                               "fullName":           "oops",
+                                               "localPasswd":        
"pa$$word",
+                                               "confirmLocalPasswd": 
"pa$$word",
+                                               "role":               4,
+                                               "tenant":             "root",
+                                               "tenantId":           
GetTenantID(t, "root")(),
+                                               "username":           "admin",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)),
+                               },
+                               "FORBIDDEN when CHILD TENANT USER updates 
PARENT TENANT USER": {
+                                       EndpointId:    GetUserID(t, 
"tenant3user"),
+                                       ClientSession: tenant4UserSession,
+                                       RequestBody: map[string]interface{}{
+                                               "email":              
"[email protected]",
+                                               "fullName":           "Parent 
tenant test",
+                                               "localPasswd":        
"pa$$word",
+                                               "confirmLocalPasswd": 
"pa$$word",
+                                               "role":               4,
+                                               "tenant":             "tenant2",
+                                               "tenantId":           
GetTenantID(t, "tenant2")(),
+                                               "username":           
"tenant3user",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)),
+                               },
+                       },
+               }
+
+               for method, testCases := range methodTests {
+                       t.Run(method, func(t *testing.T) {
+                               for name, testCase := range testCases {
+                                       user := tc.User{}
+
+                                       if testCase.RequestBody != nil {
+                                               dat, err := 
json.Marshal(testCase.RequestBody)
+                                               assert.NoError(t, err, "Error 
occurred when marshalling request body: %v", err)
+                                               err = json.Unmarshal(dat, &user)
+                                               assert.NoError(t, err, "Error 
occurred when unmarshalling request body: %v", err)
+                                       }
+
+                                       switch method {
+                                       case "GET":
+                                               t.Run(name, func(t *testing.T) {
+                                                       resp, reqInf, err := 
testCase.ClientSession.GetUsersWithHdr(testCase.RequestHeaders)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp, tc.Alerts{}, err)
+                                                       }
+                                               })
+                                       case "POST":
+                                               t.Run(name, func(t *testing.T) {
+                                                       resp, reqInf, err := 
testCase.ClientSession.CreateUser(&user)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                                       }
+                                               })
+                                       case "PUT":
+                                               t.Run(name, func(t *testing.T) {
+                                                       resp, reqInf, err := 
testCase.ClientSession.UpdateUserByID(testCase.EndpointId(), &user)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                                       }
+                                               })
+                                       }
+                               }
+                       })
+               }
+       })
+}
+
+func validateUsersFields(expectedResp map[string]interface{}) utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected Users response to not 
be nil.")
+               userResp := resp.([]tc.User)
+               for field, expected := range expectedResp {
+                       for _, user := range userResp {
+                               switch field {
+                               case "AddressLine1":
+                                       assert.RequireNotNil(t, 
user.AddressLine1, "Expected AddressLine1 to not be nil.")
+                                       assert.Equal(t, expected, 
*user.AddressLine1, "Expected AddressLine1 to be %v, but got %s", expected, 
*user.AddressLine1)
+                               case "AddressLine2":
+                                       assert.RequireNotNil(t, 
user.AddressLine2, "Expected AddressLine2 to not be nil.")
+                                       assert.Equal(t, expected, 
*user.AddressLine2, "Expected AddressLine2 to be %v, but got %s", expected, 
*user.AddressLine2)
+                               case "City":
+                                       assert.RequireNotNil(t, user.City, 
"Expected City to not be nil.")
+                                       assert.Equal(t, expected, *user.City, 
"Expected City to be %v, but got %s", expected, *user.City)
+                               case "Company":
+                                       assert.RequireNotNil(t, user.Company, 
"Expected Company to not be nil.")
+                                       assert.Equal(t, expected, 
*user.Company, "Expected Company to be %v, but got %s", expected, *user.Company)
+                               case "Country":
+                                       assert.RequireNotNil(t, user.Country, 
"Expected Country to not be nil.")
+                                       assert.Equal(t, expected, 
*user.Country, "Expected Country to be %v, but got %s", expected, *user.Country)
+                               case "Email":
+                                       assert.RequireNotNil(t, user.Email, 
"Expected Email to not be nil.")
+                                       assert.Equal(t, expected, *user.Email, 
"Expected Email to be %v, but got %s", expected, *user.Email)
+                               case "FullName":
+                                       assert.RequireNotNil(t, user.FullName, 
"Expected FullName to not be nil.")
+                                       assert.Equal(t, expected, 
*user.FullName, "Expected FullName to be %v, but got %s", expected, 
*user.FullName)
+                               case "ID":
+                                       assert.RequireNotNil(t, user.ID, 
"Expected ID to not be nil.")
+                                       assert.Equal(t, expected, *user.ID, 
"Expected ID to be %v, but got %d", expected, user.ID)
+                               case "PhoneNumber":
+                                       assert.RequireNotNil(t, 
user.PhoneNumber, "Expected PhoneNumber to not be nil.")
+                                       assert.Equal(t, expected, 
*user.PhoneNumber, "Expected PhoneNumber to be %v, but got %s", expected, 
*user.PhoneNumber)
+                               case "PostalCode":
+                                       assert.RequireNotNil(t, 
user.PostalCode, "Expected PostalCode to not be nil.")
+                                       assert.Equal(t, expected, 
*user.PostalCode, "Expected PostalCode to be %v, but got %s", expected, 
*user.PostalCode)
+                               case "RegistrationSent":
+                                       assert.RequireNotNil(t, 
user.RegistrationSent, "Expected RegistrationSent to not be nil.")
+                                       assert.Equal(t, expected, 
*user.RegistrationSent, "Expected RegistrationSent to be %v, but got %v", 
expected, *user.RegistrationSent)
+                               case "Role":
+                                       assert.RequireNotNil(t, user.Role, 
"Expected Role to not be nil.")
+                                       assert.Equal(t, expected, *user.Role, 
"Expected Role to be %v, but got %s", expected, *user.Role)
+                               case "StateOrProvince":
+                                       assert.RequireNotNil(t, 
user.StateOrProvince, "Expected StateOrProvince to not be nil.")
+                                       assert.Equal(t, expected, 
*user.StateOrProvince, "Expected StateOrProvince to be %v, but got %s", 
expected, *user.StateOrProvince)
+                               case "Tenant":
+                                       assert.RequireNotNil(t, user.Tenant, 
"Expected Tenant to not be nil.")
+                                       assert.Equal(t, expected, *user.Tenant, 
"Expected Tenant to be %v, but got %s", expected, *user.Tenant)
+                               case "TenantID":
+                                       assert.RequireNotNil(t, user.TenantID, 
"Expected Tenant to not be nil.")
+                                       assert.Equal(t, expected, 
*user.TenantID, "Expected TenantID to be %v, but got %d", expected, 
*user.TenantID)
+                               case "Username":
+                                       assert.RequireNotNil(t, user.Username, 
"Expected Username to not be nil.")
+                                       assert.Equal(t, expected, 
*user.Username, "Expected Username to be %v, but got %s", expected, 
*user.Username)
+                               default:
+                                       t.Errorf("Expected field: %v, does not 
exist in response", field)
+                               }
+                       }
+               }
+       }
+}
+
+func validateTenants(expectedTenants map[string]bool) utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected Users response to not 
be nil.")
+               userResp := resp.([]tc.User)
+
+               for _, user := range userResp {
+                       for tenant, expected := range expectedTenants {
+                               assert.RequireNotNil(t, user.Tenant, "Expected 
Users response to not be nil.")
+                               if *user.Tenant == tenant && !expected {
+                                       t.Errorf("Tenant: %s was not expected", 
*user.Tenant)
+                               }
+                       }
+               }
+       }
+}
+
+func validateUsersUpdateCreateFields(expectedResp map[string]interface{}) 
utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected Users response to not 
be nil.")
+               assert.RequireNotEqual(t, resp.(tc.User), tc.User{}, "Expected 
a non empty response.")
+               userResp := resp.(tc.User)
+               users := []tc.User{userResp}
+               validateUsersFields(expectedResp)(t, toclientlib.ReqInf{}, 
users, tc.Alerts{}, nil)
+       }
+}
+
+func validateUsersSort() utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, 
alerts tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected Users response to not 
be nil.")
+               var usernames []string
+               usersResp := resp.([]tc.User)
+               for _, user := range usersResp {
+                       assert.RequireNotNil(t, user.Username, "Expected 
Username to not be nil.")
+                       usernames = append(usernames, *user.Username)
+               }
+               assert.Equal(t, true, sort.StringsAreSorted(usernames), "List 
is not sorted by their usernames: %v", usernames)
+       }
+}
+
+func GetUserID(t *testing.T, username string) func() int {
+       return func() int {
+               users, _, err := TOSession.GetUserByUsernameWithHdr(username, 
nil)
+               assert.RequireNoError(t, err, "Get Users Request failed with 
error:", err)
+               assert.RequireEqual(t, 1, len(users), "Expected response object 
length 1, but got %d", len(users))
+               assert.RequireNotNil(t, users[0].ID, "Expected ID to not be 
nil.")
+               return *users[0].ID
+       }
+}
+
+func CreateTestUsers(t *testing.T) {
+       for _, user := range testData.Users {
+               resp, _, err := TOSession.CreateUser(&user)
+               assert.RequireNoError(t, err, "Could not create user: %v - 
alerts: %+v", err, resp.Alerts)
+       }
+}
+
+// ForceDeleteTestUsers forcibly deletes the users from the db.
+// NOTE: Special circumstances!  This should *NOT* be done without a really 
good reason!
+// Connects directly to the DB to remove users rather than going through the 
client.
+// This is required here because the DeleteUser action does not really delete 
users,  but disables them.
+func ForceDeleteTestUsers(t *testing.T) {
+
+       db, err := OpenConnection()
+       assert.RequireNoError(t, err, "Cannot open db")
+       defer db.Close()
+
+       var usernames []string
+       for _, user := range testData.Users {
+               usernames = append(usernames, `'`+*user.Username+`'`)
+       }
+
+       // there is a constraint that prevents users from being deleted when 
they have a log
+       q := `DELETE FROM log WHERE NOT tm_user = (SELECT id FROM tm_user WHERE 
username = 'admin')`
+       err = execSQL(db, q)
+       assert.RequireNoError(t, err, "Cannot execute SQL: %v; SQL is %s", err, 
q)
+
+       q = `DELETE FROM tm_user WHERE username IN (` + strings.Join(usernames, 
",") + `)`
+       err = execSQL(db, q)
+       assert.NoError(t, err, "Cannot execute SQL: %v; SQL is %s", err, q)
+}
diff --git a/traffic_ops/testing/api/v4/federation_users_test.go 
b/traffic_ops/testing/api/v4/federation_users_test.go
index 2984c6bc3c..8b5f9a14ab 100644
--- a/traffic_ops/testing/api/v4/federation_users_test.go
+++ b/traffic_ops/testing/api/v4/federation_users_test.go
@@ -218,18 +218,6 @@ func validateFederationUserIDSort(desc bool) 
utils.CkReqFunc {
        }
 }
 
-func GetUserID(t *testing.T, username string) func() int {
-       return func() int {
-               opts := client.NewRequestOptions()
-               opts.QueryParameters.Set("username", username)
-               users, _, err := TOSession.GetUsers(opts)
-               assert.RequireNoError(t, err, "Get Users Request failed with 
error:", err)
-               assert.RequireEqual(t, 1, len(users.Response), "Expected 
response object length 1, but got %d", len(users.Response))
-               assert.RequireNotNil(t, users.Response[0].ID, "Expected ID to 
not be nil.")
-               return *users.Response[0].ID
-       }
-}
-
 func CreateTestFederationUsers(t *testing.T) {
        // Prerequisite Federation Users
        federationUsers := map[string]tc.FederationUserPost{
diff --git a/traffic_ops/testing/api/v4/user_current_test.go 
b/traffic_ops/testing/api/v4/user_current_test.go
new file mode 100644
index 0000000000..8284e34c27
--- /dev/null
+++ b/traffic_ops/testing/api/v4/user_current_test.go
@@ -0,0 +1,115 @@
+package v4
+
+/*
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+import (
+       "encoding/json"
+       "net/http"
+       "testing"
+
+       "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert"
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils"
+)
+
+func TestUserCurrent(t *testing.T) {
+       WithObjs(t, []TCObj{Tenants, Parameters, Users}, func() {
+
+               opsUserSession := utils.CreateV4Session(t, 
Config.TrafficOps.URL, "opsuser", "pa$$word", 
Config.Default.Session.TimeoutInSecs)
+
+               methodTests := utils.V4TestCase{
+                       "GET": {
+                               "OK when VALID request": {
+                                       ClientSession: TOSession,
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateUsersUpdateCreateFields(map[string]interface{}{"Username": "admin"})),
+                               },
+                       },
+                       "PUT": {
+                               "OK when VALID request": {
+                                       ClientSession: opsUserSession,
+                                       RequestBody: map[string]interface{}{
+                                               "addressLine1":       "address 
of ops",
+                                               "addressLine2":       "place",
+                                               "city":               
"somewhere",
+                                               "company":            "else",
+                                               "country":            "UK",
+                                               "email":              
"[email protected]",
+                                               "fullName":           
"Operations User Updated",
+                                               "localPasswd":        
"pa$$word",
+                                               "confirmLocalPasswd": 
"pa$$word",
+                                               "role":               
"operations",
+                                               "tenant":             "root",
+                                               "tenantId":           
GetTenantID(t, "root")(),
+                                               "username":           "opsuser",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateUsersUpdateCreateFields(map[string]interface{}{"Email": 
"[email protected]", "FullName": "Operations User Updated"})),
+                               },
+                               "BAD REQUEST when EMPTY EMAIL field": {
+                                       ClientSession: opsUserSession,
+                                       RequestBody: map[string]interface{}{
+                                               "addressLine1":       "address 
of ops",
+                                               "addressLine2":       "place",
+                                               "city":               
"somewhere",
+                                               "company":            "else",
+                                               "country":            "UK",
+                                               "email":              "",
+                                               "fullName":           
"Operations User Updated",
+                                               "localPasswd":        
"pa$$word",
+                                               "confirmLocalPasswd": 
"pa$$word",
+                                               "role":               
"operations",
+                                               "tenant":             "root",
+                                               "tenantId":           
GetTenantID(t, "root")(),
+                                               "username":           "opsuser",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                       },
+               }
+
+               for method, testCases := range methodTests {
+                       t.Run(method, func(t *testing.T) {
+                               for name, testCase := range testCases {
+                                       user := tc.UserV4{}
+
+                                       if testCase.RequestBody != nil {
+                                               dat, err := 
json.Marshal(testCase.RequestBody)
+                                               assert.NoError(t, err, "Error 
occurred when marshalling request body: %v", err)
+                                               err = json.Unmarshal(dat, &user)
+                                               assert.NoError(t, err, "Error 
occurred when unmarshalling request body: %v", err)
+                                       }
+
+                                       switch method {
+                                       case "GET":
+                                               t.Run(name, func(t *testing.T) {
+                                                       resp, reqInf, err := 
testCase.ClientSession.GetUserCurrent(testCase.RequestOpts)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                                       }
+                                               })
+                                       case "PUT":
+                                               t.Run(name, func(t *testing.T) {
+                                                       resp, reqInf, err := 
testCase.ClientSession.UpdateCurrentUser(user, testCase.RequestOpts)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                                       }
+                                               })
+                                       }
+                               }
+                       })
+               }
+       })
+}
diff --git a/traffic_ops/testing/api/v4/user_test.go 
b/traffic_ops/testing/api/v4/user_test.go
deleted file mode 100644
index 35864892b4..0000000000
--- a/traffic_ops/testing/api/v4/user_test.go
+++ /dev/null
@@ -1,573 +0,0 @@
-package v4
-
-/*
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-   http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
-*/
-
-import (
-       "fmt"
-       "net/http"
-       "net/mail"
-       "sort"
-       "strconv"
-       "strings"
-       "testing"
-       "time"
-
-       "github.com/apache/trafficcontrol/lib/go-rfc"
-       "github.com/apache/trafficcontrol/lib/go-tc"
-       "github.com/apache/trafficcontrol/lib/go-util"
-       client "github.com/apache/trafficcontrol/traffic_ops/v4-client"
-)
-
-func TestUsers(t *testing.T) {
-       WithObjs(t, []TCObj{Tenants, Parameters, Users}, func() {
-               GetTestUsersIMS(t)
-               currentTime := time.Now().UTC().Add(-5 * time.Second)
-               time := currentTime.Format(time.RFC1123)
-               var header http.Header
-               header = make(map[string][]string)
-               header.Set(rfc.IfModifiedSince, time)
-               SortTestUsers(t)
-               UpdateTestUsers(t)
-               GetTestUsersIMSAfterChange(t, header)
-               OpsUpdateAdminTest(t)
-               UserSelfUpdateTest(t)
-               UserUpdateOwnRoleTest(t)
-               GetTestUsers(t)
-               GetTestUserCurrent(t)
-               UserTenancyTest(t)
-               if includeSystemTests {
-                       // UserRegistrationTest deletes test users before 
registering new users, so it must come after the other user tests.
-                       UserRegistrationTest(t)
-               }
-       })
-}
-
-func GetTestUsersIMSAfterChange(t *testing.T, header http.Header) {
-       opts := client.NewRequestOptions()
-       opts.Header = header
-       resp, reqInf, err := TOSession.GetUsers(opts)
-       if err != nil {
-               t.Fatalf("Expected no error, but got: %v - alerts: %+v", err, 
resp.Alerts)
-       }
-       if reqInf.StatusCode != http.StatusOK {
-               t.Fatalf("Expected 200 status code, got %v", reqInf.StatusCode)
-       }
-
-       currentTime := time.Now().UTC()
-       currentTime = currentTime.Add(1 * time.Second)
-       timeStr := currentTime.Format(time.RFC1123)
-       opts.Header.Set(rfc.IfModifiedSince, timeStr)
-
-       resp, reqInf, err = TOSession.GetUsers(opts)
-       if err != nil {
-               t.Fatalf("Expected no error, but got: %v - alerts: %+v", err, 
resp.Alerts)
-       }
-       if reqInf.StatusCode != http.StatusNotModified {
-               t.Fatalf("Expected 304 status code, got %v", reqInf.StatusCode)
-       }
-}
-
-const SessionUserName = "admin" // TODO make dynamic?
-
-func GetTestUsersIMS(t *testing.T) {
-       futureTime := time.Now().AddDate(0, 0, 1)
-       time := futureTime.Format(time.RFC1123)
-
-       opts := client.NewRequestOptions()
-       opts.Header.Set(rfc.IfModifiedSince, time)
-
-       resp, reqInf, err := TOSession.GetUsers(opts)
-       if err != nil {
-               t.Fatalf("Expected no error, but got: %v - alerts: %+v", err, 
resp.Alerts)
-       }
-       if reqInf.StatusCode != http.StatusNotModified {
-               t.Fatalf("Expected 304 status code, got %v", reqInf.StatusCode)
-       }
-}
-
-func CreateTestUsers(t *testing.T) {
-       for _, user := range testData.Users {
-               resp, _, err := TOSession.CreateUser(user, 
client.RequestOptions{})
-               if err != nil {
-                       t.Errorf("could not create user: %v - alerts: %+v", 
err, resp.Alerts)
-               }
-       }
-}
-
-func OpsUpdateAdminTest(t *testing.T) {
-       toReqTimeout := time.Second * 
time.Duration(Config.Default.Session.TimeoutInSecs)
-       opsTOClient, _, err := client.LoginWithAgent(TOSession.URL, "opsuser", 
"pa$$word", true, "to-api-v3-client-tests/opsuser", true, toReqTimeout)
-       if err != nil {
-               t.Fatalf("failed to get log in with opsuser: %v", err.Error())
-       }
-
-       opts := client.NewRequestOptions()
-       opts.QueryParameters.Set("username", "admin")
-       resp, _, err := TOSession.GetUsers(opts)
-       if err != nil {
-               t.Errorf("cannot get users filtered by username 'admin': %v - 
alerts: %+v", err, resp.Alerts)
-       }
-       if len(resp.Response) != 1 {
-               t.Fatalf("Expected exactly one user to exist with username 
'admin', found: %d", len(resp.Response))
-       }
-       user := resp.Response[0]
-       if user.ID == nil {
-               t.Fatal("Traffic Ops returned a representation for the 'admin' 
user with null or undefined ID")
-       }
-
-       fullName := "oops"
-       email := "[email protected]"
-       user.FullName = &fullName
-       user.Email = &email
-
-       _, _, err = opsTOClient.UpdateUser(*user.ID, user, 
client.RequestOptions{})
-       if err == nil {
-               t.Error("ops user incorrectly updated an admin")
-       }
-}
-
-func SortTestUsers(t *testing.T) {
-       resp, _, err := TOSession.GetUsers(client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("Expected no error, but got: %v - alerts: %+v", err, 
resp.Alerts)
-       }
-       sortedList := make([]string, 0, len(resp.Response))
-       for _, user := range resp.Response {
-               sortedList = append(sortedList, user.Username)
-       }
-
-       if !sort.StringsAreSorted(sortedList) {
-               t.Errorf("list is not sorted by their names: %v", sortedList)
-       }
-}
-
-func UserRegistrationTest(t *testing.T) {
-       ForceDeleteTestUsers(t)
-       var emails []string
-       opts := client.NewRequestOptions()
-       for _, user := range testData.Users {
-               if user.Tenant == nil || user.Email == nil {
-                       t.Error("Found User in the testing data with null or 
undefined Tenant and/or Email address")
-                       continue
-               }
-               opts.QueryParameters.Set("name", *user.Tenant)
-               resp, _, err := TOSession.GetTenants(opts)
-               if err != nil {
-                       t.Fatalf("could not get Tenants filtered by name '%s': 
%v - alerts: %+v", *user.Tenant, err, resp.Alerts)
-               }
-               if len(resp.Response) != 1 {
-                       t.Fatalf("Expected exactly one Tenant to exist with the 
name '%s', found: %d", *user.Tenant, len(resp.Response))
-               }
-               tenant := resp.Response[0]
-
-               regResp, _, err := TOSession.RegisterNewUser(uint(tenant.ID), 
user.Role, rfc.EmailAddress{Address: mail.Address{Address: *user.Email}}, 
client.RequestOptions{})
-               if err != nil {
-                       t.Fatalf("could not register user: %v - alerts: %+v", 
err, regResp.Alerts)
-               }
-               emails = append(emails, fmt.Sprintf(`'%v'`, *user.Email))
-       }
-
-       db, err := OpenConnection()
-       if err != nil {
-               t.Error("cannot open db")
-       }
-       defer db.Close()
-       q := `DELETE FROM tm_user WHERE email IN (` + strings.Join(emails, ",") 
+ `)`
-       if err := execSQL(db, q); err != nil {
-               t.Errorf("cannot execute SQL to delete registered users: %s; 
SQL is %s", err.Error(), q)
-       }
-}
-
-func UserSelfUpdateTest(t *testing.T) {
-       toReqTimeout := time.Second * 
time.Duration(Config.Default.Session.TimeoutInSecs)
-       opsTOClient, _, err := client.LoginWithAgent(TOSession.URL, "opsuser", 
"pa$$word", true, "to-api-v3-client-tests/opsuser", true, toReqTimeout)
-       if err != nil {
-               t.Fatalf("failed to get log in with opsuser: %v", err.Error())
-       }
-
-       opts := client.NewRequestOptions()
-       opts.QueryParameters.Set("username", "opsuser")
-       resp, _, err := TOSession.GetUsers(opts)
-       if err != nil {
-               t.Fatalf("cannot get users filtered by username 'opsuser': %v - 
alerts: %+v", err, resp.Alerts)
-       }
-       if len(resp.Response) < 1 {
-               t.Fatalf("no users returned when requesting user 'opsuser'")
-       }
-       user := resp.Response[0]
-
-       if user.ID == nil {
-               t.Fatalf("user 'opsuser' has a null or missing ID - cannot 
proceed")
-       }
-
-       user.FullName = util.StrPtr("Oops-man")
-       user.Email = util.StrPtr("[email protected]")
-
-       updateResp, _, err := opsTOClient.UpdateUser(*user.ID, user, 
client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("cannot update user: %v - alerts: %+v", err, 
updateResp.Alerts)
-       }
-
-       // Make sure it got updated
-       opts.QueryParameters.Del("username")
-       opts.QueryParameters.Set("id", strconv.Itoa(*user.ID))
-       resp2, _, err := TOSession.GetUsers(opts)
-       if err != nil {
-               t.Fatalf("cannot get users filtered by ID %d: %v - alerts: 
%+v", *user.ID, err, resp2.Alerts)
-       }
-       if len(resp2.Response) < 1 {
-               t.Fatalf("no results returned when requesting user #%d", 
*user.ID)
-       }
-       updatedUser := resp2.Response[0]
-
-       if updatedUser.FullName == nil {
-               t.Errorf("user was not correctly updated, FullName is null or 
missing")
-       } else if *updatedUser.FullName != "Oops-man" {
-               t.Errorf("results do not match actual: '%s', expected: 
'Oops-man'\n", *updatedUser.FullName)
-       }
-
-       if updatedUser.Email == nil {
-               t.Errorf("user was not correctly updated, Email is null or 
missing")
-       } else if *updatedUser.Email != "[email protected]" {
-               t.Errorf("results do not match actual: '%s', expected: 
'[email protected]'\n", *updatedUser.Email)
-       }
-
-       // Same thing using /user/current
-       user.FullName = util.StrPtr("ops-man")
-       user.Email = util.StrPtr("[email protected]")
-       updateResp, _, err = opsTOClient.UpdateCurrentUser(user, 
client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("error updating current user: %v - alerts: %+v", err, 
updateResp.Alerts)
-       }
-
-       // Make sure it got updated
-       resp2, _, err = TOSession.GetUsers(opts)
-       if err != nil {
-               t.Fatalf("error getting user #%d: %v - alerts: %+v", *user.ID, 
err, resp2.Alerts)
-       }
-
-       if len(resp2.Response) < 1 {
-               t.Fatalf("no user returned when requesting user #%d", *user.ID)
-       }
-
-       if resp2.Response[0].FullName == nil {
-               t.Errorf("FullName missing or null after update")
-       } else if *resp2.Response[0].FullName != "ops-man" {
-               t.Errorf("Expected FullName to be 'ops-man', but it was '%s'", 
*resp2.Response[0].FullName)
-       }
-
-       if resp2.Response[0].Email == nil {
-               t.Errorf("Email missing or null after update")
-       } else if *resp2.Response[0].Email != "[email protected]" {
-               t.Errorf("Expected Email to be restored to 
'[email protected]', but it was '%s'", *resp2.Response[0].Email)
-       }
-
-       // now test using an invalid email address
-       currentEmail := *user.Email
-       user.Email = new(string)
-       updateResp, _, err = TOSession.UpdateCurrentUser(user, 
client.RequestOptions{})
-       if err == nil {
-               t.Fatal("error was expected updating user with email: '' - got 
none")
-       }
-
-       // Ensure it wasn't actually updated
-       resp2, _, err = TOSession.GetUsers(opts)
-       if err != nil {
-               t.Fatalf("error getting user #%d: %v - alerts: %+v", *user.ID, 
err, resp2.Alerts)
-       }
-
-       if len(resp2.Response) < 1 {
-               t.Fatalf("no user returned when requesting user #%d", *user.ID)
-       }
-
-       if resp2.Response[0].Email == nil {
-               t.Errorf("Email missing or null after update")
-       } else if *resp2.Response[0].Email != currentEmail {
-               t.Errorf("Expected Email to still be '%s', but it was '%s'", 
currentEmail, *resp2.Response[0].Email)
-       }
-}
-
-func UserUpdateOwnRoleTest(t *testing.T) {
-       opts := client.NewRequestOptions()
-       opts.QueryParameters.Set("username", SessionUserName)
-       resp, _, err := TOSession.GetUsers(opts)
-       if err != nil {
-               t.Errorf("cannot get users filtered by username '%s': %v - 
alerts: %+v", SessionUserName, err, resp.Alerts)
-       }
-       user := resp.Response[0]
-       if user.ID == nil {
-               t.Fatalf("Traffic Ops returned a representation for user '%s' 
with null or undefined ID", SessionUserName)
-       }
-
-       user.Role = user.Role + "_updated"
-       _, _, err = TOSession.UpdateUser(*user.ID, user, 
client.RequestOptions{})
-       if err == nil {
-               t.Error("user incorrectly updated their role")
-       }
-}
-
-func UpdateTestUsers(t *testing.T) {
-       if len(testData.Users) < 1 {
-               t.Fatal("Need at least one User to test updating users")
-       }
-       firstUsername := testData.Users[0].Username
-
-       opts := client.NewRequestOptions()
-       opts.QueryParameters.Set("username", firstUsername)
-       resp, _, err := TOSession.GetUsers(opts)
-       if err != nil {
-               t.Errorf("cannot get users filtered by username '%s': %v - 
alerts: %+v", firstUsername, err, resp.Alerts)
-       }
-       if len(resp.Response) != 1 {
-               t.Fatalf("Expected exactly one user to exist with username 
'%s', found: %d", firstUsername, len(resp.Response))
-       }
-       user := resp.Response[0]
-       if user.City == nil || user.ID == nil {
-               t.Fatal("Traffic Ops returned a representation for a user with 
null or undefined ID and/or City")
-       }
-       newCity := "kidz kable kown"
-       *user.City = newCity
-
-       var updateResp tc.UpdateUserResponseV4
-       updateResp, _, err = TOSession.UpdateUser(*user.ID, user, 
client.RequestOptions{})
-       if err != nil {
-               t.Errorf("cannot update user: %v - alerts: %+v", err, 
updateResp.Alerts)
-       }
-
-       // Make sure it got updated
-       opts.QueryParameters.Del("username")
-       opts.QueryParameters.Set("id", strconv.Itoa(*user.ID))
-       resp2, _, err := TOSession.GetUsers(opts)
-       if err != nil {
-               t.Errorf("cannot get users filtered by id %d: %v - alerts: 
%+v", *user.ID, err, resp2.Alerts)
-       }
-       if len(resp2.Response) != 1 {
-               t.Fatalf("Expected exactly one user to exist with ID %d, found: 
%d", *user.ID, len(resp2.Response))
-       }
-       updatedUser := resp2.Response[0]
-       if updatedUser.City == nil {
-               t.Error("Traffic Ops returned a representation of a user with 
null or undefined City")
-       } else if *updatedUser.City != newCity {
-               t.Errorf("results do not match actual: %s, expected: %s", 
*updatedUser.City, newCity)
-       }
-
-       if user.RegistrationSent == nil {
-               if updatedUser.RegistrationSent != nil {
-                       t.Errorf("Updated user has registration sent time when 
original did not (and no registration was sent): %s", 
*updatedUser.RegistrationSent)
-               }
-       } else if updatedUser.RegistrationSent == nil {
-               t.Errorf("Updated user was supposed to have registration sent 
time '%s', but it had null or undefined", *user.RegistrationSent)
-       } else if *resp.Response[0].RegistrationSent != 
*resp2.Response[0].RegistrationSent {
-               t.Errorf("registration_sent value shouldn't have been updated, 
expectd: %s, got: %s", *resp.Response[0].RegistrationSent, 
*resp2.Response[0].RegistrationSent)
-       }
-
-}
-
-func GetTestUsers(t *testing.T) {
-       resp, _, err := TOSession.GetUsers(client.RequestOptions{})
-       if err != nil {
-               t.Errorf("cannot get users: %v - alerts: %+v", err, resp.Alerts)
-       }
-       if len(resp.Response) < 1 {
-               t.Fatalf("expected a users list, got nothing")
-       }
-}
-
-func GetTestUserCurrent(t *testing.T) {
-       user, _, err := TOSession.GetUserCurrent(client.RequestOptions{})
-       if err != nil {
-               t.Errorf("cannot get current user: %v - alerts: %+v", err, 
user.Alerts)
-       }
-       if user.Response.Username != SessionUserName {
-               t.Errorf("current user expected: '%s' actual: '%s'", 
SessionUserName, user.Response.Username)
-       }
-}
-
-func UserTenancyTest(t *testing.T) {
-       users, _, err := TOSession.GetUsers(client.RequestOptions{})
-       if err != nil {
-               t.Errorf("cannot get users: %v - alerts: %+v", err, 
users.Alerts)
-       }
-       tenant3Found := false
-       tenant4Found := false
-       tenant3Username := "tenant3user"
-       tenant4Username := "tenant4user"
-       tenant3User := tc.UserV4{}
-
-       // assert admin user can view tenant3user and tenant4user
-       for _, user := range users.Response {
-               if user.ID == nil {
-                       t.Error("Traffic Ops returned a representation for a 
user with null or undefined ID")
-                       continue
-               }
-               if user.Username == tenant3Username {
-                       tenant3Found = true
-                       tenant3User = user
-               } else if user.Username == tenant4Username {
-                       tenant4Found = true
-               }
-               if tenant3Found && tenant4Found {
-                       break
-               }
-       }
-       if !tenant3Found || !tenant4Found {
-               t.Error("expected admin to be able to view tenants: tenant3 and 
tenant4")
-       }
-
-       toReqTimeout := time.Second * 
time.Duration(Config.Default.Session.TimeoutInSecs)
-       tenant4TOClient, _, err := client.LoginWithAgent(TOSession.URL, 
"tenant4user", "pa$$word", true, "to-api-v3-client-tests/tenant4user", true, 
toReqTimeout)
-       if err != nil {
-               t.Fatalf("failed to log in with tenant4user: %v", err.Error())
-       }
-
-       usersReadableByTenant4, _, err := 
tenant4TOClient.GetUsers(client.RequestOptions{})
-       if err != nil {
-               t.Errorf("tenant4user cannot get users: %v - alerts: %+v", err, 
usersReadableByTenant4.Alerts)
-       }
-
-       tenant4canReadItself := false
-       for _, user := range usersReadableByTenant4.Response {
-               // assert that tenant4user cannot read tenant3user
-               if user.Username == tenant3Username {
-                       t.Error("expected tenant4user to be unable to read 
tenant3user")
-               }
-               // assert that tenant4user can read itself
-               if user.Username == tenant4Username {
-                       tenant4canReadItself = true
-               }
-       }
-       if !tenant4canReadItself {
-               t.Error("expected tenant4user to be able to read itself")
-       }
-
-       // assert that tenant4user cannot update tenant3user
-       if _, _, err = tenant4TOClient.UpdateUser(*tenant3User.ID, tenant3User, 
client.RequestOptions{}); err == nil {
-               t.Error("expected tenant4user to be unable to update 
tenant4user")
-       }
-
-       // assert that tenant4user cannot create a user outside of its tenant
-       opts := client.NewRequestOptions()
-       opts.QueryParameters.Set("name", "root")
-       resp, _, err := TOSession.GetTenants(opts)
-       if err != nil {
-               t.Errorf("Unexpected error getting the root Tenant: %v - 
alerts: %+v", err, resp.Alerts)
-       }
-       if len(resp.Response) != 1 {
-               t.Fatalf("Expected exactly one Tenant to exist with the name 
'root', found: %d", len(resp.Response))
-       }
-       rootTenant := resp.Response[0]
-
-       if len(testData.Users) < 1 {
-               t.Fatal("Need at least one User to continue testing User 
Tenancy")
-       }
-       newUser := testData.Users[0]
-       newUser.Email = util.StrPtr("[email protected]")
-       newUser.Username = "testusertenancy"
-       newUser.TenantID = rootTenant.ID
-       if _, _, err = tenant4TOClient.CreateUser(newUser, 
client.RequestOptions{}); err == nil {
-               t.Error("expected tenant4user to be unable to create a new user 
in the root tenant")
-       }
-}
-
-// ForceDeleteTestUsers forcibly deletes the users from the db.
-func ForceDeleteTestUsers(t *testing.T) {
-
-       // NOTE: Special circumstances!  This should *NOT* be done without a 
really good reason!
-       //  Connects directly to the DB to remove users rather than going thru 
the client.
-       //  This is required here because the DeleteUser action does not really 
delete users,  but disables them.
-       db, err := OpenConnection()
-       if err != nil {
-               t.Error("cannot open db")
-       }
-       defer db.Close()
-
-       var usernames []string
-       for _, user := range testData.Users {
-               usernames = append(usernames, `'`+user.Username+`'`)
-       }
-
-       // there is a constraint that prevents users from being deleted when 
they have a log
-       q := `DELETE FROM log WHERE NOT tm_user = (SELECT id FROM tm_user WHERE 
username = 'admin')`
-       err = execSQL(db, q)
-       if err != nil {
-               t.Errorf("cannot execute SQL: %s; SQL is %s", err.Error(), q)
-       }
-
-       q = `DELETE FROM tm_user WHERE username IN (` + strings.Join(usernames, 
",") + `)`
-       err = execSQL(db, q)
-       if err != nil {
-               t.Errorf("cannot execute SQL: %s; SQL is %s", err.Error(), q)
-       }
-}
-
-func ForceDeleteTestUsersByUsernames(t *testing.T, usernames []string) {
-
-       // NOTE: Special circumstances!  This should *NOT* be done without a 
really good reason!
-       //  Connects directly to the DB to remove users rather than going thru 
the client.
-       //  This is required here because the DeleteUser action does not really 
delete users,  but disables them.
-       db, err := OpenConnection()
-       if err != nil {
-               t.Error("cannot open db")
-       }
-       defer db.Close()
-
-       for i, u := range usernames {
-               usernames[i] = `'` + u + `'`
-       }
-       // there is a constraint that prevents users from being deleted when 
they have a log
-       q := `DELETE FROM log WHERE NOT tm_user = (SELECT id FROM tm_user WHERE 
username = 'admin')`
-       err = execSQL(db, q)
-       if err != nil {
-               t.Errorf("cannot execute SQL: %s; SQL is %s", err.Error(), q)
-       }
-
-       q = `DELETE FROM tm_user WHERE username IN (` + strings.Join(usernames, 
",") + `)`
-       err = execSQL(db, q)
-       if err != nil {
-               t.Errorf("cannot execute SQL: %s; SQL is %s", err.Error(), q)
-       }
-}
-
-func DeleteTestUsers(t *testing.T) {
-       opts := client.NewRequestOptions()
-       for _, user := range testData.Users {
-               opts.QueryParameters.Set("username", user.Username)
-               resp, _, err := TOSession.GetUsers(opts)
-               if err != nil {
-                       t.Errorf("cannot get users filtered by username '%s': 
%v - alerts: %+v", user.Username, err, resp.Alerts)
-               }
-               if len(resp.Response) > 0 {
-                       respUser := resp.Response[0]
-                       if respUser.ID == nil {
-                               t.Error("Traffic Ops returned a representation 
for a user with null or undefined ID")
-                               continue
-                       }
-
-                       delResp, _, err := TOSession.DeleteUser(*respUser.ID, 
client.RequestOptions{})
-                       if err != nil {
-                               t.Errorf("cannot delete user '%s': %v - alerts: 
%+v", user.Username, err, delResp.Alerts)
-                       }
-
-                       // Make sure it got deleted
-                       resp, _, err := TOSession.GetUsers(opts)
-                       if err != nil {
-                               t.Errorf("error getting users filtered by 
username after supposed deletion: %v - alerts: %+v", err, resp.Alerts)
-                       }
-                       if len(resp.Response) > 0 {
-                               t.Errorf("expected user: %s to be deleted", 
user.Username)
-                       }
-               }
-       }
-}
diff --git a/traffic_ops/testing/api/v4/users_register_test.go 
b/traffic_ops/testing/api/v4/users_register_test.go
new file mode 100644
index 0000000000..023be22ada
--- /dev/null
+++ b/traffic_ops/testing/api/v4/users_register_test.go
@@ -0,0 +1,93 @@
+package v4
+
+/*
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+import (
+       "encoding/json"
+       "net/http"
+       "testing"
+
+       "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert"
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils"
+       "github.com/apache/trafficcontrol/traffic_ops/toclientlib"
+)
+
+func TestUsersRegister(t *testing.T) {
+       if includeSystemTests {
+               WithObjs(t, []TCObj{Tenants, Parameters}, func() {
+
+                       methodTests := utils.V4TestCase{
+                               "POST": {
+                                       "OK when VALID request": {
+                                               ClientSession: TOSession,
+                                               RequestBody: 
map[string]interface{}{
+                                                       "addressLine1":       
"address of ops",
+                                                       "addressLine2":       
"place",
+                                                       "city":               
"somewhere",
+                                                       "company":            
"else",
+                                                       "country":            
"UK",
+                                                       "email":              
"[email protected]",
+                                                       "fullName":           
"Operations User Updated",
+                                                       "localPasswd":        
"pa$$word",
+                                                       "confirmLocalPasswd": 
"pa$$word",
+                                                       "role":               
"operations",
+                                                       "tenant":             
"root",
+                                                       "tenantId":           
GetTenantID(t, "root")(),
+                                                       "username":           
"opsuser",
+                                               },
+                                               Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
validateDeletion("[email protected]")),
+                                       },
+                               },
+                       }
+
+                       for method, testCases := range methodTests {
+                               t.Run(method, func(t *testing.T) {
+                                       for name, testCase := range testCases {
+                                               userRegistration := 
tc.UserRegistrationRequestV4{}
+
+                                               if testCase.RequestBody != nil {
+                                                       dat, err := 
json.Marshal(testCase.RequestBody)
+                                                       assert.NoError(t, err, 
"Error occurred when marshalling request body: %v", err)
+                                                       err = 
json.Unmarshal(dat, &userRegistration)
+                                                       assert.NoError(t, err, 
"Error occurred when unmarshalling request body: %v", err)
+                                               }
+
+                                               switch method {
+                                               case "POST":
+                                                       t.Run(name, func(t 
*testing.T) {
+                                                               alerts, reqInf, 
err := testCase.ClientSession.RegisterNewUser(userRegistration.TenantID, 
userRegistration.Role, userRegistration.Email, testCase.RequestOpts)
+                                                               for _, check := 
range testCase.Expectations {
+                                                                       
check(t, reqInf, nil, alerts, err)
+                                                               }
+                                                       })
+                                               }
+                                       }
+                               })
+                       }
+               })
+       }
+}
+
+func validateDeletion(email string) utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               db, err := OpenConnection()
+               assert.RequireNoError(t, err, "Cannot open db")
+               defer db.Close()
+               q := `DELETE FROM tm_user WHERE email = '` + email + `'`
+               err = execSQL(db, q)
+               assert.NoError(t, err, "Cannot execute SQL to delete registered 
users: %s; SQL is %s", err, q)
+       }
+}
diff --git a/traffic_ops/testing/api/v4/users_test.go 
b/traffic_ops/testing/api/v4/users_test.go
new file mode 100644
index 0000000000..f7023c7163
--- /dev/null
+++ b/traffic_ops/testing/api/v4/users_test.go
@@ -0,0 +1,375 @@
+package v4
+
+/*
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+import (
+       "encoding/json"
+       "net/http"
+       "net/url"
+       "sort"
+       "strings"
+       "testing"
+       "time"
+
+       "github.com/apache/trafficcontrol/lib/go-rfc"
+       "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert"
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils"
+       "github.com/apache/trafficcontrol/traffic_ops/toclientlib"
+       client "github.com/apache/trafficcontrol/traffic_ops/v4-client"
+)
+
+func TestUsers(t *testing.T) {
+       WithObjs(t, []TCObj{Tenants, Parameters, Users}, func() {
+
+               opsUserSession := utils.CreateV4Session(t, 
Config.TrafficOps.URL, "opsuser", "pa$$word", 
Config.Default.Session.TimeoutInSecs)
+               tenant4UserSession := utils.CreateV4Session(t, 
Config.TrafficOps.URL, "tenant4user", "pa$$word", 
Config.Default.Session.TimeoutInSecs)
+
+               currentTime := time.Now().UTC().Add(-15 * time.Second)
+               currentTimeRFC := currentTime.Format(time.RFC1123)
+               tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123)
+
+               methodTests := utils.V4TestCase{
+                       "GET": {
+                               "NOT MODIFIED when NO CHANGES made": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)),
+                               },
+                               "OK when CHANGES made": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: 
{currentTimeRFC}}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                               },
+                               "OK when VALID request": {
+                                       ClientSession: TOSession,
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseLengthGreaterOrEqual(1),
+                                               validateUsersSort()),
+                               },
+                               "ADMIN can view CHILD TENANT": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"tenant": {"tenant4"}}},
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseLengthGreaterOrEqual(1),
+                                               
validateUsersFields(map[string]interface{}{"Tenant": "tenant4"})),
+                               },
+                               "EMPTY RESPONSE when CHILD TENANT reads PARENT 
TENANT": {
+                                       ClientSession: tenant4UserSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"tenant": {"tenant3"}}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseHasLength(0)),
+                               },
+                       },
+                       "POST": {
+                               "FORBIDDEN when CHILD TENANT creates USER with 
PARENT TENANCY": {
+                                       ClientSession: tenant4UserSession,
+                                       RequestBody: map[string]interface{}{
+                                               "email":              
"[email protected]",
+                                               "fullName":           "Outside 
Tenancy",
+                                               "localPasswd":        
"pa$$word",
+                                               "confirmLocalPasswd": 
"pa$$word",
+                                               "role":               
"operations",
+                                               "tenantId":           
GetTenantID(t, "tenant3")(),
+                                               "username":           
"outsideTenantUser",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)),
+                               },
+                       },
+                       "PUT": {
+                               "OK when VALID request": {
+                                       EndpointId:    GetUserID(t, "steering"),
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "addressLine1":       "updated 
line 1",
+                                               "addressLine2":       "updated 
line 2",
+                                               "city":               "updated 
city name",
+                                               "company":            "new 
company",
+                                               "country":            "US",
+                                               "email":              
"[email protected]",
+                                               "fullName":           "Steering 
User Updated",
+                                               "localPasswd":        
"pa$$word",
+                                               "confirmLocalPasswd": 
"pa$$word",
+                                               "newUser":            false,
+                                               "role":               
"steering",
+                                               "tenant":             "root",
+                                               "tenantId":           
GetTenantID(t, "root")(),
+                                               "username":           
"steering",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateUsersUpdateCreateFields(map[string]interface{}{"AddressLine1": "updated 
line 1",
+                                                       "AddressLine2": 
"updated line 2", "City": "updated city name", "Company": "new company",
+                                                       "Country": "US", 
"Email": "[email protected]", "FullName": "Steering User Updated"})),
+                               },
+                               "OK when UPDATING SELF": {
+                                       EndpointId:    GetUserID(t, "opsuser"),
+                                       ClientSession: opsUserSession,
+                                       RequestBody: map[string]interface{}{
+                                               "addressLine1":       "address 
of ops",
+                                               "addressLine2":       "place",
+                                               "city":               
"somewhere",
+                                               "company":            "else",
+                                               "country":            "UK",
+                                               "email":              
"[email protected]",
+                                               "fullName":           
"Operations User Updated",
+                                               "localPasswd":        
"pa$$word",
+                                               "confirmLocalPasswd": 
"pa$$word",
+                                               "role":               
"operations",
+                                               "tenant":             "root",
+                                               "tenantId":           
GetTenantID(t, "root")(),
+                                               "username":           "opsuser",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateUsersUpdateCreateFields(map[string]interface{}{"Email": 
"[email protected]", "FullName": "Operations User Updated"})),
+                               },
+                               "NOT FOUND when UPDATING SELF with ROLE that 
DOESNT EXIST": {
+                                       EndpointId:    GetUserID(t, "opsuser"),
+                                       ClientSession: opsUserSession,
+                                       RequestBody: map[string]interface{}{
+                                               "addressLine1":       "address 
of ops",
+                                               "addressLine2":       "place",
+                                               "city":               
"somewhere",
+                                               "company":            "else",
+                                               "country":            "UK",
+                                               "email":              
"[email protected]",
+                                               "fullName":           
"Operations User Updated",
+                                               "localPasswd":        
"pa$$word",
+                                               "confirmLocalPasswd": 
"pa$$word",
+                                               "role":               
"operations_updated",
+                                               "tenant":             "root",
+                                               "tenantId":           
GetTenantID(t, "root")(),
+                                               "username":           "opsuser",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)),
+                               },
+                               "FORBIDDEN when OPERATIONS USER updates ADMIN 
USER": {
+                                       EndpointId:    GetUserID(t, "admin"),
+                                       ClientSession: opsUserSession,
+                                       RequestBody: map[string]interface{}{
+                                               "email":              
"[email protected]",
+                                               "fullName":           "oops",
+                                               "localPasswd":        
"pa$$word",
+                                               "confirmLocalPasswd": 
"pa$$word",
+                                               "role":               "admin",
+                                               "tenant":             "root",
+                                               "tenantId":           
GetTenantID(t, "root")(),
+                                               "username":           "admin",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)),
+                               },
+                               "FORBIDDEN when CHILD TENANT USER updates 
PARENT TENANT USER": {
+                                       EndpointId:    GetUserID(t, 
"tenant3user"),
+                                       ClientSession: tenant4UserSession,
+                                       RequestBody: map[string]interface{}{
+                                               "email":              
"[email protected]",
+                                               "fullName":           "Parent 
tenant test",
+                                               "localPasswd":        
"pa$$word",
+                                               "confirmLocalPasswd": 
"pa$$word",
+                                               "role":               "admin",
+                                               "tenant":             "tenant2",
+                                               "tenantId":           
GetTenantID(t, "tenant2")(),
+                                               "username":           
"tenant3user",
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)),
+                               },
+                       },
+               }
+
+               for method, testCases := range methodTests {
+                       t.Run(method, func(t *testing.T) {
+                               for name, testCase := range testCases {
+                                       user := tc.UserV4{}
+
+                                       if testCase.RequestBody != nil {
+                                               dat, err := 
json.Marshal(testCase.RequestBody)
+                                               assert.NoError(t, err, "Error 
occurred when marshalling request body: %v", err)
+                                               err = json.Unmarshal(dat, &user)
+                                               assert.NoError(t, err, "Error 
occurred when unmarshalling request body: %v", err)
+                                       }
+
+                                       switch method {
+                                       case "GET":
+                                               t.Run(name, func(t *testing.T) {
+                                                       resp, reqInf, err := 
testCase.ClientSession.GetUsers(testCase.RequestOpts)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                                       }
+                                               })
+                                       case "POST":
+                                               t.Run(name, func(t *testing.T) {
+                                                       resp, reqInf, err := 
testCase.ClientSession.CreateUser(user, testCase.RequestOpts)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                                       }
+                                               })
+                                       case "PUT":
+                                               t.Run(name, func(t *testing.T) {
+                                                       resp, reqInf, err := 
testCase.ClientSession.UpdateUser(testCase.EndpointId(), user, 
testCase.RequestOpts)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                                       }
+                                               })
+                                       }
+                               }
+                       })
+               }
+       })
+}
+
+func validateUsersFields(expectedResp map[string]interface{}) utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected Users response to not 
be nil.")
+               userResp := resp.([]tc.UserV4)
+               for field, expected := range expectedResp {
+                       for _, user := range userResp {
+                               switch field {
+                               case "AddressLine1":
+                                       assert.RequireNotNil(t, 
user.AddressLine1, "Expected AddressLine1 to not be nil.")
+                                       assert.Equal(t, expected, 
*user.AddressLine1, "Expected AddressLine1 to be %v, but got %s", expected, 
*user.AddressLine1)
+                               case "AddressLine2":
+                                       assert.RequireNotNil(t, 
user.AddressLine2, "Expected AddressLine2 to not be nil.")
+                                       assert.Equal(t, expected, 
*user.AddressLine2, "Expected AddressLine2 to be %v, but got %s", expected, 
*user.AddressLine2)
+                               case "City":
+                                       assert.RequireNotNil(t, user.City, 
"Expected City to not be nil.")
+                                       assert.Equal(t, expected, *user.City, 
"Expected City to be %v, but got %s", expected, *user.City)
+                               case "Company":
+                                       assert.RequireNotNil(t, user.Company, 
"Expected Company to not be nil.")
+                                       assert.Equal(t, expected, 
*user.Company, "Expected Company to be %v, but got %s", expected, *user.Company)
+                               case "Country":
+                                       assert.RequireNotNil(t, user.Country, 
"Expected Country to not be nil.")
+                                       assert.Equal(t, expected, 
*user.Country, "Expected Country to be %v, but got %s", expected, *user.Country)
+                               case "Email":
+                                       assert.RequireNotNil(t, user.Email, 
"Expected Email to not be nil.")
+                                       assert.Equal(t, expected, *user.Email, 
"Expected Email to be %v, but got %s", expected, *user.Email)
+                               case "FullName":
+                                       assert.RequireNotNil(t, user.FullName, 
"Expected FullName to not be nil.")
+                                       assert.Equal(t, expected, 
*user.FullName, "Expected FullName to be %v, but got %s", expected, 
*user.FullName)
+                               case "ID":
+                                       assert.RequireNotNil(t, user.ID, 
"Expected ID to not be nil.")
+                                       assert.Equal(t, expected, *user.ID, 
"Expected ID to be %v, but got %d", expected, user.ID)
+                               case "PhoneNumber":
+                                       assert.RequireNotNil(t, 
user.PhoneNumber, "Expected PhoneNumber to not be nil.")
+                                       assert.Equal(t, expected, 
*user.PhoneNumber, "Expected PhoneNumber to be %v, but got %s", expected, 
*user.PhoneNumber)
+                               case "PostalCode":
+                                       assert.RequireNotNil(t, 
user.PostalCode, "Expected PostalCode to not be nil.")
+                                       assert.Equal(t, expected, 
*user.PostalCode, "Expected PostalCode to be %v, but got %s", expected, 
*user.PostalCode)
+                               case "RegistrationSent":
+                                       assert.RequireNotNil(t, 
user.RegistrationSent, "Expected RegistrationSent to not be nil.")
+                                       assert.Equal(t, expected, 
*user.RegistrationSent, "Expected RegistrationSent to be %v, but got %v", 
expected, *user.RegistrationSent)
+                               case "Role":
+                                       assert.Equal(t, expected, user.Role, 
"Expected Role to be %v, but got %s", expected, user.Role)
+                               case "StateOrProvince":
+                                       assert.RequireNotNil(t, 
user.StateOrProvince, "Expected StateOrProvince to not be nil.")
+                                       assert.Equal(t, expected, 
*user.StateOrProvince, "Expected StateOrProvince to be %v, but got %s", 
expected, *user.StateOrProvince)
+                               case "Tenant":
+                                       assert.RequireNotNil(t, user.Tenant, 
"Expected Tenant to not be nil.")
+                                       assert.Equal(t, expected, *user.Tenant, 
"Expected Tenant to be %v, but got %s", expected, *user.Tenant)
+                               case "TenantID":
+                                       assert.Equal(t, expected, 
user.TenantID, "Expected TenantID to be %v, but got %d", expected, 
user.TenantID)
+                               case "Username":
+                                       assert.Equal(t, expected, 
user.Username, "Expected Username to be %v, but got %s", expected, 
user.Username)
+                               default:
+                                       t.Errorf("Expected field: %v, does not 
exist in response", field)
+                               }
+                       }
+               }
+       }
+}
+
+func validateUsersUpdateCreateFields(expectedResp map[string]interface{}) 
utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected Users response to not 
be nil.")
+               assert.RequireNotEqual(t, resp.(tc.UserV4), tc.UserV4{}, 
"Expected a non empty response.")
+               userResp := resp.(tc.UserV4)
+               users := []tc.UserV4{userResp}
+               validateUsersFields(expectedResp)(t, toclientlib.ReqInf{}, 
users, tc.Alerts{}, nil)
+       }
+}
+
+func validateUsersSort() utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, 
alerts tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected Users response to not 
be nil.")
+               var usernames []string
+               usersResp := resp.([]tc.UserV4)
+               for _, user := range usersResp {
+                       usernames = append(usernames, user.Username)
+               }
+               assert.Equal(t, true, sort.StringsAreSorted(usernames), "List 
is not sorted by their usernames: %v", usernames)
+       }
+}
+
+func GetUserID(t *testing.T, username string) func() int {
+       return func() int {
+               opts := client.NewRequestOptions()
+               opts.QueryParameters.Set("username", username)
+               users, _, err := TOSession.GetUsers(opts)
+               assert.RequireNoError(t, err, "Get Users Request failed with 
error:", err)
+               assert.RequireEqual(t, 1, len(users.Response), "Expected 
response object length 1, but got %d", len(users.Response))
+               assert.RequireNotNil(t, users.Response[0].ID, "Expected ID to 
not be nil.")
+               return *users.Response[0].ID
+       }
+}
+
+func CreateTestUsers(t *testing.T) {
+       for _, user := range testData.Users {
+               resp, _, err := TOSession.CreateUser(user, 
client.RequestOptions{})
+               assert.RequireNoError(t, err, "Could not create user: %v - 
alerts: %+v", err, resp.Alerts)
+       }
+}
+
+// ForceDeleteTestUsers forcibly deletes the users from the db.
+// NOTE: Special circumstances!  This should *NOT* be done without a really 
good reason!
+// Connects directly to the DB to remove users rather than going through the 
client.
+// This is required here because the DeleteUser action does not really delete 
users,  but disables them.
+func ForceDeleteTestUsers(t *testing.T) {
+
+       db, err := OpenConnection()
+       assert.RequireNoError(t, err, "Cannot open db")
+       defer db.Close()
+
+       var usernames []string
+       for _, user := range testData.Users {
+               usernames = append(usernames, `'`+user.Username+`'`)
+       }
+
+       // there is a constraint that prevents users from being deleted when 
they have a log
+       q := `DELETE FROM log WHERE NOT tm_user = (SELECT id FROM tm_user WHERE 
username = 'admin')`
+       err = execSQL(db, q)
+       assert.RequireNoError(t, err, "Cannot execute SQL: %v; SQL is %s", err, 
q)
+
+       q = `DELETE FROM tm_user WHERE username IN (` + strings.Join(usernames, 
",") + `)`
+       err = execSQL(db, q)
+       assert.NoError(t, err, "Cannot execute SQL: %v; SQL is %s", err, q)
+}
+
+// ForceDeleteTestUsersByUsernames forcibly deletes the users passed in from a 
slice of usernames from the db.
+// NOTE: Special circumstances!  This should *NOT* be done without a really 
good reason!
+// Connects directly to the DB to remove users rather than going through the 
client.
+// This is required here because the DeleteUser action does not really delete 
users, but disables them.
+func ForceDeleteTestUsersByUsernames(t *testing.T, usernames []string) {
+
+       db, err := OpenConnection()
+       assert.RequireNoError(t, err, "Cannot open db")
+       defer db.Close()
+
+       for i, u := range usernames {
+               usernames[i] = `'` + u + `'`
+       }
+       // there is a constraint that prevents users from being deleted when 
they have a log
+       q := `DELETE FROM log WHERE NOT tm_user = (SELECT id FROM tm_user WHERE 
username = 'admin')`
+       err = execSQL(db, q)
+       assert.RequireNoError(t, err, "Cannot execute SQL: %s; SQL is %s", err, 
q)
+
+       q = `DELETE FROM tm_user WHERE username IN (` + strings.Join(usernames, 
",") + `)`
+       err = execSQL(db, q)
+       assert.NoError(t, err, "Cannot execute SQL: %s; SQL is %s", err, q)
+}

Reply via email to