From 6d987e0dd18ef830484641166a816661f4b16074 Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Sun, 27 Mar 2016 08:02:14 -0700 Subject: Adding the update_individual API --- .../com/p4square/ccbapi/ApacheHttpClientImpl.java | 11 +- src/main/java/com/p4square/ccbapi/CCBAPI.java | 9 + .../java/com/p4square/ccbapi/CCBAPIClient.java | 34 +- .../java/com/p4square/ccbapi/HTTPInterface.java | 4 +- .../java/com/p4square/ccbapi/model/Countries.java | 8 + .../java/com/p4square/ccbapi/model/Country.java | 24 ++ .../java/com/p4square/ccbapi/model/Gender.java | 11 +- .../model/GetIndividualProfilesResponse.java | 2 +- .../model/UpdateIndividualProfileRequest.java | 378 +++++++++++++++++++++ .../model/UpdateIndividualProfileResponse.java | 27 ++ .../ccbapi/serializer/AbstractFormSerializer.java | 96 ++++++ .../ccbapi/serializer/AddressFormSerializer.java | 54 +++ .../serializer/IndividualProfileSerializer.java | 125 +++++++ .../ccbapi/serializer/PhoneFormSerializer.java | 24 ++ .../com/p4square/ccbapi/serializer/Serializer.java | 11 + .../java/com/p4square/ccbapi/CCBAPIClientTest.java | 63 +++- .../serializer/AddressFormSerializerTest.java | 101 ++++++ .../IndividualProfileSerializerTest.java | 203 +++++++++++ .../ccbapi/serializer/PhoneFormSerializerTest.java | 97 ++++++ 19 files changed, 1256 insertions(+), 26 deletions(-) create mode 100644 src/main/java/com/p4square/ccbapi/model/Countries.java create mode 100644 src/main/java/com/p4square/ccbapi/model/UpdateIndividualProfileRequest.java create mode 100644 src/main/java/com/p4square/ccbapi/model/UpdateIndividualProfileResponse.java create mode 100644 src/main/java/com/p4square/ccbapi/serializer/AbstractFormSerializer.java create mode 100644 src/main/java/com/p4square/ccbapi/serializer/AddressFormSerializer.java create mode 100644 src/main/java/com/p4square/ccbapi/serializer/IndividualProfileSerializer.java create mode 100644 src/main/java/com/p4square/ccbapi/serializer/PhoneFormSerializer.java create mode 100644 src/main/java/com/p4square/ccbapi/serializer/Serializer.java create mode 100644 src/test/java/com/p4square/ccbapi/serializer/AddressFormSerializerTest.java create mode 100644 src/test/java/com/p4square/ccbapi/serializer/IndividualProfileSerializerTest.java create mode 100644 src/test/java/com/p4square/ccbapi/serializer/PhoneFormSerializerTest.java (limited to 'src') diff --git a/src/main/java/com/p4square/ccbapi/ApacheHttpClientImpl.java b/src/main/java/com/p4square/ccbapi/ApacheHttpClientImpl.java index 0833c67..4c3a777 100644 --- a/src/main/java/com/p4square/ccbapi/ApacheHttpClientImpl.java +++ b/src/main/java/com/p4square/ccbapi/ApacheHttpClientImpl.java @@ -9,6 +9,8 @@ import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ByteArrayEntity; +import org.apache.http.entity.InputStreamEntity; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.message.BasicNameValuePair; import org.apache.http.util.EntityUtils; @@ -52,14 +54,13 @@ public class ApacheHttpClientImpl implements HTTPInterface { } @Override - public InputStream sendPostRequest(URI uri, Map form) throws IOException { + public InputStream sendPostRequest(final URI uri, final byte[] form) throws IOException { // Build the request. final HttpPost httpPost = new HttpPost(uri); - final List formParameters = new ArrayList<>(); - for (Map.Entry entry : form.entrySet()) { - formParameters.add(new BasicNameValuePair(entry.getKey(), entry.getValue())); + + if (form != null) { + httpPost.setEntity(new ByteArrayEntity(form)); } - httpPost.setEntity(new UrlEncodedFormEntity(formParameters)); // Make the request. final HttpResponse response = httpClient.execute(httpPost); diff --git a/src/main/java/com/p4square/ccbapi/CCBAPI.java b/src/main/java/com/p4square/ccbapi/CCBAPI.java index e54e20b..f81a02f 100644 --- a/src/main/java/com/p4square/ccbapi/CCBAPI.java +++ b/src/main/java/com/p4square/ccbapi/CCBAPI.java @@ -39,4 +39,13 @@ public interface CCBAPI extends Closeable { * @throws IOException on failure. */ GetIndividualProfilesResponse getIndividualProfiles(GetIndividualProfilesRequest request) throws IOException; + + /** + * Update an IndividualProfile. + * + * @param request An UpdateIndividualProfileRequest including the fields to modify. + * @return An UpdateIndividualProfileResponse, including the updated IndividualProfile. + * @throws IOException on failure. + */ + UpdateIndividualProfileResponse updateIndividualProfile(UpdateIndividualProfileRequest request) throws IOException; } diff --git a/src/main/java/com/p4square/ccbapi/CCBAPIClient.java b/src/main/java/com/p4square/ccbapi/CCBAPIClient.java index ee309c6..404253a 100644 --- a/src/main/java/com/p4square/ccbapi/CCBAPIClient.java +++ b/src/main/java/com/p4square/ccbapi/CCBAPIClient.java @@ -2,11 +2,15 @@ package com.p4square.ccbapi; import com.p4square.ccbapi.exception.CCBErrorResponseException; import com.p4square.ccbapi.model.*; +import com.p4square.ccbapi.serializer.AddressFormSerializer; +import com.p4square.ccbapi.serializer.IndividualProfileSerializer; +import com.p4square.ccbapi.serializer.PhoneFormSerializer; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -23,6 +27,8 @@ public class CCBAPIClient implements CCBAPI { private static final Map EMPTY_MAP = Collections.emptyMap(); + private static final IndividualProfileSerializer INDIVIDUAL_PROFILE_SERIALIZER = new IndividualProfileSerializer(); + private final URI apiBaseUri; private final HTTPInterface httpClient; private final CCBXmlBinder xmlBinder; @@ -119,12 +125,27 @@ public class CCBAPIClient implements CCBAPI { } // Send the request and parse the response. - return makeRequest(serviceName, params, EMPTY_MAP, GetIndividualProfilesResponse.class); + return makeRequest(serviceName, params, null, GetIndividualProfilesResponse.class); } @Override public GetCustomFieldLabelsResponse getCustomFieldLabels() throws IOException { - return makeRequest("custom_field_labels", EMPTY_MAP, EMPTY_MAP, GetCustomFieldLabelsResponse.class); + return makeRequest("custom_field_labels", EMPTY_MAP, null, GetCustomFieldLabelsResponse.class); + } + + @Override + public UpdateIndividualProfileResponse updateIndividualProfile(UpdateIndividualProfileRequest request) + throws IOException { + + if (request.getIndividualId() == 0) { + throw new IllegalArgumentException("individualId must be set on the request."); + } + + final Map params = Collections.singletonMap("individual_id", + String.valueOf(request.getIndividualId())); + final String form = INDIVIDUAL_PROFILE_SERIALIZER.encode(request); + + return makeRequest("update_individual", params, form, UpdateIndividualProfileResponse.class); } /** @@ -164,10 +185,15 @@ public class CCBAPIClient implements CCBAPI { * @throws IOException if an error occurs. */ private T makeRequest(final String api, final Map params, - final Map form, final Class clazz) + final String form, final Class clazz) throws IOException { - final InputStream entity = httpClient.sendPostRequest(makeURI(api, params), form); + byte[] payload = null; + if (form != null) { + payload = form.getBytes(StandardCharsets.UTF_8); + } + + final InputStream entity = httpClient.sendPostRequest(makeURI(api, params), payload); try { T response = xmlBinder.bindResponseXML(entity, clazz); if (response.getErrors() != null && response.getErrors().size() > 0) { diff --git a/src/main/java/com/p4square/ccbapi/HTTPInterface.java b/src/main/java/com/p4square/ccbapi/HTTPInterface.java index 11d87ec..9c5e818 100644 --- a/src/main/java/com/p4square/ccbapi/HTTPInterface.java +++ b/src/main/java/com/p4square/ccbapi/HTTPInterface.java @@ -24,11 +24,11 @@ public interface HTTPInterface extends Closeable { * The form data for the request is specified in the form Map. * * @param uri The URI to request. - * @param form Map of key/value pairs to send as form data. + * @param form Form data or null. * @return The response received. * @throws com.p4square.ccbapi.exception.CCBRetryableErrorException * @throws CCBRetryableErrorException if a retryable error occurs. * @throws IOException If a non-retryable error occurs. */ - InputStream sendPostRequest(URI uri, Map form) throws IOException; + InputStream sendPostRequest(URI uri, byte[] form) throws IOException; } diff --git a/src/main/java/com/p4square/ccbapi/model/Countries.java b/src/main/java/com/p4square/ccbapi/model/Countries.java new file mode 100644 index 0000000..ec9df70 --- /dev/null +++ b/src/main/java/com/p4square/ccbapi/model/Countries.java @@ -0,0 +1,8 @@ +package com.p4square.ccbapi.model; + +/** + * Constants which define common countries. + */ +public class Countries { + public static final Country UNITED_STATES = new Country("US", "United States"); +} diff --git a/src/main/java/com/p4square/ccbapi/model/Country.java b/src/main/java/com/p4square/ccbapi/model/Country.java index e6936a5..ac9e9ec 100644 --- a/src/main/java/com/p4square/ccbapi/model/Country.java +++ b/src/main/java/com/p4square/ccbapi/model/Country.java @@ -15,6 +15,30 @@ public class Country { @XmlValue private String name; + /** + * Default Country constructor. + */ + public Country() { + + } + + /** + * This package-private Country constructor is used to create the + * constants in the {@link Countries} class. + * + * Usually the country name cannot be set except when binding to + * responses from CCB. This constructor is an exception so that the + * constants in {@link Countries} can be provide with + * known values used by CCB. + * + * @param code The two letter country code. + * @param name The country name as typically presented by CCB. + */ + Country(final String code, final String name) { + setCountryCode(code); + this.name = name; + } + /** * @return The two letter country code. */ diff --git a/src/main/java/com/p4square/ccbapi/model/Gender.java b/src/main/java/com/p4square/ccbapi/model/Gender.java index eabaa42..cf6736a 100644 --- a/src/main/java/com/p4square/ccbapi/model/Gender.java +++ b/src/main/java/com/p4square/ccbapi/model/Gender.java @@ -6,8 +6,15 @@ import javax.xml.bind.annotation.XmlEnumValue; * Enum representing the gender of an individual in CCB. */ public enum Gender { - @XmlEnumValue("M") MALE("M"), - @XmlEnumValue("F") FEMALE("F"); + /** + * The documentation currently provides conflicting examples for the gender code. + * The documentation says it must be 'M' or 'F', but the example given uses 'm'. + * According to an API Village posting, it should actually be lower case. + * + * https://village.ccbchurch.com/message_comment_list.php?message_id=3308 + */ + @XmlEnumValue("M") MALE("m"), + @XmlEnumValue("F") FEMALE("f"); private final String code; diff --git a/src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesResponse.java b/src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesResponse.java index f88bcf7..bc7915c 100644 --- a/src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesResponse.java +++ b/src/main/java/com/p4square/ccbapi/model/GetIndividualProfilesResponse.java @@ -4,7 +4,7 @@ import javax.xml.bind.annotation.*; import java.util.List; /** - * GetCustomFieldLabelsResponse models the response of a variety of APIs which return one or more Individual Profiles. + * GetIndividualProfilesResponse models the response of a variety of APIs which return one or more Individual Profiles. */ @XmlRootElement(name="response") @XmlAccessorType(XmlAccessType.NONE) diff --git a/src/main/java/com/p4square/ccbapi/model/UpdateIndividualProfileRequest.java b/src/main/java/com/p4square/ccbapi/model/UpdateIndividualProfileRequest.java new file mode 100644 index 0000000..613b678 --- /dev/null +++ b/src/main/java/com/p4square/ccbapi/model/UpdateIndividualProfileRequest.java @@ -0,0 +1,378 @@ +package com.p4square.ccbapi.model; + +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * UpdateIndividualProfileRequest encapsulates a change to an IndividualProfile. + * + * The only property which must be set is {@link #withIndividualId(int)}, + * to indicate the profile to update. + * + * A property can be unset by setting it to null. + */ +public class UpdateIndividualProfileRequest { + + /** + * The set of all valid custom text field names. + */ + public static final Set CUSTOM_TEXT_FIELD_NAMES = + IntStream.rangeClosed(1, 12).mapToObj(x -> "udf_text_" + x).collect(Collectors.toSet()); + + /** + * The set of all valid custom date field names. + */ + public static final Set CUSTOM_DATE_FIELD_NAMES = + IntStream.rangeClosed(1, 6).mapToObj(x -> "udf_date_" + x).collect(Collectors.toSet()); + + /** + * The set of all valid custom pulldown field names. + */ + public static final Set CUSTOM_PULLDOWN_FIELD_NAMES = + IntStream.rangeClosed(1, 6).mapToObj(x -> "udf_pulldown_" + x).collect(Collectors.toSet()); + + private int individualId; + + private Integer syncId; + private String otherId; + private String givingNumber; + + private String firstName; + private String lastName; + private String middleName; + private String legalFirstName; + private String salutation; + private String suffix; + + private Integer familyId; + + private FamilyPosition familyPosition; + + private MaritalStatus maritalStatus; + + private Gender gender; + private LocalDate birthday; + private LocalDate anniversary; + private LocalDate deceased; + private LocalDate membershipDate; + private LocalDate membershipEnd; + + private String email; + + private List
addresses; + private List phones; + + private String emergencyContactName; + private String allergies; + private Boolean confirmedNoAllergies; + private Boolean baptized; + + private Map customTextFields = new HashMap<>(); + private Map customDateFields = new HashMap<>(); + private Map customPulldownFields = new HashMap<>(); + + private Integer modifiedById; + + public int getIndividualId() { + return individualId; + } + + public UpdateIndividualProfileRequest withIndividualId(int individualId) { + this.individualId = individualId; + return this; + } + + public Integer getSyncId() { + return syncId; + } + + public UpdateIndividualProfileRequest withSyncId(Integer syncId) { + this.syncId = syncId; + return this; + } + + public String getOtherId() { + return otherId; + } + + public UpdateIndividualProfileRequest withOtherId(String otherId) { + this.otherId = otherId; + return this; + } + + public String getGivingNumber() { + return givingNumber; + } + + public UpdateIndividualProfileRequest withGivingNumber(String givingNumber) { + this.givingNumber = givingNumber; + return this; + } + + public String getFirstName() { + return firstName; + } + + public UpdateIndividualProfileRequest withFirstName(String firstName) { + this.firstName = firstName; + return this; + } + + public String getLastName() { + return lastName; + } + + public UpdateIndividualProfileRequest withLastName(String lastName) { + this.lastName = lastName; + return this; + } + + public String getMiddleName() { + return middleName; + } + + public UpdateIndividualProfileRequest withMiddleName(String middleName) { + this.middleName = middleName; + return this; + } + + public String getLegalFirstName() { + return legalFirstName; + } + + public UpdateIndividualProfileRequest withLegalFirstName(String legalFirstName) { + this.legalFirstName = legalFirstName; + return this; + } + + public String getSalutation() { + return salutation; + } + + public UpdateIndividualProfileRequest withSalutation(String salutation) { + this.salutation = salutation; + return this; + } + + public String getSuffix() { + return suffix; + } + + public UpdateIndividualProfileRequest withSuffix(String suffix) { + this.suffix = suffix; + return this; + } + + public Integer getFamilyId() { + return familyId; + } + + public UpdateIndividualProfileRequest withFamilyId(Integer familyId) { + this.familyId = familyId; + return this; + } + + public FamilyPosition getFamilyPosition() { + return familyPosition; + } + + public UpdateIndividualProfileRequest withFamilyPosition(FamilyPosition familyPosition) { + this.familyPosition = familyPosition; + return this; + } + + public MaritalStatus getMaritalStatus() { + return maritalStatus; + } + + public UpdateIndividualProfileRequest withMaritalStatus(MaritalStatus maritalStatus) { + this.maritalStatus = maritalStatus; + return this; + } + + public Gender getGender() { + return gender; + } + + public UpdateIndividualProfileRequest withGender(Gender gender) { + this.gender = gender; + return this; + } + + public LocalDate getBirthday() { + return birthday; + } + + public UpdateIndividualProfileRequest withBirthday(LocalDate birthday) { + this.birthday = birthday; + return this; + } + + public LocalDate getAnniversary() { + return anniversary; + } + + public UpdateIndividualProfileRequest withAnniversary(LocalDate anniversary) { + this.anniversary = anniversary; + return this; + } + + public LocalDate getDeceased() { + return deceased; + } + + public UpdateIndividualProfileRequest withDeceased(LocalDate deceased) { + this.deceased = deceased; + return this; + } + + public LocalDate getMembershipDate() { + return membershipDate; + } + + public UpdateIndividualProfileRequest withMembershipDate(LocalDate membershipDate) { + this.membershipDate = membershipDate; + return this; + } + + public LocalDate getMembershipEnd() { + return membershipEnd; + } + + public UpdateIndividualProfileRequest withMembershipEnd(LocalDate membershipEnd) { + this.membershipEnd = membershipEnd; + return this; + } + + public String getEmail() { + return email; + } + + public UpdateIndividualProfileRequest withEmail(String email) { + this.email = email; + return this; + } + + public List
getAddresses() { + return addresses; + } + + public UpdateIndividualProfileRequest withAddresses(List
addresses) { + this.addresses = addresses; + return this; + } + + public List getPhones() { + return phones; + } + + public UpdateIndividualProfileRequest withPhones(List phones) { + this.phones = phones; + return this; + } + + public String getEmergencyContactName() { + return emergencyContactName; + } + + public UpdateIndividualProfileRequest withEmergencyContactName(String emergencyContactName) { + this.emergencyContactName = emergencyContactName; + return this; + } + + public String getAllergies() { + return allergies; + } + + public UpdateIndividualProfileRequest withAllergies(String allergies) { + this.allergies = allergies; + return this; + } + + public Boolean getConfirmedNoAllergies() { + return confirmedNoAllergies; + } + + public UpdateIndividualProfileRequest withConfirmedNoAllergies(Boolean confirmedNoAllergies) { + this.confirmedNoAllergies = confirmedNoAllergies; + return this; + } + + public Boolean getBaptized() { + return baptized; + } + + public UpdateIndividualProfileRequest withBaptized(Boolean baptized) { + this.baptized = baptized; + return this; + } + + /** + * @return A map of custom field identifiers to text field values. + */ + public Map getCustomTextFields() { + return customTextFields; + } + + public UpdateIndividualProfileRequest withCustomTextField(final String name, final String value) { + if (!CUSTOM_TEXT_FIELD_NAMES.contains(name)) { + throw new IllegalArgumentException(name + " is not a valid a valid text field name."); + } + customTextFields.put(name, value); + return this; + } + + public UpdateIndividualProfileRequest withCustomTextField(final int number, final String value) { + return withCustomTextField("udf_text_" + number, value); + } + + /** + * @return A map of custom field identifiers to date field values. + */ + public Map getCustomDateFields() { + return customDateFields; + } + + public UpdateIndividualProfileRequest withCustomDateField(final String name, final LocalDate value) { + if (!CUSTOM_DATE_FIELD_NAMES.contains(name)) { + throw new IllegalArgumentException(name + " is not a valid a valid date field name."); + } + customDateFields.put(name, value); + return this; + } + + public UpdateIndividualProfileRequest withCustomDateField(final int number, final LocalDate value) { + return withCustomDateField("udf_date_" + number, value); + } + + /** + * @return A map of custom field identifiers to pulldown field values. + */ + public Map getCustomPulldownFields() { + return customPulldownFields; + } + + public UpdateIndividualProfileRequest withCustomPulldownField(final String name, final Integer value) { + if (!CUSTOM_PULLDOWN_FIELD_NAMES.contains(name)) { + throw new IllegalArgumentException(name + " is not a valid a valid pulldown field name."); + } + customPulldownFields.put(name, value); + return this; + } + + public UpdateIndividualProfileRequest withCustomPulldownField(final int number, final Integer value) { + return withCustomPulldownField("udf_pulldown_" + number, value); + } + + public Integer getModifiedById() { + return modifiedById; + } + + public UpdateIndividualProfileRequest withModifiedById(Integer modifiedById) { + this.modifiedById = modifiedById; + return this; + } + +} diff --git a/src/main/java/com/p4square/ccbapi/model/UpdateIndividualProfileResponse.java b/src/main/java/com/p4square/ccbapi/model/UpdateIndividualProfileResponse.java new file mode 100644 index 0000000..9067d56 --- /dev/null +++ b/src/main/java/com/p4square/ccbapi/model/UpdateIndividualProfileResponse.java @@ -0,0 +1,27 @@ +package com.p4square.ccbapi.model; + +import javax.xml.bind.annotation.*; +import java.util.List; + +/** + * The response from an update_individual request. + */ +@XmlRootElement(name="response") +@XmlAccessorType(XmlAccessType.NONE) +public class UpdateIndividualProfileResponse extends CCBAPIResponse { + + @XmlElementWrapper(name = "individuals") + @XmlElement(name="individual") + private List individuals; + + /** + * @return The list of individuals retrieved from CCB. + */ + public List getIndividuals() { + return individuals; + } + + public void setIndividuals(List individuals) { + this.individuals = individuals; + } +} diff --git a/src/main/java/com/p4square/ccbapi/serializer/AbstractFormSerializer.java b/src/main/java/com/p4square/ccbapi/serializer/AbstractFormSerializer.java new file mode 100644 index 0000000..06a83a1 --- /dev/null +++ b/src/main/java/com/p4square/ccbapi/serializer/AbstractFormSerializer.java @@ -0,0 +1,96 @@ +package com.p4square.ccbapi.serializer; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +/** + * AbstractFormSerializer provides default implementations for some methods defined in Serializer + * and methods to support encoding form data for CCB. + */ +public abstract class AbstractFormSerializer implements Serializer { + + /** + * This is the datetime format specified by the CCB API Doc. + */ + private static final DateTimeFormatter DATE_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + + @Override + public String encode(final T obj) { + final StringBuilder sb = new StringBuilder(); + encode(obj, sb); + return sb.toString(); + } + + /** + * Append a field to the form. + * + * @param builder The StringBuilder to use. + * @param key The form key, which must be URLEncoded before calling this method. + * @param value The value associated with the key. The value will be URLEncoded by this method. + */ + protected void appendField(final StringBuilder builder, final String key, final String value) { + if (builder.length() > 0) { + builder.append("&"); + } + + try { + builder.append(key).append("=").append(URLEncoder.encode(value, "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new AssertionError("UTF-8 encoding should always be available."); + } + } + + /** + * Append an integer field to the form. + * + * @param builder The StringBuilder to use. + * @param key The form key, which must be URLEncoded before calling this method. + * @param value The value associated with the key. + */ + protected void appendField(final StringBuilder builder, final String key, final int value) { + if (builder.length() > 0) { + builder.append("&"); + } + builder.append(key).append("=").append(value); + } + + /** + * Append a boolean field to the form. + * + * @param builder The StringBuilder to use. + * @param key The form key, which must be URLEncoded before calling this method. + * @param value The value associated with the key. + */ + protected void appendField(final StringBuilder builder, final String key, final boolean value) { + if (builder.length() > 0) { + builder.append("&"); + } + builder.append(key).append("=").append(value ? "true" : "false"); + } + + /** + * Append a LocalDate field to the form. + * + * @param builder The StringBuilder to use. + * @param key The form key, which must be URLEncoded before calling this method. + * @param value The value associated with the key. + */ + protected void appendField(final StringBuilder builder, final String key, final LocalDate value) { + appendField(builder, key, value.toString()); + + } + + /** + * Append a LocalDateTime field to the form. + * + * @param builder The StringBuilder to use. + * @param key The form key, which must be URLEncoded before calling this method. + * @param value The value associated with the key. + */ + protected void appendField(final StringBuilder builder, final String key, final LocalDateTime value) { + appendField(builder, key, DATE_TIME_FORMAT.format(value)); + } +} diff --git a/src/main/java/com/p4square/ccbapi/serializer/AddressFormSerializer.java b/src/main/java/com/p4square/ccbapi/serializer/AddressFormSerializer.java new file mode 100644 index 0000000..00af004 --- /dev/null +++ b/src/main/java/com/p4square/ccbapi/serializer/AddressFormSerializer.java @@ -0,0 +1,54 @@ +package com.p4square.ccbapi.serializer; + +import com.p4square.ccbapi.model.Address; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; + +/** + * Encode an Address object as form data for CCB. + */ +public class AddressFormSerializer extends AbstractFormSerializer
{ + + @Override + public void encode(final Address address, final StringBuilder builder) { + // Sanity check. + if (address.getType() == null) { + throw new IllegalArgumentException("Address type cannot be null"); + } + + // Every form field will be prefixed with the type. + final String type = address.getType().toString().toLowerCase(); + + if (address.getStreetAddress() != null) { + appendField(builder, type, "street_address", address.getStreetAddress()); + } + + if (address.getCity() != null) { + appendField(builder, type, "city", address.getCity()); + } + + if (address.getState() != null) { + appendField(builder, type, "state", address.getState()); + } + + if (address.getZip() != null) { + appendField(builder, type, "zip", address.getZip()); + } + + if (address.getCountry() != null) { + appendField(builder, type, "country", address.getCountry().getCountryCode()); + } + } + + private void appendField(final StringBuilder builder, final String type, final String key, final String value) { + if (builder.length() > 0) { + builder.append("&"); + } + try { + builder.append(type).append("_").append(key).append("=").append(URLEncoder.encode(value, "UTF-8")); + } catch (UnsupportedEncodingException e) { + throw new AssertionError("UTF-8 encoding should always be available."); + } + } +} diff --git a/src/main/java/com/p4square/ccbapi/serializer/IndividualProfileSerializer.java b/src/main/java/com/p4square/ccbapi/serializer/IndividualProfileSerializer.java new file mode 100644 index 0000000..52b1a44 --- /dev/null +++ b/src/main/java/com/p4square/ccbapi/serializer/IndividualProfileSerializer.java @@ -0,0 +1,125 @@ +package com.p4square.ccbapi.serializer; + +import com.p4square.ccbapi.model.Address; +import com.p4square.ccbapi.model.Phone; +import com.p4square.ccbapi.model.UpdateIndividualProfileRequest; + +import java.time.LocalDate; +import java.util.Map; + +/** + * Serializes an {@link UpdateIndividualProfileRequest} into a form suitable for the update_individual API. + */ +public class IndividualProfileSerializer extends AbstractFormSerializer { + + private static final AddressFormSerializer ADDRESS_FORM_SERIALIZER = new AddressFormSerializer(); + private static final PhoneFormSerializer PHONE_FORM_SERIALIZER = new PhoneFormSerializer(); + + @Override + public void encode(final UpdateIndividualProfileRequest request, final StringBuilder builder) { + // Encode any fields which are present. + if (request.getSyncId() != null) { + appendField(builder, "sync_id", request.getSyncId()); + } + if (request.getOtherId() != null) { + appendField(builder, "other_id", request.getOtherId()); + } + if (request.getGivingNumber() != null) { + appendField(builder, "giving_number", request.getGivingNumber()); + } + if (request.getFirstName() != null) { + appendField(builder, "first_name", request.getFirstName()); + } + if (request.getLastName() != null) { + appendField(builder, "last_name", request.getLastName()); + } + if (request.getMiddleName() != null) { + appendField(builder, "middle_name", request.getMiddleName()); + } + if (request.getLegalFirstName() != null) { + appendField(builder, "legal_first_name", request.getLegalFirstName()); + } + if (request.getSalutation() != null) { + appendField(builder, "salutation", request.getSalutation()); + } + if (request.getSuffix() != null) { + appendField(builder, "suffix", request.getSuffix()); + } + if (request.getFamilyId() != null) { + appendField(builder, "family_id", request.getFamilyId()); + } + if (request.getFamilyPosition() != null) { + appendField(builder, "family_position", request.getFamilyPosition().getCode()); + } + if (request.getMaritalStatus() != null) { + appendField(builder, "marital_status", request.getMaritalStatus().getCode()); + } + if (request.getGender() != null) { + appendField(builder, "gender", request.getGender().getCode()); + } + if (request.getBirthday() != null) { + appendField(builder, "birthday", request.getBirthday()); + } + if (request.getAnniversary() != null) { + appendField(builder, "anniversary", request.getAnniversary()); + } + if (request.getDeceased() != null) { + appendField(builder, "deceased", request.getDeceased()); + } + if (request.getMembershipDate() != null) { + appendField(builder, "membership_date", request.getMembershipDate()); + } + if (request.getMembershipEnd() != null) { + appendField(builder, "membership_end", request.getMembershipEnd()); + } + if (request.getEmail() != null) { + appendField(builder, "email", request.getEmail()); + } + if (request.getEmergencyContactName() != null) { + appendField(builder, "emergency_contact_name", request.getEmergencyContactName()); + } + if (request.getAllergies() != null) { + appendField(builder, "allergies", request.getAllergies()); + } + if (request.getConfirmedNoAllergies() != null) { + appendField(builder, "confirmed_no_allergies", request.getConfirmedNoAllergies()); + } + if (request.getBaptized() != null) { + appendField(builder, "baptized", request.getBaptized()); + } + if (request.getModifiedById() != null) { + appendField(builder, "modifier_id", request.getModifiedById()); + } + + // Encode all the addresses. + if (request.getAddresses() != null) { + for (Address address : request.getAddresses()) { + ADDRESS_FORM_SERIALIZER.encode(address, builder); + } + } + + // and the phone numbers. + if (request.getPhones() != null) { + for (Phone phone : request.getPhones()) { + PHONE_FORM_SERIALIZER.encode(phone, builder); + } + } + + // Add the User-defined fields. + for (Map.Entry entry : request.getCustomTextFields().entrySet()) { + if (entry.getValue() != null) { + appendField(builder, entry.getKey(), entry.getValue()); + } + } + for (Map.Entry entry : request.getCustomDateFields().entrySet()) { + if (entry.getValue() != null) { + appendField(builder, entry.getKey(), entry.getValue()); + } + } + for (Map.Entry entry : request.getCustomPulldownFields().entrySet()) { + if (entry.getValue() != null) { + appendField(builder, entry.getKey(), entry.getValue()); + } + } + } +} diff --git a/src/main/java/com/p4square/ccbapi/serializer/PhoneFormSerializer.java b/src/main/java/com/p4square/ccbapi/serializer/PhoneFormSerializer.java new file mode 100644 index 0000000..3569321 --- /dev/null +++ b/src/main/java/com/p4square/ccbapi/serializer/PhoneFormSerializer.java @@ -0,0 +1,24 @@ +package com.p4square.ccbapi.serializer; + +import com.p4square.ccbapi.model.Phone; + +/** + * Encode a Phone object as form data for CCB. + */ +public class PhoneFormSerializer extends AbstractFormSerializer { + @Override + public void encode(final Phone phone, final StringBuilder builder) { + // Sanity check. + if (phone.getType() == null) { + throw new IllegalArgumentException("Phone type cannot be null"); + } + + final String key; + if (phone.getType() == Phone.Type.EMERGENCY) { + key = "phone_emergency"; + } else { + key = phone.getType().toString().toLowerCase() + "_phone"; + } + appendField(builder, key, phone.getNumber()); + } +} diff --git a/src/main/java/com/p4square/ccbapi/serializer/Serializer.java b/src/main/java/com/p4square/ccbapi/serializer/Serializer.java new file mode 100644 index 0000000..775aa09 --- /dev/null +++ b/src/main/java/com/p4square/ccbapi/serializer/Serializer.java @@ -0,0 +1,11 @@ +package com.p4square.ccbapi.serializer; + +/** + * A Serializer converts an object of type T to a payload suitable for CCB. + */ +public interface Serializer { + + String encode(T obj); + + void encode(T obj, StringBuilder builder); +} diff --git a/src/test/java/com/p4square/ccbapi/CCBAPIClientTest.java b/src/test/java/com/p4square/ccbapi/CCBAPIClientTest.java index b15d16f..69f50c2 100644 --- a/src/test/java/com/p4square/ccbapi/CCBAPIClientTest.java +++ b/src/test/java/com/p4square/ccbapi/CCBAPIClientTest.java @@ -2,9 +2,7 @@ package com.p4square.ccbapi; import com.p4square.ccbapi.exception.CCBErrorResponseException; import com.p4square.ccbapi.exception.CCBRetryableErrorException; -import com.p4square.ccbapi.model.GetCustomFieldLabelsResponse; -import com.p4square.ccbapi.model.GetIndividualProfilesRequest; -import com.p4square.ccbapi.model.GetIndividualProfilesResponse; +import com.p4square.ccbapi.model.*; import org.junit.Before; import java.io.IOException; @@ -16,6 +14,7 @@ import java.util.Map; import org.easymock.EasyMock; import org.junit.Test; +import org.junit.rules.ExpectedException; import static org.junit.Assert.*; @@ -51,7 +50,7 @@ public class CCBAPIClientTest { // Set expectation. URI expectedURI = new URI("https://localhost:8080/api.php?srv=custom_field_labels"); InputStream is = getClass().getResourceAsStream("model/ccb_custom_field_labels_response.xml"); - EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, Collections.emptyMap())) + EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, null)) .andReturn(is); EasyMock.replay(mockHttpClient); @@ -72,7 +71,7 @@ public class CCBAPIClientTest { // Set expectation. URI expectedURI = new URI("https://localhost:8080/api.php?srv=custom_field_labels"); InputStream is = getClass().getResourceAsStream("model/ccb_error_response.xml"); - EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, Collections.emptyMap())) + EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, null)) .andReturn(is); EasyMock.replay(mockHttpClient); @@ -92,7 +91,7 @@ public class CCBAPIClientTest { @Test(expected = CCBRetryableErrorException.class) public void testGetCustomFieldLabelsTimeout() throws Exception { // Setup mocks. - EasyMock.expect(mockHttpClient.sendPostRequest(EasyMock.anyObject(URI.class), EasyMock.anyObject(Map.class))) + EasyMock.expect(mockHttpClient.sendPostRequest(EasyMock.anyObject(URI.class), EasyMock.anyObject())) .andThrow(new CCBRetryableErrorException("Retryable error")); EasyMock.replay(mockHttpClient); @@ -103,7 +102,7 @@ public class CCBAPIClientTest { @Test(expected = IOException.class) public void testGetCustomFieldLabelsException() throws Exception { // Setup mocks. - EasyMock.expect(mockHttpClient.sendPostRequest(EasyMock.anyObject(URI.class), EasyMock.anyObject(Map.class))) + EasyMock.expect(mockHttpClient.sendPostRequest(EasyMock.anyObject(URI.class), EasyMock.anyObject())) .andThrow(new IOException()); EasyMock.replay(mockHttpClient); @@ -116,7 +115,7 @@ public class CCBAPIClientTest { // Set expectation. URI expectedURI = new URI("https://localhost:8080/api.php?srv=individual_profile_from_id&individual_id=48"); InputStream is = getClass().getResourceAsStream("model/ccb_individual_profile_response.xml"); - EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, Collections.emptyMap())) + EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, null)) .andReturn(is); EasyMock.replay(mockHttpClient); @@ -137,7 +136,7 @@ public class CCBAPIClientTest { URI expectedURI = new URI("https://localhost:8080/api.php?" + "srv=individual_profile_from_login_password&password=pass&login=user"); InputStream is = getClass().getResourceAsStream("model/ccb_individual_profile_response.xml"); - EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, Collections.emptyMap())) + EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, null)) .andReturn(is); EasyMock.replay(mockHttpClient); @@ -159,7 +158,7 @@ public class CCBAPIClientTest { URI expectedURI = new URI("https://localhost:8080/api.php?" + "srv=individual_profile_from_micr&account_number=4567&routing_number=1234"); InputStream is = getClass().getResourceAsStream("model/ccb_individual_profile_response.xml"); - EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, Collections.emptyMap())) + EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, null)) .andReturn(is); EasyMock.replay(mockHttpClient); @@ -179,7 +178,7 @@ public class CCBAPIClientTest { // Set expectation. URI expectedURI = new URI("https://localhost:8080/api.php?srv=individual_profiles"); InputStream is = getClass().getResourceAsStream("model/ccb_individual_profile_response.xml"); - EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, Collections.emptyMap())) + EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, null)) .andReturn(is); EasyMock.replay(mockHttpClient); @@ -200,7 +199,7 @@ public class CCBAPIClientTest { URI expectedURI = new URI("https://localhost:8080/api.php?srv=individual_profiles" + "&per_page=15&include_inactive=true&modified_since=2016-03-19&page=5"); InputStream is = getClass().getResourceAsStream("model/ccb_individual_profile_response.xml"); - EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, Collections.emptyMap())) + EasyMock.expect(mockHttpClient.sendPostRequest(expectedURI, null)) .andReturn(is); EasyMock.replay(mockHttpClient); @@ -219,6 +218,46 @@ public class CCBAPIClientTest { assertEquals(48, response.getIndividuals().get(0).getId()); } + @Test + public void testUpdateIndividual() throws Exception { + // Set expectation. + URI expectedURI = new URI("https://localhost:8080/api.php?srv=update_individual&individual_id=48"); + InputStream is = getClass().getResourceAsStream("model/ccb_individual_profile_response.xml"); + EasyMock.expect(mockHttpClient.sendPostRequest(EasyMock.eq(expectedURI), + EasyMock.aryEq("legal_first_name=Larabar".getBytes("UTF-8")))) + .andReturn(is); + EasyMock.replay(mockHttpClient); + + UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest() + .withIndividualId(48) + .withLegalFirstName("Larabar"); + + // Test update_individual. + UpdateIndividualProfileResponse response = client.updateIndividualProfile(request); + + // Verify results. + EasyMock.verify(mockHttpClient); + assertNull(response.getErrors()); + assertEquals(1, response.getIndividuals().size()); + assertEquals(48, response.getIndividuals().get(0).getId()); + } + + /** + * This test ensures {@link CCBAPIClient#updateIndividualProfile(UpdateIndividualProfileRequest)} + * throws an exception if the individualId property isn't set on the request. + */ + @Test(expected = IllegalArgumentException.class) + public void testUpdateIndividualWithoutIndividualId() throws Exception { + // Set expectation. + UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest() + .withLegalFirstName("Larabar"); + + // Test update_individual. + UpdateIndividualProfileResponse response = client.updateIndividualProfile(request); + + // Expect exception. + } + /** * Simple extension of CCBAPIClient to swap out the HTTPInterface with a mock. */ diff --git a/src/test/java/com/p4square/ccbapi/serializer/AddressFormSerializerTest.java b/src/test/java/com/p4square/ccbapi/serializer/AddressFormSerializerTest.java new file mode 100644 index 0000000..0275358 --- /dev/null +++ b/src/test/java/com/p4square/ccbapi/serializer/AddressFormSerializerTest.java @@ -0,0 +1,101 @@ +package com.p4square.ccbapi.serializer; + +import com.p4square.ccbapi.model.Address; +import com.p4square.ccbapi.model.Countries; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Tests for the AddressFormSerializer. + * + * Serializer output is compared to the expected output in the update_individual API example. + */ +public class AddressFormSerializerTest { + + private AddressFormSerializer serializer; + + @Before + public void setUp() { + serializer = new AddressFormSerializer(); + } + + @Test + public void testEncodeAllFields() { + final Address address = new Address(); + address.setType(Address.Type.MAILING); + address.setStreetAddress("12265 Oracle Blvd, Suite 105"); + address.setCity("Colorado Springs"); + address.setState("CO"); + address.setZip("80921"); + address.setCountry(Countries.UNITED_STATES); + + final String actual = serializer.encode(address); + + assertEquals("mailing_street_address=12265+Oracle+Blvd%2C+Suite+105" + + "&mailing_city=Colorado+Springs&mailing_state=CO&mailing_zip=80921&mailing_country=US", + actual); + } + + @Test + public void testEncodeSomeFields() { + final Address address = new Address(); + address.setType(Address.Type.MAILING); + address.setStreetAddress("12265 Oracle Blvd, Suite 105"); + + final String actual = serializer.encode(address); + + assertEquals("mailing_street_address=12265+Oracle+Blvd%2C+Suite+105", actual); + } + + @Test + public void testEncodeSomeFieldsWithExistingData() { + final StringBuilder sb = new StringBuilder("foo=bar"); + + final Address address = new Address(); + address.setType(Address.Type.MAILING); + address.setStreetAddress("12265 Oracle Blvd, Suite 105"); + address.setCity("Colorado Springs"); + + serializer.encode(address, sb); + final String actual = sb.toString(); + + assertEquals("foo=bar&mailing_street_address=12265+Oracle+Blvd%2C+Suite+105&mailing_city=Colorado+Springs", + actual); + } + + @Test + public void testEncodeHomeAddress() { + final Address address = new Address(); + address.setType(Address.Type.HOME); + address.setStreetAddress("12265 Oracle Blvd, Suite 105"); + + final String actual = serializer.encode(address); + + assertEquals("home_street_address=12265+Oracle+Blvd%2C+Suite+105", actual); + } + + @Test + public void testEncodeWorkAddress() { + final Address address = new Address(); + address.setType(Address.Type.WORK); + address.setStreetAddress("12265 Oracle Blvd, Suite 105"); + + final String actual = serializer.encode(address); + + assertEquals("work_street_address=12265+Oracle+Blvd%2C+Suite+105", actual); + } + + @Test + public void testEncodeOtherAddress() { + final Address address = new Address(); + address.setType(Address.Type.OTHER); + address.setStreetAddress("12265 Oracle Blvd, Suite 105"); + + final String actual = serializer.encode(address); + + assertEquals("other_street_address=12265+Oracle+Blvd%2C+Suite+105", actual); + } + +} \ No newline at end of file diff --git a/src/test/java/com/p4square/ccbapi/serializer/IndividualProfileSerializerTest.java b/src/test/java/com/p4square/ccbapi/serializer/IndividualProfileSerializerTest.java new file mode 100644 index 0000000..a1bb8c9 --- /dev/null +++ b/src/test/java/com/p4square/ccbapi/serializer/IndividualProfileSerializerTest.java @@ -0,0 +1,203 @@ +package com.p4square.ccbapi.serializer; + +import com.p4square.ccbapi.model.*; +import org.junit.Before; +import org.junit.Test; + +import java.time.LocalDate; +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.Assert.*; + +/** + * Tests for the IndividualProfileSerializer. + * + * Serializer output is compared to the expected output in the update_individual API example. + */ +public class IndividualProfileSerializerTest { + + private IndividualProfileSerializer serializer; + + @Before + public void setUp() { + serializer = new IndividualProfileSerializer(); + } + + /** + * This test sets all of the fields on an + * {@link UpdateIndividualProfileRequest} and tries to serialize them. + */ + @Test + public void testEncodeAllFields() { + final Address mailing_address = new Address(); + mailing_address.setType(Address.Type.MAILING); + mailing_address.setStreetAddress("12265 Oracle Blvd, Suite 105"); + mailing_address.setCity("Colorado Springs"); + mailing_address.setState("CO"); + mailing_address.setZip("80921"); + mailing_address.setCountry(Countries.UNITED_STATES); + + final Phone phone = new Phone(); + phone.setType(Phone.Type.CONTACT); + phone.setNumber("719-555-2888"); + + final UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest() + .withSyncId(1001) + .withOtherId("A1001") + .withGivingNumber("ABC123") + .withFirstName("Larry") + .withLastName("Bob") + .withMiddleName("P") + .withLegalFirstName("Larabar") + .withSalutation("Mr.") + .withSuffix("Jr.") + .withFamilyId(48) + .withFamilyPosition(FamilyPosition.PRIMARY_CONTACT) + .withMaritalStatus(MaritalStatus.MARRIED) + .withGender(Gender.MALE) + .withBirthday(LocalDate.of(1966, 2, 12)) + .withAnniversary(LocalDate.of(1989, 5, 6)) + .withDeceased(LocalDate.of(2016, 12, 31)) + .withMembershipDate(LocalDate.of(2010, 1, 1)) + .withMembershipEnd(LocalDate.of(2016, 12, 1)) + .withEmail("ken@example.com") + .withAddresses(Collections.singletonList(mailing_address)) + .withPhones(Collections.singletonList(phone)) + .withEmergencyContactName("Marry Bob") + .withAllergies("Pine Nuts") + .withConfirmedNoAllergies(true) + .withBaptized(true) + .withModifiedById(12); + + final String actual = serializer.encode(request); + + final String expected = "sync_id=1001&other_id=A1001&giving_number=ABC123" + + "&first_name=Larry&last_name=Bob&middle_name=P&legal_first_name=Larabar&salutation=Mr." + + "&suffix=Jr.&family_id=48&family_position=h&marital_status=m&gender=m" + + "&birthday=1966-02-12&anniversary=1989-05-06&deceased=2016-12-31" + + "&membership_date=2010-01-01&membership_end=2016-12-01" + + "&email=ken%40example.com&emergency_contact_name=Marry+Bob" + + "&allergies=Pine+Nuts&confirmed_no_allergies=true&baptized=true&modifier_id=12" + + "&mailing_street_address=12265+Oracle+Blvd%2C+Suite+105" + + "&mailing_city=Colorado+Springs&mailing_state=CO&mailing_zip=80921&mailing_country=US" + + "&contact_phone=719-555-2888"; + + assertEquals(expected, actual); + } + + @Test + public void testEncodeSomeFields() { + final UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest() + .withSyncId(1001) + .withMiddleName("P") + .withMaritalStatus(MaritalStatus.DIVORCED) + .withGender(Gender.FEMALE) + .withBirthday(LocalDate.of(1966, 2, 12)); + + final String actual = serializer.encode(request); + + assertEquals("sync_id=1001&middle_name=P&marital_status=d&gender=f&birthday=1966-02-12", actual); + } + + @Test + public void testEncodeMultipleAddresses() { + final Address address1 = new Address(); + address1.setType(Address.Type.HOME); + address1.setStreetAddress("123 Happy Lane"); + + final Address address2 = new Address(); + address2.setType(Address.Type.WORK); + address2.setStreetAddress("12265 Oracle Blvd, Suite 105"); + + final UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest() + .withAddresses(Arrays.asList(address1, address2)); + + final String actual = serializer.encode(request); + + assertEquals("home_street_address=123+Happy+Lane&work_street_address=12265+Oracle+Blvd%2C+Suite+105", actual); + } + + @Test + public void testEncodeMultiplePhones() { + final Phone phone1 = new Phone(); + phone1.setType(Phone.Type.MOBILE); + phone1.setNumber("719-555-2888"); + + final Phone phone2 = new Phone(); + phone2.setType(Phone.Type.EMERGENCY); + phone2.setNumber("719-555-3999"); + + final UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest() + .withPhones(Arrays.asList(phone1, phone2)); + + final String actual = serializer.encode(request); + + assertEquals("mobile_phone=719-555-2888&phone_emergency=719-555-3999", actual); + } + + @Test + public void testEncodeCustomFieldsByName() { + final UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest() + .withCustomTextField("udf_text_1", "Hello") + .withCustomDateField("udf_date_1", LocalDate.of(1966, 2, 12)) + .withCustomPulldownField("udf_pulldown_1", 12); + + final String actual = serializer.encode(request); + + assertEquals("udf_text_1=Hello&udf_date_1=1966-02-12&udf_pulldown_1=12", actual); + } + + @Test + public void testEncodeCustomFieldsByNumber() { + final UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest() + .withCustomTextField(2, "Hello") + .withCustomDateField(2, LocalDate.of(1966, 2, 12)) + .withCustomPulldownField(2, 12); + + final String actual = serializer.encode(request); + + assertEquals("udf_text_2=Hello&udf_date_2=1966-02-12&udf_pulldown_2=12", actual); + } + + @Test + public void testNullCustomField() { + final UpdateIndividualProfileRequest request = new UpdateIndividualProfileRequest() + .withCustomTextField(1, null) + .withCustomDateField(1, null) + .withCustomPulldownField(1, null); + + final String actual = serializer.encode(request); + assertEquals("", actual); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidCustomTextFieldName() { + new UpdateIndividualProfileRequest().withCustomTextField("udf_text_13", "foo"); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidCustomTextFieldNumber() { + new UpdateIndividualProfileRequest().withCustomTextField(0, "foo"); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidCustomDateFieldName() { + new UpdateIndividualProfileRequest().withCustomDateField("udf_date_7", LocalDate.of(1966, 2, 12)); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidCustomDateFieldNumber() { + new UpdateIndividualProfileRequest().withCustomDateField(0, LocalDate.of(1966, 2, 12)); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidCustomPulldownFieldName() { + new UpdateIndividualProfileRequest().withCustomPulldownField("udf_pulldown_7", 12); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidCustomPulldownFieldNumber() { + new UpdateIndividualProfileRequest().withCustomPulldownField(0, 12); + } +} \ No newline at end of file diff --git a/src/test/java/com/p4square/ccbapi/serializer/PhoneFormSerializerTest.java b/src/test/java/com/p4square/ccbapi/serializer/PhoneFormSerializerTest.java new file mode 100644 index 0000000..7074ad3 --- /dev/null +++ b/src/test/java/com/p4square/ccbapi/serializer/PhoneFormSerializerTest.java @@ -0,0 +1,97 @@ +package com.p4square.ccbapi.serializer; + +import com.p4square.ccbapi.model.Phone; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Tests for the PhoneFormSerializer. + * + * Serializer output is compared to the expected output in the update_individual API example. + */ +public class PhoneFormSerializerTest { + + private PhoneFormSerializer serializer; + + @Before + public void setUp() { + serializer = new PhoneFormSerializer(); + } + + @Test + public void testEncodeContactPhone() { + final Phone phone = new Phone(); + phone.setType(Phone.Type.CONTACT); + phone.setNumber("719-555-2888"); + + final String actual = serializer.encode(phone); + + assertEquals("contact_phone=719-555-2888", actual); + } + + @Test + public void testEncodeHomePhone() { + final Phone phone = new Phone(); + phone.setType(Phone.Type.HOME); + phone.setNumber("719-555-2888"); + + final String actual = serializer.encode(phone); + + assertEquals("home_phone=719-555-2888", actual); + } + + @Test + public void testEncodeWorkPhone() { + final Phone phone = new Phone(); + phone.setType(Phone.Type.WORK); + phone.setNumber("719-555-2888"); + + final String actual = serializer.encode(phone); + + assertEquals("work_phone=719-555-2888", actual); + } + + @Test + public void testEncodeMobilePhone() { + final Phone phone = new Phone(); + phone.setType(Phone.Type.MOBILE); + phone.setNumber("719-555-2888"); + + final String actual = serializer.encode(phone); + + assertEquals("mobile_phone=719-555-2888", actual); + } + + @Test + public void testEncodeEmergencyPhone() { + final Phone phone = new Phone(); + phone.setType(Phone.Type.EMERGENCY); + phone.setNumber("719-555-2888"); + + final String actual = serializer.encode(phone); + + assertEquals("phone_emergency=719-555-2888", actual); + } + + @Test + public void testEncodeWithExistingData() { + final StringBuilder sb = new StringBuilder(); + + final Phone phone1 = new Phone(); + phone1.setType(Phone.Type.MOBILE); + phone1.setNumber("719-555-2888"); + + final Phone phone2 = new Phone(); + phone2.setType(Phone.Type.EMERGENCY); + phone2.setNumber("719-555-3999"); + + serializer.encode(phone1, sb); + serializer.encode(phone2, sb); + final String actual = sb.toString(); + + assertEquals("mobile_phone=719-555-2888&phone_emergency=719-555-3999", actual); + } + +} \ No newline at end of file -- cgit v1.2.3