diff --git a/foreign/go/internal/command/user.go b/foreign/go/internal/command/user.go index 628140a43..a0470d9ab 100644 --- a/foreign/go/internal/command/user.go +++ b/foreign/go/internal/command/user.go @@ -37,7 +37,7 @@ func (c *CreateUser) Code() Code { func (c *CreateUser) MarshalBinary() ([]byte, error) { capacity := 4 + len(c.Username) + len(c.Password) 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 000000000..c1d9cd4c6 --- /dev/null +++ b/foreign/go/internal/command/user_test.go @@ -0,0 +1,209 @@ +// 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" + "encoding/binary" + "testing" + + iggcon "github.com/apache/iggy/foreign/go/contracts" +) + +func TestSerialize_CreateUser_NilPermissions(t *testing.T) { + request := CreateUser{ + Username: "u", + Password: "p", + Status: iggcon.Active, + } + + serialized, err := request.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreateUser: %v", err) + } + + // Wire format: [username_len:u8][username][password_len:u8][password][status:u8][has_permissions:u8] + expected := []byte{ + 0x01, // username_len + 0x75, // 'u' + 0x01, // password_len + 0x70, // 'p' + 0x01, // status (Active=1) + 0x00, // has_permissions=0 + } + + if !bytes.Equal(serialized, expected) { + t.Errorf("CreateUser nil permissions:\nExpected:\t%v\nGot:\t\t%v", expected, serialized) + } +} + +func TestSerialize_CreateUser_WithPermissions(t *testing.T) { + perms := &iggcon.Permissions{ + Global: iggcon.GlobalPermissions{ + ManageServers: true, + }, + Streams: nil, + } + + request := CreateUser{ + Username: "user", + Password: "pass", + Status: iggcon.Inactive, + Permissions: perms, + } + + serialized, err := request.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize CreateUser: %v", err) + } + + permBytes, err := perms.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize Permissions: %v", err) + } + + // 1(username_len) + 4(username) + 1(password_len) + 4(password) + 1(status) + 1(has_perm) + 4(perm_len) + permBytes + expectedLen := 1 + 4 + 1 + 4 + 1 + 1 + 4 + len(permBytes) + if len(serialized) != expectedLen { + t.Fatalf("Expected length %d, got %d", expectedLen, len(serialized)) + } + + pos := 0 + + if serialized[pos] != 4 { + t.Errorf("username_len: expected 4, got %d", serialized[pos]) + } + pos++ + if string(serialized[pos:pos+4]) != "user" { + t.Errorf("username: expected 'user', got %q", serialized[pos:pos+4]) + } + pos += 4 + + if serialized[pos] != 4 { + t.Errorf("password_len: expected 4, got %d", serialized[pos]) + } + pos++ + if string(serialized[pos:pos+4]) != "pass" { + t.Errorf("password: expected 'pass', got %q", serialized[pos:pos+4]) + } + pos += 4 + + if serialized[pos] != 2 { + t.Errorf("status: expected 2 (Inactive), got %d", serialized[pos]) + } + pos++ + + if serialized[pos] != 1 { + t.Errorf("has_permissions: expected 1, got %d", serialized[pos]) + } + pos++ + + permLen := binary.LittleEndian.Uint32(serialized[pos : pos+4]) + if int(permLen) != len(permBytes) { + t.Errorf("permissions_len: expected %d, got %d", len(permBytes), permLen) + } + pos += 4 + + if !bytes.Equal(serialized[pos:], permBytes) { + t.Errorf("permissions payload mismatch") + } +} + +func TestSerialize_UpdatePermissions_NilPermissions(t *testing.T) { + userId, err := iggcon.NewIdentifier(uint32(42)) + if err != nil { + t.Fatalf("Failed to create identifier: %v", err) + } + + request := UpdatePermissions{ + UserID: userId, + Permissions: nil, + } + + serialized, err := request.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize UpdatePermissions: %v", err) + } + + userIdBytes, _ := userId.MarshalBinary() + + // [user_id bytes][has_permissions:u8=0] + expectedLen := len(userIdBytes) + 1 + if len(serialized) != expectedLen { + t.Fatalf("Expected length %d, got %d", expectedLen, len(serialized)) + } + + if !bytes.Equal(serialized[:len(userIdBytes)], userIdBytes) { + t.Errorf("UserID bytes mismatch") + } + + if serialized[len(userIdBytes)] != 0 { + t.Errorf("has_permissions: expected 0, got %d", serialized[len(userIdBytes)]) + } +} + +func TestSerialize_UpdatePermissions_WithPermissions(t *testing.T) { + userId, err := iggcon.NewIdentifier(uint32(7)) + if err != nil { + t.Fatalf("Failed to create identifier: %v", err) + } + + perms := &iggcon.Permissions{ + Global: iggcon.GlobalPermissions{ + ManageServers: true, + ReadUsers: true, + }, + Streams: nil, + } + + request := UpdatePermissions{ + UserID: userId, + Permissions: perms, + } + + serialized, err := request.MarshalBinary() + if err != nil { + t.Fatalf("Failed to serialize UpdatePermissions: %v", err) + } + + userIdBytes, _ := userId.MarshalBinary() + permBytes, _ := perms.MarshalBinary() + + // [user_id][has_permissions:u8=1][permissions_len:u32_le][permissions] + expectedLen := len(userIdBytes) + 1 + 4 + len(permBytes) + if len(serialized) != expectedLen { + t.Fatalf("Expected length %d, got %d", expectedLen, len(serialized)) + } + + pos := len(userIdBytes) + + if serialized[pos] != 1 { + t.Errorf("has_permissions: expected 1, got %d", serialized[pos]) + } + pos++ + + permLen := binary.LittleEndian.Uint32(serialized[pos : pos+4]) + if int(permLen) != len(permBytes) { + t.Errorf("permissions_len: expected %d, got %d", len(permBytes), permLen) + } + pos += 4 + + if !bytes.Equal(serialized[pos:], permBytes) { + t.Errorf("permissions payload mismatch") + } +}