Skip to content
12 changes: 12 additions & 0 deletions src/Elastic.Markdown/IO/MarkdownFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,18 @@ private YamlFrontMatter ProcessYamlFrontMatter(MarkdownDocument document)
Collector.Emit(severity, FilePath, message);
}

// Validate mapped_pages URLs
if (fm.MappedPages is not null)
{
foreach (var url in fm.MappedPages)
{
if (!string.IsNullOrEmpty(url) && (!url.StartsWith("https://www.elastic.co/guide", StringComparison.OrdinalIgnoreCase) || !Uri.IsWellFormedUriString(url, UriKind.Absolute)))
{
Collector.EmitError(FilePath, $"Invalid mapped_pages URL: \"{url}\". All mapped_pages URLs must start with \"https://www.elastic.co/guide\". Please update the URL to reference content under the Elastic documentation guide.");
}
}
}

// TODO remove when migration tool and our demo content sets are updated
var deprecatedTitle = fm.Title;
if (!string.IsNullOrEmpty(deprecatedTitle))
Expand Down
153 changes: 153 additions & 0 deletions tests/Elastic.Markdown.Tests/FrontMatter/YamlFrontMatterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -183,3 +183,156 @@ public void HasErrors()
Collector.Diagnostics.Should().Contain(d => d.Message.Contains("Invalid products frontmatter value: \"Product 'id' field is required."));
}
}

public class MappedPagesValidUrl(ITestOutputHelper output) : DirectiveTest(output,
"""
---
mapped_pages:
- "https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html"
---

# Test Page
"""
)
{
[Fact]
public void NoErrors()
{
Collector.Diagnostics.Should().BeEmpty();
}
}

public class MappedPagesInvalidUrl(ITestOutputHelper output) : DirectiveTest(output,
"""
---
mapped_pages:
- "https://www.elastic.co/docs/get-started/deployment-options"
---

# Test Page
"""
)
{
[Fact]
public void HasErrors()
{
Collector.Diagnostics.Should().HaveCount(1);
Collector.Diagnostics.Should().Contain(d => d.Message.Contains("Invalid mapped_pages URL: \"https://www.elastic.co/docs/get-started/deployment-options\". All mapped_pages URLs must start with \"https://www.elastic.co/guide\". Please update the URL to reference content under the Elastic documentation guide."));
}
}

public class MappedPagesMixedUrls(ITestOutputHelper output) : DirectiveTest(output,
"""
---
mapped_pages:
- "https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html"
- "https://www.elastic.co/docs/invalid-url"
- "https://www.elastic.co/guide/en/kibana/current/index.html"
---

# Test Page
"""
)
{
[Fact]
public void HasErrorsForInvalidUrl()
{
Collector.Diagnostics.Should().HaveCount(1);
Collector.Diagnostics.Should().Contain(d => d.Message.Contains("Invalid mapped_pages URL: \"https://www.elastic.co/docs/invalid-url\". All mapped_pages URLs must start with \"https://www.elastic.co/guide\""));
}
}

public class MappedPagesEmptyUrl(ITestOutputHelper output) : DirectiveTest(output,
"""
---
mapped_pages:
- ""
---

# Test Page
"""
)
{
[Fact]
public void NoErrorsForEmptyUrl()
{
// Empty URLs are ignored, no validation error should occur
Collector.Diagnostics.Should().BeEmpty();
}
}

public class MappedPagesExternalUrl(ITestOutputHelper output) : DirectiveTest(output,
"""
---
mapped_pages:
- "https://github.com/elastic/docs-builder"
---

# Test Page
"""
)
{
[Fact]
public void HasErrorsForExternalUrl()
{
Collector.Diagnostics.Should().HaveCount(1);
Collector.Diagnostics.Should().Contain(d => d.Message.Contains("Invalid mapped_pages URL: \"https://github.com/elastic/docs-builder\". All mapped_pages URLs must start with \"https://www.elastic.co/guide\""));
}
}

public class MappedPagesMalformedUri(ITestOutputHelper output) : DirectiveTest(output,
"""
---
mapped_pages:
- "https://www.elastic.co/guide/[invalid-characters]"
---

# Test Page
"""
)
{
[Fact]
public void HasErrorsForMalformedUri()
{
Collector.Diagnostics.Should().HaveCount(1);
Collector.Diagnostics.Should().Contain(d => d.Message.Contains("Invalid mapped_pages URL: \"https://www.elastic.co/guide/[invalid-characters]\". All mapped_pages URLs must start with \"https://www.elastic.co/guide\""));
}
}

public class MappedPagesInvalidScheme(ITestOutputHelper output) : DirectiveTest(output,
"""
---
mapped_pages:
- "https://www.elastic.co/guide/invalid uri with spaces"
---

# Test Page
"""
)
{
[Fact]
public void HasErrorsForInvalidScheme()
{
Collector.Diagnostics.Should().HaveCount(1);
Collector.Diagnostics.Should().Contain(d => d.Message.Contains("Invalid mapped_pages URL: \"https://www.elastic.co/guide/invalid uri with spaces\". All mapped_pages URLs must start with \"https://www.elastic.co/guide\""));
}
}

public class MappedPagesNotAbsoluteUri(ITestOutputHelper output) : DirectiveTest(output,
"""
---
mapped_pages:
- "not-a-uri-at-all"
---

# Test Page
"""
)
{
[Fact]
public void HasErrorsForNotAbsoluteUri()
{
Collector.Diagnostics.Should().HaveCount(1);
Collector.Diagnostics.Should().Contain(d => d.Message.Contains("Invalid mapped_pages URL: \"not-a-uri-at-all\". All mapped_pages URLs must start with \"https://www.elastic.co/guide\""));
}
}
Loading