/*
* Copyright (c) 2013 Dario Castañé.
* This file is part of Zas.
*
* Zas is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Zas is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Zas. If not, see .
*/
package zas
import (
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"github.com/PuerkitoBio/goquery"
)
// installStub writes an executable shell-script plugin stub named name
// into a fresh directory and prepends it to PATH - unlike
// mime_plugin_test.go's stubs, several of these scripts shell out to real
// utilities (cat) to read stdin, so the original PATH must stay reachable.
func installStub(t *testing.T, name, script string) {
t.Helper()
if runtime.GOOS == "windows" {
t.Skip("shell script plugin stub is not portable to windows")
}
bin := t.TempDir()
if err := os.WriteFile(filepath.Join(bin, name), []byte(script), 0o755); err != nil {
t.Fatal(err)
}
t.Setenv("PATH", bin+string(os.PathListSeparator)+os.Getenv("PATH"))
}
func mustParseDoc(t *testing.T, htmlStr string) *goquery.Document {
t.Helper()
doc, err := goquery.NewDocumentFromReader(strings.NewReader(htmlStr))
if err != nil {
t.Fatal(err)
}
return doc
}
func TestSplitArgs(t *testing.T) {
tests := []struct {
name string
in string
want []string
}{
{"empty", "", nil},
{"all whitespace", " ", nil},
{"simple fields", "a b c", []string{"a", "b", "c"}},
{"single quoted with space", `--title 'My Post'`, []string{"--title", "My Post"}},
{"double quoted with space", `--title "My Post"`, []string{"--title", "My Post"}},
{"empty single quotes", "''", []string{""}},
{"empty double quotes", `""`, []string{""}},
{"quote removal mid-field", `a''b`, []string{"ab"}},
{"mixed quote removal", `-x'y'"z"`, []string{"-xyz"}},
{"single quotes keep backslash literal", `'a\b'`, []string{`a\b`}},
{"double quotes escape quote", `"a\"b"`, []string{`a"b`}},
{"double quotes escape backslash", `"a\\b"`, []string{`a\b`}},
{"double quotes keep other escapes literal", "\"a\\nb\"", []string{`a\nb`}},
{"backslash escapes space", `a\ b`, []string{"a b"}},
{"backslash escapes quote outside quotes", `a\'b`, []string{"a'b"}},
{"shell metacharacters are literal", `a\|b;c&d`, []string{"a|b;c&d"}},
{"utf8 argument", "café", []string{"café"}},
{"glob is literal", "*.md", []string{"*.md"}},
{"hash is literal", "#x", []string{"#x"}},
{"newline separates fields", "a\nb", []string{"a", "b"}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := splitArgs(tt.in)
if err != nil {
t.Fatalf("splitArgs(%q) error = %v, want nil", tt.in, err)
}
if !reflect.DeepEqual(got, tt.want) {
t.Fatalf("splitArgs(%q) = %#v, want %#v", tt.in, got, tt.want)
}
})
}
}
func TestSplitArgsRejectsUnbalancedQuoting(t *testing.T) {
tests := []struct {
name string
in string
}{
{"unterminated single quote", `'x`},
{"unterminated double quote", `"x`},
{"trailing backslash", `x\`},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
args, err := splitArgs(tt.in)
if err == nil {
t.Fatalf("splitArgs(%q) error = nil, want an error", tt.in)
}
if args != nil {
t.Fatalf("splitArgs(%q) args = %#v, want nil", tt.in, args)
}
})
}
}
func TestHandleScriptTagsRunsExternalCommand(t *testing.T) {
installStub(t, "zstest", "#!/bin/sh\necho 'ok'\n")
doc := mustParseDoc(t, `
`)
gen := &Generator{}
if err := gen.handleScriptTags(doc, headDropped); err != nil {
t.Fatalf("handleScriptTags() error = %v, want nil", err)
}
if got := doc.Find("b").Text(); got != "ok" {
t.Fatalf("plugin output not spliced into the document: got %q", got)
}
if doc.Find("script[type='application/zas+test']").Length() != 0 {
t.Fatal("zas script tag still present after handleScriptTags()")
}
}
func TestHandleScriptTagsPipesInnerContentToStdin(t *testing.T) {
installStub(t, "zswrap", "#!/bin/sh\nprintf ''; cat; printf '
'\n")
doc := mustParseDoc(t, ``)
gen := &Generator{}
if err := gen.handleScriptTags(doc, headDropped); err != nil {
t.Fatalf("handleScriptTags() error = %v, want nil", err)
}
if got := strings.TrimSpace(doc.Find("pre").Text()); got != `{"k":"v"}` {
t.Fatalf("stdin not piped through: got %q", got)
}
}
func TestHandleScriptTagsStdinIsNotEntityDecoded(t *testing.T) {
// The point of this test: if the tokenizer entity-decoded