/*
* Copyright 2014-2016 CyberVision, Inc.
*
* 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 org.kaaproject.kaa.server.verifiers.facebook.verifier;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import org.apache.http.HttpEntity;
import org.apache.http.StatusLine;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.kaaproject.kaa.server.common.verifier.UserVerifierCallback;
import org.kaaproject.kaa.server.verifiers.facebook.config.gen.FacebookAvroConfig;
import org.mockito.Mockito;
import org.slf4j.Logger;
import org.springframework.test.util.ReflectionTestUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.charset.StandardCharsets;
public class FacebookUserVerifierTest extends FacebookUserVerifier {
private static FacebookUserVerifier verifier;
private static FacebookAvroConfig config;
@BeforeClass
public static void setUp() {
config = mock(FacebookAvroConfig.class);
when(config.getMaxParallelConnections()).thenReturn(1);
when(config.getAppId()).thenReturn("xxx");
when(config.getAppSecret()).thenReturn("xxx");
}
@Test
public void invalidUserAccessCodeTest() {
verifier = new MyFacebookVerifier(400, " {" +
" \"error\": {" +
" \"message\": \"Message describing the error\", " +
" \"type\": \"OAuthException\", " +
" \"code\": 190," +
" \"error_subcode\": 467," +
" \"error_user_title\": \"A title\"," +
" \"error_user_msg\": \"A message\"" +
" }" +
" }");
verifier.init(null, config);
verifier.start();
UserVerifierCallback callback = mock(UserVerifierCallback.class);
verifier.checkAccessToken("invalidUserId", "falseUserAccessToken", callback);
verify(callback, Mockito.timeout(1000).atLeastOnce()).onTokenInvalid();
}
@Test
public void expiredUserAccessTokenTest() {
verifier = new MyFacebookVerifier(400, " {" +
" \"error\": {" +
" \"message\": \"Message describing the error\", " +
" \"type\": \"OAuthException\", " +
" \"code\": 190," +
" \"error_subcode\": 463," +
" \"error_user_title\": \"A title\"," +
" \"error_user_msg\": \"A message\"" +
" }" +
" }");
verifier.init(null, config);
verifier.start();
UserVerifierCallback callback = mock(UserVerifierCallback.class);
verifier.checkAccessToken("invalidUserId", "falseUserAccessToken", callback);
verify(callback, Mockito.timeout(1000).atLeastOnce()).onTokenExpired();
}
@Test
public void incompatibleUserIdsTest() {
verifier = new MyFacebookVerifier(200, "{\"data\":{\"app_id\":\"1557997434440423\"," +
"\"application\":\"testApp\",\"expires_at\":1422990000," +
"\"is_valid\":true,\"scopes\":[\"public_profile\"],\"user_id\"" +
":\"800033850084728\"}}");
verifier.init(null, config);
verifier.start();
UserVerifierCallback callback = mock(UserVerifierCallback.class);
verifier.checkAccessToken("invalidUserId", "falseUserAccessToken", callback);
verify(callback, Mockito.timeout(1000).atLeastOnce()).onVerificationFailure(anyString());
verifier.stop();
}
@Test
public void badRequestTest() {
verifier = new MyFacebookVerifier(400, "{}");
verifier.init(null, config);
verifier.start();
UserVerifierCallback callback = mock(UserVerifierCallback.class);
verifier.checkAccessToken("invalidUserId", "falseUserAccessToken", callback);
// no exception is thrown, if onVerificationFailure(String) was called
verify(callback, Mockito.timeout(1000).atLeastOnce()).onVerificationFailure(anyString());
verifier.stop();
}
@Test
public void successfulVerificationTest() {
String userId = "12456789123456";
verifier = new MyFacebookVerifier(200, "{\"data\":{\"app_id\":\"1557997434440423\"," +
"\"application\":\"testApp\",\"expires_at\":1422990000," +
"\"is_valid\":true,\"scopes\":[\"public_profile\"],\"user_id\"" +
":" + userId + "}}");
verifier.init(null, config);
verifier.start();
UserVerifierCallback callback = mock(UserVerifierCallback.class);
verifier.checkAccessToken(userId, "someToken", callback);
verify(callback, Mockito.timeout(1000).atLeastOnce()).onSuccess();
verifier.stop();
}
@Test
public void connectionErrorTest() throws IOException {
verifier = new FacebookUserVerifier();
verifier.init(null, config);
verifier.start();
CloseableHttpClient httpClientMock = mock(CloseableHttpClient.class);
doThrow(new IOException()).when(httpClientMock).execute(any(HttpGet.class));
ReflectionTestUtils.setField(verifier, "httpClient", httpClientMock);
UserVerifierCallback callback = mock(UserVerifierCallback.class);
verifier.checkAccessToken("id", "token", callback);
Mockito.verify(callback, Mockito.timeout(1000)).onConnectionError(any(String.class));
verifier.stop();
}
@Test
public void internalErrorTest() throws IOException {
verifier = new FacebookUserVerifier();
verifier.init(null, config);
verifier.start();
CloseableHttpClient httpClientMock = mock(CloseableHttpClient.class);
// Throw any descendant of Exception, as the indicator of an internal error
doThrow(new NullPointerException()).when(httpClientMock).execute(any(HttpGet.class));
ReflectionTestUtils.setField(verifier, "httpClient", httpClientMock);
UserVerifierCallback callback = mock(UserVerifierCallback.class);
verifier.checkAccessToken("id", "token", callback);
Mockito.verify(callback, Mockito.timeout(1000)).onInternalError(any(String.class));
verifier.stop();
}
@Test
public void unrecognizedResponseCodeTest() throws IOException {
verifier = new MyFacebookVerifier(300, "");
verifier.init(null, config);
verifier.start();
UserVerifierCallback callback = mock(UserVerifierCallback.class);
verifier.checkAccessToken("id", "token", callback);
Mockito.verify(callback, Mockito.timeout(1000)).onVerificationFailure(any(String.class));
verifier.stop();
}
@Test
public void oauthErrorNoSubcodeTest() {
verifier = new MyFacebookVerifier(400, " {" +
" \"error\": {" +
" \"message\": \"Message describing the error\", " +
" \"type\": \"OAuthException\", " +
" \"code\": 190," +
" \"error_subcode\": null," +
" \"error_user_title\": \"A title\"," +
" \"error_user_msg\": \"A message\"" +
" }" +
" }");
verifier.init(null, config);
verifier.start();
UserVerifierCallback callback = mock(UserVerifierCallback.class);
verifier.checkAccessToken("invalidUserId", "falseUserAccessToken", callback);
verify(callback, Mockito.timeout(1000).atLeastOnce()).onVerificationFailure(any(String.class));
}
@Test
public void unrecognizedOauthErrorSubcodeTest() {
verifier = new MyFacebookVerifier(400, " {" +
" \"error\": {" +
" \"message\": \"Message describing the error\", " +
" \"type\": \"OAuthException\", " +
" \"code\": 190," +
" \"error_subcode\": 111," +
" \"error_user_title\": \"A title\"," +
" \"error_user_msg\": \"A message\"" +
" }" +
" }");
verifier.init(null, config);
verifier.start();
UserVerifierCallback callback = mock(UserVerifierCallback.class);
verifier.checkAccessToken("invalidUserId", "falseUserAccessToken", callback);
verify(callback, Mockito.timeout(1000).atLeastOnce()).onVerificationFailure(any(String.class));
}
@Test
public void unrecognizedResponseErrorCodeTest() {
verifier = new MyFacebookVerifier(400, " {" +
" \"error\": {" +
" \"message\": \"Message describing the error\", " +
" \"type\": \"OAuthException\", " +
" \"code\": 111," +
" \"error_subcode\": 111," +
" \"error_user_title\": \"A title\"," +
" \"error_user_msg\": \"A message\"" +
" }" +
" }");
verifier.init(null, config);
verifier.start();
UserVerifierCallback callback = mock(UserVerifierCallback.class);
verifier.checkAccessToken("invalidUserId", "falseUserAccessToken", callback);
verify(callback, Mockito.timeout(1000).atLeastOnce()).onVerificationFailure(any(String.class));
}
@Test
public void unableToCloseHttpClientTest() throws Exception {
verifier = new FacebookUserVerifier();
verifier.init(null, config);
verifier.start();
CloseableHttpClient httpClientMock = mock(CloseableHttpClient.class);
// Throw any descendant of Exception, as the indicator of an internal error
doThrow(new IOException()).when(httpClientMock).close();
ReflectionTestUtils.setField(verifier, "httpClient", httpClientMock);
Logger LOG = mock(Logger.class);
Field logField = FacebookUserVerifier.class.getDeclaredField("LOG");
// set final static field
setFinalStatic(logField, LOG);
UserVerifierCallback callback = mock(UserVerifierCallback.class);
verifier.checkAccessToken("id", "token", callback);
verifier.stop();
Mockito.verify(callback, Mockito.timeout(1000)).onInternalError(any(String.class));
}
@Test
public void getConfigurationClassTest() {
verifier = new FacebookUserVerifier();
Assert.assertEquals(verifier.getConfigurationClass(), FacebookAvroConfig.class);
}
@Test
public void errorInDataResponseTest() {
String userId = "12456789123456";
verifier = new MyFacebookVerifier(200, "{" +
"\"data\":{" +
"\"app_id\":\"1557997434440423\"," +
"\"application\":\"testApp\"," +
"\"expires_at\":1422990000," +
"\"is_valid\":true," +
"\"scopes\":[" +
"\"public_profile\"" +
"]," +
"\"user_id\":134," +
"\"error\":{" +
"\"message\":\"Message describing the error\"," +
"\"code\":111}}}");
verifier.init(null, config);
verifier.start();
UserVerifierCallback callback = mock(UserVerifierCallback.class);
verifier.checkAccessToken(userId, "someToken", callback);
verify(callback, Mockito.timeout(1000)).onTokenInvalid();
verifier.stop();
}
private void setFinalStatic(Field field, Object newValue) throws Exception {
field.setAccessible(true);
Field modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
}
private static class MyFacebookVerifier extends FacebookUserVerifier {
int responseCode;
String inputStreamString = "";
MyFacebookVerifier(int responseCode, String intputStreamString) {
this.responseCode = responseCode;
this.inputStreamString = intputStreamString;
}
@Override
protected CloseableHttpResponse establishConnection(String userAccessToken, String accessToken) {
CloseableHttpResponse connection = mock(CloseableHttpResponse.class);
try {
StatusLine statusLine = mock(StatusLine.class);
when(statusLine.getStatusCode()).thenReturn(responseCode);
HttpEntity httpEntity = mock(HttpEntity.class);
when(httpEntity.getContent()).thenReturn(new ByteArrayInputStream(inputStreamString.
getBytes(StandardCharsets.UTF_8)));
when(connection.getStatusLine()).thenReturn(statusLine);
when(connection.getEntity()).thenReturn(httpEntity);
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
}
}