github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/markup/asciidocext/convert_test.go (about)

     1  // Copyright 2020 The Hugo Authors. All rights reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  // Package asciidocext converts AsciiDoc to HTML using Asciidoctor
    15  // external binary. The `asciidoc` module is reserved for a future golang
    16  // implementation.
    17  
    18  package asciidocext
    19  
    20  import (
    21  	"path/filepath"
    22  	"testing"
    23  
    24  	"github.com/gohugoio/hugo/common/hexec"
    25  	"github.com/gohugoio/hugo/common/loggers"
    26  	"github.com/gohugoio/hugo/config"
    27  	"github.com/gohugoio/hugo/config/security"
    28  	"github.com/gohugoio/hugo/markup/converter"
    29  	"github.com/gohugoio/hugo/markup/markup_config"
    30  	"github.com/gohugoio/hugo/markup/tableofcontents"
    31  
    32  	qt "github.com/frankban/quicktest"
    33  )
    34  
    35  func TestAsciidoctorDefaultArgs(t *testing.T) {
    36  	c := qt.New(t)
    37  	cfg := config.New()
    38  	mconf := markup_config.Default
    39  
    40  	p, err := Provider.New(
    41  		converter.ProviderConfig{
    42  			Cfg:          cfg,
    43  			MarkupConfig: mconf,
    44  			Logger:       loggers.NewErrorLogger(),
    45  		},
    46  	)
    47  	c.Assert(err, qt.IsNil)
    48  
    49  	conv, err := p.New(converter.DocumentContext{})
    50  	c.Assert(err, qt.IsNil)
    51  
    52  	ac := conv.(*asciidocConverter)
    53  	c.Assert(ac, qt.Not(qt.IsNil))
    54  
    55  	args := ac.parseArgs(converter.DocumentContext{})
    56  	expected := []string{"--no-header-footer"}
    57  	c.Assert(args, qt.DeepEquals, expected)
    58  }
    59  
    60  func TestAsciidoctorNonDefaultArgs(t *testing.T) {
    61  	c := qt.New(t)
    62  	cfg := config.New()
    63  	mconf := markup_config.Default
    64  	mconf.AsciidocExt.Backend = "manpage"
    65  	mconf.AsciidocExt.NoHeaderOrFooter = false
    66  	mconf.AsciidocExt.SafeMode = "safe"
    67  	mconf.AsciidocExt.SectionNumbers = true
    68  	mconf.AsciidocExt.Verbose = true
    69  	mconf.AsciidocExt.Trace = false
    70  	mconf.AsciidocExt.FailureLevel = "warn"
    71  	p, err := Provider.New(
    72  		converter.ProviderConfig{
    73  			Cfg:          cfg,
    74  			MarkupConfig: mconf,
    75  			Logger:       loggers.NewErrorLogger(),
    76  		},
    77  	)
    78  	c.Assert(err, qt.IsNil)
    79  
    80  	conv, err := p.New(converter.DocumentContext{})
    81  	c.Assert(err, qt.IsNil)
    82  
    83  	ac := conv.(*asciidocConverter)
    84  	c.Assert(ac, qt.Not(qt.IsNil))
    85  
    86  	args := ac.parseArgs(converter.DocumentContext{})
    87  	expected := []string{"-b", "manpage", "--section-numbers", "--verbose", "--failure-level", "warn", "--safe-mode", "safe"}
    88  	c.Assert(args, qt.DeepEquals, expected)
    89  }
    90  
    91  func TestAsciidoctorDisallowedArgs(t *testing.T) {
    92  	c := qt.New(t)
    93  	cfg := config.New()
    94  	mconf := markup_config.Default
    95  	mconf.AsciidocExt.Backend = "disallowed-backend"
    96  	mconf.AsciidocExt.Extensions = []string{"./disallowed-extension"}
    97  	mconf.AsciidocExt.Attributes = map[string]string{"outdir": "disallowed-attribute"}
    98  	mconf.AsciidocExt.SafeMode = "disallowed-safemode"
    99  	mconf.AsciidocExt.FailureLevel = "disallowed-failurelevel"
   100  	p, err := Provider.New(
   101  		converter.ProviderConfig{
   102  			Cfg:          cfg,
   103  			MarkupConfig: mconf,
   104  			Logger:       loggers.NewErrorLogger(),
   105  		},
   106  	)
   107  	c.Assert(err, qt.IsNil)
   108  
   109  	conv, err := p.New(converter.DocumentContext{})
   110  	c.Assert(err, qt.IsNil)
   111  
   112  	ac := conv.(*asciidocConverter)
   113  	c.Assert(ac, qt.Not(qt.IsNil))
   114  
   115  	args := ac.parseArgs(converter.DocumentContext{})
   116  	expected := []string{"--no-header-footer"}
   117  	c.Assert(args, qt.DeepEquals, expected)
   118  }
   119  
   120  func TestAsciidoctorArbitraryExtension(t *testing.T) {
   121  	c := qt.New(t)
   122  	cfg := config.New()
   123  	mconf := markup_config.Default
   124  	mconf.AsciidocExt.Extensions = []string{"arbitrary-extension"}
   125  	p, err := Provider.New(
   126  		converter.ProviderConfig{
   127  			Cfg:          cfg,
   128  			MarkupConfig: mconf,
   129  			Logger:       loggers.NewErrorLogger(),
   130  		},
   131  	)
   132  	c.Assert(err, qt.IsNil)
   133  
   134  	conv, err := p.New(converter.DocumentContext{})
   135  	c.Assert(err, qt.IsNil)
   136  
   137  	ac := conv.(*asciidocConverter)
   138  	c.Assert(ac, qt.Not(qt.IsNil))
   139  
   140  	args := ac.parseArgs(converter.DocumentContext{})
   141  	expected := []string{"-r", "arbitrary-extension", "--no-header-footer"}
   142  	c.Assert(args, qt.DeepEquals, expected)
   143  }
   144  
   145  func TestAsciidoctorDisallowedExtension(t *testing.T) {
   146  	c := qt.New(t)
   147  	cfg := config.New()
   148  	for _, disallowedExtension := range []string{
   149  		`foo-bar//`,
   150  		`foo-bar\\ `,
   151  		`../../foo-bar`,
   152  		`/foo-bar`,
   153  		`C:\foo-bar`,
   154  		`foo-bar.rb`,
   155  		`foo.bar`,
   156  	} {
   157  		mconf := markup_config.Default
   158  		mconf.AsciidocExt.Extensions = []string{disallowedExtension}
   159  		p, err := Provider.New(
   160  			converter.ProviderConfig{
   161  				Cfg:          cfg,
   162  				MarkupConfig: mconf,
   163  				Logger:       loggers.NewErrorLogger(),
   164  			},
   165  		)
   166  		c.Assert(err, qt.IsNil)
   167  
   168  		conv, err := p.New(converter.DocumentContext{})
   169  		c.Assert(err, qt.IsNil)
   170  
   171  		ac := conv.(*asciidocConverter)
   172  		c.Assert(ac, qt.Not(qt.IsNil))
   173  
   174  		args := ac.parseArgs(converter.DocumentContext{})
   175  		expected := []string{"--no-header-footer"}
   176  		c.Assert(args, qt.DeepEquals, expected)
   177  	}
   178  }
   179  
   180  func TestAsciidoctorWorkingFolderCurrent(t *testing.T) {
   181  	c := qt.New(t)
   182  	cfg := config.New()
   183  	mconf := markup_config.Default
   184  	mconf.AsciidocExt.WorkingFolderCurrent = true
   185  	mconf.AsciidocExt.Trace = false
   186  	p, err := Provider.New(
   187  		converter.ProviderConfig{
   188  			Cfg:          cfg,
   189  			MarkupConfig: mconf,
   190  			Logger:       loggers.NewErrorLogger(),
   191  		},
   192  	)
   193  	c.Assert(err, qt.IsNil)
   194  
   195  	ctx := converter.DocumentContext{Filename: "/tmp/hugo_asciidoc_ddd/docs/chapter2/index.adoc", DocumentName: "chapter2/index.adoc"}
   196  	conv, err := p.New(ctx)
   197  	c.Assert(err, qt.IsNil)
   198  
   199  	ac := conv.(*asciidocConverter)
   200  	c.Assert(ac, qt.Not(qt.IsNil))
   201  
   202  	args := ac.parseArgs(ctx)
   203  	c.Assert(len(args), qt.Equals, 5)
   204  	c.Assert(args[0], qt.Equals, "--base-dir")
   205  	c.Assert(filepath.ToSlash(args[1]), qt.Matches, "/tmp/hugo_asciidoc_ddd/docs/chapter2")
   206  	c.Assert(args[2], qt.Equals, "-a")
   207  	c.Assert(args[3], qt.Matches, `outdir=.*[/\\]{1,2}asciidocext[/\\]{1,2}chapter2`)
   208  	c.Assert(args[4], qt.Equals, "--no-header-footer")
   209  }
   210  
   211  func TestAsciidoctorWorkingFolderCurrentAndExtensions(t *testing.T) {
   212  	c := qt.New(t)
   213  	cfg := config.New()
   214  	mconf := markup_config.Default
   215  	mconf.AsciidocExt.NoHeaderOrFooter = true
   216  	mconf.AsciidocExt.Extensions = []string{"asciidoctor-html5s", "asciidoctor-diagram"}
   217  	mconf.AsciidocExt.Backend = "html5s"
   218  	mconf.AsciidocExt.WorkingFolderCurrent = true
   219  	mconf.AsciidocExt.Trace = false
   220  	p, err := Provider.New(
   221  		converter.ProviderConfig{
   222  			Cfg:          cfg,
   223  			MarkupConfig: mconf,
   224  			Logger:       loggers.NewErrorLogger(),
   225  		},
   226  	)
   227  	c.Assert(err, qt.IsNil)
   228  
   229  	conv, err := p.New(converter.DocumentContext{})
   230  	c.Assert(err, qt.IsNil)
   231  
   232  	ac := conv.(*asciidocConverter)
   233  	c.Assert(ac, qt.Not(qt.IsNil))
   234  
   235  	args := ac.parseArgs(converter.DocumentContext{})
   236  	c.Assert(len(args), qt.Equals, 11)
   237  	c.Assert(args[0], qt.Equals, "-b")
   238  	c.Assert(args[1], qt.Equals, "html5s")
   239  	c.Assert(args[2], qt.Equals, "-r")
   240  	c.Assert(args[3], qt.Equals, "asciidoctor-html5s")
   241  	c.Assert(args[4], qt.Equals, "-r")
   242  	c.Assert(args[5], qt.Equals, "asciidoctor-diagram")
   243  	c.Assert(args[6], qt.Equals, "--base-dir")
   244  	c.Assert(args[7], qt.Equals, ".")
   245  	c.Assert(args[8], qt.Equals, "-a")
   246  	c.Assert(args[9], qt.Contains, "outdir=")
   247  	c.Assert(args[10], qt.Equals, "--no-header-footer")
   248  }
   249  
   250  func TestAsciidoctorAttributes(t *testing.T) {
   251  	c := qt.New(t)
   252  	cfg := config.New()
   253  	mconf := markup_config.Default
   254  	mconf.AsciidocExt.Attributes = map[string]string{"my-base-url": "https://gohugo.io/", "my-attribute-name": "my value"}
   255  	mconf.AsciidocExt.Trace = false
   256  	p, err := Provider.New(
   257  		converter.ProviderConfig{
   258  			Cfg:          cfg,
   259  			MarkupConfig: mconf,
   260  			Logger:       loggers.NewErrorLogger(),
   261  		},
   262  	)
   263  	c.Assert(err, qt.IsNil)
   264  
   265  	conv, err := p.New(converter.DocumentContext{})
   266  	c.Assert(err, qt.IsNil)
   267  
   268  	ac := conv.(*asciidocConverter)
   269  	c.Assert(ac, qt.Not(qt.IsNil))
   270  
   271  	expectedValues := map[string]bool{
   272  		"my-base-url=https://gohugo.io/": true,
   273  		"my-attribute-name=my value":     true,
   274  	}
   275  
   276  	args := ac.parseArgs(converter.DocumentContext{})
   277  	c.Assert(len(args), qt.Equals, 5)
   278  	c.Assert(args[0], qt.Equals, "-a")
   279  	c.Assert(expectedValues[args[1]], qt.Equals, true)
   280  	c.Assert(args[2], qt.Equals, "-a")
   281  	c.Assert(expectedValues[args[3]], qt.Equals, true)
   282  	c.Assert(args[4], qt.Equals, "--no-header-footer")
   283  }
   284  
   285  func getProvider(c *qt.C, mconf markup_config.Config) converter.Provider {
   286  	sc := security.DefaultConfig
   287  	sc.Exec.Allow = security.NewWhitelist("asciidoctor")
   288  
   289  	p, err := Provider.New(
   290  		converter.ProviderConfig{
   291  			MarkupConfig: mconf,
   292  			Logger:       loggers.NewErrorLogger(),
   293  			Exec:         hexec.New(sc),
   294  		},
   295  	)
   296  	c.Assert(err, qt.IsNil)
   297  	return p
   298  }
   299  
   300  func TestConvert(t *testing.T) {
   301  	if !Supports() {
   302  		t.Skip("asciidoctor not installed")
   303  	}
   304  	c := qt.New(t)
   305  
   306  	p := getProvider(c, markup_config.Default)
   307  
   308  	conv, err := p.New(converter.DocumentContext{})
   309  	c.Assert(err, qt.IsNil)
   310  
   311  	b, err := conv.Convert(converter.RenderContext{Src: []byte("testContent")})
   312  	c.Assert(err, qt.IsNil)
   313  	c.Assert(string(b.Bytes()), qt.Equals, "<div class=\"paragraph\">\n<p>testContent</p>\n</div>\n")
   314  }
   315  
   316  func TestTableOfContents(t *testing.T) {
   317  	if !Supports() {
   318  		t.Skip("asciidoctor not installed")
   319  	}
   320  	c := qt.New(t)
   321  	p := getProvider(c, markup_config.Default)
   322  
   323  	conv, err := p.New(converter.DocumentContext{})
   324  	c.Assert(err, qt.IsNil)
   325  	r, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc: macro
   326  :toclevels: 4
   327  toc::[]
   328  
   329  === Introduction
   330  
   331  == Section 1
   332  
   333  === Section 1.1
   334  
   335  ==== Section 1.1.1
   336  
   337  === Section 1.2
   338  
   339  testContent
   340  
   341  == Section 2
   342  `)})
   343  	c.Assert(err, qt.IsNil)
   344  	toc, ok := r.(converter.TableOfContentsProvider)
   345  	c.Assert(ok, qt.Equals, true)
   346  	expected := tableofcontents.Root{
   347  		Headings: tableofcontents.Headings{
   348  			{
   349  				ID:   "",
   350  				Text: "",
   351  				Headings: tableofcontents.Headings{
   352  					{
   353  						ID:       "_introduction",
   354  						Text:     "Introduction",
   355  						Headings: nil,
   356  					},
   357  					{
   358  						ID:   "_section_1",
   359  						Text: "Section 1",
   360  						Headings: tableofcontents.Headings{
   361  							{
   362  								ID:   "_section_1_1",
   363  								Text: "Section 1.1",
   364  								Headings: tableofcontents.Headings{
   365  									{
   366  										ID:       "_section_1_1_1",
   367  										Text:     "Section 1.1.1",
   368  										Headings: nil,
   369  									},
   370  								},
   371  							},
   372  							{
   373  								ID:       "_section_1_2",
   374  								Text:     "Section 1.2",
   375  								Headings: nil,
   376  							},
   377  						},
   378  					},
   379  					{
   380  						ID:       "_section_2",
   381  						Text:     "Section 2",
   382  						Headings: nil,
   383  					},
   384  				},
   385  			},
   386  		},
   387  	}
   388  	c.Assert(toc.TableOfContents(), qt.DeepEquals, expected)
   389  	c.Assert(string(r.Bytes()), qt.Not(qt.Contains), "<div id=\"toc\" class=\"toc\">")
   390  }
   391  
   392  func TestTableOfContentsWithCode(t *testing.T) {
   393  	if !Supports() {
   394  		t.Skip("asciidoctor not installed")
   395  	}
   396  	c := qt.New(t)
   397  	p := getProvider(c, markup_config.Default)
   398  	conv, err := p.New(converter.DocumentContext{})
   399  	c.Assert(err, qt.IsNil)
   400  	r, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc: auto
   401  
   402  == Some ` + "`code`" + ` in the title
   403  `)})
   404  	c.Assert(err, qt.IsNil)
   405  	toc, ok := r.(converter.TableOfContentsProvider)
   406  	c.Assert(ok, qt.Equals, true)
   407  	expected := tableofcontents.Root{
   408  		Headings: tableofcontents.Headings{
   409  			{
   410  				ID:   "",
   411  				Text: "",
   412  				Headings: tableofcontents.Headings{
   413  					{
   414  						ID:       "_some_code_in_the_title",
   415  						Text:     "Some <code>code</code> in the title",
   416  						Headings: nil,
   417  					},
   418  				},
   419  			},
   420  		},
   421  	}
   422  	c.Assert(toc.TableOfContents(), qt.DeepEquals, expected)
   423  	c.Assert(string(r.Bytes()), qt.Not(qt.Contains), "<div id=\"toc\" class=\"toc\">")
   424  }
   425  
   426  func TestTableOfContentsPreserveTOC(t *testing.T) {
   427  	if !Supports() {
   428  		t.Skip("asciidoctor not installed")
   429  	}
   430  	c := qt.New(t)
   431  	mconf := markup_config.Default
   432  	mconf.AsciidocExt.PreserveTOC = true
   433  	p := getProvider(c, mconf)
   434  
   435  	conv, err := p.New(converter.DocumentContext{})
   436  	c.Assert(err, qt.IsNil)
   437  	r, err := conv.Convert(converter.RenderContext{Src: []byte(`:toc:
   438  :idprefix:
   439  :idseparator: -
   440  
   441  == Some title
   442  `)})
   443  	c.Assert(err, qt.IsNil)
   444  	toc, ok := r.(converter.TableOfContentsProvider)
   445  	c.Assert(ok, qt.Equals, true)
   446  	expected := tableofcontents.Root{
   447  		Headings: tableofcontents.Headings{
   448  			{
   449  				ID:   "",
   450  				Text: "",
   451  				Headings: tableofcontents.Headings{
   452  					{
   453  						ID:       "some-title",
   454  						Text:     "Some title",
   455  						Headings: nil,
   456  					},
   457  				},
   458  			},
   459  		},
   460  	}
   461  	c.Assert(toc.TableOfContents(), qt.DeepEquals, expected)
   462  	c.Assert(string(r.Bytes()), qt.Contains, "<div id=\"toc\" class=\"toc\">")
   463  }