Files
hugo/hugolib/alias_test.go
hexbinoct feb3d494b9 hugolib: Use the output format's suffix for alias paths
Alias generation hardcoded "index.html" and only recognized a ".html"
suffix, so an alias with an explicit ".htm" extension (or any other
suffix configured for the html media type) was written to the wrong
path. For example "d.htm" produced the directory "d.htm/index.html"
instead of the file "d.htm".

Build the alias file name from the output format's BaseName and the
media type's first suffix, and detect an explicit file by matching the
alias extension against the configured suffixes. The same check now
drives the uglyURLs case in Aliases.

Fixes #15066
2026-07-04 14:31:34 +02:00

868 lines
30 KiB
Go

// Copyright 2025 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package hugolib
import (
"path/filepath"
"runtime"
"strings"
"testing"
"github.com/gohugoio/hugo/common/loggers"
"github.com/gohugoio/hugo/config"
"github.com/gohugoio/hugo/output"
)
func TestAlias(t *testing.T) {
t.Parallel()
tests := []struct {
fileSuffix string
urlPrefix string
urlSuffix string
settings map[string]any
}{
{"/index.html", "http://example.com", "/", map[string]any{"baseURL": "http://example.com"}},
{"/index.html", "http://example.com/some/path", "/", map[string]any{"baseURL": "http://example.com/some/path"}},
{"/index.html", "http://example.com", "/", map[string]any{"baseURL": "http://example.com", "canonifyURLs": true}},
{"/index.html", "../..", "/", map[string]any{"relativeURLs": true}},
{".html", "", ".html", map[string]any{"uglyURLs": true}},
}
for _, test := range tests {
files := `
-- hugo.toml --
disableKinds = ["rss", "sitemap", "taxonomy", "term"]
CONFIG
-- content/blog/page.md --
---
title: Has Alias
aliases: ["/foo/bar/", "rel"]
---
For some moments the old man did not reply. He stood with bowed head, buried in deep thought. But at last he spoke.
-- layouts/all.html --
Title: {{ .Title }}|Content: {{ .Content }}|
`
files = strings.Replace(files, "CONFIG", config.FromMapToTOMLString(test.settings), 1)
b := Test(t, files)
// the real page
b.AssertFileContent("public/blog/page"+test.fileSuffix, "For some moments the old man")
// the alias redirectors
b.AssertFileContent("public/foo/bar"+test.fileSuffix, "<meta http-equiv=\"refresh\" content=\"0; url="+test.urlPrefix+"/blog/page"+test.urlSuffix+"\">")
b.AssertFileContent("public/blog/rel"+test.fileSuffix, "<meta http-equiv=\"refresh\" content=\"0; url="+test.urlPrefix+"/blog/page"+test.urlSuffix+"\">")
}
}
func TestAliasMultipleOutputFormats(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "http://example.com"
-- layouts/single.html --
{{ .Content }}
-- layouts/single.amp.html --
{{ .Content }}
-- layouts/single.json --
{{ .Content }}
-- content/blog/page.md --
---
title: Has Alias for HTML and AMP
aliases: ["/foo/bar/"]
outputs: ["html", "amp", "json"]
---
For some moments the old man did not reply. He stood with bowed head, buried in deep thought. But at last he spoke.
`
b := Test(t, files)
// the real pages
b.AssertFileContent("public/blog/page/index.html", "For some moments the old man")
b.AssertFileContent("public/amp/blog/page/index.html", "For some moments the old man")
b.AssertFileContent("public/blog/page/index.json", "For some moments the old man")
// the alias redirectors
b.AssertFileContent("public/foo/bar/index.html", "<meta http-equiv=\"refresh\" content=\"0; ")
b.AssertFileContent("public/amp/foo/bar/index.html", "<meta http-equiv=\"refresh\" content=\"0; ")
b.AssertFileExists("public/foo/bar/index.json", false)
}
func TestAliasTemplate(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "http://example.com"
-- layouts/single.html --
Single.
-- layouts/home.html --
Home.
-- layouts/alias.html --
ALIASTEMPLATE
-- content/page.md --
---
title: "Page"
aliases: ["/foo/bar/"]
---
`
b := Test(t, files)
// the real page
b.AssertFileContent("public/page/index.html", "Single.")
// the alias redirector
b.AssertFileContent("public/foo/bar/index.html", "ALIASTEMPLATE")
}
func TestTargetPathHTMLRedirectAlias(t *testing.T) {
h := newAliasHandler(nil, loggers.NewDefault(), false)
errIsNilForThisOS := runtime.GOOS != "windows"
tests := []struct {
value string
expected string
errIsNil bool
}{
{"", "", false},
{"s", filepath.FromSlash("s/index.html"), true},
{"/", "", false},
{"alias 1", filepath.FromSlash("alias 1/index.html"), true},
{"alias 2/", filepath.FromSlash("alias 2/index.html"), true},
{"alias 3.html", "alias 3.html", true},
{"alias4.html", "alias4.html", true},
{"/alias 5.html", "alias 5.html", true},
{"/трям.html", "трям.html", true},
{"../../../../tmp/passwd", "", false},
{"/foo/../../../../tmp/passwd", filepath.FromSlash("tmp/passwd/index.html"), true},
{"foo/../../../../tmp/passwd", "", false},
{"C:\\Windows", filepath.FromSlash("C:\\Windows/index.html"), errIsNilForThisOS},
{"/trailing-space /", filepath.FromSlash("trailing-space /index.html"), errIsNilForThisOS},
{"/trailing-period./", filepath.FromSlash("trailing-period./index.html"), errIsNilForThisOS},
{"/tab\tseparated/", filepath.FromSlash("tab\tseparated/index.html"), errIsNilForThisOS},
{"/chrome/?p=help&ctx=keyboard#topic=3227046", filepath.FromSlash("chrome/?p=help&ctx=keyboard#topic=3227046/index.html"), errIsNilForThisOS},
{"/LPT1/Printer/", filepath.FromSlash("LPT1/Printer/index.html"), errIsNilForThisOS},
}
for _, test := range tests {
path, err := h.targetPathAlias(test.value, output.HTMLFormat)
if (err == nil) != test.errIsNil {
t.Errorf("Expected err == nil => %t, got: %t. err: %s", test.errIsNil, err == nil, err)
continue
}
if err == nil && path != test.expected {
t.Errorf("Expected: %q, got: %q", test.expected, path)
}
}
}
func TestAliasNIssue14053(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = "http://example.com"
-- layouts/all.html --
All.
-- content/page.md --
---
title: "Page"
aliases:
- n
- y
- no
- yes
---
`
b := Test(t, files)
b.AssertPublishDir("n/index.html", "yes/index.html", "no/index.html", "yes/index.html")
}
// Issue 14381
func TestIssue14381(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = 'https://example.org'
disableKinds = ['home', 'rss', 'sitemap', 'taxonomy', 'term']
[outputFormats.print]
isHTML = IS_HTML
permalinkable = true
mediaType = 'text/html'
path = 'print'
[outputs]
page = ['html', 'print']
section = ['html', 'print']
-- content/foo/s1/_index.md --
---
title: s1
aliases: [s1-alias]
---
-- content/foo/s1/p1.md --
---
title: p1
aliases: [p1-alias]
---
-- content/foo/s2/_index.md --
---
title: s2
aliases: [/s2-alias]
---
-- content/foo/s2/p2.md --
---
title: p2
aliases: [/p2-alias]
---
-- layouts/all.html --
{{ .Title }}
`
// ------------------------------------------------------------------------
// Test 1: Create aliases for the html output format only
// ------------------------------------------------------------------------
// public/
// ├── foo/
// │ ├── s1/
// │ │ ├── p1/
// │ │ │ └── index.html
// │ │ ├── p1-alias/
// │ │ │ └── index.html
// │ │ └── index.html
// │ ├── s1-alias/
// │ │ └── index.html
// │ ├── s2/
// │ │ ├── p2/
// │ │ │ └── index.html
// │ │ └── index.html
// │ └── index.html
// ├── p2-alias/
// │ └── index.html
// ├── print/
// │ └── foo/
// │ ├── s1/
// │ │ ├── p1/
// │ │ │ └── index.html
// │ │ └── index.html
// │ ├── s2/
// │ │ ├── p2/
// │ │ │ └── index.html
// │ │ └── index.html
// │ └── index.html
// └── s2-alias/
// └── index.html
f := strings.ReplaceAll(files, "IS_HTML", "false")
b := Test(t, f)
// output format: html
b.AssertFileContent("public/foo/s1-alias/index.html",
`<title>https://example.org/foo/s1/</title>`,
`<link rel="canonical" href="https://example.org/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/foo/s1/">`,
)
b.AssertFileContent("public/foo/s1/p1-alias/index.html",
`<title>https://example.org/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://example.org/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/foo/s1/p1/">`,
)
b.AssertFileContent("public/s2-alias/index.html",
`<title>https://example.org/foo/s2/</title>`,
`<link rel="canonical" href="https://example.org/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/foo/s2/">`,
)
b.AssertFileContent("public/p2-alias/index.html",
`<title>https://example.org/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://example.org/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/foo/s2/p2/">`,
)
// output format: print
b.AssertFileExists("public/print/foo/s1-alias/index.html", false)
b.AssertFileExists("public/print/foo/s1/p1-alias/index.html", false)
b.AssertFileExists("public/print/s2-alias/index.html", false)
b.AssertFileExists("public/print/p2-alias/index.html", false)
// ------------------------------------------------------------------------
// Test 2: Create aliases for the html and print output formats
// ------------------------------------------------------------------------
// public/
// ├── foo/
// │ ├── s1/
// │ │ ├── p1/
// │ │ │ └── index.html
// │ │ ├── p1-alias/
// │ │ │ └── index.html
// │ │ └── index.html
// │ ├── s1-alias/
// │ │ └── index.html
// │ ├── s2/
// │ │ ├── p2/
// │ │ │ └── index.html
// │ │ └── index.html
// │ └── index.html
// ├── p2-alias/
// │ └── index.html
// ├── print/
// │ ├── foo/
// │ │ ├── s1/
// │ │ │ ├── p1/
// │ │ │ │ └── index.html
// │ │ │ ├── p1-alias/
// │ │ │ │ └── index.html
// │ │ │ └── index.html
// │ │ ├── s1-alias/
// │ │ │ └── index.html
// │ │ ├── s2/
// │ │ │ ├── p2/
// │ │ │ │ └── index.html
// │ │ │ └── index.html
// │ │ └── index.html
// │ ├── p2-alias/
// │ │ └── index.html
// │ └── s2-alias/
// │ └── index.html
// └── s2-alias/
// └── index.html
f = strings.ReplaceAll(files, "IS_HTML", "true")
b = Test(t, f)
// output format: html
b.AssertFileContent("public/foo/s1-alias/index.html",
`<title>https://example.org/foo/s1/</title>`,
`<link rel="canonical" href="https://example.org/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/foo/s1/">`,
)
b.AssertFileContent("public/foo/s1/p1-alias/index.html",
`<title>https://example.org/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://example.org/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/foo/s1/p1/">`,
)
b.AssertFileContent("public/s2-alias/index.html",
`<title>https://example.org/foo/s2/</title>`,
`<link rel="canonical" href="https://example.org/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/foo/s2/">`,
)
b.AssertFileContent("public/p2-alias/index.html",
`<title>https://example.org/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://example.org/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/foo/s2/p2/">`,
)
// output format: print
b.AssertFileContent("public/print/foo/s1-alias/index.html",
`<title>https://example.org/print/foo/s1/</title>`,
`<link rel="canonical" href="https://example.org/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/print/foo/s1/">`,
)
b.AssertFileContent("public/print/foo/s1/p1-alias/index.html",
`<title>https://example.org/print/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://example.org/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/print/foo/s1/p1/">`,
)
b.AssertFileContent("public/print/s2-alias/index.html",
`<title>https://example.org/print/foo/s2/</title>`,
`<link rel="canonical" href="https://example.org/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/print/foo/s2/">`,
)
b.AssertFileContent("public/print/p2-alias/index.html",
`<title>https://example.org/print/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://example.org/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/print/foo/s2/p2/">`,
)
}
// Issue 14388
func TestIssue14388(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
baseURL = 'https://example.org'
disableKinds = ['home', 'rss', 'sitemap', 'taxonomy', 'term']
defaultContentLanguage = 'en'
defaultContentLanguageInSubdir = true
defaultContentRoleInSubdir = true
defaultContentVersionInSubdir = true
[languages.en]
weight = 1
# baseURL = 'https://en.example.org/'
[languages.de]
weight = 2
# baseURL = 'https://de.example.org/'
[outputFormats.print]
isHTML = true
mediaType = 'text/html'
path = 'print'
permalinkable = true
[outputs]
page = ['html', 'print']
section = ['html', 'print']
-- content/foo/s1/_index.de.md --
---
title: s1 de
aliases: [s1-alias]
---
-- content/foo/s1/_index.en.md --
---
title: s1 en
aliases: [s1-alias]
---
-- content/foo/s1/p1.de.md --
---
title: p1 de
aliases: [p1-alias]
---
-- content/foo/s1/p1.en.md --
---
title: p1 en
aliases: [p1-alias]
---
-- content/foo/s2/_index.de.md --
---
title: s2 de
aliases: [/s2-alias]
---
-- content/foo/s2/_index.en.md --
---
title: s2 en
aliases: [/s2-alias]
---
-- content/foo/s2/p2.de.md --
---
title: p2 de
aliases: [/p2-alias]
---
-- content/foo/s2/p2.en.md --
---
title: p2 en
aliases: [/p2-alias]
---
-- layouts/all.html --
{{ .Title }}
`
// ------------------------------------------------------------------------
// Test 1: Multilingual single-host
// ------------------------------------------------------------------------
b := Test(t, files)
// language: de, output format: html
b.AssertFileContent("public/guest/v1.0.0/de/foo/s1-alias/index.html",
`<title>https://example.org/guest/v1.0.0/de/foo/s1/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/de/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/de/foo/s1/">`,
)
b.AssertFileContent("public/guest/v1.0.0/de/foo/s1/p1-alias/index.html",
`<title>https://example.org/guest/v1.0.0/de/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/de/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/de/foo/s1/p1/">`,
)
b.AssertFileContent("public/guest/v1.0.0/de/s2-alias/index.html",
`<title>https://example.org/guest/v1.0.0/de/foo/s2/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/de/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/de/foo/s2/">`,
)
b.AssertFileContent("public/guest/v1.0.0/de/p2-alias/index.html",
`<title>https://example.org/guest/v1.0.0/de/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/de/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/de/foo/s2/p2/">`,
)
// language: de, output format: print
b.AssertFileContent("public/guest/v1.0.0/de/print/foo/s1-alias/index.html",
`<title>https://example.org/guest/v1.0.0/de/print/foo/s1/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/de/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/de/print/foo/s1/">`,
)
b.AssertFileContent("public/guest/v1.0.0/de/print/foo/s1/p1-alias/index.html",
`<title>https://example.org/guest/v1.0.0/de/print/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/de/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/de/print/foo/s1/p1/">`,
)
b.AssertFileContent("public/guest/v1.0.0/de/print/s2-alias/index.html",
`<title>https://example.org/guest/v1.0.0/de/print/foo/s2/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/de/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/de/print/foo/s2/">`,
)
b.AssertFileContent("public/guest/v1.0.0/de/print/p2-alias/index.html",
`<title>https://example.org/guest/v1.0.0/de/print/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/de/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/de/print/foo/s2/p2/">`,
)
// language: en, output format: html
b.AssertFileContent("public/guest/v1.0.0/en/foo/s1-alias/index.html",
`<title>https://example.org/guest/v1.0.0/en/foo/s1/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/en/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/en/foo/s1/">`,
)
b.AssertFileContent("public/guest/v1.0.0/en/foo/s1/p1-alias/index.html",
`<title>https://example.org/guest/v1.0.0/en/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/en/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/en/foo/s1/p1/">`,
)
b.AssertFileContent("public/guest/v1.0.0/en/s2-alias/index.html",
`<title>https://example.org/guest/v1.0.0/en/foo/s2/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/en/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/en/foo/s2/">`,
)
b.AssertFileContent("public/guest/v1.0.0/en/p2-alias/index.html",
`<title>https://example.org/guest/v1.0.0/en/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/en/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/en/foo/s2/p2/">`,
)
// language: en, output format: print
b.AssertFileContent("public/guest/v1.0.0/en/print/foo/s1-alias/index.html",
`<title>https://example.org/guest/v1.0.0/en/print/foo/s1/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/en/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/en/print/foo/s1/">`,
)
b.AssertFileContent("public/guest/v1.0.0/en/print/foo/s1/p1-alias/index.html",
`<title>https://example.org/guest/v1.0.0/en/print/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/en/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/en/print/foo/s1/p1/">`,
)
b.AssertFileContent("public/guest/v1.0.0/en/print/s2-alias/index.html",
`<title>https://example.org/guest/v1.0.0/en/print/foo/s2/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/en/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/en/print/foo/s2/">`,
)
b.AssertFileContent("public/guest/v1.0.0/en/print/p2-alias/index.html",
`<title>https://example.org/guest/v1.0.0/en/print/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://example.org/guest/v1.0.0/en/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://example.org/guest/v1.0.0/en/print/foo/s2/p2/">`,
)
// ------------------------------------------------------------------------
// Test 2: Multilingual multihost
// ------------------------------------------------------------------------
files = strings.ReplaceAll(files, "# baseURL", "baseURL")
b = Test(t, files)
// language: de, output format: html
b.AssertFileContent("public/de/guest/v1.0.0/foo/s1-alias/index.html",
`<title>https://de.example.org/guest/v1.0.0/foo/s1/</title>`,
`<link rel="canonical" href="https://de.example.org/guest/v1.0.0/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://de.example.org/guest/v1.0.0/foo/s1/">`,
)
b.AssertFileContent("public/de/guest/v1.0.0/foo/s1/p1-alias/index.html",
`<title>https://de.example.org/guest/v1.0.0/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://de.example.org/guest/v1.0.0/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://de.example.org/guest/v1.0.0/foo/s1/p1/">`,
)
b.AssertFileContent("public/de/guest/v1.0.0/s2-alias/index.html",
`<title>https://de.example.org/guest/v1.0.0/foo/s2/</title>`,
`<link rel="canonical" href="https://de.example.org/guest/v1.0.0/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://de.example.org/guest/v1.0.0/foo/s2/">`,
)
b.AssertFileContent("public/de/guest/v1.0.0/p2-alias/index.html",
`<title>https://de.example.org/guest/v1.0.0/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://de.example.org/guest/v1.0.0/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://de.example.org/guest/v1.0.0/foo/s2/p2/">`,
)
// language: de, output format: print
b.AssertFileContent("public/de/guest/v1.0.0/print/foo/s1-alias/index.html",
`<title>https://de.example.org/guest/v1.0.0/print/foo/s1/</title>`,
`<link rel="canonical" href="https://de.example.org/guest/v1.0.0/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://de.example.org/guest/v1.0.0/print/foo/s1/">`,
)
b.AssertFileContent("public/de/guest/v1.0.0/print/foo/s1/p1-alias/index.html",
`<title>https://de.example.org/guest/v1.0.0/print/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://de.example.org/guest/v1.0.0/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://de.example.org/guest/v1.0.0/print/foo/s1/p1/">`,
)
b.AssertFileContent("public/de/guest/v1.0.0/print/s2-alias/index.html",
`<title>https://de.example.org/guest/v1.0.0/print/foo/s2/</title>`,
`<link rel="canonical" href="https://de.example.org/guest/v1.0.0/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://de.example.org/guest/v1.0.0/print/foo/s2/">`,
)
b.AssertFileContent("public/de/guest/v1.0.0/print/p2-alias/index.html",
`<title>https://de.example.org/guest/v1.0.0/print/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://de.example.org/guest/v1.0.0/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://de.example.org/guest/v1.0.0/print/foo/s2/p2/">`,
)
// language: en, output format: html
b.AssertFileContent("public/en/guest/v1.0.0/foo/s1-alias/index.html",
`<title>https://en.example.org/guest/v1.0.0/foo/s1/</title>`,
`<link rel="canonical" href="https://en.example.org/guest/v1.0.0/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://en.example.org/guest/v1.0.0/foo/s1/">`,
)
b.AssertFileContent("public/en/guest/v1.0.0/foo/s1/p1-alias/index.html",
`<title>https://en.example.org/guest/v1.0.0/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://en.example.org/guest/v1.0.0/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://en.example.org/guest/v1.0.0/foo/s1/p1/">`,
)
b.AssertFileContent("public/en/guest/v1.0.0/s2-alias/index.html",
`<title>https://en.example.org/guest/v1.0.0/foo/s2/</title>`,
`<link rel="canonical" href="https://en.example.org/guest/v1.0.0/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://en.example.org/guest/v1.0.0/foo/s2/">`,
)
b.AssertFileContent("public/en/guest/v1.0.0/p2-alias/index.html",
`<title>https://en.example.org/guest/v1.0.0/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://en.example.org/guest/v1.0.0/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://en.example.org/guest/v1.0.0/foo/s2/p2/">`,
)
// language: en, output format: print
b.AssertFileContent("public/en/guest/v1.0.0/print/foo/s1-alias/index.html",
`<title>https://en.example.org/guest/v1.0.0/print/foo/s1/</title>`,
`<link rel="canonical" href="https://en.example.org/guest/v1.0.0/foo/s1/">`,
`<meta http-equiv="refresh" content="0; url=https://en.example.org/guest/v1.0.0/print/foo/s1/">`,
)
b.AssertFileContent("public/en/guest/v1.0.0/print/foo/s1/p1-alias/index.html",
`<title>https://en.example.org/guest/v1.0.0/print/foo/s1/p1/</title>`,
`<link rel="canonical" href="https://en.example.org/guest/v1.0.0/foo/s1/p1/">`,
`<meta http-equiv="refresh" content="0; url=https://en.example.org/guest/v1.0.0/print/foo/s1/p1/">`,
)
b.AssertFileContent("public/en/guest/v1.0.0/print/s2-alias/index.html",
`<title>https://en.example.org/guest/v1.0.0/print/foo/s2/</title>`,
`<link rel="canonical" href="https://en.example.org/guest/v1.0.0/foo/s2/">`,
`<meta http-equiv="refresh" content="0; url=https://en.example.org/guest/v1.0.0/print/foo/s2/">`,
)
b.AssertFileContent("public/en/guest/v1.0.0/print/p2-alias/index.html",
`<title>https://en.example.org/guest/v1.0.0/print/foo/s2/p2/</title>`,
`<link rel="canonical" href="https://en.example.org/guest/v1.0.0/foo/s2/p2/">`,
`<meta http-equiv="refresh" content="0; url=https://en.example.org/guest/v1.0.0/print/foo/s2/p2/">`,
)
}
// Issue #14402.
func TestComprehensiveAliasesRedirectsFile(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableAliases = true
defaultContentLanguageInSubdir = true
defaultContentRoleInSubdir = true
defaultContentVersionInSubdir = true
baseURL = "https://example.org/"
-- content/foo/p1.md --
---
aliases: ["/foo/p2/", "../p3/"]
---
-- content/foo/p2.md --
-- content/p3.md --
-- layouts/home.html --
Home.
{{ range $p := site.RegularPages }}{{ range .Aliases }}{{ . | printf "%-35s" }}=>{{ $p.RelPermalink -}}|{{ end -}}{{ end }}
`
b := Test(t, files)
b.AssertFileContent("public/guest/v1.0.0/en/index.html",
"/guest/v1.0.0/en/foo/p2 =>/guest/v1.0.0/en/foo/p1/|",
"/guest/v1.0.0/en/p3 =>/guest/v1.0.0/en/foo/p1/|",
)
}
// Issue 14482
func TestOutputFormatIsHTMLWithMultilangAliases(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
defaultContentLanguageInSubdir = true
disableKinds = ['page', 'section', 'rss', 'sitemap', 'taxonomy', 'term']
[languages.en]
[languages.de]
[outputFormats.foo]
baseName = 'foo'
isHTML = false
mediaType = 'text/html'
[outputs]
home = ['html', 'foo']
-- content/index.de.md --
---
title: home de
---
-- content/index.en.md --
---
title: home en
---
-- layouts/home.html --
Output format: html|Title: {{ .Title }}
-- layouts/home.foo.html --
Output format: foo|Title: {{ .Title }}
`
b := Test(t, files)
b.AssertFileContent("public/en/index.html", `Output format: html|Title: home en`)
b.AssertFileContent("public/en/foo.html", `Output format: foo|Title: home en`)
b.AssertFileContent("public/de/index.html", `Output format: html|Title: home de`)
b.AssertFileContent("public/de/foo.html", `Output format: foo|Title: home de`)
b.AssertFileContent("public/index.html", `
<head>
<title>/en/</title>
<link rel="canonical" href="/en/">
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=/en/">
</head>
`)
files = strings.ReplaceAll(files, "isHTML = false", "isHTML = true")
b = Test(t, files)
b.AssertFileContent("public/en/index.html", `Output format: html|Title: home en`)
b.AssertFileContent("public/en/foo.html", `Output format: foo|Title: home en`)
b.AssertFileContent("public/de/index.html", `Output format: html|Title: home de`)
b.AssertFileContent("public/de/foo.html", `Output format: foo|Title: home de`)
b.AssertFileContent("public/index.html", `
<head>
<title>/en/</title>
<link rel="canonical" href="/en/">
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=/en/">
</head>
`)
}
// Issue 14807
func TestDoNotRenderAliasesIfPageNotRendered14807(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
-- content/p1.md --
---
title: p1
aliases: [p1-alias]
---
-- content/p2.md --
---
title: p2
aliases: [p2-alias]
build:
render: never
---
-- content/p3.md --
---
title: p3
aliases: [p3-alias]
build:
render: link
---
-- layouts/page.html --
{{ .Title }}
`
b := Test(t, files)
b.AssertFileExists("public/p1/index.html", true)
b.AssertFileExists("public/p2/index.html", false)
b.AssertFileExists("public/p3/index.html", false)
b.AssertFileExists("public/p1-alias/index.html", true)
b.AssertFileExists("public/p2-alias/index.html", false)
b.AssertFileExists("public/p3-alias/index.html", false)
}
// See issue 15066.
func TestAliasExplicitExtension(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ['home','rss','section','sitemap','taxonomy','term']
uglyURLs = UGLYURLS
[mediaTypes]
[mediaTypes.'text/html']
suffixes = SUFFIXES
[outputFormats]
[outputFormats.html]
mediaType = 'text/html'
-- content/s1/p1.md --
---
title: p1
aliases: [a, b.foo, c.html, d.htm, e.foo.html, f.foo.htm]
---
-- layouts/page.html --
{{ .Title }}
`
r := strings.NewReplacer("UGLYURLS", "false", "SUFFIXES", "['html', 'htm']")
f := r.Replace(files)
b := Test(t, f)
b.AssertFileContent("public/s1/a/index.html", "url=/s1/p1/")
b.AssertFileContent("public/s1/b.foo/index.html", "url=/s1/p1/")
b.AssertFileContent("public/s1/c.html", "url=/s1/p1/")
b.AssertFileContent("public/s1/d.htm", "url=/s1/p1/")
b.AssertFileContent("public/s1/e.foo.html", "url=/s1/p1/")
b.AssertFileContent("public/s1/f.foo.htm", "url=/s1/p1/")
r = strings.NewReplacer("UGLYURLS", "false", "SUFFIXES", "['htm', 'html']")
f = r.Replace(files)
b = Test(t, f)
b.AssertFileContent("public/s1/a/index.htm", "url=/s1/p1/")
b.AssertFileContent("public/s1/b.foo/index.htm", "url=/s1/p1/")
b.AssertFileContent("public/s1/c.html", "url=/s1/p1/")
b.AssertFileContent("public/s1/d.htm", "url=/s1/p1/")
b.AssertFileContent("public/s1/e.foo.html", "url=/s1/p1/")
b.AssertFileContent("public/s1/f.foo.htm", "url=/s1/p1/")
r = strings.NewReplacer("UGLYURLS", "true", "SUFFIXES", "['html', 'htm']")
f = r.Replace(files)
b = Test(t, f)
b.AssertFileContent("public/s1/a.html", "url=/s1/p1.html")
b.AssertFileContent("public/s1/b.foo.html", "url=/s1/p1.html")
b.AssertFileContent("public/s1/c.html", "url=/s1/p1.html")
b.AssertFileContent("public/s1/d.htm", "url=/s1/p1.html")
b.AssertFileContent("public/s1/e.foo.html", "url=/s1/p1.html")
b.AssertFileContent("public/s1/f.foo.htm", "url=/s1/p1.html")
r = strings.NewReplacer("UGLYURLS", "true", "SUFFIXES", "['htm', 'html']")
f = r.Replace(files)
b = Test(t, f)
b.AssertFileContent("public/s1/a.htm", "url=/s1/p1.htm")
b.AssertFileContent("public/s1/b.foo.htm", "url=/s1/p1.htm")
b.AssertFileContent("public/s1/c.html", "url=/s1/p1.htm")
b.AssertFileContent("public/s1/d.htm", "url=/s1/p1.htm")
b.AssertFileContent("public/s1/e.foo.html", "url=/s1/p1.htm")
b.AssertFileContent("public/s1/f.foo.htm", "url=/s1/p1.htm")
}