Skip to content

Commit

Permalink
nested components tests
Browse files Browse the repository at this point in the history
fixes
  • Loading branch information
lumber1000 committed Sep 3, 2024
1 parent 0e5f7a0 commit 3a7db1e
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 7 deletions.
11 changes: 5 additions & 6 deletions src/main/kotlin/com/exactpro/th2/codec/fixng/FixNgCodec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ class FixNgCodec(dictionary: IDictionaryStructure, settings: FixNgCodecSettings)
private fun validateRequiredTags(requiredTags: Set<Int>, tagsSet: Set<Int>, isDirty: Boolean, context: IReportingContext) {
for (tag in requiredTags) {
if (!tagsSet.contains(tag)) {
handleError(isDirty, context, "Required tag missing. Tag: ${tag}.")
handleError(isDirty, context, "Required tag missing. Tag: $tag.")
}
}
}
Expand Down Expand Up @@ -367,7 +367,7 @@ class FixNgCodec(dictionary: IDictionaryStructure, settings: FixNgCodecSettings)
handleError(isDirty, context, "Wrong date/time value in ${field.primitiveType.name} field '$field.name'. Value: $value.", value)
}
}
else -> handleError(isDirty, context, "Wrong type value in field ${field.name}. Actual: ${value.javaClass} (value: $value). Expected ${field.primitiveType}")
else -> handleError(isDirty, context, "Wrong type value in field ${field.name}. Actual: ${value.javaClass} (value: $value). Expected ${field.primitiveType}", value)
}

val stringValue = when (valueToEncode) {
Expand Down Expand Up @@ -603,16 +603,15 @@ class FixNgCodec(dictionary: IDictionaryStructure, settings: FixNgCodecSettings)
return target
}

private fun collectConditionallyRequiredTags(fields: Map<String, IFieldStructure>, isParentRequired: Boolean, target: MutableMap<String, Set<Int>>): Map<String, Set<Int>> {
private fun collectConditionallyRequiredTags(fields: Map<String, IFieldStructure>, target: MutableMap<String, Set<Int>>): Map<String, Set<Int>> {
for (field in fields.values) {
if (field is IMessageStructure && field.isComponent) {
val isCurrentRequired = isParentRequired && field.isRequired
val isCurrentRequired = field.isRequired
// There is no point in adding tags from optional components that contain only one field
// (such a field is effectively optional even if it has a required flag).
if (!isCurrentRequired && field.fields.size > 1) {
target[field.name] = collectRequiredTags(field.fields, mutableSetOf())
}
collectConditionallyRequiredTags(field.fields, isCurrentRequired, target)
}
}
return target
Expand All @@ -625,7 +624,7 @@ class FixNgCodec(dictionary: IDictionaryStructure, settings: FixNgCodecSettings)
path = path,
isRequired = isRequired,
requiredTags = if (isForEncode) emptySet() else collectRequiredTags(fields, mutableSetOf()),
conditionallyRequiredTags = if (isForEncode) emptyMap() else collectConditionallyRequiredTags(fields, true, mutableMapOf())
conditionallyRequiredTags = if (isForEncode) emptyMap() else collectConditionallyRequiredTags(fields, mutableMapOf())
)

private fun getFirstTag(message: IMessageStructure): Int = message.fields.values.first().let {
Expand Down
118 changes: 117 additions & 1 deletion src/test/kotlin/com/exactpro/th2/codec/fixng/FixNgCodecTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,76 @@ class FixNgCodecTest {
fun `tag appears out of order`() =
decodeTest(MSG_TAG_OUT_OF_ORDER, "Tag appears out of order: 999", dirtyMode = false)

@Test
fun `decode nested components`() =
decodeTest(MSG_NESTED_REQ_COMPONENTS, expectedMessage = parsedMessageWithNestedComponents)

@Test
fun `decode with missing req field in req nested component`() {
@Suppress("UNCHECKED_CAST")
(parsedBodyWithNestedComponents["OuterComponent"] as MutableMap<String, MutableMap<String, Any>>)["InnerComponent"]?.remove("OrdType")
decodeTest(MSG_NESTED_REQ_COMPONENTS_MISSED_REQ, expectedErrorText = "Required tag missing. Tag: 40.", expectedMessage = parsedMessageWithNestedComponents)
}

@Test
fun `decode with missing optional field in req nested component`() {
@Suppress("UNCHECKED_CAST")
(parsedBodyWithNestedComponents["OuterComponent"] as MutableMap<String, MutableMap<String, Any>>)["InnerComponent"]?.remove("Text")
decodeTest(MSG_NESTED_REQ_COMPONENTS_MISSED_OPTIONAL, expectedMessage = parsedMessageWithNestedComponents)
}

private fun convertToOptionalComponent(): ParsedMessage {
@Suppress("UNCHECKED_CAST")
(parsedBodyWithNestedComponents["header"] as MutableMap<String, String>)["MsgType"] = "TEST_2"
val msgBuilder = parsedMessageWithNestedComponents.toBuilder()
msgBuilder.setType("NestedOptionalComponentTestMessage")
return msgBuilder.build()
}

@Test
fun `decode nested optional components`() {
val message = convertToOptionalComponent()
decodeTest(MSG_NESTED_OPT_COMPONENTS, expectedMessage = message)
}

@Test
fun `decode with missing req field in opt nested component`() {
val message = convertToOptionalComponent()
@Suppress("UNCHECKED_CAST")
(parsedBodyWithNestedComponents["OuterComponent"] as MutableMap<String, MutableMap<String, Any>>)["InnerComponent"]?.remove("OrdType")
decodeTest(MSG_NESTED_OPT_COMPONENTS_MISSED_REQ, expectedErrorText = "Required tag missing. Tag: 40.", expectedMessage = message)
}

@Test
fun `decode with missing all fields in opt nested component`() {
val message = convertToOptionalComponent()
@Suppress("UNCHECKED_CAST")
(parsedBodyWithNestedComponents["OuterComponent"] as MutableMap<String, MutableMap<String, Any>>).remove("InnerComponent")
decodeTest(MSG_NESTED_OPT_COMPONENTS_MISSED_ALL_FIELDS, expectedErrorText = "Required tag missing. Tag: 40.", expectedMessage = message)
}

@Test
fun `decode with missing all fields in inner and outer nested components`() {
val message = convertToOptionalComponent()
parsedBodyWithNestedComponents.remove("OuterComponent")
decodeTest(MSG_NESTED_OPT_COMPONENTS_MISSED_ALL_FIELDS_INNER_AND_OUTER, expectedMessage = message)
}

@Test
fun `decode with missing req fields in both inner and outer components`() {
val message = convertToOptionalComponent()
@Suppress("UNCHECKED_CAST")
(parsedBodyWithNestedComponents["OuterComponent"] as MutableMap<String, MutableMap<String, Any>>).remove("LeavesQty")
@Suppress("UNCHECKED_CAST")
(parsedBodyWithNestedComponents["OuterComponent"] as MutableMap<String, MutableMap<String, Any>>)["InnerComponent"]!!.remove("OrdType")
decodeTest(
MSG_NESTED_OPT_COMPONENTS_MISSED_ALL_OUTER_FIELDS_AND_REQ_INNER_FIELD,
expectedErrorText = "Required tag missing. Tag: 40.",
expectedSecondErrorText = "Required tag missing. Tag: 151.",
expectedMessage = message
)
}

private fun encodeTest(
expectedRawMessage: String,
expectedWarning: String? = null,
Expand All @@ -258,6 +328,7 @@ class FixNgCodecTest {
private fun decodeTest(
rawMessageString: String,
expectedErrorText: String? = null,
expectedSecondErrorText: String? = null,
expectedMessage: ParsedMessage = parsedMessage,
dirtyMode: Boolean = true,
decodeToStringValues: Boolean = false
Expand Down Expand Up @@ -296,7 +367,13 @@ class FixNgCodecTest {
if (expectedErrorText == null) {
assertThat(reportingContext.warnings).isEmpty()
} else {
assertThat(reportingContext.warnings.single()).startsWith(DIRTY_MODE_WARNING_PREFIX + expectedErrorText)
if (expectedSecondErrorText == null) {
assertThat(reportingContext.warnings.single()).startsWith(DIRTY_MODE_WARNING_PREFIX + expectedErrorText)
} else {
assertThat(reportingContext.warnings).size().isEqualTo(2)
assertThat(reportingContext.warnings[0]).startsWith(DIRTY_MODE_WARNING_PREFIX + expectedErrorText)
assertThat(reportingContext.warnings[1]).startsWith(DIRTY_MODE_WARNING_PREFIX + expectedSecondErrorText)
}
}
}

Expand Down Expand Up @@ -385,6 +462,35 @@ class FixNgCodecTest {
)
)

private val parsedMessageWithNestedComponents = ParsedMessage(
MessageId("test_alias", Direction.OUTGOING, 0L, Instant.now(), emptyList()),
EventId("test_id", "test_book", "test_scope", Instant.now()),
"NestedRequiredComponentTestMessage",
mutableMapOf("encode-mode" to "dirty"),
PROTOCOL,
mutableMapOf(
"header" to mutableMapOf(
"BeginString" to "FIXT.1.1",
"BodyLength" to 59,
"MsgType" to "TEST_1",
"MsgSeqNum" to 125,
"TargetCompID" to "INET",
"SenderCompID" to "MZHOT0"
),
"OuterComponent" to mutableMapOf(
"LeavesQty" to BigDecimal(1234), // tag 151
"InnerComponent" to mutableMapOf(
"Text" to "text_1", // tag 58
"OrdType" to '1' // 40
)
),
"trailer" to mapOf(
"CheckSum" to "191"
)
)
)
private val parsedBodyWithNestedComponents: MutableMap<String, Any?> = parsedMessageWithNestedComponents.body as MutableMap

companion object {
private const val DIRTY_MODE_WARNING_PREFIX = "Dirty mode WARNING: "

Expand All @@ -402,5 +508,15 @@ class FixNgCodecTest {
private const val MSG_NON_PRINTABLE = "8=FIXT.1.1\u00019=303\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\taccount\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=171\u0001"
private const val MSG_REQUIRED_HEADER_REMOVED = "8=FIXT.1.1\u00019=236\u000135=8\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=050\u0001"
private const val MSG_TAG_OUT_OF_ORDER = "8=FIXT.1.1\u00019=295\u000135=8\u000149=SENDER\u000156=RECEIVER\u000134=10947\u000152=20230419-10:36:07.415088\u000117=495504662\u000111=zSuNbrBIZyVljs\u000141=zSuNbrBIZyVljs\u000137=49415882\u0001150=0\u000139=0\u0001151=500\u000114=500\u000148=NWDR\u000122=8\u0001453=2\u0001448=NGALL1FX01\u0001447=D\u0001452=76\u0001448=0\u0001447=P\u0001452=3\u00011=test\u000140=A\u000159=0\u000154=B\u000155=ABC\u000138=500\u000144=1000\u000147=500\u000160=20180205-10:38:08.000008\u000110=000\u0001999=500\u0001"

private const val MSG_NESTED_REQ_COMPONENTS = "8=FIXT.1.19=5935=TEST_149=MZHOT056=INET34=12558=text_140=1151=123410=191"
private const val MSG_NESTED_REQ_COMPONENTS_MISSED_REQ = "8=FIXT.1.19=5935=TEST_149=MZHOT056=INET34=12558=text_1151=123410=191"
private const val MSG_NESTED_REQ_COMPONENTS_MISSED_OPTIONAL = "8=FIXT.1.19=5935=TEST_149=MZHOT056=INET34=12540=1151=123410=191"

private const val MSG_NESTED_OPT_COMPONENTS = "8=FIXT.1.19=5935=TEST_249=MZHOT056=INET34=12558=text_140=1151=123410=191"
private const val MSG_NESTED_OPT_COMPONENTS_MISSED_REQ = "8=FIXT.1.19=5935=TEST_249=MZHOT056=INET34=12558=text_1151=123410=191"
private const val MSG_NESTED_OPT_COMPONENTS_MISSED_ALL_FIELDS = "8=FIXT.1.19=5935=TEST_249=MZHOT056=INET34=125151=123410=191"
private const val MSG_NESTED_OPT_COMPONENTS_MISSED_ALL_FIELDS_INNER_AND_OUTER = "8=FIXT.1.19=5935=TEST_249=MZHOT056=INET34=12510=191"
private const val MSG_NESTED_OPT_COMPONENTS_MISSED_ALL_OUTER_FIELDS_AND_REQ_INNER_FIELD = "8=FIXT.1.19=5935=TEST_249=MZHOT056=INET34=12558=text_110=191"
}
}
22 changes: 22 additions & 0 deletions src/test/resources/dictionary.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7361,6 +7361,28 @@
<field name="MinQty" reference="field-MinQty"/>
<field name="trailer" reference="trailer" required="true"/>
</message>
<message name="NestedRequiredComponentTestMessage">
<attribute name="entity_type" type="java.lang.String">Message</attribute>
<attribute name="IsAdmin" type="java.lang.Boolean">false</attribute>
<attribute name="MessageType" type="java.lang.String">TEST_1</attribute>
<field name="OuterComponent" reference="component-OuterComponent" required="true"/>
</message>
<message name="NestedOptionalComponentTestMessage">
<attribute name="entity_type" type="java.lang.String">Message</attribute>
<attribute name="IsAdmin" type="java.lang.Boolean">false</attribute>
<attribute name="MessageType" type="java.lang.String">TEST_2</attribute>
<field name="OuterComponent" reference="component-OuterComponent"/>
</message>
<message id="component-OuterComponent" name="OuterComponent">
<attribute name="entity_type" type="java.lang.String">Component</attribute>
<field name="InnerComponent" reference="component-InnerComponent" required="true"/>
<field name="LeavesQty" reference="field-LeavesQty" required="true"/>
</message>
<message id="component-InnerComponent" name="InnerComponent">
<attribute name="entity_type" type="java.lang.String">Component</attribute>
<field name="Text" reference="field-Text"/>
<field name="OrdType" reference="field-OrdType" required="true"/>
</message>
<message id="component-TradingParty" name="TradingParty">
<attribute name="entity_type" type="java.lang.String">Component</attribute>
<field name="NoPartyIDs" reference="group-TradingParty_NoPartyIDs" isCollection="true" required="true"/>
Expand Down

0 comments on commit 3a7db1e

Please sign in to comment.