package contract_test import ( "context" "errors" "strings" "testing" "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/fullsync/contract" "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/queue/zset" "github.com/gogf/gf/v2/container/gvar" ) type unusedRedis struct{} func (unusedRedis) Do(context.Context, string, ...any) (*gvar.Var, error) { panic("naming contracts must not access Redis") } func (unusedRedis) Eval(context.Context, string, int64, []string, []any) (*gvar.Var, error) { panic("naming contracts must not access Redis") } func TestKeysMatchExistingQueue(t *testing.T) { for _, prefix := range []string{"", "flow:", "sim-status:", "custom-prefix"} { keys, err := contract.NewKeys(prefix) if err != nil { t.Fatal(err) } store, err := zset.New(unusedRedis{}, prefix) if err != nil { t.Fatal(err) } for _, pair := range []struct { old, next func(string) (string, error) }{ {store.RunMetaKey, keys.RunMetaKey}, {store.RunChannelsKey, keys.RunChannelsKey}, {store.TaskStateKey, keys.TaskStateKey}, {store.FailedKey, keys.FailedKey}, } { old, oldErr := pair.old("run:1") next, nextErr := pair.next("run:1") if oldErr != nil || nextErr != nil || old != next || !strings.Contains(next, "{run:1}") { t.Errorf("run naming changed: old=%q new=%q errors=%v/%v", old, next, oldErr, nextErr) } } for _, pair := range []struct { old, next func(string, int, string) (string, error) }{ {store.RoundActiveKey, keys.RoundActiveKey}, {store.RoundWaitingKey, keys.RoundWaitingKey}, {store.RoundLeaseKey, keys.RoundLeaseKey}, } { old, oldErr := pair.old("run:1", 3, "channel") next, nextErr := pair.next("run:1", 3, "channel") if oldErr != nil || nextErr != nil || old != next { t.Errorf("round naming changed: old=%q new=%q errors=%v/%v", old, next, oldErr, nextErr) } } if keys.ActiveRunsKey() != store.ActiveRunsKey() || keys.FailedRunsKey() != store.FailedRunsKey() { t.Fatal("existing global index names changed") } } } func TestNewKeysAndClusterBoundaries(t *testing.T) { keys, _ := contract.NewKeys("flow:") stats, _ := keys.RoundStatsKey("run", 2) archive, _ := keys.ArchiveStateKey("run") if stats != "flow:{run}:round:2:stats" || archive != "flow:{run}:archive" { t.Fatalf("new run keys changed: %s, %s", stats, archive) } action, _ := keys.ReplayActionKey("a") lease, _ := keys.ReplayLeaseKey("a") idempotency, _ := keys.ReplayIdempotencyKey("source", "a{different-slot}:key") for _, key := range []string{action, lease, idempotency, keys.PendingReplayActionsKey()} { if !strings.HasPrefix(key, "flow:{replay}:") || strings.Count(key, "{") != 1 { t.Fatalf("replay key escaped action slot: %s", key) } } if action != "flow:{replay}:action:a:meta" || lease != "flow:{replay}:action:a:lease" { t.Fatal("action naming changed") } for _, key := range []string{keys.ActiveRunsKey(), keys.FailedRunsKey(), keys.AllRunsKey(), keys.PendingArchivesKey()} { if strings.ContainsAny(key, "{}") { t.Fatalf("global index unexpectedly shares run slot: %s", key) } } first, _ := keys.ReplayIdempotencyKey("a:b", "c") second, _ := keys.ReplayIdempotencyKey("a", "b:c") repeat, _ := keys.ReplayIdempotencyKey("a:b", "c") otherSource, _ := keys.ReplayIdempotencyKey("other", "c") if first == second || first == otherSource || first != repeat { t.Fatal("idempotency scope is ambiguous or unstable") } } func TestInvalidKeyParts(t *testing.T) { keys := contract.Keys{} for _, id := range []string{"", " ", "trim ", "{run}", "run}", "run/path", "run\\path", "run\n", "invalid\xff"} { if _, err := keys.RunMetaKey(id); !errors.Is(err, contract.ErrInvalidArgument) { t.Errorf("invalid ID %q returned %v", id, err) } } if _, err := contract.NewKeys("prefix:{wrong}:"); !errors.Is(err, contract.ErrInvalidArgument) { t.Fatalf("invalid prefix returned %v", err) } if _, err := keys.RoundStatsKey("run", 0); !errors.Is(err, contract.ErrInvalidArgument) { t.Fatalf("invalid round returned %v", err) } if _, err := keys.RoundActiveKey("run", 1, " "); !errors.Is(err, contract.ErrInvalidArgument) { t.Fatalf("invalid channel returned %v", err) } if key, err := keys.RunMetaKey("run"); err != nil || key != "flow:{run}:meta" { t.Fatalf("zero-value keys differ from legacy default: %s %v", key, err) } }