
PathsTest.pudu
Pudu37 lines3.2 KB
1/** @Docgen.Paths.Tests — publication boundaries and destination escaping remain observable */2module PuduLangDocgen.PathsTest34import Std.Io as Io5import Std.Test as Test6import PuduLangDocgen.Paths as Paths789fn main() -> Int {10 let checks = Test.suite("Paths", &[11 Test.that("portable relative paths", Paths.relative("articles/hello-world.html")),12 Test.that("unicode path", Paths.relative("guide/été.md")),13 Test.that("invalid publication paths", ["", "/root", "../root", "a/../b", "a/./b", "a//b", "a/", "C:/file", "a\\b", "a%2fb", "a?b", "a#b", "a\n", "CON.txt", "a/lpt9", "a.", "a ", "<a>"].filter(|value: Str| Paths.relative(value)).isEmpty()),14 Test.that("safe browser destinations", ["https://example.org/a?q=1#b", "http://localhost:8080/", "mailto:a@example.org", "../other.md", "#section", "?lang=en", "a.md?x=1#h"].filter(|value: Str| !Paths.href(value)).isEmpty()),15 Test.that("unsafe browser destinations", ["", "javascript:alert(1)", "JaVaScRiPt:alert(1)", "data:text/html,hi", "//example.org", " https://example.org", "a\n", "a\\b", "https://", "https:///host", "https://user@host"].filter(|value: Str| Paths.href(value)).isEmpty()),16 Test.equals("slug separators collapse", &Paths.slug(" Hello, World! "), &"hello-world"),17 Test.equals("slug unicode preserved", &Paths.slug("Été 中文"), &"été-中文"),18 Test.equals("empty slug fallback", &Paths.slug("---"), &"section"),19 Test.equals("article suffix mapping", &[Paths.output("a.md"), Paths.output("a.MARKDOWN"), Paths.output("a.png")], &["a.html", "a.html", "a.png"]),20 Test.equals("relative resolution and query", &Paths.resolve("guide/start.md", "../api.md?x=1#hello"), &Ok("api.html?x=1#hello")),21 Test.equals("same page fragments", &Paths.resolve("guide/start.md", "#hello"), &Ok("guide/start.html#hello")),22 Test.equals("same page queries", &Paths.resolve("guide/start.md", "?lang=en#hello"), &Ok("guide/start.html?lang=en#hello")),23 Test.equals("external stays external", &Paths.resolve("index.md", "https://example.org/a"), &Ok("https://example.org/a")),24 Test.equals("root escape is refused", &Paths.resolve("index.md", "../secret.md"), &Err("destination escapes docset root")),25 Test.equals("encoded local path refused", &Paths.resolve("index.md", "%2e%2e/secret.md"), &Err("encoded local path is not supported")),26 Test.equals("unsafe source refused", &Paths.resolve("../index.md", "a.md"), &Err("invalid source path")),27 Test.equals("unsafe destination refused", &Paths.resolve("index.md", "javascript:run"), &Err("unsafe destination")),28 Test.equals("dot segments resolve", &Paths.resolve("guide/start.md", "./next.md"), &Ok("guide/next.html")),29 Test.equals("empty destination segment refused", &Paths.resolve("index.md", "a//b.md"), &Err("empty destination segment")),30 Test.equals("invalid local output refused", &Paths.resolve("index.md", "a|b.md"), &Err("invalid local destination")),31 Test.equals("html escaping exact", &Paths.escape("<&\"'>"), &"<&"'>")32 ])33 let ran = Test.run(&checks)34 for failure in Test.failuresOf(&ran) { let _printed = Io.writeErrorLine(failure) }35 Test.report(&ran)36}37