261 lines
6.4 KiB
Go
261 lines
6.4 KiB
Go
package timerx
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/yuninks/timerx/logger"
|
|
)
|
|
|
|
func TestOnceBuildRedisKey(t *testing.T) {
|
|
once := &Once{keySeparator: "[:]", keyPrefix: "test_prefix"}
|
|
|
|
key := once.buildRedisKey("normal", "task123")
|
|
if key != "normal[:]task123" {
|
|
t.Errorf("unexpected key: %s", key)
|
|
}
|
|
}
|
|
|
|
func TestOnceParseRedisKey(t *testing.T) {
|
|
once := &Once{keySeparator: "[:]"}
|
|
|
|
taskType, taskId, err := once.parseRedisKey("normal[:]task123")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if taskType != "normal" {
|
|
t.Errorf("unexpected taskType: %s", taskType)
|
|
}
|
|
if taskId != "task123" {
|
|
t.Errorf("unexpected taskId: %s", taskId)
|
|
}
|
|
|
|
_, _, err = once.parseRedisKey("invalid")
|
|
if err == nil {
|
|
t.Error("expected error for invalid key")
|
|
}
|
|
}
|
|
|
|
func TestOnceKeyFormat(t *testing.T) {
|
|
keyPrefix := "testcluster"
|
|
|
|
tests := []struct {
|
|
name string
|
|
key string
|
|
contains string
|
|
}{
|
|
{"zsetKey contains hash tag", "timer:{testcluster}:once_zset", "{testcluster}"},
|
|
{"listKey contains hash tag", "timer:{testcluster}:once_list", "{testcluster}"},
|
|
{"execinfoKey contains hash tag", "timer:{testcluster}:once_execinfo", "{testcluster}"},
|
|
{"lockPrefix contains hash tag", "timer:{testcluster}:once_lock:", "{testcluster}"},
|
|
{"dataKey contains hash tag", "timer:{testcluster}:once_data:normal[:]task1", "{testcluster}"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if !strings.Contains(tt.key, tt.contains) {
|
|
t.Errorf("key %q does not contain hash tag %q", tt.key, tt.contains)
|
|
}
|
|
})
|
|
}
|
|
|
|
_ = keyPrefix
|
|
}
|
|
|
|
func TestOnceDataKeyFormat(t *testing.T) {
|
|
dataKey := "timer:{myapp}:once_data:normal[:]task1"
|
|
|
|
if !strings.Contains(dataKey, "{myapp}") {
|
|
t.Errorf("data key %q missing hash tag {myapp}", dataKey)
|
|
}
|
|
if !strings.HasPrefix(dataKey, "timer:") {
|
|
t.Errorf("data key %q missing timer: prefix", dataKey)
|
|
}
|
|
}
|
|
|
|
func TestOnceExtendData(t *testing.T) {
|
|
taskTimes := []time.Time{
|
|
time.Now().Add(10 * time.Second),
|
|
time.Now().Add(5 * time.Second),
|
|
time.Now().Add(15 * time.Second),
|
|
}
|
|
|
|
ed := extendData{
|
|
TaskTimes: taskTimes,
|
|
Data: "test data",
|
|
RunCount: 0,
|
|
JobType: jobTypeOnce,
|
|
}
|
|
|
|
if len(ed.TaskTimes) != 3 {
|
|
t.Errorf("expected 3 task times, got %d", len(ed.TaskTimes))
|
|
}
|
|
if ed.Data != "test data" {
|
|
t.Errorf("unexpected data: %v", ed.Data)
|
|
}
|
|
if ed.RunCount != 0 {
|
|
t.Errorf("unexpected runCount: %d", ed.RunCount)
|
|
}
|
|
}
|
|
|
|
func TestOnceSaveValidation(t *testing.T) {
|
|
var once Once
|
|
|
|
once = Once{keyPrefix: "test_app", keySeparator: "[:]"}
|
|
|
|
if once.keySeparator != "[:]" {
|
|
t.Errorf("unexpected keySeparator: %s", once.keySeparator)
|
|
}
|
|
if once.keyPrefix != "test_app" {
|
|
t.Errorf("unexpected keyPrefix: %s", once.keyPrefix)
|
|
}
|
|
}
|
|
|
|
func TestOnceKeyPrefixConsistency(t *testing.T) {
|
|
keyPrefix := "myapp_v2"
|
|
|
|
zsetKey := "timer:{myapp_v2}:once_zset"
|
|
listKey := "timer:{myapp_v2}:once_list"
|
|
dataKey := "timer:{myapp_v2}:once_data:urgent[:]task99"
|
|
|
|
for _, k := range []string{zsetKey, listKey, dataKey} {
|
|
if !strings.Contains(k, "{"+keyPrefix+"}") {
|
|
t.Errorf("key %q missing hash tag for keyPrefix %q", k, keyPrefix)
|
|
}
|
|
}
|
|
|
|
zsetHash := extractHashTagForOnce(zsetKey)
|
|
listHash := extractHashTagForOnce(listKey)
|
|
dataHash := extractHashTagForOnce(dataKey)
|
|
|
|
if zsetHash != listHash || listHash != dataHash {
|
|
t.Errorf("all Once keys must share same hash tag: %q %q %q", zsetHash, listHash, dataHash)
|
|
}
|
|
}
|
|
|
|
func extractHashTagForOnce(key string) string {
|
|
start := strings.Index(key, "{")
|
|
end := strings.Index(key, "}")
|
|
if start >= 0 && end > start {
|
|
return key[start+1 : end]
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func TestInitOnce_NilRedis(t *testing.T) {
|
|
ctx := context.Background()
|
|
_, err := InitOnce(ctx, nil, "test", nil)
|
|
if err == nil {
|
|
t.Error("expected error for nil redis")
|
|
}
|
|
}
|
|
|
|
func TestInitOnce_NilCallback(t *testing.T) {
|
|
ctx := context.Background()
|
|
// nil worker callback should be rejected before redis check
|
|
_, err := InitOnce(ctx, nil, "test", nil)
|
|
if err == nil {
|
|
t.Error("expected error for nil callback")
|
|
}
|
|
}
|
|
|
|
func TestOnceErrorVariables(t *testing.T) {
|
|
// 验证 sentinel errors 可用且可比较
|
|
if ErrExecuteTime == nil {
|
|
t.Error("ErrExecuteTime should not be nil")
|
|
}
|
|
if ErrRunCount == nil {
|
|
t.Error("ErrRunCount should not be nil")
|
|
}
|
|
if ErrDelayTime == nil {
|
|
t.Error("ErrDelayTime should not be nil")
|
|
}
|
|
if ErrTaskExists == nil {
|
|
t.Error("ErrTaskExists should not be nil")
|
|
}
|
|
if ErrTaskIdExists == nil {
|
|
t.Error("ErrTaskIdExists should not be nil")
|
|
}
|
|
}
|
|
|
|
func TestClusterErrorVariables(t *testing.T) {
|
|
if ErrMonthDay == nil {
|
|
t.Error("ErrMonthDay should not be nil")
|
|
}
|
|
if ErrWeekday == nil {
|
|
t.Error("ErrWeekday should not be nil")
|
|
}
|
|
if ErrHour == nil {
|
|
t.Error("ErrHour should not be nil")
|
|
}
|
|
if ErrMinute == nil {
|
|
t.Error("ErrMinute should not be nil")
|
|
}
|
|
if ErrSecond == nil {
|
|
t.Error("ErrSecond should not be nil")
|
|
}
|
|
if ErrIntervalTime == nil {
|
|
t.Error("ErrIntervalTime should not be nil")
|
|
}
|
|
if ErrBaseTime == nil {
|
|
t.Error("ErrBaseTime should not be nil")
|
|
}
|
|
if ErrCronExpression == nil {
|
|
t.Error("ErrCronExpression should not be nil")
|
|
}
|
|
if ErrCronParser == nil {
|
|
t.Error("ErrCronParser should not be nil")
|
|
}
|
|
}
|
|
|
|
func TestOnceKeySeparator(t *testing.T) {
|
|
// buildRedisKey 使用 keySeparator
|
|
o := &Once{keySeparator: "[:]"}
|
|
key := o.buildRedisKey("normal", "task_1")
|
|
if key != "normal[:]task_1" {
|
|
t.Errorf("unexpected key: %s", key)
|
|
}
|
|
|
|
// parseRedisKey
|
|
tp, id, err := o.parseRedisKey(key)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if tp != "normal" || id != "task_1" {
|
|
t.Errorf("unexpected parse result: %s, %s", tp, id)
|
|
}
|
|
|
|
// custom separator
|
|
o = &Once{keySeparator: ":"}
|
|
key = o.buildRedisKey("urgent", "id123")
|
|
if key != "urgent:id123" {
|
|
t.Errorf("unexpected key with custom separator: %s", key)
|
|
}
|
|
}
|
|
|
|
func TestOnceSanitizedKeyPrefix(t *testing.T) {
|
|
// 模拟 sanitizeKeyPrefix 对 Once key 的影响
|
|
raw := "app{v1}"
|
|
sanitized := sanitizeKeyPrefix(raw)
|
|
if sanitized != "app_v1_" {
|
|
t.Errorf("unexpected sanitized prefix: %s", sanitized)
|
|
}
|
|
|
|
dataKey := fmt.Sprintf("timer:{%s}:once_data:%s", sanitized, "type[:]id")
|
|
zsetKey := fmt.Sprintf("timer:{%s}:once_zset", sanitized)
|
|
|
|
if !strings.Contains(dataKey, "{app_v1_}") {
|
|
t.Errorf("data key missing expected hash tag: %s", dataKey)
|
|
}
|
|
if !strings.Contains(zsetKey, "{app_v1_}") {
|
|
t.Errorf("zset key missing expected hash tag: %s", zsetKey)
|
|
}
|
|
}
|
|
|
|
func newDefaultLoggerForTest() logger.Logger {
|
|
return logger.NewLogger()
|
|
}
|