Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion pkg/util/rand/rand.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@ func String(n int) (string, error) {

// StringFromCharset generates, from a given charset, a cryptographically-secure pseudo-random string of a given length.
func StringFromCharset(n int, charset string) (string, error) {
if n<0{
return "", fmt.Errorf("n must be greater than 0")
}
if n > 0 && len(charset) == 0 {
return "", fmt.Errorf("charset must not be empty when n > 0")
}
Comment thread
Harsh4902 marked this conversation as resolved.
Comment thread
Harsh4902 marked this conversation as resolved.
b := make([]byte, n)
maxIdx := big.NewInt(int64(len(charset)))
for i := 0; i < n; i++ {
randIdx, err := rand.Int(rand.Reader, maxIdx)
if err != nil {
return "", fmt.Errorf("failed to generate random string: %w", err)
}
// randIdx is necessarily safe to convert to int, because the max came from an int.
randIdxInt := int(randIdx.Int64())
b[i] = charset[randIdxInt]
}
Expand Down
114 changes: 114 additions & 0 deletions pkg/util/rand/rand_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package rand

import (
"strings"
"testing"
)

func TestString(t *testing.T) {
t.Parallel()

tests := []struct {
name string
length int
}{
{name: "zero length", length: 0},
{name: "single character", length: 1},
{name: "short string", length: 8},
{name: "longer string", length: 32},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()

got, err := String(tt.length)
if err != nil {
t.Fatalf("String(%d) returned error: %v", tt.length, err)
}
if len(got) != tt.length {
t.Fatalf("String(%d) length = %d, want %d", tt.length, len(got), tt.length)
}
for i := 0; i < len(got); i++ {
if !strings.Contains(letterBytes, string(got[i])) {
t.Fatalf("String(%d) contains invalid character %q at index %d", tt.length, got[i], i)
}
}
})
}
}

func TestStringRepeatedCalls(t *testing.T) {
t.Parallel()


const (
length = 32
attempts=5
)

for i := 0; i < attempts; i++ {
got, err := String(length)
if err != nil {
t.Fatalf("String(%d) returned error on attempt %d: %v", length, i, err)
}
if len(got) != length {
t.Fatalf("String(%d) length on attempt %d = %d, want %d", length, i, len(got), length)
}
}
}
Comment thread
Harsh4902 marked this conversation as resolved.
Outdated

func TestStringFromCharset(t *testing.T) {
t.Parallel()

const charset = "01"

got, err := StringFromCharset(16, charset)
if err != nil {
t.Fatalf("StringFromCharset returned error: %v", err)
}
if len(got) != 16 {
t.Fatalf("StringFromCharset length = %d, want 16", len(got))
}
for i := 0; i < len(got); i++ {
if !strings.Contains(charset, string(got[i])) {
t.Fatalf("StringFromCharset contains invalid character %q at index %d", got[i], i)
}
}
}

func TestStringFromCharsetZeroLength(t *testing.T) {
t.Parallel()

got, err := StringFromCharset(0, "abc")
if err != nil {
t.Fatalf("StringFromCharset(0) returned error: %v", err)
}
if got != "" {
t.Fatalf("StringFromCharset(0) = %q, want empty string", got)
}
}

func TestStringFromCharsetSingleCharacter(t *testing.T) {
t.Parallel()

got, err := StringFromCharset(10, "x")
if err != nil {
t.Fatalf("StringFromCharset returned error: %v", err)
}
if got != "xxxxxxxxxx" {
t.Fatalf("StringFromCharset with single-character charset = %q, want %q", got, "xxxxxxxxxx")
}
}

func TestStringFromCharsetEmptyCharset(t *testing.T) {
t.Parallel()

got, err := StringFromCharset(5, "")
if err == nil {
t.Fatal("StringFromCharset with empty charset expected error, got nil")
}
if got != "" {
t.Fatalf("StringFromCharset with empty charset = %q, want empty string on error", got)
}
}