//go:build integration package redisactivity import ( "context" "testing" "time" controllerPool "proxy-pool/internal/controller/pool" "proxy-pool/internal/domain/workerruntime" ) func TestRedisCapacityInventoryCombinesProxyAndWorkerRuntime(t *testing.T) { fixture := newRedisTestFixture(t) now := redisTestNow() unowned := testProxy("proxy-unowned", "192.0.2.10") unowned.MaxConcurrency = 10 owned := testProxy("proxy-owned", "192.0.2.11") owned.MaxConcurrency = 10 seedRedisAvailable(t, fixture.Adapter, "provider-a", now, now.Add(time.Second), 2*time.Minute, unowned) seedRedisAvailable(t, fixture.Adapter, "provider-a", now, now.Add(time.Second), 2*time.Minute, owned) assignment, err := fixture.Adapter.Assign(context.Background(), now.Add(2*time.Second), "proxy-owned", "worker-a", time.Minute) if err != nil { t.Fatalf("Assign(): %v", err) } if err := fixture.Adapter.ReplaceSession(context.Background(), workerruntime.Session{ WorkerID: "worker-a", InstanceID: "instance-a", SessionID: "session-a", AckedSnapshotVersion: 1, AckedOwnershipEpoch: assignment.Epoch, }, time.Minute); err != nil { t.Fatalf("ReplaceSession(): %v", err) } if err := fixture.Adapter.ReplaceRuntime(context.Background(), workerruntime.Report{ WorkerID: "worker-a", SessionID: "session-a", Sequence: 1, SnapshotVersion: 1, OwnershipEpoch: assignment.Epoch, ObservedAt: now, Counters: []workerruntime.Counter{{ProxyID: "proxy-owned", Active: 3, Reserved: 2}}, }, time.Minute); err != nil { t.Fatalf("ReplaceRuntime(): %v", err) } inventory, err := fixture.Adapter.ReadInventory(context.Background(), "provider-a", 0) if err != nil || inventory.Managed != 2 || inventory.AvailableSlots != 15 { t.Fatalf("ReadInventory() = %+v, %v; want managed=2 slots=15", inventory, err) } if _, ok := any(fixture.Adapter).(controllerPool.InventoryReader); !ok { t.Fatal("Adapter does not implement pool.InventoryReader") } inventory, err = fixture.Adapter.ReadInventory(context.Background(), "provider-a", 2*time.Hour) if err != nil || inventory.Managed != 2 || inventory.AvailableSlots != 0 { t.Fatalf("ReadInventory(safety margin) = %+v, %v", inventory, err) } fixture.Adapter.options.MaxInventoryScan = 1 if _, err := fixture.Adapter.ReadInventory(context.Background(), "provider-a", 0); err == nil { t.Fatal("ReadInventory(over scan limit) error = nil") } } func TestRedisCapacityInventoryFailsClosedForExpiredRuntime(t *testing.T) { fixture := newRedisTestFixture(t) now := redisTestNow() owned := testProxy("proxy-owned", "192.0.2.11") owned.MaxConcurrency = 10 seedRedisAvailable(t, fixture.Adapter, "provider-a", now, now.Add(time.Second), 2*time.Minute, owned) assignment, err := fixture.Adapter.Assign(context.Background(), now.Add(2*time.Second), "proxy-owned", "worker-a", time.Minute) if err != nil { t.Fatalf("Assign(): %v", err) } if err := fixture.Adapter.ReplaceSession(context.Background(), workerruntime.Session{ WorkerID: "worker-a", InstanceID: "instance-a", SessionID: "session-a", AckedSnapshotVersion: 1, AckedOwnershipEpoch: assignment.Epoch, }, 100*time.Millisecond); err != nil { t.Fatalf("ReplaceSession(): %v", err) } if err := fixture.Adapter.ReplaceRuntime(context.Background(), workerruntime.Report{ WorkerID: "worker-a", SessionID: "session-a", Sequence: 1, SnapshotVersion: 1, OwnershipEpoch: assignment.Epoch, ObservedAt: now, }, 100*time.Millisecond); err != nil { t.Fatalf("ReplaceRuntime(): %v", err) } time.Sleep(150 * time.Millisecond) inventory, err := fixture.Adapter.ReadInventory(context.Background(), "provider-a", 0) if err != nil || inventory.Managed != 1 || inventory.AvailableSlots != 0 { t.Fatalf("ReadInventory(expired runtime) = %+v, %v", inventory, err) } } func TestRedisCapacityInventoryScanIsIsolatedPerUpstream(t *testing.T) { fixture := newRedisTestFixture(t) fixture.Adapter.options.MaxInventoryScan = 1 now := redisTestNow() target := testProxy("proxy-target", "192.0.2.10") target.MaxConcurrency = 4 seedRedisAvailable(t, fixture.Adapter, "provider-a", now, now.Add(time.Second), 2*time.Minute, target) seedRedisAvailable(t, fixture.Adapter, "provider-b", now, now.Add(time.Second), 2*time.Minute, testProxy("proxy-other-1", "192.0.2.11")) seedRedisAvailable(t, fixture.Adapter, "provider-b", now, now.Add(2*time.Second), 2*time.Minute, testProxy("proxy-other-2", "192.0.2.12")) inventory, err := fixture.Adapter.ReadInventory(context.Background(), "provider-a", 0) if err != nil || inventory.Managed != 1 || inventory.AvailableSlots != 4 { t.Fatalf("ReadInventory(provider-a) = %+v, %v; want isolated managed=1 slots=4", inventory, err) } }