Skip to content

Commit

Permalink
Implement tests for interdependent fields
Browse files Browse the repository at this point in the history
  • Loading branch information
yevhenii-nadtochii committed Dec 5, 2024
1 parent 12328d6 commit c04d6c7
Show file tree
Hide file tree
Showing 5 changed files with 358 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Copyright 2024, TeamDev. All rights reserved.
*
* 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and/or binary forms, with or without
* modification, must retain the above copyright notice and the following
* disclaimer.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package io.spine.test.options.goes

import com.google.protobuf.Message
import com.google.protobuf.util.Timestamps
import io.spine.test.tools.validate.EnumForGoes
import io.spine.test.tools.validate.MutualMessageCompanion
import io.spine.validate.ValidationException
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertDoesNotThrow
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.MethodSource

@DisplayName("`(goes)` constraint should")
internal class GoesMutualITest {

@MethodSource("io.spine.test.options.goes.TestDataMutual#interdependentFields")
@ParameterizedTest(name = "throw if one of mutually dependent `{1}` and `{3}` fields is not set")
fun throwIfOneOfMutuallyDependentFieldsNotSet(
message: Class<out Message>,
fieldName1: String,
fieldValue1: Any,
fieldName2: String,
fieldValue2: Any
) {
val descriptor = message.protoDescriptor()

val field1 = descriptor.findFieldByName(fieldName1)!!
val protoValue1 = protoValue(field1, fieldValue1)
assertThrows<ValidationException> {
message.newBuilder()
.setField(field1, protoValue1)
.build()
}

val field2 = descriptor.findFieldByName(fieldName2)!!
val protoValue2 = protoValue(field2, fieldValue2)
assertThrows<ValidationException> {
message.newBuilder()
.setField(field2, protoValue2)
.build()
}
}

@MethodSource("io.spine.test.options.goes.TestDataMutual#interdependentFields")
@ParameterizedTest(name = "not throw if both mutually dependent `{1}` and `{3}` fields are set")
fun notThrowIfBothMutuallyDependentFieldsSet(
message: Class<out Message>,
fieldName1: String,
fieldValue1: Any,
fieldName2: String,
fieldValue2: Any
) {
val descriptor = message.protoDescriptor()
val field1 = descriptor.findFieldByName(fieldName1)!!
val protoValue1 = protoValue(field1, fieldValue1)
val field2 = descriptor.findFieldByName(fieldName2)!!
val protoValue2 = protoValue(field2, fieldValue2)
assertDoesNotThrow {
message.newBuilder()
.setField(field1, protoValue1)
.setField(field2, protoValue2)
.build()
}
}

@MethodSource("io.spine.test.options.goes.TestDataMutual#messagesWithInterdependentFields")
@ParameterizedTest(name = "not throw if both mutually dependent fields are not set")
fun notThrowIfBothMutuallyDependentFieldsNotSet(message: Class<out Message>) {
assertDoesNotThrow {
message.newBuilder()
.build()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,15 @@ internal object TestData {
*/
@JvmStatic
fun bothTargetAndCompanionFields() = fieldValues.flatMap { (messageClass, companionValue) ->
val companionType = messageClass.typeUnderTest()
fieldValues.map { (fieldClass, fieldValue) ->
val companionType = messageClass.typeUnderTest()
val fieldType = fieldClass.typeUnderTest()
val fieldName = fieldClass.fieldName()
arguments(
messageClass.java,
named(companionType, COMPANION_FIELD_NAME),
companionValue,
named(fieldType, fieldClass.fieldName()),
named(fieldType, fieldName),
fieldValue
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/*
* Copyright 2024, TeamDev. All rights reserved.
*
* 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and/or binary forms, with or without
* modification, must retain the above copyright notice and the following
* disclaimer.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package io.spine.test.options.goes

import com.google.protobuf.ByteString
import com.google.protobuf.Message
import com.google.protobuf.util.Timestamps
import io.spine.test.tools.validate.EnumForGoes
import io.spine.test.tools.validate.MutualBytesCompanion
import io.spine.test.tools.validate.MutualEnumCompanion
import io.spine.test.tools.validate.MutualMapCompanion
import io.spine.test.tools.validate.MutualMessageCompanion
import io.spine.test.tools.validate.MutualRepeatedCompanion
import io.spine.test.tools.validate.MutualStringCompanion
import kotlin.reflect.KClass
import org.junit.jupiter.api.Named.named
import org.junit.jupiter.params.provider.Arguments.arguments

/**
* Provides data for parameterized [GoesMutualITest].
*/
@Suppress("unused")
internal object TestDataMutual {

private val fieldValues = listOf(
MutualMessageCompanion::class to Timestamps.now(),
MutualEnumCompanion::class to EnumForGoes.EFG_ITEM1.valueDescriptor,
MutualStringCompanion::class to "some companion text",
MutualBytesCompanion::class to ByteString.copyFromUtf8("some companion data"),
MutualRepeatedCompanion::class to listOf(1L, 2L, 3L),
MutualMapCompanion::class to mapOf("key" to 32),
)

/**
* Test data for [GoesITest.notThrowIfOnlyCompanionFieldSet].
*/
@JvmStatic
fun messagesWithInterdependentFields() = fieldValues.map { it.first.java }

/**
* Test data for [GoesITest.notThrowIfBothTargetAndCompanionFieldsSet].
*/
@JvmStatic
fun interdependentFields() = fieldValues.flatMap { (messageClass, companionValue) ->
val companionType = messageClass.typeUnderTest()
fieldValues.map { (fieldClass, fieldValue) ->
val companionName = fieldClass.companionName()
val fieldType = fieldClass.typeUnderTest()
val fieldName = fieldClass.fieldName()
arguments(
messageClass.java,
named(companionType, companionName),
companionValue,
named(fieldType, fieldName),
fieldValue
)
}
}
}

/**
* Extracts a simple name of the field type, which is under test from this [KClass].
*
* This extension relies on naming consistency within `goes_mutual.proto` message stubs.
* So, the message name shows a data type of the companion field.
*
* For example, `MutualStringCompanion` becomes just `string`.
*/
private fun KClass<out Message>.typeUnderTest() = simpleName!!
.substringAfter("Mutual")
.substringBefore("Companion")
.lowercase()

/**
* Extracts a simple field name of the field, which declares a dependency
* on another field (companion).
*
* This extension relies on naming consistency within `goes_mutual.proto` message stubs.
* So, each target field (with the option) is named as following: `{data_type}_field`.
*
* For example, `MutualStringCompanion` becomes `string_field`.
*/
private fun KClass<out Message>.fieldName() = "${typeUnderTest()}_field"

/**
* Extracts a simple companion field name of the field, which declares a dependency
* on another field.
*
* This extension relies on naming consistency within `goes_mutual.proto` message stubs.
* So, each target field (with the option) is named as following: `{data_type}_companion`.
*
* For example, `MutualStringCompanion` becomes `string_companion`.
*/
private fun KClass<out Message>.companionName() = "${typeUnderTest()}_companion"
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright 2024, TeamDev. All rights reserved.
*
* 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
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and/or binary forms, with or without
* modification, must retain the above copyright notice and the following
* disclaimer.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
syntax = "proto3";

package spine.test.tools.validate;

import "spine/options.proto";

option (type_url_prefix) = "type.spine.io";
option java_package = "io.spine.test.tools.validate";
option java_outer_classname = "GoesMutualProto";
option java_multiple_files = true;

import "spine/test/tools/validate/goes_values.proto";
import "google/protobuf/timestamp.proto";

message MutualMessageCompanion {
google.protobuf.Timestamp message_companion = 1 [(goes).with = "message_field"];
google.protobuf.Timestamp enum_companion = 2 [(goes).with = "enum_field"];
google.protobuf.Timestamp string_companion = 3 [(goes).with = "string_field"];
google.protobuf.Timestamp bytes_companion = 4 [(goes).with = "bytes_field"];
google.protobuf.Timestamp repeated_companion = 5 [(goes).with = "repeated_field"];
google.protobuf.Timestamp map_companion = 6 [(goes).with = "map_field"];

google.protobuf.Timestamp message_field = 7 [(goes).with = "message_companion"];
EnumForGoes enum_field = 8 [(goes).with = "enum_companion"];
string string_field = 9 [(goes).with = "string_companion"];
bytes bytes_field = 10 [(goes).with = "bytes_companion"];
repeated int64 repeated_field = 11 [(goes).with = "repeated_companion"];
map<string, int32> map_field = 12 [(goes).with = "map_companion"];
}

message MutualEnumCompanion {
EnumForGoes message_companion = 1 [(goes).with = "message_field"];
EnumForGoes enum_companion = 2 [(goes).with = "enum_field"];
EnumForGoes string_companion = 3 [(goes).with = "string_field"];
EnumForGoes bytes_companion = 4 [(goes).with = "bytes_field"];
EnumForGoes repeated_companion = 5 [(goes).with = "repeated_field"];
EnumForGoes map_companion = 6 [(goes).with = "map_field"];

google.protobuf.Timestamp message_field = 7 [(goes).with = "message_companion"];
EnumForGoes enum_field = 8 [(goes).with = "enum_companion"];
string string_field = 9 [(goes).with = "string_companion"];
bytes bytes_field = 10 [(goes).with = "bytes_companion"];
repeated int64 repeated_field = 11 [(goes).with = "repeated_companion"];
map<string, int32> map_field = 12 [(goes).with = "map_companion"];
}

message MutualStringCompanion {
string message_companion = 1 [(goes).with = "message_field"];
string enum_companion = 2 [(goes).with = "enum_field"];
string string_companion = 3 [(goes).with = "string_field"];
string bytes_companion = 4 [(goes).with = "bytes_field"];
string repeated_companion = 5 [(goes).with = "repeated_field"];
string map_companion = 6 [(goes).with = "map_field"];

google.protobuf.Timestamp message_field = 7 [(goes).with = "message_companion"];
EnumForGoes enum_field = 8 [(goes).with = "enum_companion"];
string string_field = 9 [(goes).with = "string_companion"];
bytes bytes_field = 10 [(goes).with = "bytes_companion"];
repeated int64 repeated_field = 11 [(goes).with = "repeated_companion"];
map<string, int32> map_field = 12 [(goes).with = "map_companion"];
}

message MutualBytesCompanion {
bytes message_companion = 1 [(goes).with = "message_field"];
bytes enum_companion = 2 [(goes).with = "enum_field"];
bytes string_companion = 3 [(goes).with = "string_field"];
bytes bytes_companion = 4 [(goes).with = "bytes_field"];
bytes repeated_companion = 5 [(goes).with = "repeated_field"];
bytes map_companion = 6 [(goes).with = "map_field"];

google.protobuf.Timestamp message_field = 7 [(goes).with = "message_companion"];
EnumForGoes enum_field = 8 [(goes).with = "enum_companion"];
string string_field = 9 [(goes).with = "string_companion"];
bytes bytes_field = 10 [(goes).with = "bytes_companion"];
repeated int64 repeated_field = 11 [(goes).with = "repeated_companion"];
map<string, int32> map_field = 12 [(goes).with = "map_companion"];
}

message MutualRepeatedCompanion {
repeated int64 message_companion = 1 [(goes).with = "message_field"];
repeated int64 enum_companion = 2 [(goes).with = "enum_field"];
repeated int64 string_companion = 3 [(goes).with = "string_field"];
repeated int64 bytes_companion = 4 [(goes).with = "bytes_field"];
repeated int64 repeated_companion = 5 [(goes).with = "repeated_field"];
repeated int64 map_companion = 6 [(goes).with = "map_field"];

google.protobuf.Timestamp message_field = 7 [(goes).with = "message_companion"];
EnumForGoes enum_field = 8 [(goes).with = "enum_companion"];
string string_field = 9 [(goes).with = "string_companion"];
bytes bytes_field = 10 [(goes).with = "bytes_companion"];
repeated int64 repeated_field = 11 [(goes).with = "repeated_companion"];
map<string, int32> map_field = 12 [(goes).with = "map_companion"];
}

message MutualMapCompanion {
map<string, int32> message_companion = 1 [(goes).with = "message_field"];
map<string, int32> enum_companion = 2 [(goes).with = "enum_field"];
map<string, int32> string_companion = 3 [(goes).with = "string_field"];
map<string, int32> bytes_companion = 4 [(goes).with = "bytes_field"];
map<string, int32> repeated_companion = 5 [(goes).with = "repeated_field"];
map<string, int32> map_companion = 6 [(goes).with = "map_field"];

google.protobuf.Timestamp message_field = 7 [(goes).with = "message_companion"];
EnumForGoes enum_field = 8 [(goes).with = "enum_companion"];
string string_field = 9 [(goes).with = "string_companion"];
bytes bytes_field = 10 [(goes).with = "bytes_companion"];
repeated int64 repeated_field = 11 [(goes).with = "repeated_companion"];
map<string, int32> map_field = 12 [(goes).with = "map_companion"];
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 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
* https://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and/or binary forms, with or without
* modification, must retain the above copyright notice and the following
Expand Down

0 comments on commit c04d6c7

Please sign in to comment.