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 14KB

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