package mcp import ( "context" "encoding/json" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // writePending drops a raw/ note whose created_at is age old. func writePending(t *testing.T, brainDir, name string, age time.Duration, now time.Time) { t.Helper() raw := filepath.Join(brainDir, "raw") require.NoError(t, os.MkdirAll(raw, 0o755)) body := "---\ncreated_at: " + now.Add(-age).UTC().Format(time.RFC3339) + "\n---\n\nnote body\n" require.NoError(t, os.WriteFile(filepath.Join(raw, name), []byte(body), 0o644)) } func TestPendingNudge_SilentWhenNothingIsPending(t *testing.T) { // No queue, no nudge. A notice that fires every session is one people // learn to skip, which is how the queue got ignored for three months in // the first place (#82). got := pendingNudge(t.TempDir(), time.Now(), 14*24*time.Hour) assert.Nil(t, got) } func TestPendingNudge_SilentWhileEverythingIsBelowTheThreshold(t *testing.T) { now := time.Now() dir := t.TempDir() writePending(t, dir, "fresh.md", 2*24*time.Hour, now) got := pendingNudge(dir, now, 14*24*time.Hour) assert.Nil(t, got, "a two-day-old note is not aging") } func TestPendingNudge_ReportsCountAndOldestAgeOnceSomethingAges(t *testing.T) { now := time.Now() dir := t.TempDir() writePending(t, dir, "old.md", 100*24*time.Hour, now) writePending(t, dir, "older.md", 120*24*time.Hour, now) writePending(t, dir, "fresh.md", 1*24*time.Hour, now) got := pendingNudge(dir, now, 14*24*time.Hour) require.NotNil(t, got) // All three are pending; the threshold decides whether to speak, not // what to count. Someone opening the queue sees the whole queue. assert.Equal(t, 3, got.Pending) assert.Equal(t, 2, got.Aging) assert.Equal(t, 120, got.OldestDays) assert.Equal(t, "older.md", got.Oldest) } func TestPendingNudge_NeverDecidesAnything(t *testing.T) { // Aging is a trigger to ask a human, never a verdict. The nudge must // carry no action and must leave the queue exactly as it found it. now := time.Now() dir := t.TempDir() writePending(t, dir, "ancient.md", 365*24*time.Hour, now) before, err := os.ReadDir(filepath.Join(dir, "raw")) require.NoError(t, err) got := pendingNudge(dir, now, 14*24*time.Hour) require.NotNil(t, got) after, err := os.ReadDir(filepath.Join(dir, "raw")) require.NoError(t, err) assert.Len(t, after, len(before), "the nudge must not promote or reject anything") assert.Contains(t, got.Hint, "brain_pending", "the nudge should say how to look, not act") } func TestPendingNudge_SurvivesAnUnreadableQueue(t *testing.T) { // brain_context is a hot path; a broken raw/ dir must degrade to silence // rather than failing the whole context call. dir := t.TempDir() require.NoError(t, os.WriteFile(filepath.Join(dir, "raw"), []byte("not a directory"), 0o644)) assert.NotPanics(t, func() { assert.Nil(t, pendingNudge(dir, time.Now(), 14*24*time.Hour)) }) } func TestBrainContext_CarriesTheNudgeWithoutFailingWhenTheQueueIsEmpty(t *testing.T) { // The wiring, not the summary: a nudge that never reaches a caller is the // same as no nudge, and brain_context must still work with no queue. dir := t.TempDir() srv := NewServer(dir, nil, nil, nil) raw, err := srv.brainContext(context.Background(), json.RawMessage(`{"project_root":"/home/mathias/dev/AGENTS/hyperguild"}`)) require.NoError(t, err) var empty map[string]any require.NoError(t, json.Unmarshal(raw, &empty)) assert.NotContains(t, empty, "pending_review", "silence when nothing is aging") writePending(t, dir, "ancient.md", 200*24*time.Hour, time.Now()) raw, err = srv.brainContext(context.Background(), json.RawMessage(`{"project_root":"/home/mathias/dev/AGENTS/hyperguild"}`)) require.NoError(t, err) var withQueue map[string]any require.NoError(t, json.Unmarshal(raw, &withQueue)) nudge, ok := withQueue["pending_review"].(map[string]any) require.True(t, ok, "the nudge must reach the caller: %v", withQueue) assert.Equal(t, float64(1), nudge["aging"]) assert.Equal(t, "ancient.md", nudge["oldest"]) }