github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/references/references_test.go (about)

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