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

352 lines
13 KiB
Go

package mcp
import (
"sync"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.uber.org/zap"
"github.com/zzet/gortex/internal/semantic/lsp"
)
// fakeSpecificSender records every SendNotificationToSpecificClient
// call so tests can assert delivery target + payload.
type fakeSpecificSender struct {
mu sync.Mutex
calls []fakeSpecificCall
}
type fakeSpecificCall struct {
sessionID string
method string
params map[string]any
}
func (f *fakeSpecificSender) SendNotificationToSpecificClient(sessionID, method string, params map[string]any) error {
f.mu.Lock()
defer f.mu.Unlock()
f.calls = append(f.calls, fakeSpecificCall{sessionID: sessionID, method: method, params: params})
return nil
}
func (f *fakeSpecificSender) snapshot() []fakeSpecificCall {
f.mu.Lock()
defer f.mu.Unlock()
out := make([]fakeSpecificCall, len(f.calls))
copy(out, f.calls)
return out
}
func (f *fakeSpecificSender) sessionsTargeted() map[string]int {
f.mu.Lock()
defer f.mu.Unlock()
out := make(map[string]int)
for _, c := range f.calls {
out[c.sessionID]++
}
return out
}
// noSnapshot is a snapFn used by tests that don't exercise the
// initial-state replay path.
func noSnapshot() []lsp.DiagnosticsEntry { return nil }
// TestDiagnosticsBroadcaster_NoSubscribers — no subscribers means no
// SendNotification calls. The hash IS still recorded so a late
// subscriber doesn't get a stale replay through the publish path.
func TestDiagnosticsBroadcaster_NoSubscribers(t *testing.T) {
fake := &fakeSpecificSender{}
b := newDiagnosticsBroadcaster(fake, noSnapshot, zap.NewNop())
diags := []lsp.Diagnostic{{Message: "missing semicolon"}}
b.publish("gopls", "/abs/path/main.go", diags)
assert.Empty(t, fake.snapshot(), "no subscribers — no broadcast")
b.subscribe("session-A", subscribeOptions{})
b.publish("gopls", "/abs/path/main.go", diags)
assert.Empty(t, fake.snapshot(), "duplicate payload still suppressed by delta filter")
}
// TestDiagnosticsBroadcaster_PerSessionDelivery — only subscribed
// sessions receive notifications, and they receive exactly one each.
// A non-subscribed session must NEVER appear in the call log.
func TestDiagnosticsBroadcaster_PerSessionDelivery(t *testing.T) {
fake := &fakeSpecificSender{}
b := newDiagnosticsBroadcaster(fake, noSnapshot, zap.NewNop())
b.subscribe("session-A", subscribeOptions{})
b.subscribe("session-B", subscribeOptions{})
// session-C never subscribes.
b.publish("gopls", "/work/main.go", []lsp.Diagnostic{{Message: "x"}})
targets := fake.sessionsTargeted()
assert.Equal(t, 1, targets["session-A"])
assert.Equal(t, 1, targets["session-B"])
assert.Equal(t, 0, targets["session-C"])
}
// TestDiagnosticsBroadcaster_DeltaFilter — identical re-publishes are
// suppressed; payload changes produce a new delivery.
func TestDiagnosticsBroadcaster_DeltaFilter(t *testing.T) {
fake := &fakeSpecificSender{}
b := newDiagnosticsBroadcaster(fake, noSnapshot, zap.NewNop())
b.subscribe("session-A", subscribeOptions{})
first := []lsp.Diagnostic{{Message: "one"}}
second := []lsp.Diagnostic{{Message: "two"}}
b.publish("gopls", "/work/main.go", first)
b.publish("gopls", "/work/main.go", first)
b.publish("gopls", "/work/main.go", second)
b.publish("gopls", "/work/main.go", second)
require.Len(t, fake.snapshot(), 2, "exactly two deliveries after delta filter")
}
// TestDiagnosticsBroadcaster_ClearPrunesHash — a file clearing its
// diagnostics broadcasts the clear exactly once (only when it was
// previously non-empty) and drops its lastHash key, so the map doesn't
// retain an entry per file the server ever touched.
func TestDiagnosticsBroadcaster_ClearPrunesHash(t *testing.T) {
fake := &fakeSpecificSender{}
b := newDiagnosticsBroadcaster(fake, noSnapshot, zap.NewNop())
b.subscribe("session-A", subscribeOptions{})
const path = "/work/main.go"
b.publish("gopls", path, []lsp.Diagnostic{{Message: "boom", Severity: 1}}) // non-empty
b.publish("gopls", path, nil) // cleared
b.publish("gopls", path, nil) // still empty — suppressed
calls := fake.snapshot()
require.Len(t, calls, 2, "one non-empty publish + one clear; the second clear is suppressed")
// The clear delivery carries an empty diagnostics list.
clear, _ := calls[1].params["diagnostics"].([]map[string]any)
assert.Empty(t, clear, "clear broadcast carries no diagnostics")
// The key is pruned, so lastHash doesn't grow one entry per touched file.
b.mu.RLock()
_, present := b.lastHash[path]
b.mu.RUnlock()
assert.False(t, present, "lastHash key removed after clear")
}
// TestDiagnosticsBroadcaster_ClearWithoutPriorSuppressed — an empty
// publish for a file that never had diagnostics is not broadcast (no
// spurious clear) and leaves no lastHash entry.
func TestDiagnosticsBroadcaster_ClearWithoutPriorSuppressed(t *testing.T) {
fake := &fakeSpecificSender{}
b := newDiagnosticsBroadcaster(fake, noSnapshot, zap.NewNop())
b.subscribe("session-A", subscribeOptions{})
b.publish("gopls", "/work/clean.go", nil)
assert.Empty(t, fake.snapshot(), "no prior diagnostics — clear suppressed")
b.mu.RLock()
_, present := b.lastHash["/work/clean.go"]
b.mu.RUnlock()
assert.False(t, present)
}
// TestDiagnosticsBroadcaster_PayloadShape — payload carries the
// expected fields and the wire-form diagnostics list.
func TestDiagnosticsBroadcaster_PayloadShape(t *testing.T) {
fake := &fakeSpecificSender{}
b := newDiagnosticsBroadcaster(fake, noSnapshot, zap.NewNop())
b.subscribe("session-A", subscribeOptions{})
diags := []lsp.Diagnostic{{Message: "boom", Severity: 1}}
b.publish("gopls", "/work/main.go", diags)
calls := fake.snapshot()
require.Len(t, calls, 1)
assert.Equal(t, "session-A", calls[0].sessionID)
assert.Equal(t, "notifications/diagnostics", calls[0].method)
assert.Equal(t, "file:///work/main.go", calls[0].params["uri"])
assert.Equal(t, "/work/main.go", calls[0].params["path"])
assert.Equal(t, "gopls", calls[0].params["server"])
wire, ok := calls[0].params["diagnostics"].([]map[string]any)
require.True(t, ok, "diagnostics should be wire-form slice")
assert.Len(t, wire, 1)
}
// TestDiagnosticsBroadcaster_SeverityFilter — a min_severity=2
// subscriber receives errors and warnings but not info / hint.
// Severity-0 (unset) entries always pass through.
func TestDiagnosticsBroadcaster_SeverityFilter(t *testing.T) {
fake := &fakeSpecificSender{}
b := newDiagnosticsBroadcaster(fake, noSnapshot, zap.NewNop())
b.subscribe("session-A", subscribeOptions{MinSeverity: 2})
diags := []lsp.Diagnostic{
{Severity: 1, Message: "err"},
{Severity: 2, Message: "warn"},
{Severity: 3, Message: "info"},
{Severity: 4, Message: "hint"},
{Severity: 0, Message: "no-severity"},
}
b.publish("gopls", "/work/main.go", diags)
calls := fake.snapshot()
require.Len(t, calls, 1)
wire := calls[0].params["diagnostics"].([]map[string]any)
require.Len(t, wire, 3, "expected err + warn + no-severity through the filter")
}
// TestDiagnosticsBroadcaster_PathPrefixFilter — a subscriber scoped
// to /work/api/ does not receive diagnostics for /work/web/.
func TestDiagnosticsBroadcaster_PathPrefixFilter(t *testing.T) {
fake := &fakeSpecificSender{}
b := newDiagnosticsBroadcaster(fake, noSnapshot, zap.NewNop())
b.subscribe("session-A", subscribeOptions{PathPrefix: "/work/api/"})
b.publish("gopls", "/work/api/users.go", []lsp.Diagnostic{{Message: "in scope"}})
b.publish("gopls", "/work/web/page.go", []lsp.Diagnostic{{Message: "out of scope"}})
calls := fake.snapshot()
require.Len(t, calls, 1)
assert.Equal(t, "/work/api/users.go", calls[0].params["path"])
}
// TestDiagnosticsBroadcaster_PerSubscriberFilters — two subscribers
// with different filters receive different slices of the same publish.
func TestDiagnosticsBroadcaster_PerSubscriberFilters(t *testing.T) {
fake := &fakeSpecificSender{}
b := newDiagnosticsBroadcaster(fake, noSnapshot, zap.NewNop())
b.subscribe("errors-only", subscribeOptions{MinSeverity: 1})
b.subscribe("everything", subscribeOptions{})
diags := []lsp.Diagnostic{
{Severity: 1, Message: "err"},
{Severity: 4, Message: "hint"},
}
b.publish("gopls", "/work/main.go", diags)
bySession := make(map[string][]map[string]any)
for _, c := range fake.snapshot() {
bySession[c.sessionID] = c.params["diagnostics"].([]map[string]any)
}
require.Len(t, bySession["errors-only"], 1)
require.Len(t, bySession["everything"], 2)
}
// TestDiagnosticsBroadcaster_InitialReplay — subscribing replays the
// current snapshot for files matching the filter, tagged
// initial_replay=true.
func TestDiagnosticsBroadcaster_InitialReplay(t *testing.T) {
fake := &fakeSpecificSender{}
snap := func() []lsp.DiagnosticsEntry {
return []lsp.DiagnosticsEntry{
{SpecName: "gopls", AbsPath: "/work/api/main.go", Diagnostics: []lsp.Diagnostic{{Severity: 1, Message: "old"}}},
{SpecName: "gopls", AbsPath: "/work/web/x.go", Diagnostics: []lsp.Diagnostic{{Severity: 1, Message: "elsewhere"}}},
}
}
b := newDiagnosticsBroadcaster(fake, snap, zap.NewNop())
replayed := b.subscribe("session-A", subscribeOptions{PathPrefix: "/work/api/"})
assert.Equal(t, 1, replayed, "only /work/api/* should replay")
calls := fake.snapshot()
require.Len(t, calls, 1)
assert.Equal(t, true, calls[0].params["initial_replay"])
assert.Equal(t, "/work/api/main.go", calls[0].params["path"])
}
// TestDiagnosticsBroadcaster_Resubscribe — resubscribing with new
// options overwrites the previous filter and re-replays.
func TestDiagnosticsBroadcaster_Resubscribe(t *testing.T) {
fake := &fakeSpecificSender{}
snap := func() []lsp.DiagnosticsEntry {
return []lsp.DiagnosticsEntry{
{SpecName: "gopls", AbsPath: "/work/a.go", Diagnostics: []lsp.Diagnostic{{Severity: 1}}},
}
}
b := newDiagnosticsBroadcaster(fake, snap, zap.NewNop())
b.subscribe("session-A", subscribeOptions{PathPrefix: "/elsewhere/"})
b.subscribe("session-A", subscribeOptions{PathPrefix: "/work/"})
// Two replay attempts — first found nothing in scope, second
// found one match.
calls := fake.snapshot()
require.Len(t, calls, 1)
assert.Equal(t, "/work/a.go", calls[0].params["path"])
}
// TestDiagnosticsBroadcaster_Unsubscribe — after unsubscribe the
// session stops receiving.
func TestDiagnosticsBroadcaster_Unsubscribe(t *testing.T) {
fake := &fakeSpecificSender{}
b := newDiagnosticsBroadcaster(fake, noSnapshot, zap.NewNop())
b.subscribe("session-A", subscribeOptions{})
b.publish("gopls", "/work/a.go", []lsp.Diagnostic{{Message: "a"}})
require.Len(t, fake.snapshot(), 1)
b.unsubscribe("session-A")
b.publish("gopls", "/work/b.go", []lsp.Diagnostic{{Message: "b"}})
require.Len(t, fake.snapshot(), 1, "no subscribers — no broadcast")
assert.Equal(t, 0, b.subscriberCount())
}
// TestDiagnosticsBroadcaster_NilBroadcaster — publish on a broadcaster
// with no underlying server is a safe no-op.
func TestDiagnosticsBroadcaster_NilBroadcaster(t *testing.T) {
b := newDiagnosticsBroadcaster(nil, noSnapshot, zap.NewNop())
b.subscribe("session-A", subscribeOptions{})
// Should not panic.
b.publish("gopls", "/abs/main.go", []lsp.Diagnostic{{Message: "x"}})
}
// TestServer_ReleaseSession_UnsubscribesDiagnostics — the Server's
// session-lifecycle hook must drop the session out of the diagnostics
// broadcaster so subscriber slots don't leak across the daemon's
// lifetime.
func TestServer_ReleaseSession_UnsubscribesDiagnostics(t *testing.T) {
fake := &fakeSpecificSender{}
b := newDiagnosticsBroadcaster(fake, noSnapshot, zap.NewNop())
srv := &Server{diagBroadcaster: b}
b.subscribe("session-A", subscribeOptions{})
require.Equal(t, 1, b.subscriberCount())
srv.ReleaseSession("session-A")
assert.Equal(t, 0, b.subscriberCount(), "ReleaseSession should drop the subscriber")
}
// TestPathToFileURI_Absolute — POSIX path round-trips into the
// expected file:// URI.
func TestPathToFileURI_Absolute(t *testing.T) {
assert.Equal(t, "file:///work/main.go", pathToFileURI("/work/main.go"))
assert.Equal(t, "", pathToFileURI(""))
}
// TestHashDiagnostics_Stable — identical content hashes match;
// different content does not.
func TestHashDiagnostics_Stable(t *testing.T) {
a := []lsp.Diagnostic{{Message: "x", Severity: 2}}
b := []lsp.Diagnostic{{Message: "x", Severity: 2}}
assert.Equal(t, hashDiagnostics(a), hashDiagnostics(b))
c := []lsp.Diagnostic{{Message: "y", Severity: 2}}
assert.NotEqual(t, hashDiagnostics(a), hashDiagnostics(c))
assert.Equal(t, "empty", hashDiagnostics(nil))
}
// TestFilterDiagnosticsBySeverity — boundary checks: severity-0 always
// passes, threshold inclusion is ≤, no-filter copies input.
func TestFilterDiagnosticsBySeverity(t *testing.T) {
in := []lsp.Diagnostic{
{Severity: 1, Message: "err"},
{Severity: 2, Message: "warn"},
{Severity: 0, Message: "blank"},
}
assert.Len(t, filterDiagnosticsBySeverity(in, 0), 3, "no filter copies all")
assert.Len(t, filterDiagnosticsBySeverity(in, 1), 2, "min 1 keeps err + blank")
assert.Len(t, filterDiagnosticsBySeverity(in, 2), 3, "min 2 keeps everything")
}