diff --git a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/FirebaseChannel.java b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/FirebaseChannel.java index a2903f4a6f3..6d39cd4ca85 100644 --- a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/FirebaseChannel.java +++ b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/FirebaseChannel.java @@ -17,6 +17,7 @@ package com.example.appengine.firetactoe; import com.google.api.client.extensions.appengine.http.UrlFetchTransport; +import com.google.api.client.auth.oauth2.Credential; import com.google.api.client.googleapis.auth.oauth2.GoogleCredential; import com.google.api.client.http.ByteArrayContent; import com.google.api.client.http.GenericUrl; @@ -172,4 +173,63 @@ public String createFirebaseToken(Game game, String userId) { AppIdentityService.SigningResult result = appIdentity.signForApp(toSign.getBytes()); return String.format("%s.%s", toSign, base64.encode(result.getSignature())); } + + // The following methods are to illustrate making various calls to Firebase from App Engine + // Standard + + public HttpResponse firebasePut(String path, Object object) throws IOException { + // Make requests auth'ed using Application Default Credentials + Credential credential = GoogleCredential.getApplicationDefault().createScoped(FIREBASE_SCOPES); + HttpRequestFactory requestFactory = httpTransport.createRequestFactory(credential); + + String json = new Gson().toJson(object); + GenericUrl url = new GenericUrl(path); + + return requestFactory.buildPutRequest( + url, new ByteArrayContent("application/json", json.getBytes())).execute(); + } + + public HttpResponse firebasePatch(String path, Object object) throws IOException { + // Make requests auth'ed using Application Default Credentials + Credential credential = GoogleCredential.getApplicationDefault().createScoped(FIREBASE_SCOPES); + HttpRequestFactory requestFactory = httpTransport.createRequestFactory(credential); + + String json = new Gson().toJson(object); + GenericUrl url = new GenericUrl(path); + + return requestFactory.buildPatchRequest( + url, new ByteArrayContent("application/json", json.getBytes())).execute(); + } + + public HttpResponse firebasePost(String path, Object object) throws IOException { + // Make requests auth'ed using Application Default Credentials + Credential credential = GoogleCredential.getApplicationDefault().createScoped(FIREBASE_SCOPES); + HttpRequestFactory requestFactory = httpTransport.createRequestFactory(credential); + + String json = new Gson().toJson(object); + GenericUrl url = new GenericUrl(path); + + return requestFactory.buildPostRequest( + url, new ByteArrayContent("application/json", json.getBytes())).execute(); + } + + public HttpResponse firebaseGet(String path) throws IOException { + // Make requests auth'ed using Application Default Credentials + Credential credential = GoogleCredential.getApplicationDefault().createScoped(FIREBASE_SCOPES); + HttpRequestFactory requestFactory = httpTransport.createRequestFactory(credential); + + GenericUrl url = new GenericUrl(path); + + return requestFactory.buildGetRequest(url).execute(); + } + + public HttpResponse firebaseDelete(String path) throws IOException { + // Make requests auth'ed using Application Default Credentials + Credential credential = GoogleCredential.getApplicationDefault().createScoped(FIREBASE_SCOPES); + HttpRequestFactory requestFactory = httpTransport.createRequestFactory(credential); + + GenericUrl url = new GenericUrl(path); + + return requestFactory.buildDeleteRequest(url).execute(); + } } diff --git a/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/FirebaseChannelTest.java b/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/FirebaseChannelTest.java new file mode 100644 index 00000000000..802ba9705c9 --- /dev/null +++ b/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/FirebaseChannelTest.java @@ -0,0 +1,280 @@ +/* + * Copyright 2016 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.example.appengine.firetactoe; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.api.client.http.LowLevelHttpRequest; +import com.google.api.client.http.LowLevelHttpResponse; +import com.google.api.client.testing.http.MockHttpTransport; +import com.google.api.client.testing.http.MockLowLevelHttpRequest; +import com.google.api.client.testing.http.MockLowLevelHttpResponse; +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; +import com.google.appengine.tools.development.testing.LocalURLFetchServiceTestConfig; +import com.google.appengine.tools.development.testing.LocalUserServiceTestConfig; +import com.google.common.collect.ImmutableMap; +import com.googlecode.objectify.Objectify; +import com.googlecode.objectify.ObjectifyFactory; +import com.googlecode.objectify.ObjectifyService; +import com.googlecode.objectify.util.Closeable; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.lang.StringBuffer; +import java.util.HashMap; +import javax.servlet.RequestDispatcher; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import com.google.appengine.tools.development.testing.LocalAppIdentityServiceTestConfig; + +/** + * Unit tests for {@link FirebaseChannel}. + */ +@RunWith(JUnit4.class) +public class FirebaseChannelTest { + private static final String FIREBASE_DB_URL = "http://firebase.com/dburl"; + private final LocalServiceTestHelper helper = + new LocalServiceTestHelper(new LocalAppIdentityServiceTestConfig()); + + private FirebaseChannel firebaseChannel; + + @BeforeClass + public static void setUpBeforeClass() { + // Reset the Factory so that all translators work properly. + ObjectifyService.setFactory(new ObjectifyFactory()); + ObjectifyService.register(Game.class); + // Mock out the firebase config + FirebaseChannel.firebaseConfigStream = new ByteArrayInputStream( + String.format("databaseURL: \"%s\"", FIREBASE_DB_URL).getBytes()); + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + helper.setUp(); + firebaseChannel = FirebaseChannel.getInstance(); + } + + @After + public void tearDown() { + helper.tearDown(); + } + + @Test + public void sendFirebaseMessage_create() throws Exception { + // Mock out the firebase response. See + // http://g.co/dv/api-client-library/java/google-http-java-client/unit-testing + MockHttpTransport mockHttpTransport = spy(new MockHttpTransport() { + @Override + public LowLevelHttpRequest buildRequest(String method, String url) throws IOException { + return new MockLowLevelHttpRequest() { + @Override + public LowLevelHttpResponse execute() throws IOException { + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); + response.setStatusCode(200); + return response; + } + }; + } + }); + FirebaseChannel.getInstance().httpTransport = mockHttpTransport; + + firebaseChannel.sendFirebaseMessage("my_key", new Game("userX", "userO", "board", true)); + + verify(mockHttpTransport, times(1)).buildRequest( + "PATCH", FIREBASE_DB_URL + "/channels/my_key.json"); + } + + @Test + public void sendFirebaseMessage_delete() throws Exception { + // Mock out the firebase response. See + // http://g.co/dv/api-client-library/java/google-http-java-client/unit-testing + MockHttpTransport mockHttpTransport = spy(new MockHttpTransport() { + @Override + public LowLevelHttpRequest buildRequest(String method, String url) throws IOException { + return new MockLowLevelHttpRequest() { + @Override + public LowLevelHttpResponse execute() throws IOException { + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); + response.setStatusCode(200); + return response; + } + }; + } + }); + FirebaseChannel.getInstance().httpTransport = mockHttpTransport; + + firebaseChannel.sendFirebaseMessage("my_key", null); + + verify(mockHttpTransport, times(1)).buildRequest( + "DELETE", FIREBASE_DB_URL + "/channels/my_key.json"); + } + + @Test + public void createFirebaseToken() throws Exception { + Game game = new Game(); + game.setId("myid"); + + String jwt = firebaseChannel.createFirebaseToken(game, "userId"); + + assertThat(jwt).matches("^([\\w+/=-]+\\.){2}[\\w+/=-]+$"); + } + + @Test + public void firebasePut() throws Exception { + // Mock out the firebase response. See + // http://g.co/dv/api-client-library/java/google-http-java-client/unit-testing + MockHttpTransport mockHttpTransport = spy(new MockHttpTransport() { + @Override + public LowLevelHttpRequest buildRequest(String method, String url) throws IOException { + return new MockLowLevelHttpRequest() { + @Override + public LowLevelHttpResponse execute() throws IOException { + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); + response.setStatusCode(200); + return response; + } + }; + } + }); + FirebaseChannel.getInstance().httpTransport = mockHttpTransport; + Game game = new Game("userX", "userO", "board", true); + game.setId("myid"); + + firebaseChannel.firebasePut(FIREBASE_DB_URL + "/my/path", game); + + verify(mockHttpTransport, times(1)).buildRequest("PUT", FIREBASE_DB_URL + "/my/path"); + } + + @Test + public void firebasePatch() throws Exception { + // Mock out the firebase response. See + // http://g.co/dv/api-client-library/java/google-http-java-client/unit-testing + MockHttpTransport mockHttpTransport = spy(new MockHttpTransport() { + @Override + public LowLevelHttpRequest buildRequest(String method, String url) throws IOException { + return new MockLowLevelHttpRequest() { + @Override + public LowLevelHttpResponse execute() throws IOException { + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); + response.setStatusCode(200); + return response; + } + }; + } + }); + FirebaseChannel.getInstance().httpTransport = mockHttpTransport; + Game game = new Game("userX", "userO", "board", true); + game.setId("myid"); + + firebaseChannel.firebasePatch(FIREBASE_DB_URL + "/my/path", game); + + verify(mockHttpTransport, times(1)).buildRequest("PATCH", FIREBASE_DB_URL + "/my/path"); + } + + @Test + public void firebasePost() throws Exception { + // Mock out the firebase response. See + // http://g.co/dv/api-client-library/java/google-http-java-client/unit-testing + MockHttpTransport mockHttpTransport = spy(new MockHttpTransport() { + @Override + public LowLevelHttpRequest buildRequest(String method, String url) throws IOException { + return new MockLowLevelHttpRequest() { + @Override + public LowLevelHttpResponse execute() throws IOException { + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); + response.setStatusCode(200); + return response; + } + }; + } + }); + FirebaseChannel.getInstance().httpTransport = mockHttpTransport; + Game game = new Game("userX", "userO", "board", true); + game.setId("myid"); + + firebaseChannel.firebasePost(FIREBASE_DB_URL + "/my/path", game); + + verify(mockHttpTransport, times(1)).buildRequest("POST", FIREBASE_DB_URL + "/my/path"); + } + + @Test + public void firebaseGet() throws Exception { + // Mock out the firebase response. See + // http://g.co/dv/api-client-library/java/google-http-java-client/unit-testing + MockHttpTransport mockHttpTransport = spy(new MockHttpTransport() { + @Override + public LowLevelHttpRequest buildRequest(String method, String url) throws IOException { + return new MockLowLevelHttpRequest() { + @Override + public LowLevelHttpResponse execute() throws IOException { + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); + response.setStatusCode(200); + return response; + } + }; + } + }); + FirebaseChannel.getInstance().httpTransport = mockHttpTransport; + + firebaseChannel.firebaseGet(FIREBASE_DB_URL + "/my/path"); + + verify(mockHttpTransport, times(1)).buildRequest("GET", FIREBASE_DB_URL + "/my/path"); + } + + @Test + public void firebaseDelete() throws Exception { + // Mock out the firebase response. See + // http://g.co/dv/api-client-library/java/google-http-java-client/unit-testing + MockHttpTransport mockHttpTransport = spy(new MockHttpTransport() { + @Override + public LowLevelHttpRequest buildRequest(String method, String url) throws IOException { + return new MockLowLevelHttpRequest() { + @Override + public LowLevelHttpResponse execute() throws IOException { + MockLowLevelHttpResponse response = new MockLowLevelHttpResponse(); + response.setStatusCode(200); + return response; + } + }; + } + }); + FirebaseChannel.getInstance().httpTransport = mockHttpTransport; + Game game = new Game("userX", "userO", "board", true); + game.setId("myid"); + + firebaseChannel.firebaseDelete(FIREBASE_DB_URL + "/my/path"); + + verify(mockHttpTransport, times(1)).buildRequest("DELETE", FIREBASE_DB_URL + "/my/path"); + } +}