From 26730f09b5e364c207f9105b5a28246d93c6f398 Mon Sep 17 00:00:00 2001 From: saie-ch Date: Mon, 23 Mar 2026 18:00:36 +0530 Subject: [PATCH 1/8] fix(go): fix command serialization bugs --- foreign/go/contracts/users.go | 25 ++++++++++++++++++---- foreign/go/internal/command/update_user.go | 15 ++++++------- foreign/go/internal/command/user.go | 8 +++---- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/foreign/go/contracts/users.go b/foreign/go/contracts/users.go index 4667dfc2cf..2f57bf63e5 100644 --- a/foreign/go/contracts/users.go +++ b/foreign/go/contracts/users.go @@ -60,10 +60,12 @@ func (p *Permissions) MarshalBinary() ([]byte, error) { position := 10 - if p.Streams != nil { + if p.Streams != nil && len(p.Streams) > 0 { bytes[position] = byte(1) position += 1 + streamsCount := len(p.Streams) + currentStream := 1 for streamID, stream := range p.Streams { binary.LittleEndian.PutUint32(bytes[position:position+4], uint32(streamID)) position += 4 @@ -76,10 +78,12 @@ func (p *Permissions) MarshalBinary() ([]byte, error) { bytes[position+5] = boolToByte(stream.SendMessages) position += 6 - if stream.Topics != nil { + if stream.Topics != nil && len(stream.Topics) > 0 { bytes[position] = byte(1) position += 1 + topicsCount := len(stream.Topics) + currentTopic := 1 for topicID, topic := range stream.Topics { binary.LittleEndian.PutUint32(bytes[position:position+4], uint32(topicID)) position += 4 @@ -90,16 +94,29 @@ func (p *Permissions) MarshalBinary() ([]byte, error) { bytes[position+3] = boolToByte(topic.SendMessages) position += 4 - bytes[position] = byte(0) + if currentTopic < topicsCount { + currentTopic++ + bytes[position] = byte(1) + } else { + bytes[position] = byte(0) + } position += 1 } } else { bytes[position] = byte(0) position += 1 } + + if currentStream < streamsCount { + currentStream++ + bytes[position] = byte(1) + } else { + bytes[position] = byte(0) + } + position += 1 } } else { - bytes[0] = byte(0) + bytes[position] = byte(0) } return bytes, nil diff --git a/foreign/go/internal/command/update_user.go b/foreign/go/internal/command/update_user.go index a20d647fb7..bfb01ca684 100644 --- a/foreign/go/internal/command/update_user.go +++ b/foreign/go/internal/command/update_user.go @@ -34,23 +34,22 @@ 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) + username := "" + if u.Username != nil { + username = *u.Username } - 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/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) From 57d356f6747ec6378563f6bbe15eee581e3d0438 Mon Sep 17 00:00:00 2001 From: saie-ch Date: Mon, 23 Mar 2026 18:35:37 +0530 Subject: [PATCH 2/8] fix(go): remove redundant nil checks for lint --- foreign/go/contracts/users.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/foreign/go/contracts/users.go b/foreign/go/contracts/users.go index 2f57bf63e5..7bcfc108ca 100644 --- a/foreign/go/contracts/users.go +++ b/foreign/go/contracts/users.go @@ -60,7 +60,7 @@ func (p *Permissions) MarshalBinary() ([]byte, error) { position := 10 - if p.Streams != nil && len(p.Streams) > 0 { + if len(p.Streams) > 0 { bytes[position] = byte(1) position += 1 @@ -78,7 +78,7 @@ func (p *Permissions) MarshalBinary() ([]byte, error) { bytes[position+5] = boolToByte(stream.SendMessages) position += 6 - if stream.Topics != nil && len(stream.Topics) > 0 { + if len(stream.Topics) > 0 { bytes[position] = byte(1) position += 1 From cf9f5086d2e5b6adb955a3836cb5b957c6409939 Mon Sep 17 00:00:00 2001 From: saie-ch Date: Tue, 24 Mar 2026 00:02:47 +0530 Subject: [PATCH 3/8] scope PR to Permissions fix only, add tests --- foreign/go/contracts/users_test.go | 235 +++++++++++++++++++++ foreign/go/internal/command/update_user.go | 15 +- foreign/go/internal/command/user.go | 8 +- 3 files changed, 247 insertions(+), 11 deletions(-) create mode 100644 foreign/go/contracts/users_test.go diff --git a/foreign/go/contracts/users_test.go b/foreign/go/contracts/users_test.go new file mode 100644 index 0000000000..f786eba312 --- /dev/null +++ b/foreign/go/contracts/users_test.go @@ -0,0 +1,235 @@ +// 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 iggcon + +import ( + "encoding/binary" + "testing" +) + +func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { + // Test case: Permissions with 2 streams, first stream has 2 topics, second has none + permissions := &Permissions{ + Global: GlobalPermissions{ + ManageServers: true, + ReadServers: false, + ManageUsers: true, + ReadUsers: false, + ManageStreams: true, + ReadStreams: false, + ManageTopics: true, + ReadTopics: false, + PollMessages: true, + SendMessages: false, + }, + Streams: map[int]*StreamPermissions{ + 1: { + ManageStream: true, + ReadStream: false, + ManageTopics: true, + ReadTopics: false, + PollMessages: true, + SendMessages: false, + Topics: map[int]*TopicPermissions{ + 10: { + ManageTopic: true, + ReadTopic: false, + PollMessages: true, + SendMessages: false, + }, + 20: { + ManageTopic: false, + ReadTopic: true, + PollMessages: false, + SendMessages: true, + }, + }, + }, + 2: { + ManageStream: false, + ReadStream: true, + ManageTopics: false, + ReadTopics: true, + PollMessages: false, + SendMessages: true, + Topics: nil, + }, + }, + } + + bytes, err := permissions.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary failed: %v", err) + } + + // Verify structure + position := 0 + + // Global permissions (10 bytes) + if bytes[position] != 1 { + t.Errorf("Expected ManageServers=1, got %d", bytes[position]) + } + position += 10 + + // Has streams flag + if bytes[position] != 1 { + t.Errorf("Expected has_streams=1, got %d", bytes[position]) + } + position++ + + // Verify continuation flags are present for streams + // We should have 2 streams, so we need to find stream continuation flags + streamsFound := 0 + for position < len(bytes) { + // Each stream has: 4 bytes (ID) + 6 bytes (perms) + 1 byte (has_topics) + topics + 1 byte (has_next_stream) + if position+4 > len(bytes) { + break + } + streamID := binary.LittleEndian.Uint32(bytes[position : position+4]) + if streamID == 0 { + break + } + position += 4 // stream ID + position += 6 // stream permissions + position += 1 // has_topics flag + + // Skip topics if present + if bytes[position-1] == 1 { + // Topics exist, need to skip them + for { + if position+4 > len(bytes) { + break + } + position += 4 // topic ID + position += 4 // topic permissions + if position >= len(bytes) { + t.Fatalf("Unexpected end of bytes while reading topic continuation flag") + } + hasNextTopic := bytes[position] + position++ + if hasNextTopic == 0 { + break + } + } + } + + // Check stream continuation flag + if position >= len(bytes) { + t.Fatalf("Unexpected end of bytes while reading stream continuation flag") + } + hasNextStream := bytes[position] + position++ + streamsFound++ + + if streamsFound == 1 && hasNextStream != 1 { + t.Errorf("Expected has_next_stream=1 for first stream, got %d", hasNextStream) + } + if streamsFound == 2 && hasNextStream != 0 { + t.Errorf("Expected has_next_stream=0 for second stream, got %d", hasNextStream) + } + + if hasNextStream == 0 { + break + } + } + + if streamsFound != 2 { + t.Errorf("Expected 2 streams, found %d", streamsFound) + } +} + +func TestPermissions_MarshalBinary_WithEmptyStreamsMap(t *testing.T) { + // Test case: Permissions with empty streams map should be treated as nil + permissions := &Permissions{ + Global: GlobalPermissions{ + ManageServers: true, + }, + Streams: map[int]*StreamPermissions{}, // Empty map + } + + bytes, err := permissions.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary failed: %v", err) + } + + // Should be 10 bytes (global) + 1 byte (has_streams=0) + if len(bytes) != 11 { + t.Errorf("Expected 11 bytes for empty streams, got %d", len(bytes)) + } + + // Check has_streams flag is 0 + if bytes[10] != 0 { + t.Errorf("Expected has_streams=0 for empty map, got %d", bytes[10]) + } +} + +func TestPermissions_MarshalBinary_WithNilStreams(t *testing.T) { + // Test case: Permissions with nil streams + permissions := &Permissions{ + Global: GlobalPermissions{ + ManageServers: true, + }, + Streams: nil, + } + + bytes, err := permissions.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary failed: %v", err) + } + + // Should be 10 bytes (global) + 1 byte (has_streams=0) + if len(bytes) != 11 { + t.Errorf("Expected 11 bytes for nil streams, got %d", len(bytes)) + } + + // Check has_streams flag is 0 + if bytes[10] != 0 { + t.Errorf("Expected has_streams=0 for nil, got %d", bytes[10]) + } +} + +func TestPermissions_MarshalBinary_WithEmptyTopicsMap(t *testing.T) { + // Test case: Stream with empty topics map should be treated as nil + permissions := &Permissions{ + Global: GlobalPermissions{}, + Streams: map[int]*StreamPermissions{ + 1: { + ManageStream: true, + Topics: map[int]*TopicPermissions{}, // Empty topics map + }, + }, + } + + bytes, err := permissions.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary failed: %v", err) + } + + // Verify structure + position := 10 // Skip global permissions + position++ // Skip has_streams flag + + // Skip stream ID and permissions + position += 4 // stream ID + position += 6 // stream permissions + + // Check has_topics flag is 0 for empty map + if bytes[position] != 0 { + t.Errorf("Expected has_topics=0 for empty topics map, got %d", bytes[position]) + } +} diff --git a/foreign/go/internal/command/update_user.go b/foreign/go/internal/command/update_user.go index bfb01ca684..a20d647fb7 100644 --- a/foreign/go/internal/command/update_user.go +++ b/foreign/go/internal/command/update_user.go @@ -34,22 +34,23 @@ func (u *UpdateUser) MarshalBinary() ([]byte, error) { if err != nil { return nil, err } - length := len(userIdBytes) + 2 + length := len(userIdBytes) - username := "" - if u.Username != nil { - username = *u.Username + if u.Username == nil { + u.Username = new(string) } + username := *u.Username + if len(username) != 0 { - length += 1 + len(username) + length += 2 + len(username) } if u.Status != nil { - length += 1 + length += 2 } - bytes := make([]byte, length) + bytes := make([]byte, length+1) position := 0 copy(bytes[position:position+len(userIdBytes)], userIdBytes) diff --git a/foreign/go/internal/command/user.go b/foreign/go/internal/command/user.go index 9896ca4697..628140a43a 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 := 1 + len(c.Username) + 1 + len(c.Password) + 1 + 1 + capacity := 4 + len(c.Username) + len(c.Password) if c.Permissions != nil { - capacity += 4 + c.Permissions.Size() + capacity += 1 + 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) + 1 + length := len(userIdBytes) if u.Permissions != nil { - length += 4 + u.Permissions.Size() + length += 1 + 4 + u.Permissions.Size() } bytes := make([]byte, length) From 1764aa255b3f2a58b1190981e00740f9e519c3c7 Mon Sep 17 00:00:00 2001 From: saie-ch Date: Tue, 24 Mar 2026 01:04:09 +0530 Subject: [PATCH 4/8] fix(go): format test file --- foreign/go/contracts/users_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/foreign/go/contracts/users_test.go b/foreign/go/contracts/users_test.go index f786eba312..0e7c2283e9 100644 --- a/foreign/go/contracts/users_test.go +++ b/foreign/go/contracts/users_test.go @@ -104,9 +104,9 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { if streamID == 0 { break } - position += 4 // stream ID - position += 6 // stream permissions - position += 1 // has_topics flag + position += 4 // stream ID + position += 6 // stream permissions + position += 1 // has_topics flag // Skip topics if present if bytes[position-1] == 1 { @@ -115,8 +115,8 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { if position+4 > len(bytes) { break } - position += 4 // topic ID - position += 4 // topic permissions + position += 4 // topic ID + position += 4 // topic permissions if position >= len(bytes) { t.Fatalf("Unexpected end of bytes while reading topic continuation flag") } From 231c53142a526e75ae17630727c9438738e03737 Mon Sep 17 00:00:00 2001 From: saie-ch Date: Tue, 24 Mar 2026 13:50:17 +0530 Subject: [PATCH 5/8] fix(go): address staticcheck lint issue --- foreign/go/contracts/users_test.go | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/foreign/go/contracts/users_test.go b/foreign/go/contracts/users_test.go index 0e7c2283e9..12e0b9980e 100644 --- a/foreign/go/contracts/users_test.go +++ b/foreign/go/contracts/users_test.go @@ -111,10 +111,7 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { // Skip topics if present if bytes[position-1] == 1 { // Topics exist, need to skip them - for { - if position+4 > len(bytes) { - break - } + for position+4 <= len(bytes) { position += 4 // topic ID position += 4 // topic permissions if position >= len(bytes) { From 343bff980d7563fb02b5bf62a1d6650a7687289c Mon Sep 17 00:00:00 2001 From: saie-ch Date: Tue, 24 Mar 2026 21:10:11 +0530 Subject: [PATCH 6/8] test(go): enhance Permissions test to verify all encoded values --- foreign/go/contracts/users_test.go | 139 ++++++++++++++++++++++------- 1 file changed, 105 insertions(+), 34 deletions(-) diff --git a/foreign/go/contracts/users_test.go b/foreign/go/contracts/users_test.go index 12e0b9980e..f34e5c0c0e 100644 --- a/foreign/go/contracts/users_test.go +++ b/foreign/go/contracts/users_test.go @@ -77,12 +77,15 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { t.Fatalf("MarshalBinary failed: %v", err) } - // Verify structure + // Verify structure and values position := 0 - // Global permissions (10 bytes) - if bytes[position] != 1 { - t.Errorf("Expected ManageServers=1, got %d", bytes[position]) + // Verify global permissions (10 bytes) + expectedGlobal := []byte{1, 0, 1, 0, 1, 0, 1, 0, 1, 0} + for i := 0; i < 10; i++ { + if bytes[position+i] != expectedGlobal[i] { + t.Errorf("Global permission byte %d: expected %d, got %d", i, expectedGlobal[i], bytes[position+i]) + } } position += 10 @@ -92,52 +95,116 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { } position++ - // Verify continuation flags are present for streams - // We should have 2 streams, so we need to find stream continuation flags - streamsFound := 0 - for position < len(bytes) { - // Each stream has: 4 bytes (ID) + 6 bytes (perms) + 1 byte (has_topics) + topics + 1 byte (has_next_stream) - if position+4 > len(bytes) { - break - } + // Track streams found (map because order is non-deterministic) + streamsFound := make(map[uint32]bool) + + // Read and verify streams + for position+4 <= len(bytes) { + // Read stream ID streamID := binary.LittleEndian.Uint32(bytes[position : position+4]) - if streamID == 0 { - break + position += 4 + + // Verify stream permissions based on stream ID + var expectedStreamPerms []byte + switch streamID { + case 1: + expectedStreamPerms = []byte{1, 0, 1, 0, 1, 0} // ManageStream=true, ReadStream=false, etc. + case 2: + expectedStreamPerms = []byte{0, 1, 0, 1, 0, 1} + default: + t.Fatalf("Unexpected stream ID: %d", streamID) } - position += 4 // stream ID - position += 6 // stream permissions - position += 1 // has_topics flag - - // Skip topics if present - if bytes[position-1] == 1 { - // Topics exist, need to skip them - for position+4 <= len(bytes) { - position += 4 // topic ID - position += 4 // topic permissions - if position >= len(bytes) { - t.Fatalf("Unexpected end of bytes while reading topic continuation flag") + + for i := 0; i < 6; i++ { + if bytes[position+i] != expectedStreamPerms[i] { + t.Errorf("Stream %d permission byte %d: expected %d, got %d", streamID, i, expectedStreamPerms[i], bytes[position+i]) + } + } + position += 6 + + // Read has_topics flag + hasTopics := bytes[position] + position++ + + // Verify and read topics if present + if hasTopics == 1 { + if streamID != 1 { + t.Errorf("Stream %d should not have topics", streamID) + } + + topicsFound := make(map[uint32]bool) + for { + if position+4 > len(bytes) { + t.Fatalf("Unexpected end while reading topic ID") } + + // Read topic ID + topicID := binary.LittleEndian.Uint32(bytes[position : position+4]) + position += 4 + + // Verify topic permissions + var expectedTopicPerms []byte + switch topicID { + case 10: + expectedTopicPerms = []byte{1, 0, 1, 0} + case 20: + expectedTopicPerms = []byte{0, 1, 0, 1} + default: + t.Fatalf("Unexpected topic ID: %d", topicID) + } + + for i := 0; i < 4; i++ { + if bytes[position+i] != expectedTopicPerms[i] { + t.Errorf("Topic %d permission byte %d: expected %d, got %d", topicID, i, expectedTopicPerms[i], bytes[position+i]) + } + } + position += 4 + + topicsFound[topicID] = true + + // Read has_next_topic flag hasNextTopic := bytes[position] position++ + + // Verify continuation flag logic + if len(topicsFound) == 1 && hasNextTopic != 1 { + t.Errorf("Expected has_next_topic=1 for first topic, got %d", hasNextTopic) + } + if len(topicsFound) == 2 && hasNextTopic != 0 { + t.Errorf("Expected has_next_topic=0 for last topic, got %d", hasNextTopic) + } + if hasNextTopic == 0 { break } } + + // Verify all topics were found + if len(topicsFound) != 2 { + t.Errorf("Expected 2 topics, found %d", len(topicsFound)) + } + if !topicsFound[10] || !topicsFound[20] { + t.Errorf("Expected topics 10 and 20, found: %v", topicsFound) + } + } else if streamID == 1 { + t.Errorf("Stream 1 should have topics") } - // Check stream continuation flag + streamsFound[streamID] = true + + // Read has_next_stream flag if position >= len(bytes) { - t.Fatalf("Unexpected end of bytes while reading stream continuation flag") + t.Fatalf("Unexpected end while reading stream continuation flag") } hasNextStream := bytes[position] position++ - streamsFound++ - if streamsFound == 1 && hasNextStream != 1 { + // Verify continuation flag logic + if len(streamsFound) == 1 && hasNextStream != 1 { t.Errorf("Expected has_next_stream=1 for first stream, got %d", hasNextStream) } - if streamsFound == 2 && hasNextStream != 0 { - t.Errorf("Expected has_next_stream=0 for second stream, got %d", hasNextStream) + if len(streamsFound) == 2 && hasNextStream != 0 { + t.Errorf("Expected has_next_stream=0 for last stream, got %d", hasNextStream) } if hasNextStream == 0 { @@ -145,8 +212,12 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { } } - if streamsFound != 2 { - t.Errorf("Expected 2 streams, found %d", streamsFound) + // Verify all streams were found + if len(streamsFound) != 2 { + t.Errorf("Expected 2 streams, found %d", len(streamsFound)) + } + if !streamsFound[1] || !streamsFound[2] { + t.Errorf("Expected streams 1 and 2, found: %v", streamsFound) } } From e6add02894a7607e75bc524d7dd10cf729907538 Mon Sep 17 00:00:00 2001 From: saie-ch Date: Thu, 26 Mar 2026 17:13:46 +0530 Subject: [PATCH 7/8] test(go): add individual field tests and use asymmetric values for Permissions --- foreign/go/contracts/users_test.go | 505 +++++++++++++++++++++++++++-- 1 file changed, 474 insertions(+), 31 deletions(-) diff --git a/foreign/go/contracts/users_test.go b/foreign/go/contracts/users_test.go index f34e5c0c0e..01d6bdaf32 100644 --- a/foreign/go/contracts/users_test.go +++ b/foreign/go/contracts/users_test.go @@ -22,17 +22,26 @@ import ( "testing" ) +// requireRemaining checks that there are enough bytes remaining in the slice +func requireRemaining(t *testing.T, data []byte, position, needed int, context string) { + t.Helper() + if position+needed > len(data) { + t.Fatalf("%s: need %d bytes at position %d, but only %d bytes remain (total length: %d)", + context, needed, position, len(data)-position, len(data)) + } +} + func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { // Test case: Permissions with 2 streams, first stream has 2 topics, second has none permissions := &Permissions{ Global: GlobalPermissions{ ManageServers: true, ReadServers: false, - ManageUsers: true, - ReadUsers: false, - ManageStreams: true, - ReadStreams: false, - ManageTopics: true, + ManageUsers: false, + ReadUsers: true, + ManageStreams: true, + ReadStreams: true, + ManageTopics: false, ReadTopics: false, PollMessages: true, SendMessages: false, @@ -40,8 +49,8 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { Streams: map[int]*StreamPermissions{ 1: { ManageStream: true, - ReadStream: false, - ManageTopics: true, + ReadStream: true, + ManageTopics: false, ReadTopics: false, PollMessages: true, SendMessages: false, @@ -49,13 +58,13 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { 10: { ManageTopic: true, ReadTopic: false, - PollMessages: true, - SendMessages: false, + PollMessages: false, + SendMessages: true, }, 20: { ManageTopic: false, - ReadTopic: true, - PollMessages: false, + ReadTopic: false, + PollMessages: true, SendMessages: true, }, }, @@ -63,8 +72,8 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { 2: { ManageStream: false, ReadStream: true, - ManageTopics: false, - ReadTopics: true, + ManageTopics: true, + ReadTopics: false, PollMessages: false, SendMessages: true, Topics: nil, @@ -81,7 +90,8 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { position := 0 // Verify global permissions (10 bytes) - expectedGlobal := []byte{1, 0, 1, 0, 1, 0, 1, 0, 1, 0} + requireRemaining(t, bytes, position, 10, "global permissions") + expectedGlobal := []byte{1, 0, 0, 1, 1, 1, 0, 0, 1, 0} for i := 0; i < 10; i++ { if bytes[position+i] != expectedGlobal[i] { t.Errorf("Global permission byte %d: expected %d, got %d", i, expectedGlobal[i], bytes[position+i]) @@ -90,6 +100,7 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { position += 10 // Has streams flag + requireRemaining(t, bytes, position, 1, "has_streams flag") if bytes[position] != 1 { t.Errorf("Expected has_streams=1, got %d", bytes[position]) } @@ -101,6 +112,7 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { // Read and verify streams for position+4 <= len(bytes) { // Read stream ID + requireRemaining(t, bytes, position, 4, "stream ID") streamID := binary.LittleEndian.Uint32(bytes[position : position+4]) position += 4 @@ -108,13 +120,14 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { var expectedStreamPerms []byte switch streamID { case 1: - expectedStreamPerms = []byte{1, 0, 1, 0, 1, 0} // ManageStream=true, ReadStream=false, etc. + expectedStreamPerms = []byte{1, 1, 0, 0, 1, 0} case 2: - expectedStreamPerms = []byte{0, 1, 0, 1, 0, 1} + expectedStreamPerms = []byte{0, 1, 1, 0, 0, 1} default: t.Fatalf("Unexpected stream ID: %d", streamID) } + requireRemaining(t, bytes, position, 6, "stream permissions") for i := 0; i < 6; i++ { if bytes[position+i] != expectedStreamPerms[i] { t.Errorf("Stream %d permission byte %d: expected %d, got %d", streamID, i, expectedStreamPerms[i], bytes[position+i]) @@ -123,6 +136,7 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { position += 6 // Read has_topics flag + requireRemaining(t, bytes, position, 1, "has_topics flag") hasTopics := bytes[position] position++ @@ -134,9 +148,7 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { topicsFound := make(map[uint32]bool) for { - if position+4 > len(bytes) { - t.Fatalf("Unexpected end while reading topic ID") - } + requireRemaining(t, bytes, position, 4, "topic ID") // Read topic ID topicID := binary.LittleEndian.Uint32(bytes[position : position+4]) @@ -146,13 +158,14 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { var expectedTopicPerms []byte switch topicID { case 10: - expectedTopicPerms = []byte{1, 0, 1, 0} + expectedTopicPerms = []byte{1, 0, 0, 1} case 20: - expectedTopicPerms = []byte{0, 1, 0, 1} + expectedTopicPerms = []byte{0, 0, 1, 1} default: t.Fatalf("Unexpected topic ID: %d", topicID) } + requireRemaining(t, bytes, position, 4, "topic permissions") for i := 0; i < 4; i++ { if bytes[position+i] != expectedTopicPerms[i] { t.Errorf("Topic %d permission byte %d: expected %d, got %d", topicID, i, expectedTopicPerms[i], bytes[position+i]) @@ -163,6 +176,7 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { topicsFound[topicID] = true // Read has_next_topic flag + requireRemaining(t, bytes, position, 1, "has_next_topic flag") hasNextTopic := bytes[position] position++ @@ -193,9 +207,7 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { streamsFound[streamID] = true // Read has_next_stream flag - if position >= len(bytes) { - t.Fatalf("Unexpected end while reading stream continuation flag") - } + requireRemaining(t, bytes, position, 1, "has_next_stream flag") hasNextStream := bytes[position] position++ @@ -219,6 +231,395 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { if !streamsFound[1] || !streamsFound[2] { t.Errorf("Expected streams 1 and 2, found: %v", streamsFound) } + + // Verify exact payload consumption + if position != len(bytes) { + t.Errorf("Payload not fully consumed: read %d bytes, but payload length is %d (unread: %d bytes)", + position, len(bytes), len(bytes)-position) + } +} + +func TestPermissions_MarshalBinary_GlobalPermissions_IndividualFields(t *testing.T) { + // Test each global permission field individually to ensure correct byte positions + tests := []struct { + name string + perms GlobalPermissions + expected []byte + }{ + { + name: "Only ManageServers", + perms: GlobalPermissions{ManageServers: true}, + expected: []byte{1, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + }, + { + name: "Only ReadServers", + perms: GlobalPermissions{ReadServers: true}, + expected: []byte{0, 1, 0, 0, 0, 0, 0, 0, 0, 0}, + }, + { + name: "Only ManageUsers", + perms: GlobalPermissions{ManageUsers: true}, + expected: []byte{0, 0, 1, 0, 0, 0, 0, 0, 0, 0}, + }, + { + name: "Only ReadUsers", + perms: GlobalPermissions{ReadUsers: true}, + expected: []byte{0, 0, 0, 1, 0, 0, 0, 0, 0, 0}, + }, + { + name: "Only ManageStreams", + perms: GlobalPermissions{ManageStreams: true}, + expected: []byte{0, 0, 0, 0, 1, 0, 0, 0, 0, 0}, + }, + { + name: "Only ReadStreams", + perms: GlobalPermissions{ReadStreams: true}, + expected: []byte{0, 0, 0, 0, 0, 1, 0, 0, 0, 0}, + }, + { + name: "Only ManageTopics", + perms: GlobalPermissions{ManageTopics: true}, + expected: []byte{0, 0, 0, 0, 0, 0, 1, 0, 0, 0}, + }, + { + name: "Only ReadTopics", + perms: GlobalPermissions{ReadTopics: true}, + expected: []byte{0, 0, 0, 0, 0, 0, 0, 1, 0, 0}, + }, + { + name: "Only PollMessages", + perms: GlobalPermissions{PollMessages: true}, + expected: []byte{0, 0, 0, 0, 0, 0, 0, 0, 1, 0}, + }, + { + name: "Only SendMessages", + perms: GlobalPermissions{SendMessages: true}, + expected: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 1}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + permissions := &Permissions{ + Global: tt.perms, + Streams: nil, + } + + bytes, err := permissions.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary failed: %v", err) + } + + // Verify global permissions bytes + for i := 0; i < 10; i++ { + if bytes[i] != tt.expected[i] { + t.Errorf("Byte %d: expected %d, got %d", i, tt.expected[i], bytes[i]) + } + } + + // Verify has_streams flag is 0 + if bytes[10] != 0 { + t.Errorf("Expected has_streams=0, got %d", bytes[10]) + } + }) + } +} + +func TestPermissions_MarshalBinary_StreamPermissions_IndividualFields(t *testing.T) { + // Test each stream permission field individually + tests := []struct { + name string + perms StreamPermissions + expected []byte + }{ + { + name: "Only ManageStream", + perms: StreamPermissions{ManageStream: true}, + expected: []byte{1, 0, 0, 0, 0, 0}, + }, + { + name: "Only ReadStream", + perms: StreamPermissions{ReadStream: true}, + expected: []byte{0, 1, 0, 0, 0, 0}, + }, + { + name: "Only ManageTopics", + perms: StreamPermissions{ManageTopics: true}, + expected: []byte{0, 0, 1, 0, 0, 0}, + }, + { + name: "Only ReadTopics", + perms: StreamPermissions{ReadTopics: true}, + expected: []byte{0, 0, 0, 1, 0, 0}, + }, + { + name: "Only PollMessages", + perms: StreamPermissions{PollMessages: true}, + expected: []byte{0, 0, 0, 0, 1, 0}, + }, + { + name: "Only SendMessages", + perms: StreamPermissions{SendMessages: true}, + expected: []byte{0, 0, 0, 0, 0, 1}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + permissions := &Permissions{ + Global: GlobalPermissions{}, + Streams: map[int]*StreamPermissions{ + 1: &tt.perms, + }, + } + + bytes, err := permissions.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary failed: %v", err) + } + + // Skip global (10 bytes) + has_streams (1 byte) + stream ID (4 bytes) + position := 15 + + // Verify stream permissions bytes + for i := 0; i < 6; i++ { + if bytes[position+i] != tt.expected[i] { + t.Errorf("Stream permission byte %d: expected %d, got %d", i, tt.expected[i], bytes[position+i]) + } + } + }) + } +} + +func TestPermissions_MarshalBinary_TopicPermissions_IndividualFields(t *testing.T) { + // Test each topic permission field individually + tests := []struct { + name string + perms TopicPermissions + expected []byte + }{ + { + name: "Only ManageTopic", + perms: TopicPermissions{ManageTopic: true}, + expected: []byte{1, 0, 0, 0}, + }, + { + name: "Only ReadTopic", + perms: TopicPermissions{ReadTopic: true}, + expected: []byte{0, 1, 0, 0}, + }, + { + name: "Only PollMessages", + perms: TopicPermissions{PollMessages: true}, + expected: []byte{0, 0, 1, 0}, + }, + { + name: "Only SendMessages", + perms: TopicPermissions{SendMessages: true}, + expected: []byte{0, 0, 0, 1}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + permissions := &Permissions{ + Global: GlobalPermissions{}, + Streams: map[int]*StreamPermissions{ + 1: { + Topics: map[int]*TopicPermissions{ + 1: &tt.perms, + }, + }, + }, + } + + bytes, err := permissions.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary failed: %v", err) + } + + // Skip global (10) + has_streams (1) + stream ID (4) + stream perms (6) + has_topics (1) + topic ID (4) + position := 26 + + // Verify topic permissions bytes + for i := 0; i < 4; i++ { + if bytes[position+i] != tt.expected[i] { + t.Errorf("Topic permission byte %d: expected %d, got %d", i, tt.expected[i], bytes[position+i]) + } + } + }) + } +} + +func TestPermissions_MarshalBinary_SingleStream_HasNextStreamIsZero(t *testing.T) { + // Test case: Single stream with no topics - verify has_next_stream=0 + permissions := &Permissions{ + Global: GlobalPermissions{ + ManageServers: true, + ReadUsers: true, + }, + Streams: map[int]*StreamPermissions{ + 5: { + ManageStream: true, + ReadStream: true, + ManageTopics: false, + ReadTopics: true, + PollMessages: false, + SendMessages: true, + Topics: nil, + }, + }, + } + + bytes, err := permissions.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary failed: %v", err) + } + + position := 0 + + // Skip global permissions + requireRemaining(t, bytes, position, 10, "global permissions") + position += 10 + + // Verify has_streams flag + requireRemaining(t, bytes, position, 1, "has_streams flag") + if bytes[position] != 1 { + t.Errorf("Expected has_streams=1, got %d", bytes[position]) + } + position++ + + // Read and verify stream ID + requireRemaining(t, bytes, position, 4, "stream ID") + streamID := binary.LittleEndian.Uint32(bytes[position : position+4]) + if streamID != 5 { + t.Errorf("Expected stream ID 5, got %d", streamID) + } + position += 4 + + // Verify stream permissions + requireRemaining(t, bytes, position, 6, "stream permissions") + expectedPerms := []byte{1, 1, 0, 1, 0, 1} + for i := 0; i < 6; i++ { + if bytes[position+i] != expectedPerms[i] { + t.Errorf("Stream permission byte %d: expected %d, got %d", i, expectedPerms[i], bytes[position+i]) + } + } + position += 6 + + // Verify has_topics flag + requireRemaining(t, bytes, position, 1, "has_topics flag") + if bytes[position] != 0 { + t.Errorf("Expected has_topics=0, got %d", bytes[position]) + } + position++ + + // Verify has_next_stream flag (should be 0 for single stream) + requireRemaining(t, bytes, position, 1, "has_next_stream flag") + if bytes[position] != 0 { + t.Errorf("Expected has_next_stream=0 for single stream, got %d", bytes[position]) + } + position++ + + // Verify exact payload consumption + if position != len(bytes) { + t.Errorf("Payload not fully consumed: read %d bytes, but payload length is %d", position, len(bytes)) + } +} + +func TestPermissions_MarshalBinary_SingleTopic_HasNextTopicIsZero(t *testing.T) { + // Test case: Single stream with single topic - verify has_next_topic=0 and has_next_stream=0 + permissions := &Permissions{ + Global: GlobalPermissions{ + PollMessages: true, + }, + Streams: map[int]*StreamPermissions{ + 3: { + ReadStream: true, + PollMessages: true, + Topics: map[int]*TopicPermissions{ + 7: { + ReadTopic: true, + PollMessages: true, + }, + }, + }, + }, + } + + bytes, err := permissions.MarshalBinary() + if err != nil { + t.Fatalf("MarshalBinary failed: %v", err) + } + + position := 0 + + // Skip global permissions + requireRemaining(t, bytes, position, 10, "global permissions") + position += 10 + + // Verify has_streams flag + requireRemaining(t, bytes, position, 1, "has_streams flag") + if bytes[position] != 1 { + t.Errorf("Expected has_streams=1, got %d", bytes[position]) + } + position++ + + // Read and verify stream ID + requireRemaining(t, bytes, position, 4, "stream ID") + streamID := binary.LittleEndian.Uint32(bytes[position : position+4]) + if streamID != 3 { + t.Errorf("Expected stream ID 3, got %d", streamID) + } + position += 4 + + // Skip stream permissions + requireRemaining(t, bytes, position, 6, "stream permissions") + position += 6 + + // Verify has_topics flag + requireRemaining(t, bytes, position, 1, "has_topics flag") + if bytes[position] != 1 { + t.Errorf("Expected has_topics=1, got %d", bytes[position]) + } + position++ + + // Read and verify topic ID + requireRemaining(t, bytes, position, 4, "topic ID") + topicID := binary.LittleEndian.Uint32(bytes[position : position+4]) + if topicID != 7 { + t.Errorf("Expected topic ID 7, got %d", topicID) + } + position += 4 + + // Verify topic permissions + requireRemaining(t, bytes, position, 4, "topic permissions") + expectedPerms := []byte{0, 1, 1, 0} + for i := 0; i < 4; i++ { + if bytes[position+i] != expectedPerms[i] { + t.Errorf("Topic permission byte %d: expected %d, got %d", i, expectedPerms[i], bytes[position+i]) + } + } + position += 4 + + // Verify has_next_topic flag (should be 0 for single topic) + requireRemaining(t, bytes, position, 1, "has_next_topic flag") + if bytes[position] != 0 { + t.Errorf("Expected has_next_topic=0 for single topic, got %d", bytes[position]) + } + position++ + + // Verify has_next_stream flag (should be 0 for single stream) + requireRemaining(t, bytes, position, 1, "has_next_stream flag") + if bytes[position] != 0 { + t.Errorf("Expected has_next_stream=0 for single stream, got %d", bytes[position]) + } + position++ + + // Verify exact payload consumption + if position != len(bytes) { + t.Errorf("Payload not fully consumed: read %d bytes, but payload length is %d", position, len(bytes)) + } } func TestPermissions_MarshalBinary_WithEmptyStreamsMap(t *testing.T) { @@ -278,6 +679,11 @@ func TestPermissions_MarshalBinary_WithEmptyTopicsMap(t *testing.T) { Streams: map[int]*StreamPermissions{ 1: { ManageStream: true, + ReadStream: false, + ManageTopics: true, + ReadTopics: false, + PollMessages: false, + SendMessages: true, Topics: map[int]*TopicPermissions{}, // Empty topics map }, }, @@ -288,16 +694,53 @@ func TestPermissions_MarshalBinary_WithEmptyTopicsMap(t *testing.T) { t.Fatalf("MarshalBinary failed: %v", err) } - // Verify structure - position := 10 // Skip global permissions - position++ // Skip has_streams flag + position := 0 - // Skip stream ID and permissions - position += 4 // stream ID - position += 6 // stream permissions + // Skip global permissions + requireRemaining(t, bytes, position, 10, "global permissions") + position += 10 - // Check has_topics flag is 0 for empty map + // Verify has_streams flag + requireRemaining(t, bytes, position, 1, "has_streams flag") + if bytes[position] != 1 { + t.Errorf("Expected has_streams=1, got %d", bytes[position]) + } + position++ + + // Read and verify stream ID + requireRemaining(t, bytes, position, 4, "stream ID") + streamID := binary.LittleEndian.Uint32(bytes[position : position+4]) + if streamID != 1 { + t.Errorf("Expected stream ID 1, got %d", streamID) + } + position += 4 + + // Verify stream permissions + requireRemaining(t, bytes, position, 6, "stream permissions") + expectedPerms := []byte{1, 0, 1, 0, 0, 1} + for i := 0; i < 6; i++ { + if bytes[position+i] != expectedPerms[i] { + t.Errorf("Stream permission byte %d: expected %d, got %d", i, expectedPerms[i], bytes[position+i]) + } + } + position += 6 + + // Verify has_topics flag is 0 for empty map + requireRemaining(t, bytes, position, 1, "has_topics flag") if bytes[position] != 0 { t.Errorf("Expected has_topics=0 for empty topics map, got %d", bytes[position]) } + position++ + + // Verify has_next_stream flag (should be 0 for single stream) + requireRemaining(t, bytes, position, 1, "has_next_stream flag") + if bytes[position] != 0 { + t.Errorf("Expected has_next_stream=0 for single stream, got %d", bytes[position]) + } + position++ + + // Verify exact payload consumption + if position != len(bytes) { + t.Errorf("Payload not fully consumed: read %d bytes, but payload length is %d", position, len(bytes)) + } } From 0fe976595c853430f71197ec57711be0a60dbdb5 Mon Sep 17 00:00:00 2001 From: saie-ch Date: Thu, 26 Mar 2026 17:24:42 +0530 Subject: [PATCH 8/8] fix(go): correct gofmt alignment in test file --- foreign/go/contracts/users_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/foreign/go/contracts/users_test.go b/foreign/go/contracts/users_test.go index 01d6bdaf32..cf28cc465a 100644 --- a/foreign/go/contracts/users_test.go +++ b/foreign/go/contracts/users_test.go @@ -39,8 +39,8 @@ func TestPermissions_MarshalBinary_WithStreamsAndTopics(t *testing.T) { ReadServers: false, ManageUsers: false, ReadUsers: true, - ManageStreams: true, - ReadStreams: true, + ManageStreams: true, + ReadStreams: true, ManageTopics: false, ReadTopics: false, PollMessages: true,