"""Test Source Model classes."""
import unittest
from hashlib import sha256
from uuid import uuid4

from preseem.source_model import Attachment, AccessPoint, Olt, Package, Router, Account, Service, Site
from preseem.grpc_nm_model import NetworkMetadataEntity, NetworkMetadataReference
from preseem_protobuf.model.account_pb2 import AccountStatus
from preseem_protobuf.model.common_pb2 import AccountType

class TestSourceModel(unittest.TestCase):
    def setUp(self):
        pass

    def tearDown(self):
        pass

    def assertError(self, obj, field, value):
        """Check an error is properly assigned to a source object.
           We can add more specific checks as we design how this will work."""
        self.assertTrue(hasattr(obj, 'errors'))
        self.assertEqual(len(obj.errors), 1)
        err = obj.errors.pop()
        self.assertEqual(err.field, field)
        self.assertEqual(err.value, value)
        
    def test_router_id(self):
        """Test field checking of router.id"""
        with self.assertRaises(TypeError):
            Router()  # id is required
        with self.assertRaises(ValueError):
            Router(None)  # id can't be None
        with self.assertRaises(ValueError):
            Router("")  # id can't be empty string
        router = Router(123)
        self.assertEqual(router.id, "123")  # int converted to string
        router = Router(0)
        self.assertEqual(router.id, "0")  # int converted to string
        router = Router("my_id")
        with self.assertRaises(AttributeError):
            router.id = "new_id"  # id is immutable once set

    def test_router_host(self):
        """Test field checking of router.host"""
        router = Router("r1", host=None)
        self.assertEqual(router.host, None)
        router = Router("r1", host="")
        self.assertError(router, 'host', '')  # host can't be empty
        self.assertError(Router("R1", host=""), 'host', '')
        router.host = "192.0.2.1"
        self.assertEqual(router.host, "192.0.2.1")
        router.host = "192.0.2.X"
        self.assertError(router, 'host', '192.0.2.X') # invalid IP is checked
        router.host = "192.0.2.1/24"
        self.assertError(router, 'host', '192.0.2.1/24') # host can't be a subnet
        router.host = 3221225986
        self.assertEqual(router.host, "192.0.2.2")  # host is normalized
        router.host = None
        self.assertEqual(router.host, None)

    def test_router_name(self):
        router = Router("r1", name=None)
        self.assertEqual(router.name, None)
        router = Router("r1", name="")
        self.assertError(router, 'name', '')  # name can't be empty
        router = Router("r1", name='router 1')
        self.assertEqual(router.name, 'router 1')
        router.name = 123
        self.assertError(router, 'name', 123)  # name must be a string
        self.assertEqual(router.name, 'router 1')
        router.name = 'router 2'
        self.assertEqual(router.name, 'router 2')
        router.name = None
        self.assertEqual(router.name, None)

    def test_router_site(self):
        router = Router("r1", site=None)
        self.assertEqual(router.site, None)
        router = Router("r1", site="")
        self.assertError(router, 'site', '')  # site can't be empty
        router = Router("r1", site='site 1')
        self.assertEqual(router.site, 'site 1')
        router.site = 123
        self.assertError(router, 'site', 123)  # site must be a string
        self.assertEqual(router.site, 'site 1')
        router.site = 'site 2'
        self.assertEqual(router.site, 'site 2')
        router.site = None
        self.assertEqual(router.site, None)

    def test_router_url(self):
        router = Router("r1", url=None)
        self.assertEqual(router.url, None)
        router = Router("r1", url="")
        self.assertError(router, 'url', '')  # url can't be empty
        router = Router("r1", url='https://router.url')
        self.assertEqual(router.url, 'https://router.url')
        router.url = 123
        self.assertError(router, 'url', 123)  # url must be a string
        self.assertEqual(router.url, 'https://router.url')
        router.url = 'https://router.url/routers/2'
        self.assertEqual(router.url, 'https://router.url/routers/2')
        router.url = None
        self.assertEqual(router.url, None)

    def test_router_nm(self):
        """Test that the router object generates a good NetworkMetadataReference."""
        router = Router("r1")
        ref = NetworkMetadataReference('router', 'r1', {})
        self.assertEqual(router.get_nm(), [ref])
        router.host = ref.attributes['host'] = '192.0.2.1'
        self.assertEqual(router.get_nm(), [ref])
        router.name = ref.attributes['name'] = 'Router 1'
        self.assertEqual(router.get_nm(), [ref])
        router.site = ref.attributes['site'] = 'Site 1'
        self.assertEqual(router.get_nm(), [ref])
        router.community = ref.attributes['community'] = 'public'
        self.assertEqual(router.get_nm(), [ref])
        ref.attributes['system'] = 'my-system'
        self.assertEqual(router.get_nm('my-system'), [ref])
        uuid = uuid4()
        ref.attributes['uuid'] = str(uuid)
        router._uuid = uuid.bytes
        self.assertEqual(router.get_nm('my-system'), [ref])

    def test_olt(self):
        """Simple unit test for OLT, the code is shared with Router so most of the
           testing is done there."""
        olt = Olt("olt1", name='OLT 1', host='192.0.2.2', site='Site 1')
        ref = NetworkMetadataReference(type='olt', value='olt1', attributes={'host': '192.0.2.2', 'name': 'OLT 1', 'site': 'Site 1', 'system': 'test'})
        uuid = uuid4()
        ref.attributes['uuid'] = str(uuid)
        olt._uuid = uuid.bytes
        self.assertEqual(olt.get_nm('test'), [ref])

    def test_ap(self):
        """Simple unit test for AP, the code is shared with Router so most of the
           testing is done there; the main difference is the expanded set of network
           metadata used for APs."""
        ap = AccessPoint("ap1", name='AP 1', host='192.0.2.2', site='Site 1')
        ref = NetworkMetadataReference(type='ap', value='ap1', attributes={'ip_address': '192.0.2.2', 'topology1': 'AP 1', 'topology2': 'Site 1', 'system': 'test', 'topology1_snmp': 'ap1', 'ap_config': 'ap1'})
        uuid = uuid4()
        ref.attributes['uuid'] = str(uuid)
        ap._uuid = uuid.bytes
        nms = ap.get_nm('test')

        self.assertEqual(len(nms), 3)
        ap_ref = next((x for x in nms if isinstance(x, NetworkMetadataReference) and x.type == 'ap'), None)
        self.assertEqual(ap_ref, ref)
        ref = NetworkMetadataReference('session', '192.0.2.2', {'service_rate_down': 10000000, 'service_rate_up': 10000000, 'subscriber_identifier': '192.0.2.2', 'subscriber_name': 'AP 1', 'system': 'test'})
        session_ref = next((x for x in nms if isinstance(x, NetworkMetadataReference) and x.type == 'session'), None)
        self.assertEqual(session_ref, ref)
        entity = next((x for x in nms if isinstance(x, NetworkMetadataEntity)), None)
        self.assertEqual(entity.network_prefix, '192.0.2.2')
        self.assertEqual(entity.attributes, {'session': '192.0.2.2', 'snmp_session': '192.0.2.2'})

    def test_account_id(self):
        """Test field checking of account.id"""
        with self.assertRaises(TypeError):
            Account()  # id is required
        with self.assertRaises(ValueError):
            Account(None)  # id can't be None
        with self.assertRaises(ValueError):
            Account("")  # id can't be empty string
        account = Account(123)
        self.assertEqual(account.id, "123")  # int converted to string
        account = Account(0)
        self.assertEqual(account.id, "0")  # int converted to string
        account = Account("my_id")
        with self.assertRaises(AttributeError):
            account.id = "new_id"  # id is immutable once set

    def test_account_type(self):
        """Test field checking of account.type"""
        account = Account("a1", type=None)
        self.assertEqual(account.type, None)
        account = Account("a1", type="")
        self.assertError(account, 'type', '')  # type can't be a string
        account = Account("a1", type=0)
        self.assertError(account, 'type', 0)  # type can't be set to the zero enum
        account = Account("a1", type=-1)
        self.assertError(account, 'type', -1)  # type can't be set to a non-enum value
        account = Account("a1", type=AccountType.ACCOUNT_TYPE_COMMERCIAL)
        self.assertEqual(account.type, AccountType.ACCOUNT_TYPE_COMMERCIAL)
        account.type = 'RESIDENTIAL'
        self.assertError(account, 'type', 'RESIDENTIAL')  # type must be an int
        self.assertEqual(account.type, AccountType.ACCOUNT_TYPE_COMMERCIAL)
        account.type = AccountType.ACCOUNT_TYPE_RESIDENTIAL
        self.assertEqual(account.type, 2)
        account.type = None
        self.assertEqual(account.type, None)

    def test_account_status(self):
        """Test field checking of account.status"""
        account = Account("a1", status=None)
        self.assertEqual(account.status, None)
        account = Account("a1", status="")
        self.assertError(account, 'status', '')  # status can't be a string
        account = Account("a1", status=0)
        self.assertError(account, 'status', 0)  # status can't be set to the zero enum
        account = Account("a1", status=-1)
        self.assertError(account, 'status', -1)  # status can't be set to a non-enum value
        account = Account("a1", status=AccountStatus.ACCOUNT_STATUS_INACTIVE)
        self.assertEqual(account.status, AccountStatus.ACCOUNT_STATUS_INACTIVE)
        account.status = 'ACTIVE'
        self.assertError(account, 'status', 'ACTIVE')  # status must be an int
        self.assertEqual(account.status, AccountStatus.ACCOUNT_STATUS_INACTIVE)
        account.status = AccountStatus.ACCOUNT_STATUS_ACTIVE
        self.assertEqual(account.status, 3)
        account.status = None
        self.assertEqual(account.status, None)

    def test_account_name(self):
        """Test field checking of account.name"""
        account = Account("a1", name=None)
        self.assertEqual(account.name, None)
        account = Account("a1", name="")
        self.assertError(account, 'name', '')  # name can't be empty
        account = Account("a1", name='account 1')
        self.assertEqual(account.name, 'account 1')
        account.name = 123
        self.assertError(account, 'name', 123)  # name must be a string
        self.assertEqual(account.name, 'account 1')
        account.name = 'account 2'
        self.assertEqual(account.name, 'account 2')
        account.name = None
        self.assertEqual(account.name, None)

    def test_account_first_name(self):
        """Test field checking of account.first_name"""
        account = Account("a1", first_name=None)
        self.assertEqual(account.first_name, None)
        account = Account("a1", first_name="")
        self.assertError(account, 'first_name', '')  # first_name can't be empty
        account = Account("a1", first_name='fname')
        self.assertEqual(account.first_name, 'fname')
        account.first_name = 123
        self.assertError(account, 'first_name', 123)  # first_name must be a string
        self.assertEqual(account.first_name, 'fname')
        account.first_name = 'fname2'
        self.assertEqual(account.first_name, 'fname2')
        account.first_name = None
        self.assertEqual(account.first_name, None)

    def test_account_last_name(self):
        """Test field checking of account.last_name"""
        account = Account("a1", last_name=None)
        self.assertEqual(account.last_name, None)
        account = Account("a1", last_name="")
        self.assertError(account, 'last_name', '')  # last_name can't be empty
        account = Account("a1", last_name='lname')
        self.assertEqual(account.last_name, 'lname')
        account.last_name = 123
        self.assertError(account, 'last_name', 123)  # last_name must be a string
        self.assertEqual(account.last_name, 'lname')
        account.last_name = 'lname2'
        self.assertEqual(account.last_name, 'lname2')
        account.last_name = None
        self.assertEqual(account.last_name, None)

    def test_account_url(self):
        """Test field checking of account.url"""
        account = Account("a1", url=None)
        self.assertEqual(account.url, None)
        account = Account("a1", url="")  # url can't be empty string
        self.assertError(account, 'url', "")
        self.assertEqual(account.url, None)
        account = Account("a1")
        self.assertEqual(account.url, None)
        account.url = "https://my.url.com"
        self.assertEqual(account.url, "https://my.url.com")
        account = Account("s1", url="https://my.url.com")
        self.assertEqual(account.url, "https://my.url.com")
        account.url = "https://url2.net"
        self.assertEqual(account.url, "https://url2.net")
        account.url = 0
        self.assertError(account, 'url', 0)
        self.assertEqual(account.url, "https://url2.net")

    def test_account_equivalence(self):
        """Test that equivalence works for Account."""
        a1 = Account('a1', type=1, status=2, name='Full Name')
        a2 = Account('a1', type=1, status=2, name='Full Name')
        a3 = Account('a1', type=1, status=2, first_name='Full', last_name='Name')
        self.assertEqual(a1, a2)
        self.assertNotEqual(a2, a3)

    def test_service_id(self):
        """Test field checking of service.id"""
        with self.assertRaises(TypeError):
            Service()  # id is required
        with self.assertRaises(ValueError):
            Service(None)  # id can't be None
        with self.assertRaises(ValueError):
            Service("")  # id can't be empty string
        service = Service(123)
        self.assertEqual(service.id, "123")  # int converted to string
        service = Service(0)
        self.assertEqual(service.id, "0")  # int converted to string
        service = Service("my_id")
        with self.assertRaises(AttributeError):
            service.id = "new_id"  # id is immutable once set

    def test_service_account_id(self):
        """Test field checking of service.account_id"""
        service = Service("s1", account_id=None)
        self.assertEqual(service.account_id, None)
        service = Service("s1", account_id="")  # id can't be empty string
        self.assertError(service, 'account_id', "")
        self.assertEqual(service.account_id, None)
        service = Service("s1")
        self.assertEqual(service.account_id, None)
        service.account_id = "a1"
        self.assertEqual(service.account_id, "a1")
        service = Service("s1", account_id="a1")
        self.assertEqual(service.account_id, "a1")
        service.account_id = "a2"
        self.assertEqual(service.account_id, "a2")
        service.account_id = 0
        self.assertEqual(service.account_id, "0")
        service.account_id = 123
        self.assertEqual(service.account_id, "123")

    def test_service_package_id(self):
        """Test field checking of service.package_id"""
        service = Service("s1", package_id=None)
        self.assertEqual(service.package_id, None)
        service = Service("s1", package_id="")  # id can't be empty string
        self.assertError(service, 'package_id', "")
        self.assertEqual(service.package_id, None)
        service = Service("s1")
        self.assertEqual(service.package_id, None)
        service.package_id = "a1"
        self.assertEqual(service.package_id, "a1")
        service = Service("s1", package_id="a1")
        self.assertEqual(service.package_id, "a1")
        service.package_id = "a2"
        self.assertEqual(service.package_id, "a2")
        service.package_id = 0
        self.assertEqual(service.package_id, "0")
        service.package_id = 123
        self.assertEqual(service.package_id, "123")

    def test_service_download_rate(self):
        """Test field checking of service.download_rate"""
        service = Service("s1")
        self.assertEqual(service.download_rate, None)
        service = Service("s1", download_rate=None)
        self.assertEqual(service.download_rate, None)
        service = Service("s1", download_rate=0)
        self.assertEqual(service.download_rate, 0)
        service.download_rate = 1000
        self.assertEqual(service.download_rate, 1000)
        service.download_rate = "lots"
        self.assertError(service, 'download_rate', 'lots')
        self.assertEqual(service.download_rate, 1000)
        service.download_rate = 1.7
        self.assertEqual(service.download_rate, 1)

    def test_service_upload_rate(self):
        """Test field checking of service.upload_rate"""
        service = Service("s1")
        self.assertEqual(service.upload_rate, None)
        service = Service("s1", upload_rate=None)
        self.assertEqual(service.upload_rate, None)
        service = Service("s1", upload_rate=0)
        self.assertEqual(service.upload_rate, 0)
        service.upload_rate = 1000
        self.assertEqual(service.upload_rate, 1000)
        service.upload_rate = "lots"
        self.assertError(service, 'upload_rate', 'lots')
        self.assertEqual(service.upload_rate, 1000)
        service.upload_rate = 1.7
        self.assertEqual(service.upload_rate, 1)

    def test_service_subscriber_identifier(self):
        """Test field checking of service.subscriber_identifier"""
        service = Service("s1", subscriber_identifier=None)
        self.assertEqual(service.subscriber_identifier, None)
        service = Service("s1", subscriber_identifier="")  # id can't be empty string
        self.assertError(service, 'subscriber_identifier', "")
        self.assertEqual(service.subscriber_identifier, None)
        service = Service("s1")
        self.assertEqual(service.subscriber_identifier, None)
        service.subscriber_identifier = "a1"
        self.assertEqual(service.subscriber_identifier, "a1")
        service = Service("s1", subscriber_identifier="a1")
        self.assertEqual(service.subscriber_identifier, "a1")
        service.subscriber_identifier = "a2"
        self.assertEqual(service.subscriber_identifier, "a2")
        service.subscriber_identifier = 0
        self.assertEqual(service.subscriber_identifier, "0")
        service.subscriber_identifier = 123
        self.assertEqual(service.subscriber_identifier, "123")

    def test_service_url(self):
        """Test field checking of service.url"""
        service = Service("s1", url=None)
        self.assertEqual(service.url, None)
        service = Service("s1", url="")  # url can't be empty string
        self.assertError(service, 'url', "")
        self.assertEqual(service.url, None)
        service = Service("s1")
        self.assertEqual(service.url, None)
        service.url = "https://my.url.com"
        self.assertEqual(service.url, "https://my.url.com")
        service = Service("s1", url="https://my.url.com")
        self.assertEqual(service.url, "https://my.url.com")
        service.url = "https://url2.net"
        self.assertEqual(service.url, "https://url2.net")
        service.url = 0
        self.assertError(service, 'url', 0)
        self.assertEqual(service.url, "https://url2.net")

    def test_service_attachment(self):
        """Test field checking of service.attachment"""
        service = Service("s1")
        self.assertEqual(service.attachment, Attachment())
        service = Service("s1", attachment=None)
        self.assertEqual(service.attachment, None)
        with self.assertRaises(TypeError):
            service = Service("s1", attachment=1)
        with self.assertRaises(TypeError):
            service = Service("s1", attachment="attachment")
        attachment = Attachment()
        service = Service("s1", attachment=attachment)
        self.assertEqual(service.attachment, attachment)
        self.assertEqual(service.attachment.pb, attachment.pb)
        self.assertIsNot(service.attachment, attachment)  # it should make a copy

    def test_service_attachment_error_propagation(self):
        """Errors on the attachment bubble up to the service."""
        service = Service("s1")
        attachment = service.attachment
        self.assertIsNotNone(attachment)
        self.assertEqual(attachment, Attachment())
        attachment.cpe_mac_address = ""
        self.assertError(service, 'cpe_mac_address', "")
        attachment.mac_addresses.append("00:11:22:33:44:ZZ")
        self.assertError(service, 'mac_addresses', "00:11:22:33:44:ZZ")

    def test_service_equivalence(self):
        """Test that equivalence works for Service."""
        attachment1 = Attachment(cpe_mac_address='00:11:22:33:44:55')
        attachment2 = Attachment(cpe_mac_address='00:11:22:33:44:55')
        srv1 = Service('s1', account_id='a1', package_id='p1', download_rate=100000, upload_rate=20000, subscriber_identifier='a1.s1', attachment=attachment1)
        srv2 = Service('s1', account_id='a1', package_id='p1', download_rate=100000, upload_rate=20000, subscriber_identifier='a1.s1', attachment=attachment2)
        self.assertEqual(srv1, srv2)

    def test_attachment_cpe_mac_address(self):
        """Test field checking of attachment.cpe_mac_address"""
        attachment = Attachment()
        self.assertEqual(attachment, Attachment())
        attachment = Attachment(cpe_mac_address="")
        self.assertError(attachment, 'cpe_mac_address', "")
        attachment.cpe_mac_address = ""
        self.assertError(attachment, 'cpe_mac_address', "")
        attachment = Attachment(cpe_mac_address=1)  # value must be a string
        self.assertError(attachment, 'cpe_mac_address', 1)
        attachment.cpe_mac_address = 1
        self.assertError(attachment, 'cpe_mac_address', 1)
        attachment = Attachment(cpe_mac_address='zz')  # value must be a mac address
        self.assertError(attachment, 'cpe_mac_address', "zz")
        attachment.cpe_mac_address = 'zz'
        self.assertError(attachment, 'cpe_mac_address', "zz")
        attachment = Attachment(cpe_mac_address = '00:00:00:00:00:00')  # value can't be the zero mac
        self.assertError(attachment, 'cpe_mac_address', "00:00:00:00:00:00")
        attachment.cpe_mac_address = '00:00:00:00:00:00'
        self.assertError(attachment, 'cpe_mac_address', "00:00:00:00:00:00")
        attachment = Attachment()
        attachment.cpe_mac_address = '00:11:22:33:44:55'
        self.assertEqual(Attachment(cpe_mac_address='001122334455'), attachment)
        attachment.cpe_mac_address = '00:11:22:33:44:66'
        self.assertNotEqual(Attachment(cpe_mac_address='00:11:22:33:44:55'), attachment)

    def test_attachment_imsi(self):
        """Test field checking of attachment.imsi"""
        attachment = Attachment(imsi="")
        self.assertError(attachment, 'imsi', "")
        attachment.imsi = ""
        self.assertError(attachment, 'imsi', "")
        attachment = Attachment(imsi=1)
        self.assertError(attachment, 'imsi', 1)  # value must be a string
        attachment.imsi = 1
        self.assertError(attachment, 'imsi', 1)
        attachment.imsi = 310150123456789
        self.assertError(attachment, 'imsi', 310150123456789)
        attachment.imsi = '310150123456789'
        self.assertEqual(Attachment(imsi='310150123456789'), attachment)
        attachment.imsi = '515020987654321'
        self.assertNotEqual(Attachment(imsi='310150123456789'), attachment)

    def test_attachment_cpe_serial_number(self):
        """Test field checking of attachment.cpe_serial_number"""
        attachment = Attachment(cpe_serial_number="")  # value can't be empty string
        self.assertError(attachment, 'cpe_serial_number', "")
        attachment.cpe_serial_number = ""
        self.assertError(attachment, 'cpe_serial_number', "")
        attachment = Attachment(cpe_serial_number=1)  # value must be a string
        self.assertError(attachment, 'cpe_serial_number', 1)
        attachment.cpe_serial_number = 1
        self.assertError(attachment, 'cpe_serial_number', 1)
        attachment = Attachment(cpe_serial_number=310150123456789)
        self.assertError(attachment, 'cpe_serial_number', 310150123456789)
        attachment = Attachment()
        attachment.cpe_serial_number = '310150123456789'
        self.assertEqual(Attachment(cpe_serial_number='310150123456789'), attachment)
        attachment.cpe_serial_number = '515020987654321'
        self.assertNotEqual(Attachment(cpe_serial_number='310150123456789'), attachment)

    def test_attachment_cpe_name(self):
        """Test field checking of attachment.cpe_name"""
        attachment = Attachment(cpe_name="")  # value can't be empty string
        self.assertError(attachment, 'cpe_name', "")
        attachment.cpe_name = ""
        self.assertError(attachment, 'cpe_name', "")
        attachment = Attachment(cpe_name=1)
        self.assertError(attachment, 'cpe_name', 1)
        attachment.cpe_name = 1
        self.assertError(attachment, 'cpe_name', 1)
        attachment.cpe_name = 310150123456789
        self.assertError(attachment, 'cpe_name', 310150123456789)
        attachment = Attachment()
        attachment.cpe_name = '310150123456789'
        self.assertEqual(Attachment(cpe_name='310150123456789'), attachment)
        attachment.cpe_name = '515020987654321'
        self.assertNotEqual(Attachment(cpe_name='310150123456789'), attachment)

    def test_attachment_device_mac_address(self):
        """Test field checking of attachment.device_mac_address"""
        attachment = Attachment(device_mac_address="")  # value can't be empty string
        self.assertError(attachment, 'device_mac_address', "")
        attachment.device_mac_address = ""
        self.assertError(attachment, 'device_mac_address', "")
        attachment = Attachment(device_mac_address=1)  # value must be a string
        self.assertError(attachment, 'device_mac_address', 1)
        attachment.device_mac_address = 1
        self.assertError(attachment, 'device_mac_address', 1)
        attachment = Attachment(device_mac_address='zz')  # value must be a mac address
        self.assertError(attachment, 'device_mac_address', "zz")
        attachment.device_mac_address = 'zz'
        self.assertError(attachment, 'device_mac_address', "zz")
        attachment = Attachment(device_mac_address = '00:00:00:00:00:00')  # value can't be the zero mac
        self.assertError(attachment, 'device_mac_address', "00:00:00:00:00:00")
        attachment.device_mac_address = '00:00:00:00:00:00'
        self.assertError(attachment, 'device_mac_address', "00:00:00:00:00:00")
        attachment = Attachment()
        attachment.device_mac_address = '00:11:22:33:44:55'
        self.assertEqual(Attachment(device_mac_address='001122334455'), attachment)
        attachment.device_mac_address = '00:11:22:33:44:66'
        self.assertNotEqual(Attachment(device_mac_address='00:11:22:33:44:55'), attachment)

    def test_attachment_username(self):
        """Test field checking of attachment.username"""
        attachment = Attachment(username="")  # value can't be empty string
        self.assertError(attachment, 'username', "")
        attachment.username = ""
        self.assertError(attachment, 'username', "")
        attachment = Attachment(username=1)
        self.assertError(attachment, 'username', 1)
        attachment.username = 1
        self.assertError(attachment, 'username', 1)
        attachment.username = 310150123456789
        self.assertError(attachment, 'username', 310150123456789)
        attachment = Attachment()
        attachment.username = '310150123456789'
        self.assertEqual(Attachment(username='310150123456789'), attachment)
        attachment.username = '515020987654321'
        self.assertNotEqual(Attachment(username='310150123456789'), attachment)

    def test_attachment_mac_addresses(self):
        """Test field checking of attachment.mac_addresses"""
        with self.assertRaises(AttributeError):
            Attachment(mac_addresses=None)  # can't set repeated element directly
        with self.assertRaises(AttributeError):
            Attachment().mac_addresses = []
        attachment = Attachment()
        attachment.mac_addresses.append("00:11:22:33:44:ZZ")
        self.assertError(attachment, 'mac_addresses', "00:11:22:33:44:ZZ")
        self.assertFalse(attachment.mac_addresses)
        attachment.mac_addresses.append(1)
        self.assertError(attachment, 'mac_addresses', 1)
        self.assertFalse(attachment.mac_addresses)
        attachment.mac_addresses.append("")
        self.assertError(attachment, 'mac_addresses', "")
        self.assertFalse(attachment.mac_addresses)
        attachment.mac_addresses.append(None)
        self.assertError(attachment, 'mac_addresses', None)
        self.assertFalse(attachment.mac_addresses)
        attachment.mac_addresses.append('00:00:00:00:00:00')
        self.assertError(attachment, 'mac_addresses', '00:00:00:00:00:00')
        self.assertFalse(attachment.mac_addresses)
        attachment1 = Attachment()
        attachment1.mac_addresses.append('00:11:22:33:44:55')
        attachment2 = Attachment()
        attachment2.mac_addresses.extend(['00:11:22:33:44:55'])
        self.assertEqual(attachment1, attachment2)
        attachment2.mac_addresses.append('00:11:22:33:44:66')
        self.assertNotEqual(attachment1, attachment2)
        self.assertEqual(len(attachment1.mac_addresses), 1)
        self.assertEqual(len(attachment2.mac_addresses), 2)
        self.assertEqual(attachment2.mac_addresses[1], '00:11:22:33:44:66')
        del attachment2.mac_addresses[1]
        attachment1.mac_addresses[0] = '001122334455'
        self.assertEqual(attachment1, attachment2)

    def test_attachment_network_prefixes(self):
        """Test field checking of attachment.network_prefixes"""
        with self.assertRaises(AttributeError):
            Attachment(network_prefixes=None)  # can't set repeated element directly
        with self.assertRaises(AttributeError):
            Attachment().network_prefixes = []
        attachment = Attachment()
        attachment.network_prefixes.append("XYZ")
        self.assertError(attachment, 'network_prefixes', "XYZ")
        self.assertFalse(attachment.network_prefixes)
        attachment.network_prefixes.append(1.5)
        self.assertError(attachment, 'network_prefixes', 1.5)
        self.assertFalse(attachment.network_prefixes)
        attachment.network_prefixes.append("")
        self.assertError(attachment, 'network_prefixes', "")
        self.assertFalse(attachment.network_prefixes)
        attachment.network_prefixes.append(None)
        self.assertError(attachment, 'network_prefixes', None)
        self.assertFalse(attachment.network_prefixes)
        attachment.network_prefixes.append('0.0.0.0/0')
        self.assertError(attachment, 'network_prefixes', '0.0.0.0/0')
        self.assertFalse(attachment.network_prefixes)
        attachment.network_prefixes.append('::/0')
        self.assertError(attachment, 'network_prefixes', '::/0')
        self.assertFalse(attachment.network_prefixes)
        attachment1 = Attachment()
        attachment1.network_prefixes.append('192.0.2.1')
        attachment2 = Attachment()
        attachment2.network_prefixes.extend(['192.0.2.1/32'])
        self.assertEqual(attachment1, attachment2)
        attachment2.network_prefixes.append('2001:0DB8:1234:0000:0000:0000:0000:0000/48')
        self.assertNotEqual(attachment1, attachment2)
        self.assertEqual(len(attachment1.network_prefixes), 1)
        self.assertEqual(len(attachment2.network_prefixes), 2)
        self.assertEqual(attachment2.network_prefixes[1], '2001:db8:1234::/48')
        del attachment2.network_prefixes[0]
        attachment1.network_prefixes[0] = '2001:db8:1234::/48'
        self.assertEqual(attachment1, attachment2)

    def test_attachment_network_prefixes_no_dups(self):
        attachment1 = Attachment()
        attachment1.network_prefixes.append('192.0.2.1')
        self.assertEqual(len(attachment1.network_prefixes), 1)
        self.assertEqual(attachment1.network_prefixes[0], '192.0.2.1')

        attachment1.network_prefixes.append('192.0.2.1')
        self.assertEqual(len(attachment1.network_prefixes), 1)
        self.assertEqual(attachment1.network_prefixes[0], '192.0.2.1')

        attachment1 = Attachment()
        attachment1.network_prefixes.extend(['192.0.2.1/32', '192.0.2.1'])
        self.assertEqual(len(attachment1.network_prefixes), 1)
        self.assertEqual(attachment1.network_prefixes[0], '192.0.2.1')

        attachment1.network_prefixes.extend(['192.0.2.1/32', '192.0.2.1'])
        self.assertEqual(len(attachment1.network_prefixes), 1)
        self.assertEqual(attachment1.network_prefixes[0], '192.0.2.1')

    def test_attachment_mac_addresses_no_dups(self):
        mac = '00:11:22:33:44:55'
        attachment1 = Attachment()
        attachment1.mac_addresses.append(mac)
        self.assertEqual(len(attachment1.mac_addresses), 1)
        self.assertEqual(attachment1.mac_addresses[0], mac)

        attachment1.mac_addresses.append(mac)
        self.assertEqual(len(attachment1.mac_addresses), 1)
        self.assertEqual(attachment1.mac_addresses[0], mac)

        attachment1 = Attachment()
        attachment1.mac_addresses.extend([mac, '0:11:22:33:44:55'])
        self.assertEqual(len(attachment1.mac_addresses), 1)
        self.assertEqual(attachment1.mac_addresses[0], mac)

        attachment1.mac_addresses.extend([mac, '0:11:22:33:44:55'])
        self.assertEqual(len(attachment1.mac_addresses), 1)
        self.assertEqual(attachment1.mac_addresses[0], mac)

    def test_attachment_mac_addresses_order(self):
        """order of MAC addresses doesn't matter"""
        mac1 = '00:11:22:33:44:55'
        mac2 = '00:11:22:33:44:66'
        attachment1 = Attachment()
        attachment1.mac_addresses.append(mac1)
        attachment1.mac_addresses.append(mac2)

        attachment2 = Attachment()
        attachment2.mac_addresses.append(mac2)
        attachment2.mac_addresses.append(mac1)

        self.assertEqual(attachment1, attachment2)

    def test_attachment_network_prefix_order(self):
        """order of network prefixesdoesn't matter"""
        ip1 = '192.0.2.1'
        ip2 = '192.0.2.2'
        attachment1 = Attachment()
        attachment1.network_prefixes.append(ip1)
        attachment1.network_prefixes.append(ip2)

        attachment2 = Attachment()
        attachment2.network_prefixes.append(ip2)
        attachment2.network_prefixes.append(ip1)

        self.assertEqual(attachment1, attachment2)

    def test_site_id(self):
        """Test field checking of site.id"""
        with self.assertRaises(TypeError):
            Site()  # id is required
        with self.assertRaises(ValueError):
            Site(None)  # id can't be None
        with self.assertRaises(ValueError):
            Site("")  # id can't be empty string
        site = Site(123)
        self.assertEqual(site.id, "123")  # int converted to string
        site = Site(0)
        self.assertEqual(site.id, "0")  # int converted to string
        site = Site("my_id")
        with self.assertRaises(AttributeError):
            site.id = "new_id"  # id is immutable once set

    def test_site_name(self):
        """Test field checking of site.name"""
        site = Site("a1", name=None)
        self.assertEqual(site.name, None)
        site = Site("a1", name="")
        self.assertError(site, 'name', '')  # name can't be empty
        site = Site("a1", name='site 1')
        self.assertEqual(site.name, 'site 1')
        site.name = 123
        self.assertError(site, 'name', 123)  # name must be a string
        self.assertEqual(site.name, 'site 1')
        site.name = 'site 2'
        self.assertEqual(site.name, 'site 2')
        site.name = None
        self.assertEqual(site.name, None)

    def test_site_network_prefixes(self):
        """Test field checking of site.network_prefixes"""
        site = Site('s1')
        self.assertEqual(site.network_prefixes, [])
        site = Site('s1', network_prefixes=None)
        self.assertEqual(site.network_prefixes, [])
        with self.assertRaises(TypeError):
            site.network_prefixes = ''  # must be a list
        site.network_prefixes.append('192.0.2.1')
        self.assertEqual(site.network_prefixes[0], '192.0.2.1')

    def test_package_id(self):
        """Test field checking of package.id"""
        with self.assertRaises(TypeError):
            Package()  # id is required
        with self.assertRaises(ValueError):
            Package(None)  # id can't be None
        with self.assertRaises(ValueError):
            Package("")  # id can't be empty string
        pkg = Package(123)
        self.assertEqual(pkg.id, "123")  # int converted to string
        pkg = Package(0)
        self.assertEqual(pkg.id, "0")  # int converted to string
        pkg = Package("my_id")
        with self.assertRaises(AttributeError):
            pkg.id = "new_id"  # id is immutable once set

    def test_package_name(self):
        """Test field checking of package.name"""
        pkg = Package("p1", name=None)
        self.assertEqual(pkg.name, None)
        pkg = Package("p1", name="")
        self.assertError(pkg, 'name', '')  # name can't be empty
        pkg = Package("p1", name='Gold Plan')
        self.assertEqual(pkg.name, 'Gold Plan')
        pkg.name = 123
        self.assertError(pkg, 'name', 123)  # name must be a string
        self.assertEqual(pkg.name, 'Gold Plan')
        pkg.name = 'Silver Plan'
        self.assertEqual(pkg.name, 'Silver Plan')
        pkg.name = None
        self.assertEqual(pkg.name, None)

    def test_package_download_rate(self):
        """Test field checking of package.download_rate"""
        pkg = Package("p1")
        self.assertEqual(pkg.download_rate, None)
        pkg = Package("p1", download_rate=None)
        self.assertEqual(pkg.download_rate, None)
        pkg = Package("p1", download_rate=0)
        self.assertEqual(pkg.download_rate, 0)
        pkg.download_rate = 100000
        self.assertEqual(pkg.download_rate, 100000)
        pkg.download_rate = "lots"
        self.assertError(pkg, 'download_rate', 'lots')
        self.assertEqual(pkg.download_rate, 100000)
        pkg.download_rate = 1.7
        self.assertEqual(pkg.download_rate, 1)

    def test_package_upload_rate(self):
        """Test field checking of package.upload_rate"""
        pkg = Package("p1")
        self.assertEqual(pkg.upload_rate, None)
        pkg = Package("p1", upload_rate=None)
        self.assertEqual(pkg.upload_rate, None)
        pkg = Package("p1", upload_rate=0)
        self.assertEqual(pkg.upload_rate, 0)
        pkg.upload_rate = 50000
        self.assertEqual(pkg.upload_rate, 50000)
        pkg.upload_rate = "lots"
        self.assertError(pkg, 'upload_rate', 'lots')
        self.assertEqual(pkg.upload_rate, 50000)
        pkg.upload_rate = 1.7
        self.assertEqual(pkg.upload_rate, 1)

    def test_package_equivalence(self):
        """Test that equivalence works for Package."""
        p1 = Package('p1', name='Gold', download_rate=100000, upload_rate=50000)
        p2 = Package('p1', name='Gold', download_rate=100000, upload_rate=50000)
        p3 = Package('p1', name='Silver', download_rate=100000, upload_rate=50000)
        self.assertEqual(p1, p2)
        self.assertNotEqual(p1, p3)

    def test_package_nm(self):
        """Test that the package object generates a good NetworkMetadataReference."""
        pkg = Package("p1")
        ref = NetworkMetadataReference('package', 'p1', {})
        self.assertEqual(pkg.get_nm(), [ref])
        pkg.name = ref.attributes['service_name'] = 'Gold Plan'
        self.assertEqual(pkg.get_nm(), [ref])
        pkg.download_rate = ref.attributes['service_rate_down'] = 100000
        self.assertEqual(pkg.get_nm(), [ref])
        pkg.upload_rate = ref.attributes['service_rate_up'] = 50000
        self.assertEqual(pkg.get_nm(), [ref])
        ref.attributes['system'] = 'my-system'
        self.assertEqual(pkg.get_nm('my-system'), [ref])

    def test_element_poller_hash(self):
        """Test field checking of element.poller_hash"""
        elem = Olt("elem1", poller_hash=None)
        self.assertEqual(elem.poller_hash, None)
        elem = Olt("elem1", poller_hash="")
        self.assertEqual(elem.poller_hash, None)
        self.assertError(elem, 'poller_hash', "")
        elem.poller_hash = b'asdf'
        self.assertError(elem, 'poller_hash', b"asdf")
        poller_hash = sha256(b"asdf").digest()
        elem.poller_hash = poller_hash
        self.assertEqual(elem.poller_hash, poller_hash)
