def test_representation(self): with admin_logged_in(): faq = self.factory.makeFAQ(title="Nothing works") with notify_modified(faq, ['keywords', 'content'], user=faq.owner): faq.keywords = "foo bar" faq.content = "It is all broken." faq_url = api_url(faq) webservice = webservice_for_person(self.factory.makePerson()) repr = webservice.get(faq_url, api_version='devel').jsonBody() with admin_logged_in(): self.assertThat( repr, ContainsDict({ "id": Equals(faq.id), "title": Equals("Nothing works"), "keywords": Equals("foo bar"), "content": Equals("It is all broken."), "date_created": MatchesRegex("\d\d\d\d-\d\d-\d\dT.*"), "date_last_updated": MatchesRegex("\d\d\d\d-\d\d-\d\dT.*"), "last_updated_by_link": Contains("/devel/~%s" % faq.owner.name), "target_link": Contains("/devel/%s" % faq.target.name), }))
def test_workitems_added_notification_message(self): """ Test that we get a notification for setting work items on a new specification.""" stub.test_emails = [] spec = self.factory.makeSpecification() # For API requests, lazr.restful does the notification; for this # test we need to call ourselves. with notify_modified(spec, ['workitems_text']): new_work_item = { 'title': 'A work item', 'status': SpecificationWorkItemStatus.TODO, 'assignee': None, 'milestone': None, 'sequence': 0 } login_person(spec.owner) spec.updateWorkItems([new_work_item]) transaction.commit() self.assertEqual(1, len(stub.test_emails)) rationale = 'Work items set to:\nWork items:\n%s: %s' % ( new_work_item['title'], new_work_item['status'].name) [email] = stub.test_emails # Actual message is part 2 of the email. msg = email[2] self.assertIn(rationale, msg)
def test_activity_rule_changed(self): owner = self.factory.makeTeam() member = self.factory.makePerson(member_of=[owner]) repository = self.factory.makeGitRepository(owner=owner) rule = self.factory.makeGitRule(repository=repository, ref_pattern="refs/heads/*") with person_logged_in(member): with notify_modified(rule, ["ref_pattern"]): rule.ref_pattern = "refs/heads/other/*" self.assertThat( repository.getActivity().first(), MatchesStructure(repository=Equals(repository), changer=Equals(member), changee=Is(None), what_changed=Equals(GitActivityType.RULE_CHANGED), old_value=MatchesDict({ "ref_pattern": Equals("refs/heads/*"), "position": Equals(0), }), new_value=MatchesDict({ "ref_pattern": Equals("refs/heads/other/*"), "position": Equals(0), })))
def setGrants(self, grants, user): """See `IGitRule`.""" self._validateGrants(grants) existing_grants = {(grant.grantee_type, grant.grantee): grant for grant in self.grants} new_grants = OrderedDict( ((grant.grantee_type, grant.grantee), grant) for grant in grants) for grant_key, grant in existing_grants.items(): if grant_key not in new_grants: grant.destroySelf(user) for grant_key, new_grant in new_grants.items(): grant = existing_grants.get(grant_key) if grant is None: new_grantee = (new_grant.grantee if new_grant.grantee_type == GitGranteeType.PERSON else new_grant.grantee_type) grant = self.addGrant(new_grantee, user, can_create=new_grant.can_create, can_push=new_grant.can_push, can_force_push=new_grant.can_force_push) else: edited_fields = [] with notify_modified(grant, edited_fields): for field in ("can_create", "can_push", "can_force_push"): if getattr(grant, field) != getattr(new_grant, field): setattr(grant, field, getattr(new_grant, field)) edited_fields.append(field)
def test_modifiedevent_sets_date_last_modified(self): # When a LiveFS receives an object modified event, the last modified # date is set to UTC_NOW. livefs = self.factory.makeLiveFS( date_created=datetime(2014, 4, 25, 10, 38, 0, tzinfo=pytz.UTC)) with notify_modified(removeSecurityProxy(livefs), ["name"]): pass self.assertSqlAttributeEqualsDate(livefs, "date_last_modified", UTC_NOW)
def test_duplicate_edit_notifications(self): # Bug edits for a duplicate are sent to duplicate subscribers only. with notify_modified(self.dupe_bug, ['description'], user=self.dupe_bug.owner): self.dupe_bug.description = 'A changed description' latest_notification = BugNotification.selectFirst(orderBy='-id') recipients = set(recipient.person for recipient in latest_notification.recipients) self.assertEqual(self.dupe_subscribers, recipients)
def test_notifications_for_question_subscribers(self): # Ensure that notifications are sent to subscribers of a # question linked to the expired bug. bugtask = self.bug.default_bugtask with notify_modified(bugtask, ["status"]): bugtask.transitionToStatus(BugTaskStatus.EXPIRED, self.product.owner) recipients = [ job.metadata['recipient_set'] for job in pop_questionemailjobs() ] self.assertContentEqual(['ASKER_SUBSCRIBER'], recipients)
def test_modifiedevent_sets_date_last_modified(self): # When a Webhook receives an object modified event, the last modified # date is set to UTC_NOW. webhook = self.factory.makeWebhook() transaction.commit() with admin_logged_in(): old_mtime = webhook.date_last_modified with notify_modified(webhook, ['delivery_url']): pass with admin_logged_in(): self.assertThat(webhook.date_last_modified, GreaterThan(old_mtime))
def test_change_rule(self): repository = self.factory.makeGitRepository() rule = self.factory.makeGitRule( repository=repository, ref_pattern="refs/heads/foo") transaction.commit() snapshot = Snapshot(repository, providing=providedBy(repository)) with person_logged_in(repository.owner): with notify_modified(rule, ["ref_pattern"]): rule.ref_pattern = "refs/heads/bar" self.assertDeltaDescriptionEqual( [], ["Changed protected ref: refs/heads/foo => refs/heads/bar"], snapshot, repository)
def test_modifyPOTemplate_makes_job(self): """Creating a Packaging should make a TranslationMergeJob.""" potemplate = self.factory.makePOTemplate() finder = JobFinder(None, None, TranslationTemplateChangeJob, potemplate) self.assertEqual([], finder.find()) with person_logged_in(potemplate.owner): with notify_modified(potemplate, ["name"]): potemplate.name = self.factory.getUniqueString() (job, ) = finder.find() self.assertIsInstance(job, TranslationTemplateChangeJob)
def test_assignee_new_subscriber(self): """Build a list of people who will receive emails about the bug task changes and ensure the assignee is not one.""" with notify_modified(self.bug_task, ['assignee'], user=self.user): self.bug_task.transitionToAssignee(self.person_assigned) latest_notification = BugNotification.selectFirst(orderBy='-id') notifications, omitted, messages = construct_email_notifications( [latest_notification]) self.assertEqual(len(notifications), 1, 'email notication not created') receivers = [message['To'] for message in messages] self.assertFalse(self.person_assigned_email in receivers, 'Assignee was emailed about the bug task change')
def test_assignee_notification_message(self): """Test notification string when a person is assigned a task by someone else.""" self.assertEqual(len(stub.test_emails), 0, 'emails in queue') with notify_modified(self.bug_task, ['assignee'], user=self.user): self.bug_task.transitionToAssignee(self.person_assigned) transaction.commit() self.assertEqual(len(stub.test_emails), 1, 'email not sent') rationale = ( 'Sample Person (name12) has assigned this bug to you for Rebirth') msg = stub.test_emails[-1][2] self.assertTrue(rationale in msg, '%s not in\n%s\n' % (rationale, msg))
def test_assignee_not_a_subscriber(self): """Test that a new recipient being assigned a bug task does send a NEW message.""" self.assertEqual(len(stub.test_emails), 0, 'emails in queue') with notify_modified(self.bug_task, ['assignee'], user=self.user): self.bug_task.transitionToAssignee(self.person_assigned) transaction.commit() self.assertEqual(len(stub.test_emails), 1, 'email not sent') new_message = '[NEW]' msg = stub.test_emails[-1][2] self.assertTrue(new_message in msg, '%s not in \n%s\n' % (new_message, msg))
def test_self_assignee_notification_message(self): """Test notification string when a person is assigned a task by themselves.""" stub.test_emails = [] with notify_modified(self.bug_task, ['assignee']): self.bug_task.transitionToAssignee(self.user) transaction.commit() self.assertEqual(1, len(stub.test_emails)) rationale = ('You have assigned this bug to yourself for Rebirth') [email] = stub.test_emails # Actual message is part 2 of the email. msg = email[2] self.assertIn(rationale, msg)
def test_for_bug_modifier_header(self): """Test X-Launchpad-Bug-Modifier appears when a bug is modified.""" with notify_modified(self.bug_task, ['status'], user=self.user): self.bug_task.transitionToStatus(BugTaskStatus.CONFIRMED, self.user) transaction.commit() latest_notification = BugNotification.selectFirst(orderBy='-id') notifications, omitted, messages = construct_email_notifications( [latest_notification]) self.assertEqual(len(notifications), 1, 'email notification not created') headers = [msg['X-Launchpad-Bug-Modifier'] for msg in messages] self.assertEqual(len(headers), len(messages))
def test_team_assigned_new_subscriber(self): """Assign a team, who is not subscribed to a bug, a bug task and ensure that team members do not receive an email about the bug task changes.""" with notify_modified(self.bug_task, ['assignee'], user=self.user): self.bug_task.transitionToAssignee(self.team_assigned) latest_notification = BugNotification.selectFirst(orderBy='-id') notifications, omitted, messages = construct_email_notifications( [latest_notification]) self.assertEqual(len(notifications), 1, 'email notification not created') receivers = [message['To'] for message in messages] self.assertFalse(self.team_member_email in receivers, 'Team member was emailed about the bug task change')
def create_action(self, action, data): """Create a Bug from a Question.""" question = self.context with notify_modified(question, ['bugs']): params = CreateBugParams(owner=self.user, title=data['title'], comment=data['description']) bug = question.target.createBug(params) question.linkBug(bug, user=self.user) bug.subscribe(question.owner, self.user) self.request.response.addNotification( _('Thank you! Bug #$bugid created.', mapping={'bugid': bug.id})) self.next_url = canonical_url(bug)
def test_dup_subscriber_change_notification_message(self): """Duplicate bug number in the reason (email footer) for duplicate subscribers when a master bug is modified.""" with notify_modified(self.master_bug_task, ['status'], user=self.user): self.master_bug_task.transitionToStatus( BugTaskStatus.CONFIRMED, self.user) transaction.commit() latest_notification = BugNotification.selectFirst(orderBy='-id') notifications, omitted, messages = construct_email_notifications( [latest_notification]) self.assertEqual( len(notifications), 1, 'email notification not created') rationale = 'duplicate bug report (%i)' % self.dup_bug.id self.assertIn(rationale, str(messages[-1]))
def _setAndNotifyDateActivated(self): """Set the date_activated field and fire a SQLObjectModified event. The date_activated field is only set once - repeated calls will not change the field's value. Similarly, the modification event only fires the first time that the field is set. """ if self.date_activated is not None: return with notify_modified(self, ['date_activated']): self.date_activated = UTC_NOW
def test_change_grant(self): repository = self.factory.makeGitRepository() grant = self.factory.makeGitRuleGrant( repository=repository, ref_pattern="refs/heads/*", can_create=True) transaction.commit() snapshot = Snapshot(repository, providing=providedBy(repository)) with person_logged_in(repository.owner): with notify_modified(grant, ["can_push"]): grant.can_push = True self.assertDeltaDescriptionEqual( [], ["Changed access for ~{grantee} to refs/heads/*: " "create => create and push".format(grantee=grant.grantee.name)], snapshot, repository)
def expireBugTasks(self, transaction_manager): """Expire old, unassigned, Incomplete BugTasks. Only BugTasks for projects that use Malone are updated. This method will login as the bug_watch_updater celebrity and logout after the expiration is done. """ message_template = ('[Expired for %s because there has been no ' 'activity for %d days.]') self.log.info('Expiring unattended, INCOMPLETE bugtasks older than ' '%d days for projects that use Launchpad Bugs.' % self.days_before_expiration) self._login() try: expired_count = 0 bugtask_set = getUtility(IBugTaskSet) incomplete_bugtasks = bugtask_set.findExpirableBugTasks( self.days_before_expiration, user=self.janitor, target=self.target, limit=self.limit) self.log.info('Found %d bugtasks to expire.' % incomplete_bugtasks.count()) for bugtask in incomplete_bugtasks: # We don't expire bugtasks with conjoined masters. if bugtask.conjoined_master: continue with notify_modified(bugtask, ['status'], user=self.janitor): bugtask.transitionToStatus(BugTaskStatus.EXPIRED, self.janitor) content = message_template % (bugtask.bugtargetdisplayname, self.days_before_expiration) bugtask.bug.newMessage( owner=self.janitor, subject=bugtask.bug.followup_subject(), content=content) # We commit after each expiration because emails are sent # immediately in zopeless. This minimize the risk of # duplicate expiration emails being sent in case an error # occurs later on. transaction_manager.commit() expired_count += 1 self.log.info('Expired %d bugtasks.' % expired_count) finally: self._logout() self.log.info('Finished expiration run.')
def test_generates_notification(self): obj = Thing(0) with EventRecorder() as recorder: with notify_modified(obj, ["attr"]): obj.attr = 1 self.assertThat( recorder.events, MatchesListwise([ MatchesAll( Provides(IObjectModifiedEvent), MatchesStructure( object=MatchesStructure(attr=Equals(1)), object_before_modification=MatchesAll( Provides(IThing), MatchesStructure(attr=Equals(0))), edited_fields=Equals(["attr"]), user=Provides(IUnauthenticatedPrincipal))), ]))
def test_different_user(self): obj = Thing(0) user = self.factory.makePerson() with EventRecorder() as recorder: with notify_modified(obj, ["attr"], user=user): obj.attr = 1 self.assertThat( recorder.events, MatchesListwise([ MatchesAll( Provides(IObjectModifiedEvent), MatchesStructure(object=MatchesStructure(attr=Equals(1)), object_before_modification=MatchesAll( Provides(IThing), MatchesStructure(attr=Equals(0))), edited_fields=Equals(["attr"]), user=Equals(user))), ]))
def test_workitems_deleted_notification_message(self): """ Test that we get a notification for deleting a work item.""" stub.test_emails = [] wi = self.factory.makeSpecificationWorkItem() spec = wi.specification # In production this notification is fired by lazr.restful, but we # need to do it ourselves in this test. with notify_modified(spec, ['workitems_text']): login_person(spec.owner) spec.updateWorkItems([]) transaction.commit() self.assertEqual(1, len(stub.test_emails)) rationale = '- %s: %s' % (wi.title, wi.status.name) [email] = stub.test_emails # Actual message is part 2 of the email. msg = email[2] self.assertIn(rationale, msg)
def test_activity_grant_changed(self): owner = self.factory.makeTeam() member = self.factory.makePerson(member_of=[owner]) repository = self.factory.makeGitRepository(owner=owner) grant = self.factory.makeGitRuleGrant( repository=repository, grantee=GitGranteeType.REPOSITORY_OWNER, can_create=True) with person_logged_in(member): with notify_modified(grant, ["can_create", "can_force_push"]): grant.can_create = False grant.can_force_push = True self.assertThat( repository.getActivity().first(), MatchesStructure(repository=Equals(repository), changer=Equals(member), changee=Is(None), what_changed=Equals( GitActivityType.GRANT_CHANGED), old_value=MatchesDict({ "changee_type": Equals("Repository owner"), "ref_pattern": Equals("refs/heads/*"), "can_create": Is(True), "can_push": Is(False), "can_force_push": Is(False), }), new_value=MatchesDict({ "changee_type": Equals("Repository owner"), "ref_pattern": Equals("refs/heads/*"), "can_create": Is(False), "can_push": Is(False), "can_force_push": Is(True), })))
def test_mutate_edited_fields_within_block(self): obj = Thing(0) with EventRecorder() as recorder: edited_fields = set() with notify_modified(obj, edited_fields): obj.attr = 1 edited_fields.add("attr") self.assertThat( recorder.events, MatchesListwise([ MatchesAll( Provides(IObjectModifiedEvent), MatchesStructure( object=MatchesStructure(attr=Equals(1)), object_before_modification=MatchesAll( Provides(IThing), MatchesStructure(attr=Equals(0))), edited_fields=Equals(["attr"]), user=Provides(IUnauthenticatedPrincipal))), ]))
def test_workitems_changed_notification_message(self): """ Test that we get a notification about a work item status change. This will be in the form of a line added and one deleted.""" spec = self.factory.makeSpecification() original_status = SpecificationWorkItemStatus.TODO new_status = SpecificationWorkItemStatus.DONE original_work_item = { 'title': 'The same work item', 'status': original_status, 'assignee': None, 'milestone': None, 'sequence': 0 } new_work_item = { 'title': 'The same work item', 'status': new_status, 'assignee': None, 'milestone': None, 'sequence': 0 } login_person(spec.owner) spec.updateWorkItems([original_work_item]) # In production this notification is fired by lazr.restful, but we # need to do it ourselves in this test. with notify_modified(spec, ['workitems_text']): stub.test_emails = [] spec.updateWorkItems([new_work_item]) transaction.commit() self.assertEqual(1, len(stub.test_emails)) rationale_removed = '- %s: %s' % (original_work_item['title'], original_work_item['status'].name) rationale_added = '+ %s: %s' % (new_work_item['title'], new_work_item['status'].name) [email] = stub.test_emails # Actual message is part 2 of the email. msg = email[2] self.assertIn(rationale_removed, msg) self.assertIn(rationale_added, msg)
def subscribe(self, person, subscribed_by=None, essential=False): """See ISpecification.""" if subscribed_by is None: subscribed_by = person # Create or modify a user's subscription to this blueprint. # First see if a relevant subscription exists, and if so, return it sub = self.subscription(person) if sub is not None: if sub.essential != essential: # If a subscription already exists, but the value for # 'essential' changes, there's no need to create a new # subscription, but we modify the existing subscription # and notify the user about the change. with notify_modified(sub, ['essential'], user=subscribed_by): sub.essential = essential return sub # since no previous subscription existed, create and return a new one sub = SpecificationSubscription(specification=self, person=person, essential=essential) property_cache = get_property_cache(self) if 'subscription' in property_cache: from lp.registry.model.person import person_sort_key property_cache.subscriptions.append(sub) property_cache.subscriptions.sort( key=lambda sub: person_sort_key(sub.person)) if self.information_type in PRIVATE_INFORMATION_TYPES: # Grant the subscriber access if they can't see the # specification. service = getUtility(IService, 'sharing') _, _, _, shared_specs = service.getVisibleArtifacts( person, specifications=[self], ignore_permissions=True) if not shared_specs: service.ensureAccessGrants( [person], subscribed_by, specifications=[self]) notify(ObjectCreatedEvent(sub, user=subscribed_by)) return sub
def test_yields_previous_object(self): obj = Thing(0) with notify_modified(obj, []) as previous_obj: obj.attr = 1 self.assertIsInstance(previous_obj, Snapshot) self.assertEqual(0, previous_obj.attr)