diff --git a/echo-webhooks/src/main/groovy/com/netflix/spinnaker/echo/scm/BitbucketWebhookEventHandler.java b/echo-webhooks/src/main/groovy/com/netflix/spinnaker/echo/scm/BitbucketWebhookEventHandler.java index bd7b10de9..5d1bbce8e 100644 --- a/echo-webhooks/src/main/groovy/com/netflix/spinnaker/echo/scm/BitbucketWebhookEventHandler.java +++ b/echo-webhooks/src/main/groovy/com/netflix/spinnaker/echo/scm/BitbucketWebhookEventHandler.java @@ -22,20 +22,26 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.netflix.spinnaker.echo.api.events.Event; import com.netflix.spinnaker.echo.jackson.EchoObjectMapper; +import com.netflix.spinnaker.echo.scm.bitbucket.server.BitbucketServerEventHandler; import java.util.List; import java.util.Map; +import java.util.Optional; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.springframework.lang.Nullable; import org.springframework.stereotype.Component; @Component @Slf4j public class BitbucketWebhookEventHandler implements GitWebhookHandler { - private ObjectMapper objectMapper; + private final ObjectMapper objectMapper; + private final Optional bitbucketServerEventHandler; - public BitbucketWebhookEventHandler() { + public BitbucketWebhookEventHandler( + @Nullable BitbucketServerEventHandler bitbucketServerEventHandler) { this.objectMapper = EchoObjectMapper.getInstance(); + this.bitbucketServerEventHandler = Optional.ofNullable(bitbucketServerEventHandler); } public boolean handles(String source) { @@ -115,6 +121,10 @@ private boolean looksLikeBitbucketCloud(Event event) { } private boolean looksLikeBitbucketServer(Event event) { + if (bitbucketServerEventHandler.isPresent()) { + return bitbucketServerEventHandler.get().looksLikeBitbucketServer(event); + } + String eventType = event.content.get("event_type").toString(); return (eventType.equals("repo:refs_changed") || eventType.equals("pr:merged")); } @@ -193,6 +203,11 @@ private void handleBitbucketCloudEvent(Event event, Map postedEvent) { } private void handleBitbucketServerEvent(Event event, Map postedEvent) { + if (bitbucketServerEventHandler.isPresent()) { + bitbucketServerEventHandler.get().handleBitbucketServerEvent(event); + return; + } + String repoProject = ""; String slug = ""; String hash = ""; diff --git a/echo-webhooks/src/main/groovy/com/netflix/spinnaker/echo/scm/bitbucket/server/BitbucketServerEventHandler.java b/echo-webhooks/src/main/groovy/com/netflix/spinnaker/echo/scm/bitbucket/server/BitbucketServerEventHandler.java new file mode 100644 index 000000000..94d9a2156 --- /dev/null +++ b/echo-webhooks/src/main/groovy/com/netflix/spinnaker/echo/scm/bitbucket/server/BitbucketServerEventHandler.java @@ -0,0 +1,219 @@ +/* + * Copyright 2022 Armory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.echo.scm.bitbucket.server; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.netflix.spinnaker.echo.api.events.Event; +import com.netflix.spinnaker.echo.jackson.EchoObjectMapper; +import java.util.List; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.stereotype.Component; + +@Component +@ConditionalOnProperty("webhooks.bitbucket.server.extras.enabled") +public class BitbucketServerEventHandler { + + private final List bitbucketServerEventTypes = + List.of( + "pr:opened", + "repo:refs_changed", + "pr:from_ref_updated", + "pr:merged", + "pr:declined", + "pr:deleted"); + + private String repoProject; + private String slug; + private String hash; + private String branch; + + private final ObjectMapper objectMapper = EchoObjectMapper.getInstance(); + + public boolean looksLikeBitbucketServer(Event event) { + String eventType = event.content.get("event_type").toString(); + return bitbucketServerEventTypes.contains(eventType); + } + + public void handleBitbucketServerEvent(Event event) { + + if (!event.content.containsKey("event_type")) { + return; + } + + String eventType = event.content.get("event_type").toString(); + + switch (eventType) { + case "pr:opened": + handlePrOpenedEvent(event); + break; + + case "repo:refs_changed": + handleRepoRefsChangedEvent(event); + break; + + case "pr:from_ref_updated": + handlePrFromRefUpdatedEvent(event); + break; + + case "pr:merged": + handlePrMergedEvent(event); + break; + + case "pr:declined": + handlePrDeclinedEvent(event); + break; + + case "pr:deleted": + handlePrDeletedEvent(event); + break; + + default: // Do nothing + break; + } + + event.content.put("repoProject", repoProject); + event.content.put("slug", slug); + event.content.put("hash", hash); + event.content.put("branch", branch); + event.content.put("action", eventType); + } + + private void handlePrOpenedEvent(Event event) { + BitbucketServerPrEvent prOpenedEvent = + objectMapper.convertValue(event.content, BitbucketServerPrEvent.class); + + if (prOpenedEvent.getPullRequest() != null + && prOpenedEvent.getPullRequest().getFromRef() != null) { + + BitbucketServerPrEvent.Ref fromRef = prOpenedEvent.getPullRequest().getFromRef(); + branch = StringUtils.defaultIfEmpty(fromRef.getId(), "").replace("refs/heads/", ""); + + if (fromRef.getRepository() != null) { + repoProject = StringUtils.defaultIfEmpty(fromRef.getRepository().getProject().getKey(), ""); + slug = StringUtils.defaultIfEmpty(fromRef.getRepository().getSlug(), ""); + } + + if (fromRef.getLatestCommit() != null) { + hash = StringUtils.defaultIfEmpty(fromRef.latestCommit, ""); + } + } + } + + private void handleRepoRefsChangedEvent(Event event) { + BitbucketServerRepoEvent refsChangedEvent = + objectMapper.convertValue(event.content, BitbucketServerRepoEvent.class); + + if (refsChangedEvent.repository != null) { + repoProject = StringUtils.defaultIfEmpty(refsChangedEvent.repository.project.key, ""); + slug = StringUtils.defaultIfEmpty(refsChangedEvent.repository.slug, ""); + } + + if (!refsChangedEvent.changes.isEmpty()) { + BitbucketServerRepoEvent.Change change = refsChangedEvent.changes.get(0); + hash = StringUtils.defaultIfEmpty(change.toHash, ""); + if (change.ref != null) { + branch = StringUtils.defaultIfEmpty(change.ref.id, "").replace("refs/heads/", ""); + } + } + } + + private void handlePrFromRefUpdatedEvent(Event event) { + BitbucketServerPrEvent fromRefUpdatedEvent = + objectMapper.convertValue(event.content, BitbucketServerPrEvent.class); + + if (fromRefUpdatedEvent.getPullRequest() != null + && fromRefUpdatedEvent.getPullRequest().getFromRef() != null) { + + BitbucketServerPrEvent.Ref fromRef = fromRefUpdatedEvent.getPullRequest().getFromRef(); + branch = StringUtils.defaultIfEmpty(fromRef.getId(), "").replace("refs/heads/", ""); + + if (fromRef.getRepository() != null) { + repoProject = StringUtils.defaultIfEmpty(fromRef.getRepository().getProject().getKey(), ""); + slug = StringUtils.defaultIfEmpty(fromRef.getRepository().getSlug(), ""); + } + + if (fromRef.getLatestCommit() != null) { + hash = StringUtils.defaultIfEmpty(fromRef.latestCommit, ""); + } + } + } + + private void handlePrMergedEvent(Event event) { + BitbucketServerPrEvent prMergedEvent = + objectMapper.convertValue(event.content, BitbucketServerPrEvent.class); + + if (prMergedEvent.getPullRequest() != null && prMergedEvent.getPullRequest().toRef != null) { + BitbucketServerPrEvent.Ref toRef = prMergedEvent.getPullRequest().toRef; + branch = StringUtils.defaultIfEmpty(toRef.getId(), "").replace("refs/heads/", ""); + if (toRef.getRepository() != null) { + repoProject = StringUtils.defaultIfEmpty(toRef.getRepository().getProject().getKey(), ""); + slug = StringUtils.defaultIfEmpty(toRef.getRepository().getSlug(), ""); + } + } + + if (prMergedEvent.getPullRequest() != null + && prMergedEvent.getPullRequest().getProperties() != null) { + BitbucketServerPrEvent.Properties properties = prMergedEvent.getPullRequest().getProperties(); + if (properties.getMergeCommit() != null) { + hash = StringUtils.defaultIfEmpty(properties.getMergeCommit().getId(), ""); + } + } + } + + private void handlePrDeclinedEvent(Event event) { + BitbucketServerPrEvent prDeclinedEvent = + objectMapper.convertValue(event.content, BitbucketServerPrEvent.class); + + if (prDeclinedEvent.getPullRequest() != null + && prDeclinedEvent.getPullRequest().getFromRef() != null) { + + BitbucketServerPrEvent.Ref fromRef = prDeclinedEvent.getPullRequest().getFromRef(); + branch = StringUtils.defaultIfEmpty(fromRef.getId(), "").replace("refs/heads/", ""); + + if (fromRef.getRepository() != null) { + repoProject = StringUtils.defaultIfEmpty(fromRef.getRepository().getProject().getKey(), ""); + slug = StringUtils.defaultIfEmpty(fromRef.getRepository().getSlug(), ""); + } + + if (fromRef.getLatestCommit() != null) { + hash = StringUtils.defaultIfEmpty(fromRef.latestCommit, ""); + } + } + } + + private void handlePrDeletedEvent(Event event) { + BitbucketServerPrEvent prDeletedEvent = + objectMapper.convertValue(event.content, BitbucketServerPrEvent.class); + + if (prDeletedEvent.getPullRequest() != null + && prDeletedEvent.getPullRequest().getFromRef() != null) { + + BitbucketServerPrEvent.Ref fromRef = prDeletedEvent.getPullRequest().getFromRef(); + branch = StringUtils.defaultIfEmpty(fromRef.getId(), "").replace("refs/heads/", ""); + + if (fromRef.getRepository() != null) { + repoProject = StringUtils.defaultIfEmpty(fromRef.getRepository().getProject().getKey(), ""); + slug = StringUtils.defaultIfEmpty(fromRef.getRepository().getSlug(), ""); + } + + if (fromRef.getLatestCommit() != null) { + hash = StringUtils.defaultIfEmpty(fromRef.latestCommit, ""); + } + } + } +} diff --git a/echo-webhooks/src/main/groovy/com/netflix/spinnaker/echo/scm/bitbucket/server/BitbucketServerPrEvent.java b/echo-webhooks/src/main/groovy/com/netflix/spinnaker/echo/scm/bitbucket/server/BitbucketServerPrEvent.java new file mode 100644 index 000000000..d30f0501b --- /dev/null +++ b/echo-webhooks/src/main/groovy/com/netflix/spinnaker/echo/scm/bitbucket/server/BitbucketServerPrEvent.java @@ -0,0 +1,61 @@ +/* + * Copyright 2022 Armory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.echo.scm.bitbucket.server; + +import lombok.Data; + +@Data +public class BitbucketServerPrEvent { + + private PullRequest pullRequest; + + @Data + public static class PullRequest { + BitbucketServerPrEvent.Ref fromRef; + BitbucketServerPrEvent.Ref toRef; + BitbucketServerPrEvent.Properties properties; + } + + @Data + public static class Ref { + String id; + String latestCommit; + BitbucketServerPrEvent.Repository repository; + } + + @Data + public static class Project { + String key; + } + + @Data + public static class Repository { + String name; + String slug; + Project project; + } + + @Data + public static class Properties { + BitbucketServerPrEvent.MergeCommit mergeCommit; + } + + @Data + public static class MergeCommit { + String id; + } +} diff --git a/echo-webhooks/src/main/groovy/com/netflix/spinnaker/echo/scm/bitbucket/server/BitbucketServerRepoEvent.java b/echo-webhooks/src/main/groovy/com/netflix/spinnaker/echo/scm/bitbucket/server/BitbucketServerRepoEvent.java new file mode 100644 index 000000000..1105a01ad --- /dev/null +++ b/echo-webhooks/src/main/groovy/com/netflix/spinnaker/echo/scm/bitbucket/server/BitbucketServerRepoEvent.java @@ -0,0 +1,50 @@ +/* + * Copyright 2022 Armory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.echo.scm.bitbucket.server; + +import java.util.List; +import lombok.Data; + +@Data +public class BitbucketServerRepoEvent { + + List changes; + Repository repository; + + @Data + public static class Project { + String key; + } + + @Data + public static class Repository { + String name; + String slug; + BitbucketServerRepoEvent.Project project; + } + + @Data + public static class Change { + public String toHash; + Ref ref; + } + + @Data + public static class Ref { + String id; + } +} diff --git a/echo-webhooks/src/test/java/com/netflix/spinnaker/echo/scm/BitbucketWebhookEventHandlerTest.java b/echo-webhooks/src/test/java/com/netflix/spinnaker/echo/scm/BitbucketWebhookEventHandlerTest.java index 873949a64..8fb6d50d6 100644 --- a/echo-webhooks/src/test/java/com/netflix/spinnaker/echo/scm/BitbucketWebhookEventHandlerTest.java +++ b/echo-webhooks/src/test/java/com/netflix/spinnaker/echo/scm/BitbucketWebhookEventHandlerTest.java @@ -50,7 +50,7 @@ void canHandlePayload() throws IOException { event.content = payload; event.content.put("event_type", "repo:push"); - BitbucketWebhookEventHandler handler = new BitbucketWebhookEventHandler(); + BitbucketWebhookEventHandler handler = new BitbucketWebhookEventHandler(null); assertThatCode(() -> handler.handle(event, payload)).doesNotThrowAnyException(); } } diff --git a/echo-webhooks/src/test/java/com/netflix/spinnaker/echo/scm/bitbucket/server/BitbucketServerEventHandlerTest.java b/echo-webhooks/src/test/java/com/netflix/spinnaker/echo/scm/bitbucket/server/BitbucketServerEventHandlerTest.java new file mode 100644 index 000000000..f76186e4a --- /dev/null +++ b/echo-webhooks/src/test/java/com/netflix/spinnaker/echo/scm/bitbucket/server/BitbucketServerEventHandlerTest.java @@ -0,0 +1,269 @@ +/* + * Copyright 2022 Armory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.spinnaker.echo.scm.bitbucket.server; + +import static org.assertj.core.api.Assertions.*; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.netflix.spinnaker.echo.api.events.Event; +import com.netflix.spinnaker.echo.api.events.Metadata; +import com.netflix.spinnaker.echo.jackson.EchoObjectMapper; +import com.netflix.spinnaker.echo.scm.BitbucketWebhookEventHandler; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.Map; +import java.util.Objects; +import org.junit.jupiter.api.Test; + +class BitbucketServerEventHandlerTest { + + @Test + void testFeatureFlag() throws IOException { + File file = getPayloadFile("/bitbucket-server/bitbucket_server_pr_opened_payload.json"); + String rawPayload = new String(Files.readAllBytes(file.toPath())); + + ObjectMapper mapper = EchoObjectMapper.getInstance(); + Map payload = mapper.readValue(rawPayload, new TypeReference<>() {}); + + Event event = new Event(); + Metadata metadata = new Metadata(); + metadata.setType("git"); + metadata.setSource("bitbucket"); + event.details = metadata; + event.rawContent = rawPayload; + event.payload = payload; + event.content = payload; + event.content.put("event_type", "pr:opened"); + + BitbucketWebhookEventHandler handler = new BitbucketWebhookEventHandler(null); + + assertThatCode(() -> handler.handle(event, payload)).doesNotThrowAnyException(); + + assertThat(event.content) + .doesNotContain( + entry("action", "pr:opened"), + entry("repoProject", "RT"), + entry("slug", "repo-test"), + entry("hash", "1eb0b0f6e1725c4040f2605e1d58c9f9d2095658"), + entry("branch", "feature-branch")); + } + + @Test + void testHandleBitbucketServerPrOpenedEvent() throws IOException { + File file = getPayloadFile("/bitbucket-server/bitbucket_server_pr_opened_payload.json"); + String rawPayload = new String(Files.readAllBytes(file.toPath())); + + ObjectMapper mapper = EchoObjectMapper.getInstance(); + Map payload = mapper.readValue(rawPayload, new TypeReference<>() {}); + + Event event = new Event(); + Metadata metadata = new Metadata(); + metadata.setType("git"); + metadata.setSource("bitbucket"); + event.details = metadata; + event.rawContent = rawPayload; + event.payload = payload; + event.content = payload; + event.content.put("event_type", "pr:opened"); + + BitbucketServerEventHandler serverEventHandler = new BitbucketServerEventHandler(); + + BitbucketWebhookEventHandler handler = new BitbucketWebhookEventHandler(serverEventHandler); + + assertThatCode(() -> handler.handle(event, payload)).doesNotThrowAnyException(); + + assertThat(event.content) + .contains( + entry("action", "pr:opened"), + entry("repoProject", "RT"), + entry("slug", "repo-test"), + entry("hash", "1eb0b0f6e1725c4040f2605e1d58c9f9d2095658"), + entry("branch", "feature-branch")); + } + + @Test + void testBitbucketServerRefsChangedEvent() throws IOException { + File file = getPayloadFile("/bitbucket-server/bitbucket_server_repo_refs_changed_payload.json"); + String rawPayload = new String(Files.readAllBytes(file.toPath())); + + ObjectMapper mapper = EchoObjectMapper.getInstance(); + Map payload = mapper.readValue(rawPayload, new TypeReference<>() {}); + + Event event = new Event(); + Metadata metadata = new Metadata(); + metadata.setType("git"); + metadata.setSource("bitbucket"); + event.details = metadata; + event.rawContent = rawPayload; + event.payload = payload; + event.content = payload; + event.content.put("event_type", "repo:refs_changed"); + + BitbucketServerEventHandler serverEventHandler = new BitbucketServerEventHandler(); + + BitbucketWebhookEventHandler handler = new BitbucketWebhookEventHandler(serverEventHandler); + + assertThatCode(() -> handler.handle(event, payload)).doesNotThrowAnyException(); + + assertThat(event.content) + .contains( + entry("action", "repo:refs_changed"), + entry("repoProject", "RT"), + entry("slug", "repo-test"), + entry("hash", "9065c394975cf8750a83abb6e54ba27245a38926"), + entry("branch", "feature-branch")); + } + + @Test + void testBitbucketServerFromRefUpdatedEvent() throws IOException { + File file = + getPayloadFile("/bitbucket-server/bitbucket_server_pr_from_ref_updated_payload.json"); + String rawPayload = new String(Files.readAllBytes(file.toPath())); + + ObjectMapper mapper = EchoObjectMapper.getInstance(); + Map payload = mapper.readValue(rawPayload, new TypeReference<>() {}); + + Event event = new Event(); + Metadata metadata = new Metadata(); + metadata.setType("git"); + metadata.setSource("bitbucket"); + event.details = metadata; + event.rawContent = rawPayload; + event.payload = payload; + event.content = payload; + event.content.put("event_type", "pr:from_ref_updated"); + + BitbucketServerEventHandler serverEventHandler = new BitbucketServerEventHandler(); + + BitbucketWebhookEventHandler handler = new BitbucketWebhookEventHandler(serverEventHandler); + + assertThatCode(() -> handler.handle(event, payload)).doesNotThrowAnyException(); + + assertThat(event.content) + .contains( + entry("action", "pr:from_ref_updated"), + entry("repoProject", "RT"), + entry("slug", "repo-test"), + entry("hash", "9065c394975cf8750a83abb6e54ba27245a38926"), + entry("branch", "feature-branch")); + } + + @Test + void testHandleBitbucketServerPrMergedEvent() throws IOException { + File file = getPayloadFile("/bitbucket-server/bitbucket_server_pr_merged_payload.json"); + String rawPayload = new String(Files.readAllBytes(file.toPath())); + + ObjectMapper mapper = EchoObjectMapper.getInstance(); + Map payload = mapper.readValue(rawPayload, new TypeReference<>() {}); + + Event event = new Event(); + Metadata metadata = new Metadata(); + metadata.setType("git"); + metadata.setSource("bitbucket"); + event.details = metadata; + event.rawContent = rawPayload; + event.payload = payload; + event.content = payload; + event.content.put("event_type", "pr:merged"); + + BitbucketServerEventHandler serverEventHandler = new BitbucketServerEventHandler(); + + BitbucketWebhookEventHandler handler = new BitbucketWebhookEventHandler(serverEventHandler); + + assertThatCode(() -> handler.handle(event, payload)).doesNotThrowAnyException(); + + assertThat(event.content) + .contains( + entry("action", "pr:merged"), + entry("repoProject", "RT"), + entry("slug", "repo-test"), + entry("hash", "3e325b06e6f13b948dbb33ae8177cfa78a043ac5"), + entry("branch", "main")); + } + + @Test + void testHandleBitbucketServerPrDeletedEvent() throws IOException { + File file = getPayloadFile("/bitbucket-server/bitbucket_server_pr_deleted_payload.json"); + String rawPayload = new String(Files.readAllBytes(file.toPath())); + + ObjectMapper mapper = EchoObjectMapper.getInstance(); + Map payload = mapper.readValue(rawPayload, new TypeReference<>() {}); + + Event event = new Event(); + Metadata metadata = new Metadata(); + metadata.setType("git"); + metadata.setSource("bitbucket"); + event.details = metadata; + event.rawContent = rawPayload; + event.payload = payload; + event.content = payload; + event.content.put("event_type", "pr:deleted"); + + BitbucketServerEventHandler serverEventHandler = new BitbucketServerEventHandler(); + + BitbucketWebhookEventHandler handler = new BitbucketWebhookEventHandler(serverEventHandler); + + assertThatCode(() -> handler.handle(event, payload)).doesNotThrowAnyException(); + + assertThat(event.content) + .contains( + entry("action", "pr:deleted"), + entry("repoProject", "RT"), + entry("slug", "repo-test"), + entry("hash", "a87ec312573d13fa35b9d877bbf03be0385ae314"), + entry("branch", "my-feature-branch")); + } + + @Test + void testHandleBitbucketServerPrDeclinedEvent() throws IOException { + File file = getPayloadFile("/bitbucket-server/bitbucket_server_pr_declined_payload.json"); + String rawPayload = new String(Files.readAllBytes(file.toPath())); + + ObjectMapper mapper = EchoObjectMapper.getInstance(); + Map payload = mapper.readValue(rawPayload, new TypeReference<>() {}); + + Event event = new Event(); + Metadata metadata = new Metadata(); + metadata.setType("git"); + metadata.setSource("bitbucket"); + event.details = metadata; + event.rawContent = rawPayload; + event.payload = payload; + event.content = payload; + event.content.put("event_type", "pr:declined"); + + BitbucketServerEventHandler serverEventHandler = new BitbucketServerEventHandler(); + + BitbucketWebhookEventHandler handler = new BitbucketWebhookEventHandler(serverEventHandler); + + assertThatCode(() -> handler.handle(event, payload)).doesNotThrowAnyException(); + + assertThat(event.content) + .contains( + entry("action", "pr:declined"), + entry("repoProject", "RT"), + entry("slug", "repo-test"), + entry("hash", "a87ec312573d13fa35b9d877bbf03be0385ae314"), + entry("branch", "my-feature-branch")); + } + + private File getPayloadFile(String name) { + return new File(Objects.requireNonNull(getClass().getResource(name)).getFile()); + } +} diff --git a/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_pr_declined_payload.json b/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_pr_declined_payload.json new file mode 100644 index 000000000..f1c9c9dff --- /dev/null +++ b/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_pr_declined_payload.json @@ -0,0 +1,158 @@ +{ + "eventKey": "pr:declined", + "date": "2022-11-18T01:10:08+0000", + "actor": { + "name": "generic-username", + "emailAddress": "generic.username@email.com", + "id": 52, + "displayName": "Generic Username", + "active": true, + "slug": "generic-username", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/users/generic-username" + } + ] + } + }, + "pullRequest": { + "id": 3, + "version": 1, + "title": "my-new-pr", + "state": "DECLINED", + "open": false, + "closed": true, + "createdDate": 1668733796440, + "updatedDate": 1668733807933, + "closedDate": 1668733807933, + "fromRef": { + "id": "refs/heads/my-feature-branch", + "displayId": "my-feature-branch", + "latestCommit": "a87ec312573d13fa35b9d877bbf03be0385ae314", + "type": "BRANCH", + "repository": { + "slug": "repo-test", + "id": 1, + "name": "repo-test", + "hierarchyId": "3415a20adbadb58372c2", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "RT", + "id": 1, + "name": "repo-test", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/RT" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "http://localhost:7990/scm/rt/repo-test.git", + "name": "http" + }, + { + "href": "ssh://git@localhost:7999/rt/repo-test.git", + "name": "ssh" + } + ], + "self": [ + { + "href": "http://localhost:7990/projects/RT/repos/repo-test/browse" + } + ] + } + } + }, + "toRef": { + "id": "refs/heads/main", + "displayId": "main", + "latestCommit": "3e325b06e6f13b948dbb33ae8177cfa78a043ac5", + "type": "BRANCH", + "repository": { + "slug": "repo-test", + "id": 1, + "name": "repo-test", + "hierarchyId": "3415a20adbadb58372c2", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "RT", + "id": 1, + "name": "repo-test", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/RT" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "http://localhost:7990/scm/rt/repo-test.git", + "name": "http" + }, + { + "href": "ssh://git@localhost:7999/rt/repo-test.git", + "name": "ssh" + } + ], + "self": [ + { + "href": "http://localhost:7990/projects/RT/repos/repo-test/browse" + } + ] + } + } + }, + "locked": false, + "author": { + "user": { + "name": "generic-username", + "emailAddress": "generic.username@email.com", + "id": 52, + "displayName": "Generic Username", + "active": true, + "slug": "generic-username", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/users/generic-username" + } + ] + } + }, + "role": "AUTHOR", + "approved": false, + "status": "UNAPPROVED" + }, + "reviewers": [], + "participants": [], + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/RT/repos/repo-test/pull-requests/3" + } + ] + } + } +} diff --git a/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_pr_deleted_payload.json b/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_pr_deleted_payload.json new file mode 100644 index 000000000..db8002629 --- /dev/null +++ b/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_pr_deleted_payload.json @@ -0,0 +1,157 @@ +{ + "eventKey": "pr:deleted", + "date": "2022-11-18T00:56:59+0000", + "actor": { + "name": "generic-username", + "emailAddress": "generic.username@email.com", + "id": 52, + "displayName": "Generic Username", + "active": true, + "slug": "generic-username", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/users/generic-username" + } + ] + } + }, + "pullRequest": { + "id": 2, + "version": 0, + "title": "Update readme", + "state": "OPEN", + "open": true, + "closed": false, + "createdDate": 1668732996442, + "updatedDate": 1668732996442, + "fromRef": { + "id": "refs/heads/my-feature-branch", + "displayId": "my-feature-branch", + "latestCommit": "a87ec312573d13fa35b9d877bbf03be0385ae314", + "type": "BRANCH", + "repository": { + "slug": "repo-test", + "id": 1, + "name": "repo-test", + "hierarchyId": "3415a20adbadb58372c2", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "RT", + "id": 1, + "name": "repo-test", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/RT" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "http://localhost:7990/scm/rt/repo-test.git", + "name": "http" + }, + { + "href": "ssh://git@localhost:7999/rt/repo-test.git", + "name": "ssh" + } + ], + "self": [ + { + "href": "http://localhost:7990/projects/RT/repos/repo-test/browse" + } + ] + } + } + }, + "toRef": { + "id": "refs/heads/main", + "displayId": "main", + "latestCommit": "3e325b06e6f13b948dbb33ae8177cfa78a043ac5", + "type": "BRANCH", + "repository": { + "slug": "repo-test", + "id": 1, + "name": "repo-test", + "hierarchyId": "3415a20adbadb58372c2", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "RT", + "id": 1, + "name": "repo-test", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/RT" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "http://localhost:7990/scm/rt/repo-test.git", + "name": "http" + }, + { + "href": "ssh://git@localhost:7999/rt/repo-test.git", + "name": "ssh" + } + ], + "self": [ + { + "href": "http://localhost:7990/projects/RT/repos/repo-test/browse" + } + ] + } + } + }, + "locked": false, + "author": { + "user": { + "name": "generic-username", + "emailAddress": "generic.username@email.com", + "id": 52, + "displayName": "Generic Username", + "active": true, + "slug": "generic-username", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/users/generic-username" + } + ] + } + }, + "role": "AUTHOR", + "approved": false, + "status": "UNAPPROVED" + }, + "reviewers": [], + "participants": [], + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/RT/repos/repo-test/pull-requests/2" + } + ] + } + } +} diff --git a/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_pr_from_ref_updated_payload.json b/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_pr_from_ref_updated_payload.json new file mode 100644 index 000000000..5c6f44e19 --- /dev/null +++ b/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_pr_from_ref_updated_payload.json @@ -0,0 +1,158 @@ +{ + "eventKey": "pr:from_ref_updated", + "date": "2022-11-17T18:49:18+0000", + "actor": { + "name": "generic-username", + "emailAddress": "generic.username@email.com", + "id": 52, + "displayName": "Generic Username", + "active": true, + "slug": "generic-username", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/users/generic-username" + } + ] + } + }, + "pullRequest": { + "id": 1, + "version": 0, + "title": "Feature branch", + "state": "OPEN", + "open": true, + "closed": false, + "createdDate": 1668638324711, + "updatedDate": 1668710955942, + "fromRef": { + "id": "refs/heads/feature-branch", + "displayId": "feature-branch", + "latestCommit": "9065c394975cf8750a83abb6e54ba27245a38926", + "type": "BRANCH", + "repository": { + "slug": "repo-test", + "id": 1, + "name": "repo-test", + "hierarchyId": "3415a20adbadb58372c2", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "RT", + "id": 1, + "name": "repo-test", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/RT" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "http://localhost:7990/scm/ec/repo-test.git", + "name": "http" + }, + { + "href": "ssh://git@localhost:7999/ec/repo-test.git", + "name": "ssh" + } + ], + "self": [ + { + "href": "http://localhost:7990/projects/EC/repos/repo-test/browse" + } + ] + } + } + }, + "toRef": { + "id": "refs/heads/main", + "displayId": "main", + "latestCommit": "1632daf625e522ad4dd4c6b78e0ad1589115afbb", + "type": "BRANCH", + "repository": { + "slug": "repo-test", + "id": 1, + "name": "repo-test", + "hierarchyId": "3415a20adbadb58372c2", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "RT", + "id": 1, + "name": "repo-test", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/EC" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "http://localhost:7990/scm/ec/repo-test.git", + "name": "http" + }, + { + "href": "ssh://git@localhost:7999/ec/repo-test.git", + "name": "ssh" + } + ], + "self": [ + { + "href": "http://localhost:7990/projects/EC/repos/repo-test/browse" + } + ] + } + } + }, + "locked": false, + "author": { + "user": { + "name": "generic-username", + "emailAddress": "generic.username@email.com", + "id": 52, + "displayName": "Generic Username", + "active": true, + "slug": "generic-username", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/users/generic-username" + } + ] + } + }, + "role": "AUTHOR", + "approved": false, + "status": "UNAPPROVED" + }, + "reviewers": [], + "participants": [], + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/EC/repos/repo-test/pull-requests/1" + } + ] + } + }, + "previousFromHash": "1eb0b0f6e1725c4040f2605e1d58c9f9d2095658" +} diff --git a/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_pr_merged_payload.json b/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_pr_merged_payload.json new file mode 100644 index 000000000..f360dfb5d --- /dev/null +++ b/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_pr_merged_payload.json @@ -0,0 +1,164 @@ +{ + "eventKey": "pr:merged", + "date": "2022-11-18T00:45:00+0000", + "actor": { + "name": "generic-username", + "emailAddress": "generic.username@email.com", + "id": 52, + "displayName": "Generic Username", + "active": true, + "slug": "generic-username", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/users/generic-username" + } + ] + } + }, + "pullRequest": { + "id": 1, + "version": 3, + "title": "Feature branch", + "state": "MERGED", + "open": false, + "closed": true, + "createdDate": 1668638324711, + "updatedDate": 1668732298421, + "closedDate": 1668732298421, + "fromRef": { + "id": "refs/heads/feature-branch", + "displayId": "feature-branch", + "latestCommit": "9065c394975cf8750a83abb6e54ba27245a38926", + "type": "BRANCH", + "repository": { + "slug": "repo-test", + "id": 1, + "name": "repo-test", + "hierarchyId": "3415a20adbadb58372c2", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "RT", + "id": 1, + "name": "repo-test", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/RT" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "http://localhost:7990/scm/rt/repo-test.git", + "name": "http" + }, + { + "href": "ssh://git@localhost:7999/rt/repo-test.git", + "name": "ssh" + } + ], + "self": [ + { + "href": "http://localhost:7990/projects/RT/repos/repo-test/browse" + } + ] + } + } + }, + "toRef": { + "id": "refs/heads/main", + "displayId": "main", + "latestCommit": "1632daf625e522ad4dd4c6b78e0ad1589115afbb", + "type": "BRANCH", + "repository": { + "slug": "repo-test", + "id": 1, + "name": "repo-test", + "hierarchyId": "3415a20adbadb58372c2", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "RT", + "id": 1, + "name": "repo-test", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/RT" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "http://localhost:7990/scm/rt/repo-test.git", + "name": "http" + }, + { + "href": "ssh://git@localhost:7999/rt/repo-test.git", + "name": "ssh" + } + ], + "self": [ + { + "href": "http://localhost:7990/projects/RT/repos/repo-test/browse" + } + ] + } + } + }, + "locked": false, + "author": { + "user": { + "name": "generic-username", + "emailAddress": "generic.username@email.com", + "id": 52, + "displayName": "Generic Username", + "active": true, + "slug": "generic-username", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/users/generic-username" + } + ] + } + }, + "role": "AUTHOR", + "approved": false, + "status": "UNAPPROVED" + }, + "reviewers": [], + "participants": [], + "properties": { + "mergeCommit": { + "displayId": "3e325b06e6f", + "id": "3e325b06e6f13b948dbb33ae8177cfa78a043ac5" + } + }, + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/RT/repos/repo-test/pull-requests/1" + } + ] + } + } +} diff --git a/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_pr_opened_payload.json b/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_pr_opened_payload.json new file mode 100644 index 000000000..d258de37d --- /dev/null +++ b/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_pr_opened_payload.json @@ -0,0 +1,157 @@ +{ + "eventKey": "pr:opened", + "date": "2022-11-16T22:38:45+0000", + "actor": { + "name": "generic-username", + "emailAddress": "generic.username@email.com", + "id": 52, + "displayName": "Generic Username", + "active": true, + "slug": "generic-username", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/users/generic-username" + } + ] + } + }, + "pullRequest": { + "id": 1, + "version": 0, + "title": "Feature branch", + "state": "OPEN", + "open": true, + "closed": false, + "createdDate": 1668638324711, + "updatedDate": 1668638324711, + "fromRef": { + "id": "refs/heads/feature-branch", + "displayId": "feature-branch", + "latestCommit": "1eb0b0f6e1725c4040f2605e1d58c9f9d2095658", + "type": "BRANCH", + "repository": { + "slug": "repo-test", + "id": 1, + "name": "repo-test", + "hierarchyId": "3415a20adbadb58372c2", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "RT", + "id": 1, + "name": "repo-test", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/RT" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "http://localhost:7990/scm/RT/repo-test.git", + "name": "http" + }, + { + "href": "ssh://git@localhost:7999/RT/repo-test.git", + "name": "ssh" + } + ], + "self": [ + { + "href": "http://localhost:7990/projects/RT/repos/echo-test/browse" + } + ] + } + } + }, + "toRef": { + "id": "refs/heads/main", + "displayId": "main", + "latestCommit": "1632daf625e522ad4dd4c6b78e0ad1589115afbb", + "type": "BRANCH", + "repository": { + "slug": "repo-test", + "id": 1, + "name": "repo-test", + "hierarchyId": "3415a20adbadb58372c2", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "RT", + "id": 1, + "name": "echo-test", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/RT" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "http://localhost:7990/scm/RT/repo-test.git", + "name": "http" + }, + { + "href": "ssh://git@localhost:7999/RT/repo-test.git", + "name": "ssh" + } + ], + "self": [ + { + "href": "http://localhost:7990/projects/RT/repos/repo-test/browse" + } + ] + } + } + }, + "locked": false, + "author": { + "user": { + "name": "generic-username", + "emailAddress": "generic.username@email.com", + "id": 52, + "displayName": "Generic Username", + "active": true, + "slug": "generic-username", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/users/genric-username" + } + ] + } + }, + "role": "AUTHOR", + "approved": false, + "status": "UNAPPROVED" + }, + "reviewers": [], + "participants": [], + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/RT/repos/repo-test/pull-requests/1" + } + ] + } + } +} diff --git a/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_repo_refs_changed_payload.json b/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_repo_refs_changed_payload.json new file mode 100644 index 000000000..3cace8049 --- /dev/null +++ b/echo-webhooks/src/test/resources/bitbucket-server/bitbucket_server_repo_refs_changed_payload.json @@ -0,0 +1,75 @@ +{ + "eventKey": "repo:refs_changed", + "date": "2022-11-17T18:49:15+0000", + "actor": { + "name": "generic-username", + "emailAddress": "generic.username@email.com", + "id": 52, + "displayName": "Generic Username", + "active": true, + "slug": "generic-username", + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/users/generic-username" + } + ] + } + }, + "repository": { + "slug": "repo-test", + "id": 1, + "name": "repo-test", + "hierarchyId": "3415a20adbadb58372c2", + "scmId": "git", + "state": "AVAILABLE", + "statusMessage": "Available", + "forkable": true, + "project": { + "key": "RT", + "id": 1, + "name": "repo-test", + "public": false, + "type": "NORMAL", + "links": { + "self": [ + { + "href": "http://localhost:7990/projects/RT" + } + ] + } + }, + "public": false, + "links": { + "clone": [ + { + "href": "http://localhost:7990/scm/rt/repo-test.git", + "name": "http" + }, + { + "href": "ssh://git@localhost:7999/rt/repo-test.git", + "name": "ssh" + } + ], + "self": [ + { + "href": "http://localhost:7990/projects/RT/repos/repo-test/browse" + } + ] + } + }, + "changes": [ + { + "ref": { + "id": "refs/heads/feature-branch", + "displayId": "feature-branch", + "type": "BRANCH" + }, + "refId": "refs/heads/feature-branch", + "fromHash": "1eb0b0f6e1725c4040f2605e1d58c9f9d2095658", + "toHash": "9065c394975cf8750a83abb6e54ba27245a38926", + "type": "UPDATE" + } + ] +}