-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhtmlcheck_test.go
More file actions
195 lines (178 loc) · 6 KB
/
htmlcheck_test.go
File metadata and controls
195 lines (178 loc) · 6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
package htmlcheck
import (
"errors"
"fmt"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"golang.org/x/net/html"
)
var v Validator = Validator{}
func TestMain(m *testing.M) {
v.AddGroup(&TagGroup{
Name: "example",
Attrs: []Attribute{{Name: "test1"}, {Name: "test2"}},
})
v.AddValidTag(ValidTag{
Name: "", //global tag
Attrs: []Attribute{{Name: "id"}},
AttrStartsWith: "data-",
IsSelfClosing: true,
})
v.AddValidTag(ValidTag{
Name: "a",
Attrs: []Attribute{{Name: "href"}},
Groups: []string{"example"},
IsSelfClosing: true,
})
v.AddValidTag(ValidTag{
Name: "b",
Attrs: []Attribute{{Name: "id"}},
IsSelfClosing: false,
})
v.AddValidTag(ValidTag{
Name: "c",
Attrs: []Attribute{{Name: "id"}},
IsSelfClosing: false,
})
v.AddValidTag(ValidTag{
Name: "style",
Attrs: []Attribute{{Name: "id"}},
IsSelfClosing: false,
})
v.AddValidTag(ValidTag{
Name: "img",
Attrs: []Attribute{{Name: "src", Value: &AttributeValue{Regex: "^(http(s|))://.*"}}},
})
v.AddValidTag(ValidTag{
Name: "testregex",
AttrRegex: "^(test).*",
})
v.AddValidTag(ValidTag{
Name: "startswith",
AttrStartsWith: "test-",
})
v.AddValidTag(ValidTag{
Name: "q",
Attrs: []Attribute{{Name: "cite", Value: &AttributeValue{StartsWith: "http"}}},
})
v.AddValidTag(ValidTag{
Name: "span",
Attrs: []Attribute{{Name: "class", Value: &AttributeValue{List: []string{"text-sm", "text-md", "text-lg"}}}},
})
os.Exit(m.Run())
}
func Test_ValidateHTMLString(t *testing.T) {
testCases := []struct {
desc string
rawHTML string
isValid bool
expectedErrors []interface{}
}{
{"Empty HTML", "", true, nil},
{"Single Tag", "<a></a>", true, nil},
{"Self Closing Tag", "<a>", true, nil},
{"Nested Tags", "<b><a></a></b>", true, nil},
{"Nested Tags + Self Closing", "<b><a></b>", true, nil},
{"Single Attribute", "<a href='test'>", true, nil},
{"Global Attribute", "<a data-test='test'>", true, nil}, // `data-` is added as global attribute
{"Single Attribute - Starts With (From Global)", "<style data-jiis='cc' id='gstyle'></style>", true, nil},
{"Single Attribute - Starts With", "<startswith test-data='test'></startswith>", true, nil},
{"Single Attribute - Regex", "<testregex test-attribute='test'></testregex>", true, nil},
{"Attributes Without Value", "<a test1 test2></a>", true, nil},
{"Attribute Value - List", "<span class='text-sm'></span>", true, nil},
{"Attribute Value - StartsWith", "<q cite='http://test.com'></q>", true, nil},
{"Attribute Value - Regex", "<img src='http://test.com'></img>", true, nil},
{"Attribute Value - Regex", "<img src='https://test.com'></img>", true, nil},
{"Groups", "<a test1=4 test2=5></a>", true, nil},
{"Unclosed Tag", "<b>text", false, []interface{}{&ErrInvNotProperlyClosed{}}},
{"Closed Before Opened", "</b><b>", false, []interface{}{&ErrInvClosedBeforeOpened{}}},
{"Unknown Tag", "<asd></asd>", false, []interface{}{&ErrInvTag{}}},
{"Unknown Tag - Single", "<asd>", false, []interface{}{&ErrInvTag{}}},
{"Wrongly Nested Tags", "<b><c></b></c>", false, nil},
{"Unknown Attribute", "<a hrefff='test'>", false, []interface{}{&ErrInvAttribute{}}},
{"Unknown Attribute - Regex", "<testregex attr-test='test'></testregex>", false, []interface{}{&ErrInvAttribute{}}},
{"Unknown Attribute - StartsWith", "<startswith attr-test='test'></startswith>", false, []interface{}{&ErrInvAttribute{}}},
{"Unknown Attribute + Nested", "<b kkk='kkk'><a></a></b>", false, []interface{}{&ErrInvAttribute{}}},
{"Unknown Attribute Value - List", "<span class='someclass'></span>", false, []interface{}{&ErrInvAttributeValue{}}},
{"Invalid Attribute Value - Regex", "<img src='ftp://test.com'></img>", false, []interface{}{&ErrInvAttributeValue{}}},
{"Duplicate Attribute", "<a href='test' href='test2'>", false, []interface{}{&ErrInvDuplicatedAttribute{}}},
}
for _, tC := range testCases {
t.Run(tC.desc, func(t *testing.T) {
_errors := v.ValidateHtmlString(tC.rawHTML)
if !tC.isValid {
assert.NotEmpty(t, _errors)
} else {
assert.Len(t, _errors, 0)
}
if len(tC.expectedErrors) > 0 {
for _, expectedError := range tC.expectedErrors {
found := false
for _, err := range _errors {
if errors.As(err, expectedError) {
found = true
}
}
assert.True(t, found)
}
}
})
}
}
func Test_Stop_On_Error(t *testing.T) {
v.StopAfterFirstError = true
defer func() {
v.StopAfterFirstError = false
}()
errors := v.ValidateHtmlString("<kkk></a>")
assert.Len(t, errors, 1)
}
func Test_Errors_Join(t *testing.T) {
err := v.ValidateHtmlString("<kkk></a>").Join()
assert.Error(t, err)
assert.ErrorAs(t, err, &ErrInvTag{})
assert.ErrorAs(t, err, &ErrInvClosedBeforeOpened{})
}
func Test_Callback(t *testing.T) {
generalError := errors.New("validation error")
triggerd := false
v.RegisterCallback(func(tagName string, attributeName string, value string, reason ErrorReason) error {
triggerd = true
return generalError
})
errors := v.ValidateHtmlString("<kkk>")
assert.True(t, triggerd)
assert.ErrorIs(t, errors[0], generalError)
}
func Test_Callback_DisableErrors(t *testing.T) {
v.RegisterCallback(func(tagName string, attributeName string, value string, reason ErrorReason) error {
if reason == InvTag || reason == InvAttribute {
return fmt.Errorf("validation error: tag '%s', attr: %s", tagName, attributeName)
}
return nil
})
errors := v.ValidateHtmlString("<kkk>")
assert.NotEmpty(t, errors)
errors = v.ValidateHtmlString("<a>")
assert.Empty(t, errors)
}
func BenchmarkValidateHtmlString(b *testing.B) {
for i := 0; i < b.N; i++ {
v.ValidateHtmlString("<b></b>\n<b></b>\n<b kkk='kkk'></b>")
}
}
func BenchmarkPlainTokenizerString(b *testing.B) {
for i := 0; i < b.N; i++ {
str := "<b></b>\n<b></b>\n<b kkk='kkk'></b>"
d := html.NewTokenizer(strings.NewReader(str))
for {
d.Token()
t := d.Next()
if t == html.ErrorToken {
break
}
}
}
}