package brain_test import ( "fmt" "os" "path/filepath" "syscall" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/mathiasbq/hyperguild/ingestion/internal/brain" "github.com/mathiasbq/hyperguild/ingestion/internal/brainfs/brainfstest" ) // hyperguild#174 / #157 slice 5b: DetectTunnels walked wiki/ and opened // every note with a blocking os.Open, so a FIFO named *.md anywhere under // wiki/ hung every brain_write and brain_update (wikiUpkeep -> AutoTunnel), // and a symlinked note's title became a tunnel candidate. func TestDetectTunnelsReturnsPromptlyWithAFIFOUnderWikiWithAndWithoutAWriter(t *testing.T) { for _, withWriter := range []bool{false, true} { t.Run(fmt.Sprintf("writer=%v", withWriter), func(t *testing.T) { b := t.TempDir() facts := filepath.Join(b, "wiki", "w", "facts") require.NoError(t, os.MkdirAll(facts, 0o755)) require.NoError(t, os.WriteFile(filepath.Join(facts, "ok.md"), []byte("---\ntitle: Zqinside Phrase\n---\n"), 0o644)) fifo := filepath.Join(facts, "f.md") require.NoError(t, syscall.Mkfifo(fifo, 0o644)) if withWriter { // A writer held open: a reader that opens the FIFO gets no // EOF and parks, unless the FIFO is never opened. w, err := os.OpenFile(fifo, os.O_RDWR, 0) require.NoError(t, err) t.Cleanup(func() { _ = w.Close() }) } done := make(chan []brain.TunnelCandidate, 1) go func() { cs, _ := brain.DetectTunnels(b, "text mentioning zqinside phrase here") done <- cs }() select { case cs := <-done: assert.Contains(t, fmt.Sprintf("%+v", cs), "Zqinside Phrase", "positive control") case <-time.After(3 * time.Second): if r, err := os.OpenFile(fifo, os.O_WRONLY|syscall.O_NONBLOCK, 0); err == nil { _ = r.Close() } t.Fatal("DetectTunnels blocked on a FIFO") } }) } } func TestDetectTunnelsRefusesSymlinkedNotesAndSurvivesSwapRaces(t *testing.T) { b := t.TempDir() facts := filepath.Join(b, "wiki", "w", "facts") require.NoError(t, os.MkdirAll(facts, 0o755)) // The static note's title differs from the raced note's, so the // harness's "inside was read" floor can only be met by reading r.md. require.NoError(t, os.WriteFile(filepath.Join(facts, "ok.md"), []byte("---\ntitle: Zqstatic Words\n---\n"), 0o644)) require.NoError(t, os.Symlink(brainfstest.Outside(t), filepath.Join(facts, "out.md"))) inBrain := brainfstest.WriteSentinelAt(t, filepath.Join(b, "raw", "s.md")) require.NoError(t, os.Symlink(filepath.Join("..", "..", "..", "raw", "s.md"), filepath.Join(facts, "in.md"))) content := "zqstatic words, zqinside phrase and " + brainfstest.Sentinel cs, err := brain.DetectTunnels(b, content) require.NoError(t, err) got := fmt.Sprintf("%+v", cs) assert.Contains(t, got, "Zqstatic Words", "positive control") assert.NotContains(t, got, brainfstest.Sentinel) p := filepath.Join(facts, "r.md") read := func() string { cs, _ := brain.DetectTunnels(b, content) return fmt.Sprintf("%+v", cs) } brainfstest.AssertNoLeak(t, p, "---\ntitle: Zqinside Phrase\n---\n", 1500*time.Millisecond, read) brainfstest.AssertNoLeakFrom(t, p, "---\ntitle: Zqinside Phrase\n---\n", inBrain, 1500*time.Millisecond, read) } // A wiki/ that is not a real directory yields no candidates and no error: // AutoTunnel runs inside brain_write. func TestDetectTunnelsSkipsAWikiThatIsNotARealDirectory(t *testing.T) { b := t.TempDir() require.NoError(t, os.MkdirAll(filepath.Join(b, "raw", "w", "facts"), 0o755)) require.NoError(t, os.WriteFile(filepath.Join(b, "raw", "w", "facts", "r.md"), []byte("---\ntitle: Zqrawonly Phrase\n---\n"), 0o644)) require.NoError(t, os.Symlink("raw", filepath.Join(b, "wiki"))) cs, err := brain.DetectTunnels(b, "zqrawonly phrase") require.NoError(t, err) assert.Empty(t, cs) } // An unreadable directory under wiki/ is a walk error and DetectTunnels // reports it, as filepath.WalkDir did; it is not silently dropped. func TestDetectTunnelsReportsAnUnreadableDirectoryUnderWiki(t *testing.T) { if os.Geteuid() == 0 { t.Skip("root ignores directory permissions") } b := t.TempDir() require.NoError(t, os.MkdirAll(filepath.Join(b, "wiki", "w", "facts"), 0o755)) require.NoError(t, os.WriteFile(filepath.Join(b, "wiki", "w", "facts", "ok.md"), []byte("---\ntitle: Zqinside Phrase\n---\n"), 0o644)) locked := filepath.Join(b, "wiki", "other", "locked") require.NoError(t, os.MkdirAll(locked, 0o755)) require.NoError(t, os.Chmod(locked, 0o000)) t.Cleanup(func() { _ = os.Chmod(locked, 0o755) }) _, err := brain.DetectTunnels(b, "zqinside phrase") assert.ErrorIs(t, err, os.ErrPermission) } // _index.md files are generated, not notes: their title never becomes a // candidate. func TestDetectTunnelsSkipsIndexFiles(t *testing.T) { b := t.TempDir() w := filepath.Join(b, "wiki", "w") require.NoError(t, os.MkdirAll(filepath.Join(w, "facts"), 0o755)) require.NoError(t, os.WriteFile(filepath.Join(w, "_index.md"), []byte("---\ntitle: Zqindex Words\n---\n"), 0o644)) require.NoError(t, os.WriteFile(filepath.Join(w, "facts", "ok.md"), []byte("---\ntitle: Zqinside Phrase\n---\n"), 0o644)) cs, err := brain.DetectTunnels(b, "zqindex words and zqinside phrase") require.NoError(t, err) got := fmt.Sprintf("%+v", cs) assert.Contains(t, got, "Zqinside Phrase", "positive control") assert.NotContains(t, got, "Zqindex") }