Align topdata wiki canonical paths (#11)

Reviewed-on: https://gitea.westgate.pw/ShadowsOverWestgate/sow-tools/pulls/11
Co-authored-by: Michał Piasecki <mpiasecki720@protonmail.com>
Co-committed-by: Michał Piasecki <mpiasecki720@protonmail.com>
This commit is contained in:
2026-05-23 16:38:02 +02:00
committed by archvillainette
parent 7288d7e2b7
commit 6d6367ad18
6 changed files with 754 additions and 76 deletions
+209
View File
@@ -108,6 +108,54 @@ func TestCollectLocalPagesReadsGeneratedPageIDFromMarkerWhenPathIsTitleBased(t *
}
}
func TestCollectLocalPagesReadsCanonicalPublicPathFromPageIndex(t *testing.T) {
root := t.TempDir()
sourceDir := filepath.Join(root, "pages")
if err := os.MkdirAll(filepath.Join(sourceDir, "spells"), 0755); err != nil {
t.Fatalf("create source dir: %v", err)
}
generated := `<!-- sow-topdata-wiki:page=spells:pdk:fear -->
<!-- sow-topdata-wiki:managed:start hash="sha256:local" -->
<h1>Fear</h1>
<p>Generated page.</p>
<!-- sow-topdata-wiki:managed:end -->
`
if err := os.WriteFile(filepath.Join(sourceDir, "spells", "Fear.html"), []byte(generated), 0644); err != nil {
t.Fatalf("write source page: %v", err)
}
if err := saveWikiPageIndex(filepath.Join(root, "page-index.json"), wikiPageIndex{
Version: wikiGeneratorVersion,
Pages: []wikiPageIndexEntry{
{
PageID: "spells:pdk:fear",
PublicPath: "Magic/Fear",
OutputPath: "pages/spells/Fear.html",
},
},
}); err != nil {
t.Fatalf("write page index: %v", err)
}
pages, err := collectLocalPages(sourceDir, []string{"spells"})
if err != nil {
t.Fatalf("collectLocalPages failed: %v", err)
}
if got := pages["spells:pdk:fear"].PublicPath; got != "Magic/Fear" {
t.Fatalf("expected canonical public path from page-index, got %q", got)
}
}
func TestExtractWikiPagePublicPathAcceptsCanonicalSlashPath(t *testing.T) {
content := `<!-- sow-topdata-wiki:page=spells:pdk:fear public_path="Magic/Fear" -->
<!-- sow-topdata-wiki:managed:start hash="sha256:local" -->
<h1>Fear</h1>
<!-- sow-topdata-wiki:managed:end -->
`
if got := extractWikiPagePublicPath(content); got != "Magic/Fear" {
t.Fatalf("expected slash-separated public path, got %q", got)
}
}
func TestDeployWikiDryRunReadoptsMissingMappedPost(t *testing.T) {
root := t.TempDir()
sourceDir := filepath.Join(root, "pages")
@@ -200,6 +248,83 @@ func TestMatchExistingNodeBBPageIgnoresRetiredGeneratedPublicSlug(t *testing.T)
}
}
func TestMatchExistingNodeBBPageDoesNotPreferRetiredGeneratedSlugWhenRemoteOrderChanges(t *testing.T) {
page := wikiDeployPage{
PageID: "spells:pdk:fear",
Title: "Fear",
PublicPath: "Spells/Fear",
}
matched, ok := matchExistingNodeBBPage(page, []nodeBBWikiPage{
{TID: 32, Title: "Fear", TitleLeaf: "Fear", Slug: "32/pdk-fear", WikiPath: "/wiki/spells/pdk-fear"},
{TID: 31, Title: "Fear", TitleLeaf: "Fear", Slug: "31/fear"},
})
if !ok || matched.TID != 31 {
t.Fatalf("expected canonical leaf match to select tid 31 over retired generated slug, got %#v ok=%t", matched, ok)
}
}
func TestMatchExistingNodeBBPageAdoptsCanonicalWikiPathWhenSlugIsRetired(t *testing.T) {
page := wikiDeployPage{
PageID: "spells:pdk:fear",
Title: "Fear",
}
matched, ok := matchExistingNodeBBPage(page, []nodeBBWikiPage{
{TID: 32, Title: "Fear", TitleLeaf: "Fear", Slug: "32/pdk-fear", WikiPath: "/wiki/Spells/Fear"},
})
if !ok || matched.TID != 32 {
t.Fatalf("expected title fallback to adopt canonical wiki path despite retired NodeBB topic slug, got %#v ok=%t", matched, ok)
}
}
func TestMatchExistingNodeBBPageRequiresFullPathBeforeLeafFallback(t *testing.T) {
page := wikiDeployPage{
PageID: "spells:pdk:fear",
Title: "Fear",
PublicPath: "Spells/Necromancy/Fear",
}
matched, ok := matchExistingNodeBBPage(page, []nodeBBWikiPage{
{TID: 31, Title: "Fear", TitleLeaf: "Fear", WikiPath: "/wiki/Spells/Illusion/Fear"},
})
if ok {
t.Fatalf("expected no cross-namespace leaf fallback match, got %#v", matched)
}
}
func TestMatchExistingNodeBBPageRequiresCanonicalPathForNestedPublicPath(t *testing.T) {
page := wikiDeployPage{
PageID: "spells:pdk:fear",
Title: "Fear",
PublicPath: "Spells/Necromancy/Fear",
}
matched, ok := matchExistingNodeBBPage(page, []nodeBBWikiPage{
{TID: 31, Title: "Fear", TitleLeaf: "Fear", Slug: "31/fear"},
})
if ok {
t.Fatalf("expected no nested-path title or leaf fallback match without canonical path, got %#v", matched)
}
matched, ok = matchExistingNodeBBPage(page, []nodeBBWikiPage{
{TID: 31, Title: "Fear", TitleLeaf: "Fear", CanonicalPath: "Spells/Necromancy/Fear"},
})
if !ok || matched.TID != 31 {
t.Fatalf("expected canonical path match for nested public path, got %#v ok=%t", matched, ok)
}
}
func TestNestedCanonicalAdoptionFallbackError(t *testing.T) {
page := wikiDeployPage{
PageID: "spells:pdk:fear",
Title: "Fear",
PublicPath: "Spells/Necromancy/Fear",
}
err := nestedCanonicalAdoptionFallbackError(page, []nodeBBWikiPage{
{TID: 31, Title: "Fear", TitleLeaf: "Fear", Slug: "31/fear"},
})
if err == nil || !strings.Contains(err.Error(), "requires exact canonical wikiPath/canonicalPath") {
t.Fatalf("expected exact canonical remapping error, got %v", err)
}
}
func TestDeployCanonicalWikiSegmentMatchesGeneratedPathPolicy(t *testing.T) {
tests := map[string]string{
`Grandmaster's Battle Momentum`: "Grandmasters_Battle_Momentum",
@@ -217,6 +342,90 @@ func TestDeployCanonicalWikiSegmentMatchesGeneratedPathPolicy(t *testing.T) {
}
}
func TestLoadWikiNamespaceDeclarationsValidatesCanonicalPaths(t *testing.T) {
root := testProjectRoot(t)
wikiDir := filepath.Join(root, "topdata", "wiki")
if err := os.MkdirAll(wikiDir, 0755); err != nil {
t.Fatalf("create wiki dir: %v", err)
}
write := func(text string) {
t.Helper()
if err := os.WriteFile(filepath.Join(wikiDir, "namespaces.yaml"), []byte(text), 0644); err != nil {
t.Fatalf("write namespaces: %v", err)
}
}
write(`
namespaces:
- id: feat
title: Feats
canonical_path: Wiki/Feats
category_env: SOW_WIKI_FEATS_CID
edit_policy: preserve_manual_sections
`)
declarations, err := loadWikiNamespaceDeclarations(testProject(root))
if err != nil {
t.Fatalf("expected canonical namespace path declaration to load: %v", err)
}
if got := declarations[0].CanonicalPath; got != "Wiki/Feats" {
t.Fatalf("expected canonical namespace path to be preserved, got %q", got)
}
write(`
namespaces:
- id: search
title: Search
canonical_path: Wiki/Search
category_env: SOW_WIKI_SEARCH_CID
edit_policy: preserve_manual_sections
`)
declarations, err = loadWikiNamespaceDeclarations(testProject(root))
if err != nil {
t.Fatalf("expected reserved display title with safe canonical path to load: %v", err)
}
if got := declarations[0].CanonicalPath; got != "Wiki/Search" {
t.Fatalf("expected safe canonical namespace path to be preserved, got %q", got)
}
write(`
namespaces:
- id: bad
title: "!!!"
category_env: SOW_WIKI_BAD_CID
edit_policy: preserve_manual_sections
`)
_, err = loadWikiNamespaceDeclarations(testProject(root))
if err == nil || !strings.Contains(err.Error(), `topdata wiki namespace "bad" title`) {
t.Fatalf("expected invalid namespace title error, got %v", err)
}
write(`
namespaces:
- id: search
title: Search
canonical_path: search
category_env: SOW_WIKI_SEARCH_CID
edit_policy: preserve_manual_sections
`)
_, err = loadWikiNamespaceDeclarations(testProject(root))
if err == nil || !strings.Contains(err.Error(), `reserved first segment`) {
t.Fatalf("expected reserved namespace path error, got %v", err)
}
write(`
namespaces:
- id: numeric
title: Feats
canonical_path: 123/Feats
category_env: SOW_WIKI_NUMERIC_CID
edit_policy: preserve_manual_sections
`)
_, err = loadWikiNamespaceDeclarations(testProject(root))
if err == nil || !strings.Contains(err.Error(), `reserved first segment`) {
t.Fatalf("expected numeric namespace path error, got %v", err)
}
}
func TestDeployWikiCreatesNodeBBTopicAndWritesManifest(t *testing.T) {
root := t.TempDir()
sourceDir := filepath.Join(root, "pages")