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

srijeet0406 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 a32a37fc01 Refactor Snapshot Tests (#7114)
a32a37fc01 is described below

commit a32a37fc012efdf9075f7e81d8a9571470325e03
Author: Eric Holguin <[email protected]>
AuthorDate: Wed Nov 2 12:09:22 2022 -0600

    Refactor Snapshot Tests (#7114)
    
    * initial refactor
    
    * Initial refactor
    
    * snapshot tests refactor
    
    * crconfig v3 tests refactor
    
    * crconfig v5 tests refactor
    
    * clean up
    
    * remove unsued type
    
    * Use edge not being used by server id ds test
---
 traffic_ops/testing/api/utils/utils.go             |   3 +
 .../api/v3/cdns_name_configs_monitoring_test.go    | 103 ++++++
 .../testing/api/v3/cdns_name_snapshot_new_test.go  | 122 +++++++
 .../testing/api/v3/cdns_name_snapshot_test.go      |  60 ++++
 traffic_ops/testing/api/v3/crconfig_test.go        | 279 ----------------
 traffic_ops/testing/api/v3/snapshot_test.go        | 141 ++++++++
 traffic_ops/testing/api/v3/tc-fixtures.json        |  10 +
 .../api/v4/cdns_name_configs_monitoring_test.go    | 104 ++++++
 .../testing/api/v4/cdns_name_snapshot_new_test.go  | 121 +++++++
 .../testing/api/v4/cdns_name_snapshot_test.go      |  60 ++++
 traffic_ops/testing/api/v4/crconfig_test.go        | 356 ---------------------
 traffic_ops/testing/api/v4/snapshot_test.go        | 126 ++++++++
 traffic_ops/testing/api/v4/tc-fixtures.json        |  11 +-
 .../api/v5/cdns_name_configs_monitoring_test.go    | 104 ++++++
 .../testing/api/v5/cdns_name_snapshot_new_test.go  | 121 +++++++
 .../testing/api/v5/cdns_name_snapshot_test.go      |  60 ++++
 traffic_ops/testing/api/v5/crconfig_test.go        | 356 ---------------------
 traffic_ops/testing/api/v5/snapshot_test.go        | 126 ++++++++
 traffic_ops/testing/api/v5/tc-fixtures.json        |  11 +-
 19 files changed, 1281 insertions(+), 993 deletions(-)

diff --git a/traffic_ops/testing/api/utils/utils.go 
b/traffic_ops/testing/api/utils/utils.go
index 6b72505675..6315d5af66 100644
--- a/traffic_ops/testing/api/utils/utils.go
+++ b/traffic_ops/testing/api/utils/utils.go
@@ -113,6 +113,7 @@ type V3TestData struct {
        RequestParams  url.Values
        RequestHeaders http.Header
        RequestBody    map[string]interface{}
+       PreReqFuncs    []func()
        Expectations   []CkReqFunc
 }
 
@@ -132,6 +133,7 @@ type V4TestData struct {
        ClientSession *v4client.Session
        RequestOpts   v4client.RequestOptions
        RequestBody   map[string]interface{}
+       PreReqFuncs   []func()
        Expectations  []CkReqFunc
 }
 
@@ -141,6 +143,7 @@ type V5TestData struct {
        ClientSession *v5client.Session
        RequestOpts   v5client.RequestOptions
        RequestBody   map[string]interface{}
+       PreReqFuncs   []func()
        Expectations  []CkReqFunc
 }
 
diff --git a/traffic_ops/testing/api/v3/cdns_name_configs_monitoring_test.go 
b/traffic_ops/testing/api/v3/cdns_name_configs_monitoring_test.go
new file mode 100644
index 0000000000..dd3d2686d3
--- /dev/null
+++ b/traffic_ops/testing/api/v3/cdns_name_configs_monitoring_test.go
@@ -0,0 +1,103 @@
+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 (
+       "net/http"
+       "net/url"
+       "strings"
+       "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 TestCDNNameConfigsMonitoring(t *testing.T) {
+       WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
ProfileParameters, Statuses, Divisions, Regions, PhysLocations, CacheGroups, 
Servers, Topologies, ServiceCategories, DeliveryServices}, func() {
+
+               methodTests := utils.V3TestCase{
+                       "GET": {
+                               "OK when VALID request": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"cdn": 
{"cdn1"}},
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateHealthThresholdParameters("EDGE1", map[string]string{"loadavg": "25.0", 
"availableBandwidthInKbps": ">1750000", "queryTime": "1000"})),
+                               },
+                       },
+               }
+
+               for method, testCases := range methodTests {
+                       t.Run(method, func(t *testing.T) {
+                               for name, testCase := range testCases {
+                                       switch method {
+                                       case "GET":
+                                               t.Run(name, func(t *testing.T) {
+                                                       var cdn string
+                                                       if val, ok := 
testCase.RequestParams["cdn"]; ok {
+                                                               cdn = val[0]
+                                                       }
+                                                       resp, reqInf, err := 
testCase.ClientSession.GetTrafficMonitorConfig(cdn)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp, tc.Alerts{}, err)
+                                                       }
+                                               })
+                                       }
+                               }
+                       })
+               }
+       })
+}
+
+func validateHealthThresholdParameters(profileName string, 
healthThresholdParams map[string]string) utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected Traffic Monitor Config 
response to not be nil.")
+               tmConfig := resp.(*tc.TrafficMonitorConfig)
+               parameterMap := map[string]tc.HealthThreshold{}
+               parameterFound := map[string]bool{}
+               for parameter, parameterValue := range healthThresholdParams {
+                       threshold, err := tc.StrToThreshold(parameterValue)
+                       parameterMap[parameter] = threshold
+                       assert.RequireNoError(t, err, "Error: converting string 
'%s' to HealthThreshold: %v", parameterValue, err)
+                       parameterFound[parameter] = false
+               }
+
+               profileFound := false
+               var profile tc.TMProfile
+               for _, profile = range tmConfig.Profiles {
+                       if profile.Name == profileName {
+                               profileFound = true
+                               break
+                       }
+               }
+               assert.RequireEqual(t, true, profileFound, "Traffic Monitor 
Config contained no Profile named '%s", profileName)
+
+               for parameterName, value := range profile.Parameters.Thresholds 
{
+                       _, ok := parameterFound[parameterName]
+                       assert.Equal(t, true, ok, "Unexpected Threshold 
Parameter name '%s' found in Profile '%s' in Traffic Monitor Config", 
parameterName, profileName)
+                       parameterFound[parameterName] = true
+                       assert.Equal(t, parameterMap[parameterName].String(), 
value.String(), "Expected '%s' but received '%s' for Threshold Parameter '%s' 
in Profile '%s' in Traffic Monitor Config", 
parameterMap[parameterName].String(), value.String(), parameterName, 
profileName)
+               }
+               missingParameters := []string{}
+               for parameterName, found := range parameterFound {
+                       if !found {
+                               missingParameters = append(missingParameters, 
parameterName)
+                       }
+               }
+               assert.Equal(t, 0, len(missingParameters), "Threshold 
parameters defined for Profile '%s' but missing for Profile '%s' in Traffic 
Monitor Config: %s", profileName, profileName, strings.Join(missingParameters, 
", "))
+       }
+}
diff --git a/traffic_ops/testing/api/v3/cdns_name_snapshot_new_test.go 
b/traffic_ops/testing/api/v3/cdns_name_snapshot_new_test.go
new file mode 100644
index 0000000000..eff9af5e75
--- /dev/null
+++ b/traffic_ops/testing/api/v3/cdns_name_snapshot_new_test.go
@@ -0,0 +1,122 @@
+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"
+       "net/url"
+       "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"
+)
+
+var baselineCRConfig tc.CRConfig
+
+func TestCDNNameSnapshotNew(t *testing.T) {
+       WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, 
ServiceCategories, DeliveryServices}, func() {
+
+               methodTests := utils.V3TestCase{
+                       "GET": {
+                               "VERIFY SNAPSHOT UPDATE CAPTURED CORRECTLY": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"cdn": 
{"cdn1"}},
+                                       PreReqFuncs:   
[]func(){getBaselineCRConfig(t, "cdn1"), deleteParameter(t, "tm.url")},
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateCRConfigNewFields("cdn1", map[string]interface{}{"TMHost": ""}), 
validateDeliveryServicesUnchanged()),
+                               },
+                       },
+               }
+
+               for method, testCases := range methodTests {
+                       t.Run(method, func(t *testing.T) {
+                               for name, testCase := range testCases {
+                                       switch method {
+                                       case "GET":
+                                               t.Run(name, func(t *testing.T) {
+                                                       var cdn string
+                                                       if val, ok := 
testCase.RequestParams["cdn"]; ok {
+                                                               cdn = val[0]
+                                                       }
+                                                       for _, prerequisite := 
range testCase.PreReqFuncs {
+                                                               prerequisite()
+                                                       }
+                                                       resp, reqInf, err := 
testCase.ClientSession.GetCRConfigNew(cdn)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp, tc.Alerts{}, err)
+                                                       }
+                                               })
+                                       }
+                               }
+                       })
+               }
+       })
+}
+
+func validateCRConfigNewFields(cdn string, expectedResp 
map[string]interface{}) utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected CRConfigNew response to 
not be nil.")
+               var newCRConfig tc.CRConfig
+               err := json.Unmarshal(resp.([]byte), &newCRConfig)
+               assert.NoError(t, err, "Error occurred when unmarshalling 
request body: %v", err)
+
+               for field, expected := range expectedResp {
+                       switch field {
+                       case "TMPath":
+                               assert.RequireNotNil(t, 
newCRConfig.Stats.TMPath, "Expected Stats TM Path to not be nil.")
+                       case "TMHost":
+                               assert.RequireNotNil(t, 
newCRConfig.Stats.TMHost, "Expected Stats TM Host to not be nil.")
+                               assert.Equal(t, expected, 
*newCRConfig.Stats.TMHost, "Expected Stats TM Host to be %v, but got %s", 
expected, *newCRConfig.Stats.TMHost)
+                       default:
+                               t.Errorf("Expected field: %v, does not exist in 
response", field)
+                       }
+               }
+       }
+}
+
+func validateDeliveryServicesUnchanged() utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected new snapshot response 
to not be nil.")
+               var newCRConfig tc.CRConfig
+               err := json.Unmarshal(resp.([]byte), &newCRConfig)
+               assert.NoError(t, err, "Error occurred when unmarshalling 
request body: %v", err)
+               assert.Exactly(t, newCRConfig.DeliveryServices, 
baselineCRConfig.DeliveryServices, "Expected Delivery Services to be 
unchanged.")
+       }
+}
+
+func getBaselineCRConfig(t *testing.T, cdn string) func() {
+       return func() {
+               _, err := TOSession.SnapshotCRConfigWithHdr(cdn, nil)
+               assert.RequireNoError(t, err, "Unexpected error taking Snapshot 
of CDN '%s': %v", cdn, err)
+               getCRConfig, _, err := TOSession.GetCRConfig(cdn)
+               assert.RequireNoError(t, err, "Unexpected error retrieving 
Snapshot of CDN '%s': %v", cdn, err)
+               err = json.Unmarshal(getCRConfig, &baselineCRConfig)
+               assert.NoError(t, err, "Error occurred when unmarshalling 
request body: %v", err)
+       }
+}
+
+func deleteParameter(t *testing.T, paramName string) func() {
+       return func() {
+               paramResp, _, err := 
TOSession.GetParameterByNameWithHdr(paramName, nil)
+               assert.RequireNoError(t, err, "Cannot get Parameter by name 
'%s': %v", paramName, err)
+               assert.RequireGreaterOrEqual(t, len(paramResp), 1, "Expected at 
least one parameter to be returned.")
+               delResp, _, err := 
TOSession.DeleteParameterByID(paramResp[0].ID)
+               assert.RequireNoError(t, err, "Cannot DELETE Parameter by name: 
%v - %v", err, delResp)
+       }
+}
diff --git a/traffic_ops/testing/api/v3/cdns_name_snapshot_test.go 
b/traffic_ops/testing/api/v3/cdns_name_snapshot_test.go
new file mode 100644
index 0000000000..eb1fde46a6
--- /dev/null
+++ b/traffic_ops/testing/api/v3/cdns_name_snapshot_test.go
@@ -0,0 +1,60 @@
+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 (
+       "net/http"
+       "net/url"
+       "testing"
+
+       "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils"
+)
+
+func TestCDNNameSnapshot(t *testing.T) {
+       WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, 
ServiceCategories, DeliveryServices}, func() {
+
+               methodTests := utils.V3TestCase{
+                       "GET": {
+                               "OK when VALID request": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"cdn": 
{"cdn1"}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                               },
+                       },
+               }
+
+               for method, testCases := range methodTests {
+                       t.Run(method, func(t *testing.T) {
+                               for name, testCase := range testCases {
+                                       switch method {
+                                       case "GET":
+                                               t.Run(name, func(t *testing.T) {
+                                                       var cdn string
+                                                       if val, ok := 
testCase.RequestParams["cdn"]; ok {
+                                                               cdn = val[0]
+                                                       }
+                                                       resp, reqInf, err := 
testCase.ClientSession.GetCRConfig(cdn)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp, tc.Alerts{}, err)
+                                                       }
+                                               })
+                                       }
+                               }
+                       })
+               }
+       })
+}
diff --git a/traffic_ops/testing/api/v3/crconfig_test.go 
b/traffic_ops/testing/api/v3/crconfig_test.go
deleted file mode 100644
index c293d95d0b..0000000000
--- a/traffic_ops/testing/api/v3/crconfig_test.go
+++ /dev/null
@@ -1,279 +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 (
-       "encoding/json"
-       "strings"
-       "testing"
-
-       "github.com/apache/trafficcontrol/lib/go-tc"
-)
-
-func TestCRConfig(t *testing.T) {
-       WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
ProfileParameters, Statuses, Divisions, Regions, PhysLocations, CacheGroups, 
Servers, Topologies, DeliveryServices}, func() {
-               UpdateTestCRConfigSnapshot(t)
-               MonitoringConfig(t)
-               SnapshotTestCDNbyName(t)
-               SnapshotTestCDNbyInvalidName(t)
-               SnapshotTestCDNbyID(t)
-               SnapshotTestCDNbyInvalidID(t)
-       })
-}
-
-func UpdateTestCRConfigSnapshot(t *testing.T) {
-       if len(testData.CDNs) < 1 {
-               t.Error("no cdn test data")
-       }
-       cdn := testData.CDNs[0].Name
-
-       tmURLParamName := "tm.url"
-       tmURLExpected := "crconfig.tm.url.test.invalid"
-       _, _, err := TOSession.CreateParameter(tc.Parameter{
-               ConfigFile: "global",
-               Name:       tmURLParamName,
-               Value:      "https://crconfig.tm.url.test.invalid";,
-       })
-       if err != nil {
-               t.Fatalf("GetCRConfig CreateParameter error expected: nil, 
actual: " + err.Error())
-       }
-
-       // create an ANY_MAP DS assignment to verify that it doesn't show up in 
the CRConfig
-       resp, _, err := TOSession.GetServers(nil)
-       if err != nil {
-               t.Fatalf("GetServers err expected nil, actual %+v", err)
-       }
-       servers := resp
-       serverID := 0
-       for _, server := range servers {
-               if server.Type == "EDGE" && server.CDNName == "cdn1" {
-                       serverID = server.ID
-                       break
-               }
-       }
-       if serverID == 0 {
-               t.Errorf("GetServers expected EDGE server in cdn1, actual: 
%+v", servers)
-       }
-       res, _, err := TOSession.GetDeliveryServiceByXMLIDNullable("anymap-ds")
-       if err != nil {
-               t.Errorf("GetDeliveryServiceByXMLIDNullable err expected nil, 
actual %+v", err)
-       }
-       if len(res) != 1 {
-               t.Error("GetDeliveryServiceByXMLIDNullable expected 1 DS, 
actual 0")
-       }
-       if res[0].ID == nil {
-               t.Error("GetDeliveryServiceByXMLIDNullable got unknown delivery 
service id")
-       }
-       anymapDSID := *res[0].ID
-       _, _, err = TOSession.CreateDeliveryServiceServers(anymapDSID, 
[]int{serverID}, true)
-       if err != nil {
-               t.Errorf("POST delivery service servers: %v", err)
-       }
-
-       _, err = TOSession.SnapshotCRConfig(cdn)
-       if err != nil {
-               t.Errorf("SnapshotCRConfig err expected nil, actual %+v", err)
-       }
-       crcBts, _, err := TOSession.GetCRConfig(cdn)
-       if err != nil {
-               t.Errorf("GetCRConfig err expected nil, actual %+v", err)
-       }
-       crc := tc.CRConfig{}
-       if err := json.Unmarshal(crcBts, &crc); err != nil {
-               t.Errorf("GetCRConfig bytes expected: valid tc.CRConfig, actual 
JSON unmarshal err: %+v", err)
-       }
-
-       if len(crc.DeliveryServices) == 0 {
-               t.Error("GetCRConfig len(crc.DeliveryServices) expected: >0, 
actual: 0")
-       }
-
-       // verify no ANY_MAP delivery services are in the CRConfig
-       for ds := range crc.DeliveryServices {
-               if ds == "anymap-ds" {
-                       t.Error("found ANY_MAP delivery service in CRConfig 
deliveryServices")
-               }
-       }
-       for server := range crc.ContentServers {
-               for ds := range crc.ContentServers[server].DeliveryServices {
-                       if ds == "anymap-ds" {
-                               t.Error("found ANY_MAP delivery service in 
contentServers deliveryServices mapping")
-                       }
-               }
-       }
-
-       if crc.Stats.TMPath == nil {
-               t.Error("GetCRConfig crc.Stats.Path expected: some non-null 
string (but we don't check contents because it's deprecated), actual: null")
-       }
-
-       if crc.Stats.TMHost == nil {
-               t.Errorf("GetCRConfig crc.Stats.Path expected: 
'"+tmURLExpected+"', actual: %+v", crc.Stats.TMHost)
-       } else if *crc.Stats.TMHost != tmURLExpected {
-               t.Errorf("GetCRConfig crc.Stats.Path expected: 
'"+tmURLExpected+"', actual: %+v", *crc.Stats.TMHost)
-       }
-
-       paramResp, _, err := TOSession.GetParameterByName(tmURLParamName)
-       if err != nil {
-               t.Fatalf("cannot GET Parameter by name: %v - %v", 
tmURLParamName, err)
-       }
-       if len(paramResp) == 0 {
-               t.Fatal("CRConfig create tm.url parameter was successful, but 
GET returned no parameters")
-       }
-       tmURLParam := paramResp[0]
-
-       delResp, _, err := TOSession.DeleteParameterByID(tmURLParam.ID)
-       if err != nil {
-               t.Fatalf("cannot DELETE Parameter by name: %v - %v", err, 
delResp)
-       }
-
-       crcBtsNew, _, err := TOSession.GetCRConfigNew(cdn)
-       if err != nil {
-               t.Errorf("GetCRConfig err expected nil, actual %+v", err)
-       }
-       crcNew := tc.CRConfig{}
-       if err := json.Unmarshal(crcBtsNew, &crcNew); err != nil {
-               t.Errorf("GetCRConfig bytes expected: valid tc.CRConfig, actual 
JSON unmarshal err: %+v", err)
-       }
-
-       if len(crcNew.DeliveryServices) != len(crc.DeliveryServices) {
-               t.Errorf("/new endpoint returned a different snapshot. 
DeliveryServices length expected %v, was %v", len(crc.DeliveryServices), 
len(crcNew.DeliveryServices))
-       }
-
-       if *crcNew.Stats.TMHost != "" {
-               t.Errorf("update to snapshot not captured in /new endpoint")
-       }
-}
-
-func MonitoringConfig(t *testing.T) {
-       if len(testData.CDNs) < 1 {
-               t.Fatalf("no cdn test data")
-       }
-       const cdnName = "cdn1"
-       const profileName = "EDGE1"
-       cdns, _, err := TOSession.GetCDNByNameWithHdr(cdnName, nil)
-       if err != nil {
-               t.Fatalf("getting CDNs with name '%s': %s", cdnName, 
err.Error())
-       }
-       if len(cdns) < 1 {
-               t.Fatalf("expected to find a CDN named %s", cdnName)
-       }
-       if len(cdns) > 1 {
-               t.Fatalf("expected exactly 1 CDN named %s but found %d CDNs", 
cdnName, len(cdns))
-       }
-       profiles, _, err := TOSession.GetProfileByNameWithHdr(profileName, nil)
-       if err != nil {
-               t.Fatalf("getting Profiles with name '%s': %s", profileName, 
err.Error())
-       }
-       if len(profiles) != 1 {
-               t.Fatalf("expected exactly 1 Profiles named %s but found %d 
Profiles", profileName, len(profiles))
-       }
-       parameters, _, err := 
TOSession.GetParametersByProfileNameWithHdr(profileName, nil)
-       if err != nil {
-               t.Fatalf("getting Parameters by Profile name '%s': %s", 
profileName, err.Error())
-       }
-       parameterMap := map[string]tc.HealthThreshold{}
-       parameterFound := map[string]bool{}
-       const thresholdPrefixLength = len(tc.ThresholdPrefix)
-       for _, parameter := range parameters {
-               if !strings.HasPrefix(parameter.Name, tc.ThresholdPrefix) {
-                       continue
-               }
-               parameterName := parameter.Name[thresholdPrefixLength:]
-               parameterMap[parameterName], err = 
tc.StrToThreshold(parameter.Value)
-               if err != nil {
-                       t.Fatalf("converting string '%s' to HealthThreshold: 
%s", parameter.Value, err.Error())
-               }
-               parameterFound[parameterName] = false
-       }
-       const expectedThresholdParameters = 3
-       if len(parameterMap) != expectedThresholdParameters {
-               t.Fatalf("expected Profile '%s' to contain %d Parameters with 
names starting with '%s' but %d such Parameters were found", profileName, 
expectedThresholdParameters, tc.ThresholdPrefix, len(parameterMap))
-       }
-       tmConfig, _, err := TOSession.GetTrafficMonitorConfig(cdnName)
-       if err != nil {
-               t.Fatalf("getting Traffic Monitor Config: %s", err.Error())
-       }
-       profileFound := false
-       var profile tc.TMProfile
-       for _, profile = range tmConfig.Profiles {
-               if profile.Name == profileName {
-                       profileFound = true
-                       break
-               }
-       }
-       if !profileFound {
-               t.Fatalf("Traffic Monitor Config contained no Profile named 
'%s", profileName)
-       }
-       for parameterName, value := range profile.Parameters.Thresholds {
-               if _, ok := parameterFound[parameterName]; !ok {
-                       t.Fatalf("unexpected Threshold Parameter name '%s' 
found in Profile '%s' in Traffic Monitor Config", parameterName, profileName)
-               }
-               parameterFound[parameterName] = true
-               if parameterMap[parameterName].String() != value.String() {
-                       t.Fatalf("expected '%s' but received '%s' for Threshold 
Parameter '%s' in Profile '%s' in Traffic Monitor Config", 
parameterMap[parameterName].String(), value.String(), parameterName, 
profileName)
-               }
-       }
-       missingParameters := []string{}
-       for parameterName, found := range parameterFound {
-               if !found {
-                       missingParameters = append(missingParameters, 
parameterName)
-               }
-       }
-       if len(missingParameters) != 0 {
-               t.Fatalf("Threshold parameters defined for Profile '%s' but 
missing for Profile '%s' in Traffic Monitor Config: %s", profileName, 
profileName, strings.Join(missingParameters, ", "))
-       }
-}
-
-func SnapshotTestCDNbyName(t *testing.T) {
-
-       firstCDN := testData.CDNs[0]
-       _, err := TOSession.SnapshotCRConfig(firstCDN.Name)
-       if err != nil {
-               t.Errorf("failed to snapshot CDN by name: %v", err)
-       }
-}
-
-func SnapshotTestCDNbyInvalidName(t *testing.T) {
-
-       invalidCDNName := "cdn-invalid"
-       _, err := TOSession.SnapshotCRConfig(invalidCDNName)
-       if err == nil {
-               t.Errorf("snapshot occurred on invalid cdn name: %v - %v", 
invalidCDNName, err)
-       }
-}
-
-func SnapshotTestCDNbyID(t *testing.T) {
-
-       firstCDN := testData.CDNs[0]
-       // Retrieve the CDN by name so we can get the id for the snapshot
-       resp, _, err := TOSession.GetCDNByName(firstCDN.Name)
-       if err != nil {
-               t.Errorf("cannot GET CDN by name: '%s', %v", firstCDN.Name, err)
-       }
-       remoteCDN := resp[0]
-       alert, _, err := TOSession.SnapshotCRConfigByID(remoteCDN.ID)
-       if err != nil {
-               t.Errorf("failed to snapshot CDN by id: %v - %v", err, alert)
-       }
-}
-
-func SnapshotTestCDNbyInvalidID(t *testing.T) {
-
-       invalidCDNID := 999999
-       alert, _, err := TOSession.SnapshotCRConfigByID(invalidCDNID)
-       if err == nil {
-               t.Errorf("snapshot occurred on invalid cdn id: %v - %v - %v", 
invalidCDNID, err, alert)
-       }
-}
diff --git a/traffic_ops/testing/api/v3/snapshot_test.go 
b/traffic_ops/testing/api/v3/snapshot_test.go
new file mode 100644
index 0000000000..ab37ccab4a
--- /dev/null
+++ b/traffic_ops/testing/api/v3/snapshot_test.go
@@ -0,0 +1,141 @@
+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"
+       "net/url"
+       "strconv"
+       "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 TestSnapshot(t *testing.T) {
+       WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, 
ServiceCategories, DeliveryServices, DeliveryServiceServerAssignments}, func() {
+
+               methodTests := utils.V3TestCase{
+                       "PUT": {
+                               "VERIFY ANYMAP DELIVERY SERVICE is NOT in 
CRCONFIG": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"cdn": 
{"cdn1"}},
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateDeliveryServiceNotInCRConfig("cdn1", "anymap-ds"),
+                                               validateCRConfigFields("cdn1", 
map[string]interface{}{"TMPath": "", "TMHost": 
"crconfig.tm.url.test.invalid"})),
+                               },
+                               "OK when VALID CDN parameter": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"cdn": 
{"cdn1"}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                               },
+                               "OK when VALID CDNID parameter": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"cdnID": 
{strconv.Itoa(GetCDNID(t, "cdn1")())}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                               },
+                               "NOT FOUND when NON-EXISTENT CDN": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"cdn": 
{"cdn-invalid"}},
+                                       Expectations:  
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)),
+                               },
+                               "NOT FOUND when NON-EXISTENT CDNID": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"cdnID": 
{"999999"}},
+                                       Expectations:  
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)),
+                               },
+                       },
+               }
+
+               for method, testCases := range methodTests {
+                       t.Run(method, func(t *testing.T) {
+                               for name, testCase := range testCases {
+                                       switch method {
+                                       case "PUT":
+                                               t.Run(name, func(t *testing.T) {
+                                                       if name == "OK when 
VALID CDNID parameter" {
+                                                               var cdnID int
+                                                               var err error
+                                                               if val, ok := 
testCase.RequestParams["cdnID"]; ok {
+                                                                       cdnID, 
err = strconv.Atoi(val[0])
+                                                                       
assert.NoError(t, err, "Error converting string to integer: %v", err)
+                                                               }
+                                                               resp, reqInf, 
err := testCase.ClientSession.SnapshotCRConfigByID(cdnID)
+                                                               for _, check := 
range testCase.Expectations {
+                                                                       
check(t, reqInf, resp, tc.Alerts{}, err)
+                                                               }
+                                                       } else {
+                                                               var cdn string
+                                                               if val, ok := 
testCase.RequestParams["cdn"]; ok {
+                                                                       cdn = 
val[0]
+                                                               }
+                                                               reqInf, err := 
testCase.ClientSession.SnapshotCRConfigWithHdr(cdn, testCase.RequestHeaders)
+                                                               for _, check := 
range testCase.Expectations {
+                                                                       
check(t, reqInf, nil, tc.Alerts{}, err)
+                                                               }
+                                                       }
+                                               })
+                                       }
+                               }
+                       })
+               }
+       })
+}
+
+func validateCRConfigFields(cdn string, expectedResp map[string]interface{}) 
utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, _ interface{}, _ 
tc.Alerts, _ error) {
+               var crconfig tc.CRConfig
+               snapshotResp, _, err := TOSession.GetCRConfig(cdn)
+               assert.RequireNoError(t, err, "Unexpected error retrieving 
Snapshot of CDN '%s': %v", cdn, err)
+               err = json.Unmarshal(snapshotResp, &crconfig)
+               assert.NoError(t, err, "Error occurred when unmarshalling 
request body: %v", err)
+
+               for field, expected := range expectedResp {
+                       switch field {
+                       case "TMPath":
+                               assert.RequireNotNil(t, crconfig.Stats.TMPath, 
"Expected Stats TM Path to not be nil.")
+                       case "TMHost":
+                               assert.RequireNotNil(t, crconfig.Stats.TMHost, 
"Expected Stats TM Host to not be nil.")
+                               assert.Equal(t, expected, 
*crconfig.Stats.TMHost, "Expected Stats TM Host to be %v, but got %s", 
expected, *crconfig.Stats.TMHost)
+                       default:
+                               t.Errorf("Expected field: %v, does not exist in 
response", field)
+                       }
+               }
+       }
+}
+
+func validateDeliveryServiceNotInCRConfig(cdn string, deliveryService string) 
utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, _ interface{}, _ 
tc.Alerts, _ error) {
+               var crconfig tc.CRConfig
+               snapshotResp, _, err := TOSession.GetCRConfig(cdn)
+               assert.RequireNoError(t, err, "Unexpected error retrieving 
Snapshot of CDN '%s': %v", cdn, err)
+               err = json.Unmarshal(snapshotResp, &crconfig)
+               assert.NoError(t, err, "Error occurred when unmarshalling 
request body: %v", err)
+
+               for ds := range crconfig.DeliveryServices {
+                       assert.NotEqual(t, ds, deliveryService, "Found 
unexpected delivery service: %s in CRConfig Delivery Services.", 
deliveryService)
+               }
+
+               for _, server := range crconfig.ContentServers {
+                       for ds := range server.DeliveryServices {
+                               assert.NotEqual(t, ds, deliveryService, "Found 
unexpected delivery service: %s in CRConfig Content Servers Delivery 
Services.", deliveryService)
+                       }
+               }
+       }
+}
diff --git a/traffic_ops/testing/api/v3/tc-fixtures.json 
b/traffic_ops/testing/api/v3/tc-fixtures.json
index 5240c8e0ad..f153e2cbef 100644
--- a/traffic_ops/testing/api/v3/tc-fixtures.json
+++ b/traffic_ops/testing/api/v3/tc-fixtures.json
@@ -1580,6 +1580,10 @@
         {
             "xmlId": "ds2",
             "serverNames": ["atlanta-org-2"]
+        },
+        {
+            "xmlId": "anymap-ds",
+            "serverNames": ["atlanta-edge-15"]
         }
     ],
     "topologyBasedDeliveryServicesRequiredCapabilities": [
@@ -1845,6 +1849,12 @@
             "name": "location",
             "secure": false,
             "value": "/remap/config/location/parameter/"
+        },
+        {
+            "configFile": "global",
+            "name": "tm.url",
+            "secure": false,
+            "value": "https://crconfig.tm.url.test.invalid";
         }
     ],
     "physLocations": [
diff --git a/traffic_ops/testing/api/v4/cdns_name_configs_monitoring_test.go 
b/traffic_ops/testing/api/v4/cdns_name_configs_monitoring_test.go
new file mode 100644
index 0000000000..bb6860f66a
--- /dev/null
+++ b/traffic_ops/testing/api/v4/cdns_name_configs_monitoring_test.go
@@ -0,0 +1,104 @@
+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 (
+       "net/http"
+       "net/url"
+       "strings"
+       "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"
+       client "github.com/apache/trafficcontrol/traffic_ops/v4-client"
+)
+
+func TestCDNNameConfigsMonitoring(t *testing.T) {
+       WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
ProfileParameters, Statuses, Divisions, Regions, PhysLocations, CacheGroups, 
Servers, Topologies, ServiceCategories, DeliveryServices}, func() {
+
+               methodTests := utils.V4TestCase{
+                       "GET": {
+                               "OK when VALID request": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn1"}}},
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateHealthThresholdParameters("EDGE1", map[string]string{"loadavg": "25.0", 
"availableBandwidthInKbps": ">1750000", "queryTime": "1000"})),
+                               },
+                       },
+               }
+
+               for method, testCases := range methodTests {
+                       t.Run(method, func(t *testing.T) {
+                               for name, testCase := range testCases {
+                                       switch method {
+                                       case "GET":
+                                               t.Run(name, func(t *testing.T) {
+                                                       var cdn string
+                                                       if val, ok := 
testCase.RequestOpts.QueryParameters["cdn"]; ok {
+                                                               cdn = val[0]
+                                                       }
+                                                       resp, reqInf, err := 
testCase.ClientSession.GetTrafficMonitorConfig(cdn, testCase.RequestOpts)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                                       }
+                                               })
+                                       }
+                               }
+                       })
+               }
+       })
+}
+
+func validateHealthThresholdParameters(profileName string, 
healthThresholdParams map[string]string) utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected Traffic Monitor Config 
response to not be nil.")
+               tmConfig := resp.(tc.TrafficMonitorConfig)
+               parameterMap := map[string]tc.HealthThreshold{}
+               parameterFound := map[string]bool{}
+               for parameter, parameterValue := range healthThresholdParams {
+                       threshold, err := tc.StrToThreshold(parameterValue)
+                       parameterMap[parameter] = threshold
+                       assert.RequireNoError(t, err, "Error: converting string 
'%s' to HealthThreshold: %v", parameterValue, err)
+                       parameterFound[parameter] = false
+               }
+
+               profileFound := false
+               var profile tc.TMProfile
+               for _, profile = range tmConfig.Profiles {
+                       if profile.Name == profileName {
+                               profileFound = true
+                               break
+                       }
+               }
+               assert.RequireEqual(t, true, profileFound, "Traffic Monitor 
Config contained no Profile named '%s", profileName)
+
+               for parameterName, value := range profile.Parameters.Thresholds 
{
+                       _, ok := parameterFound[parameterName]
+                       assert.Equal(t, true, ok, "Unexpected Threshold 
Parameter name '%s' found in Profile '%s' in Traffic Monitor Config", 
parameterName, profileName)
+                       parameterFound[parameterName] = true
+                       assert.Equal(t, parameterMap[parameterName].String(), 
value.String(), "Expected '%s' but received '%s' for Threshold Parameter '%s' 
in Profile '%s' in Traffic Monitor Config", 
parameterMap[parameterName].String(), value.String(), parameterName, 
profileName)
+               }
+               missingParameters := []string{}
+               for parameterName, found := range parameterFound {
+                       if !found {
+                               missingParameters = append(missingParameters, 
parameterName)
+                       }
+               }
+               assert.Equal(t, 0, len(missingParameters), "Threshold 
parameters defined for Profile '%s' but missing for Profile '%s' in Traffic 
Monitor Config: %s", profileName, profileName, strings.Join(missingParameters, 
", "))
+       }
+}
diff --git a/traffic_ops/testing/api/v4/cdns_name_snapshot_new_test.go 
b/traffic_ops/testing/api/v4/cdns_name_snapshot_new_test.go
new file mode 100644
index 0000000000..b702a1d385
--- /dev/null
+++ b/traffic_ops/testing/api/v4/cdns_name_snapshot_new_test.go
@@ -0,0 +1,121 @@
+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 (
+       "net/http"
+       "net/url"
+       "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"
+       client "github.com/apache/trafficcontrol/traffic_ops/v4-client"
+)
+
+var baselineCRConfig tc.CRConfig
+
+func TestCDNNameSnapshotNew(t *testing.T) {
+       WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, 
ServiceCategories, DeliveryServices}, func() {
+
+               methodTests := utils.V4TestCase{
+                       "GET": {
+                               "VERIFY SNAPSHOT UPDATE CAPTURED CORRECTLY": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn1"}}},
+                                       PreReqFuncs:   
[]func(){getBaselineCRConfig(t, "cdn1"), deleteParameter(t, "tm.url")},
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateCRConfigNewFields("cdn1", map[string]interface{}{"TMHost": ""}), 
validateDeliveryServicesUnchanged()),
+                               },
+                       },
+               }
+
+               for method, testCases := range methodTests {
+                       t.Run(method, func(t *testing.T) {
+                               for name, testCase := range testCases {
+                                       switch method {
+                                       case "GET":
+                                               t.Run(name, func(t *testing.T) {
+                                                       var cdn string
+                                                       if val, ok := 
testCase.RequestOpts.QueryParameters["cdn"]; ok {
+                                                               cdn = val[0]
+                                                       }
+                                                       for _, prerequisite := 
range testCase.PreReqFuncs {
+                                                               prerequisite()
+                                                       }
+                                                       resp, reqInf, err := 
testCase.ClientSession.GetCRConfigNew(cdn, testCase.RequestOpts)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                                       }
+                                               })
+                                       }
+                               }
+                       })
+               }
+       })
+}
+
+func validateCRConfigNewFields(cdn string, expectedResp 
map[string]interface{}) utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected CRConfigNew response to 
not be nil.")
+               crconfig := resp.(tc.CRConfig)
+
+               for field, expected := range expectedResp {
+                       switch field {
+                       case "TMPath":
+                               assert.Equal(t, expected, 
crconfig.Stats.TMPath, "Expected no TMPath in APIv4, but it was: %s", 
*crconfig.Stats.TMPath)
+                       case "TMHost":
+                               assert.RequireNotNil(t, crconfig.Stats.TMHost, 
"Expected Stats TM Host to not be nil.")
+                               assert.Equal(t, expected, 
*crconfig.Stats.TMHost, "Expected Stats TM Host to be %v, but got %s", 
expected, *crconfig.Stats.TMHost)
+                       default:
+                               t.Errorf("Expected field: %v, does not exist in 
response", field)
+                       }
+               }
+       }
+}
+
+func validateDeliveryServicesUnchanged() utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected new snapshot response 
to not be nil.")
+               newSnapshot := resp.(tc.CRConfig)
+               assert.Exactly(t, newSnapshot.DeliveryServices, 
baselineCRConfig.DeliveryServices, "Expected Delivery Services to be 
unchanged.")
+       }
+}
+
+func getBaselineCRConfig(t *testing.T, cdn string) func() {
+       return func() {
+               opts := client.NewRequestOptions()
+               opts.QueryParameters.Set("cdn", cdn)
+               snapshotResp, _, err := TOSession.SnapshotCRConfig(opts)
+               assert.RequireNoError(t, err, "Unexpected error taking Snapshot 
of CDN '%s': %v - alerts: %+v", cdn, err, snapshotResp.Alerts)
+               getCRConfig, _, err := TOSession.GetCRConfig(cdn, 
client.RequestOptions{})
+               assert.RequireNoError(t, err, "Unexpected error retrieving 
Snapshot of CDN '%s': %v - alerts: %+v", cdn, err, snapshotResp.Alerts)
+               baselineCRConfig = getCRConfig.Response
+       }
+}
+
+func deleteParameter(t *testing.T, paramName string) func() {
+       return func() {
+               opts := client.NewRequestOptions()
+               opts.QueryParameters.Set("name", paramName)
+               paramResp, _, err := TOSession.GetParameters(opts)
+               assert.RequireNoError(t, err, "Cannot get Parameter by name 
'%s': %v - alerts: %+v", paramName, err, paramResp.Alerts)
+               assert.RequireGreaterOrEqual(t, len(paramResp.Response), 1, 
"Expected at least one parameter to be returned.")
+               delResp, _, err := 
TOSession.DeleteParameter(paramResp.Response[0].ID, client.RequestOptions{})
+               assert.RequireNoError(t, err, "Cannot DELETE Parameter by name: 
%v - %v", err, delResp)
+       }
+}
diff --git a/traffic_ops/testing/api/v4/cdns_name_snapshot_test.go 
b/traffic_ops/testing/api/v4/cdns_name_snapshot_test.go
new file mode 100644
index 0000000000..71e218aeb4
--- /dev/null
+++ b/traffic_ops/testing/api/v4/cdns_name_snapshot_test.go
@@ -0,0 +1,60 @@
+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 (
+       "net/http"
+       "net/url"
+       "testing"
+
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils"
+       client "github.com/apache/trafficcontrol/traffic_ops/v4-client"
+)
+
+func TestCDNNameSnapshot(t *testing.T) {
+       WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, 
ServiceCategories, DeliveryServices}, func() {
+
+               methodTests := utils.V4TestCase{
+                       "GET": {
+                               "OK when VALID request": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn1"}}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                               },
+                       },
+               }
+
+               for method, testCases := range methodTests {
+                       t.Run(method, func(t *testing.T) {
+                               for name, testCase := range testCases {
+                                       switch method {
+                                       case "GET":
+                                               t.Run(name, func(t *testing.T) {
+                                                       var cdn string
+                                                       if val, ok := 
testCase.RequestOpts.QueryParameters["cdn"]; ok {
+                                                               cdn = val[0]
+                                                       }
+                                                       resp, reqInf, err := 
testCase.ClientSession.GetCRConfig(cdn, testCase.RequestOpts)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                                       }
+                                               })
+                                       }
+                               }
+                       })
+               }
+       })
+}
diff --git a/traffic_ops/testing/api/v4/crconfig_test.go 
b/traffic_ops/testing/api/v4/crconfig_test.go
deleted file mode 100644
index 4574cfb16b..0000000000
--- a/traffic_ops/testing/api/v4/crconfig_test.go
+++ /dev/null
@@ -1,356 +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 (
-       "net/http"
-       "strconv"
-       "strings"
-       "testing"
-       "time"
-
-       "github.com/apache/trafficcontrol/lib/go-tc"
-       "github.com/apache/trafficcontrol/lib/go-util"
-       client "github.com/apache/trafficcontrol/traffic_ops/v4-client"
-       toclient "github.com/apache/trafficcontrol/traffic_ops/v4-client"
-)
-
-func TestCRConfig(t *testing.T) {
-       WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
ProfileParameters, Statuses, Divisions, Regions, PhysLocations, CacheGroups, 
Servers, Topologies, ServiceCategories, DeliveryServices}, func() {
-               UpdateTestCRConfigSnapshot(t)
-               MonitoringConfig(t)
-               SnapshotTestCDNbyName(t)
-               SnapshotTestCDNbyInvalidName(t)
-               SnapshotTestCDNbyID(t)
-               SnapshotTestCDNbyInvalidID(t)
-               SnapshotWithReadOnlyUser(t)
-       })
-}
-
-func SnapshotWithReadOnlyUser(t *testing.T) {
-       if len(testData.CDNs) == 0 {
-               t.Fatalf("expected one or more valid CDNs, but got none")
-       }
-
-       tenantOpts := client.NewRequestOptions()
-       tenantOpts.QueryParameters.Set("name", "root")
-       resp, _, err := TOSession.GetTenants(tenantOpts)
-       if err != nil {
-               t.Fatalf("couldn't get the root tenant ID: %v - alerts: %+v", 
err, resp.Alerts)
-       }
-       if len(resp.Response) != 1 {
-               t.Fatalf("Expected exactly one Tenant to have the name 'root', 
found: %d", len(resp.Response))
-       }
-
-       toReqTimeout := time.Second * 
time.Duration(Config.Default.Session.TimeoutInSecs)
-       user := tc.UserV4{
-               Username:         "test_user_tm",
-               RegistrationSent: new(time.Time),
-               LocalPassword:    util.StrPtr("test_pa$$word"),
-               Role:             "read-only",
-       }
-       user.Email = util.StrPtr("[email protected]")
-       user.TenantID = resp.Response[0].ID
-       user.FullName = util.StrPtr("firstName LastName")
-
-       u, _, err := TOSession.CreateUser(user, client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("could not create read-only user: %v - alerts: %+v", 
err, u.Alerts)
-       }
-       client, _, err := toclient.LoginWithAgent(TOSession.URL, 
"test_user_tm", "test_pa$$word", true, "to-api-v4-client-tests/tenant4user", 
true, toReqTimeout)
-       if err != nil {
-               t.Fatalf("failed to log in with test_user: %v", err.Error())
-       }
-       opts := toclient.NewRequestOptions()
-       opts.QueryParameters.Set("cdn", testData.CDNs[0].Name)
-       _, reqInf, err := client.SnapshotCRConfig(opts)
-       if err == nil {
-               t.Errorf("expected to get an error about a read-only client 
trying to snap a CDN, but got none")
-       }
-       if reqInf.StatusCode != http.StatusForbidden {
-               t.Errorf("expected a 403 forbidden status code, but got %d", 
reqInf.StatusCode)
-       }
-       ForceDeleteTestUsersByUsernames(t, []string{"test_user_tm"})
-}
-
-func UpdateTestCRConfigSnapshot(t *testing.T) {
-       if len(testData.CDNs) < 1 {
-               t.Error("no cdn test data")
-       }
-       cdn := testData.CDNs[0].Name
-
-       tmURLParamName := "tm.url"
-       tmURLExpected := "crconfig.tm.url.test.invalid"
-       paramAlerts, _, err := TOSession.CreateParameter(tc.Parameter{
-               ConfigFile: "global",
-               Name:       tmURLParamName,
-               Value:      "https://crconfig.tm.url.test.invalid";,
-       }, client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("GetCRConfig CreateParameter error expected: nil, 
actual: %v - alerts: %+v", err, paramAlerts.Alerts)
-       }
-
-       // create an ANY_MAP DS assignment to verify that it doesn't show up in 
the CRConfig
-       resp, _, err := TOSession.GetServers(client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("GetServers err expected nil, actual: %v - alerts: 
%+v", err, resp.Alerts)
-       }
-       servers := resp.Response
-       serverID := 0
-       for _, server := range servers {
-               if server.CDNName == nil || server.ID == nil {
-                       t.Error("Traffic Ops returned a representation for a 
servver with null or undefined ID and/or CDN name")
-                       continue
-               }
-               if server.Type == "EDGE" && *server.CDNName == "cdn1" {
-                       serverID = *server.ID
-                       break
-               }
-       }
-       if serverID == 0 {
-               t.Errorf("GetServers expected EDGE server in cdn1, actual: 
%+v", servers)
-       }
-       opts := client.NewRequestOptions()
-       opts.QueryParameters.Set("xmlId", "anymap-ds")
-       res, _, err := TOSession.GetDeliveryServices(opts)
-       if err != nil {
-               t.Errorf("Unexpected error getting Delivery Services filtered 
by XMLID 'anymap-ds': %v - alerts: %+v", err, res.Alerts)
-       }
-       if len(res.Response) != 1 {
-               t.Fatalf("Expected exactly 1 Delivery Service to exist with 
XMLID 'anymap-ds', actual %d", len(res.Response))
-       }
-       if res.Response[0].ID == nil {
-               t.Fatal("Traffic Ops returned a representation of Delivery 
Service 'anymap-ds' that had a null or undefined ID")
-       }
-       anymapDSID := *res.Response[0].ID
-       alerts, _, err := TOSession.CreateDeliveryServiceServers(anymapDSID, 
[]int{serverID}, true, client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("Unexpected error assigning server #%d to Delivery 
Service #%d: %v - alerts: %+v", serverID, anymapDSID, err, alerts.Alerts)
-       }
-
-       opts = client.NewRequestOptions()
-       opts.QueryParameters.Set("cdn", cdn)
-       snapshotResp, _, err := TOSession.SnapshotCRConfig(opts)
-       if err != nil {
-               t.Errorf("Unexpected error taking Snapshot of CDN '%s': %v - 
alerts: %+v", cdn, err, snapshotResp.Alerts)
-       }
-       crcResp, _, err := TOSession.GetCRConfig(cdn, client.RequestOptions{})
-       if err != nil {
-               t.Errorf("Unexpected error retrieving Snapshot of CDN '%s': %v 
- alerts: %+v", cdn, err, crcResp.Alerts)
-       }
-       crc := crcResp.Response
-
-       if len(crc.DeliveryServices) == 0 {
-               t.Error("GetCRConfig len(crc.DeliveryServices) expected: >0, 
actual: 0")
-       }
-
-       // verify no ANY_MAP delivery services are in the CRConfig
-       for ds := range crc.DeliveryServices {
-               if ds == "anymap-ds" {
-                       t.Error("found ANY_MAP delivery service in CRConfig 
deliveryServices")
-               }
-       }
-       for server := range crc.ContentServers {
-               for ds := range crc.ContentServers[server].DeliveryServices {
-                       if ds == "anymap-ds" {
-                               t.Error("found ANY_MAP delivery service in 
contentServers deliveryServices mapping")
-                       }
-               }
-       }
-
-       if crc.Stats.TMPath != nil {
-               t.Errorf("Expected no TMPath in APIv4, but it was: %v", 
*crc.Stats.TMPath)
-       }
-
-       if crc.Stats.TMHost == nil {
-               t.Errorf("GetCRConfig crc.Stats.Path expected: 
'"+tmURLExpected+"', actual: %+v", crc.Stats.TMHost)
-       } else if *crc.Stats.TMHost != tmURLExpected {
-               t.Errorf("GetCRConfig crc.Stats.Path expected: 
'"+tmURLExpected+"', actual: %+v", *crc.Stats.TMHost)
-       }
-
-       opts.QueryParameters.Del("cdn")
-       opts.QueryParameters.Set("name", tmURLParamName)
-       paramResp, _, err := TOSession.GetParameters(opts)
-       if err != nil {
-               t.Fatalf("cannot get Parameter by name '%s': %v - alerts: %+v", 
tmURLParamName, err, paramResp.Alerts)
-       }
-       if len(paramResp.Response) == 0 {
-               t.Fatal("CRConfig create tm.url parameter was successful, but 
GET returned no parameters")
-       }
-       tmURLParam := paramResp.Response[0]
-
-       delResp, _, err := TOSession.DeleteParameter(tmURLParam.ID, 
client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("cannot DELETE Parameter by name: %v - %v", err, 
delResp)
-       }
-
-       crcResp, _, err = TOSession.GetCRConfigNew(cdn, client.RequestOptions{})
-       if err != nil {
-               t.Errorf("Unexpected error getting new Snapshot for CDN '%s': 
%v - alerts: %+v", cdn, err, crcResp.Alerts)
-       }
-       crcNew := crcResp.Response
-
-       if len(crcNew.DeliveryServices) != len(crc.DeliveryServices) {
-               t.Errorf("/new endpoint returned a different snapshot. 
DeliveryServices length expected %v, was %v", len(crc.DeliveryServices), 
len(crcNew.DeliveryServices))
-       }
-
-       if *crcNew.Stats.TMHost != "" {
-               t.Errorf("update to snapshot not captured in /new endpoint")
-       }
-}
-
-func MonitoringConfig(t *testing.T) {
-       if len(testData.CDNs) < 1 {
-               t.Fatalf("no cdn test data")
-       }
-       const cdnName = "cdn1"
-       const profileName = "EDGE1"
-       opts := client.NewRequestOptions()
-       opts.QueryParameters.Set("name", cdnName)
-       cdns, _, err := TOSession.GetCDNs(opts)
-       if err != nil {
-               t.Fatalf("getting CDNs with name '%s': %v - alerts: %+v", 
cdnName, err, cdns.Alerts)
-       }
-       if len(cdns.Response) != 1 {
-               t.Fatalf("expected exactly 1 CDN named '%s' but found %d CDNs", 
cdnName, len(cdns.Response))
-       }
-       opts.QueryParameters.Set("name", profileName)
-       profiles, _, err := TOSession.GetProfiles(opts)
-       if err != nil {
-               t.Fatalf("getting Profiles with name '%s': %v - alerts: %+v", 
profileName, err, profiles.Alerts)
-       }
-       if len(profiles.Response) != 1 {
-               t.Fatalf("expected exactly 1 Profiles named %s but found %d 
Profiles", profileName, len(profiles.Response))
-       }
-       parameters, _, err := TOSession.GetParametersByProfileName(profileName, 
client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("getting Parameters by Profile name '%s': %v - alerts: 
%+v", profileName, err, parameters.Alerts)
-       }
-       parameterMap := map[string]tc.HealthThreshold{}
-       parameterFound := map[string]bool{}
-       const thresholdPrefixLength = len(tc.ThresholdPrefix)
-       for _, parameter := range parameters.Response {
-               if !strings.HasPrefix(parameter.Name, tc.ThresholdPrefix) {
-                       continue
-               }
-               parameterName := parameter.Name[thresholdPrefixLength:]
-               parameterMap[parameterName], err = 
tc.StrToThreshold(parameter.Value)
-               if err != nil {
-                       t.Fatalf("converting string '%s' to HealthThreshold: 
%s", parameter.Value, err.Error())
-               }
-               parameterFound[parameterName] = false
-       }
-       const expectedThresholdParameters = 3
-       if len(parameterMap) != expectedThresholdParameters {
-               t.Fatalf("expected Profile '%s' to contain %d Parameters with 
names starting with '%s' but %d such Parameters were found", profileName, 
expectedThresholdParameters, tc.ThresholdPrefix, len(parameterMap))
-       }
-       tmConfig, _, err := TOSession.GetTrafficMonitorConfig(cdnName, 
client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("getting Traffic Monitor Config: %v - alerts: %+v", 
err, tmConfig.Alerts)
-       }
-       profileFound := false
-       var profile tc.TMProfile
-       for _, profile = range tmConfig.Response.Profiles {
-               if profile.Name == profileName {
-                       profileFound = true
-                       break
-               }
-       }
-       if !profileFound {
-               t.Fatalf("Traffic Monitor Config contained no Profile named 
'%s", profileName)
-       }
-       for parameterName, value := range profile.Parameters.Thresholds {
-               if _, ok := parameterFound[parameterName]; !ok {
-                       t.Fatalf("unexpected Threshold Parameter name '%s' 
found in Profile '%s' in Traffic Monitor Config", parameterName, profileName)
-               }
-               parameterFound[parameterName] = true
-               if parameterMap[parameterName].String() != value.String() {
-                       t.Fatalf("expected '%s' but received '%s' for Threshold 
Parameter '%s' in Profile '%s' in Traffic Monitor Config", 
parameterMap[parameterName].String(), value.String(), parameterName, 
profileName)
-               }
-       }
-       missingParameters := []string{}
-       for parameterName, found := range parameterFound {
-               if !found {
-                       missingParameters = append(missingParameters, 
parameterName)
-               }
-       }
-       if len(missingParameters) != 0 {
-               t.Fatalf("Threshold parameters defined for Profile '%s' but 
missing for Profile '%s' in Traffic Monitor Config: %s", profileName, 
profileName, strings.Join(missingParameters, ", "))
-       }
-}
-
-func SnapshotTestCDNbyName(t *testing.T) {
-       if len(testData.CDNs) < 1 {
-               t.Fatal("Need at least one CDN to test taking CDN Snapshot 
using CDN name")
-       }
-       firstCDN := testData.CDNs[0].Name
-       opts := client.NewRequestOptions()
-       opts.QueryParameters.Set("cdn", firstCDN)
-       resp, _, err := TOSession.SnapshotCRConfig(opts)
-       if err != nil {
-               t.Errorf("failed to snapshot CDN '%s' by name: %v - alerts: 
%+v", firstCDN, err, resp.Alerts)
-       }
-}
-
-// Note that this test will break if anyone adds a CDN to the fixture data with
-// the name "cdn-invalid".
-func SnapshotTestCDNbyInvalidName(t *testing.T) {
-       invalidCDNName := "cdn-invalid"
-       opts := client.NewRequestOptions()
-       opts.QueryParameters.Set("cdn", invalidCDNName)
-       _, _, err := TOSession.SnapshotCRConfig(opts)
-       if err == nil {
-               t.Errorf("snapshot occurred without error on (presumed) invalid 
CDN '%s'", invalidCDNName)
-       }
-}
-
-func SnapshotTestCDNbyID(t *testing.T) {
-       if len(testData.CDNs) < 1 {
-               t.Fatal("Need at least one CDN to test Snapshotting CDNs")
-       }
-       firstCDNName := testData.CDNs[0].Name
-       opts := client.NewRequestOptions()
-       opts.QueryParameters.Set("name", firstCDNName)
-       // Retrieve the CDN by name so we can get the id for the snapshot
-       resp, _, err := TOSession.GetCDNs(opts)
-       if err != nil {
-               t.Errorf("cannot get CDN '%s': %v - alerts: %+v", firstCDNName, 
err, resp.Alerts)
-       }
-       if len(resp.Response) != 1 {
-               t.Fatalf("Expected exactly one CDN to exist with name '%s', 
found: %d", firstCDNName, len(resp.Response))
-       }
-       remoteCDNID := resp.Response[0].ID
-       opts.QueryParameters.Del("name")
-       opts.QueryParameters.Set("cdnID", strconv.Itoa(remoteCDNID))
-       alert, _, err := TOSession.SnapshotCRConfig(opts)
-       if err != nil {
-               t.Errorf("failed to snapshot CDN '%s' (#%d) by id: %v - alerts: 
%+v", firstCDNName, remoteCDNID, err, alert.Alerts)
-       }
-}
-
-// Note that this test will break in the event that 1,000,000 CDNs are created
-// in the TO instance at any time (they don't need to exist concurrently, just
-// that many successful CDN creations have to happen, even if they are
-// all immediately deleted except the 999999th one).
-func SnapshotTestCDNbyInvalidID(t *testing.T) {
-       opts := client.NewRequestOptions()
-       invalidCDNID := 999999
-       opts.QueryParameters.Set("cdnID", strconv.Itoa(invalidCDNID))
-       alert, _, err := TOSession.SnapshotCRConfig(opts)
-       if err == nil {
-               t.Errorf("snapshot occurred on (presumed) invalid CDN #%d: %v - 
alerts: %+v", invalidCDNID, err, alert.Alerts)
-       }
-}
diff --git a/traffic_ops/testing/api/v4/snapshot_test.go 
b/traffic_ops/testing/api/v4/snapshot_test.go
new file mode 100644
index 0000000000..17ff44a47d
--- /dev/null
+++ b/traffic_ops/testing/api/v4/snapshot_test.go
@@ -0,0 +1,126 @@
+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 (
+       "net/http"
+       "net/url"
+       "strconv"
+       "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"
+       client "github.com/apache/trafficcontrol/traffic_ops/v4-client"
+)
+
+func TestSnapshot(t *testing.T) {
+       WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, 
ServiceCategories, DeliveryServices, DeliveryServiceServerAssignments}, func() {
+
+               readOnlyUserSession := utils.CreateV4Session(t, 
Config.TrafficOps.URL, "readonlyuser", "pa$$word", 
Config.Default.Session.TimeoutInSecs)
+
+               methodTests := utils.V4TestCase{
+                       "PUT": {
+                               "VERIFY ANYMAP DELIVERY SERVICE is NOT in 
CRCONFIG": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn1"}}},
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateDeliveryServiceNotInCRConfig("cdn1", "anymap-ds"),
+                                               validateCRConfigFields("cdn1", 
map[string]interface{}{"TMPath": (*string)(nil), "TMHost": 
"crconfig.tm.url.test.invalid"})),
+                               },
+                               "OK when VALID CDN parameter": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn1"}}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                               },
+                               "OK when VALID CDNID parameter": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdnID": 
{strconv.Itoa(GetCDNID(t, "cdn1")())}}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                               },
+                               "NOT FOUND when NON-EXISTENT CDN": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn-invalid"}}},
+                                       Expectations:  
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)),
+                               },
+                               "NOT FOUND when NON-EXISTENT CDNID": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdnID": {"999999"}}},
+                                       Expectations:  
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)),
+                               },
+                               "FORBIDDEN when READ-ONLY user": {
+                                       ClientSession: readOnlyUserSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn1"}}},
+                                       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 {
+                                       switch method {
+                                       case "PUT":
+                                               t.Run(name, func(t *testing.T) {
+                                                       resp, reqInf, err := 
testCase.ClientSession.SnapshotCRConfig(testCase.RequestOpts)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                                       }
+                                               })
+                                       }
+                               }
+                       })
+               }
+       })
+}
+
+func validateCRConfigFields(cdn string, expectedResp map[string]interface{}) 
utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, _ interface{}, _ 
tc.Alerts, _ error) {
+               snapshotResp, _, err := TOSession.GetCRConfig(cdn, 
client.RequestOptions{})
+               assert.RequireNoError(t, err, "Unexpected error retrieving 
Snapshot of CDN '%s': %v - alerts: %+v", cdn, err, snapshotResp.Alerts)
+               crconfig := snapshotResp.Response
+
+               for field, expected := range expectedResp {
+                       switch field {
+                       case "TMPath":
+                               assert.Equal(t, expected, 
crconfig.Stats.TMPath, "Expected no TMPath in APIv4, but it was: %v", 
crconfig.Stats.TMPath)
+                       case "TMHost":
+                               assert.RequireNotNil(t, crconfig.Stats.TMHost, 
"Expected Stats TM Host to not be nil.")
+                               assert.Equal(t, expected, 
*crconfig.Stats.TMHost, "Expected Stats TM Host to be %v, but got %s", 
expected, *crconfig.Stats.TMHost)
+                       default:
+                               t.Errorf("Expected field: %v, does not exist in 
response", field)
+                       }
+               }
+       }
+}
+
+func validateDeliveryServiceNotInCRConfig(cdn string, deliveryService string) 
utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, _ interface{}, _ 
tc.Alerts, _ error) {
+               snapshotResp, _, err := TOSession.GetCRConfig(cdn, 
client.RequestOptions{})
+               assert.RequireNoError(t, err, "Unexpected error retrieving 
Snapshot of CDN '%s': %v - alerts: %+v", cdn, err, snapshotResp.Alerts)
+
+               for ds := range snapshotResp.Response.DeliveryServices {
+                       assert.NotEqual(t, ds, deliveryService, "Found 
unexpected delivery service: %s in CRConfig Delivery Services.", 
deliveryService)
+               }
+
+               for _, server := range snapshotResp.Response.ContentServers {
+                       for ds := range server.DeliveryServices {
+                               assert.NotEqual(t, ds, deliveryService, "Found 
unexpected delivery service: %s in CRConfig Content Servers Delivery 
Services.", deliveryService)
+                       }
+               }
+       }
+}
diff --git a/traffic_ops/testing/api/v4/tc-fixtures.json 
b/traffic_ops/testing/api/v4/tc-fixtures.json
index 49c23a60cb..86260687cc 100644
--- a/traffic_ops/testing/api/v4/tc-fixtures.json
+++ b/traffic_ops/testing/api/v4/tc-fixtures.json
@@ -1801,6 +1801,10 @@
         {
             "xmlId": "ds2",
             "serverNames": ["atlanta-org-2"]
+        },
+        {
+            "xmlId": "anymap-ds",
+            "serverNames": ["atlanta-edge-15"]
         }
     ],
     "divisions": [
@@ -2024,8 +2028,13 @@
             "name": "testSecure",
             "secure": true,
             "value": "hidden value"
+        },
+        {
+            "configFile": "global",
+            "name": "tm.url",
+            "secure": false,
+            "value": "https://crconfig.tm.url.test.invalid";
         }
-
     ],
     "physLocations": [
         {
diff --git a/traffic_ops/testing/api/v5/cdns_name_configs_monitoring_test.go 
b/traffic_ops/testing/api/v5/cdns_name_configs_monitoring_test.go
new file mode 100644
index 0000000000..966f44a93c
--- /dev/null
+++ b/traffic_ops/testing/api/v5/cdns_name_configs_monitoring_test.go
@@ -0,0 +1,104 @@
+package v5
+
+/*
+
+   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 (
+       "net/http"
+       "net/url"
+       "strings"
+       "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"
+       client "github.com/apache/trafficcontrol/traffic_ops/v5-client"
+)
+
+func TestCDNNameConfigsMonitoring(t *testing.T) {
+       WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
ProfileParameters, Statuses, Divisions, Regions, PhysLocations, CacheGroups, 
Servers, Topologies, ServiceCategories, DeliveryServices}, func() {
+
+               methodTests := utils.V5TestCase{
+                       "GET": {
+                               "OK when VALID request": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn1"}}},
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateHealthThresholdParameters("EDGE1", map[string]string{"loadavg": "25.0", 
"availableBandwidthInKbps": ">1750000", "queryTime": "1000"})),
+                               },
+                       },
+               }
+
+               for method, testCases := range methodTests {
+                       t.Run(method, func(t *testing.T) {
+                               for name, testCase := range testCases {
+                                       switch method {
+                                       case "GET":
+                                               t.Run(name, func(t *testing.T) {
+                                                       var cdn string
+                                                       if val, ok := 
testCase.RequestOpts.QueryParameters["cdn"]; ok {
+                                                               cdn = val[0]
+                                                       }
+                                                       resp, reqInf, err := 
testCase.ClientSession.GetTrafficMonitorConfig(cdn, testCase.RequestOpts)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                                       }
+                                               })
+                                       }
+                               }
+                       })
+               }
+       })
+}
+
+func validateHealthThresholdParameters(profileName string, 
healthThresholdParams map[string]string) utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected Traffic Monitor Config 
response to not be nil.")
+               tmConfig := resp.(tc.TrafficMonitorConfig)
+               parameterMap := map[string]tc.HealthThreshold{}
+               parameterFound := map[string]bool{}
+               for parameter, parameterValue := range healthThresholdParams {
+                       threshold, err := tc.StrToThreshold(parameterValue)
+                       parameterMap[parameter] = threshold
+                       assert.RequireNoError(t, err, "Error: converting string 
'%s' to HealthThreshold: %v", parameterValue, err)
+                       parameterFound[parameter] = false
+               }
+
+               profileFound := false
+               var profile tc.TMProfile
+               for _, profile = range tmConfig.Profiles {
+                       if profile.Name == profileName {
+                               profileFound = true
+                               break
+                       }
+               }
+               assert.RequireEqual(t, true, profileFound, "Traffic Monitor 
Config contained no Profile named '%s", profileName)
+
+               for parameterName, value := range profile.Parameters.Thresholds 
{
+                       _, ok := parameterFound[parameterName]
+                       assert.Equal(t, true, ok, "Unexpected Threshold 
Parameter name '%s' found in Profile '%s' in Traffic Monitor Config", 
parameterName, profileName)
+                       parameterFound[parameterName] = true
+                       assert.Equal(t, parameterMap[parameterName].String(), 
value.String(), "Expected '%s' but received '%s' for Threshold Parameter '%s' 
in Profile '%s' in Traffic Monitor Config", 
parameterMap[parameterName].String(), value.String(), parameterName, 
profileName)
+               }
+               missingParameters := []string{}
+               for parameterName, found := range parameterFound {
+                       if !found {
+                               missingParameters = append(missingParameters, 
parameterName)
+                       }
+               }
+               assert.Equal(t, 0, len(missingParameters), "Threshold 
parameters defined for Profile '%s' but missing for Profile '%s' in Traffic 
Monitor Config: %s", profileName, profileName, strings.Join(missingParameters, 
", "))
+       }
+}
diff --git a/traffic_ops/testing/api/v5/cdns_name_snapshot_new_test.go 
b/traffic_ops/testing/api/v5/cdns_name_snapshot_new_test.go
new file mode 100644
index 0000000000..473e3e9c78
--- /dev/null
+++ b/traffic_ops/testing/api/v5/cdns_name_snapshot_new_test.go
@@ -0,0 +1,121 @@
+package v5
+
+/*
+
+   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 (
+       "net/http"
+       "net/url"
+       "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"
+       client "github.com/apache/trafficcontrol/traffic_ops/v5-client"
+)
+
+var baselineCRConfig tc.CRConfig
+
+func TestCDNNameSnapshotNew(t *testing.T) {
+       WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, 
ServiceCategories, DeliveryServices}, func() {
+
+               methodTests := utils.V5TestCase{
+                       "GET": {
+                               "VERIFY SNAPSHOT UPDATE CAPTURED CORRECTLY": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn1"}}},
+                                       PreReqFuncs:   
[]func(){getBaselineCRConfig(t, "cdn1"), deleteParameter(t, "tm.url")},
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateCRConfigNewFields("cdn1", map[string]interface{}{"TMHost": ""}), 
validateDeliveryServicesUnchanged()),
+                               },
+                       },
+               }
+
+               for method, testCases := range methodTests {
+                       t.Run(method, func(t *testing.T) {
+                               for name, testCase := range testCases {
+                                       switch method {
+                                       case "GET":
+                                               t.Run(name, func(t *testing.T) {
+                                                       var cdn string
+                                                       if val, ok := 
testCase.RequestOpts.QueryParameters["cdn"]; ok {
+                                                               cdn = val[0]
+                                                       }
+                                                       for _, prerequisite := 
range testCase.PreReqFuncs {
+                                                               prerequisite()
+                                                       }
+                                                       resp, reqInf, err := 
testCase.ClientSession.GetCRConfigNew(cdn, testCase.RequestOpts)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                                       }
+                                               })
+                                       }
+                               }
+                       })
+               }
+       })
+}
+
+func validateCRConfigNewFields(cdn string, expectedResp 
map[string]interface{}) utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected CRConfigNew response to 
not be nil.")
+               crconfig := resp.(tc.CRConfig)
+
+               for field, expected := range expectedResp {
+                       switch field {
+                       case "TMPath":
+                               assert.Equal(t, expected, 
crconfig.Stats.TMPath, "Expected no TMPath in APIv4, but it was: %s", 
*crconfig.Stats.TMPath)
+                       case "TMHost":
+                               assert.RequireNotNil(t, crconfig.Stats.TMHost, 
"Expected Stats TM Host to not be nil.")
+                               assert.Equal(t, expected, 
*crconfig.Stats.TMHost, "Expected Stats TM Host to be %v, but got %s", 
expected, *crconfig.Stats.TMHost)
+                       default:
+                               t.Errorf("Expected field: %v, does not exist in 
response", field)
+                       }
+               }
+       }
+}
+
+func validateDeliveryServicesUnchanged() utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
+               assert.RequireNotNil(t, resp, "Expected new snapshot response 
to not be nil.")
+               newSnapshot := resp.(tc.CRConfig)
+               assert.Exactly(t, newSnapshot.DeliveryServices, 
baselineCRConfig.DeliveryServices, "Expected Delivery Services to be 
unchanged.")
+       }
+}
+
+func getBaselineCRConfig(t *testing.T, cdn string) func() {
+       return func() {
+               opts := client.NewRequestOptions()
+               opts.QueryParameters.Set("cdn", cdn)
+               snapshotResp, _, err := TOSession.SnapshotCRConfig(opts)
+               assert.RequireNoError(t, err, "Unexpected error taking Snapshot 
of CDN '%s': %v - alerts: %+v", cdn, err, snapshotResp.Alerts)
+               getCRConfig, _, err := TOSession.GetCRConfig(cdn, 
client.RequestOptions{})
+               assert.RequireNoError(t, err, "Unexpected error retrieving 
Snapshot of CDN '%s': %v - alerts: %+v", cdn, err, snapshotResp.Alerts)
+               baselineCRConfig = getCRConfig.Response
+       }
+}
+
+func deleteParameter(t *testing.T, paramName string) func() {
+       return func() {
+               opts := client.NewRequestOptions()
+               opts.QueryParameters.Set("name", paramName)
+               paramResp, _, err := TOSession.GetParameters(opts)
+               assert.RequireNoError(t, err, "Cannot get Parameter by name 
'%s': %v - alerts: %+v", paramName, err, paramResp.Alerts)
+               assert.RequireGreaterOrEqual(t, len(paramResp.Response), 1, 
"Expected at least one parameter to be returned.")
+               delResp, _, err := 
TOSession.DeleteParameter(paramResp.Response[0].ID, client.RequestOptions{})
+               assert.RequireNoError(t, err, "Cannot DELETE Parameter by name: 
%v - %v", err, delResp)
+       }
+}
diff --git a/traffic_ops/testing/api/v5/cdns_name_snapshot_test.go 
b/traffic_ops/testing/api/v5/cdns_name_snapshot_test.go
new file mode 100644
index 0000000000..3dba9ea686
--- /dev/null
+++ b/traffic_ops/testing/api/v5/cdns_name_snapshot_test.go
@@ -0,0 +1,60 @@
+package v5
+
+/*
+
+   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 (
+       "net/http"
+       "net/url"
+       "testing"
+
+       "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils"
+       client "github.com/apache/trafficcontrol/traffic_ops/v5-client"
+)
+
+func TestCDNNameSnapshot(t *testing.T) {
+       WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, 
ServiceCategories, DeliveryServices}, func() {
+
+               methodTests := utils.V5TestCase{
+                       "GET": {
+                               "OK when VALID request": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn1"}}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                               },
+                       },
+               }
+
+               for method, testCases := range methodTests {
+                       t.Run(method, func(t *testing.T) {
+                               for name, testCase := range testCases {
+                                       switch method {
+                                       case "GET":
+                                               t.Run(name, func(t *testing.T) {
+                                                       var cdn string
+                                                       if val, ok := 
testCase.RequestOpts.QueryParameters["cdn"]; ok {
+                                                               cdn = val[0]
+                                                       }
+                                                       resp, reqInf, err := 
testCase.ClientSession.GetCRConfig(cdn, testCase.RequestOpts)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                                       }
+                                               })
+                                       }
+                               }
+                       })
+               }
+       })
+}
diff --git a/traffic_ops/testing/api/v5/crconfig_test.go 
b/traffic_ops/testing/api/v5/crconfig_test.go
deleted file mode 100644
index 3e03bf3b16..0000000000
--- a/traffic_ops/testing/api/v5/crconfig_test.go
+++ /dev/null
@@ -1,356 +0,0 @@
-package v5
-
-/*
-
-   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 (
-       "net/http"
-       "strconv"
-       "strings"
-       "testing"
-       "time"
-
-       "github.com/apache/trafficcontrol/lib/go-tc"
-       "github.com/apache/trafficcontrol/lib/go-util"
-       client "github.com/apache/trafficcontrol/traffic_ops/v5-client"
-       toclient "github.com/apache/trafficcontrol/traffic_ops/v5-client"
-)
-
-func TestCRConfig(t *testing.T) {
-       WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
ProfileParameters, Statuses, Divisions, Regions, PhysLocations, CacheGroups, 
Servers, Topologies, ServiceCategories, DeliveryServices}, func() {
-               UpdateTestCRConfigSnapshot(t)
-               MonitoringConfig(t)
-               SnapshotTestCDNbyName(t)
-               SnapshotTestCDNbyInvalidName(t)
-               SnapshotTestCDNbyID(t)
-               SnapshotTestCDNbyInvalidID(t)
-               SnapshotWithReadOnlyUser(t)
-       })
-}
-
-func SnapshotWithReadOnlyUser(t *testing.T) {
-       if len(testData.CDNs) == 0 {
-               t.Fatalf("expected one or more valid CDNs, but got none")
-       }
-
-       tenantOpts := client.NewRequestOptions()
-       tenantOpts.QueryParameters.Set("name", "root")
-       resp, _, err := TOSession.GetTenants(tenantOpts)
-       if err != nil {
-               t.Fatalf("couldn't get the root tenant ID: %v - alerts: %+v", 
err, resp.Alerts)
-       }
-       if len(resp.Response) != 1 {
-               t.Fatalf("Expected exactly one Tenant to have the name 'root', 
found: %d", len(resp.Response))
-       }
-
-       toReqTimeout := time.Second * 
time.Duration(Config.Default.Session.TimeoutInSecs)
-       user := tc.UserV4{
-               Username:         "test_user_tm",
-               RegistrationSent: new(time.Time),
-               LocalPassword:    util.StrPtr("test_pa$$word"),
-               Role:             "read-only",
-       }
-       user.Email = util.StrPtr("[email protected]")
-       user.TenantID = resp.Response[0].ID
-       user.FullName = util.StrPtr("firstName LastName")
-
-       u, _, err := TOSession.CreateUser(user, client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("could not create read-only user: %v - alerts: %+v", 
err, u.Alerts)
-       }
-       client, _, err := toclient.LoginWithAgent(TOSession.URL, 
"test_user_tm", "test_pa$$word", true, "to-api-v5-client-tests/tenant4user", 
true, toReqTimeout)
-       if err != nil {
-               t.Fatalf("failed to log in with test_user: %v", err.Error())
-       }
-       opts := toclient.NewRequestOptions()
-       opts.QueryParameters.Set("cdn", testData.CDNs[0].Name)
-       _, reqInf, err := client.SnapshotCRConfig(opts)
-       if err == nil {
-               t.Errorf("expected to get an error about a read-only client 
trying to snap a CDN, but got none")
-       }
-       if reqInf.StatusCode != http.StatusForbidden {
-               t.Errorf("expected a 403 forbidden status code, but got %d", 
reqInf.StatusCode)
-       }
-       ForceDeleteTestUsersByUsernames(t, []string{"test_user_tm"})
-}
-
-func UpdateTestCRConfigSnapshot(t *testing.T) {
-       if len(testData.CDNs) < 1 {
-               t.Error("no cdn test data")
-       }
-       cdn := testData.CDNs[0].Name
-
-       tmURLParamName := "tm.url"
-       tmURLExpected := "crconfig.tm.url.test.invalid"
-       paramAlerts, _, err := TOSession.CreateParameter(tc.Parameter{
-               ConfigFile: "global",
-               Name:       tmURLParamName,
-               Value:      "https://crconfig.tm.url.test.invalid";,
-       }, client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("GetCRConfig CreateParameter error expected: nil, 
actual: %v - alerts: %+v", err, paramAlerts.Alerts)
-       }
-
-       // create an ANY_MAP DS assignment to verify that it doesn't show up in 
the CRConfig
-       resp, _, err := TOSession.GetServers(client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("GetServers err expected nil, actual: %v - alerts: 
%+v", err, resp.Alerts)
-       }
-       servers := resp.Response
-       serverID := 0
-       for _, server := range servers {
-               if server.CDNName == nil || server.ID == nil {
-                       t.Error("Traffic Ops returned a representation for a 
servver with null or undefined ID and/or CDN name")
-                       continue
-               }
-               if server.Type == "EDGE" && *server.CDNName == "cdn1" {
-                       serverID = *server.ID
-                       break
-               }
-       }
-       if serverID == 0 {
-               t.Errorf("GetServers expected EDGE server in cdn1, actual: 
%+v", servers)
-       }
-       opts := client.NewRequestOptions()
-       opts.QueryParameters.Set("xmlId", "anymap-ds")
-       res, _, err := TOSession.GetDeliveryServices(opts)
-       if err != nil {
-               t.Errorf("Unexpected error getting Delivery Services filtered 
by XMLID 'anymap-ds': %v - alerts: %+v", err, res.Alerts)
-       }
-       if len(res.Response) != 1 {
-               t.Fatalf("Expected exactly 1 Delivery Service to exist with 
XMLID 'anymap-ds', actual %d", len(res.Response))
-       }
-       if res.Response[0].ID == nil {
-               t.Fatal("Traffic Ops returned a representation of Delivery 
Service 'anymap-ds' that had a null or undefined ID")
-       }
-       anymapDSID := *res.Response[0].ID
-       alerts, _, err := TOSession.CreateDeliveryServiceServers(anymapDSID, 
[]int{serverID}, true, client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("Unexpected error assigning server #%d to Delivery 
Service #%d: %v - alerts: %+v", serverID, anymapDSID, err, alerts.Alerts)
-       }
-
-       opts = client.NewRequestOptions()
-       opts.QueryParameters.Set("cdn", cdn)
-       snapshotResp, _, err := TOSession.SnapshotCRConfig(opts)
-       if err != nil {
-               t.Errorf("Unexpected error taking Snapshot of CDN '%s': %v - 
alerts: %+v", cdn, err, snapshotResp.Alerts)
-       }
-       crcResp, _, err := TOSession.GetCRConfig(cdn, client.RequestOptions{})
-       if err != nil {
-               t.Errorf("Unexpected error retrieving Snapshot of CDN '%s': %v 
- alerts: %+v", cdn, err, crcResp.Alerts)
-       }
-       crc := crcResp.Response
-
-       if len(crc.DeliveryServices) == 0 {
-               t.Error("GetCRConfig len(crc.DeliveryServices) expected: >0, 
actual: 0")
-       }
-
-       // verify no ANY_MAP delivery services are in the CRConfig
-       for ds := range crc.DeliveryServices {
-               if ds == "anymap-ds" {
-                       t.Error("found ANY_MAP delivery service in CRConfig 
deliveryServices")
-               }
-       }
-       for server := range crc.ContentServers {
-               for ds := range crc.ContentServers[server].DeliveryServices {
-                       if ds == "anymap-ds" {
-                               t.Error("found ANY_MAP delivery service in 
contentServers deliveryServices mapping")
-                       }
-               }
-       }
-
-       if crc.Stats.TMPath != nil {
-               t.Errorf("Expected no TMPath in APIv4, but it was: %v", 
*crc.Stats.TMPath)
-       }
-
-       if crc.Stats.TMHost == nil {
-               t.Errorf("GetCRConfig crc.Stats.Path expected: 
'"+tmURLExpected+"', actual: %+v", crc.Stats.TMHost)
-       } else if *crc.Stats.TMHost != tmURLExpected {
-               t.Errorf("GetCRConfig crc.Stats.Path expected: 
'"+tmURLExpected+"', actual: %+v", *crc.Stats.TMHost)
-       }
-
-       opts.QueryParameters.Del("cdn")
-       opts.QueryParameters.Set("name", tmURLParamName)
-       paramResp, _, err := TOSession.GetParameters(opts)
-       if err != nil {
-               t.Fatalf("cannot get Parameter by name '%s': %v - alerts: %+v", 
tmURLParamName, err, paramResp.Alerts)
-       }
-       if len(paramResp.Response) == 0 {
-               t.Fatal("CRConfig create tm.url parameter was successful, but 
GET returned no parameters")
-       }
-       tmURLParam := paramResp.Response[0]
-
-       delResp, _, err := TOSession.DeleteParameter(tmURLParam.ID, 
client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("cannot DELETE Parameter by name: %v - %v", err, 
delResp)
-       }
-
-       crcResp, _, err = TOSession.GetCRConfigNew(cdn, client.RequestOptions{})
-       if err != nil {
-               t.Errorf("Unexpected error getting new Snapshot for CDN '%s': 
%v - alerts: %+v", cdn, err, crcResp.Alerts)
-       }
-       crcNew := crcResp.Response
-
-       if len(crcNew.DeliveryServices) != len(crc.DeliveryServices) {
-               t.Errorf("/new endpoint returned a different snapshot. 
DeliveryServices length expected %v, was %v", len(crc.DeliveryServices), 
len(crcNew.DeliveryServices))
-       }
-
-       if *crcNew.Stats.TMHost != "" {
-               t.Errorf("update to snapshot not captured in /new endpoint")
-       }
-}
-
-func MonitoringConfig(t *testing.T) {
-       if len(testData.CDNs) < 1 {
-               t.Fatalf("no cdn test data")
-       }
-       const cdnName = "cdn1"
-       const profileName = "EDGE1"
-       opts := client.NewRequestOptions()
-       opts.QueryParameters.Set("name", cdnName)
-       cdns, _, err := TOSession.GetCDNs(opts)
-       if err != nil {
-               t.Fatalf("getting CDNs with name '%s': %v - alerts: %+v", 
cdnName, err, cdns.Alerts)
-       }
-       if len(cdns.Response) != 1 {
-               t.Fatalf("expected exactly 1 CDN named '%s' but found %d CDNs", 
cdnName, len(cdns.Response))
-       }
-       opts.QueryParameters.Set("name", profileName)
-       profiles, _, err := TOSession.GetProfiles(opts)
-       if err != nil {
-               t.Fatalf("getting Profiles with name '%s': %v - alerts: %+v", 
profileName, err, profiles.Alerts)
-       }
-       if len(profiles.Response) != 1 {
-               t.Fatalf("expected exactly 1 Profiles named %s but found %d 
Profiles", profileName, len(profiles.Response))
-       }
-       parameters, _, err := TOSession.GetParametersByProfileName(profileName, 
client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("getting Parameters by Profile name '%s': %v - alerts: 
%+v", profileName, err, parameters.Alerts)
-       }
-       parameterMap := map[string]tc.HealthThreshold{}
-       parameterFound := map[string]bool{}
-       const thresholdPrefixLength = len(tc.ThresholdPrefix)
-       for _, parameter := range parameters.Response {
-               if !strings.HasPrefix(parameter.Name, tc.ThresholdPrefix) {
-                       continue
-               }
-               parameterName := parameter.Name[thresholdPrefixLength:]
-               parameterMap[parameterName], err = 
tc.StrToThreshold(parameter.Value)
-               if err != nil {
-                       t.Fatalf("converting string '%s' to HealthThreshold: 
%s", parameter.Value, err.Error())
-               }
-               parameterFound[parameterName] = false
-       }
-       const expectedThresholdParameters = 3
-       if len(parameterMap) != expectedThresholdParameters {
-               t.Fatalf("expected Profile '%s' to contain %d Parameters with 
names starting with '%s' but %d such Parameters were found", profileName, 
expectedThresholdParameters, tc.ThresholdPrefix, len(parameterMap))
-       }
-       tmConfig, _, err := TOSession.GetTrafficMonitorConfig(cdnName, 
client.RequestOptions{})
-       if err != nil {
-               t.Fatalf("getting Traffic Monitor Config: %v - alerts: %+v", 
err, tmConfig.Alerts)
-       }
-       profileFound := false
-       var profile tc.TMProfile
-       for _, profile = range tmConfig.Response.Profiles {
-               if profile.Name == profileName {
-                       profileFound = true
-                       break
-               }
-       }
-       if !profileFound {
-               t.Fatalf("Traffic Monitor Config contained no Profile named 
'%s", profileName)
-       }
-       for parameterName, value := range profile.Parameters.Thresholds {
-               if _, ok := parameterFound[parameterName]; !ok {
-                       t.Fatalf("unexpected Threshold Parameter name '%s' 
found in Profile '%s' in Traffic Monitor Config", parameterName, profileName)
-               }
-               parameterFound[parameterName] = true
-               if parameterMap[parameterName].String() != value.String() {
-                       t.Fatalf("expected '%s' but received '%s' for Threshold 
Parameter '%s' in Profile '%s' in Traffic Monitor Config", 
parameterMap[parameterName].String(), value.String(), parameterName, 
profileName)
-               }
-       }
-       missingParameters := []string{}
-       for parameterName, found := range parameterFound {
-               if !found {
-                       missingParameters = append(missingParameters, 
parameterName)
-               }
-       }
-       if len(missingParameters) != 0 {
-               t.Fatalf("Threshold parameters defined for Profile '%s' but 
missing for Profile '%s' in Traffic Monitor Config: %s", profileName, 
profileName, strings.Join(missingParameters, ", "))
-       }
-}
-
-func SnapshotTestCDNbyName(t *testing.T) {
-       if len(testData.CDNs) < 1 {
-               t.Fatal("Need at least one CDN to test taking CDN Snapshot 
using CDN name")
-       }
-       firstCDN := testData.CDNs[0].Name
-       opts := client.NewRequestOptions()
-       opts.QueryParameters.Set("cdn", firstCDN)
-       resp, _, err := TOSession.SnapshotCRConfig(opts)
-       if err != nil {
-               t.Errorf("failed to snapshot CDN '%s' by name: %v - alerts: 
%+v", firstCDN, err, resp.Alerts)
-       }
-}
-
-// Note that this test will break if anyone adds a CDN to the fixture data with
-// the name "cdn-invalid".
-func SnapshotTestCDNbyInvalidName(t *testing.T) {
-       invalidCDNName := "cdn-invalid"
-       opts := client.NewRequestOptions()
-       opts.QueryParameters.Set("cdn", invalidCDNName)
-       _, _, err := TOSession.SnapshotCRConfig(opts)
-       if err == nil {
-               t.Errorf("snapshot occurred without error on (presumed) invalid 
CDN '%s'", invalidCDNName)
-       }
-}
-
-func SnapshotTestCDNbyID(t *testing.T) {
-       if len(testData.CDNs) < 1 {
-               t.Fatal("Need at least one CDN to test Snapshotting CDNs")
-       }
-       firstCDNName := testData.CDNs[0].Name
-       opts := client.NewRequestOptions()
-       opts.QueryParameters.Set("name", firstCDNName)
-       // Retrieve the CDN by name so we can get the id for the snapshot
-       resp, _, err := TOSession.GetCDNs(opts)
-       if err != nil {
-               t.Errorf("cannot get CDN '%s': %v - alerts: %+v", firstCDNName, 
err, resp.Alerts)
-       }
-       if len(resp.Response) != 1 {
-               t.Fatalf("Expected exactly one CDN to exist with name '%s', 
found: %d", firstCDNName, len(resp.Response))
-       }
-       remoteCDNID := resp.Response[0].ID
-       opts.QueryParameters.Del("name")
-       opts.QueryParameters.Set("cdnID", strconv.Itoa(remoteCDNID))
-       alert, _, err := TOSession.SnapshotCRConfig(opts)
-       if err != nil {
-               t.Errorf("failed to snapshot CDN '%s' (#%d) by id: %v - alerts: 
%+v", firstCDNName, remoteCDNID, err, alert.Alerts)
-       }
-}
-
-// Note that this test will break in the event that 1,000,000 CDNs are created
-// in the TO instance at any time (they don't need to exist concurrently, just
-// that many successful CDN creations have to happen, even if they are
-// all immediately deleted except the 999999th one).
-func SnapshotTestCDNbyInvalidID(t *testing.T) {
-       opts := client.NewRequestOptions()
-       invalidCDNID := 999999
-       opts.QueryParameters.Set("cdnID", strconv.Itoa(invalidCDNID))
-       alert, _, err := TOSession.SnapshotCRConfig(opts)
-       if err == nil {
-               t.Errorf("snapshot occurred on (presumed) invalid CDN #%d: %v - 
alerts: %+v", invalidCDNID, err, alert.Alerts)
-       }
-}
diff --git a/traffic_ops/testing/api/v5/snapshot_test.go 
b/traffic_ops/testing/api/v5/snapshot_test.go
new file mode 100644
index 0000000000..9da1fca93d
--- /dev/null
+++ b/traffic_ops/testing/api/v5/snapshot_test.go
@@ -0,0 +1,126 @@
+package v5
+
+/*
+
+   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 (
+       "net/http"
+       "net/url"
+       "strconv"
+       "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"
+       client "github.com/apache/trafficcontrol/traffic_ops/v5-client"
+)
+
+func TestSnapshot(t *testing.T) {
+       WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, 
ServiceCategories, DeliveryServices, DeliveryServiceServerAssignments}, func() {
+
+               readOnlyUserSession := utils.CreateV5Session(t, 
Config.TrafficOps.URL, "readonlyuser", "pa$$word", 
Config.Default.Session.TimeoutInSecs)
+
+               methodTests := utils.V5TestCase{
+                       "PUT": {
+                               "VERIFY ANYMAP DELIVERY SERVICE is NOT in 
CRCONFIG": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn1"}}},
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateDeliveryServiceNotInCRConfig("cdn1", "anymap-ds"),
+                                               validateCRConfigFields("cdn1", 
map[string]interface{}{"TMPath": (*string)(nil), "TMHost": 
"crconfig.tm.url.test.invalid"})),
+                               },
+                               "OK when VALID CDN parameter": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn1"}}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                               },
+                               "OK when VALID CDNID parameter": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdnID": 
{strconv.Itoa(GetCDNID(t, "cdn1")())}}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                               },
+                               "NOT FOUND when NON-EXISTENT CDN": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn-invalid"}}},
+                                       Expectations:  
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)),
+                               },
+                               "NOT FOUND when NON-EXISTENT CDNID": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdnID": {"999999"}}},
+                                       Expectations:  
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)),
+                               },
+                               "FORBIDDEN when READ-ONLY user": {
+                                       ClientSession: readOnlyUserSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn1"}}},
+                                       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 {
+                                       switch method {
+                                       case "PUT":
+                                               t.Run(name, func(t *testing.T) {
+                                                       resp, reqInf, err := 
testCase.ClientSession.SnapshotCRConfig(testCase.RequestOpts)
+                                                       for _, check := range 
testCase.Expectations {
+                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
+                                                       }
+                                               })
+                                       }
+                               }
+                       })
+               }
+       })
+}
+
+func validateCRConfigFields(cdn string, expectedResp map[string]interface{}) 
utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, _ interface{}, _ 
tc.Alerts, _ error) {
+               snapshotResp, _, err := TOSession.GetCRConfig(cdn, 
client.RequestOptions{})
+               assert.RequireNoError(t, err, "Unexpected error retrieving 
Snapshot of CDN '%s': %v - alerts: %+v", cdn, err, snapshotResp.Alerts)
+               crconfig := snapshotResp.Response
+
+               for field, expected := range expectedResp {
+                       switch field {
+                       case "TMPath":
+                               assert.Equal(t, expected, 
crconfig.Stats.TMPath, "Expected no TMPath in APIv4, but it was: %v", 
crconfig.Stats.TMPath)
+                       case "TMHost":
+                               assert.RequireNotNil(t, crconfig.Stats.TMHost, 
"Expected Stats TM Host to not be nil.")
+                               assert.Equal(t, expected, 
*crconfig.Stats.TMHost, "Expected Stats TM Host to be %v, but got %s", 
expected, *crconfig.Stats.TMHost)
+                       default:
+                               t.Errorf("Expected field: %v, does not exist in 
response", field)
+                       }
+               }
+       }
+}
+
+func validateDeliveryServiceNotInCRConfig(cdn string, deliveryService string) 
utils.CkReqFunc {
+       return func(t *testing.T, _ toclientlib.ReqInf, _ interface{}, _ 
tc.Alerts, _ error) {
+               snapshotResp, _, err := TOSession.GetCRConfig(cdn, 
client.RequestOptions{})
+               assert.RequireNoError(t, err, "Unexpected error retrieving 
Snapshot of CDN '%s': %v - alerts: %+v", cdn, err, snapshotResp.Alerts)
+
+               for ds := range snapshotResp.Response.DeliveryServices {
+                       assert.NotEqual(t, ds, deliveryService, "Found 
unexpected delivery service: %s in CRConfig Delivery Services.", 
deliveryService)
+               }
+
+               for _, server := range snapshotResp.Response.ContentServers {
+                       for ds := range server.DeliveryServices {
+                               assert.NotEqual(t, ds, deliveryService, "Found 
unexpected delivery service: %s in CRConfig Content Servers Delivery 
Services.", deliveryService)
+                       }
+               }
+       }
+}
diff --git a/traffic_ops/testing/api/v5/tc-fixtures.json 
b/traffic_ops/testing/api/v5/tc-fixtures.json
index c3d5a9803f..3258ce828f 100644
--- a/traffic_ops/testing/api/v5/tc-fixtures.json
+++ b/traffic_ops/testing/api/v5/tc-fixtures.json
@@ -1801,6 +1801,10 @@
         {
             "xmlId": "ds2",
             "serverNames": ["atlanta-org-2"]
+        },
+        {
+            "xmlId": "anymap-ds",
+            "serverNames": ["atlanta-edge-15"]
         }
     ],
     "divisions": [
@@ -2024,8 +2028,13 @@
             "name": "testSecure",
             "secure": true,
             "value": "hidden value"
+        },
+        {
+            "configFile": "global",
+            "name": "tm.url",
+            "secure": false,
+            "value": "https://crconfig.tm.url.test.invalid";
         }
-
     ],
     "physLocations": [
         {


Reply via email to