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

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


The following commit(s) were added to refs/heads/master by this push:
     new da0dc68067 Add unit tests for deliveryservice folder in Traffic Ops 
(#7390)
da0dc68067 is described below

commit da0dc6806798b30fcf2b3843d1054f00fd6bea10
Author: Srijeet Chatterjee <[email protected]>
AuthorDate: Tue Mar 14 09:01:27 2023 -0600

    Add unit tests for deliveryservice folder in Traffic Ops (#7390)
    
    * wip
    
    * wip
    
    * Adding tests for ds folder
    
    * sort imports
    
    * go fmt
    
    * license
---
 .../deliveryservice/acme_test.go                   | 169 ++++++++
 .../deliveryservice/gencert_test.go                |  31 ++
 .../deliveryservice/request/requests_test.go       | 465 +++++++++++++++++++++
 .../deliveryservice/request/validate_test.go       | 278 ++++++++++++
 .../deliveryservice/safe_test.go                   |  70 ++++
 .../deliveryservice/servers/delete_test.go         | 119 ++++++
 .../deliveryservice/servers/servers.go             |   2 +-
 .../deliveryservice/servers/servers_test.go        | 339 +++++++++++++++
 8 files changed, 1472 insertions(+), 1 deletion(-)

diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/acme_test.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/acme_test.go
new file mode 100644
index 0000000000..24089cd643
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/acme_test.go
@@ -0,0 +1,169 @@
+package deliveryservice
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "bytes"
+       "crypto/rand"
+       "crypto/rsa"
+       "crypto/sha256"
+       "crypto/x509"
+       "encoding/base64"
+       "encoding/pem"
+       "testing"
+
+       "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/lib/go-util"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config"
+       "github.com/go-acme/lego/challenge/dns01"
+
+       "github.com/jmoiron/sqlx"
+       "gopkg.in/DATA-DOG/go-sqlmock.v1"
+)
+
+func TestGetStoredAcmeAccountInfo(t *testing.T) {
+       mockDB, mock, err := sqlmock.New()
+       if err != nil {
+               t.Fatalf("an error '%s' was not expected when opening a stub 
database connection", err)
+       }
+       defer mockDB.Close()
+
+       db := sqlx.NewDb(mockDB, "sqlmock")
+       defer db.Close()
+
+       priv, err := rsa.GenerateKey(rand.Reader, 2048)
+       if err != nil {
+               t.Fatalf("expected no error while generating key, but got %v", 
err)
+       }
+       keyBuf := bytes.Buffer{}
+       keyDer := x509.MarshalPKCS1PrivateKey(priv)
+       err = pem.Encode(&keyBuf, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: 
keyDer})
+       if err != nil {
+               t.Fatalf("expected no error while encoding key, but got %v", 
err)
+       }
+
+       mock.ExpectBegin()
+       rows := sqlmock.NewRows([]string{"email", "private_key", "uri"})
+       rows.AddRow("[email protected]", keyBuf.Bytes(), "https://uri.com";)
+       mock.ExpectQuery("SELECT email, private_key, 
uri").WithArgs("[email protected]", "Lets Encrypt").WillReturnRows(rows)
+
+       info, err := getStoredAcmeAccountInfo(db.MustBegin().Tx, 
"[email protected]", "Lets Encrypt")
+       if err != nil {
+               t.Errorf("expected no error while getting stored acme account 
into, but got %v", err)
+       }
+       if info == nil {
+               t.Fatalf("expected valid acme account info in response, but got 
nothing")
+       }
+       if info.Email != "[email protected]" {
+               t.Errorf("expected email to be [email protected], but got %s", 
info.Email)
+       }
+       if info.Key != string(keyBuf.Bytes()) {
+               t.Errorf("expected key to be %s, but got %s", 
string(keyBuf.Bytes()), info.Key)
+       }
+       if info.URI != "https://uri.com"; {
+               t.Errorf("expected uri to be https://uri.com, but got %s", 
info.URI)
+       }
+}
+
+func TestGetAcmeAccountConfig(t *testing.T) {
+       cfgAcmeAccounts := make([]config.ConfigAcmeAccount, 0)
+       cfg := config.Config{
+               AcmeAccounts: cfgAcmeAccounts,
+               ConfigLetsEncrypt: config.ConfigLetsEncrypt{
+                       Email:       "[email protected]",
+                       Environment: "production",
+               },
+       }
+       c := GetAcmeAccountConfig(&cfg, tc.LetsEncryptAuthType)
+       if c == nil {
+               t.Fatalf("expected a valid Acme Account Config in response, but 
got nothing")
+       }
+       if c.UserEmail != cfg.Email {
+               t.Errorf("expected user email to be %s, but got %s", cfg.Email, 
c.UserEmail)
+       }
+       if c.AcmeUrl != "https://acme-v02.api.letsencrypt.org/directory"; {
+               t.Errorf("expected AcmeProvider to be 
https://acme-v02.api.letsencrypt.org/directory, but got %s", c.AcmeUrl)
+       }
+       if c.AcmeProvider != tc.LetsEncryptAuthType {
+               t.Errorf("expected AcmeProvider to be Lets Encrypt, but got 
%s", c.AcmeProvider)
+       }
+}
+
+func TestDNSProviderTrafficRouter_Present(t *testing.T) {
+       mockDB, mock, err := sqlmock.New()
+       if err != nil {
+               t.Fatalf("an error '%s' was not expected when opening a stub 
database connection", err)
+       }
+       defer mockDB.Close()
+
+       db := sqlx.NewDb(mockDB, "sqlmock")
+       defer db.Close()
+
+       d := DNSProviderTrafficRouter{
+               db:    db,
+               xmlId: util.Ptr("dsXMLID"),
+       }
+       keyAuthShaBytes := sha256.Sum256([]byte("blah"))
+       value := 
base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
+       mock.ExpectBegin()
+       mock.ExpectExec("INSERT INTO 
dnschallenges").WithArgs("_acme-challenge.test.", value, 
*d.xmlId).WillReturnResult(sqlmock.NewResult(1, 1))
+       mock.ExpectCommit()
+       err = d.Present("test", "token", "blah")
+       if err != nil {
+               t.Errorf("expected no error, but got %v", err)
+       }
+}
+
+func TestDNSProviderTrafficRouter_Cleanup(t *testing.T) {
+       mockDB, mock, err := sqlmock.New()
+       if err != nil {
+               t.Fatalf("an error '%s' was not expected when opening a stub 
database connection", err)
+       }
+       defer mockDB.Close()
+
+       db := sqlx.NewDb(mockDB, "sqlmock")
+       defer db.Close()
+
+       d := DNSProviderTrafficRouter{
+               db:    db,
+               xmlId: util.Ptr("dsXMLID"),
+       }
+       keyAuthShaBytes := sha256.Sum256([]byte("blah"))
+       value := 
base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
+       mock.ExpectBegin()
+       mock.ExpectExec("DELETE FROM 
dnschallenges").WithArgs("_acme-challenge.test.", 
value).WillReturnResult(sqlmock.NewResult(1, 1))
+       mock.ExpectCommit()
+       err = d.CleanUp("test", "token", "blah")
+       if err != nil {
+               t.Errorf("expected no error, but got %v", err)
+       }
+}
+
+func TestGetRecord(t *testing.T) {
+       fqdn, val := dns01.GetRecord("test", "blah")
+       keyAuthShaBytes := sha256.Sum256([]byte("blah"))
+       value := 
base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
+       if fqdn != "_acme-challenge.test." {
+               t.Errorf("expected fqdn to be _acme-challenge.test., but got 
%s", fqdn)
+       }
+       if val != value {
+               t.Errorf("expected returned value to be %s, but got %s", value, 
val)
+       }
+}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/gencert_test.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/gencert_test.go
new file mode 100644
index 0000000000..724c9d2e86
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/gencert_test.go
@@ -0,0 +1,31 @@
+package deliveryservice
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "testing"
+)
+
+func TestGenerateCert(t *testing.T) {
+       _, _, _, err := GenerateCert("localhost", "US", "Denver", "CO", 
"Comcast", "IPCDN")
+       if err != nil {
+               t.Errorf("expected no error, but got %v", err)
+       }
+}
diff --git 
a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go
index 7e403fe3b6..388288d008 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go
@@ -20,12 +20,477 @@ package request
  */
 
 import (
+       "net/http"
        "testing"
+       "time"
+
+       "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/lib/go-util"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
 
        "github.com/jmoiron/sqlx"
        "gopkg.in/DATA-DOG/go-sqlmock.v1"
 )
 
+func TestInsert(t *testing.T) {
+       mockDB, mock, err := sqlmock.New()
+       if err != nil {
+               t.Fatalf("opening mock database: %v", err)
+       }
+       defer mockDB.Close()
+
+       db := sqlx.NewDb(mockDB, "sqlmock")
+       defer db.Close()
+
+       mock.ExpectBegin()
+       inf := api.APIInfo{
+               Params:    nil,
+               IntParams: nil,
+               User: &auth.CurrentUser{
+                       UserName:     "testUser",
+                       ID:           1,
+                       PrivLevel:    10,
+                       TenantID:     1,
+                       Role:         1,
+                       RoleName:     "testRole",
+                       Capabilities: nil,
+                       UCDN:         "",
+               },
+               ReqID:    0,
+               Version:  nil,
+               Tx:       db.MustBegin(),
+               CancelTx: nil,
+               Vault:    nil,
+               Config:   nil,
+       }
+       dsr := tc.DeliveryServiceRequestV5{
+               Assignee:       util.StrPtr("assignee"),
+               AssigneeID:     util.IntPtr(25),
+               Author:         "test",
+               AuthorID:       util.IntPtr(35),
+               ChangeType:     tc.DSRChangeTypeUpdate,
+               CreatedAt:      time.Now(),
+               LastEditedBy:   "test",
+               LastEditedByID: util.IntPtr(35),
+               LastUpdated:    time.Now(),
+               Original:       nil,
+               Requested:      &tc.DeliveryServiceV5{},
+               Status:         tc.RequestStatusDraft,
+               XMLID:          "dsXMLID",
+       }
+
+       rows := sqlmock.NewRows([]string{"id", "last_updated", "created_at"})
+       rows.AddRow(1, time.Now(), time.Now())
+       mock.ExpectQuery("INSERT INTO 
deliveryservice_request*").WillReturnRows(rows)
+
+       rows2 := sqlmock.NewRows([]string{"active", 
"anonymous_blocking_enabled", "ccr_dns_ttl", "cdn_id", "cdnname", "check_path",
+               "consistent_hash_regex", "deep_caching_type", "display_name", 
"dns_bypass_cname", "dns_bypass_ip", "dns_bypass_ip6",
+               "dns_bypass_ttl", "dscp", "ecs_enabled", "edge_header_rewrite", 
"first_header_rewrite", "geolimit_redirect_url",
+               "geo_limit", "geo_limit_countries", "geo_provider", 
"global_max_mbps", "global_max_tps", "fq_pacing_rate", "http_bypass_fqdn",
+               "id", "info_url", "initial_dispersion", "inner_header_rewrite", 
"ipv6_routing_enabled", "last_header_rewrite", "last_updated",
+               "logs_enabled", "long_desc", "long_desc_1", "long_desc_2", 
"max_dns_answers", "max_origin_connections", "max_request_header_bytes",
+               "mid_header_rewrite", "miss_lat", "miss_long", 
"multi_site_origin", "org_server_fqdn ", "origin_shield", "profileid", 
"profile_name",
+               "profile_description", "protocol", "qstring_ignore", 
"query_keys", "range_request_handling", "regex_remap", "regional", 
"regional_geo_blocking",
+               "remap_text", "required_capabilities", "routing_name", 
"service_category", "signing_algorithm", "range_slice_block_size", 
"ssl_key_version", "tenant_id",
+               "name", "tls_versions", "topology", "tr_request_headers", 
"tr_response_headers", "name", "type_id", "xml_id", "cdn_domain",
+       })
+       ds := tc.DeliveryServiceV5{
+               Active:                   
tc.DeliveryServiceActiveState("PRIMED"),
+               AnonymousBlockingEnabled: false,
+               CCRDNSTTL:                util.IntPtr(20),
+               CDNID:                    11,
+               CDNName:                  util.StrPtr("testCDN"),
+               CheckPath:                util.StrPtr("blah"),
+               ConsistentHashRegex:      nil,
+               DeepCachingType:          tc.DeepCachingTypeNever,
+               DisplayName:              "ds",
+               DNSBypassCNAME:           nil,
+               DNSBypassIP:              nil,
+               DNSBypassIP6:             nil,
+               DNSBypassTTL:             nil,
+               DSCP:                     0,
+               EcsEnabled:               false,
+               EdgeHeaderRewrite:        nil,
+               FirstHeaderRewrite:       nil,
+               GeoLimitRedirectURL:      nil,
+               GeoLimit:                 0,
+               GeoLimitCountries:        nil,
+               GeoProvider:              0,
+               GlobalMaxMBPS:            nil,
+               GlobalMaxTPS:             nil,
+               FQPacingRate:             nil,
+               HTTPBypassFQDN:           nil,
+               ID:                       util.IntPtr(1),
+               InfoURL:                  nil,
+               InitialDispersion:        util.IntPtr(1),
+               InnerHeaderRewrite:       nil,
+               IPV6RoutingEnabled:       util.BoolPtr(true),
+               LastHeaderRewrite:        nil,
+               LastUpdated:              time.Now(),
+               LogsEnabled:              true,
+               LongDesc:                 "",
+               MaxDNSAnswers:            util.IntPtr(5),
+               MaxOriginConnections:     util.IntPtr(2),
+               MaxRequestHeaderBytes:    util.IntPtr(0),
+               MidHeaderRewrite:         nil,
+               MissLat:                  util.FloatPtr(0.0),
+               MissLong:                 util.FloatPtr(0.0),
+               MultiSiteOrigin:          false,
+               OrgServerFQDN:            util.StrPtr("http://1.2.3.4";),
+               OriginShield:             nil,
+               ProfileID:                util.IntPtr(99),
+               ProfileName:              util.StrPtr("profile99"),
+               ProfileDesc:              nil,
+               Protocol:                 util.IntPtr(1),
+               QStringIgnore:            nil,
+               RangeRequestHandling:     nil,
+               RegexRemap:               nil,
+               Regional:                 false,
+               RegionalGeoBlocking:      false,
+               RemapText:                nil,
+               RequiredCapabilities:     nil,
+               RoutingName:              "",
+               ServiceCategory:          nil,
+               SigningAlgorithm:         nil,
+               RangeSliceBlockSize:      nil,
+               SSLKeyVersion:            nil,
+               TenantID:                 100,
+               Tenant:                   util.StrPtr("tenant100"),
+               TLSVersions:              nil,
+               Topology:                 nil,
+               TRRequestHeaders:         nil,
+               TRResponseHeaders:        nil,
+               Type:                     util.StrPtr("type101"),
+               TypeID:                   101,
+               XMLID:                    "dsXMLID",
+       }
+
+       rows2.AddRow(
+               ds.Active,
+               ds.AnonymousBlockingEnabled,
+               ds.CCRDNSTTL,
+               ds.CDNID,
+               ds.CDNName,
+               ds.CheckPath,
+               ds.ConsistentHashRegex,
+               ds.DeepCachingType,
+               ds.DisplayName,
+               ds.DNSBypassCNAME,
+               ds.DNSBypassIP,
+               ds.DNSBypassIP6,
+               ds.DNSBypassTTL,
+               ds.DSCP,
+               ds.EcsEnabled,
+               ds.EdgeHeaderRewrite,
+               ds.FirstHeaderRewrite,
+               ds.GeoLimitRedirectURL,
+               ds.GeoLimit,
+               nil,
+               ds.GeoProvider,
+               ds.GlobalMaxMBPS,
+               ds.GlobalMaxTPS,
+               ds.FQPacingRate,
+               ds.HTTPBypassFQDN,
+               ds.ID,
+               ds.InfoURL,
+               ds.InitialDispersion,
+               ds.InnerHeaderRewrite,
+               ds.IPV6RoutingEnabled,
+               ds.LastHeaderRewrite,
+               ds.LastUpdated,
+               ds.LogsEnabled,
+               ds.LongDesc,
+               ds.LongDesc,
+               ds.LongDesc,
+               ds.MaxDNSAnswers,
+               ds.MaxOriginConnections,
+               ds.MaxRequestHeaderBytes,
+               ds.MidHeaderRewrite,
+               ds.MissLat,
+               ds.MissLong,
+               ds.MultiSiteOrigin,
+               ds.OrgServerFQDN,
+               ds.OriginShield,
+               ds.ProfileID,
+               ds.ProfileName,
+               ds.ProfileDesc,
+               ds.Protocol,
+               ds.QStringIgnore,
+               nil,
+               ds.RangeRequestHandling,
+               ds.RegexRemap,
+               ds.Regional,
+               ds.RegionalGeoBlocking,
+               ds.RemapText,
+               nil,
+               ds.RoutingName,
+               ds.ServiceCategory,
+               ds.SigningAlgorithm,
+               ds.RangeSliceBlockSize,
+               ds.SSLKeyVersion,
+               ds.TenantID,
+               ds.Tenant,
+               nil,
+               ds.Topology,
+               ds.TRRequestHeaders,
+               ds.TRResponseHeaders,
+               ds.Type,
+               ds.TypeID,
+               ds.XMLID,
+               "cdn_domain_name")
+
+       mock.ExpectQuery("SELECT ds.active*").WillReturnRows(rows2)
+
+       rows3 := sqlmock.NewRows([]string{
+               "ds_name",
+               "type",
+               "pattern",
+               "set_number",
+       })
+       rows3.AddRow(
+               "dsXMLID",
+               "HOST_REGEXP",
+               ".*\\.dsXMLID\\..*",
+               0)
+       mock.ExpectQuery("SELECT ds.xml_id as ds_name*").WillReturnRows(rows3)
+
+       sc, userErr, sysErr := insert(&dsr, &inf)
+
+       if userErr != nil || sysErr != nil {
+               t.Fatalf("expected no error, but got userErr: %v, sysErr: %v", 
userErr, sysErr)
+       }
+       if sc != http.StatusOK {
+               t.Fatalf("expected a 200 status code, but got %d", sc)
+       }
+       if dsr.Original == nil {
+               t.Fatalf("expected original to be a valid delivery service, but 
got nothing")
+       }
+       if dsr.Original.XMLID != "dsXMLID" {
+               t.Fatalf("expected original to have a DS with XMLID 'dsXMLID', 
but got %s", dsr.Original.XMLID)
+       }
+
+}
+func TestGetOriginals(t *testing.T) {
+       mockDB, mock, err := sqlmock.New()
+       if err != nil {
+               t.Fatalf("opening mock database: %v", err)
+       }
+       defer mockDB.Close()
+
+       db := sqlx.NewDb(mockDB, "sqlmock")
+       defer db.Close()
+
+       mock.ExpectBegin()
+       ID := 66
+       ids := []int{ID}
+       needOriginals := make(map[int][]*tc.DeliveryServiceRequestV5)
+       dsr := tc.DeliveryServiceRequestV5{
+               Assignee:       util.StrPtr("assignee"),
+               AssigneeID:     util.IntPtr(25),
+               Author:         "test",
+               AuthorID:       util.IntPtr(35),
+               ChangeType:     tc.DSRChangeTypeUpdate,
+               CreatedAt:      time.Now(),
+               ID:             util.IntPtr(1),
+               LastEditedBy:   "test",
+               LastEditedByID: util.IntPtr(35),
+               LastUpdated:    time.Now(),
+               Original:       nil,
+               Requested:      nil,
+               Status:         tc.RequestStatusDraft,
+               XMLID:          "dsXMLID",
+       }
+       needOriginals[ID] = []*tc.DeliveryServiceRequestV5{&dsr}
+
+       rows := sqlmock.NewRows([]string{"active", 
"anonymous_blocking_enabled", "ccr_dns_ttl", "cdn_id", "cdnname", "check_path",
+               "consistent_hash_regex", "deep_caching_type", "display_name", 
"dns_bypass_cname", "dns_bypass_ip", "dns_bypass_ip6",
+               "dns_bypass_ttl", "dscp", "ecs_enabled", "edge_header_rewrite", 
"first_header_rewrite", "geolimit_redirect_url",
+               "geo_limit", "geo_limit_countries", "geo_provider", 
"global_max_mbps", "global_max_tps", "fq_pacing_rate", "http_bypass_fqdn",
+               "id", "info_url", "initial_dispersion", "inner_header_rewrite", 
"ipv6_routing_enabled", "last_header_rewrite", "last_updated",
+               "logs_enabled", "long_desc", "long_desc_1", "long_desc_2", 
"max_dns_answers", "max_origin_connections", "max_request_header_bytes",
+               "mid_header_rewrite", "miss_lat", "miss_long", 
"multi_site_origin", "org_server_fqdn ", "origin_shield", "profileid", 
"profile_name",
+               "profile_description", "protocol", "qstring_ignore", 
"query_keys", "range_request_handling", "regex_remap", "regional", 
"regional_geo_blocking",
+               "remap_text", "required_capabilities", "routing_name", 
"service_category", "signing_algorithm", "range_slice_block_size", 
"ssl_key_version", "tenant_id",
+               "name", "tls_versions", "topology", "tr_request_headers", 
"tr_response_headers", "name", "type_id", "xml_id", "cdn_domain",
+       })
+       ds := tc.DeliveryServiceV5{
+               Active:                   
tc.DeliveryServiceActiveState("PRIMED"),
+               AnonymousBlockingEnabled: false,
+               CCRDNSTTL:                util.IntPtr(20),
+               CDNID:                    11,
+               CDNName:                  util.StrPtr("testCDN"),
+               CheckPath:                util.StrPtr("blah"),
+               ConsistentHashRegex:      nil,
+               DeepCachingType:          tc.DeepCachingTypeNever,
+               DisplayName:              "ds",
+               DNSBypassCNAME:           nil,
+               DNSBypassIP:              nil,
+               DNSBypassIP6:             nil,
+               DNSBypassTTL:             nil,
+               DSCP:                     0,
+               EcsEnabled:               false,
+               EdgeHeaderRewrite:        nil,
+               FirstHeaderRewrite:       nil,
+               GeoLimitRedirectURL:      nil,
+               GeoLimit:                 0,
+               GeoLimitCountries:        nil,
+               GeoProvider:              0,
+               GlobalMaxMBPS:            nil,
+               GlobalMaxTPS:             nil,
+               FQPacingRate:             nil,
+               HTTPBypassFQDN:           nil,
+               ID:                       util.IntPtr(ID),
+               InfoURL:                  nil,
+               InitialDispersion:        util.IntPtr(1),
+               InnerHeaderRewrite:       nil,
+               IPV6RoutingEnabled:       util.BoolPtr(true),
+               LastHeaderRewrite:        nil,
+               LastUpdated:              time.Now(),
+               LogsEnabled:              true,
+               LongDesc:                 "",
+               MaxDNSAnswers:            util.IntPtr(5),
+               MaxOriginConnections:     util.IntPtr(2),
+               MaxRequestHeaderBytes:    util.IntPtr(0),
+               MidHeaderRewrite:         nil,
+               MissLat:                  util.FloatPtr(0.0),
+               MissLong:                 util.FloatPtr(0.0),
+               MultiSiteOrigin:          false,
+               OrgServerFQDN:            util.StrPtr("http://1.2.3.4";),
+               OriginShield:             nil,
+               ProfileID:                util.IntPtr(99),
+               ProfileName:              util.StrPtr("profile99"),
+               ProfileDesc:              nil,
+               Protocol:                 util.IntPtr(1),
+               QStringIgnore:            nil,
+               RangeRequestHandling:     nil,
+               RegexRemap:               nil,
+               Regional:                 false,
+               RegionalGeoBlocking:      false,
+               RemapText:                nil,
+               RequiredCapabilities:     nil,
+               RoutingName:              "",
+               ServiceCategory:          nil,
+               SigningAlgorithm:         nil,
+               RangeSliceBlockSize:      nil,
+               SSLKeyVersion:            nil,
+               TenantID:                 100,
+               Tenant:                   util.StrPtr("tenant100"),
+               TLSVersions:              nil,
+               Topology:                 nil,
+               TRRequestHeaders:         nil,
+               TRResponseHeaders:        nil,
+               Type:                     util.StrPtr("type101"),
+               TypeID:                   101,
+               XMLID:                    "dsXMLID",
+       }
+       rows.AddRow(
+               ds.Active,
+               ds.AnonymousBlockingEnabled,
+               ds.CCRDNSTTL,
+               ds.CDNID,
+               ds.CDNName,
+               ds.CheckPath,
+               ds.ConsistentHashRegex,
+               ds.DeepCachingType,
+               ds.DisplayName,
+               ds.DNSBypassCNAME,
+               ds.DNSBypassIP,
+               ds.DNSBypassIP6,
+               ds.DNSBypassTTL,
+               ds.DSCP,
+               ds.EcsEnabled,
+               ds.EdgeHeaderRewrite,
+               ds.FirstHeaderRewrite,
+               ds.GeoLimitRedirectURL,
+               ds.GeoLimit,
+               nil,
+               ds.GeoProvider,
+               ds.GlobalMaxMBPS,
+               ds.GlobalMaxTPS,
+               ds.FQPacingRate,
+               ds.HTTPBypassFQDN,
+               ds.ID,
+               ds.InfoURL,
+               ds.InitialDispersion,
+               ds.InnerHeaderRewrite,
+               ds.IPV6RoutingEnabled,
+               ds.LastHeaderRewrite,
+               ds.LastUpdated,
+               ds.LogsEnabled,
+               ds.LongDesc,
+               ds.LongDesc,
+               ds.LongDesc,
+               ds.MaxDNSAnswers,
+               ds.MaxOriginConnections,
+               ds.MaxRequestHeaderBytes,
+               ds.MidHeaderRewrite,
+               ds.MissLat,
+               ds.MissLong,
+               ds.MultiSiteOrigin,
+               ds.OrgServerFQDN,
+               ds.OriginShield,
+               ds.ProfileID,
+               ds.ProfileName,
+               ds.ProfileDesc,
+               ds.Protocol,
+               ds.QStringIgnore,
+               nil,
+               ds.RangeRequestHandling,
+               ds.RegexRemap,
+               ds.Regional,
+               ds.RegionalGeoBlocking,
+               ds.RemapText,
+               nil,
+               ds.RoutingName,
+               ds.ServiceCategory,
+               ds.SigningAlgorithm,
+               ds.RangeSliceBlockSize,
+               ds.SSLKeyVersion,
+               ds.TenantID,
+               ds.Tenant,
+               nil,
+               ds.Topology,
+               ds.TRRequestHeaders,
+               ds.TRResponseHeaders,
+               ds.Type,
+               ds.TypeID,
+               ds.XMLID,
+               "cdn_domain_name")
+
+       mock.ExpectQuery("SELECT ds.active*").WillReturnRows(rows)
+
+       rows2 := sqlmock.NewRows([]string{
+               "ds_name",
+               "type",
+               "pattern",
+               "set_number",
+       })
+       rows2.AddRow(
+               "dsXMLID",
+               "HOST_REGEXP",
+               ".*\\.dsXMLID\\..*",
+               0)
+       mock.ExpectQuery("SELECT ds.xml_id as ds_name*").WillReturnRows(rows2)
+
+       if needOriginals[ID][0].Original != nil {
+               t.Errorf("expected original to be initially empty")
+       }
+       sc, userErr, sysErr := getOriginals(ids, db.MustBegin(), needOriginals)
+       if userErr != nil || sysErr != nil {
+               t.Fatalf("expected no error, but got userErr: %v, sysErr: %v", 
userErr, sysErr)
+       }
+       if sc != http.StatusOK {
+               t.Fatalf("expected a 200 status code, but got %d", sc)
+       }
+       if needOriginals[ID][0].Original == nil {
+               t.Fatalf("expected original to be a valid delivery service, but 
got nothing")
+       }
+       if needOriginals[ID][0].Original.XMLID != "dsXMLID" {
+               t.Fatalf("expected original to have a DS with XMLID 'dsXMLID', 
but got %s", needOriginals[ID][0].Original.XMLID)
+       }
+}
+
 func TestGetAssignee(t *testing.T) {
        req := assignmentRequest{
                AssigneeID: nil,
diff --git 
a/traffic_ops/traffic_ops_golang/deliveryservice/request/validate_test.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/request/validate_test.go
new file mode 100644
index 0000000000..60012cb70b
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/validate_test.go
@@ -0,0 +1,278 @@
+package request
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "testing"
+       "time"
+
+       "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/lib/go-util"
+       "github.com/jmoiron/sqlx"
+
+       "gopkg.in/DATA-DOG/go-sqlmock.v1"
+)
+
+var ds = tc.DeliveryServiceV5{
+       Active:                   tc.DeliveryServiceActiveState("PRIMED"),
+       AnonymousBlockingEnabled: false,
+       CCRDNSTTL:                util.IntPtr(20),
+       CDNID:                    11,
+       CDNName:                  util.StrPtr("testCDN"),
+       CheckPath:                util.StrPtr("blah"),
+       ConsistentHashRegex:      nil,
+       DeepCachingType:          tc.DeepCachingTypeNever,
+       DisplayName:              "ds",
+       DNSBypassCNAME:           nil,
+       DNSBypassIP:              nil,
+       DNSBypassIP6:             nil,
+       DNSBypassTTL:             nil,
+       DSCP:                     0,
+       EcsEnabled:               false,
+       EdgeHeaderRewrite:        nil,
+       FirstHeaderRewrite:       nil,
+       GeoLimitRedirectURL:      nil,
+       GeoLimit:                 0,
+       GeoLimitCountries:        nil,
+       GeoProvider:              0,
+       GlobalMaxMBPS:            nil,
+       GlobalMaxTPS:             nil,
+       FQPacingRate:             nil,
+       HTTPBypassFQDN:           nil,
+       ID:                       util.IntPtr(1),
+       InfoURL:                  nil,
+       InitialDispersion:        util.IntPtr(1),
+       InnerHeaderRewrite:       nil,
+       IPV6RoutingEnabled:       util.BoolPtr(true),
+       LastHeaderRewrite:        nil,
+       LastUpdated:              time.Now(),
+       LogsEnabled:              true,
+       LongDesc:                 "",
+       MaxDNSAnswers:            util.IntPtr(5),
+       MaxOriginConnections:     util.IntPtr(2),
+       MaxRequestHeaderBytes:    util.IntPtr(0),
+       MidHeaderRewrite:         nil,
+       MissLat:                  util.FloatPtr(0.0),
+       MissLong:                 util.FloatPtr(0.0),
+       MultiSiteOrigin:          false,
+       OrgServerFQDN:            util.StrPtr("http://1.2.3.4";),
+       OriginShield:             nil,
+       ProfileID:                util.IntPtr(99),
+       ProfileName:              util.StrPtr("profile99"),
+       ProfileDesc:              nil,
+       Protocol:                 util.IntPtr(1),
+       QStringIgnore:            nil,
+       RangeRequestHandling:     nil,
+       RegexRemap:               nil,
+       Regional:                 false,
+       RegionalGeoBlocking:      false,
+       RemapText:                nil,
+       RequiredCapabilities:     nil,
+       RoutingName:              "",
+       ServiceCategory:          nil,
+       SigningAlgorithm:         nil,
+       RangeSliceBlockSize:      nil,
+       SSLKeyVersion:            nil,
+       TenantID:                 100,
+       Tenant:                   util.StrPtr("tenant100"),
+       TLSVersions:              nil,
+       Topology:                 nil,
+       TRRequestHeaders:         nil,
+       TRResponseHeaders:        nil,
+       Type:                     util.StrPtr("type101"),
+       TypeID:                   101,
+       XMLID:                    "dsXMLID",
+}
+
+var dsr = tc.DeliveryServiceRequestNullable{
+       AssigneeID:      nil,
+       Assignee:        nil,
+       AuthorID:        nil,
+       Author:          nil,
+       ChangeType:      nil,
+       CreatedAt:       nil,
+       ID:              nil,
+       LastEditedBy:    nil,
+       LastEditedByID:  nil,
+       LastUpdated:     nil,
+       DeliveryService: nil,
+       Status:          util.Ptr(tc.RequestStatusDraft),
+       XMLID:           util.StrPtr("dsXMLID"),
+}
+
+func TestValidateV5(t *testing.T) {
+       mockDB, mock, err := sqlmock.New()
+       if err != nil {
+               t.Fatalf("opening mock database: %v", err)
+       }
+       defer mockDB.Close()
+
+       db := sqlx.NewDb(mockDB, "sqlmock")
+       defer db.Close()
+
+       mock.ExpectBegin()
+
+       dsrV5 := dsr.Upgrade().Upgrade().Upgrade()
+       userErr, sysErr := validateV5(dsrV5, db.MustBegin().Tx)
+
+       if sysErr != nil {
+               t.Fatalf("expected no error, but got sysErr: %v", sysErr)
+       }
+       if userErr == nil {
+               t.Fatalf("expected userErr because change type is absent, but 
got nothing")
+       }
+
+       dsrV5.ChangeType = tc.DSRChangeTypeCreate
+       mock.ExpectBegin()
+       userErr, sysErr = validateV5(dsrV5, db.MustBegin().Tx)
+       if sysErr != nil {
+               t.Fatalf("expected no error, but got sysErr: %v", sysErr)
+       }
+       if userErr == nil {
+               t.Fatalf("expected userErr because requested is absent for 
changetype 'change', but got nothing")
+       }
+
+       dsrV5.Requested = &ds
+       mock.ExpectBegin()
+       rows := sqlmock.NewRows([]string{
+               "name",
+               "use_in_table",
+       })
+       rows.AddRow("type101", "server")
+       mock.ExpectQuery("SELECT name, use_in_table*").WillReturnRows(rows)
+       userErr, sysErr = validateV5(dsrV5, db.MustBegin().Tx)
+       if sysErr != nil {
+               t.Fatalf("expected no error, but got sysErr: %v", sysErr)
+       }
+       if userErr == nil {
+               t.Fatalf("expected userErr because use_in_table is not 
deliveryservice, but got nothing")
+       }
+
+       mock.ExpectBegin()
+       rows = sqlmock.NewRows([]string{
+               "name",
+               "use_in_table",
+       })
+       rows.AddRow("type101", "deliveryservice")
+       mock.ExpectQuery("SELECT name, use_in_table*").WillReturnRows(rows)
+       userErr, sysErr = validateV5(dsrV5, db.MustBegin().Tx)
+       if userErr != nil || sysErr != nil {
+               t.Fatalf("no error expected, but got usererr: %v, sysErr: %v", 
userErr, sysErr)
+       }
+
+       dsrV5.ChangeType = tc.DSRChangeTypeDelete
+       mock.ExpectBegin()
+       rows = sqlmock.NewRows([]string{
+               "name",
+               "use_in_table",
+       })
+       rows.AddRow("type101", "deliveryservice")
+       userErr, sysErr = validateV5(dsrV5, db.MustBegin().Tx)
+       if sysErr != nil {
+               t.Fatalf("expected no error, but got sysErr: %v", sysErr)
+       }
+       if userErr == nil {
+               t.Fatalf("expected userErr because original is not present for 
changetype 'delete', but got nothing")
+       }
+
+       dsrV5.Requested = nil
+       dsrV5.Original = &ds
+       mock.ExpectBegin()
+       userErr, sysErr = validateV5(dsrV5, db.MustBegin().Tx)
+       if userErr != nil || sysErr != nil {
+               t.Fatalf("no error expected, but got usererr: %v, sysErr: %v", 
userErr, sysErr)
+       }
+
+       dsrV5.Assignee = util.StrPtr("testUser")
+       mock.ExpectBegin()
+       rows = sqlmock.NewRows([]string{
+               "id",
+       })
+       rows.AddRow(10)
+       mock.ExpectQuery("SELECT id FROM tm_user*").WillReturnRows(rows)
+       userErr, sysErr = validateV5(dsrV5, db.MustBegin().Tx)
+       if userErr != nil || sysErr != nil {
+               t.Fatalf("no error expected, but got usererr: %v, sysErr: %v", 
userErr, sysErr)
+       }
+}
+
+func TestValidateLegacy(t *testing.T) {
+       mockDB, mock, err := sqlmock.New()
+       if err != nil {
+               t.Fatalf("opening mock database: %v", err)
+       }
+       defer mockDB.Close()
+
+       db := sqlx.NewDb(mockDB, "sqlmock")
+       defer db.Close()
+
+       mock.ExpectBegin()
+
+       dsV30 := ds.Downgrade().DowngradeToV31()
+       dsr.DeliveryService = &dsV30
+       // expect error because ChangeType is absent
+       userErr, sysErr := validateLegacy(dsr, db.MustBegin().Tx)
+       if sysErr != nil {
+               t.Fatalf("expected no error, but got sysErr: %v", sysErr)
+       }
+       if userErr == nil {
+               t.Fatalf("expected userErr because change type is absent, but 
got nothing")
+       }
+       dsr.ChangeType = util.StrPtr(string(tc.DSRChangeTypeCreate))
+       mock.ExpectBegin()
+       rows := sqlmock.NewRows([]string{
+               "name",
+               "use_in_table",
+       })
+       rows.AddRow("type101", "server")
+       mock.ExpectQuery("SELECT name, use_in_table*").WillReturnRows(rows)
+       userErr, sysErr = validateLegacy(dsr, db.MustBegin().Tx)
+       if sysErr != nil {
+               t.Fatalf("expected no error, but got sysErr: %v", sysErr)
+       }
+       if userErr == nil {
+               t.Fatalf("expected userErr because use_in_table is not 
deliveryservice, but got nothing")
+       }
+
+       mock.ExpectBegin()
+       rows.AddRow("type101", "deliveryservice")
+       mock.ExpectQuery("SELECT name, use_in_table*").WillReturnRows(rows)
+       userErr, sysErr = validateLegacy(dsr, db.MustBegin().Tx)
+       if userErr != nil || sysErr != nil {
+               t.Fatalf("no error expected, but got usererr: %v, sysErr: %v", 
userErr, sysErr)
+       }
+
+       dsr.ID = util.IntPtr(1)
+       dsr.Status = util.Ptr(tc.RequestStatusSubmitted)
+       mock.ExpectBegin()
+
+       rows2 := sqlmock.NewRows([]string{
+               "status",
+       })
+       rows2.AddRow([]byte("submitted"))
+       mock.ExpectQuery("SELECT status*").WillReturnRows(rows2)
+
+       rows.AddRow("type101", "deliveryservice")
+       mock.ExpectQuery("SELECT name, use_in_table*").WillReturnRows(rows)
+       userErr, sysErr = validateLegacy(dsr, db.MustBegin().Tx)
+       if userErr != nil || sysErr != nil {
+               t.Fatalf("no error expected, but got usererr: %v, sysErr: %v", 
userErr, sysErr)
+       }
+}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/safe_test.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/safe_test.go
new file mode 100644
index 0000000000..9cdea41527
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/safe_test.go
@@ -0,0 +1,70 @@
+package deliveryservice
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "testing"
+
+       "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/lib/go-util"
+       "github.com/jmoiron/sqlx"
+
+       "gopkg.in/DATA-DOG/go-sqlmock.v1"
+)
+
+func TestUpdateDSSafe(t *testing.T) {
+       mockDB, mock, err := sqlmock.New()
+       if err != nil {
+               t.Fatalf("an error '%s' was not expected when opening a stub 
database connection", err)
+       }
+       defer mockDB.Close()
+
+       db := sqlx.NewDb(mockDB, "sqlmock")
+       defer db.Close()
+
+       // test with a DS that exists
+       mock.ExpectBegin()
+       dsID := 1
+       dsr := tc.DeliveryServiceSafeUpdateRequest{
+               DisplayName: util.Ptr("displayName"),
+               InfoURL:     util.Ptr("http://blah.com";),
+               LongDesc:    util.Ptr("longdesc"),
+               LongDesc1:   util.Ptr("longdesc1"),
+       }
+       mock.ExpectExec("UPDATE deliveryservice").WithArgs(*dsr.DisplayName, 
*dsr.InfoURL, *dsr.LongDesc, *dsr.LongDesc1, 
dsID).WillReturnResult(sqlmock.NewResult(int64(dsID), 1))
+       exists, err := updateDSSafe(db.MustBegin().Tx, dsID, dsr, false)
+       if err != nil {
+               t.Errorf("expected no error, but got: %v", err)
+       }
+       if !exists {
+               t.Errorf("expected DS with id 1 to exist")
+       }
+
+       // test with a DS that doesn't exist
+       mock.ExpectBegin()
+       mock.ExpectExec("UPDATE deliveryservice").WithArgs(*dsr.DisplayName, 
*dsr.InfoURL, *dsr.LongDesc, *dsr.LongDesc1, 
2).WillReturnResult(sqlmock.NewResult(2, 0))
+       exists, err = updateDSSafe(db.MustBegin().Tx, 2, dsr, false)
+       if err != nil {
+               t.Errorf("expected no error, but got: %v", err)
+       }
+       if exists {
+               t.Errorf("expected DS with id 2 to not exist")
+       }
+}
diff --git 
a/traffic_ops/traffic_ops_golang/deliveryservice/servers/delete_test.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/servers/delete_test.go
new file mode 100644
index 0000000000..5f08e11c98
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/servers/delete_test.go
@@ -0,0 +1,119 @@
+package servers
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+       "net/http"
+       "testing"
+
+       "github.com/jmoiron/sqlx"
+
+       "gopkg.in/DATA-DOG/go-sqlmock.v1"
+)
+
+func TestCheckLastAvailableEdgeOrOrigin(t *testing.T) {
+       mockDB, mock, err := sqlmock.New()
+       if err != nil {
+               t.Fatalf("an error '%s' was not expected when opening a stub 
database connection", err)
+       }
+       defer mockDB.Close()
+
+       db := sqlx.NewDb(mockDB, "sqlmock")
+       defer db.Close()
+
+       // check DS with no topology or mso
+       mock.ExpectBegin()
+       rows := sqlmock.NewRows([]string{"available", "available"})
+       rows.AddRow(true, true)
+       mock.ExpectQuery("SELECT").WithArgs(1, 2).WillReturnRows(rows)
+       sc, userErr, sysErr := checkLastAvailableEdgeOrOrigin(1, 2, false, 
false, db.MustBegin().Tx)
+       if sysErr != nil {
+               t.Errorf("expected no system error, but got %v", sysErr)
+       }
+       if userErr == nil {
+               t.Errorf("expected error because removing the given server 
would result in active DS with no REPORTED/ ONLINE EDGE servers, but got 
nothing")
+       }
+       if sc != http.StatusConflict {
+               t.Errorf("expected 409 status code, but got %d", sc)
+       }
+
+       // check DS with topology, but no MSO
+       mock.ExpectBegin()
+       rows = sqlmock.NewRows([]string{"available", "available"})
+       rows.AddRow(true, true)
+       mock.ExpectQuery("SELECT").WithArgs(1, 2).WillReturnRows(rows)
+       sc, userErr, sysErr = checkLastAvailableEdgeOrOrigin(1, 2, false, true, 
db.MustBegin().Tx)
+       if userErr != nil || sysErr != nil {
+               t.Errorf("expected no error, but got userErr: %v, sysErr: %v", 
userErr, sysErr)
+       }
+       if sc != http.StatusOK {
+               t.Errorf("ecpected status code 200, but got %d", sc)
+       }
+
+       // check DS with MSO, but no topology
+       mock.ExpectBegin()
+       rows = sqlmock.NewRows([]string{"available", "available"})
+       rows.AddRow(false, true)
+       mock.ExpectQuery("SELECT").WithArgs(1, 2).WillReturnRows(rows)
+       sc, userErr, sysErr = checkLastAvailableEdgeOrOrigin(1, 2, true, false, 
db.MustBegin().Tx)
+       if sysErr != nil {
+               t.Errorf("expected no system error, but got %v", sysErr)
+       }
+       if userErr == nil {
+               t.Errorf("expected error because removing the given server 
would result in active DS with no REPORTED/ ONLINE EDGE servers, but got 
nothing")
+       }
+       if sc != http.StatusConflict {
+               t.Errorf("expected 409 status code, but got %d", sc)
+       }
+}
+
+func TestDeleteDSServer(t *testing.T) {
+       mockDB, mock, err := sqlmock.New()
+       if err != nil {
+               t.Fatalf("an error '%s' was not expected when opening a stub 
database connection", err)
+       }
+       defer mockDB.Close()
+
+       db := sqlx.NewDb(mockDB, "sqlmock")
+       defer db.Close()
+
+       mock.ExpectBegin()
+       rows := sqlmock.NewRows([]string{"server"})
+       mock.ExpectQuery("DELETE").WithArgs(1, 2).WillReturnRows(rows)
+       exists, err := deleteDSServer(db.MustBegin().Tx, 1, 2)
+       if err != nil {
+               t.Errorf("expected no error, but got %v", err)
+       }
+       if exists {
+               t.Errorf("expected exists to be false, but got true")
+       }
+
+       rows = sqlmock.NewRows([]string{"server"})
+       rows.AddRow(2)
+       mock.ExpectBegin()
+       mock.ExpectQuery("DELETE").WithArgs(1, 2).WillReturnRows(rows)
+       exists, err = deleteDSServer(db.MustBegin().Tx, 1, 2)
+       if err != nil {
+               t.Errorf("expected no error, but got %v", err)
+       }
+       if !exists {
+               t.Errorf("expected exists to be true, but got false")
+       }
+}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go
index e5b4463c7b..0dc7c0bd13 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go
@@ -78,7 +78,7 @@ func (dss TODeliveryServiceServer) GetKeys() 
(map[string]interface{}, bool) {
 }
 
 func (dss *TODeliveryServiceServer) GetAuditName() string {
-       if dss.DeliveryService != nil {
+       if dss.DeliveryService != nil && dss.Server != nil {
                return strconv.Itoa(*dss.DeliveryService) + "-" + 
strconv.Itoa(*dss.Server)
        }
        return "unknown"
diff --git 
a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go
index eed56f1613..d3e077cd59 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go
@@ -21,19 +21,358 @@ package servers
 
 import (
        "fmt"
+       "net/http"
        "strconv"
        "strings"
        "testing"
+       "time"
 
        "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/lib/go-util"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
 
        "github.com/jmoiron/sqlx"
+       "github.com/lib/pq"
        "gopkg.in/DATA-DOG/go-sqlmock.v1"
 )
 
+func getTestDeliveryServiceServer() TODeliveryServiceServer {
+       return TODeliveryServiceServer{
+               APIInfoImpl: api.APIInfoImpl{},
+               DeliveryServiceServer: tc.DeliveryServiceServer{
+                       Server:          nil,
+                       DeliveryService: nil,
+                       LastUpdated:     nil,
+               },
+               TenantIDs:          nil,
+               DeliveryServiceIDs: nil,
+               ServerIDs:          nil,
+               CDN:                "",
+       }
+}
+
 func TestValidateDSSAssignments(t *testing.T) {
+       mockDB, mock, err := sqlmock.New()
+       if err != nil {
+               t.Fatalf("an error '%s' was not expected when opening a stub 
database connection", err)
+       }
+       defer mockDB.Close()
+
+       db := sqlx.NewDb(mockDB, "sqlmock")
+       defer db.Close()
+       mock.ExpectBegin()
+
+       serverInfo := make([]tc.ServerInfo, 0)
+       s := tc.ServerInfo{
+               Cachegroup:   "cg1",
+               CachegroupID: 20,
+               CDNID:        10,
+               DomainName:   "test",
+               HostName:     "blah",
+               ID:           100,
+               Status:       "ONLINE",
+               Type:         "EDGE",
+       }
+       serverInfo = append(serverInfo, s)
+       s2 := s
+       s2.ID = 200
+       s2.HostName = "blah2"
+       serverInfo = append(serverInfo, s2)
+
+       dsInfo := DSInfo{Active: true,
+               ID:                   1,
+               Name:                 "ds1",
+               Type:                 tc.DSTypeDNS,
+               EdgeHeaderRewrite:    nil,
+               MidHeaderRewrite:     nil,
+               RegexRemap:           nil,
+               SigningAlgorithm:     nil,
+               CacheURL:             nil,
+               MaxOriginConnections: nil,
+               Topology:             util.Ptr("topology1"),
+               CDNID:                util.Ptr(10),
+               UseMultiSiteOrigin:   false}
+
+       // Try to assign non-ORG servers to a topology based DS (with required 
capabilities)
+       userErr, sysErr, sc := validateDSSAssignments(db.MustBegin().Tx, 
dsInfo, serverInfo, false)
+
+       if sysErr != nil {
+               t.Errorf("expected no system error, but got sysErr: %v", sysErr)
+       }
+       if userErr == nil {
+               t.Errorf("expected error while trying to assign EDGE server to 
a topology, but got nothing")
+       }
+       if sc != http.StatusBadRequest {
+               t.Errorf("expected status code to be 400, but got %d instead", 
sc)
+       }
+
+       // Try to assign ORG servers without required capabilities to a 
topology based DS (with required capabilities)
+       for i, _ := range serverInfo {
+               serverInfo[i].Type = "ORG"
+       }
+       mock.ExpectBegin()
+
+       rows := sqlmock.NewRows([]string{"array_agg", "array_agg"})
+       rows.AddRow([]byte("{20,21}"), []byte("{cg1,cg2}"))
+       mock.ExpectQuery("SELECT 
ARRAY(c.id)*").WithArgs("topology1").WillReturnRows(rows)
+
+       rows = sqlmock.NewRows([]string{"required_capabilities"})
+       rows.AddRow("{reqCap1}")
+       mock.ExpectQuery("SELECT 
required_capabilities*").WithArgs(1).WillReturnRows(rows)
+
+       rows = sqlmock.NewRows([]string{"host_name", "capabilities"})
+       rows.AddRow("blah", "{reqCap2, reqCap3}")
+       mock.ExpectQuery("SELECT 
s.host_name*").WithArgs(pq.StringArray{}).WillReturnRows(rows)
+       userErr, sysErr, sc = validateDSSAssignments(db.MustBegin().Tx, dsInfo, 
serverInfo, false)
+
+       if sysErr != nil {
+               t.Errorf("expected no system error, but got sysErr: %v", sysErr)
+       }
+       if userErr == nil {
+               t.Errorf("expected error while trying to assign server without 
a required capability, but got nothing")
+       }
+       if sc != http.StatusBadRequest {
+               t.Errorf("expected status code to be 400, but got %d instead", 
sc)
+       }
+
+       // Try to assign ORG servers with required capabilities to a topology 
based DS (with required capabilities)
+       mock.ExpectBegin()
+       rows = sqlmock.NewRows([]string{"array_agg", "array_agg"})
+       rows.AddRow([]byte("{20,21}"), []byte("{cg1,cg2}"))
+       mock.ExpectQuery("SELECT 
ARRAY(c.id)*").WithArgs("topology1").WillReturnRows(rows)
+
+       rows = sqlmock.NewRows([]string{"required_capabilities"})
+       rows.AddRow("{reqCap1}")
+       mock.ExpectQuery("SELECT 
required_capabilities*").WithArgs(1).WillReturnRows(rows)
+
+       rows = sqlmock.NewRows([]string{"host_name", "capabilities"})
+       rows.AddRow("blah", "{reqCap1, reqCap2, reqCap3}")
+       mock.ExpectQuery("SELECT 
s.host_name*").WithArgs(pq.StringArray{}).WillReturnRows(rows)
+       userErr, sysErr, sc = validateDSSAssignments(db.MustBegin().Tx, dsInfo, 
serverInfo, false)
+
+       if userErr != nil || sysErr != nil {
+               t.Errorf("expected no errors, but got userErr: %v, sysErr: %v", 
userErr, sysErr)
+       }
+       if sc != http.StatusOK {
+               t.Errorf("expected status code to be 200, but got %d instead", 
sc)
+       }
+
+       // Try to assign EDGE servers without required capabilities to a DS 
(with required capabilities)
+       dsInfo.Topology = nil
+       for i, _ := range serverInfo {
+               serverInfo[i].Type = "EDGE"
+       }
+
+       mock.ExpectBegin()
+       rows = sqlmock.NewRows([]string{"required_capabilities"})
+       rows.AddRow("{reqCap1}")
+       mock.ExpectQuery("SELECT 
required_capabilities*").WithArgs(1).WillReturnRows(rows)
+
+       rows = sqlmock.NewRows([]string{"host_name", "capabilities"})
+       rows.AddRow("blah", "{reqCap2, reqCap3}")
+       mock.ExpectQuery("SELECT s.host_name*").WithArgs(pq.StringArray{"blah", 
"blah2"}).WillReturnRows(rows)
+       userErr, sysErr, sc = validateDSSAssignments(db.MustBegin().Tx, dsInfo, 
serverInfo, false)
+
+       if sysErr != nil {
+               t.Errorf("expected no system error, but got sysErr: %v", sysErr)
+       }
+       if userErr == nil {
+               t.Errorf("expected error while trying to assign server without 
a required capability, but got nothing")
+       }
+       if sc != http.StatusBadRequest {
+               t.Errorf("expected status code to be 400, but got %d instead", 
sc)
+       }
+
+       // Try to assign EDGE servers with required capabilities to a DS (with 
required capabilities)
+       mock.ExpectBegin()
+       rows = sqlmock.NewRows([]string{"required_capabilities"})
+       rows.AddRow("{reqCap1}")
+       mock.ExpectQuery("SELECT 
required_capabilities*").WithArgs(1).WillReturnRows(rows)
+
+       rows = sqlmock.NewRows([]string{"host_name", "capabilities"})
+       rows.AddRow("blah", "{reqCap1, reqCap2, reqCap3}")
+       mock.ExpectQuery("SELECT s.host_name*").WithArgs(pq.StringArray{"blah", 
"blah2"}).WillReturnRows(rows)
+       userErr, sysErr, sc = validateDSSAssignments(db.MustBegin().Tx, dsInfo, 
serverInfo, false)
+
+       if userErr != nil || sysErr != nil {
+               t.Errorf("expected no errors, but got userErr: %v, sysErr: %v", 
userErr, sysErr)
+       }
+       if sc != http.StatusOK {
+               t.Errorf("expected status code to be 200, but got %d instead", 
sc)
+       }
+}
+
+func TestHasAvailableEdgesCurrentlyAssigned(t *testing.T) {
+       mockDB, mock, err := sqlmock.New()
+       if err != nil {
+               t.Fatalf("an error '%s' was not expected when opening a stub 
database connection", err)
+       }
+       defer mockDB.Close()
+
+       db := sqlx.NewDb(mockDB, "sqlmock")
+       defer db.Close()
+       mock.ExpectBegin()
+
+       rows := sqlmock.NewRows([]string{"name"})
+       rows.AddRow("edge1")
+
+       mock.ExpectQuery("SELECT t.name AS 
name*").WithArgs(1).WillReturnRows(rows)
+       assigned, err := hasAvailableEdgesCurrentlyAssigned(db.MustBegin().Tx, 
1)
+       if err != nil {
+               t.Fatalf("expected no error, but got %v", err)
+       }
+       if !assigned {
+               t.Errorf("expected 'hasAvailableEdgesCurrentlyAssigned' to 
return true, but got false")
+       }
+}
+
+func TestReadDSS(t *testing.T) {
+       //func (dss *TODeliveryServiceServer) readDSS(h http.Header, tx 
*sqlx.Tx, user *auth.CurrentUser, params map[string]string, intParams 
map[string]int, dsIDs []int64, serverIDs []int64, useIMS bool) 
(*tc.DeliveryServiceServerResponse, error, *time.Time)
+       dss := getTestDeliveryServiceServer()
+
+       mockDB, mock, err := sqlmock.New()
+       if err != nil {
+               t.Fatalf("an error '%s' was not expected when opening a stub 
database connection", err)
+       }
+       defer mockDB.Close()
+
+       db := sqlx.NewDb(mockDB, "sqlmock")
+       defer db.Close()
+
+       rows := sqlmock.NewRows([]string{"id"})
+       rows.AddRow(10)
+       rows.AddRow(20)
+
+       mock.ExpectBegin()
+       mock.ExpectQuery("WITH RECURSIVE*").WithArgs(10).WillReturnRows(rows)
+
+       rows = sqlmock.NewRows([]string{"server", "deliveryservice", 
"last_updated"})
+       rows.AddRow(1, 2, time.Now())
+
+       mock.ExpectQuery("SELECT*").WithArgs(pq.Int64Array{10, 
20}).WillReturnRows(rows)
+       response, err, _ := dss.readDSS(nil, db.MustBegin(), 
&auth.CurrentUser{PrivLevel: 30, TenantID: 10}, nil, nil, nil, nil, false)
+       if err != nil {
+               t.Fatalf("expected no error, but got: %v", err)
+       }
+       if response == nil {
+               t.Fatalf("expected a valid response, but got nothing")
+       }
+       if len(response.Response) != 1 {
+               t.Fatalf("expected response to have 1 deliveryserviceServer, 
but got %d", len(response.Response))
+       }
+       if response.Response[0].Server == nil || 
response.Response[0].DeliveryService == nil {
+               t.Fatalf("expected valid values for server and deliveryservice, 
but got nil instead. server: %v, deliveryservice: %v", 
response.Response[0].Server, response.Response[0].DeliveryService)
+       }
+       if *response.Response[0].Server != 1 || 
*response.Response[0].DeliveryService != 2 {
+               t.Errorf("expected server to be 1 and deliveryservice to be 2, 
but got server: %d, deliveryservice: %d instead", *response.Response[0].Server, 
*response.Response[0].DeliveryService)
+       }
+}
+
+func TestValidate(t *testing.T) {
+       dss := getTestDeliveryServiceServer()
+       err := dss.Validate(nil)
+       if err == nil {
+               t.Errorf("expected error about deliveryservice and server not 
being present, but got nothing")
+       }
+       dss.Server = util.Ptr(1)
+       dss.DeliveryService = util.Ptr(2)
+       err = dss.Validate(nil)
+       if err != nil {
+               t.Errorf("expected no error, but got %v", err)
+       }
+}
+
+func TestSetKeys(t *testing.T) {
+       dss := getTestDeliveryServiceServer()
+       keys := make(map[string]interface{})
+       keys["server"] = 1
+       keys["deliveryservice"] = 2
+       dss.SetKeys(keys)
+       if dss.DeliveryService == nil || dss.Server == nil {
+               t.Fatalf("expected both server and deliveryservice to be not 
nil")
+       }
+       if *dss.DeliveryService != 2 {
+               t.Errorf("expected deliveryservice key to be 2, but got %d", 
*dss.DeliveryService)
+       }
+       if *dss.Server != 1 {
+               t.Errorf("expected server key to be 1, but got %d", *dss.Server)
+       }
+}
+
+func TestGetAuditName(t *testing.T) {
+       dss := getTestDeliveryServiceServer()
+
+       auditName := dss.GetAuditName()
+       if auditName != "unknown" {
+               t.Errorf("expected audit name to be 'unknown', but got %s", 
auditName)
+       }
+
+       dss.DeliveryServiceServer.Server = util.Ptr(1)
+       dss.DeliveryServiceServer.DeliveryService = util.Ptr(2)
+       auditName = dss.GetAuditName()
+       if auditName != "2-1" {
+               t.Errorf("expected audit name to be '2-1', but got %s", 
auditName)
+       }
+}
+
+func TestGetKeys(t *testing.T) {
+       dss := getTestDeliveryServiceServer()
+       dss.Server = util.Ptr(1)
+       dss.DeliveryService = util.Ptr(2)
+       keys, exists := dss.GetKeys()
+       if keys == nil {
+               t.Fatalf("expected function to return a valid map of keys, but 
got nothing")
+       }
+       if !exists {
+               t.Fatalf("expected function to return a true boolean for 
exists, got false")
+       }
+       if serverID, ok := keys["server"]; !ok {
+               t.Fatalf("expected returned keys to have 'server' key, but key 
wasn't present")
+       } else if serverID.(int) != 1 {
+               t.Errorf("expected serverID to be 1, but got %d", 
serverID.(int))
+       }
+
+       if dsID, ok := keys["deliveryservice"]; !ok {
+               t.Fatalf("expected returned keys to have 'deliveryservice' key, 
but key wasn't present")
+       } else if dsID.(int) != 2 {
+               t.Errorf("expected dsID to be 2, but got %d", dsID.(int))
+       }
+
+       // check with nil values for server and deliveryservice
+       dss.DeliveryServiceServer.Server = nil
+       dss.DeliveryServiceServer.DeliveryService = nil
+       keys, exists = dss.GetKeys()
+       if keys == nil {
+               t.Fatalf("expected function to return a valid map of keys, but 
got nothing")
+       }
+       if exists {
+               t.Fatalf("expected function to return a false boolean for 
exists, got true")
+       }
+       if dsID, ok := keys["deliveryservice"]; !ok {
+               t.Fatalf("expected returned keys to have 'deliveryservice' key, 
but key wasn't present")
+       } else if dsID.(int) != 0 {
+               t.Errorf("expected dsID to be 0, but got %d", dsID.(int))
+       }
+       if _, ok := keys["server"]; ok {
+               t.Errorf("'server' key was not expected to be present")
+       }
+       dss.DeliveryServiceServer.DeliveryService = util.Ptr(2)
+       keys, exists = dss.GetKeys()
+       if keys == nil {
+               t.Fatalf("expected function to return a valid map of keys, but 
got nothing")
+       }
+       if exists {
+               t.Fatalf("expected function to return a false boolean for 
exists, got true")
+       }
+       if serverID, ok := keys["server"]; !ok {
+               t.Fatalf("expected returned keys to have 'server' key, but key 
wasn't present")
+       } else if serverID.(int) != 0 {
+               t.Errorf("expected serverID to be 0, but got %d", 
serverID.(int))
+       }
+}
+
+func TestValidateDSS(t *testing.T) {
        expected := `server and delivery service CDNs do not match`
        cdnID := 1
        ds := DSInfo{

Reply via email to