diff --git a/self-core-impl/src/main/java/com/selfxdsd/core/BitbucketRepo.java b/self-core-impl/src/main/java/com/selfxdsd/core/BitbucketRepo.java index 42a70a2f..3a9472cf 100644 --- a/self-core-impl/src/main/java/com/selfxdsd/core/BitbucketRepo.java +++ b/self-core-impl/src/main/java/com/selfxdsd/core/BitbucketRepo.java @@ -34,8 +34,6 @@ * @since 0.0.67 * @todo #976:60min Start implement and test BitbucketIssues for * BitbucketRepo#issues(). - * @todo #976:60min Start implement and test BitbucketWebhooks for - * BitbucketRepo#webhooks(). * @todo #976:60min Start implement and test BitbucketStars for * BitbucketRepo#stars(). */ @@ -126,7 +124,11 @@ public Collaborators collaborators() { @Override public Webhooks webhooks() { - throw new UnsupportedOperationException("Not implemented yet"); + return new BitbucketWebhooks( + this.resources(), + URI.create(this.repoUri().toString() + "/hooks"), + this.storage() + ); } @Override diff --git a/self-core-impl/src/main/java/com/selfxdsd/core/BitbucketWebhooks.java b/self-core-impl/src/main/java/com/selfxdsd/core/BitbucketWebhooks.java new file mode 100644 index 00000000..e744b6d5 --- /dev/null +++ b/self-core-impl/src/main/java/com/selfxdsd/core/BitbucketWebhooks.java @@ -0,0 +1,200 @@ +/** + * Copyright (c) 2020-2021, Self XDSD Contributors + * All rights reserved. + *

+ * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), + * to read the Software only. Permission is hereby NOT GRANTED to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software. + *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, + * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT + * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.selfxdsd.core; + +import com.selfxdsd.api.Project; +import com.selfxdsd.api.Webhook; +import com.selfxdsd.api.Webhooks; +import com.selfxdsd.api.storage.Storage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; +import javax.json.JsonValue; +import java.net.HttpURLConnection; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +/** + * Bitbucket repo webhooks. + * + * @author Ali FELLAHI (fellahi.ali@gmail.com) + * @version $Id$ + * @since 0.0.68 + * + * @todo #1015:60min Continue writing tests for BitbucketWebhooks class. + */ +final class BitbucketWebhooks implements Webhooks { + + /** + * Logger. + */ + private static final Logger LOG = LoggerFactory.getLogger( + BitbucketWebhooks.class + ); + + /** + * Bitbucket repo Webhooks base uri. + */ + private final URI hooksUri; + + /** + * Gitlab's JSON Resources. + */ + private final JsonResources resources; + + /** + * Self storage, in case we want to store something. + */ + private final Storage storage; + + /** + * Ctor. + * + * @param resources Gitlab's JSON Resources. + * @param hooksUri Hooks base URI. + * @param storage Storage. + */ + BitbucketWebhooks(final JsonResources resources, + final URI hooksUri, + final Storage storage) { + this.resources = resources; + this.hooksUri = hooksUri; + this.storage = storage; + } + + @Override + public boolean add(final Project project) { + LOG.debug("Adding Bitbucket webhook for Project " + + project.repoFullName()); + final boolean added; + final Resource response = this.resources.post( + this.hooksUri, + Json.createObjectBuilder() + .add("description", "Self-XDSD PM") + .add("url", System.getenv(Env.WEBHOOK_BASE_URL) + + "/bitbucket/" + project.repoFullName()) + .add("active", true) + .add("events", + Json.createArrayBuilder() + .add("repo:push") + .add("issue:created") + .add("issue:comment_created") + .add("pullrequest:created") + .add("pullrequest:comment_created") + ) + .build() + ); + if (response.statusCode() == HttpURLConnection.HTTP_CREATED) { + added = true; + LOG.debug("Webhook added successfully!"); + } else { + added = false; + LOG.debug("Problem when adding webhook. Expected 201 CREATED, " + + " but got " + response.statusCode()); + } + return added; + } + + @Override + public boolean remove() { + boolean removed = true; + for(final Webhook hook : this) { + if(hook.url().contains("//self-xdsd.")) { + LOG.debug( + "Removing Self XDSD Webhook from [" + + this.hooksUri + "]..." + ); + final Resource response = this.resources + .delete( + URI.create( + this.hooksUri.toString() + "/" + hook.id() + ), + Json.createObjectBuilder().build() + ); + final int status = response.statusCode(); + if(status == HttpURLConnection.HTTP_NO_CONTENT) { + LOG.debug("Hook removed successfully!"); + } else { + LOG.debug( + "Problem while removing webhook. " + + "Expected 204 NO CONTENT, but got " + status + "." + ); + removed = false; + } + } + } + return removed; + } + + @Override + public Iterator iterator() { + final Iterator iterator; + LOG.debug( + "Fetching Bitbucket webhooks [" + this.hooksUri + "]..." + ); + final Resource response = this.resources.get( + URI.create(this.hooksUri.toString() + "?per_page=100") + ); + if(response.statusCode() == HttpURLConnection.HTTP_OK) { + LOG.debug("Webhooks fetched successfully!"); + final List list = new ArrayList<>(); + final JsonArray hooks = response.asJsonObject() + .getJsonArray("values"); + for(final JsonValue hook : hooks) { + list.add( + new Webhook() { + /** + * Hook in JSON. + */ + private final JsonObject json = hook.asJsonObject(); + + @Override + public String id() { + return this.json.getString("uuid"); + } + + @Override + public String url() { + return this.json.getString("url"); + } + } + ); + } + iterator = list.iterator(); + } else { + LOG.error( + "Problem when fetching webhooks. Expected 200 OK, " + + " but got " + response.statusCode() + + ". Returning empty iterable." + ); + iterator = Collections.emptyIterator(); + } + return iterator; + } +} diff --git a/self-core-impl/src/test/java/com/selfxdsd/core/BitbucketRepoTestCase.java b/self-core-impl/src/test/java/com/selfxdsd/core/BitbucketRepoTestCase.java index b966d1e9..cb053ea1 100644 --- a/self-core-impl/src/test/java/com/selfxdsd/core/BitbucketRepoTestCase.java +++ b/self-core-impl/src/test/java/com/selfxdsd/core/BitbucketRepoTestCase.java @@ -185,14 +185,21 @@ public void returnsCollaborators() { /** * BitbucketRepo.webhooks() returns its webhooks. */ - @Test(expected = UnsupportedOperationException.class) + @Test public void returnsWebhooks() { - new BitbucketRepo( + final BitbucketRepo repo = new BitbucketRepo( Mockito.mock(JsonResources.class), URI.create("https://bitbucket.org/api/2.0/repositories/john/test"), Mockito.mock(User.class), Mockito.mock(Storage.class) - ).webhooks(); + ); + MatcherAssert.assertThat( + repo.webhooks(), + Matchers.allOf( + Matchers.notNullValue(), + Matchers.instanceOf(BitbucketWebhooks.class) + ) + ); } /**