Files
zzet--gortex/internal/mcp/tools_analyze_hotspot_modes_test.go
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
5.0 KiB
Go

package mcp
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/zzet/gortex/internal/analysis"
"github.com/zzet/gortex/internal/graph"
)
// buildHotspotRerankFixture seeds three function nodes with deterministic
// complexity scores AND varying blame / releases metadata so the
// novelty / directional modes can reorder them in predictable ways.
func buildHotspotRerankFixture(t *testing.T, now time.Time) (graph.Store, []analysis.HotspotEntry) {
t.Helper()
g := graph.New()
g.AddNode(&graph.Node{
ID: "fresh", Name: "Fresh", Kind: graph.KindFunction, FilePath: "fresh.go",
Meta: map[string]any{
"last_authored": map[string]any{"timestamp": now.Add(-2 * 24 * time.Hour).Unix()},
"added_in": map[string]any{"timestamp": now.Add(-2 * 24 * time.Hour).Unix()},
},
})
g.AddNode(&graph.Node{
ID: "midway", Name: "Midway", Kind: graph.KindFunction, FilePath: "midway.go",
Meta: map[string]any{
"last_authored": map[string]any{"timestamp": now.Add(-15 * 24 * time.Hour).Unix()},
"added_in": map[string]any{"timestamp": now.Add(-15 * 24 * time.Hour).Unix()},
},
})
g.AddNode(&graph.Node{
ID: "stale", Name: "Stale", Kind: graph.KindFunction, FilePath: "stale.go",
Meta: map[string]any{
"last_authored": map[string]any{"timestamp": now.Add(-90 * 24 * time.Hour).Unix()},
"added_in": map[string]any{"timestamp": now.Add(-90 * 24 * time.Hour).Unix()},
},
})
// Identical complexity scores so the rerank is decisive.
entries := []analysis.HotspotEntry{
{ID: "fresh", Name: "Fresh", ComplexityScore: 10.0},
{ID: "midway", Name: "Midway", ComplexityScore: 10.0},
{ID: "stale", Name: "Stale", ComplexityScore: 10.0},
}
return g, entries
}
func TestRerankHotspots_ComplexityIsPassThrough(t *testing.T) {
g, entries := buildHotspotRerankFixture(t, time.Now())
out := rerankHotspots(entries, g, "complexity", "", 30)
// Same order, same scores.
assert.Len(t, out, 3)
for i, e := range out {
assert.Equal(t, entries[i].ComplexityScore, e.ComplexityScore)
}
}
func TestRerankHotspots_NoveltyPromotesRecent(t *testing.T) {
now := time.Now()
g, entries := buildHotspotRerankFixture(t, now)
out := rerankHotspots(entries, g, "novelty", "", 30)
// 'fresh' (2 days old) ranks first; 'stale' (90 days) drops to 0.
assert.Equal(t, "fresh", out[0].ID)
var staleScore float64
for _, e := range out {
if e.ID == "stale" {
staleScore = e.ComplexityScore
}
}
assert.InDelta(t, 0.0, staleScore, 1e-6, "stale outside window gets zero novelty weight")
}
func TestRerankHotspots_DirectionalAddsRewardsRecent(t *testing.T) {
g, entries := buildHotspotRerankFixture(t, time.Now())
out := rerankHotspots(entries, g, "directional", "adds", 30)
assert.Equal(t, "fresh", out[0].ID, "adds direction promotes the newest addition")
}
func TestRerankHotspots_DirectionalStableRewardsOld(t *testing.T) {
g, entries := buildHotspotRerankFixture(t, time.Now())
out := rerankHotspots(entries, g, "directional", "stable", 30)
// stale (90 days) saturates frac=1.0 (clamped), midway and fresh
// are < 1.0 — stale wins.
assert.Equal(t, "stale", out[0].ID, "stable direction promotes the oldest addition")
}
func TestRerankHotspots_MissingMetaScoresZero(t *testing.T) {
g := graph.New()
g.AddNode(&graph.Node{ID: "nometa", Name: "NoMeta", Kind: graph.KindFunction, FilePath: "x.go"})
entries := []analysis.HotspotEntry{{ID: "nometa", Name: "NoMeta", ComplexityScore: 10.0}}
out := rerankHotspots(entries, g, "novelty", "", 30)
for _, e := range out {
if e.ID == "nometa" {
assert.InDelta(t, 0.0, e.ComplexityScore, 1e-6, "missing meta -> zero weight under novelty")
}
}
}
func TestRerankHotspots_MissingNodeDropped(t *testing.T) {
g := graph.New()
entries := []analysis.HotspotEntry{{ID: "doesnotexist", Name: "X", ComplexityScore: 5.0}}
out := rerankHotspots(entries, g, "novelty", "", 30)
assert.Empty(t, out, "entries whose node is missing from the graph are dropped")
}
func TestDecodeMetaTimestamp_Forms(t *testing.T) {
now := time.Now().Truncate(time.Second).UTC()
cases := []any{
now.Unix(),
int(now.Unix()),
float64(now.Unix()),
now.Format(time.RFC3339),
}
for _, c := range cases {
got := decodeMetaTimestamp(c)
assert.Equal(t, now.Unix(), got.Unix(), "decode %T(%v) → %v", c, c, got)
}
assert.True(t, decodeMetaTimestamp(nil).IsZero())
assert.True(t, decodeMetaTimestamp("garbage").IsZero())
}
func TestNoveltyWeight_LinearDecay(t *testing.T) {
now := time.Now()
window := 30 * 24 * time.Hour
// Day 0 → weight 1.0
n := &graph.Node{Meta: map[string]any{"last_authored": map[string]any{"timestamp": now.Unix()}}}
assert.InDelta(t, 1.0, noveltyWeight(nil, n, now, window), 1e-6)
// Day 15 → weight 0.5
n.Meta["last_authored"] = map[string]any{"timestamp": now.Add(-15 * 24 * time.Hour).Unix()}
assert.InDelta(t, 0.5, noveltyWeight(nil, n, now, window), 1e-2)
// Day 30+ → weight 0
n.Meta["last_authored"] = map[string]any{"timestamp": now.Add(-31 * 24 * time.Hour).Unix()}
assert.InDelta(t, 0.0, noveltyWeight(nil, n, now, window), 1e-6)
}