/*
* 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.assertNull;
import com.google.common.collect.ImmutableList;
import com.google.firebase.FirebaseApp;
import com.google.firebase.database.ChildEventListener;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.DatabaseReference.CompletionListener;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.MapBuilder;
import com.google.firebase.database.Query;
import com.google.firebase.database.TestChildEventListener;
import com.google.firebase.database.TestFailure;
import com.google.firebase.database.TestHelpers;
import com.google.firebase.database.ValueEventListener;
import com.google.firebase.database.future.ReadFuture;
import com.google.firebase.database.future.WriteFuture;
import com.google.firebase.testing.IntegrationTestUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeoutException;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class OrderByTestIT {
private static FirebaseApp masterApp;
@BeforeClass
public static void setUpClass() {
masterApp = IntegrationTestUtils.ensureDefaultApp();
}
@AfterClass
public static void tearDownClass() throws IOException {
uploadRules(masterApp, "{\"rules\": {\".read\": true, \".write\": true}}");
}
@Before
public void prepareApp() {
TestHelpers.wrapForErrorHandling(masterApp);
}
@After
public void checkAndCleanupApp() {
TestHelpers.assertAndUnwrapErrorHandlers(masterApp);
}
private static String formatRules(DatabaseReference ref, String rules) {
return String.format(
"{\"rules\": {\".read\": true, \".write\": true, \"%s\": %s}}", ref.getKey(), rules);
}
private static void uploadRules(FirebaseApp app, String rules) throws IOException {
IntegrationTestUtils.AppHttpClient client = new IntegrationTestUtils.AppHttpClient(app);
IntegrationTestUtils.ResponseInfo response = client.put("/.settings/rules.json", rules);
assertEquals(200, response.getStatus());
}
@Test
public void testBasicIndexing() throws IOException, InterruptedException {
DatabaseReference ref = IntegrationTestUtils.getRandomNode(masterApp);
String rules = formatRules(ref, "{ \".indexOn\": \"nuggets\"}");
uploadRules(masterApp, rules);
final Semaphore semaphore = new Semaphore(0);
CompletionListener listener =
new CompletionListener() {
@Override
public void onComplete(DatabaseError error, DatabaseReference ref) {
Assert.assertNull(error);
semaphore.release();
}
};
ref.push()
.setValue(TestHelpers.fromJsonString("{\"name\": \"Andrew\", \"nuggets\": 35}"), listener);
ref.push()
.setValue(TestHelpers.fromJsonString("{\"name\": \"Rob\", \"nuggets\": 40}"), listener);
ref.push()
.setValue(TestHelpers.fromJsonString("{\"name\": \"Greg\", \"nuggets\": 38}"), listener);
TestHelpers.waitFor(semaphore, 3);
final List<String> names = new ArrayList<>();
final ChildEventListener testListener =
ref.orderByChild("nuggets")
.addChildEventListener(
new TestChildEventListener() {
@SuppressWarnings("unchecked")
@Override
public void onChildAdded(DataSnapshot snapshot, String previousChildName) {
names.add(((Map<String, String>) snapshot.getValue()).get("name"));
semaphore.release();
}
});
TestHelpers.waitFor(semaphore, 3);
Assert.assertEquals(ImmutableList.of("Andrew", "Greg", "Rob"), names);
TestHelpers.waitForRoundtrip(ref);
ref.removeEventListener(testListener);
}
@Test
public void testUseFallbackThenDefineIndex()
throws InterruptedException, ExecutionException, TimeoutException, TestFailure, IOException {
DatabaseReference writer = IntegrationTestUtils.getRandomNode(masterApp) ;
DatabaseReference readerReference = FirebaseDatabase.getInstance(masterApp).getReference();
DatabaseReference reader = readerReference.child(writer.getPath().toString());
Map<String, Object> foo1 =
TestHelpers.fromJsonString(
"{ "
+ "\"a\": {\"order\": 2, \"foo\": 1}, "
+ "\"b\": {\"order\": 0}, "
+ "\"c\": {\"order\": 1, \"foo\": false}, "
+ "\"d\": {\"order\": 3, \"foo\": \"hello\"} }");
new WriteFuture(writer, foo1).timedGet();
final List<DataSnapshot> snapshots = new ArrayList<>();
final Semaphore semaphore = new Semaphore(0);
Query query = reader.orderByChild("order").limitToLast(2);
final ValueEventListener listener =
query.addValueEventListener(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
snapshots.add(snapshot);
semaphore.release();
}
@Override
public void onCancelled(DatabaseError error) {
Assert.fail();
}
});
TestHelpers.waitFor(semaphore);
Assert.assertEquals(1, snapshots.size());
Map<String, Object> expected = MapBuilder.of(
"d", MapBuilder.of("order", 3L, "foo", "hello"),
"a", MapBuilder.of("order", 2L, "foo", 1L));
Assert.assertEquals(expected, snapshots.get(0).getValue());
uploadRules(masterApp, formatRules(reader, "{ \".indexOn\": \"order\" }"));
Map<String, Object> fooE = TestHelpers.fromJsonString("{\"order\": 1.5, \"foo\": true}");
new WriteFuture(writer.child("e"), fooE).timedGet();
TestHelpers.waitForRoundtrip(reader);
Map<String, Object> fooF =
TestHelpers.fromJsonString("{\"order\": 4, \"foo\": {\"bar\": \"baz\"}}");
new WriteFuture(writer.child("f"), fooF).timedGet();
TestHelpers.waitForRoundtrip(reader);
TestHelpers.waitFor(semaphore);
Assert.assertEquals(2, snapshots.size());
Map<String, Object> expected2 =
new MapBuilder()
.put(
"f",
new MapBuilder()
.put("order", 4L)
.put("foo", MapBuilder.of("bar", "baz"))
.build())
.put("d", MapBuilder.of("order", 3L, "foo", "hello"))
.build();
Assert.assertEquals(expected2, snapshots.get(1).getValue());
// cleanup
TestHelpers.waitForRoundtrip(reader);
reader.removeEventListener(listener);
}
@Test
public void testSnapshotIterationOrder()
throws InterruptedException, ExecutionException, TimeoutException, TestFailure {
DatabaseReference ref = IntegrationTestUtils.getRandomNode(masterApp) ;
Map<String, Object> initial =
TestHelpers.fromJsonString(
"{"
+ "\"alex\": {\"nuggets\": 60},"
+ "\"greg\": {\"nuggets\": 52},"
+ "\"rob\": {\"nuggets\": 56},"
+ "\"vassili\": {\"nuggets\": 55.5},"
+ "\"tony\": {\"nuggets\": 52}"
+ "}");
Query query = ref.orderByChild("nuggets");
final List<String> valueOrder = new ArrayList<>();
final List<String> childOrder = new ArrayList<>();
final List<String> childPrevNames = new ArrayList<>();
final ValueEventListener valueListener =
query.addValueEventListener(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
for (DataSnapshot child : snapshot.getChildren()) {
valueOrder.add(child.getKey());
}
}
@Override
public void onCancelled(DatabaseError error) {
Assert.fail();
}
});
final ChildEventListener testListener =
query.addChildEventListener(
new TestChildEventListener() {
@Override
public void onChildAdded(DataSnapshot snapshot, String previousChildName) {
childOrder.add(snapshot.getKey());
childPrevNames.add(previousChildName);
}
});
new WriteFuture(ref, initial).timedGet();
List<String> expectedOrder = Arrays.asList("greg", "tony", "vassili", "rob", "alex");
List<String> expectedPrevNames = Arrays.asList(null, "greg", "tony", "vassili", "rob");
Assert.assertEquals(expectedOrder, valueOrder);
Assert.assertEquals(expectedOrder, childOrder);
Assert.assertEquals(expectedPrevNames, childPrevNames);
// cleanup
TestHelpers.waitForRoundtrip(ref);
ref.removeEventListener(testListener);
ref.removeEventListener(valueListener);
}
@Test
public void testDeepPathsForIndex()
throws InterruptedException, ExecutionException, TimeoutException, TestFailure {
DatabaseReference ref = IntegrationTestUtils.getRandomNode(masterApp) ;
Map<String, Object> initial =
TestHelpers.fromJsonString(
"{"
+ "\"alex\": {\"deep\": {\"nuggets\": 60}},"
+ "\"greg\": {\"deep\": {\"nuggets\": 52}},"
+ "\"rob\": {\"deep\": {\"nuggets\": 56}},"
+ "\"vassili\": {\"deep\": {\"nuggets\": 55.5}},"
+ "\"tony\": {\"deep\": {\"nuggets\": 52}}"
+ "}");
new WriteFuture(ref, initial).timedGet();
Query query = ref.orderByChild("deep/nuggets").limitToFirst(3);
final List<String> valueOrder = new ArrayList<>();
final List<String> childOrder = new ArrayList<>();
final List<String> childPrevNames = new ArrayList<>();
final ValueEventListener valueListener =
query.addValueEventListener(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
for (DataSnapshot child : snapshot.getChildren()) {
valueOrder.add(child.getKey());
}
}
@Override
public void onCancelled(DatabaseError error) {
Assert.fail();
}
});
final ChildEventListener testListener =
query.addChildEventListener(
new TestChildEventListener() {
@Override
public void onChildAdded(DataSnapshot snapshot, String previousChildName) {
childOrder.add(snapshot.getKey());
childPrevNames.add(previousChildName);
}
});
TestHelpers.waitForRoundtrip(ref);
List<String> expectedOrder = Arrays.asList("greg", "tony", "vassili");
List<String> expectedPrevNames = Arrays.asList(null, "greg", "tony");
Assert.assertEquals(expectedOrder, valueOrder);
Assert.assertEquals(expectedOrder, childOrder);
Assert.assertEquals(expectedPrevNames, childPrevNames);
// cleanup
ref.removeEventListener(testListener);
ref.removeEventListener(valueListener);
}
@Test
public void testSnapshotIterationOrderForValueIndex()
throws InterruptedException, ExecutionException, TimeoutException, TestFailure {
DatabaseReference ref = IntegrationTestUtils.getRandomNode(masterApp) ;
Map<String, Object> initial =
TestHelpers.fromJsonString(
"{"
+ "\"alex\": 60,"
+ "\"greg\": 52,"
+ "\"rob\": 56,"
+ "\"vassili\": 55.5,"
+ "\"tony\": 52"
+ "}");
Query query = ref.orderByValue();
final List<String> valueOrder = new ArrayList<>();
final List<String> childOrder = new ArrayList<>();
final List<String> childPrevNames = new ArrayList<>();
final ValueEventListener valueListener =
query.addValueEventListener(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
for (DataSnapshot child : snapshot.getChildren()) {
valueOrder.add(child.getKey());
}
}
@Override
public void onCancelled(DatabaseError error) {
Assert.fail();
}
});
final ChildEventListener testListener =
query.addChildEventListener(
new TestChildEventListener() {
@Override
public void onChildAdded(DataSnapshot snapshot, String previousChildName) {
childOrder.add(snapshot.getKey());
childPrevNames.add(previousChildName);
}
});
new WriteFuture(ref, initial).timedGet();
List<String> expectedOrder = Arrays.asList("greg", "tony", "vassili", "rob", "alex");
List<String> expectedPrevNames = Arrays.asList(null, "greg", "tony", "vassili", "rob");
Assert.assertEquals(expectedOrder, valueOrder);
Assert.assertEquals(expectedOrder, childOrder);
Assert.assertEquals(expectedPrevNames, childPrevNames);
// cleanup
TestHelpers.waitForRoundtrip(ref);
ref.removeEventListener(testListener);
ref.removeEventListener(valueListener);
}
@Test
public void testCildMovedEvents() throws InterruptedException {
DatabaseReference ref = IntegrationTestUtils.getRandomNode(masterApp) ;
Map<String, Object> initial =
TestHelpers.fromJsonString(
"{"
+ "\"alex\": {\"nuggets\": 60},"
+ "\"greg\": {\"nuggets\": 52},"
+ "\"rob\": {\"nuggets\": 56},"
+ "\"vassili\": {\"nuggets\": 55.5},"
+ "\"tony\": {\"nuggets\": 52}"
+ "}");
Query query = ref.orderByChild("nuggets");
final Semaphore semaphore = new Semaphore(0);
final String[] prevName = new String[1];
final DataSnapshot[] snapshot = new DataSnapshot[1];
final ChildEventListener testListener =
query.addChildEventListener(
new TestChildEventListener() {
@Override
public void onChildAdded(DataSnapshot snapshot, String previousChildName) {}
@Override
public void onChildMoved(DataSnapshot snap, String previousChildName) {
snapshot[0] = snap;
prevName[0] = previousChildName;
semaphore.release();
}
@Override
public void onChildChanged(DataSnapshot snap, String previousChildName) {}
});
ref.setValue(initial);
ref.child("greg/nuggets").setValue(57);
TestHelpers.waitFor(semaphore);
Assert.assertEquals("greg", snapshot[0].getKey());
Assert.assertEquals("rob", prevName[0]);
Map<String, Object> expectedValue = MapBuilder.of("nuggets", 57L);
Assert.assertEquals(expectedValue, snapshot[0].getValue());
TestHelpers.waitForRoundtrip(ref);
ref.removeEventListener(testListener);
}
@Test
public void testMultipleIndexesAtLocation()
throws IOException, ExecutionException, InterruptedException, TimeoutException, TestFailure {
List<DatabaseReference> refs = IntegrationTestUtils.getRandomNode(masterApp, 2);
DatabaseReference reader = refs.get(0);
DatabaseReference writer = refs.get(1);
Map<String, Object> foo1 =
TestHelpers.fromJsonString(
"{"
+ "\"a\": {\"order\": 2, \"foo\": 2},"
+ "\"b\": {\"order\": 0},"
+ "\"c\": {\"order\": 1, \"foo\": false},"
+ "\"d\": {\"order\": 3, \"foo\": \"hello\"}"
+ "}");
String indexDef = "{\".indexOn\": [\"order\", \"foo\"]}";
String rules = formatRules(reader, indexDef);
uploadRules(masterApp, rules);
new WriteFuture(writer, foo1).timedGet();
Query fooQuery = reader.orderByChild("foo");
Query orderQuery = reader.orderByChild("order");
final List<DataSnapshot> fooSnaps = new ArrayList<>();
final List<DataSnapshot> orderSnaps = new ArrayList<>();
final Semaphore semaphore = new Semaphore(0);
final ValueEventListener fooListener =
fooQuery
.startAt(null)
.endAt(1)
.addValueEventListener(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
fooSnaps.add(snapshot);
semaphore.release();
}
@Override
public void onCancelled(DatabaseError error) {
Assert.fail();
}
});
final ValueEventListener orderListener =
orderQuery
.limitToLast(2)
.addValueEventListener(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
orderSnaps.add(snapshot);
semaphore.release();
}
@Override
public void onCancelled(DatabaseError error) {
Assert.fail();
}
});
TestHelpers.waitFor(semaphore, 2);
Map<String, Object> fooExpected = new HashMap<>();
fooExpected.put("b", MapBuilder.of("order", 0L));
fooExpected.put("c", MapBuilder.of("order", 1L, "foo", false));
Map<String, Object> orderExpected = new HashMap<>();
orderExpected.put("d", MapBuilder.of("order", 3L, "foo", "hello"));
orderExpected.put("a", MapBuilder.of("order", 2L, "foo", 2L));
Assert.assertEquals(1, fooSnaps.size());
Assert.assertEquals(fooExpected, fooSnaps.get(0).getValue());
Assert.assertEquals(1, orderSnaps.size());
Assert.assertEquals(orderExpected, orderSnaps.get(0).getValue());
new WriteFuture(writer.child("a"), MapBuilder.of("order", -1L, "foo", 1L)).timedGet();
fooExpected = new HashMap<>();
fooExpected.put("a", MapBuilder.of("order", -1L, "foo", 1L));
fooExpected.put("b", MapBuilder.of("order", 0L));
fooExpected.put("c", MapBuilder.of("order", 1L, "foo", false));
orderExpected = new HashMap<>();
orderExpected.put("d", MapBuilder.of("order", 3L, "foo", "hello"));
orderExpected.put("c", MapBuilder.of("order", 1L, "foo", false));
TestHelpers.waitFor(semaphore, 2);
Assert.assertEquals(2, fooSnaps.size());
Assert.assertEquals(fooExpected, fooSnaps.get(1).getValue());
//FIXME: There are 3 snapshots here for some reason, instead of 2.
//Assert.assertEquals(2, orderSnaps.size());
//Assert.assertEquals(orderExpected, orderSnaps.get(1).getValue());
// cleanup
TestHelpers.waitForRoundtrip(reader);
reader.removeEventListener(fooListener);
reader.removeEventListener(orderListener);
}
@Test
public void testCallbackRemoval() throws InterruptedException {
DatabaseReference ref = IntegrationTestUtils.getRandomNode(masterApp) ;
final int[] reads = new int[1];
final Semaphore semaphore = new Semaphore(0);
final ValueEventListener fooListener =
ref.orderByChild("foo")
.addValueEventListener(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
reads[0]++;
semaphore.release();
}
@Override
public void onCancelled(DatabaseError error) {
Assert.fail();
}
});
final ValueEventListener barListener =
ref.orderByChild("bar")
.addValueEventListener(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
reads[0]++;
semaphore.release();
}
@Override
public void onCancelled(DatabaseError error) {
Assert.fail();
}
});
final ValueEventListener bazListener =
ref.orderByChild("baz")
.addValueEventListener(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
reads[0]++;
semaphore.release();
}
@Override
public void onCancelled(DatabaseError error) {
Assert.fail();
}
});
final ValueEventListener defaultListener =
ref.addValueEventListener(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
reads[0]++;
semaphore.release();
}
@Override
public void onCancelled(DatabaseError error) {
Assert.fail();
}
});
// wait for initial null events.
TestHelpers.waitFor(semaphore, 4);
Assert.assertEquals(4, reads[0]);
ref.setValue(1);
TestHelpers.waitFor(semaphore, 4);
Assert.assertEquals(8, reads[0]);
ref.removeEventListener(fooListener);
ref.setValue(2);
TestHelpers.waitFor(semaphore, 3);
Assert.assertEquals(11, reads[0]);
// Should be a no-op resulting in 3 more reads
ref.orderByChild("foo").removeEventListener(bazListener);
ref.setValue(3);
TestHelpers.waitFor(semaphore, 3);
Assert.assertEquals(14, reads[0]);
ref.orderByChild("bar").removeEventListener(barListener);
ref.setValue(4);
TestHelpers.waitFor(semaphore, 2);
Assert.assertEquals(16, reads[0]);
// Now, remove everything
ref.removeEventListener(bazListener);
ref.removeEventListener(defaultListener);
ref.setValue(5);
// No more reads
TestHelpers.waitForRoundtrip(ref);
Assert.assertEquals(16, reads[0]);
}
@Test
public void testChildAddedEvents() throws InterruptedException {
DatabaseReference ref = IntegrationTestUtils.getRandomNode(masterApp) ;
Map<String, Object> initial =
new MapBuilder()
.put("a", MapBuilder.of("value", 5L))
.put("c", MapBuilder.of("value", 3L))
.build();
final List<String> snapshotNames = new ArrayList<>();
final List<String> prevNames = new ArrayList<>();
final Semaphore semaphore = new Semaphore(0);
final ChildEventListener testListener =
ref.orderByChild("value")
.addChildEventListener(
new TestChildEventListener() {
@Override
public void onChildAdded(DataSnapshot snap, String prevName) {
snapshotNames.add(snap.getKey());
prevNames.add(prevName);
semaphore.release();
}
});
ref.setValue(initial);
TestHelpers.waitFor(semaphore, 2);
Assert.assertEquals(Arrays.asList("c", "a"), snapshotNames);
Assert.assertEquals(Arrays.asList(null, "c"), prevNames);
Map<String, Object> updates = new HashMap<>();
updates.put("b", MapBuilder.of("value", 4));
updates.put("d", MapBuilder.of("value", 2));
ref.updateChildren(updates);
TestHelpers.waitFor(semaphore, 2);
Assert.assertEquals(Arrays.asList("c", "a", "d", "b"), snapshotNames);
Assert.assertEquals(Arrays.asList(null, "c", null, "c"), prevNames);
ref.removeEventListener(testListener);
}
@Test
public void testUpdatesForUnindexedQuery()
throws InterruptedException, ExecutionException, TestFailure, TimeoutException {
List<DatabaseReference> refs = IntegrationTestUtils.getRandomNode(masterApp, 2);
final DatabaseReference reader = refs.get(0);
final DatabaseReference writer = refs.get(1);
final List<DataSnapshot> snapshots = new ArrayList<>();
Map<String, Object> value = new HashMap<>();
value.put("one", new MapBuilder().put("index", 1).put("value", "one").build());
value.put("two", new MapBuilder().put("index", 2).put("value", "two").build());
value.put("three", new MapBuilder().put("index", 3).put("value", "three").build());
new WriteFuture(writer, value).timedGet();
final Semaphore semaphore = new Semaphore(0);
Query query = reader.orderByChild("index").limitToLast(2);
final ValueEventListener listener =
query.addValueEventListener(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
snapshots.add(snapshot);
semaphore.release();
}
@Override
public void onCancelled(DatabaseError error) {
Assert.fail();
}
});
TestHelpers.waitFor(semaphore);
Assert.assertEquals(1, snapshots.size());
Map<String, Object> expected1 = new HashMap<>();
expected1.put("two", new MapBuilder().put("index", 2L).put("value", "two").build());
expected1.put("three", new MapBuilder().put("index", 3L).put("value", "three").build());
Assert.assertEquals(expected1, snapshots.get(0).getValue());
// update child which should trigger value event
writer.child("one/index").setValue(4);
TestHelpers.waitFor(semaphore);
Assert.assertEquals(2, snapshots.size());
Map<String, Object> expected2 = new HashMap<>();
expected2.put("three", new MapBuilder().put("index", 3L).put("value", "three").build());
expected2.put("one", new MapBuilder().put("index", 4L).put("value", "one").build());
Assert.assertEquals(expected2, snapshots.get(1).getValue());
// cleanup
TestHelpers.waitForRoundtrip(reader);
reader.removeEventListener(listener);
}
@Test
public void testQueriesOnLeafNodes()
throws InterruptedException, ExecutionException, TestFailure, TimeoutException {
DatabaseReference ref = IntegrationTestUtils.getRandomNode(masterApp) ;
final Semaphore semaphore = new Semaphore(0);
new WriteFuture(ref, "leaf-node").timedGet();
final List<DataSnapshot> snapshots = new ArrayList<>();
Query query = ref.orderByChild("foo").limitToLast(1);
final ValueEventListener listener =
query.addValueEventListener(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
snapshots.add(snapshot);
semaphore.release();
}
@Override
public void onCancelled(DatabaseError error) {
Assert.fail();
}
});
TestHelpers.waitFor(semaphore);
Assert.assertEquals(1, snapshots.size());
Assert.assertNull(snapshots.get(0).getValue());
// cleanup
TestHelpers.waitForRoundtrip(ref);
ref.removeEventListener(listener);
}
@Test
public void testServerRespectsKeyIndex()
throws InterruptedException, ExecutionException, TimeoutException, TestFailure {
List<DatabaseReference> refs = IntegrationTestUtils.getRandomNode(masterApp, 2);
DatabaseReference writer = refs.get(0);
DatabaseReference reader = refs.get(1);
Map<String, Object> initial = MapBuilder.of("a", 1, "b", 2, "c", 3);
// If the server doesn't respect the index, it will send down limited data, but with no
// offset, so the expected and actual data don't match.
Query query = reader.orderByKey().startAt("b").limitToFirst(2);
new WriteFuture(writer, initial).timedGet();
final List<String> actualChildren = new ArrayList<>();
final Semaphore semaphore = new Semaphore(0);
ValueEventListener valueListener =
query.addValueEventListener(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
for (DataSnapshot child : snapshot.getChildren()) {
actualChildren.add(child.getKey());
}
semaphore.release();
}
@Override
public void onCancelled(DatabaseError error) {
Assert.fail();
}
});
TestHelpers.waitFor(semaphore);
Assert.assertEquals(ImmutableList.of("b", "c"), actualChildren);
// cleanup
reader.removeEventListener(valueListener);
}
@Test
public void testServerRespectsValueIndex()
throws InterruptedException, ExecutionException, TimeoutException, TestFailure, IOException {
List<DatabaseReference> refs = IntegrationTestUtils.getRandomNode(masterApp, 2);
DatabaseReference writer = refs.get(0);
DatabaseReference reader = refs.get(1);
final String indexRule = "{ \".indexOn\": \".value\"}";
String rules = formatRules(writer, indexRule);
uploadRules(masterApp, rules);
Map<String, Object> initial = MapBuilder.of("a", 1, "c", 2, "b", 3);
// If the server doesn't respect the index, it will send down limited data, but with no
// offset, so the expected and actual data don't match.
Query query = reader.orderByValue().startAt(2).limitToFirst(2);
new WriteFuture(writer, initial).timedGet();
final List<String> actualChildren = new ArrayList<>();
final Semaphore semaphore = new Semaphore(0);
ValueEventListener valueListener =
query.addValueEventListener(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
for (DataSnapshot child : snapshot.getChildren()) {
actualChildren.add(child.getKey());
}
semaphore.release();
}
@Override
public void onCancelled(DatabaseError error) {
Assert.fail();
}
});
TestHelpers.waitFor(semaphore);
Assert.assertEquals(ImmutableList.of("c", "b"), actualChildren);
// cleanup
reader.removeEventListener(valueListener);
}
@Test
public void testDeepUpdates()
throws InterruptedException, ExecutionException, TimeoutException, TestFailure, IOException {
List<DatabaseReference> refs = IntegrationTestUtils.getRandomNode(masterApp, 2);
DatabaseReference writer = refs.get(0);
DatabaseReference reader = refs.get(1);
final String indexRule = "{ \".indexOn\": \"idx\"}";
String rules = formatRules(writer, indexRule);
uploadRules(masterApp, rules);
Map<String, Object> initial =
new MapBuilder()
.put("a", MapBuilder.of("data", "foo", "idx", true))
.put("b", MapBuilder.of("data", "bar", "idx", true))
.put("c", MapBuilder.of("data", "baz", "idx", false))
.build();
new WriteFuture(writer, initial).timedGet();
Query query = reader.orderByChild("idx").equalTo(true);
DataSnapshot snap = TestHelpers.getSnap(query);
TestHelpers.assertDeepEquals(
new MapBuilder()
.put("a", MapBuilder.of("data", "foo", "idx", true))
.put("b", MapBuilder.of("data", "bar", "idx", true))
.build(),
snap.getValue());
Map<String, Object> update =
new MapBuilder().put("a/idx", false).put("b/data", "blah").put("c/idx", true).build();
final Semaphore semaphore = new Semaphore(0);
writer.updateChildren(
update,
new CompletionListener() {
@Override
public void onComplete(DatabaseError error, DatabaseReference ref) {
assertNull(error);
semaphore.release(1);
}
});
TestHelpers.waitFor(semaphore);
snap = TestHelpers.getSnap(query);
TestHelpers.assertDeepEquals(
new MapBuilder()
.put("b", MapBuilder.of("data", "blah", "idx", true))
.put("c", MapBuilder.of("data", "baz", "idx", true))
.build(),
snap.getValue());
}
@Test
public void testStartAtAndEndAtOnValueIndex()
throws InterruptedException, ExecutionException, TimeoutException, TestFailure, IOException {
DatabaseReference ref = IntegrationTestUtils.getRandomNode(masterApp) ;
Map<String, Object> initial =
TestHelpers.fromJsonString(
"{"
+ "\"alex\": 60,"
+ "\"greg\": 52,"
+ "\"rob\": 56,"
+ "\"vassili\": 55.5,"
+ "\"tony\": 52"
+ "}");
Query query = ref.orderByValue().startAt(52, "tony").endAt(56);
final List<String> valueOrder = new ArrayList<>();
final List<String> childOrder = new ArrayList<>();
final List<String> childPrevNames = new ArrayList<>();
final ValueEventListener valueListener =
query.addValueEventListener(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
for (DataSnapshot child : snapshot.getChildren()) {
valueOrder.add(child.getKey());
}
}
@Override
public void onCancelled(DatabaseError error) {
Assert.fail();
}
});
final ChildEventListener testListener =
query.addChildEventListener(
new TestChildEventListener() {
@Override
public void onChildAdded(DataSnapshot snapshot, String previousChildName) {
childOrder.add(snapshot.getKey());
childPrevNames.add(previousChildName);
}
});
new WriteFuture(ref, initial).timedGet();
List<String> expectedOrder = Arrays.asList("tony", "vassili", "rob");
List<String> expectedPrevNames = Arrays.asList(null, "tony", "vassili");
Assert.assertEquals(expectedOrder, valueOrder);
Assert.assertEquals(expectedOrder, childOrder);
Assert.assertEquals(expectedPrevNames, childPrevNames);
// cleanup
TestHelpers.waitForRoundtrip(ref);
ref.removeEventListener(testListener);
ref.removeEventListener(valueListener);
}
@Test
public void testRemovingDefaultListener()
throws InterruptedException, ExecutionException, TimeoutException, TestFailure, IOException {
DatabaseReference ref = IntegrationTestUtils.getRandomNode(masterApp) ;
Object initialData = MapBuilder.of("key", "value");
new WriteFuture(ref, initialData).timedGet();
ValueEventListener listener =
ref.orderByKey()
.addValueEventListener(
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {}
@Override
public void onCancelled(DatabaseError error) {}
});
ref.addValueEventListener(listener);
// Should remove both listener and should remove the listen sent to the server
ref.removeEventListener(listener);
// This used to crash because a listener for ref.orderByKey() existed already
Object result = new ReadFuture(ref.orderByKey()).waitForLastValue();
assertEquals(initialData, result);
}
}