package ulid import ( "fmt" "io" "log" "math/rand" "testing" "time" ) func getTestRandomSource() *rand.Rand { // nolint return rand.New(rand.NewSource(0)) } func TestTimeMSBytes(t *testing.T) { runAll := true type testData struct { TestName string RunIt bool Time time.Time Expected []byte Error bool } tests := []testData{ { TestName: "normal time", RunIt: false || runAll, Time: time.Date(2024, 02, 16, 14, 02, 15, 17, time.UTC), Expected: []byte{0x01, 0x8D, 0xB2, 0x39, 0x96, 0x58}, Error: false, }, { TestName: "zero time", RunIt: false || runAll, Time: time.Time{}, // zero time overflows when using Unix epoch Expected: []byte{}, Error: true, }, { TestName: "max time", RunIt: false || runAll, Time: time.UnixMilli(1 << 48), Expected: []byte{}, Error: true, }, { TestName: "max time minus 1", RunIt: false || runAll, Time: time.UnixMilli(int64(1<<48) - 1), Expected: []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, Error: false, }, } for _, test := range tests { t.Run(test.TestName, func(t *testing.T) { if !test.RunIt { t.SkipNow() } output, err := TimeMSBytes(test.Time) if test.Error { if err == nil { t.Errorf("expected an error") } } else { if err != nil { t.Errorf("unexpected error: %v", err) } errMsg := fmt.Sprintf("expected: %X, received %X", test.Expected, output) if len(test.Expected) != len(output) { t.Fatal(errMsg) } for i, b := range test.Expected { if b != output[i] { t.Error(errMsg) } } } }) } } func TestNewULID(t *testing.T) { runAll := true type testData struct { TestName string RunIt bool Time time.Time Entropy io.Reader Expected []byte Error bool } tests := []testData{ { TestName: "nil entropy", RunIt: false || runAll, Time: time.Time{}, Entropy: nil, Expected: nil, Error: true, }, { TestName: "Unix zero time", RunIt: false || runAll, Time: time.Unix(0, 0), Entropy: getTestRandomSource(), Expected: []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x94, 0xFD, 0xC2, 0xFA, 0x2F, 0xFC, 0xC0, 0x41, 0xD3}, Error: false, }, { TestName: "time overflow", RunIt: false || runAll, Time: time.Time{}, // zero time overflows when using Unix epoch time Entropy: getTestRandomSource(), Expected: nil, Error: true, }, { TestName: "seed 0, real time", RunIt: false || runAll, Time: time.Date(2024, 02, 16, 14, 02, 15, 17, time.UTC), Entropy: getTestRandomSource(), Expected: []byte{0x01, 0x8D, 0xB2, 0x39, 0x96, 0x58, 0x01, 0x94, 0xFD, 0xC2, 0xFA, 0x2F, 0xFC, 0xC0, 0x41, 0xD3}, Error: false, }, } for _, test := range tests { t.Run(test.TestName, func(t *testing.T) { if !test.RunIt { t.SkipNow() } output, err := NewULID(test.Time, test.Entropy) if test.Error { if err == nil { t.Errorf("expected an error") } } else { if err != nil { t.Errorf("unexpected error: %v", err) } errMsg := fmt.Sprintf("expected: %X, received %X", test.Expected, output) if len(test.Expected) != len(output) { t.Fatal(errMsg) } for i, b := range test.Expected { if b != output[i] { t.Fatal(errMsg) } } } }) } } func TestULIDString(t *testing.T) { runAll := true type testData struct { TestName string RunIt bool Time time.Time Entropy io.Reader Expected string Error bool } tests := []testData{ { TestName: "nil entropy", RunIt: false || runAll, Time: time.Time{}, Entropy: nil, Expected: "", Error: true, }, { TestName: "Unix zero time", RunIt: false || runAll, Time: time.Unix(0, 0), Entropy: getTestRandomSource(), Expected: "000000000006AFVGQT5ZYC0GEK", Error: false, }, { TestName: "time overflow", RunIt: false || runAll, Time: time.Time{}, // zero time overflows when using Unix epoch time Entropy: getTestRandomSource(), Expected: "", Error: true, }, { TestName: "seed 0, real time", RunIt: false || runAll, Time: time.Date(2024, 02, 16, 14, 02, 15, 17, time.UTC), Entropy: getTestRandomSource(), Expected: "01HPS3K5JR06AFVGQT5ZYC0GEK", Error: false, }, } for _, test := range tests { t.Run(test.TestName, func(t *testing.T) { if !test.RunIt { t.SkipNow() } output, err := NewULIDString(test.Time, test.Entropy) if test.Error { if err == nil { t.Errorf("expected an error") } } else { if err != nil { t.Errorf("unexpected error: %v", err) } if test.Expected != output { log.Fatalf("expected: %s, received %s", test.Expected, output) } } }) } } func TestULIDFromString(t *testing.T) { runAll := true type testData struct { TestName string RunIt bool Input string Expected []byte Error bool } tests := []testData{ { TestName: "empty string", RunIt: false || runAll, Input: "", Expected: nil, Error: true, }, { TestName: "valid ulid", RunIt: false || runAll, Input: "01HPS3K5JR06AFVGQT5ZYC0GEK", Expected: []byte{1, 141, 178, 57, 150, 88, 1, 148, 253, 194, 250, 47, 252, 192, 65, 211}, Error: false, }, { TestName: "time overflow", RunIt: false || runAll, Input: "ZZZZZZZZZZZZZZZZZZZZZZZZZZ", Expected: nil, Error: true, }, { TestName: "max ULID", RunIt: false || runAll, Input: "7ZZZZZZZZZZZZZZZZZZZZZZZZZ", Expected: []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, Error: false, }, } for _, test := range tests { t.Run(test.TestName, func(t *testing.T) { if !test.RunIt { t.SkipNow() } output, err := ParseULID(test.Input) if test.Error { if err == nil { t.Errorf("expected an error") } } else { if err != nil { t.Errorf("unexpected error: %v", err) } msg := fmt.Sprintf("expected %b, got %b", test.Expected, output) if len(test.Expected) != len(output) { t.Fatalf(msg) } for i := range test.Expected { if test.Expected[i] != output[i] { t.Errorf(msg) } } } }) } }