Skip to content

Commit 50d8e37

Browse files
authored
Fix /autolink test command for disabled links (#123)
Also: - Started a "library" of domain-specific tests, to be used as templates for configuration - Added a template for ProductBoard - Fixed a lint nit in client.go
1 parent 1a6ac33 commit 50d8e37

File tree

7 files changed

+401
-316
lines changed

7 files changed

+401
-316
lines changed

server/autolink/autolink_test.go

+44-312
Large diffs are not rendered by default.
+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package autolink_test
2+
3+
import (
4+
"regexp"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
9+
"github.com/mattermost/mattermost-plugin-autolink/server/autolink"
10+
)
11+
12+
const (
13+
reVISA = `(?P<VISA>(?P<part1>4\d{3})[ -]?(?P<part2>\d{4})[ -]?(?P<part3>\d{4})[ -]?(?P<LastFour>[0-9]{4}))`
14+
reMasterCard = `(?P<MasterCard>(?P<part1>5[1-5]\d{2})[ -]?(?P<part2>\d{4})[ -]?(?P<part3>\d{4})[ -]?(?P<LastFour>[0-9]{4}))`
15+
reSwitchSolo = `(?P<SwitchSolo>(?P<part1>67\d{2})[ -]?(?P<part2>\d{4})[ -]?(?P<part3>\d{4})[ -]?(?P<LastFour>[0-9]{4}))`
16+
reDiscover = `(?P<Discover>(?P<part1>6011)[ -]?(?P<part2>\d{4})[ -]?(?P<part3>\d{4})[ -]?(?P<LastFour>[0-9]{4}))`
17+
reAMEX = `(?P<AMEX>(?P<part1>3[47]\d{2})[ -]?(?P<part2>\d{6})[ -]?(?P<part3>\d)(?P<LastFour>[0-9]{4}))`
18+
19+
replaceVISA = "VISA XXXX-XXXX-XXXX-$LastFour"
20+
replaceMasterCard = "MasterCard XXXX-XXXX-XXXX-$LastFour"
21+
replaceSwitchSolo = "Switch/Solo XXXX-XXXX-XXXX-$LastFour"
22+
replaceDiscover = "Discover XXXX-XXXX-XXXX-$LastFour"
23+
replaceAMEX = "American Express XXXX-XXXXXX-X$LastFour"
24+
)
25+
26+
func TestCreditCardRegex(t *testing.T) {
27+
for _, tc := range []struct {
28+
Name string
29+
RE string
30+
Replace string
31+
In string
32+
Out string
33+
}{
34+
{"Visa happy spaces", reVISA, replaceVISA, " abc 4111 1111 1111 1234 def", " abc VISA XXXX-XXXX-XXXX-1234 def"},
35+
{"Visa happy dashes", reVISA, replaceVISA, "4111-1111-1111-1234", "VISA XXXX-XXXX-XXXX-1234"},
36+
{"Visa happy mixed", reVISA, replaceVISA, "41111111 1111-1234", "VISA XXXX-XXXX-XXXX-1234"},
37+
{"Visa happy digits", reVISA, replaceVISA, "abc 4111111111111234 def", "abc VISA XXXX-XXXX-XXXX-1234 def"},
38+
{"Visa non-match start", reVISA, replaceVISA, "3111111111111234", ""},
39+
{"Visa non-match num digits", reVISA, replaceVISA, " 4111-1111-1111-123", ""},
40+
{"Visa non-match sep", reVISA, replaceVISA, "4111=1111=1111_1234", ""},
41+
{"Visa non-match no break before", reVISA, replaceVISA, "abc4111-1111-1111-1234", "abcVISA XXXX-XXXX-XXXX-1234"},
42+
{"Visa non-match no break after", reVISA, replaceVISA, "4111-1111-1111-1234def", "VISA XXXX-XXXX-XXXX-1234def"},
43+
44+
{"MasterCard happy spaces", reMasterCard, replaceMasterCard, " abc 5111 1111 1111 1234 def", " abc MasterCard XXXX-XXXX-XXXX-1234 def"},
45+
{"MasterCard happy dashes", reMasterCard, replaceMasterCard, "5211-1111-1111-1234", "MasterCard XXXX-XXXX-XXXX-1234"},
46+
{"MasterCard happy mixed", reMasterCard, replaceMasterCard, "53111111 1111-1234", "MasterCard XXXX-XXXX-XXXX-1234"},
47+
{"MasterCard happy digits", reMasterCard, replaceMasterCard, "abc 5411111111111234 def", "abc MasterCard XXXX-XXXX-XXXX-1234 def"},
48+
{"MasterCard non-match start", reMasterCard, replaceMasterCard, "3111111111111234", ""},
49+
{"MasterCard non-match num digits", reMasterCard, replaceMasterCard, " 5111-1111-1111-123", ""},
50+
{"MasterCard non-match sep", reMasterCard, replaceMasterCard, "5111=1111=1111_1234", ""},
51+
{"MasterCard non-match no break before", reMasterCard, replaceMasterCard, "abc5511-1111-1111-1234", "abcMasterCard XXXX-XXXX-XXXX-1234"},
52+
{"MasterCard non-match no break after", reMasterCard, replaceMasterCard, "5111-1111-1111-1234def", "MasterCard XXXX-XXXX-XXXX-1234def"},
53+
54+
{"SwitchSolo happy spaces", reSwitchSolo, replaceSwitchSolo, " abc 6711 1111 1111 1234 def", " abc Switch/Solo XXXX-XXXX-XXXX-1234 def"},
55+
{"SwitchSolo happy dashes", reSwitchSolo, replaceSwitchSolo, "6711-1111-1111-1234", "Switch/Solo XXXX-XXXX-XXXX-1234"},
56+
{"SwitchSolo happy mixed", reSwitchSolo, replaceSwitchSolo, "67111111 1111-1234", "Switch/Solo XXXX-XXXX-XXXX-1234"},
57+
{"SwitchSolo happy digits", reSwitchSolo, replaceSwitchSolo, "abc 6711111111111234 def", "abc Switch/Solo XXXX-XXXX-XXXX-1234 def"},
58+
{"SwitchSolo non-match start", reSwitchSolo, replaceSwitchSolo, "3111111111111234", ""},
59+
{"SwitchSolo non-match num digits", reSwitchSolo, replaceSwitchSolo, " 6711-1111-1111-123", ""},
60+
{"SwitchSolo non-match sep", reSwitchSolo, replaceSwitchSolo, "6711=1111=1111_1234", ""},
61+
{"SwitchSolo non-match no break before", reSwitchSolo, replaceSwitchSolo, "abc6711-1111-1111-1234", "abcSwitch/Solo XXXX-XXXX-XXXX-1234"},
62+
{"SwitchSolo non-match no break after", reSwitchSolo, replaceSwitchSolo, "6711-1111-1111-1234def", "Switch/Solo XXXX-XXXX-XXXX-1234def"},
63+
64+
{"Discover happy spaces", reDiscover, replaceDiscover, " abc 6011 1111 1111 1234 def", " abc Discover XXXX-XXXX-XXXX-1234 def"},
65+
{"Discover happy dashes", reDiscover, replaceDiscover, "6011-1111-1111-1234", "Discover XXXX-XXXX-XXXX-1234"},
66+
{"Discover happy mixed", reDiscover, replaceDiscover, "60111111 1111-1234", "Discover XXXX-XXXX-XXXX-1234"},
67+
{"Discover happy digits", reDiscover, replaceDiscover, "abc 6011111111111234 def", "abc Discover XXXX-XXXX-XXXX-1234 def"},
68+
{"Discover non-match start", reDiscover, replaceDiscover, "3111111111111234", ""},
69+
{"Discover non-match num digits", reDiscover, replaceDiscover, " 6011-1111-1111-123", ""},
70+
{"Discover non-match sep", reDiscover, replaceDiscover, "6011=1111=1111_1234", ""},
71+
{"Discover non-match no break before", reDiscover, replaceDiscover, "abc6011-1111-1111-1234", "abcDiscover XXXX-XXXX-XXXX-1234"},
72+
{"Discover non-match no break after", reDiscover, replaceDiscover, "6011-1111-1111-1234def", "Discover XXXX-XXXX-XXXX-1234def"},
73+
74+
{"AMEX happy spaces", reAMEX, replaceAMEX, " abc 3411 123456 12345 def", " abc American Express XXXX-XXXXXX-X2345 def"},
75+
{"AMEX happy dashes", reAMEX, replaceAMEX, "3711-123456-12345", "American Express XXXX-XXXXXX-X2345"},
76+
{"AMEX happy mixed", reAMEX, replaceAMEX, "3411-123456 12345", "American Express XXXX-XXXXXX-X2345"},
77+
{"AMEX happy digits", reAMEX, replaceAMEX, "abc 371112345612345 def", "abc American Express XXXX-XXXXXX-X2345 def"},
78+
{"AMEX non-match start 41", reAMEX, replaceAMEX, "411112345612345", ""},
79+
{"AMEX non-match start 31", reAMEX, replaceAMEX, "3111111111111234", ""},
80+
{"AMEX non-match num digits", reAMEX, replaceAMEX, " 4111-1111-1111-123", ""},
81+
{"AMEX non-match sep", reAMEX, replaceAMEX, "4111-1111=1111-1234", ""},
82+
{"AMEX non-match no break before", reAMEX, replaceAMEX, "abc3711-123456-12345", "abcAmerican Express XXXX-XXXXXX-X2345"},
83+
{"AMEX non-match no break after", reAMEX, replaceAMEX, "3711-123456-12345def", "American Express XXXX-XXXXXX-X2345def"},
84+
} {
85+
t.Run(tc.Name, func(t *testing.T) {
86+
re := regexp.MustCompile(tc.RE)
87+
result := re.ReplaceAllString(tc.In, tc.Replace)
88+
if tc.Out != "" {
89+
assert.Equal(t, tc.Out, result)
90+
} else {
91+
assert.Equal(t, tc.In, result)
92+
}
93+
})
94+
}
95+
}
96+
97+
func TestCreditCard(t *testing.T) {
98+
var tests = []struct {
99+
Name string
100+
Link autolink.Autolink
101+
inputMessage string
102+
expectedMessage string
103+
}{
104+
{
105+
"VISA happy",
106+
autolink.Autolink{
107+
Pattern: reVISA,
108+
Template: replaceVISA,
109+
},
110+
"A credit card 4111-1111-2222-1234 mentioned",
111+
"A credit card VISA XXXX-XXXX-XXXX-1234 mentioned",
112+
}, {
113+
"VISA",
114+
autolink.Autolink{
115+
Pattern: reVISA,
116+
Template: replaceVISA,
117+
DisableNonWordPrefix: true,
118+
DisableNonWordSuffix: true,
119+
},
120+
"A credit card4111-1111-2222-3333mentioned",
121+
"A credit cardVISA XXXX-XXXX-XXXX-3333mentioned",
122+
}, {
123+
"Multiple VISA replacements",
124+
autolink.Autolink{
125+
Pattern: reVISA,
126+
Template: replaceVISA,
127+
},
128+
"Credit cards 4111-1111-2222-3333 4222-3333-4444-5678 mentioned",
129+
"Credit cards VISA XXXX-XXXX-XXXX-3333 VISA XXXX-XXXX-XXXX-5678 mentioned",
130+
},
131+
}
132+
133+
for _, tt := range tests {
134+
t.Run(tt.Name, func(t *testing.T) {
135+
err := tt.Link.Compile()
136+
actual := tt.Link.Replace(tt.inputMessage)
137+
138+
assert.Equal(t, tt.expectedMessage, actual)
139+
assert.NoError(t, err)
140+
})
141+
}
142+
}

server/autolink/lib_jira_test.go

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package autolink_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/mattermost/mattermost-plugin-autolink/server/autolink"
7+
)
8+
9+
var jiraTests = []linkTest{
10+
{
11+
"Jira example",
12+
autolink.Autolink{
13+
Pattern: "(MM)(-)(?P<jira_id>\\d+)",
14+
Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)",
15+
},
16+
"Welcome MM-12345 should link!",
17+
"Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should link!",
18+
}, {
19+
"Jira example 2 (within a ())",
20+
autolink.Autolink{
21+
Pattern: "(MM)(-)(?P<jira_id>\\d+)",
22+
Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)",
23+
},
24+
"Link in brackets should link (see MM-12345)",
25+
"Link in brackets should link (see [MM-12345](https://mattermost.atlassian.net/browse/MM-12345))",
26+
}, {
27+
"Jira example 3 (before ,)",
28+
autolink.Autolink{
29+
Pattern: "(MM)(-)(?P<jira_id>\\d+)",
30+
Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)",
31+
},
32+
"Link a ticket MM-12345, before a comma",
33+
"Link a ticket [MM-12345](https://mattermost.atlassian.net/browse/MM-12345), before a comma",
34+
}, {
35+
"Jira example 3 (at begin of the message)",
36+
autolink.Autolink{
37+
Pattern: "(MM)(-)(?P<jira_id>\\d+)",
38+
Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)",
39+
},
40+
"MM-12345 should link!",
41+
"[MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should link!",
42+
}, {
43+
"Pattern word prefix and suffix disabled",
44+
autolink.Autolink{
45+
Pattern: "(?P<previous>^|\\s)(MM)(-)(?P<jira_id>\\d+)",
46+
Template: "${previous}[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)",
47+
DisableNonWordPrefix: true,
48+
DisableNonWordSuffix: true,
49+
},
50+
"Welcome MM-12345 should link!",
51+
"Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should link!",
52+
}, {
53+
"Pattern word prefix and suffix disabled (at begin of the message)",
54+
autolink.Autolink{
55+
Pattern: "(?P<previous>^|\\s)(MM)(-)(?P<jira_id>\\d+)",
56+
Template: "${previous}[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)",
57+
DisableNonWordPrefix: true,
58+
DisableNonWordSuffix: true,
59+
},
60+
"MM-12345 should link!",
61+
"[MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should link!",
62+
}, {
63+
"Pattern word prefix and suffix enable (in the middle of other text)",
64+
autolink.Autolink{
65+
Pattern: "(MM)(-)(?P<jira_id>\\d+)",
66+
Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)",
67+
},
68+
"WelcomeMM-12345should not link!",
69+
"WelcomeMM-12345should not link!",
70+
}, {
71+
"Pattern word prefix and suffix disabled (in the middle of other text)",
72+
autolink.Autolink{
73+
Pattern: "(MM)(-)(?P<jira_id>\\d+)",
74+
Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)",
75+
DisableNonWordPrefix: true,
76+
DisableNonWordSuffix: true,
77+
},
78+
"WelcomeMM-12345should link!",
79+
"Welcome[MM-12345](https://mattermost.atlassian.net/browse/MM-12345)should link!",
80+
}, {
81+
"Not relinking",
82+
autolink.Autolink{
83+
Pattern: "(MM)(-)(?P<jira_id>\\d+)",
84+
Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)",
85+
},
86+
"Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should not re-link!",
87+
"Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should not re-link!",
88+
}, {
89+
"Url replacement",
90+
autolink.Autolink{
91+
Pattern: "(https://mattermost.atlassian.net/browse/)(MM)(-)(?P<jira_id>\\d+)",
92+
Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)",
93+
},
94+
"Welcome https://mattermost.atlassian.net/browse/MM-12345 should link!",
95+
"Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should link!",
96+
}, {
97+
"Url replacement multiple times",
98+
autolink.Autolink{
99+
Pattern: "(https://mattermost.atlassian.net/browse/)(MM)(-)(?P<jira_id>\\d+)",
100+
Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)",
101+
},
102+
"Welcome https://mattermost.atlassian.net/browse/MM-12345. should link https://mattermost.atlassian.net/browse/MM-12346 !",
103+
"Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345). should link [MM-12346](https://mattermost.atlassian.net/browse/MM-12346) !",
104+
}, {
105+
"Url replacement multiple times and at beginning",
106+
autolink.Autolink{
107+
Pattern: "(https:\\/\\/mattermost.atlassian.net\\/browse\\/)(MM)(-)(?P<jira_id>\\d+)",
108+
Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)",
109+
},
110+
"https://mattermost.atlassian.net/browse/MM-12345 https://mattermost.atlassian.net/browse/MM-12345",
111+
"[MM-12345](https://mattermost.atlassian.net/browse/MM-12345) [MM-12345](https://mattermost.atlassian.net/browse/MM-12345)",
112+
}, {
113+
"Url replacement at end",
114+
autolink.Autolink{
115+
Pattern: "(https://mattermost.atlassian.net/browse/)(MM)(-)(?P<jira_id>\\d+)",
116+
Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)",
117+
},
118+
"Welcome https://mattermost.atlassian.net/browse/MM-12345",
119+
"Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345)",
120+
},
121+
}
122+
123+
func TestJira(t *testing.T) {
124+
testLinks(t, jiraTests...)
125+
}
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package autolink_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/mattermost/mattermost-plugin-autolink/server/autolink"
7+
)
8+
9+
var productboardLink = autolink.Autolink{
10+
Pattern: `(?P<url>https://mattermost\.productboard\.com/.+)`,
11+
Template: "[ProductBoard link]($url)",
12+
WordMatch: true,
13+
}
14+
15+
var productboardTests = []linkTest{
16+
{
17+
"Url replacement",
18+
productboardLink,
19+
"Welcome to https://mattermost.productboard.com/somepage should link!",
20+
"Welcome to [ProductBoard link](https://mattermost.productboard.com/somepage) should link!",
21+
}, {
22+
"Not relinking",
23+
productboardLink,
24+
"Welcome to [other link](https://mattermost.productboard.com/somepage) should not re-link!",
25+
"Welcome to [other link](https://mattermost.productboard.com/somepage) should not re-link!",
26+
}, {
27+
"Word boundary happy",
28+
productboardLink,
29+
"Welcome to (https://mattermost.productboard.com/somepage) should link!",
30+
"Welcome to ([ProductBoard link](https://mattermost.productboard.com/somepage)) should link!",
31+
}, {
32+
"Word boundary un-happy",
33+
productboardLink,
34+
"Welcome to (BADhttps://mattermost.productboard.com/somepage) should not link!",
35+
"Welcome to (BADhttps://mattermost.productboard.com/somepage) should not link!",
36+
},
37+
}
38+
39+
func TestProductBoard(t *testing.T) {
40+
testLinks(t, productboardTests...)
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package autolink_test
2+
3+
import (
4+
"regexp"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
const (
11+
reSSN = `(?P<SSN>(?P<part1>\d{3})[ -]?(?P<part2>\d{2})[ -]?(?P<LastFour>[0-9]{4}))`
12+
replaceSSN = `XXX-XX-$LastFour`
13+
)
14+
15+
func TestSocialSecurityNumberRegex(t *testing.T) {
16+
for _, tc := range []struct {
17+
Name string
18+
RE string
19+
Replace string
20+
In string
21+
Out string
22+
}{
23+
{"SSN happy spaces", reSSN, replaceSSN, " abc 652 47 3356 def", " abc XXX-XX-3356 def"},
24+
{"SSN happy dashes", reSSN, replaceSSN, " abc 652-47-3356 def", " abc XXX-XX-3356 def"},
25+
{"SSN happy digits", reSSN, replaceSSN, " abc 652473356 def", " abc XXX-XX-3356 def"},
26+
{"SSN happy mixed1", reSSN, replaceSSN, " abc 65247-3356 def", " abc XXX-XX-3356 def"},
27+
{"SSN happy mixed2", reSSN, replaceSSN, " abc 652 47-3356 def", " abc XXX-XX-3356 def"},
28+
{"SSN non-match 19-09-9999", reSSN, replaceSSN, " abc 19-09-9999 def", " abc 19-09-9999 def"},
29+
{"SSN non-match 652_47-3356", reSSN, replaceSSN, " abc 652_47-3356 def", " abc 652_47-3356 def"},
30+
} {
31+
t.Run(tc.Name, func(t *testing.T) {
32+
re := regexp.MustCompile(tc.RE)
33+
result := re.ReplaceAllString(tc.In, tc.Replace)
34+
if tc.Out != "" {
35+
assert.Equal(t, tc.Out, result)
36+
} else {
37+
assert.Equal(t, tc.In, result)
38+
}
39+
})
40+
}
41+
}

server/autolinkclient/client.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func NewClientPlugin(api PluginAPI) *Client {
4040

4141
func (c *Client) Add(links ...autolink.Autolink) error {
4242
for _, link := range links {
43-
linkBytes, err := json.Marshal(&link)
43+
linkBytes, err := json.Marshal(link)
4444
if err != nil {
4545
return err
4646
}

server/autolinkplugin/command.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -207,16 +207,20 @@ func executeTest(p *Plugin, c *plugin.Context, header *model.CommandArgs, args .
207207
restOfCommand := header.Command[10:] // "/autolink "
208208
restOfCommand = restOfCommand[strings.Index(restOfCommand, args[0])+len(args[0]):]
209209
orig := strings.TrimSpace(restOfCommand)
210-
out := ""
210+
out := fmt.Sprintf("- Original: `%s`\n", orig)
211211

212212
for _, ref := range refs {
213213
l := links[ref]
214214
l.Disabled = false
215+
err = l.Compile()
216+
if err != nil {
217+
return responsef("failed to compile link %s: %v", l.DisplayName(), err)
218+
}
215219
replaced := l.Replace(orig)
216220
if replaced == orig {
217-
out += fmt.Sprintf("- %s: _no change_\n", l.DisplayName())
221+
out += fmt.Sprintf("- Link %s: _no change_\n", l.DisplayName())
218222
} else {
219-
out += fmt.Sprintf("- %s: `%s`\n", l.DisplayName(), replaced)
223+
out += fmt.Sprintf("- Link %s: changed to `%s`\n", l.DisplayName(), replaced)
220224
orig = replaced
221225
}
222226
}

0 commit comments

Comments
 (0)