Files
go-jdenticon/jdenticon/hash_test.go
Kevin McIntyre f84b511895 init
2025-06-18 01:00:00 -04:00

481 lines
12 KiB
Go

package jdenticon
import (
"strings"
"testing"
)
func TestComputeHash(t *testing.T) {
tests := []struct {
name string
input interface{}
expected string // Known SHA-1 hash values
}{
{
name: "empty string",
input: "",
expected: "da39a3ee5e6b4b0d3255bfef95601890afd80709",
},
{
name: "simple string",
input: "hello",
expected: "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d",
},
{
name: "email address",
input: "user@example.com",
expected: "63a710569261a24b3766275b7000ce8d7b32e2f7",
},
{
name: "nil input",
input: nil,
expected: "da39a3ee5e6b4b0d3255bfef95601890afd80709", // Same as empty string
},
{
name: "integer input",
input: 123,
expected: "40bd001563085fc35165329ea1ff5c5ecbdbbeef", // SHA-1 of "123"
},
{
name: "byte slice input",
input: []byte("test"),
expected: "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := ComputeHash(tt.input)
if result != tt.expected {
t.Errorf("ComputeHash(%v) = %s, want %s", tt.input, result, tt.expected)
}
})
}
}
func TestHashValue(t *testing.T) {
// Test that HashValue produces the same result as ComputeHash for strings
testStrings := []string{"", "hello", "user@example.com", "test123"}
for _, str := range testStrings {
hashValue := HashValue(str)
computeHash := ComputeHash(str)
if hashValue != computeHash {
t.Errorf("HashValue(%s) = %s, but ComputeHash(%s) = %s", str, hashValue, str, computeHash)
}
}
}
func TestIsValidHash(t *testing.T) {
tests := []struct {
name string
input string
expected bool
}{
{
name: "valid long hash",
input: "da39a3ee5e6b4b0d3255bfef95601890afd80709",
expected: true,
},
{
name: "valid minimum length",
input: "da39a3ee5e6",
expected: true,
},
{
name: "too short",
input: "da39a3ee5e",
expected: false,
},
{
name: "invalid character",
input: "da39a3ee5e6g4b0d3255bfef95601890afd80709",
expected: false,
},
{
name: "uppercase valid",
input: "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709",
expected: true,
},
{
name: "empty string",
input: "",
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := isValidHash(tt.input)
if result != tt.expected {
t.Errorf("isValidHash(%s) = %v, want %v", tt.input, result, tt.expected)
}
})
}
}
func TestParseHex(t *testing.T) {
hash := "da39a3ee5e6b4b0d3255bfef95601890afd80709"
tests := []struct {
name string
startPosition int
octets int
expected int
}{
{
name: "first character",
startPosition: 0,
octets: 1,
expected: 0xd, // 'd' in hex
},
{
name: "two characters",
startPosition: 0,
octets: 2,
expected: 0xda,
},
{
name: "middle position",
startPosition: 10,
octets: 1,
expected: 0x6, // '6' at position 10
},
{
name: "negative index",
startPosition: -1,
octets: 1,
expected: 0x9, // last character '9'
},
{
name: "negative index multiple chars",
startPosition: -2,
octets: 2,
expected: 0x09, // last two characters "09"
},
{
name: "out of bounds",
startPosition: 100,
octets: 1,
expected: 0,
},
{
name: "zero octets reads to end",
startPosition: -7,
octets: 0,
expected: 0xfd80709, // Last 7 characters "fd80709"
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := parseHex(hash, tt.startPosition, tt.octets)
if err != nil {
// For out of bounds case, we expect an error but return 0
if tt.expected == 0 && tt.name == "out of bounds" {
// This is expected
return
}
t.Errorf("parseHex(%s, %d, %d) unexpected error: %v", hash, tt.startPosition, tt.octets, err)
return
}
if result != tt.expected {
t.Errorf("parseHex(%s, %d, %d) = %d, want %d", hash, tt.startPosition, tt.octets, result, tt.expected)
}
})
}
}
func TestParseHexErrors(t *testing.T) {
tests := []struct {
name string
hash string
startPosition int
octets int
expectError bool
errorContains string
}{
{
name: "invalid hex character",
hash: "da39g3ee5e6b4b0d3255bfef95601890afd80709",
startPosition: 4,
octets: 1,
expectError: true,
errorContains: "failed to parse hex",
},
{
name: "out of bounds positive",
hash: "da39a3ee",
startPosition: 10,
octets: 1,
expectError: true,
errorContains: "out of bounds",
},
{
name: "out of bounds negative",
hash: "da39a3ee",
startPosition: -20,
octets: 1,
expectError: true,
errorContains: "out of bounds",
},
{
name: "valid hex",
hash: "da39a3ee5e6b4b0d3255bfef95601890afd80709",
startPosition: 0,
octets: 2,
expectError: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := parseHex(tt.hash, tt.startPosition, tt.octets)
if tt.expectError {
if err == nil {
t.Errorf("parseHex(%s, %d, %d) expected error, got nil", tt.hash, tt.startPosition, tt.octets)
return
}
if tt.errorContains != "" && !strings.Contains(err.Error(), tt.errorContains) {
t.Errorf("parseHex(%s, %d, %d) error = %v, want error containing %s", tt.hash, tt.startPosition, tt.octets, err, tt.errorContains)
}
} else {
if err != nil {
t.Errorf("parseHex(%s, %d, %d) unexpected error: %v", tt.hash, tt.startPosition, tt.octets, err)
}
// For valid case, just ensure no error and result >= 0
if result < 0 {
t.Errorf("parseHex(%s, %d, %d) = %d, want >= 0", tt.hash, tt.startPosition, tt.octets, result)
}
}
})
}
}
func TestParseHash(t *testing.T) {
tests := []struct {
name string
input string
expectError bool
expected []byte
}{
{
name: "valid hex string",
input: "da39a3ee",
expectError: false,
expected: []byte{0xda, 0x39, 0xa3, 0xee},
},
{
name: "with 0x prefix",
input: "0xda39a3ee",
expectError: false,
expected: []byte{0xda, 0x39, 0xa3, 0xee},
},
{
name: "with 0X prefix",
input: "0Xda39a3ee",
expectError: false,
expected: []byte{0xda, 0x39, 0xa3, 0xee},
},
{
name: "uppercase hex",
input: "DA39A3EE",
expectError: false,
expected: []byte{0xda, 0x39, 0xa3, 0xee},
},
{
name: "empty string",
input: "",
expectError: true,
expected: nil,
},
{
name: "odd length",
input: "da39a3e",
expectError: true,
expected: nil,
},
{
name: "invalid character",
input: "da39g3ee",
expectError: true,
expected: nil,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ParseHash(tt.input)
if tt.expectError {
if err == nil {
t.Errorf("ParseHash(%s) expected error, got nil", tt.input)
}
} else {
if err != nil {
t.Errorf("ParseHash(%s) unexpected error: %v", tt.input, err)
}
if len(result) != len(tt.expected) {
t.Errorf("ParseHash(%s) length = %d, want %d", tt.input, len(result), len(tt.expected))
}
for i, b := range result {
if i < len(tt.expected) && b != tt.expected[i] {
t.Errorf("ParseHash(%s)[%d] = %x, want %x", tt.input, i, b, tt.expected[i])
}
}
}
})
}
}
func TestExtractHue(t *testing.T) {
// Test with a known hash to ensure consistent behavior with JavaScript
hash := "da39a3ee5e6b4b0d3255bfef95601890afd80709"
hue := ExtractHue(hash)
// Verify it's in [0,1] range
if hue < 0.0 || hue > 1.0 {
t.Errorf("ExtractHue(%s) = %f, want value in [0,1] range", hash, hue)
}
// Verify it matches manual calculation
expectedValue, err := parseHex(hash, -7, 0)
if err != nil {
t.Fatalf("parseHex failed: %v", err)
}
expectedHue := float64(expectedValue) / 0xfffffff
if hue != expectedHue {
t.Errorf("ExtractHue(%s) = %f, want %f", hash, hue, expectedHue)
}
// Test error cases - should return 0.0 for JavaScript compatibility
t.Run("invalid hash", func(t *testing.T) {
invalidHash := "invalid-hash-with-non-hex-chars"
hue := ExtractHue(invalidHash)
if hue != 0.0 {
t.Errorf("ExtractHue with invalid hash should return 0.0, got %f", hue)
}
})
t.Run("short hash", func(t *testing.T) {
shortHash := "short"
hue := ExtractHue(shortHash)
if hue != 0.0 {
t.Errorf("ExtractHue with short hash should return 0.0, got %f", hue)
}
})
}
func TestExtractShapeIndex(t *testing.T) {
hash := "da39a3ee5e6b4b0d3255bfef95601890afd80709"
tests := []struct {
position int
expected int
}{
{1, 0xa}, // 'a' at position 1
{2, 0x3}, // '3' at position 2
{4, 0xa}, // 'a' at position 4
}
for _, tt := range tests {
result := ExtractShapeIndex(hash, tt.position)
if result != tt.expected {
t.Errorf("ExtractShapeIndex(%s, %d) = %d, want %d", hash, tt.position, result, tt.expected)
}
}
// Test error cases - should return 0 for JavaScript compatibility
t.Run("invalid hash", func(t *testing.T) {
invalidHash := "invalid-hash-with-non-hex-chars"
result := ExtractShapeIndex(invalidHash, 0)
if result != 0 {
t.Errorf("ExtractShapeIndex with invalid hash should return 0, got %d", result)
}
})
t.Run("out of bounds position", func(t *testing.T) {
result := ExtractShapeIndex(hash, 100)
if result != 0 {
t.Errorf("ExtractShapeIndex with out of bounds position should return 0, got %d", result)
}
})
}
func TestExtractColorIndex(t *testing.T) {
hash := "da39a3ee5e6b4b0d3255bfef95601890afd80709"
// Test modulo behavior
availableColors := 5
position := 8
rawValue, err := parseHex(hash, position, 1)
if err != nil {
t.Fatalf("parseHex failed: %v", err)
}
expected := rawValue % availableColors
result := ExtractColorIndex(hash, position, availableColors)
if result != expected {
t.Errorf("ExtractColorIndex(%s, %d, %d) = %d, want %d", hash, position, availableColors, result, expected)
}
// Test with zero availableColors
result = ExtractColorIndex(hash, position, 0)
if result != rawValue {
t.Errorf("ExtractColorIndex(%s, %d, 0) = %d, want %d", hash, position, result, rawValue)
}
// Test error cases - should return 0 for JavaScript compatibility
t.Run("invalid hash", func(t *testing.T) {
invalidHash := "invalid-hash-with-non-hex-chars"
result := ExtractColorIndex(invalidHash, 0, 5)
if result != 0 {
t.Errorf("ExtractColorIndex with invalid hash should return 0, got %d", result)
}
})
t.Run("out of bounds position", func(t *testing.T) {
result := ExtractColorIndex(hash, 100, 5)
if result != 0 {
t.Errorf("ExtractColorIndex with out of bounds position should return 0, got %d", result)
}
})
}
func TestExtractRotation(t *testing.T) {
hash := "da39a3ee5e6b4b0d3255bfef95601890afd80709"
tests := []struct {
position int
expected int
}{
{0, 0xd}, // 'd' at position 0
{1, 0xa}, // 'a' at position 1
{5, 0x3}, // '3' at position 5
}
for _, tt := range tests {
result := ExtractRotation(hash, tt.position)
if result != tt.expected {
t.Errorf("ExtractRotation(%s, %d) = %d, want %d", hash, tt.position, result, tt.expected)
}
}
// Test error cases - should return 0 for JavaScript compatibility
t.Run("invalid hash", func(t *testing.T) {
invalidHash := "invalid-hash-with-non-hex-chars"
result := ExtractRotation(invalidHash, 0)
if result != 0 {
t.Errorf("ExtractRotation with invalid hash should return 0, got %d", result)
}
})
t.Run("out of bounds position", func(t *testing.T) {
result := ExtractRotation(hash, 100)
if result != 0 {
t.Errorf("ExtractRotation with out of bounds position should return 0, got %d", result)
}
})
}