diff --git a/firebase-dataconnect/androidTestutil/src/main/kotlin/com/google/firebase/dataconnect/testutil/DataConnectIntegrationTestBase.kt b/firebase-dataconnect/androidTestutil/src/main/kotlin/com/google/firebase/dataconnect/testutil/DataConnectIntegrationTestBase.kt index a82b5f7cf0e..c0fa7d451ad 100644 --- a/firebase-dataconnect/androidTestutil/src/main/kotlin/com/google/firebase/dataconnect/testutil/DataConnectIntegrationTestBase.kt +++ b/firebase-dataconnect/androidTestutil/src/main/kotlin/com/google/firebase/dataconnect/testutil/DataConnectIntegrationTestBase.kt @@ -19,7 +19,12 @@ package com.google.firebase.dataconnect.testutil import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.firebase.dataconnect.ConnectorConfig import com.google.firebase.util.nextAlphanumericString +import io.kotest.property.Arb import io.kotest.property.RandomSource +import io.kotest.property.arbitrary.Codepoint +import io.kotest.property.arbitrary.alphanumeric +import io.kotest.property.arbitrary.arbitrary +import io.kotest.property.arbitrary.string import kotlin.random.Random import org.junit.Rule import org.junit.rules.TestName @@ -40,6 +45,31 @@ abstract class DataConnectIntegrationTestBase { val rs: RandomSource by randomSeedTestRule.rs + /** + * Generates and returns a string containing random alphanumeric characters, including the name of + * the currently-running test as returned from [testName]. + * + * @param string The [Arb] to use to generate the random string; if not specified, then an [Arb] + * that generates strings of 20 alphanumeric characters is used. + * @param prefix A prefix to include in the returned string; if null (the default) then no prefix + * will be included. + * @return a string containing random characters and incorporating the other information + * identified above. + */ + fun Arb.Companion.alphanumericString( + string: Arb = Arb.string(20, Codepoint.alphanumeric()), + prefix: String? = null, + ): Arb = arbitrary { + buildString { + if (prefix != null) { + append(prefix) + } + append(testName) + append("_") + append(string.bind()) + } + } + companion object { val testConnectorConfig: ConnectorConfig get() = diff --git a/firebase-dataconnect/connectors/src/androidTest/kotlin/com/google/firebase/dataconnect/connectors/demo/OperationExecuteIntegrationTest.kt b/firebase-dataconnect/connectors/src/androidTest/kotlin/com/google/firebase/dataconnect/connectors/demo/OperationExecuteIntegrationTest.kt index 3e1259f51e4..abb3257340f 100644 --- a/firebase-dataconnect/connectors/src/androidTest/kotlin/com/google/firebase/dataconnect/connectors/demo/OperationExecuteIntegrationTest.kt +++ b/firebase-dataconnect/connectors/src/androidTest/kotlin/com/google/firebase/dataconnect/connectors/demo/OperationExecuteIntegrationTest.kt @@ -16,279 +16,274 @@ package com.google.firebase.dataconnect.connectors.demo -import com.google.common.truth.Truth.assertThat -import com.google.firebase.dataconnect.* +import com.google.firebase.dataconnect.DataConnectException import com.google.firebase.dataconnect.connectors.demo.testutil.DemoConnectorIntegrationTestBase -import com.google.firebase.dataconnect.connectors.demo.testutil.assertWith -import com.google.firebase.dataconnect.testutil.assertThrows -import com.google.firebase.dataconnect.testutil.randomAlphanumericString -import kotlinx.coroutines.test.* +import io.kotest.assertions.assertSoftly +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.nulls.shouldBeNull +import io.kotest.matchers.shouldBe +import io.kotest.property.Arb +import io.kotest.property.arbitrary.next +import kotlinx.coroutines.test.runTest import org.junit.Test class OperationExecuteIntegrationTest : DemoConnectorIntegrationTestBase() { @Test fun insert_ShouldSucceedIfPrimaryKeyDoesNotExist() = runTest { - val id = randomFooId() - assertWith(connector).thatFooWithId(id).doesNotExist() - val bar = randomBar() + val id = Arb.fooId().next(rs) + fooWithId(id).shouldNotExist() + val bar = Arb.bar().next(rs) connector.insertFoo.execute(id = id) { this.bar = bar } - assertWith(connector).thatFooWithId(id).existsWithBar(bar) + fooWithId(id) shouldExistWithBar bar } @Test fun insert_ShouldThrowIfPrimaryKeyExists() = runTest { - val id = randomFooId() - val bar = randomBar() - connector.insertFoo.execute(id = id) { this.bar = bar } - assertWith(connector).thatFooWithId(id).exists() + val id = insertFooWithRandomId() - connector.insertFoo.assertThrows(DataConnectException::class) { - execute(id = id) { this.bar = bar } + shouldThrow { + connector.insertFoo.execute(id = id) { bar = Arb.bar().next(rs) } } } @Test fun insert_ShouldContainTheIdInTheResult() = runTest { - val id = randomFooId() - val bar = randomBar() + val id = Arb.fooId().next(rs) + val bar = Arb.bar().next(rs) val mutationResult = connector.insertFoo.execute(id = id) { this.bar = bar } - assertThat(mutationResult.data.key).isEqualTo(FooKey(id)) + mutationResult.data.key shouldBe FooKey(id) } @Test fun upsert_ShouldSucceedIfPrimaryKeyDoesNotExist() = runTest { - val id = randomFooId() - assertWith(connector).thatFooWithId(id).doesNotExist() - val bar = randomBar() + val id = Arb.fooId().next(rs) + fooWithId(id).shouldNotExist() + val bar = Arb.bar().next(rs) connector.upsertFoo.execute(id = id) { this.bar = bar } - assertWith(connector).thatFooWithId(id).existsWithBar(bar) + fooWithId(id) shouldExistWithBar bar } @Test fun upsert_ShouldSucceedIfPrimaryKeyExists() = runTest { - val id = randomFooId() - val bar1 = randomBar() - val bar2 = randomBar() + val id = Arb.fooId().next(rs) + val bar1 = Arb.bar().next(rs) + val bar2 = Arb.bar().next(rs) connector.insertFoo.execute(id = id) { bar = bar1 } - assertWith(connector).thatFooWithId(id).existsWithBar(bar1) + fooWithId(id) shouldExistWithBar bar1 connector.upsertFoo.execute(id = id) { bar = bar2 } - assertWith(connector).thatFooWithId(id).existsWithBar(bar2) + fooWithId(id) shouldExistWithBar bar2 } @Test fun upsert_ShouldContainTheIdInTheResultOnInsert() = runTest { - val id = randomFooId() + val id = Arb.fooId().next(rs) - val mutationResult = connector.upsertFoo.execute(id = id) { bar = randomBar() } + val mutationResult = connector.upsertFoo.execute(id = id) { bar = Arb.bar().next(rs) } - assertThat(mutationResult.data.key).isEqualTo(FooKey(id)) + mutationResult.data.key shouldBe FooKey(id) } @Test fun upsert_ShouldContainTheIdInTheResultOnUpdate() = runTest { - val id = randomFooId() - connector.insertFoo.execute(id = id) { bar = randomBar() } + val id = Arb.fooId().next(rs) + connector.insertFoo.execute(id = id) { bar = Arb.bar().next(rs) } - val mutationResult = connector.upsertFoo.execute(id = id) { bar = randomBar() } + val mutationResult = connector.upsertFoo.execute(id = id) { bar = Arb.bar().next(rs) } - assertThat(mutationResult.data.key).isEqualTo(FooKey(id)) + mutationResult.data.key shouldBe FooKey(id) } @Test fun delete_ShouldSucceedIfPrimaryKeyDoesNotExist() = runTest { - val id = randomFooId() - assertWith(connector).thatFooWithId(id).doesNotExist() + val id = Arb.fooId().next(rs) + fooWithId(id).shouldNotExist() connector.deleteFoo.execute(id = id) - assertWith(connector).thatFooWithId(id).doesNotExist() + fooWithId(id).shouldNotExist() } @Test fun delete_ShouldSucceedIfPrimaryKeyExists() = runTest { - val id = randomFooId() - val bar = randomBar() - connector.insertFoo.execute(id = id) { this.bar = bar } - assertWith(connector).thatFooWithId(id).existsWithBar(bar) + val id = insertFooWithRandomId() connector.deleteFoo.execute(id = id) - assertWith(connector).thatFooWithId(id).doesNotExist() + fooWithId(id).shouldNotExist() } @Test fun delete_ShouldNotContainTheIdInTheResultIfNothingWasDeleted() = runTest { - val id = randomFooId() - assertWith(connector).thatFooWithId(id).doesNotExist() + val id = Arb.fooId().next(rs) + fooWithId(id).shouldNotExist() val mutationResult = connector.deleteFoo.execute(id = id) - assertThat(mutationResult.data.key).isNull() + mutationResult.data.key.shouldBeNull() } @Test fun delete_ShouldContainTheIdInTheResultIfTheRowWasDeleted() = runTest { - val id = randomFooId() - val bar = randomBar() - connector.insertFoo.execute(id = id) { this.bar = bar } - assertWith(connector).thatFooWithId(id).existsWithBar(bar) + val id = insertFooWithRandomId() val mutationResult = connector.deleteFoo.execute(id = id) - assertThat(mutationResult.data.key).isEqualTo(FooKey(id)) + mutationResult.data.key shouldBe FooKey(id) } @Test fun deleteMany_ShouldSucceedIfNoMatches() = runTest { - val bar = randomBar() - assertWith(connector).thatFoosWithBar(bar).doNotExist() + val bar = Arb.bar().next(rs) + foosWithBar(bar).shouldNotExist() connector.deleteFoosByBar.execute(bar = bar) - assertWith(connector).thatFoosWithBar(bar).doNotExist() + foosWithBar(bar).shouldNotExist() } @Test fun deleteMany_ShouldSucceedIfMultipleMatches() = runTest { - val bar = randomBar() - repeat(5) { connector.insertFoo.execute(id = randomFooId()) { this.bar = bar } } - assertWith(connector).thatFoosWithBar(bar).exist(expectedCount = 5) + val bar = Arb.bar().next(rs) + repeat(5) { connector.insertFoo.execute(id = Arb.fooId().next(rs)) { this.bar = bar } } + foosWithBar(bar).shouldExist() connector.deleteFoosByBar.execute(bar = bar) - assertWith(connector).thatFoosWithBar(bar).doNotExist() + foosWithBar(bar).shouldNotExist() } @Test fun deleteMany_ShouldReturnZeroIfNoMatches() = runTest { - val bar = randomBar() - assertWith(connector).thatFoosWithBar(bar).doNotExist() + val bar = Arb.bar().next(rs) + foosWithBar(bar).shouldNotExist() val mutationResult = connector.deleteFoosByBar.execute(bar = bar) - assertThat(mutationResult.data.count).isEqualTo(0) + mutationResult.data.count shouldBe 0 } @Test fun deleteMany_ShouldReturn5If5Matches() = runTest { - val bar = randomBar() - repeat(5) { connector.insertFoo.execute(id = randomFooId()) { this.bar = bar } } - assertWith(connector).thatFoosWithBar(bar).exist(expectedCount = 5) + val bar = Arb.bar().next(rs) + repeat(5) { connector.insertFoo.execute(id = Arb.fooId().next(rs)) { this.bar = bar } } + foosWithBar(bar).shouldExist() val mutationResult = connector.deleteFoosByBar.execute(bar = bar) - assertThat(mutationResult.data.count).isEqualTo(5) + mutationResult.data.count shouldBe 5 } @Test fun update_ShouldSucceedIfPrimaryKeyDoesNotExist() = runTest { - val id = randomFooId() - assertWith(connector).thatFooWithId(id).doesNotExist() + val id = Arb.fooId().next(rs) + fooWithId(id).shouldNotExist() - connector.updateFoo.execute(id = id) { newBar = randomBar() } + connector.updateFoo.execute(id = id) { newBar = Arb.bar().next(rs) } - assertWith(connector).thatFooWithId(id).doesNotExist() + fooWithId(id).shouldNotExist() } @Test fun update_ShouldSucceedIfPrimaryKeyExists() = runTest { - val id = randomFooId() - val oldBar = randomBar() - val newBar = randomBar() + val id = Arb.fooId().next(rs) + val oldBar = Arb.bar().next(rs) + val newBar = Arb.bar().next(rs) connector.insertFoo.execute(id = id) { bar = oldBar } - assertWith(connector).thatFooWithId(id).existsWithBar(oldBar) + fooWithId(id) shouldExistWithBar oldBar connector.updateFoo.execute(id = id) { this.newBar = newBar } - assertWith(connector).thatFooWithId(id).existsWithBar(newBar) + fooWithId(id) shouldExistWithBar newBar } @Test fun update_ShouldNotContainTheIdInTheResultIfNotFound() = runTest { - val id = randomFooId() - assertWith(connector).thatFooWithId(id).doesNotExist() + val id = Arb.fooId().next(rs) + fooWithId(id).shouldNotExist() - val mutationResult = connector.updateFoo.execute(id = id) { newBar = randomBar() } + val mutationResult = connector.updateFoo.execute(id = id) { newBar = Arb.bar().next(rs) } - assertThat(mutationResult.data.key).isNull() + mutationResult.data.key.shouldBeNull() } @Test fun update_ShouldContainTheIdInTheResultIfFound() = runTest { - val id = randomFooId() - val oldBar = randomBar() - val newBar = randomBar() + val id = Arb.fooId().next(rs) + val oldBar = Arb.bar().next(rs) + val newBar = Arb.bar().next(rs) connector.insertFoo.execute(id = id) { bar = oldBar } - assertWith(connector).thatFooWithId(id).existsWithBar(oldBar) + fooWithId(id) shouldExistWithBar oldBar val mutationResult = connector.updateFoo.execute(id = id) { this.newBar = newBar } - assertThat(mutationResult.data.key).isEqualTo(FooKey(id)) + mutationResult.data.key shouldBe FooKey(id) } @Test fun updateMany_ShouldSucceedIfNoMatches() = runTest { - val oldBar = randomBar() - val newBar = randomBar() - assertWith(connector).thatFoosWithBar(oldBar).doNotExist() - assertWith(connector).thatFoosWithBar(newBar).doNotExist() + val oldBar = Arb.bar().next(rs) + val newBar = Arb.bar().next(rs) + foosWithBar(oldBar).shouldNotExist() + foosWithBar(newBar).shouldNotExist() connector.updateFoosByBar.execute { this.oldBar = oldBar this.newBar = newBar } - assertWith(connector).thatFoosWithBar(oldBar).doNotExist() - assertWith(connector).thatFoosWithBar(newBar).doNotExist() + foosWithBar(oldBar).shouldNotExist() + foosWithBar(newBar).shouldNotExist() } @Test fun updateMany_ShouldSucceedIfMultipleMatches() = runTest { - val oldBar = randomBar() - val newBar = randomBar() - repeat(5) { connector.insertFoo.execute(id = randomFooId()) { bar = oldBar } } - assertWith(connector).thatFoosWithBar(oldBar).exist(expectedCount = 5) - assertWith(connector).thatFoosWithBar(newBar).doNotExist() + val oldBar = Arb.bar().next(rs) + val newBar = Arb.bar().next(rs) + repeat(5) { connector.insertFoo.execute(id = Arb.fooId().next(rs)) { bar = oldBar } } + foosWithBar(oldBar).shouldExist() + foosWithBar(newBar).shouldNotExist() connector.updateFoosByBar.execute { this.oldBar = oldBar this.newBar = newBar } - assertWith(connector).thatFoosWithBar(oldBar).doNotExist() - assertWith(connector).thatFoosWithBar(newBar).exist(expectedCount = 5) + assertSoftly { + foosWithBar(oldBar).shouldNotExist() + foosWithBar(newBar).shouldExist() + } } @Test fun updateMany_ShouldReturnZeroIfNoMatches() = runTest { - val oldBar = randomBar() - assertWith(connector).thatFoosWithBar(oldBar).doNotExist() + val oldBar = Arb.bar().next(rs) + foosWithBar(oldBar).shouldNotExist() val mutationResult = connector.updateFoosByBar.execute { this.oldBar = oldBar - newBar = randomBar() + newBar = Arb.bar().next(rs) } - assertThat(mutationResult.data.count).isEqualTo(0) + mutationResult.data.count shouldBe 0 } @Test fun updateMany_ShouldReturn5If5Matches() = runTest { - val oldBar = randomBar() - val newBar = randomBar() - repeat(5) { connector.insertFoo.execute(id = randomFooId()) { bar = oldBar } } - assertWith(connector).thatFoosWithBar(oldBar).exist(expectedCount = 5) - assertWith(connector).thatFoosWithBar(newBar).doNotExist() + val oldBar = Arb.bar().next(rs) + val newBar = Arb.bar().next(rs) + repeat(5) { connector.insertFoo.execute(id = Arb.fooId().next(rs)) { bar = oldBar } } + foosWithBar(oldBar).shouldExist() + foosWithBar(newBar).shouldNotExist() val mutationResult = connector.updateFoosByBar.execute { @@ -296,15 +291,6 @@ class OperationExecuteIntegrationTest : DemoConnectorIntegrationTestBase() { this.newBar = newBar } - assertThat(mutationResult.data.count).isEqualTo(5) - } - - private fun randomFooId() = randomAlphanumericString(prefix = "FooId", numRandomChars = 20) - private fun randomBar() = randomAlphanumericString(prefix = "Bar", numRandomChars = 20) - - suspend fun DemoConnector.insertFooWithRandomId(): String { - val id = randomFooId() - insertFoo.execute(id = id) { bar = randomBar() } - return id + mutationResult.data.count shouldBe 5 } } diff --git a/firebase-dataconnect/connectors/src/androidTest/kotlin/com/google/firebase/dataconnect/connectors/demo/testutil/DemoConnectorIntegrationTestBase.kt b/firebase-dataconnect/connectors/src/androidTest/kotlin/com/google/firebase/dataconnect/connectors/demo/testutil/DemoConnectorIntegrationTestBase.kt index 4a4dead2eab..e540aec757c 100644 --- a/firebase-dataconnect/connectors/src/androidTest/kotlin/com/google/firebase/dataconnect/connectors/demo/testutil/DemoConnectorIntegrationTestBase.kt +++ b/firebase-dataconnect/connectors/src/androidTest/kotlin/com/google/firebase/dataconnect/connectors/demo/testutil/DemoConnectorIntegrationTestBase.kt @@ -16,8 +16,19 @@ package com.google.firebase.dataconnect.connectors.demo.testutil +import com.google.firebase.dataconnect.QueryResult import com.google.firebase.dataconnect.connectors.demo.DemoConnector +import com.google.firebase.dataconnect.connectors.demo.GetFooByIdQuery +import com.google.firebase.dataconnect.connectors.demo.GetFoosByBarQuery +import com.google.firebase.dataconnect.connectors.demo.execute import com.google.firebase.dataconnect.testutil.DataConnectIntegrationTestBase +import io.kotest.matchers.Matcher +import io.kotest.matchers.MatcherResult +import io.kotest.matchers.MatcherResult.Companion.invoke +import io.kotest.matchers.should +import io.kotest.matchers.shouldNot +import io.kotest.property.Arb +import io.kotest.property.arbitrary.next import org.junit.Rule abstract class DemoConnectorIntegrationTestBase : DataConnectIntegrationTestBase() { @@ -26,4 +37,119 @@ abstract class DemoConnectorIntegrationTestBase : DataConnectIntegrationTestBase val demoConnectorFactory = TestDemoConnectorFactory(firebaseAppFactory, dataConnectFactory) val connector: DemoConnector by lazy { demoConnectorFactory.newInstance() } + + fun Arb.Companion.fooId(): Arb = Arb.alphanumericString(prefix = "FooId_") + fun Arb.Companion.bar(): Arb = Arb.alphanumericString(prefix = "Bar_") + + suspend fun fooWithId(id: String): QueryResult = + connector.getFooById.execute(id) + + suspend fun foosWithBar( + bar: String + ): QueryResult = + connector.getFoosByBar.execute { this.bar = bar } + + suspend fun insertFooWithRandomId(): String = connector.insertFooWithRandomId() + + suspend fun DemoConnector.insertFooWithRandomId(): String { + val fooId = Arb.fooId().next(rs) + insertFoo.execute(id = fooId) { bar = Arb.bar().next(rs) } + return fooId + } + + companion object { + suspend fun DemoConnector.fooWithId( + id: String + ): QueryResult = getFooById.execute(id) + + @JvmName("getFooByIdShouldNotExist") + fun QueryResult.shouldNotExist() { + this shouldNot GetFooById.exist() + } + + @JvmName("getFooByIdShouldExist") + fun QueryResult.shouldExist() { + this should GetFooById.exist() + } + + infix fun QueryResult.shouldExistWithBar( + bar: String + ) { + this should GetFooById.existWithBar(bar) + } + + @JvmName("GetFoosByBarShouldExist") + fun QueryResult.shouldExist() { + this should GetFoosByBar.exist() + } + + @JvmName("GetFoosByBarShouldNotExist") + fun QueryResult.shouldNotExist() { + this shouldNot GetFoosByBar.exist() + } + + object GetFooById { + fun exist() = + object : Matcher> { + override fun test( + value: QueryResult + ): MatcherResult { + return invoke( + value.data.foo !== null, + { "Expected Foo with ID ${value.ref.variables.id} to exist, but it did not exist." }, + { + "Expected Foo with ID ${value.ref.variables.id} to not exist," + + " but it did exist with bar: ${value.data.foo!!.bar}." + } + ) + } + } + + fun existWithBar(bar: String) = + object : Matcher> { + override fun test( + value: QueryResult + ): MatcherResult { + val exists = value.data.foo !== null + val barMatches = value.data.foo.let { it?.bar == bar } + return invoke( + exists && barMatches, + { + "Expected Foo with ID ${value.ref.variables.id} to have bar=$bar, " + + if (exists) "but bar does not match: ${value.data.foo?.bar}" + else "but no Foo with that ID exists" + }, + { + throw Exception( + "existWithBar() does not support negation," + + " because it's ambiguous if the 'not' refers to 'exist' or 'bar'." + ) + } + ) + } + } + } + + object GetFoosByBar { + + fun exist() = + object : Matcher> { + override fun test( + value: QueryResult + ): MatcherResult { + return invoke( + value.data.foos.isNotEmpty(), + { + "Expected at least 1 Foo with Bar ${value.ref.variables.bar} to exist," + + " but none existed." + }, + { + "Expected no Foos with Bar ${value.ref.variables.bar} to exist," + + " but ${value.data.foos.size} existed." + }, + ) + } + } + } + } } diff --git a/firebase-dataconnect/connectors/src/androidTest/kotlin/com/google/firebase/dataconnect/connectors/demo/testutil/DemoConnectorTruth.kt b/firebase-dataconnect/connectors/src/androidTest/kotlin/com/google/firebase/dataconnect/connectors/demo/testutil/DemoConnectorTruth.kt deleted file mode 100644 index 8089e80a70b..00000000000 --- a/firebase-dataconnect/connectors/src/androidTest/kotlin/com/google/firebase/dataconnect/connectors/demo/testutil/DemoConnectorTruth.kt +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright 2024 Google LLC - * - * 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.dataconnect.connectors.demo.testutil - -import androidx.annotation.CheckResult -import com.google.firebase.dataconnect.connectors.demo.DemoConnector -import com.google.firebase.dataconnect.connectors.demo.GetFooByIdQuery -import com.google.firebase.dataconnect.connectors.demo.execute -import com.google.firebase.dataconnect.testutil.fail - -/** - * Returns an object with which fluent assertions can be made using the given [DemoConnector] - * instance, similar to [com.google.common.truth.Truth] assertions. - */ -fun assertWith(connector: DemoConnector): DemoConnectorSubject = DemoConnectorSubjectImpl(connector) - -interface DemoConnectorSubject { - - /** Returns an object with which assertions can be performed about a `Foo` with the given ID. */ - @CheckResult fun thatFooWithId(id: String): FooSubject - - /** - * Returns an object with which assertions can be performed about all `Foo` objects whose `bar` - * field is equal to the given value. - */ - @CheckResult fun thatFoosWithBar(bar: String): FooListSubject - - /** Provides methods for performing assertions on a `Foo` object. */ - interface FooSubject { - - /** Throws if the `Foo` does not exist. */ - suspend fun exists() - - /** Throws if the `Foo` exists. */ - suspend fun doesNotExist() - - /** - * Throws if the `Foo` does not exist, or exists with a `bar` field value different than the - * given value. - */ - suspend fun existsWithBar(expectedBar: String) - } - - /** Provides methods for performing assertions on a (possibly empty) list of `Foo` objects. */ - interface FooListSubject { - - /** Throws if the number of existing `Foo` objects is different than the given value. */ - suspend fun exist(expectedCount: Int) - - /** Throws if one or more `Foo` objects exist. */ - suspend fun doNotExist() - } -} - -private class DemoConnectorSubjectImpl(private val connector: DemoConnector) : - DemoConnectorSubject { - override fun thatFooWithId(id: String) = FooSubjectImpl(connector, id) - override fun thatFoosWithBar(bar: String) = FooListSubjectImpl(connector, bar) -} - -private class FooSubjectImpl(private val connector: DemoConnector, private val id: String) : - DemoConnectorSubject.FooSubject { - override suspend fun exists() { - loadFoo() ?: fail("Expected Foo with id=$id to exist, but it does not exist") - } - - override suspend fun existsWithBar(expectedBar: String) { - val foo = loadFoo() - if (foo == null) { - fail("Expected Foo with id=$id to exist with bar=$expectedBar, but it does not exist at all") - } else if (foo.bar != expectedBar) { - fail( - "Expected Foo with id=$id to exist with bar=$expectedBar, and it does exist, " + - "but its bar is different: ${foo.bar}" - ) - } - } - - override suspend fun doesNotExist() { - val foo = loadFoo() - if (foo != null) { - fail("Expected Foo with id=$id to not exist, but it exists with bar=${foo.bar}") - } - } - - private suspend fun loadFoo(): GetFooByIdQuery.Data.Foo? = - connector.getFooById.execute(id).data.foo -} - -private class FooListSubjectImpl(private val connector: DemoConnector, private val bar: String) : - DemoConnectorSubject.FooListSubject { - - override suspend fun doNotExist() { - val count = fooCount() - if (count > 0) { - fail("Expected zero Foo rows to exist with bar=$bar to exist, but found $count") - } - } - - override suspend fun exist(expectedCount: Int) { - val count = fooCount() - if (count != expectedCount) { - fail("Expected ${expectedCount} Foo rows to exist with bar=$bar to exist, but found $count") - } - } - - private suspend fun fooCount(): Int = - connector.getFoosByBar.execute { bar = this@FooListSubjectImpl.bar }.data.foos.size -} diff --git a/firebase-dataconnect/emulator/dataconnect/schema/demo_schema.gql b/firebase-dataconnect/emulator/dataconnect/schema/demo_schema.gql index a9326c0a0c3..1970d016460 100644 --- a/firebase-dataconnect/emulator/dataconnect/schema/demo_schema.gql +++ b/firebase-dataconnect/emulator/dataconnect/schema/demo_schema.gql @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -type Foo @table { +type Foo @table @index(fields: ["bar"]) { id: String! bar: String }