You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

references_test.go 15KB

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