mmodzelewski commented on code in PR #2607: URL: https://github.com/apache/iggy/pull/2607#discussion_r2726807108
########## foreign/java/java-sdk/src/test/java/org/apache/iggy/client/async/tcp/IggyFrameDecoderTest.java: ########## @@ -0,0 +1,460 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.iggy.client.async.tcp; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.embedded.EmbeddedChannel; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Helper method to read inbound message and assert it's null. Review Comment: this comment seems unrelated to the test class itself ########## foreign/java/java-sdk/src/test/java/org/apache/iggy/client/blocking/tcp/BytesSerializerTest.java: ########## @@ -138,4 +156,551 @@ void shouldFailForValueLargerThanU128() { assertThatThrownBy(() -> BytesSerializer.toBytesAsU128(value)).isInstanceOf(IllegalArgumentException.class); } } + + @Nested + class StringSerialization { + + @Test + void shouldSerializeSimpleString() { + // given + String input = "test"; + + // when + ByteBuf result = BytesSerializer.toBytes(input); + + // then + assertThat(result.readByte()).isEqualTo((byte) 4); // length + byte[] stringBytes = new byte[4]; + result.readBytes(stringBytes); + assertThat(new String(stringBytes, StandardCharsets.UTF_8)).isEqualTo("test"); + } + + @Test + void shouldSerializeEmptyString() { + // given + String input = ""; + + // when + ByteBuf result = BytesSerializer.toBytes(input); + + // then + assertThat(result.readByte()).isEqualTo((byte) 0); // length = 0 + assertThat(result.readableBytes()).isEqualTo(0); + } + + @Test + void shouldSerializeUtf8Characters() { + // given + String input = "Hello世界"; + + // when + ByteBuf result = BytesSerializer.toBytes(input); + + // then + byte[] expectedBytes = input.getBytes(StandardCharsets.UTF_8); + // Note: serializer writes character count, not byte count as length prefix + assertThat(result.readByte()).isEqualTo((byte) input.length()); // 7 characters + byte[] stringBytes = new byte[expectedBytes.length]; + result.readBytes(stringBytes); + assertThat(stringBytes).isEqualTo(expectedBytes); // but writes 11 bytes + } + } + + @Nested + class IdentifierSerialization { + + @Test + void shouldSerializeNumericIdentifier() { + // given + var identifier = StreamId.of(123L); + + // when + ByteBuf result = BytesSerializer.toBytes(identifier); + + // then + assertThat(result.readByte()).isEqualTo((byte) 1); // kind = 1 (numeric) + assertThat(result.readByte()).isEqualTo((byte) 4); // length = 4 + assertThat(result.readIntLE()).isEqualTo(123); // id value + } + + @Test + void shouldSerializeStringIdentifier() { + // given + var identifier = StreamId.of("test-stream"); + + // when + ByteBuf result = BytesSerializer.toBytes(identifier); + + // then + assertThat(result.readByte()).isEqualTo((byte) 2); // kind = 2 (string) + assertThat(result.readByte()).isEqualTo((byte) 11); // length = "test-stream".length() + byte[] nameBytes = new byte[11]; + result.readBytes(nameBytes); + assertThat(new String(nameBytes)).isEqualTo("test-stream"); + } + + @Test + void shouldFailForUnknownIdentifierKind() { Review Comment: I think we can drop this one as there's no implementation ########## foreign/java/java-sdk/src/test/java/org/apache/iggy/client/blocking/tcp/BytesSerializerTest.java: ########## @@ -138,4 +156,551 @@ void shouldFailForValueLargerThanU128() { assertThatThrownBy(() -> BytesSerializer.toBytesAsU128(value)).isInstanceOf(IllegalArgumentException.class); } } + + @Nested + class StringSerialization { + + @Test + void shouldSerializeSimpleString() { + // given + String input = "test"; + + // when + ByteBuf result = BytesSerializer.toBytes(input); + + // then + assertThat(result.readByte()).isEqualTo((byte) 4); // length + byte[] stringBytes = new byte[4]; + result.readBytes(stringBytes); + assertThat(new String(stringBytes, StandardCharsets.UTF_8)).isEqualTo("test"); + } + + @Test + void shouldSerializeEmptyString() { + // given + String input = ""; + + // when + ByteBuf result = BytesSerializer.toBytes(input); + + // then + assertThat(result.readByte()).isEqualTo((byte) 0); // length = 0 + assertThat(result.readableBytes()).isEqualTo(0); + } + + @Test + void shouldSerializeUtf8Characters() { + // given + String input = "Hello世界"; + + // when + ByteBuf result = BytesSerializer.toBytes(input); + + // then + byte[] expectedBytes = input.getBytes(StandardCharsets.UTF_8); + // Note: serializer writes character count, not byte count as length prefix Review Comment: this looks like a bug then, it should be a number of bytes, can you update the serializer's code? ########## foreign/java/java-sdk/src/test/java/org/apache/iggy/client/async/tcp/IggyFrameDecoderTest.java: ########## @@ -0,0 +1,460 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.iggy.client.async.tcp; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.embedded.EmbeddedChannel; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Helper method to read inbound message and assert it's null. + * This avoids ambiguous method reference issues with assertThat. + */ +class IggyFrameDecoderTest { + + private EmbeddedChannel channel; + + @AfterEach + void tearDown() { + if (channel != null) { + // Release any remaining messages + Object msg; + while ((msg = channel.readInbound()) != null) { + if (msg instanceof ByteBuf) { + ((ByteBuf) msg).release(); + } + } + channel.finishAndReleaseAll(); + } + } + + @Nested + class CompleteFrames { + + @Test + void shouldDecodeCompleteFrameWithSmallPayload() { + // given + channel = new EmbeddedChannel(new IggyFrameDecoder()); + ByteBuf input = Unpooled.buffer(); + input.writeIntLE(0); // status = success + input.writeIntLE(5); // length = 5 bytes + input.writeBytes("hello".getBytes()); // payload + + // when + channel.writeInbound(input); + + // then + ByteBuf decoded = channel.readInbound(); + assertThat(decoded).isNotNull(); + assertThat(decoded.readableBytes()).isEqualTo(8 + 5); // header + payload + assertThat(decoded.readIntLE()).isEqualTo(0); // status + assertThat(decoded.readIntLE()).isEqualTo(5); // length + byte[] payload = new byte[5]; + decoded.readBytes(payload); + assertThat(new String(payload)).isEqualTo("hello"); + decoded.release(); + } + + @Test + void shouldDecodeCompleteFrameWithZeroLengthPayload() { + // given + channel = new EmbeddedChannel(new IggyFrameDecoder()); + ByteBuf input = Unpooled.buffer(); + input.writeIntLE(0); // status + input.writeIntLE(0); // length = 0 + + // when + channel.writeInbound(input); + + // then + ByteBuf decoded = channel.readInbound(); + assertThat(decoded).isNotNull(); + assertThat(decoded.readableBytes()).isEqualTo(8); // header only + assertThat(decoded.readIntLE()).isEqualTo(0); + assertThat(decoded.readIntLE()).isEqualTo(0); + decoded.release(); + } + + @Test + void shouldDecodeCompleteFrameWithLargePayload() { + // given + channel = new EmbeddedChannel(new IggyFrameDecoder()); + byte[] largePayload = new byte[10000]; + for (int i = 0; i < largePayload.length; i++) { + largePayload[i] = (byte) (i % 256); + } + + ByteBuf input = Unpooled.buffer(); + input.writeIntLE(200); // error status + input.writeIntLE(10000); // large length + input.writeBytes(largePayload); + + // when + channel.writeInbound(input); + + // then + ByteBuf decoded = channel.readInbound(); + assertThat(decoded).isNotNull(); + assertThat(decoded.readableBytes()).isEqualTo(8 + 10000); + assertThat(decoded.readIntLE()).isEqualTo(200); + assertThat(decoded.readIntLE()).isEqualTo(10000); + decoded.release(); + } + + @Test + void shouldDecodeFrameWithVariousStatusCodes() { + // given + channel = new EmbeddedChannel(new IggyFrameDecoder()); + + for (int status = 0; status <= 5; status++) { + ByteBuf input = Unpooled.buffer(); + input.writeIntLE(status); + input.writeIntLE(1); + input.writeByte(42); + + // when + channel.writeInbound(input); + + // then + ByteBuf decoded = channel.readInbound(); + assertThat(decoded).isNotNull(); + assertThat(decoded.readIntLE()).isEqualTo(status); + decoded.skipBytes(4); // length + decoded.skipBytes(1); // payload + decoded.release(); + } + } + } + + @Nested + class IncompleteFrames { + + @Test + void shouldWaitForCompleteHeaderWhenOnlyPartialHeaderAvailable() { + // given + channel = new EmbeddedChannel(new IggyFrameDecoder()); + ByteBuf input = Unpooled.buffer(); + input.writeIntLE(0); // only status, missing length (4 bytes total, need 8) + + // when + boolean hasMessage = channel.writeInbound(input); + + // then + assertThat(hasMessage).isFalse(); // No complete frame yet + ByteBuf decoded = channel.readInbound(); + assertThat(decoded).isNull(); // Nothing to read + } + + @Test + void shouldWaitForCompletePayloadWhenOnlyHeaderAvailable() { + // given + channel = new EmbeddedChannel(new IggyFrameDecoder()); + ByteBuf input = Unpooled.buffer(); + input.writeIntLE(0); // status + input.writeIntLE(100); // expects 100 bytes payload + // But no payload written + + // when + boolean hasMessage = channel.writeInbound(input); + + // then + assertThat(hasMessage).isFalse(); + ByteBuf decoded = channel.readInbound(); + assertThat(decoded).isNull(); + } + + @Test + void shouldWaitForCompletePayloadWhenPartialPayloadAvailable() { + // given + channel = new EmbeddedChannel(new IggyFrameDecoder()); + ByteBuf input = Unpooled.buffer(); + input.writeIntLE(0); + input.writeIntLE(100); // expects 100 bytes + input.writeBytes(new byte[50]); // only 50 bytes available + + // when + boolean hasMessage = channel.writeInbound(input); + + // then + assertThat(hasMessage).isFalse(); + assertThat((ByteBuf) channel.readInbound()).isNull(); + } + + @Test + void shouldEventuallyDecodeWhenMoreDataArrives() { + // given + channel = new EmbeddedChannel(new IggyFrameDecoder()); + ByteBuf firstChunk = Unpooled.buffer(); + firstChunk.writeIntLE(0); + firstChunk.writeIntLE(10); + firstChunk.writeBytes(new byte[5]); // only 5 of 10 bytes + + // when - first chunk + boolean hasMessage1 = channel.writeInbound(firstChunk); + assertThat(hasMessage1).isFalse(); + + // when - second chunk completes the frame + ByteBuf secondChunk = Unpooled.buffer(); + secondChunk.writeBytes(new byte[5]); // remaining 5 bytes + boolean hasMessage2 = channel.writeInbound(secondChunk); + + // then + assertThat(hasMessage2).isTrue(); + ByteBuf decoded = channel.readInbound(); + assertThat(decoded).isNotNull(); + assertThat(decoded.readableBytes()).isEqualTo(8 + 10); + decoded.release(); + } + } + + @Nested + class MultipleFrames { + + @Test + void shouldDecodeMultipleFramesInSequence() { + // given + channel = new EmbeddedChannel(new IggyFrameDecoder()); + ByteBuf input = Unpooled.buffer(); + + // Frame 1 + input.writeIntLE(0); + input.writeIntLE(3); + input.writeBytes("abc".getBytes()); + + // Frame 2 + input.writeIntLE(1); + input.writeIntLE(2); + input.writeBytes("de".getBytes()); + + // when + channel.writeInbound(input); + + // then - frame 1 + ByteBuf decoded1 = channel.readInbound(); + assertThat(decoded1).isNotNull(); + assertThat(decoded1.readableBytes()).isEqualTo(8 + 3); + decoded1.release(); + + // then - frame 2 + ByteBuf decoded2 = channel.readInbound(); + assertThat(decoded2).isNotNull(); + assertThat(decoded2.readableBytes()).isEqualTo(8 + 2); + decoded2.release(); + + // No more frames + assertThat((ByteBuf) channel.readInbound()).isNull(); + } + + @Test + void shouldDecodeThreeFramesCorrectly() { + // given + channel = new EmbeddedChannel(new IggyFrameDecoder()); + ByteBuf input = Unpooled.buffer(); + + for (int i = 0; i < 3; i++) { + input.writeIntLE(i); + input.writeIntLE(1); + input.writeByte(i); + } + + // when + channel.writeInbound(input); + + // then + for (int i = 0; i < 3; i++) { + ByteBuf decoded = channel.readInbound(); + assertThat(decoded).isNotNull(); + assertThat(decoded.readIntLE()).isEqualTo(i); + decoded.skipBytes(4 + 1); // skip length and payload + decoded.release(); + } + + assertThat((ByteBuf) channel.readInbound()).isNull(); + } + } + + @Nested + class EdgeCases { + + @Test + void shouldNotAdvanceReaderIndexWhenPayloadIncomplete() { + // given + channel = new EmbeddedChannel(new IggyFrameDecoder()); + ByteBuf input = Unpooled.buffer(); + int initialWriterIndex = input.writerIndex(); + input.writeIntLE(0); + input.writeIntLE(100); // expects 100 bytes + input.writeBytes(new byte[50]); // only 50 bytes + + int readerIndexBefore = input.readerIndex(); Review Comment: the `initialWriterIndex` and `readerIndexBefore` are unused, did you intend to add some assertions for these? ########## foreign/java/java-sdk/src/test/java/org/apache/iggy/client/blocking/tcp/BytesSerializerTest.java: ########## @@ -138,4 +156,551 @@ void shouldFailForValueLargerThanU128() { assertThatThrownBy(() -> BytesSerializer.toBytesAsU128(value)).isInstanceOf(IllegalArgumentException.class); } } + + @Nested + class StringSerialization { + + @Test + void shouldSerializeSimpleString() { + // given + String input = "test"; + + // when + ByteBuf result = BytesSerializer.toBytes(input); + + // then + assertThat(result.readByte()).isEqualTo((byte) 4); // length + byte[] stringBytes = new byte[4]; + result.readBytes(stringBytes); + assertThat(new String(stringBytes, StandardCharsets.UTF_8)).isEqualTo("test"); + } + + @Test + void shouldSerializeEmptyString() { + // given + String input = ""; + + // when + ByteBuf result = BytesSerializer.toBytes(input); + + // then + assertThat(result.readByte()).isEqualTo((byte) 0); // length = 0 + assertThat(result.readableBytes()).isEqualTo(0); + } + + @Test + void shouldSerializeUtf8Characters() { + // given + String input = "Hello世界"; + + // when + ByteBuf result = BytesSerializer.toBytes(input); + + // then + byte[] expectedBytes = input.getBytes(StandardCharsets.UTF_8); + // Note: serializer writes character count, not byte count as length prefix + assertThat(result.readByte()).isEqualTo((byte) input.length()); // 7 characters + byte[] stringBytes = new byte[expectedBytes.length]; + result.readBytes(stringBytes); + assertThat(stringBytes).isEqualTo(expectedBytes); // but writes 11 bytes + } + } + + @Nested + class IdentifierSerialization { + + @Test + void shouldSerializeNumericIdentifier() { + // given + var identifier = StreamId.of(123L); + + // when + ByteBuf result = BytesSerializer.toBytes(identifier); + + // then + assertThat(result.readByte()).isEqualTo((byte) 1); // kind = 1 (numeric) + assertThat(result.readByte()).isEqualTo((byte) 4); // length = 4 + assertThat(result.readIntLE()).isEqualTo(123); // id value + } + + @Test + void shouldSerializeStringIdentifier() { + // given + var identifier = StreamId.of("test-stream"); + + // when + ByteBuf result = BytesSerializer.toBytes(identifier); + + // then + assertThat(result.readByte()).isEqualTo((byte) 2); // kind = 2 (string) + assertThat(result.readByte()).isEqualTo((byte) 11); // length = "test-stream".length() + byte[] nameBytes = new byte[11]; + result.readBytes(nameBytes); + assertThat(new String(nameBytes)).isEqualTo("test-stream"); + } + + @Test + void shouldFailForUnknownIdentifierKind() { + // given - create an identifier with invalid kind via reflection would be complex + // This is actually tested implicitly by the Identifier class constraints + // Testing the serializer with valid inputs is sufficient + } + } + + @Nested + class ConsumerSerialization { + + @Test + void shouldSerializeConsumerWithNumericId() { + // given + var consumer = Consumer.of(42L); + + // when + ByteBuf result = BytesSerializer.toBytes(consumer); + + // then + assertThat(result.readByte()).isEqualTo((byte) 1); // Consumer kind + assertThat(result.readByte()).isEqualTo((byte) 1); // Identifier kind (numeric) + assertThat(result.readByte()).isEqualTo((byte) 4); // Identifier length + assertThat(result.readIntLE()).isEqualTo(42); // ID value + } + + @Test + void shouldSerializeConsumerGroupWithNumericId() { + // given + var consumer = Consumer.group(99L); + + // when + ByteBuf result = BytesSerializer.toBytes(consumer); + + // then + assertThat(result.readByte()).isEqualTo((byte) 2); // ConsumerGroup kind + assertThat(result.readByte()).isEqualTo((byte) 1); // Identifier kind (numeric) + assertThat(result.readByte()).isEqualTo((byte) 4); // Identifier length + assertThat(result.readIntLE()).isEqualTo(99); // ID value + } + + @Test + void shouldSerializeConsumerWithStringId() { + // given + var consumer = Consumer.of(ConsumerId.of("my-consumer")); + + // when + ByteBuf result = BytesSerializer.toBytes(consumer); + + // then + assertThat(result.readByte()).isEqualTo((byte) 1); // Consumer kind + assertThat(result.readByte()).isEqualTo((byte) 2); // Identifier kind (string) + assertThat(result.readByte()).isEqualTo((byte) 11); // "my-consumer".length() + } + } + + @Nested + class PartitioningSerialization { + + @Test + void shouldSerializeBalancedPartitioning() { + // given + var partitioning = Partitioning.balanced(); + + // when + ByteBuf result = BytesSerializer.toBytes(partitioning); + + // then + assertThat(result.readByte()).isEqualTo((byte) 1); // Balanced kind + assertThat(result.readByte()).isEqualTo((byte) 0); // Empty value + } + + @Test + void shouldSerializePartitionIdPartitioning() { + // given + var partitioning = Partitioning.partitionId(5L); + + // when + ByteBuf result = BytesSerializer.toBytes(partitioning); + + // then + assertThat(result.readByte()).isEqualTo((byte) 2); // PartitionId kind + assertThat(result.readByte()).isEqualTo((byte) 4); // 4 bytes for int + assertThat(result.readIntLE()).isEqualTo(5); // partition ID + } + + @Test + void shouldSerializeMessagesKeyPartitioning() { + // given + var partitioning = Partitioning.messagesKey("user-123"); + + // when + ByteBuf result = BytesSerializer.toBytes(partitioning); + + // then + assertThat(result.readByte()).isEqualTo((byte) 3); // MessagesKey kind + assertThat(result.readByte()).isEqualTo((byte) 8); // "user-123".length() + byte[] keyBytes = new byte[8]; + result.readBytes(keyBytes); + assertThat(new String(keyBytes)).isEqualTo("user-123"); + } + } + + @Nested + class PollingStrategySerialization { + + @Test + void shouldSerializeFirstStrategy() { + // given + var strategy = PollingStrategy.first(); + + // when + ByteBuf result = BytesSerializer.toBytes(strategy); + + // then + assertThat(result.readByte()).isEqualTo((byte) 3); // First kind = 3 + // Followed by 8 bytes of U64 (zero value) + assertThat(result.readableBytes()).isEqualTo(8); + } + + @Test + void shouldSerializeLastStrategy() { + // given + var strategy = PollingStrategy.last(); + + // when + ByteBuf result = BytesSerializer.toBytes(strategy); + + // then + assertThat(result.readByte()).isEqualTo((byte) 4); // Last kind = 4 + assertThat(result.readableBytes()).isEqualTo(8); + } + + @Test + void shouldSerializeOffsetStrategy() { + // given + var strategy = PollingStrategy.offset(BigInteger.valueOf(100)); + + // when + ByteBuf result = BytesSerializer.toBytes(strategy); + + // then + assertThat(result.readByte()).isEqualTo((byte) 1); // Offset kind = 1 + assertThat(result.readableBytes()).isEqualTo(8); + } + + @Test + void shouldSerializeNextStrategy() { + // given + var strategy = PollingStrategy.next(); + + // when + ByteBuf result = BytesSerializer.toBytes(strategy); + + // then + assertThat(result.readByte()).isEqualTo((byte) 5); // Next kind = 5 + assertThat(result.readableBytes()).isEqualTo(8); + } + + @Test + void shouldSerializeTimestampStrategy() { + // given + var strategy = PollingStrategy.timestamp(BigInteger.valueOf(1234567890)); + + // when + ByteBuf result = BytesSerializer.toBytes(strategy); + + // then + assertThat(result.readByte()).isEqualTo((byte) 2); // Timestamp kind = 2 + assertThat(result.readableBytes()).isEqualTo(8); + } + } + + @Nested + class OptionalValueSerialization { + + @Test + void shouldSerializePresentOptional() { + // given + Optional<Long> optional = Optional.of(42L); + + // when + ByteBuf result = BytesSerializer.toBytes(optional); + + // then + assertThat(result.readByte()).isEqualTo((byte) 1); // present flag + assertThat(result.readIntLE()).isEqualTo(42); // value + } + + @Test + void shouldSerializeEmptyOptional() { + // given + Optional<Long> optional = Optional.empty(); + + // when + ByteBuf result = BytesSerializer.toBytes(optional); + + // then + assertThat(result.readByte()).isEqualTo((byte) 0); // not present flag + assertThat(result.readIntLE()).isEqualTo(0); // zero value + } + } + + @Nested + class HeadersSerialization { + + @Test + void shouldSerializeEmptyHeaders() { + // given + Map<String, HeaderValue> headers = new HashMap<>(); + + // when + ByteBuf result = BytesSerializer.toBytes(headers); + + // then + assertThat(result.readableBytes()).isEqualTo(0); // Empty buffer + } + + @Test + void shouldSerializeSingleHeader() { + // given + Map<String, HeaderValue> headers = new HashMap<>(); + headers.put("key1", new HeaderValue(HeaderKind.Raw, "value1")); + + // when + ByteBuf result = BytesSerializer.toBytes(headers); + + // then + assertThat(result.readIntLE()).isEqualTo(4); // "key1".length() + byte[] keyBytes = new byte[4]; + result.readBytes(keyBytes); + assertThat(new String(keyBytes)).isEqualTo("key1"); + assertThat(result.readByte()).isEqualTo((byte) 1); // Raw kind + assertThat(result.readIntLE()).isEqualTo(6); // "value1".length() + byte[] valueBytes = new byte[6]; + result.readBytes(valueBytes); + assertThat(new String(valueBytes)).isEqualTo("value1"); + } + + @Test + void shouldSerializeMultipleHeaders() { + // given + Map<String, HeaderValue> headers = new HashMap<>(); + headers.put("k1", new HeaderValue(HeaderKind.Raw, "v1")); + headers.put("k2", new HeaderValue(HeaderKind.String, "v2")); + + // when + ByteBuf result = BytesSerializer.toBytes(headers); + + // then - verify buffer contains data for both headers + assertThat(result.readableBytes()).isGreaterThan(0); Review Comment: we could check for a specific number of bytes that should be in the output -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
