You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by sr...@apache.org on 2022/11/02 18:09:28 UTC

[trafficcontrol] branch master updated: Refactor Snapshot Tests (#7114)

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 <14...@users.noreply.github.com>
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_tm@domain.com")
-	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_tm@domain.com")
-	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": [
         {