package pipeline import ( "context" "os" "path/filepath" "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 sourceAndEntity() []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"}, {Title: "Jev", Type: "entity", Subtype: "tool", Domain: "ai", Content: "## Definition\n\nA decision model.\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) } func TestRun_WithAWingWritesIntoThatWingsHalls(t *testing.T) { dir := t.TempDir() _, err := Run(context.Background(), wingCfg("radar", sourceAndEntity()...), dir, "body of the talk", "dark-factory-talk", false) require.NoError(t, err) src := read(t, dir, "wiki/radar/sources/dark-factory-talk.md") assert.Contains(t, src, "wing: radar") assert.Contains(t, src, "hall: sources") ent := read(t, dir, "wiki/radar/facts/jev.md") assert.Contains(t, ent, "wing: radar") assert.Contains(t, ent, "hall: facts") assert.Contains(t, ent, "kind: entity") } func TestRun_WithAWingDoesNotTouchTheRetiredLayout(t *testing.T) { dir := t.TempDir() _, err := Run(context.Background(), wingCfg("radar", sourceAndEntity()...), dir, "body of the talk", "dark-factory-talk", false) require.NoError(t, err) for _, retired := range []string{"wiki/sources", "wiki/concepts", "wiki/entities"} { assert.False(t, exists(dir, retired), "%s is the retired layout and must not be written", retired) } } func TestRun_WithAWingTheSourceLinksBackFromTheEntity(t *testing.T) { dir := t.TempDir() _, err := Run(context.Background(), wingCfg("radar", sourceAndEntity()...), dir, "body of the talk", "dark-factory-talk", false) require.NoError(t, err) assert.Contains(t, read(t, dir, "wiki/radar/facts/jev.md"), "[[dark-factory-talk|Dark Factory Talk]]") } func TestRun_WithAWingASecondSourceReusesTheSameEntityPage(t *testing.T) { dir := t.TempDir() _, err := Run(context.Background(), wingCfg("radar", sourceAndEntity()...), dir, "first body", "talk-one", false) require.NoError(t, err) 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("radar", again...), dir, "second body", "talk-two", false) require.NoError(t, err) files, err := filepath.Glob(filepath.Join(dir, "wiki/radar/facts/*.md")) require.NoError(t, err) assert.Len(t, files, 1, "an entity matched by title (leading article ignored) is merged, not duplicated") assert.Contains(t, read(t, dir, "wiki/radar/facts/jev.md"), "talk-two") } func TestRun_WithAWingNeverMergesIntoAnotherWingsPage(t *testing.T) { dir := t.TempDir() other := "---\ntitle: Jev\nwing: client-x\nhall: facts\nkind: entity\n---\n\n## Definition\n\nPrivate to another wing.\n" require.NoError(t, os.MkdirAll(filepath.Join(dir, "wiki/client-x/facts"), 0o755)) require.NoError(t, os.WriteFile(filepath.Join(dir, "wiki/client-x/facts/jev.md"), []byte(other), 0o644)) _, err := Run(context.Background(), wingCfg("radar", sourceAndEntity()...), dir, "body", "dark-factory-talk", false) require.NoError(t, err) assert.Equal(t, other, read(t, dir, "wiki/client-x/facts/jev.md"), "another wing's page is never read into or rewritten") assert.True(t, exists(dir, "wiki/radar/facts/jev.md")) } 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, sourceAndEntity()...), 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 } // The dangerous path is the back-reference: a source that links [[Jev]] without // proposing a Jev page makes the pipeline go and edit an existing Jev page. That // lookup must be confined to the target wing. func TestRun_WithAWingASourceLinkingToAnotherWingsEntityDoesNotEditIt(t *testing.T) { dir := t.TempDir() other := "---\ntitle: Jev\nwing: client-x\nhall: facts\nkind: entity\n---\n\n## Definition\n\nPrivate to another wing.\n\n## Sources\n" require.NoError(t, os.MkdirAll(filepath.Join(dir, "wiki/client-x/facts"), 0o755)) require.NoError(t, os.WriteFile(filepath.Join(dir, "wiki/client-x/facts/jev.md"), []byte(other), 0o644)) onlySource := sourceAndEntity()[:1] _, err := Run(context.Background(), wingCfg("radar", onlySource...), dir, "body", "dark-factory-talk", false) require.NoError(t, err) assert.Equal(t, other, read(t, dir, "wiki/client-x/facts/jev.md"), "no back-reference may be written into another wing") }