Skip to content

Commit 047969b

Browse files
committed
Collection Tests
1 parent b9eacff commit 047969b

File tree

1 file changed

+301
-0
lines changed

1 file changed

+301
-0
lines changed
Lines changed: 301 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,301 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.firestore.pipeline
16+
17+
import com.google.common.truth.Truth.assertThat
18+
import com.google.firebase.firestore.FieldPath as PublicFieldPath
19+
import com.google.firebase.firestore.RealtimePipelineSource
20+
import com.google.firebase.firestore.TestUtil
21+
import com.google.firebase.firestore.model.MutableDocument
22+
import com.google.firebase.firestore.pipeline.Expr.Companion.array
23+
import com.google.firebase.firestore.pipeline.Expr.Companion.constant
24+
import com.google.firebase.firestore.pipeline.Expr.Companion.eqAny
25+
import com.google.firebase.firestore.pipeline.Expr.Companion.field
26+
import com.google.firebase.firestore.runPipeline
27+
import com.google.firebase.firestore.testutil.TestUtilKtx.doc
28+
import kotlinx.coroutines.flow.flowOf
29+
import kotlinx.coroutines.flow.toList
30+
import kotlinx.coroutines.runBlocking
31+
import org.junit.Test
32+
import org.junit.runner.RunWith
33+
import org.robolectric.RobolectricTestRunner
34+
35+
@RunWith(RobolectricTestRunner::class)
36+
internal class CollectionTests {
37+
38+
private val db = TestUtil.firestore()
39+
40+
@Test
41+
fun `empty database returns no results`(): Unit = runBlocking {
42+
val pipeline = RealtimePipelineSource(db).collection("/users")
43+
val inputDocs = emptyList<MutableDocument>()
44+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
45+
assertThat(result).isEmpty()
46+
}
47+
48+
@Test
49+
fun `empty collection other collection ids returns no results`(): Unit = runBlocking {
50+
val pipeline = RealtimePipelineSource(db).collection("/users/bob/games")
51+
val doc1 = doc("users/alice/games/doc1", 1000, mapOf("title" to "minecraft"))
52+
val doc2 = doc("users/charlie/games/doc1", 1000, mapOf("title" to "halo"))
53+
val inputDocs = listOf(doc1, doc2)
54+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
55+
assertThat(result).isEmpty()
56+
}
57+
58+
@Test
59+
fun `empty collection other parents returns no results`(): Unit = runBlocking {
60+
val pipeline = RealtimePipelineSource(db).collection("/users/bob/games")
61+
val doc1 = doc("users/bob/addresses/doc1", 1000, mapOf("city" to "New York"))
62+
val doc2 = doc("users/bob/inventories/doc1", 1000, mapOf("item_id" to 42L))
63+
val inputDocs = listOf(doc1, doc2)
64+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
65+
assertThat(result).isEmpty()
66+
}
67+
68+
@Test
69+
fun `singleton at root returns single document`(): Unit = runBlocking {
70+
val pipeline = RealtimePipelineSource(db).collection("/users")
71+
val doc1 = doc("games/42", 1000, mapOf("title" to "minecraft"))
72+
val doc2 = doc("users/bob", 1000, mapOf("score" to 90L, "rank" to 1L))
73+
val inputDocs = listOf(doc1, doc2)
74+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
75+
assertThat(result).containsExactly(doc2)
76+
}
77+
78+
@Test
79+
fun `singleton nested collection returns single document`(): Unit = runBlocking {
80+
val pipeline = RealtimePipelineSource(db).collection("/users/bob/games")
81+
val doc1 = doc("users/bob/addresses/doc1", 1000, mapOf("city" to "New York"))
82+
val doc2 = doc("users/bob/games/doc1", 1000, mapOf("title" to "minecraft"))
83+
val doc3 = doc("users/alice/games/doc1", 1000, mapOf("title" to "halo"))
84+
val inputDocs = listOf(doc1, doc2, doc3)
85+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
86+
assertThat(result).containsExactly(doc2)
87+
}
88+
89+
@Test
90+
fun `multiple documents at root returns documents`(): Unit = runBlocking {
91+
val pipeline = RealtimePipelineSource(db).collection("/users")
92+
val doc1 = doc("users/bob", 1000, mapOf("score" to 90L, "rank" to 1L))
93+
val doc2 = doc("users/alice", 1000, mapOf("score" to 50L, "rank" to 3L))
94+
val doc3 = doc("users/charlie", 1000, mapOf("score" to 97L, "rank" to 2L))
95+
val doc4 = doc("games/doc1", 1000, mapOf("title" to "minecraft"))
96+
val inputDocs = listOf(doc1, doc2, doc3, doc4)
97+
// Firestore backend sorts by document key as a tie-breaker.
98+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
99+
assertThat(result).containsExactly(doc2, doc1, doc3)
100+
}
101+
102+
@Test
103+
fun `multiple documents nested collection returns documents`(): Unit = runBlocking {
104+
// This test seems identical to MultipleDocumentsAtRootReturnsDocuments in C++?
105+
// Replicating the C++ test name and logic.
106+
val pipeline = RealtimePipelineSource(db).collection("/users")
107+
val doc1 = doc("users/bob", 1000, mapOf("score" to 90L, "rank" to 1L))
108+
val doc2 = doc("users/alice", 1000, mapOf("score" to 50L, "rank" to 3L))
109+
val doc3 = doc("users/charlie", 1000, mapOf("score" to 97L, "rank" to 2L))
110+
val doc4 = doc("games/doc1", 1000, mapOf("title" to "minecraft"))
111+
val inputDocs = listOf(doc1, doc2, doc3, doc4)
112+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
113+
assertThat(result).containsExactly(doc2, doc1, doc3)
114+
}
115+
116+
@Test
117+
fun `subcollection not returned`(): Unit = runBlocking {
118+
val pipeline = RealtimePipelineSource(db).collection("/users")
119+
val doc1 = doc("users/bob", 1000, mapOf("score" to 90L, "rank" to 1L))
120+
val doc2 = doc("users/bob/games/minecraft", 1000, mapOf("title" to "minecraft"))
121+
val doc3 = doc("users/bob/games/minecraft/players/player1", 1000, mapOf("location" to "sf"))
122+
val inputDocs = listOf(doc1, doc2, doc3)
123+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
124+
assertThat(result).containsExactly(doc1)
125+
}
126+
127+
@Test
128+
fun `skips other collection ids`(): Unit = runBlocking {
129+
val pipeline = RealtimePipelineSource(db).collection("/users")
130+
val doc1 = doc("users/bob", 1000, mapOf("score" to 90L, "rank" to 1L))
131+
val doc2 = doc("users-other/bob", 1000, mapOf("score" to 90L, "rank" to 1L))
132+
val doc3 = doc("users/alice", 1000, mapOf("score" to 50L, "rank" to 3L))
133+
val doc4 = doc("users-other/alice", 1000, mapOf("score" to 50L, "rank" to 3L))
134+
val doc5 = doc("users/charlie", 1000, mapOf("score" to 97L, "rank" to 2L))
135+
val doc6 = doc("users-other/charlie", 1000, mapOf("score" to 97L, "rank" to 2L))
136+
val inputDocs = listOf(doc1, doc2, doc3, doc4, doc5, doc6)
137+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
138+
assertThat(result).containsExactly(doc3, doc1, doc5)
139+
}
140+
141+
@Test
142+
fun `skips other parents`(): Unit = runBlocking {
143+
val pipeline = RealtimePipelineSource(db).collection("/users/bob/games")
144+
val doc1 = doc("users/bob/games/doc1", 1000, mapOf("score" to 90L))
145+
val doc2 = doc("users/alice/games/doc1", 1000, mapOf("score" to 90L))
146+
val doc3 = doc("users/bob/games/doc2", 1000, mapOf("score" to 20L))
147+
val doc4 = doc("users/charlie/games/doc1", 1000, mapOf("score" to 20L))
148+
val doc5 = doc("users/bob/games/doc3", 1000, mapOf("score" to 30L))
149+
val doc6 = doc("users/alice/games/doc1", 1000, mapOf("score" to 30L))
150+
val inputDocs = listOf(doc1, doc2, doc3, doc4, doc5, doc6)
151+
// Expected order based on key for user bob's games
152+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
153+
assertThat(result).containsExactly(doc1, doc3, doc5).inOrder()
154+
}
155+
156+
// --- Where Tests ---
157+
158+
@Test
159+
fun `where on values`(): Unit = runBlocking {
160+
val pipeline =
161+
RealtimePipelineSource(db)
162+
.collection("/users")
163+
.where(eqAny(field("score"), array(constant(90L), constant(97L))))
164+
165+
val doc1 = doc("users/bob", 1000, mapOf("score" to 90L))
166+
val doc2 = doc("users/alice", 1000, mapOf("score" to 50L))
167+
val doc3 = doc("users/charlie", 1000, mapOf("score" to 97L))
168+
val doc4 = doc("users/diane", 1000, mapOf("score" to 97L))
169+
val inputDocs = listOf(doc1, doc2, doc3, doc4)
170+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
171+
assertThat(result).containsExactly(doc1, doc3, doc4)
172+
}
173+
174+
@Test
175+
fun `where inequality on values`(): Unit = runBlocking {
176+
val pipeline = RealtimePipelineSource(db).collection("/users").where(field("score").gt(80L))
177+
178+
val doc1 = doc("users/bob", 1000, mapOf("score" to 90L))
179+
val doc2 = doc("users/alice", 1000, mapOf("score" to 50L))
180+
val doc3 = doc("users/charlie", 1000, mapOf("score" to 97L))
181+
val inputDocs = listOf(doc1, doc2, doc3)
182+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
183+
assertThat(result).containsExactly(doc1, doc3)
184+
}
185+
186+
@Test
187+
fun `where not equal on values`(): Unit = runBlocking {
188+
val pipeline = RealtimePipelineSource(db).collection("/users").where(field("score").neq(50L))
189+
190+
val doc1 = doc("users/bob", 1000, mapOf("score" to 90L))
191+
val doc2 = doc("users/alice", 1000, mapOf("score" to 50L))
192+
val doc3 = doc("users/charlie", 1000, mapOf("score" to 97L))
193+
val inputDocs = listOf(doc1, doc2, doc3)
194+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
195+
assertThat(result).containsExactly(doc1, doc3)
196+
}
197+
198+
@Test
199+
fun `where array contains values`(): Unit = runBlocking {
200+
val pipeline =
201+
RealtimePipelineSource(db)
202+
.collection("/users")
203+
.where(field("rounds").arrayContains(constant("round3")))
204+
205+
val doc1 = doc("users/bob", 1000, mapOf("score" to 90L, "rounds" to listOf("round1", "round3")))
206+
val doc2 =
207+
doc("users/alice", 1000, mapOf("score" to 50L, "rounds" to listOf("round2", "round4")))
208+
val doc3 =
209+
doc(
210+
"users/charlie",
211+
1000,
212+
mapOf("score" to 97L, "rounds" to listOf("round2", "round3", "round4"))
213+
)
214+
val inputDocs = listOf(doc1, doc2, doc3)
215+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
216+
assertThat(result).containsExactly(doc1, doc3)
217+
}
218+
219+
// --- Sort Tests ---
220+
221+
@Test
222+
fun `sort on values`(): Unit = runBlocking {
223+
val pipeline = RealtimePipelineSource(db).collection("/users").sort(field("score").descending())
224+
225+
val doc1 = doc("users/bob", 1000, mapOf("score" to 90L))
226+
val doc2 = doc("users/alice", 1000, mapOf("score" to 50L))
227+
val doc3 = doc("users/charlie", 1000, mapOf("score" to 97L))
228+
val inputDocs = listOf(doc1, doc2, doc3)
229+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
230+
assertThat(result).containsExactly(doc3, doc1, doc2).inOrder()
231+
}
232+
233+
@Test
234+
fun `sort on path`(): Unit = runBlocking {
235+
val pipeline =
236+
RealtimePipelineSource(db)
237+
.collection("/users")
238+
.sort(field(PublicFieldPath.documentId()).ascending())
239+
240+
val doc1 = doc("users/bob", 1000, mapOf("score" to 90L))
241+
val doc2 = doc("users/alice", 1000, mapOf("score" to 50L))
242+
val doc3 = doc("users/charlie", 1000, mapOf("score" to 97L))
243+
val inputDocs = listOf(doc1, doc2, doc3)
244+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
245+
assertThat(result).containsExactly(doc2, doc1, doc3).inOrder()
246+
}
247+
248+
// --- Limit Tests ---
249+
250+
@Test
251+
fun limit(): Unit = runBlocking {
252+
val pipeline =
253+
RealtimePipelineSource(db)
254+
.collection("/users")
255+
.sort(field(PublicFieldPath.documentId()).ascending())
256+
.limit(2)
257+
258+
val doc1 = doc("users/bob", 1000, mapOf("score" to 90L))
259+
val doc2 = doc("users/alice", 1000, mapOf("score" to 50L))
260+
val doc3 = doc("users/charlie", 1000, mapOf("score" to 97L))
261+
val inputDocs = listOf(doc1, doc2, doc3)
262+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
263+
assertThat(result).containsExactly(doc2, doc1).inOrder()
264+
}
265+
266+
// --- Sort on Key Tests ---
267+
268+
@Test
269+
fun `sort on key ascending`(): Unit = runBlocking {
270+
val pipeline =
271+
RealtimePipelineSource(db)
272+
.collection("/users/bob/games")
273+
.sort(field(PublicFieldPath.documentId()).ascending())
274+
275+
val doc1 = doc("users/bob/games/a", 1000, mapOf("title" to "minecraft"))
276+
val doc2 = doc("users/bob/games/b", 1000, mapOf("title" to "halo"))
277+
val doc3 = doc("users/bob/games/c", 1000, mapOf("title" to "mariocart"))
278+
val doc4 = doc("users/bob/inventories/a", 1000, mapOf("type" to "sword"))
279+
val doc5 = doc("users/alice/games/c", 1000, mapOf("title" to "skyrim"))
280+
val inputDocs = listOf(doc1, doc2, doc3, doc4, doc5)
281+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
282+
assertThat(result).containsExactly(doc1, doc2, doc3).inOrder()
283+
}
284+
285+
@Test
286+
fun `sort on key descending`(): Unit = runBlocking {
287+
val pipeline =
288+
RealtimePipelineSource(db)
289+
.collection("/users/bob/games")
290+
.sort(field(PublicFieldPath.documentId()).descending())
291+
292+
val doc1 = doc("users/bob/games/a", 1000, mapOf("title" to "minecraft"))
293+
val doc2 = doc("users/bob/games/b", 1000, mapOf("title" to "halo"))
294+
val doc3 = doc("users/bob/games/c", 1000, mapOf("title" to "mariocart"))
295+
val doc4 = doc("users/bob/inventories/a", 1000, mapOf("type" to "sword"))
296+
val doc5 = doc("users/alice/games/c", 1000, mapOf("title" to "skyrim"))
297+
val inputDocs = listOf(doc1, doc2, doc3, doc4, doc5)
298+
val result = runPipeline(db, pipeline, flowOf(*inputDocs.toTypedArray())).toList()
299+
assertThat(result).containsExactly(doc3, doc2, doc1).inOrder()
300+
}
301+
}

0 commit comments

Comments
 (0)