package zset import ( "context" "errors" "testing" "time" ) func TestIMEIProducerLeaseRejectsStaleWrites(t *testing.T) { for _, invalid := range []string{"missing", "expired", "other_owner", "other_run", "legacy", "invalid_json"} { t.Run(invalid, func(t *testing.T) { store := newLockStore(t) ctx := context.Background() const key = "imei:producer:active" view, err := store.WithIMEIProducerLease(key, "owner", "run") if err != nil { t.Fatal(err) } if err := store.CreateRun(ctx, RunMeta{RunID: "run"}); err != nil { t.Fatal(err) } switch invalid { case "expired": if _, err := store.redis.Do(ctx, "SET", key, `{"owner":"owner","runId":"run"}`, "PX", 1); err != nil { t.Fatal(err) } time.Sleep(10 * time.Millisecond) case "other_owner": _, err = store.redis.Do(ctx, "SET", key, `{"owner":"new-owner","runId":"run"}`) case "other_run": _, err = store.redis.Do(ctx, "SET", key, `{"owner":"owner","runId":"other-run"}`) case "legacy": _, err = store.redis.Do(ctx, "SET", key, "owner") case "invalid_json": _, err = store.redis.Do(ctx, "SET", key, "{") } if err != nil { t.Fatal(err) } _, err = view.EnqueueRunBatch(ctx, "run", "ch", []Task{{TaskID: "old", RunID: "run", ChanKey: "ch"}}, time.Now()) if !errors.Is(err, ErrProducerLeaseLost) { t.Fatalf("enqueue did not report lease loss: %v", err) } if err := view.MarkProducerDone(ctx, "run"); !errors.Is(err, ErrProducerLeaseLost) { t.Fatalf("done did not report lease loss: %v", err) } if err := view.CreateRun(ctx, RunMeta{RunID: "run", ScopeDigest: "stale"}); !errors.Is(err, ErrProducerLeaseLost) { t.Fatalf("metadata write did not report lease loss: %v", err) } meta, err := store.GetRunMeta(ctx, "run") if err != nil || meta.ActiveCount != 0 || meta.ProducerDone || meta.ScopeDigest != "" { t.Fatalf("stale state mutation: %+v, %v", meta, err) } channels, err := store.RunChannels(ctx, "run") if err != nil || len(channels) != 0 { t.Fatalf("stale channel mutation: %v, %v", channels, err) } // The private view never changes the consumer/other-producer store. if added, err := store.EnqueueRunBatch(ctx, "run", "ch", []Task{{TaskID: "next", RunID: "run", ChanKey: "ch"}}, time.Now()); err != nil || added != 1 { t.Fatalf("shared queue semantics changed: %d, %v", added, err) } }) } } func TestIMEIProducerLeasePreservesValidOwnerSemantics(t *testing.T) { store := newLockStore(t) ctx := context.Background() const key = "imei:producer:active" if ok, err := store.AcquireLockOrTakeover(ctx, key, "owner", "run", time.Minute); err != nil || !ok { t.Fatalf("acquire: %v, %v", ok, err) } view, err := store.WithIMEIProducerLease(key, "owner", "run") if err != nil { t.Fatal(err) } if err := view.CreateRun(ctx, RunMeta{RunID: "run", ScopeDigest: "scope"}); err != nil { t.Fatal(err) } tasks := []Task{{TaskID: "one", RunID: "run", ChanKey: "ch"}, {TaskID: "two", RunID: "run", ChanKey: "ch"}} for _, want := range []int{2, 0} { if added, err := view.EnqueueRunBatch(ctx, "run", "ch", tasks, time.Now()); err != nil || added != want { t.Fatalf("idempotent batch pairs: %d, %v; want %d", added, err, want) } } if err := view.MarkProducerDone(ctx, "run"); err != nil { t.Fatal(err) } meta, err := store.GetRunMeta(ctx, "run") if err != nil || meta.ActiveCount != 2 || !meta.ProducerDone || meta.ScopeDigest != "scope" { t.Fatalf("valid owner state: %+v, %v", meta, err) } }