github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/pkg/plugins/jira/jira_test.go (about)

     1  /*
     2  Copyright 2020 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package jira
    18  
    19  import (
    20  	"errors"
    21  	"reflect"
    22  	"testing"
    23  
    24  	"github.com/andygrunwald/go-jira"
    25  	"github.com/google/go-cmp/cmp"
    26  	"github.com/sirupsen/logrus"
    27  	"k8s.io/apimachinery/pkg/util/sets"
    28  
    29  	"sigs.k8s.io/prow/pkg/github"
    30  	"sigs.k8s.io/prow/pkg/github/fakegithub"
    31  	jiraclient "sigs.k8s.io/prow/pkg/jira"
    32  	"sigs.k8s.io/prow/pkg/jira/fakejira"
    33  	"sigs.k8s.io/prow/pkg/plugins"
    34  )
    35  
    36  func TestRegex(t *testing.T) {
    37  	t.Parallel()
    38  	testCases := []struct {
    39  		name     string
    40  		input    string
    41  		expected []string
    42  	}{
    43  		{
    44  			name:     "Simple",
    45  			input:    "issue-123",
    46  			expected: []string{"issue-123"},
    47  		},
    48  		{
    49  			name:     "Simple with leading space",
    50  			input:    " issue-123",
    51  			expected: []string{"issue-123"},
    52  		},
    53  		{
    54  			name:     "Simple with trailing space",
    55  			input:    "issue-123 ",
    56  			expected: []string{"issue-123"},
    57  		},
    58  		{
    59  			name:     "Simple with leading newline",
    60  			input:    "\nissue-123",
    61  			expected: []string{"issue-123"},
    62  		},
    63  		{
    64  			name:     "Simple with trailing newline",
    65  			input:    "issue-123\n",
    66  			expected: []string{"issue-123"},
    67  		},
    68  		{
    69  			name:     "Simple with trailing colon",
    70  			input:    "issue-123:",
    71  			expected: []string{"issue-123"},
    72  		},
    73  		{
    74  			name:     "Multiple matches",
    75  			input:    "issue-123\nissue-456",
    76  			expected: []string{"issue-123", "issue-456"},
    77  		},
    78  		{
    79  			name:  "Trailing character, no match",
    80  			input: "issue-123a",
    81  		},
    82  		{
    83  			name:     "Issue from url",
    84  			input:    "https://my-jira.com/browse/ABC-123",
    85  			expected: []string{"ABC-123"},
    86  		},
    87  		{
    88  			name:  "Trailing special characters, no match",
    89  			input: "rehearse-15676-pull",
    90  		},
    91  		{
    92  			name:     "Included in markdown link",
    93  			input:    "[Jira Bug ABC-123](https://my-jira.com/browse/ABC-123)",
    94  			expected: []string{"ABC-123", "ABC-123"},
    95  		},
    96  	}
    97  
    98  	for _, tc := range testCases {
    99  		t.Run(tc.name, func(t *testing.T) {
   100  			result := extractCandidatesFromText(tc.input)
   101  			if diff := cmp.Diff(tc.expected, result); diff != "" {
   102  				t.Errorf("expected differs from actual: %s", diff)
   103  			}
   104  		})
   105  	}
   106  }
   107  
   108  func TestHandle(t *testing.T) {
   109  	t.Parallel()
   110  	testCases := []struct {
   111  		name                   string
   112  		event                  github.GenericCommentEvent
   113  		cfg                    *plugins.Jira
   114  		projectCache           *threadsafeSet
   115  		getIssueClientError    map[string]error
   116  		existingIssues         []jira.Issue
   117  		existingLinks          map[string][]jira.RemoteLink
   118  		expectedNewLinks       []jira.RemoteLink
   119  		expectedCommentUpdates []string
   120  	}{
   121  		{
   122  			name: "No issue referenced, nothing to do",
   123  		},
   124  		{
   125  			name: "Link is created based on body",
   126  			event: github.GenericCommentEvent{
   127  				CommentID:  intPtr(1),
   128  				HTMLURL:    "https://github.com/org/repo/issues/3",
   129  				IssueTitle: "Some issue",
   130  				Body:       "Some text and also ABC-123",
   131  				Repo:       github.Repo{FullName: "org/repo", Owner: github.User{Login: "org"}, Name: "repo"},
   132  				Number:     3,
   133  			},
   134  			projectCache:   &threadsafeSet{data: sets.New[string]("abc")},
   135  			existingIssues: []jira.Issue{{ID: "ABC-123"}},
   136  			expectedNewLinks: []jira.RemoteLink{{Object: &jira.RemoteLinkObject{
   137  				URL:   "https://github.com/org/repo/issues/3",
   138  				Title: "org/repo#3: Some issue",
   139  				Icon: &jira.RemoteLinkIcon{
   140  					Url16x16: "https://github.com/favicon.ico",
   141  					Title:    "GitHub",
   142  				},
   143  			},
   144  			}},
   145  			expectedCommentUpdates: []string{"org/repo#1:Some text and also [ABC-123](https://my-jira.com/browse/ABC-123)"},
   146  		},
   147  		{
   148  			name: "Link is created based on body with pasted link",
   149  			event: github.GenericCommentEvent{
   150  				CommentID:  intPtr(1),
   151  				HTMLURL:    "https://github.com/org/repo/issues/3",
   152  				IssueTitle: "Some issue",
   153  				Body:       "Some text and also https://my-jira.com/browse/ABC-123",
   154  				Repo:       github.Repo{FullName: "org/repo", Owner: github.User{Login: "org"}, Name: "repo"},
   155  				Number:     3,
   156  			},
   157  			projectCache:   &threadsafeSet{data: sets.New[string]("abc")},
   158  			existingIssues: []jira.Issue{{ID: "ABC-123"}},
   159  			expectedNewLinks: []jira.RemoteLink{{Object: &jira.RemoteLinkObject{
   160  				URL:   "https://github.com/org/repo/issues/3",
   161  				Title: "org/repo#3: Some issue",
   162  				Icon: &jira.RemoteLinkIcon{
   163  					Url16x16: "https://github.com/favicon.ico",
   164  					Title:    "GitHub",
   165  				},
   166  			},
   167  			}},
   168  		},
   169  		{
   170  			name: "Link is created based on body and issuecomment suffix is removed from url",
   171  			event: github.GenericCommentEvent{
   172  				CommentID:  intPtr(1),
   173  				HTMLURL:    "https://github.com/org/repo/issues/3#issuecomment-705743977",
   174  				IssueTitle: "Some issue",
   175  				Body:       "Some text and also ABC-123",
   176  				Repo:       github.Repo{FullName: "org/repo", Owner: github.User{Login: "org"}, Name: "repo"},
   177  				Number:     3,
   178  			},
   179  			projectCache:   &threadsafeSet{data: sets.New[string]("abc")},
   180  			existingIssues: []jira.Issue{{ID: "ABC-123"}},
   181  			expectedNewLinks: []jira.RemoteLink{{Object: &jira.RemoteLinkObject{
   182  				URL:   "https://github.com/org/repo/issues/3",
   183  				Title: "org/repo#3: Some issue",
   184  				Icon: &jira.RemoteLinkIcon{
   185  					Url16x16: "https://github.com/favicon.ico",
   186  					Title:    "GitHub",
   187  				},
   188  			},
   189  			}},
   190  			expectedCommentUpdates: []string{"org/repo#1:Some text and also [ABC-123](https://my-jira.com/browse/ABC-123)"},
   191  		},
   192  		{
   193  			name: "Link is created based on title",
   194  			event: github.GenericCommentEvent{
   195  				HTMLURL:    "https://github.com/org/repo/issues/3",
   196  				IssueTitle: "ABC-123: Some issue",
   197  				Body:       "Some text",
   198  				Repo:       github.Repo{FullName: "org/repo"},
   199  				Number:     3,
   200  			},
   201  			projectCache:   &threadsafeSet{data: sets.New[string]("abc")},
   202  			existingIssues: []jira.Issue{{ID: "ABC-123"}},
   203  			expectedNewLinks: []jira.RemoteLink{{Object: &jira.RemoteLinkObject{
   204  				URL:   "https://github.com/org/repo/issues/3",
   205  				Title: "org/repo#3: ABC-123: Some issue",
   206  				Icon: &jira.RemoteLinkIcon{
   207  					Url16x16: "https://github.com/favicon.ico",
   208  					Title:    "GitHub",
   209  				},
   210  			},
   211  			}},
   212  		},
   213  		{
   214  			name: "Multiple references for issue, one link is created",
   215  			event: github.GenericCommentEvent{
   216  				CommentID:  intPtr(1),
   217  				HTMLURL:    "https://github.com/org/repo/issues/3",
   218  				IssueTitle: "Some issue",
   219  				Body:       "Some text and also ABC-123 and again ABC-123",
   220  				Repo:       github.Repo{FullName: "org/repo", Owner: github.User{Login: "org"}, Name: "repo"},
   221  				Number:     3,
   222  			},
   223  			projectCache:   &threadsafeSet{data: sets.New[string]("abc")},
   224  			existingIssues: []jira.Issue{{ID: "ABC-123"}},
   225  			expectedNewLinks: []jira.RemoteLink{{Object: &jira.RemoteLinkObject{
   226  				URL:   "https://github.com/org/repo/issues/3",
   227  				Title: "org/repo#3: Some issue",
   228  				Icon: &jira.RemoteLinkIcon{
   229  					Url16x16: "https://github.com/favicon.ico",
   230  					Title:    "GitHub",
   231  				},
   232  			},
   233  			}},
   234  			expectedCommentUpdates: []string{"org/repo#1:Some text and also [ABC-123](https://my-jira.com/browse/ABC-123) and again [ABC-123](https://my-jira.com/browse/ABC-123)"},
   235  		},
   236  		{
   237  			name: "Referenced issue doesn't exist, nothing to do",
   238  			event: github.GenericCommentEvent{
   239  				HTMLURL:    "https://github.com/org/repo/issues/3#issuecomment-705743977",
   240  				IssueTitle: "Some issue",
   241  				Body:       "Some text and also ABC-123",
   242  				Repo:       github.Repo{FullName: "org/repo"},
   243  				Number:     3,
   244  			},
   245  			projectCache: &threadsafeSet{data: sets.New[string]("abc")},
   246  		},
   247  		{
   248  			name: "Link already exists, nothing to do",
   249  			event: github.GenericCommentEvent{
   250  				HTMLURL:    "https://github.com/org/repo/issues/3",
   251  				IssueTitle: "Some issue",
   252  				Body:       "Some text and also [ABC-123](https://my-jira.com/browse/ABC-123)",
   253  				Repo:       github.Repo{FullName: "org/repo"},
   254  				Number:     3,
   255  			},
   256  			projectCache:   &threadsafeSet{data: sets.New[string]("abc")},
   257  			existingIssues: []jira.Issue{{ID: "ABC-123"}},
   258  			existingLinks:  map[string][]jira.RemoteLink{"ABC-123": {{Object: &jira.RemoteLinkObject{URL: "https://github.com/org/repo/issues/3", Title: "org/repo#3: Some issue"}}}},
   259  		},
   260  		{
   261  			name: "Link exists but title is different, replacing it",
   262  			event: github.GenericCommentEvent{
   263  				HTMLURL:    "https://github.com/org/repo/issues/3",
   264  				IssueTitle: "Some issue NEW",
   265  				Body:       "Some text and also [ABC-123:](https://my-jira.com/browse/ABC-123)",
   266  				Repo:       github.Repo{FullName: "org/repo"},
   267  				Number:     3,
   268  			},
   269  			projectCache:   &threadsafeSet{data: sets.New[string]("abc")},
   270  			existingIssues: []jira.Issue{{ID: "ABC-123"}},
   271  			existingLinks: map[string][]jira.RemoteLink{
   272  				"ABC-123": {
   273  					{
   274  						Object: &jira.RemoteLinkObject{
   275  							URL:   "https://github.com/org/repo/issues/3",
   276  							Title: "org/repo#3: Some issue",
   277  							Icon:  &jira.RemoteLinkIcon{Url16x16: "https://github.com/favicon.ico", Title: "GitHub"},
   278  						},
   279  					},
   280  				},
   281  			},
   282  			expectedNewLinks: []jira.RemoteLink{
   283  				{
   284  					Object: &jira.RemoteLinkObject{
   285  						URL:   "https://github.com/org/repo/issues/3",
   286  						Title: "org/repo#3: Some issue NEW",
   287  						Icon:  &jira.RemoteLinkIcon{Url16x16: "https://github.com/favicon.ico", Title: "GitHub"},
   288  					},
   289  				},
   290  			},
   291  		},
   292  		{
   293  			name: "Valid issue in disabled project, case insensitive matching and no link",
   294  			event: github.GenericCommentEvent{
   295  				HTMLURL:    "https://github.com/org/repo/issues/3",
   296  				IssueTitle: "Some issue",
   297  				Body:       "Some text and also ENTERPRISE-4",
   298  				Repo:       github.Repo{FullName: "org/repo"},
   299  				Number:     3,
   300  			},
   301  			projectCache:   &threadsafeSet{data: sets.New[string]("enterprise")},
   302  			cfg:            &plugins.Jira{DisabledJiraProjects: []string{"Enterprise"}},
   303  			existingIssues: []jira.Issue{{ID: "ENTERPRISE-4"}},
   304  		},
   305  		{
   306  			name: "Valid issue in disabled project, multiple references, with markdown link, case insensitive matching, nothing to do",
   307  			event: github.GenericCommentEvent{
   308  				HTMLURL:    "https://github.com/org/repo/issues/3",
   309  				IssueTitle: "ABC-123: Fixes Some issue",
   310  				Body:       "Some text and also [ABC-123](https://my-jira.com/browse/ABC-123)",
   311  				Repo:       github.Repo{FullName: "org/repo"},
   312  				Number:     3,
   313  			},
   314  			projectCache: &threadsafeSet{data: sets.New[string]("abc")},
   315  			cfg:          &plugins.Jira{DisabledJiraProjects: []string{"abc"}},
   316  		},
   317  		{
   318  			name: "Project 404 gets served from cache, nothing happens",
   319  			event: github.GenericCommentEvent{
   320  				HTMLURL:    "https://github.com/org/repo/issues/3",
   321  				IssueTitle: "Some issue",
   322  				Body:       "ABC-123",
   323  				Repo:       github.Repo{FullName: "org/repo"},
   324  				Number:     3,
   325  			},
   326  			projectCache:        &threadsafeSet{},
   327  			getIssueClientError: map[string]error{"ABC-123": errors.New("error: didn't serve 404 from cache")},
   328  		},
   329  	}
   330  
   331  	for _, tc := range testCases {
   332  		t.Run(tc.name, func(t *testing.T) {
   333  			// convert []jira.Issue to []*jira.Issue
   334  			var ptrIssues []*jira.Issue
   335  			for index := range tc.existingIssues {
   336  				ptrIssues = append(ptrIssues, &tc.existingIssues[index])
   337  			}
   338  			jiraClient := &fakejira.FakeClient{
   339  				Issues:        ptrIssues,
   340  				ExistingLinks: tc.existingLinks,
   341  				GetIssueError: tc.getIssueClientError,
   342  			}
   343  			githubClient := fakegithub.NewFakeClient()
   344  
   345  			if err := handleWithProjectCache(jiraClient, githubClient, tc.cfg, logrus.NewEntry(logrus.New()), &tc.event, tc.projectCache); err != nil {
   346  				t.Fatalf("handle failed: %v", err)
   347  			}
   348  
   349  			if diff := cmp.Diff(jiraClient.NewLinks, tc.expectedNewLinks); diff != "" {
   350  				t.Errorf("new links differs from expected new links: %s", diff)
   351  			}
   352  
   353  			if diff := cmp.Diff(githubClient.IssueCommentsEdited, tc.expectedCommentUpdates); diff != "" {
   354  				t.Errorf("comment updates differ from expected: %s", diff)
   355  			}
   356  		})
   357  	}
   358  
   359  }
   360  
   361  func intPtr(i int) *int {
   362  	return &i
   363  }
   364  
   365  func TestInsertLinksIntoComment(t *testing.T) {
   366  	t.Parallel()
   367  	const issueName = "ABC-123"
   368  	testCases := []struct {
   369  		name     string
   370  		body     string
   371  		expected string
   372  	}{
   373  		{
   374  			name: "Multiline body starting with issue name",
   375  			body: `ABC-123: Fix problems:
   376  * First problem
   377  * Second problem`,
   378  			expected: `[ABC-123](https://my-jira.com/browse/ABC-123): Fix problems:
   379  * First problem
   380  * Second problem`,
   381  		},
   382  		{
   383  			name: "Multiline body starting with already replaced issue name",
   384  			body: `[ABC-123](https://my-jira.com/browse/ABC-123): Fix problems:
   385  * First problem
   386  * Second problem`,
   387  			expected: `[ABC-123](https://my-jira.com/browse/ABC-123): Fix problems:
   388  * First problem
   389  * Second problem`,
   390  		},
   391  		{
   392  			name: "Multiline body with multiple occurrence in the middle",
   393  			body: `This change:
   394  * Does stuff related to ABC-123
   395  * And even more stuff related to ABC-123
   396  * But also something else`,
   397  			expected: `This change:
   398  * Does stuff related to [ABC-123](https://my-jira.com/browse/ABC-123)
   399  * And even more stuff related to [ABC-123](https://my-jira.com/browse/ABC-123)
   400  * But also something else`,
   401  		},
   402  		{
   403  			name: "Multiline body with multiple occurrence in the middle, some already replaced",
   404  			body: `This change:
   405  * Does stuff related to [ABC-123](https://my-jira.com/browse/ABC-123)
   406  * And even more stuff related to ABC-123
   407  * But also something else`,
   408  			expected: `This change:
   409  * Does stuff related to [ABC-123](https://my-jira.com/browse/ABC-123)
   410  * And even more stuff related to [ABC-123](https://my-jira.com/browse/ABC-123)
   411  * But also something else`,
   412  		},
   413  		{
   414  			name: "Multiline body with issue name at the end",
   415  			body: `This change:
   416  is very important
   417  because of ABC-123`,
   418  			expected: `This change:
   419  is very important
   420  because of [ABC-123](https://my-jira.com/browse/ABC-123)`,
   421  		},
   422  		{
   423  			name: "Multiline body with already replaced issue name at the end",
   424  			body: `This change:
   425  is very important
   426  because of [ABC-123](https://my-jira.com/browse/ABC-123)`,
   427  			expected: `This change:
   428  is very important
   429  because of [ABC-123](https://my-jira.com/browse/ABC-123)`,
   430  		},
   431  		{
   432  			name:     "Pasted links are not replaced, as they are already clickable",
   433  			body:     "https://my-jira.com/browse/ABC-123",
   434  			expected: "https://my-jira.com/browse/ABC-123",
   435  		},
   436  		{
   437  			name: "code section is not replaced",
   438  			body: `This change:
   439  is very important` + "\n```bash\n" +
   440  				`ABC-123` +
   441  				"\n```\n" + `ABC-123
   442  `,
   443  			expected: `This change:
   444  is very important` + "\n```bash\n" +
   445  				`ABC-123` +
   446  				"\n```\n" + `[ABC-123](https://my-jira.com/browse/ABC-123)
   447  `,
   448  		},
   449  		{
   450  			name: "inline code is not replaced",
   451  			body: `This change:
   452  is very important` + "\n``ABC-123`` and `ABC-123` shouldn't be replaced, as well as ``ABC-123: text text``. " +
   453  				`ABC-123 should be replaced.
   454  `,
   455  			expected: `This change:
   456  is very important` + "\n``ABC-123`` and `ABC-123` shouldn't be replaced, as well as ``ABC-123: text text``. " +
   457  				`[ABC-123](https://my-jira.com/browse/ABC-123) should be replaced.
   458  `,
   459  		},
   460  		{
   461  			name:     "Multiline codeblock that is denoted through four leading spaces",
   462  			body:     "I meant to do this test:\r\n\r\n    operator_test.go:1914: failed to read output from pod unique-id-header-test-1: container \"curl\" in pod \"unique-id-header-ABC-123\" is waiting to start: ContainerCreating\r\n\r\n",
   463  			expected: "I meant to do this test:\r\n\r\n    operator_test.go:1914: failed to read output from pod unique-id-header-test-1: container \"curl\" in pod \"unique-id-header-ABC-123\" is waiting to start: ContainerCreating\r\n\r\n",
   464  		},
   465  		{
   466  			name:     "parts of words starting with a dash are not replaced",
   467  			body:     "this shouldn't be replaced: whatever-ABC-123 and also inline `whatever-ABC-123`",
   468  			expected: "this shouldn't be replaced: whatever-ABC-123 and also inline `whatever-ABC-123`",
   469  		},
   470  	}
   471  
   472  	for _, tc := range testCases {
   473  		t.Run(tc.name, func(t *testing.T) {
   474  			if diff := cmp.Diff(insertLinksIntoComment(tc.body, []string{issueName}, fakejira.FakeJiraUrl), tc.expected); diff != "" {
   475  				t.Errorf("actual result differs from expected result: %s", diff)
   476  			}
   477  		})
   478  	}
   479  }
   480  
   481  func TestProjectCachingJiraClient(t *testing.T) {
   482  	t.Parallel()
   483  	lowerCaseIssue := jira.Issue{ID: "issue-123"}
   484  	upperCaseIssue := jira.Issue{ID: "ISSUE-123"}
   485  	testCases := []struct {
   486  		name           string
   487  		client         jiraclient.Client
   488  		issueToRequest string
   489  		cache          *threadsafeSet
   490  		expectedError  error
   491  	}{
   492  		{
   493  			name:           "404 gets served from cache",
   494  			client:         &fakejira.FakeClient{},
   495  			issueToRequest: "issue-123",
   496  			cache:          &threadsafeSet{data: sets.Set[string]{}},
   497  			expectedError:  jiraclient.NewNotFoundError(errors.New("404 from cache")),
   498  		},
   499  		{
   500  			name:           "Success",
   501  			client:         &fakejira.FakeClient{Issues: []*jira.Issue{&lowerCaseIssue}},
   502  			issueToRequest: "issue-123",
   503  			cache:          &threadsafeSet{data: sets.New[string]("issue")},
   504  		},
   505  		{
   506  			name:           "Success case-insensitive",
   507  			client:         &fakejira.FakeClient{Issues: []*jira.Issue{&upperCaseIssue}},
   508  			issueToRequest: "ISSUE-123",
   509  			cache:          &threadsafeSet{data: sets.New[string]("issue")},
   510  		},
   511  	}
   512  
   513  	for _, tc := range testCases {
   514  		t.Run(tc.name, func(t *testing.T) {
   515  			cachingClient := &projectCachingJiraClient{
   516  				Client: tc.client,
   517  				cache:  tc.cache,
   518  			}
   519  
   520  			_, err := cachingClient.GetIssue(tc.issueToRequest)
   521  			if diff := cmp.Diff(tc.expectedError, err, cmp.Exporter(func(_ reflect.Type) bool { return true })); diff != "" {
   522  				t.Fatalf("expected error differs from expected: %s", diff)
   523  			}
   524  		})
   525  	}
   526  }
   527  
   528  func TestFilterOutDisabledJiraProjects(t *testing.T) {
   529  	t.Parallel()
   530  	testCases := []struct {
   531  		name           string
   532  		candidates     []string
   533  		jiraConfig     *plugins.Jira
   534  		expectedOutput []string
   535  	}{{
   536  		name:           "empty jira config",
   537  		candidates:     []string{"ABC-123", "DEF-567"},
   538  		jiraConfig:     nil,
   539  		expectedOutput: []string{"ABC-123", "DEF-567"},
   540  	}, {
   541  		name:           "upper case disabled list",
   542  		candidates:     []string{"ABC-123", "DEF-567"},
   543  		jiraConfig:     &plugins.Jira{DisabledJiraProjects: []string{"ABC"}},
   544  		expectedOutput: []string{"DEF-567"},
   545  	}, {
   546  		name:           "lower case disabled list",
   547  		candidates:     []string{"ABC-123", "DEF-567"},
   548  		jiraConfig:     &plugins.Jira{DisabledJiraProjects: []string{"abc"}},
   549  		expectedOutput: []string{"DEF-567"},
   550  	}, {
   551  		name:           "multiple disabled projects",
   552  		candidates:     []string{"ABC-123", "DEF-567"},
   553  		jiraConfig:     &plugins.Jira{DisabledJiraProjects: []string{"abc", "def"}},
   554  		expectedOutput: []string{},
   555  	}}
   556  
   557  	for _, tc := range testCases {
   558  		t.Run(tc.name, func(t *testing.T) {
   559  			output := filterOutDisabledJiraProjects(tc.candidates, tc.jiraConfig)
   560  			if diff := cmp.Diff(tc.expectedOutput, output); diff != "" {
   561  				t.Fatalf("actual output differes from expected output: %s", diff)
   562  			}
   563  		})
   564  	}
   565  }