package upstream import ( "context" "testing" "cmpp-platform/gateway/internal/queue" ) func TestConnectionPoolAcquiresAcrossConnections(t *testing.T) { pool := &connectionPool{ conns: []*connection{ {window: make(chan struct{}, 1)}, {window: make(chan struct{}, 1)}, }, } first, releaseFirst := pool.tryAcquireConnection() if first == nil { t.Fatalf("expected first connection") } second, releaseSecond := pool.tryAcquireConnection() if second == nil { t.Fatalf("expected second connection") } if first == second { t.Fatalf("expected pool to use another connection when the first window is full") } third, _ := pool.tryAcquireConnection() if third != nil { t.Fatalf("expected nil connection while all windows are full") } releaseFirst() reacquired, releaseReacquired := pool.tryAcquireConnection() if reacquired == nil { t.Fatalf("expected a connection after releasing a window") } releaseReacquired() releaseSecond() } func TestManagerDisconnectChannelRemovesPoolAndStopsReconnects(t *testing.T) { pool := &connectionPool{ channelID: "channel-1", connectionID: "channel-1:primary", config: normalizeUpstreamConfig(queueUpstreamConfigForTest()), reconnectSignal: make(chan struct{}, 1), stopCh: make(chan struct{}), } manager := &Manager{conns: map[string]*connectionPool{"channel-1": pool}} state, err := manager.DisconnectChannel(context.Background(), queue.DisconnectChannelCommand{ MessageType: queue.MessageTypeDisconnectChannel, ChannelID: "channel-1", ConnectionID: "channel-1:primary", }) if err != nil { t.Fatalf("disconnect channel: %v", err) } if state.Status != "disconnected" || state.CurrentConnections != 0 { t.Fatalf("unexpected state: %+v", state) } if _, exists := manager.conns["channel-1"]; exists { t.Fatal("expected channel pool to be removed") } select { case <-pool.stopCh: default: t.Fatal("expected reconnect supervisor to be stopped") } } func TestNormalizeUpstreamConfigDefaults(t *testing.T) { config := normalizeUpstreamConfig(queueUpstreamConfigForTest()) if config.DesiredConnections != 1 { t.Fatalf("DesiredConnections = %d, want 1", config.DesiredConnections) } if config.WindowSize != defaultWindowSize { t.Fatalf("WindowSize = %d, want %d", config.WindowSize, defaultWindowSize) } if config.HeartbeatIntervalSeconds != 30 || config.HeartbeatMissThreshold != 3 { t.Fatalf("unexpected heartbeat defaults: interval=%d threshold=%d", config.HeartbeatIntervalSeconds, config.HeartbeatMissThreshold) } } func TestManagerSeparatesConnectionStateAndSupplierEventAPIs(t *testing.T) { manager := &Manager{APIBaseURL: "http://main-api/api", EventAPIBaseURL: "http://callback-api/api"} pool := manager.newConnectionPool("channel-1", "channel-1:primary", queue.UpstreamConfig{DesiredConnections: 1}) if pool.apiBaseURL != manager.EventAPIBaseURL { t.Fatalf("supplier events must use callback API, got %q", pool.apiBaseURL) } if manager.APIBaseURL == pool.apiBaseURL { t.Fatal("connection-state control API must remain separate from supplier events") } } func queueUpstreamConfigForTest() queue.UpstreamConfig { return queue.UpstreamConfig{ GatewayHost: "127.0.0.1", GatewayPort: 17890, Account: "account", PasswordCipher: "secret", CMPPVersion: "3.0", } }