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  }