package brain_test import ( "os" "path/filepath" "strings" "testing" "github.com/mathiasbq/hyperguild/ingestion/internal/brain" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) // seedNote writes a minimal markdown note at brainDir/relPath with the given body. func seedNote(t *testing.T, brainDir, relPath, body string) { t.Helper() full := filepath.Join(brainDir, relPath) require.NoError(t, os.MkdirAll(filepath.Dir(full), 0o755)) require.NoError(t, os.WriteFile(full, []byte(body), 0o644)) } func TestWriteTunnel_AppendsBidirectionalLinks(t *testing.T) { dir := t.TempDir() seedNote(t, dir, "wiki/jepa-fx/decisions/val-vol.md", "---\nwing: jepa-fx\nhall: decisions\n---\n# Val Vol R2\n\nbody.\n") seedNote(t, dir, "wiki/hyperguild/decisions/routing.md", "---\nwing: hyperguild\nhall: decisions\n---\n# Routing\n\nbody.\n") err := brain.WriteTunnel(dir, "wiki/jepa-fx/decisions/val-vol.md", "wiki/hyperguild/decisions/routing.md", ) require.NoError(t, err) src, err := os.ReadFile(filepath.Join(dir, "wiki/jepa-fx/decisions/val-vol.md")) require.NoError(t, err) assert.Contains(t, string(src), "## See also") assert.Contains(t, string(src), "[[hyperguild/decisions/routing]]") tgt, err := os.ReadFile(filepath.Join(dir, "wiki/hyperguild/decisions/routing.md")) require.NoError(t, err) assert.Contains(t, string(tgt), "## See also") assert.Contains(t, string(tgt), "[[jepa-fx/decisions/val-vol]]") } func TestWriteTunnel_Idempotent(t *testing.T) { dir := t.TempDir() seedNote(t, dir, "wiki/a/facts/x.md", "# X\n\nbody.\n") seedNote(t, dir, "wiki/b/facts/y.md", "# Y\n\nbody.\n") for i := 0; i < 3; i++ { require.NoError(t, brain.WriteTunnel(dir, "wiki/a/facts/x.md", "wiki/b/facts/y.md")) } src, err := os.ReadFile(filepath.Join(dir, "wiki/a/facts/x.md")) require.NoError(t, err) assert.Equal(t, 1, strings.Count(string(src), "[[b/facts/y]]"), "link should appear exactly once after 3 calls") assert.Equal(t, 1, strings.Count(string(src), "## See also")) tgt, err := os.ReadFile(filepath.Join(dir, "wiki/b/facts/y.md")) require.NoError(t, err) assert.Equal(t, 1, strings.Count(string(tgt), "[[a/facts/x]]")) assert.Equal(t, 1, strings.Count(string(tgt), "## See also")) } func TestWriteTunnel_RejectsSameWing(t *testing.T) { dir := t.TempDir() seedNote(t, dir, "wiki/jepa-fx/facts/x.md", "x") seedNote(t, dir, "wiki/jepa-fx/facts/y.md", "y") err := brain.WriteTunnel(dir, "wiki/jepa-fx/facts/x.md", "wiki/jepa-fx/facts/y.md") require.Error(t, err) assert.Contains(t, err.Error(), "cross wings") } func TestWriteTunnel_RejectsMissingNote(t *testing.T) { dir := t.TempDir() seedNote(t, dir, "wiki/a/facts/x.md", "x") err := brain.WriteTunnel(dir, "wiki/a/facts/x.md", "wiki/b/facts/ghost.md") require.Error(t, err) } func TestDetectTunnels_ExactTitleMatch(t *testing.T) { dir := t.TempDir() seedNote(t, dir, "wiki/jepa-fx/decisions/val-vol.md", "---\nwing: jepa-fx\nhall: decisions\ntitle: Val Vol R2\n---\nbody.\n") seedNote(t, dir, "wiki/jepa-fx/facts/lejpa.md", "---\nwing: jepa-fx\nhall: facts\ntitle: LeJPA Architecture\n---\nbody.\n") candidates, err := brain.DetectTunnels(dir, "We need to revisit Val Vol R2 in light of new tier data.") require.NoError(t, err) require.Len(t, candidates, 1) assert.Equal(t, "wiki/jepa-fx/decisions/val-vol.md", candidates[0].TargetPath) assert.Equal(t, "Val Vol R2", candidates[0].MatchedTerm) assert.True(t, candidates[0].Exact) } func TestDetectTunnels_FuzzyMatch(t *testing.T) { dir := t.TempDir() seedNote(t, dir, "wiki/x/facts/routing.md", "---\ntitle: Routing\n---\nbody.\n") // Substring of title appears in content, but not as a whole word. candidates, err := brain.DetectTunnels(dir, "rerouting handles failover") require.NoError(t, err) require.Len(t, candidates, 1) assert.False(t, candidates[0].Exact, "substring-only match should be fuzzy") } func TestDetectTunnels_NoFrontmatterFallsBackToSlug(t *testing.T) { dir := t.TempDir() seedNote(t, dir, "wiki/x/facts/widget-flags.md", "# widget flags\n\nbody.\n") candidates, err := brain.DetectTunnels(dir, "Documented Widget Flags after the deploy issue.") require.NoError(t, err) require.Len(t, candidates, 1) assert.True(t, candidates[0].Exact) assert.Equal(t, "widget flags", candidates[0].MatchedTerm) } func TestAutoTunnel_FuzzyGoesToCandidatesFile(t *testing.T) { dir := t.TempDir() // Existing note in a different wing whose title is "Routing". seedNote(t, dir, "wiki/other/facts/routing.md", "---\nwing: other\nhall: facts\ntitle: Routing\n---\nbody.\n") // Source note in another wing whose body mentions "rerouting" (substring match only). seedNote(t, dir, "wiki/jepa-fx/facts/new.md", "---\nwing: jepa-fx\nhall: facts\n---\nrerouting traffic\n") require.NoError(t, brain.AutoTunnel(dir, "wiki/jepa-fx/facts/new.md", "rerouting traffic")) // Source must not get auto-linked (fuzzy). got, err := os.ReadFile(filepath.Join(dir, "wiki/jepa-fx/facts/new.md")) require.NoError(t, err) assert.NotContains(t, string(got), "[[other/facts/routing]]") // Candidates file must list the pair. matches, err := filepath.Glob(filepath.Join(dir, "raw", "tunnel-candidates-*.md")) require.NoError(t, err) require.Len(t, matches, 1) body, err := os.ReadFile(matches[0]) require.NoError(t, err) assert.Contains(t, string(body), "wiki/jepa-fx/facts/new.md") assert.Contains(t, string(body), "wiki/other/facts/routing.md") assert.Contains(t, string(body), "Routing") } func TestDetectTunnels_EmptyWiki(t *testing.T) { dir := t.TempDir() cs, err := brain.DetectTunnels(dir, "anything") require.NoError(t, err) assert.Empty(t, cs) } func TestWriteTunnel_AppendsToExistingSeeAlso(t *testing.T) { dir := t.TempDir() seedNote(t, dir, "wiki/a/facts/x.md", "# X\n\nbody.\n\n## See also\n\n- [[a/facts/old]]\n") seedNote(t, dir, "wiki/b/facts/y.md", "# Y\n\nbody.\n") require.NoError(t, brain.WriteTunnel(dir, "wiki/a/facts/x.md", "wiki/b/facts/y.md")) src, err := os.ReadFile(filepath.Join(dir, "wiki/a/facts/x.md")) require.NoError(t, err) s := string(src) assert.Equal(t, 1, strings.Count(s, "## See also"), "should reuse existing section") assert.Contains(t, s, "[[a/facts/old]]") assert.Contains(t, s, "[[b/facts/y]]") }