From cac52cf3a07fb4c032f352ec48b56640b246f04f Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Sat, 19 Mar 2016 16:42:31 -0700 Subject: Adding feature to set configs and domain with environment variables. --- src/com/p4square/grow/config/Config.java | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) (limited to 'src/com') diff --git a/src/com/p4square/grow/config/Config.java b/src/com/p4square/grow/config/Config.java index e89990b..2fc2ea3 100644 --- a/src/com/p4square/grow/config/Config.java +++ b/src/com/p4square/grow/config/Config.java @@ -35,12 +35,27 @@ public class Config { private Properties mProperties; /** - * Construct a new Config object. Domain defaults to prod. + * Construct a new Config object. + * + * Sets the domain to the value of the system property CONFIG_DOMAIN, if present. + * If the system property is not set then the environment variable CONFIG_DOMAIN is checked. + * If neither are set the domain defaults to prod. */ public Config() { - mDomain = "prod"; - mProperties = new Properties(); + // Check the command line for a domain property. + mDomain = System.getProperty("CONFIG_DOMAIN"); + + // If the domain was not set with a property, check for an environment variable. + if (mDomain == null) { + mDomain = System.getenv("CONFIG_DOMAIN"); + } + + // If neither were set, default to prod + if (mDomain == null) { + mDomain = "prod"; + } + mProperties = new Properties(); } /** @@ -62,7 +77,7 @@ public class Config { /** * Load properties from a file. - * Any exceptions are logged and suppressed. + * Any exception are logged and suppressed. */ public void updateConfig(String propertyFilename) { final File propFile = new File(propertyFilename); @@ -114,6 +129,13 @@ public class Config { return result; } + // Environment variables can also override configs + result = System.getenv(key); + if (result != null) { + LOG.debug("Reading System.getenv(" + key + "). Got result = { " + result + " }"); + return result; + } + final String domainKey = mDomain + "." + key; result = mProperties.getProperty(domainKey); if (result != null) { -- cgit v1.2.3 From 55cba1e0f3373fa69d3b9a66f455ad36ab4b82cf Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Sun, 20 Mar 2016 17:07:26 -0700 Subject: Adding support for Church Community Builder login. Beginning with this change all of the Church Management System integration logic is moving into implementations of the new IntegrationDriver interface. The desired IntegrationDriver can be selected by setting the integrationDriver config to the appropriate class name. This commit is only moving login support. Progress reporting will move in a later commit. --- ivy.xml | 3 + .../f1oauth/FellowshipOneIntegrationDriver.java | 45 +++++++ src/com/p4square/grow/ccb/CCBUser.java | 37 ++++++ src/com/p4square/grow/ccb/CCBUserVerifier.java | 50 ++++++++ .../ChurchCommunityBuilderIntegrationDriver.java | 50 ++++++++ src/com/p4square/grow/ccb/MonitoredCCBAPI.java | 68 ++++++++++ .../grow/frontend/AssessmentResultsPage.java | 4 +- .../grow/frontend/ChapterCompletePage.java | 4 +- src/com/p4square/grow/frontend/GrowFrontend.java | 39 +++--- .../p4square/grow/frontend/IntegrationDriver.java | 17 +++ .../p4square/grow/frontend/NewAccountResource.java | 16 ++- tst/com/p4square/grow/ccb/CCBUserVerifierTest.java | 139 +++++++++++++++++++++ 12 files changed, 449 insertions(+), 23 deletions(-) create mode 100644 src/com/p4square/f1oauth/FellowshipOneIntegrationDriver.java create mode 100644 src/com/p4square/grow/ccb/CCBUser.java create mode 100644 src/com/p4square/grow/ccb/CCBUserVerifier.java create mode 100644 src/com/p4square/grow/ccb/ChurchCommunityBuilderIntegrationDriver.java create mode 100644 src/com/p4square/grow/ccb/MonitoredCCBAPI.java create mode 100644 src/com/p4square/grow/frontend/IntegrationDriver.java create mode 100644 tst/com/p4square/grow/ccb/CCBUserVerifierTest.java (limited to 'src/com') diff --git a/ivy.xml b/ivy.xml index aa15ef2..7a29104 100644 --- a/ivy.xml +++ b/ivy.xml @@ -24,7 +24,10 @@ + + + diff --git a/src/com/p4square/f1oauth/FellowshipOneIntegrationDriver.java b/src/com/p4square/f1oauth/FellowshipOneIntegrationDriver.java new file mode 100644 index 0000000..e72df5e --- /dev/null +++ b/src/com/p4square/f1oauth/FellowshipOneIntegrationDriver.java @@ -0,0 +1,45 @@ +package com.p4square.f1oauth; + +import com.codahale.metrics.MetricRegistry; +import com.p4square.grow.config.Config; +import com.p4square.grow.frontend.IntegrationDriver; +import org.restlet.Context; +import org.restlet.security.Verifier; + +/** + * The FellowshipOneIntegrationDriver creates implementations of various + * objects to support integration with Fellowship One. + */ +public class FellowshipOneIntegrationDriver implements IntegrationDriver { + + private final Context mContext; + private final MetricRegistry mMetricRegistry; + private final Config mConfig; + private final F1Access mAPI; + + public FellowshipOneIntegrationDriver(final Context context) { + mContext = context; + mConfig = (Config) context.getAttributes().get("com.p4square.grow.config"); + mMetricRegistry = (MetricRegistry) context.getAttributes().get("com.p4square.grow.metrics"); + + mAPI = new F1Access(context, + mConfig.getString("f1ConsumerKey", ""), + mConfig.getString("f1ConsumerSecret", ""), + mConfig.getString("f1BaseUrl", "staging.fellowshiponeapi.com"), + mConfig.getString("f1ChurchCode", "pfseawa"), + F1Access.UserType.WEBLINK); + mAPI.setMetricRegistry(mMetricRegistry); + } + + /** + * @return An F1Access instance. + */ + public F1Access getF1Access() { + return mAPI; + } + + @Override + public Verifier newUserAuthenticationVerifier() { + return new SecondPartyVerifier(mContext, mAPI); + } +} diff --git a/src/com/p4square/grow/ccb/CCBUser.java b/src/com/p4square/grow/ccb/CCBUser.java new file mode 100644 index 0000000..7313172 --- /dev/null +++ b/src/com/p4square/grow/ccb/CCBUser.java @@ -0,0 +1,37 @@ +package com.p4square.grow.ccb; + +import com.p4square.ccbapi.model.IndividualProfile; +import org.restlet.security.User; + +/** + * CCBUser is an adapter between a CCB IndividualProfile and a Restlet User. + * + * Note: CCBUser prefixes the user's identifier with "CCB-". This is done to + * ensure the identifier does not collide with identifiers from other + * systems. + */ +public class CCBUser extends User { + + private final IndividualProfile mProfile; + + /** + * Wrap an IndividualProfile inside a User object. + * + * @param profile The CCB IndividualProfile for the user. + */ + public CCBUser(final IndividualProfile profile) { + mProfile = profile; + + setIdentifier("CCB-" + mProfile.getId()); + setFirstName(mProfile.getFirstName()); + setLastName(mProfile.getLastName()); + setEmail(mProfile.getEmail()); + } + + /** + * @return The IndividualProfile of the user. + */ + public IndividualProfile getProfile() { + return mProfile; + } +} diff --git a/src/com/p4square/grow/ccb/CCBUserVerifier.java b/src/com/p4square/grow/ccb/CCBUserVerifier.java new file mode 100644 index 0000000..db10b75 --- /dev/null +++ b/src/com/p4square/grow/ccb/CCBUserVerifier.java @@ -0,0 +1,50 @@ +package com.p4square.grow.ccb; + +import com.p4square.ccbapi.CCBAPI; +import com.p4square.ccbapi.model.GetIndividualProfilesRequest; +import com.p4square.ccbapi.model.GetIndividualProfilesResponse; +import org.apache.log4j.Logger; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.security.Verifier; + +/** + * CCBUserVerifier authenticates a user through the CCB individual_profile_from_login_password API. + */ +public class CCBUserVerifier implements Verifier { + private static final Logger LOG = Logger.getLogger(CCBUserVerifier.class); + + private final CCBAPI mAPI; + + public CCBUserVerifier(final CCBAPI api) { + mAPI = api; + } + + @Override + public int verify(Request request, Response response) { + if (request.getChallengeResponse() == null) { + return RESULT_MISSING; // no credentials + } + + final String username = request.getChallengeResponse().getIdentifier(); + final char[] password = request.getChallengeResponse().getSecret(); + + try { + GetIndividualProfilesResponse resp = mAPI.getIndividualProfiles( + new GetIndividualProfilesRequest().withLoginPassword(username, password)); + + if (resp.getIndividuals().size() == 1) { + // Wrap the IndividualProfile up in an User and update the user on the request. + final CCBUser user = new CCBUser(resp.getIndividuals().get(0)); + LOG.info("Successfully authenticated " + user.getIdentifier()); + request.getClientInfo().setUser(user); + return RESULT_VALID; + } + + } catch (Exception e) { + LOG.error("CCB API Exception: " + e, e); + } + + return RESULT_INVALID; // Invalid credentials + } +} diff --git a/src/com/p4square/grow/ccb/ChurchCommunityBuilderIntegrationDriver.java b/src/com/p4square/grow/ccb/ChurchCommunityBuilderIntegrationDriver.java new file mode 100644 index 0000000..3aeca2c --- /dev/null +++ b/src/com/p4square/grow/ccb/ChurchCommunityBuilderIntegrationDriver.java @@ -0,0 +1,50 @@ +package com.p4square.grow.ccb; + +import com.codahale.metrics.MetricRegistry; +import com.p4square.ccbapi.CCBAPI; +import com.p4square.ccbapi.CCBAPIClient; +import com.p4square.grow.config.Config; +import com.p4square.grow.frontend.IntegrationDriver; +import org.restlet.Context; +import org.restlet.security.Verifier; + +import java.net.URI; +import java.net.URISyntaxException; + +/** + * The ChurchCommunityBuilderIntegrationDriver is used to integrate Grow with Church Community Builder. + */ +public class ChurchCommunityBuilderIntegrationDriver implements IntegrationDriver { + + private final Context mContext; + private final MetricRegistry mMetricRegistry; + private final Config mConfig; + + private final CCBAPI mAPI; + + public ChurchCommunityBuilderIntegrationDriver(final Context context) { + mContext = context; + mConfig = (Config) context.getAttributes().get("com.p4square.grow.config"); + mMetricRegistry = (MetricRegistry) context.getAttributes().get("com.p4square.grow.metrics"); + + try { + CCBAPI api = new CCBAPIClient(new URI(mConfig.getString("CCBAPIURL", "")), + mConfig.getString("CCBAPIUser", ""), + mConfig.getString("CCBAPIPassword", "")); + + if (mMetricRegistry != null) { + api = new MonitoredCCBAPI(api, mMetricRegistry); + } + + mAPI = api; + + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + @Override + public Verifier newUserAuthenticationVerifier() { + return new CCBUserVerifier(mAPI); + } +} diff --git a/src/com/p4square/grow/ccb/MonitoredCCBAPI.java b/src/com/p4square/grow/ccb/MonitoredCCBAPI.java new file mode 100644 index 0000000..6903460 --- /dev/null +++ b/src/com/p4square/grow/ccb/MonitoredCCBAPI.java @@ -0,0 +1,68 @@ +package com.p4square.grow.ccb; + +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.Timer; +import com.p4square.ccbapi.CCBAPI; +import com.p4square.ccbapi.model.GetCustomFieldLabelsResponse; +import com.p4square.ccbapi.model.GetIndividualProfilesRequest; +import com.p4square.ccbapi.model.GetIndividualProfilesResponse; + +import java.io.IOException; + +/** + * MonitoredCCBAPI is a CCBAPI decorator which records metrics for each API call. + */ +public class MonitoredCCBAPI implements CCBAPI { + + private final CCBAPI mAPI; + private final MetricRegistry mMetricRegistry; + + public MonitoredCCBAPI(final CCBAPI api, final MetricRegistry metricRegistry) { + if (api == null) { + throw new IllegalArgumentException("api must not be null."); + } + mAPI = api; + + if (metricRegistry == null) { + throw new IllegalArgumentException("metricRegistry must not be null."); + } + mMetricRegistry = metricRegistry; + } + + @Override + public GetCustomFieldLabelsResponse getCustomFieldLabels() throws IOException { + final Timer.Context timer = mMetricRegistry.timer("CCBAPI.getCustomFieldLabels.time").time(); + boolean success = false; + try { + final GetCustomFieldLabelsResponse resp = mAPI.getCustomFieldLabels(); + success = true; + return resp; + } finally { + timer.stop(); + mMetricRegistry.counter("CCBAPI.getCustomFieldLabels.success").inc(success ? 1 : 0); + mMetricRegistry.counter("CCBAPI.getCustomFieldLabels.failure").inc(!success ? 1 : 0); + } + } + + @Override + public GetIndividualProfilesResponse getIndividualProfiles(GetIndividualProfilesRequest request) + throws IOException { + final Timer.Context timer = mMetricRegistry.timer("CCBAPI.getIndividualProfiles").time(); + boolean success = false; + try { + final GetIndividualProfilesResponse resp = mAPI.getIndividualProfiles(request); + mMetricRegistry.counter("CCBAPI.getCustomFieldLabels.count").inc(resp.getIndividuals().size()); + success = true; + return resp; + } finally { + timer.stop(); + mMetricRegistry.counter("CCBAPI.getCustomFieldLabels.success").inc(success ? 1 : 0); + mMetricRegistry.counter("CCBAPI.getCustomFieldLabels.failure").inc(!success ? 1 : 0); + } + } + + @Override + public void close() throws IOException { + mAPI.close(); + } +} diff --git a/src/com/p4square/grow/frontend/AssessmentResultsPage.java b/src/com/p4square/grow/frontend/AssessmentResultsPage.java index 2035ce8..9b66794 100644 --- a/src/com/p4square/grow/frontend/AssessmentResultsPage.java +++ b/src/com/p4square/grow/frontend/AssessmentResultsPage.java @@ -7,6 +7,7 @@ package com.p4square.grow.frontend; import java.util.Date; import java.util.Map; +import com.p4square.f1oauth.FellowshipOneIntegrationDriver; import freemarker.template.Template; import org.restlet.data.MediaType; @@ -117,7 +118,8 @@ public class AssessmentResultsPage extends FreeMarkerPageResource { attribute.setStartDate(new Date()); attribute.setComment(JsonEncodedProvider.MAPPER.writeValueAsString(results)); - F1API f1 = mGrowFrontend.getF1Access().getAuthenticatedApi(user); + F1API f1 = ((FellowshipOneIntegrationDriver) mGrowFrontend.getThirdPartyIntegrationFactory()) + .getF1Access().getAuthenticatedApi(user); if (!f1.addAttribute(user.getIdentifier(), attribute)) { LOG.error("addAttribute failed for " + user.getIdentifier() + " with attribute " + attributeName); diff --git a/src/com/p4square/grow/frontend/ChapterCompletePage.java b/src/com/p4square/grow/frontend/ChapterCompletePage.java index f07a870..2dd1ecf 100644 --- a/src/com/p4square/grow/frontend/ChapterCompletePage.java +++ b/src/com/p4square/grow/frontend/ChapterCompletePage.java @@ -7,6 +7,7 @@ package com.p4square.grow.frontend; import java.util.Date; import java.util.Map; +import com.p4square.f1oauth.FellowshipOneIntegrationDriver; import freemarker.template.Template; import org.restlet.data.MediaType; @@ -172,7 +173,8 @@ public class ChapterCompletePage extends FreeMarkerPageResource { Attribute attribute = new Attribute(attributeName); attribute.setStartDate(new Date()); - F1API f1 = mGrowFrontend.getF1Access().getAuthenticatedApi(user); + F1API f1 = ((FellowshipOneIntegrationDriver) mGrowFrontend.getThirdPartyIntegrationFactory()) + .getF1Access().getAuthenticatedApi(user); if (!f1.addAttribute(user.getIdentifier(), attribute)) { LOG.error("addAttribute failed for " + user.getIdentifier() + " with attribute " + attributeName); diff --git a/src/com/p4square/grow/frontend/GrowFrontend.java b/src/com/p4square/grow/frontend/GrowFrontend.java index 1d221cc..b5f62fb 100644 --- a/src/com/p4square/grow/frontend/GrowFrontend.java +++ b/src/com/p4square/grow/frontend/GrowFrontend.java @@ -6,15 +6,12 @@ package com.p4square.grow.frontend; import java.io.File; import java.io.IOException; - -import java.util.Arrays; -import java.util.UUID; +import java.lang.reflect.Constructor; import freemarker.template.Template; import org.restlet.Application; import org.restlet.Component; -import org.restlet.Client; import org.restlet.Context; import org.restlet.Restlet; import org.restlet.data.Protocol; @@ -32,13 +29,11 @@ import com.p4square.fmfacade.FreeMarkerPageResource; import com.p4square.grow.config.Config; -import com.p4square.f1oauth.F1Access; -import com.p4square.f1oauth.SecondPartyVerifier; - import com.p4square.restlet.metrics.MetricRouter; import com.p4square.session.SessionCheckingAuthenticator; import com.p4square.session.SessionCreatingAuthenticator; +import org.restlet.security.Verifier; /** * This is the Restlet Application implementing the Grow project front-end. @@ -54,7 +49,7 @@ public class GrowFrontend extends FMFacade { private final Config mConfig; private final MetricRegistry mMetricRegistry; - private F1Access mHelper; + private IntegrationDriver mIntegrationFactory; public GrowFrontend() { this(new Config(), new MetricRegistry()); @@ -81,20 +76,26 @@ public class GrowFrontend extends FMFacade { FreeMarkerPageResource.baseRootObject(getContext(), this)); } + getContext().getAttributes().put("com.p4square.grow.config", mConfig); + getContext().getAttributes().put("com.p4square.grow.metrics", mMetricRegistry); + super.start(); } - synchronized F1Access getF1Access() { - if (mHelper == null) { - mHelper = new F1Access(getContext(), mConfig.getString("f1ConsumerKey", ""), - mConfig.getString("f1ConsumerSecret", ""), - mConfig.getString("f1BaseUrl", "staging.fellowshiponeapi.com"), - mConfig.getString("f1ChurchCode", "pfseawa"), - F1Access.UserType.WEBLINK); - mHelper.setMetricRegistry(mMetricRegistry); + public synchronized IntegrationDriver getThirdPartyIntegrationFactory() { + if (mIntegrationFactory == null) { + final String driverClassName = getConfig().getString("integrationDriver", + "com.p4square.f1oauth.FellowshipOneIntegrationDriver"); + try { + Class clazz = Class.forName(driverClassName); + Constructor constructor = clazz.getConstructor(Context.class); + mIntegrationFactory = (IntegrationDriver) constructor.newInstance(getContext()); + } catch (Exception e) { + LOG.error("Failed to instantiate IntegrationDriver " + driverClassName); + } } - return mHelper; + return mIntegrationFactory; } @Override @@ -141,8 +142,8 @@ public class GrowFrontend extends FMFacade { SessionCheckingAuthenticator sessionChk = new SessionCheckingAuthenticator(context, true); // This is used to authenticate the user - SecondPartyVerifier f1Verifier = new SecondPartyVerifier(context, getF1Access()); - LoginFormAuthenticator loginAuth = new LoginFormAuthenticator(context, false, f1Verifier); + Verifier verifier = getThirdPartyIntegrationFactory().newUserAuthenticationVerifier(); + LoginFormAuthenticator loginAuth = new LoginFormAuthenticator(context, false, verifier); loginAuth.setLoginFormUrl(loginPage); loginAuth.setLoginPostUrl(loginPost); loginAuth.setDefaultPage(defaultPage); diff --git a/src/com/p4square/grow/frontend/IntegrationDriver.java b/src/com/p4square/grow/frontend/IntegrationDriver.java new file mode 100644 index 0000000..3370116 --- /dev/null +++ b/src/com/p4square/grow/frontend/IntegrationDriver.java @@ -0,0 +1,17 @@ +package com.p4square.grow.frontend; + +import org.restlet.security.Verifier; + +/** + * An IntegrationDriver is used to create implementations of various objects + * used to integration Grow with a particular Church Management System. + */ +public interface IntegrationDriver { + + /** + * Create a new Restlet Verifier to authenticate users when they login to the site. + * + * @return A Verifier. + */ + Verifier newUserAuthenticationVerifier(); +} diff --git a/src/com/p4square/grow/frontend/NewAccountResource.java b/src/com/p4square/grow/frontend/NewAccountResource.java index 54c1790..5c13017 100644 --- a/src/com/p4square/grow/frontend/NewAccountResource.java +++ b/src/com/p4square/grow/frontend/NewAccountResource.java @@ -6,12 +6,12 @@ package com.p4square.grow.frontend; import java.util.Map; +import com.p4square.f1oauth.FellowshipOneIntegrationDriver; import freemarker.template.Template; import org.restlet.data.Form; import org.restlet.data.MediaType; import org.restlet.data.Status; -import org.restlet.resource.ServerResource; import org.restlet.representation.Representation; import org.restlet.representation.StringRepresentation; import org.restlet.ext.freemarker.TemplateRepresentation; @@ -44,7 +44,14 @@ public class NewAccountResource extends FreeMarkerPageResource { super.doInit(); mGrowFrontend = (GrowFrontend) getApplication(); - mHelper = mGrowFrontend.getF1Access(); + + final IntegrationDriver driver = mGrowFrontend.getThirdPartyIntegrationFactory(); + if (driver instanceof FellowshipOneIntegrationDriver) { + mHelper = ((FellowshipOneIntegrationDriver) driver).getF1Access(); + } else { + LOG.error("NewAccountResource only works with F1!"); + mHelper = null; + } mErrorMessage = ""; @@ -83,6 +90,11 @@ public class NewAccountResource extends FreeMarkerPageResource { @Override protected Representation post(Representation rep) { + if (mHelper == null) { + mErrorMessage += "F1 support is not enabled! "; + return get(); + } + Form form = new Form(rep); String firstname = form.getFirstValue("firstname"); diff --git a/tst/com/p4square/grow/ccb/CCBUserVerifierTest.java b/tst/com/p4square/grow/ccb/CCBUserVerifierTest.java new file mode 100644 index 0000000..d17b698 --- /dev/null +++ b/tst/com/p4square/grow/ccb/CCBUserVerifierTest.java @@ -0,0 +1,139 @@ +package com.p4square.grow.ccb; + +import com.p4square.ccbapi.CCBAPI; +import com.p4square.ccbapi.model.GetIndividualProfilesRequest; +import com.p4square.ccbapi.model.GetIndividualProfilesResponse; +import com.p4square.ccbapi.model.IndividualProfile; +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.Test; +import org.restlet.Request; +import org.restlet.Response; +import org.restlet.data.ChallengeResponse; +import org.restlet.data.ChallengeScheme; +import org.restlet.data.ClientInfo; +import org.restlet.security.Verifier; + +import java.io.IOException; +import java.util.Collections; + +import static org.junit.Assert.*; + +/** + * Tests for CCBUserVerifier. + */ +public class CCBUserVerifierTest { + + private IndividualProfile mProfile = new IndividualProfile(); + + private CCBAPI mAPI; + private CCBUserVerifier verifier; + + private ClientInfo mClientInfo; + private Request mMockRequest; + private Response mMockResponse; + + @Before + public void setUp() { + mAPI = EasyMock.mock(CCBAPI.class); + verifier = new CCBUserVerifier(mAPI); + + mClientInfo = new ClientInfo(); + mMockRequest = EasyMock.mock(Request.class); + EasyMock.expect(mMockRequest.getClientInfo()).andReturn(mClientInfo).anyTimes(); + + mMockResponse = EasyMock.mock(Response.class); + + mProfile.setId(48); + mProfile.setFirstName("Larry"); + mProfile.setLastName("Bob"); + mProfile.setEmail("larry.bob@example.com"); + } + + private void replay() { + EasyMock.replay(mAPI, mMockRequest, mMockResponse); + } + + private void verify() { + EasyMock.verify(mAPI, mMockRequest, mMockResponse); + } + + + @Test + public void testVerifyNoCredentials() throws Exception { + // Prepare mocks + EasyMock.expect(mMockRequest.getChallengeResponse()).andReturn(null).anyTimes(); + replay(); + + // Test + int result = verifier.verify(mMockRequest, mMockResponse); + + // Verify + verify(); + assertEquals(Verifier.RESULT_MISSING, result); + assertNull(mClientInfo.getUser()); + } + + @Test + public void testVerifyAuthFailure() throws Exception { + // Prepare mocks + ChallengeResponse challenge = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "user", "pass"); + EasyMock.expect(mMockRequest.getChallengeResponse()).andReturn(challenge).anyTimes(); + GetIndividualProfilesResponse response = new GetIndividualProfilesResponse(); + response.setIndividuals(Collections.emptyList()); + EasyMock.expect(mAPI.getIndividualProfiles(new GetIndividualProfilesRequest() + .withLoginPassword("user", "pass".toCharArray()))).andReturn(response); + replay(); + + // Test + int result = verifier.verify(mMockRequest, mMockResponse); + + // Verify + verify(); + assertEquals(Verifier.RESULT_INVALID, result); + assertNull(mClientInfo.getUser()); + } + + @Test + public void testVerifyAuthException() throws Exception { + // Prepare mocks + ChallengeResponse challenge = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "user", "pass"); + EasyMock.expect(mMockRequest.getChallengeResponse()).andReturn(challenge).anyTimes(); + EasyMock.expect(mAPI.getIndividualProfiles(EasyMock.anyObject(GetIndividualProfilesRequest.class))) + .andThrow(new IOException()); + replay(); + + // Test + int result = verifier.verify(mMockRequest, mMockResponse); + + // Verify + verify(); + assertEquals(Verifier.RESULT_INVALID, result); + assertNull(mClientInfo.getUser()); + } + + @Test + public void testVerifyAuthSuccess() throws Exception { + // Prepare mocks + ChallengeResponse challenge = new ChallengeResponse(ChallengeScheme.HTTP_BASIC, "user", "pass"); + EasyMock.expect(mMockRequest.getChallengeResponse()).andReturn(challenge).anyTimes(); + GetIndividualProfilesResponse response = new GetIndividualProfilesResponse(); + response.setIndividuals(Collections.singletonList(mProfile)); + EasyMock.expect(mAPI.getIndividualProfiles(new GetIndividualProfilesRequest() + .withLoginPassword("user", "pass".toCharArray()))).andReturn(response); + + replay(); + + // Test + int result = verifier.verify(mMockRequest, mMockResponse); + + // Verify + verify(); + assertEquals(Verifier.RESULT_VALID, result); + assertNotNull(mClientInfo.getUser()); + assertEquals("CCB-48", mClientInfo.getUser().getIdentifier()); + assertEquals("Larry", mClientInfo.getUser().getFirstName()); + assertEquals("Bob", mClientInfo.getUser().getLastName()); + assertEquals("larry.bob@example.com", mClientInfo.getUser().getEmail()); + } +} \ No newline at end of file -- cgit v1.2.3 From ab52d26e4c0e3d939e681020c7c2cc3d14d4b595 Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Mon, 21 Mar 2016 22:16:52 -0700 Subject: Introducing the ProgressReporter interface. A ProgressReporter will be used to record progress in the ChMS. The F1 version in complete, but needs to be plugged into AssessmentResultPage and ChapterCompletePage. --- .gitignore | 3 ++ src/com/p4square/f1oauth/F1ProgressReporter.java | 57 ++++++++++++++++++++++ .../p4square/grow/frontend/ProgressReporter.java | 29 +++++++++++ 3 files changed, 89 insertions(+) create mode 100644 src/com/p4square/f1oauth/F1ProgressReporter.java create mode 100644 src/com/p4square/grow/frontend/ProgressReporter.java (limited to 'src/com') diff --git a/.gitignore b/.gitignore index 4002c41..7e32fa1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,7 @@ build +out/ lib *.swp .idea +*.iml +devfiles/grow-server.properties diff --git a/src/com/p4square/f1oauth/F1ProgressReporter.java b/src/com/p4square/f1oauth/F1ProgressReporter.java new file mode 100644 index 0000000..8382020 --- /dev/null +++ b/src/com/p4square/f1oauth/F1ProgressReporter.java @@ -0,0 +1,57 @@ +package com.p4square.f1oauth; + +import com.p4square.grow.frontend.ProgressReporter; +import org.apache.log4j.Logger; +import org.restlet.security.User; + +import java.util.Date; + +/** + * A ProgressReporter implementation to record progress in F1. + */ +public class F1ProgressReporter implements ProgressReporter { + + private static final Logger LOG = Logger.getLogger(F1ProgressReporter.class); + + private F1Access mF1Access; + + public F1ProgressReporter(final F1Access f1access) { + mF1Access = f1access; + } + + @Override + public void reportAssessmentComplete(final User user, final String level, final Date date, final String results) { + String attributeName = "Assessment Complete - " + level; + Attribute attribute = new Attribute(attributeName); + attribute.setStartDate(date); + attribute.setComment(results); + addAttribute(user, attribute); + } + + @Override + public void reportChapterComplete(final User user, final String chapter, final Date date) { + final String attributeName = "Training Complete - " + chapter; + final Attribute attribute = new Attribute(attributeName); + attribute.setStartDate(date); + addAttribute(user, attribute); + } + + private void addAttribute(final User user, final Attribute attribute) { + if (!(user instanceof F1User)) { + throw new IllegalArgumentException("User must be an F1User, but got " + user.getClass().getName()); + } + + try { + final F1User f1User = (F1User) user; + final F1API f1 = mF1Access.getAuthenticatedApi(f1User); + + if (!f1.addAttribute(user.getIdentifier(), attribute)) { + LOG.error("addAttribute failed for " + user.getIdentifier() + " with attribute " + + attribute.getAttributeName()); + } + } catch (Exception e) { + LOG.error("addAttribute failed for " + user.getIdentifier() + " with attribute " + + attribute.getAttributeName(), e); + } + } +} diff --git a/src/com/p4square/grow/frontend/ProgressReporter.java b/src/com/p4square/grow/frontend/ProgressReporter.java new file mode 100644 index 0000000..9b57ff4 --- /dev/null +++ b/src/com/p4square/grow/frontend/ProgressReporter.java @@ -0,0 +1,29 @@ +package com.p4square.grow.frontend; + +import org.restlet.security.User; + +import java.util.Date; + +/** + * A ProgressReporter is used to record a User's progress in a Church Management System. + */ +public interface ProgressReporter { + + /** + * Report that the User completed the assessment. + * + * @param user The user who completed the assessment. + * @param level The assessment level. + * @param date The completion date. + * @param results Result information (e.g. json of the results). + */ + void reportAssessmentComplete(User user, String level, Date date, String results); + + /** + * + * @param user The user who completed the chapter. + * @param chapter The chatper completed. + * @param date Teh completion date. + */ + void reportChapterComplete(User user, String chapter, Date date); +} -- cgit v1.2.3 From 6698ffbb300ff8e48b5f3fe59144c699d39ab094 Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Tue, 22 Mar 2016 21:42:03 -0700 Subject: Integrating the ProgressReporter Using the new ProgressReporter interface in AssessmentResultsPage and ChapterCompletePage. --- .../f1oauth/FellowshipOneIntegrationDriver.java | 10 +++++++ src/com/p4square/grow/ccb/CCBProgressReporter.java | 32 ++++++++++++++++++++++ .../ChurchCommunityBuilderIntegrationDriver.java | 10 +++++++ .../grow/frontend/AssessmentResultsPage.java | 32 ++++++++-------------- .../grow/frontend/ChapterCompletePage.java | 27 ++++-------------- .../p4square/grow/frontend/IntegrationDriver.java | 9 ++++++ .../p4square/grow/frontend/ProgressReporter.java | 5 ++-- 7 files changed, 80 insertions(+), 45 deletions(-) create mode 100644 src/com/p4square/grow/ccb/CCBProgressReporter.java (limited to 'src/com') diff --git a/src/com/p4square/f1oauth/FellowshipOneIntegrationDriver.java b/src/com/p4square/f1oauth/FellowshipOneIntegrationDriver.java index e72df5e..865f5d6 100644 --- a/src/com/p4square/f1oauth/FellowshipOneIntegrationDriver.java +++ b/src/com/p4square/f1oauth/FellowshipOneIntegrationDriver.java @@ -3,6 +3,7 @@ package com.p4square.f1oauth; import com.codahale.metrics.MetricRegistry; import com.p4square.grow.config.Config; import com.p4square.grow.frontend.IntegrationDriver; +import com.p4square.grow.frontend.ProgressReporter; import org.restlet.Context; import org.restlet.security.Verifier; @@ -17,6 +18,8 @@ public class FellowshipOneIntegrationDriver implements IntegrationDriver { private final Config mConfig; private final F1Access mAPI; + private final ProgressReporter mProgressReporter; + public FellowshipOneIntegrationDriver(final Context context) { mContext = context; mConfig = (Config) context.getAttributes().get("com.p4square.grow.config"); @@ -29,6 +32,8 @@ public class FellowshipOneIntegrationDriver implements IntegrationDriver { mConfig.getString("f1ChurchCode", "pfseawa"), F1Access.UserType.WEBLINK); mAPI.setMetricRegistry(mMetricRegistry); + + mProgressReporter = new F1ProgressReporter(mAPI); } /** @@ -42,4 +47,9 @@ public class FellowshipOneIntegrationDriver implements IntegrationDriver { public Verifier newUserAuthenticationVerifier() { return new SecondPartyVerifier(mContext, mAPI); } + + @Override + public ProgressReporter getProgressReporter() { + return mProgressReporter; + } } diff --git a/src/com/p4square/grow/ccb/CCBProgressReporter.java b/src/com/p4square/grow/ccb/CCBProgressReporter.java new file mode 100644 index 0000000..e2304fe --- /dev/null +++ b/src/com/p4square/grow/ccb/CCBProgressReporter.java @@ -0,0 +1,32 @@ +package com.p4square.grow.ccb; + +import com.p4square.ccbapi.CCBAPI; +import com.p4square.grow.frontend.ProgressReporter; +import org.restlet.security.User; + +import java.util.Date; + +/** + * A ProgressReporter which records progress in CCB. + * + * Except not really, because it's not implemented yet. + * This is just a placeholder until ccb-api-client-java has support for updating an individual. + */ +public class CCBProgressReporter implements ProgressReporter { + + private final CCBAPI mAPI; + + public CCBProgressReporter(final CCBAPI api) { + mAPI = api; + } + + @Override + public void reportAssessmentComplete(User user, String level, Date date, String results) { + // TODO + } + + @Override + public void reportChapterComplete(User user, String chapter, Date date) { + // TODO + } +} diff --git a/src/com/p4square/grow/ccb/ChurchCommunityBuilderIntegrationDriver.java b/src/com/p4square/grow/ccb/ChurchCommunityBuilderIntegrationDriver.java index 3aeca2c..48d143c 100644 --- a/src/com/p4square/grow/ccb/ChurchCommunityBuilderIntegrationDriver.java +++ b/src/com/p4square/grow/ccb/ChurchCommunityBuilderIntegrationDriver.java @@ -5,6 +5,7 @@ import com.p4square.ccbapi.CCBAPI; import com.p4square.ccbapi.CCBAPIClient; import com.p4square.grow.config.Config; import com.p4square.grow.frontend.IntegrationDriver; +import com.p4square.grow.frontend.ProgressReporter; import org.restlet.Context; import org.restlet.security.Verifier; @@ -22,6 +23,8 @@ public class ChurchCommunityBuilderIntegrationDriver implements IntegrationDrive private final CCBAPI mAPI; + private final CCBProgressReporter mProgressReporter; + public ChurchCommunityBuilderIntegrationDriver(final Context context) { mContext = context; mConfig = (Config) context.getAttributes().get("com.p4square.grow.config"); @@ -38,6 +41,8 @@ public class ChurchCommunityBuilderIntegrationDriver implements IntegrationDrive mAPI = api; + mProgressReporter = new CCBProgressReporter(mAPI); + } catch (URISyntaxException e) { throw new RuntimeException(e); } @@ -47,4 +52,9 @@ public class ChurchCommunityBuilderIntegrationDriver implements IntegrationDrive public Verifier newUserAuthenticationVerifier() { return new CCBUserVerifier(mAPI); } + + @Override + public ProgressReporter getProgressReporter() { + return mProgressReporter; + } } diff --git a/src/com/p4square/grow/frontend/AssessmentResultsPage.java b/src/com/p4square/grow/frontend/AssessmentResultsPage.java index 9b66794..f1c924b 100644 --- a/src/com/p4square/grow/frontend/AssessmentResultsPage.java +++ b/src/com/p4square/grow/frontend/AssessmentResultsPage.java @@ -7,6 +7,7 @@ package com.p4square.grow.frontend; import java.util.Date; import java.util.Map; +import com.fasterxml.jackson.core.JsonProcessingException; import com.p4square.f1oauth.FellowshipOneIntegrationDriver; import freemarker.template.Template; @@ -30,6 +31,7 @@ import com.p4square.f1oauth.F1User; import com.p4square.grow.config.Config; import com.p4square.grow.provider.JsonEncodedProvider; +import org.restlet.security.User; /** * This page fetches the user's final score and displays the transitional page between @@ -103,30 +105,18 @@ public class AssessmentResultsPage extends FreeMarkerPageResource { } private void publishScoreInF1(Map results) { - if (!(getRequest().getClientInfo().getUser() instanceof F1User)) { - // Only useful if the user is from F1. - return; - } + final ProgressReporter reporter = mGrowFrontend.getThirdPartyIntegrationFactory().getProgressReporter(); - F1User user = (F1User) getRequest().getClientInfo().getUser(); + try { + final User user = getRequest().getClientInfo().getUser(); + final String level = results.get("result").toString(); + final Date completionDate = new Date(); + final String data = JsonEncodedProvider.MAPPER.writeValueAsString(results); - // Update the attribute. - String attributeName = "Assessment Complete - " + results.get("result"); + reporter.reportAssessmentComplete(user, level, completionDate, data); - try { - Attribute attribute = new Attribute(attributeName); - attribute.setStartDate(new Date()); - attribute.setComment(JsonEncodedProvider.MAPPER.writeValueAsString(results)); - - F1API f1 = ((FellowshipOneIntegrationDriver) mGrowFrontend.getThirdPartyIntegrationFactory()) - .getF1Access().getAuthenticatedApi(user); - if (!f1.addAttribute(user.getIdentifier(), attribute)) { - LOG.error("addAttribute failed for " + user.getIdentifier() - + " with attribute " + attributeName); - } - } catch (Exception e) { - LOG.error("addAttribute failed for " + user.getIdentifier() - + " with attribute " + attributeName, e); + } catch (JsonProcessingException e) { + LOG.error("Failed to generate json " + e.getMessage(), e); } } diff --git a/src/com/p4square/grow/frontend/ChapterCompletePage.java b/src/com/p4square/grow/frontend/ChapterCompletePage.java index 2dd1ecf..35abc43 100644 --- a/src/com/p4square/grow/frontend/ChapterCompletePage.java +++ b/src/com/p4square/grow/frontend/ChapterCompletePage.java @@ -32,6 +32,7 @@ import com.p4square.grow.config.Config; import com.p4square.grow.model.TrainingRecord; import com.p4square.grow.provider.Provider; import com.p4square.grow.provider.TrainingRecordProvider; +import org.restlet.security.User; /** * This resource displays the transitional page between chapters. @@ -159,30 +160,12 @@ public class ChapterCompletePage extends FreeMarkerPageResource { } private void assignAttribute() { - if (!(getRequest().getClientInfo().getUser() instanceof F1User)) { - // Only useful if the user is from F1. - return; - } - - F1User user = (F1User) getRequest().getClientInfo().getUser(); + final ProgressReporter reporter = mGrowFrontend.getThirdPartyIntegrationFactory().getProgressReporter(); - // Update the attribute. - String attributeName = "Training Complete - " + mChapter; + final User user = getRequest().getClientInfo().getUser(); + final Date completionDate = new Date(); - try { - Attribute attribute = new Attribute(attributeName); - attribute.setStartDate(new Date()); - - F1API f1 = ((FellowshipOneIntegrationDriver) mGrowFrontend.getThirdPartyIntegrationFactory()) - .getF1Access().getAuthenticatedApi(user); - if (!f1.addAttribute(user.getIdentifier(), attribute)) { - LOG.error("addAttribute failed for " + user.getIdentifier() - + " with attribute " + attributeName); - } - } catch (Exception e) { - LOG.error("addAttribute failed for " + user.getIdentifier() - + " with attribute " + attributeName, e); - } + reporter.reportChapterComplete(user, mChapter, completionDate); } /** diff --git a/src/com/p4square/grow/frontend/IntegrationDriver.java b/src/com/p4square/grow/frontend/IntegrationDriver.java index 3370116..b9c3508 100644 --- a/src/com/p4square/grow/frontend/IntegrationDriver.java +++ b/src/com/p4square/grow/frontend/IntegrationDriver.java @@ -14,4 +14,13 @@ public interface IntegrationDriver { * @return A Verifier. */ Verifier newUserAuthenticationVerifier(); + + /** + * Return a ProgressReporter for this Church Management System. + * + * The ProgressReporter should be thread-safe. + * + * @return The ProgressReporter. + */ + ProgressReporter getProgressReporter(); } diff --git a/src/com/p4square/grow/frontend/ProgressReporter.java b/src/com/p4square/grow/frontend/ProgressReporter.java index 9b57ff4..2f36832 100644 --- a/src/com/p4square/grow/frontend/ProgressReporter.java +++ b/src/com/p4square/grow/frontend/ProgressReporter.java @@ -20,10 +20,11 @@ public interface ProgressReporter { void reportAssessmentComplete(User user, String level, Date date, String results); /** + * Report that the User completed the chapter. * * @param user The user who completed the chapter. - * @param chapter The chatper completed. - * @param date Teh completion date. + * @param chapter The chapter completed. + * @param date The completion date. */ void reportChapterComplete(User user, String chapter, Date date); } -- cgit v1.2.3 From 37c60c6edea56fedfeb4a22467ec85fca5fc8881 Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Thu, 7 Apr 2016 18:28:49 -0700 Subject: Adding updateIndividualProfile API to MonitoredCCBAPI --- src/com/p4square/grow/ccb/MonitoredCCBAPI.java | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) (limited to 'src/com') diff --git a/src/com/p4square/grow/ccb/MonitoredCCBAPI.java b/src/com/p4square/grow/ccb/MonitoredCCBAPI.java index 6903460..1463c77 100644 --- a/src/com/p4square/grow/ccb/MonitoredCCBAPI.java +++ b/src/com/p4square/grow/ccb/MonitoredCCBAPI.java @@ -3,9 +3,7 @@ package com.p4square.grow.ccb; import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.Timer; import com.p4square.ccbapi.CCBAPI; -import com.p4square.ccbapi.model.GetCustomFieldLabelsResponse; -import com.p4square.ccbapi.model.GetIndividualProfilesRequest; -import com.p4square.ccbapi.model.GetIndividualProfilesResponse; +import com.p4square.ccbapi.model.*; import java.io.IOException; @@ -51,13 +49,28 @@ public class MonitoredCCBAPI implements CCBAPI { boolean success = false; try { final GetIndividualProfilesResponse resp = mAPI.getIndividualProfiles(request); - mMetricRegistry.counter("CCBAPI.getCustomFieldLabels.count").inc(resp.getIndividuals().size()); + mMetricRegistry.counter("CCBAPI.getIndividualProfiles.count").inc(resp.getIndividuals().size()); success = true; return resp; } finally { timer.stop(); - mMetricRegistry.counter("CCBAPI.getCustomFieldLabels.success").inc(success ? 1 : 0); - mMetricRegistry.counter("CCBAPI.getCustomFieldLabels.failure").inc(!success ? 1 : 0); + mMetricRegistry.counter("CCBAPI.getIndividualProfiles.success").inc(success ? 1 : 0); + mMetricRegistry.counter("CCBAPI.getIndividualProfiles.failure").inc(!success ? 1 : 0); + } + } + + @Override + public UpdateIndividualProfileResponse updateIndividualProfile(UpdateIndividualProfileRequest request) throws IOException { + final Timer.Context timer = mMetricRegistry.timer("CCBAPI.updateIndividualProfile").time(); + boolean success = false; + try { + final UpdateIndividualProfileResponse resp = mAPI.updateIndividualProfile(request); + success = true; + return resp; + } finally { + timer.stop(); + mMetricRegistry.counter("CCBAPI.updateIndividualProfile.success").inc(success ? 1 : 0); + mMetricRegistry.counter("CCBAPI.updateIndividualProfile.failure").inc(!success ? 1 : 0); } } -- cgit v1.2.3 From 01efa2f05df48cac2f1e22742aa911a7c0abdb25 Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Thu, 7 Apr 2016 18:38:50 -0700 Subject: Adding getLookupTable API to MonitoredCCBAPI --- src/com/p4square/grow/ccb/MonitoredCCBAPI.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src/com') diff --git a/src/com/p4square/grow/ccb/MonitoredCCBAPI.java b/src/com/p4square/grow/ccb/MonitoredCCBAPI.java index 1463c77..43b6433 100644 --- a/src/com/p4square/grow/ccb/MonitoredCCBAPI.java +++ b/src/com/p4square/grow/ccb/MonitoredCCBAPI.java @@ -42,6 +42,21 @@ public class MonitoredCCBAPI implements CCBAPI { } } + @Override + public GetLookupTableResponse getLookupTable(final GetLookupTableRequest request) throws IOException { + final Timer.Context timer = mMetricRegistry.timer("CCBAPI.getLookupTable.time").time(); + boolean success = false; + try { + final GetLookupTableResponse resp = mAPI.getLookupTable(request); + success = true; + return resp; + } finally { + timer.stop(); + mMetricRegistry.counter("CCBAPI.getLookupTable.success").inc(success ? 1 : 0); + mMetricRegistry.counter("CCBAPI.getLookupTable.failure").inc(!success ? 1 : 0); + } + } + @Override public GetIndividualProfilesResponse getIndividualProfiles(GetIndividualProfilesRequest request) throws IOException { -- cgit v1.2.3 From 918e806c83f812721f0e9527fbc3e3e67d071580 Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Thu, 7 Apr 2016 21:25:12 -0700 Subject: Adding CustomFieldCache for CCB User Defined Fields. --- src/com/p4square/grow/ccb/CustomFieldCache.java | 126 ++++++++++++ .../p4square/grow/ccb/CustomFieldCacheTest.java | 221 +++++++++++++++++++++ 2 files changed, 347 insertions(+) create mode 100644 src/com/p4square/grow/ccb/CustomFieldCache.java create mode 100644 tst/com/p4square/grow/ccb/CustomFieldCacheTest.java (limited to 'src/com') diff --git a/src/com/p4square/grow/ccb/CustomFieldCache.java b/src/com/p4square/grow/ccb/CustomFieldCache.java new file mode 100644 index 0000000..ba473fb --- /dev/null +++ b/src/com/p4square/grow/ccb/CustomFieldCache.java @@ -0,0 +1,126 @@ +package com.p4square.grow.ccb; + +import com.p4square.ccbapi.CCBAPI; +import com.p4square.ccbapi.model.*; +import org.apache.log4j.Logger; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * CustomFieldCache maintains an index from custom field labels to names. + */ +public class CustomFieldCache { + + private static final Logger LOG = Logger.getLogger(CustomFieldCache.class); + + private final CCBAPI mAPI; + + private CustomFieldCollection mTextFields; + private CustomFieldCollection mDateFields; + private CustomFieldCollection mIndividualPulldownFields; + private CustomFieldCollection mGroupPulldownFields; + + private final Map> mItemByNameTable; + + public CustomFieldCache(final CCBAPI api) { + mAPI = api; + mTextFields = new CustomFieldCollection<>(); + mDateFields = new CustomFieldCollection<>(); + mIndividualPulldownFields = new CustomFieldCollection<>(); + mGroupPulldownFields = new CustomFieldCollection<>(); + mItemByNameTable = new HashMap<>(); + } + + public CustomField getTextFieldByLabel(final String label) { + if (mTextFields.size() == 0) { + refresh(); + } + return mTextFields.getByLabel(label); + } + + public CustomField getDateFieldByLabel(final String label) { + if (mDateFields.size() == 0) { + refresh(); + } + return mDateFields.getByLabel(label); + } + + public CustomField getIndividualPulldownByLabel(final String label) { + if (mIndividualPulldownFields.size() == 0) { + refresh(); + } + return mIndividualPulldownFields.getByLabel(label); + } + + public CustomField getGroupPulldownByLabel(final String label) { + if (mGroupPulldownFields.size() == 0) { + refresh(); + } + return mGroupPulldownFields.getByLabel(label); + } + + public LookupTableItem getPulldownItemByName(final LookupTableType type, final String name) { + Map items = mItemByNameTable.get(type); + if (items == null) { + if (!cacheLookupTable(type)) { + return null; + } + items = mItemByNameTable.get(type); + } + + return items.get(name); + } + + private synchronized void refresh() { + try { + // Get all of the custom fields. + final GetCustomFieldLabelsResponse resp = mAPI.getCustomFieldLabels(); + + final CustomFieldCollection newTextFields = new CustomFieldCollection<>(); + final CustomFieldCollection newDateFields = new CustomFieldCollection<>(); + final CustomFieldCollection newIndPulldownFields = new CustomFieldCollection<>(); + final CustomFieldCollection newGrpPulldownFields = new CustomFieldCollection<>(); + + for (final CustomField field : resp.getCustomFields()) { + if (field.getName().startsWith("udf_ind_text_")) { + newTextFields.add(field); + } else if (field.getName().startsWith("udf_ind_date_")) { + newDateFields.add(field); + } else if (field.getName().startsWith("udf_ind_pulldown_")) { + newIndPulldownFields.add(field); + } else if (field.getName().startsWith("udf_grp_pulldown_")) { + newGrpPulldownFields.add(field); + } else { + LOG.warn("Unknown custom field type " + field.getName()); + } + } + + this.mTextFields = newTextFields; + this.mDateFields = newDateFields; + this.mIndividualPulldownFields = newIndPulldownFields; + this.mGroupPulldownFields = newGrpPulldownFields; + + } catch (IOException e) { + // Error fetching labels. + LOG.error("Error fetching custom fields: " + e.getMessage(), e); + } + } + + private synchronized boolean cacheLookupTable(final LookupTableType type) { + try { + final GetLookupTableResponse resp = mAPI.getLookupTable(new GetLookupTableRequest().withType(type)); + mItemByNameTable.put(type, + resp.getItems().stream().collect(Collectors.toMap(LookupTableItem::getName, Function.identity()))); + return true; + + } catch (IOException e) { + LOG.error("Exception caching lookup table of type " + type, e); + } + + return false; + } +} diff --git a/tst/com/p4square/grow/ccb/CustomFieldCacheTest.java b/tst/com/p4square/grow/ccb/CustomFieldCacheTest.java new file mode 100644 index 0000000..e374496 --- /dev/null +++ b/tst/com/p4square/grow/ccb/CustomFieldCacheTest.java @@ -0,0 +1,221 @@ +package com.p4square.grow.ccb; + +import com.p4square.ccbapi.CCBAPI; +import com.p4square.ccbapi.model.*; +import org.easymock.Capture; +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.Arrays; + +import static org.junit.Assert.*; + +/** + * Tests for the CustomFieldCache. + */ +public class CustomFieldCacheTest { + + private CustomFieldCache cache; + + private CCBAPI api; + private GetCustomFieldLabelsResponse customFieldsResponse; + private GetLookupTableResponse lookupTableResponse; + + @Before + public void setUp() { + api = EasyMock.mock(CCBAPI.class); + cache = new CustomFieldCache(api); + + // Prepare some custom fields for the test. + CustomField textField = new CustomField(); + textField.setName("udf_ind_text_6"); + textField.setLabel("Grow Level"); + + CustomField dateField = new CustomField(); + dateField.setName("udf_ind_date_6"); + dateField.setLabel("Grow Level"); + + CustomField pullDown = new CustomField(); + pullDown.setName("udf_ind_pulldown_6"); + pullDown.setLabel("Grow Level"); + + customFieldsResponse = new GetCustomFieldLabelsResponse(); + customFieldsResponse.setCustomFields(Arrays.asList(textField, dateField, pullDown)); + + // Prepare some pulldown items for the tests. + LookupTableItem seeker = new LookupTableItem(); + seeker.setId(1); + seeker.setOrder(1); + seeker.setName("Seeker"); + + LookupTableItem believer = new LookupTableItem(); + believer.setId(2); + believer.setOrder(2); + believer.setName("Believer"); + + lookupTableResponse = new GetLookupTableResponse(); + lookupTableResponse.setItems(Arrays.asList(seeker, believer)); + } + + @Test + public void testGetTextFieldByLabel() throws Exception { + // Setup mocks + EasyMock.expect(api.getCustomFieldLabels()).andReturn(customFieldsResponse); + EasyMock.replay(api); + + // Test the cache + CustomField field = cache.getTextFieldByLabel("Grow Level"); + + // Verify result. + EasyMock.verify(api); + assertEquals("udf_ind_text_6", field.getName()); + assertEquals("Grow Level", field.getLabel()); + } + + @Test + public void testGetDateFieldByLabel() throws Exception { + // Setup mocks + EasyMock.expect(api.getCustomFieldLabels()).andReturn(customFieldsResponse); + EasyMock.replay(api); + + // Test the cache + CustomField field = cache.getDateFieldByLabel("Grow Level"); + + // Verify result. + EasyMock.verify(api); + assertEquals("udf_ind_date_6", field.getName()); + assertEquals("Grow Level", field.getLabel()); + } + + @Test + public void testGetPullDownFieldByLabel() throws Exception { + // Setup mocks + EasyMock.expect(api.getCustomFieldLabels()).andReturn(customFieldsResponse); + EasyMock.replay(api); + + // Test the cache + CustomField field = cache.getIndividualPulldownByLabel("Grow Level"); + + // Verify result. + EasyMock.verify(api); + assertEquals("udf_ind_pulldown_6", field.getName()); + assertEquals("Grow Level", field.getLabel()); + } + + @Test + public void testGetPullDownFieldByLabelMissing() throws Exception { + // Setup mocks + EasyMock.expect(api.getCustomFieldLabels()).andReturn(customFieldsResponse); + EasyMock.replay(api); + + // Test the cache + CustomField field = cache.getIndividualPulldownByLabel("Missing Label"); + + // Verify result. + EasyMock.verify(api); + assertNull(field); + } + + @Test + public void testGetPullDownFieldByLabelException() throws Exception { + // Setup mocks + EasyMock.expect(api.getCustomFieldLabels()).andThrow(new IOException()); + EasyMock.expect(api.getCustomFieldLabels()).andReturn(customFieldsResponse); + EasyMock.replay(api); + + // Test the cache + CustomField field1 = cache.getIndividualPulldownByLabel("Grow Level"); + CustomField field2 = cache.getIndividualPulldownByLabel("Grow Level"); + + // Verify result. + EasyMock.verify(api); + assertNull(field1); + assertNotNull(field2); + } + + @Test + public void testGetMultipleFields() throws Exception { + // Setup mocks + // Note: only one API call. + EasyMock.expect(api.getCustomFieldLabels()).andReturn(customFieldsResponse); + EasyMock.replay(api); + + // Test the cache + CustomField field1 = cache.getTextFieldByLabel("Grow Level"); + CustomField field2 = cache.getIndividualPulldownByLabel("Grow Level"); + + // Verify result. + EasyMock.verify(api); + assertEquals("udf_ind_text_6", field1.getName()); + assertEquals("Grow Level", field1.getLabel()); + assertEquals("udf_ind_pulldown_6", field2.getName()); + assertEquals("Grow Level", field2.getLabel()); + } + + @Test + public void testGetPullDownOptions() throws Exception { + // Setup mocks + Capture requestCapture = EasyMock.newCapture(); + EasyMock.expect(api.getLookupTable(EasyMock.capture(requestCapture))).andReturn(lookupTableResponse); + EasyMock.replay(api); + + // Test the cache + LookupTableItem item = cache.getPulldownItemByName( + LookupTableType.valueOf("udf_ind_pulldown_6".toUpperCase()), + "Believer"); + + // Verify result. + EasyMock.verify(api); + assertEquals(LookupTableType.UDF_IND_PULLDOWN_6, requestCapture.getValue().getType()); + assertEquals(2, item.getId()); + assertEquals(2, item.getOrder()); + assertEquals("Believer", item.getName()); + } + + @Test + public void testGetPullDownOptionMissing() throws Exception { + // Setup mocks + EasyMock.expect(api.getLookupTable(EasyMock.anyObject())).andReturn(lookupTableResponse); + EasyMock.replay(api); + + // Test the cache + LookupTableItem item = cache.getPulldownItemByName(LookupTableType.UDF_IND_PULLDOWN_6, "Something else"); + + // Verify result. + EasyMock.verify(api); + assertNull(item); + } + + @Test + public void testGetPullDownMissing() throws Exception { + // Setup mocks + EasyMock.expect(api.getLookupTable(EasyMock.anyObject())).andReturn(new GetLookupTableResponse()); + EasyMock.replay(api); + + // Test the cache + LookupTableItem item = cache.getPulldownItemByName(LookupTableType.UDF_IND_PULLDOWN_6, "Believer"); + + // Verify result. + EasyMock.verify(api); + assertNull(item); + } + + @Test + public void testGetPullDownException() throws Exception { + // Setup mocks + EasyMock.expect(api.getLookupTable(EasyMock.anyObject())).andThrow(new IOException()); + EasyMock.expect(api.getLookupTable(EasyMock.anyObject())).andReturn(lookupTableResponse); + EasyMock.replay(api); + + // Test the cache + LookupTableItem item1 = cache.getPulldownItemByName(LookupTableType.UDF_IND_PULLDOWN_6, "Believer"); + LookupTableItem item2 = cache.getPulldownItemByName(LookupTableType.UDF_IND_PULLDOWN_6, "Believer"); + + // Verify result. + EasyMock.verify(api); + assertNull(item1); + assertNotNull(item2); + } +} \ No newline at end of file -- cgit v1.2.3 From 10c5fd17b603f125ae2c0ef14b1a65341dbdf961 Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Sat, 9 Apr 2016 09:48:56 -0700 Subject: CustomFieldCache is now case-insensitive. --- src/com/p4square/grow/ccb/CustomFieldCache.java | 6 +++--- tst/com/p4square/grow/ccb/CustomFieldCacheTest.java | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) (limited to 'src/com') diff --git a/src/com/p4square/grow/ccb/CustomFieldCache.java b/src/com/p4square/grow/ccb/CustomFieldCache.java index ba473fb..d93e6d9 100644 --- a/src/com/p4square/grow/ccb/CustomFieldCache.java +++ b/src/com/p4square/grow/ccb/CustomFieldCache.java @@ -72,7 +72,7 @@ public class CustomFieldCache { items = mItemByNameTable.get(type); } - return items.get(name); + return items.get(name.toLowerCase()); } private synchronized void refresh() { @@ -113,8 +113,8 @@ public class CustomFieldCache { private synchronized boolean cacheLookupTable(final LookupTableType type) { try { final GetLookupTableResponse resp = mAPI.getLookupTable(new GetLookupTableRequest().withType(type)); - mItemByNameTable.put(type, - resp.getItems().stream().collect(Collectors.toMap(LookupTableItem::getName, Function.identity()))); + mItemByNameTable.put(type, resp.getItems().stream().collect( + Collectors.toMap(item -> item.getName().toLowerCase(), Function.identity()))); return true; } catch (IOException e) { diff --git a/tst/com/p4square/grow/ccb/CustomFieldCacheTest.java b/tst/com/p4square/grow/ccb/CustomFieldCacheTest.java index e374496..bcfd260 100644 --- a/tst/com/p4square/grow/ccb/CustomFieldCacheTest.java +++ b/tst/com/p4square/grow/ccb/CustomFieldCacheTest.java @@ -174,6 +174,26 @@ public class CustomFieldCacheTest { assertEquals("Believer", item.getName()); } + @Test + public void testGetPullDownOptionsMixedCase() throws Exception { + // Setup mocks + Capture requestCapture = EasyMock.newCapture(); + EasyMock.expect(api.getLookupTable(EasyMock.capture(requestCapture))).andReturn(lookupTableResponse); + EasyMock.replay(api); + + // Test the cache + LookupTableItem item = cache.getPulldownItemByName( + LookupTableType.valueOf("udf_ind_pulldown_6".toUpperCase()), + "BeLiEvEr"); + + // Verify result. + EasyMock.verify(api); + assertEquals(LookupTableType.UDF_IND_PULLDOWN_6, requestCapture.getValue().getType()); + assertEquals(2, item.getId()); + assertEquals(2, item.getOrder()); + assertEquals("Believer", item.getName()); + } + @Test public void testGetPullDownOptionMissing() throws Exception { // Setup mocks -- cgit v1.2.3 From 9d7cd517e8a00049357ce6ec4b65cb5a12dc2f24 Mon Sep 17 00:00:00 2001 From: Jesse Morgan Date: Sat, 9 Apr 2016 09:51:01 -0700 Subject: Implementing CCBProgressReporter This reporter updates two pairs of user-defined fields on an Individual Profile. The first pair are pulldown & date fields used to track the assessment results. The second pair are updated after each chapter is completed. --- src/com/p4square/grow/ccb/CCBProgressReporter.java | 82 ++++++- .../ChurchCommunityBuilderIntegrationDriver.java | 3 +- src/com/p4square/grow/model/Score.java | 2 + .../p4square/grow/ccb/CCBProgressReporterTest.java | 235 +++++++++++++++++++++ 4 files changed, 316 insertions(+), 6 deletions(-) create mode 100644 tst/com/p4square/grow/ccb/CCBProgressReporterTest.java (limited to 'src/com') diff --git a/src/com/p4square/grow/ccb/CCBProgressReporter.java b/src/com/p4square/grow/ccb/CCBProgressReporter.java index e2304fe..d2826eb 100644 --- a/src/com/p4square/grow/ccb/CCBProgressReporter.java +++ b/src/com/p4square/grow/ccb/CCBProgressReporter.java @@ -1,9 +1,15 @@ package com.p4square.grow.ccb; import com.p4square.ccbapi.CCBAPI; +import com.p4square.ccbapi.model.*; import com.p4square.grow.frontend.ProgressReporter; +import com.p4square.grow.model.Score; +import org.apache.log4j.Logger; import org.restlet.security.User; +import java.io.IOException; +import java.time.LocalDate; +import java.time.ZoneId; import java.util.Date; /** @@ -14,19 +20,85 @@ import java.util.Date; */ public class CCBProgressReporter implements ProgressReporter { + private static final Logger LOG = Logger.getLogger(CCBProgressReporter.class); + + private static final String GROW_LEVEL = "GrowLevelTrain"; + private static final String GROW_ASSESSMENT = "GrowLevelAsmnt"; + private final CCBAPI mAPI; + private final CustomFieldCache mCache; - public CCBProgressReporter(final CCBAPI api) { + public CCBProgressReporter(final CCBAPI api, final CustomFieldCache cache) { mAPI = api; + mCache = cache; } @Override - public void reportAssessmentComplete(User user, String level, Date date, String results) { - // TODO + public void reportAssessmentComplete(final User user, final String level, final Date date, final String results) { + if (!(user instanceof CCBUser)) { + throw new IllegalArgumentException("Expected CCBUser but got " + user.getClass().getCanonicalName()); + } + final CCBUser ccbuser = (CCBUser) user; + + updateLevelAndDate(ccbuser, GROW_ASSESSMENT, level, date); } @Override - public void reportChapterComplete(User user, String chapter, Date date) { - // TODO + public void reportChapterComplete(final User user, final String chapter, final Date date) { + if (!(user instanceof CCBUser)) { + throw new IllegalArgumentException("Expected CCBUser but got " + user.getClass().getCanonicalName()); + } + final CCBUser ccbuser = (CCBUser) user; + + // Only update the level if it is increasing. + final CustomPulldownFieldValue currentLevel = ccbuser.getProfile() + .getCustomPulldownFields().getByLabel(GROW_LEVEL); + + if (currentLevel != null) { + if (Score.numericScore(chapter) <= Score.numericScore(currentLevel.getSelection().getLabel())) { + LOG.info("Not updating level for " + user.getIdentifier() + + " because current level (" + currentLevel.getSelection().getLabel() + + ") is greater than new level (" + chapter + ")"); + return; + } + } + + updateLevelAndDate(ccbuser, GROW_LEVEL, chapter, date); + } + + private void updateLevelAndDate(final CCBUser user, final String field, final String level, final Date date) { + boolean modified = false; + + final UpdateIndividualProfileRequest req = new UpdateIndividualProfileRequest() + .withIndividualId(user.getProfile().getId()); + + final CustomField pulldownField = mCache.getIndividualPulldownByLabel(field); + if (pulldownField != null) { + final LookupTableType type = LookupTableType.valueOf(pulldownField.getName().toUpperCase()); + final LookupTableItem item = mCache.getPulldownItemByName(type, level); + if (item != null) { + req.withCustomPulldownField(pulldownField.getName(), item.getId()); + modified = true; + } + } + + final CustomField dateField = mCache.getDateFieldByLabel(field); + if (dateField != null) { + req.withCustomDateField(dateField.getName(), date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate()); + modified = true; + } + + try { + // Only update if a field exists. + if (modified) { + mAPI.updateIndividualProfile(req); + } + + } catch (IOException e) { + LOG.error("updateIndividual failed for " + user.getIdentifier() + + ", field " + field + + ", level " + level + + ", date " + date.toString()); + } } } diff --git a/src/com/p4square/grow/ccb/ChurchCommunityBuilderIntegrationDriver.java b/src/com/p4square/grow/ccb/ChurchCommunityBuilderIntegrationDriver.java index 48d143c..fc6148f 100644 --- a/src/com/p4square/grow/ccb/ChurchCommunityBuilderIntegrationDriver.java +++ b/src/com/p4square/grow/ccb/ChurchCommunityBuilderIntegrationDriver.java @@ -41,7 +41,8 @@ public class ChurchCommunityBuilderIntegrationDriver implements IntegrationDrive mAPI = api; - mProgressReporter = new CCBProgressReporter(mAPI); + final CustomFieldCache cache = new CustomFieldCache(mAPI); + mProgressReporter = new CCBProgressReporter(mAPI, cache); } catch (URISyntaxException e) { throw new RuntimeException(e); diff --git a/src/com/p4square/grow/model/Score.java b/src/com/p4square/grow/model/Score.java index 82f26c9..031c309 100644 --- a/src/com/p4square/grow/model/Score.java +++ b/src/com/p4square/grow/model/Score.java @@ -17,6 +17,8 @@ public class Score { * numericScore(x.toString()) <= x.getScore() */ public static double numericScore(String score) { + score = score.toLowerCase(); + if ("teacher".equals(score)) { return 3.5; } else if ("disciple".equals(score)) { diff --git a/tst/com/p4square/grow/ccb/CCBProgressReporterTest.java b/tst/com/p4square/grow/ccb/CCBProgressReporterTest.java new file mode 100644 index 0000000..6854f22 --- /dev/null +++ b/tst/com/p4square/grow/ccb/CCBProgressReporterTest.java @@ -0,0 +1,235 @@ +package com.p4square.grow.ccb; + +import com.p4square.ccbapi.CCBAPI; +import com.p4square.ccbapi.model.*; +import org.easymock.Capture; +import org.easymock.EasyMock; +import org.junit.Before; +import org.junit.Test; + +import java.time.LocalDate; +import java.util.Date; + +import static org.junit.Assert.*; + +/** + * Tests for the CCBProgressReporter. + */ +public class CCBProgressReporterTest { + + private static final String GROW_LEVEL = "GrowLevelTrain"; + private static final String ASSESSMENT_LEVEL = "GrowLevelAsmnt"; + + private CCBProgressReporter reporter; + + private CCBAPI api; + private CustomFieldCache cache; + + private CCBUser user; + private Date date; + + @Before + public void setUp() { + // Setup some data for testing. + IndividualProfile profile = new IndividualProfile(); + profile.setId(123); + profile.setFirstName("Larry"); + profile.setLastName("Cucumber"); + profile.setEmail("larry.cucumber@example.com"); + + user = new CCBUser(profile); + date = new Date(1427889600000L); // 2015-04-01 + + // Setup the mocks. + api = EasyMock.mock(CCBAPI.class); + cache = EasyMock.mock(CustomFieldCache.class); + reporter = new CCBProgressReporter(api, cache); + } + + private void setupCacheMocks() { + // Setup the Grow Level field. + CustomField growLevelDate = new CustomField(); + growLevelDate.setName("udf_ind_date_1"); + growLevelDate.setLabel(GROW_LEVEL); + + CustomField growLevelPulldown = new CustomField(); + growLevelPulldown.setName("udf_ind_pulldown_1"); + growLevelPulldown.setLabel(GROW_LEVEL); + + LookupTableItem believer = new LookupTableItem(); + believer.setId(1); + believer.setOrder(2); + believer.setName("Believer"); + + EasyMock.expect(cache.getDateFieldByLabel(GROW_LEVEL)) + .andReturn(growLevelDate).anyTimes(); + EasyMock.expect(cache.getIndividualPulldownByLabel(GROW_LEVEL)) + .andReturn(growLevelPulldown).anyTimes(); + EasyMock.expect(cache.getPulldownItemByName(LookupTableType.UDF_IND_PULLDOWN_1, "Believer")) + .andReturn(believer).anyTimes(); + + // Setup the Grow Assessment field. + CustomField growAssessmentDate = new CustomField(); + growAssessmentDate.setName("udf_ind_date_2"); + growAssessmentDate.setLabel(ASSESSMENT_LEVEL); + + CustomField growAssessmentPulldown = new CustomField(); + growAssessmentPulldown.setName("udf_ind_pulldown_2"); + growAssessmentPulldown.setLabel(ASSESSMENT_LEVEL); + + EasyMock.expect(cache.getDateFieldByLabel(ASSESSMENT_LEVEL)) + .andReturn(growAssessmentDate).anyTimes(); + EasyMock.expect(cache.getIndividualPulldownByLabel(ASSESSMENT_LEVEL)) + .andReturn(growAssessmentPulldown).anyTimes(); + EasyMock.expect(cache.getPulldownItemByName(LookupTableType.UDF_IND_PULLDOWN_2, "Believer")) + .andReturn(believer).anyTimes(); + } + + @Test + public void reportAssessmentComplete() throws Exception { + // Setup mocks + setupCacheMocks(); + Capture reqCapture = EasyMock.newCapture(); + EasyMock.expect(api.updateIndividualProfile(EasyMock.capture(reqCapture))) + .andReturn(EasyMock.mock(UpdateIndividualProfileResponse.class)); + replay(); + + // Test reporter + reporter.reportAssessmentComplete(user, "Believer", date, "Data"); + + // Assert that the profile was updated. + verify(); + assertTrue(reqCapture.hasCaptured()); + UpdateIndividualProfileRequest req = reqCapture.getValue(); + + // Both the Grow Level and Grow Assessment fields should be updated. + assertEquals(1, req.getCustomPulldownFields().get("udf_pulldown_1").intValue()); + assertEquals("2015-04-01", req.getCustomDateFields().get("udf_date_1").toString()); + assertEquals(1, req.getCustomPulldownFields().get("udf_pulldown_2").intValue()); + assertEquals("2015-04-01", req.getCustomDateFields().get("udf_date_2").toString()); + } + + @Test + public void testReportChapterCompleteNoPreviousChapter() throws Exception { + // Setup mocks + setupCacheMocks(); + Capture reqCapture = EasyMock.newCapture(); + EasyMock.expect(api.updateIndividualProfile(EasyMock.capture(reqCapture))) + .andReturn(EasyMock.mock(UpdateIndividualProfileResponse.class)); + replay(); + + // Test reporter + reporter.reportChapterComplete(user, "Believer", date); + + // Assert that the profile was updated. + verify(); + assertTrue(reqCapture.hasCaptured()); + UpdateIndividualProfileRequest req = reqCapture.getValue(); + assertEquals(1, req.getCustomPulldownFields().get("udf_pulldown_1").intValue()); + assertEquals("2015-04-01", req.getCustomDateFields().get("udf_date_1").toString()); + } + + @Test + public void testReportChapterCompleteLowerPreviousChapter() throws Exception { + // Setup mocks + setupCacheMocks(); + Capture reqCapture = EasyMock.newCapture(); + EasyMock.expect(api.updateIndividualProfile(EasyMock.capture(reqCapture))) + .andReturn(EasyMock.mock(UpdateIndividualProfileResponse.class)); + + setUserPulldownSelection(GROW_LEVEL, "Seeker"); + + replay(); + + // Test reporter + reporter.reportChapterComplete(user, "Believer", date); + + // Assert that the profile was updated. + verify(); + assertTrue(reqCapture.hasCaptured()); + UpdateIndividualProfileRequest req = reqCapture.getValue(); + assertEquals(1, req.getCustomPulldownFields().get("udf_pulldown_1").intValue()); + assertEquals("2015-04-01", req.getCustomDateFields().get("udf_date_1").toString()); + } + + @Test + public void testReportChapterCompleteHigherPreviousChapter() throws Exception { + // Setup mocks + setupCacheMocks(); + setUserPulldownSelection(GROW_LEVEL, "Disciple"); + + replay(); + + // Test reporter + reporter.reportChapterComplete(user, "Believer", date); + + // Assert that the profile was updated. + verify(); + } + + @Test + public void testReportChapterCompleteNoCustomField() throws Exception { + // Setup mocks + EasyMock.expect(cache.getDateFieldByLabel(EasyMock.anyString())).andReturn(null).anyTimes(); + EasyMock.expect(cache.getIndividualPulldownByLabel(EasyMock.anyString())).andReturn(null).anyTimes(); + EasyMock.expect(cache.getPulldownItemByName(EasyMock.anyObject(), EasyMock.anyString())) + .andReturn(null).anyTimes(); + replay(); + + // Test reporter + reporter.reportChapterComplete(user, "Believer", date); + + // Assert that the profile was updated. + verify(); + } + + @Test + public void testReportChapterCompleteNoSuchValue() throws Exception { + // Setup mocks + setupCacheMocks(); + EasyMock.expect(cache.getPulldownItemByName(LookupTableType.UDF_IND_PULLDOWN_1, "Foo")) + .andReturn(null).anyTimes(); + Capture reqCapture = EasyMock.newCapture(); + EasyMock.expect(api.updateIndividualProfile(EasyMock.capture(reqCapture))) + .andReturn(EasyMock.mock(UpdateIndividualProfileResponse.class)); + replay(); + + // Test reporter + reporter.reportChapterComplete(user, "Foo", date); + + // Assert that the profile was updated. + verify(); + assertTrue(reqCapture.hasCaptured()); + UpdateIndividualProfileRequest req = reqCapture.getValue(); + assertNull(req.getCustomPulldownFields().get("udf_pulldown_1")); + assertEquals("2015-04-01", req.getCustomDateFields().get("udf_date_1").toString()); + } + + private void setUserPulldownSelection(final String field, final String value) { + // Get the pulldown field collection for the user. + CustomFieldCollection pulldowns = user.getProfile().getCustomPulldownFields(); + if (pulldowns == null) { + pulldowns = new CustomFieldCollection<>(); + user.getProfile().setCustomPulldownFields(pulldowns); + } + + // Create the selection for the value. + PulldownSelection selection = new PulldownSelection(); + selection.setLabel(value); + + // Create the field/value pair and add it to the collection. + CustomPulldownFieldValue fieldValue = new CustomPulldownFieldValue(); + fieldValue.setName(field); // This is unused by the test, but it should be a udf_ identifier. + fieldValue.setLabel(field); + fieldValue.setSelection(selection); + pulldowns.add(fieldValue); + } + + private void replay() { + EasyMock.replay(api, cache); + } + + private void verify() { + EasyMock.verify(api, cache); + } +} \ No newline at end of file -- cgit v1.2.3