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