def test_item_attachments(self): item = self.get_test_item(folder=self.test_folder) item.attachments = [] attached_item1 = self.get_test_item(folder=self.test_folder) attached_item1.attachments = [] attached_item1.save() attachment1 = ItemAttachment(name='attachment1', item=attached_item1) item.attach(attachment1) self.assertEqual(len(item.attachments), 1) item.save() fresh_item = list(self.account.fetch(ids=[item]))[0] self.assertEqual(len(fresh_item.attachments), 1) fresh_attachments = sorted(fresh_item.attachments, key=lambda a: a.name) self.assertEqual(fresh_attachments[0].name, 'attachment1') self.assertIsInstance(fresh_attachments[0].item, self.ITEM_CLASS) for f in self.ITEM_CLASS.FIELDS: with self.subTest(f=f): # Normalize some values we don't control if f.is_read_only: continue if self.ITEM_CLASS == CalendarItem and f in CalendarItem.timezone_fields( ): # Timezone fields will (and must) be populated automatically from the timestamp continue if isinstance(f, ExtendedPropertyField): # Attachments don't have these values. It may be possible to request it if we can find the FieldURI continue if f.name == 'is_read': # This is always true for item attachments? continue if f.name == 'reminder_due_by': # EWS sets a default value if it is not set on insert. Ignore continue if f.name == 'mime_content': # This will change depending on other contents fields continue old_val = getattr(attached_item1, f.name) new_val = getattr(fresh_attachments[0].item, f.name) if f.is_list: old_val, new_val = set(old_val or ()), set(new_val or ()) self.assertEqual(old_val, new_val, (f.name, old_val, new_val)) # Test attach on saved object attached_item2 = self.get_test_item(folder=self.test_folder) attached_item2.attachments = [] attached_item2.save() attachment2 = ItemAttachment(name='attachment2', item=attached_item2) item.attach(attachment2) self.assertEqual(len(item.attachments), 2) fresh_item = list(self.account.fetch(ids=[item]))[0] self.assertEqual(len(fresh_item.attachments), 2) fresh_attachments = sorted(fresh_item.attachments, key=lambda a: a.name) self.assertEqual(fresh_attachments[0].name, 'attachment1') self.assertIsInstance(fresh_attachments[0].item, self.ITEM_CLASS) for f in self.ITEM_CLASS.FIELDS: with self.subTest(f=f): # Normalize some values we don't control if f.is_read_only: continue if self.ITEM_CLASS == CalendarItem and f in CalendarItem.timezone_fields( ): # Timezone fields will (and must) be populated automatically from the timestamp continue if isinstance(f, ExtendedPropertyField): # Attachments don't have these values. It may be possible to request it if we can find the FieldURI continue if f.name == 'reminder_due_by': # EWS sets a default value if it is not set on insert. Ignore continue if f.name == 'is_read': # This is always true for item attachments? continue if f.name == 'mime_content': # This will change depending on other contents fields continue old_val = getattr(attached_item1, f.name) new_val = getattr(fresh_attachments[0].item, f.name) if f.is_list: old_val, new_val = set(old_val or ()), set(new_val or ()) self.assertEqual(old_val, new_val, (f.name, old_val, new_val)) self.assertEqual(fresh_attachments[1].name, 'attachment2') self.assertIsInstance(fresh_attachments[1].item, self.ITEM_CLASS) for f in self.ITEM_CLASS.FIELDS: # Normalize some values we don't control if f.is_read_only: continue if self.ITEM_CLASS == CalendarItem and f in CalendarItem.timezone_fields( ): # Timezone fields will (and must) be populated automatically from the timestamp continue if isinstance(f, ExtendedPropertyField): # Attachments don't have these values. It may be possible to request it if we can find the FieldURI continue if f.name == 'reminder_due_by': # EWS sets a default value if it is not set on insert. Ignore continue if f.name == 'is_read': # This is always true for item attachments? continue if f.name == 'mime_content': # This will change depending on other contents fields continue old_val = getattr(attached_item2, f.name) new_val = getattr(fresh_attachments[1].item, f.name) if f.is_list: old_val, new_val = set(old_val or ()), set(new_val or ()) self.assertEqual(old_val, new_val, (f.name, old_val, new_val)) # Test detach item.detach(attachment2) self.assertTrue(attachment2.attachment_id is None) self.assertTrue(attachment2.parent_item is None) fresh_item = list(self.account.fetch(ids=[item]))[0] self.assertEqual(len(fresh_item.attachments), 1) fresh_attachments = sorted(fresh_item.attachments, key=lambda a: a.name) for f in self.ITEM_CLASS.FIELDS: with self.subTest(f=f): # Normalize some values we don't control if f.is_read_only: continue if self.ITEM_CLASS == CalendarItem and f in CalendarItem.timezone_fields( ): # Timezone fields will (and must) be populated automatically from the timestamp continue if isinstance(f, ExtendedPropertyField): # Attachments don't have these values. It may be possible to request it if we can find the FieldURI continue if f.name == 'reminder_due_by': # EWS sets a default value if it is not set on insert. Ignore continue if f.name == 'is_read': # This is always true for item attachments? continue if f.name == 'mime_content': # This will change depending on other contents fields continue old_val = getattr(attached_item1, f.name) new_val = getattr(fresh_attachments[0].item, f.name) if f.is_list: old_val, new_val = set(old_val or ()), set(new_val or ()) self.assertEqual(old_val, new_val, (f.name, old_val, new_val)) # Test attach with non-saved item attached_item3 = self.get_test_item(folder=self.test_folder) attached_item3.attachments = [] attachment3 = ItemAttachment(name='attachment2', item=attached_item3) item.attach(attachment3) item.detach(attachment3)
def test_item(self): # Test insert insert_kwargs = self.get_random_insert_kwargs() insert_kwargs['categories'] = self.categories item = self.ITEM_CLASS(folder=self.test_folder, **insert_kwargs) # Test with generator as argument insert_ids = self.test_folder.bulk_create(items=(i for i in [item])) self.assertEqual(len(insert_ids), 1) self.assertIsInstance(insert_ids[0], BaseItem) find_ids = list( self.test_folder.filter( categories__contains=item.categories).values_list( 'id', 'changekey')) self.assertEqual(len(find_ids), 1) self.assertEqual(len(find_ids[0]), 2, find_ids[0]) self.assertEqual(insert_ids, find_ids) # Test with generator as argument item = list(self.account.fetch(ids=(i for i in find_ids)))[0] for f in self.ITEM_CLASS.FIELDS: with self.subTest(f=f): if not f.supports_version(self.account.version): # Cannot be used with this EWS version continue if self.ITEM_CLASS == CalendarItem and f in CalendarItem.timezone_fields( ): # Timezone fields will (and must) be populated automatically from the timestamp continue if f.is_read_only: continue if f.name == 'reminder_due_by': # EWS sets a default value if it is not set on insert. Ignore continue if f.name == 'mime_content': # This will change depending on other contents fields continue old, new = getattr(item, f.name), insert_kwargs[f.name] if f.is_list: old, new = set(old or ()), set(new or ()) self.assertEqual(old, new, (f.name, old, new)) # Test update update_kwargs = self.get_random_update_kwargs( item=item, insert_kwargs=insert_kwargs) if self.ITEM_CLASS in (Contact, DistributionList): # Contact and DistributionList don't support mime_type updates at all update_kwargs.pop('mime_content', None) update_fieldnames = [ f for f in update_kwargs.keys() if f != 'attachments' ] for k, v in update_kwargs.items(): setattr(item, k, v) # Test with generator as argument update_ids = self.account.bulk_update( items=(i for i in [(item, update_fieldnames)])) self.assertEqual(len(update_ids), 1) self.assertEqual(len(update_ids[0]), 2, update_ids) self.assertEqual(insert_ids[0].id, update_ids[0][0]) # ID should be the same self.assertNotEqual( insert_ids[0].changekey, update_ids[0][1]) # Changekey should change when item is updated item = list(self.account.fetch(update_ids))[0] for f in self.ITEM_CLASS.FIELDS: with self.subTest(f=f): if not f.supports_version(self.account.version): # Cannot be used with this EWS version continue if self.ITEM_CLASS == CalendarItem and f in CalendarItem.timezone_fields( ): # Timezone fields will (and must) be populated automatically from the timestamp continue if f.is_read_only or f.is_read_only_after_send: # These cannot be changed continue if f.name == 'mime_content': # This will change depending on other contents fields continue old, new = getattr(item, f.name), update_kwargs[f.name] if f.name == 'reminder_due_by': if old is None: # EWS does not always return a value if reminder_is_set is False. Set one now item.reminder_due_by = new continue if new is not None: # EWS sometimes randomly sets the new reminder due date to one month before or after we # wanted it, and sometimes 30 days before or after. But only sometimes... old_date = old.astimezone( self.account.default_timezone).date() new_date = new.astimezone( self.account.default_timezone).date() if getattr(item, 'is_all_day', False) and old_date == new_date: # There is some weirdness with the time part of the reminder_due_by value for all-day events item.reminder_due_by = new continue if relativedelta(month=1) + new_date == old_date: item.reminder_due_by = new continue if relativedelta(month=1) + old_date == new_date: item.reminder_due_by = new continue if abs(old_date - new_date) == datetime.timedelta(days=30): item.reminder_due_by = new continue if f.is_list: old, new = set(old or ()), set(new or ()) self.assertEqual(old, new, (f.name, old, new)) # Test wiping or removing fields wipe_kwargs = {} for f in self.ITEM_CLASS.FIELDS: if not f.supports_version(self.account.version): # Cannot be used with this EWS version continue if self.ITEM_CLASS == CalendarItem and f in CalendarItem.timezone_fields( ): # Timezone fields will (and must) be populated automatically from the timestamp continue if f.is_required or f.is_required_after_save: # These cannot be deleted continue if f.is_read_only or f.is_read_only_after_send: # These cannot be changed continue wipe_kwargs[f.name] = None for k, v in wipe_kwargs.items(): setattr(item, k, v) wipe_ids = self.account.bulk_update([ (item, update_fieldnames), ]) self.assertEqual(len(wipe_ids), 1) self.assertEqual(len(wipe_ids[0]), 2, wipe_ids) self.assertEqual(insert_ids[0].id, wipe_ids[0][0]) # ID should be the same self.assertNotEqual( insert_ids[0].changekey, wipe_ids[0] [1]) # Changekey should not be the same when item is updated item = list(self.account.fetch(wipe_ids))[0] for f in self.ITEM_CLASS.FIELDS: with self.subTest(f=f): if not f.supports_version(self.account.version): # Cannot be used with this EWS version continue if self.ITEM_CLASS == CalendarItem and f in CalendarItem.timezone_fields( ): # Timezone fields will (and must) be populated automatically from the timestamp continue if f.is_required or f.is_required_after_save: continue if f.is_read_only or f.is_read_only_after_send: continue old, new = getattr(item, f.name), wipe_kwargs[f.name] if f.is_list: old, new = set(old or ()), set(new or ()) self.assertEqual(old, new, (f.name, old, new)) try: self.ITEM_CLASS.register('extern_id', ExternId) # Test extern_id = None, which deletes the extended property entirely extern_id = None item.extern_id = extern_id wipe2_ids = self.account.bulk_update([ (item, ['extern_id']), ]) self.assertEqual(len(wipe2_ids), 1) self.assertEqual(len(wipe2_ids[0]), 2, wipe2_ids) self.assertEqual(insert_ids[0].id, wipe2_ids[0][0]) # ID must be the same self.assertNotEqual( insert_ids[0].changekey, wipe2_ids[0][1]) # Changekey must change when item is updated item = list(self.account.fetch(wipe2_ids))[0] self.assertEqual(item.extern_id, extern_id) finally: self.ITEM_CLASS.deregister('extern_id')
def get_random_update_kwargs(self, item, insert_kwargs): update_kwargs = {} now = UTC_NOW() for f in self.ITEM_CLASS.FIELDS: if not f.supports_version(self.account.version): # Cannot be used with this EWS version continue if self.ITEM_CLASS == CalendarItem and f in CalendarItem.timezone_fields( ): # Timezone fields will (and must) be populated automatically from the timestamp continue if f.is_read_only: # These cannot be changed continue if not item.is_draft and f.is_read_only_after_send: # These cannot be changed when the item is no longer a draft continue if f.name == 'message_id' and f.is_read_only_after_send: # Cannot be updated, regardless of draft status continue if f.name == 'attachments': # Testing attachments is heavy. Leave this to specific tests update_kwargs[f.name] = [] continue if f.name == 'resources': # The test server doesn't have any resources update_kwargs[f.name] = [] continue if isinstance(f, AttachmentField): # Attachments are handled separately continue if f.name == 'start': start = get_random_date(start_date=insert_kwargs['end'].date()) update_kwargs[f.name], update_kwargs['end'] = \ get_random_datetime_range(start_date=start, end_date=start, tz=self.account.default_timezone) update_kwargs['recurrence'] = self.random_val( self.ITEM_CLASS.get_field_by_fieldname('recurrence')) update_kwargs['recurrence'].boundary.start = update_kwargs[ f.name].date() continue if f.name == 'end': continue if f.name == 'recurrence': continue if f.name == 'due_date': # start_date must be before due_date, and before complete_date which must be in the past update_kwargs['start_date'], update_kwargs[f.name] = \ get_random_datetime_range(end_date=now.date(), tz=self.account.default_timezone) continue if f.name == 'start_date': continue if f.name == 'status': # Update task to a completed state. complete_date must be a date in the past, and < than start_date update_kwargs[f.name] = Task.COMPLETED update_kwargs['percent_complete'] = Decimal(100) continue if f.name == 'percent_complete': continue if f.name == 'reminder_is_set': if self.ITEM_CLASS == Task: # Task type doesn't allow updating 'reminder_is_set' to True update_kwargs[f.name] = False else: update_kwargs[f.name] = not insert_kwargs[f.name] continue if isinstance(f, BooleanField): update_kwargs[f.name] = not insert_kwargs[f.name] continue if f.value_cls in (Mailbox, Attendee): if insert_kwargs[f.name] is None: update_kwargs[f.name] = self.random_val(f) else: update_kwargs[f.name] = None continue update_kwargs[f.name] = self.random_val(f) if self.ITEM_CLASS == CalendarItem: # EWS always sets due date to 'start' update_kwargs['reminder_due_by'] = update_kwargs['start'] if update_kwargs.get('is_all_day', False): # For is_all_day items, EWS will remove the time part of start and end values update_kwargs['start'] = update_kwargs['start'].date() update_kwargs['end'] = (update_kwargs['end'] + datetime.timedelta(days=1)).date() return update_kwargs
def get_random_insert_kwargs(self): insert_kwargs = {} for f in self.ITEM_CLASS.FIELDS: if not f.supports_version(self.account.version): # Cannot be used with this EWS version continue if self.ITEM_CLASS == CalendarItem and f in CalendarItem.timezone_fields( ): # Timezone fields will (and must) be populated automatically from the timestamp continue if f.is_read_only: # These cannot be created continue if f.name == 'mime_content': # This needs special formatting. See separate test_mime_content() test continue if f.name == 'attachments': # Testing attachments is heavy. Leave this to specific tests insert_kwargs[f.name] = [] continue if f.name == 'resources': # The test server doesn't have any resources insert_kwargs[f.name] = [] continue if f.name == 'optional_attendees': # 'optional_attendees' and 'required_attendees' are mutually exclusive insert_kwargs[f.name] = None continue if f.name == 'start': start = get_random_date() insert_kwargs[f.name], insert_kwargs['end'] = \ get_random_datetime_range(start_date=start, end_date=start, tz=self.account.default_timezone) insert_kwargs['recurrence'] = self.random_val( self.ITEM_CLASS.get_field_by_fieldname('recurrence')) insert_kwargs['recurrence'].boundary.start = insert_kwargs[ f.name].date() continue if f.name == 'end': continue if f.name == 'is_all_day': # For CalendarItem instances, the 'is_all_day' attribute affects the 'start' and 'end' values. Changing # from 'false' to 'true' removes the time part of these datetimes. insert_kwargs['is_all_day'] = False continue if f.name == 'recurrence': continue if f.name == 'due_date': # start_date must be before due_date insert_kwargs['start_date'], insert_kwargs[f.name] = \ get_random_datetime_range(tz=self.account.default_timezone) continue if f.name == 'start_date': continue if f.name == 'status': # Start with an incomplete task status = get_random_choice( set(f.supported_choices(version=self.account.version)) - {Task.COMPLETED}) insert_kwargs[f.name] = status if status == Task.NOT_STARTED: insert_kwargs['percent_complete'] = Decimal(0) else: insert_kwargs['percent_complete'] = get_random_decimal( 1, 99) continue if f.name == 'percent_complete': continue insert_kwargs[f.name] = self.random_val(f) return insert_kwargs
def get_random_update_kwargs(self, item, insert_kwargs): update_kwargs = {} for f in self.ITEM_CLASS.FIELDS: if not f.supports_version(self.account.version): # Cannot be used with this EWS version continue if self.ITEM_CLASS == CalendarItem and f in CalendarItem.timezone_fields( ): # Timezone fields will (and must) be populated automatically from the timestamp continue if f.is_read_only: # These cannot be changed continue if not item.is_draft and f.is_read_only_after_send: # These cannot be changed when the item is no longer a draft continue if f.name == "message_id" and f.is_read_only_after_send: # Cannot be updated, regardless of draft status continue if f.name == "attachments": # Testing attachments is heavy. Leave this to specific tests update_kwargs[f.name] = [] continue if f.name == "resources": # The test server doesn't have any resources update_kwargs[f.name] = [] continue if isinstance(f, AttachmentField): # Attachments are handled separately continue if f.name == "start": start = get_random_date(start_date=insert_kwargs["end"].date()) update_kwargs[ f.name], update_kwargs["end"] = get_random_datetime_range( start_date=start, end_date=start, tz=self.account.default_timezone) update_kwargs["recurrence"] = self.random_val( self.ITEM_CLASS.get_field_by_fieldname("recurrence")) update_kwargs["recurrence"].boundary.start = update_kwargs[ f.name].date() continue if f.name == "start_date": update_kwargs[f.name] = get_random_datetime().date() update_kwargs["due_date"] = update_kwargs[f.name] # Don't set 'recurrence' here. It's difficult to test updates so we'll test task recurrence separately update_kwargs["recurrence"] = None continue if f.name == "end": continue if f.name == "recurrence": continue if f.name == "due_date": continue if f.name == "start_date": continue if f.name == "status": # Update task to a completed state update_kwargs[f.name] = Task.COMPLETED update_kwargs["percent_complete"] = Decimal(100) continue if f.name == "percent_complete": continue if f.name == "reminder_is_set": if self.ITEM_CLASS == Task: # Task type doesn't allow updating 'reminder_is_set' to True update_kwargs[f.name] = False else: update_kwargs[f.name] = not insert_kwargs[f.name] continue if isinstance(f, BooleanField): update_kwargs[f.name] = not insert_kwargs[f.name] continue if f.value_cls in (Mailbox, Attendee): if insert_kwargs[f.name] is None: update_kwargs[f.name] = self.random_val(f) else: update_kwargs[f.name] = None continue update_kwargs[f.name] = self.random_val(f) if self.ITEM_CLASS == CalendarItem: # EWS always sets due date to 'start' update_kwargs["reminder_due_by"] = update_kwargs["start"] if update_kwargs.get("is_all_day", False): # For is_all_day items, EWS will remove the time part of start and end values update_kwargs["start"] = update_kwargs["start"].date() update_kwargs["end"] = (update_kwargs["end"] + datetime.timedelta(days=1)).date() return update_kwargs