Ines Almeida has proposed merging ~ines-almeida/launchpad:webhook-patterns/add-functionality into launchpad:master with ~ines-almeida/launchpad:webhook-patterns/update-models as a prerequisite.
Commit message: Filter git repository webhooks by git_ref_pattern field Git push, merge proposal and CI Build events (all events associated with a git repository/git references/git branches) will now be matched against the webhook's git_ref_pattern field. The webhook is not be triggered if there is a git_ref_pattern but it doesn't match the git references associated with the events. Requested reviews: Launchpad code reviewers (launchpad-reviewers) For more details, see: https://code.launchpad.net/~ines-almeida/launchpad/+git/launchpad/+merge/446982 This MP follows this one: https://code.launchpad.net/~ines-almeida/launchpad/+git/launchpad/+merge/446948 In the first we update the Webhook and CIBuild models. In this one we add all the remaining functionality for filtering webhook triggers by a git ref pattern: - Update 'WebhookSet.trigger()' function to match against the pattern - Update UI to show new `git_ref_pattern` field for git repo webhooks - FilterCI Builds, Merge Proposal and Git Push events -- Your team Launchpad code reviewers is requested to review the proposed merge of ~ines-almeida/launchpad:webhook-patterns/add-functionality into launchpad:master.
diff --git a/lib/lp/code/model/gitjob.py b/lib/lp/code/model/gitjob.py index 566c728..1c51b94 100644 --- a/lib/lp/code/model/gitjob.py +++ b/lib/lp/code/model/gitjob.py @@ -253,8 +253,12 @@ class GitRefScanJob(GitJobDerived): upserted_refs, removed_refs, ) + git_refs = list(payload["ref_changes"].keys()) getUtility(IWebhookSet).trigger( - self.repository, "git:push:0.1", payload + self.repository, + "git:push:0.1", + payload, + git_refs=git_refs, ) except LostObjectError: log.info( diff --git a/lib/lp/code/model/tests/test_branchmergeproposal.py b/lib/lp/code/model/tests/test_branchmergeproposal.py index 2a7007a..06f8c04 100644 --- a/lib/lp/code/model/tests/test_branchmergeproposal.py +++ b/lib/lp/code/model/tests/test_branchmergeproposal.py @@ -112,6 +112,7 @@ class WithVCSScenarios(WithScenarios): stacked_on=None, information_type=None, owner=None, + name=None, ): # Create the product pillar and its access policy if information # type is "PROPRIETARY". @@ -128,10 +129,12 @@ class WithVCSScenarios(WithScenarios): kwargs = {"information_type": information_type, "owner": owner} if self.git: kwargs["target"] = product - return self.factory.makeGitRefs(**kwargs)[0] + paths = [name] if name else None + return self.factory.makeGitRefs(paths=paths, **kwargs)[0] else: kwargs["product"] = product kwargs["stacked_on"] = stacked_on + kwargs["name"] = name return self.factory.makeProductBranch(**kwargs) def makeBranchMergeProposal( @@ -1297,16 +1300,17 @@ class TestMergeProposalWebhooks(WithVCSScenarios, TestCaseWithFactory): self.assertCorrectDelivery(expected_payload, hook, delivery) self.assertCorrectLogging(expected_redacted_payload, hook, logger) - def test_create_triggers_webhooks(self): + def test_create_triggers_webhooks(self, ref_pattern=None): # When a merge proposal is created, any relevant webhooks are # triggered. logger = self.useFixture(FakeLogger()) source = self.makeBranch() - target = self.makeBranch(same_target_as=source) + target = self.makeBranch(same_target_as=source, name="foo-bar") registrant = self.factory.makePerson() hook = self.factory.makeWebhook( target=self.getWebhookTarget(target), event_types=["merge-proposal:0.1"], + git_ref_pattern=ref_pattern, ) proposal = source.addLandingTarget( registrant, target, needs_review=True @@ -1325,12 +1329,12 @@ class TestMergeProposalWebhooks(WithVCSScenarios, TestCaseWithFactory): self.assertCorrectDelivery(expected_payload, hook, delivery) self.assertCorrectLogging(expected_redacted_payload, hook, logger) - def test_modify_triggers_webhooks(self): + def test_modify_triggers_webhooks(self, ref_pattern=None): logger = self.useFixture(FakeLogger()) # When an existing merge proposal is modified, any relevant webhooks # are triggered. source = self.makeBranch() - target = self.makeBranch(same_target_as=source) + target = self.makeBranch(same_target_as=source, name="foo-bar") registrant = self.factory.makePerson() proposal = source.addLandingTarget( registrant, target, needs_review=True @@ -1338,6 +1342,7 @@ class TestMergeProposalWebhooks(WithVCSScenarios, TestCaseWithFactory): hook = self.factory.makeWebhook( target=self.getWebhookTarget(target), event_types=["merge-proposal:0.1"], + git_ref_pattern=ref_pattern, ) login_person(target.owner) expected_payload = { @@ -1364,12 +1369,12 @@ class TestMergeProposalWebhooks(WithVCSScenarios, TestCaseWithFactory): self.assertCorrectDelivery(expected_payload, hook, delivery) self.assertCorrectLogging(expected_redacted_payload, hook, logger) - def test_delete_triggers_webhooks(self): + def test_delete_triggers_webhooks(self, ref_pattern=None): # When an existing merge proposal is deleted, any relevant webhooks # are triggered. logger = self.useFixture(FakeLogger()) source = self.makeBranch() - target = self.makeBranch(same_target_as=source) + target = self.makeBranch(same_target_as=source, name="foo-bar") registrant = self.factory.makePerson() proposal = source.addLandingTarget( registrant, target, needs_review=True @@ -1377,6 +1382,7 @@ class TestMergeProposalWebhooks(WithVCSScenarios, TestCaseWithFactory): hook = self.factory.makeWebhook( target=self.getWebhookTarget(target), event_types=["merge-proposal:0.1"], + git_ref_pattern=ref_pattern, ) login_person(target.owner) expected_payload = { @@ -1394,6 +1400,77 @@ class TestMergeProposalWebhooks(WithVCSScenarios, TestCaseWithFactory): self.assertCorrectLogging(expected_redacted_payload, hook, logger) +class TestGitMergeProposalWebhooks(TestMergeProposalWebhooks): + # Override scenarios to only have git related tests in this class + scenarios = [ + ("git", {"git": True}), + ] + + def test_create_triggers_webhooks_with_matching_ref_pattern(self): + self.test_create_triggers_webhooks("*foo*") + + def test_modify_triggers_webhooks_with_matching_ref_pattern(self): + self.test_modify_triggers_webhooks("*foo*") + + def test_delete_triggers_webhooks_with_matching_ref_pattern(self): + self.test_delete_triggers_webhooks("*foo*") + + def test_create_doesnt_trigger_webhooks_without_matching_ref_pattern(self): + source = self.makeBranch() + target = self.makeBranch(same_target_as=source, name="foo-bar") + registrant = self.factory.makePerson() + hook = self.factory.makeWebhook( + target=self.getWebhookTarget(target), + event_types=["merge-proposal:0.1"], + git_ref_pattern="not-matching-test", + ) + source.addLandingTarget(registrant, target, needs_review=True) + with admin_logged_in(): + self.assertEqual(0, len(list(hook.deliveries))) + + def test_modify_doesnt_trigger_webhooks_without_matching_ref_pattern(self): + source = self.makeBranch() + target = self.makeBranch(same_target_as=source, name="foo-bar") + registrant = self.factory.makePerson() + proposal = source.addLandingTarget( + registrant, target, needs_review=True + ) + hook = self.factory.makeWebhook( + target=self.getWebhookTarget(target), + event_types=["merge-proposal:0.1"], + git_ref_pattern="not-matching-test", + ) + + with person_logged_in( + target.owner + ), BranchMergeProposalNoPreviewDiffDelta.monitor(proposal): + proposal.setStatus( + BranchMergeProposalStatus.CODE_APPROVED, user=target.owner + ) + proposal.description = "An excellent proposal." + + with admin_logged_in(): + self.assertEqual(0, len(list(hook.deliveries))) + + def test_delete_doesnt_trigger_webhooks_without_matching_ref_pattern(self): + source = self.makeBranch() + target = self.makeBranch(same_target_as=source, name="foo-bar") + registrant = self.factory.makePerson() + proposal = source.addLandingTarget( + registrant, target, needs_review=True + ) + hook = self.factory.makeWebhook( + target=self.getWebhookTarget(target), + event_types=["merge-proposal:0.1"], + git_ref_pattern="not-matching-test", + ) + with person_logged_in(target.owner): + proposal.deleteProposal() + + with admin_logged_in(): + self.assertEqual(0, len(list(hook.deliveries))) + + class TestGetAddress(TestCaseWithFactory): """Test that the address property gives expected results.""" diff --git a/lib/lp/code/model/tests/test_gitjob.py b/lib/lp/code/model/tests/test_gitjob.py index 28f4a5f..36b26a2 100644 --- a/lib/lp/code/model/tests/test_gitjob.py +++ b/lib/lp/code/model/tests/test_gitjob.py @@ -230,6 +230,71 @@ class TestGitRefScanJob(TestCaseWithFactory): ), ) + def test_git_ref_pattern_match_triggers_webhooks(self): + # Jobs trigger any relevant webhooks if their `git_ref_pattern` matches + # at least one of the git references + repository = self.factory.makeGitRepository() + self.factory.makeGitRefs( + repository, paths=["refs/heads/master", "refs/tags/1.0"] + ) + hook = self.factory.makeWebhook( + target=repository, + event_types=["git:push:0.1"], + git_ref_pattern="*tags/1.0", + ) + job = GitRefScanJob.create(repository) + paths = ("refs/heads/master", "refs/tags/2.0") + self.useFixture(GitHostingFixture(refs=self.makeFakeRefs(paths))) + with dbuser("branchscanner"): + JobRunner([job]).runAll() + delivery = hook.deliveries.one() + sha1 = lambda s: hashlib.sha1(s).hexdigest() + payload_matcher = MatchesDict( + { + "git_repository": Equals("/" + repository.unique_name), + "git_repository_path": Equals(repository.unique_name), + "ref_changes": Equals( + { + "refs/tags/1.0": { + "old": {"commit_sha1": sha1(b"refs/tags/1.0")}, + "new": None, + }, + "refs/tags/2.0": { + "old": None, + "new": {"commit_sha1": sha1(b"refs/tags/2.0")}, + }, + } + ), + } + ) + self.assertThat( + delivery, + MatchesStructure( + event_type=Equals("git:push:0.1"), payload=payload_matcher + ), + ) + + def test_git_ref_pattern_doesnt_match_doesnt_trigger_webhooks(self): + # Jobs are not triggered if `git_ref_pattern` doesn't match any of the + # git references. + repository = self.factory.makeGitRepository() + self.factory.makeGitRefs( + repository, paths=["refs/heads/master", "refs/tags/1.0"] + ) + hook = self.factory.makeWebhook( + target=repository, + event_types=["git:push:0.1"], + git_ref_pattern="*foo", + ) + job = GitRefScanJob.create(repository) + paths = ("refs/heads/master", "refs/tags/2.0") + self.useFixture(GitHostingFixture(refs=self.makeFakeRefs(paths))) + with dbuser("branchscanner"): + JobRunner([job]).runAll() + + with person_logged_in(repository.owner): + self.assertEqual(0, len(list(hook.deliveries))) + def test_triggers_webhooks_with_oci_project_as_repository_target(self): # Jobs trigger any relevant webhooks when they're enabled. logger = self.useFixture(FakeLogger()) diff --git a/lib/lp/code/model/tests/test_gitrepository.py b/lib/lp/code/model/tests/test_gitrepository.py index 057c80a..89a44f5 100644 --- a/lib/lp/code/model/tests/test_gitrepository.py +++ b/lib/lp/code/model/tests/test_gitrepository.py @@ -4172,13 +4172,15 @@ class TestGitRepositoryRequestCIBuilds(TestCaseWithFactory): ) self.assertEqual("", logger.getLogBuffer()) - def test_triggers_webhooks(self): + def test_triggers_webhooks(self, git_ref_pattern=None): # Requesting CI builds triggers any relevant webhooks. self.useFixture(FeatureFixture({CI_WEBHOOKS_FEATURE_FLAG: "on"})) logger = self.useFixture(FakeLogger()) repository = self.factory.makeGitRepository() hook = self.factory.makeWebhook( - target=repository, event_types=["ci:build:0.1"] + target=repository, + event_types=["ci:build:0.1"], + git_ref_pattern=git_ref_pattern, ) ubuntu = getUtility(ILaunchpadCelebrities).ubuntu distroseries = self.factory.makeDistroSeries(distribution=ubuntu) @@ -4249,6 +4251,59 @@ class TestGitRepositoryRequestCIBuilds(TestCaseWithFactory): ), ) + def test_triggers_webhooks_when_git_ref_pattern_matches(self): + self.test_triggers_webhooks(git_ref_pattern="*test") + + def test_doesnt_trigger_webhooks_when_git_ref_pattern_doesnt_matches(self): + # Requesting CI builds doesnt trigger relevant webhooks when git ref + # pattern doesn't match. + self.useFixture(FeatureFixture({CI_WEBHOOKS_FEATURE_FLAG: "on"})) + repository = self.factory.makeGitRepository() + hook = self.factory.makeWebhook( + target=repository, + event_types=["ci:build:0.1"], + git_ref_pattern="non_matching_pattern", + ) + ubuntu = getUtility(ILaunchpadCelebrities).ubuntu + distroseries = self.factory.makeDistroSeries(distribution=ubuntu) + das = self.factory.makeBuildableDistroArchSeries( + distroseries=distroseries + ) + configuration = dedent( + """\ + pipeline: [test] + jobs: + test: + series: {series} + architectures: [{architecture}] + """.format( + series=distroseries.name, architecture=das.architecturetag + ) + ).encode() + new_commit = hashlib.sha1(self.factory.getUniqueBytes()).hexdigest() + self.useFixture( + GitHostingFixture( + commits=[ + { + "sha1": new_commit, + "blobs": {".launchpad.yaml": configuration}, + }, + ] + ) + ) + with dbuser("branchscanner"): + repository.createOrUpdateRefs( + { + "refs/heads/test": { + "sha1": new_commit, + "type": GitObjectType.COMMIT, + } + } + ) + + getUtility(ICIBuildSet).findByGitRepository(repository) + self.assertIsNone(hook.deliveries.one()) + class TestGitRepositoryGetBlob(TestCaseWithFactory): """Tests for retrieving files from a Git repository.""" diff --git a/lib/lp/code/subscribers/branchmergeproposal.py b/lib/lp/code/subscribers/branchmergeproposal.py index 4eb94dc..32da5a5 100644 --- a/lib/lp/code/subscribers/branchmergeproposal.py +++ b/lib/lp/code/subscribers/branchmergeproposal.py @@ -54,8 +54,19 @@ def _trigger_webhook(merge_proposal, payload): target = merge_proposal.target_branch else: target = merge_proposal.target_git_repository + + git_refs = [] + if "new" in payload: + git_refs.append(payload["new"]["target_git_path"]) + if "old" in payload: + git_refs.append(payload["old"]["target_git_path"]) + getUtility(IWebhookSet).trigger( - target, "merge-proposal:0.1", payload, context=merge_proposal + target, + "merge-proposal:0.1", + payload, + context=merge_proposal, + git_refs=git_refs, ) diff --git a/lib/lp/code/subscribers/cibuild.py b/lib/lp/code/subscribers/cibuild.py index 83d3363..90aa556 100644 --- a/lib/lp/code/subscribers/cibuild.py +++ b/lib/lp/code/subscribers/cibuild.py @@ -25,7 +25,10 @@ def _trigger_ci_build_webhook(build: ICIBuild, action: str) -> None: ) ) getUtility(IWebhookSet).trigger( - build.git_repository, "ci:build:0.1", payload + build.git_repository, + "ci:build:0.1", + payload, + git_refs=build.git_refs, ) diff --git a/lib/lp/services/webhooks/browser.py b/lib/lp/services/webhooks/browser.py index 991d7ef..9b9104e 100644 --- a/lib/lp/services/webhooks/browser.py +++ b/lib/lp/services/webhooks/browser.py @@ -19,6 +19,7 @@ from lp.app.browser.launchpadform import ( action, ) from lp.app.widgets.itemswidgets import LabeledMultiCheckBoxWidget +from lp.code.interfaces.gitrepository import IGitRepository from lp.services.propertycache import cachedproperty from lp.services.webapp import ( LaunchpadView, @@ -100,7 +101,14 @@ class WebhookBreadcrumb(Breadcrumb): class WebhookEditSchema(Interface): use_template( - IWebhook, include=["delivery_url", "event_types", "active", "secret"] + IWebhook, + include=[ + "delivery_url", + "event_types", + "active", + "secret", + "git_ref_pattern", + ], ) @@ -111,6 +119,14 @@ class WebhookAddView(LaunchpadFormView): custom_widget_event_types = LabeledMultiCheckBoxWidget next_url = None + def initialize(self): + # Don't show `git_ref_pattern` in webhook creation page unless target + # is Git Repository + self.field_names = ["delivery_url", "event_types", "active", "secret"] + if IGitRepository.providedBy(self.context): + self.field_names.append("git_ref_pattern") + super().initialize() + @property def inside_breadcrumb(self): return WebhooksBreadcrumb(self.context) @@ -143,11 +159,16 @@ class WebhookView(LaunchpadEditFormView): label = "Manage webhook" schema = WebhookEditSchema - # XXX wgrant 2015-08-04: Need custom widget for secret. - field_names = ["delivery_url", "event_types", "active"] custom_widget_event_types = LabeledMultiCheckBoxWidget def initialize(self): + # Don't show `git_ref_pattern` in webhook edit page unless target + # is Git Repository + # XXX wgrant 2015-08-04: Need custom widget for secret. + self.field_names = ["delivery_url", "event_types", "active"] + if IGitRepository.providedBy(self.context.target): + self.field_names.append("git_ref_pattern") + super().initialize() cache = IJSONRequestCache(self.request) cache.objects["deliveries"] = list(self.deliveries.batch) diff --git a/lib/lp/services/webhooks/interfaces.py b/lib/lp/services/webhooks/interfaces.py index 72c2e12..f3309e0 100644 --- a/lib/lp/services/webhooks/interfaces.py +++ b/lib/lp/services/webhooks/interfaces.py @@ -240,7 +240,7 @@ class IWebhookSet(Interface): def findByTarget(target): """Find all webhooks for the given target.""" - def trigger(target, event_type, payload, context=None): + def trigger(target, event_type, payload, context=None, git_refs=None): """Trigger subscribed webhooks to deliver a payload.""" diff --git a/lib/lp/services/webhooks/model.py b/lib/lp/services/webhooks/model.py index 605d5c9..ec3e283 100644 --- a/lib/lp/services/webhooks/model.py +++ b/lib/lp/services/webhooks/model.py @@ -13,6 +13,7 @@ import re import socket from datetime import datetime, timedelta, timezone from fnmatch import fnmatch +from typing import List from urllib.parse import urlsplit import iso8601 @@ -328,7 +329,28 @@ class WebhookSet: ) ) - def trigger(self, target, event_type, payload, context=None): + def _checkGitRefs(self, webhook: Webhook, git_refs: List[str]): + """Check if any of the `git_refs` matches the webhook's + `git_ref_pattern`. + + :return: True if any of the git_refs match, if there are no git_refs, + or if there is no git_ref_pattern. + otherwise False. + """ + if not webhook.git_ref_pattern or git_refs is None: + return True + + for git_ref in git_refs: + if webhook.checkGitRefPattern(git_ref): + return True + return False + + def trigger( + self, target, event_type, payload, context=None, git_refs=None + ): + print("!!!!!!!!!!!!!!") + print(git_refs) + if context is None: context = target user = removeSecurityProxy(target).owner @@ -338,7 +360,11 @@ class WebhookSet: # each webhook, but the set should be small and we'd have to defer # the triggering itself to a job to fix it. for webhook in self.findByTarget(target): - if webhook.active and event_type in webhook.event_types: + if ( + webhook.active + and event_type in webhook.event_types + and self._checkGitRefs(webhook, git_refs) + ): WebhookDeliveryJob.create(webhook, event_type, payload) diff --git a/lib/lp/services/webhooks/tests/test_browser.py b/lib/lp/services/webhooks/tests/test_browser.py index f8a76a7..a218a96 100644 --- a/lib/lp/services/webhooks/tests/test_browser.py +++ b/lib/lp/services/webhooks/tests/test_browser.py @@ -14,6 +14,7 @@ from lp.charms.interfaces.charmrecipe import ( CHARM_RECIPE_ALLOW_CREATE, CHARM_RECIPE_WEBHOOKS_FEATURE_FLAG, ) +from lp.code.interfaces.gitrepository import IGitRepository from lp.oci.interfaces.ocirecipe import OCI_RECIPE_ALLOW_CREATE from lp.services.features.testing import FeatureFixture from lp.services.webapp.interfaces import IPlacelessAuthUtility @@ -418,19 +419,37 @@ class TestWebhookAddViewBase(WebhookTargetViewTestHelpers): ), ) - def test_creates(self): - view = self.makeView( - "+new-webhook", - method="POST", - form={ - "field.delivery_url": "http://example.com/test", - "field.active": "on", - "field.event_types-empty-marker": "1", - "field.event_types": self.event_type, - "field.secret": "secret code", - "field.actions.new": "Add webhook", - }, - ) + def test_rendering_check_git_ref_pattern_field(self): + # Verify that `git_ref_pattern` field exists in webhook's forms for git + # repositories, but not for other webhook forms + if IGitRepository.providedBy(self.target): + self.assertThat( + self.makeView("+new-webhook")(), + soupmatchers.HTMLContains( + soupmatchers.TagWithId("field.git_ref_pattern", count=1) + ), + ) + else: + self.assertThat( + self.makeView("+new-webhook")(), + soupmatchers.HTMLContains( + soupmatchers.TagWithId("field.git_ref_pattern", count=0) + ), + ) + + def test_creates(self, ref_pattern_input=None, expected_ref_pattern=None): + form_data = { + "field.delivery_url": "http://example.com/test", + "field.active": "on", + "field.event_types-empty-marker": "1", + "field.event_types": self.event_type, + "field.secret": "secret code", + "field.actions.new": "Add webhook", + } + if ref_pattern_input: + form_data.update({"field.git_ref_pattern": ref_pattern_input}) + + view = self.makeView("+new-webhook", method="POST", form=form_data) self.assertEqual([], view.errors) hook = self.target.webhooks.one() self.assertThat( @@ -442,9 +461,23 @@ class TestWebhookAddViewBase(WebhookTargetViewTestHelpers): active=True, event_types=[self.event_type], secret="secret code", + git_ref_pattern=expected_ref_pattern, ), ) + def test_creates_with_git_ref_pattern_field(self): + # Adding webhook with `git_ref_pattern` field works for git repository + # webhooks, but not for other webhooks (creation is successful, but + # `git_ref_pattern` param is ignored) + if IGitRepository.providedBy(self.target): + self.test_creates( + ref_pattern_input="*test*", expected_ref_pattern="*test*" + ) + else: + self.test_creates( + ref_pattern_input="*test*", expected_ref_pattern=None + ) + def test_rejects_bad_scheme(self): transaction.commit() view = self.makeView( @@ -603,16 +636,37 @@ class TestWebhookViewBase(WebhookViewTestHelpers): ), ) - def test_saves(self): + def test_rendering_check_git_ref_pattern_field(self): + # Verify that `git_ref_pattern` field exists in webhook's forms for git + # repositories, but not for other webhook forms + if IGitRepository.providedBy(self.target): + self.assertThat( + self.makeView("+index")(), + soupmatchers.HTMLContains( + soupmatchers.TagWithId("field.git_ref_pattern", count=1) + ), + ) + else: + self.assertThat( + self.makeView("+index")(), + soupmatchers.HTMLContains( + soupmatchers.TagWithId("field.git_ref_pattern", count=0) + ), + ) + + def test_saves(self, ref_pattern_input=None, expected_ref_pattern=None): + form_data = { + "field.delivery_url": "http://example.com/edited", + "field.active": "off", + "field.event_types-empty-marker": "1", + "field.actions.save": "Save webhook", + } + if ref_pattern_input: + form_data.update({"field.git_ref_pattern": ref_pattern_input}) view = self.makeView( "+index", method="POST", - form={ - "field.delivery_url": "http://example.com/edited", - "field.active": "off", - "field.event_types-empty-marker": "1", - "field.actions.save": "Save webhook", - }, + form=form_data, ) self.assertEqual([], view.errors) self.assertThat( @@ -621,9 +675,23 @@ class TestWebhookViewBase(WebhookViewTestHelpers): delivery_url="http://example.com/edited", active=False, event_types=[], + git_ref_pattern=expected_ref_pattern, ), ) + def test_saves_with_git_ref_pattern_field(self): + # Editing `git_ref_pattern` field works for git repository webhooks, + # but not for other webhooks (edit is successful, but `git_ref_pattern` + # param is ignored) + if IGitRepository.providedBy(self.target): + self.test_saves( + ref_pattern_input="*test*", expected_ref_pattern="*test*" + ) + else: + self.test_saves( + ref_pattern_input="*test*", expected_ref_pattern=None + ) + def test_rejects_bad_scheme(self): transaction.commit() view = self.makeView( diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py index a7af4de..8514437 100644 --- a/lib/lp/testing/factory.py +++ b/lib/lp/testing/factory.py @@ -6884,6 +6884,7 @@ class LaunchpadObjectFactory(ObjectFactory): status=BuildStatus.NEEDSBUILD, builder=None, duration=None, + git_refs=None, ): """Make a new `CIBuild`.""" if git_repository is None: @@ -6900,6 +6901,7 @@ class LaunchpadObjectFactory(ObjectFactory): distro_arch_series, stages, date_created=date_created, + git_refs=git_refs, ) if duration is not None: removeSecurityProxy(build).updateStatus(
_______________________________________________ Mailing list: https://launchpad.net/~launchpad-reviewers Post to : launchpad-reviewers@lists.launchpad.net Unsubscribe : https://launchpad.net/~launchpad-reviewers More help : https://help.launchpad.net/ListHelp