From 76f1bd34ef0bfa97799bfec9ffabe82d04b520dd Mon Sep 17 00:00:00 2001
From: Jisha Abubaker <jishaa@google.com>
Date: Mon, 25 Sep 2017 09:58:25 -0700
Subject: [PATCH 1/2] updating Firebase on App Engine sample to use latest libs
 fixing issue in running locally

---
 appengine/firebase-tictactoe/README.md        |  44 +++---
 appengine/firebase-tictactoe/pom.xml          | 130 ++++++------------
 .../appengine/firetactoe/DeleteServlet.java   |   2 +-
 .../appengine/firetactoe/FirebaseChannel.java |  16 ++-
 .../example/appengine/firetactoe/Game.java    |  20 +--
 .../appengine/firetactoe/MoveServlet.java     |   2 +-
 .../appengine/firetactoe/ObjectifyHelper.java |   1 +
 .../appengine/firetactoe/OpenedServlet.java   |   2 +-
 .../firetactoe/TicTacToeServlet.java          |   6 +-
 .../src/main/webapp/WEB-INF/view/index.jsp    |   3 +-
 .../firetactoe/DeleteServletTest.java         |  24 ++--
 .../firetactoe/FirebaseChannelTest.java       |  25 ++--
 .../appengine/firetactoe/MoveServletTest.java |  23 ++--
 .../firetactoe/OpenedServletTest.java         |  20 +--
 .../firetactoe/TicTacToeServletTest.java      |  34 +++--
 15 files changed, 162 insertions(+), 190 deletions(-)

diff --git a/appengine/firebase-tictactoe/README.md b/appengine/firebase-tictactoe/README.md
index 2e94a9dc012..8a3889bf574 100644
--- a/appengine/firebase-tictactoe/README.md
+++ b/appengine/firebase-tictactoe/README.md
@@ -10,17 +10,11 @@ for realtime notifications when the board changes.
 ## Prerequisites
 
 * Install [Apache Maven][maven] 3.0.5 or later
-* Install the [Google Cloud SDK][sdk]
 * Create a project in the [Firebase Console][fb-console]
 * In the [Overview section][fb-overview] of the Firebase console, click 'Add
   Firebase to your web app' and replace the contents of the file
   `src/main/webapp/WEB-INF/view/firebase_config.jspf` with that code snippet.
-* If using the development appserver to run the app locally, you must supply
-  credentials that would otherwise be inferred from the App Engine environment.
-  Download [service account credentials][creds] and set the
-  `GOOGLE_APPLICATION_CREDENTIALS` environment variable to its path:
-
-        export GOOGLE_APPLICATION_CREDENTIALS=/path/to/your/credentials.json
+* [Enable the Identity API](https://console.cloud.google.com/apis/api/identitytoolkit.googleapis.com/overview)
 
 
 [fb-console]: https://console.firebase.google.com
@@ -29,26 +23,36 @@ for realtime notifications when the board changes.
 [fb-overview]: https://console.firebase.google.com/project/_/overview
 [maven]: https://maven.apache.org
 
+## Setup
+
+* If you haven't already, Download and initialize the [Cloud SDK](https://cloud.google.com/sdk/)
+```
+    gcloud init
+```
+* If you haven't already, Create an App Engine app within the current Google Cloud Project
+```
+    gcloud app create
+```
+* If you haven't already, setup
+[Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials)
+to run your application locally:
+Download [service account credentials][creds] and set the `GOOGLE_APPLICATION_CREDENTIALS`
+environment variable to its path:
+```
+   export GOOGLE_APPLICATION_CREDENTIALS=/path/to/your/credentials.json
+```
 
-## Run the sample
 
-* To run the app locally using the development appserver:
+### Running locally
 
-    ```sh
     $ mvn appengine:run
-    ```
 
-## Troubleshooting
+When running locally, the page does not automatically refresh,
+please reload the page manually after each move.
 
-* If you see the error `Google Cloud SDK path was not provided ...`:
-    * Make sure you've installed the [Google Cloud SDK][sdk]
-    * Make sure the Google Cloud SDK's `bin/` directory is in your `PATH`. If
-      you prefer it not to be, you can also set the environment variable
-      `GOOGLE_CLOUD_SDK_HOME` to point to where you installed the SDK:
+### Deploying to App Engine Standard
 
-        ```sh
-        export GOOGLE_CLOUD_SDK_HOME=/path/to/google-cloud-sdk
-        ```
+    $ mvn appengine:deploy
 
 ## Contributing changes
 
diff --git a/appengine/firebase-tictactoe/pom.xml b/appengine/firebase-tictactoe/pom.xml
index 9949658ce49..af21b34fde6 100644
--- a/appengine/firebase-tictactoe/pom.xml
+++ b/appengine/firebase-tictactoe/pom.xml
@@ -25,95 +25,55 @@
         <version>1.0.0</version>
         <relativePath>..</relativePath>
     </parent>
-    <properties>
-        <objectify.version>5.1.21</objectify.version>
-        <servlet-api.version>2.5</servlet-api.version>
-        <gson.version>2.7</gson.version>
-        <guava.version>20.0</guava.version>
-        <google-api-client.version>1.22.0</google-api-client.version>
-        <junit.version>4.12</junit.version>
-        <mockito.version>1.10.19</mockito.version>
-        <google-truth.version>0.34</google-truth.version>
-        <appengine-maven.version>1.3.1</appengine-maven.version>
-    </properties>
     <!-- [START set_versions] -->
     <prerequisites>
         <maven>3.3.9</maven>
     </prerequisites>
     <!-- [END set_versions] -->
-    <dependencies>
-        <dependency>
-            <groupId>com.google.appengine</groupId>
-            <artifactId>appengine-api-1.0-sdk</artifactId>
-            <version>${appengine.sdk.version}</version>
-        </dependency>
-        <dependency>
-            <groupId>javax.servlet</groupId>
-            <artifactId>servlet-api</artifactId>
-            <version>${servlet-api.version}</version>
-            <type>jar</type>
-            <scope>provided</scope>
-        </dependency>
-        <dependency>
-          <groupId>com.google.code.gson</groupId>
-          <artifactId>gson</artifactId>
-          <version>2.8.1</version>
-        </dependency>
-        <dependency>
-            <groupId>com.googlecode.objectify</groupId>
-            <artifactId>objectify</artifactId>
-            <version>${objectify.version}</version>
-        </dependency>
-        <dependency>
-          <groupId>com.google.guava</groupId>
-          <artifactId>guava</artifactId>
-          <version>${guava.version}</version>
-        </dependency>
-        <dependency>
-          <groupId>com.google.api-client</groupId>
-          <artifactId>google-api-client-appengine</artifactId>
-          <version>${google-api-client.version}</version>
-        </dependency>
-
-
-        <!-- Test Dependencies -->
-        <dependency>
-            <groupId>junit</groupId>
-            <artifactId>junit</artifactId>
-            <version>${junit.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>org.mockito</groupId>
-            <artifactId>mockito-all</artifactId>
-            <version>${mockito.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.google.appengine</groupId>
-            <artifactId>appengine-testing</artifactId>
-            <version>${appengine.sdk.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.google.appengine</groupId>
-            <artifactId>appengine-api-stubs</artifactId>
-            <version>${appengine.sdk.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.google.appengine</groupId>
-            <artifactId>appengine-tools-sdk</artifactId>
-            <version>${appengine.sdk.version}</version>
-            <scope>test</scope>
-        </dependency>
-        <dependency>
-            <groupId>com.google.truth</groupId>
-            <artifactId>truth</artifactId>
-            <version>${google-truth.version}</version>
-            <scope>test</scope>
-        </dependency>
-    </dependencies>
+   <dependencies>
+       <dependency>
+           <groupId>com.googlecode.objectify</groupId>
+           <artifactId>objectify</artifactId>
+           <version>5.1.21</version>
+       </dependency>
+       <dependency>
+           <groupId>com.google.appengine</groupId>
+           <artifactId>appengine-api-1.0-sdk</artifactId>
+           <version>1.9.56</version>
+       </dependency>
+       <dependency>
+           <groupId>com.google.api-client</groupId>
+           <artifactId>google-api-client-appengine</artifactId>
+           <version>1.22.0</version>
+       </dependency>
+       <dependency>
+           <groupId>com.google.code.gson</groupId>
+           <artifactId>gson</artifactId>
+           <version>2.8.1</version>
+       </dependency>
+       <!-- Test dependences -->
+       <dependency>
+           <groupId>junit</groupId>
+           <artifactId>junit</artifactId>
+           <version>4.12</version>
+       </dependency>
+       <dependency>
+           <groupId>org.mockito</groupId>
+           <artifactId>mockito-core</artifactId>
+           <version>2.9.0</version>
+       </dependency>
+       <dependency>
+         <groupId>com.google.appengine</groupId>
+         <artifactId>appengine-api-stubs</artifactId>
+         <version>1.9.56</version>
+         <scope>test</scope>
+         </dependency>
+       <dependency>
+           <groupId>com.google.appengine</groupId>
+           <artifactId>appengine-testing</artifactId>
+           <version>1.9.56</version>
+       </dependency>
+   </dependencies>
     <build>
         <!-- for hot reload of the web application -->
         <outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes
@@ -122,7 +82,7 @@
             <plugin>
                 <groupId>com.google.cloud.tools</groupId>
                 <artifactId>appengine-maven-plugin</artifactId>
-                <version>${appengine-maven.version}</version>
+                <version>1.3.1</version>
             </plugin>
         </plugins>
     </build>
diff --git a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/DeleteServlet.java b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/DeleteServlet.java
index 1988424fbb6..f58514fe3d1 100644
--- a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/DeleteServlet.java
+++ b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/DeleteServlet.java
@@ -42,6 +42,6 @@ public void doPost(HttpServletRequest request, HttpServletResponse response)
     String currentUserId = userService.getCurrentUser().getUserId();
 
     // TODO(you): In practice, first validate that the user has permission to delete the Game
-    game.deleteChannel(currentUserId);
+    game.deleteChannel(getServletContext(), currentUserId);
   }
 }
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 5c859d65dbe..f7193543219 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
@@ -24,13 +24,13 @@
 import com.google.api.client.http.HttpRequestFactory;
 import com.google.api.client.http.HttpResponse;
 import com.google.api.client.http.HttpTransport;
+import com.google.api.client.util.Preconditions;
 import com.google.appengine.api.appidentity.AppIdentityService;
 import com.google.appengine.api.appidentity.AppIdentityServiceFactory;
 import com.google.common.io.BaseEncoding;
 import com.google.common.io.CharStreams;
 import com.google.gson.Gson;
 
-import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -39,6 +39,7 @@
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
+import javax.servlet.ServletContext;
 
 /**
  * Utility functions for communicating with the realtime communication channel using Firebase.
@@ -47,7 +48,7 @@
  * Firebase.
  */
 public class FirebaseChannel {
-  private static final String FIREBASE_SNIPPET_PATH = "WEB-INF/view/firebase_config.jspf";
+  private static final String FIREBASE_SNIPPET_PATH = "/WEB-INF/view/firebase_config.jspf";
   static InputStream firebaseConfigStream = null;
   private static final Collection FIREBASE_SCOPES = Arrays.asList(
       "https://www.googleapis.com/auth/firebase.database",
@@ -67,9 +68,9 @@ public class FirebaseChannel {
    * FirebaseChannel is a singleton, since it's just utility functions.
    * The class derives auth information when first instantiated.
    */
-  public static FirebaseChannel getInstance() {
+  public static FirebaseChannel getInstance(ServletContext servletContext) {
     if (instance == null) {
-      instance = new FirebaseChannel();
+      instance = new FirebaseChannel(servletContext);
     }
     return instance;
   }
@@ -81,11 +82,13 @@ public static FirebaseChannel getInstance() {
    * communicate with Firebase is derived from App Engine's default credentials, and given
    * Firebase's OAuth scopes.
    */
-  private FirebaseChannel() {
+  private FirebaseChannel(ServletContext servletContext) {
     try {
       // This variables exist primarily so it can be stubbed out in unit tests.
       if (null == firebaseConfigStream) {
-        firebaseConfigStream = new FileInputStream(FIREBASE_SNIPPET_PATH);
+        Preconditions.checkNotNull(servletContext,
+            "Servlet context expected to initialize Firebase channel");
+        firebaseConfigStream = servletContext.getResourceAsStream(FIREBASE_SNIPPET_PATH);
       }
 
       String firebaseSnippet = CharStreams.toString(new InputStreamReader(
@@ -176,7 +179,6 @@ public String createFirebaseToken(Game game, String userId) {
 
   // 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);
diff --git a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/Game.java b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/Game.java
index 7f6a1e84135..6acb7e486ea 100644
--- a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/Game.java
+++ b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/Game.java
@@ -24,11 +24,11 @@
 import java.util.logging.Level;
 import java.util.logging.Logger;
 import java.util.regex.Pattern;
+import javax.servlet.ServletContext;
 
 /**
  * The datastore-persisted Game object. This holds the entire game state - from a representation of
  * the board, to the players are and whose turn it is, and who the winner is and how they won.
- *
  * It also contains some convenience functions for communicating updates to the board to the
  * clients, via Firebase.
  */
@@ -109,26 +109,26 @@ public String getChannelKey(String userId) {
     return userId + id;
   }
 
-  public void deleteChannel(String userId)
+  public void deleteChannel(ServletContext servletContext, String userId)
       throws IOException {
     if (userId != null) {
       String channelKey = getChannelKey(userId);
-      FirebaseChannel.getInstance().sendFirebaseMessage(channelKey, null);
+      FirebaseChannel.getInstance(servletContext).sendFirebaseMessage(channelKey, null);
     }
   }
 
-  private void sendUpdateToUser(String userId)
+  private void sendUpdateToUser(ServletContext servletContext, String userId)
       throws IOException {
     if (userId != null) {
       String channelKey = getChannelKey(userId);
-      FirebaseChannel.getInstance().sendFirebaseMessage(channelKey, this);
+      FirebaseChannel.getInstance(servletContext).sendFirebaseMessage(channelKey, this);
     }
   }
 
-  public void sendUpdateToClients()
+  public void sendUpdateToClients(ServletContext servletContext)
       throws IOException {
-    sendUpdateToUser(userX);
-    sendUpdateToUser(userO);
+    sendUpdateToUser(servletContext, userX);
+    sendUpdateToUser(servletContext, userO);
   }
   // [END send_updates]
 
@@ -152,7 +152,7 @@ public void checkWin() {
     }
   }
 
-  public boolean makeMove(int position, String userId) {
+  public boolean makeMove(ServletContext servletContext, int position, String userId) {
     String currentMovePlayer;
     char value;
     if (getMoveX()) {
@@ -170,7 +170,7 @@ public boolean makeMove(int position, String userId) {
       checkWin();
       setMoveX(!getMoveX());
       try {
-        sendUpdateToClients();
+        sendUpdateToClients(servletContext);
       } catch (IOException e) {
         LOGGER.log(Level.SEVERE, "Error sending Game update to Firebase", e);
         throw new RuntimeException(e);
diff --git a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/MoveServlet.java b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/MoveServlet.java
index 4911d22bc37..354e346c6f2 100644
--- a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/MoveServlet.java
+++ b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/MoveServlet.java
@@ -43,7 +43,7 @@ public void doPost(HttpServletRequest request, HttpServletResponse response)
     String currentUserId = userService.getCurrentUser().getUserId();
 
     int cell = new Integer(request.getParameter("cell"));
-    if (!game.makeMove(cell, currentUserId)) {
+    if (!game.makeMove(getServletContext(), cell, currentUserId)) {
       response.sendError(HttpServletResponse.SC_FORBIDDEN);
     } else {
       ofy.save().entity(game).now();
diff --git a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/ObjectifyHelper.java b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/ObjectifyHelper.java
index abc1a28e33c..0544e7b87fa 100644
--- a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/ObjectifyHelper.java
+++ b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/ObjectifyHelper.java
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package com.example.appengine.firetactoe;
 
 import com.googlecode.objectify.ObjectifyService;
diff --git a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/OpenedServlet.java b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/OpenedServlet.java
index 2e71f9391cb..5efcc21e6a2 100644
--- a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/OpenedServlet.java
+++ b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/OpenedServlet.java
@@ -35,6 +35,6 @@ public void doPost(HttpServletRequest request, HttpServletResponse response)
     String gameId = request.getParameter("gameKey");
     Objectify ofy = ObjectifyService.ofy();
     Game game = ofy.load().type(Game.class).id(gameId).safe();
-    game.sendUpdateToClients();
+    game.sendUpdateToClients(getServletContext());
   }
 }
diff --git a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/TicTacToeServlet.java b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/TicTacToeServlet.java
index 8b248577971..d34d63fed7f 100644
--- a/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/TicTacToeServlet.java
+++ b/appengine/firebase-tictactoe/src/main/java/com/example/appengine/firetactoe/TicTacToeServlet.java
@@ -24,6 +24,7 @@
 import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.net.URL;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
@@ -84,14 +85,15 @@ public void doGet(HttpServletRequest request, HttpServletResponse response)
     }
 
     // 2. Create this Game in the firebase db
-    game.sendUpdateToClients();
+    game.sendUpdateToClients(getServletContext());
 
     // 3. Inject a secure token into the client, so it can get game updates
 
     // [START pass_token]
     // The 'Game' object exposes a method which creates a unique string based on the game's key
     // and the user's id.
-    String token = FirebaseChannel.getInstance().createFirebaseToken(game, userId);
+    String token = FirebaseChannel.getInstance(getServletContext())
+        .createFirebaseToken(game, userId);
     request.setAttribute("token", token);
 
     // 4. More general template values
diff --git a/appengine/firebase-tictactoe/src/main/webapp/WEB-INF/view/index.jsp b/appengine/firebase-tictactoe/src/main/webapp/WEB-INF/view/index.jsp
index fab4ac6f0ec..f037d891fb0 100644
--- a/appengine/firebase-tictactoe/src/main/webapp/WEB-INF/view/index.jsp
+++ b/appengine/firebase-tictactoe/src/main/webapp/WEB-INF/view/index.jsp
@@ -32,8 +32,7 @@
     <div id="display-area" class="waiting">
       <h2>Firebase-enabled Tic Tac Toe</h2>
       <div id="other-player">
-        Waiting for another player to join.<br>
-        Send them this link to play:<br>
+        Send the other player this link to play, and click link below to wait on the other player:<br>
         <div id="game-link"><a href="<%= request.getAttribute("game_link") %>"><%= request.getAttribute("game_link") %></a></div>
       </div>
       <div id="your-move">Your move! Click a square to place your piece.</div>
diff --git a/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/DeleteServletTest.java b/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/DeleteServletTest.java
index 0df68aaaefc..c466695c841 100644
--- a/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/DeleteServletTest.java
+++ b/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/DeleteServletTest.java
@@ -16,7 +16,7 @@
 
 package com.example.appengine.firetactoe;
 
-import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.spy;
@@ -38,6 +38,11 @@
 import com.googlecode.objectify.ObjectifyFactory;
 import com.googlecode.objectify.ObjectifyService;
 import com.googlecode.objectify.util.Closeable;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -46,14 +51,9 @@
 import org.junit.runners.JUnit4;
 import org.mockito.Matchers;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.util.HashMap;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
 /**
  * Unit tests for {@link DeleteServlet}.
  */
@@ -98,11 +98,11 @@ public void setUp() throws Exception {
     helper.setUp();
     dbSession = ObjectifyService.begin();
 
-    servletUnderTest = new DeleteServlet();
+    servletUnderTest = spy(new DeleteServlet());
 
     helper.setEnvIsLoggedIn(true);
     // Make sure there are no firebase requests if we don't expect it
-    FirebaseChannel.getInstance().httpTransport = null;
+    FirebaseChannel.getInstance(null).httpTransport = null;
   }
 
   @After
@@ -117,7 +117,7 @@ public void doPost_noGameKey() throws Exception {
       servletUnderTest.doPost(mockRequest, mockResponse);
       fail("Should not succeed with no gameKey specified.");
     } catch (IllegalArgumentException e) {
-      assertThat(e.getMessage()).startsWith("id 'null'");
+      assertTrue(e.getMessage().startsWith("id 'null'"));
     }
   }
 
@@ -145,8 +145,8 @@ public LowLevelHttpResponse execute() throws IOException {
         };
       }
     });
-    FirebaseChannel.getInstance().httpTransport = mockHttpTransport;
-
+    FirebaseChannel.getInstance(null).httpTransport = mockHttpTransport;
+    Mockito.doReturn(null).when(servletUnderTest).getServletContext();
     servletUnderTest.doPost(mockRequest, mockResponse);
 
     verify(mockHttpTransport, times(1)).buildRequest(
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
index 0c92863f77e..5f54243240c 100644
--- 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
@@ -16,7 +16,7 @@
 
 package com.example.appengine.firetactoe;
 
-import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -28,6 +28,8 @@
 import com.google.api.client.testing.http.MockLowLevelHttpResponse;
 import com.google.appengine.tools.development.testing.LocalAppIdentityServiceTestConfig;
 import com.google.appengine.tools.development.testing.LocalServiceTestHelper;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -36,9 +38,6 @@
 import org.junit.runners.JUnit4;
 import org.mockito.MockitoAnnotations;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-
 /**
  * Unit tests for {@link FirebaseChannel}.
  */
@@ -56,7 +55,7 @@ public static void setUpBeforeClass() {
     FirebaseChannel.firebaseConfigStream = new ByteArrayInputStream(
         String.format("databaseURL: \"%s\"", FIREBASE_DB_URL).getBytes());
 
-    firebaseChannel = FirebaseChannel.getInstance();
+    firebaseChannel = FirebaseChannel.getInstance(null);
   }
 
   @Before
@@ -87,7 +86,7 @@ public LowLevelHttpResponse execute() throws IOException {
         };
       }
     });
-    FirebaseChannel.getInstance().httpTransport = mockHttpTransport;
+    FirebaseChannel.getInstance(null).httpTransport = mockHttpTransport;
 
     firebaseChannel.sendFirebaseMessage("my_key", new Game());
 
@@ -112,7 +111,7 @@ public LowLevelHttpResponse execute() throws IOException {
         };
       }
     });
-    FirebaseChannel.getInstance().httpTransport = mockHttpTransport;
+    FirebaseChannel.getInstance(null).httpTransport = mockHttpTransport;
 
     firebaseChannel.sendFirebaseMessage("my_key", null);
 
@@ -126,7 +125,7 @@ public void createFirebaseToken() throws Exception {
 
     String jwt = firebaseChannel.createFirebaseToken(game, "userId");
 
-    assertThat(jwt).matches("^([\\w+/=-]+\\.){2}[\\w+/=-]+$");
+    assertTrue(jwt.matches("^([\\w+/=-]+\\.){2}[\\w+/=-]+$"));
   }
 
   @Test
@@ -146,7 +145,7 @@ public LowLevelHttpResponse execute() throws IOException {
         };
       }
     });
-    FirebaseChannel.getInstance().httpTransport = mockHttpTransport;
+    FirebaseChannel.getInstance(null).httpTransport = mockHttpTransport;
     Game game = new Game();
 
     firebaseChannel.firebasePut(FIREBASE_DB_URL + "/my/path", game);
@@ -171,7 +170,7 @@ public LowLevelHttpResponse execute() throws IOException {
         };
       }
     });
-    FirebaseChannel.getInstance().httpTransport = mockHttpTransport;
+    FirebaseChannel.getInstance(null).httpTransport = mockHttpTransport;
     Game game = new Game();
 
     firebaseChannel.firebasePatch(FIREBASE_DB_URL + "/my/path", game);
@@ -196,7 +195,7 @@ public LowLevelHttpResponse execute() throws IOException {
         };
       }
     });
-    FirebaseChannel.getInstance().httpTransport = mockHttpTransport;
+    FirebaseChannel.getInstance(null).httpTransport = mockHttpTransport;
     Game game = new Game();
 
     firebaseChannel.firebasePost(FIREBASE_DB_URL + "/my/path", game);
@@ -221,7 +220,7 @@ public LowLevelHttpResponse execute() throws IOException {
         };
       }
     });
-    FirebaseChannel.getInstance().httpTransport = mockHttpTransport;
+    FirebaseChannel.getInstance(null).httpTransport = mockHttpTransport;
 
     firebaseChannel.firebaseGet(FIREBASE_DB_URL + "/my/path");
 
@@ -245,7 +244,7 @@ public LowLevelHttpResponse execute() throws IOException {
         };
       }
     });
-    FirebaseChannel.getInstance().httpTransport = mockHttpTransport;
+    FirebaseChannel.getInstance(null).httpTransport = mockHttpTransport;
 
     firebaseChannel.firebaseDelete(FIREBASE_DB_URL + "/my/path");
 
diff --git a/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/MoveServletTest.java b/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/MoveServletTest.java
index ac07a251028..814d7a6fc8e 100644
--- a/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/MoveServletTest.java
+++ b/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/MoveServletTest.java
@@ -16,7 +16,7 @@
 
 package com.example.appengine.firetactoe;
 
-import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -37,6 +37,11 @@
 import com.googlecode.objectify.ObjectifyFactory;
 import com.googlecode.objectify.ObjectifyService;
 import com.googlecode.objectify.util.Closeable;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -45,14 +50,9 @@
 import org.junit.runners.JUnit4;
 import org.mockito.Matchers;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.util.HashMap;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
 /**
  * Unit tests for {@link MoveServlet}.
  */
@@ -97,11 +97,11 @@ public void setUp() throws Exception {
     helper.setUp();
     dbSession = ObjectifyService.begin();
 
-    servletUnderTest = new MoveServlet();
+    servletUnderTest = spy(new MoveServlet());
 
     helper.setEnvIsLoggedIn(true);
     // Make sure there are no firebase requests if we don't expect it
-    FirebaseChannel.getInstance().httpTransport = null;
+    FirebaseChannel.getInstance(null).httpTransport = null;
   }
 
   @After
@@ -136,12 +136,13 @@ public LowLevelHttpResponse execute() throws IOException {
         };
       }
     });
-    FirebaseChannel.getInstance().httpTransport = mockHttpTransport;
+    FirebaseChannel.getInstance(null).httpTransport = mockHttpTransport;
 
+    Mockito.doReturn(null).when(servletUnderTest).getServletContext();
     servletUnderTest.doPost(mockRequest, mockResponse);
 
     game = ofy.load().type(Game.class).id(gameKey).safe();
-    assertThat(game.board).isEqualTo(" X       ");
+    assertEquals(game.board, " X       ");
 
     verify(mockHttpTransport, times(2)).buildRequest(
         eq("PATCH"), Matchers.matches(FIREBASE_DB_URL + "/channels/[\\w-]+.json$"));
diff --git a/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/OpenedServletTest.java b/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/OpenedServletTest.java
index 9b2990196f6..d3a97eded0c 100644
--- a/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/OpenedServletTest.java
+++ b/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/OpenedServletTest.java
@@ -36,6 +36,11 @@
 import com.googlecode.objectify.ObjectifyFactory;
 import com.googlecode.objectify.ObjectifyService;
 import com.googlecode.objectify.util.Closeable;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -44,14 +49,9 @@
 import org.junit.runners.JUnit4;
 import org.mockito.Matchers;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.util.HashMap;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
 /**
  * Unit tests for {@link OpenedServlet}.
  */
@@ -96,11 +96,11 @@ public void setUp() throws Exception {
     helper.setUp();
     dbSession = ObjectifyService.begin();
 
-    servletUnderTest = new OpenedServlet();
+    servletUnderTest = spy(new OpenedServlet());
 
     helper.setEnvIsLoggedIn(true);
     // Make sure there are no firebase requests if we don't expect it
-    FirebaseChannel.getInstance().httpTransport = null;
+    FirebaseChannel.getInstance(null).httpTransport = null;
   }
 
   @After
@@ -134,8 +134,8 @@ public LowLevelHttpResponse execute() throws IOException {
         };
       }
     });
-    FirebaseChannel.getInstance().httpTransport = mockHttpTransport;
-
+    FirebaseChannel.getInstance(null).httpTransport = mockHttpTransport;
+    Mockito.doReturn(null).when(servletUnderTest).getServletContext();
     servletUnderTest.doPost(mockRequest, mockResponse);
 
     verify(mockHttpTransport, times(2)).buildRequest(
diff --git a/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/TicTacToeServletTest.java b/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/TicTacToeServletTest.java
index 7f5fdf90150..de180b2d8e8 100644
--- a/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/TicTacToeServletTest.java
+++ b/appengine/firebase-tictactoe/src/test/java/com/example/appengine/firetactoe/TicTacToeServletTest.java
@@ -16,7 +16,7 @@
 
 package com.example.appengine.firetactoe;
 
-import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
 import static org.mockito.Mockito.anyString;
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.spy;
@@ -38,6 +38,13 @@
 import com.googlecode.objectify.ObjectifyFactory;
 import com.googlecode.objectify.ObjectifyService;
 import com.googlecode.objectify.util.Closeable;
+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 org.junit.After;
 import org.junit.Before;
 import org.junit.BeforeClass;
@@ -46,16 +53,9 @@
 import org.junit.runners.JUnit4;
 import org.mockito.Matchers;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 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;
-
 /**
  * Unit tests for {@link TicTacToeServlet}.
  */
@@ -105,7 +105,7 @@ public void setUp() throws Exception {
     when(mockRequest.getRequestURL()).thenReturn(new StringBuffer("https://timbre/"));
     when(mockRequest.getRequestDispatcher("/WEB-INF/view/index.jsp")).thenReturn(requestDispatcher);
 
-    servletUnderTest = new TicTacToeServlet();
+    servletUnderTest = spy(new TicTacToeServlet());
 
     helper.setEnvIsLoggedIn(true);
   }
@@ -133,14 +133,16 @@ public LowLevelHttpResponse execute() throws IOException {
         };
       }
     });
-    FirebaseChannel.getInstance().httpTransport = mockHttpTransport;
+    FirebaseChannel.getInstance(null).httpTransport = mockHttpTransport;
+
+    Mockito.doReturn(null).when(servletUnderTest).getServletContext();
 
     servletUnderTest.doGet(mockRequest, mockResponse);
 
     // Make sure the game object was created for a new game
     Objectify ofy = ObjectifyService.ofy();
     Game game = ofy.load().type(Game.class).first().safe();
-    assertThat(game.userX).isEqualTo(USER_ID);
+    assertEquals(game.userX, USER_ID);
 
     verify(mockHttpTransport, times(1)).buildRequest(
         eq("PATCH"), Matchers.matches(FIREBASE_DB_URL + "/channels/[\\w-]+.json$"));
@@ -170,7 +172,7 @@ public LowLevelHttpResponse execute() throws IOException {
         };
       }
     });
-    FirebaseChannel.getInstance().httpTransport = mockHttpTransport;
+    FirebaseChannel.getInstance(null).httpTransport = mockHttpTransport;
 
     // Insert a game
     Objectify ofy = ObjectifyService.ofy();
@@ -180,12 +182,14 @@ public LowLevelHttpResponse execute() throws IOException {
 
     when(mockRequest.getParameter("gameKey")).thenReturn(gameKey);
 
+    Mockito.doReturn(null).when(servletUnderTest).getServletContext();
+
     servletUnderTest.doGet(mockRequest, mockResponse);
 
     // Make sure the game object was updated with the other player
     game = ofy.load().type(Game.class).first().safe();
-    assertThat(game.userX).isEqualTo("some-other-user-id");
-    assertThat(game.userO).isEqualTo(USER_ID);
+    assertEquals(game.userX, "some-other-user-id");
+    assertEquals(game.userO, USER_ID);
 
     verify(mockHttpTransport, times(2)).buildRequest(
         eq("PATCH"), Matchers.matches(FIREBASE_DB_URL + "/channels/[\\w-]+.json$"));

From 633dbf7eab3b47495fc6c285f4e33633cf743293 Mon Sep 17 00:00:00 2001
From: Jisha Abubaker <jishaa@google.com>
Date: Mon, 25 Sep 2017 13:27:33 -0700
Subject: [PATCH 2/2] readme updates

---
 appengine/firebase-tictactoe/README.md | 34 ++++++++++++--------------
 1 file changed, 15 insertions(+), 19 deletions(-)

diff --git a/appengine/firebase-tictactoe/README.md b/appengine/firebase-tictactoe/README.md
index 8a3889bf574..4e2799844e7 100644
--- a/appengine/firebase-tictactoe/README.md
+++ b/appengine/firebase-tictactoe/README.md
@@ -7,34 +7,25 @@ for realtime notifications when the board changes.
 [Firebase]: https://firebase.google.com
 [standard]: https://cloud.google.com/appengine/docs/about-the-standard-environment
 
-## Prerequisites
+## Setup
 
 * Install [Apache Maven][maven] 3.0.5 or later
 * Create a project in the [Firebase Console][fb-console]
+* Download and install the [Cloud SDK](https://cloud.google.com/sdk/)
+
+Initialize the `gcloud` configuration to use your new project:
+```
+   gcloud init
+```
 * In the [Overview section][fb-overview] of the Firebase console, click 'Add
   Firebase to your web app' and replace the contents of the file
   `src/main/webapp/WEB-INF/view/firebase_config.jspf` with that code snippet.
 * [Enable the Identity API](https://console.cloud.google.com/apis/api/identitytoolkit.googleapis.com/overview)
-
-
-[fb-console]: https://console.firebase.google.com
-[sdk]: https://cloud.google.com/sdk
-[creds]: https://console.firebase.google.com/iam-admin/serviceaccounts/project?project=_&consoleReturnUrl=https:%2F%2Fconsole.firebase.google.com%2Fproject%2F_%2Fsettings%2Fgeneral%2F
-[fb-overview]: https://console.firebase.google.com/project/_/overview
-[maven]: https://maven.apache.org
-
-## Setup
-
-* If you haven't already, Download and initialize the [Cloud SDK](https://cloud.google.com/sdk/)
-```
-    gcloud init
-```
 * If you haven't already, Create an App Engine app within the current Google Cloud Project
 ```
     gcloud app create
 ```
-* If you haven't already, setup
-[Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials)
+* Setup [Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials)
 to run your application locally:
 Download [service account credentials][creds] and set the `GOOGLE_APPLICATION_CREDENTIALS`
 environment variable to its path:
@@ -42,15 +33,20 @@ environment variable to its path:
    export GOOGLE_APPLICATION_CREDENTIALS=/path/to/your/credentials.json
 ```
 
+[fb-console]: https://console.firebase.google.com
+[sdk]: https://cloud.google.com/sdk
+[creds]: https://console.firebase.google.com/iam-admin/serviceaccounts/project?project=_&consoleReturnUrl=https:%2F%2Fconsole.firebase.google.com%2Fproject%2F_%2Fsettings%2Fgeneral%2F
+[fb-overview]: https://console.firebase.google.com/project/_/overview
+[maven]: https://maven.apache.org
 
-### Running locally
+## Running locally
 
     $ mvn appengine:run
 
 When running locally, the page does not automatically refresh,
 please reload the page manually after each move.
 
-### Deploying to App Engine Standard
+## Deploying to App Engine Standard
 
     $ mvn appengine:deploy