Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

138 lines
4.8 KiB
Go

package mcp
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestComboManager_RecordAndBoost(t *testing.T) {
cm := &comboManager{now: func() int64 { return 1 }}
// Below threshold: no boost yet.
cm.Record("auth middleware", "sym-a")
cm.Record("auth middleware", "sym-a")
assert.Nil(t, cm.BoostMap("auth middleware"), "below min-hits should not boost")
// Third hit tips over the threshold.
cm.Record("auth middleware", "sym-a")
boosts := cm.BoostMap("auth middleware")
require.NotNil(t, boosts)
assert.Greater(t, boosts["sym-a"], 1.0)
assert.Less(t, boosts["sym-a"], comboMaxBoost+0.01)
}
func TestComboManager_NormalizeQuery(t *testing.T) {
assert.Equal(t, "auth middleware", normalizeQuery(" AUTH Middleware "))
assert.Equal(t, "auth middleware", normalizeQuery("auth middleware"))
assert.Equal(t, "", normalizeQuery(" "))
}
func TestComboManager_BoostCappedAtMax(t *testing.T) {
cm := &comboManager{now: func() int64 { return 1 }}
for i := 0; i < 50; i++ {
cm.Record("q", "sym-hot")
}
boosts := cm.BoostMap("q")
require.NotNil(t, boosts)
assert.Equal(t, comboMaxBoost, boosts["sym-hot"])
}
func TestComboManager_PerQueryEntriesCapped(t *testing.T) {
cm := &comboManager{now: func() int64 { return 1 }}
// Record many distinct symbols against the same query — the matches
// list must stay bounded so a single chatty query can't blow up the
// file.
for i := 0; i < 100; i++ {
cm.Record("q", "sym-"+string(rune('A'+i%26))+string(rune('0'+i%10)))
}
// Lookup the query's entry via an internal peek: at most MaxComboEntries.
cm.mu.Lock()
defer cm.mu.Unlock()
require.Len(t, cm.store.Queries, 1)
assert.LessOrEqual(t, len(cm.store.Queries[0].Matches), 20)
}
func TestComboManager_MultipleQueriesIsolated(t *testing.T) {
cm := &comboManager{now: func() int64 { return 1 }}
for i := 0; i < 3; i++ {
cm.Record("query one", "alpha")
cm.Record("query two", "beta")
}
b1 := cm.BoostMap("query one")
b2 := cm.BoostMap("query two")
require.NotNil(t, b1)
require.NotNil(t, b2)
assert.Contains(t, b1, "alpha")
assert.NotContains(t, b1, "beta")
assert.Contains(t, b2, "beta")
}
func TestComboManager_Persistence(t *testing.T) {
dir := t.TempDir()
repoPath := filepath.Join(dir, "repo")
cacheDir := filepath.Join(dir, "cache")
cm := newComboManager(cacheDir, repoPath, ModeAI)
cm.now = func() int64 { return 1 }
for i := 0; i < 3; i++ {
cm.Record("auth middleware", "sym-a")
}
require.True(t, cm.HasData())
// Reload and verify the boost survives a restart. Freeze the reloader's
// clock at the same instant so the reap-on-BoostMap pass doesn't treat
// the entries as stale relative to wall-clock now.
cm2 := newComboManager(cacheDir, repoPath, ModeAI)
cm2.now = func() int64 { return 1 }
boosts := cm2.BoostMap("auth middleware")
require.NotNil(t, boosts, "persisted combo should still boost after reload")
assert.Greater(t, boosts["sym-a"], 1.0)
}
func TestComboManager_AIReapsFasterThanHuman(t *testing.T) {
// Both modes record identical histories at t=0.
ai := &comboManager{mode: ModeAI, now: func() int64 { return 0 }}
hu := &comboManager{mode: ModeHuman, now: func() int64 { return 0 }}
for i := 0; i < comboMinHits; i++ {
ai.Record("q", "sym")
hu.Record("q", "sym")
}
// Jump ahead 14 days: past AI's 7-day max age, within human's 30.
ai.now = func() int64 { return int64(14 * 86400) }
hu.now = func() int64 { return int64(14 * 86400) }
assert.Nil(t, ai.BoostMap("q"), "AI mode should have reaped stale combos")
assert.NotNil(t, hu.BoostMap("q"), "human mode keeps combos for 30 days")
}
func TestComboManager_NilSafe(t *testing.T) {
// Nil-safe paths used by servers running in --no-cache modes.
var cm *comboManager
cm.Record("x", "y")
assert.Nil(t, cm.BoostMap("x"))
assert.False(t, cm.HasData())
}
// TestComboManager_ReapPrunesEmptyShells confirms that once every match
// for a query/keyword expires, the emptied outer entry is removed (not
// left as a zero-match shell) and HasData reflects that.
func TestComboManager_ReapPrunesEmptyShells(t *testing.T) {
now := int64(1_000_000_000)
cm := &comboManager{mode: ModeAI, now: func() int64 { return now }}
for i := 0; i < comboMinHits; i++ {
cm.Record("auth token", "sym-a")
}
require.True(t, cm.HasData())
require.Len(t, cm.store.Queries, 1)
require.NotEmpty(t, cm.kwStore.Keywords)
// Jump past the AI max-age window so every match is stale.
now += comboMaxAgeAISec + 1
// HasData reaps first, so it must both prune the shells and report false.
assert.False(t, cm.HasData(), "a store holding only expired matches must report no data")
assert.Empty(t, cm.store.Queries, "emptied query shells must be pruned, not retained")
assert.Empty(t, cm.kwStore.Keywords, "emptied keyword shells must be pruned, not retained")
}