From d10056178ae13c25289f8cdd41c48062da2aab62 Mon Sep 17 00:00:00 2001
From: Erik Olof Gunnar Andersson <eandersson@blizzard.com>
Date: Thu, 14 Jul 2022 17:06:09 -0700
Subject: [PATCH] Cleaned up and fixed record objects and tests

We are currently raising the wrong exceptions for a few record
types. This can cause only part of the failures to get raised
to the user, since the first exception will fail the process.
Ideally we want to inform the user of all invalid records.

This patch fixes this and cleans up some of code as well.
- Cleaned up objects to use consistent exceptions.
- Fixed all broken tests.
- Re-named various objects tests.

Change-Id: I3d54a47f73929377122249c4d971e70e553725a6
---
 designate/objects/rrdata_cert.py              | 66 +++++++++++--------
 designate/objects/rrdata_spf.py               | 16 ++---
 designate/objects/rrdata_txt.py               | 36 +++++-----
 designate/tests/unit/objects/test_rrdata_a.py | 46 ++++++++++++-
 .../tests/unit/objects/test_rrdata_aaaa.py    | 55 ++++++++++++++++
 ...{test_caa_object.py => test_rrdata_caa.py} |  2 +-
 ...est_cert_object.py => test_rrdata_cert.py} | 22 ++++---
 .../{test_mx_object.py => test_rrdata_mx.py}  |  2 +-
 ...t_naptr_object.py => test_rrdata_naptr.py} |  2 +-
 .../tests/unit/objects/test_rrdata_spf.py     | 18 +++--
 ...t_sshfp_object.py => test_rrdata_sshfp.py} | 22 ++++---
 .../tests/unit/objects/test_rrdata_txt.py     | 31 ++++++---
 12 files changed, 229 insertions(+), 89 deletions(-)
 create mode 100644 designate/tests/unit/objects/test_rrdata_aaaa.py
 rename designate/tests/unit/objects/{test_caa_object.py => test_rrdata_caa.py} (98%)
 rename designate/tests/unit/objects/{test_cert_object.py => test_rrdata_cert.py} (86%)
 rename designate/tests/unit/objects/{test_mx_object.py => test_rrdata_mx.py} (96%)
 rename designate/tests/unit/objects/{test_naptr_object.py => test_rrdata_naptr.py} (96%)
 rename designate/tests/unit/objects/{test_sshfp_object.py => test_rrdata_sshfp.py} (78%)

diff --git a/designate/objects/rrdata_cert.py b/designate/objects/rrdata_cert.py
index 712ae83c9..d0b27ed9e 100644
--- a/designate/objects/rrdata_cert.py
+++ b/designate/objects/rrdata_cert.py
@@ -16,12 +16,21 @@
 
 import base64
 
-from designate.exceptions import InvalidObject
 from designate.objects import base
 from designate.objects import fields
 from designate.objects.record import Record
 from designate.objects.record import RecordList
 
+VALID_ALGOS = [
+    'RSAMD5', 'DSA', 'RSASHA1', 'DSA-NSEC3-SHA1', 'RSASHA1-NSEC3-SHA1',
+    'RSASHA256', 'RSASHA512', 'ECC-GOST', 'ECDSAP256SHA256', 'ECDSAP384SHA384',
+    'ED25519', 'ED448'
+]
+VALID_CERTS = [
+    'PKIX', 'SPKI', 'PGP', 'IPKIX', 'ISPKI', 'IPGP', 'ACPKIX', 'IACPKIX',
+    'URI', 'OID', 'DPKIX', 'DPTR'
+]
+
 
 @base.DesignateRegistry.register
 class CERT(Record):
@@ -36,39 +45,42 @@ class CERT(Record):
         'certificate': fields.StringFields(),
     }
 
-    def validate_cert_type(self, cert_type):
+    @staticmethod
+    def validate_cert_type(cert_type):
+        if cert_type in VALID_CERTS:
+            return cert_type
+
         try:
             int_cert_type = int(cert_type)
-            if int_cert_type < 0 or int_cert_type > 65535:
-                err = ("Cert type value should be between 0 and 65535")
-                raise InvalidObject(err)
         except ValueError:
-            # cert type is specified as Mnemonic
-            VALID_CERTS = ['PKIX', 'SPKI', 'PGP', 'IPKIX', 'ISPKI', 'IPGP',
-                           'ACPKIX', 'IACPKIX', 'URI', 'OID', 'DPKIX', 'DPTR']
-            if cert_type not in VALID_CERTS:
-                err = ("Cert type is not valid Mnemonic.")
-                raise InvalidObject(err)
+            raise ValueError('Cert type is not valid Mnemonic.')
+
+        if int_cert_type < 0 or int_cert_type > 65535:
+            raise ValueError(
+                'Cert type value should be between 0 and 65535'
+            )
+
         return cert_type
 
-    def validate_cert_algo(self, cert_algo):
+    @staticmethod
+    def validate_cert_algo(cert_algo):
+        if cert_algo in VALID_ALGOS:
+            return cert_algo
+
         try:
             int_cert_algo = int(cert_algo)
-            if int_cert_algo < 0 or int_cert_algo > 255:
-                err = ("Cert algorithm value should be between 0 and 255")
-                raise InvalidObject(err)
         except ValueError:
-            # cert algo is specified as Mnemonic
-            VALID_ALGOS = ['RSAMD5', 'DSA', 'RSASHA1', 'DSA-NSEC3-SHA1',
-                           'RSASHA1-NSEC3-SHA1', 'RSASHA256', 'RSASHA512',
-                           'ECC-GOST', 'ECDSAP256SHA256', 'ECDSAP384SHA384',
-                           'ED25519', 'ED448']
-            if cert_algo not in VALID_ALGOS:
-                err = ("Cert algorithm is not valid Mnemonic.")
-                raise InvalidObject(err)
+            raise ValueError('Cert algorithm is not valid Mnemonic.')
+
+        if int_cert_algo < 0 or int_cert_algo > 255:
+            raise ValueError(
+                'Cert algorithm value should be between 0 and 255'
+            )
+
         return cert_algo
 
-    def validate_cert_certificate(self, certificate):
+    @staticmethod
+    def validate_cert_certificate(certificate):
         try:
             chunks = certificate.split(' ')
             encoded_chunks = []
@@ -77,13 +89,11 @@ class CERT(Record):
             b64 = b''.join(encoded_chunks)
             base64.b64decode(b64)
         except Exception:
-            err = ("Cert certificate is not valid.")
-            raise InvalidObject(err)
+            raise ValueError('Cert certificate is not valid.')
         return certificate
 
     def _to_string(self):
-        return ("%(cert_type)s %(key_tag)s %(cert_algo)s "
-                "%(certificate)s" % self)
+        return '%(cert_type)s %(key_tag)s %(cert_algo)s %(certificate)s' % self
 
     def _from_string(self, v):
         cert_type, key_tag, cert_algo, certificate = v.split(' ', 3)
diff --git a/designate/objects/rrdata_spf.py b/designate/objects/rrdata_spf.py
index 323daf66e..f409991d7 100644
--- a/designate/objects/rrdata_spf.py
+++ b/designate/objects/rrdata_spf.py
@@ -12,7 +12,6 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
-from designate.exceptions import InvalidObject
 from designate.objects import base
 from designate.objects import fields
 from designate.objects.record import Record
@@ -33,22 +32,23 @@ class SPF(Record):
         return self.txt_data
 
     def _from_string(self, value):
-        if (not value.startswith('"') and not value.endswith('"')):
+        if not value.startswith('"') and not value.endswith('"'):
             # value with spaces should be quoted as per RFC1035 5.1
             for element in value:
                 if element.isspace():
-                    err = ("Empty spaces are not allowed in SPF record, "
-                           "unless wrapped in double quotes.")
-                    raise InvalidObject(err)
+                    raise ValueError(
+                        'Empty spaces are not allowed in SPF record, '
+                        'unless wrapped in double quotes.'
+                    )
         else:
             # quotes within value should be escaped with backslash
             strip_value = value.strip('"')
             for index, char in enumerate(strip_value):
                 if char == '"':
                     if strip_value[index - 1] != "\\":
-                        err = ("Quotation marks should be escaped with "
-                               "backslash.")
-                        raise InvalidObject(err)
+                        raise ValueError(
+                            'Quotation marks should be escaped with backslash.'
+                        )
 
         self.txt_data = value
 
diff --git a/designate/objects/rrdata_txt.py b/designate/objects/rrdata_txt.py
index 7526bda56..654094ef0 100644
--- a/designate/objects/rrdata_txt.py
+++ b/designate/objects/rrdata_txt.py
@@ -12,7 +12,6 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
-from designate.exceptions import InvalidObject
 from designate.objects import base
 from designate.objects import fields
 from designate.objects.record import Record
@@ -43,31 +42,34 @@ class TXT(Record):
 
     def _validate_record_single_string(self, value):
         if len(value) > 255:
-            err = ("Any TXT record string exceeding "
-                   "255 characters has to be split.")
-            raise InvalidObject(err)
+            raise ValueError(
+                'Any TXT record string exceeding 255 characters has to be '
+                'split.'
+            )
 
         if self._is_missing_double_quote(value):
-            err = ("TXT record is missing a double quote either at beginning "
-                   "or at end.")
-            raise InvalidObject(err)
+            raise ValueError(
+                'TXT record is missing a double quote either at beginning '
+                'or at end.'
+            )
 
         if not self._is_wrapped_in_double_quotes(value):
             # value with spaces should be quoted as per RFC1035 5.1
             for element in value:
                 if element.isspace():
-                    err = ("Empty spaces are not allowed in TXT record, "
-                           "unless wrapped in double quotes.")
-                    raise InvalidObject(err)
+                    raise ValueError(
+                        'Empty spaces are not allowed in TXT record, '
+                        'unless wrapped in double quotes.'
+                    )
         else:
             # quotes within value should be escaped with backslash
             strip_value = value.strip('"')
             for index, char in enumerate(strip_value):
                 if char == '"':
                     if strip_value[index - 1] != "\\":
-                        err = ("Quotation marks should be escaped with "
-                               "backslash.")
-                        raise InvalidObject(err)
+                        raise ValueError(
+                            'Quotation marks should be escaped with backslash.'
+                        )
 
     def _from_string(self, value):
         if len(value) > 255:
@@ -76,10 +78,10 @@ class TXT(Record):
             stripped_value = value.strip('"')
             if (not self._is_wrapped_in_double_quotes(value) and
                     '" "' not in stripped_value):
-                err = ("TXT record strings over 255 characters "
-                       "have to be split into multiple strings "
-                       "wrapped in double quotes.")
-                raise InvalidObject(err)
+                raise ValueError(
+                    'TXT record strings over 255 characters have to be split '
+                    'into multiple strings wrapped in double quotes.'
+                )
 
             record_strings = stripped_value.split('" "')
             for record_string in record_strings:
diff --git a/designate/tests/unit/objects/test_rrdata_a.py b/designate/tests/unit/objects/test_rrdata_a.py
index 3b508c3a0..1a03ffbaa 100644
--- a/designate/tests/unit/objects/test_rrdata_a.py
+++ b/designate/tests/unit/objects/test_rrdata_a.py
@@ -23,10 +23,50 @@ LOG = logging.getLogger(__name__)
 
 
 class RRDataATest(oslotest.base.BaseTestCase):
-    def test_reject_leading_zeros(self):
-        record = objects.A(data='10.0.001.1')
+    def test_valid_a_record(self):
+        recordset = objects.RecordSet(
+            name='www.example.test.', type='A',
+            records=objects.RecordList(objects=[
+                objects.Record(data='192.168.0.1'),
+            ])
+        )
+        recordset.validate()
+
+    def test_reject_aaaa_record(self):
+        recordset = objects.RecordSet(
+            name='www.example.test.', type='A',
+            records=objects.RecordList(objects=[
+                objects.Record(data='2001:db8:0:1::1'),
+            ])
+        )
         self.assertRaisesRegex(
             exceptions.InvalidObject,
             'Provided object does not match schema',
-            record.validate
+            recordset.validate
+        )
+
+    def test_reject_invalid_data(self):
+        recordset = objects.RecordSet(
+            name='www.example.test.', type='A',
+            records=objects.RecordList(objects=[
+                objects.Record(data='TXT'),
+            ])
+        )
+        self.assertRaisesRegex(
+            exceptions.InvalidObject,
+            'Provided object does not match schema',
+            recordset.validate
+        )
+
+    def test_reject_leading_zeros(self):
+        recordset = objects.RecordSet(
+            name='www.example.test.', type='A',
+            records=objects.RecordList(objects=[
+                objects.Record(data='10.0.001.1'),
+            ])
+        )
+        self.assertRaisesRegex(
+            exceptions.InvalidObject,
+            'Provided object does not match schema',
+            recordset.validate
         )
diff --git a/designate/tests/unit/objects/test_rrdata_aaaa.py b/designate/tests/unit/objects/test_rrdata_aaaa.py
new file mode 100644
index 000000000..f65204c40
--- /dev/null
+++ b/designate/tests/unit/objects/test_rrdata_aaaa.py
@@ -0,0 +1,55 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+from oslo_log import log as logging
+import oslotest.base
+
+from designate import exceptions
+from designate import objects
+
+LOG = logging.getLogger(__name__)
+
+
+class RRDataAAAATest(oslotest.base.BaseTestCase):
+    def test_valid_aaaa_record(self):
+        recordset = objects.RecordSet(
+            name='www.example.test.', type='AAAA',
+            records=objects.RecordList(objects=[
+                objects.Record(data='2001:db8:0:1::1'),
+            ])
+        )
+        recordset.validate()
+
+    def test_reject_a_record(self):
+        recordset = objects.RecordSet(
+            name='www.example.test.', type='AAAA',
+            records=objects.RecordList(objects=[
+                objects.Record(data='192.168.0.1'),
+            ])
+        )
+        self.assertRaisesRegex(
+            exceptions.InvalidObject,
+            'Provided object does not match schema',
+            recordset.validate
+        )
+
+    def test_reject_invalid_data(self):
+        recordset = objects.RecordSet(
+            name='www.example.test.', type='AAAA',
+            records=objects.RecordList(objects=[
+                objects.Record(data='TXT'),
+            ])
+        )
+        self.assertRaisesRegex(
+            exceptions.InvalidObject,
+            'Provided object does not match schema',
+            recordset.validate
+        )
diff --git a/designate/tests/unit/objects/test_caa_object.py b/designate/tests/unit/objects/test_rrdata_caa.py
similarity index 98%
rename from designate/tests/unit/objects/test_caa_object.py
rename to designate/tests/unit/objects/test_rrdata_caa.py
index cab9e6fb0..9c93979dc 100644
--- a/designate/tests/unit/objects/test_caa_object.py
+++ b/designate/tests/unit/objects/test_rrdata_caa.py
@@ -21,7 +21,7 @@ from designate import objects
 LOG = logging.getLogger(__name__)
 
 
-class CAARecordTest(oslotest.base.BaseTestCase):
+class RRDataCAATest(oslotest.base.BaseTestCase):
     def test_parse_caa_issue(self):
         caa_record = objects.CAA()
         caa_record._from_string('0 issue ca.example.net')
diff --git a/designate/tests/unit/objects/test_cert_object.py b/designate/tests/unit/objects/test_rrdata_cert.py
similarity index 86%
rename from designate/tests/unit/objects/test_cert_object.py
rename to designate/tests/unit/objects/test_rrdata_cert.py
index c650b6f0b..030c5bd6e 100644
--- a/designate/tests/unit/objects/test_cert_object.py
+++ b/designate/tests/unit/objects/test_rrdata_cert.py
@@ -17,28 +17,30 @@
 from oslo_log import log as logging
 import oslotest.base
 
-from designate import exceptions
 from designate import objects
 
 LOG = logging.getLogger(__name__)
 
 
-class CERTRecordTest(oslotest.base.BaseTestCase):
+class RRDataCERTTest(oslotest.base.BaseTestCase):
     def test_parse_cert(self):
         cert_record = objects.CERT()
         cert_record._from_string(
-            'DPKIX 1 RSASHA256 KR1L0GbocaIOOim1+qdHtOSrDcOsGiI2NCcxuX2/Tqc=')  # noqa
+            'DPKIX 1 RSASHA256 KR1L0GbocaIOOim1+qdHtOSrDcOsGiI2NCcxuX2/Tqc='
+        )
 
         self.assertEqual('DPKIX', cert_record.cert_type)
         self.assertEqual(1, cert_record.key_tag)
         self.assertEqual('RSASHA256', cert_record.cert_algo)
-        self.assertEqual('KR1L0GbocaIOOim1+qdHtOSrDcOsGiI2NCcxuX2/Tqc=',
-            cert_record.certificate)
+        self.assertEqual(
+            'KR1L0GbocaIOOim1+qdHtOSrDcOsGiI2NCcxuX2/Tqc=',
+            cert_record.certificate
+        )
 
     def test_parse_invalid_cert_type_value(self):
         cert_record = objects.CERT()
         self.assertRaisesRegex(
-            exceptions.InvalidObject,
+            ValueError,
             'Cert type value should be between 0 and 65535',
             cert_record._from_string,
             '99999 1 RSASHA256 KR1L0GbocaIOOim1+qdHtOSrDcOsGiI2NCcxuX2/Tqc='
@@ -47,7 +49,7 @@ class CERTRecordTest(oslotest.base.BaseTestCase):
     def test_parse_invalid_cert_type_mnemonic(self):
         cert_record = objects.CERT()
         self.assertRaisesRegex(
-            exceptions.InvalidObject,
+            ValueError,
             'Cert type is not valid Mnemonic.',
             cert_record._from_string,
             'FAKETYPE 1 RSASHA256 KR1L0GbocaIOOim1+qdHtOSrDcOsGiI2NCcxuX2/Tqc='
@@ -56,7 +58,7 @@ class CERTRecordTest(oslotest.base.BaseTestCase):
     def test_parse_invalid_cert_algo_value(self):
         cert_record = objects.CERT()
         self.assertRaisesRegex(
-            exceptions.InvalidObject,
+            ValueError,
             'Cert algorithm value should be between 0 and 255',
             cert_record._from_string,
             'DPKIX 1 256 KR1L0GbocaIOOim1+qdHtOSrDcOsGiI2NCcxuX2/Tqc='
@@ -65,7 +67,7 @@ class CERTRecordTest(oslotest.base.BaseTestCase):
     def test_parse_invalid_cert_algo_mnemonic(self):
         cert_record = objects.CERT()
         self.assertRaisesRegex(
-            exceptions.InvalidObject,
+            ValueError,
             'Cert algorithm is not valid Mnemonic.',
             cert_record._from_string,
             'DPKIX 1 FAKESHA256 KR1L0GbocaIOOim1+qdHtOSrDcOsGiI2NCcxuX2/Tqc='
@@ -74,7 +76,7 @@ class CERTRecordTest(oslotest.base.BaseTestCase):
     def test_parse_invalid_cert_certificate(self):
         cert_record = objects.CERT()
         self.assertRaisesRegex(
-            exceptions.InvalidObject,
+            ValueError,
             'Cert certificate is not valid.',
             cert_record._from_string,
             'DPKIX 1 RSASHA256 KR1L0GbocaIOOim1+qdHtOSrDcOsGiI2NCcxuX2/Tqc'
diff --git a/designate/tests/unit/objects/test_mx_object.py b/designate/tests/unit/objects/test_rrdata_mx.py
similarity index 96%
rename from designate/tests/unit/objects/test_mx_object.py
rename to designate/tests/unit/objects/test_rrdata_mx.py
index 589e5be80..d5b708237 100644
--- a/designate/tests/unit/objects/test_mx_object.py
+++ b/designate/tests/unit/objects/test_rrdata_mx.py
@@ -22,7 +22,7 @@ from designate import objects
 LOG = logging.getLogger(__name__)
 
 
-class MXRecordTest(oslotest.base.BaseTestCase):
+class RRDataMXTest(oslotest.base.BaseTestCase):
     def test_parse_mx(self):
         mx_record = objects.MX()
         mx_record._from_string('0 mail.example.org.')
diff --git a/designate/tests/unit/objects/test_naptr_object.py b/designate/tests/unit/objects/test_rrdata_naptr.py
similarity index 96%
rename from designate/tests/unit/objects/test_naptr_object.py
rename to designate/tests/unit/objects/test_rrdata_naptr.py
index 4415bcbd0..81223a200 100644
--- a/designate/tests/unit/objects/test_naptr_object.py
+++ b/designate/tests/unit/objects/test_rrdata_naptr.py
@@ -21,7 +21,7 @@ from designate import objects
 LOG = logging.getLogger(__name__)
 
 
-class NAPTRRecordTest(oslotest.base.BaseTestCase):
+class RRDataNAPTRTest(oslotest.base.BaseTestCase):
     def test_parse_naptr(self):
         naptr_record = objects.NAPTR()
         naptr_record._from_string(
diff --git a/designate/tests/unit/objects/test_rrdata_spf.py b/designate/tests/unit/objects/test_rrdata_spf.py
index 4e1bc102c..df4620849 100644
--- a/designate/tests/unit/objects/test_rrdata_spf.py
+++ b/designate/tests/unit/objects/test_rrdata_spf.py
@@ -20,17 +20,27 @@ LOG = logging.getLogger(__name__)
 
 class RRDataSPFTest(oslotest.base.BaseTestCase):
     def test_reject_non_quoted_spaces(self):
-        record = objects.SPF(data='foo bar')
+        recordset = objects.RecordSet(
+            name='www.example.test.', type='SPF',
+            records=objects.RecordList(objects=[
+                objects.Record(data='foo bar'),
+            ])
+        )
         self.assertRaisesRegex(
             exceptions.InvalidObject,
             'Provided object does not match schema',
-            record.validate
+            recordset.validate
         )
 
     def test_reject_non_escaped_quotes(self):
-        record = objects.SPF(data='foo"bar')
+        recordset = objects.RecordSet(
+            name='www.example.test.', type='SPF',
+            records=objects.RecordList(objects=[
+                objects.Record(data='"foo"bar"'),
+            ])
+        )
         self.assertRaisesRegex(
             exceptions.InvalidObject,
             'Provided object does not match schema',
-            record.validate
+            recordset.validate
         )
diff --git a/designate/tests/unit/objects/test_sshfp_object.py b/designate/tests/unit/objects/test_rrdata_sshfp.py
similarity index 78%
rename from designate/tests/unit/objects/test_sshfp_object.py
rename to designate/tests/unit/objects/test_rrdata_sshfp.py
index ac1ea3803..eee2da6f5 100644
--- a/designate/tests/unit/objects/test_sshfp_object.py
+++ b/designate/tests/unit/objects/test_rrdata_sshfp.py
@@ -16,13 +16,13 @@
 from oslo_log import log as logging
 import oslotest.base
 
-from designate.exceptions import InvalidObject
+from designate import exceptions
 from designate import objects
 
 LOG = logging.getLogger(__name__)
 
 
-class SSHFPecordTest(oslotest.base.BaseTestCase):
+class RRDataSSHTPTest(oslotest.base.BaseTestCase):
     def test_parse_sshfp(self):
         sshfp_record = objects.SSHFP()
         sshfp_record._from_string(
@@ -34,7 +34,7 @@ class SSHFPecordTest(oslotest.base.BaseTestCase):
                          sshfp_record.fingerprint)
 
     def test_validate_sshfp_signed_zero_alg(self):
-        record_set = objects.RecordSet(
+        recordset = objects.RecordSet(
             name='www.example.org.', type='SSHFP',
             records=objects.RecordList(objects=[
                 objects.Record(
@@ -42,11 +42,14 @@ class SSHFPecordTest(oslotest.base.BaseTestCase):
                     status='ACTIVE'),
             ])
         )
-
-        self.assertRaises(InvalidObject, record_set.validate)
+        self.assertRaisesRegex(
+            exceptions.InvalidObject,
+            'Provided object does not match schema',
+            recordset.validate
+        )
 
     def test_validate_sshfp_signed_zero_fptype(self):
-        record_set = objects.RecordSet(
+        recordset = objects.RecordSet(
             name='www.example.org.', type='SSHFP',
             records=objects.RecordList(objects=[
                 objects.Record(
@@ -54,5 +57,8 @@ class SSHFPecordTest(oslotest.base.BaseTestCase):
                     status='ACTIVE'),
             ])
         )
-
-        self.assertRaises(InvalidObject, record_set.validate)
+        self.assertRaisesRegex(
+            exceptions.InvalidObject,
+            'Provided object does not match schema',
+            recordset.validate
+        )
diff --git a/designate/tests/unit/objects/test_rrdata_txt.py b/designate/tests/unit/objects/test_rrdata_txt.py
index 40ec06a96..55742664a 100644
--- a/designate/tests/unit/objects/test_rrdata_txt.py
+++ b/designate/tests/unit/objects/test_rrdata_txt.py
@@ -20,42 +20,57 @@ LOG = logging.getLogger(__name__)
 
 class RRDataTXTTest(oslotest.base.BaseTestCase):
     def test_reject_non_quoted_spaces(self):
-        record = objects.TXT(data='foo bar')
+        recordset = objects.RecordSet(
+            name='www.example.test.', type='TXT',
+            records=objects.RecordList(objects=[
+                objects.Record(data='foo bar'),
+            ])
+        )
         self.assertRaisesRegex(
             exceptions.InvalidObject,
             'Provided object does not match schema',
-            record.validate
+            recordset.validate
         )
 
     def test_reject_non_escaped_quotes(self):
-        record = objects.TXT(data='foo"bar')
+        recordset = objects.RecordSet(
+            name='www.example.test.', type='TXT',
+            records=objects.RecordList(objects=[
+                objects.Record(data='"foo"bar"'),
+            ])
+        )
         self.assertRaisesRegex(
             exceptions.InvalidObject,
             'Provided object does not match schema',
-            record.validate
+            recordset.validate
         )
 
     def test_multiple_strings_one_record(self):
         # these quotes do not have to be escaped as
         # per rfc7208 3.3 and rfc1035 3.3.14
-        record = objects.TXT(data='"foo" "bar"')
+        recordset = objects.RecordSet(
+            name='www.example.test.', type='TXT',
+            records=objects.RecordList(objects=[
+                objects.Record(data='"foo" "bar"'),
+            ])
+        )
         self.assertRaisesRegex(
             exceptions.InvalidObject,
             'Provided object does not match schema',
-            record.validate
+            recordset.validate
         )
 
     def test_reject_non_matched_quotes(self):
         record = objects.TXT()
         self.assertRaisesRegex(
-            exceptions.InvalidObject,
+            ValueError,
             "TXT record is missing a double quote either at beginning "
             "or at end.",
             record._from_string,
             '"foo'
         )
         self.assertRaisesRegex(
-            exceptions.InvalidObject,
+            ValueError,
             "TXT record is missing a double quote either at beginning "
             "or at end.",
             record._from_string,