// ingestion/internal/pipeline/links_test.go package pipeline import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/mathiasbq/hyperguild/ingestion/internal/wiki" ) func TestCanonicalizeLinks_KnownTitle(t *testing.T) { pages := []wiki.Page{ { Path: "wiki/sources/shape-up.md", Content: "---\ntitle: 'Shape Up'\n---\n\n## Summary\n\nSee [[Betting]].\n", }, } inventory := map[wiki.PageType][]wiki.Entry{ wiki.PageTypeConcept: { {Slug: "betting", Title: "Betting"}, }, } got, warnings := CanonicalizeLinks(pages, inventory) require.Len(t, got, 1) assert.Empty(t, warnings) assert.Contains(t, got[0].Content, "[[betting|Betting]]") assert.NotContains(t, got[0].Content, "[[Betting]]") } func TestCanonicalizeLinks_UnknownTitleLeftAsIs(t *testing.T) { pages := []wiki.Page{ { Path: "wiki/sources/shape-up.md", Content: "---\ntitle: 'Shape Up'\n---\n\n## Summary\n\nSee [[Ghost Concept]].\n", }, } inventory := map[wiki.PageType][]wiki.Entry{} got, warnings := CanonicalizeLinks(pages, inventory) require.Len(t, got, 1) assert.NotEmpty(t, warnings) assert.Contains(t, got[0].Content, "[[Ghost Concept]]") } func TestCanonicalizeLinks_AlreadyCanonicalLinkUntouched(t *testing.T) { // Links already in [[slug|Display]] format must not be double-converted pages := []wiki.Page{ { Path: "wiki/sources/shape-up.md", Content: "---\ntitle: 'Shape Up'\n---\n\n## Summary\n\nSee [[betting|Betting]].\n", }, } inventory := map[wiki.PageType][]wiki.Entry{ wiki.PageTypeConcept: { {Slug: "betting", Title: "Betting"}, }, } got, warnings := CanonicalizeLinks(pages, inventory) require.Len(t, got, 1) assert.Empty(t, warnings) // Should remain exactly as-is — not double-wrapped assert.Contains(t, got[0].Content, "[[betting|Betting]]") assert.NotContains(t, got[0].Content, "[[betting|[[betting|Betting]]]]") } func TestCanonicalizeLinks_CaseInsensitiveMatch(t *testing.T) { pages := []wiki.Page{ { Path: "wiki/sources/foo.md", Content: "---\ntitle: 'Foo'\n---\n\n## Summary\n\nSee [[domain driven design]].\n", }, } inventory := map[wiki.PageType][]wiki.Entry{ wiki.PageTypeConcept: { {Slug: "domain-driven-design", Title: "Domain Driven Design"}, }, } got, warnings := CanonicalizeLinks(pages, inventory) require.Len(t, got, 1) assert.Empty(t, warnings) assert.Contains(t, got[0].Content, "[[domain-driven-design|domain driven design]]") } func TestCanonicalizeLinks_CurrentBatchPagesResolved(t *testing.T) { // A concept created in the same batch should be canonicalizable pages := []wiki.Page{ { Path: "wiki/sources/shape-up.md", Content: "---\ntitle: 'Shape Up'\n---\n\n## Summary\n\nSee [[Betting]].\n", }, { Path: "wiki/concepts/betting.md", Content: "---\ntitle: 'Betting'\n---\n\n## Definition\n\nA technique.\n", }, } inventory := map[wiki.PageType][]wiki.Entry{} // empty — Betting is in the batch, not inventory got, warnings := CanonicalizeLinks(pages, inventory) require.Len(t, got, 2) assert.Empty(t, warnings) assert.Contains(t, got[0].Content, "[[betting|Betting]]") } func TestCanonicalizeLinks_StripsWingColonPrefix(t *testing.T) { pages := []wiki.Page{ { Path: "wiki/homelab/failures/act-runner-host-mode-container-needs-node-and-libatomic.md", Content: "---\ntitle: 'act_runner host-mode'\n---\n\nSee [[wing:homelab/failures/rootless-buildah-act-runner-run-containers-denied]].\n", }, } got, warnings := CanonicalizeLinks(pages, map[wiki.PageType][]wiki.Entry{}) require.Len(t, got, 1) assert.Empty(t, warnings) assert.Contains(t, got[0].Content, "[[homelab/failures/rootless-buildah-act-runner-run-containers-denied]]") assert.NotContains(t, got[0].Content, "wing:") } func TestCanonicalizeLinks_StripsWikiSlashPrefix(t *testing.T) { pages := []wiki.Page{ { Path: "wiki/agentsquad/hypotheses/council-consolidation-standalone-deliberation-service.md", Content: "---\ntitle: 'council consolidation'\n---\n\nSee [[wiki/agentsquad/decisions/autoresearch-council-sibling-pipe]].\n", }, } got, warnings := CanonicalizeLinks(pages, map[wiki.PageType][]wiki.Entry{}) require.Len(t, got, 1) assert.Empty(t, warnings) assert.Contains(t, got[0].Content, "[[agentsquad/decisions/autoresearch-council-sibling-pipe]]") assert.NotContains(t, got[0].Content, "wiki/agentsquad/decisions/autoresearch-council-sibling-pipe]]\n\n") // no leftover wiki/ prefix assert.NotContains(t, got[0].Content, "[[wiki/") } func TestCanonicalizeLinks_TitleLookupStillTakesPriorityOverPrefixStrip(t *testing.T) { // A plain link that resolves via the title map must still use the // normal slug|Display form, not fall through to prefix-strip repair. pages := []wiki.Page{ { Path: "wiki/sources/shape-up.md", Content: "---\ntitle: 'Shape Up'\n---\n\nSee [[Betting]].\n", }, } inventory := map[wiki.PageType][]wiki.Entry{ wiki.PageTypeConcept: {{Slug: "betting", Title: "Betting"}}, } got, warnings := CanonicalizeLinks(pages, inventory) require.Len(t, got, 1) assert.Empty(t, warnings) assert.Contains(t, got[0].Content, "[[betting|Betting]]") } func TestCanonicalizeLinks_MultipleLinksInOnePage(t *testing.T) { pages := []wiki.Page{ { Path: "wiki/sources/foo.md", Content: "---\ntitle: 'Foo'\n---\n\n## Summary\n\nSee [[Betting]] and [[Shape Up]].\n", }, } inventory := map[wiki.PageType][]wiki.Entry{ wiki.PageTypeConcept: { {Slug: "betting", Title: "Betting"}, }, wiki.PageTypeSource: { {Slug: "shape-up", Title: "Shape Up"}, }, } got, warnings := CanonicalizeLinks(pages, inventory) require.Len(t, got, 1) assert.Empty(t, warnings) assert.Contains(t, got[0].Content, "[[betting|Betting]]") assert.Contains(t, got[0].Content, "[[shape-up|Shape Up]]") }