Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import io.kotest.property.arbitrary.alphanumeric
import io.kotest.property.arbitrary.egyptianHieroglyphs
import io.kotest.property.arbitrary.enum
import io.kotest.property.arbitrary.int
import io.kotest.property.arbitrary.map
import io.kotest.property.arbitrary.merge
import io.kotest.property.arbitrary.next
import io.kotest.property.arbitrary.string
Expand Down Expand Up @@ -104,7 +105,7 @@ class DataConnectGrpcClientUnitTest {
private val connectorConfig = Arb.dataConnect.connectorConfig().next(rs)
private val requestId = Arb.dataConnect.requestId().next(rs)
private val operationName = Arb.dataConnect.operationName().next(rs)
private val variables = Arb.proto.struct().next(rs)
private val variables = Arb.proto.struct().next(rs).struct
private val callerSdkType = Arb.enum<FirebaseDataConnect.CallerSdkType>().next(rs)

private val mockDataConnectAuth: DataConnectAuth =
Expand Down Expand Up @@ -193,7 +194,7 @@ class DataConnectGrpcClientUnitTest {

@Test
fun `executeQuery() should return data and errors`() = runTest {
val responseData = Arb.proto.struct().next(rs)
val responseData = Arb.proto.struct().next(rs).struct
val responseErrors = List(3) { GraphqlErrorInfo.random(RandomSource.default()) }
coEvery { mockDataConnectGrpcRPCs.executeQuery(any(), any(), any()) } returns
ExecuteQueryResponse.newBuilder()
Expand All @@ -210,7 +211,7 @@ class DataConnectGrpcClientUnitTest {

@Test
fun `executeMutation() should return data and errors`() = runTest {
val responseData = Arb.proto.struct().next(rs)
val responseData = Arb.proto.struct().next(rs).struct
val responseErrors = List(3) { GraphqlErrorInfo.random(RandomSource.default()) }
coEvery { mockDataConnectGrpcRPCs.executeMutation(any(), any(), any()) } returns
ExecuteMutationResponse.newBuilder()
Expand Down Expand Up @@ -253,7 +254,7 @@ class DataConnectGrpcClientUnitTest {

@Test
fun `executeQuery() should retry with a fresh auth token on UNAUTHENTICATED`() = runTest {
val responseData = Arb.proto.struct().next(rs)
val responseData = Arb.proto.struct().next(rs).struct
val forceRefresh = AtomicBoolean(false)
coEvery { mockDataConnectAuth.forceRefresh() } answers { forceRefresh.set(true) }
coEvery { mockDataConnectGrpcRPCs.executeQuery(any(), any(), any()) } answers
Expand Down Expand Up @@ -284,7 +285,7 @@ class DataConnectGrpcClientUnitTest {

@Test
fun `executeMutation() should retry with a fresh auth token on UNAUTHENTICATED`() = runTest {
val responseData = Arb.proto.struct().next(rs)
val responseData = Arb.proto.struct().next(rs).struct
val forceRefresh = AtomicBoolean(false)
coEvery { mockDataConnectAuth.forceRefresh() } answers { forceRefresh.set(true) }
coEvery { mockDataConnectGrpcRPCs.executeMutation(any(), any(), any()) } answers
Expand Down Expand Up @@ -315,7 +316,7 @@ class DataConnectGrpcClientUnitTest {

@Test
fun `executeQuery() should retry with a fresh AppCheck token on UNAUTHENTICATED`() = runTest {
val responseData = Arb.proto.struct().next(rs)
val responseData = Arb.proto.struct().next(rs).struct
val forceRefresh = AtomicBoolean(false)
coEvery { mockDataConnectAppCheck.forceRefresh() } answers { forceRefresh.set(true) }
coEvery { mockDataConnectGrpcRPCs.executeQuery(any(), any(), any()) } answers
Expand Down Expand Up @@ -346,7 +347,7 @@ class DataConnectGrpcClientUnitTest {

@Test
fun `executeMutation() should retry with a fresh AppCheck token on UNAUTHENTICATED`() = runTest {
val responseData = Arb.proto.struct().next(rs)
val responseData = Arb.proto.struct().next(rs).struct
val forceRefresh = AtomicBoolean(false)
coEvery { mockDataConnectAppCheck.forceRefresh() } answers { forceRefresh.set(true) }
coEvery { mockDataConnectGrpcRPCs.executeMutation(any(), any(), any()) } answers
Expand Down Expand Up @@ -593,9 +594,9 @@ class DataConnectGrpcClientOperationResultUnitTest {
fun `deserialize() with non-null data should treat DataConnectUntypedData specially`() = runTest {
checkAll(propTestConfig, Arb.proto.struct(), Arb.dataConnect.operationErrors()) { data, errors
->
val operationResult = OperationResult(data, errors)
val operationResult = OperationResult(data.struct, errors)
val result = operationResult.deserialize(DataConnectUntypedData, serializersModule = null)
result.shouldHaveDataAndErrors(data, errors)
result.shouldHaveDataAndErrors(data.struct, errors)
}
}

Expand Down Expand Up @@ -635,7 +636,7 @@ class DataConnectGrpcClientOperationResultUnitTest {
runTest {
checkAll(
propTestConfig,
Arb.proto.struct(),
Arb.proto.struct().map { it.struct },
Arb.dataConnect.operationErrors(range = 1..10)
) { dataStruct, errors ->
val operationResult = OperationResult(dataStruct, errors)
Expand Down Expand Up @@ -697,7 +698,7 @@ class DataConnectGrpcClientOperationResultUnitTest {

@Test
fun `deserialize() should throw if decoding fails and error list is empty`() = runTest {
checkAll(propTestConfig, Arb.proto.struct()) { dataStruct ->
checkAll(propTestConfig, Arb.proto.struct().map { it.struct }) { dataStruct ->
assume(!dataStruct.containsFields("foo"))
val operationResult = OperationResult(dataStruct, emptyList())
val exception: DataConnectOperationException =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ internal fun DataConnectArb.operationErrorInfo(
Arb.bind(message, path) { message0, path0 -> ErrorInfoImpl(message0, path0) }

internal fun DataConnectArb.operationRawData(): Arb<Map<String, Any?>?> =
Arb.proto.struct().map { it.toMap() }.orNull(nullProbability = 0.33)
Arb.proto.struct().map { it.struct.toMap() }.orNull(nullProbability = 0.33)

internal data class SampleOperationData(val value: String)

Expand All @@ -112,7 +112,7 @@ internal fun DataConnectArb.operationFailureResponseImpl(
}

internal fun DataConnectArb.operationResult(
data: Arb<Struct?> = Arb.proto.struct().orNull(nullProbability = 0.2),
data: Arb<Struct?> = Arb.proto.struct().map { it.struct }.orNull(nullProbability = 0.2),
errors: Arb<List<ErrorInfoImpl>> = operationErrors(),
) =
Arb.bind(data, errors) { data0, errors0 -> DataConnectGrpcClient.OperationResult(data0, errors0) }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* 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.testutil

import com.google.protobuf.ListValue
import com.google.protobuf.Struct
import com.google.protobuf.Value

fun Boolean.toValueProto(): Value = Value.newBuilder().setBoolValue(this).build()

fun String.toValueProto(): Value = Value.newBuilder().setStringValue(this).build()

fun Double.toValueProto(): Value = Value.newBuilder().setNumberValue(this).build()

fun Struct.toValueProto(): Value = Value.newBuilder().setStructValue(this).build()

fun ListValue.toValueProto(): Value = Value.newBuilder().setListValue(this).build()

val Value.isStructValue: Boolean
get() = kindCase == Value.KindCase.STRUCT_VALUE

val Value.isListValue: Boolean
get() = kindCase == Value.KindCase.LIST_VALUE
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* 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.testutil

import com.google.protobuf.Value
import java.util.Objects

sealed interface ProtoValuePathComponent {

class StructKey(val key: String) : ProtoValuePathComponent {
override fun equals(other: Any?) = other is StructKey && other.key == key
override fun hashCode() = Objects.hash(StructKey::class.java, key)
override fun toString() = "StructKey(\"$key\")"
}

class ListIndex(val index: Int) : ProtoValuePathComponent {
override fun equals(other: Any?) = other is ListIndex && other.index == index
override fun hashCode() = Objects.hash(ListIndex::class.java, index)
override fun toString() = "ListIndex($index)"
}
}

typealias ProtoValuePath = List<ProtoValuePathComponent>

data class ProtoValuePathPair(val path: ProtoValuePath, val value: Value)

fun ProtoValuePath.withAppendedListIndex(index: Int): ProtoValuePath =
withAppendedComponent(ProtoValuePathComponent.ListIndex(index))

fun ProtoValuePath.withAppendedStructKey(key: String): ProtoValuePath =
withAppendedComponent(ProtoValuePathComponent.StructKey(key))

fun ProtoValuePath.withAppendedComponent(component: ProtoValuePathComponent): ProtoValuePath =
buildList {
addAll(this@withAppendedComponent)
add(component)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* 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.testutil.property.arbitrary

import io.kotest.common.ExperimentalKotest
import io.kotest.property.PropTestConfig

fun PropTestConfig.withIterations(iterations: Int): PropTestConfig {
@OptIn(ExperimentalKotest::class) return copy(iterations = iterations)
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.google.firebase.dataconnect.ConnectorConfig
import com.google.firebase.dataconnect.DataConnectPathSegment
import com.google.firebase.dataconnect.DataConnectSettings
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.arabic
Expand Down Expand Up @@ -161,3 +162,14 @@ val Arb.Companion.dataConnect: DataConnectArb
get() = DataConnectArb

inline fun <reified T : Any> Arb.Companion.mock(): Arb<T> = arbitrary { mockk<T>(relaxed = true) }

fun <T> Arb<T>.next(rs: RandomSource, edgeCaseProbability: Float): T {
require(edgeCaseProbability in 0.0f..1.0f) {
"invalid edgeCaseProbability: $edgeCaseProbability (must be between 0.0 and 1.0, inclusive)"
}
return if (rs.random.nextFloat() < edgeCaseProbability) {
edgecase(rs)!!
} else {
sample(rs).value
}
}
Loading