From 121a72acf8a545bfe80022e26657145a06daaf11 Mon Sep 17 00:00:00 2001
From: Thomas 'Panda' Attwood | TickleThePanda
Date: Fri, 11 Aug 2017 16:33:30 +0100
Subject: [PATCH 01/11] Add mailing list client API and Mailchimp
implementation
---
.gitignore | 7 +
README.md | 7 +
mailchimp-webhooks/README.md | 16 +
mailchimp-webhooks/pom.xml | 44 ++
.../mailing/spring/Application.java | 14 +
.../spring/MailchimpWebhookController.java | 27 ++
.../src/main/resources/application.properties | 5 +
mailing-list-client/README.md | 31 ++
.../application.test.properties | 17 +
mailing-list-client/pom.xml | 87 ++++
.../mailing/api/MailingListClient.java | 221 ++++++++++
.../api/data/MailingListDescriptor.java | 17 +
.../api/data/MailingListDescriptors.java | 14 +
.../mailing/api/data/MailingListMember.java | 32 ++
.../mailing/api/data/MailingListMembers.java | 14 +
.../data/MailingListSegmentDescriptor.java | 18 +
.../data/MailingListSegmentDescriptors.java | 15 +
.../data/MailingListSegmentMemberChanges.java | 20 +
.../mailing/api/data/SubscriptionStatus.java | 22 +
.../MailingListClientException.java | 16 +
.../NotFoundMailingListClientException.java | 15 +
.../mailing/mailchimp/MailchimpClient.java | 164 ++++++++
.../MailchimpClientConfiguration.java | 50 +++
.../MailchimpInternalHttpClient.java | 183 ++++++++
.../mailchimp/MailchimpListDescriptor.java | 39 ++
.../mailchimp/MailchimpListDescriptors.java | 33 ++
.../mailchimp/MailchimpListMember.java | 140 +++++++
.../mailchimp/MailchimpListMemberRequest.java | 82 ++++
.../mailchimp/MailchimpListMembers.java | 33 ++
.../MailchimpSegmentCreateRequest.java | 42 ++
.../mailchimp/MailchimpSegmentDescriptor.java | 37 ++
.../MailchimpSegmentDescriptors.java | 36 ++
.../MailchimpSegmentMemberChanges.java | 43 ++
.../MailchimpSegmentMemberRequest.java | 44 ++
.../gson/adapters/LocalDateTimeAdapter.java | 22 +
.../mailing/mailchimp/MailchimpClientIT.java | 396 ++++++++++++++++++
.../MailchimpInternalHttpClientTest.java | 285 +++++++++++++
.../MailchimpListMemberRequestTest.java | 52 +++
.../MailchimpSegmentCreateRequestTest.java | 20 +
.../MailchimpSegmentMemberRequestTest.java | 37 ++
40 files changed, 2397 insertions(+)
create mode 100644 .gitignore
create mode 100644 README.md
create mode 100644 mailchimp-webhooks/README.md
create mode 100644 mailchimp-webhooks/pom.xml
create mode 100644 mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/Application.java
create mode 100644 mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/MailchimpWebhookController.java
create mode 100644 mailchimp-webhooks/src/main/resources/application.properties
create mode 100644 mailing-list-client/README.md
create mode 100644 mailing-list-client/application.test.properties
create mode 100644 mailing-list-client/pom.xml
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/MailingListClient.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListDescriptor.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListDescriptors.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListMember.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListMembers.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListSegmentDescriptor.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListSegmentDescriptors.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListSegmentMemberChanges.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/SubscriptionStatus.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/exceptions/MailingListClientException.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/exceptions/NotFoundMailingListClientException.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClient.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientConfiguration.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClient.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListDescriptor.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListDescriptors.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListMember.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListMemberRequest.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListMembers.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentCreateRequest.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentDescriptor.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentDescriptors.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentMemberChanges.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentMemberRequest.java
create mode 100644 mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/gson/adapters/LocalDateTimeAdapter.java
create mode 100644 mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientIT.java
create mode 100644 mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClientTest.java
create mode 100644 mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListMemberRequestTest.java
create mode 100644 mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentCreateRequestTest.java
create mode 100644 mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentMemberRequestTest.java
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c047a70
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+target
+*.class
+
+# intellij
+*.iml
+.idea
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..9e9a42a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,7 @@
+[`mailing-list-client`](mailing-list-client/README.md) - This project
+provides an API for integrating with a mailing list service. It provides
+an implementation of the API for Mailchimp.
+
+[`mailchimp-webhooks`](mailchimp-webhooks/README.md) - This project
+provides an small example Spring Boot client that can listen to webhooks
+from Mailchimp that register user details changing in Mailchimp.
diff --git a/mailchimp-webhooks/README.md b/mailchimp-webhooks/README.md
new file mode 100644
index 0000000..828a218
--- /dev/null
+++ b/mailchimp-webhooks/README.md
@@ -0,0 +1,16 @@
+## Mailchimp Webhook Service
+
+A proof of concept Spring Boot service used create a simple webhook
+service for MailChimp. Currently accepts only "changed email" hooks.
+
+On recieving a webhook, it will write the `data[old_email]` and the
+`data[new_url]` to the logs.
+
+### Configuration
+
+Requires `${mailing.webhook.key}` to be set in the Spring
+`application.properties`, [providing some security to the
+application](https://developer.mailchimp.com/documentation/mailchimp/guides/about-webhooks/#securing-webhooks).
+
+The URL for the webhook will then be `${base.uri}/${mailing.webhook.key}`.
+
diff --git a/mailchimp-webhooks/pom.xml b/mailchimp-webhooks/pom.xml
new file mode 100644
index 0000000..ca49737
--- /dev/null
+++ b/mailchimp-webhooks/pom.xml
@@ -0,0 +1,44 @@
+
+
+ 4.0.0
+
+ uk.ac.stfc.facilities
+ mailing
+ 0.0.1-SNAPSHOT
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 1.5.4.RELEASE
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.apache.logging.log4j
+ log4j-api
+ 2.8.2
+
+
+ org.apache.logging.log4j
+ log4j-core
+ 2.8.2
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
\ No newline at end of file
diff --git a/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/Application.java b/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/Application.java
new file mode 100644
index 0000000..d6b1b92
--- /dev/null
+++ b/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/Application.java
@@ -0,0 +1,14 @@
+package uk.ac.stfc.facilities.mailing.spring;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+@EnableAutoConfiguration
+public class Application {
+
+ public static void main(String[] args) {
+ SpringApplication.run(Application.class, args);
+ }
+}
diff --git a/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/MailchimpWebhookController.java b/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/MailchimpWebhookController.java
new file mode 100644
index 0000000..dd01281
--- /dev/null
+++ b/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/MailchimpWebhookController.java
@@ -0,0 +1,27 @@
+package uk.ac.stfc.facilities.mailing.spring;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+@Controller
+@RequestMapping("/${mailing.webhook.key}")
+public class MailchimpWebhookController {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ @GetMapping()
+ @ResponseBody
+ public void handshake() {
+ }
+
+ @PostMapping()
+ @ResponseBody
+ public void hook(
+ @RequestParam("data[old_email]") String oldEmail,
+ @RequestParam("data[new_email]") String newEmail
+ ) {
+ LOGGER.info("old: " + oldEmail + " to " + newEmail);
+ }
+}
diff --git a/mailchimp-webhooks/src/main/resources/application.properties b/mailchimp-webhooks/src/main/resources/application.properties
new file mode 100644
index 0000000..24eab5e
--- /dev/null
+++ b/mailchimp-webhooks/src/main/resources/application.properties
@@ -0,0 +1,5 @@
+# The mailing.key is used to create a more secure webhook URL, see:
+# https://developer.mailchimp.com/documentation/mailchimp/guides/about-webhooks/#securing-webhooks
+# This will mean the Webhook URL will look like "${base.uri}/${mailing.key}"
+mailing.webhook.key=
+
diff --git a/mailing-list-client/README.md b/mailing-list-client/README.md
new file mode 100644
index 0000000..b994112
--- /dev/null
+++ b/mailing-list-client/README.md
@@ -0,0 +1,31 @@
+## `mailing-list-api`
+
+Contains an API for connecting to mailing lists -
+`uk.ac.stfc.facilities.mailing.api`.
+
+Contains an implementation of the mailing lists API for Mailchimp -
+`uk.ac.stfc.facilities.mailing.mailchimp`
+
+### Using the `MailchimpClient`
+
+1. Create an instance of the `MailchimpClientConfiguration`, with the API key
+given as an argument to the constructor.
+```java
+MailchimpClientConfiguration config = new MailchimpClientConfiguration(API_KEY);
+```
+2. Get a new instance of the `MailchimpClient` using the factory method. This
+takes the configuration in as an argument.
+```java
+MailingListClient client = MailchimpClient.getInstance(config);
+```
+
+The client is now ready to use.
+
+### Running the integration tests for the `MailchimpClient`.
+
+An [`applications.test.properties`](mailing-list-api/application.test.properties)
+file must exist for the mailchimp client.
+
+**NB:** please ensure these tests are ran against an account with no
+billing information attatched to it.
+
diff --git a/mailing-list-client/application.test.properties b/mailing-list-client/application.test.properties
new file mode 100644
index 0000000..7da8245
--- /dev/null
+++ b/mailing-list-client/application.test.properties
@@ -0,0 +1,17 @@
+# The API key for the Mailchimp. This can be found at:
+# https://admin.mailchimp.com/account/api/
+mailchimp.api.key=
+
+# The ID of an existing list in Mailchimp e.g. abcde1234
+mailchimp.list.id=
+# The name of the existing list in Mailchimp e.g. My Mailing List
+mailchimp.list.name=
+
+# The test domain that users belong to e.g. test.your-domain.com
+# This cannot be a domain like example.com because Mailchimp checks
+# for fake looking emails. Three emails should be added to the
+# existing mailing list with the following statuses:
+# - permanent.test.1@ - subscribed
+# - permanent.test.2@ - subscribed
+# - premenant.test.3@ - subscribed
+mailchimp.test.domain=
diff --git a/mailing-list-client/pom.xml b/mailing-list-client/pom.xml
new file mode 100644
index 0000000..5234f1f
--- /dev/null
+++ b/mailing-list-client/pom.xml
@@ -0,0 +1,87 @@
+
+
+ 4.0.0
+
+ uk.ac.stfc.facilities
+ mailing-list-client
+ 0.0.1-SNAPSHOT
+
+
+ 1.8
+ 1.8
+ 1.8
+
+
+
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.3
+
+
+ com.google.code.gson
+ gson
+ 2.6.2
+
+
+ org.apache.logging.log4j
+ log4j-core
+ 2.8.2
+
+
+ org.apache.logging.log4j
+ log4j-api
+ 2.8.2
+
+
+
+ org.junit.jupiter
+ junit-jupiter-api
+ 5.0.0-RC2
+ test
+
+
+ org.junit.jupiter
+ junit-jupiter-params
+ 5.0.0-RC2
+ test
+
+
+
+ org.assertj
+ assertj-core
+ 3.8.0
+ test
+
+
+ org.mockito
+ mockito-core
+ 2.8.47
+ test
+
+
+
+
+
+
+ maven-surefire-plugin
+ 2.19.1
+
+
+ org.junit.platform
+ junit-platform-surefire-provider
+ 1.0.0-RC2
+
+
+ org.junit.jupiter
+ junit-jupiter-engine
+ 5.0.0-RC2
+
+
+
+
+
+
+
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/MailingListClient.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/MailingListClient.java
new file mode 100644
index 0000000..6490110
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/MailingListClient.java
@@ -0,0 +1,221 @@
+package uk.ac.stfc.facilities.mailing.api;
+
+import uk.ac.stfc.facilities.mailing.api.data.*;
+import uk.ac.stfc.facilities.mailing.api.exceptions.MailingListClientException;
+
+import java.util.Set;
+
+/**
+ * An client for mailing lists service with the following functionality:
+ *
+ * - retrieving lists & list members
+ * - managing members' subscriptions to lists
+ * - managing list segments & list segment members
+ *
+ */
+public interface MailingListClient {
+
+ /**
+ * Retrieves all lists.
+ *
+ * @return the retrieved lists
+ * @throws MailingListClientException if the resource is unavailable
+ */
+ MailingListDescriptors getAllListDescriptors()
+ throws MailingListClientException;
+
+ /**
+ * Retrieves the list with the given list ID.
+ *
+ * @param listId the ID of the list to retrieve
+ * @return the list with the given list ID
+ * @throws MailingListClientException if the resource is unavailable
+ */
+ MailingListDescriptor getListDescriptor(String listId)
+ throws MailingListClientException;
+
+ /**
+ * Retrieves all members of the list by the given list ID.
+ *
+ * @param listId the ID of the list from which the members are
+ * retrieved
+ * @return all members of the list by the given list ID
+ * @throws MailingListClientException if the resource is unavailable
+ */
+ MailingListMembers getMembersByList(String listId)
+ throws MailingListClientException;
+
+ /**
+ * Retrieves a member by their email.
+ *
+ * @param listId the ID of the list from which the members is
+ * retrieved
+ * @param email the email of the member to retrieve
+ * @return the member of the list with the given email
+ * @throws MailingListClientException if the resource is unavailable
+ */
+ MailingListMember getMemberOfList(String listId, String email)
+ throws MailingListClientException;
+
+ /**
+ * Subscribes the member of the given email to list with the given
+ * list ID. If the member has already specified their subscription
+ * status this will not change the status but the request will be
+ * successful. The response will show the actual status of the
+ * member.
+ *
+ * @param listId the ID of the list to which the member will be
+ * subscribed
+ * @param email the email of the member which will be subscribed
+ * @return the member of the list for which the subscription was
+ * requested
+ * @throws MailingListClientException if the resource is unavailable
+ */
+ MailingListMember subscribeMember(String listId, String email)
+ throws MailingListClientException;
+
+ /**
+ * Unsubscribes the member of the given email from the list with
+ * the given list ID. If the member has already specified their
+ * subscription status, this will not change the status but the
+ * request will be successful. The response will show the actual
+ * status of the member.
+ *
+ * @param listId the ID of the list from which the member will be
+ * unsubscribed
+ * @param email the email of the member which will be unsubscribed
+ * @return the member of the list for which the unsubscription was
+ * requested
+ * @throws MailingListClientException if the resource is unavailable
+ */
+ MailingListMember unsubscribeMember(String listId, String email)
+ throws MailingListClientException;
+
+ /**
+ * Forces the subscription of the member with the given email
+ * from the list with the given list ID. This overrides any
+ * previous preference the member has.
+ *
+ * @param listId the ID of the list to which the member will be
+ * subscribed
+ * @param email the email of the member which will be subscribed
+ * @return the member of the list for which the subcription was
+ * requested
+ * @throws MailingListClientException if the resource is unavailable
+ */
+ MailingListMember forceSubscribeMember(String listId, String email)
+ throws MailingListClientException;
+
+ /**
+ * Forces the unsubscription of the member with the given email
+ * from the list with the given list ID. This overrides any
+ * previous preference the member has.
+ *
+ * @param listId the ID of the list from which the member will
+ * be unsubcribed
+ * @param email the email of the member which will be unsubscribed
+ * @return the member of the list for which the unsubscription was
+ * requested
+ * @throws MailingListClientException if the resource is unavailable
+ */
+ MailingListMember forceUnsubscribeMember(String listId, String email)
+ throws MailingListClientException;
+
+ /**
+ * Removes the member with the given email from the list entirely,
+ * this will remove any additional data stored about the member
+ * in the mailing list service.
+ *
+ * @param listId the ID of the list from which the member will be
+ * removed from
+ * @param email the email of the member who will be removed
+ */
+ void deleteMemberFromList(String listId, String email)
+ throws MailingListClientException;
+
+ /**
+ * Retrieves the segments of the list with the given list ID.
+ *
+ * @param listId the ID of the list from which to retrieve the
+ * segment
+ * @return the segments of the list with the given ID
+ * @throws MailingListClientException if the resource is unavailable
+ */
+ MailingListSegmentDescriptors getSegmentDescriptors(String listId)
+ throws MailingListClientException;
+
+ /**
+ * Retrieves the segment of the list with the given list ID.
+ *
+ * @param listId the ID of the list from which to retrieve the
+ * segment
+ * @param segmentId the ID of the segment to retrieve
+ * @return the segment of the list with the given ID
+ * @throws MailingListClientException if the resource is unavailable
+ */
+ MailingListSegmentDescriptor getSegmentDescriptor(String listId, String segmentId)
+ throws MailingListClientException;
+
+ /**
+ * Retrieves the members of the segment with the given segment ID
+ * within the list with the given list ID.
+ *
+ * @param listId the ID of the list from which to retrieve the
+ * segment members
+ * @param segmentId the ID of the segment to retrieve the segment
+ * members
+ * @return the members for the segment with the given ID
+ * @throws MailingListClientException if the resource is unavailable
+ */
+ MailingListMembers getMembersOfSegment(String listId, String segmentId)
+ throws MailingListClientException;
+
+ /**
+ * Adds the members who have the given email addresses from the
+ * specified segment in the specified list. If the member does
+ * not exist in the list they will not be added to the list.
+ *
+ * @param listId the ID of the list in which the segment is
+ * @param segmentId the ID of the segment to add the members to
+ * @param emails the emails of the members to add
+ * @return the successfully added members
+ * @throws MailingListClientException if the resource is unavailable
+ */
+ MailingListSegmentMemberChanges addMembersToSegment(String listId, String segmentId, Set emails)
+ throws MailingListClientException;
+
+ /**
+ * Removes the members who have the given email addresses to the
+ * specified segment in the specified list.
+ *
+ * @param listId the ID of the list in which the segment is
+ * @param segmentId the ID of the segment to remove the members from
+ * @param emails the emails of the members to remove
+ * @return the successfully removed members
+ * @throws MailingListClientException if the resource is unavailable
+ */
+ MailingListSegmentMemberChanges removeMembersFromSegment(String listId, String segmentId, Set emails)
+ throws MailingListClientException;
+
+ /**
+ * Creates a segment with the given name in the given list.
+ *
+ * @param listId the ID of the list in which to create the
+ * segment
+ * @param segmentName the name of the segment
+ * @return the descriptor of the segment
+ * @throws MailingListClientException if the resource is unavailable
+ */
+ MailingListSegmentDescriptor createSegment(String listId, String segmentName)
+ throws MailingListClientException;
+
+ /**
+ * Deletes the segment with the given ID in the given list.
+ *
+ * @param listId this ID of the list in which to create the segment
+ * @param segmentId the ID of the segment
+ * @throws MailingListClientException if the resource is unavailable
+ */
+ void deleteSegment(String listId, String segmentId)
+ throws MailingListClientException;
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListDescriptor.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListDescriptor.java
new file mode 100644
index 0000000..4e23198
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListDescriptor.java
@@ -0,0 +1,17 @@
+package uk.ac.stfc.facilities.mailing.api.data;
+
+/**
+ * Describes a mailing list.
+ */
+public interface MailingListDescriptor {
+
+ /**
+ * @return the ID of the mailing list
+ */
+ String getId();
+
+ /**
+ * @return the name of the mailing list
+ */
+ String getName();
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListDescriptors.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListDescriptors.java
new file mode 100644
index 0000000..b138bb5
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListDescriptors.java
@@ -0,0 +1,14 @@
+package uk.ac.stfc.facilities.mailing.api.data;
+
+import java.util.Set;
+
+/**
+ * Contains a set of mailing list descriptors.
+ */
+public interface MailingListDescriptors {
+
+ /**
+ * @return the set of mailing list descriptors
+ */
+ Set extends MailingListDescriptor> getLists();
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListMember.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListMember.java
new file mode 100644
index 0000000..cd9ba1c
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListMember.java
@@ -0,0 +1,32 @@
+package uk.ac.stfc.facilities.mailing.api.data;
+
+/**
+ * Describes a member of a mailing list.
+ */
+public interface MailingListMember {
+
+ /**
+ * @return the ID of the mailing list member
+ */
+ String getId();
+
+ /**
+ * @return the email address of the list member
+ */
+ String getEmailAddress();
+
+ /**
+ * @return a global identifier for the member
+ */
+ String getUniqueEmailId();
+
+ /**
+ * @return the status of the list member
+ */
+ SubscriptionStatus getSubscriptionStatus();
+
+ /**
+ * @return the ID of the list
+ */
+ String getListId();
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListMembers.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListMembers.java
new file mode 100644
index 0000000..2301a4c
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListMembers.java
@@ -0,0 +1,14 @@
+package uk.ac.stfc.facilities.mailing.api.data;
+
+import java.util.Set;
+
+/**
+ * Contains a set of mailing list members.
+ */
+public interface MailingListMembers {
+
+ /**
+ * @return the set of members
+ */
+ Set extends MailingListMember> getMembers();
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListSegmentDescriptor.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListSegmentDescriptor.java
new file mode 100644
index 0000000..627c755
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListSegmentDescriptor.java
@@ -0,0 +1,18 @@
+package uk.ac.stfc.facilities.mailing.api.data;
+
+/**
+ * Describes a mailing list segment.
+ */
+public interface MailingListSegmentDescriptor {
+
+ /**
+ * @return the ID of the segment
+ */
+ String getId();
+
+ /**
+ * @return the name of the segment
+ */
+ String getName();
+
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListSegmentDescriptors.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListSegmentDescriptors.java
new file mode 100644
index 0000000..fa5f6a0
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListSegmentDescriptors.java
@@ -0,0 +1,15 @@
+package uk.ac.stfc.facilities.mailing.api.data;
+
+import java.util.Set;
+
+/**
+ * Contains a set of mailing list segment descriptors.
+ */
+public interface MailingListSegmentDescriptors {
+
+ /**
+ * @return the set of mailing list segment descriptors
+ */
+ Set extends MailingListSegmentDescriptor> getSegmentDescriptors();
+
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListSegmentMemberChanges.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListSegmentMemberChanges.java
new file mode 100644
index 0000000..7090c87
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/MailingListSegmentMemberChanges.java
@@ -0,0 +1,20 @@
+package uk.ac.stfc.facilities.mailing.api.data;
+
+import java.util.Set;
+
+/**
+ * Describes the changes made to the segment members
+ */
+public interface MailingListSegmentMemberChanges {
+
+ /**
+ * @return the set of members added to the segment
+ */
+ Set extends MailingListMember> getAddedMembers();
+
+ /**
+ * @return the set of members removed from the segment
+ */
+ Set extends MailingListMember> getRemovedMembers();
+
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/SubscriptionStatus.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/SubscriptionStatus.java
new file mode 100644
index 0000000..1d04d5f
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/data/SubscriptionStatus.java
@@ -0,0 +1,22 @@
+package uk.ac.stfc.facilities.mailing.api.data;
+
+/**
+ * The subscription status of a member.
+ */
+public enum SubscriptionStatus {
+
+ /**
+ * The status when a member is subscribed.
+ */
+ SUBSCRIBED,
+
+ /**
+ * The status when a member is unsubscribed.
+ */
+ UNSUBSCRIBED,
+
+ /**
+ * The status when the state of subscription for a member is unknown.
+ */
+ UNKNOWN
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/exceptions/MailingListClientException.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/exceptions/MailingListClientException.java
new file mode 100644
index 0000000..b20b317
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/exceptions/MailingListClientException.java
@@ -0,0 +1,16 @@
+package uk.ac.stfc.facilities.mailing.api.exceptions;
+
+/**
+ * Describes errors that occurred trying to get a resource from the
+ * mailing list client.
+ */
+public class MailingListClientException extends Exception {
+
+ public MailingListClientException(String s) {
+ super(s);
+ }
+
+ public MailingListClientException(String s, Throwable e) {
+ super(s, e);
+ }
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/exceptions/NotFoundMailingListClientException.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/exceptions/NotFoundMailingListClientException.java
new file mode 100644
index 0000000..c6e195e
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/exceptions/NotFoundMailingListClientException.java
@@ -0,0 +1,15 @@
+package uk.ac.stfc.facilities.mailing.api.exceptions;
+
+/**
+ * A MailingListClientException that describes the situation where a
+ * resource was not found.
+ */
+public class NotFoundMailingListClientException extends MailingListClientException {
+ public NotFoundMailingListClientException(String s) {
+ super(s);
+ }
+
+ public NotFoundMailingListClientException(String s, Throwable e) {
+ super(s, e);
+ }
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClient.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClient.java
new file mode 100644
index 0000000..b3c4225
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClient.java
@@ -0,0 +1,164 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import uk.ac.stfc.facilities.mailing.api.MailingListClient;
+import uk.ac.stfc.facilities.mailing.api.data.*;
+import uk.ac.stfc.facilities.mailing.api.exceptions.MailingListClientException;
+
+import java.util.Set;
+
+/**
+ * Implementation of MailingListClient
that retrieves
+ * mailing list data from Mailchimp. It is best to have only one
+ * instance of this client per application. The implementation is
+ * thread safe.
+ *
+ * Creating a mailchimp client, where API_KEY
is
+ * the Mailchimp API key:
+ *
+ * MailchimpClientConfiguration config = new MailchimpClientConfiguration(API_KEY);
+ * MailingListClient client = MailchimpClient.getInstance(configuration);
+ *
+ *
+ */
+public class MailchimpClient implements MailingListClient {
+
+ /**
+ * Generates an instance of the MailchimpClient with the given
+ * configuration.
+ *
+ * @param configuration the Mailchimp client configuration
+ * @return a new instance of the Mailchimp client
+ */
+ public static MailchimpClient getInstance(MailchimpClientConfiguration configuration) {
+ return new MailchimpClient(MailchimpInternalHttpClient.getInstance(configuration));
+ }
+
+ private static String convertEmailToId(String email) {
+ return DigestUtils.md5Hex(email.toLowerCase());
+ }
+
+ private final MailchimpInternalHttpClient httpClient;
+
+ private MailchimpClient(MailchimpInternalHttpClient internalHttpClient) {
+ this.httpClient = internalHttpClient;
+ }
+
+ @Override
+ public MailingListDescriptors getAllListDescriptors() throws MailingListClientException {
+ return httpClient.get(
+ "lists",
+ MailchimpListDescriptors.class);
+ }
+
+ @Override
+ public MailingListDescriptor getListDescriptor(String listId) throws MailingListClientException {
+ return httpClient.get(
+ "lists/" + listId,
+ MailchimpListDescriptor.class);
+ }
+
+ @Override
+ public MailingListMembers getMembersByList(String listId) throws MailingListClientException {
+ return httpClient.get(
+ "lists/" + listId + "/members",
+ MailchimpListMembers.class);
+ }
+
+ @Override
+ public MailingListMember getMemberOfList(String listId, String email) throws MailingListClientException {
+ return httpClient.get(
+ "lists/" + listId + "/members/" + convertEmailToId(email),
+ MailchimpListMember.class);
+ }
+
+ @Override
+ public MailingListMember subscribeMember(String listId, String email) throws MailingListClientException {
+
+ return httpClient.put("lists/" + listId + "/members/" + convertEmailToId(email),
+ MailchimpListMemberRequest.whichSubscribes(email),
+ MailchimpListMember.class);
+ }
+
+ @Override
+ public MailingListMember unsubscribeMember(String listId, String email) throws MailingListClientException {
+
+ return httpClient.put("lists/" + listId + "/members/" + convertEmailToId(email),
+ MailchimpListMemberRequest.whichUnsubscribes(email),
+ MailchimpListMember.class);
+ }
+
+ @Override
+ public MailingListMember forceSubscribeMember(String listId, String email) throws MailingListClientException {
+
+ return httpClient.put("lists/" + listId + "/members/" + convertEmailToId(email),
+ MailchimpListMemberRequest.whichForceSubscribes(email),
+ MailchimpListMember.class);
+ }
+
+ @Override
+ public MailingListMember forceUnsubscribeMember(String listId, String email) throws MailingListClientException {
+
+ return httpClient.put("lists/" + listId + "/members/" + convertEmailToId(email),
+ MailchimpListMemberRequest.whichForceUnsubscribes(email),
+ MailchimpListMember.class);
+ }
+
+ @Override
+ public void deleteMemberFromList(String listId, String email) throws MailingListClientException {
+ httpClient.delete("lists/" + listId + "/members/" + convertEmailToId(email),
+ Void.class);
+ }
+
+ @Override
+ public MailingListSegmentDescriptors getSegmentDescriptors(String listId) throws MailingListClientException {
+ return httpClient.get(
+ "lists/" + listId + "/segments",
+ MailchimpSegmentDescriptors.class);
+ }
+
+ @Override
+ public MailingListSegmentDescriptor getSegmentDescriptor(String listId, String segmentId) throws MailingListClientException {
+ return httpClient.get(
+ "lists/" + listId + "/segments/" + segmentId,
+ MailchimpSegmentDescriptor.class);
+ }
+
+ @Override
+ public MailingListMembers getMembersOfSegment(String listId, String segmentId) throws MailingListClientException {
+ return httpClient.get(
+ "lists/" + listId + "/segments/" + segmentId + "/members",
+ MailchimpListMembers.class);
+ }
+
+ @Override
+ public MailingListSegmentMemberChanges addMembersToSegment(String listId, String segmentId, Set emails) throws MailingListClientException {
+ return httpClient.post(
+ "lists/" + listId + "/segments/" + segmentId,
+ MailchimpSegmentMemberRequest.whichAddsMembers(emails),
+ MailchimpSegmentMemberChanges.class
+ );
+ }
+
+ @Override
+ public MailingListSegmentMemberChanges removeMembersFromSegment(String listId, String segmentId, Set emails) throws MailingListClientException {
+ return httpClient.post(
+ "lists/" + listId + "/segments/" + segmentId,
+ MailchimpSegmentMemberRequest.whichRemovesMembers(emails),
+ MailchimpSegmentMemberChanges.class
+ );
+ }
+
+ @Override
+ public MailingListSegmentDescriptor createSegment(String listId, String segmentName) throws MailingListClientException {
+ return httpClient.post("lists/" + listId + "/segments",
+ MailchimpSegmentCreateRequest.whichCreatesASegment(segmentName),
+ MailchimpSegmentDescriptor.class);
+ }
+
+ @Override
+ public void deleteSegment(String listId, String segmentId) throws MailingListClientException {
+ httpClient.delete("lists/" + listId + "/segments/" + segmentId,
+ Void.class);
+ }
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientConfiguration.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientConfiguration.java
new file mode 100644
index 0000000..bf197ce
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientConfiguration.java
@@ -0,0 +1,50 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+/**
+ * Configuration for the Mailchimp client.
+ */
+public class MailchimpClientConfiguration {
+ private static final String SCHEME = "https";
+ private static final int PORT = 443;
+ private static final String MAILCHIMP_API_BASE_HOST = "api.mailchimp.com";
+ private static final String MAILCHIMP_API_VERSION_URI = "/3.0/";
+ private static final String API_KEY_SPLITTER = "-";
+
+ private final String apiKey;
+
+ private final String baseUri;
+ private final String host;
+
+ /**
+ * Creates the configuration from the Mailchimp API key which
+ * contains all of the connection information for Mailchimp.
+ *
+ * @param apiKey the API key for Mailchimp.
+ */
+ public MailchimpClientConfiguration(String apiKey) {
+ this.apiKey = apiKey;
+ String dataCenter = apiKey.split(API_KEY_SPLITTER)[1];
+ this.host = dataCenter + "." + MAILCHIMP_API_BASE_HOST;
+ this.baseUri = SCHEME + "://" + host + MAILCHIMP_API_VERSION_URI;
+ }
+
+ public String getApiKey() {
+ return apiKey;
+ }
+
+ public String getBaseUrl() {
+ return baseUri;
+ }
+
+ public String getHost() {
+ return host;
+ }
+
+ public String getScheme() {
+ return SCHEME;
+ }
+
+ public int getPort() {
+ return PORT;
+ }
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClient.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClient.java
new file mode 100644
index 0000000..1e5128c
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClient.java
@@ -0,0 +1,183 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpResponse;
+import org.apache.http.HttpStatus;
+import org.apache.http.auth.AuthScope;
+import org.apache.http.auth.Credentials;
+import org.apache.http.auth.UsernamePasswordCredentials;
+import org.apache.http.client.AuthCache;
+import org.apache.http.client.CredentialsProvider;
+import org.apache.http.client.methods.*;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.auth.BasicScheme;
+import org.apache.http.impl.client.BasicAuthCache;
+import org.apache.http.impl.client.BasicCredentialsProvider;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import uk.ac.stfc.facilities.mailing.api.exceptions.MailingListClientException;
+import uk.ac.stfc.facilities.mailing.api.exceptions.NotFoundMailingListClientException;
+import uk.ac.stfc.facilities.mailing.mailchimp.gson.adapters.LocalDateTimeAdapter;
+
+import java.io.IOException;
+import java.time.LocalDateTime;
+
+/**
+ *
+ */
+class MailchimpInternalHttpClient {
+
+ private static final Logger LOG = LogManager.getLogger();
+
+ public static MailchimpInternalHttpClient getInstance(MailchimpClientConfiguration configuration) {
+
+ CredentialsProvider provider = new BasicCredentialsProvider();
+ Credentials credentials = new UsernamePasswordCredentials("user", configuration.getApiKey());
+ provider.setCredentials(AuthScope.ANY, credentials);
+
+ AuthCache authCache = new BasicAuthCache();
+ authCache.put(new HttpHost(configuration.getHost(), configuration.getPort(), configuration.getScheme()), new BasicScheme());
+
+ HttpClientContext context = HttpClientContext.create();
+
+ context.setCredentialsProvider(provider);
+ context.setAuthCache(authCache);
+
+ CloseableHttpClient client = HttpClientBuilder.create()
+ .build();
+
+ return new MailchimpInternalHttpClient(configuration, client, context);
+ }
+
+ private final CloseableHttpClient client;
+ private final HttpContext context;
+ private final Gson gson = new GsonBuilder()
+ .registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter())
+ .create();
+
+ private final MailchimpClientConfiguration configuration;
+
+ MailchimpInternalHttpClient(
+ MailchimpClientConfiguration configuration,
+ CloseableHttpClient client,
+ HttpContext context) {
+ this.configuration = configuration;
+ this.client = client;
+ this.context = context;
+ }
+
+ T delete(String uri, Class responseClass) throws MailingListClientException {
+ HttpDelete request = new HttpDelete(withBaseUrl(uri));
+
+ return doRequest(request, responseClass);
+ }
+
+ T put(String uri, Object body, Class responseClass) throws MailingListClientException {
+ HttpPut request = new HttpPut(withBaseUrl(uri));
+ request.setEntity(createEntity(body));
+
+ return doRequest(request, responseClass);
+ }
+
+ T post(String uri, Object body, Class responseClass) throws MailingListClientException {
+ HttpPost request = new HttpPost(withBaseUrl(uri));
+ request.setEntity(createEntity(body));
+
+ return doRequest(request, responseClass);
+ }
+
+ T get(String uri, Class responseClass) throws MailingListClientException {
+ HttpGet request = new HttpGet(withBaseUrl(uri));
+
+ return doRequest(request, responseClass);
+ }
+
+ private HttpEntity createEntity(Object object) {
+ String requestText = gson.toJson(object);
+
+ return new StringEntity(requestText, "UTF-8");
+ }
+
+ private String withBaseUrl(String url) {
+ return configuration.getBaseUrl() + url;
+ }
+
+ private T doRequest(HttpUriRequest request, Class responseClass) throws MailingListClientException {
+ LOG.debug("making {} request for {}", request::getMethod, request::getURI);
+ try {
+ HttpResponse response = client.execute(request, context);
+
+ manageResponseStatus(request, response);
+
+ LOG.debug("successful {} request for {}", request::getMethod, request::getURI);
+
+ return fromJson(response.getEntity(), responseClass);
+ } catch (IOException e) {
+ LOG.error("unable to complete {} request for {}", request.getMethod(), request.getURI(), e);
+ throw new MailingListClientException("Unable to execute the request " + request.getURI(), e);
+ }
+ }
+
+ private void manageResponseStatus(
+ HttpUriRequest request,
+ HttpResponse response) throws IOException, MailingListClientException {
+
+ switch (response.getStatusLine().getStatusCode()) {
+ case HttpStatus.SC_OK:
+ case HttpStatus.SC_CREATED:
+ case HttpStatus.SC_ACCEPTED:
+ case HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION:
+ case HttpStatus.SC_NO_CONTENT:
+ case HttpStatus.SC_RESET_CONTENT:
+ case HttpStatus.SC_PARTIAL_CONTENT:
+ case HttpStatus.SC_MULTI_STATUS:
+ return;
+ case HttpStatus.SC_NOT_FOUND:
+ throw new NotFoundMailingListClientException(
+ "Could not find resource for " + request.getURI().toString());
+ default:
+ String errorMessage = String.format("failed %s request for %s with %s and body \"%s\"",
+ request.getMethod(),
+ request.getURI(),
+ response.getStatusLine(),
+ EntityUtils.toString(response.getEntity()));
+
+ throw new MailingListClientException(errorMessage);
+
+ }
+ }
+
+ private T fromJson(HttpEntity entity, Class responseClass) throws MailingListClientException {
+ LOG.debug("converting from JSON for {}", responseClass::getCanonicalName);
+ try {
+ if (entity != null && entity.getContentLength() != 0) {
+ String resultText = EntityUtils.toString(entity);
+ T result = gson.fromJson(
+ resultText,
+ responseClass
+ );
+ LOG.debug("converted from JSON for {}, with JSON {}",
+ responseClass::getCanonicalName,
+ () -> resultText);
+ return result;
+ } else {
+ return null;
+ }
+ } catch (IOException e) {
+ LOG.error("failed to convert from JSON for {}", responseClass.getCanonicalName());
+ throw new MailingListClientException("Unable to read the response", e);
+ } finally {
+ EntityUtils.consumeQuietly(entity);
+ }
+ }
+
+
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListDescriptor.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListDescriptor.java
new file mode 100644
index 0000000..1bd6bcf
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListDescriptor.java
@@ -0,0 +1,39 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+import uk.ac.stfc.facilities.mailing.api.data.MailingListDescriptor;
+
+class MailchimpListDescriptor implements MailingListDescriptor {
+ private String id;
+ private String name;
+
+ public MailchimpListDescriptor(String id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return "MailchimpListDescriptor{" +
+ "id='" + id + '\'' +
+ ", name='" + name + '\'' +
+ '}';
+ }
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListDescriptors.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListDescriptors.java
new file mode 100644
index 0000000..e0f38e6
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListDescriptors.java
@@ -0,0 +1,33 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+import uk.ac.stfc.facilities.mailing.api.data.MailingListDescriptors;
+
+import java.util.Set;
+
+/**
+ *
+ */
+class MailchimpListDescriptors implements MailingListDescriptors {
+
+ Set lists;
+
+ public MailchimpListDescriptors(Set lists) {
+ this.lists = lists;
+ }
+
+ @Override
+ public Set getLists() {
+ return lists;
+ }
+
+ public void setLists(Set lists) {
+ this.lists = lists;
+ }
+
+ @Override
+ public String toString() {
+ return "MailchimpListDescriptors{" +
+ "lists=" + lists +
+ '}';
+ }
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListMember.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListMember.java
new file mode 100644
index 0000000..da6a185
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListMember.java
@@ -0,0 +1,140 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+import com.google.gson.annotations.SerializedName;
+import uk.ac.stfc.facilities.mailing.api.data.MailingListMember;
+import uk.ac.stfc.facilities.mailing.api.data.SubscriptionStatus;
+
+import java.time.LocalDateTime;
+
+/**
+ *
+ */
+class MailchimpListMember implements MailingListMember {
+
+
+ private String id;
+ @SerializedName("email_address")
+ private String emailAddress;
+ @SerializedName("unique_email_id")
+ private String uniqueEmailId;
+ @SerializedName("email_type")
+ private String emailType;
+ @SerializedName("status")
+ private String status;
+
+ @SerializedName("timestamp_opt")
+ private LocalDateTime timestampOpt;
+ @SerializedName("last_change")
+ private LocalDateTime lastChange;
+
+ @SerializedName("language")
+ private String language;
+ @SerializedName("list_id")
+ private String listId;
+
+ public MailchimpListMember() {
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String getEmailAddress() {
+ return emailAddress;
+ }
+
+ public void setEmailAddress(String emailAddress) {
+ this.emailAddress = emailAddress;
+ }
+
+ @Override
+ public String getUniqueEmailId() {
+ return uniqueEmailId;
+ }
+
+ @Override
+ public SubscriptionStatus getSubscriptionStatus() {
+ switch (status) {
+ case "subscribed":
+ return SubscriptionStatus.SUBSCRIBED;
+ case "unsubscribed":
+ return SubscriptionStatus.UNSUBSCRIBED;
+ default:
+ return SubscriptionStatus.UNKNOWN;
+ }
+ }
+
+ public void setUniqueEmailId(String uniqueEmailId) {
+ this.uniqueEmailId = uniqueEmailId;
+ }
+
+ public String getEmailType() {
+ return emailType;
+ }
+
+ public void setEmailType(String emailType) {
+ this.emailType = emailType;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public LocalDateTime getTimestampOpt() {
+ return timestampOpt;
+ }
+
+ public void setTimestampOpt(LocalDateTime timestampOpt) {
+ this.timestampOpt = timestampOpt;
+ }
+
+ public LocalDateTime getLastChange() {
+ return lastChange;
+ }
+
+ public void setLastChange(LocalDateTime lastChange) {
+ this.lastChange = lastChange;
+ }
+
+ public String getLanguage() {
+ return language;
+ }
+
+ public void setLanguage(String language) {
+ this.language = language;
+ }
+
+ @Override
+ public String getListId() {
+ return listId;
+ }
+
+ public void setListId(String listId) {
+ this.listId = listId;
+ }
+
+ @Override
+ public String toString() {
+ return "MailchimpListMember{" +
+ "id='" + id + '\'' +
+ ", emailAddress='" + emailAddress + '\'' +
+ ", uniqueEmailId='" + uniqueEmailId + '\'' +
+ ", emailType='" + emailType + '\'' +
+ ", status='" + status + '\'' +
+ ", timestampOpt=" + timestampOpt +
+ ", lastChange=" + lastChange +
+ ", language='" + language + '\'' +
+ ", listId='" + listId + '\'' +
+ '}';
+ }
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListMemberRequest.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListMemberRequest.java
new file mode 100644
index 0000000..72f4d62
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListMemberRequest.java
@@ -0,0 +1,82 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+import com.google.gson.annotations.SerializedName;
+
+/**
+ *
+ */
+class MailchimpListMemberRequest {
+
+ private static final String SUBSCRIBED = "subscribed";
+ private static final String UNSUBSCRIBED = "unsubscribed";
+
+ public static MailchimpListMemberRequest whichSubscribes(String email) {
+ MailchimpListMemberRequest request = new MailchimpListMemberRequest();
+
+ request.email = email;
+ request.statusIfNew = SUBSCRIBED;
+ return request;
+ }
+
+ public static MailchimpListMemberRequest whichForceSubscribes(String email) {
+
+ MailchimpListMemberRequest request = new MailchimpListMemberRequest();
+
+ request.email = email;
+ request.statusIfNew = SUBSCRIBED;
+ request.status = SUBSCRIBED;
+ return request;
+ }
+
+ public static MailchimpListMemberRequest whichUnsubscribes(String email) {
+
+ MailchimpListMemberRequest requests = new MailchimpListMemberRequest();
+
+ requests.email = email;
+ requests.statusIfNew = UNSUBSCRIBED;
+ return requests;
+ }
+
+ public static MailchimpListMemberRequest whichForceUnsubscribes(String email) {
+
+ MailchimpListMemberRequest request = new MailchimpListMemberRequest();
+
+ request.email = email;
+ request.statusIfNew = UNSUBSCRIBED;
+ request.status = UNSUBSCRIBED;
+ return request;
+ }
+
+ @SerializedName("email_address")
+ private String email;
+
+ @SerializedName("status_if_new")
+ private String statusIfNew;
+
+ @SerializedName("status")
+ private String status;
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ }
+
+ public String getStatusIfNew() {
+ return statusIfNew;
+ }
+
+ public void setStatusIfNew(String statusIfNew) {
+ this.statusIfNew = statusIfNew;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListMembers.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListMembers.java
new file mode 100644
index 0000000..e163cec
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListMembers.java
@@ -0,0 +1,33 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+import uk.ac.stfc.facilities.mailing.api.data.MailingListMembers;
+
+import java.util.Set;
+
+/**
+ *
+ */
+class MailchimpListMembers implements MailingListMembers {
+
+ Set members;
+
+ public MailchimpListMembers(Set members) {
+ this.members = members;
+ }
+
+ @Override
+ public Set getMembers() {
+ return members;
+ }
+
+ public void setMembers(Set members) {
+ this.members = members;
+ }
+
+ @Override
+ public String toString() {
+ return "MailchimpListMembers{" +
+ "members=" + members +
+ '}';
+ }
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentCreateRequest.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentCreateRequest.java
new file mode 100644
index 0000000..ffabc00
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentCreateRequest.java
@@ -0,0 +1,42 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.HashSet;
+
+/**
+ *
+ */
+public class MailchimpSegmentCreateRequest {
+
+
+ @SerializedName("name")
+ private String name;
+
+ @SerializedName("static_segment")
+ private HashSet emails;
+
+ public static MailchimpSegmentCreateRequest whichCreatesASegment(String segmentName) {
+ MailchimpSegmentCreateRequest createRequest = new MailchimpSegmentCreateRequest();
+ createRequest.name = segmentName;
+ createRequest.emails = new HashSet<>();
+
+ return createRequest;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public HashSet getEmails() {
+ return emails;
+ }
+
+ public void setEmails(HashSet emails) {
+ this.emails = emails;
+ }
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentDescriptor.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentDescriptor.java
new file mode 100644
index 0000000..c250498
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentDescriptor.java
@@ -0,0 +1,37 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+import uk.ac.stfc.facilities.mailing.api.data.MailingListSegmentDescriptor;
+
+/**
+ *
+ */
+class MailchimpSegmentDescriptor implements MailingListSegmentDescriptor {
+ private String id;
+ private String name;
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @Override
+ public String toString() {
+ return "MailchimpSegmentDescriptor{" +
+ "id='" + id + '\'' +
+ ", name='" + name + '\'' +
+ '}';
+ }
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentDescriptors.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentDescriptors.java
new file mode 100644
index 0000000..3675676
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentDescriptors.java
@@ -0,0 +1,36 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+import com.google.gson.annotations.SerializedName;
+import uk.ac.stfc.facilities.mailing.api.data.MailingListSegmentDescriptors;
+
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ *
+ */
+class MailchimpSegmentDescriptors implements MailingListSegmentDescriptors {
+
+ @SerializedName("segments")
+ Set segmentDescriptors;
+
+ @Override
+ public Set getSegmentDescriptors() {
+ return segmentDescriptors;
+ }
+
+ public Set setSegmentDescriptors() {
+ return segmentDescriptors;
+ }
+
+ public Iterator iterator() {
+ return segmentDescriptors.iterator();
+ }
+
+ @Override
+ public String toString() {
+ return "MailchimpSegmentDescriptors{" +
+ "segmentDescriptors=" + segmentDescriptors +
+ '}';
+ }
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentMemberChanges.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentMemberChanges.java
new file mode 100644
index 0000000..8dbdedc
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentMemberChanges.java
@@ -0,0 +1,43 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+import com.google.gson.annotations.SerializedName;
+import uk.ac.stfc.facilities.mailing.api.data.MailingListSegmentMemberChanges;
+
+import java.util.Set;
+
+/**
+ *
+ */
+class MailchimpSegmentMemberChanges implements MailingListSegmentMemberChanges {
+ @SerializedName("members_added")
+ private Set addedMembers;
+ @SerializedName("members_removed")
+ private Set removedMembers;
+
+ @Override
+ public Set getAddedMembers() {
+ return addedMembers;
+ }
+
+ @Override
+ public Set getRemovedMembers() {
+ return removedMembers;
+ }
+
+ public void setAddedMembers(Set addedMembers) {
+ this.addedMembers = addedMembers;
+ }
+
+ public void setRemovedMembers(Set removedMembers) {
+ this.removedMembers = removedMembers;
+ }
+
+ @Override
+ public String toString() {
+ return "MailchimpSegmentMemberChanges{" +
+ "addedMembers=" + addedMembers +
+ ", removedMembers=" + removedMembers +
+ '}';
+ }
+
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentMemberRequest.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentMemberRequest.java
new file mode 100644
index 0000000..0807454
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentMemberRequest.java
@@ -0,0 +1,44 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+import com.google.gson.annotations.SerializedName;
+
+import java.util.Set;
+
+/**
+ *
+ */
+class MailchimpSegmentMemberRequest {
+
+ @SerializedName("members_to_add")
+ private Set membersToAdd;
+ @SerializedName("members_to_remove")
+ private Set membersToRemove;
+
+ public static MailchimpSegmentMemberRequest whichAddsMembers(Set email) {
+ MailchimpSegmentMemberRequest request = new MailchimpSegmentMemberRequest();
+ request.membersToAdd = email;
+ return request;
+ }
+
+ public static MailchimpSegmentMemberRequest whichRemovesMembers(Set email) {
+ MailchimpSegmentMemberRequest request = new MailchimpSegmentMemberRequest();
+ request.membersToRemove = email;
+ return request;
+ }
+
+ public Set getMembersToAdd() {
+ return membersToAdd;
+ }
+
+ public void setMembersToAdd(Set membersToAdd) {
+ this.membersToAdd = membersToAdd;
+ }
+
+ public Set getMembersToRemove() {
+ return membersToRemove;
+ }
+
+ public void setMembersToRemove(Set membersToRemove) {
+ this.membersToRemove = membersToRemove;
+ }
+}
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/gson/adapters/LocalDateTimeAdapter.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/gson/adapters/LocalDateTimeAdapter.java
new file mode 100644
index 0000000..50af65e
--- /dev/null
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/gson/adapters/LocalDateTimeAdapter.java
@@ -0,0 +1,22 @@
+package uk.ac.stfc.facilities.mailing.mailchimp.gson.adapters;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+
+import java.lang.reflect.Type;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+/**
+ *
+ */
+public class LocalDateTimeAdapter implements JsonDeserializer {
+
+
+ @Override
+ public LocalDateTime deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
+ return LocalDateTime.parse(jsonElement.getAsJsonPrimitive().getAsString(), DateTimeFormatter.ISO_DATE_TIME);
+ }
+}
diff --git a/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientIT.java b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientIT.java
new file mode 100644
index 0000000..562504d
--- /dev/null
+++ b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientIT.java
@@ -0,0 +1,396 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+import org.junit.jupiter.api.*;
+import uk.ac.stfc.facilities.mailing.api.data.*;
+import uk.ac.stfc.facilities.mailing.api.exceptions.MailingListClientException;
+import uk.ac.stfc.facilities.mailing.api.exceptions.NotFoundMailingListClientException;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Properties;
+import java.util.Random;
+
+import static org.assertj.core.api.Assertions.*;
+
+@DisplayName("The Mailchimp client")
+public class MailchimpClientIT {
+
+ private static final String NAME_FIELD_NAME = "name";
+ private static final String ID_FIELD_NAME = "id";
+ private static final String EMAIL_ADDRESS_FIELD_NAME = "emailAddress";
+ private static final String SUBSCRIPTION_STATUS_FIELD_NAME = "subscriptionStatus";
+
+ private static final String EXPECTED_SEGMENT_NAME = "test";
+
+ private static final SubscriptionStatus PERMENANT_SUBSCRIPTION_STATUS_1 = SubscriptionStatus.SUBSCRIBED;
+ private static final SubscriptionStatus PERMENANT_SUBSCRIPTION_STATUS_2 = SubscriptionStatus.SUBSCRIBED;
+ private static final SubscriptionStatus PERMENANT_SUBSCRIPTION_STATUS_3 = SubscriptionStatus.UNSUBSCRIBED;
+
+ private static String expectedUserEmail;
+
+ private static String listId;
+ private static String listName;
+
+ private static String testDomain;
+
+ private static String noSuchMemberEmail;
+
+ private static String permenantEmail1;
+ private static String permenantEmail2;
+ private static String permenantEmail3;
+
+ private static MailchimpClient mailchimpClient;
+
+ @BeforeAll
+ public static void setupClass() throws IOException {
+ Properties properties = new Properties();
+
+ File file = new File("application.test.properties");
+
+ properties.load(new FileInputStream(file));
+
+ String mailchimpApiKey = properties.getProperty("mailchimp.api.key");
+
+ mailchimpClient = MailchimpClient.getInstance(new MailchimpClientConfiguration(mailchimpApiKey));
+
+ listId = properties.getProperty("mailchimp.list.id");
+ listName = properties.getProperty("mailchimp.list.name");
+
+ testDomain = properties.getProperty("mailchimp.test.domain");
+
+ permenantEmail1 = "permenant.test.1@" + testDomain;
+ permenantEmail2 = "permenant.test.2@" + testDomain;
+ permenantEmail3 = "permenant.test.3@" + testDomain;
+
+ noSuchMemberEmail = "no.such.member@" + testDomain;
+
+ }
+
+ @BeforeEach
+ public void generateRandomUser() throws IOException {
+ Random random = new Random();
+
+ expectedUserEmail = random.ints(20)
+ .map(i -> Math.abs(i % 26) + 97)
+ .mapToObj(i -> Character.toString((char) i))
+ .reduce("", (s, i) -> s + i) + "@test.stfc.co.uk";
+
+ }
+
+ @Nested
+ @DisplayName("for lists")
+ public class Lists {
+
+ @Test
+ @DisplayName("is able to get all lists")
+ public void getAllListDescriptors() throws MailingListClientException {
+
+ MailingListDescriptors descriptors = mailchimpClient.getAllListDescriptors();
+
+ assertThat(descriptors.getLists())
+ .extracting(ID_FIELD_NAME, NAME_FIELD_NAME)
+ .contains(tuple(listId, listName));
+ }
+
+ @Test
+ @DisplayName("is able to get a list by ID")
+ public void getListDescriptor() throws MailingListClientException {
+ MailingListDescriptor descriptor = mailchimpClient.getListDescriptor(listId);
+ assertThat(descriptor.getId()).isEqualTo(listId);
+ assertThat(descriptor.getName()).isEqualTo(listName);
+
+ }
+
+ @Nested
+ @DisplayName("for members")
+ public class Members {
+
+ @Nested
+ @DisplayName("when retrieving")
+ public class Retriving {
+ @BeforeEach
+ public void ensureRandomUserExists() throws MailingListClientException {
+
+ addExpectedUser();
+ }
+
+ @AfterEach
+ public void ensureRandomUserIsRemoved() throws MailingListClientException {
+ removeExpectedUser();
+ }
+
+ @Test
+ @DisplayName("is able to get all members of a list by list ID")
+ public void getMembersByList() throws MailingListClientException {
+ MailingListMembers mailingListMembers = mailchimpClient.getMembersByList(listId);
+ assertThat(mailingListMembers.getMembers())
+ .extracting(EMAIL_ADDRESS_FIELD_NAME, SUBSCRIPTION_STATUS_FIELD_NAME)
+ .contains(
+ tuple(permenantEmail1, PERMENANT_SUBSCRIPTION_STATUS_1),
+ tuple(permenantEmail2, PERMENANT_SUBSCRIPTION_STATUS_2),
+ tuple(permenantEmail3, PERMENANT_SUBSCRIPTION_STATUS_3)
+ );
+
+ }
+
+ @Test
+ @DisplayName("is able to get a member of a list by list ID and member email")
+ public void getMemberOfList() throws MailingListClientException {
+ MailingListMember member = mailchimpClient.getMemberOfList(listId, expectedUserEmail);
+
+ assertThat(member.getEmailAddress()).isEqualTo(expectedUserEmail);
+ }
+
+ @Test
+ @DisplayName("throws a NotFoundMailingListClientException when a member cannot be found in a list")
+ public void getMemberOfList_notFound() throws MailingListClientException {
+ assertThatThrownBy(() -> mailchimpClient.getMemberOfList(listId, noSuchMemberEmail))
+ .isInstanceOf(NotFoundMailingListClientException.class);
+ }
+
+ }
+
+ @Nested
+ @DisplayName("can delete")
+ public class Deleting {
+ @BeforeEach
+ public void ensureRandomUserExists() throws MailingListClientException {
+
+ addExpectedUser();
+ }
+
+ @Test
+ @DisplayName("members from a list")
+ public void deleteMemberFromList() throws MailingListClientException {
+ mailchimpClient.deleteMemberFromList(listId, expectedUserEmail);
+ }
+ }
+
+ }
+ }
+
+ @Nested
+ @DisplayName("when changing the subscription status of")
+ public class Subscribing {
+
+ @Nested
+ @DisplayName("a new user")
+ public class New {
+
+ @Test
+ @DisplayName("to be subscribed their status should be subscribed")
+ public void subscribeUser() throws MailingListClientException {
+ MailingListMember member = mailchimpClient.subscribeMember(listId, expectedUserEmail);
+ assertThat(member.getSubscriptionStatus()).isEqualTo(SubscriptionStatus.SUBSCRIBED);
+
+ removeExpectedUser();
+ }
+
+ @Test
+ @DisplayName("to be unsubscribed their status should be unsubscribed")
+ public void unsubscribeUser() throws MailingListClientException {
+ MailingListMember member = mailchimpClient.unsubscribeMember(listId, expectedUserEmail);
+ assertThat(member.getSubscriptionStatus()).isEqualTo(SubscriptionStatus.UNSUBSCRIBED);
+
+ removeExpectedUser();
+ }
+
+ }
+
+ @Nested
+ @DisplayName("an existing user")
+ public class Existing {
+
+ @BeforeEach
+ public void ensureUserExists() throws MailingListClientException {
+ addExpectedUser();
+ }
+
+ @AfterEach
+ public void ensureUserRemoved() throws MailingListClientException {
+ removeExpectedUser();
+ }
+
+ @Test
+ @DisplayName("to be force subscribed their status should be subscribed")
+ public void forceSubscribeUser() throws MailingListClientException {
+ MailingListMember member = mailchimpClient.forceSubscribeMember(listId, expectedUserEmail);
+ assertThat(member.getSubscriptionStatus()).isEqualTo(SubscriptionStatus.SUBSCRIBED);
+ }
+
+ @Test
+ @DisplayName("to be force unsubscribed their status should be unsubscribed")
+ public void forceUnsubscribeUser() throws MailingListClientException {
+ MailingListMember member = mailchimpClient.forceUnsubscribeMember(listId, expectedUserEmail);
+ assertThat(member.getSubscriptionStatus()).isEqualTo(SubscriptionStatus.UNSUBSCRIBED);
+ }
+ }
+
+ }
+
+ @Nested
+ @DisplayName("for segment")
+ public class Segments {
+
+
+ @Nested
+ @DisplayName("creation")
+ public class New {
+
+ private String segmentId;
+
+ @AfterEach
+ public void ensureSegmentDeleted() throws MailingListClientException {
+ mailchimpClient.deleteSegment(listId, segmentId);
+ }
+
+ @Test
+ @DisplayName("the segment is created successfully")
+ public void createSegment() throws MailingListClientException {
+ MailingListSegmentDescriptor descriptor = mailchimpClient.createSegment(
+ listId, EXPECTED_SEGMENT_NAME);
+
+ segmentId = descriptor.getId();
+ }
+ }
+
+ @Nested
+ @DisplayName("retrieval")
+ public class Existing {
+
+ private String segmentId;
+
+ @BeforeEach
+ public void ensureSegmentExists() throws MailingListClientException {
+ segmentId = mailchimpClient.createSegment(listId, EXPECTED_SEGMENT_NAME).getId();
+
+ }
+
+ @AfterEach
+ public void ensureSegmentDeleted() throws MailingListClientException {
+ mailchimpClient.deleteSegment(listId, segmentId);
+ }
+
+ @Test
+ @DisplayName("all of the segment descriptors are returned correctly")
+ public void getSegmentDescriptors() throws MailingListClientException {
+ MailingListSegmentDescriptors descriptors = mailchimpClient.getSegmentDescriptors(listId);
+
+ assertThat(descriptors.getSegmentDescriptors())
+ .extracting(NAME_FIELD_NAME, ID_FIELD_NAME)
+ .contains(tuple(EXPECTED_SEGMENT_NAME, segmentId));
+ }
+
+ @Test
+ @DisplayName("the correct segment, by given ID, is returned correctly")
+ public void getSegmentDescriptor() throws MailingListClientException {
+ mailchimpClient.getSegmentDescriptor(listId, segmentId);
+ MailingListSegmentDescriptor descriptor = mailchimpClient.getSegmentDescriptor(listId, segmentId);
+
+ assertThat(descriptor.getId()).isEqualTo(segmentId);
+ assertThat(descriptor.getName()).isEqualTo(EXPECTED_SEGMENT_NAME);
+ }
+ }
+
+ @Nested
+ @DisplayName("members")
+ public class Members {
+
+ MailingListSegmentDescriptor descriptor;
+
+ @BeforeEach
+ public void ensureUserExists() throws MailingListClientException {
+ addExpectedUser();
+
+ descriptor = mailchimpClient.createSegment(listId, EXPECTED_SEGMENT_NAME);
+
+ }
+
+ @AfterEach
+ public void ensureUserRemoved() throws MailingListClientException {
+ removeExpectedUser();
+
+ mailchimpClient.deleteSegment(listId, descriptor.getId());
+ }
+
+ @Nested
+ @DisplayName("which are new")
+ public class New {
+
+ @Test
+ @DisplayName("the members are correctly added to the segment")
+ public void addMembersToSegment() throws MailingListClientException {
+
+ MailingListSegmentMemberChanges changes = mailchimpClient.addMembersToSegment(
+ listId,
+ descriptor.getId(),
+ new HashSet<>(Arrays.asList(expectedUserEmail))
+ );
+
+ assertThat(changes.getAddedMembers())
+ .extracting(EMAIL_ADDRESS_FIELD_NAME)
+ .contains(expectedUserEmail);
+
+ }
+ }
+
+ @Nested
+ @DisplayName("which are existing")
+ public class Existing {
+
+ @BeforeEach
+ public void ensureMemberIsAddedToSegment() throws MailingListClientException {
+ mailchimpClient.addMembersToSegment(
+ listId,
+ descriptor.getId(),
+ new HashSet<>(Arrays.asList(expectedUserEmail))
+ );
+ }
+
+ @Test
+ @DisplayName("they can be retrieved")
+ public void getMembersOfSegment() throws MailingListClientException {
+
+ MailingListMembers members = mailchimpClient.getMembersOfSegment(
+ listId,
+ descriptor.getId()
+ );
+
+ assertThat(members.getMembers())
+ .extracting(EMAIL_ADDRESS_FIELD_NAME)
+ .contains(expectedUserEmail);
+ }
+
+ @Test
+ @DisplayName("they can be removed")
+ public void removeMembersFromSegment() throws MailingListClientException {
+
+ MailingListSegmentMemberChanges changes = mailchimpClient.removeMembersFromSegment(
+ listId,
+ descriptor.getId(),
+ new HashSet<>(Arrays.asList(expectedUserEmail))
+ );
+
+ assertThat(changes.getRemovedMembers())
+ .extracting(EMAIL_ADDRESS_FIELD_NAME)
+ .contains(expectedUserEmail);
+
+ }
+ }
+ }
+
+ }
+
+ private static void addExpectedUser() throws MailingListClientException {
+ mailchimpClient.subscribeMember(listId, expectedUserEmail);
+ }
+
+ private static void removeExpectedUser() throws MailingListClientException {
+ mailchimpClient.deleteMemberFromList(listId, expectedUserEmail);
+ }
+
+
+}
diff --git a/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClientTest.java b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClientTest.java
new file mode 100644
index 0000000..d203ba5
--- /dev/null
+++ b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClientTest.java
@@ -0,0 +1,285 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+import com.google.gson.annotations.SerializedName;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpStatus;
+import org.apache.http.client.methods.*;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.protocol.HttpContext;
+import org.apache.http.util.EntityUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import uk.ac.stfc.facilities.mailing.api.exceptions.MailingListClientException;
+import uk.ac.stfc.facilities.mailing.api.exceptions.NotFoundMailingListClientException;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.concurrent.Callable;
+import java.util.function.Function;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isA;
+import static org.mockito.Mockito.*;
+
+/**
+ *
+ */
+public class MailchimpInternalHttpClientTest {
+
+ public static class TestDto {
+
+ @SerializedName("value")
+ private String value;
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public TestDto(String value) {
+ this.value = value;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ TestDto testDto = (TestDto) o;
+
+ return value != null ? value.equals(testDto.value) : testDto.value == null;
+ }
+
+ @Override
+ public int hashCode() {
+ return value != null ? value.hashCode() : 0;
+ }
+ }
+
+ // dash is required in the api key
+ private static final MailchimpClientConfiguration config = new MailchimpClientConfiguration("abcd-ab1");
+
+ private static final IOException EXPECTED_IO_EXCEPTION_CAUSE = new IOException("test");
+ private static final String TEST_DTO_JSON = "{\"value\":\"hello world\"}";
+ private static final TestDto EXPECTED_TEST_DTO = new TestDto("hello world");
+
+ public static Collection methodProvider() {
+
+ Function getFunction =
+ httpClient -> () -> httpClient.get("", TestDto.class);
+
+ Function putFunction =
+ httpClient -> () -> httpClient.put("", EXPECTED_TEST_DTO, TestDto.class);
+
+ Function postFunction =
+ httpClient -> () -> httpClient.post("", EXPECTED_TEST_DTO, TestDto.class);
+
+ Function deleteFunction =
+ httpClient -> () -> httpClient.delete("", TestDto.class);
+
+ return Arrays.asList(
+ Arguments.of(getFunction, HttpGet.class, false),
+ Arguments.of(putFunction, HttpPut.class, true),
+ Arguments.of(postFunction, HttpPost.class, true),
+ Arguments.of(deleteFunction, HttpDelete.class, false)
+ );
+ }
+
+
+ private CloseableHttpClient mockedHttpClient;
+ private HttpContext mockedHttpContext;
+ private CloseableHttpResponse mockedHttpResponse;
+ private HttpEntity mockedHttpEntity;
+
+ @BeforeEach
+ public void setupHttpMock() {
+ this.mockedHttpClient = mock(CloseableHttpClient.class);
+ this.mockedHttpContext = mock(HttpContext.class);
+ this.mockedHttpResponse = mock(CloseableHttpResponse.class, Mockito.RETURNS_DEEP_STUBS);
+ this.mockedHttpEntity = mock(HttpEntity.class);
+ }
+
+ @ParameterizedTest
+ @MethodSource("methodProvider")
+ public void onRequest_successfulResponse_entitySet(
+ Function> method,
+ Class extends HttpUriRequest> methodType,
+ boolean expectRequestBody
+ ) throws Exception {
+
+ MailchimpInternalHttpClient client = setupResponseWithBody(methodType);
+
+ assertThat(method.apply(client).call()).isEqualTo(EXPECTED_TEST_DTO);
+
+ if (expectRequestBody) {
+ verifyExpectedRequestBody();
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("methodProvider")
+ public void onRequest_successfulResponse_correctResult(
+ Function> method,
+ Class extends HttpUriRequest> methodType
+ ) throws Exception {
+
+ MailchimpInternalHttpClient client = setupResponseWithBody(methodType);
+
+ assertThat(method.apply(client).call()).isEqualTo(EXPECTED_TEST_DTO);
+ }
+
+ @ParameterizedTest
+ @MethodSource("methodProvider")
+ public void onRequest_unexpectedIOException_errorThrown(
+ Function> method,
+ Class extends HttpUriRequest> methodType
+ ) throws Exception {
+
+ MailchimpInternalHttpClient httpClient = setupIOExceptionOnRequestExecution(methodType);
+
+ assertThatThrownBy(method.apply(httpClient)::call)
+ .isInstanceOf(MailingListClientException.class)
+ .hasCause(EXPECTED_IO_EXCEPTION_CAUSE);
+ }
+
+ @ParameterizedTest
+ @MethodSource("methodProvider")
+ public void onRequest_404_errorThrown(
+ Function> method,
+ Class extends HttpUriRequest> methodType
+ ) throws Exception {
+
+ MailchimpInternalHttpClient httpClient = setupResponseWithStatusCode(methodType, HttpStatus.SC_NOT_FOUND);
+
+ assertThatThrownBy(method.apply(httpClient)::call)
+ .isInstanceOf(NotFoundMailingListClientException.class)
+ .hasNoCause();
+ }
+
+ @ParameterizedTest
+ @MethodSource("methodProvider")
+ public void onRequest_unexpectedStatusCode_errorThrown(
+ Function> method,
+ Class extends HttpUriRequest> methodType
+ ) throws Exception {
+
+ MailchimpInternalHttpClient httpClient = setupResponseWithStatusCode(methodType, HttpStatus.SC_INTERNAL_SERVER_ERROR);
+
+ assertThatThrownBy(method.apply(httpClient)::call)
+ .isInstanceOf(MailingListClientException.class)
+ .hasNoCause();
+ }
+
+ @ParameterizedTest
+ @MethodSource("methodProvider")
+ public void onRequest_entityIOException_errorThrown(
+ Function> method,
+ Class extends HttpUriRequest> methodType
+ ) throws Exception {
+
+ MailchimpInternalHttpClient httpClient = setupExceptionWhileRetrievingContent(methodType);
+
+ assertThatThrownBy(method.apply(httpClient)::call)
+ .isInstanceOf(MailingListClientException.class)
+ .hasCause(EXPECTED_IO_EXCEPTION_CAUSE);
+ }
+
+ @ParameterizedTest
+ @MethodSource("methodProvider")
+ public void onRequest_nullEntity_nothingReturned(
+ Function> method,
+ Class extends HttpUriRequest> methodType
+ ) throws Exception {
+
+ MailchimpInternalHttpClient httpClient = setupResponseWithNullEntity(methodType);
+
+ assertThat(method.apply(httpClient).call())
+ .isNull();
+ }
+
+
+ private void verifyExpectedRequestBody() throws IOException {
+ ArgumentCaptor argumentCaptor
+ = ArgumentCaptor.forClass(HttpEntityEnclosingRequestBase.class);
+ verify(mockedHttpClient).execute(argumentCaptor.capture(), eq(mockedHttpContext));
+ HttpEntityEnclosingRequestBase capturedArgument = argumentCaptor.getValue();
+
+ String entityContent = EntityUtils.toString(capturedArgument.getEntity())
+ .replaceAll("\\s", "");
+
+ String expectedContent = TEST_DTO_JSON.replaceAll("\\s", "");
+
+ assertThat(entityContent).isEqualTo(expectedContent);
+ }
+
+ private MailchimpInternalHttpClient setupExceptionWhileRetrievingContent(Class extends HttpUriRequest> requestClass) throws IOException {
+
+ when(mockedHttpResponse.getStatusLine().getStatusCode()).thenReturn(HttpStatus.SC_OK);
+
+ when(mockedHttpResponse.getEntity()).thenReturn(mockedHttpEntity);
+
+ when(mockedHttpEntity.getContentLength()).thenReturn(100L);
+ when(mockedHttpEntity.getContent()).thenThrow(EXPECTED_IO_EXCEPTION_CAUSE);
+
+ when(mockedHttpClient.execute(isA(requestClass), eq(mockedHttpContext)))
+ .thenReturn(mockedHttpResponse);
+
+
+ return new MailchimpInternalHttpClient(config, mockedHttpClient, mockedHttpContext);
+ }
+
+ private MailchimpInternalHttpClient setupResponseWithBody(Class extends HttpUriRequest> requestClass) throws IOException {
+
+ when(mockedHttpResponse.getStatusLine().getStatusCode()).thenReturn(HttpStatus.SC_OK);
+
+ when(mockedHttpResponse.getEntity()).thenReturn(new StringEntity(TEST_DTO_JSON));
+
+ when(mockedHttpClient.execute(isA(requestClass), eq(mockedHttpContext)))
+ .thenReturn(mockedHttpResponse);
+
+
+ return new MailchimpInternalHttpClient(config, mockedHttpClient, mockedHttpContext);
+ }
+
+ private MailchimpInternalHttpClient setupIOExceptionOnRequestExecution(Class extends HttpUriRequest> requestClass) throws IOException {
+
+ when(mockedHttpClient.execute(isA(requestClass), eq(mockedHttpContext)))
+ .thenThrow(EXPECTED_IO_EXCEPTION_CAUSE);
+
+ return new MailchimpInternalHttpClient(config, mockedHttpClient, mockedHttpContext);
+ }
+
+ private MailchimpInternalHttpClient setupResponseWithStatusCode(Class extends HttpUriRequest> requestClass, int statusCode) throws IOException {
+
+ when(mockedHttpResponse.getStatusLine().getStatusCode()).thenReturn(statusCode);
+
+ when(mockedHttpResponse.getEntity()).thenReturn(new StringEntity("hello world"));
+
+ when(mockedHttpClient.execute(isA(requestClass), eq(mockedHttpContext)))
+ .thenReturn(mockedHttpResponse);
+
+ return new MailchimpInternalHttpClient(config, mockedHttpClient, mockedHttpContext);
+ }
+
+ private MailchimpInternalHttpClient setupResponseWithNullEntity(Class extends HttpUriRequest> requestClass) throws IOException {
+
+ when(mockedHttpResponse.getStatusLine().getStatusCode()).thenReturn(HttpStatus.SC_OK);
+ when(mockedHttpResponse.getEntity()).thenReturn(null);
+
+ when(mockedHttpClient.execute(isA(requestClass), eq(mockedHttpContext)))
+ .thenReturn(mockedHttpResponse);
+
+ return new MailchimpInternalHttpClient(config, mockedHttpClient, mockedHttpContext);
+ }
+}
diff --git a/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListMemberRequestTest.java b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListMemberRequestTest.java
new file mode 100644
index 0000000..01113a6
--- /dev/null
+++ b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpListMemberRequestTest.java
@@ -0,0 +1,52 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ *
+ */
+public class MailchimpListMemberRequestTest {
+
+ @Test
+ public void whichSubscribes() {
+ MailchimpListMemberRequest request = MailchimpListMemberRequest.whichSubscribes("test");
+
+ assertThat(request.getEmail()).isEqualTo("test");
+ assertThat(request.getStatusIfNew()).isEqualTo("subscribed");
+ assertThat(request.getStatus()).isNull();
+
+ }
+
+ @Test
+ public void whichUnsubscribes() {
+ MailchimpListMemberRequest request = MailchimpListMemberRequest.whichUnsubscribes("test");
+
+ assertThat(request.getEmail()).isEqualTo("test");
+ assertThat(request.getStatusIfNew()).isEqualTo("unsubscribed");
+ assertThat(request.getStatus()).isNull();
+
+ }
+
+ @Test
+ public void whichForceSubscribes() {
+ MailchimpListMemberRequest request = MailchimpListMemberRequest.whichForceSubscribes("test");
+
+ assertThat(request.getEmail()).isEqualTo("test");
+ assertThat(request.getStatusIfNew()).isEqualTo("subscribed");
+ assertThat(request.getStatus()).isEqualTo("subscribed");
+
+ }
+
+ @Test
+ public void whichForceUnsubscribes() {
+ MailchimpListMemberRequest request = MailchimpListMemberRequest.whichForceUnsubscribes("test");
+
+ assertThat(request.getEmail()).isEqualTo("test");
+ assertThat(request.getStatusIfNew()).isEqualTo("unsubscribed");
+ assertThat(request.getStatus()).isEqualTo("unsubscribed");
+
+ }
+}
diff --git a/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentCreateRequestTest.java b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentCreateRequestTest.java
new file mode 100644
index 0000000..8c73cee
--- /dev/null
+++ b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentCreateRequestTest.java
@@ -0,0 +1,20 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ *
+ */
+public class MailchimpSegmentCreateRequestTest {
+
+ @Test
+ public void whichCreatesASegment() {
+ MailchimpSegmentCreateRequest request = MailchimpSegmentCreateRequest.whichCreatesASegment("test");
+
+ assertThat(request.getName()).isEqualTo("test");
+ }
+
+}
diff --git a/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentMemberRequestTest.java b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentMemberRequestTest.java
new file mode 100644
index 0000000..2eb16b9
--- /dev/null
+++ b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpSegmentMemberRequestTest.java
@@ -0,0 +1,37 @@
+package uk.ac.stfc.facilities.mailing.mailchimp;
+
+
+import org.junit.jupiter.api.Test;
+
+import java.util.Arrays;
+import java.util.HashSet;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ *
+ */
+public class MailchimpSegmentMemberRequestTest {
+
+
+ @Test
+ public void whichAddsMembers() {
+ MailchimpSegmentMemberRequest request = MailchimpSegmentMemberRequest.whichAddsMembers(
+ new HashSet<>(Arrays.asList("test", "email"))
+ );
+
+ assertThat(request.getMembersToAdd()).contains("test", "email");
+ assertThat(request.getMembersToRemove()).isNullOrEmpty();
+ }
+
+ @Test
+ public void whichRemovesMembers() {
+ MailchimpSegmentMemberRequest request = MailchimpSegmentMemberRequest.whichRemovesMembers(
+ new HashSet<>(Arrays.asList("test", "email"))
+ );
+
+ assertThat(request.getMembersToRemove()).contains("test", "email");
+ assertThat(request.getMembersToAdd()).isNullOrEmpty();
+ }
+
+}
From eb8635f80d56a11378ff5b27acb64557f9324f16 Mon Sep 17 00:00:00 2001
From: Thomas 'Panda' Attwood | TickleThePanda
Date: Tue, 12 Sep 2017 18:18:21 +0100
Subject: [PATCH 02/11] Add Javadoc to the webhook example
---
.../mailing/spring/Application.java | 2 --
.../spring/MailchimpWebhookController.java | 20 +++++++++++++++++--
2 files changed, 18 insertions(+), 4 deletions(-)
diff --git a/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/Application.java b/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/Application.java
index d6b1b92..cb9c4c8 100644
--- a/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/Application.java
+++ b/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/Application.java
@@ -1,11 +1,9 @@
package uk.ac.stfc.facilities.mailing.spring;
import org.springframework.boot.SpringApplication;
-import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
-@EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
diff --git a/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/MailchimpWebhookController.java b/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/MailchimpWebhookController.java
index dd01281..dbb9805 100644
--- a/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/MailchimpWebhookController.java
+++ b/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/MailchimpWebhookController.java
@@ -5,18 +5,34 @@
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
+/**
+ * A Spring controller that listens to webhooks from Mailchimp, it
+ * currently handles the webhook handshake and updates to the users
+ * email. It refers to the mailing.webhook.key
key in
+ * config to create the mapping.
+ */
@Controller
@RequestMapping("/${mailing.webhook.key}")
public class MailchimpWebhookController {
private static final Logger LOGGER = LogManager.getLogger();
- @GetMapping()
+ /**
+ * A simple method to respond with a successful status which is
+ * required for Mailchimp to accept a webhook client.
+ */
+ @GetMapping
@ResponseBody
public void handshake() {
}
- @PostMapping()
+ /**
+ * A webhook that listens to changes to a user's email
+ * address change.
+ * @param oldEmail the old email for the user
+ * @param newEmail the new email for the user
+ */
+ @PostMapping
@ResponseBody
public void hook(
@RequestParam("data[old_email]") String oldEmail,
From aae696a894b6578ec3e9ee91518a38488004e1f3 Mon Sep 17 00:00:00 2001
From: Thomas 'Panda' Attwood | TickleThePanda
Date: Tue, 12 Sep 2017 18:19:56 +0100
Subject: [PATCH 03/11] Fix grammatical typo
---
.../uk/ac/stfc/facilities/mailing/api/MailingListClient.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/MailingListClient.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/MailingListClient.java
index 6490110..492a800 100644
--- a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/MailingListClient.java
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/MailingListClient.java
@@ -6,7 +6,7 @@
import java.util.Set;
/**
- * An client for mailing lists service with the following functionality:
+ * A client for mailing lists service with the following functionality:
*
* - retrieving lists & list members
* - managing members' subscriptions to lists
From 62117c7849be355ba824418a78b02c2e68ee3a0c Mon Sep 17 00:00:00 2001
From: Thomas 'Panda' Attwood | TickleThePanda
Date: Thu, 14 Sep 2017 09:51:52 +0100
Subject: [PATCH 04/11] make mailchimp placeholder user more clear
---
.../mailchimp/MailchimpInternalHttpClient.java | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClient.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClient.java
index 1e5128c..86eb60c 100644
--- a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClient.java
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClient.java
@@ -35,12 +35,22 @@
*/
class MailchimpInternalHttpClient {
+ /**
+ * The Mailchimp username for basic authentication doesn't matter,
+ * as long as some value is provided, it will be accepted. In this
+ * situation, “user” has been used as a placeholder.
+ */
+ private static final String MAILCHIMP_USERNAME_PLACEHOLDER = "user";
+
private static final Logger LOG = LogManager.getLogger();
public static MailchimpInternalHttpClient getInstance(MailchimpClientConfiguration configuration) {
CredentialsProvider provider = new BasicCredentialsProvider();
- Credentials credentials = new UsernamePasswordCredentials("user", configuration.getApiKey());
+ Credentials credentials = new UsernamePasswordCredentials(
+ MAILCHIMP_USERNAME_PLACEHOLDER,
+ configuration.getApiKey()
+ );
provider.setCredentials(AuthScope.ANY, credentials);
AuthCache authCache = new BasicAuthCache();
From 22641345ba4179b9e37ac39f35d60eb740aebef9 Mon Sep 17 00:00:00 2001
From: Thomas 'Panda' Attwood | TickleThePanda
Date: Thu, 14 Sep 2017 11:50:26 +0100
Subject: [PATCH 05/11] improve documentation for the internal mailchimp client
---
.../MailchimpInternalHttpClient.java | 91 +++++++++++++++++--
1 file changed, 81 insertions(+), 10 deletions(-)
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClient.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClient.java
index 86eb60c..921e494 100644
--- a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClient.java
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClient.java
@@ -31,7 +31,9 @@
import java.time.LocalDateTime;
/**
- *
+ * An internal client that handles the requests to Mailchimp, dealing
+ * with the conversion to and from the requests and responses from
+ * the DTOs.
*/
class MailchimpInternalHttpClient {
@@ -44,7 +46,13 @@ class MailchimpInternalHttpClient {
private static final Logger LOG = LogManager.getLogger();
- public static MailchimpInternalHttpClient getInstance(MailchimpClientConfiguration configuration) {
+ /**
+ * Used to create an instance of the internal client.
+ *
+ * @param configuration the configuration for the Mailchimp client.
+ * @return a new instance of the internal HTTP client
+ */
+ static MailchimpInternalHttpClient getInstance(MailchimpClientConfiguration configuration) {
CredentialsProvider provider = new BasicCredentialsProvider();
Credentials credentials = new UsernamePasswordCredentials(
@@ -75,6 +83,10 @@ public static MailchimpInternalHttpClient getInstance(MailchimpClientConfigurati
private final MailchimpClientConfiguration configuration;
+ /**
+ * This should only be used within the internal client.
+ */
+ @Deprecated
MailchimpInternalHttpClient(
MailchimpClientConfiguration configuration,
CloseableHttpClient client,
@@ -84,28 +96,87 @@ public static MailchimpInternalHttpClient getInstance(MailchimpClientConfigurati
this.context = context;
}
- T delete(String uri, Class responseClass) throws MailingListClientException {
- HttpDelete request = new HttpDelete(withBaseUrl(uri));
+ /**
+ * Completes a get request for a resource, converts the response to
+ * the given type.
+ *
+ * @param uri the location of the resource that the
+ * request will get from. This is relative to
+ * the base URL for Mailchimp.
+ * @param responseClass the class of the response.
+ * @param the type of the response, taken from the
+ * given class.
+ * @return the response object
+ * @throws MailingListClientException if the resource is unavailable
+ */
+ T get(String uri, Class responseClass) throws MailingListClientException {
+ HttpGet request = new HttpGet(withBaseUrl(uri));
return doRequest(request, responseClass);
}
- T put(String uri, Object body, Class responseClass) throws MailingListClientException {
- HttpPut request = new HttpPut(withBaseUrl(uri));
+ /**
+ * Completes a post request for a resource, converts the given
+ * request body to JSON and the response to the given type.
+ *
+ * @param uri the location of the resource that the
+ * request will post to. This is relative to
+ * the base URL for Mailchimp.
+ * @param body the object for the body, which will be
+ * converted to JSON.
+ * @param responseClass the class of the response.
+ * @param the type of the response, taken from the
+ * given class.
+ * @return the response body converted from JSON to the correct
+ * type.
+ * @throws MailingListClientException if the resource is unavailable.
+ */
+ T post(String uri, Object body, Class responseClass) throws MailingListClientException {
+ HttpPost request = new HttpPost(withBaseUrl(uri));
request.setEntity(createEntity(body));
return doRequest(request, responseClass);
}
- T post(String uri, Object body, Class responseClass) throws MailingListClientException {
- HttpPost request = new HttpPost(withBaseUrl(uri));
+ /**
+ * Completes a put request for a resource, converts the given
+ * request body to JSON and the response to the given type.
+ *
+ * @param uri the location of the resource that the
+ * request will put to. This is relative to
+ * the base URL for Mailchimp.
+ * @param body the object for the body, which will be
+ * converted to JSON.
+ * @param responseClass the class of the response.
+ * @param the type of the response, taken from the
+ * given class.
+ * @return the response body converted from JSON to the correct
+ * type.
+ * @throws MailingListClientException if the resource is unavailable.
+ */
+ T put(String uri, Object body, Class responseClass) throws MailingListClientException {
+ HttpPut request = new HttpPut(withBaseUrl(uri));
request.setEntity(createEntity(body));
return doRequest(request, responseClass);
}
- T get(String uri, Class responseClass) throws MailingListClientException {
- HttpGet request = new HttpGet(withBaseUrl(uri));
+ /**
+ * Completes a delete request for a resource, converts the response
+ * to the given type.
+ *
+ * @param uri the location of the resource that the
+ * request will delete. This is relative to
+ * the base URL for Mailchimp.
+ * @param responseClass the class of the response
+ * @param the type of the response, taken from the
+ * given class
+ * @return the response body converted from JSON to the correct
+ * type.
+ * @throws MailingListClientException if the resource is unavailable.
+ */
+ T delete(String uri, Class responseClass) throws MailingListClientException {
+ HttpDelete request = new HttpDelete(withBaseUrl(uri));
return doRequest(request, responseClass);
}
From 21f03c802828c947b3bb3f5ac8b096933aa61287 Mon Sep 17 00:00:00 2001
From: Thomas 'Panda' Attwood | TickleThePanda
Date: Thu, 14 Sep 2017 11:53:49 +0100
Subject: [PATCH 06/11] improve name of example config for tests
---
.../mailchimp/MailchimpInternalHttpClientTest.java | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClientTest.java b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClientTest.java
index d203ba5..e50db1b 100644
--- a/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClientTest.java
+++ b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClientTest.java
@@ -68,7 +68,8 @@ public int hashCode() {
}
// dash is required in the api key
- private static final MailchimpClientConfiguration config = new MailchimpClientConfiguration("abcd-ab1");
+ private static final MailchimpClientConfiguration EXAMPLE_CONFIGURATION =
+ new MailchimpClientConfiguration("abcd-ab1");
private static final IOException EXPECTED_IO_EXCEPTION_CAUSE = new IOException("test");
private static final String TEST_DTO_JSON = "{\"value\":\"hello world\"}";
@@ -236,7 +237,7 @@ private MailchimpInternalHttpClient setupExceptionWhileRetrievingContent(Class
.thenReturn(mockedHttpResponse);
- return new MailchimpInternalHttpClient(config, mockedHttpClient, mockedHttpContext);
+ return new MailchimpInternalHttpClient(EXAMPLE_CONFIGURATION, mockedHttpClient, mockedHttpContext);
}
private MailchimpInternalHttpClient setupResponseWithBody(Class extends HttpUriRequest> requestClass) throws IOException {
@@ -249,7 +250,7 @@ private MailchimpInternalHttpClient setupResponseWithBody(Class extends HttpUr
.thenReturn(mockedHttpResponse);
- return new MailchimpInternalHttpClient(config, mockedHttpClient, mockedHttpContext);
+ return new MailchimpInternalHttpClient(EXAMPLE_CONFIGURATION, mockedHttpClient, mockedHttpContext);
}
private MailchimpInternalHttpClient setupIOExceptionOnRequestExecution(Class extends HttpUriRequest> requestClass) throws IOException {
@@ -257,7 +258,7 @@ private MailchimpInternalHttpClient setupIOExceptionOnRequestExecution(Class e
when(mockedHttpClient.execute(isA(requestClass), eq(mockedHttpContext)))
.thenThrow(EXPECTED_IO_EXCEPTION_CAUSE);
- return new MailchimpInternalHttpClient(config, mockedHttpClient, mockedHttpContext);
+ return new MailchimpInternalHttpClient(EXAMPLE_CONFIGURATION, mockedHttpClient, mockedHttpContext);
}
private MailchimpInternalHttpClient setupResponseWithStatusCode(Class extends HttpUriRequest> requestClass, int statusCode) throws IOException {
@@ -269,7 +270,7 @@ private MailchimpInternalHttpClient setupResponseWithStatusCode(Class extends
when(mockedHttpClient.execute(isA(requestClass), eq(mockedHttpContext)))
.thenReturn(mockedHttpResponse);
- return new MailchimpInternalHttpClient(config, mockedHttpClient, mockedHttpContext);
+ return new MailchimpInternalHttpClient(EXAMPLE_CONFIGURATION, mockedHttpClient, mockedHttpContext);
}
private MailchimpInternalHttpClient setupResponseWithNullEntity(Class extends HttpUriRequest> requestClass) throws IOException {
@@ -280,6 +281,6 @@ private MailchimpInternalHttpClient setupResponseWithNullEntity(Class extends
when(mockedHttpClient.execute(isA(requestClass), eq(mockedHttpContext)))
.thenReturn(mockedHttpResponse);
- return new MailchimpInternalHttpClient(config, mockedHttpClient, mockedHttpContext);
+ return new MailchimpInternalHttpClient(EXAMPLE_CONFIGURATION, mockedHttpClient, mockedHttpContext);
}
}
From 719bf743c607b12bfdf58b3b3abaa20924d98bdb Mon Sep 17 00:00:00 2001
From: Thomas 'Panda' Attwood | TickleThePanda
Date: Thu, 14 Sep 2017 11:56:59 +0100
Subject: [PATCH 07/11] use correct annotation for simple response
---
.../facilities/mailing/spring/MailchimpWebhookController.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/MailchimpWebhookController.java b/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/MailchimpWebhookController.java
index dbb9805..2dd7200 100644
--- a/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/MailchimpWebhookController.java
+++ b/mailchimp-webhooks/src/main/java/uk/ac/stfc/facilities/mailing/spring/MailchimpWebhookController.java
@@ -22,7 +22,7 @@ public class MailchimpWebhookController {
* required for Mailchimp to accept a webhook client.
*/
@GetMapping
- @ResponseBody
+ @ResponseStatus(value = HttpStatus.OK)
public void handshake() {
}
From 47c31d7bed6d85b6246b8e8798da9a2ba4cb709c Mon Sep 17 00:00:00 2001
From: Thomas 'Panda' Attwood | TickleThePanda
Date: Fri, 15 Sep 2017 08:33:31 +0100
Subject: [PATCH 08/11] add IT for deleting non-existant member from list
---
.../MailchimpInternalHttpClient.java | 51 +++++++++++--------
.../mailing/mailchimp/MailchimpClientIT.java | 36 ++++++++++---
2 files changed, 58 insertions(+), 29 deletions(-)
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClient.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClient.java
index 921e494..d4b5811 100644
--- a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClient.java
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpInternalHttpClient.java
@@ -211,28 +211,37 @@ private void manageResponseStatus(
HttpUriRequest request,
HttpResponse response) throws IOException, MailingListClientException {
- switch (response.getStatusLine().getStatusCode()) {
- case HttpStatus.SC_OK:
- case HttpStatus.SC_CREATED:
- case HttpStatus.SC_ACCEPTED:
- case HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION:
- case HttpStatus.SC_NO_CONTENT:
- case HttpStatus.SC_RESET_CONTENT:
- case HttpStatus.SC_PARTIAL_CONTENT:
- case HttpStatus.SC_MULTI_STATUS:
- return;
- case HttpStatus.SC_NOT_FOUND:
- throw new NotFoundMailingListClientException(
- "Could not find resource for " + request.getURI().toString());
- default:
- String errorMessage = String.format("failed %s request for %s with %s and body \"%s\"",
- request.getMethod(),
- request.getURI(),
- response.getStatusLine(),
- EntityUtils.toString(response.getEntity()));
-
- throw new MailingListClientException(errorMessage);
+ try {
+
+ switch (response.getStatusLine().getStatusCode()) {
+ case HttpStatus.SC_OK:
+ case HttpStatus.SC_CREATED:
+ case HttpStatus.SC_ACCEPTED:
+ case HttpStatus.SC_NON_AUTHORITATIVE_INFORMATION:
+ case HttpStatus.SC_NO_CONTENT:
+ case HttpStatus.SC_RESET_CONTENT:
+ case HttpStatus.SC_PARTIAL_CONTENT:
+ case HttpStatus.SC_MULTI_STATUS:
+ return;
+ case HttpStatus.SC_NOT_FOUND:
+ String notFoundErrorMessage =
+ String.format("Could not find resource for %s. The response body was: \"%s\"",
+ request.getURI(),
+ EntityUtils.toString(response.getEntity()));
+ throw new NotFoundMailingListClientException(notFoundErrorMessage);
+ default:
+ String unexpectedErrorMessage = String.format("failed %s request for %s with %s and body \"%s\"",
+ request.getMethod(),
+ request.getURI(),
+ response.getStatusLine(),
+ EntityUtils.toString(response.getEntity()));
+
+ throw new MailingListClientException(unexpectedErrorMessage);
+ }
+ } catch (MailingListClientException e) {
+ EntityUtils.consumeQuietly(response.getEntity());
+ throw e;
}
}
diff --git a/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientIT.java b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientIT.java
index 562504d..b84f1c7 100644
--- a/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientIT.java
+++ b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientIT.java
@@ -154,21 +154,41 @@ public void getMemberOfList_notFound() throws MailingListClientException {
}
@Nested
- @DisplayName("can delete")
+ @DisplayName("when deleting")
public class Deleting {
- @BeforeEach
- public void ensureRandomUserExists() throws MailingListClientException {
- addExpectedUser();
+ @Nested
+ @DisplayName("can delete")
+ public class Successfully {
+
+ @BeforeEach
+ public void ensureRandomUserExists() throws MailingListClientException {
+
+ addExpectedUser();
+ }
+
+ @Test
+ @DisplayName("members from a list")
+ public void deleteMemberFromList() throws MailingListClientException {
+ mailchimpClient.deleteMemberFromList(listId, expectedUserEmail);
+ }
}
- @Test
- @DisplayName("members from a list")
- public void deleteMemberFromList() throws MailingListClientException {
- mailchimpClient.deleteMemberFromList(listId, expectedUserEmail);
+ @Nested
+ @DisplayName("a non-existant member")
+ public class NonExistantMember {
+
+ @Test
+ @DisplayName("can't be deleted")
+ public void deleteMemberFromList() throws MailingListClientException {
+ assertThatThrownBy(() -> mailchimpClient.deleteMemberFromList(listId, expectedUserEmail))
+ .isInstanceOf(NotFoundMailingListClientException.class);
+ }
}
}
+
+
}
}
From 49f972603804ec7a5abd8bb2135736cef979980e Mon Sep 17 00:00:00 2001
From: Thomas 'Panda' Attwood | TickleThePanda
Date: Fri, 15 Sep 2017 08:34:44 +0100
Subject: [PATCH 09/11] throw exception in IT setup when required keys are
missing
---
.../mailing/mailchimp/MailchimpClientIT.java | 23 +++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientIT.java b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientIT.java
index b84f1c7..fa6219e 100644
--- a/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientIT.java
+++ b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientIT.java
@@ -44,6 +44,20 @@ public class MailchimpClientIT {
private static MailchimpClient mailchimpClient;
+ private static String loadProperty(Properties properties, String key) {
+ final String EXCEPTION_MESSAGE = ""
+ + "\"{}\" was not specified in the application.test.properties file. This key is required to run these"
+ + " tests. This file should be placed in the directory that the tests are running from.";
+
+ String value = properties.getProperty(key);
+
+ if (value == null || value.isEmpty()) {
+ throw new RuntimeException(EXCEPTION_MESSAGE.replace("{}", key));
+ }
+
+ return value;
+ }
+
@BeforeAll
public static void setupClass() throws IOException {
Properties properties = new Properties();
@@ -52,14 +66,15 @@ public static void setupClass() throws IOException {
properties.load(new FileInputStream(file));
- String mailchimpApiKey = properties.getProperty("mailchimp.api.key");
+ String mailchimpApiKey = loadProperty(properties, "mailchimp.api.key");
mailchimpClient = MailchimpClient.getInstance(new MailchimpClientConfiguration(mailchimpApiKey));
- listId = properties.getProperty("mailchimp.list.id");
- listName = properties.getProperty("mailchimp.list.name");
+ listId = loadProperty(properties, "mailchimp.list.id");
+
+ listName = loadProperty(properties, "mailchimp.list.name");
- testDomain = properties.getProperty("mailchimp.test.domain");
+ testDomain = loadProperty(properties, "mailchimp.test.domain");
permenantEmail1 = "permenant.test.1@" + testDomain;
permenantEmail2 = "permenant.test.2@" + testDomain;
From 6daa6d0963db23b5a8617e122d798ea9437b43ad Mon Sep 17 00:00:00 2001
From: Thomas 'Panda' Attwood | TickleThePanda
Date: Fri, 15 Sep 2017 08:36:29 +0100
Subject: [PATCH 10/11] fix spelling of permanent
---
.../application.test.properties | 2 +-
.../mailing/mailchimp/MailchimpClientIT.java | 24 +++++++++----------
2 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/mailing-list-client/application.test.properties b/mailing-list-client/application.test.properties
index 7da8245..d028f91 100644
--- a/mailing-list-client/application.test.properties
+++ b/mailing-list-client/application.test.properties
@@ -13,5 +13,5 @@ mailchimp.list.name=
# existing mailing list with the following statuses:
# - permanent.test.1@ - subscribed
# - permanent.test.2@ - subscribed
-# - premenant.test.3@ - subscribed
+# - permanent.test.3@ - subscribed
mailchimp.test.domain=
diff --git a/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientIT.java b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientIT.java
index fa6219e..ec430a5 100644
--- a/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientIT.java
+++ b/mailing-list-client/src/test/java/uk/ac/stfc/facilities/mailing/mailchimp/MailchimpClientIT.java
@@ -25,9 +25,9 @@ public class MailchimpClientIT {
private static final String EXPECTED_SEGMENT_NAME = "test";
- private static final SubscriptionStatus PERMENANT_SUBSCRIPTION_STATUS_1 = SubscriptionStatus.SUBSCRIBED;
- private static final SubscriptionStatus PERMENANT_SUBSCRIPTION_STATUS_2 = SubscriptionStatus.SUBSCRIBED;
- private static final SubscriptionStatus PERMENANT_SUBSCRIPTION_STATUS_3 = SubscriptionStatus.UNSUBSCRIBED;
+ private static final SubscriptionStatus PERMANENT_SUBSCRIPTION_STATUS_1 = SubscriptionStatus.SUBSCRIBED;
+ private static final SubscriptionStatus PERMANENT_SUBSCRIPTION_STATUS_2 = SubscriptionStatus.SUBSCRIBED;
+ private static final SubscriptionStatus PERMANENT_SUBSCRIPTION_STATUS_3 = SubscriptionStatus.UNSUBSCRIBED;
private static String expectedUserEmail;
@@ -38,9 +38,9 @@ public class MailchimpClientIT {
private static String noSuchMemberEmail;
- private static String permenantEmail1;
- private static String permenantEmail2;
- private static String permenantEmail3;
+ private static String permanentEmail1;
+ private static String permanentEmail2;
+ private static String permanentEmail3;
private static MailchimpClient mailchimpClient;
@@ -76,9 +76,9 @@ public static void setupClass() throws IOException {
testDomain = loadProperty(properties, "mailchimp.test.domain");
- permenantEmail1 = "permenant.test.1@" + testDomain;
- permenantEmail2 = "permenant.test.2@" + testDomain;
- permenantEmail3 = "permenant.test.3@" + testDomain;
+ permanentEmail1 = "permanent.test.1@" + testDomain;
+ permanentEmail2 = "permanent.test.2@" + testDomain;
+ permanentEmail3 = "permanent.test.3@" + testDomain;
noSuchMemberEmail = "no.such.member@" + testDomain;
@@ -144,9 +144,9 @@ public void getMembersByList() throws MailingListClientException {
assertThat(mailingListMembers.getMembers())
.extracting(EMAIL_ADDRESS_FIELD_NAME, SUBSCRIPTION_STATUS_FIELD_NAME)
.contains(
- tuple(permenantEmail1, PERMENANT_SUBSCRIPTION_STATUS_1),
- tuple(permenantEmail2, PERMENANT_SUBSCRIPTION_STATUS_2),
- tuple(permenantEmail3, PERMENANT_SUBSCRIPTION_STATUS_3)
+ tuple(permanentEmail1, PERMANENT_SUBSCRIPTION_STATUS_1),
+ tuple(permanentEmail2, PERMANENT_SUBSCRIPTION_STATUS_2),
+ tuple(permanentEmail3, PERMANENT_SUBSCRIPTION_STATUS_3)
);
}
From 643b80c0eebc30988722a111b62e3125c334c28f Mon Sep 17 00:00:00 2001
From: Thomas 'Panda' Attwood | TickleThePanda
Date: Fri, 15 Sep 2017 08:41:21 +0100
Subject: [PATCH 11/11] add documentation to highlight not found exceptions
---
.../mailing/api/MailingListClient.java | 105 +++++++++++++++---
1 file changed, 89 insertions(+), 16 deletions(-)
diff --git a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/MailingListClient.java b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/MailingListClient.java
index 492a800..d6a8184 100644
--- a/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/MailingListClient.java
+++ b/mailing-list-client/src/main/java/uk/ac/stfc/facilities/mailing/api/MailingListClient.java
@@ -2,6 +2,7 @@
import uk.ac.stfc.facilities.mailing.api.data.*;
import uk.ac.stfc.facilities.mailing.api.exceptions.MailingListClientException;
+import uk.ac.stfc.facilities.mailing.api.exceptions.NotFoundMailingListClientException;
import java.util.Set;
@@ -19,6 +20,7 @@ public interface MailingListClient {
* Retrieves all lists.
*
* @return the retrieved lists
+ *
* @throws MailingListClientException if the resource is unavailable
*/
MailingListDescriptors getAllListDescriptors()
@@ -28,8 +30,12 @@ MailingListDescriptors getAllListDescriptors()
* Retrieves the list with the given list ID.
*
* @param listId the ID of the list to retrieve
+ *
* @return the list with the given list ID
- * @throws MailingListClientException if the resource is unavailable
+ *
+ * @throws MailingListClientException if the resource is unavailable
+ * @throws NotFoundMailingListClientException if the list with the given ID
+ * does not exist
*/
MailingListDescriptor getListDescriptor(String listId)
throws MailingListClientException;
@@ -39,8 +45,12 @@ MailingListDescriptor getListDescriptor(String listId)
*
* @param listId the ID of the list from which the members are
* retrieved
+ *
* @return all members of the list by the given list ID
- * @throws MailingListClientException if the resource is unavailable
+ *
+ * @throws MailingListClientException if the resource is unavailable
+ * @throws NotFoundMailingListClientException if the list with the given ID
+ * does not exist
*/
MailingListMembers getMembersByList(String listId)
throws MailingListClientException;
@@ -51,8 +61,13 @@ MailingListMembers getMembersByList(String listId)
* @param listId the ID of the list from which the members is
* retrieved
* @param email the email of the member to retrieve
+ *
* @return the member of the list with the given email
- * @throws MailingListClientException if the resource is unavailable
+ *
+ * @throws MailingListClientException if the resource is unavailable
+ * @throws NotFoundMailingListClientException if the list with the ID does
+ * not exist or the member of the
+ * list does not exist
*/
MailingListMember getMemberOfList(String listId, String email)
throws MailingListClientException;
@@ -67,9 +82,13 @@ MailingListMember getMemberOfList(String listId, String email)
* @param listId the ID of the list to which the member will be
* subscribed
* @param email the email of the member which will be subscribed
+ *
* @return the member of the list for which the subscription was
* requested
- * @throws MailingListClientException if the resource is unavailable
+ *
+ * @throws MailingListClientException if the resource is unavailable
+ * @throws NotFoundMailingListClientException if the list with the given ID
+ * does not exist
*/
MailingListMember subscribeMember(String listId, String email)
throws MailingListClientException;
@@ -84,9 +103,13 @@ MailingListMember subscribeMember(String listId, String email)
* @param listId the ID of the list from which the member will be
* unsubscribed
* @param email the email of the member which will be unsubscribed
+ *
* @return the member of the list for which the unsubscription was
* requested
- * @throws MailingListClientException if the resource is unavailable
+ *
+ * @throws MailingListClientException if the resource is unavailable
+ * @throws NotFoundMailingListClientException if the list with the given ID
+ * does not exist
*/
MailingListMember unsubscribeMember(String listId, String email)
throws MailingListClientException;
@@ -99,9 +122,13 @@ MailingListMember unsubscribeMember(String listId, String email)
* @param listId the ID of the list to which the member will be
* subscribed
* @param email the email of the member which will be subscribed
+ *
* @return the member of the list for which the subcription was
* requested
- * @throws MailingListClientException if the resource is unavailable
+ *
+ * @throws MailingListClientException if the resource is unavailable
+ * @throws NotFoundMailingListClientException if the list with the given ID
+ * does not exist
*/
MailingListMember forceSubscribeMember(String listId, String email)
throws MailingListClientException;
@@ -114,9 +141,13 @@ MailingListMember forceSubscribeMember(String listId, String email)
* @param listId the ID of the list from which the member will
* be unsubcribed
* @param email the email of the member which will be unsubscribed
+ *
* @return the member of the list for which the unsubscription was
* requested
- * @throws MailingListClientException if the resource is unavailable
+ *
+ * @throws MailingListClientException if the resource is unavailable
+ * @throws NotFoundMailingListClientException if the list with the given ID
+ * does not exist
*/
MailingListMember forceUnsubscribeMember(String listId, String email)
throws MailingListClientException;
@@ -129,6 +160,11 @@ MailingListMember forceUnsubscribeMember(String listId, String email)
* @param listId the ID of the list from which the member will be
* removed from
* @param email the email of the member who will be removed
+ *
+ * @throws MailingListClientException if the resource is unavailable
+ * @throws NotFoundMailingListClientException if the list with the given ID
+ * does not exist or the member
+ * of the list does not exist
*/
void deleteMemberFromList(String listId, String email)
throws MailingListClientException;
@@ -138,8 +174,12 @@ void deleteMemberFromList(String listId, String email)
*
* @param listId the ID of the list from which to retrieve the
* segment
+ *
* @return the segments of the list with the given ID
- * @throws MailingListClientException if the resource is unavailable
+ *
+ * @throws MailingListClientException if the resource is unavailable
+ * @throws NotFoundMailingListClientException if the list with the given ID
+ * does not exist
*/
MailingListSegmentDescriptors getSegmentDescriptors(String listId)
throws MailingListClientException;
@@ -150,8 +190,14 @@ MailingListSegmentDescriptors getSegmentDescriptors(String listId)
* @param listId the ID of the list from which to retrieve the
* segment
* @param segmentId the ID of the segment to retrieve
+ *
* @return the segment of the list with the given ID
- * @throws MailingListClientException if the resource is unavailable
+ *
+ * @throws MailingListClientException if the resource is unavailable
+ * @throws NotFoundMailingListClientException if the list with the given ID
+ * does not exist or if the
+ * segment with the given ID does
+ * not exist
*/
MailingListSegmentDescriptor getSegmentDescriptor(String listId, String segmentId)
throws MailingListClientException;
@@ -164,8 +210,14 @@ MailingListSegmentDescriptor getSegmentDescriptor(String listId, String segmentI
* segment members
* @param segmentId the ID of the segment to retrieve the segment
* members
+ *
* @return the members for the segment with the given ID
- * @throws MailingListClientException if the resource is unavailable
+ *
+ * @throws MailingListClientException if the resource is unavailable
+ * @throws NotFoundMailingListClientException if the list with the given ID
+ * does not exist or if the
+ * segment with the given ID does
+ * not exist
*/
MailingListMembers getMembersOfSegment(String listId, String segmentId)
throws MailingListClientException;
@@ -178,8 +230,14 @@ MailingListMembers getMembersOfSegment(String listId, String segmentId)
* @param listId the ID of the list in which the segment is
* @param segmentId the ID of the segment to add the members to
* @param emails the emails of the members to add
+ *
* @return the successfully added members
- * @throws MailingListClientException if the resource is unavailable
+ *
+ * @throws MailingListClientException if the resource is unavailable
+ * @throws NotFoundMailingListClientException if the list with the given ID
+ * does not exist or if the
+ * segment with the given ID does
+ * not exist
*/
MailingListSegmentMemberChanges addMembersToSegment(String listId, String segmentId, Set emails)
throws MailingListClientException;
@@ -191,8 +249,14 @@ MailingListSegmentMemberChanges addMembersToSegment(String listId, String segmen
* @param listId the ID of the list in which the segment is
* @param segmentId the ID of the segment to remove the members from
* @param emails the emails of the members to remove
+ *
* @return the successfully removed members
- * @throws MailingListClientException if the resource is unavailable
+ *
+ * @throws MailingListClientException if the resource is unavailable
+ * @throws NotFoundMailingListClientException if the list with given ID does
+ * not exist or if the segment
+ * with the given ID does not
+ * exist
*/
MailingListSegmentMemberChanges removeMembersFromSegment(String listId, String segmentId, Set emails)
throws MailingListClientException;
@@ -203,8 +267,12 @@ MailingListSegmentMemberChanges removeMembersFromSegment(String listId, String s
* @param listId the ID of the list in which to create the
* segment
* @param segmentName the name of the segment
+ *
* @return the descriptor of the segment
- * @throws MailingListClientException if the resource is unavailable
+ *
+ * @throws MailingListClientException if the resource is unavailable
+ * @throws NotFoundMailingListClientException if the list with the given ID
+ * does not exist
*/
MailingListSegmentDescriptor createSegment(String listId, String segmentName)
throws MailingListClientException;
@@ -212,9 +280,14 @@ MailingListSegmentDescriptor createSegment(String listId, String segmentName)
/**
* Deletes the segment with the given ID in the given list.
*
- * @param listId this ID of the list in which to create the segment
- * @param segmentId the ID of the segment
- * @throws MailingListClientException if the resource is unavailable
+ * @param listId this ID of the list in which to create the segment
+ * @param segmentId the ID of the segment
+ *
+ * @throws MailingListClientException if the resource is unavailable
+ * @throws NotFoundMailingListClientException if the list with the given ID
+ * does not exist or if the
+ * segment with the given ID does
+ * not exist
*/
void deleteSegment(String listId, String segmentId)
throws MailingListClientException;