Merge branch 'master' of https://code.google.com/p/plexi.sharepoint
diff --git a/src/com/google/enterprise/adaptor/sharepoint/AuthenticationResult.java b/src/com/google/enterprise/adaptor/sharepoint/AuthenticationResult.java
new file mode 100644
index 0000000..0e86c0e
--- /dev/null
+++ b/src/com/google/enterprise/adaptor/sharepoint/AuthenticationResult.java
@@ -0,0 +1,44 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+
+package com.google.enterprise.adaptor.sharepoint;
+
+public class AuthenticationResult {
+  private final String cookie;
+  private final int cookieTimeOut;
+  private final String errorCode;
+
+  public AuthenticationResult(String cookie,
+      int cookieTimeOut, String errorCode) {
+    if (cookieTimeOut <= 0) {
+      throw new IllegalArgumentException();
+    }
+    this.cookie = cookie;
+    this.cookieTimeOut = cookieTimeOut;
+    this.errorCode = errorCode;      
+  }
+
+  public String getCookie() {
+    return cookie;
+  }
+
+  public int getCookieTimeOut() {
+    return cookieTimeOut;
+  }
+
+  public String getErrorCode() {
+    return errorCode;
+  }
+}
diff --git a/src/com/google/enterprise/adaptor/sharepoint/FormsAuthenticationHandler.java b/src/com/google/enterprise/adaptor/sharepoint/FormsAuthenticationHandler.java
index 035dafc..a223818 100644
--- a/src/com/google/enterprise/adaptor/sharepoint/FormsAuthenticationHandler.java
+++ b/src/com/google/enterprise/adaptor/sharepoint/FormsAuthenticationHandler.java
@@ -16,24 +16,15 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
-import com.microsoft.schemas.sharepoint.soap.authentication.AuthenticationMode;
-import com.microsoft.schemas.sharepoint.soap.authentication.AuthenticationSoap;
-import com.microsoft.schemas.sharepoint.soap.authentication.LoginErrorCode;
-import com.microsoft.schemas.sharepoint.soap.authentication.LoginResult;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.TimeUnit;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import javax.xml.ws.BindingProvider;
-import javax.xml.ws.WebServiceException;
-import javax.xml.ws.handler.MessageContext;
-
 
 /**
  * Helper class to handle forms authentication.
@@ -54,19 +45,19 @@
   private final Runnable refreshRunnable = new RefreshRunnable();  
   private final List<String> authenticationCookiesList 
       = new CopyOnWriteArrayList<String>();
-  private AuthenticationMode authenticationMode;
-  private final AuthenticationSoap authenticationClient;
-  
+  private final AuthenticationHandler authenticationClient;
+  private boolean isFormsAuthentication = false;
+
   @VisibleForTesting    
   FormsAuthenticationHandler(String userName, String password,
       ScheduledExecutorService executor,
-      AuthenticationSoap authenticationClient) {
+      AuthenticationHandler authenticationClient) {
     if (userName == null || password == null || executor == null || 
         authenticationClient == null) {
       throw new NullPointerException();
     }
     this.userName = userName;
-    this.password = password;   
+    this.password = password;
     this.executor = executor;
     this.authenticationClient = authenticationClient;
   }
@@ -76,54 +67,38 @@
   }
   
   public boolean isFormsAuthentication() {
-    return authenticationMode == AuthenticationMode.FORMS;
+    return isFormsAuthentication;
   }
   
   private void refreshCookies() throws IOException {
-    log.log(Level.FINE, "AuthenticationMode = {0}", authenticationMode);
-    if (authenticationMode != AuthenticationMode.FORMS) {
-      return;
-    }
     
     if ("".equals(userName) || "".equals(password)) {
       log.log(Level.FINE, 
           "Empty username / password. Using authentication mode as Windows");
-       authenticationMode = AuthenticationMode.WINDOWS;
+       isFormsAuthentication = false;
        return;
     }
-    
-    LoginResult result;
-    try {
-      result = authenticationClient.login(userName, password);
-    } catch (WebServiceException ex) {
-      log.log(Level.WARNING,
-          "Possible SP2013 environment with windows authentication", ex);
-      authenticationMode = AuthenticationMode.WINDOWS;
+
+    if (!isFormsAuthentication) {
       return;
     }
-    log.log(Level.FINE, 
-        "Login Cookie Expiration in = {0}", result.getTimeoutSeconds());
-    if (result.getErrorCode() != LoginErrorCode.NO_ERROR) {
-      log.log(Level.WARNING, "Forms authentication failed with authentication "
-          + "web service with Error Code {0}. Possible SharePoint environment "
-          + "with multiple claims provider. Adaptor might have been configured "
-          + "to use windows authentication.", result.getErrorCode());
+
+    log.log(Level.FINE, "About to refresh authentication cookie.");
+    AuthenticationResult result = authenticationClient.authenticate();
+    log.log(Level.FINE, "Authentication Result {0}", result.getErrorCode());
+
+    String cookie = result.getCookie();
+    if (Strings.isNullOrEmpty(cookie)) {
+      log.log(Level.INFO, "Authentication cookie is null or empty."
+          + " Adaptor will use Windows authentication.");
       return;
     }
-    @SuppressWarnings("unchecked")
-    Map<String, Object> responseHeaders
-        = (Map<String, Object>) ((BindingProvider) authenticationClient)
-        .getResponseContext().get(MessageContext.HTTP_RESPONSE_HEADERS);
-    log.log(Level.FINEST, "Response headers: {0}", responseHeaders);
-    @SuppressWarnings("unchecked")
-    String cookies = ((List<String>) responseHeaders.get("Set-cookie")).get(0);
     if (authenticationCookiesList.isEmpty()) {
-      authenticationCookiesList.add(cookies);
+      authenticationCookiesList.add(cookie);
     } else {
-      authenticationCookiesList.set(0, cookies);
+      authenticationCookiesList.set(0, cookie);
     }
-    long cookieTimeOut = (result.getTimeoutSeconds() == null) ? 
-        DEFAULT_COOKIE_TIMEOUT_SECONDS : result.getTimeoutSeconds();
+    long cookieTimeOut = result.getCookieTimeOut();
 
     long rerunAfter = (cookieTimeOut + 1) / 2;
     executor.schedule(refreshRunnable, rerunAfter, TimeUnit.SECONDS);
@@ -132,7 +107,13 @@
  }
  
   public void start() throws IOException {
-    authenticationMode = authenticationClient.mode();
+    if ("".equals(userName) || "".equals(password)) {
+      log.log(Level.FINE, "Empty username or password. Using windows"
+          + " integrated authentication.");
+       isFormsAuthentication = false;
+       return;
+    }
+    isFormsAuthentication = authenticationClient.isFormsAuthentication();
     refreshCookies();
   }
 
@@ -148,4 +129,9 @@
       }
     }
   }
-}
+  
+  interface AuthenticationHandler {
+    AuthenticationResult authenticate() throws IOException;
+    boolean isFormsAuthentication() throws IOException;
+  }  
+}
\ No newline at end of file
diff --git a/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java b/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java
index 4500ef4..22fc5eb 100644
--- a/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java
+++ b/src/com/google/enterprise/adaptor/sharepoint/SharePointAdaptor.java
@@ -474,9 +474,12 @@
     scheduledExecutor = new ScheduledThreadPoolExecutor(1);
     String authenticationEndPoint = spUrlToUri(
         virtualServer + "/_vti_bin/Authentication.asmx").toString();
+    SharePointFormsAuthenticationHandler authenticationClient 
+        = new SharePointFormsAuthenticationHandler(
+            soapFactory.newAuthentication(authenticationEndPoint),
+            username, password);
     authenticationHandler = new FormsAuthenticationHandler(username,
-        password, scheduledExecutor,
-        soapFactory.newAuthentication(authenticationEndPoint));
+        password, scheduledExecutor, authenticationClient);
     authenticationHandler.start();
     executor = executorFactory.call();
     try {
diff --git a/src/com/google/enterprise/adaptor/sharepoint/SharePointFormsAuthenticationHandler.java b/src/com/google/enterprise/adaptor/sharepoint/SharePointFormsAuthenticationHandler.java
new file mode 100644
index 0000000..c3c9406
--- /dev/null
+++ b/src/com/google/enterprise/adaptor/sharepoint/SharePointFormsAuthenticationHandler.java
@@ -0,0 +1,131 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.enterprise.adaptor.sharepoint;
+
+import com.google.enterprise.adaptor.sharepoint.FormsAuthenticationHandler.AuthenticationHandler;
+import com.microsoft.schemas.sharepoint.soap.authentication.AuthenticationMode;
+import com.microsoft.schemas.sharepoint.soap.authentication.AuthenticationSoap;
+import com.microsoft.schemas.sharepoint.soap.authentication.LoginErrorCode;
+import com.microsoft.schemas.sharepoint.soap.authentication.LoginResult;
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.xml.ws.BindingProvider;
+import javax.xml.ws.WebServiceException;
+import javax.xml.ws.handler.MessageContext;
+
+/**
+ * AuthenticationHandler implementation for SharePoint forms authentication
+ * using Authentication.asmx web service.
+*/
+public class SharePointFormsAuthenticationHandler 
+    implements AuthenticationHandler {
+  private static final Logger log
+      = Logger.getLogger(SharePointFormsAuthenticationHandler.class.getName());
+  // Default time out for forms authentication with .NET is 30 mins
+  private static final int DEFAULT_COOKIE_TIMEOUT_SECONDS = 30 * 60;
+  private final AuthenticationSoap authenticationClient;
+  private AuthenticationMode authenticationMode;
+  private final String username;
+  private final String password;
+
+  SharePointFormsAuthenticationHandler(AuthenticationSoap authenticationClient,
+      String username, String password) {
+    if (authenticationClient == null || username == null || password == null) {
+      throw new NullPointerException();
+    }
+    this.authenticationClient = authenticationClient;
+    this.username = username;
+    this.password = password;
+  }
+
+  public AuthenticationResult authenticate() throws IOException {
+    if (!isFormsAuthentication()) {
+      return new AuthenticationResult(null, DEFAULT_COOKIE_TIMEOUT_SECONDS,
+          LoginErrorCode.NOT_IN_FORMS_AUTHENTICATION_MODE.toString());
+    }
+    LoginResult result;
+    try {
+      result = authenticationClient.login(username, password);
+    } catch (WebServiceException ex) {
+      log.log(Level.WARNING,
+          "Forms authentication failed.", ex);
+      log.log(Level.INFO, "Possible SharePoint environment configured to use "
+          + "claims based windows integrated authentication. "
+          + "Adaptor will fallback to use windows integrated authentication.");      
+      return new AuthenticationResult(null, DEFAULT_COOKIE_TIMEOUT_SECONDS,
+          LoginErrorCode.NOT_IN_FORMS_AUTHENTICATION_MODE.toString());
+    }
+
+    log.log(Level.FINE,
+        "Login Cookie Expiration in = {0}", result.getTimeoutSeconds());
+    if (result.getErrorCode() != LoginErrorCode.NO_ERROR) {
+      log.log(Level.WARNING, "Forms authentication failed with error code {0}. "
+          + "Possible SharePoint environment with multiple claims providers. "
+          + "Adaptor will fallback to use windows integrated authentication.",
+          result.getErrorCode());      
+      return new AuthenticationResult(null, DEFAULT_COOKIE_TIMEOUT_SECONDS,
+          result.getErrorCode().toString());
+    }
+
+    @SuppressWarnings("unchecked")
+    Map<String, Object> responseHeaders
+        = (Map<String, Object>) ((BindingProvider) authenticationClient)
+        .getResponseContext().get(MessageContext.HTTP_RESPONSE_HEADERS);
+    log.log(Level.FINEST, "Response headers: {0}", responseHeaders);
+    if(!responseHeaders.containsKey("Set-cookie")) {
+      throw new IOException("Unable to extract authentication cookie.");
+    }
+    
+    @SuppressWarnings("unchecked")
+    List<String> cookies = (List<String>) responseHeaders.get("Set-cookie");
+    if (cookies == null || cookies.isEmpty()) {
+      throw new IOException("Unable to extract authentication cookie.");
+    }
+    
+    int cookieTimeout;
+    // On SP2007 result.getTimeoutSeconds() can be null
+    if (result.getTimeoutSeconds() == null) {
+      log.log(Level.FINE,
+          "Login cookie timeout is null. Using default cookie timeout.");
+      cookieTimeout = DEFAULT_COOKIE_TIMEOUT_SECONDS;
+    } else {
+      cookieTimeout = result.getTimeoutSeconds() > 0 
+          ? result.getTimeoutSeconds() : DEFAULT_COOKIE_TIMEOUT_SECONDS;
+    }
+    
+    log.log(Level.FINE,
+        "Login Cookie Expiration in = {0} seconds", cookieTimeout);
+ 
+    return new AuthenticationResult(cookies.get(0), cookieTimeout,
+        result.getErrorCode().toString());
+  }
+
+  public boolean isFormsAuthentication() throws IOException {
+    if (authenticationMode == null) {
+      // Cache authentication mode value to avoid repetitive web service 
+      // calls to get authentication mode.     
+      setAuthenticationMode(authenticationClient.mode());
+      log.log(Level.FINE, "Authentication Mode {0}", authenticationMode);
+    }
+    return authenticationMode == AuthenticationMode.FORMS;
+  }
+  
+  private synchronized void setAuthenticationMode(AuthenticationMode mode) {
+    authenticationMode = mode;
+  }
+}
diff --git a/src/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptor.java b/src/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptor.java
index 5ff3932..c4e3b87 100644
--- a/src/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptor.java
+++ b/src/com/google/enterprise/adaptor/sharepoint/SharePointUserProfileAdaptor.java
@@ -226,9 +226,13 @@
     Authenticator.setDefault(ntlmAuthenticator);
     String authenticationEndPoint 
         =  virtualServer + "/_vti_bin/Authentication.asmx";
+    SharePointFormsAuthenticationHandler authenticationClient 
+        = new SharePointFormsAuthenticationHandler(
+            userProfileServiceFactory.newAuthentication(authenticationEndPoint),
+            username, password); 
     FormsAuthenticationHandler authenticationHandler 
         = new FormsAuthenticationHandler(username, password, scheduledExecutor,
-        userProfileServiceFactory.newAuthentication(authenticationEndPoint));
+        authenticationClient);
     authenticationHandler.start();
     log.log(Level.FINEST, "Initializing User profile Service Client for {0}",
         virtualServer + USER_PROFILE_SERVICE_ENDPOINT);
diff --git a/test/com/google/enterprise/adaptor/sharepoint/FormsAuthenticationHandlerTest.java b/test/com/google/enterprise/adaptor/sharepoint/FormsAuthenticationHandlerTest.java
new file mode 100644
index 0000000..84ec9da
--- /dev/null
+++ b/test/com/google/enterprise/adaptor/sharepoint/FormsAuthenticationHandlerTest.java
@@ -0,0 +1,200 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.enterprise.adaptor.sharepoint;
+
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.enterprise.adaptor.sharepoint.FormsAuthenticationHandler.AuthenticationHandler;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+public class FormsAuthenticationHandlerTest {
+  
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+  
+  private static class UnsupportedAuthenticationHandler 
+      implements AuthenticationHandler {
+
+    public AuthenticationResult authenticate() throws IOException {
+      throw new UnsupportedOperationException();
+    }
+
+    public boolean isFormsAuthentication() throws IOException {
+      throw new UnsupportedOperationException();
+    }
+  }
+  
+  private static class MockAuthenticationHandler 
+      extends UnsupportedAuthenticationHandler {
+    
+    private boolean isFormsAuthentication;
+    private AuthenticationResult authenticationResult;
+    
+    MockAuthenticationHandler(boolean isFormsAuthentication,
+        AuthenticationResult authenticationResult) {
+      this.isFormsAuthentication = isFormsAuthentication;
+      this.authenticationResult = authenticationResult;      
+    }
+
+    @Override
+    public AuthenticationResult authenticate() throws IOException {
+      if (authenticationResult == null) {
+        throw new UnsupportedOperationException();
+      }
+      return authenticationResult;
+    }
+
+    @Override
+    public boolean isFormsAuthentication() throws IOException {
+      return isFormsAuthentication;
+    }
+  }
+  
+  private static class UnsupportedScheduledExecutor extends CallerRunsExecutor 
+      implements ScheduledExecutorService {
+
+    public ScheduledFuture<?> schedule(Runnable command, long delay,
+        TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay,
+        TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
+        long initialDelay, long period, TimeUnit unit) {
+      throw new UnsupportedOperationException();
+    }
+
+    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
+        long initialDelay, long delay, TimeUnit unit) {
+      throw new UnsupportedOperationException(); 
+    }
+    
+  }
+  
+  private static class MockScheduledExecutor 
+      extends UnsupportedScheduledExecutor {
+    long executionDelay;
+    TimeUnit executionTimeUnit;   
+    
+    @Override
+    public ScheduledFuture<?> schedule(Runnable command, long delay,
+        TimeUnit unit) {
+      executionDelay = delay;
+      executionTimeUnit = unit;
+      return null;
+    }
+  }
+  
+  @Test
+  public void testConstructor() {
+    new FormsAuthenticationHandler("username", "password",
+        new UnsupportedScheduledExecutor(),
+        new UnsupportedAuthenticationHandler());
+  }
+  
+  @Test
+  public void testNullUserName() {
+    thrown.expect(NullPointerException.class);
+    new FormsAuthenticationHandler(null, "password",
+        new UnsupportedScheduledExecutor(),
+        new UnsupportedAuthenticationHandler());
+  }
+  
+  @Test
+  public void testNullPassword() {
+    thrown.expect(NullPointerException.class);
+    new FormsAuthenticationHandler("username", null,
+        new UnsupportedScheduledExecutor(),
+        new UnsupportedAuthenticationHandler());
+  }
+  
+  @Test
+  public void testNullScheduledExecutor() {
+    thrown.expect(NullPointerException.class);
+    new FormsAuthenticationHandler("username", "password", null,
+        new UnsupportedAuthenticationHandler());
+  }
+  
+  @Test
+  public void testNullAuthenticationHandler() {
+    thrown.expect(NullPointerException.class);
+    new FormsAuthenticationHandler("username", "password",
+        new UnsupportedScheduledExecutor(),null);
+  }
+  
+  @Test
+  public void testWindowsAuthentication() throws IOException {
+    FormsAuthenticationHandler formsHandler = new FormsAuthenticationHandler(
+        "username", "password",  new UnsupportedScheduledExecutor(),
+        new MockAuthenticationHandler(false, null));
+    formsHandler.start();
+    assertFalse(formsHandler.isFormsAuthentication());
+    assertTrue(formsHandler.getAuthenticationCookies().isEmpty());
+  }
+  
+  @Test
+  public void testEmptyUsernamePassword() throws IOException {
+    FormsAuthenticationHandler formsHandler = new FormsAuthenticationHandler(
+        "", "",  new UnsupportedScheduledExecutor(),
+        new UnsupportedAuthenticationHandler());
+    formsHandler.start();
+    assertFalse(formsHandler.isFormsAuthentication());
+    assertTrue(formsHandler.getAuthenticationCookies().isEmpty());
+  }
+  
+  @Test
+  public void testFormsAuthenticationNoError() throws IOException {
+    MockScheduledExecutor executor = new MockScheduledExecutor();    
+    FormsAuthenticationHandler formsHandler = new FormsAuthenticationHandler(
+        "username", "password", executor, new MockAuthenticationHandler(true,
+            new AuthenticationResult("AuthenCookie", 99, "NO_ERROR")));
+    formsHandler.start();
+    assertTrue(formsHandler.isFormsAuthentication());
+    assertEquals(Collections.unmodifiableList(Arrays.asList("AuthenCookie")),
+        formsHandler.getAuthenticationCookies());
+    assertEquals(50, executor.executionDelay);
+    assertEquals(TimeUnit.SECONDS, executor.executionTimeUnit);
+    executor.shutdown();
+  }
+  
+  @Test
+  public void testFormsAuthenticationPasswordMismatch() throws IOException {
+    FormsAuthenticationHandler formsHandler = new FormsAuthenticationHandler(
+        "username", "password", new UnsupportedScheduledExecutor(),
+        new MockAuthenticationHandler(true,
+            new AuthenticationResult(null, 99, "PASSWORD_NOT_MATCH")));
+    formsHandler.start();
+    assertTrue(formsHandler.isFormsAuthentication());
+    assertTrue(formsHandler.getAuthenticationCookies().isEmpty());
+  }
+}
diff --git a/test/com/google/enterprise/adaptor/sharepoint/SharePointFormsAuthenticationHandlerTest.java b/test/com/google/enterprise/adaptor/sharepoint/SharePointFormsAuthenticationHandlerTest.java
new file mode 100644
index 0000000..fb9251f
--- /dev/null
+++ b/test/com/google/enterprise/adaptor/sharepoint/SharePointFormsAuthenticationHandlerTest.java
@@ -0,0 +1,191 @@
+// Copyright 2014 Google Inc. All Rights Reserved.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.enterprise.adaptor.sharepoint;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.microsoft.schemas.sharepoint.soap.authentication.AuthenticationMode;
+import com.microsoft.schemas.sharepoint.soap.authentication.AuthenticationSoap;
+import com.microsoft.schemas.sharepoint.soap.authentication.LoginErrorCode;
+import com.microsoft.schemas.sharepoint.soap.authentication.LoginResult;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.ws.Binding;
+import javax.xml.ws.BindingProvider;
+import javax.xml.ws.EndpointReference;
+import javax.xml.ws.handler.MessageContext;
+
+public class SharePointFormsAuthenticationHandlerTest {
+  @Rule
+  public ExpectedException thrown = ExpectedException.none();
+  
+  private static class UnsupportedAuthenticationSoap 
+      implements AuthenticationSoap {
+
+    public LoginResult login(String username, String password) {
+      throw new UnsupportedOperationException();
+    }
+
+    public AuthenticationMode mode() {
+      throw new UnsupportedOperationException(); 
+    }    
+  }
+  
+  private static class MockFormsAuthenticationSoap 
+      extends UnsupportedAuthenticationSoap implements BindingProvider {
+    @Override
+    public LoginResult login(String username, String password) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public AuthenticationMode mode() {
+      return AuthenticationMode.FORMS; 
+    }
+
+    public Map<String, Object> getRequestContext() {
+      throw new UnsupportedOperationException(); 
+    }
+
+    public Map<String, Object> getResponseContext() {
+      throw new UnsupportedOperationException();
+    }
+
+    public Binding getBinding() {
+      throw new UnsupportedOperationException();
+    }
+
+    public EndpointReference getEndpointReference() {
+      throw new UnsupportedOperationException();
+    }
+
+    public <T extends EndpointReference> T 
+        getEndpointReference(Class<T> clazz) {
+      throw new UnsupportedOperationException();
+    }
+  }
+  
+  @Test
+  public void testConstructor() {
+    new SharePointFormsAuthenticationHandler (
+        new UnsupportedAuthenticationSoap(), "username", "password");
+  }
+  
+  @Test
+  public void testNullUserName() {
+    thrown.expect(NullPointerException.class);
+    new SharePointFormsAuthenticationHandler (
+        new UnsupportedAuthenticationSoap(), null, "password");
+  }
+  
+  @Test
+  public void testNullPassword() {
+    thrown.expect(NullPointerException.class);
+    new SharePointFormsAuthenticationHandler (
+        new UnsupportedAuthenticationSoap(), "username", null);
+  }
+  
+  @Test
+  public void testNullAuthenticationClient() {
+    thrown.expect(NullPointerException.class);
+    new SharePointFormsAuthenticationHandler ( null, "username", "password");
+  }
+  
+  @Test
+  public void testSharePointWithWindowsAuthentication() throws IOException{
+    SharePointFormsAuthenticationHandler authenHandler 
+        = new SharePointFormsAuthenticationHandler(
+            new MockFormsAuthenticationSoap(){
+                @Override public AuthenticationMode mode()
+                {
+                  return AuthenticationMode.WINDOWS;
+                }
+            }, "username", "password");
+    
+    assertFalse(authenHandler.isFormsAuthentication());
+    AuthenticationResult ar = authenHandler.authenticate();
+    assertNotNull(ar);
+    assertNull(ar.getCookie());
+    assertEquals(LoginErrorCode.NOT_IN_FORMS_AUTHENTICATION_MODE.toString(),
+        ar.getErrorCode());
+  }
+  
+  @Test
+  public void testSharePointWithFormsPasswordMismatch() throws IOException {
+    SharePointFormsAuthenticationHandler authenHandler 
+        = new SharePointFormsAuthenticationHandler(
+            new MockFormsAuthenticationSoap(){                
+                @Override public LoginResult login(
+                    String username, String password) {
+                  LoginResult lr = new LoginResult();
+                  lr.setErrorCode(LoginErrorCode.PASSWORD_NOT_MATCH);
+                  return lr;                  
+                }
+            }, "username", "password");
+    
+    assertTrue(authenHandler.isFormsAuthentication());
+    AuthenticationResult ar = authenHandler.authenticate();
+    assertNotNull(ar);
+    assertNull(ar.getCookie());
+    assertEquals(LoginErrorCode.PASSWORD_NOT_MATCH.toString(),
+        ar.getErrorCode());
+  }
+  
+  @Test
+  public void testSharePointWithFormsAuthentication() throws IOException {
+    SharePointFormsAuthenticationHandler authenHandler 
+        = new SharePointFormsAuthenticationHandler(
+            new MockFormsAuthenticationSoap() {
+              @Override public LoginResult login(
+                    String username, String password) {
+                LoginResult lr = new LoginResult();
+                lr.setErrorCode(LoginErrorCode.NO_ERROR);
+                return lr;                  
+              }
+              
+              @Override public Map<String, Object> getResponseContext() {
+                Map<String, Object> responseContext 
+                    = new HashMap<String, Object>();
+                Map<String, List<String>> responseHeaders 
+                    = new HashMap<String, List<String>>();
+                responseHeaders.put("Set-cookie",
+                    Arrays.asList("AuthenticationCookie"));      
+                responseContext.put(MessageContext.HTTP_RESPONSE_HEADERS,
+                    Collections.unmodifiableMap(responseHeaders));
+                return  Collections.unmodifiableMap(responseContext);                
+              }
+            }, "username", "password");
+    assertTrue(authenHandler.isFormsAuthentication());
+    AuthenticationResult ar = authenHandler.authenticate();
+    assertNotNull(ar);
+    assertEquals("AuthenticationCookie", ar.getCookie());
+    assertEquals(LoginErrorCode.NO_ERROR.toString(),
+        ar.getErrorCode());
+  }
+}