package discovery import ( "testing" "time" ) func TestRegistryUpdateHeartbeatByKey_UpdatesOnlyTargetEntry(t *testing.T) { now := time.Date(2026, 2, 27, 12, 0, 0, 0, time.UTC) oldHeartbeat := now.Add(-45 * time.Second) registry := NewRegistry() oldVersion := RegistryEntry{ ID: "mcards", InstanceID: "inst-1", Service: "CARD", Rail: RailCardPayout, Operations: []string{OperationSend}, Version: "3.0.0-old", Status: "ok", Health: HealthParams{TimeoutSec: 30}, LastHeartbeat: oldHeartbeat, } newVersion := RegistryEntry{ ID: "mcards", InstanceID: "inst-1", Service: "CARD", Rail: RailCardPayout, Operations: []string{OperationSend}, Version: "3.0.0-new", Status: "ok", Health: HealthParams{TimeoutSec: 30}, LastHeartbeat: oldHeartbeat, } registry.UpsertEntry(oldVersion, now) registry.UpsertEntry(newVersion, now) oldKey := registryEntryKey(normalizeEntry(oldVersion)) newKey := registryEntryKey(normalizeEntry(newVersion)) result, found := registry.UpdateHeartbeatByKey(newKey, "ok", now, now) if !found { t.Fatalf("expected heartbeat target key to exist: %q", newKey) } if got := registryEntryKey(normalizeEntry(result.Entry)); got != newKey { t.Fatalf("unexpected updated key: got=%q want=%q", got, newKey) } entries := registry.List(now, false) if len(entries) != 2 { t.Fatalf("unexpected entries count: got=%d want=2", len(entries)) } sawOld := false sawNew := false for _, entry := range entries { key := registryEntryKey(normalizeEntry(entry)) switch key { case oldKey: sawOld = true if !entry.LastHeartbeat.Equal(oldHeartbeat) { t.Fatalf("old key heartbeat changed unexpectedly: got=%s want=%s", entry.LastHeartbeat, oldHeartbeat) } if entry.Healthy { t.Fatalf("old key should remain unhealthy") } case newKey: sawNew = true if !entry.LastHeartbeat.Equal(now) { t.Fatalf("new key heartbeat not updated: got=%s want=%s", entry.LastHeartbeat, now) } if !entry.Healthy { t.Fatalf("new key should be healthy after heartbeat") } } } if !sawOld { t.Fatalf("old key entry not found after update: %q", oldKey) } if !sawNew { t.Fatalf("new key entry not found after update: %q", newKey) } } func TestRegistryUpdateHeartbeat_LegacyFallbackUpdatesAllMatchingInstanceEntries(t *testing.T) { now := time.Date(2026, 2, 27, 12, 0, 0, 0, time.UTC) oldHeartbeat := now.Add(-45 * time.Second) registry := NewRegistry() entries := []RegistryEntry{ { ID: "mcards", InstanceID: "inst-1", Service: "CARD", Rail: RailCardPayout, Operations: []string{OperationSend}, Version: "3.0.0-old", Status: "ok", Health: HealthParams{TimeoutSec: 30}, LastHeartbeat: oldHeartbeat, }, { ID: "mcards", InstanceID: "inst-1", Service: "CARD", Rail: RailCardPayout, Operations: []string{OperationSend}, Version: "3.0.0-new", Status: "ok", Health: HealthParams{TimeoutSec: 30}, LastHeartbeat: oldHeartbeat, }, } for _, entry := range entries { registry.UpsertEntry(entry, now) } results := registry.UpdateHeartbeat("mcards", "inst-1", "ok", now, now) if len(results) != 2 { t.Fatalf("unexpected updated count: got=%d want=2", len(results)) } all := registry.List(now, false) for _, entry := range all { if !entry.LastHeartbeat.Equal(now) { t.Fatalf("entry heartbeat not updated: key=%s got=%s want=%s", registryEntryKey(normalizeEntry(entry)), entry.LastHeartbeat, now) } if !entry.Healthy { t.Fatalf("entry should be healthy after legacy heartbeat: key=%s", registryEntryKey(normalizeEntry(entry))) } } }