/* * Copyright 2017 Google 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 com.google.firebase.database.integration; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import com.google.firebase.FirebaseApp; import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.EventRecord; import com.google.firebase.database.FirebaseDatabase; import com.google.firebase.database.MapBuilder; import com.google.firebase.database.ServerValue; import com.google.firebase.database.TestFailure; import com.google.firebase.database.TestHelpers; import com.google.firebase.database.ValueEventListener; import com.google.firebase.database.core.DatabaseConfig; import com.google.firebase.database.core.RepoManager; import com.google.firebase.database.future.ReadFuture; import com.google.firebase.database.future.WriteFuture; import com.google.firebase.database.utilities.ParsedUrl; import com.google.firebase.database.utilities.Utilities; import com.google.firebase.testing.IntegrationTestUtils; import java.util.List; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; public class RealtimeTestIT { private static FirebaseApp masterApp; @BeforeClass public static void setUpClass() { masterApp = IntegrationTestUtils.ensureDefaultApp(); } @Before public void prepareApp() { TestHelpers.wrapForErrorHandling(masterApp); } @After public void checkAndCleanupApp() { TestHelpers.assertAndUnwrapErrorHandlers(masterApp); } @Test public void testUrlParsing() { ParsedUrl parsed = Utilities.parseUrl("https://admin-java-sdk.firebaseio.com"); assertEquals("/", parsed.path.toString()); assertEquals("admin-java-sdk.firebaseio.com", parsed.repoInfo.host); assertEquals("admin-java-sdk.firebaseio.com", parsed.repoInfo.internalHost); assertEquals(true, parsed.repoInfo.secure); parsed = Utilities.parseUrl("https://admin-java-sdk.firebaseio.com/foo/bar"); assertEquals("/foo/bar", parsed.path.toString()); assertEquals("admin-java-sdk.firebaseio.com", parsed.repoInfo.host); assertEquals("admin-java-sdk.firebaseio.com", parsed.repoInfo.internalHost); assertEquals(true, parsed.repoInfo.secure); } @Test public void testOnDisconnectSetWorks() throws TestFailure, TimeoutException, InterruptedException { List<DatabaseReference> refs = IntegrationTestUtils.getRandomNode(masterApp, 2); DatabaseReference writer = refs.get(0); DatabaseReference reader = refs.get(1); final DatabaseConfig ctx = TestHelpers.getDatabaseConfig(masterApp); RepoManager.resume(ctx); final Semaphore opSemaphore = new Semaphore(0); final Semaphore valSemaphore = new Semaphore(0); final ReadFuture writerFuture = new ReadFuture(writer.child("disconnected"), new ReadFuture.CompletionCondition() { @Override public boolean isComplete(List<EventRecord> events) { valSemaphore.release(); Object snap = events.get(events.size() - 1).getSnapshot().getValue(); return snap != null; } }); final ReadFuture readerFuture = new ReadFuture(reader.child("disconnected"), new ReadFuture.CompletionCondition() { @Override public boolean isComplete(List<EventRecord> events) { valSemaphore.release(); Object snap = events.get(events.size() - 1).getSnapshot().getValue(); return snap != null; } }); // Wait for initial (null) value on both reader and writer. TestHelpers.waitFor(valSemaphore, 2); Object expected = "dummy"; writer.child("disconnected").onDisconnect().setValue(expected, new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { RepoManager.interrupt(ctx); opSemaphore.release(); } }); TestHelpers.waitFor(opSemaphore); EventRecord writerEventRecord = writerFuture.timedGet().get(1); EventRecord readerEventRecord = readerFuture.timedGet().get(1); RepoManager.resume(ctx); TestHelpers.assertDeepEquals(expected, writerEventRecord.getSnapshot().getValue()); TestHelpers.assertDeepEquals(expected, readerEventRecord.getSnapshot().getValue()); } @Test public void testOnDisconnectSetWithPriorityWorks() throws TestFailure, TimeoutException, InterruptedException { List<DatabaseReference> refs = IntegrationTestUtils.getRandomNode(masterApp, 2); DatabaseReference writer = refs.get(0); DatabaseReference reader = refs.get(1); final DatabaseConfig ctx = TestHelpers.getDatabaseConfig(masterApp); RepoManager.resume(ctx); final Semaphore opSemaphore = new Semaphore(0); final Semaphore valSemaphore = new Semaphore(0); final ReadFuture writerFuture = new ReadFuture(writer.child("disconnected"), new ReadFuture.CompletionCondition() { @Override public boolean isComplete(List<EventRecord> events) { Object snap = events.get(events.size() - 1).getSnapshot().getValue(); valSemaphore.release(); return snap != null; } }); final ReadFuture readerFuture = new ReadFuture(reader.child("disconnected"), new ReadFuture.CompletionCondition() { @Override public boolean isComplete(List<EventRecord> events) { Object snap = events.get(events.size() - 1).getSnapshot().getValue(); valSemaphore.release(); return snap != null; } }); // Wait for initial (null) value on both reader and writer. TestHelpers.waitFor(valSemaphore, 2); String expectedPriority = "12345"; writer.child("disconnected").onDisconnect().setValue(true, expectedPriority, new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { RepoManager.interrupt(ctx); opSemaphore.release(); } }); TestHelpers.waitFor(opSemaphore); EventRecord writerEventRecord = writerFuture.timedGet().get(1); final EventRecord readerEventRecord = readerFuture.timedGet().get(1); RepoManager.resume(ctx); TestHelpers.assertDeepEquals(true, writerEventRecord.getSnapshot().getValue()); TestHelpers.assertDeepEquals(expectedPriority, writerEventRecord.getSnapshot().getPriority()); TestHelpers.assertDeepEquals(true, readerEventRecord.getSnapshot().getValue()); TestHelpers.assertDeepEquals(expectedPriority, readerEventRecord.getSnapshot().getPriority()); } @Test public void testOnDisconnectRemoveWorks() throws TestFailure, TimeoutException, InterruptedException { List<DatabaseReference> refs = IntegrationTestUtils.getRandomNode(masterApp, 2); DatabaseReference writer = refs.get(0); DatabaseReference reader = refs.get(1); final DatabaseConfig ctx = TestHelpers.getDatabaseConfig(masterApp); RepoManager.resume(ctx); final Semaphore opSemaphore = new Semaphore(0); final Semaphore valSemaphore = new Semaphore(0); final ReadFuture writerFuture = new ReadFuture(writer.child("foo"), new ReadFuture.CompletionCondition() { @Override public boolean isComplete(List<EventRecord> events) { valSemaphore.release(); return events.size() == 3; } }); final ReadFuture readerFuture = new ReadFuture(reader.child("foo"), new ReadFuture.CompletionCondition() { @Override public boolean isComplete(List<EventRecord> events) { valSemaphore.release(); return events.size() == 3; } }); // Wait for initial (null) value on both reader and writer. TestHelpers.waitFor(valSemaphore, 2); writer.child("foo").setValue("bar", new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(); } }); TestHelpers.waitFor(opSemaphore); writer.child("foo").onDisconnect().removeValue(new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { RepoManager.interrupt(ctx); opSemaphore.release(); } }); TestHelpers.waitFor(opSemaphore); EventRecord writerEventRecord = writerFuture.timedGet().get(2); EventRecord readerEventRecord = readerFuture.timedGet().get(2); RepoManager.resume(ctx); TestHelpers.assertDeepEquals(null, writerEventRecord.getSnapshot().getValue()); TestHelpers.assertDeepEquals(null, readerEventRecord.getSnapshot().getValue()); } @Test public void testOnDisconnectUpdateWorks() throws TestFailure, TimeoutException, InterruptedException { List<DatabaseReference> refs = IntegrationTestUtils.getRandomNode(masterApp, 2); DatabaseReference writer = refs.get(0); DatabaseReference reader = refs.get(1); final DatabaseConfig ctx = TestHelpers.getDatabaseConfig(masterApp); RepoManager.resume(ctx); final Semaphore opSemaphore = new Semaphore(0); final Semaphore valSemaphore = new Semaphore(0); final ReadFuture writerFuture = new ReadFuture(writer.child("foo"), new ReadFuture.CompletionCondition() { @Override public boolean isComplete(List<EventRecord> events) { valSemaphore.release(); return events.size() == 4; } }); final ReadFuture readerFuture = new ReadFuture(reader.child("foo"), new ReadFuture.CompletionCondition() { @Override public boolean isComplete(List<EventRecord> events) { valSemaphore.release(); return events.size() == 4; } }); // Wait for initial (null) value on both reader and writer. TestHelpers.waitFor(valSemaphore, 2); Map<String, Object> initialValues = new MapBuilder().put("bar", "a").put("baz", "b").build(); writer.child("foo").setValue(initialValues, new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(); } }); TestHelpers.waitFor(opSemaphore); Map<String, Object> updatedValues = new MapBuilder().put("baz", "c").put("bat", "d").build(); writer.child("foo").onDisconnect().updateChildren(updatedValues, new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { RepoManager.interrupt(ctx); opSemaphore.release(); } }); TestHelpers.waitFor(opSemaphore); EventRecord writerEventRecord = writerFuture.timedGet().get(3); EventRecord readerEventRecord = readerFuture.timedGet().get(3); RepoManager.resume(ctx); Map<String, Object> expected = new MapBuilder().put("bar", "a").put("baz", "c").put("bat", "d") .build(); TestHelpers.assertDeepEquals(expected, writerEventRecord.getSnapshot().getValue()); TestHelpers.assertDeepEquals(expected, readerEventRecord.getSnapshot().getValue()); } @Test public void testOnDisconnectTriggersSingleLocalValueEventForWriter() throws TestFailure, TimeoutException, InterruptedException { List<DatabaseReference> refs = IntegrationTestUtils.getRandomNode(masterApp, 1); DatabaseReference writer = refs.get(0); final DatabaseConfig ctx = TestHelpers.getDatabaseConfig(masterApp); RepoManager.resume(ctx); final AtomicInteger callbackCount = new AtomicInteger(0); final Semaphore valSemaphore = new Semaphore(0); final ReadFuture writerFuture = new ReadFuture(writer, new ReadFuture.CompletionCondition() { @Override public boolean isComplete(List<EventRecord> events) { callbackCount.incrementAndGet(); valSemaphore.release(1); return events.size() == 2; } }); TestHelpers.waitFor(valSemaphore); final Semaphore opSemaphore = new Semaphore(0); writer.child("foo").onDisconnect().setValue( new MapBuilder().put("bar", "a").put("baz", "b").build(), new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(1); } }); TestHelpers.waitFor(opSemaphore); writer.child("foo").onDisconnect().updateChildren(new MapBuilder().put("bam", "c").build(), new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(1); } }); TestHelpers.waitFor(opSemaphore); writer.child("foo/baz").onDisconnect().removeValue(new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(1); } }); TestHelpers.waitFor(opSemaphore); RepoManager.interrupt(ctx); TestHelpers.waitFor(valSemaphore); EventRecord writerEventRecord = writerFuture.timedGet().get(1); RepoManager.resume(ctx); Map<String, Object> expected = new MapBuilder() .put("foo", new MapBuilder().put("bam", "c").put("bar", "a").build()).build(); TestHelpers.assertDeepEquals(expected, writerEventRecord.getSnapshot().getValue()); assertTrue(callbackCount.get() == 2); } @Test public void testOnDisconnectTriggersSingleLocalValueEventForReader() throws TestFailure, TimeoutException, InterruptedException { List<DatabaseReference> refs = IntegrationTestUtils.getRandomNode(masterApp, 2); DatabaseReference writer = refs.get(0); DatabaseReference reader = refs.get(1); final DatabaseConfig ctx = TestHelpers.getDatabaseConfig(masterApp); RepoManager.resume(ctx); final AtomicInteger callbackCount = new AtomicInteger(0); final Semaphore valSemaphore = new Semaphore(0); final ReadFuture readerFuture = new ReadFuture(reader, new ReadFuture.CompletionCondition() { @Override public boolean isComplete(List<EventRecord> events) { callbackCount.incrementAndGet(); valSemaphore.release(1); return events.size() == 2; } }); TestHelpers.waitFor(valSemaphore); final Semaphore opSemaphore = new Semaphore(0); writer.child("foo").onDisconnect().setValue( new MapBuilder().put("bar", "a").put("baz", "b").build(), new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(1); } }); TestHelpers.waitFor(opSemaphore); writer.child("foo").onDisconnect().updateChildren(new MapBuilder().put("bam", "c").build(), new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(1); } }); TestHelpers.waitFor(opSemaphore); writer.child("foo/baz").onDisconnect().removeValue(new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(1); } }); TestHelpers.waitFor(opSemaphore); RepoManager.interrupt(ctx); TestHelpers.waitFor(valSemaphore); EventRecord readerEventRecord = readerFuture.timedGet().get(1); RepoManager.resume(ctx); Map<String, Object> expected = new MapBuilder() .put("foo", new MapBuilder().put("bam", "c").put("bar", "a").build()).build(); TestHelpers.assertDeepEquals(expected, readerEventRecord.getSnapshot().getValue()); assertTrue(callbackCount.get() == 2); } @Test public void testOnDisconnectTriggersSingleLocalValueEventForWriterWithQuery() throws TestFailure, TimeoutException, InterruptedException { List<DatabaseReference> refs = IntegrationTestUtils.getRandomNode(masterApp, 1); DatabaseReference writer = refs.get(0); final DatabaseConfig ctx = TestHelpers.getDatabaseConfig(masterApp); RepoManager.resume(ctx); final AtomicInteger callbackCount = new AtomicInteger(0); final Semaphore valSemaphore = new Semaphore(0); final ReadFuture writerFuture = new ReadFuture(writer, new ReadFuture.CompletionCondition() { @Override public boolean isComplete(List<EventRecord> events) { callbackCount.incrementAndGet(); valSemaphore.release(1); return events.size() == 2; } }); TestHelpers.waitFor(valSemaphore); final Semaphore opSemaphore = new Semaphore(0); writer.child("foo").onDisconnect().setValue( new MapBuilder().put("bar", "a").put("baz", "b").build(), new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(1); } }); TestHelpers.waitFor(opSemaphore); writer.child("foo").onDisconnect().updateChildren(new MapBuilder().put("bam", "c").build(), new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(1); } }); TestHelpers.waitFor(opSemaphore); writer.child("foo/baz").onDisconnect().removeValue(new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(1); } }); TestHelpers.waitFor(opSemaphore); RepoManager.interrupt(ctx); TestHelpers.waitFor(valSemaphore); EventRecord writerEventRecord = writerFuture.timedGet().get(1); RepoManager.resume(ctx); Map<String, Object> expected = new MapBuilder() .put("foo", new MapBuilder().put("bam", "c").put("bar", "a").build()).build(); TestHelpers.assertDeepEquals(expected, writerEventRecord.getSnapshot().getValue()); assertTrue(callbackCount.get() == 2); } @Test public void testOnDisconnectTriggersSingleLocalValueEventForReaderWithQuery() throws TestFailure, TimeoutException, InterruptedException { List<DatabaseReference> refs = IntegrationTestUtils.getRandomNode(masterApp, 2); DatabaseReference writer = refs.get(0); DatabaseReference reader = refs.get(1); final DatabaseConfig ctx = TestHelpers.getDatabaseConfig(masterApp); final AtomicInteger callbackCount = new AtomicInteger(0); final Semaphore valSemaphore = new Semaphore(0); final ReadFuture readerFuture = new ReadFuture(reader, new ReadFuture.CompletionCondition() { @Override public boolean isComplete(List<EventRecord> events) { callbackCount.incrementAndGet(); valSemaphore.release(1); return events.size() == 2; } }); TestHelpers.waitFor(valSemaphore); final Semaphore opSemaphore = new Semaphore(0); writer.child("foo").onDisconnect().setValue( new MapBuilder().put("bar", "a").put("baz", "b").build(), new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(1); } }); TestHelpers.waitFor(opSemaphore); writer.child("foo").onDisconnect().updateChildren(new MapBuilder().put("bam", "c").build(), new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(1); } }); TestHelpers.waitFor(opSemaphore); writer.child("foo/baz").onDisconnect().removeValue(new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(1); } }); TestHelpers.waitFor(opSemaphore); RepoManager.interrupt(ctx); TestHelpers.waitFor(valSemaphore); EventRecord readerEventRecord = readerFuture.timedGet().get(1); RepoManager.resume(ctx); Map<String, Object> expected = new MapBuilder() .put("foo", new MapBuilder().put("bam", "c").put("bar", "a").build()).build(); TestHelpers.assertDeepEquals(expected, readerEventRecord.getSnapshot().getValue()); assertTrue(callbackCount.get() == 2); } @Test public void testOnDisconnectDeepMergeTriggersOnlyOneValueEventForReaderWithQuery() throws TestFailure, TimeoutException, InterruptedException { List<DatabaseReference> refs = IntegrationTestUtils.getRandomNode(masterApp, 2); DatabaseReference writer = refs.get(0); DatabaseReference reader = refs.get(1); final DatabaseConfig ctx = TestHelpers.getDatabaseConfig(masterApp); final AtomicInteger callbackCount = new AtomicInteger(0); final Semaphore valSemaphore = new Semaphore(0); final ReadFuture readerFuture = new ReadFuture(reader, new ReadFuture.CompletionCondition() { @Override public boolean isComplete(List<EventRecord> events) { callbackCount.incrementAndGet(); valSemaphore.release(1); return events.size() == 4; } }); TestHelpers.waitFor(valSemaphore); final Semaphore opSemaphore = new Semaphore(0); Map<String, Object> initialValues = new MapBuilder().put("a", 1) .put("b", new MapBuilder().put("c", true).put("d", "scalar") .put("e", new MapBuilder().put("f", "hooray").build()).build()) .build(); writer.setValue(initialValues, new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(1); } }); TestHelpers.waitFor(valSemaphore); TestHelpers.waitFor(opSemaphore); writer.child("b/c").onDisconnect().setValue(false, new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(1); } }); TestHelpers.waitFor(opSemaphore); writer.child("b/d").onDisconnect().removeValue(new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(1); } }); TestHelpers.waitFor(opSemaphore); RepoManager.interrupt(ctx); TestHelpers.waitFor(valSemaphore); EventRecord readerEventRecord = readerFuture.timedGet().get(3); RepoManager.resume(ctx); Map<String, Object> expected = new MapBuilder().put("a", 1L).put("b", new MapBuilder() .put("c", false).put("e", new MapBuilder().put("f", "hooray").build()).build()).build(); TestHelpers.assertDeepEquals(expected, readerEventRecord.getSnapshot().getValue()); assertEquals(4, callbackCount.get()); } @Test public void testOnDisconnectCancelWorks() throws TestFailure, TimeoutException, InterruptedException { List<DatabaseReference> refs = IntegrationTestUtils.getRandomNode(masterApp, 2); DatabaseReference writer = refs.get(0); DatabaseReference reader = refs.get(1); final DatabaseConfig ctx = TestHelpers.getDatabaseConfig(masterApp); RepoManager.resume(ctx); final Semaphore opSemaphore = new Semaphore(0); final Semaphore valSemaphore = new Semaphore(0); final ReadFuture writerFuture = new ReadFuture(writer.child("foo"), new ReadFuture.CompletionCondition() { @Override public boolean isComplete(List<EventRecord> events) { valSemaphore.release(); return events.size() == 3; } }); final ReadFuture readerFuture = new ReadFuture(reader.child("foo"), new ReadFuture.CompletionCondition() { @Override public boolean isComplete(List<EventRecord> events) { valSemaphore.release(); return events.size() == 3; } }); // Wait for initial (null) value on both reader and writer. TestHelpers.waitFor(valSemaphore, 2); Map<String, Object> initialValues = new MapBuilder().put("bar", "a").put("baz", "b").build(); writer.child("foo").setValue(initialValues, new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(); } }); TestHelpers.waitFor(opSemaphore); Map<String, Object> updatedValues = new MapBuilder().put("baz", "c").put("bat", "d").build(); writer.child("foo").onDisconnect().updateChildren(updatedValues, new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { opSemaphore.release(); } }); TestHelpers.waitFor(opSemaphore); writer.child("foo/bat").onDisconnect().cancel(new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { RepoManager.interrupt(ctx); opSemaphore.release(); } }); TestHelpers.waitFor(opSemaphore); EventRecord writerEventRecord = writerFuture.timedGet().get(2); EventRecord readerEventRecord = readerFuture.timedGet().get(2); RepoManager.resume(ctx); Map<String, Object> expected = new MapBuilder().put("bar", "a").put("baz", "c").build(); TestHelpers.assertDeepEquals(expected, writerEventRecord.getSnapshot().getValue()); TestHelpers.assertDeepEquals(expected, readerEventRecord.getSnapshot().getValue()); } @Test public void testOnDisconnectWithServerValuesWorks() throws TestFailure, TimeoutException, InterruptedException { List<DatabaseReference> refs = IntegrationTestUtils.getRandomNode(masterApp, 1); DatabaseReference writer = refs.get(0); final DatabaseConfig ctx = TestHelpers.getDatabaseConfig(masterApp); RepoManager.resume(ctx); final Semaphore opSemaphore = new Semaphore(0); final Semaphore valSemaphore = new Semaphore(0); final ReadFuture writerFuture = new ReadFuture(writer, new ReadFuture.CompletionCondition() { @Override public boolean isComplete(List<EventRecord> events) { valSemaphore.release(); Object snapVal = events.get(events.size() - 1).getSnapshot().getValue(); return snapVal != null; } }); // Wait for initial (null) value. TestHelpers.waitFor(valSemaphore); Map<String, Object> initialValues = new MapBuilder() .put("a", ServerValue.TIMESTAMP).put("b", new MapBuilder() .put(".value", ServerValue.TIMESTAMP).put(".priority", ServerValue.TIMESTAMP).build()) .build(); writer.onDisconnect().setValue(initialValues, ServerValue.TIMESTAMP, new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { RepoManager.interrupt(ctx); opSemaphore.release(); } }); TestHelpers.waitFor(opSemaphore); EventRecord readerEventRecord = writerFuture.timedGet().get(1); DataSnapshot snap = readerEventRecord.getSnapshot(); RepoManager.resume(ctx); assertEquals(snap.child("a").getValue().getClass(), Long.class); assertEquals(snap.getPriority().getClass(), Double.class); assertEquals(snap.getPriority(), snap.child("b").getPriority()); assertEquals(snap.child("a").getValue(), snap.child("b").getValue()); TestHelpers.assertTimeDelta(Long.parseLong(snap.child("a").getValue().toString())); } // TODO: Find better way to test shutdown behavior. This test is not worth a // 13-second pause (6 // second sleep and then 7 second timeout via timedGet())! @Test @Ignore public void testShutdown() throws InterruptedException, ExecutionException, TestFailure, TimeoutException { DatabaseConfig config = TestHelpers.getDatabaseConfig(masterApp); // Shut it down right away RepoManager.interrupt(config); Thread.sleep(6 * 1000); // Long enough for all of the threads to exit assertTrue(config.isStopped()); // Test that we can use an existing ref DatabaseReference ref = FirebaseDatabase.getInstance().getReference(); DatabaseReference pushed = ref.push(); try { new WriteFuture(pushed, "foo").timedGet(); fail("Should time out, we're offline"); } catch (TimeoutException t) { // Expected, we're offline } assertFalse(config.isStopped()); RepoManager.resume(config); final Semaphore ready = new Semaphore(0); ref.child(".info/connected").addValueEventListener(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { Boolean connected = snapshot.getValue(Boolean.class); if (connected) { ready.release(1); } } @Override public void onCancelled(DatabaseError error) { } }); // Wait for us to be connected so we send the buffered put TestHelpers.waitFor(ready); DataSnapshot snap = TestHelpers.getSnap(pushed); assertEquals("foo", snap.getValue(String.class)); } @Test public void testWritesToSameLocationWhileOfflineAreInOrder() throws InterruptedException, ExecutionException, TimeoutException, TestFailure { DatabaseReference ref = IntegrationTestUtils.getRandomNode(masterApp); DatabaseReference.goOffline(); for (int i = 0; i < 100; i++) { ref.setValue(i); } // This should be the last write and the actual value WriteFuture future = new WriteFuture(ref, 100); DatabaseReference.goOnline(); future.timedGet(); final Semaphore semaphore = new Semaphore(0); ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { assertEquals(100L, snapshot.getValue()); semaphore.release(); } @Override public void onCancelled(DatabaseError error) { fail("Shouldn't be cancelled"); } }); TestHelpers.waitFor(semaphore); } @Test public void testOnDisconnectIsNotRerunOnReconnect() throws TestFailure, TimeoutException, InterruptedException { DatabaseReference ref = IntegrationTestUtils.getRandomNode(masterApp); final DatabaseConfig ctx = TestHelpers.getDatabaseConfig(masterApp); RepoManager.resume(ctx); final Semaphore semaphore = new Semaphore(0); // Will ensure that the operation is queued RepoManager.interrupt(ctx); final int[] counter = new int[1]; ref.child("disconnected").onDisconnect().setValue(true, new DatabaseReference.CompletionListener() { @Override public void onComplete(DatabaseError error, DatabaseReference ref) { assertNull(error); semaphore.release(); counter[0]++; } }); // Will trigger sending the onDisconnect RepoManager.resume(ctx); // Should be complete initially TestHelpers.waitFor(semaphore); // One onComplete called assertEquals(1, counter[0]); // Will trigger a reconnect RepoManager.interrupt(ctx); RepoManager.resume(ctx); // Make sure we sent all outstanding onDisconnects TestHelpers.waitForRoundtrip(ref); // Two are needed because writes are restored first, then onDisconnects TestHelpers.waitForRoundtrip(ref); assertEquals(1, counter[0]); // No onComplete should have triggered } }