186 lines
4.8 KiB
Go
186 lines
4.8 KiB
Go
package cart
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
// TestNewCartIdUniqueness generates many ids and checks for collisions.
|
|
func TestNewCartIdUniqueness(t *testing.T) {
|
|
const n = 20000
|
|
seen := make(map[string]struct{}, n)
|
|
for i := 0; i < n; i++ {
|
|
id, err := NewCartId()
|
|
if err != nil {
|
|
t.Fatalf("NewCartId error: %v", err)
|
|
}
|
|
s := id.String()
|
|
if _, exists := seen[s]; exists {
|
|
t.Fatalf("duplicate id encountered: %s", s)
|
|
}
|
|
seen[s] = struct{}{}
|
|
if s == "" {
|
|
t.Fatalf("empty string representation for id %d", id)
|
|
}
|
|
if len(s) > 11 {
|
|
t.Fatalf("encoded id length exceeds 11 chars: %s (%d)", s, len(s))
|
|
}
|
|
if id == 0 {
|
|
// We force regeneration on zero, extremely unlikely but test guards intent.
|
|
t.Fatalf("zero id generated (should be regenerated)")
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestParseCartIdRoundTrip ensures parse -> string -> parse is stable.
|
|
func TestParseCartIdRoundTrip(t *testing.T) {
|
|
id := MustNewCartId()
|
|
txt := id.String()
|
|
parsed, ok := ParseCartId(txt)
|
|
if !ok {
|
|
t.Fatalf("ParseCartId failed for valid text %q", txt)
|
|
}
|
|
if parsed != id {
|
|
t.Fatalf("round trip mismatch: original=%d parsed=%d txt=%s", id, parsed, txt)
|
|
}
|
|
}
|
|
|
|
// TestParseCartIdInvalid covers invalid inputs.
|
|
func TestParseCartIdInvalid(t *testing.T) {
|
|
invalid := []string{
|
|
"", // empty
|
|
" ", // space
|
|
"01234567890abc", // >11 chars
|
|
"!!!!", // invalid chars
|
|
"-underscore-", // invalid chars
|
|
"abc_def", // underscore invalid for base62
|
|
"0123456789ABCD", // 14 chars
|
|
}
|
|
for _, s := range invalid {
|
|
if _, ok := ParseCartId(s); ok {
|
|
t.Fatalf("expected parse failure for %q", s)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestMustParseCartIdPanics verifies panic behavior for invalid input.
|
|
func TestMustParseCartIdPanics(t *testing.T) {
|
|
defer func() {
|
|
if r := recover(); r == nil {
|
|
t.Fatalf("expected panic for invalid MustParseCartId input")
|
|
}
|
|
}()
|
|
_ = MustParseCartId("not*base62")
|
|
}
|
|
|
|
// TestJSONMarshalUnmarshalCartId verifies JSON round trip.
|
|
func TestJSONMarshalUnmarshalCartId(t *testing.T) {
|
|
id := MustNewCartId()
|
|
data, err := json.Marshal(struct {
|
|
Cart CartId `json:"cart"`
|
|
}{Cart: id})
|
|
if err != nil {
|
|
t.Fatalf("marshal error: %v", err)
|
|
}
|
|
var out struct {
|
|
Cart CartId `json:"cart"`
|
|
}
|
|
if err := json.Unmarshal(data, &out); err != nil {
|
|
t.Fatalf("unmarshal error: %v", err)
|
|
}
|
|
if out.Cart != id {
|
|
t.Fatalf("JSON round trip mismatch: have %d got %d", id, out.Cart)
|
|
}
|
|
}
|
|
|
|
// TestBase62LengthBound checks worst-case length (near max uint64).
|
|
func TestBase62LengthBound(t *testing.T) {
|
|
// Largest uint64
|
|
const maxU64 = ^uint64(0)
|
|
s := encodeBase62(maxU64)
|
|
if len(s) > 11 {
|
|
t.Fatalf("max uint64 encoded length > 11: %d (%s)", len(s), s)
|
|
}
|
|
dec, ok := decodeBase62(s)
|
|
if !ok || dec != maxU64 {
|
|
t.Fatalf("decode failed for max uint64: ok=%v dec=%d want=%d", ok, dec, maxU64)
|
|
}
|
|
}
|
|
|
|
// TestZeroEncoding ensures zero value encodes to "0" and parses back.
|
|
func TestZeroEncoding(t *testing.T) {
|
|
if s := encodeBase62(0); s != "0" {
|
|
t.Fatalf("encodeBase62(0) expected '0', got %q", s)
|
|
}
|
|
v, ok := decodeBase62("0")
|
|
if !ok || v != 0 {
|
|
t.Fatalf("decodeBase62('0') failed: ok=%v v=%d", ok, v)
|
|
}
|
|
if _, ok := ParseCartId("0"); !ok {
|
|
t.Fatalf("ParseCartId(\"0\") should succeed")
|
|
}
|
|
}
|
|
|
|
// TestSequentialParse ensures sequentially generated ids parse correctly.
|
|
func TestSequentialParse(t *testing.T) {
|
|
for i := 0; i < 1000; i++ {
|
|
id := MustNewCartId()
|
|
txt := id.String()
|
|
parsed, ok := ParseCartId(txt)
|
|
if !ok || parsed != id {
|
|
t.Fatalf("sequential parse mismatch: idx=%d orig=%d parsed=%d txt=%s", i, id, parsed, txt)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkNewCartId measures generation performance.
|
|
func BenchmarkNewCartId(b *testing.B) {
|
|
for i := 0; i < b.N; i++ {
|
|
if _, err := NewCartId(); err != nil {
|
|
b.Fatalf("NewCartId error: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// BenchmarkEncodeBase62 measures encoding performance.
|
|
func BenchmarkEncodeBase62(b *testing.B) {
|
|
// Precompute sample values
|
|
samples := make([]uint64, 1024)
|
|
for i := range samples {
|
|
// Spread bits without crypto randomness overhead
|
|
samples[i] = (uint64(i) << 53) ^ (uint64(i) * 0x9E3779B185EBCA87)
|
|
}
|
|
b.ResetTimer()
|
|
var sink string
|
|
for i := 0; i < b.N; i++ {
|
|
sink = encodeBase62(samples[i%len(samples)])
|
|
}
|
|
_ = sink
|
|
}
|
|
|
|
// BenchmarkDecodeBase62 measures decoding performance.
|
|
func BenchmarkDecodeBase62(b *testing.B) {
|
|
encoded := make([]string, 1024)
|
|
for i := range encoded {
|
|
encoded[i] = encodeBase62((uint64(i) << 32) | uint64(i))
|
|
}
|
|
b.ResetTimer()
|
|
var sum uint64
|
|
for i := 0; i < b.N; i++ {
|
|
v, ok := decodeBase62(encoded[i%len(encoded)])
|
|
if !ok {
|
|
b.Fatalf("decode failure for %s", encoded[i%len(encoded)])
|
|
}
|
|
sum ^= v
|
|
}
|
|
_ = sum
|
|
}
|
|
|
|
// ExampleCartIdString documents usage of CartId string form.
|
|
func ExampleCartId_string() {
|
|
id := MustNewCartId()
|
|
fmt.Println(len(id.String()) <= 11) // outputs true
|
|
// Output: true
|
|
}
|