def setUp(self): super(TestStoreChannelsWidget, self).setUp() field = List(__name__="channels", title="Store channels") self.context = self.factory.makeSnap() field = field.bind(self.context) request = LaunchpadTestRequest() self.widget = StoreChannelsWidget(field, None, request) # setup fake store client response for available channels/risks self.risks = [ { "name": "stable", "display_name": "Stable" }, { "name": "candidate", "display_name": "Candidate" }, { "name": "beta", "display_name": "Beta" }, { "name": "edge", "display_name": "Edge" }, ] snap_store_client = FakeMethod() snap_store_client.listChannels = FakeMethod(result=self.risks) self.useFixture(ZopeUtilityFixture(snap_store_client, ISnapStoreClient))
def test_automatic_retries(self): hook = self.factory.makeWebhook() job = WebhookDeliveryJob.create(hook, 'test', payload={'foo': 'bar'}) client = MockWebhookClient(response_status=503) self.useFixture(ZopeUtilityFixture(client, IWebhookClient)) # The first attempt fails but schedules a retry five minutes later. self.assertEqual(False, self.runJob(job)) self.assertEqual(JobStatus.WAITING, job.status) self.assertEqual(False, job.successful) self.assertTrue(job.pending) self.assertIsNot(None, job.date_sent) last_date_sent = job.date_sent # Pretend we're five minutes in the future and try again. The # job will be retried again. job.json_data['date_first_sent'] = ( job.date_first_sent - timedelta(minutes=5)).isoformat() job.scheduled_start -= timedelta(minutes=5) self.assertEqual(False, self.runJob(job)) self.assertEqual(JobStatus.WAITING, job.status) self.assertEqual(False, job.successful) self.assertTrue(job.pending) self.assertThat(job.date_sent, GreaterThan(last_date_sent)) # If the job was first tried a day ago, the next attempt gives up. job.json_data['date_first_sent'] = ( job.date_first_sent - timedelta(hours=24)).isoformat() job.scheduled_start -= timedelta(hours=24) self.assertEqual(False, self.runJob(job)) self.assertEqual(JobStatus.FAILED, job.status) self.assertEqual(False, job.successful) self.assertFalse(job.pending)
def test_retry_delay(self): # The job is retried every minute, unless it just made one of its # first four attempts to poll the status endpoint, in which case the # delays are 15/15/30/30 seconds. self.useFixture(FakeLogger()) snapbuild = self.makeSnapBuild() job = SnapStoreUploadJob.create(snapbuild) client = FakeSnapStoreClient() client.upload.failure = UploadFailedResponse("Proxy error", can_retry=True) self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) with dbuser(config.ISnapStoreUploadJobSource.dbuser): JobRunner([job]).runAll() self.assertNotIn("status_url", job.metadata) self.assertEqual(timedelta(seconds=60), job.retry_delay) job.scheduled_start = None client.upload.failure = None client.upload.result = self.status_url client.checkStatus.failure = UploadNotScannedYetResponse() for expected_delay in (15, 15, 30, 30, 60): with dbuser(config.ISnapStoreUploadJobSource.dbuser): JobRunner([job]).runAll() self.assertIn("status_url", job.snapbuild.store_upload_metadata) self.assertIsNone(job.store_url) self.assertEqual(timedelta(seconds=expected_delay), job.retry_delay) job.scheduled_start = None client.checkStatus.failure = None client.checkStatus.result = (self.store_url, 1) with dbuser(config.ISnapStoreUploadJobSource.dbuser): JobRunner([job]).runAll() self.assertEqual(self.store_url, job.store_url) self.assertIsNone(job.error_message) self.assertEqual([], pop_notifications()) self.assertEqual(JobStatus.COMPLETED, job.job.status)
def test_fixture(self): fake = DummyMailer() # In BaseLayer there should be no mailer by default. self.assertRaises(ComponentLookupError, self.getMailer) with ZopeUtilityFixture(fake, IMailDelivery, 'Mail'): self.assertEqual(fake, self.getMailer()) self.assertRaises(ComponentLookupError, self.getMailer)
def setUp(self): super(MacaroonTests, self).setUp() self.issuer = DummyMacaroonIssuer() self.useFixture( ZopeUtilityFixture(self.issuer, IMacaroonIssuer, name='test')) private_root = getUtility(IPrivateApplication) self.authserver = AuthServerAPIView(private_root.authserver, TestRequest())
def _setUp(self): self.useFixture(ZopeUtilityFixture(self, IGitHostingClient)) if self.disable_memcache: # Most tests that involve GitRef._getLog don't want to cache the # result: doing so requires more time-consuming test setup and # makes it awkward to repeat the same call with different log # responses. For convenience, we make it easy to disable that # here. self.memcache_fixture = self.useFixture(MemcacheFixture())
def test_fixture(self): def get_mailer(): return getGlobalSiteManager().getUtility(IMailDelivery, 'Mail') fake = DummyMailer() # In BaseLayer there should be no mailer by default. self.assertRaises(ComponentLookupError, get_mailer) with ZopeUtilityFixture(fake, IMailDelivery, 'Mail'): self.assertEquals(get_mailer(), fake) self.assertRaises(ComponentLookupError, get_mailer)
def test_MailController_into_timeline(self): """sendmail records stuff in the timeline.""" fake_mailer = RecordingMailer() self.useFixture(ZopeUtilityFixture(fake_mailer, IMailDelivery, 'Mail')) to_addresses = ['*****@*****.**', '*****@*****.**'] subject = self.getUniqueString('subject') with CaptureTimeline() as ctl: ctrl = MailController('*****@*****.**', to_addresses, subject, 'body', {'key': 'value'}) ctrl.send() self.assertEqual(fake_mailer.from_addr, '*****@*****.**') self.assertEqual(fake_mailer.to_addr, to_addresses) self.checkTimelineHasOneMailAction(ctl.timeline, subject=subject)
def test_run_upload_failure_notifies(self): # A run that gets some other upload failure from the store sends # mail. requester = self.factory.makePerson(name="requester") requester_team = self.factory.makeTeam(owner=requester, name="requester-team", members=[requester]) snapbuild = self.makeSnapBuild(requester=requester_team, name="test-snap", owner=requester_team) self.assertContentEqual([], snapbuild.store_upload_jobs) job = SnapStoreUploadJob.create(snapbuild) client = FakeSnapStoreClient() client.upload.failure = UploadFailedResponse( "Failed to upload", detail="The proxy exploded.\n") self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) with dbuser(config.ISnapStoreUploadJobSource.dbuser): JobRunner([job]).runAll() self.assertEqual([((snapbuild, ), {})], client.upload.calls) self.assertEqual([], client.checkStatus.calls) self.assertEqual([], client.release.calls) self.assertContentEqual([job], snapbuild.store_upload_jobs) self.assertIsNone(job.store_url) self.assertIsNone(job.store_revision) self.assertEqual("Failed to upload", job.error_message) [notification] = pop_notifications() self.assertEqual(config.canonical.noreply_from_address, notification["From"]) self.assertEqual("Requester <%s>" % requester.preferredemail.email, notification["To"]) subject = notification["Subject"].replace("\n ", " ") self.assertEqual("Store upload failed for test-snap", subject) self.assertEqual("Requester @requester-team", notification["X-Launchpad-Message-Rationale"]) self.assertEqual(requester_team.name, notification["X-Launchpad-Message-For"]) self.assertEqual("snap-build-upload-failed", notification["X-Launchpad-Notification-Type"]) body, footer = notification.get_payload(decode=True).split("\n-- \n") self.assertIn("Failed to upload", body) build_url = ( "http://launchpad.dev/~requester-team/+snap/test-snap/+build/%d" % snapbuild.id) self.assertIn(build_url, body) self.assertEqual( "%s\nYour team Requester Team is the requester of the build.\n" % build_url, footer) self.assertWebhookDeliveries(snapbuild, ["Pending", "Failed to upload"]) self.assertIn(("error_detail", "The proxy exploded.\n"), job.getOopsVars())
def test_run_release_manual_review_notifies(self): # A run configured to automatically release the package to certain # channels but that encounters the manual review state on upload # sends mail. requester = self.factory.makePerson(name="requester") requester_team = self.factory.makeTeam(owner=requester, name="requester-team", members=[requester]) snapbuild = self.makeSnapBuild(requester=requester_team, name="test-snap", owner=requester_team, store_channels=["stable", "edge"]) self.assertContentEqual([], snapbuild.store_upload_jobs) job = SnapStoreUploadJob.create(snapbuild) client = FakeSnapStoreClient() client.upload.result = self.status_url client.checkStatus.result = (self.store_url, None) self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) with dbuser(config.ISnapStoreUploadJobSource.dbuser): JobRunner([job]).runAll() self.assertEqual([((snapbuild, ), {})], client.upload.calls) self.assertEqual([((self.status_url, ), {})], client.checkStatus.calls) self.assertEqual([], client.release.calls) self.assertContentEqual([job], snapbuild.store_upload_jobs) self.assertEqual(self.store_url, job.store_url) self.assertIsNone(job.store_revision) self.assertEqual( "Package held for manual review on the store; " "cannot release it automatically.", job.error_message) [notification] = pop_notifications() self.assertEqual(config.canonical.noreply_from_address, notification["From"]) self.assertEqual("Requester <%s>" % requester.preferredemail.email, notification["To"]) subject = notification["Subject"].replace("\n ", " ") self.assertEqual("test-snap held for manual review", subject) self.assertEqual("Requester @requester-team", notification["X-Launchpad-Message-Rationale"]) self.assertEqual(requester_team.name, notification["X-Launchpad-Message-For"]) self.assertEqual("snap-build-release-manual-review", notification["X-Launchpad-Notification-Type"]) body, footer = notification.get_payload(decode=True).split("\n-- \n") self.assertIn(self.store_url, body) self.assertEqual( "http://launchpad.dev/~requester-team/+snap/test-snap/+build/%d\n" "Your team Requester Team is the requester of the build.\n" % snapbuild.id, footer) self.assertWebhookDeliveries( snapbuild, ["Pending", "Failed to release to channels"])
def makeAndRunJob(self, response_status=200, raises=None, mock=True, secret=None, active=True): hook = self.factory.makeWebhook( delivery_url=u'http://example.com/ep', secret=secret, active=active) job = WebhookDeliveryJob.create(hook, 'test', payload={'foo': 'bar'}) client = MockWebhookClient( response_status=response_status, raises=raises) if mock: self.useFixture(ZopeUtilityFixture(client, IWebhookClient)) with dbuser("webhookrunner"): JobRunner([job]).runAll() return job, client.requests
def test_restores_previous_utility(self): # If there was a previous utility, ZopeUtilityFixture restores it on # cleanup. original_fake = DummyMailer() getGlobalSiteManager().registerUtility(original_fake, IMailDelivery, 'Mail') try: self.assertEqual(original_fake, self.getMailer()) fake = DummyMailer() with ZopeUtilityFixture(fake, IMailDelivery, 'Mail'): self.assertEqual(fake, self.getMailer()) self.assertEqual(original_fake, self.getMailer()) finally: getGlobalSiteManager().unregisterUtility(original_fake, IMailDelivery, 'Mail')
def test_sendmail_with_email_header(self): """Check the timeline is ok even if there is an email.header.Header. See https://bugs.launchpad.net/launchpad/+bug/885972 """ fake_mailer = RecordingMailer() self.useFixture(ZopeUtilityFixture(fake_mailer, IMailDelivery, 'Mail')) subject_str = self.getUniqueString('subject') subject_header = email.header.Header(subject_str) message = Message() message.add_header('From', '*****@*****.**') message['Subject'] = subject_header message.add_header('To', '*****@*****.**') with CaptureTimeline() as ctl: sendmail.sendmail(message) self.assertEqual(fake_mailer.from_addr, '*****@*****.**') self.assertEqual(fake_mailer.to_addr, ['*****@*****.**']) self.checkTimelineHasOneMailAction(ctl.timeline, subject=subject_str)
def test_manual_retries(self): hook = self.factory.makeWebhook() job = WebhookDeliveryJob.create(hook, 'test', payload={'foo': 'bar'}) client = MockWebhookClient(response_status=503) self.useFixture(ZopeUtilityFixture(client, IWebhookClient)) # Simulate a first attempt failure. self.assertEqual(False, self.runJob(job)) self.assertEqual(JobStatus.WAITING, job.status) self.assertIsNot(None, job.scheduled_start) # A manual retry brings the scheduled start forward. job.retry() self.assertEqual(JobStatus.WAITING, job.status) self.assertIs(None, job.scheduled_start) # Force the next attempt to fail hard by pretending it was more # than 24 hours later. job.json_data['date_first_sent'] = ( job.date_first_sent - timedelta(hours=24)).isoformat() self.assertEqual(False, self.runJob(job)) self.assertEqual(JobStatus.FAILED, job.status) # A manual retry brings the job out of FAILED and schedules it # to run as soon as possible. If it fails again, it fails hard; # the initial attempt more than 24 hours ago is remembered. job.retry() self.assertEqual(JobStatus.WAITING, job.status) self.assertIs(None, job.scheduled_start) self.assertEqual(False, self.runJob(job)) self.assertEqual(JobStatus.FAILED, job.status) # A completed job can be retried just like a failed one. The # endpoint may have erroneously returned a 200 without recording # the event. client.response_status = 200 job.retry() self.assertEqual(JobStatus.WAITING, job.status) self.assertEqual(True, self.runJob(job)) self.assertEqual(JobStatus.COMPLETED, job.status) job.retry() self.assertEqual(JobStatus.WAITING, job.status) self.assertEqual(True, self.runJob(job)) self.assertEqual(JobStatus.COMPLETED, job.status)
def test_run_scan_pending_retries(self): # A run that finds that the store has not yet finished scanning the # package schedules itself to be retried. self.useFixture(FakeLogger()) snapbuild = self.makeSnapBuild() self.assertContentEqual([], snapbuild.store_upload_jobs) job = SnapStoreUploadJob.create(snapbuild) client = FakeSnapStoreClient() client.upload.result = self.status_url client.checkStatus.failure = UploadNotScannedYetResponse() self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) with dbuser(config.ISnapStoreUploadJobSource.dbuser): JobRunner([job]).runAll() self.assertEqual([((snapbuild, ), {})], client.upload.calls) self.assertEqual([((self.status_url, ), {})], client.checkStatus.calls) self.assertEqual([], client.release.calls) self.assertContentEqual([job], snapbuild.store_upload_jobs) self.assertIsNone(job.store_url) self.assertIsNone(job.store_revision) self.assertIsNone(job.error_message) self.assertEqual([], pop_notifications()) self.assertEqual(JobStatus.WAITING, job.job.status) self.assertWebhookDeliveries(snapbuild, ["Pending"]) # Try again. The upload part of the job is not retried, and this # time the scan completes. job.scheduled_start = None client.upload.calls = [] client.checkStatus.calls = [] client.checkStatus.failure = None client.checkStatus.result = (self.store_url, 1) with dbuser(config.ISnapStoreUploadJobSource.dbuser): JobRunner([job]).runAll() self.assertEqual([], client.upload.calls) self.assertEqual([((self.status_url, ), {})], client.checkStatus.calls) self.assertEqual([], client.release.calls) self.assertContentEqual([job], snapbuild.store_upload_jobs) self.assertEqual(self.store_url, job.store_url) self.assertEqual(1, job.store_revision) self.assertIsNone(job.error_message) self.assertEqual([], pop_notifications()) self.assertEqual(JobStatus.COMPLETED, job.job.status) self.assertWebhookDeliveries(snapbuild, ["Pending", "Uploaded"])
def test_run_502_retries(self): # A run that gets a 502 error from the store schedules itself to be # retried. self.useFixture(FakeLogger()) snapbuild = self.makeSnapBuild() self.assertContentEqual([], snapbuild.store_upload_jobs) job = SnapStoreUploadJob.create(snapbuild) client = FakeSnapStoreClient() client.upload.failure = UploadFailedResponse("Proxy error", can_retry=True) self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) with dbuser(config.ISnapStoreUploadJobSource.dbuser): JobRunner([job]).runAll() self.assertEqual([((snapbuild, ), {})], client.upload.calls) self.assertEqual([], client.checkStatus.calls) self.assertEqual([], client.release.calls) self.assertContentEqual([job], snapbuild.store_upload_jobs) self.assertIsNone(job.store_url) self.assertIsNone(job.store_revision) self.assertIsNone(job.error_message) self.assertEqual([], pop_notifications()) self.assertEqual(JobStatus.WAITING, job.job.status) self.assertWebhookDeliveries(snapbuild, ["Pending"]) # Try again. The upload part of the job is retried, and this time # it succeeds. job.scheduled_start = None client.upload.calls = [] client.upload.failure = None client.upload.result = self.status_url client.checkStatus.result = (self.store_url, 1) with dbuser(config.ISnapStoreUploadJobSource.dbuser): JobRunner([job]).runAll() self.assertEqual([((snapbuild, ), {})], client.upload.calls) self.assertEqual([((self.status_url, ), {})], client.checkStatus.calls) self.assertEqual([], client.release.calls) self.assertContentEqual([job], snapbuild.store_upload_jobs) self.assertEqual(self.store_url, job.store_url) self.assertEqual(1, job.store_revision) self.assertIsNone(job.error_message) self.assertEqual([], pop_notifications()) self.assertEqual(JobStatus.COMPLETED, job.job.status) self.assertWebhookDeliveries(snapbuild, ["Pending", "Uploaded"])
def test_run_failed(self): # A failed run sets the store upload status to FAILED. snapbuild = self.makeSnapBuild() self.assertContentEqual([], snapbuild.store_upload_jobs) job = SnapStoreUploadJob.create(snapbuild) client = FakeSnapStoreClient() client.upload.failure = ValueError("An upload failure") self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) with dbuser(config.ISnapStoreUploadJobSource.dbuser): JobRunner([job]).runAll() self.assertEqual([((snapbuild, ), {})], client.upload.calls) self.assertEqual([], client.checkStatus.calls) self.assertEqual([], client.release.calls) self.assertContentEqual([job], snapbuild.store_upload_jobs) self.assertIsNone(job.store_url) self.assertIsNone(job.store_revision) self.assertEqual("An upload failure", job.error_message) self.assertEqual([], pop_notifications()) self.assertWebhookDeliveries(snapbuild, ["Pending", "Failed to upload"])
def test_run_release(self): # A run configured to automatically release the package to certain # channels does so. snapbuild = self.makeSnapBuild(store_channels=["stable", "edge"]) self.assertContentEqual([], snapbuild.store_upload_jobs) job = SnapStoreUploadJob.create(snapbuild) client = FakeSnapStoreClient() client.upload.result = self.status_url client.checkStatus.result = (self.store_url, 1) self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) with dbuser(config.ISnapStoreUploadJobSource.dbuser): JobRunner([job]).runAll() self.assertEqual([((snapbuild, ), {})], client.upload.calls) self.assertEqual([((self.status_url, ), {})], client.checkStatus.calls) self.assertEqual([((snapbuild, 1), {})], client.release.calls) self.assertContentEqual([job], snapbuild.store_upload_jobs) self.assertEqual(self.store_url, job.store_url) self.assertEqual(1, job.store_revision) self.assertIsNone(job.error_message) self.assertEqual([], pop_notifications()) self.assertWebhookDeliveries(snapbuild, ["Pending", "Uploaded"])
def test_run(self): # The job uploads the build to the store and records the store URL # and revision. snapbuild = self.makeSnapBuild() self.assertContentEqual([], snapbuild.store_upload_jobs) job = SnapStoreUploadJob.create(snapbuild) client = FakeSnapStoreClient() client.upload.result = self.status_url client.checkStatus.result = (self.store_url, 1) self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) with dbuser(config.ISnapStoreUploadJobSource.dbuser): JobRunner([job]).runAll() self.assertEqual([((snapbuild, ), {})], client.upload.calls) self.assertEqual([((self.status_url, ), {})], client.checkStatus.calls) self.assertEqual([], client.release.calls) self.assertContentEqual([job], snapbuild.store_upload_jobs) self.assertEqual(self.store_url, job.store_url) self.assertEqual(1, job.store_revision) self.assertIsNone(job.error_message) self.assertEqual([], pop_notifications()) self.assertWebhookDeliveries(snapbuild, ["Pending", "Uploaded"])
def test_retry_after_upload_does_not_upload(self): # If the job has uploaded, but failed to release, it should # not attempt to upload again on the next run. self.useFixture(FakeLogger()) snapbuild = self.makeSnapBuild(store_channels=["stable", "edge"]) self.assertContentEqual([], snapbuild.store_upload_jobs) job = SnapStoreUploadJob.create(snapbuild) client = FakeSnapStoreClient() client.upload.result = self.status_url client.checkStatus.result = (self.store_url, 1) client.release.failure = UploadFailedResponse("Proxy error", can_retry=True) self.useFixture(ZopeUtilityFixture(client, ISnapStoreClient)) with dbuser(config.ISnapStoreUploadJobSource.dbuser): JobRunner([job]).runAll() previous_upload = client.upload.calls previous_checkStatus = client.checkStatus.calls len_previous_release = len(client.release.calls) # Check we uploaded as expected self.assertEqual(self.store_url, job.store_url) self.assertEqual(1, job.store_revision) self.assertEqual(timedelta(seconds=60), job.retry_delay) self.assertEqual(1, len(client.upload.calls)) self.assertIsNone(job.error_message) # Run the job again with dbuser(config.ISnapStoreUploadJobSource.dbuser): JobRunner([job]).runAll() # We should not have called `upload`, but moved straight to `release` self.assertEqual(previous_upload, client.upload.calls) self.assertEqual(previous_checkStatus, client.checkStatus.calls) self.assertEqual(len_previous_release + 1, len(client.release.calls)) self.assertIsNone(job.error_message)
def test_manual_retry_with_reset(self): # retry(reset=True) unsets date_first_sent so the automatic # retries can be resumed. This can be useful for recovering from # systemic errors that erroneously failed many deliveries. hook = self.factory.makeWebhook() job = WebhookDeliveryJob.create(hook, 'test', payload={'foo': 'bar'}) client = MockWebhookClient(response_status=503) self.useFixture(ZopeUtilityFixture(client, IWebhookClient)) # Simulate a first attempt failure. self.assertEqual(False, self.runJob(job)) self.assertEqual(JobStatus.WAITING, job.status) self.assertIsNot(None, job.date_first_sent) # A manual retry brings the scheduled start forward. job.retry() self.assertEqual(JobStatus.WAITING, job.status) self.assertIsNot(None, job.date_first_sent) # When reset=True, date_first_sent is unset to restart the 24 # hour auto-retry window. job.retry(reset=True) self.assertEqual(JobStatus.WAITING, job.status) self.assertIs(None, job.date_first_sent)
def setUp(self): super(TestChannelsValidator, self).setUp() self.risks = [ { "name": "stable", "display_name": "Stable" }, { "name": "candidate", "display_name": "Candidate" }, { "name": "beta", "display_name": "Beta" }, { "name": "edge", "display_name": "Edge" }, ] snap_store_client = FakeMethod() snap_store_client.listChannels = FakeMethod(result=self.risks) self.useFixture(ZopeUtilityFixture(snap_store_client, ISnapStoreClient))
def setUp(self): super(SnapTestHelpers, self).setUp() snap_store_client = FakeMethod() snap_store_client.listChannels = FakeMethod(result=[]) self.useFixture(ZopeUtilityFixture(snap_store_client, ISnapStoreClient))
def _setUp(self): self.useFixture(ZopeUtilityFixture(self, IMemcacheClient))