diff --git a/foreign/go/internal/command/access_token_test.go b/foreign/go/internal/command/access_token_test.go new file mode 100644 index 0000000000..06b03f6666 --- /dev/null +++ b/foreign/go/internal/command/access_token_test.go @@ -0,0 +1,225 @@ +// 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 command + +import ( + "bytes" + "testing" +) + +// TestSerialize_GetPersonalAccessTokens tests serialization of GetPersonalAccessTokens command +func TestSerialize_GetPersonalAccessTokens(t *testing.T) { + cmd := GetPersonalAccessTokens{} + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetPersonalAccessTokens: %v", err) + } + + expected := []byte{} // Empty byte array + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_DeletePersonalAccessToken tests serialization with normal token name +func TestSerialize_DeletePersonalAccessToken(t *testing.T) { + cmd := DeletePersonalAccessToken{ + Name: "test_token", + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize DeletePersonalAccessToken: %v", err) + } + + expected := []byte{ + 0x0A, // Name length = 10 + 0x74, 0x65, 0x73, 0x74, 0x5F, 0x74, 0x6F, 0x6B, 0x65, 0x6E, // "test_token" + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_DeletePersonalAccessToken_SingleChar tests edge case with single character name +func TestSerialize_DeletePersonalAccessToken_SingleChar(t *testing.T) { + cmd := DeletePersonalAccessToken{ + Name: "a", + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize DeletePersonalAccessToken with single char: %v", err) + } + + expected := []byte{ + 0x01, // Name length = 1 + 0x61, // "a" + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_DeletePersonalAccessToken_EmptyName tests edge case with empty name +func TestSerialize_DeletePersonalAccessToken_EmptyName(t *testing.T) { + cmd := DeletePersonalAccessToken{ + Name: "", + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize DeletePersonalAccessToken with empty name: %v", err) + } + + expected := []byte{ + 0x00, // Name length = 0 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_CreatePersonalAccessToken tests serialization with normal values +func TestSerialize_CreatePersonalAccessToken(t *testing.T) { + cmd := CreatePersonalAccessToken{ + Name: "test", + Expiry: 3600, // 1 hour in seconds + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreatePersonalAccessToken: %v", err) + } + + expected := []byte{ + 0x04, // Name length = 4 + 0x74, 0x65, 0x73, 0x74, // "test" + 0x00, 0x00, 0x00, 0x00, // Expiry high bytes (protocol uses u64, Go writes u32 at end) + 0x10, 0x0E, 0x00, 0x00, // Expiry low bytes = 3600 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_CreatePersonalAccessToken_ZeroExpiry tests edge case with zero expiry +func TestSerialize_CreatePersonalAccessToken_ZeroExpiry(t *testing.T) { + cmd := CreatePersonalAccessToken{ + Name: "token", + Expiry: 0, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreatePersonalAccessToken with zero expiry: %v", err) + } + + expected := []byte{ + 0x05, // Name length = 5 + 0x74, 0x6F, 0x6B, 0x65, 0x6E, // "token" + 0x00, 0x00, 0x00, 0x00, // Expiry high bytes (protocol uses u64, Go writes u32 at end) + 0x00, 0x00, 0x00, 0x00, // Expiry = 0 (little endian uint32) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_CreatePersonalAccessToken_MaxExpiry tests edge case with maximum uint32 expiry +func TestSerialize_CreatePersonalAccessToken_MaxExpiry(t *testing.T) { + cmd := CreatePersonalAccessToken{ + Name: "long_token", + Expiry: 4294967295, // Max uint32 value + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreatePersonalAccessToken with max expiry: %v", err) + } + + expected := []byte{ + 0x0A, // Name length = 10 + 0x6C, 0x6F, 0x6E, 0x67, 0x5F, 0x74, 0x6F, 0x6B, 0x65, 0x6E, // "long_token" + 0x00, 0x00, 0x00, 0x00, // Expiry high bytes (protocol uses u64, Go writes u32 at end) + 0xFF, 0xFF, 0xFF, 0xFF, // Expiry = 4294967295 (little endian uint32) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_CreatePersonalAccessToken_EmptyName tests edge case with empty name +func TestSerialize_CreatePersonalAccessToken_EmptyName(t *testing.T) { + cmd := CreatePersonalAccessToken{ + Name: "", + Expiry: 1000, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreatePersonalAccessToken with empty name: %v", err) + } + + expected := []byte{ + 0x00, // Name length = 0 + 0x00, 0x00, 0x00, 0x00, // Expiry high bytes (protocol uses u64, Go writes u32 at end) + 0xE8, 0x03, 0x00, 0x00, // Expiry = 1000 (little endian uint32) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_CreatePersonalAccessToken_LongName tests with a longer token name +func TestSerialize_CreatePersonalAccessToken_LongName(t *testing.T) { + cmd := CreatePersonalAccessToken{ + Name: "my_very_long_personal_access_token_name", + Expiry: 86400, // 24 hours in seconds + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreatePersonalAccessToken with long name: %v", err) + } + + expected := []byte{ + 0x27, // Name length = 39 + // "my_very_long_personal_access_token_name" + 0x6D, 0x79, 0x5F, 0x76, 0x65, 0x72, 0x79, 0x5F, + 0x6C, 0x6F, 0x6E, 0x67, 0x5F, 0x70, 0x65, 0x72, + 0x73, 0x6F, 0x6E, 0x61, 0x6C, 0x5F, 0x61, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x5F, 0x74, 0x6F, 0x6B, + 0x65, 0x6E, 0x5F, 0x6E, 0x61, 0x6D, 0x65, + 0x00, 0x00, 0x00, 0x00, // Expiry high bytes (protocol uses u64, Go writes u32 at end) + 0x80, 0x51, 0x01, 0x00, // Expiry = 86400 (little endian uint32) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} diff --git a/foreign/go/internal/command/consumer_group_test.go b/foreign/go/internal/command/consumer_group_test.go new file mode 100644 index 0000000000..1aa96e94c0 --- /dev/null +++ b/foreign/go/internal/command/consumer_group_test.go @@ -0,0 +1,436 @@ +// 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 command + +import ( + "bytes" + "testing" + + iggcon "github.com/apache/iggy/foreign/go/contracts" +) + +// TestSerialize_CreateConsumerGroup_NumericIds tests CreateConsumerGroup with numeric identifiers +func TestSerialize_CreateConsumerGroup_NumericIds(t *testing.T) { + streamId, _ := iggcon.NewIdentifier(uint32(1)) + topicId, _ := iggcon.NewIdentifier(uint32(2)) + + cmd := CreateConsumerGroup{ + TopicPath: TopicPath{ + StreamId: streamId, + TopicId: topicId, + }, + Name: "group1", + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreateConsumerGroup: %v", err) + } + + expected := []byte{ + 0x01, // StreamId Kind = NumericId + 0x04, // StreamId Length = 4 + 0x01, 0x00, 0x00, 0x00, // StreamId Value = 1 + 0x01, // TopicId Kind = NumericId + 0x04, // TopicId Length = 4 + 0x02, 0x00, 0x00, 0x00, // TopicId Value = 2 + 0x06, // Name Length = 6 + 0x67, 0x72, 0x6F, 0x75, 0x70, 0x31, // "group1" + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_CreateConsumerGroup_StringIds tests CreateConsumerGroup with string identifiers +func TestSerialize_CreateConsumerGroup_StringIds(t *testing.T) { + streamId, _ := iggcon.NewIdentifier("my_stream") + topicId, _ := iggcon.NewIdentifier("my_topic") + + cmd := CreateConsumerGroup{ + TopicPath: TopicPath{ + StreamId: streamId, + TopicId: topicId, + }, + Name: "consumers", + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreateConsumerGroup: %v", err) + } + + expected := []byte{ + 0x02, // StreamId Kind = StringId + 0x09, // StreamId Length = 9 + 0x6D, 0x79, 0x5F, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6D, // "my_stream" + 0x02, // TopicId Kind = StringId + 0x08, // TopicId Length = 8 + 0x6D, 0x79, 0x5F, 0x74, 0x6F, 0x70, 0x69, 0x63, // "my_topic" + 0x09, // Name Length = 9 + 0x63, 0x6F, 0x6E, 0x73, 0x75, 0x6D, 0x65, 0x72, 0x73, // "consumers" + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_CreateConsumerGroup_EmptyName tests edge case with empty group name +func TestSerialize_CreateConsumerGroup_EmptyName(t *testing.T) { + streamId, _ := iggcon.NewIdentifier("stream") + topicId, _ := iggcon.NewIdentifier("topic") + + cmd := CreateConsumerGroup{ + TopicPath: TopicPath{ + StreamId: streamId, + TopicId: topicId, + }, + Name: "", + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreateConsumerGroup with empty name: %v", err) + } + + expected := []byte{ + 0x02, // StreamId Kind = StringId + 0x06, // StreamId Length = 6 + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6D, // "stream" + 0x02, // TopicId Kind = StringId + 0x05, // TopicId Length = 5 + 0x74, 0x6F, 0x70, 0x69, 0x63, // "topic" + 0x00, // Name Length = 0 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetConsumerGroups_NumericIds tests GetConsumerGroups with numeric identifiers +func TestSerialize_GetConsumerGroups_NumericIds(t *testing.T) { + streamId, _ := iggcon.NewIdentifier(uint32(10)) + topicId, _ := iggcon.NewIdentifier(uint32(20)) + + cmd := GetConsumerGroups{ + StreamId: streamId, + TopicId: topicId, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetConsumerGroups: %v", err) + } + + expected := []byte{ + 0x01, // StreamId Kind = NumericId + 0x04, // StreamId Length = 4 + 0x0A, 0x00, 0x00, 0x00, // StreamId Value = 10 + 0x01, // TopicId Kind = NumericId + 0x04, // TopicId Length = 4 + 0x14, 0x00, 0x00, 0x00, // TopicId Value = 20 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetConsumerGroups_StringIds tests GetConsumerGroups with string identifiers +func TestSerialize_GetConsumerGroups_StringIds(t *testing.T) { + streamId, _ := iggcon.NewIdentifier("events") + topicId, _ := iggcon.NewIdentifier("logs") + + cmd := GetConsumerGroups{ + StreamId: streamId, + TopicId: topicId, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetConsumerGroups: %v", err) + } + + expected := []byte{ + 0x02, // StreamId Kind = StringId + 0x06, // StreamId Length = 6 + 0x65, 0x76, 0x65, 0x6E, 0x74, 0x73, // "events" + 0x02, // TopicId Kind = StringId + 0x04, // TopicId Length = 4 + 0x6C, 0x6F, 0x67, 0x73, // "logs" + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetConsumerGroup_NumericIds tests GetConsumerGroup with all numeric identifiers +func TestSerialize_GetConsumerGroup_NumericIds(t *testing.T) { + streamId, _ := iggcon.NewIdentifier(uint32(100)) + topicId, _ := iggcon.NewIdentifier(uint32(200)) + groupId, _ := iggcon.NewIdentifier(uint32(300)) + + cmd := GetConsumerGroup{ + TopicPath: TopicPath{ + StreamId: streamId, + TopicId: topicId, + }, + GroupId: groupId, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetConsumerGroup: %v", err) + } + + expected := []byte{ + 0x01, // StreamId Kind = NumericId + 0x04, // StreamId Length = 4 + 0x64, 0x00, 0x00, 0x00, // StreamId Value = 100 + 0x01, // TopicId Kind = NumericId + 0x04, // TopicId Length = 4 + 0xC8, 0x00, 0x00, 0x00, // TopicId Value = 200 + 0x01, // GroupId Kind = NumericId + 0x04, // GroupId Length = 4 + 0x2C, 0x01, 0x00, 0x00, // GroupId Value = 300 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetConsumerGroup_StringIds tests GetConsumerGroup with all string identifiers +func TestSerialize_GetConsumerGroup_StringIds(t *testing.T) { + streamId, _ := iggcon.NewIdentifier("stream1") + topicId, _ := iggcon.NewIdentifier("topic1") + groupId, _ := iggcon.NewIdentifier("group1") + + cmd := GetConsumerGroup{ + TopicPath: TopicPath{ + StreamId: streamId, + TopicId: topicId, + }, + GroupId: groupId, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetConsumerGroup: %v", err) + } + + expected := []byte{ + 0x02, // StreamId Kind = StringId + 0x07, // StreamId Length = 7 + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x31, // "stream1" + 0x02, // TopicId Kind = StringId + 0x06, // TopicId Length = 6 + 0x74, 0x6F, 0x70, 0x69, 0x63, 0x31, // "topic1" + 0x02, // GroupId Kind = StringId + 0x06, // GroupId Length = 6 + 0x67, 0x72, 0x6F, 0x75, 0x70, 0x31, // "group1" + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetConsumerGroup_MixedIds tests GetConsumerGroup with mixed identifier types +func TestSerialize_GetConsumerGroup_MixedIds(t *testing.T) { + streamId, _ := iggcon.NewIdentifier(uint32(42)) + topicId, _ := iggcon.NewIdentifier("events") + groupId, _ := iggcon.NewIdentifier(uint32(999)) + + cmd := GetConsumerGroup{ + TopicPath: TopicPath{ + StreamId: streamId, + TopicId: topicId, + }, + GroupId: groupId, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetConsumerGroup with mixed IDs: %v", err) + } + + expected := []byte{ + 0x01, // StreamId Kind = NumericId + 0x04, // StreamId Length = 4 + 0x2A, 0x00, 0x00, 0x00, // StreamId Value = 42 + 0x02, // TopicId Kind = StringId + 0x06, // TopicId Length = 6 + 0x65, 0x76, 0x65, 0x6E, 0x74, 0x73, // "events" + 0x01, // GroupId Kind = NumericId + 0x04, // GroupId Length = 4 + 0xE7, 0x03, 0x00, 0x00, // GroupId Value = 999 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_JoinConsumerGroup tests JoinConsumerGroup serialization +func TestSerialize_JoinConsumerGroup(t *testing.T) { + streamId, _ := iggcon.NewIdentifier("prod") + topicId, _ := iggcon.NewIdentifier("orders") + groupId, _ := iggcon.NewIdentifier(uint32(5)) + + cmd := JoinConsumerGroup{ + TopicPath: TopicPath{ + StreamId: streamId, + TopicId: topicId, + }, + GroupId: groupId, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize JoinConsumerGroup: %v", err) + } + + expected := []byte{ + 0x02, // StreamId Kind = StringId + 0x04, // StreamId Length = 4 + 0x70, 0x72, 0x6F, 0x64, // "prod" + 0x02, // TopicId Kind = StringId + 0x06, // TopicId Length = 6 + 0x6F, 0x72, 0x64, 0x65, 0x72, 0x73, // "orders" + 0x01, // GroupId Kind = NumericId + 0x04, // GroupId Length = 4 + 0x05, 0x00, 0x00, 0x00, // GroupId Value = 5 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_LeaveConsumerGroup tests LeaveConsumerGroup serialization +func TestSerialize_LeaveConsumerGroup(t *testing.T) { + streamId, _ := iggcon.NewIdentifier(uint32(7)) + topicId, _ := iggcon.NewIdentifier(uint32(8)) + groupId, _ := iggcon.NewIdentifier("my_group") + + cmd := LeaveConsumerGroup{ + TopicPath: TopicPath{ + StreamId: streamId, + TopicId: topicId, + }, + GroupId: groupId, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize LeaveConsumerGroup: %v", err) + } + + expected := []byte{ + 0x01, // StreamId Kind = NumericId + 0x04, // StreamId Length = 4 + 0x07, 0x00, 0x00, 0x00, // StreamId Value = 7 + 0x01, // TopicId Kind = NumericId + 0x04, // TopicId Length = 4 + 0x08, 0x00, 0x00, 0x00, // TopicId Value = 8 + 0x02, // GroupId Kind = StringId + 0x08, // GroupId Length = 8 + 0x6D, 0x79, 0x5F, 0x67, 0x72, 0x6F, 0x75, 0x70, // "my_group" + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_DeleteConsumerGroup_NumericIds tests DeleteConsumerGroup with numeric identifiers +func TestSerialize_DeleteConsumerGroup_NumericIds(t *testing.T) { + streamId, _ := iggcon.NewIdentifier(uint32(11)) + topicId, _ := iggcon.NewIdentifier(uint32(22)) + groupId, _ := iggcon.NewIdentifier(uint32(33)) + + cmd := DeleteConsumerGroup{ + TopicPath: TopicPath{ + StreamId: streamId, + TopicId: topicId, + }, + GroupId: groupId, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize DeleteConsumerGroup: %v", err) + } + + expected := []byte{ + 0x01, // StreamId Kind = NumericId + 0x04, // StreamId Length = 4 + 0x0B, 0x00, 0x00, 0x00, // StreamId Value = 11 + 0x01, // TopicId Kind = NumericId + 0x04, // TopicId Length = 4 + 0x16, 0x00, 0x00, 0x00, // TopicId Value = 22 + 0x01, // GroupId Kind = NumericId + 0x04, // GroupId Length = 4 + 0x21, 0x00, 0x00, 0x00, // GroupId Value = 33 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_DeleteConsumerGroup_StringIds tests DeleteConsumerGroup with string identifiers +func TestSerialize_DeleteConsumerGroup_StringIds(t *testing.T) { + streamId, _ := iggcon.NewIdentifier("analytics") + topicId, _ := iggcon.NewIdentifier("metrics") + groupId, _ := iggcon.NewIdentifier("deprecated") + + cmd := DeleteConsumerGroup{ + TopicPath: TopicPath{ + StreamId: streamId, + TopicId: topicId, + }, + GroupId: groupId, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize DeleteConsumerGroup: %v", err) + } + + expected := []byte{ + 0x02, // StreamId Kind = StringId + 0x09, // StreamId Length = 9 + 0x61, 0x6E, 0x61, 0x6C, 0x79, 0x74, 0x69, 0x63, 0x73, // "analytics" + 0x02, // TopicId Kind = StringId + 0x07, // TopicId Length = 7 + 0x6D, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, // "metrics" + 0x02, // GroupId Kind = StringId + 0x0A, // GroupId Length = 10 + 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, // "deprecated" + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} diff --git a/foreign/go/internal/command/offset_test.go b/foreign/go/internal/command/offset_test.go new file mode 100644 index 0000000000..f26d1b881f --- /dev/null +++ b/foreign/go/internal/command/offset_test.go @@ -0,0 +1,419 @@ +// 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 command + +import ( + "bytes" + "testing" + + iggcon "github.com/apache/iggy/foreign/go/contracts" +) + +// Helper function to create pointer to uint32 +func uint32Ptr(v uint32) *uint32 { + return &v +} + +// TestSerialize_StoreConsumerOffsetRequest_WithPartition tests StoreConsumerOffset with partition specified +func TestSerialize_StoreConsumerOffsetRequest_WithPartition(t *testing.T) { + consumerId, _ := iggcon.NewIdentifier(uint32(42)) + streamId, _ := iggcon.NewIdentifier("stream1") + topicId, _ := iggcon.NewIdentifier(uint32(10)) + partitionId := uint32Ptr(5) + + cmd := StoreConsumerOffsetRequest{ + Consumer: iggcon.NewSingleConsumer(consumerId), + StreamId: streamId, + TopicId: topicId, + PartitionId: partitionId, + Offset: 1000, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize StoreConsumerOffsetRequest: %v", err) + } + + expected := []byte{ + 0x01, // Consumer Kind = Single + 0x01, // ConsumerId Kind = NumericId + 0x04, // ConsumerId Length = 4 + 0x2A, 0x00, 0x00, 0x00, // ConsumerId Value = 42 + 0x02, // StreamId Kind = StringId + 0x07, // StreamId Length = 7 + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6D, 0x31, // "stream1" + 0x01, // TopicId Kind = NumericId + 0x04, // TopicId Length = 4 + 0x0A, 0x00, 0x00, 0x00, // TopicId Value = 10 + 0x01, // hasPartition = 1 + 0x05, 0x00, 0x00, 0x00, // PartitionId = 5 + 0xE8, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Offset = 1000 (uint64) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_StoreConsumerOffsetRequest_WithoutPartition tests StoreConsumerOffset without partition +func TestSerialize_StoreConsumerOffsetRequest_WithoutPartition(t *testing.T) { + consumerId, _ := iggcon.NewIdentifier("consumer1") + streamId, _ := iggcon.NewIdentifier(uint32(1)) + topicId, _ := iggcon.NewIdentifier(uint32(2)) + + cmd := StoreConsumerOffsetRequest{ + Consumer: iggcon.NewGroupConsumer(consumerId), + StreamId: streamId, + TopicId: topicId, + PartitionId: nil, // No partition specified + Offset: 5000, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize StoreConsumerOffsetRequest without partition: %v", err) + } + + expected := []byte{ + 0x02, // Consumer Kind = Group + 0x02, // ConsumerId Kind = StringId + 0x09, // ConsumerId Length = 9 + 0x63, 0x6F, 0x6E, 0x73, 0x75, 0x6D, 0x65, 0x72, 0x31, // "consumer1" + 0x01, // StreamId Kind = NumericId + 0x04, // StreamId Length = 4 + 0x01, 0x00, 0x00, 0x00, // StreamId Value = 1 + 0x01, // TopicId Kind = NumericId + 0x04, // TopicId Length = 4 + 0x02, 0x00, 0x00, 0x00, // TopicId Value = 2 + 0x00, // hasPartition = 0 + 0x00, 0x00, 0x00, 0x00, // PartitionId = 0 (default when not set) + 0x88, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Offset = 5000 (uint64) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_StoreConsumerOffsetRequest_ZeroOffset tests with zero offset +func TestSerialize_StoreConsumerOffsetRequest_ZeroOffset(t *testing.T) { + consumerId, _ := iggcon.NewIdentifier(uint32(1)) + streamId, _ := iggcon.NewIdentifier("s") + topicId, _ := iggcon.NewIdentifier("t") + + cmd := StoreConsumerOffsetRequest{ + Consumer: iggcon.NewSingleConsumer(consumerId), + StreamId: streamId, + TopicId: topicId, + PartitionId: uint32Ptr(0), + Offset: 0, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize StoreConsumerOffsetRequest with zero offset: %v", err) + } + + expected := []byte{ + 0x01, // Consumer Kind = Single + 0x01, // ConsumerId Kind = NumericId + 0x04, // ConsumerId Length = 4 + 0x01, 0x00, 0x00, 0x00, // ConsumerId Value = 1 + 0x02, // StreamId Kind = StringId + 0x01, // StreamId Length = 1 + 0x73, // "s" + 0x02, // TopicId Kind = StringId + 0x01, // TopicId Length = 1 + 0x74, // "t" + 0x01, // hasPartition = 1 + 0x00, 0x00, 0x00, 0x00, // PartitionId = 0 + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Offset = 0 (uint64) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_StoreConsumerOffsetRequest_MaxOffset tests with maximum uint64 offset +func TestSerialize_StoreConsumerOffsetRequest_MaxOffset(t *testing.T) { + consumerId, _ := iggcon.NewIdentifier(uint32(999)) + streamId, _ := iggcon.NewIdentifier(uint32(100)) + topicId, _ := iggcon.NewIdentifier(uint32(200)) + + cmd := StoreConsumerOffsetRequest{ + Consumer: iggcon.NewSingleConsumer(consumerId), + StreamId: streamId, + TopicId: topicId, + PartitionId: uint32Ptr(10), + Offset: 18446744073709551615, // Max uint64 + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize StoreConsumerOffsetRequest with max offset: %v", err) + } + + expected := []byte{ + 0x01, // Consumer Kind = Single + 0x01, // ConsumerId Kind = NumericId + 0x04, // ConsumerId Length = 4 + 0xE7, 0x03, 0x00, 0x00, // ConsumerId Value = 999 + 0x01, // StreamId Kind = NumericId + 0x04, // StreamId Length = 4 + 0x64, 0x00, 0x00, 0x00, // StreamId Value = 100 + 0x01, // TopicId Kind = NumericId + 0x04, // TopicId Length = 4 + 0xC8, 0x00, 0x00, 0x00, // TopicId Value = 200 + 0x01, // hasPartition = 1 + 0x0A, 0x00, 0x00, 0x00, // PartitionId = 10 + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, // Offset = max uint64 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetConsumerOffset_WithPartition tests GetConsumerOffset with partition +func TestSerialize_GetConsumerOffset_WithPartition(t *testing.T) { + consumerId, _ := iggcon.NewIdentifier("grp1") + streamId, _ := iggcon.NewIdentifier("events") + topicId, _ := iggcon.NewIdentifier(uint32(5)) + + cmd := GetConsumerOffset{ + Consumer: iggcon.NewGroupConsumer(consumerId), + StreamId: streamId, + TopicId: topicId, + PartitionId: uint32Ptr(3), + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetConsumerOffset: %v", err) + } + + expected := []byte{ + 0x02, // Consumer Kind = Group + 0x02, // ConsumerId Kind = StringId + 0x04, // ConsumerId Length = 4 + 0x67, 0x72, 0x70, 0x31, // "grp1" + 0x02, // StreamId Kind = StringId + 0x06, // StreamId Length = 6 + 0x65, 0x76, 0x65, 0x6E, 0x74, 0x73, // "events" + 0x01, // TopicId Kind = NumericId + 0x04, // TopicId Length = 4 + 0x05, 0x00, 0x00, 0x00, // TopicId Value = 5 + 0x01, // hasPartition = 1 + 0x03, 0x00, 0x00, 0x00, // PartitionId = 3 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetConsumerOffset_WithoutPartition tests GetConsumerOffset without partition +func TestSerialize_GetConsumerOffset_WithoutPartition(t *testing.T) { + consumerId, _ := iggcon.NewIdentifier(uint32(123)) + streamId, _ := iggcon.NewIdentifier(uint32(1)) + topicId, _ := iggcon.NewIdentifier(uint32(2)) + + cmd := GetConsumerOffset{ + Consumer: iggcon.NewSingleConsumer(consumerId), + StreamId: streamId, + TopicId: topicId, + PartitionId: nil, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetConsumerOffset without partition: %v", err) + } + + expected := []byte{ + 0x01, // Consumer Kind = Single + 0x01, // ConsumerId Kind = NumericId + 0x04, // ConsumerId Length = 4 + 0x7B, 0x00, 0x00, 0x00, // ConsumerId Value = 123 + 0x01, // StreamId Kind = NumericId + 0x04, // StreamId Length = 4 + 0x01, 0x00, 0x00, 0x00, // StreamId Value = 1 + 0x01, // TopicId Kind = NumericId + 0x04, // TopicId Length = 4 + 0x02, 0x00, 0x00, 0x00, // TopicId Value = 2 + 0x00, // hasPartition = 0 + 0x00, 0x00, 0x00, 0x00, // PartitionId = 0 (default) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetConsumerOffset_MixedIds tests with mixed identifier types +func TestSerialize_GetConsumerOffset_MixedIds(t *testing.T) { + consumerId, _ := iggcon.NewIdentifier(uint32(42)) + streamId, _ := iggcon.NewIdentifier("prod") + topicId, _ := iggcon.NewIdentifier("logs") + + cmd := GetConsumerOffset{ + Consumer: iggcon.NewSingleConsumer(consumerId), + StreamId: streamId, + TopicId: topicId, + PartitionId: uint32Ptr(7), + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetConsumerOffset with mixed IDs: %v", err) + } + + expected := []byte{ + 0x01, // Consumer Kind = Single + 0x01, // ConsumerId Kind = NumericId + 0x04, // ConsumerId Length = 4 + 0x2A, 0x00, 0x00, 0x00, // ConsumerId Value = 42 + 0x02, // StreamId Kind = StringId + 0x04, // StreamId Length = 4 + 0x70, 0x72, 0x6F, 0x64, // "prod" + 0x02, // TopicId Kind = StringId + 0x04, // TopicId Length = 4 + 0x6C, 0x6F, 0x67, 0x73, // "logs" + 0x01, // hasPartition = 1 + 0x07, 0x00, 0x00, 0x00, // PartitionId = 7 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_DeleteConsumerOffset_WithPartition tests DeleteConsumerOffset with partition +func TestSerialize_DeleteConsumerOffset_WithPartition(t *testing.T) { + consumerId, _ := iggcon.NewIdentifier("consumer_grp") + streamId, _ := iggcon.NewIdentifier(uint32(10)) + topicId, _ := iggcon.NewIdentifier(uint32(20)) + + cmd := DeleteConsumerOffset{ + Consumer: iggcon.NewGroupConsumer(consumerId), + StreamId: streamId, + TopicId: topicId, + PartitionId: uint32Ptr(15), + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize DeleteConsumerOffset: %v", err) + } + + expected := []byte{ + 0x02, // Consumer Kind = Group + 0x02, // ConsumerId Kind = StringId + 0x0C, // ConsumerId Length = 12 + 0x63, 0x6F, 0x6E, 0x73, 0x75, 0x6D, 0x65, 0x72, 0x5F, 0x67, 0x72, 0x70, // "consumer_grp" + 0x01, // StreamId Kind = NumericId + 0x04, // StreamId Length = 4 + 0x0A, 0x00, 0x00, 0x00, // StreamId Value = 10 + 0x01, // TopicId Kind = NumericId + 0x04, // TopicId Length = 4 + 0x14, 0x00, 0x00, 0x00, // TopicId Value = 20 + 0x01, // hasPartition = 1 + 0x0F, 0x00, 0x00, 0x00, // PartitionId = 15 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_DeleteConsumerOffset_WithoutPartition tests DeleteConsumerOffset without partition +func TestSerialize_DeleteConsumerOffset_WithoutPartition(t *testing.T) { + consumerId, _ := iggcon.NewIdentifier(uint32(7)) + streamId, _ := iggcon.NewIdentifier("analytics") + topicId, _ := iggcon.NewIdentifier("metrics") + + cmd := DeleteConsumerOffset{ + Consumer: iggcon.NewSingleConsumer(consumerId), + StreamId: streamId, + TopicId: topicId, + PartitionId: nil, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize DeleteConsumerOffset without partition: %v", err) + } + + expected := []byte{ + 0x01, // Consumer Kind = Single + 0x01, // ConsumerId Kind = NumericId + 0x04, // ConsumerId Length = 4 + 0x07, 0x00, 0x00, 0x00, // ConsumerId Value = 7 + 0x02, // StreamId Kind = StringId + 0x09, // StreamId Length = 9 + 0x61, 0x6E, 0x61, 0x6C, 0x79, 0x74, 0x69, 0x63, 0x73, // "analytics" + 0x02, // TopicId Kind = StringId + 0x07, // TopicId Length = 7 + 0x6D, 0x65, 0x74, 0x72, 0x69, 0x63, 0x73, // "metrics" + 0x00, // hasPartition = 0 + 0x00, 0x00, 0x00, 0x00, // PartitionId = 0 (default) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_DeleteConsumerOffset_MaxPartition tests with maximum partition ID +func TestSerialize_DeleteConsumerOffset_MaxPartition(t *testing.T) { + consumerId, _ := iggcon.NewIdentifier(uint32(1)) + streamId, _ := iggcon.NewIdentifier(uint32(2)) + topicId, _ := iggcon.NewIdentifier(uint32(3)) + + cmd := DeleteConsumerOffset{ + Consumer: iggcon.NewSingleConsumer(consumerId), + StreamId: streamId, + TopicId: topicId, + PartitionId: uint32Ptr(4294967295), // Max uint32 + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize DeleteConsumerOffset with max partition: %v", err) + } + + expected := []byte{ + 0x01, // Consumer Kind = Single + 0x01, // ConsumerId Kind = NumericId + 0x04, // ConsumerId Length = 4 + 0x01, 0x00, 0x00, 0x00, // ConsumerId Value = 1 + 0x01, // StreamId Kind = NumericId + 0x04, // StreamId Length = 4 + 0x02, 0x00, 0x00, 0x00, // StreamId Value = 2 + 0x01, // TopicId Kind = NumericId + 0x04, // TopicId Length = 4 + 0x03, 0x00, 0x00, 0x00, // TopicId Value = 3 + 0x01, // hasPartition = 1 + 0xFF, 0xFF, 0xFF, 0xFF, // PartitionId = 4294967295 (max uint32) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} diff --git a/foreign/go/internal/command/partition_test.go b/foreign/go/internal/command/partition_test.go new file mode 100644 index 0000000000..60d66bbb52 --- /dev/null +++ b/foreign/go/internal/command/partition_test.go @@ -0,0 +1,242 @@ +// 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 command + +import ( + "bytes" + "testing" + + iggcon "github.com/apache/iggy/foreign/go/contracts" +) + +// TestSerialize_CreatePartitions_NumericIds tests serialization with numeric identifiers +func TestSerialize_CreatePartitions_NumericIds(t *testing.T) { + streamId, _ := iggcon.NewIdentifier(uint32(123)) + topicId, _ := iggcon.NewIdentifier(uint32(456)) + + cmd := CreatePartitions{ + StreamId: streamId, + TopicId: topicId, + PartitionsCount: 10, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreatePartitions: %v", err) + } + + expected := []byte{ + 0x01, // StreamId Kind = NumericId + 0x04, // StreamId Length = 4 + 0x7B, 0x00, 0x00, 0x00, // StreamId Value = 123 (little endian) + 0x01, // TopicId Kind = NumericId + 0x04, // TopicId Length = 4 + 0xC8, 0x01, 0x00, 0x00, // TopicId Value = 456 (little endian) + 0x0A, 0x00, 0x00, 0x00, // PartitionsCount = 10 (little endian) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_CreatePartitions_StringIds tests serialization with string identifiers +func TestSerialize_CreatePartitions_StringIds(t *testing.T) { + streamId, _ := iggcon.NewIdentifier("my_stream") + topicId, _ := iggcon.NewIdentifier("my_topic") + + cmd := CreatePartitions{ + StreamId: streamId, + TopicId: topicId, + PartitionsCount: 5, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreatePartitions: %v", err) + } + + expected := []byte{ + 0x02, // StreamId Kind = StringId + 0x09, // StreamId Length = 9 + 0x6D, 0x79, 0x5F, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6D, // "my_stream" + 0x02, // TopicId Kind = StringId + 0x08, // TopicId Length = 8 + 0x6D, 0x79, 0x5F, 0x74, 0x6F, 0x70, 0x69, 0x63, // "my_topic" + 0x05, 0x00, 0x00, 0x00, // PartitionsCount = 5 (little endian) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_CreatePartitions_MixedIds tests serialization with mixed identifier types +func TestSerialize_CreatePartitions_MixedIds(t *testing.T) { + streamId, _ := iggcon.NewIdentifier(uint32(42)) + topicId, _ := iggcon.NewIdentifier("test") + + cmd := CreatePartitions{ + StreamId: streamId, + TopicId: topicId, + PartitionsCount: 100, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreatePartitions with mixed IDs: %v", err) + } + + expected := []byte{ + 0x01, // StreamId Kind = NumericId + 0x04, // StreamId Length = 4 + 0x2A, 0x00, 0x00, 0x00, // StreamId Value = 42 (little endian) + 0x02, // TopicId Kind = StringId + 0x04, // TopicId Length = 4 + 0x74, 0x65, 0x73, 0x74, // "test" + 0x64, 0x00, 0x00, 0x00, // PartitionsCount = 100 (little endian) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_CreatePartitions_ZeroCount tests edge case with zero partitions count +func TestSerialize_CreatePartitions_ZeroCount(t *testing.T) { + streamId, _ := iggcon.NewIdentifier("stream") + topicId, _ := iggcon.NewIdentifier("topic") + + cmd := CreatePartitions{ + StreamId: streamId, + TopicId: topicId, + PartitionsCount: 0, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreatePartitions with zero count: %v", err) + } + + expected := []byte{ + 0x02, // StreamId Kind = StringId + 0x06, // StreamId Length = 6 + 0x73, 0x74, 0x72, 0x65, 0x61, 0x6D, // "stream" + 0x02, // TopicId Kind = StringId + 0x05, // TopicId Length = 5 + 0x74, 0x6F, 0x70, 0x69, 0x63, // "topic" + 0x00, 0x00, 0x00, 0x00, // PartitionsCount = 0 (little endian) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_DeletePartitions_NumericIds tests DeletePartitions with numeric identifiers +func TestSerialize_DeletePartitions_NumericIds(t *testing.T) { + streamId, _ := iggcon.NewIdentifier(uint32(1)) + topicId, _ := iggcon.NewIdentifier(uint32(2)) + + cmd := DeletePartitions{ + StreamId: streamId, + TopicId: topicId, + PartitionsCount: 3, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize DeletePartitions: %v", err) + } + + expected := []byte{ + 0x01, // StreamId Kind = NumericId + 0x04, // StreamId Length = 4 + 0x01, 0x00, 0x00, 0x00, // StreamId Value = 1 (little endian) + 0x01, // TopicId Kind = NumericId + 0x04, // TopicId Length = 4 + 0x02, 0x00, 0x00, 0x00, // TopicId Value = 2 (little endian) + 0x03, 0x00, 0x00, 0x00, // PartitionsCount = 3 (little endian) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_DeletePartitions_StringIds tests DeletePartitions with string identifiers +func TestSerialize_DeletePartitions_StringIds(t *testing.T) { + streamId, _ := iggcon.NewIdentifier("prod_stream") + topicId, _ := iggcon.NewIdentifier("events") + + cmd := DeletePartitions{ + StreamId: streamId, + TopicId: topicId, + PartitionsCount: 2, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize DeletePartitions: %v", err) + } + + expected := []byte{ + 0x02, // StreamId Kind = StringId + 0x0B, // StreamId Length = 11 + 0x70, 0x72, 0x6F, 0x64, 0x5F, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6D, // "prod_stream" + 0x02, // TopicId Kind = StringId + 0x06, // TopicId Length = 6 + 0x65, 0x76, 0x65, 0x6E, 0x74, 0x73, // "events" + 0x02, 0x00, 0x00, 0x00, // PartitionsCount = 2 (little endian) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_DeletePartitions_MaxCount tests edge case with maximum uint32 partitions count +func TestSerialize_DeletePartitions_MaxCount(t *testing.T) { + streamId, _ := iggcon.NewIdentifier(uint32(999)) + topicId, _ := iggcon.NewIdentifier(uint32(888)) + + cmd := DeletePartitions{ + StreamId: streamId, + TopicId: topicId, + PartitionsCount: 4294967295, // Max uint32 value + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize DeletePartitions with max count: %v", err) + } + + expected := []byte{ + 0x01, // StreamId Kind = NumericId + 0x04, // StreamId Length = 4 + 0xE7, 0x03, 0x00, 0x00, // StreamId Value = 999 (little endian) + 0x01, // TopicId Kind = NumericId + 0x04, // TopicId Length = 4 + 0x78, 0x03, 0x00, 0x00, // TopicId Value = 888 (little endian) + 0xFF, 0xFF, 0xFF, 0xFF, // PartitionsCount = 4294967295 (little endian) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} diff --git a/foreign/go/internal/command/session_test.go b/foreign/go/internal/command/session_test.go index 6646232294..2105e3bd6e 100644 --- a/foreign/go/internal/command/session_test.go +++ b/foreign/go/internal/command/session_test.go @@ -18,12 +18,48 @@ package command import ( + "bytes" "encoding/binary" "testing" iggcon "github.com/apache/iggy/foreign/go/contracts" ) +// buildExpectedLoginUser constructs the expected byte representation for a LoginUser command. +// Format: [username_len(1)][username][password_len(1)][password][version_len(4)][version][context_len(4)][context] +func buildExpectedLoginUser(username, password string) []byte { + versionBytes := []byte(iggcon.Version) + contextBytes := []byte("") + + totalLength := 1 + len(username) + 1 + len(password) + + 4 + len(versionBytes) + 4 + len(contextBytes) + + buf := make([]byte, totalLength) + pos := 0 + + buf[pos] = byte(len(username)) + pos++ + copy(buf[pos:], username) + pos += len(username) + + buf[pos] = byte(len(password)) + pos++ + copy(buf[pos:], password) + pos += len(password) + + binary.LittleEndian.PutUint32(buf[pos:], uint32(len(versionBytes))) + pos += 4 + copy(buf[pos:], versionBytes) + pos += len(versionBytes) + + binary.LittleEndian.PutUint32(buf[pos:], uint32(len(contextBytes))) + pos += 4 + copy(buf[pos:], contextBytes) + + return buf +} + +// TestSerialize_LoginUser_ContainsVersion verifies that the SDK version is included in login serialization. func TestSerialize_LoginUser_ContainsVersion(t *testing.T) { request := LoginUser{ Username: "iggy", @@ -49,3 +85,188 @@ func TestSerialize_LoginUser_ContainsVersion(t *testing.T) { t.Errorf("Version mismatch. Expected: %q, Got: %q", iggcon.Version, version) } } + +// TestSerialize_LoginUser tests normal login with username and password +func TestSerialize_LoginUser(t *testing.T) { + cmd := LoginUser{ + Username: "admin", + Password: "secret123", + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize LoginUser: %v", err) + } + + expected := buildExpectedLoginUser("admin", "secret123") + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_LoginUser_EmptyCredentials tests edge case with empty username and password +func TestSerialize_LoginUser_EmptyCredentials(t *testing.T) { + cmd := LoginUser{ + Username: "", + Password: "", + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize LoginUser with empty credentials: %v", err) + } + + expected := buildExpectedLoginUser("", "") + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_LoginUser_LongCredentials tests with longer username and password +func TestSerialize_LoginUser_LongCredentials(t *testing.T) { + cmd := LoginUser{ + Username: "user@example.com", + Password: "very_secure_password_123!", + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize LoginUser with long credentials: %v", err) + } + + expected := buildExpectedLoginUser("user@example.com", "very_secure_password_123!") + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_LoginUser_SingleCharCredentials tests edge case with single character credentials +func TestSerialize_LoginUser_SingleCharCredentials(t *testing.T) { + cmd := LoginUser{ + Username: "a", + Password: "b", + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize LoginUser with single char credentials: %v", err) + } + + expected := buildExpectedLoginUser("a", "b") + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_LoginWithPersonalAccessToken tests login with token +func TestSerialize_LoginWithPersonalAccessToken(t *testing.T) { + cmd := LoginWithPersonalAccessToken{ + Token: "my_access_token_12345", + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize LoginWithPersonalAccessToken: %v", err) + } + + expected := []byte{ + 0x15, // Token length = 21 + // "my_access_token_12345" + 0x6D, 0x79, 0x5F, 0x61, 0x63, 0x63, 0x65, 0x73, + 0x73, 0x5F, 0x74, 0x6F, 0x6B, 0x65, 0x6E, 0x5F, + 0x31, 0x32, 0x33, 0x34, 0x35, + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_LoginWithPersonalAccessToken_ShortToken tests with short token +func TestSerialize_LoginWithPersonalAccessToken_ShortToken(t *testing.T) { + cmd := LoginWithPersonalAccessToken{ + Token: "abc", + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize LoginWithPersonalAccessToken with short token: %v", err) + } + + expected := []byte{ + 0x03, // Token length = 3 + 0x61, 0x62, 0x63, // "abc" + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_LoginWithPersonalAccessToken_EmptyToken tests edge case with empty token +func TestSerialize_LoginWithPersonalAccessToken_EmptyToken(t *testing.T) { + cmd := LoginWithPersonalAccessToken{ + Token: "", + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize LoginWithPersonalAccessToken with empty token: %v", err) + } + + expected := []byte{ + 0x00, // Token length = 0 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_LoginWithPersonalAccessToken_LongToken tests with longer token +func TestSerialize_LoginWithPersonalAccessToken_LongToken(t *testing.T) { + cmd := LoginWithPersonalAccessToken{ + Token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ", + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize LoginWithPersonalAccessToken with long token: %v", err) + } + + expected := []byte{ + 0x6F, // Token length = 111 + // "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ" + 0x65, 0x79, 0x4A, 0x68, 0x62, 0x47, 0x63, 0x69, 0x4F, 0x69, 0x4A, 0x49, 0x55, 0x7A, 0x49, 0x31, + 0x4E, 0x69, 0x49, 0x73, 0x49, 0x6E, 0x52, 0x35, 0x63, 0x43, 0x49, 0x36, 0x49, 0x6B, 0x70, 0x58, + 0x56, 0x43, 0x4A, 0x39, 0x2E, 0x65, 0x79, 0x4A, 0x7A, 0x64, 0x57, 0x49, 0x69, 0x4F, 0x69, 0x49, + 0x78, 0x4D, 0x6A, 0x4D, 0x30, 0x4E, 0x54, 0x59, 0x33, 0x4F, 0x44, 0x6B, 0x77, 0x49, 0x69, 0x77, + 0x69, 0x62, 0x6D, 0x46, 0x74, 0x5A, 0x53, 0x49, 0x36, 0x49, 0x6B, 0x70, 0x76, 0x61, 0x47, 0x34, + 0x67, 0x52, 0x47, 0x39, 0x6C, 0x49, 0x69, 0x77, 0x69, 0x61, 0x57, 0x46, 0x30, 0x49, 0x6A, 0x6F, + 0x78, 0x4E, 0x54, 0x45, 0x32, 0x4D, 0x6A, 0x4D, 0x35, 0x4D, 0x44, 0x49, 0x79, 0x66, 0x51, + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_LogoutUser tests LogoutUser serialization +func TestSerialize_LogoutUser(t *testing.T) { + cmd := LogoutUser{} + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize LogoutUser: %v", err) + } + + expected := []byte{} // Empty byte array + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} diff --git a/foreign/go/internal/command/system_test.go b/foreign/go/internal/command/system_test.go new file mode 100644 index 0000000000..547ed1a109 --- /dev/null +++ b/foreign/go/internal/command/system_test.go @@ -0,0 +1,167 @@ +// 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 command + +import ( + "bytes" + "testing" +) + +// TestSerialize_Ping tests serialization of Ping command +func TestSerialize_Ping(t *testing.T) { + cmd := Ping{} + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize Ping: %v", err) + } + + expected := []byte{} // Empty byte array + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetStats tests serialization of GetStats command +func TestSerialize_GetStats(t *testing.T) { + cmd := GetStats{} + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetStats: %v", err) + } + + expected := []byte{} // Empty byte array + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetClients tests serialization of GetClients command +func TestSerialize_GetClients(t *testing.T) { + cmd := GetClients{} + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetClients: %v", err) + } + + expected := []byte{} // Empty byte array + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetClusterMetadata tests serialization of GetClusterMetadata command +func TestSerialize_GetClusterMetadata(t *testing.T) { + cmd := GetClusterMetadata{} + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetClusterMetadata: %v", err) + } + + expected := []byte{} // Empty byte array + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetClient tests serialization of GetClient command with normal value +func TestSerialize_GetClient(t *testing.T) { + cmd := GetClient{ + ClientID: 42, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetClient: %v", err) + } + + expected := []byte{ + 0x2A, 0x00, 0x00, 0x00, // ClientID = 42 (little endian uint32) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetClient_Zero tests serialization with zero ClientID (edge case) +func TestSerialize_GetClient_Zero(t *testing.T) { + cmd := GetClient{ + ClientID: 0, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetClient with zero ClientID: %v", err) + } + + expected := []byte{ + 0x00, 0x00, 0x00, 0x00, // ClientID = 0 (little endian uint32) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetClient_MaxValue tests serialization with maximum uint32 value (edge case) +func TestSerialize_GetClient_MaxValue(t *testing.T) { + cmd := GetClient{ + ClientID: 4294967295, // Max uint32 value + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetClient with max ClientID: %v", err) + } + + expected := []byte{ + 0xFF, 0xFF, 0xFF, 0xFF, // ClientID = 4294967295 (little endian uint32) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetClient_LargeValue tests serialization with a large ClientID value +func TestSerialize_GetClient_LargeValue(t *testing.T) { + cmd := GetClient{ + ClientID: 16909060, // 0x01020304 in hex + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetClient with large ClientID: %v", err) + } + + expected := []byte{ + 0x04, 0x03, 0x02, 0x01, // ClientID = 16909060 (little endian: 0x01020304) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} diff --git a/foreign/go/internal/command/update_user.go b/foreign/go/internal/command/update_user.go index a20d647fb7..e83c23209e 100644 --- a/foreign/go/internal/command/update_user.go +++ b/foreign/go/internal/command/update_user.go @@ -34,7 +34,7 @@ func (u *UpdateUser) MarshalBinary() ([]byte, error) { if err != nil { return nil, err } - length := len(userIdBytes) + length := len(userIdBytes) + 2 if u.Username == nil { u.Username = new(string) @@ -43,14 +43,14 @@ func (u *UpdateUser) MarshalBinary() ([]byte, error) { username := *u.Username if len(username) != 0 { - length += 2 + len(username) + length += 1 + len(username) } if u.Status != nil { - length += 2 + length += 1 } - bytes := make([]byte, length+1) + bytes := make([]byte, length) position := 0 copy(bytes[position:position+len(userIdBytes)], userIdBytes) diff --git a/foreign/go/internal/command/update_user_test.go b/foreign/go/internal/command/update_user_test.go new file mode 100644 index 0000000000..71cbbc6a0d --- /dev/null +++ b/foreign/go/internal/command/update_user_test.go @@ -0,0 +1,248 @@ +// 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 command + +import ( + "bytes" + "testing" + + iggcon "github.com/apache/iggy/foreign/go/contracts" +) + +// Helper functions for creating pointers +func stringPtr(s string) *string { + return &s +} + +func userStatusPtr(s iggcon.UserStatus) *iggcon.UserStatus { + return &s +} + +// TestSerialize_UpdateUser_BothFields tests UpdateUser with both username and status +func TestSerialize_UpdateUser_BothFields(t *testing.T) { + userId, _ := iggcon.NewIdentifier(uint32(42)) + username := "new_admin" + status := iggcon.Active + + cmd := UpdateUser{ + UserID: userId, + Username: &username, + Status: &status, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize UpdateUser: %v", err) + } + + expected := []byte{ + 0x01, // UserId Kind = NumericId + 0x04, // UserId Length = 4 + 0x2A, 0x00, 0x00, 0x00, // UserId Value = 42 + 0x01, // Has username = 1 + 0x09, // Username length = 9 + 0x6E, 0x65, 0x77, 0x5F, 0x61, 0x64, 0x6D, 0x69, 0x6E, // "new_admin" + 0x01, // Has status = 1 + 0x01, // Status = Active (1) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_UpdateUser_OnlyUsername tests UpdateUser with only username +func TestSerialize_UpdateUser_OnlyUsername(t *testing.T) { + userId, _ := iggcon.NewIdentifier("user123") + + cmd := UpdateUser{ + UserID: userId, + Username: stringPtr("updated_name"), + Status: nil, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize UpdateUser with only username: %v", err) + } + + expected := []byte{ + 0x02, // UserId Kind = StringId + 0x07, // UserId Length = 7 + 0x75, 0x73, 0x65, 0x72, 0x31, 0x32, 0x33, // "user123" + 0x01, // Has username = 1 + 0x0C, // Username length = 12 + 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x64, 0x5F, 0x6E, 0x61, 0x6D, 0x65, // "updated_name" + 0x00, // Has status = 0 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_UpdateUser_OnlyStatus tests UpdateUser with only status +func TestSerialize_UpdateUser_OnlyStatus(t *testing.T) { + userId, _ := iggcon.NewIdentifier(uint32(1)) + + cmd := UpdateUser{ + UserID: userId, + Username: nil, + Status: userStatusPtr(iggcon.Inactive), + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize UpdateUser with only status: %v", err) + } + + expected := []byte{ + 0x01, // UserId Kind = NumericId + 0x04, // UserId Length = 4 + 0x01, 0x00, 0x00, 0x00, // UserId Value = 1 + 0x00, // Has username = 0 + 0x01, // Has status = 1 + 0x02, // Status = Inactive (2) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_UpdateUser_NeitherField tests UpdateUser with no updates (both nil) +func TestSerialize_UpdateUser_NeitherField(t *testing.T) { + userId, _ := iggcon.NewIdentifier("admin") + + cmd := UpdateUser{ + UserID: userId, + Username: nil, + Status: nil, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize UpdateUser with no fields: %v", err) + } + + expected := []byte{ + 0x02, // UserId Kind = StringId + 0x05, // UserId Length = 5 + 0x61, 0x64, 0x6D, 0x69, 0x6E, // "admin" + 0x00, // Has username = 0 + 0x00, // Has status = 0 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_UpdateUser_EmptyUsername tests UpdateUser with empty username string +func TestSerialize_UpdateUser_EmptyUsername(t *testing.T) { + userId, _ := iggcon.NewIdentifier(uint32(99)) + + cmd := UpdateUser{ + UserID: userId, + Username: stringPtr(""), + Status: userStatusPtr(iggcon.Active), + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize UpdateUser with empty username: %v", err) + } + + expected := []byte{ + 0x01, // UserId Kind = NumericId + 0x04, // UserId Length = 4 + 0x63, 0x00, 0x00, 0x00, // UserId Value = 99 + 0x00, // Has username = 0 (empty string treated as no username) + 0x01, // Has status = 1 + 0x01, // Status = Active (1) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_UpdateUser_SingleCharUsername tests with single character username +func TestSerialize_UpdateUser_SingleCharUsername(t *testing.T) { + userId, _ := iggcon.NewIdentifier(uint32(5)) + + cmd := UpdateUser{ + UserID: userId, + Username: stringPtr("a"), + Status: userStatusPtr(iggcon.Inactive), + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize UpdateUser with single char username: %v", err) + } + + expected := []byte{ + 0x01, // UserId Kind = NumericId + 0x04, // UserId Length = 4 + 0x05, 0x00, 0x00, 0x00, // UserId Value = 5 + 0x01, // Has username = 1 + 0x01, // Username length = 1 + 0x61, // "a" + 0x01, // Has status = 1 + 0x02, // Status = Inactive (2) + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_UpdateUser_LongUsername tests with a longer username +func TestSerialize_UpdateUser_LongUsername(t *testing.T) { + userId, _ := iggcon.NewIdentifier("test") + + cmd := UpdateUser{ + UserID: userId, + Username: stringPtr("very_long_username_for_testing"), + Status: nil, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize UpdateUser with long username: %v", err) + } + + expected := []byte{ + 0x02, // UserId Kind = StringId + 0x04, // UserId Length = 4 + 0x74, 0x65, 0x73, 0x74, // "test" + 0x01, // Has username = 1 + 0x1E, // Username length = 30 + // "very_long_username_for_testing" + 0x76, 0x65, 0x72, 0x79, 0x5F, 0x6C, 0x6F, 0x6E, + 0x67, 0x5F, 0x75, 0x73, 0x65, 0x72, 0x6E, 0x61, + 0x6D, 0x65, 0x5F, 0x66, 0x6F, 0x72, 0x5F, 0x74, + 0x65, 0x73, 0x74, 0x69, 0x6E, 0x67, + 0x00, // Has status = 0 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} diff --git a/foreign/go/internal/command/user.go b/foreign/go/internal/command/user.go index 628140a43a..9896ca4697 100644 --- a/foreign/go/internal/command/user.go +++ b/foreign/go/internal/command/user.go @@ -35,9 +35,9 @@ func (c *CreateUser) Code() Code { } func (c *CreateUser) MarshalBinary() ([]byte, error) { - capacity := 4 + len(c.Username) + len(c.Password) + capacity := 1 + len(c.Username) + 1 + len(c.Password) + 1 + 1 if c.Permissions != nil { - capacity += 1 + 4 + c.Permissions.Size() + capacity += 4 + c.Permissions.Size() } bytes := make([]byte, capacity) @@ -116,10 +116,10 @@ func (u *UpdatePermissions) MarshalBinary() ([]byte, error) { if err != nil { return nil, err } - length := len(userIdBytes) + length := len(userIdBytes) + 1 if u.Permissions != nil { - length += 1 + 4 + u.Permissions.Size() + length += 4 + u.Permissions.Size() } bytes := make([]byte, length) diff --git a/foreign/go/internal/command/user_test.go b/foreign/go/internal/command/user_test.go new file mode 100644 index 0000000000..02be036691 --- /dev/null +++ b/foreign/go/internal/command/user_test.go @@ -0,0 +1,439 @@ +// 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 command + +import ( + "bytes" + "testing" + + iggcon "github.com/apache/iggy/foreign/go/contracts" +) + +// Helper to create test permissions with only global permissions +func createTestGlobalPermissions(all bool) iggcon.GlobalPermissions { + return iggcon.GlobalPermissions{ + ManageServers: all, + ReadServers: all, + ManageUsers: all, + ReadUsers: all, + ManageStreams: all, + ReadStreams: all, + ManageTopics: all, + ReadTopics: all, + PollMessages: all, + SendMessages: all, + } +} + +func createTestPermissions(global iggcon.GlobalPermissions) *iggcon.Permissions { + return &iggcon.Permissions{ + Global: global, + Streams: nil, // Simple case: no stream-specific permissions + } +} + +// TestSerialize_CreateUser_WithPermissions_ActiveStatus tests CreateUser with permissions and Active status +func TestSerialize_CreateUser_WithPermissions_ActiveStatus(t *testing.T) { + globalPerms := createTestGlobalPermissions(true) + permissions := createTestPermissions(globalPerms) + + cmd := CreateUser{ + Username: "admin", + Password: "secret", + Status: iggcon.Active, + Permissions: permissions, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreateUser: %v", err) + } + + expected := []byte{ + 0x05, // Username length = 5 + 0x61, 0x64, 0x6D, 0x69, 0x6E, // "admin" + 0x06, // Password length = 6 + 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, // "secret" + 0x01, // Status = Active (1) + 0x01, // Has permissions = 1 + 0x0B, 0x00, 0x00, 0x00, // Permissions length = 11 + // Global permissions (10 bytes) - all true + 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, + 0x00, // No stream-specific permissions + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_CreateUser_WithoutPermissions tests CreateUser without permissions +func TestSerialize_CreateUser_WithoutPermissions(t *testing.T) { + cmd := CreateUser{ + Username: "user1", + Password: "pass123", + Status: iggcon.Active, + Permissions: nil, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreateUser without permissions: %v", err) + } + + expected := []byte{ + 0x05, // Username length = 5 + 0x75, 0x73, 0x65, 0x72, 0x31, // "user1" + 0x07, // Password length = 7 + 0x70, 0x61, 0x73, 0x73, 0x31, 0x32, 0x33, // "pass123" + 0x01, // Status = Active (1) + 0x00, // Has permissions = 0 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_CreateUser_InactiveStatus tests CreateUser with Inactive status +func TestSerialize_CreateUser_InactiveStatus(t *testing.T) { + cmd := CreateUser{ + Username: "inactive_user", + Password: "pwd", + Status: iggcon.Inactive, + Permissions: nil, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreateUser with inactive status: %v", err) + } + + expected := []byte{ + 0x0D, // Username length = 13 + 0x69, 0x6E, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5F, 0x75, 0x73, 0x65, 0x72, // "inactive_user" + 0x03, // Password length = 3 + 0x70, 0x77, 0x64, // "pwd" + 0x02, // Status = Inactive (2) + 0x00, // Has permissions = 0 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_CreateUser_EmptyCredentials tests edge case with empty username/password +func TestSerialize_CreateUser_EmptyCredentials(t *testing.T) { + cmd := CreateUser{ + Username: "", + Password: "", + Status: iggcon.Active, + Permissions: nil, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreateUser with empty credentials: %v", err) + } + + expected := []byte{ + 0x00, // Username length = 0 + 0x00, // Password length = 0 + 0x01, // Status = Active (1) + 0x00, // Has permissions = 0 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_CreateUser_PartialPermissions tests with partial global permissions +func TestSerialize_CreateUser_PartialPermissions(t *testing.T) { + globalPerms := iggcon.GlobalPermissions{ + ManageServers: true, + ReadServers: true, + ManageUsers: false, + ReadUsers: true, + ManageStreams: false, + ReadStreams: true, + ManageTopics: false, + ReadTopics: true, + PollMessages: true, + SendMessages: false, + } + permissions := createTestPermissions(globalPerms) + + cmd := CreateUser{ + Username: "user", + Password: "pass", + Status: iggcon.Active, + Permissions: permissions, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreateUser with partial permissions: %v", err) + } + + expected := []byte{ + 0x04, // Username length = 4 + 0x75, 0x73, 0x65, 0x72, // "user" + 0x04, // Password length = 4 + 0x70, 0x61, 0x73, 0x73, // "pass" + 0x01, // Status = Active (1) + 0x01, // Has permissions = 1 + 0x0B, 0x00, 0x00, 0x00, // Permissions length = 11 + // Global permissions: 1,1,0,1,0,1,0,1,1,0 + 0x01, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x01, 0x00, + 0x00, // No stream-specific permissions + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetUsers tests GetUsers serialization (empty) +func TestSerialize_GetUsers(t *testing.T) { + cmd := GetUsers{} + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetUsers: %v", err) + } + + expected := []byte{} // Empty byte array + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetUser_NumericId tests GetUser with numeric identifier +func TestSerialize_GetUser_NumericId(t *testing.T) { + userId, _ := iggcon.NewIdentifier(uint32(123)) + + cmd := GetUser{ + Id: userId, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetUser with numeric ID: %v", err) + } + + expected := []byte{ + 0x01, // Kind = NumericId + 0x04, // Length = 4 + 0x7B, 0x00, 0x00, 0x00, // Value = 123 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_GetUser_StringId tests GetUser with string identifier +func TestSerialize_GetUser_StringId(t *testing.T) { + userId, _ := iggcon.NewIdentifier("admin") + + cmd := GetUser{ + Id: userId, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize GetUser with string ID: %v", err) + } + + expected := []byte{ + 0x02, // Kind = StringId + 0x05, // Length = 5 + 0x61, 0x64, 0x6D, 0x69, 0x6E, // "admin" + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_UpdatePermissions_WithPermissions tests UpdatePermissions with permissions +func TestSerialize_UpdatePermissions_WithPermissions(t *testing.T) { + userId, _ := iggcon.NewIdentifier(uint32(42)) + globalPerms := createTestGlobalPermissions(false) + permissions := createTestPermissions(globalPerms) + + cmd := UpdatePermissions{ + UserID: userId, + Permissions: permissions, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize UpdatePermissions: %v", err) + } + + expected := []byte{ + 0x01, // UserId Kind = NumericId + 0x04, // UserId Length = 4 + 0x2A, 0x00, 0x00, 0x00, // UserId Value = 42 + 0x01, // Has permissions = 1 + 0x0B, 0x00, 0x00, 0x00, // Permissions length = 11 + // Global permissions (all false = 0) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, // No stream-specific permissions + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_UpdatePermissions_Nil tests UpdatePermissions without permissions +func TestSerialize_UpdatePermissions_Nil(t *testing.T) { + userId, _ := iggcon.NewIdentifier("user123") + + cmd := UpdatePermissions{ + UserID: userId, + Permissions: nil, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize UpdatePermissions without permissions: %v", err) + } + + expected := []byte{ + 0x02, // UserId Kind = StringId + 0x07, // UserId Length = 7 + 0x75, 0x73, 0x65, 0x72, 0x31, 0x32, 0x33, // "user123" + 0x00, // Has permissions = 0 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_ChangePassword tests ChangePassword serialization +func TestSerialize_ChangePassword(t *testing.T) { + userId, _ := iggcon.NewIdentifier(uint32(1)) + + cmd := ChangePassword{ + UserID: userId, + CurrentPassword: "old_pass", + NewPassword: "new_pass", + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize ChangePassword: %v", err) + } + + expected := []byte{ + 0x01, // UserId Kind = NumericId + 0x04, // UserId Length = 4 + 0x01, 0x00, 0x00, 0x00, // UserId Value = 1 + 0x08, // CurrentPassword length = 8 + 0x6F, 0x6C, 0x64, 0x5F, 0x70, 0x61, 0x73, 0x73, // "old_pass" + 0x08, // NewPassword length = 8 + 0x6E, 0x65, 0x77, 0x5F, 0x70, 0x61, 0x73, 0x73, // "new_pass" + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_ChangePassword_EmptyPasswords tests edge case with empty passwords +func TestSerialize_ChangePassword_EmptyPasswords(t *testing.T) { + userId, _ := iggcon.NewIdentifier("admin") + + cmd := ChangePassword{ + UserID: userId, + CurrentPassword: "", + NewPassword: "", + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize ChangePassword with empty passwords: %v", err) + } + + expected := []byte{ + 0x02, // UserId Kind = StringId + 0x05, // UserId Length = 5 + 0x61, 0x64, 0x6D, 0x69, 0x6E, // "admin" + 0x00, // CurrentPassword length = 0 + 0x00, // NewPassword length = 0 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_DeleteUser_NumericId tests DeleteUser with numeric identifier +func TestSerialize_DeleteUser_NumericId(t *testing.T) { + userId, _ := iggcon.NewIdentifier(uint32(999)) + + cmd := DeleteUser{ + Id: userId, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize DeleteUser with numeric ID: %v", err) + } + + expected := []byte{ + 0x01, // Kind = NumericId + 0x04, // Length = 4 + 0xE7, 0x03, 0x00, 0x00, // Value = 999 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +// TestSerialize_DeleteUser_StringId tests DeleteUser with string identifier +func TestSerialize_DeleteUser_StringId(t *testing.T) { + userId, _ := iggcon.NewIdentifier("test_user") + + cmd := DeleteUser{ + Id: userId, + } + + serialized, err := cmd.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize DeleteUser with string ID: %v", err) + } + + expected := []byte{ + 0x02, // Kind = StringId + 0x09, // Length = 9 + 0x74, 0x65, 0x73, 0x74, 0x5F, 0x75, 0x73, 0x65, 0x72, // "test_user" + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("Serialized bytes are incorrect.\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +}