package wiki import ( "fmt" "os" "path/filepath" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/mathiasbq/hyperguild/ingestion/internal/brainfs/brainfstest" ) // hyperguild#157 slice 4: LoadInventory listed wiki// with os.ReadDir // and opened each .md by path, so a symlinked page put the target's title and // aliases into the inventory (and the LLM prompts built from it), and a FIFO // hung it. RebuildIndex read first sentences the same way. func TestLoadInventoryRefusesSymlinksAndSurvivesSwapRaces(t *testing.T) { brain := t.TempDir() dir := filepath.Join(brain, "wiki", "concepts") require.NoError(t, os.MkdirAll(dir, 0o755)) require.NoError(t, os.WriteFile(filepath.Join(dir, "ok.md"), []byte("---\ntitle: zqinside\n---\n"), 0o644)) require.NoError(t, os.Symlink(brainfstest.Outside(t), filepath.Join(dir, "leak.md"))) inv, err := LoadInventory(brain) require.NoError(t, err) got := fmt.Sprintf("%+v", inv) assert.Contains(t, got, "zqinside", "positive control") assert.NotContains(t, got, brainfstest.Sentinel) assert.NotContains(t, got, "leak", "a symlinked page is not listed") p := filepath.Join(dir, "r.md") read := func() string { inv, _ := LoadInventory(brain) return fmt.Sprintf("%+v", inv) } brainfstest.AssertNoLeak(t, p, "---\ntitle: zqinside\n---\n", 1500*time.Millisecond, read) brainfstest.AssertNoHang(t, p, "---\ntitle: zqinside\n---\n", 1500*time.Millisecond, 6*time.Second, func() { _ = read() }) } func TestLoadInventorySkipsASymlinkedTypeDir(t *testing.T) { brain := t.TempDir() require.NoError(t, os.MkdirAll(filepath.Join(brain, "wiki"), 0o755)) require.NoError(t, os.MkdirAll(filepath.Join(brain, "raw"), 0o755)) require.NoError(t, os.WriteFile(filepath.Join(brain, "raw", "r.md"), []byte("---\ntitle: zqrawonly\n---\n"), 0o644)) require.NoError(t, os.Symlink(filepath.Join("..", "raw"), filepath.Join(brain, "wiki", "concepts"))) inv, _ := LoadInventory(brain) assert.NotContains(t, fmt.Sprintf("%+v", inv), "zqrawonly") } func TestRebuildIndexDoesNotReadAPageSwappedForASymlink(t *testing.T) { brain := t.TempDir() dir := filepath.Join(brain, "wiki", "concepts") require.NoError(t, os.MkdirAll(dir, 0o755)) p := filepath.Join(dir, "r.md") require.NoError(t, os.WriteFile(p, []byte("---\ntitle: R\n---\nzqinside sentence.\n"), 0o644)) require.NoError(t, RebuildIndex(brain, "2026-10-04")) idx, err := os.ReadFile(filepath.Join(brain, "wiki", "index.md")) require.NoError(t, err) require.Contains(t, string(idx), "zqinside", "positive control: the summary is read") read := func() string { _ = RebuildIndex(brain, "2026-10-04") b, _ := os.ReadFile(filepath.Join(brain, "wiki", "index.md")) return string(b) } brainfstest.AssertNoLeak(t, p, "---\ntitle: R\n---\nzqinside sentence.\n", 1500*time.Millisecond, read) brainfstest.AssertNoHang(t, p, "---\ntitle: R\n---\nzqinside sentence.\n", 1500*time.Millisecond, 6*time.Second, func() { _ = read() }) }