github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/references/references_test.go (about) 1 // Copyright 2023 The GitBundle Inc. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // Use of this source code is governed by a MIT-style 4 // license that can be found in the LICENSE file. 5 6 package references 7 8 import ( 9 "regexp" 10 "testing" 11 12 "github.com/gitbundle/modules/setting" 13 14 "github.com/stretchr/testify/assert" 15 ) 16 17 type testFixture struct { 18 input string 19 expected []testResult 20 } 21 22 type testResult struct { 23 Index int64 24 Owner string 25 Name string 26 Issue string 27 IsPull bool 28 Action XRefAction 29 RefLocation *RefSpan 30 ActionLocation *RefSpan 31 TimeLog string 32 } 33 34 func TestConvertFullHTMLReferencesToShortRefs(t *testing.T) { 35 re := regexp.MustCompile(`(\s|^|\(|\[)` + 36 regexp.QuoteMeta("https://ourgitea.com/git/") + 37 `([0-9a-zA-Z-_\.]+/[0-9a-zA-Z-_\.]+)/` + 38 `((?:issues)|(?:pulls))/([0-9]+)(?:\s|$|\)|\]|[:;,.?!]\s|[:;,.?!]$)`) 39 test := `this is a https://ourgitea.com/git/owner/repo/issues/123456789, foo 40 https://ourgitea.com/git/owner/repo/pulls/123456789 41 And https://ourgitea.com/git/owner/repo/pulls/123 42 ` 43 expect := `this is a owner/repo#123456789, foo 44 owner/repo!123456789 45 And owner/repo!123 46 ` 47 48 contentBytes := []byte(test) 49 convertFullHTMLReferencesToShortRefs(re, &contentBytes) 50 result := string(contentBytes) 51 assert.EqualValues(t, expect, result) 52 } 53 54 func TestFindAllIssueReferences(t *testing.T) { 55 fixtures := []testFixture{ 56 { 57 "Simply closes: #29 yes", 58 []testResult{ 59 {29, "", "", "29", false, XRefActionCloses, &RefSpan{Start: 15, End: 18}, &RefSpan{Start: 7, End: 13}, ""}, 60 }, 61 }, 62 { 63 "Simply closes: !29 yes", 64 []testResult{ 65 {29, "", "", "29", true, XRefActionCloses, &RefSpan{Start: 15, End: 18}, &RefSpan{Start: 7, End: 13}, ""}, 66 }, 67 }, 68 { 69 " #124 yes, this is a reference.", 70 []testResult{ 71 {124, "", "", "124", false, XRefActionNone, &RefSpan{Start: 0, End: 4}, nil, ""}, 72 }, 73 }, 74 { 75 "```\nThis is a code block.\n#723 no, it's a code block.```", 76 []testResult{}, 77 }, 78 { 79 "This `#724` no, it's inline code.", 80 []testResult{}, 81 }, 82 { 83 "This user3/repo4#200 yes.", 84 []testResult{ 85 {200, "user3", "repo4", "200", false, XRefActionNone, &RefSpan{Start: 5, End: 20}, nil, ""}, 86 }, 87 }, 88 { 89 "This user3/repo4!200 yes.", 90 []testResult{ 91 {200, "user3", "repo4", "200", true, XRefActionNone, &RefSpan{Start: 5, End: 20}, nil, ""}, 92 }, 93 }, 94 { 95 "This [one](#919) no, this is a URL fragment.", 96 []testResult{}, 97 }, 98 { 99 "This [two](/user2/repo1/issues/921) yes.", 100 []testResult{ 101 {921, "user2", "repo1", "921", false, XRefActionNone, nil, nil, ""}, 102 }, 103 }, 104 { 105 "This [three](/user2/repo1/pulls/922) yes.", 106 []testResult{ 107 {922, "user2", "repo1", "922", true, XRefActionNone, nil, nil, ""}, 108 }, 109 }, 110 { 111 "This [four](http://gitea.com:3000/user3/repo4/issues/203) yes.", 112 []testResult{ 113 {203, "user3", "repo4", "203", false, XRefActionNone, nil, nil, ""}, 114 }, 115 }, 116 { 117 "This [five](http://github.com/user3/repo4/issues/204) no.", 118 []testResult{}, 119 }, 120 { 121 "This http://gitea.com:3000/user4/repo5/201 no, bad URL.", 122 []testResult{}, 123 }, 124 { 125 "This http://gitea.com:3000/user4/repo5/pulls/202 yes.", 126 []testResult{ 127 {202, "user4", "repo5", "202", true, XRefActionNone, nil, nil, ""}, 128 }, 129 }, 130 { 131 "This http://gitea.com:3000/user4/repo5/pulls/202 yes. http://gitea.com:3000/user4/repo5/pulls/203 no", 132 []testResult{ 133 {202, "user4", "repo5", "202", true, XRefActionNone, nil, nil, ""}, 134 {203, "user4", "repo5", "203", true, XRefActionNone, nil, nil, ""}, 135 }, 136 }, 137 { 138 "This http://GiTeA.COM:3000/user4/repo6/pulls/205 yes.", 139 []testResult{ 140 {205, "user4", "repo6", "205", true, XRefActionNone, nil, nil, ""}, 141 }, 142 }, 143 { 144 "Reopens #15 yes", 145 []testResult{ 146 {15, "", "", "15", false, XRefActionReopens, &RefSpan{Start: 8, End: 11}, &RefSpan{Start: 0, End: 7}, ""}, 147 }, 148 }, 149 { 150 "This closes #20 for you yes", 151 []testResult{ 152 {20, "", "", "20", false, XRefActionCloses, &RefSpan{Start: 12, End: 15}, &RefSpan{Start: 5, End: 11}, ""}, 153 }, 154 }, 155 { 156 "Do you fix user6/repo6#300 ? yes", 157 []testResult{ 158 {300, "user6", "repo6", "300", false, XRefActionCloses, &RefSpan{Start: 11, End: 26}, &RefSpan{Start: 7, End: 10}, ""}, 159 }, 160 }, 161 { 162 "For 999 #1235 no keyword, but yes", 163 []testResult{ 164 {1235, "", "", "1235", false, XRefActionNone, &RefSpan{Start: 8, End: 13}, nil, ""}, 165 }, 166 }, 167 { 168 "For [!123] yes", 169 []testResult{ 170 {123, "", "", "123", true, XRefActionNone, &RefSpan{Start: 5, End: 9}, nil, ""}, 171 }, 172 }, 173 { 174 "For (#345) yes", 175 []testResult{ 176 {345, "", "", "345", false, XRefActionNone, &RefSpan{Start: 5, End: 9}, nil, ""}, 177 }, 178 }, 179 { 180 "For #22,#23 no, neither #28:#29 or !30!31#32;33 should", 181 []testResult{}, 182 }, 183 { 184 "For #24, and #25. yes; also #26; #27? #28! and #29: should", 185 []testResult{ 186 {24, "", "", "24", false, XRefActionNone, &RefSpan{Start: 4, End: 7}, nil, ""}, 187 {25, "", "", "25", false, XRefActionNone, &RefSpan{Start: 13, End: 16}, nil, ""}, 188 {26, "", "", "26", false, XRefActionNone, &RefSpan{Start: 28, End: 31}, nil, ""}, 189 {27, "", "", "27", false, XRefActionNone, &RefSpan{Start: 33, End: 36}, nil, ""}, 190 {28, "", "", "28", false, XRefActionNone, &RefSpan{Start: 38, End: 41}, nil, ""}, 191 {29, "", "", "29", false, XRefActionNone, &RefSpan{Start: 47, End: 50}, nil, ""}, 192 }, 193 }, 194 { 195 "This user3/repo4#200, yes.", 196 []testResult{ 197 {200, "user3", "repo4", "200", false, XRefActionNone, &RefSpan{Start: 5, End: 20}, nil, ""}, 198 }, 199 }, 200 { 201 "Merge pull request '#12345 My fix for a bug' (!1337) from feature-branch into main", 202 []testResult{ 203 {12345, "", "", "12345", false, XRefActionNone, &RefSpan{Start: 20, End: 26}, nil, ""}, 204 {1337, "", "", "1337", true, XRefActionNone, &RefSpan{Start: 46, End: 51}, nil, ""}, 205 }, 206 }, 207 { 208 "Which abc. #9434 same as above", 209 []testResult{ 210 {9434, "", "", "9434", false, XRefActionNone, &RefSpan{Start: 11, End: 16}, nil, ""}, 211 }, 212 }, 213 { 214 "This closes #600 and reopens #599", 215 []testResult{ 216 {600, "", "", "600", false, XRefActionCloses, &RefSpan{Start: 12, End: 16}, &RefSpan{Start: 5, End: 11}, ""}, 217 {599, "", "", "599", false, XRefActionReopens, &RefSpan{Start: 29, End: 33}, &RefSpan{Start: 21, End: 28}, ""}, 218 }, 219 }, 220 { 221 "This fixes #100 spent @40m and reopens #101, also fixes #102 spent @4h15m", 222 []testResult{ 223 {100, "", "", "100", false, XRefActionCloses, &RefSpan{Start: 11, End: 15}, &RefSpan{Start: 5, End: 10}, "40m"}, 224 {101, "", "", "101", false, XRefActionReopens, &RefSpan{Start: 39, End: 43}, &RefSpan{Start: 31, End: 38}, ""}, 225 {102, "", "", "102", false, XRefActionCloses, &RefSpan{Start: 56, End: 60}, &RefSpan{Start: 50, End: 55}, "4h15m"}, 226 }, 227 }, 228 } 229 230 testFixtures(t, fixtures, "default") 231 232 type alnumFixture struct { 233 input string 234 issue string 235 refLocation *RefSpan 236 action XRefAction 237 actionLocation *RefSpan 238 } 239 240 alnumFixtures := []alnumFixture{ 241 { 242 "This ref ABC-123 is alphanumeric", 243 "ABC-123", &RefSpan{Start: 9, End: 16}, 244 XRefActionNone, nil, 245 }, 246 { 247 "This closes ABCD-1234 alphanumeric", 248 "ABCD-1234", &RefSpan{Start: 12, End: 21}, 249 XRefActionCloses, &RefSpan{Start: 5, End: 11}, 250 }, 251 } 252 253 for _, fixture := range alnumFixtures { 254 found, ref := FindRenderizableReferenceAlphanumeric(fixture.input) 255 if fixture.issue == "" { 256 assert.False(t, found, "Failed to parse: {%s}", fixture.input) 257 } else { 258 assert.True(t, found, "Failed to parse: {%s}", fixture.input) 259 assert.Equal(t, fixture.issue, ref.Issue, "Failed to parse: {%s}", fixture.input) 260 assert.Equal(t, fixture.refLocation, ref.RefLocation, "Failed to parse: {%s}", fixture.input) 261 assert.Equal(t, fixture.action, ref.Action, "Failed to parse: {%s}", fixture.input) 262 assert.Equal(t, fixture.actionLocation, ref.ActionLocation, "Failed to parse: {%s}", fixture.input) 263 } 264 } 265 } 266 267 func testFixtures(t *testing.T, fixtures []testFixture, context string) { 268 // Save original value for other tests that may rely on it 269 prevURL := setting.AppURL 270 setting.AppURL = "https://gitea.com:3000/" 271 272 for _, fixture := range fixtures { 273 expraw := make([]*rawReference, len(fixture.expected)) 274 for i, e := range fixture.expected { 275 expraw[i] = &rawReference{ 276 index: e.Index, 277 owner: e.Owner, 278 name: e.Name, 279 isPull: e.IsPull, 280 action: e.Action, 281 issue: e.Issue, 282 refLocation: e.RefLocation, 283 actionLocation: e.ActionLocation, 284 timeLog: e.TimeLog, 285 } 286 } 287 expref := rawToIssueReferenceList(expraw) 288 refs := FindAllIssueReferencesMarkdown(fixture.input) 289 assert.EqualValues(t, expref, refs, "[%s] Failed to parse: {%s}", context, fixture.input) 290 rawrefs := findAllIssueReferencesMarkdown(fixture.input) 291 assert.EqualValues(t, expraw, rawrefs, "[%s] Failed to parse: {%s}", context, fixture.input) 292 } 293 294 // Restore for other tests that may rely on the original value 295 setting.AppURL = prevURL 296 } 297 298 func TestFindAllMentions(t *testing.T) { 299 res := FindAllMentionsBytes([]byte("@tasha, @mike; @lucy: @john")) 300 assert.EqualValues(t, []RefSpan{ 301 {Start: 0, End: 6}, 302 {Start: 8, End: 13}, 303 {Start: 15, End: 20}, 304 {Start: 22, End: 27}, 305 }, res) 306 } 307 308 func TestRegExp_mentionPattern(t *testing.T) { 309 trueTestCases := []struct { 310 pat string 311 exp string 312 }{ 313 {"@Unknwon", "@Unknwon"}, 314 {"@ANT_123", "@ANT_123"}, 315 {"@xxx-DiN0-z-A..uru..s-xxx", "@xxx-DiN0-z-A..uru..s-xxx"}, 316 {" @lol ", "@lol"}, 317 {" @Te-st", "@Te-st"}, 318 {"(@gitea)", "@gitea"}, 319 {"[@gitea]", "@gitea"}, 320 {"@gitea! this", "@gitea"}, 321 {"@gitea? this", "@gitea"}, 322 {"@gitea. this", "@gitea"}, 323 {"@gitea, this", "@gitea"}, 324 {"@gitea; this", "@gitea"}, 325 {"@gitea!\nthis", "@gitea"}, 326 {"\n@gitea?\nthis", "@gitea"}, 327 {"\t@gitea.\nthis", "@gitea"}, 328 {"@gitea,\nthis", "@gitea"}, 329 {"@gitea;\nthis", "@gitea"}, 330 {"@gitea!", "@gitea"}, 331 {"@gitea?", "@gitea"}, 332 {"@gitea.", "@gitea"}, 333 {"@gitea,", "@gitea"}, 334 {"@gitea;", "@gitea"}, 335 {"@gitea/team1;", "@gitea/team1"}, 336 } 337 falseTestCases := []string{ 338 "@ 0", 339 "@ ", 340 "@", 341 "", 342 "ABC", 343 "@.ABC", 344 "/home/gitea/@gitea", 345 "\"@gitea\"", 346 "@@gitea", 347 "@gitea!this", 348 "@gitea?this", 349 "@gitea,this", 350 "@gitea;this", 351 "@gitea/team1/more", 352 } 353 354 for _, testCase := range trueTestCases { 355 found := mentionPattern.FindStringSubmatch(testCase.pat) 356 assert.Len(t, found, 2) 357 assert.Equal(t, testCase.exp, found[1]) 358 } 359 for _, testCase := range falseTestCases { 360 res := mentionPattern.MatchString(testCase) 361 assert.False(t, res, "[%s] should be false", testCase) 362 } 363 } 364 365 func TestRegExp_issueNumericPattern(t *testing.T) { 366 trueTestCases := []string{ 367 "#1234", 368 "#0", 369 "#1234567890987654321", 370 " #12", 371 "#12:", 372 "ref: #12: msg", 373 } 374 falseTestCases := []string{ 375 "# 1234", 376 "# 0", 377 "# ", 378 "#", 379 "#ABC", 380 "#1A2B", 381 "", 382 "ABC", 383 } 384 385 for _, testCase := range trueTestCases { 386 assert.True(t, issueNumericPattern.MatchString(testCase)) 387 } 388 for _, testCase := range falseTestCases { 389 assert.False(t, issueNumericPattern.MatchString(testCase)) 390 } 391 } 392 393 func TestRegExp_issueAlphanumericPattern(t *testing.T) { 394 trueTestCases := []string{ 395 "ABC-1234", 396 "A-1", 397 "RC-80", 398 "ABCDEFGHIJ-1234567890987654321234567890", 399 "ABC-123.", 400 "(ABC-123)", 401 "[ABC-123]", 402 "ABC-123:", 403 } 404 falseTestCases := []string{ 405 "RC-08", 406 "PR-0", 407 "ABCDEFGHIJK-1", 408 "PR_1", 409 "", 410 "#ABC", 411 "", 412 "ABC", 413 "GG-", 414 "rm-1", 415 "/home/gitea/ABC-1234", 416 "MY-STRING-ABC-123", 417 } 418 419 for _, testCase := range trueTestCases { 420 assert.True(t, issueAlphanumericPattern.MatchString(testCase)) 421 } 422 for _, testCase := range falseTestCases { 423 assert.False(t, issueAlphanumericPattern.MatchString(testCase)) 424 } 425 } 426 427 func TestCustomizeCloseKeywords(t *testing.T) { 428 fixtures := []testFixture{ 429 { 430 "Simplemente cierra: #29 yes", 431 []testResult{ 432 {29, "", "", "29", false, XRefActionCloses, &RefSpan{Start: 20, End: 23}, &RefSpan{Start: 12, End: 18}, ""}, 433 }, 434 }, 435 { 436 "Closes: #123 no, this English.", 437 []testResult{ 438 {123, "", "", "123", false, XRefActionNone, &RefSpan{Start: 8, End: 12}, nil, ""}, 439 }, 440 }, 441 { 442 "Cerró user6/repo6#300 yes", 443 []testResult{ 444 {300, "user6", "repo6", "300", false, XRefActionCloses, &RefSpan{Start: 7, End: 22}, &RefSpan{Start: 0, End: 6}, ""}, 445 }, 446 }, 447 { 448 "Reabre user3/repo4#200 yes", 449 []testResult{ 450 {200, "user3", "repo4", "200", false, XRefActionReopens, &RefSpan{Start: 7, End: 22}, &RefSpan{Start: 0, End: 6}, ""}, 451 }, 452 }, 453 } 454 455 issueKeywordsOnce.Do(func() {}) 456 457 doNewKeywords([]string{"cierra", "cerró"}, []string{"reabre"}) 458 testFixtures(t, fixtures, "spanish") 459 460 // Restore default settings 461 doNewKeywords(setting.Repository.PullRequest.CloseKeywords, setting.Repository.PullRequest.ReopenKeywords) 462 } 463 464 func TestParseCloseKeywords(t *testing.T) { 465 // Test parsing of CloseKeywords and ReopenKeywords 466 assert.Len(t, parseKeywords([]string{""}), 0) 467 assert.Len(t, parseKeywords([]string{" aa ", " bb ", "99", "#", "", "this is", "cc"}), 3) 468 469 for _, test := range []struct { 470 pattern string 471 match string 472 expected string 473 }{ 474 {"close", "This PR will close ", "close"}, 475 {"cerró", "cerró ", "cerró"}, 476 {"cerró", "AQUÍ SE CERRÓ: ", "CERRÓ"}, 477 {"закрывается", "закрывается ", "закрывается"}, 478 {"κλείνει", "κλείνει: ", "κλείνει"}, 479 {"关闭", "关闭 ", "关闭"}, 480 {"閉じます", "閉じます ", "閉じます"}, 481 {",$!", "", ""}, 482 {"1234", "", ""}, 483 } { 484 // The pattern only needs to match the part that precedes the reference. 485 // getCrossReference() takes care of finding the reference itself. 486 pat := makeKeywordsPat([]string{test.pattern}) 487 if test.expected == "" { 488 assert.Nil(t, pat) 489 } else { 490 assert.NotNil(t, pat) 491 res := pat.FindAllStringSubmatch(test.match, -1) 492 assert.Len(t, res, 1) 493 assert.Len(t, res[0], 2) 494 assert.EqualValues(t, test.expected, res[0][1]) 495 } 496 } 497 }