package com.microsoft.live.unittest; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.HttpVersion; import org.apache.http.StatusLine; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.message.BasicStatusLine; import org.apache.http.protocol.HTTP; import org.json.JSONException; import org.json.JSONObject; import android.os.Build; import android.test.InstrumentationTestCase; import com.microsoft.live.LiveConnectClient; import com.microsoft.live.LiveDownloadOperation; import com.microsoft.live.LiveOperation; import com.microsoft.live.LiveOperationException; import com.microsoft.live.TestUtils; import com.microsoft.live.constants.ErrorCodes; import com.microsoft.live.constants.ErrorMessages; import com.microsoft.live.constants.JsonKeys; import com.microsoft.live.mock.MockHttpClient; import com.microsoft.live.mock.MockHttpEntity; import com.microsoft.live.mock.MockHttpResponse; import com.microsoft.live.test.util.AssertMessages; import com.microsoft.live.test.util.AsyncRunnable; public abstract class ApiTest<OperationType, ListenerType> extends InstrumentationTestCase { private static final String INVALID_FORMAT = "!@#098 {} [] This is an invalid formated " + "response body asdfkaj{}dfa(*&!@#"; /** * Changes the mockClient to one that checks if the incoming requests contains the * Live Library header and then reverts to the original mockClient. */ protected void loadLiveLibraryHeaderChecker() { final MockHttpClient currentMockClient = this.mockClient; MockHttpClient liveLibraryHeaderCheckerClient = new MockHttpClient() { @Override public HttpResponse execute(HttpUriRequest request) throws IOException, ClientProtocolException { Header header = request.getFirstHeader("X-HTTP-Live-Library"); assertEquals("android/" + Build.VERSION.RELEASE + "_5.0", header.getValue()); // load the old mock client back after we check. mockClient = currentMockClient; return currentMockClient.execute(request); } @Override public HttpResponse getHttpResponse() { return currentMockClient.getHttpResponse(); } @Override public void setHttpResponse(HttpResponse httpResponse) { currentMockClient.setHttpResponse(httpResponse); } @Override public void addHttpResponse(HttpResponse httpResponse) { currentMockClient.addHttpResponse(httpResponse); } @Override public void clearHttpResponseQueue() { currentMockClient.clearHttpResponseQueue(); } }; this.mockClient = liveLibraryHeaderCheckerClient; } /** wait time to retrieve a response from a blocking queue for an async call*/ protected static int WAIT_TIME_IN_SECS = 10; protected static void assertEquals(InputStream is, String response) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(is)); StringBuilder sb = new StringBuilder(); try { String line; while ((line = reader.readLine()) != null) { sb.append(line); } } finally { // Close silently. // Close could throw an exception, and if we don't catch it, it will trump // the originally thrown exception. // Unfortunately, this means that if there was a problem only with close the reader, // it will be ignored. I assume we can safely ignore this case, because if there is // a problem closing a stream, there is little we can do to correc this. try { reader.close(); } catch (Exception e) { } } assertEquals(response, sb.toString()); } protected BlockingQueue<LiveOperationException> exceptionQueue; protected LiveConnectClient liveConnectClient; protected MockHttpClient mockClient; protected MockHttpEntity mockEntity; protected MockHttpResponse mockResponse; protected ListenerType queueingListener; protected BlockingQueue<OperationType> responseQueue; public abstract void testAsyncResponseBodyInvalid() throws Throwable; public abstract void testAsyncResponseBodyValid() throws Throwable; public abstract void testAsyncResponseBodyValidWithUserState() throws Throwable; public abstract void testSyncResponseBodyInvalid() throws Exception; public abstract void testSyncResponseBodyValid() throws Exception; protected abstract void checkValidResponseBody(OperationType operation) throws Exception; protected abstract AsyncRunnable<OperationType, ListenerType> createAsyncRunnable(String requestPath); protected abstract AsyncRunnable<OperationType, ListenerType> createAsyncRunnable(String requestPath, Object userState); protected abstract void loadValidResponseBody() throws Exception; protected abstract String getMethod(); protected void checkOperationMembers(LiveDownloadOperation operation, String method, String path) { this.checkOperationMembers(operation, method, path, null); } protected void checkOperationMembers(LiveDownloadOperation operation, String method, String path, Object userState) { assertEquals(method, operation.getMethod()); assertEquals(path, operation.getPath()); assertEquals(userState, operation.getUserState()); } /** * Asserts that the given LiveOperation has the correct method, path, and userState * * @param operation * @param method * @param path */ protected void checkOperationMembers(LiveOperation operation, String method, String path) { this.checkOperationMembers(operation, method, path, null); } /** * Asserts that the given LiveOperation has the correct method, path, and userState. * * @param operation * @param method * @param path * @param userState */ protected void checkOperationMembers(LiveOperation operation, String method, String path, Object userState) { assertEquals(method, operation.getMethod()); assertEquals(path, operation.getPath()); assertEquals(userState, operation.getUserState()); } /** * Asserts the response contains the error for an Invalid Path. * * @param operation * @param requestPath * @throws JSONException */ protected void checkPathInvalid(LiveOperation operation, String requestPath) throws JSONException { JSONObject result = operation.getResult(); JSONObject error = result.getJSONObject(JsonKeys.ERROR); String message = error.getString(JsonKeys.MESSAGE); String code = error.getString(JsonKeys.CODE); assertEquals(String.format(ErrorMessages.URL_NOT_VALID, requestPath.toLowerCase()), message); assertEquals(ErrorCodes.REQUEST_URL_INVALID, code); String rawResult = operation.getRawResult(); assertEquals(result.toString(), rawResult); } protected void checkResponseBodyInvalid(LiveDownloadOperation operation) throws IOException { InputStream is = operation.getStream(); ApiTest.assertEquals(is, INVALID_FORMAT); } /** * Asserts the LiveOperation's result and raw result members are null. * * @param operation */ protected void checkResponseBodyInvalid(LiveOperation operation) { JSONObject result = operation.getResult(); assertNull(result); String rawResult = operation.getRawResult(); assertNull(rawResult); } protected void checkReturnedException(LiveDownloadOperation fromMethod, LiveDownloadOperation fromCallback, LiveOperationException exception) throws IOException { assertNotNull(exception.getMessage()); this.checkReturnedOperations(fromMethod, fromCallback); } /** * Asserts the returned exception is not null and the LiveOperations from the method, * and the callback listener are the same object. Also, asserts the responseQueue, * and exceptionQueue are empty. * * @param fromMethod * @param fromCallback * @param exception */ protected void checkReturnedException(LiveOperation fromMethod, LiveOperation fromCallback, LiveOperationException exception) { assertNotNull(exception.getMessage()); this.checkReturnedOperations(fromMethod, fromCallback); this.checkResponseBodyInvalid(fromMethod); } /** * Asserts the returned LiveOperations from the method, and from the callback listener * are the same object. Also, asserts the responseQueue and exceptionQueue are empty. * * @param fromMethod * @param fromCallback */ protected <T> void checkReturnedOperations(T fromMethod, T fromCallback) { assertTrue(fromMethod == fromCallback); assertTrue(this.responseQueue.isEmpty()); assertTrue(this.exceptionQueue.isEmpty()); } protected void failNoIllegalArgumentExceptionThrown() { this.failNoExceptionThrown(IllegalArgumentException.class); } protected void failNoLiveOperationExceptionThrown() { this.failNoExceptionThrown(LiveOperationException.class); } protected void failNoNullPointerExceptionThrown() { this.failNoExceptionThrown(NullPointerException.class); } /** Loads an invalid formated body into the HttpClient * @throws Exception */ protected void loadInvalidResponseBody() throws Exception { byte[] bytes = INVALID_FORMAT.getBytes(); this.mockResponse.addHeader(HTTP.CONTENT_LEN, Long.toString(bytes.length)); this.mockEntity.setInputStream(new ByteArrayInputStream(bytes)); this.mockClient.setHttpResponse(mockResponse); } /** * Loads an invalid path response into the HttpClient. * * @param requestPath * @throws Exception */ protected void loadPathInvalidResponse(String requestPath) throws Exception { JSONObject error = new JSONObject(); error.put(JsonKeys.CODE, ErrorCodes.REQUEST_URL_INVALID); String message = String.format(ErrorMessages.URL_NOT_VALID, requestPath.toLowerCase()); error.put(JsonKeys.MESSAGE, message); JSONObject response = new JSONObject(); response.put(JsonKeys.ERROR, error); byte[] bytes = response.toString().getBytes(); this.mockResponse.addHeader(HTTP.CONTENT_LEN, Long.toString(bytes.length)); this.mockEntity.setInputStream(new ByteArrayInputStream(bytes)); StatusLine status = new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_BAD_REQUEST, "Bad Request"); this.mockResponse.setStatusLine(status); this.mockClient.setHttpResponse(this.mockResponse); } protected LiveOperationException pollExceptionQueue() throws InterruptedException { return this.exceptionQueue.poll(WAIT_TIME_IN_SECS, TimeUnit.SECONDS); } protected OperationType pollResponseQueue() throws InterruptedException { return this.responseQueue.poll(WAIT_TIME_IN_SECS, TimeUnit.SECONDS); } @Override protected void setUp() throws Exception { super.setUp(); // Set up the MockClient this.mockEntity = new MockHttpEntity(); StatusLine statusLine = new BasicStatusLine(HttpVersion.HTTP_1_1, HttpStatus.SC_OK, "OK"); this.mockResponse = new MockHttpResponse(this.mockEntity, statusLine); this.mockClient = new MockHttpClient(this.mockResponse); this.loadLiveLibraryHeaderChecker(); this.exceptionQueue = new LinkedBlockingQueue<LiveOperationException>(); this.liveConnectClient = TestUtils.newLiveConnectClient(this.mockClient); } @Override protected void tearDown() throws Exception { super.tearDown(); this.mockEntity = null; this.mockClient = null; this.responseQueue = null; this.exceptionQueue = null; this.queueingListener = null; this.liveConnectClient = null; } private void failNoExceptionThrown(Class<?> cl) { fail(String.format(AssertMessages.NO_EXCEPTION_THROWN, cl)); } }