BuildWingIndex/BuildAllWingIndexes list wings and halls with the new brainfs.ReadDirDirs and ReadDirRegular and read titles with ReadRegular: a symlinked note no longer reaches _index.md, a symlinked wing is refused (nothing written into its target), a symlinked hall is skipped. linkcheck entryIndex walks with WalkRegular, so a symlinked .md cannot make a link resolve, and a linked tree that is not a real directory is skipped with a warning (brain_write still lands). Refs #157. Adversarial review: BLOCKED, PASS. Follow-up: #178. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Mathias <mthbqv@gmail.com>
This commit was merged in pull request #175.
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
package brain_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"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"
|
||||
)
|
||||
|
||||
func readIndex(t *testing.T, brainDir, wing string) string {
|
||||
t.Helper()
|
||||
b, _ := os.ReadFile(filepath.Join(brainDir, "wiki", wing, "_index.md"))
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// hyperguild#157 slice 5: BuildWingIndex listed wiki/<wing>/<hall>/ with
|
||||
// os.ReadDir and opened each note by path, so a symlinked note put the
|
||||
// target's title into _index.md, and a FIFO hung the index build.
|
||||
func TestBuildWingIndexRefusesSymlinkedNotesAndSurvivesSwapRaces(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\n---\n"), 0o644))
|
||||
require.NoError(t, os.Symlink(brainfstest.Outside(t), filepath.Join(facts, "leak.md")))
|
||||
inBrain := brainfstest.WriteSentinelAt(t, filepath.Join(b, "raw", "s.md"))
|
||||
require.NoError(t, os.Symlink(filepath.Join("..", "..", "..", "raw", "s.md"), filepath.Join(facts, "inlink.md")))
|
||||
|
||||
require.NoError(t, brain.BuildWingIndex(b, "w"))
|
||||
idx := readIndex(t, b, "w")
|
||||
assert.Contains(t, idx, "zqinside", "positive control")
|
||||
assert.NotContains(t, idx, brainfstest.Sentinel)
|
||||
assert.NotContains(t, idx, "leak")
|
||||
assert.NotContains(t, idx, "inlink")
|
||||
|
||||
p := filepath.Join(facts, "r.md")
|
||||
read := func() string {
|
||||
_ = brain.BuildWingIndex(b, "w")
|
||||
return readIndex(t, b, "w")
|
||||
}
|
||||
brainfstest.AssertNoLeak(t, p, "---\ntitle: zqinside\n---\n", 1500*time.Millisecond, read)
|
||||
brainfstest.AssertNoLeakFrom(t, p, "---\ntitle: zqinside\n---\n", inBrain, 1500*time.Millisecond, read)
|
||||
brainfstest.AssertNoHang(t, p, "---\ntitle: zqinside\n---\n", 1500*time.Millisecond, 6*time.Second, func() { _ = read() })
|
||||
}
|
||||
|
||||
// A symlinked hall is skipped while a real hall next to it is indexed; a
|
||||
// symlinked wing is refused and nothing is written into its target.
|
||||
func TestBuildWingIndexSkipsASymlinkedHallAndRefusesASymlinkedWing(t *testing.T) {
|
||||
b := t.TempDir()
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(b, "raw", "facts"), 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(b, "raw", "facts", "r.md"), []byte("---\ntitle: zqrawonly\n---\n"), 0o644))
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(b, "wiki", "w", "decisions"), 0o755))
|
||||
require.NoError(t, os.WriteFile(filepath.Join(b, "wiki", "w", "decisions", "d.md"), []byte("---\ntitle: zqrealhall\n---\n"), 0o644))
|
||||
require.NoError(t, os.Symlink(filepath.Join("..", "..", "raw", "facts"), filepath.Join(b, "wiki", "w", "facts")))
|
||||
require.NoError(t, os.Symlink(filepath.Join("..", "raw"), filepath.Join(b, "wiki", "alias")))
|
||||
|
||||
require.NoError(t, brain.BuildWingIndex(b, "w"))
|
||||
idx, err := os.ReadFile(filepath.Join(b, "wiki", "w", "_index.md"))
|
||||
require.NoError(t, err, "the wing index is written")
|
||||
assert.Contains(t, string(idx), "zqrealhall", "the real hall is indexed")
|
||||
assert.NotContains(t, string(idx), "zqrawonly", "the symlinked hall is not")
|
||||
|
||||
assert.Error(t, brain.BuildWingIndex(b, "alias"))
|
||||
_, err = os.Stat(filepath.Join(b, "raw", "_index.md"))
|
||||
assert.True(t, os.IsNotExist(err), "an index was written into the symlinked wing's target")
|
||||
}
|
||||
|
||||
// BuildAllWingIndexes indexes the real wings and skips a symlinked one.
|
||||
func TestBuildAllWingIndexesSkipsASymlinkedWing(t *testing.T) {
|
||||
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", "n.md"), []byte("---\ntitle: zqrealwing\n---\n"), 0o644))
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(b, "raw", "facts"), 0o755))
|
||||
require.NoError(t, os.Symlink(filepath.Join("..", "raw"), filepath.Join(b, "wiki", "alias")))
|
||||
|
||||
require.NoError(t, brain.BuildAllWingIndexes(b))
|
||||
idx, err := os.ReadFile(filepath.Join(b, "wiki", "w", "_index.md"))
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(idx), "zqrealwing")
|
||||
_, err = os.Stat(filepath.Join(b, "raw", "_index.md"))
|
||||
assert.True(t, os.IsNotExist(err))
|
||||
}
|
||||
|
||||
// A link to a symlinked note does not resolve: only regular files count as
|
||||
// entries, so StripUnresolvedLinks unwraps it.
|
||||
func TestStripUnresolvedLinksDoesNotCountASymlinkedNoteAsAnEntry(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, "real.md"), []byte("x"), 0o644))
|
||||
brainfstest.WriteSentinelAt(t, filepath.Join(b, "raw", "s.md"))
|
||||
require.NoError(t, os.Symlink(filepath.Join("..", "..", "..", "raw", "s.md"), filepath.Join(facts, "ghost.md")))
|
||||
out, unlinked, err := brain.StripUnresolvedLinks(b, "see [[real]] and [[ghost]]")
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, out, "[[real]]", "positive control")
|
||||
assert.NotContains(t, out, "[[ghost]]")
|
||||
assert.Contains(t, unlinked, "ghost")
|
||||
}
|
||||
|
||||
// A linked tree (knowledge/ or wiki/) that is not a real directory is
|
||||
// skipped, not an error: StripUnresolvedLinks runs before every brain_write,
|
||||
// and an error there loses the learning.
|
||||
func TestStripUnresolvedLinksSkipsALinkedTreeThatIsNotARealDirectory(t *testing.T) {
|
||||
for name, mk := range map[string]func(t *testing.T, b string){
|
||||
"symlink to an in-brain dir": func(t *testing.T, b string) {
|
||||
require.NoError(t, os.MkdirAll(filepath.Join(b, "raw", "k"), 0o755))
|
||||
require.NoError(t, os.Symlink(filepath.Join("raw", "k"), filepath.Join(b, "knowledge")))
|
||||
},
|
||||
"dangling symlink": func(t *testing.T, b string) {
|
||||
require.NoError(t, os.Symlink("nowhere", filepath.Join(b, "knowledge")))
|
||||
},
|
||||
"regular file": func(t *testing.T, b string) {
|
||||
require.NoError(t, os.WriteFile(filepath.Join(b, "knowledge"), []byte("x"), 0o644))
|
||||
},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
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", "real.md"), []byte("x"), 0o644))
|
||||
mk(t, b)
|
||||
out, _, err := brain.StripUnresolvedLinks(b, "learning [[real]] and [[gone]]")
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, out, "[[real]]", "links into wiki/ still resolve")
|
||||
assert.NotContains(t, out, "[[gone]]")
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user