class DeletedEvent(Event): ELEMENT_NAME = 'DeletedEvent' FIELDS = [ # type: List[Field] TextField('watermark', field_uri='Watermark', is_required=False), DateTimeField('timestamp', field_uri='TimeStamp'), IdAndChangekeyField('parent_folder_id', field_uri='ParentFolderId', is_required=True, is_attribute=False), ] __slots__ = ('watermark', 'timestamp', 'parent_folder_id')
class ModifiedEvent(Event): ELEMENT_NAME = 'ModifiedEvent' FIELDS = [ # type: List[Field] TextField('watermark', field_uri='Watermark', is_required=False), DateTimeField('timestamp', field_uri='TimeStamp'), IdAndChangekeyField('parent_folder_id', field_uri='ParentFolderId', is_required=True, is_attribute=False), IntegerField('unread_count', field_uri='UnreadCount', is_read_only=True), ] __slots__ = ('watermark', 'timestamp', 'parent_folder_id', 'unread_count')
class FreeBusyChangedEvent(Event): ELEMENT_NAME = 'FreeBusyChangedEvent' FIELDS = [ TextField('watermark', field_uri='Watermark', is_required=False), DateTimeField('timestamp', field_uri='TimeStamp'), IdAndChangekeyField('item_id', field_uri='ItemId', is_required=True, is_attribute=False), IdAndChangekeyField('parent_folder_id', field_uri='ParentFolderId', is_required=True, is_attribute=False), ] __slots__ = ('watermark', 'timestamp', 'item_id', 'parent_folder_id')
def test_naive_datetime(self): # Test that we can survive naive datetimes on a datetime field tz = zoneinfo.ZoneInfo("Europe/Copenhagen") utc = zoneinfo.ZoneInfo("UTC") account = namedtuple("Account", ["default_timezone"])(default_timezone=tz) default_value = datetime.datetime(2017, 1, 2, 3, 4, tzinfo=tz) field = DateTimeField("foo", field_uri="item:DateTimeSent", default=default_value) # TZ-aware datetime string payload = b"""\ <?xml version="1.0" encoding="utf-8"?> <Envelope xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <t:Item> <t:DateTimeSent>2017-06-21T18:40:02Z</t:DateTimeSent> </t:Item> </Envelope>""" elem = to_xml(payload).find(f"{{{TNS}}}Item") self.assertEqual(field.from_xml(elem=elem, account=account), datetime.datetime(2017, 6, 21, 18, 40, 2, tzinfo=utc)) # Naive datetime string is localized to tz of the account payload = b"""\ <?xml version="1.0" encoding="utf-8"?> <Envelope xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <t:Item> <t:DateTimeSent>2017-06-21T18:40:02</t:DateTimeSent> </t:Item> </Envelope>""" elem = to_xml(payload).find(f"{{{TNS}}}Item") self.assertEqual(field.from_xml(elem=elem, account=account), datetime.datetime(2017, 6, 21, 18, 40, 2, tzinfo=tz)) # Garbage string returns None payload = b"""\ <?xml version="1.0" encoding="utf-8"?> <Envelope xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <t:Item> <t:DateTimeSent>THIS_IS_GARBAGE</t:DateTimeSent> </t:Item> </Envelope>""" elem = to_xml(payload).find(f"{{{TNS}}}Item") self.assertEqual(field.from_xml(elem=elem, account=account), None) # Element not found returns default value payload = b"""\ <?xml version="1.0" encoding="utf-8"?> <Envelope xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <t:Item> </t:Item> </Envelope>""" elem = to_xml(payload).find(f"{{{TNS}}}Item") self.assertEqual(field.from_xml(elem=elem, account=account), default_value)
def test_naive_datetime(self): # Test that we can survive naive datetimes on a datetime field tz = EWSTimeZone.timezone('Europe/Copenhagen') account = namedtuple('Account', ['default_timezone'])(default_timezone=tz) default_value = tz.localize(EWSDateTime(2017, 1, 2, 3, 4)) field = DateTimeField('foo', field_uri='item:DateTimeSent', default=default_value) # TZ-aware datetime string payload = b'''\ <?xml version="1.0" encoding="utf-8"?> <Envelope xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <t:Item> <t:DateTimeSent>2017-06-21T18:40:02Z</t:DateTimeSent> </t:Item> </Envelope>''' elem = to_xml(payload).find('{%s}Item' % TNS) self.assertEqual(field.from_xml(elem=elem, account=account), UTC.localize(EWSDateTime(2017, 6, 21, 18, 40, 2))) # Naive datetime string is localized to tz of the account payload = b'''\ <?xml version="1.0" encoding="utf-8"?> <Envelope xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <t:Item> <t:DateTimeSent>2017-06-21T18:40:02</t:DateTimeSent> </t:Item> </Envelope>''' elem = to_xml(payload).find('{%s}Item' % TNS) self.assertEqual(field.from_xml(elem=elem, account=account), tz.localize(EWSDateTime(2017, 6, 21, 18, 40, 2))) # Garbage string returns None payload = b'''\ <?xml version="1.0" encoding="utf-8"?> <Envelope xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <t:Item> <t:DateTimeSent>THIS_IS_GARBAGE</t:DateTimeSent> </t:Item> </Envelope>''' elem = to_xml(payload).find('{%s}Item' % TNS) self.assertEqual(field.from_xml(elem=elem, account=account), None) # Element not found returns default value payload = b'''\ <?xml version="1.0" encoding="utf-8"?> <Envelope xmlns:t="http://schemas.microsoft.com/exchange/services/2006/types"> <t:Item> </t:Item> </Envelope>''' elem = to_xml(payload).find('{%s}Item' % TNS) self.assertEqual(field.from_xml(elem=elem, account=account), default_value)
def test_value_validation(self): field = TextField('foo', field_uri='bar', is_required=True, default=None) with self.assertRaises(ValueError) as e: field.clean(None) # Must have a default value on None input self.assertEqual(str(e.exception), "'foo' is a required field with no default") field = TextField('foo', field_uri='bar', is_required=True, default='XXX') self.assertEqual(field.clean(None), 'XXX') field = CharListField('foo', field_uri='bar') with self.assertRaises(ValueError) as e: field.clean('XXX') # Must be a list type self.assertEqual(str(e.exception), "Field 'foo' value 'XXX' must be a list") field = CharListField('foo', field_uri='bar') with self.assertRaises(TypeError) as e: field.clean([1, 2, 3]) # List items must be correct type self.assertEqual(str(e.exception), "Field 'foo' value 1 must be of type <class 'str'>") field = CharField('foo', field_uri='bar') with self.assertRaises(TypeError) as e: field.clean(1) # Value must be correct type self.assertEqual(str(e.exception), "Field 'foo' value 1 must be of type <class 'str'>") with self.assertRaises(ValueError) as e: field.clean('X' * 256) # Value length must be within max_length self.assertEqual( str(e.exception), "'foo' value 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' exceeds length 255" ) field = DateTimeField('foo', field_uri='bar') with self.assertRaises(ValueError) as e: field.clean(EWSDateTime(2017, 1, 1)) # Datetime values must be timezone aware self.assertEqual(str(e.exception), "Value '2017-01-01 00:00:00' on field 'foo' must be timezone aware") field = ChoiceField('foo', field_uri='bar', choices=[Choice('foo'), Choice('bar')]) with self.assertRaises(ValueError) as e: field.clean('XXX') # Value must be a valid choice self.assertEqual(str(e.exception), "Invalid choice 'XXX' for field 'foo'. Valid choices are: foo, bar") # A few tests on extended properties that override base methods field = ExtendedPropertyField('foo', value_cls=ExternId, is_required=True) with self.assertRaises(ValueError) as e: field.clean(None) # Value is required self.assertEqual(str(e.exception), "'foo' is a required field") with self.assertRaises(TypeError) as e: field.clean(123) # Correct type is required self.assertEqual(str(e.exception), "'ExternId' value 123 must be an instance of <class 'str'>") self.assertEqual(field.clean('XXX'), 'XXX') # We can clean a simple value and keep it as a simple value self.assertEqual(field.clean(ExternId('XXX')), ExternId('XXX')) # We can clean an ExternId instance as well class ExternIdArray(ExternId): property_type = 'StringArray' field = ExtendedPropertyField('foo', value_cls=ExternIdArray, is_required=True) with self.assertRaises(ValueError)as e: field.clean(None) # Value is required self.assertEqual(str(e.exception), "'foo' is a required field") with self.assertRaises(ValueError)as e: field.clean(123) # Must be an iterable self.assertEqual(str(e.exception), "'ExternIdArray' value 123 must be a list") with self.assertRaises(TypeError) as e: field.clean([123]) # Correct type is required self.assertEqual(str(e.exception), "'ExternIdArray' value element 123 must be an instance of <class 'str'>") # Test min/max on IntegerField field = IntegerField('foo', field_uri='bar', min=5, max=10) with self.assertRaises(ValueError) as e: field.clean(2) self.assertEqual(str(e.exception), "Value 2 on field 'foo' must be greater than 5") with self.assertRaises(ValueError)as e: field.clean(12) self.assertEqual(str(e.exception), "Value 12 on field 'foo' must be less than 10") # Test min/max on DecimalField field = DecimalField('foo', field_uri='bar', min=5, max=10) with self.assertRaises(ValueError) as e: field.clean(Decimal(2)) self.assertEqual(str(e.exception), "Value Decimal('2') on field 'foo' must be greater than 5") with self.assertRaises(ValueError)as e: field.clean(Decimal(12)) self.assertEqual(str(e.exception), "Value Decimal('12') on field 'foo' must be less than 10") # Test enum validation field = EnumField('foo', field_uri='bar', enum=['a', 'b', 'c']) with self.assertRaises(ValueError)as e: field.clean(0) # Enums start at 1 self.assertEqual(str(e.exception), "Value 0 on field 'foo' must be greater than 1") with self.assertRaises(ValueError) as e: field.clean(4) # Spills over list self.assertEqual(str(e.exception), "Value 4 on field 'foo' must be less than 3") with self.assertRaises(ValueError) as e: field.clean('d') # Value not in enum self.assertEqual(str(e.exception), "Value 'd' on field 'foo' must be one of ['a', 'b', 'c']") # Test enum list validation field = EnumListField('foo', field_uri='bar', enum=['a', 'b', 'c']) with self.assertRaises(ValueError)as e: field.clean([]) self.assertEqual(str(e.exception), "Value '[]' on field 'foo' must not be empty") with self.assertRaises(ValueError) as e: field.clean([0]) self.assertEqual(str(e.exception), "Value 0 on field 'foo' must be greater than 1") with self.assertRaises(ValueError) as e: field.clean([1, 1]) # Values must be unique self.assertEqual(str(e.exception), "List entries '[1, 1]' on field 'foo' must be unique") with self.assertRaises(ValueError) as e: field.clean(['d']) self.assertEqual(str(e.exception), "List value 'd' on field 'foo' must be one of ['a', 'b', 'c']")