Files

119 lines
4.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dispatch
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// TestProtoFactor_ResponsesPreference protoFactor 在 responses 入站下的软偏好:
// 匹配渠道因子为 1,不匹配渠道按策略 ResponsesMismatch 降权;ProtoAny 不降权。
func TestProtoFactor_ResponsesPreference(t *testing.T) {
pol := DefaultRoutingPolicy()
match := healthyChannel(1, TierPrimary, 10)
match.SupportsResponses = true
mismatch := healthyChannel(2, TierPrimary, 10)
assert.Equal(t, 1.0, protoFactor(match, ProtoResponses, pol))
assert.Equal(t, pol.Proto.ResponsesMismatch, protoFactor(mismatch, ProtoResponses, pol))
assert.Equal(t, 1.0, protoFactor(mismatch, ProtoAny, pol))
// chat 入站对 responses 支持与否不敏感
assert.Equal(t, 1.0, protoFactor(match, ProtoChat, pol))
}
// TestProtoFactor_ChatBridgePreference protoFactor 在 chat 入站下的软偏好:
// 原生 chat 渠道因子为 1,chat_via_responses 桥接渠道按 ChatBridgeMismatch 降权。
func TestProtoFactor_ChatBridgePreference(t *testing.T) {
pol := DefaultRoutingPolicy()
native := healthyChannel(1, TierPrimary, 10)
bridge := healthyChannel(2, TierPrimary, 10)
bridge.ChatViaResponses = true
assert.Equal(t, 1.0, protoFactor(native, ProtoChat, pol))
assert.Equal(t, pol.Proto.ChatBridgeMismatch, protoFactor(bridge, ProtoChat, pol))
assert.Equal(t, 1.0, protoFactor(bridge, ProtoResponses, pol)) // 未声明 supports_responses
}
// TestEffectiveWeight_ProtoInProduct proto 因子参与权重乘积且记录在分解明细中。
func TestEffectiveWeight_ProtoInProduct(t *testing.T) {
pol := DefaultRoutingPolicy()
c := healthyChannel(1, TierPrimary, 10)
wNoPref, _ := EffectiveWeight(c, pol)
wPref, bd := EffectiveWeight(c, pol, ProtoResponses)
require.Equal(t, pol.Proto.ResponsesMismatch, bd.Proto)
assert.InDelta(t, wNoPref*pol.Proto.ResponsesMismatch, wPref, 1e-9)
}
// TestCoordinator_ProtoResponsesPreference 统计意义验证:两个同权重同健康渠道,
// responses 入站时绝大多数会话键选中声明 SupportsResponses 的渠道(软偏好非硬过滤,
// HRW 哈希仍可能命中另一渠道——降权只是让匹配渠道的选中概率占优)。
func TestCoordinator_ProtoResponsesPreference(t *testing.T) {
ctx := context.Background()
match := healthyChannel(1, TierPrimary, 10)
match.SupportsResponses = true
other := healthyChannel(2, TierPrimary, 10)
matchWins, otherWins := 0, 0
for i := range 200 {
state := newFakeState()
co, _ := newTestCoordinator(state, match, other)
p := testProfile()
p.RequestID = fmt.Sprintf("req-%d", i)
p.Signals = SessionSignals{HeaderSessionID: fmt.Sprintf("sess-%d", i)}
p.Proto = ProtoResponses
d := co.Route(ctx, p).Next(ctx)
require.NotNil(t, d)
if d.Channel.ID == 1 {
matchWins++
} else {
otherWins++
}
}
assert.Greater(t, matchWins, otherWins*3, "responses-capable channel should dominate: match=%d other=%d", matchWins, otherWins)
}
// TestCoordinator_ProtoAnyNoPreference 对照组:无偏好时两同权重渠道选中次数同量级。
func TestCoordinator_ProtoAnyNoPreference(t *testing.T) {
ctx := context.Background()
a := healthyChannel(1, TierPrimary, 10)
a.SupportsResponses = true
b := healthyChannel(2, TierPrimary, 10)
aWins, bWins := 0, 0
for i := range 200 {
state := newFakeState()
co, _ := newTestCoordinator(state, a, b)
p := testProfile()
p.RequestID = fmt.Sprintf("req-%d", i)
p.Signals = SessionSignals{HeaderSessionID: fmt.Sprintf("sess-%d", i)}
d := co.Route(ctx, p).Next(ctx)
require.NotNil(t, d)
if d.Channel.ID == 1 {
aWins++
} else {
bWins++
}
}
// HRW 均匀性:无偏好时两渠道选中次数应为同量级(放宽到 4 倍避免哈希偶然性导致的 flake)
assert.Less(t, aWins, bWins*4)
assert.Less(t, bWins, aWins*4)
}
// TestPolicyValidate_ProtoFactors proto 因子越界(>1 或 <=0)必须被拒绝。
func TestPolicyValidate_ProtoFactors(t *testing.T) {
pol := DefaultRoutingPolicy()
require.NoError(t, pol.Validate())
pol.Proto.ResponsesMismatch = 0
assert.Error(t, pol.Validate())
pol = DefaultRoutingPolicy()
pol.Proto.ResponsesMismatch = 1.5
assert.Error(t, pol.Validate())
pol = DefaultRoutingPolicy()
pol.Proto.ChatBridgeMismatch = 0
assert.Error(t, pol.Validate())
}