From 50d8e37a45dc87ba9aecabbc77f63bbe9f668d0e Mon Sep 17 00:00:00 2001 From: Lev <1187448+levb@users.noreply.github.com> Date: Mon, 22 Jun 2020 09:23:15 -0700 Subject: [PATCH] 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 --- server/autolink/autolink_test.go | 356 +++--------------- server/autolink/lib_credit_card_test.go | 142 +++++++ server/autolink/lib_jira_test.go | 125 ++++++ server/autolink/lib_productboard_test.go | 41 ++ .../lib_social_security_number_test.go | 41 ++ server/autolinkclient/client.go | 2 +- server/autolinkplugin/command.go | 10 +- 7 files changed, 401 insertions(+), 316 deletions(-) create mode 100644 server/autolink/lib_credit_card_test.go create mode 100644 server/autolink/lib_jira_test.go create mode 100644 server/autolink/lib_productboard_test.go create mode 100644 server/autolink/lib_social_security_number_test.go diff --git a/server/autolink/autolink_test.go b/server/autolink/autolink_test.go index baa8d172..b40d7eaa 100644 --- a/server/autolink/autolink_test.go +++ b/server/autolink/autolink_test.go @@ -2,7 +2,6 @@ package autolink_test import ( "fmt" - "regexp" "testing" "github.com/mattermost/mattermost-server/v5/model" @@ -42,322 +41,51 @@ func setupTestPlugin(t *testing.T, l autolink.Autolink) *autolinkplugin.Plugin { return p } -const ( - reVISA = `(?P(?P4\d{3})[ -]?(?P\d{4})[ -]?(?P\d{4})[ -]?(?P[0-9]{4}))` - reMasterCard = `(?P(?P5[1-5]\d{2})[ -]?(?P\d{4})[ -]?(?P\d{4})[ -]?(?P[0-9]{4}))` - reSwitchSolo = `(?P(?P67\d{2})[ -]?(?P\d{4})[ -]?(?P\d{4})[ -]?(?P[0-9]{4}))` - reDiscover = `(?P(?P6011)[ -]?(?P\d{4})[ -]?(?P\d{4})[ -]?(?P[0-9]{4}))` - reAMEX = `(?P(?P3[47]\d{2})[ -]?(?P\d{6})[ -]?(?P\d)(?P[0-9]{4}))` - - replaceVISA = "VISA XXXX-XXXX-XXXX-$LastFour" - replaceMasterCard = "MasterCard XXXX-XXXX-XXXX-$LastFour" - replaceSwitchSolo = "Switch/Solo XXXX-XXXX-XXXX-$LastFour" - replaceDiscover = "Discover XXXX-XXXX-XXXX-$LastFour" - replaceAMEX = "American Express XXXX-XXXXXX-X$LastFour" -) - -func TestCCRegex(t *testing.T) { - for _, tc := range []struct { - Name string - RE string - Replace string - In string - Out string - }{ - {"Visa happy spaces", reVISA, replaceVISA, " abc 4111 1111 1111 1234 def", " abc VISA XXXX-XXXX-XXXX-1234 def"}, - {"Visa happy dashes", reVISA, replaceVISA, "4111-1111-1111-1234", "VISA XXXX-XXXX-XXXX-1234"}, - {"Visa happy mixed", reVISA, replaceVISA, "41111111 1111-1234", "VISA XXXX-XXXX-XXXX-1234"}, - {"Visa happy digits", reVISA, replaceVISA, "abc 4111111111111234 def", "abc VISA XXXX-XXXX-XXXX-1234 def"}, - {"Visa non-match start", reVISA, replaceVISA, "3111111111111234", ""}, - {"Visa non-match num digits", reVISA, replaceVISA, " 4111-1111-1111-123", ""}, - {"Visa non-match sep", reVISA, replaceVISA, "4111=1111=1111_1234", ""}, - {"Visa non-match no break before", reVISA, replaceVISA, "abc4111-1111-1111-1234", "abcVISA XXXX-XXXX-XXXX-1234"}, - {"Visa non-match no break after", reVISA, replaceVISA, "4111-1111-1111-1234def", "VISA XXXX-XXXX-XXXX-1234def"}, - - {"MasterCard happy spaces", reMasterCard, replaceMasterCard, " abc 5111 1111 1111 1234 def", " abc MasterCard XXXX-XXXX-XXXX-1234 def"}, - {"MasterCard happy dashes", reMasterCard, replaceMasterCard, "5211-1111-1111-1234", "MasterCard XXXX-XXXX-XXXX-1234"}, - {"MasterCard happy mixed", reMasterCard, replaceMasterCard, "53111111 1111-1234", "MasterCard XXXX-XXXX-XXXX-1234"}, - {"MasterCard happy digits", reMasterCard, replaceMasterCard, "abc 5411111111111234 def", "abc MasterCard XXXX-XXXX-XXXX-1234 def"}, - {"MasterCard non-match start", reMasterCard, replaceMasterCard, "3111111111111234", ""}, - {"MasterCard non-match num digits", reMasterCard, replaceMasterCard, " 5111-1111-1111-123", ""}, - {"MasterCard non-match sep", reMasterCard, replaceMasterCard, "5111=1111=1111_1234", ""}, - {"MasterCard non-match no break before", reMasterCard, replaceMasterCard, "abc5511-1111-1111-1234", "abcMasterCard XXXX-XXXX-XXXX-1234"}, - {"MasterCard non-match no break after", reMasterCard, replaceMasterCard, "5111-1111-1111-1234def", "MasterCard XXXX-XXXX-XXXX-1234def"}, - - {"SwitchSolo happy spaces", reSwitchSolo, replaceSwitchSolo, " abc 6711 1111 1111 1234 def", " abc Switch/Solo XXXX-XXXX-XXXX-1234 def"}, - {"SwitchSolo happy dashes", reSwitchSolo, replaceSwitchSolo, "6711-1111-1111-1234", "Switch/Solo XXXX-XXXX-XXXX-1234"}, - {"SwitchSolo happy mixed", reSwitchSolo, replaceSwitchSolo, "67111111 1111-1234", "Switch/Solo XXXX-XXXX-XXXX-1234"}, - {"SwitchSolo happy digits", reSwitchSolo, replaceSwitchSolo, "abc 6711111111111234 def", "abc Switch/Solo XXXX-XXXX-XXXX-1234 def"}, - {"SwitchSolo non-match start", reSwitchSolo, replaceSwitchSolo, "3111111111111234", ""}, - {"SwitchSolo non-match num digits", reSwitchSolo, replaceSwitchSolo, " 6711-1111-1111-123", ""}, - {"SwitchSolo non-match sep", reSwitchSolo, replaceSwitchSolo, "6711=1111=1111_1234", ""}, - {"SwitchSolo non-match no break before", reSwitchSolo, replaceSwitchSolo, "abc6711-1111-1111-1234", "abcSwitch/Solo XXXX-XXXX-XXXX-1234"}, - {"SwitchSolo non-match no break after", reSwitchSolo, replaceSwitchSolo, "6711-1111-1111-1234def", "Switch/Solo XXXX-XXXX-XXXX-1234def"}, - - {"Discover happy spaces", reDiscover, replaceDiscover, " abc 6011 1111 1111 1234 def", " abc Discover XXXX-XXXX-XXXX-1234 def"}, - {"Discover happy dashes", reDiscover, replaceDiscover, "6011-1111-1111-1234", "Discover XXXX-XXXX-XXXX-1234"}, - {"Discover happy mixed", reDiscover, replaceDiscover, "60111111 1111-1234", "Discover XXXX-XXXX-XXXX-1234"}, - {"Discover happy digits", reDiscover, replaceDiscover, "abc 6011111111111234 def", "abc Discover XXXX-XXXX-XXXX-1234 def"}, - {"Discover non-match start", reDiscover, replaceDiscover, "3111111111111234", ""}, - {"Discover non-match num digits", reDiscover, replaceDiscover, " 6011-1111-1111-123", ""}, - {"Discover non-match sep", reDiscover, replaceDiscover, "6011=1111=1111_1234", ""}, - {"Discover non-match no break before", reDiscover, replaceDiscover, "abc6011-1111-1111-1234", "abcDiscover XXXX-XXXX-XXXX-1234"}, - {"Discover non-match no break after", reDiscover, replaceDiscover, "6011-1111-1111-1234def", "Discover XXXX-XXXX-XXXX-1234def"}, - - {"AMEX happy spaces", reAMEX, replaceAMEX, " abc 3411 123456 12345 def", " abc American Express XXXX-XXXXXX-X2345 def"}, - {"AMEX happy dashes", reAMEX, replaceAMEX, "3711-123456-12345", "American Express XXXX-XXXXXX-X2345"}, - {"AMEX happy mixed", reAMEX, replaceAMEX, "3411-123456 12345", "American Express XXXX-XXXXXX-X2345"}, - {"AMEX happy digits", reAMEX, replaceAMEX, "abc 371112345612345 def", "abc American Express XXXX-XXXXXX-X2345 def"}, - {"AMEX non-match start 41", reAMEX, replaceAMEX, "411112345612345", ""}, - {"AMEX non-match start 31", reAMEX, replaceAMEX, "3111111111111234", ""}, - {"AMEX non-match num digits", reAMEX, replaceAMEX, " 4111-1111-1111-123", ""}, - {"AMEX non-match sep", reAMEX, replaceAMEX, "4111-1111=1111-1234", ""}, - {"AMEX non-match no break before", reAMEX, replaceAMEX, "abc3711-123456-12345", "abcAmerican Express XXXX-XXXXXX-X2345"}, - {"AMEX non-match no break after", reAMEX, replaceAMEX, "3711-123456-12345def", "American Express XXXX-XXXXXX-X2345def"}, - } { - t.Run(tc.Name, func(t *testing.T) { - re := regexp.MustCompile(tc.RE) - result := re.ReplaceAllString(tc.In, tc.Replace) - if tc.Out != "" { - assert.Equal(t, tc.Out, result) - } else { - assert.Equal(t, tc.In, result) - } - }) - } +type linkTest struct { + Name string + Link autolink.Autolink + Message string + ExpectedMessage string } -const ( - reSSN = `(?P(?P\d{3})[ -]?(?P\d{2})[ -]?(?P[0-9]{4}))` - replaceSSN = `XXX-XX-$LastFour` -) - -func TestSSNRegex(t *testing.T) { - for _, tc := range []struct { - Name string - RE string - Replace string - In string - Out string - }{ - {"SSN happy spaces", reSSN, replaceSSN, " abc 652 47 3356 def", " abc XXX-XX-3356 def"}, - {"SSN happy dashes", reSSN, replaceSSN, " abc 652-47-3356 def", " abc XXX-XX-3356 def"}, - {"SSN happy digits", reSSN, replaceSSN, " abc 652473356 def", " abc XXX-XX-3356 def"}, - {"SSN happy mixed1", reSSN, replaceSSN, " abc 65247-3356 def", " abc XXX-XX-3356 def"}, - {"SSN happy mixed2", reSSN, replaceSSN, " abc 652 47-3356 def", " abc XXX-XX-3356 def"}, - {"SSN non-match 19-09-9999", reSSN, replaceSSN, " abc 19-09-9999 def", " abc 19-09-9999 def"}, - {"SSN non-match 652_47-3356", reSSN, replaceSSN, " abc 652_47-3356 def", " abc 652_47-3356 def"}, - } { - t.Run(tc.Name, func(t *testing.T) { - re := regexp.MustCompile(tc.RE) - result := re.ReplaceAllString(tc.In, tc.Replace) - if tc.Out != "" { - assert.Equal(t, tc.Out, result) - } else { - assert.Equal(t, tc.In, result) - } - }) - } -} - -func TestCreditCard(t *testing.T) { - var tests = []struct { - Name string - Link autolink.Autolink - inputMessage string - expectedMessage string - }{ - { - "VISA happy", - autolink.Autolink{ - Pattern: reVISA, - Template: replaceVISA, - }, - "A credit card 4111-1111-2222-1234 mentioned", - "A credit card VISA XXXX-XXXX-XXXX-1234 mentioned", - }, { - "VISA", - autolink.Autolink{ - Pattern: reVISA, - Template: replaceVISA, - DisableNonWordPrefix: true, - DisableNonWordSuffix: true, - }, - "A credit card4111-1111-2222-3333mentioned", - "A credit cardVISA XXXX-XXXX-XXXX-3333mentioned", - }, { - "Multiple VISA replacements", - autolink.Autolink{ - Pattern: reVISA, - Template: replaceVISA, - }, - "Credit cards 4111-1111-2222-3333 4222-3333-4444-5678 mentioned", - "Credit cards VISA XXXX-XXXX-XXXX-3333 VISA XXXX-XXXX-XXXX-5678 mentioned", +var commonLinkTests = []linkTest{ + { + "Simple pattern", + autolink.Autolink{ + Pattern: "(Mattermost)", + Template: "[Mattermost](https://mattermost.com)", }, - } - - for _, tt := range tests { - t.Run(tt.Name, func(t *testing.T) { - err := tt.Link.Compile() - actual := tt.Link.Replace(tt.inputMessage) - - assert.Equal(t, tt.expectedMessage, actual) - assert.NoError(t, err) - }) - } + "Welcome to Mattermost!", + "Welcome to [Mattermost](https://mattermost.com)!", + }, { + "Pattern with variable name accessed using $variable", + autolink.Autolink{ + Pattern: "(?PMattermost)", + Template: "[$key](https://mattermost.com)", + }, + "Welcome to Mattermost!", + "Welcome to [Mattermost](https://mattermost.com)!", + }, { + "Multiple replacments", + autolink.Autolink{ + Pattern: "(?PMattermost)", + Template: "[$key](https://mattermost.com)", + }, + "Welcome to Mattermost and have fun with Mattermost!", + "Welcome to [Mattermost](https://mattermost.com) and have fun with [Mattermost](https://mattermost.com)!", + }, { + "Pattern with variable name accessed using ${variable}", + autolink.Autolink{ + Pattern: "(?PMattermost)", + Template: "[${key}](https://mattermost.com)", + }, + "Welcome to Mattermost!", + "Welcome to [Mattermost](https://mattermost.com)!", + }, } -func TestLink(t *testing.T) { - for _, tc := range []struct { - Name string - Link autolink.Autolink - Message string - ExpectedMessage string - }{ - { - "Simple pattern", - autolink.Autolink{ - Pattern: "(Mattermost)", - Template: "[Mattermost](https://mattermost.com)", - }, - "Welcome to Mattermost!", - "Welcome to [Mattermost](https://mattermost.com)!", - }, { - "Pattern with variable name accessed using $variable", - autolink.Autolink{ - Pattern: "(?PMattermost)", - Template: "[$key](https://mattermost.com)", - }, - "Welcome to Mattermost!", - "Welcome to [Mattermost](https://mattermost.com)!", - }, { - "Multiple replacments", - autolink.Autolink{ - Pattern: "(?PMattermost)", - Template: "[$key](https://mattermost.com)", - }, - "Welcome to Mattermost and have fun with Mattermost!", - "Welcome to [Mattermost](https://mattermost.com) and have fun with [Mattermost](https://mattermost.com)!", - }, { - "Pattern with variable name accessed using ${variable}", - autolink.Autolink{ - Pattern: "(?PMattermost)", - Template: "[${key}](https://mattermost.com)", - }, - "Welcome to Mattermost!", - "Welcome to [Mattermost](https://mattermost.com)!", - }, { - "Jira example", - autolink.Autolink{ - Pattern: "(MM)(-)(?P\\d+)", - Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", - }, - "Welcome MM-12345 should link!", - "Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should link!", - }, { - "Jira example 2 (within a ())", - autolink.Autolink{ - Pattern: "(MM)(-)(?P\\d+)", - Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", - }, - "Link in brackets should link (see MM-12345)", - "Link in brackets should link (see [MM-12345](https://mattermost.atlassian.net/browse/MM-12345))", - }, { - "Jira example 3 (before ,)", - autolink.Autolink{ - Pattern: "(MM)(-)(?P\\d+)", - Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", - }, - "Link a ticket MM-12345, before a comma", - "Link a ticket [MM-12345](https://mattermost.atlassian.net/browse/MM-12345), before a comma", - }, { - "Jira example 3 (at begin of the message)", - autolink.Autolink{ - Pattern: "(MM)(-)(?P\\d+)", - Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", - }, - "MM-12345 should link!", - "[MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should link!", - }, { - "Pattern word prefix and suffix disabled", - autolink.Autolink{ - Pattern: "(?P^|\\s)(MM)(-)(?P\\d+)", - Template: "${previous}[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", - DisableNonWordPrefix: true, - DisableNonWordSuffix: true, - }, - "Welcome MM-12345 should link!", - "Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should link!", - }, { - "Pattern word prefix and suffix disabled (at begin of the message)", - autolink.Autolink{ - Pattern: "(?P^|\\s)(MM)(-)(?P\\d+)", - Template: "${previous}[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", - DisableNonWordPrefix: true, - DisableNonWordSuffix: true, - }, - "MM-12345 should link!", - "[MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should link!", - }, { - "Pattern word prefix and suffix enable (in the middle of other text)", - autolink.Autolink{ - Pattern: "(MM)(-)(?P\\d+)", - Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", - }, - "WelcomeMM-12345should not link!", - "WelcomeMM-12345should not link!", - }, { - "Pattern word prefix and suffix disabled (in the middle of other text)", - autolink.Autolink{ - Pattern: "(MM)(-)(?P\\d+)", - Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", - DisableNonWordPrefix: true, - DisableNonWordSuffix: true, - }, - "WelcomeMM-12345should link!", - "Welcome[MM-12345](https://mattermost.atlassian.net/browse/MM-12345)should link!", - }, { - "Not relinking", - autolink.Autolink{ - Pattern: "(MM)(-)(?P\\d+)", - Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", - }, - "Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should not re-link!", - "Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should not re-link!", - }, { - "Url replacement", - autolink.Autolink{ - Pattern: "(https://mattermost.atlassian.net/browse/)(MM)(-)(?P\\d+)", - Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", - }, - "Welcome https://mattermost.atlassian.net/browse/MM-12345 should link!", - "Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should link!", - }, { - "Url replacement multiple times", - autolink.Autolink{ - Pattern: "(https://mattermost.atlassian.net/browse/)(MM)(-)(?P\\d+)", - Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", - }, - "Welcome https://mattermost.atlassian.net/browse/MM-12345. should link https://mattermost.atlassian.net/browse/MM-12346 !", - "Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345). should link [MM-12346](https://mattermost.atlassian.net/browse/MM-12346) !", - }, { - "Url replacement multiple times and at beginning", - autolink.Autolink{ - Pattern: "(https:\\/\\/mattermost.atlassian.net\\/browse\\/)(MM)(-)(?P\\d+)", - Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", - }, - "https://mattermost.atlassian.net/browse/MM-12345 https://mattermost.atlassian.net/browse/MM-12345", - "[MM-12345](https://mattermost.atlassian.net/browse/MM-12345) [MM-12345](https://mattermost.atlassian.net/browse/MM-12345)", - }, { - "Url replacement at end", - autolink.Autolink{ - Pattern: "(https://mattermost.atlassian.net/browse/)(MM)(-)(?P\\d+)", - Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", - }, - "Welcome https://mattermost.atlassian.net/browse/MM-12345", - "Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345)", - }, - } { +func testLinks(t *testing.T, tcs ...linkTest) { + for _, tc := range tcs { t.Run(tc.Name, func(t *testing.T) { p := setupTestPlugin(t, tc.Link) post, _ := p.MessageWillBePosted(nil, &model.Post{ @@ -369,6 +97,10 @@ func TestLink(t *testing.T) { } } +func TestCommonLinks(t *testing.T) { + testLinks(t, commonLinkTests...) +} + func TestLegacyWordBoundaries(t *testing.T) { const pattern = "(KEY)(-)(?P\\d+)" const template = "[KEY-$ID](someurl/KEY-$ID)" diff --git a/server/autolink/lib_credit_card_test.go b/server/autolink/lib_credit_card_test.go new file mode 100644 index 00000000..98004c17 --- /dev/null +++ b/server/autolink/lib_credit_card_test.go @@ -0,0 +1,142 @@ +package autolink_test + +import ( + "regexp" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/mattermost/mattermost-plugin-autolink/server/autolink" +) + +const ( + reVISA = `(?P(?P4\d{3})[ -]?(?P\d{4})[ -]?(?P\d{4})[ -]?(?P[0-9]{4}))` + reMasterCard = `(?P(?P5[1-5]\d{2})[ -]?(?P\d{4})[ -]?(?P\d{4})[ -]?(?P[0-9]{4}))` + reSwitchSolo = `(?P(?P67\d{2})[ -]?(?P\d{4})[ -]?(?P\d{4})[ -]?(?P[0-9]{4}))` + reDiscover = `(?P(?P6011)[ -]?(?P\d{4})[ -]?(?P\d{4})[ -]?(?P[0-9]{4}))` + reAMEX = `(?P(?P3[47]\d{2})[ -]?(?P\d{6})[ -]?(?P\d)(?P[0-9]{4}))` + + replaceVISA = "VISA XXXX-XXXX-XXXX-$LastFour" + replaceMasterCard = "MasterCard XXXX-XXXX-XXXX-$LastFour" + replaceSwitchSolo = "Switch/Solo XXXX-XXXX-XXXX-$LastFour" + replaceDiscover = "Discover XXXX-XXXX-XXXX-$LastFour" + replaceAMEX = "American Express XXXX-XXXXXX-X$LastFour" +) + +func TestCreditCardRegex(t *testing.T) { + for _, tc := range []struct { + Name string + RE string + Replace string + In string + Out string + }{ + {"Visa happy spaces", reVISA, replaceVISA, " abc 4111 1111 1111 1234 def", " abc VISA XXXX-XXXX-XXXX-1234 def"}, + {"Visa happy dashes", reVISA, replaceVISA, "4111-1111-1111-1234", "VISA XXXX-XXXX-XXXX-1234"}, + {"Visa happy mixed", reVISA, replaceVISA, "41111111 1111-1234", "VISA XXXX-XXXX-XXXX-1234"}, + {"Visa happy digits", reVISA, replaceVISA, "abc 4111111111111234 def", "abc VISA XXXX-XXXX-XXXX-1234 def"}, + {"Visa non-match start", reVISA, replaceVISA, "3111111111111234", ""}, + {"Visa non-match num digits", reVISA, replaceVISA, " 4111-1111-1111-123", ""}, + {"Visa non-match sep", reVISA, replaceVISA, "4111=1111=1111_1234", ""}, + {"Visa non-match no break before", reVISA, replaceVISA, "abc4111-1111-1111-1234", "abcVISA XXXX-XXXX-XXXX-1234"}, + {"Visa non-match no break after", reVISA, replaceVISA, "4111-1111-1111-1234def", "VISA XXXX-XXXX-XXXX-1234def"}, + + {"MasterCard happy spaces", reMasterCard, replaceMasterCard, " abc 5111 1111 1111 1234 def", " abc MasterCard XXXX-XXXX-XXXX-1234 def"}, + {"MasterCard happy dashes", reMasterCard, replaceMasterCard, "5211-1111-1111-1234", "MasterCard XXXX-XXXX-XXXX-1234"}, + {"MasterCard happy mixed", reMasterCard, replaceMasterCard, "53111111 1111-1234", "MasterCard XXXX-XXXX-XXXX-1234"}, + {"MasterCard happy digits", reMasterCard, replaceMasterCard, "abc 5411111111111234 def", "abc MasterCard XXXX-XXXX-XXXX-1234 def"}, + {"MasterCard non-match start", reMasterCard, replaceMasterCard, "3111111111111234", ""}, + {"MasterCard non-match num digits", reMasterCard, replaceMasterCard, " 5111-1111-1111-123", ""}, + {"MasterCard non-match sep", reMasterCard, replaceMasterCard, "5111=1111=1111_1234", ""}, + {"MasterCard non-match no break before", reMasterCard, replaceMasterCard, "abc5511-1111-1111-1234", "abcMasterCard XXXX-XXXX-XXXX-1234"}, + {"MasterCard non-match no break after", reMasterCard, replaceMasterCard, "5111-1111-1111-1234def", "MasterCard XXXX-XXXX-XXXX-1234def"}, + + {"SwitchSolo happy spaces", reSwitchSolo, replaceSwitchSolo, " abc 6711 1111 1111 1234 def", " abc Switch/Solo XXXX-XXXX-XXXX-1234 def"}, + {"SwitchSolo happy dashes", reSwitchSolo, replaceSwitchSolo, "6711-1111-1111-1234", "Switch/Solo XXXX-XXXX-XXXX-1234"}, + {"SwitchSolo happy mixed", reSwitchSolo, replaceSwitchSolo, "67111111 1111-1234", "Switch/Solo XXXX-XXXX-XXXX-1234"}, + {"SwitchSolo happy digits", reSwitchSolo, replaceSwitchSolo, "abc 6711111111111234 def", "abc Switch/Solo XXXX-XXXX-XXXX-1234 def"}, + {"SwitchSolo non-match start", reSwitchSolo, replaceSwitchSolo, "3111111111111234", ""}, + {"SwitchSolo non-match num digits", reSwitchSolo, replaceSwitchSolo, " 6711-1111-1111-123", ""}, + {"SwitchSolo non-match sep", reSwitchSolo, replaceSwitchSolo, "6711=1111=1111_1234", ""}, + {"SwitchSolo non-match no break before", reSwitchSolo, replaceSwitchSolo, "abc6711-1111-1111-1234", "abcSwitch/Solo XXXX-XXXX-XXXX-1234"}, + {"SwitchSolo non-match no break after", reSwitchSolo, replaceSwitchSolo, "6711-1111-1111-1234def", "Switch/Solo XXXX-XXXX-XXXX-1234def"}, + + {"Discover happy spaces", reDiscover, replaceDiscover, " abc 6011 1111 1111 1234 def", " abc Discover XXXX-XXXX-XXXX-1234 def"}, + {"Discover happy dashes", reDiscover, replaceDiscover, "6011-1111-1111-1234", "Discover XXXX-XXXX-XXXX-1234"}, + {"Discover happy mixed", reDiscover, replaceDiscover, "60111111 1111-1234", "Discover XXXX-XXXX-XXXX-1234"}, + {"Discover happy digits", reDiscover, replaceDiscover, "abc 6011111111111234 def", "abc Discover XXXX-XXXX-XXXX-1234 def"}, + {"Discover non-match start", reDiscover, replaceDiscover, "3111111111111234", ""}, + {"Discover non-match num digits", reDiscover, replaceDiscover, " 6011-1111-1111-123", ""}, + {"Discover non-match sep", reDiscover, replaceDiscover, "6011=1111=1111_1234", ""}, + {"Discover non-match no break before", reDiscover, replaceDiscover, "abc6011-1111-1111-1234", "abcDiscover XXXX-XXXX-XXXX-1234"}, + {"Discover non-match no break after", reDiscover, replaceDiscover, "6011-1111-1111-1234def", "Discover XXXX-XXXX-XXXX-1234def"}, + + {"AMEX happy spaces", reAMEX, replaceAMEX, " abc 3411 123456 12345 def", " abc American Express XXXX-XXXXXX-X2345 def"}, + {"AMEX happy dashes", reAMEX, replaceAMEX, "3711-123456-12345", "American Express XXXX-XXXXXX-X2345"}, + {"AMEX happy mixed", reAMEX, replaceAMEX, "3411-123456 12345", "American Express XXXX-XXXXXX-X2345"}, + {"AMEX happy digits", reAMEX, replaceAMEX, "abc 371112345612345 def", "abc American Express XXXX-XXXXXX-X2345 def"}, + {"AMEX non-match start 41", reAMEX, replaceAMEX, "411112345612345", ""}, + {"AMEX non-match start 31", reAMEX, replaceAMEX, "3111111111111234", ""}, + {"AMEX non-match num digits", reAMEX, replaceAMEX, " 4111-1111-1111-123", ""}, + {"AMEX non-match sep", reAMEX, replaceAMEX, "4111-1111=1111-1234", ""}, + {"AMEX non-match no break before", reAMEX, replaceAMEX, "abc3711-123456-12345", "abcAmerican Express XXXX-XXXXXX-X2345"}, + {"AMEX non-match no break after", reAMEX, replaceAMEX, "3711-123456-12345def", "American Express XXXX-XXXXXX-X2345def"}, + } { + t.Run(tc.Name, func(t *testing.T) { + re := regexp.MustCompile(tc.RE) + result := re.ReplaceAllString(tc.In, tc.Replace) + if tc.Out != "" { + assert.Equal(t, tc.Out, result) + } else { + assert.Equal(t, tc.In, result) + } + }) + } +} + +func TestCreditCard(t *testing.T) { + var tests = []struct { + Name string + Link autolink.Autolink + inputMessage string + expectedMessage string + }{ + { + "VISA happy", + autolink.Autolink{ + Pattern: reVISA, + Template: replaceVISA, + }, + "A credit card 4111-1111-2222-1234 mentioned", + "A credit card VISA XXXX-XXXX-XXXX-1234 mentioned", + }, { + "VISA", + autolink.Autolink{ + Pattern: reVISA, + Template: replaceVISA, + DisableNonWordPrefix: true, + DisableNonWordSuffix: true, + }, + "A credit card4111-1111-2222-3333mentioned", + "A credit cardVISA XXXX-XXXX-XXXX-3333mentioned", + }, { + "Multiple VISA replacements", + autolink.Autolink{ + Pattern: reVISA, + Template: replaceVISA, + }, + "Credit cards 4111-1111-2222-3333 4222-3333-4444-5678 mentioned", + "Credit cards VISA XXXX-XXXX-XXXX-3333 VISA XXXX-XXXX-XXXX-5678 mentioned", + }, + } + + for _, tt := range tests { + t.Run(tt.Name, func(t *testing.T) { + err := tt.Link.Compile() + actual := tt.Link.Replace(tt.inputMessage) + + assert.Equal(t, tt.expectedMessage, actual) + assert.NoError(t, err) + }) + } +} diff --git a/server/autolink/lib_jira_test.go b/server/autolink/lib_jira_test.go new file mode 100644 index 00000000..296fc123 --- /dev/null +++ b/server/autolink/lib_jira_test.go @@ -0,0 +1,125 @@ +package autolink_test + +import ( + "testing" + + "github.com/mattermost/mattermost-plugin-autolink/server/autolink" +) + +var jiraTests = []linkTest{ + { + "Jira example", + autolink.Autolink{ + Pattern: "(MM)(-)(?P\\d+)", + Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", + }, + "Welcome MM-12345 should link!", + "Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should link!", + }, { + "Jira example 2 (within a ())", + autolink.Autolink{ + Pattern: "(MM)(-)(?P\\d+)", + Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", + }, + "Link in brackets should link (see MM-12345)", + "Link in brackets should link (see [MM-12345](https://mattermost.atlassian.net/browse/MM-12345))", + }, { + "Jira example 3 (before ,)", + autolink.Autolink{ + Pattern: "(MM)(-)(?P\\d+)", + Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", + }, + "Link a ticket MM-12345, before a comma", + "Link a ticket [MM-12345](https://mattermost.atlassian.net/browse/MM-12345), before a comma", + }, { + "Jira example 3 (at begin of the message)", + autolink.Autolink{ + Pattern: "(MM)(-)(?P\\d+)", + Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", + }, + "MM-12345 should link!", + "[MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should link!", + }, { + "Pattern word prefix and suffix disabled", + autolink.Autolink{ + Pattern: "(?P^|\\s)(MM)(-)(?P\\d+)", + Template: "${previous}[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", + DisableNonWordPrefix: true, + DisableNonWordSuffix: true, + }, + "Welcome MM-12345 should link!", + "Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should link!", + }, { + "Pattern word prefix and suffix disabled (at begin of the message)", + autolink.Autolink{ + Pattern: "(?P^|\\s)(MM)(-)(?P\\d+)", + Template: "${previous}[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", + DisableNonWordPrefix: true, + DisableNonWordSuffix: true, + }, + "MM-12345 should link!", + "[MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should link!", + }, { + "Pattern word prefix and suffix enable (in the middle of other text)", + autolink.Autolink{ + Pattern: "(MM)(-)(?P\\d+)", + Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", + }, + "WelcomeMM-12345should not link!", + "WelcomeMM-12345should not link!", + }, { + "Pattern word prefix and suffix disabled (in the middle of other text)", + autolink.Autolink{ + Pattern: "(MM)(-)(?P\\d+)", + Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", + DisableNonWordPrefix: true, + DisableNonWordSuffix: true, + }, + "WelcomeMM-12345should link!", + "Welcome[MM-12345](https://mattermost.atlassian.net/browse/MM-12345)should link!", + }, { + "Not relinking", + autolink.Autolink{ + Pattern: "(MM)(-)(?P\\d+)", + Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", + }, + "Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should not re-link!", + "Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should not re-link!", + }, { + "Url replacement", + autolink.Autolink{ + Pattern: "(https://mattermost.atlassian.net/browse/)(MM)(-)(?P\\d+)", + Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", + }, + "Welcome https://mattermost.atlassian.net/browse/MM-12345 should link!", + "Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345) should link!", + }, { + "Url replacement multiple times", + autolink.Autolink{ + Pattern: "(https://mattermost.atlassian.net/browse/)(MM)(-)(?P\\d+)", + Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", + }, + "Welcome https://mattermost.atlassian.net/browse/MM-12345. should link https://mattermost.atlassian.net/browse/MM-12346 !", + "Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345). should link [MM-12346](https://mattermost.atlassian.net/browse/MM-12346) !", + }, { + "Url replacement multiple times and at beginning", + autolink.Autolink{ + Pattern: "(https:\\/\\/mattermost.atlassian.net\\/browse\\/)(MM)(-)(?P\\d+)", + Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", + }, + "https://mattermost.atlassian.net/browse/MM-12345 https://mattermost.atlassian.net/browse/MM-12345", + "[MM-12345](https://mattermost.atlassian.net/browse/MM-12345) [MM-12345](https://mattermost.atlassian.net/browse/MM-12345)", + }, { + "Url replacement at end", + autolink.Autolink{ + Pattern: "(https://mattermost.atlassian.net/browse/)(MM)(-)(?P\\d+)", + Template: "[MM-$jira_id](https://mattermost.atlassian.net/browse/MM-$jira_id)", + }, + "Welcome https://mattermost.atlassian.net/browse/MM-12345", + "Welcome [MM-12345](https://mattermost.atlassian.net/browse/MM-12345)", + }, +} + +func TestJira(t *testing.T) { + testLinks(t, jiraTests...) +} diff --git a/server/autolink/lib_productboard_test.go b/server/autolink/lib_productboard_test.go new file mode 100644 index 00000000..8b2df4b5 --- /dev/null +++ b/server/autolink/lib_productboard_test.go @@ -0,0 +1,41 @@ +package autolink_test + +import ( + "testing" + + "github.com/mattermost/mattermost-plugin-autolink/server/autolink" +) + +var productboardLink = autolink.Autolink{ + Pattern: `(?Phttps://mattermost\.productboard\.com/.+)`, + Template: "[ProductBoard link]($url)", + WordMatch: true, +} + +var productboardTests = []linkTest{ + { + "Url replacement", + productboardLink, + "Welcome to https://mattermost.productboard.com/somepage should link!", + "Welcome to [ProductBoard link](https://mattermost.productboard.com/somepage) should link!", + }, { + "Not relinking", + productboardLink, + "Welcome to [other link](https://mattermost.productboard.com/somepage) should not re-link!", + "Welcome to [other link](https://mattermost.productboard.com/somepage) should not re-link!", + }, { + "Word boundary happy", + productboardLink, + "Welcome to (https://mattermost.productboard.com/somepage) should link!", + "Welcome to ([ProductBoard link](https://mattermost.productboard.com/somepage)) should link!", + }, { + "Word boundary un-happy", + productboardLink, + "Welcome to (BADhttps://mattermost.productboard.com/somepage) should not link!", + "Welcome to (BADhttps://mattermost.productboard.com/somepage) should not link!", + }, +} + +func TestProductBoard(t *testing.T) { + testLinks(t, productboardTests...) +} diff --git a/server/autolink/lib_social_security_number_test.go b/server/autolink/lib_social_security_number_test.go new file mode 100644 index 00000000..06cdc12f --- /dev/null +++ b/server/autolink/lib_social_security_number_test.go @@ -0,0 +1,41 @@ +package autolink_test + +import ( + "regexp" + "testing" + + "github.com/stretchr/testify/assert" +) + +const ( + reSSN = `(?P(?P\d{3})[ -]?(?P\d{2})[ -]?(?P[0-9]{4}))` + replaceSSN = `XXX-XX-$LastFour` +) + +func TestSocialSecurityNumberRegex(t *testing.T) { + for _, tc := range []struct { + Name string + RE string + Replace string + In string + Out string + }{ + {"SSN happy spaces", reSSN, replaceSSN, " abc 652 47 3356 def", " abc XXX-XX-3356 def"}, + {"SSN happy dashes", reSSN, replaceSSN, " abc 652-47-3356 def", " abc XXX-XX-3356 def"}, + {"SSN happy digits", reSSN, replaceSSN, " abc 652473356 def", " abc XXX-XX-3356 def"}, + {"SSN happy mixed1", reSSN, replaceSSN, " abc 65247-3356 def", " abc XXX-XX-3356 def"}, + {"SSN happy mixed2", reSSN, replaceSSN, " abc 652 47-3356 def", " abc XXX-XX-3356 def"}, + {"SSN non-match 19-09-9999", reSSN, replaceSSN, " abc 19-09-9999 def", " abc 19-09-9999 def"}, + {"SSN non-match 652_47-3356", reSSN, replaceSSN, " abc 652_47-3356 def", " abc 652_47-3356 def"}, + } { + t.Run(tc.Name, func(t *testing.T) { + re := regexp.MustCompile(tc.RE) + result := re.ReplaceAllString(tc.In, tc.Replace) + if tc.Out != "" { + assert.Equal(t, tc.Out, result) + } else { + assert.Equal(t, tc.In, result) + } + }) + } +} diff --git a/server/autolinkclient/client.go b/server/autolinkclient/client.go index 69587a5b..45896c6a 100644 --- a/server/autolinkclient/client.go +++ b/server/autolinkclient/client.go @@ -40,7 +40,7 @@ func NewClientPlugin(api PluginAPI) *Client { func (c *Client) Add(links ...autolink.Autolink) error { for _, link := range links { - linkBytes, err := json.Marshal(&link) + linkBytes, err := json.Marshal(link) if err != nil { return err } diff --git a/server/autolinkplugin/command.go b/server/autolinkplugin/command.go index 6d0e56fb..d9385ac2 100644 --- a/server/autolinkplugin/command.go +++ b/server/autolinkplugin/command.go @@ -207,16 +207,20 @@ func executeTest(p *Plugin, c *plugin.Context, header *model.CommandArgs, args . restOfCommand := header.Command[10:] // "/autolink " restOfCommand = restOfCommand[strings.Index(restOfCommand, args[0])+len(args[0]):] orig := strings.TrimSpace(restOfCommand) - out := "" + out := fmt.Sprintf("- Original: `%s`\n", orig) for _, ref := range refs { l := links[ref] l.Disabled = false + err = l.Compile() + if err != nil { + return responsef("failed to compile link %s: %v", l.DisplayName(), err) + } replaced := l.Replace(orig) if replaced == orig { - out += fmt.Sprintf("- %s: _no change_\n", l.DisplayName()) + out += fmt.Sprintf("- Link %s: _no change_\n", l.DisplayName()) } else { - out += fmt.Sprintf("- %s: `%s`\n", l.DisplayName(), replaced) + out += fmt.Sprintf("- Link %s: changed to `%s`\n", l.DisplayName(), replaced) orig = replaced } }