From 4e3417c52d60b6ff19cfb5766ff6055d9c054f3d Mon Sep 17 00:00:00 2001
From: Pedro Sakuma Travi <39205549+pedrosakuma@users.noreply.github.com>
Date: Sat, 25 Apr 2026 18:45:48 +0000
Subject: [PATCH] test: foreach enumerator coverage for mixed group + top-level
varData (#156)
Follow-up to #156. Adds 3 integration tests using NewOrderData (simple Legs
group + top-level ClientOrderId varData) to cover the previously untested
mixed scenario: messages where the foreach API IS emitted (groups are simple)
but ReadGroups remains the only way to consume top-level varData.
- foreach iterates Legs correctly
- ReadGroups still reaches the varData after foreach (independent state)
- Empty groups + varData round-trip works
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../GroupForeachWithVarDataTests.cs | 95 +++++++++++++++++++
1 file changed, 95 insertions(+)
create mode 100644 tests/SbeCodeGenerator.IntegrationTests/GroupForeachWithVarDataTests.cs
diff --git a/tests/SbeCodeGenerator.IntegrationTests/GroupForeachWithVarDataTests.cs b/tests/SbeCodeGenerator.IntegrationTests/GroupForeachWithVarDataTests.cs
new file mode 100644
index 0000000..c5f0140
--- /dev/null
+++ b/tests/SbeCodeGenerator.IntegrationTests/GroupForeachWithVarDataTests.cs
@@ -0,0 +1,95 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using Xunit;
+using NO = Cross.Schema.Orders.V0.NewOrderData;
+
+namespace SbeCodeGenerator.IntegrationTests
+{
+ ///
+ /// Issue #156 follow-up: coverage for the mixed scenario — message with
+ /// simple top-level groups AND top-level varData. The foreach API must be
+ /// emitted for the groups (since they're simple), independent of the varData
+ /// that follows them. ReadGroups continues to work for both.
+ ///
+ public class GroupForeachWithVarDataTests
+ {
+ private static byte[] Encode(NO order, NO.LegsData[] legs, string clientOrderId)
+ {
+ var buffer = new byte[1024];
+ var coid = Encoding.UTF8.GetBytes(clientOrderId);
+ Assert.True(NO.TryEncode(order, buffer, legs, coid, out int written));
+ Array.Resize(ref buffer, written);
+ return buffer;
+ }
+
+ [Fact]
+ public void Foreach_OnSimpleGroup_WhenMessageAlsoHasTopLevelVarData()
+ {
+ var order = new NO { OrderId = 42, Quantity = 100, Side = Cross.Schema.Orders.V0.Side.Buy };
+ var legs = new[]
+ {
+ new NO.LegsData { LegSymbol = 1, LegRatio = 1, LegSide = Cross.Schema.Orders.V0.Side.Buy },
+ new NO.LegsData { LegSymbol = 2, LegRatio = 2, LegSide = Cross.Schema.Orders.V0.Side.Sell },
+ new NO.LegsData { LegSymbol = 3, LegRatio = 3, LegSide = Cross.Schema.Orders.V0.Side.Buy },
+ };
+ var buffer = Encode(order, legs, "CLIENT-ORDER-XYZ");
+
+ Assert.True(NO.TryParse(buffer, out var reader));
+
+ var symbols = new List();
+ foreach (ref readonly var leg in reader.Legs)
+ symbols.Add(leg.LegSymbol);
+
+ Assert.Equal(new uint[] { 1, 2, 3 }, symbols);
+ }
+
+ [Fact]
+ public void Foreach_DoesNotConsumeVarData_ReadGroupsStillWorks()
+ {
+ // After foreach iterating Legs, the top-level ClientOrderId varData
+ // must still be reachable via ReadGroups (which scans from _blockLength
+ // through its own SpanReader — independent of the foreach state).
+ var order = new NO { OrderId = 7 };
+ var legs = new[] { new NO.LegsData { LegSymbol = 99, LegRatio = 1 } };
+ var buffer = Encode(order, legs, "PERSISTED-COID");
+
+ Assert.True(NO.TryParse(buffer, out var reader));
+
+ int legsViaForeach = 0;
+ foreach (ref readonly var _ in reader.Legs) legsViaForeach++;
+
+ int legsViaReadGroups = 0;
+ string? receivedCoid = null;
+ reader.ReadGroups(
+ (in NO.LegsData _) => legsViaReadGroups++,
+ clientOrderId =>
+ {
+ receivedCoid = Encoding.UTF8.GetString(clientOrderId.VarData);
+ });
+
+ Assert.Equal(1, legsViaForeach);
+ Assert.Equal(1, legsViaReadGroups);
+ Assert.Equal("PERSISTED-COID", receivedCoid);
+ }
+
+ [Fact]
+ public void Foreach_EmptyGroup_WithVarDataStillReadable()
+ {
+ var order = new NO { OrderId = 1 };
+ var buffer = Encode(order, Array.Empty(), "EMPTY-LEGS-OK");
+
+ Assert.True(NO.TryParse(buffer, out var reader));
+
+ int count = 0;
+ foreach (ref readonly var _ in reader.Legs) count++;
+ Assert.Equal(0, count);
+
+ string? coid = null;
+ reader.ReadGroups(
+ (in NO.LegsData _) => { },
+ cid => coid = Encoding.UTF8.GetString(cid.VarData));
+ Assert.Equal("EMPTY-LEGS-OK", coid);
+ }
+ }
+}