diff --git a/build.gradle b/build.gradle index 664ce0d..42bb41b 100644 --- a/build.gradle +++ b/build.gradle @@ -33,7 +33,7 @@ ext { httpClientVersion = '4.5.7' okhttpVersion = '3.12.1' springWebVersion = '3.2.2.RELEASE' - jacksonVersion = '2.9.8' + jacksonVersion = '2.12.3' slf4jVersion = '1.7.25' } diff --git a/src/main/java/com/julienvey/trello/Trello.java b/src/main/java/com/julienvey/trello/Trello.java index 7d420fb..3f4c486 100644 --- a/src/main/java/com/julienvey/trello/Trello.java +++ b/src/main/java/com/julienvey/trello/Trello.java @@ -19,6 +19,7 @@ import com.julienvey.trello.domain.MyPrefs; import com.julienvey.trello.domain.Organization; import com.julienvey.trello.domain.TList; +import com.julienvey.trello.domain.Webhook; public interface Trello { @@ -263,4 +264,16 @@ List getBoardMemberActivity(String boardId, String memberId, List getMemberCards(String userId, Argument... args); List getMemberActions(String userId, Argument... args); + + Webhook createWebhook(Webhook webhook); + + Webhook getWebhook(String webhookId); + + Webhook updateWebhook(Webhook webhook); + + Webhook deleteWebhook(String webhookId); + + Member me(); + + List getWebhooks(); } diff --git a/src/main/java/com/julienvey/trello/domain/Webhook.java b/src/main/java/com/julienvey/trello/domain/Webhook.java new file mode 100644 index 0000000..16a7e09 --- /dev/null +++ b/src/main/java/com/julienvey/trello/domain/Webhook.java @@ -0,0 +1,125 @@ +package com.julienvey.trello.domain; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.julienvey.trello.Trello; + +import java.util.Objects; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class Webhook extends TrelloEntity { + private String id; + private String description; + private String idModel; + private String callbackURL; + private boolean active; + private int consecutiveFailures; + private String firstConsecutiveFailDate; + + public String getId() { + return id; + } + + public Webhook setId(String id) { + this.id = id; + return this; + } + + public String getDescription() { + return description; + } + + public Webhook setDescription(String description) { + this.description = description; + return this; + } + + public String getIdModel() { + return idModel; + } + + public Webhook setIdModel(String idModel) { + this.idModel = idModel; + return this; + } + + public String getCallbackURL() { + return callbackURL; + } + + public Webhook setCallbackURL(String callbackUrl) { + this.callbackURL = callbackUrl; + return this; + } + + public boolean isActive() { + return active; + } + + public Webhook setActive(boolean active) { + this.active = active; + return this; + } + + public int getConsecutiveFailures() { + return consecutiveFailures; + } + + public Webhook setConsecutiveFailures(int consecutiveFailures) { + this.consecutiveFailures = consecutiveFailures; + return this; + } + + public String getFirstConsecutiveFailDate() { + return firstConsecutiveFailDate; + } + + public Webhook setFirstConsecutiveFailDate(String firstConsecutiveFailDate) { + this.firstConsecutiveFailDate = firstConsecutiveFailDate; + return this; + } + + public Webhook create() { + Webhook webhook = getTrelloService().createWebhook(this); + id = webhook.id; + + return this; + } + + @Override + @SuppressWarnings("unchecked") + public Webhook setInternalTrello(Trello trelloService) { + return super.setInternalTrello(trelloService); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Webhook webhook = (Webhook) o; + return active == webhook.active && + consecutiveFailures == webhook.consecutiveFailures && + id.equals(webhook.id) && + Objects.equals(description, webhook.description) && + idModel.equals(webhook.idModel) && + callbackURL.equals(webhook.callbackURL) && + Objects.equals(firstConsecutiveFailDate, webhook.firstConsecutiveFailDate); + } + + @Override + public int hashCode() { + return Objects.hash(id, description, idModel, callbackURL, active, consecutiveFailures, firstConsecutiveFailDate); + } + + @Override + public String toString() { + return "Webhook{" + + "id='" + id + '\'' + + ", description='" + description + '\'' + + ", idModel='" + idModel + '\'' + + ", callbackUrl='" + callbackURL + '\'' + + ", active=" + active + + ", consecutiveFailures=" + consecutiveFailures + + ", firstConsecutiveFailDate='" + firstConsecutiveFailDate + '\'' + + '}'; + } +} diff --git a/src/main/java/com/julienvey/trello/impl/TrelloImpl.java b/src/main/java/com/julienvey/trello/impl/TrelloImpl.java index 9d7acc6..f5e0257 100644 --- a/src/main/java/com/julienvey/trello/impl/TrelloImpl.java +++ b/src/main/java/com/julienvey/trello/impl/TrelloImpl.java @@ -11,9 +11,11 @@ import static com.julienvey.trello.impl.TrelloUrl.CREATE_CARD; import static com.julienvey.trello.impl.TrelloUrl.CREATE_CHECKLIST; import static com.julienvey.trello.impl.TrelloUrl.CREATE_LABEL; +import static com.julienvey.trello.impl.TrelloUrl.CREATE_WEBHOOK; import static com.julienvey.trello.impl.TrelloUrl.DELETE_ATTACHMENT; import static com.julienvey.trello.impl.TrelloUrl.DELETE_CARD; import static com.julienvey.trello.impl.TrelloUrl.DELETE_LABEL; +import static com.julienvey.trello.impl.TrelloUrl.DELETE_WEBHOOK; import static com.julienvey.trello.impl.TrelloUrl.GET_ACTION; import static com.julienvey.trello.impl.TrelloUrl.GET_ACTION_BOARD; import static com.julienvey.trello.impl.TrelloUrl.GET_ACTION_CARD; @@ -52,11 +54,15 @@ import static com.julienvey.trello.impl.TrelloUrl.GET_ORGANIZATION; import static com.julienvey.trello.impl.TrelloUrl.GET_ORGANIZATION_BOARD; import static com.julienvey.trello.impl.TrelloUrl.GET_ORGANIZATION_MEMBER; +import static com.julienvey.trello.impl.TrelloUrl.GET_WEBHOOK; import static com.julienvey.trello.impl.TrelloUrl.REMOVE_MEMBER_FROM_BOARD; import static com.julienvey.trello.impl.TrelloUrl.REMOVE_MEMBER_FROM_CARD; import static com.julienvey.trello.impl.TrelloUrl.UPDATE_CARD; import static com.julienvey.trello.impl.TrelloUrl.UPDATE_CARD_COMMENT; import static com.julienvey.trello.impl.TrelloUrl.UPDATE_LABEL; +import static com.julienvey.trello.impl.TrelloUrl.UPDATE_WEBHOOK; +import static com.julienvey.trello.impl.TrelloUrl.ME; +import static com.julienvey.trello.impl.TrelloUrl.TOKEN_WEBHOOKS; import static com.julienvey.trello.impl.TrelloUrl.createUrl; import static com.julienvey.trello.impl.TrelloUrl.createUrlWithNoArgs; @@ -97,6 +103,7 @@ import com.julienvey.trello.domain.Organization; import com.julienvey.trello.domain.TList; import com.julienvey.trello.domain.TrelloEntity; +import com.julienvey.trello.domain.Webhook; import com.julienvey.trello.impl.domaininternal.Comment; import com.julienvey.trello.impl.http.JDKTrelloHttpClient; @@ -563,6 +570,38 @@ public List removeMemberFromCard(String idCard, String userId) { return asList(() -> delete(createUrl(REMOVE_MEMBER_FROM_CARD).asString(), Member[].class, idCard, userId)); } + @Override + public Webhook createWebhook(Webhook webhook) { + Webhook createdWebhook = postForObject(createUrlWithNoArgs(CREATE_WEBHOOK), webhook, Webhook.class); + return createdWebhook.setInternalTrello(this); + } + + @Override + public Webhook getWebhook(String webhookId) { + return get(createUrl(GET_WEBHOOK).asString(), Webhook.class, webhookId); + } + + @Override + public Webhook updateWebhook(Webhook webhook) { + Webhook createdWebhook = put(createUrlWithNoArgs(UPDATE_WEBHOOK), webhook, Webhook.class, webhook.getId()); + return createdWebhook.setInternalTrello(this); + } + + @Override + public Webhook deleteWebhook(String webhookId) { + return delete(createUrl(DELETE_WEBHOOK).asString(), Webhook.class, webhookId); + } + + @Override + public Member me() { + return get(createUrlWithNoArgs(ME), Member.class); + } + + @Override + public List getWebhooks() { + return asList(() -> get(createUrl(TOKEN_WEBHOOKS).asString(), Webhook[].class, accessToken)); + } + /* internal methods */ private T postFileForObject(String url, File file, Class objectClass, String... params) { diff --git a/src/main/java/com/julienvey/trello/impl/TrelloUrl.java b/src/main/java/com/julienvey/trello/impl/TrelloUrl.java index 430969a..46e9bf2 100644 --- a/src/main/java/com/julienvey/trello/impl/TrelloUrl.java +++ b/src/main/java/com/julienvey/trello/impl/TrelloUrl.java @@ -55,6 +55,11 @@ public class TrelloUrl { public static final String UPDATE_LABEL = "/labels/{labelId}?"; public static final String DELETE_LABEL = "/labels/{labelId}?"; + public static final String CREATE_WEBHOOK = "/webhooks/?"; + public static final String UPDATE_WEBHOOK = "/webhooks/{webhookId}?"; + public static final String GET_WEBHOOK = "/webhooks/{webhookId}?"; + public static final String DELETE_WEBHOOK = "/webhooks/{webhookId}?"; + public static final String GET_CHECK_LIST = "/checklists/{checkListId}?"; public static final String CREATE_CHECKLIST = "/checklists?"; public static final String ADD_CHECKITEMS_TO_CHECKLIST = "/checklists/{checkListId}/checkitems?"; @@ -72,6 +77,9 @@ public class TrelloUrl { public static final String DELETE_ATTACHMENT = "/cards/{cardId}/attachments/{attachmentId}?"; public static final String UPDATE_CARD = "/cards/{cardId}?"; + public static final String ME = "/members/me?"; + public static final String TOKEN_WEBHOOKS = "/tokens/{userToken}/webhooks?"; + private String baseUrl; private Argument[] args = {}; @@ -104,4 +112,4 @@ public String asString() { } return builder.toString(); } -} \ No newline at end of file +} diff --git a/src/test/java/com/julienvey/trello/integration/WebhookCRUDCase.java b/src/test/java/com/julienvey/trello/integration/WebhookCRUDCase.java new file mode 100644 index 0000000..dec1d48 --- /dev/null +++ b/src/test/java/com/julienvey/trello/integration/WebhookCRUDCase.java @@ -0,0 +1,84 @@ +package com.julienvey.trello.integration; + +import com.julienvey.trello.NotFoundException; +import com.julienvey.trello.Trello; +import com.julienvey.trello.TrelloHttpClient; +import com.julienvey.trello.domain.*; +import com.julienvey.trello.impl.TrelloImpl; +import com.julienvey.trello.impl.http.ApacheHttpClient; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.List; + +import static org.fest.assertions.Assertions.assertThat; + +@RunWith(Parameterized.class) +public class WebhookCRUDCase { + + private static final String TEST_APPLICATION_KEY = "db555c528ce160c33305d2ea51ae1197"; + + private Trello trello; + + private TrelloHttpClient httpClient; + + @Parameterized.Parameters + public static List data() { + return TrelloHttpClients.all(); + } + + public WebhookCRUDCase(TrelloHttpClient httpClient) { + this.httpClient = httpClient; + } + + @Before + public void setUp() { + trello = new TrelloImpl(TEST_APPLICATION_KEY, "", httpClient); + } + + @Test + public void testCreateGetAndDeleteWebhook() { + final String callbackUrl = ""; + final String boardId = "518baad5b05dbf4703004852"; + final Webhook newWebhook = new Webhook() + .setCallbackURL(callbackUrl) + .setIdModel(boardId); + + final Webhook createdWebhook = trello.createWebhook(newWebhook); + System.out.println(createdWebhook.getId()); + + assertThat(createdWebhook).isNotNull(); + assertThat(createdWebhook.getId()).isNotNull(); + assertThat(createdWebhook.getId()).isNotEmpty(); + assertThat(createdWebhook.getIdModel()).isEqualTo(boardId); + assertThat(createdWebhook.getCallbackURL()).isEqualTo(callbackUrl); + assertThat(createdWebhook.getDescription()).isEmpty(); + + String updatedDescription = "Updated description"; + createdWebhook.setDescription(updatedDescription); + Webhook updatedWebhook = trello.updateWebhook(createdWebhook); + + final Webhook foundWebhook = trello.getWebhook(createdWebhook.getId()); + assertThat(foundWebhook).isNotNull(); + assertThat(foundWebhook.getId()).isNotNull(); + assertThat(foundWebhook.getId()).isEqualTo(updatedWebhook.getId()); + assertThat(foundWebhook.getIdModel()).isEqualTo(updatedWebhook.getIdModel()); + assertThat(foundWebhook.getCallbackURL()).isEqualTo(updatedWebhook.getCallbackURL()); + assertThat(foundWebhook.getDescription()).isEqualTo(updatedDescription); + + trello.deleteWebhook(foundWebhook.getId()); + + boolean isCatchVisited = false; + try { + trello.getWebhook(foundWebhook.getId()); + } catch (NotFoundException notFoundException) { + isCatchVisited = true; + assertThat(notFoundException.getMessage().contains("Resource not found: ")); + } + + assertThat(isCatchVisited).isTrue(); + } + +} diff --git a/src/test/java/com/julienvey/trello/unit/WebhookUnitTest.java b/src/test/java/com/julienvey/trello/unit/WebhookUnitTest.java new file mode 100644 index 0000000..5d0f696 --- /dev/null +++ b/src/test/java/com/julienvey/trello/unit/WebhookUnitTest.java @@ -0,0 +1,140 @@ +package com.julienvey.trello.unit; + +import com.julienvey.trello.Trello; +import com.julienvey.trello.TrelloHttpClient; +import com.julienvey.trello.domain.Board; +import com.julienvey.trello.domain.Webhook; +import com.julienvey.trello.impl.TrelloImpl; +import com.julienvey.trello.impl.http.ApacheHttpClient; +import org.junit.Before; +import org.junit.Test; +import java.util.List; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Matchers.*; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +public class WebhookUnitTest { + + private final String CALLBACK_URL = "http://localhost:8080/callbackUrl"; + private final String BOARD_ID = "boardId"; + + private Trello trello; + + private TrelloHttpClient httpClient; + + @Before + public void setUp() { + httpClient = mock(TrelloHttpClient.class); + trello = new TrelloImpl("", "", httpClient); + } + + @Test + public void testCreateWebhook() { + //Given + Webhook webhook = new Webhook(); + webhook.setCallbackURL(CALLBACK_URL); + webhook.setIdModel(BOARD_ID); + + when(httpClient.postForObject(anyString(), any(Class.class), anyObject(),(String[]) anyVararg())).thenReturn(webhook); + + //When + Webhook foundWebhook = trello.createWebhook(webhook); + + //Then + assertThat(foundWebhook).isNotNull(); + assertThat(foundWebhook.getCallbackURL()).isEqualTo(CALLBACK_URL); + assertThat(foundWebhook.getIdModel()).isEqualTo(BOARD_ID); + + verify(httpClient).postForObject(eq("https://api.trello.com/1/webhooks/?key={applicationKey}&token={userToken}"), + eq(webhook), eq(Webhook.class), (String[]) anyVararg()); + verifyNoMoreInteractions(httpClient); + } + + @Test + public void testGetWebhookById() { + //Given + Webhook webhook = new Webhook(); + webhook.setCallbackURL(CALLBACK_URL); + webhook.setIdModel(BOARD_ID); + + when(httpClient.get(anyString(), any(Class.class), (String[]) anyVararg())).thenReturn(webhook); + + //When + Webhook foundWebhook = trello.getWebhook("webhookId"); + + //Then + assertThat(foundWebhook).isNotNull(); + assertThat(foundWebhook.getCallbackURL()).isEqualTo(CALLBACK_URL); + assertThat(foundWebhook.getIdModel()).isEqualTo(BOARD_ID); + + verify(httpClient).get(eq("https://api.trello.com/1/webhooks/{webhookId}?key={applicationKey}&token={userToken}"), + eq(Webhook.class), eq("webhookId"), eq(""), eq("")); + verifyNoMoreInteractions(httpClient); + } + + @Test + public void testUpdateWebhook() { + //Given + Webhook webhook = new Webhook(); + webhook.setCallbackURL(CALLBACK_URL); + webhook.setIdModel(BOARD_ID); + webhook.setDescription("updated description"); + + when(httpClient.putForObject(anyString(), any(), any(Class.class), (String[]) anyVararg())).thenReturn(webhook); + + //When + Webhook foundWebhook = trello.updateWebhook(webhook); + + //Then + assertThat(foundWebhook).isNotNull(); + assertThat(foundWebhook.getCallbackURL()).isEqualTo(CALLBACK_URL); + assertThat(foundWebhook.getIdModel()).isEqualTo(BOARD_ID); + + verify(httpClient).putForObject(eq("https://api.trello.com/1/webhooks/{webhookId}?key={applicationKey}&token={userToken}"), + eq(webhook), eq(Webhook.class), (String[]) anyVararg()); + verifyNoMoreInteractions(httpClient); + } + + @Test + public void testDeleteWebhookById() { + //Given + Webhook webhookToDelete = new Webhook(); + webhookToDelete.setCallbackURL(CALLBACK_URL); + webhookToDelete.setIdModel(BOARD_ID); + + when(httpClient.delete(anyString(), any(Class.class), (String[]) anyVararg())).thenReturn(webhookToDelete); + + //When + Webhook deletedWebhook = trello.deleteWebhook("webhookId"); + + //Then + assertThat(deletedWebhook).isNotNull(); + assertThat(deletedWebhook.getCallbackURL()).isEqualTo(CALLBACK_URL); + assertThat(deletedWebhook.getIdModel()).isEqualTo(BOARD_ID); + + verify(httpClient).delete(eq("https://api.trello.com/1/webhooks/{webhookId}?key={applicationKey}&token={userToken}"), + eq(Webhook.class), eq("webhookId"), eq(""), eq("")); + verifyNoMoreInteractions(httpClient); + } + + @Test + public void testListAllWebhooksAssociatedWithAccessToken() { + //Given + when(httpClient.get(anyString(), any(Class.class), (String[]) anyVararg())) + .thenReturn(new Webhook[] { new Webhook().setId("1"), new Webhook().setId("2") }); + + //When + List foundWebhooks = trello.getWebhooks(); + + //Then + assertThat(foundWebhooks).isNotNull(); + assertThat(foundWebhooks.size()).isEqualTo(2); + + verify(httpClient).get(eq("https://api.trello.com/1/tokens/{userToken}/webhooks?key={applicationKey}&token={userToken}"), + eq(Webhook[].class), eq(""), eq(""), eq("")); + verifyNoMoreInteractions(httpClient); + } +}