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

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