zrhoffman commented on code in PR #7056:
URL: https://github.com/apache/trafficcontrol/pull/7056#discussion_r977467947


##########
traffic_ops/testing/api/v3/topologies_test.go:
##########
@@ -20,316 +20,468 @@ package v3
  */
 
 import (
-       "fmt"
+       "encoding/json"
+       "net/http"
        "net/url"
-       "reflect"
-       "strconv"
-       "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"
 )
 
-type topologyTestCase struct {
-       testCaseDescription string
-       tc.Topology
-}
-
 func TestTopologies(t *testing.T) {
-       WithObjs(t, []TCObj{Types, CacheGroups, CDNs, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, Servers, ServerCapabilities, 
ServerServerCapabilities, Topologies, Tenants, DeliveryServices, 
DeliveryServicesRequiredCapabilities}, func() {
-               GetTestTopologies(t)
-               UpdateTestTopologies(t)
-               ValidationTestTopologies(t)
-               UpdateValidateTopologyORGServerCacheGroup(t)
-               EdgeParentOfEdgeSucceedsWithWarning(t)
-       })
-}
+       WithObjs(t, []TCObj{Tenants, Users, Types, CacheGroups, CDNs, 
Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, Servers, 
ServerCapabilities, ServerServerCapabilities, Topologies, ServiceCategories, 
DeliveryServices, DeliveryServicesRequiredCapabilities, 
DeliveryServiceServerAssignments}, func() {
 
-func CreateTestTopologies(t *testing.T) {
-       var (
-               postResponse *tc.TopologyResponse
-               err          error
-       )
-       for _, topology := range testData.Topologies {
-               if postResponse, _, err = TOSession.CreateTopology(topology); 
err != nil {
-                       t.Fatalf("could not CREATE topology: %v", err)
-               }
-               postResponse.Response.LastUpdated = nil
-               if !reflect.DeepEqual(topology, postResponse.Response) {
-                       t.Fatalf("Topology in response should be the same as 
the one POSTed. expected: %v\nactual: %v", topology, postResponse.Response)
+               methodTests := utils.V3TestCase{
+                       "GET": {
+                               "OK when VALID REQUEST": {
+                                       ClientSession: TOSession,
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseLengthGreaterOrEqual(1)),
+                               },
+                       },
+                       "POST": {
+                               "OK when MISSING DESCRIPTION": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  
"topology-missing-description",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                               },
+                               "BAD REQUEST when EMPTY NODES": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "topology-no-nodes",
+                                               "nodes": 
[]map[string]interface{}{},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when NODE PARENT of ITSELF": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "self-parent",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{0}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when TOO MANY PARENTS": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": "too-many-parents",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"secondaryCachegroup",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup2",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{0, 1, 2},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when EDGE_LOC PARENTS MID_LOC": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": "edge-parents-mid",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup",
+                                                               "parents":    
[]int{1},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup2",
+                                                               "parents":    
[]int{},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CYCLICAL NODES": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": "cyclical-nodes",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{1, 2},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup",
+                                                               "parents":    
[]int{2},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"secondaryCachegroup",
+                                                               "parents":    
[]int{1},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CYCLES ACROSS TOPOLOGIES": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": "cyclical-nodes-tiered",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup",
+                                                               "parents":    
[]int{1},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup2",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{0},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CYCLICAL NODES BUT EMPTY 
CACHE GROUPS": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": 
"cyclical-nodes-nontopology",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"edge-parent1",
+                                                               "parents":    
[]int{1},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"has-edge-parent1",
+                                                               "parents":    
[]int{},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when OUT-OF-BOUNDS PARENT INDEX": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "outofbounds",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{7}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CACHEGROUP DOESNT EXIST": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  
"topology-nonexistent-cachegroup",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "doesntexist", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when MISSING NAME": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "description": "missing name",
+                                               "nodes":       
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when ALREADY EXISTS": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "mso-topology",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CACHEGROUP has NO SERVERS": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "topology-empty-cg",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "noServers", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when DUPLICATE PARENTS": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": 
"topology-duplicate-parents",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{0, 0},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when ORG_LOC is CHILD NODE": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": "topology-orgloc-child",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"multiOriginCachegroup",
+                                                               "parents":    
[]int{0},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when LEAF NODE is a MID_LOC": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "topology-midloc-leaf",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "parentCachegroup", "parents": 
[]int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "WARNING LEVEL ALERT when MID PARENTING EDGE": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":        
"topology-mid-parent",
+                                               "description": "mid parent to 
edge",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{1},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup2",
+                                                               "parents":    
[]int{},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.HasAlertLevel(tc.WarnLevel.String())),
+                               },
+                       },
+                       "PUT": {
+                               "OK when VALID request": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"name": 
{"top-with-no-mids"}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":        
"top-with-no-mids-updated",
+                                               "description": "Updating 
fields",
+                                               "nodes":       
[]map[string]interface{}{{"cachegroup": "cachegroup2", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateTopologiesUpdateCreateFields(map[string]interface{}{"Name": 
"top-with-no-mids-updated", "Description": "Updating fields"})),
+                               },
+                               "BAD REQUEST when OUT-OF-BOUNDS PARENT INDEX": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"name": 
{"another-topology"}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  
"topology-invalid-parent",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{100}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CACHEGROUP has NO SERVERS": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"name": 
{"another-topology"}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "topology-empty-cg",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "noServers", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CACHEGROUP SERVERS DO NOT 
HAVE REQUIRED CAPABILITIES": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"name": 
{"top-for-ds-req"}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "top-for-ds-req",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when NODE PARENT of ITSELF": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"name": 
{"another-topology"}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "another-topology",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{0}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CACHEGROUP HAS NO SERVERS IN 
TOPOLOGY CDN": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"name": 
{"top-used-by-cdn1-and-cdn2"}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  
"top-used-by-cdn1-and-cdn2",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cdn1-only", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when ORG_LOC is CHILD NODE": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"name": 
{"another-topology"}},
+                                       RequestBody: map[string]interface{}{
+                                               "name": "topology-orgloc-child",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"multiOriginCachegroup",
+                                                               "parents":    
[]int{0},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when LEAF NODE is a MID_LOC": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"name": 
{"another-topology"}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":        
"topology-child-midloc",
+                                               "description": "child mid_loc",
+                                               "nodes":       
[]map[string]interface{}{{"cachegroup": "parentCachegroup", "parents": 
[]int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when DUPLICATE PARENTS": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"name": 
{"another-topology"}},
+                                       RequestBody: map[string]interface{}{
+                                               "name": "topology-same-parents",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup2",
+                                                               "parents":    
[]int{0, 0},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when REMOVING ORG ASSIGNED DS": {
+                                       ClientSession: TOSession,
+                                       RequestParams: url.Values{"name": 
{"mso-topology"}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "mso-topology",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "topology-edge-cg-01", "parents": 
[]int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                       },
                }
-       }
-}
 
-func GetTestTopologies(t *testing.T) {
-       if len(testData.Topologies) < 1 {
-               t.Fatalf("test data has no topologies, can't test")
-       }
-       topos, _, err := TOSession.GetTopologiesWithHdr(nil)
-       if err != nil {
-               t.Fatalf("expected GET error to be nil, actual: %v", err)
-       }
-       if len(topos) != len(testData.Topologies) {
-               t.Errorf("expected topologies GET to return %v topologies, 
actual %v", len(testData.Topologies), len(topos))
-       }
-}
+               for method, testCases := range methodTests {
+                       t.Run(method, func(t *testing.T) {
+                               for name, testCase := range testCases {
+                                       topology := tc.Topology{}
 
-func EdgeParentOfEdgeSucceedsWithWarning(t *testing.T) {
-       testCase := topologyTestCase{testCaseDescription: "an edge parenting a 
mid", Topology: tc.Topology{
-               Name:        "edge-parent-of-edge",
-               Description: "An edge is a parent, which is technically valid, 
but we will warn the user in case it was a mistake",
-               Nodes: []tc.TopologyNode{
-                       {Cachegroup: "cachegroup1", Parents: []int{1}},
-                       {Cachegroup: "cachegroup2", Parents: []int{}},
-               }}}
-       response, _, err := TOSession.CreateTopology(testCase.Topology)
-       if err != nil {
-               t.Fatalf("expected POST with %v to succeed, actual: nil", 
testCase.testCaseDescription)
-       }
-       containsWarning := false
-       for _, alert := range response.Alerts.Alerts {
-               if alert.Level == "warning" {
-                       containsWarning = true
+                                       if testCase.RequestBody != nil {
+                                               dat, err := 
json.Marshal(testCase.RequestBody)
+                                               assert.NoError(t, err, "Error 
occurred when marshalling request body: %v", err)
+                                               err = json.Unmarshal(dat, 
&topology)
+                                               assert.NoError(t, err, "Error 
occurred when unmarshalling request body: %v", err)
+                                       }
+
+                                       switch method {

Review Comment:
   needs a default case



##########
traffic_ops/testing/api/v4/topologies_test.go:
##########
@@ -20,1393 +20,552 @@ package v4
  */
 
 import (
-       "fmt"
+       "encoding/json"
        "net/http"
        "net/url"
-       "reflect"
-       "strconv"
-       "strings"
        "testing"
        "time"
 
        "github.com/apache/trafficcontrol/lib/go-rfc"
        "github.com/apache/trafficcontrol/lib/go-tc"
-       "github.com/apache/trafficcontrol/lib/go-util"
+       "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"
-       toclient "github.com/apache/trafficcontrol/traffic_ops/v4-client"
 )
 
-type topologyTestCase struct {
-       testCaseDescription string
-       tc.Topology
-}
-
 func TestTopologies(t *testing.T) {
-       WithObjs(t, []TCObj{Types, CacheGroups, CDNs, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, Servers, ServerCapabilities, 
ServerServerCapabilities, Topologies, Tenants, ServiceCategories, 
DeliveryServices, DeliveryServicesRequiredCapabilities}, func() {
-               GetTestTopologies(t)
-               currentTime := time.Now().UTC().Add(-5 * time.Second)
-               rfcTime := currentTime.Format(time.RFC1123)
-               var header http.Header
-               header = make(map[string][]string)
-               header.Set(rfc.IfModifiedSince, rfcTime)
-               header.Set(rfc.IfUnmodifiedSince, rfcTime)
-               UpdateTestTopologies(t)
-               UpdateTestTopologiesWithHeaders(t, header)
-               header = make(map[string][]string)
-               etag := rfc.ETag(currentTime)
-               header.Set(rfc.IfMatch, etag)
-               UpdateTestTopologiesWithHeaders(t, header)
-               ValidationTestTopologies(t)
-               UpdateValidateTopologyORGServerCacheGroup(t)
-               EdgeParentOfEdgeSucceedsWithWarning(t)
-               UpdateTopologyName(t)
-               GetTopologyWithNonExistentName(t)
-               CreateTopologyWithInvalidCacheGroup(t)
-               CreateTopologyWithInvalidParentNumber(t)
-               CreateTopologyWithoutDescription(t)
-               CreateTopologyWithoutName(t)
-               CreateTopologyWithoutServers(t)
-               CreateTopologyWithDuplicateParents(t)
-               CreateTopologyWithNodeAsParentOfItself(t)
-               CreateTopologyWithOrgLocAsChildNode(t)
-               CreateTopologyWithExistingName(t)
-               CreateTopologyWithMidLocTypeWithoutChild(t)
-               CRUDTopologyReadOnlyUser(t)
-               UpdateTopologyWithCachegroupAssignedToBecomeParentOfItself(t)
-               UpdateTopologyWithNoServers(t)
-               UpdateTopologyWithInvalidParentNumber(t)
-               UpdateTopologyWithMidLocTypeWithoutChild(t)
-               UpdateTopologyWithOrgLocAsChildNode(t)
-               UpdateTopologyWithSameParentAndSecondaryParent(t)
-               DeleteTopologyWithNonExistentName(t)
-               DeleteTopologyBeingUsedByDeliveryService(t)
-       })
-}
-
-func CreateTestTopologies(t *testing.T) {
-       for _, topology := range testData.Topologies {
-               postResponse, _, err := TOSession.CreateTopology(topology, 
toclient.RequestOptions{})
-               if err != nil {
-                       t.Fatalf("could not create Topology: %v - alerts: %+v", 
err, postResponse.Alerts)
-               }
-               postResponse.Response.LastUpdated = nil
-               if !reflect.DeepEqual(topology, postResponse.Response) {
-                       t.Fatalf("Topology in response should be the same as 
the one POSTed. expected: %v\nactual: %v", topology, postResponse.Response)
-               }
-       }
-}
-
-func GetTestTopologies(t *testing.T) {
-       if len(testData.Topologies) < 1 {
-               t.Fatalf("test data has no topologies, can't test")
-       }
-       topos, _, err := TOSession.GetTopologies(toclient.RequestOptions{})
-       if err != nil {
-               t.Fatalf("expected error to be nil, actual: %v - alerts: %+v", 
err, topos.Alerts)
-       }
-       if len(topos.Response) != len(testData.Topologies) {
-               t.Errorf("expected %d Topologies to exist in Traffic Ops, 
actual: %d", len(testData.Topologies), len(topos.Response))
-       }
-}
-
-func UpdateTestTopologiesWithHeaders(t *testing.T, header http.Header) {
-       originalName := "top-used-by-cdn1-and-cdn2"
-       newName := "blah"
-
-       // Retrieve the Topology by name so we can get the id for Update()
-       opts := toclient.NewRequestOptions()
-       opts.QueryParameters.Set("name", originalName)
-       resp, _, err := TOSession.GetTopologies(opts)
-       if err != nil {
-               t.Errorf("cannot get Topology by name '%s': %v - alerts: %+v", 
originalName, err, resp.Alerts)
-       }
-       if len(resp.Response) != 1 {
-               t.Fatalf("Expected exactly one Topology to exist with name 
'%s', found: %d", originalName, len(resp.Response))
-       }
-       resp.Response[0].Name = newName
-       _, reqInf, err := TOSession.UpdateTopology(originalName, 
resp.Response[0], toclient.RequestOptions{Header: header})
-       if err == nil {
-               t.Errorf("Expected error about Precondition Failed, got none")
-       }
-       if reqInf.StatusCode != http.StatusPreconditionFailed {
-               t.Errorf("Expected status code 412, got %v", reqInf.StatusCode)
-       }
-}
-
-func EdgeParentOfEdgeSucceedsWithWarning(t *testing.T) {
-       testCase := topologyTestCase{testCaseDescription: "an edge parenting a 
mid", Topology: tc.Topology{
-               Name:        "edge-parent-of-edge",
-               Description: "An edge is a parent, which is technically valid, 
but we will warn the user in case it was a mistake",
-               Nodes: []tc.TopologyNode{
-                       {Cachegroup: "cachegroup1", Parents: []int{1}},
-                       {Cachegroup: "cachegroup2", Parents: []int{}},
-               }}}
-       response, _, err := TOSession.CreateTopology(testCase.Topology, 
toclient.RequestOptions{})
-       if err != nil {
-               t.Fatalf("Unexpected error creating Topology for '%s': %v - 
alerts: %+v", testCase.testCaseDescription, err, response.Alerts)
-       }
-       containsWarning := false
-       for _, alert := range response.Alerts.Alerts {
-               if alert.Level == tc.WarnLevel.String() {
-                       containsWarning = true
+       WithObjs(t, []TCObj{Tenants, Users, Types, CacheGroups, CDNs, 
Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, Servers, 
ServerCapabilities, ServerServerCapabilities, Topologies, ServiceCategories, 
DeliveryServices, DeliveryServicesRequiredCapabilities, 
DeliveryServiceServerAssignments}, func() {
+
+               readOnlyUserSession := utils.CreateV4Session(t, 
Config.TrafficOps.URL, "readonlyuser", "pa$$word", 
Config.Default.Session.TimeoutInSecs)
+
+               currentTime := time.Now().UTC().Add(-15 * time.Second)
+               currentTimeRFC := currentTime.Format(time.RFC1123)
+               tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123)
+
+               methodTests := utils.V4TestCase{
+                       "GET": {
+                               "NOT MODIFIED when NO CHANGES made": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)),
+                               },
+                               "OK when CHANGES made": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: 
{currentTimeRFC}}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                               },
+                               "OK when READ ONLY USER": {
+                                       ClientSession: readOnlyUserSession,
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseLengthGreaterOrEqual(1)),
+                               },
+                               "EMPTY RESPONSE when TOPOLOGY DOESNT EXIST": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"non-existent-topology"}}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseHasLength(0)),
+                               },
+                       },
+                       "POST": {
+                               "OK when MISSING DESCRIPTION": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  
"topology-missing-description",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                               },
+                               "BAD REQUEST when EMPTY NODES": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "topology-no-nodes",
+                                               "nodes": 
[]map[string]interface{}{},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when NODE PARENT of ITSELF": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "self-parent",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{0}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when TOO MANY PARENTS": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": "too-many-parents",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"secondaryCachegroup",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup2",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{0, 1, 2},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when EDGE_LOC PARENTS MID_LOC": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": "edge-parents-mid",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup",
+                                                               "parents":    
[]int{1},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup2",
+                                                               "parents":    
[]int{},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CYCLICAL NODES": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": "cyclical-nodes",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{1, 2},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup",
+                                                               "parents":    
[]int{2},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"secondaryCachegroup",
+                                                               "parents":    
[]int{1},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CYCLES ACROSS TOPOLOGIES": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": "cyclical-nodes-tiered",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup",
+                                                               "parents":    
[]int{1},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup2",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{0},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CYCLICAL NODES BUT EMPTY 
CACHE GROUPS": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": 
"cyclical-nodes-nontopology",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"edge-parent1",
+                                                               "parents":    
[]int{1},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"has-edge-parent1",
+                                                               "parents":    
[]int{},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when OUT-OF-BOUNDS PARENT INDEX": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "outofbounds",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{7}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CACHEGROUP DOESNT EXIST": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  
"topology-nonexistent-cachegroup",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "doesntexist", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when MISSING NAME": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "description": "missing name",
+                                               "nodes":       
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when ALREADY EXISTS": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "mso-topology",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CACHEGROUP has NO SERVERS": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "topology-empty-cg",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "noServers", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when DUPLICATE PARENTS": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": 
"topology-duplicate-parents",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{0, 0},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when ORG_LOC is CHILD NODE": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": "topology-orgloc-child",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"multiOriginCachegroup",
+                                                               "parents":    
[]int{0},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when LEAF NODE is a MID_LOC": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "topology-midloc-leaf",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "parentCachegroup", "parents": 
[]int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "WARNING LEVEL ALERT when MID PARENTING EDGE": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":        
"topology-mid-parent",
+                                               "description": "mid parent to 
edge",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{1},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup2",
+                                                               "parents":    
[]int{},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.HasAlertLevel(tc.WarnLevel.String())),
+                               },
+                               "FORBIDDEN when READ-ONLY USER": {
+                                       ClientSession: readOnlyUserSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "topology-ro",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)),
+                               },
+                       },
+                       "PUT": {
+                               "OK when VALID request": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"top-with-no-mids"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":        
"top-with-no-mids-updated",
+                                               "description": "Updating 
fields",
+                                               "nodes":       
[]map[string]interface{}{{"cachegroup": "cachegroup2", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateTopologiesUpdateCreateFields(map[string]interface{}{"Name": 
"top-with-no-mids-updated", "Description": "Updating fields"})),
+                               },
+                               "BAD REQUEST when OUT-OF-BOUNDS PARENT INDEX": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"another-topology"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  
"topology-invalid-parent",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{100}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CACHEGROUP has NO SERVERS": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"another-topology"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "topology-empty-cg",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "noServers", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CACHEGROUP SERVERS DO NOT 
HAVE REQUIRED CAPABILITIES": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": {"top-for-ds-req"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "top-for-ds-req",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when NODE PARENT of ITSELF": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"another-topology"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "another-topology",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{0}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CACHEGROUP HAS NO SERVERS IN 
TOPOLOGY CDN": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"top-used-by-cdn1-and-cdn2"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  
"top-used-by-cdn1-and-cdn2",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cdn1-only", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when ORG_LOC is CHILD NODE": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"another-topology"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name": "topology-orgloc-child",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"multiOriginCachegroup",
+                                                               "parents":    
[]int{0},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when LEAF NODE is a MID_LOC": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"another-topology"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":        
"topology-child-midloc",
+                                               "description": "child mid_loc",
+                                               "nodes":       
[]map[string]interface{}{{"cachegroup": "parentCachegroup", "parents": 
[]int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when DUPLICATE PARENTS": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"another-topology"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name": "topology-same-parents",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup2",
+                                                               "parents":    
[]int{0, 0},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when REMOVING ORG ASSIGNED DS": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": {"mso-topology"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "mso-topology",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "topology-edge-cg-01", "parents": 
[]int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "FORBIDDEN when READ-ONLY USER": {
+                                       ClientSession: readOnlyUserSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"another-topology"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "topology-ro",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)),
+                               },
+                               "PRECONDITION FAILED when updating with IMS & 
IUS Headers": {
+                                       ClientSession: TOSession,
+                                       RequestOpts: client.RequestOptions{
+                                               QueryParameters: 
url.Values{"name": {"another-topology"}},
+                                               Header:          
http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}},
+                                       },
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "another-topology",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), 
utils.HasStatus(http.StatusPreconditionFailed)),
+                               },
+                               "PRECONDITION FAILED when updating with IFMATCH 
ETAG Header": {
+                                       ClientSession: TOSession,
+                                       RequestOpts: client.RequestOptions{
+                                               QueryParameters: 
url.Values{"name": {"another-topology"}},
+                                               Header:          
http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}},
+                                       },
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "another-topology",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), 
utils.HasStatus(http.StatusPreconditionFailed)),
+                               },
+                       },
+                       "DELETE": {
+                               "BAD REQUEST when TOPOLOGY DOESNT EXIST": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"non-existent-topology"}}},
+                                       Expectations:  
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when TOPOLOGY IN USE by DELIVERY 
SERVICE": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": {"mso-topology"}}},
+                                       Expectations:  
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "FORBIDDEN when READ-ONLY USER": {
+                                       ClientSession: readOnlyUserSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": {"mso-topology"}}},
+                                       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 {
+                                       topology := tc.Topology{}
+
+                                       if testCase.RequestBody != nil {
+                                               dat, err := 
json.Marshal(testCase.RequestBody)
+                                               assert.NoError(t, err, "Error 
occurred when marshalling request body: %v", err)
+                                               err = json.Unmarshal(dat, 
&topology)
+                                               assert.NoError(t, err, "Error 
occurred when unmarshalling request body: %v", err)
+                                       }
+
+                                       switch method {

Review Comment:
   needs a default case



##########
traffic_ops/testing/api/v5/topologies_test.go:
##########
@@ -20,1393 +20,552 @@ package v5
  */
 
 import (
-       "fmt"
+       "encoding/json"
        "net/http"
        "net/url"
-       "reflect"
-       "strconv"
-       "strings"
        "testing"
        "time"
 
        "github.com/apache/trafficcontrol/lib/go-rfc"
        "github.com/apache/trafficcontrol/lib/go-tc"
-       "github.com/apache/trafficcontrol/lib/go-util"
+       "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"
-       toclient "github.com/apache/trafficcontrol/traffic_ops/v5-client"
 )
 
-type topologyTestCase struct {
-       testCaseDescription string
-       tc.Topology
-}
-
 func TestTopologies(t *testing.T) {
-       WithObjs(t, []TCObj{Types, CacheGroups, CDNs, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, Servers, ServerCapabilities, 
ServerServerCapabilities, Topologies, Tenants, ServiceCategories, 
DeliveryServices, DeliveryServicesRequiredCapabilities}, func() {
-               GetTestTopologies(t)
-               currentTime := time.Now().UTC().Add(-5 * time.Second)
-               rfcTime := currentTime.Format(time.RFC1123)
-               var header http.Header
-               header = make(map[string][]string)
-               header.Set(rfc.IfModifiedSince, rfcTime)
-               header.Set(rfc.IfUnmodifiedSince, rfcTime)
-               UpdateTestTopologies(t)
-               UpdateTestTopologiesWithHeaders(t, header)
-               header = make(map[string][]string)
-               etag := rfc.ETag(currentTime)
-               header.Set(rfc.IfMatch, etag)
-               UpdateTestTopologiesWithHeaders(t, header)
-               ValidationTestTopologies(t)
-               UpdateValidateTopologyORGServerCacheGroup(t)
-               EdgeParentOfEdgeSucceedsWithWarning(t)
-               UpdateTopologyName(t)
-               GetTopologyWithNonExistentName(t)
-               CreateTopologyWithInvalidCacheGroup(t)
-               CreateTopologyWithInvalidParentNumber(t)
-               CreateTopologyWithoutDescription(t)
-               CreateTopologyWithoutName(t)
-               CreateTopologyWithoutServers(t)
-               CreateTopologyWithDuplicateParents(t)
-               CreateTopologyWithNodeAsParentOfItself(t)
-               CreateTopologyWithOrgLocAsChildNode(t)
-               CreateTopologyWithExistingName(t)
-               CreateTopologyWithMidLocTypeWithoutChild(t)
-               CRUDTopologyReadOnlyUser(t)
-               UpdateTopologyWithCachegroupAssignedToBecomeParentOfItself(t)
-               UpdateTopologyWithNoServers(t)
-               UpdateTopologyWithInvalidParentNumber(t)
-               UpdateTopologyWithMidLocTypeWithoutChild(t)
-               UpdateTopologyWithOrgLocAsChildNode(t)
-               UpdateTopologyWithSameParentAndSecondaryParent(t)
-               DeleteTopologyWithNonExistentName(t)
-               DeleteTopologyBeingUsedByDeliveryService(t)
-       })
-}
-
-func CreateTestTopologies(t *testing.T) {
-       for _, topology := range testData.Topologies {
-               postResponse, _, err := TOSession.CreateTopology(topology, 
toclient.RequestOptions{})
-               if err != nil {
-                       t.Fatalf("could not create Topology: %v - alerts: %+v", 
err, postResponse.Alerts)
-               }
-               postResponse.Response.LastUpdated = nil
-               if !reflect.DeepEqual(topology, postResponse.Response) {
-                       t.Fatalf("Topology in response should be the same as 
the one POSTed. expected: %v\nactual: %v", topology, postResponse.Response)
-               }
-       }
-}
-
-func GetTestTopologies(t *testing.T) {
-       if len(testData.Topologies) < 1 {
-               t.Fatalf("test data has no topologies, can't test")
-       }
-       topos, _, err := TOSession.GetTopologies(toclient.RequestOptions{})
-       if err != nil {
-               t.Fatalf("expected error to be nil, actual: %v - alerts: %+v", 
err, topos.Alerts)
-       }
-       if len(topos.Response) != len(testData.Topologies) {
-               t.Errorf("expected %d Topologies to exist in Traffic Ops, 
actual: %d", len(testData.Topologies), len(topos.Response))
-       }
-}
-
-func UpdateTestTopologiesWithHeaders(t *testing.T, header http.Header) {
-       originalName := "top-used-by-cdn1-and-cdn2"
-       newName := "blah"
-
-       // Retrieve the Topology by name so we can get the id for Update()
-       opts := toclient.NewRequestOptions()
-       opts.QueryParameters.Set("name", originalName)
-       resp, _, err := TOSession.GetTopologies(opts)
-       if err != nil {
-               t.Errorf("cannot get Topology by name '%s': %v - alerts: %+v", 
originalName, err, resp.Alerts)
-       }
-       if len(resp.Response) != 1 {
-               t.Fatalf("Expected exactly one Topology to exist with name 
'%s', found: %d", originalName, len(resp.Response))
-       }
-       resp.Response[0].Name = newName
-       _, reqInf, err := TOSession.UpdateTopology(originalName, 
resp.Response[0], toclient.RequestOptions{Header: header})
-       if err == nil {
-               t.Errorf("Expected error about Precondition Failed, got none")
-       }
-       if reqInf.StatusCode != http.StatusPreconditionFailed {
-               t.Errorf("Expected status code 412, got %v", reqInf.StatusCode)
-       }
-}
-
-func EdgeParentOfEdgeSucceedsWithWarning(t *testing.T) {
-       testCase := topologyTestCase{testCaseDescription: "an edge parenting a 
mid", Topology: tc.Topology{
-               Name:        "edge-parent-of-edge",
-               Description: "An edge is a parent, which is technically valid, 
but we will warn the user in case it was a mistake",
-               Nodes: []tc.TopologyNode{
-                       {Cachegroup: "cachegroup1", Parents: []int{1}},
-                       {Cachegroup: "cachegroup2", Parents: []int{}},
-               }}}
-       response, _, err := TOSession.CreateTopology(testCase.Topology, 
toclient.RequestOptions{})
-       if err != nil {
-               t.Fatalf("Unexpected error creating Topology for '%s': %v - 
alerts: %+v", testCase.testCaseDescription, err, response.Alerts)
-       }
-       containsWarning := false
-       for _, alert := range response.Alerts.Alerts {
-               if alert.Level == tc.WarnLevel.String() {
-                       containsWarning = true
+       WithObjs(t, []TCObj{Tenants, Users, Types, CacheGroups, CDNs, 
Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, Servers, 
ServerCapabilities, ServerServerCapabilities, Topologies, ServiceCategories, 
DeliveryServices, DeliveryServicesRequiredCapabilities, 
DeliveryServiceServerAssignments}, func() {
+
+               readOnlyUserSession := utils.CreateV5Session(t, 
Config.TrafficOps.URL, "readonlyuser", "pa$$word", 
Config.Default.Session.TimeoutInSecs)
+
+               currentTime := time.Now().UTC().Add(-15 * time.Second)
+               currentTimeRFC := currentTime.Format(time.RFC1123)
+               tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123)
+
+               methodTests := utils.V5TestCase{
+                       "GET": {
+                               "NOT MODIFIED when NO CHANGES made": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)),
+                               },
+                               "OK when CHANGES made": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: 
{currentTimeRFC}}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                               },
+                               "OK when READ ONLY USER": {
+                                       ClientSession: readOnlyUserSession,
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseLengthGreaterOrEqual(1)),
+                               },
+                               "EMPTY RESPONSE when TOPOLOGY DOESNT EXIST": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"non-existent-topology"}}},
+                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseHasLength(0)),
+                               },
+                       },
+                       "POST": {
+                               "OK when MISSING DESCRIPTION": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  
"topology-missing-description",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+                               },
+                               "BAD REQUEST when EMPTY NODES": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "topology-no-nodes",
+                                               "nodes": 
[]map[string]interface{}{},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when NODE PARENT of ITSELF": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "self-parent",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{0}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when TOO MANY PARENTS": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": "too-many-parents",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"secondaryCachegroup",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup2",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{0, 1, 2},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when EDGE_LOC PARENTS MID_LOC": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": "edge-parents-mid",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup",
+                                                               "parents":    
[]int{1},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup2",
+                                                               "parents":    
[]int{},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CYCLICAL NODES": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": "cyclical-nodes",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{1, 2},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup",
+                                                               "parents":    
[]int{2},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"secondaryCachegroup",
+                                                               "parents":    
[]int{1},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CYCLES ACROSS TOPOLOGIES": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": "cyclical-nodes-tiered",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup",
+                                                               "parents":    
[]int{1},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup2",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{0},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CYCLICAL NODES BUT EMPTY 
CACHE GROUPS": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": 
"cyclical-nodes-nontopology",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"edge-parent1",
+                                                               "parents":    
[]int{1},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"has-edge-parent1",
+                                                               "parents":    
[]int{},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when OUT-OF-BOUNDS PARENT INDEX": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "outofbounds",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{7}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CACHEGROUP DOESNT EXIST": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  
"topology-nonexistent-cachegroup",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "doesntexist", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when MISSING NAME": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "description": "missing name",
+                                               "nodes":       
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when ALREADY EXISTS": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "mso-topology",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CACHEGROUP has NO SERVERS": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "topology-empty-cg",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "noServers", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when DUPLICATE PARENTS": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": 
"topology-duplicate-parents",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"parentCachegroup",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{0, 0},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when ORG_LOC is CHILD NODE": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name": "topology-orgloc-child",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"multiOriginCachegroup",
+                                                               "parents":    
[]int{0},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when LEAF NODE is a MID_LOC": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "topology-midloc-leaf",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "parentCachegroup", "parents": 
[]int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "WARNING LEVEL ALERT when MID PARENTING EDGE": {
+                                       ClientSession: TOSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":        
"topology-mid-parent",
+                                               "description": "mid parent to 
edge",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{1},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup2",
+                                                               "parents":    
[]int{},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.HasAlertLevel(tc.WarnLevel.String())),
+                               },
+                               "FORBIDDEN when READ-ONLY USER": {
+                                       ClientSession: readOnlyUserSession,
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "topology-ro",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)),
+                               },
+                       },
+                       "PUT": {
+                               "OK when VALID request": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"top-with-no-mids"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":        
"top-with-no-mids-updated",
+                                               "description": "Updating 
fields",
+                                               "nodes":       
[]map[string]interface{}{{"cachegroup": "cachegroup2", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
+                                               
validateTopologiesUpdateCreateFields(map[string]interface{}{"Name": 
"top-with-no-mids-updated", "Description": "Updating fields"})),
+                               },
+                               "BAD REQUEST when OUT-OF-BOUNDS PARENT INDEX": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"another-topology"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  
"topology-invalid-parent",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{100}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CACHEGROUP has NO SERVERS": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"another-topology"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "topology-empty-cg",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "noServers", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CACHEGROUP SERVERS DO NOT 
HAVE REQUIRED CAPABILITIES": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": {"top-for-ds-req"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "top-for-ds-req",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when NODE PARENT of ITSELF": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"another-topology"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "another-topology",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{0}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when CACHEGROUP HAS NO SERVERS IN 
TOPOLOGY CDN": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"top-used-by-cdn1-and-cdn2"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  
"top-used-by-cdn1-and-cdn2",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cdn1-only", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when ORG_LOC is CHILD NODE": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"another-topology"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name": "topology-orgloc-child",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"multiOriginCachegroup",
+                                                               "parents":    
[]int{0},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when LEAF NODE is a MID_LOC": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"another-topology"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":        
"topology-child-midloc",
+                                               "description": "child mid_loc",
+                                               "nodes":       
[]map[string]interface{}{{"cachegroup": "parentCachegroup", "parents": 
[]int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when DUPLICATE PARENTS": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"another-topology"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name": "topology-same-parents",
+                                               "nodes": 
[]map[string]interface{}{
+                                                       {
+                                                               "cachegroup": 
"cachegroup1",
+                                                               "parents":    
[]int{},
+                                                       },
+                                                       {
+                                                               "cachegroup": 
"cachegroup2",
+                                                               "parents":    
[]int{0, 0},
+                                                       },
+                                               },
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when REMOVING ORG ASSIGNED DS": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": {"mso-topology"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "mso-topology",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "topology-edge-cg-01", "parents": 
[]int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "FORBIDDEN when READ-ONLY USER": {
+                                       ClientSession: readOnlyUserSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"another-topology"}}},
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "topology-ro",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)),
+                               },
+                               "PRECONDITION FAILED when updating with IMS & 
IUS Headers": {
+                                       ClientSession: TOSession,
+                                       RequestOpts: client.RequestOptions{
+                                               QueryParameters: 
url.Values{"name": {"another-topology"}},
+                                               Header:          
http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}},
+                                       },
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "another-topology",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), 
utils.HasStatus(http.StatusPreconditionFailed)),
+                               },
+                               "PRECONDITION FAILED when updating with IFMATCH 
ETAG Header": {
+                                       ClientSession: TOSession,
+                                       RequestOpts: client.RequestOptions{
+                                               QueryParameters: 
url.Values{"name": {"another-topology"}},
+                                               Header:          
http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}},
+                                       },
+                                       RequestBody: map[string]interface{}{
+                                               "name":  "another-topology",
+                                               "nodes": 
[]map[string]interface{}{{"cachegroup": "cachegroup1", "parents": []int{}}},
+                                       },
+                                       Expectations: 
utils.CkRequest(utils.HasError(), 
utils.HasStatus(http.StatusPreconditionFailed)),
+                               },
+                       },
+                       "DELETE": {
+                               "BAD REQUEST when TOPOLOGY DOESNT EXIST": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": 
{"non-existent-topology"}}},
+                                       Expectations:  
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "BAD REQUEST when TOPOLOGY IN USE by DELIVERY 
SERVICE": {
+                                       ClientSession: TOSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": {"mso-topology"}}},
+                                       Expectations:  
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+                               },
+                               "FORBIDDEN when READ-ONLY USER": {
+                                       ClientSession: readOnlyUserSession,
+                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"name": {"mso-topology"}}},
+                                       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 {
+                                       topology := tc.Topology{}
+
+                                       if testCase.RequestBody != nil {
+                                               dat, err := 
json.Marshal(testCase.RequestBody)
+                                               assert.NoError(t, err, "Error 
occurred when marshalling request body: %v", err)
+                                               err = json.Unmarshal(dat, 
&topology)
+                                               assert.NoError(t, err, "Error 
occurred when unmarshalling request body: %v", err)
+                                       }
+
+                                       switch method {

Review Comment:
   needs a default case



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to