def setUp(self): # Run the tests as a logged-in user. super(TestAssignmentNotification, self).setUp( user='******') self.user = getUtility(ILaunchBag).user self.product = self.factory.makeProduct(owner=self.user, name='rebirth') self.bug = self.factory.makeBug(target=self.product) self.bug_task = self.bug.getBugTask(self.product) self.bug_task_before_modification = Snapshot(self.bug_task, providing=providedBy(self.bug_task)) self.person_assigned_email = '*****@*****.**' self.person_assigned = self.factory.makePerson( name='assigned', displayname='Steve Rogers', email=self.person_assigned_email) self.team_member_email = '*****@*****.**' self.team_member = self.factory.makePerson( name='giantman', displayname='Hank Pym', email=self.team_member_email) self.team_assigned = self.factory.makeTeam( name='avengers', owner=self.user) self.team_assigned.addMember(self.team_member, self.user) # adding people to a team generates email transaction.commit() del stub.test_emails[:]
def test_no_modification(self): # If there are no modifications, no delta is returned. repository = self.factory.makeGitRepository(name=u"foo") old_repository = Snapshot(repository, providing=providedBy(repository)) delta = GitRepositoryDelta.construct(old_repository, repository, repository.owner) self.assertIsNone(delta)
def request_action(self, action, data): changed = False recipe_before_modification = Snapshot(self.context, providing=providedBy( self.context)) recipe_text = data.pop('recipe_text') parser = RecipeParser(recipe_text) recipe = parser.parse() if self.context.builder_recipe != recipe: try: self.error_handler(self.context.setRecipeText, recipe_text) changed = True except ErrorHandled: return distros = data.pop('distroseries') if distros != self.context.distroseries: self.context.distroseries.clear() for distroseries_item in distros: self.context.distroseries.add(distroseries_item) changed = True if self.updateContextFromData(data, notify_modified=False): changed = True if changed: field_names = [ form_field.__name__ for form_field in self.form_fields ] notify( ObjectModifiedEvent(self.context, recipe_before_modification, field_names)) self.next_url = canonical_url(self.context)
def execute(self, context, current_event): """See IEmailCommand.""" if isinstance(context, CreateBugParams): # No one intentially reports a duplicate bug. Bug email commands # support CreateBugParams, so in this case, just return. return context, current_event self._ensureNumberOfArguments() [bug_id] = self.string_args if bug_id != 'no': try: bug = getUtility(IBugSet).getByNameOrID(bug_id) except NotFoundError: raise EmailProcessingError( get_error_message('no-such-bug.txt', error_templates=error_templates, bug_id=bug_id)) else: # 'no' is a special value for unmarking a bug as a duplicate. bug = None duplicate_field = IBug['duplicateof'].bind(context) try: duplicate_field.validate(bug) except ValidationError as error: raise EmailProcessingError(error.doc()) context_snapshot = Snapshot(context, providing=providedBy(context)) context.markAsDuplicate(bug) current_event = ObjectModifiedEvent(context, context_snapshot, 'duplicateof') notify(current_event) return bug, current_event
def linkBug(self, action, data): """Link to the requested bug. Publish an ObjectModifiedEvent and display a notification. """ response = self.request.response target_unmodified = Snapshot(self.context, providing=providedBy(self.context)) bug = data['bug'] try: self.context.linkBug(bug, user=self.user) except Unauthorized: # XXX flacoste 2006-08-23 bug=57470: This should use proper _(). self.setFieldError( 'bug', 'You are not allowed to link to private bug #%d.' % bug.id) return bug_props = {'bugid': bug.id, 'title': bug.title} response.addNotification( _( u'Added link to bug #$bugid: ' u'\N{left double quotation mark}$title' u'\N{right double quotation mark}.', mapping=bug_props)) notify(ObjectModifiedEvent(self.context, target_unmodified, ['bugs'])) self.next_url = canonical_url(self.context)
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() old_spec = Snapshot(spec, providing=providedBy(spec)) new_work_item = { 'title': u'A work item', 'status': SpecificationWorkItemStatus.TODO, 'assignee': None, 'milestone': None, 'sequence': 0 } login_person(spec.owner) spec.updateWorkItems([new_work_item]) # For API requests, lazr.restful does the notify() call, for this test # we need to call ourselves. transaction.commit() notify(ObjectModifiedEvent( spec, old_spec, edited_fields=['workitems_text'])) 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 e-mail. msg = email[2] self.assertIn(rationale, msg)
def updateContextFromData(self, data, context=None, notify_modified=True): """Update the context object based on form data. If no context is given, the view's context is used. If any changes were made, ObjectModifiedEvent will be emitted. This method should be called by an action method of the form. Returns True if there were any changes to apply. """ if context is None: context = self.context if notify_modified: context_before_modification = Snapshot( context, providing=providedBy(context)) was_changed = form.applyChanges(context, self.form_fields, data, self.adapters) if was_changed and notify_modified: field_names = [ form_field.__name__ for form_field in self.form_fields ] notify( ObjectModifiedEvent(context, context_before_modification, field_names)) return was_changed
def remove_action(self, action, data): """Update the bug.""" bug = self.context.bug bug_before_modification = Snapshot(bug, providing=providedBy(bug)) bug.markAsDuplicate(None) notify( ObjectModifiedEvent(bug, bug_before_modification, 'duplicateof')) return self._duplicate_action_result()
def test_add_rule(self): repository = self.factory.makeGitRepository() transaction.commit() snapshot = Snapshot(repository, providing=providedBy(repository)) with person_logged_in(repository.owner): repository.addRule("refs/heads/*", repository.owner) self.assertDeltaDescriptionEqual( [], ["Added protected ref: refs/heads/*"], snapshot, repository)
def test_no_job_created_if_no_delta(self): """Ensure None is returned if no change has been made.""" merge_proposal, person = self.makeProposalWithSubscriber() old_merge_proposal = Snapshot( merge_proposal, providing=providedBy(merge_proposal)) event = ObjectModifiedEvent( merge_proposal, old_merge_proposal, [], merge_proposal.registrant) merge_proposal_modified(merge_proposal, event) self.assertIs(None, self.getProposalUpdatedEmailJob(merge_proposal))
def setUp(self): # Run the tests as a logged-in user. super(TestModificationNotification, self).setUp( user='******') self.user = getUtility(ILaunchBag).user self.product = self.factory.makeProduct(owner=self.user) self.bug = self.factory.makeBug(target=self.product) self.bug_task = self.bug.getBugTask(self.product) self.bug_task_before_modification = Snapshot(self.bug_task, providing=providedBy(self.bug_task))
def test_remove_rule(self): repository = self.factory.makeGitRepository() rule = self.factory.makeGitRule( repository=repository, ref_pattern="refs/heads/*") transaction.commit() snapshot = Snapshot(repository, providing=providedBy(repository)) with person_logged_in(repository.owner): rule.destroySelf(repository.owner) self.assertDeltaDescriptionEqual( [], ["Removed protected ref: refs/heads/*"], snapshot, repository)
def match(self, matchee): snapshot = Snapshot(matchee, providing=self.interface) mismatches = [] for attribute in self.attr_list: if hasattr(snapshot, attribute): mismatches.append(WasSnapshotted(matchee, attribute)) if len(mismatches) == 0: return None else: return MismatchesAll(mismatches)
def test_no_job_created_if_work_in_progress(self): """Ensure None is returned if no change has been made.""" merge_proposal, person = self.makeProposalWithSubscriber( needs_review=False) old_merge_proposal = Snapshot( merge_proposal, providing=providedBy(merge_proposal)) merge_proposal.commit_message = 'new commit message' merge_proposal.description = 'change description' event = ObjectModifiedEvent( merge_proposal, old_merge_proposal, [], merge_proposal.registrant) merge_proposal_modified(merge_proposal, event) self.assertIs(None, self.getProposalUpdatedEmailJob(merge_proposal))
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_job_created_if_work_in_progress_merged(self): # If work in progress is merged, then that is email worthy. merge_proposal, person = self.makeProposalWithSubscriber( needs_review=False) old_merge_proposal = Snapshot( merge_proposal, providing=providedBy(merge_proposal)) merge_proposal.setStatus(BranchMergeProposalStatus.MERGED) event = ObjectModifiedEvent( merge_proposal, old_merge_proposal, [], merge_proposal.registrant) merge_proposal_modified(merge_proposal, event) job = self.getProposalUpdatedEmailJob(merge_proposal) self.assertIsNot(None, job, 'Job was not created.')
def test_what_changed_works_with_fieldnames(self): # When what_changed is passed an ObjectModifiedEvent with a list # of fieldnames in its edited_fields property, it will deal with # those fields appropriately. bug = self.factory.makeBug() bug_before_modification = Snapshot(bug, providing=IBug) with person_logged_in(bug.owner): bug.setPrivate(True, bug.owner) event = ObjectModifiedEvent(bug, bug_before_modification, ['private']) expected_changes = {'private': ['False', 'True']} changes = what_changed(event) self.assertEqual(expected_changes, changes)
def test_no_job_created_if_only_preview_diff_changed(self): """Ensure None is returned if only the preview diff has changed.""" merge_proposal, person = self.makeProposalWithSubscriber() old_merge_proposal = Snapshot( merge_proposal, providing=providedBy(merge_proposal)) merge_proposal.updatePreviewDiff( ''.join(unified_diff('', 'Fake diff')), unicode(self.factory.getUniqueString('revid')), unicode(self.factory.getUniqueString('revid'))) event = ObjectModifiedEvent( merge_proposal, old_merge_proposal, [], merge_proposal.registrant) merge_proposal_modified(merge_proposal, event) self.assertIs(None, self.getProposalUpdatedEmailJob(merge_proposal))
def test_move_rule(self): repository = self.factory.makeGitRepository() rule = self.factory.makeGitRule( repository=repository, ref_pattern="refs/heads/*") self.factory.makeGitRule( repository=repository, ref_pattern="refs/heads/stable/*") transaction.commit() snapshot = Snapshot(repository, providing=providedBy(repository)) with person_logged_in(repository.owner): repository.moveRule(rule, 1, repository.owner) self.assertDeltaDescriptionEqual( [], ["Moved rule for protected ref refs/heads/*: position 0 => 1"], snapshot, repository)
def test_remove_grant(self): repository = self.factory.makeGitRepository() grant = self.factory.makeGitRuleGrant( repository=repository, ref_pattern="refs/heads/*", grantee=GitGranteeType.REPOSITORY_OWNER, can_push=True) transaction.commit() snapshot = Snapshot(repository, providing=providedBy(repository)) with person_logged_in(repository.owner): grant.destroySelf(repository.owner) self.assertDeltaDescriptionEqual( [], ["Removed access for repository owner to refs/heads/*: push"], snapshot, repository)
def test_duplicate_edit_notifications(self): # Bug edits for a duplicate are sent to duplicate subscribers only. bug_before_modification = Snapshot(self.dupe_bug, providing=providedBy(self.dupe_bug)) self.dupe_bug.description = 'A changed description' notify( ObjectModifiedEvent(self.dupe_bug, bug_before_modification, ['description'], user=self.dupe_bug.owner)) latest_notification = BugNotification.selectFirst(orderBy='-id') recipients = set(recipient.person for recipient in latest_notification.recipients) self.assertEqual(self.dupe_subscribers, recipients)
def test_what_changed_works_with_field_instances(self): # Sometimes something will pass what_changed an # ObjectModifiedEvent where the edited_fields list contains # field instances. what_changed handles that correctly, too. bug = self.factory.makeBug() bug_before_modification = Snapshot(bug, providing=IBug) with person_logged_in(bug.owner): bug.setPrivate(True, bug.owner) event = ObjectModifiedEvent(bug, bug_before_modification, [IBug['private']]) expected_changes = {'private': ['False', 'True']} changes = what_changed(event) self.assertEqual(expected_changes, changes)
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): snapshot = Snapshot(potemplate, providing=IPOTemplate) potemplate.name = self.factory.getUniqueString() notify(ObjectModifiedEvent(potemplate, snapshot, ["name"])) (job, ) = finder.find() self.assertIsInstance(job, TranslationTemplateChangeJob)
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 bugtask_before_modification = Snapshot( bugtask, providing=providedBy(bugtask)) 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) notify( ObjectModifiedEvent(bugtask, bugtask_before_modification, ['status'], user=self.janitor)) # 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_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 bugtask_before_modification = Snapshot(bugtask, providing=IBugTask) bugtask.transitionToStatus(BugTaskStatus.EXPIRED, self.product.owner) bug_modified = ObjectModifiedEvent(bugtask, bugtask_before_modification, ["status"]) notify(bug_modified) recipients = [ job.metadata['recipient_set'] for job in pop_questionemailjobs() ] self.assertContentEqual(['ASKER_SUBSCRIBER'], recipients)
def change_action(self, action, data): """Update the bug.""" data = dict(data) # We handle duplicate changes by hand instead of leaving it to # the usual machinery because we must use bug.markAsDuplicate(). bug = self.context.bug bug_before_modification = Snapshot(bug, providing=providedBy(bug)) duplicateof = data.pop('duplicateof') bug.markAsDuplicate(duplicateof) notify( ObjectModifiedEvent(bug, bug_before_modification, 'duplicateof')) # Apply other changes. self.updateBugFromData(data) return self._duplicate_action_result()
def makeProposalUpdatedEmailJob(self): """Fixture method providing a mailer for a modified merge proposal""" merge_proposal, subscriber = self.makeProposalWithSubscriber() old_merge_proposal = Snapshot( merge_proposal, providing=providedBy(merge_proposal)) merge_proposal.requestReview() merge_proposal.commit_message = 'new commit message' merge_proposal.description = 'change description' event = ObjectModifiedEvent( merge_proposal, old_merge_proposal, [], merge_proposal.registrant) merge_proposal_modified(merge_proposal, event) job = self.getProposalUpdatedEmailJob(merge_proposal) self.assertIsNot(None, job, 'Job was not created.') return job, subscriber
def execute(self, context, current_event): """See `IEmailCommand`. Much of this method was lifted from `EditEmailCommand.execute`. """ # Parse args. self._ensureNumberOfArguments() [security_flag] = self.string_args if security_flag == 'yes': security_related = True elif security_flag == 'no': security_related = False else: raise EmailProcessingError(get_error_message( 'security-parameter-mismatch.txt', error_templates=error_templates), stop_processing=True) if isinstance(context, CreateBugParams): if security_related: context.information_type = InformationType.PRIVATESECURITY return context, current_event # Take a snapshot. edited = False edited_fields = set() if IObjectModifiedEvent.providedBy(current_event): context_snapshot = current_event.object_before_modification edited_fields.update(current_event.edited_fields) else: context_snapshot = Snapshot(context, providing=providedBy(context)) # Apply requested changes. user = getUtility(ILaunchBag).user if security_related: if context.setPrivate(True, user): edited = True edited_fields.add('private') if context.security_related != security_related: context.setSecurityRelated(security_related, user) edited = True edited_fields.add('security_related') # Update the current event. if edited and not IObjectCreatedEvent.providedBy(current_event): current_event = ObjectModifiedEvent(context, context_snapshot, list(edited_fields)) return context, current_event
def test_change_basic_properties(self): repository = self.factory.makeGitRepository(name="foo") transaction.commit() snapshot = Snapshot(repository, providing=providedBy(repository)) with person_logged_in(repository.owner): repository.setName("bar", repository.owner) expected = [ "Name: foo => bar", "Git identity: lp:~{person}/{project}/+git/foo => " "lp:~{person}/{project}/+git/bar".format( person=repository.owner.name, project=repository.target.name), ] self.assertDeltaDescriptionEqual( expected, expected, snapshot, repository)
def change_action(self, action, data): """Update the bug.""" data = dict(data) bug = self.context.bug information_type = data.pop('information_type') changed_fields = ['information_type'] # When the user first submits the form, validate change is True and # so we check that the bug does not become invisible. If the user # confirms they really want to make the change, validate change is # False and we process the change as normal. if self.request.is_ajax: validate_change = data.get('validate_change', False) if (validate_change and information_type in PRIVATE_INFORMATION_TYPES and self._bug_will_be_invisible(information_type)): self.request.response.setStatus(400, "Bug Visibility") return '' user_will_be_subscribed = ( information_type in PRIVATE_INFORMATION_TYPES and bug.getSubscribersForPerson(self.user).is_empty()) bug_before_modification = Snapshot(bug, providing=providedBy(bug)) changed = bug.transitionToInformationType( information_type, self.user) if changed: self._handlePrivacyChanged(user_will_be_subscribed) notify( ObjectModifiedEvent( bug, bug_before_modification, changed_fields, user=self.user)) if self.request.is_ajax: # Avoid circular imports from lp.bugs.browser.bugtask import ( can_add_package_task_to_bug, can_add_project_task_to_bug, ) if changed: result_data = self._getSubscriptionDetails() result_data['can_add_project_task'] = ( can_add_project_task_to_bug(bug)) result_data['can_add_package_task'] = ( can_add_package_task_to_bug(bug)) self.request.response.setHeader( 'content-type', 'application/json') return dumps( result_data, cls=ResourceJSONEncoder, media_type=EntryResource.JSON_TYPE) else: return ''