package pipeline import ( "context" "os" "path/filepath" "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func wingCfg(wing string, pages ...RawPage) Config { return Config{ Wing: wing, Schema: "# Schema", Complete: func(context.Context, string, string) (string, error) { return mustJSON(pages), nil }, } } func sourceEntityConcept() []RawPage { return []RawPage{ {Title: "Dark Factory Talk", Type: "source", Subtype: "video", Domain: "ai", Content: "## Summary\n\nA talk.\n\n## Entities Mentioned\n\n[[Jev]]\n\n## Concepts Introduced or Reinforced\n\n[[Dark Factory]]\n"}, {Title: "Jev", Type: "entity", Subtype: "tool", Domain: "ai", Content: "## Definition\n\nA decision model.\n\n## Sources\n"}, {Title: "Dark Factory", Type: "concept", Domain: "ai", Content: "## Definition\n\nA fully automated build line.\n\n## Sources\n"}, } } func exists(dir, rel string) bool { _, err := os.Stat(filepath.Join(dir, rel)) return err == nil } func read(t *testing.T, dir, rel string) string { t.Helper() b, err := os.ReadFile(filepath.Join(dir, rel)) require.NoError(t, err) return string(b) } // With a wing, the SOURCE belongs to the wing; concepts and entities are the // shared cross-wing layer (wiki/concepts, wiki/entities) because their slug is // the graph's global identity and they are cited from many wings. func TestRun_WithAWingTheSourceIsPerWingAndConceptsEntitiesAreShared(t *testing.T) { dir := t.TempDir() _, err := Run(context.Background(), wingCfg("web-clip", sourceEntityConcept()...), dir, "body of the talk", "dark-factory-talk", false) require.NoError(t, err) src := read(t, dir, "wiki/web-clip/sources/dark-factory-talk.md") assert.Contains(t, src, "wing: web-clip") assert.Contains(t, src, "hall: sources") assert.True(t, exists(dir, "wiki/entities/jev.md")) assert.True(t, exists(dir, "wiki/concepts/dark-factory.md")) assert.NotContains(t, read(t, dir, "wiki/entities/jev.md"), "wing:", "a shared page belongs to no single wing") assert.False(t, exists(dir, "wiki/web-clip/facts"), "no per-wing copy of a shared page") assert.False(t, exists(dir, "wiki/sources"), "the retired source layout is not used") } func TestRun_WithAWingTheSharedPageLinksBackToTheSource(t *testing.T) { dir := t.TempDir() _, err := Run(context.Background(), wingCfg("web-clip", sourceEntityConcept()...), dir, "body", "dark-factory-talk", false) require.NoError(t, err) assert.Contains(t, read(t, dir, "wiki/entities/jev.md"), "[[dark-factory-talk|Dark Factory Talk]]") } func TestRun_TheSameEntityCitedFromTwoWingsIsOnePageWithBothSources(t *testing.T) { dir := t.TempDir() _, err := Run(context.Background(), wingCfg("web-clip", sourceEntityConcept()...), dir, "first body", "talk-one", false) require.NoError(t, err) // A wing must already exist to be named; create it the way a note would. require.NoError(t, os.MkdirAll(filepath.Join(dir, "wiki/homelab/facts"), 0o755)) again := []RawPage{ {Title: "Second Talk", Type: "source", Subtype: "video", Content: "## Summary\n\nMore.\n\n## Entities Mentioned\n\n[[The Jev]]\n"}, {Title: "The Jev", Type: "entity", Subtype: "tool", Content: "## Definition\n\nStill a decision model.\n\n## Sources\n"}, } _, err = Run(context.Background(), wingCfg("homelab", again...), dir, "second body", "talk-two", false) require.NoError(t, err) files, err := filepath.Glob(filepath.Join(dir, "wiki/entities/*.md")) require.NoError(t, err) assert.Len(t, files, 1, "one entity page, matched by title with the leading article ignored") jev := read(t, dir, "wiki/entities/jev.md") assert.Contains(t, jev, "talk-one") assert.Contains(t, jev, "talk-two") assert.False(t, exists(dir, "wiki/homelab/facts/jev.md"), "no second copy in the second wing") } // A source slug is the graph's identity too: a second wing must not take over // the slug of a page that lives elsewhere. Two wings dropping a file of the same // name (notes.md, readme.md) is ordinary, so the second source gets a distinct, // stable slug instead of failing. func TestRun_ASourceWhoseSlugIsTakenElsewhereGetsADistinctStableSlug(t *testing.T) { dir := t.TempDir() _, err := Run(context.Background(), wingCfg("web-clip", sourceEntityConcept()...), dir, "body", "talk", false) require.NoError(t, err) require.NoError(t, os.MkdirAll(filepath.Join(dir, "wiki/homelab/facts"), 0o755)) _, err = Run(context.Background(), wingCfg("homelab", sourceEntityConcept()...), dir, "different body", "talk", false) require.NoError(t, err) assert.True(t, exists(dir, "wiki/web-clip/sources/talk.md"), "the first wing's page is untouched") assert.True(t, exists(dir, "wiki/homelab/sources/talk-homelab.md"), "the second gets a wing-qualified slug") assert.False(t, exists(dir, "wiki/homelab/sources/talk.md")) _, err = Run(context.Background(), wingCfg("homelab", sourceEntityConcept()...), dir, "different body, edited", "talk", false) require.NoError(t, err) files, _ := filepath.Glob(filepath.Join(dir, "wiki/homelab/sources/*.md")) assert.Len(t, files, 1, "re-ingesting finds the same page, it does not make talk-homelab-2") } func TestRun_ASourceWhoseSlugIsTakenByARetiredPageOrASharedPageGetsADistinctSlug(t *testing.T) { dir := t.TempDir() require.NoError(t, os.MkdirAll(filepath.Join(dir, "wiki/sources"), 0o755)) require.NoError(t, os.WriteFile(filepath.Join(dir, "wiki/sources/talk.md"), []byte("---\ntitle: Talk\n---\nold"), 0o644)) require.NoError(t, os.MkdirAll(filepath.Join(dir, "wiki/entities"), 0o755)) require.NoError(t, os.WriteFile(filepath.Join(dir, "wiki/entities/other.md"), []byte("---\ntitle: Other\n---\nx"), 0o644)) _, err := Run(context.Background(), wingCfg("web-clip", sourceEntityConcept()...), dir, "body", "talk", false) require.NoError(t, err) assert.True(t, exists(dir, "wiki/web-clip/sources/talk-web-clip.md")) assert.Equal(t, "---\ntitle: Talk\n---\nold", read(t, dir, "wiki/sources/talk.md"), "the retired page is untouched") } // The other direction: a model-extracted entity or concept whose slug is a // source's slug in some wing would lock that source out of re-ingest. The page // is skipped with a warning; the ingest itself succeeds. func TestRun_AnEntityWhoseSlugIsASourceElsewhereIsSkippedNotWritten(t *testing.T) { dir := t.TempDir() _, err := Run(context.Background(), wingCfg("web-clip", []RawPage{ {Title: "Jev", Type: "source", Subtype: "video", Content: "## Summary\n\nA clip titled Jev.\n"}, }...), dir, "body", "jev", false) require.NoError(t, err) require.NoError(t, os.MkdirAll(filepath.Join(dir, "wiki/homelab/facts"), 0o755)) res, err := Run(context.Background(), wingCfg("homelab", sourceEntityConcept()...), dir, "another body", "other-talk", false) require.NoError(t, err) assert.False(t, exists(dir, "wiki/entities/jev.md"), "the entity would take the source's slug") assert.Contains(t, strings.Join(res.Warnings, "\n"), "jev") _, err = Run(context.Background(), wingCfg("web-clip", []RawPage{ {Title: "Jev", Type: "source", Subtype: "video", Content: "## Summary\n\nEdited.\n"}, }...), dir, "body, edited", "jev", false) require.NoError(t, err, "the first wing can still re-ingest its own source") } // The type comes from the model, so from the clip: in a wing it may only be one // of the three the layout has a place for. func TestRun_WithAWingAModelChosenTypeCannotWriteOutsideTheLayout(t *testing.T) { dir := t.TempDir() require.NoError(t, os.MkdirAll(filepath.Join(dir, "wiki/alpha/facts"), 0o755)) pages := append(sourceEntityConcept(), RawPage{Title: "Planted", Type: "sources", Content: "## x\n\nplanted\n"}, RawPage{Title: "Loose", Type: "alpha", Content: "## x\n\nloose\n"}, RawPage{Title: "Sneaky", Type: "homelab", Content: "## x\n\nsneaky\n"}) res, err := Run(context.Background(), wingCfg("web-clip", pages...), dir, "body", "talk", false) require.NoError(t, err) assert.False(t, exists(dir, "wiki/sources/planted.md")) assert.False(t, exists(dir, "wiki/alpha/loose.md")) assert.False(t, exists(dir, "wiki/homelab")) assert.Contains(t, strings.Join(res.Warnings, "\n"), "invalid type") } func TestRun_ReIngestingTheSameSourceIntoItsOwnWingIsNotACollision(t *testing.T) { dir := t.TempDir() _, err := Run(context.Background(), wingCfg("web-clip", sourceEntityConcept()...), dir, "body one", "talk", false) require.NoError(t, err) _, err = Run(context.Background(), wingCfg("web-clip", sourceEntityConcept()...), dir, "body two, edited", "talk", false) require.NoError(t, err) } func TestRun_WithAWingRejectsANameThatIsNotAPlainWing(t *testing.T) { for _, bad := range []string{"../escape", "a/b", "Radar", "has space", "."} { t.Run(bad, func(t *testing.T) { dir := t.TempDir() _, err := Run(context.Background(), wingCfg(bad, sourceEntityConcept()...), dir, "body", "talk", false) require.Error(t, err) assert.Len(t, listAll(t, dir), 1, "nothing is written for a rejected wing (only the temp dir itself exists)") }) } } func listAll(t *testing.T, dir string) []string { t.Helper() var out []string _ = filepath.WalkDir(dir, func(p string, _ os.DirEntry, _ error) error { out = append(out, p); return nil }) return out } // Title resolution can mask a wrong slug choice at the Run level, so the // stability of the choice is pinned directly: the page this wing made earlier // under the qualified slug is "its own" and is found again. func TestResolveSourceSlug(t *testing.T) { dir := t.TempDir() for _, rel := range []string{ "wiki/web-clip/sources/talk.md", "wiki/homelab/sources/talk-homelab.md", "wiki/entities/kube.md", } { require.NoError(t, os.MkdirAll(filepath.Dir(filepath.Join(dir, rel)), 0o755)) require.NoError(t, os.WriteFile(filepath.Join(dir, rel), []byte("---\ntitle: x\n---\n"), 0o644)) } tests := []struct { name, wing, base, want string }{ {"free slug is used as is", "homelab", "fresh", "fresh"}, {"own page under the plain slug", "web-clip", "talk", "talk"}, {"taken elsewhere: wing-qualified", "tapir", "talk", "talk-tapir"}, {"own earlier qualified page is found again", "homelab", "talk", "talk-homelab"}, {"taken by a shared page", "tapir", "kube", "kube-tapir"}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got, err := resolveSourceSlug(dir, tt.wing, tt.base) require.NoError(t, err) assert.Equal(t, tt.want, got) }) } } func TestResolveSourceSlug_AFurtherClashGetsANumber(t *testing.T) { dir := t.TempDir() for _, rel := range []string{"wiki/a/sources/talk.md", "wiki/entities/talk-b.md"} { require.NoError(t, os.MkdirAll(filepath.Dir(filepath.Join(dir, rel)), 0o755)) require.NoError(t, os.WriteFile(filepath.Join(dir, rel), []byte("x"), 0o644)) } got, err := resolveSourceSlug(dir, "b", "talk") require.NoError(t, err) assert.Equal(t, "talk-b-2", got) }