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

     1  /*
     2  Copyright 2016 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 releasenote
    18  
    19  import (
    20  	"fmt"
    21  	"reflect"
    22  	"sort"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/sirupsen/logrus"
    27  
    28  	"k8s.io/apimachinery/pkg/util/sets"
    29  	"sigs.k8s.io/prow/pkg/github"
    30  	"sigs.k8s.io/prow/pkg/github/fakegithub"
    31  	"sigs.k8s.io/prow/pkg/labels"
    32  )
    33  
    34  func TestReleaseNoteComment(t *testing.T) {
    35  	var testcases = []struct {
    36  		name          string
    37  		action        github.IssueCommentEventAction
    38  		commentBody   string
    39  		issueBody     string
    40  		isMember      bool
    41  		isAuthor      bool
    42  		currentLabels []string
    43  
    44  		deletedLabels []string
    45  		addedLabel    string
    46  		shouldComment bool
    47  	}{
    48  		{
    49  			name:          "unrelated comment",
    50  			action:        github.IssueCommentActionCreated,
    51  			commentBody:   "oh dear",
    52  			currentLabels: []string{labels.ReleaseNoteLabelNeeded, "other"},
    53  		},
    54  		{
    55  			name:          "author release-note-none with missing block",
    56  			action:        github.IssueCommentActionCreated,
    57  			isAuthor:      true,
    58  			commentBody:   "/release-note-none",
    59  			currentLabels: []string{labels.ReleaseNoteLabelNeeded, "other"},
    60  
    61  			deletedLabels: []string{labels.ReleaseNoteLabelNeeded},
    62  			addedLabel:    labels.ReleaseNoteNone,
    63  		},
    64  		{
    65  			name:          "author release-note-none with empty block",
    66  			action:        github.IssueCommentActionCreated,
    67  			isAuthor:      true,
    68  			commentBody:   "/release-note-none",
    69  			issueBody:     "bologna ```release-note \n ```",
    70  			currentLabels: []string{labels.ReleaseNoteLabelNeeded, "other"},
    71  
    72  			deletedLabels: []string{labels.ReleaseNoteLabelNeeded},
    73  			addedLabel:    labels.ReleaseNoteNone,
    74  		},
    75  		{
    76  			name:          "author release-note-none with \"none\" block",
    77  			action:        github.IssueCommentActionCreated,
    78  			isAuthor:      true,
    79  			commentBody:   "/release-note-none",
    80  			issueBody:     "bologna ```release-note \nnone \n ```",
    81  			currentLabels: []string{labels.ReleaseNoteLabelNeeded, "other"},
    82  
    83  			deletedLabels: []string{labels.ReleaseNoteLabelNeeded},
    84  			addedLabel:    labels.ReleaseNoteNone,
    85  		},
    86  		{
    87  			name:          "author release-note-none with \"no\" block",
    88  			action:        github.IssueCommentActionCreated,
    89  			isAuthor:      true,
    90  			commentBody:   "/release-note-none",
    91  			issueBody:     "bologna ```release-note \nno \n ```",
    92  			currentLabels: []string{labels.ReleaseNoteLabelNeeded, "other"},
    93  
    94  			deletedLabels: []string{labels.ReleaseNoteLabelNeeded},
    95  			addedLabel:    labels.ReleaseNoteNone,
    96  		},
    97  		{
    98  			name:          "author release-note-none, trailing space.",
    99  			action:        github.IssueCommentActionCreated,
   100  			isAuthor:      true,
   101  			commentBody:   "/release-note-none ",
   102  			currentLabels: []string{labels.ReleaseNoteLabelNeeded, "other"},
   103  
   104  			deletedLabels: []string{labels.ReleaseNoteLabelNeeded},
   105  			addedLabel:    labels.ReleaseNoteNone,
   106  		},
   107  		{
   108  			name:          "author release-note-none, no op.",
   109  			action:        github.IssueCommentActionCreated,
   110  			isAuthor:      true,
   111  			commentBody:   "/release-note-none",
   112  			currentLabels: []string{labels.ReleaseNoteNone, "other"},
   113  		},
   114  		{
   115  			name:          "member release-note",
   116  			action:        github.IssueCommentActionCreated,
   117  			isMember:      true,
   118  			commentBody:   "/release-note",
   119  			currentLabels: []string{labels.ReleaseNoteLabelNeeded, "other"},
   120  
   121  			shouldComment: true,
   122  		},
   123  		{
   124  			name:          "someone else release-note, trailing space.",
   125  			action:        github.IssueCommentActionCreated,
   126  			commentBody:   "/release-note \r",
   127  			currentLabels: []string{labels.ReleaseNoteLabelNeeded, "other"},
   128  			shouldComment: true,
   129  		},
   130  		{
   131  			name:          "someone else release-note-none",
   132  			action:        github.IssueCommentActionCreated,
   133  			commentBody:   "/release-note-none",
   134  			currentLabels: []string{labels.ReleaseNoteLabelNeeded, "other"},
   135  			shouldComment: true,
   136  		},
   137  		{
   138  			name:          "author release-note-action-required",
   139  			action:        github.IssueCommentActionCreated,
   140  			isAuthor:      true,
   141  			commentBody:   "/release-note-action-required",
   142  			currentLabels: []string{labels.ReleaseNoteLabelNeeded, "other"},
   143  			shouldComment: true,
   144  		},
   145  		{
   146  			name:          "release-note-none, delete multiple labels",
   147  			action:        github.IssueCommentActionCreated,
   148  			isMember:      true,
   149  			commentBody:   "/release-note-none",
   150  			currentLabels: []string{labels.ReleaseNote, labels.ReleaseNoteLabelNeeded, labels.ReleaseNoteActionRequired, labels.ReleaseNoteNone, "other"},
   151  
   152  			deletedLabels: []string{labels.ReleaseNoteLabelNeeded, labels.ReleaseNoteActionRequired, labels.ReleaseNote},
   153  		},
   154  		{
   155  			name:        "no label present",
   156  			action:      github.IssueCommentActionCreated,
   157  			isMember:    true,
   158  			commentBody: "/release-note-none",
   159  
   160  			addedLabel: labels.ReleaseNoteNone,
   161  		},
   162  		{
   163  			name:          "member release-note-none, PR has kind/deprecation label",
   164  			action:        github.IssueCommentActionCreated,
   165  			isMember:      true,
   166  			commentBody:   "/release-note-none",
   167  			currentLabels: []string{labels.DeprecationLabel},
   168  			shouldComment: true,
   169  		},
   170  	}
   171  	for _, tc := range testcases {
   172  		fc := fakegithub.NewFakeClient()
   173  		fc.IssueComments = make(map[int][]github.IssueComment)
   174  		fc.OrgMembers = map[string][]string{"": {"m"}}
   175  		ice := github.IssueCommentEvent{
   176  			Action: tc.action,
   177  			Comment: github.IssueComment{
   178  				Body: tc.commentBody,
   179  			},
   180  			Issue: github.Issue{
   181  				Body:        tc.issueBody,
   182  				User:        github.User{Login: "a"},
   183  				Number:      5,
   184  				State:       "open",
   185  				PullRequest: &struct{}{},
   186  				Assignees:   []github.User{{Login: "r"}},
   187  			},
   188  		}
   189  		if tc.isAuthor {
   190  			ice.Comment.User.Login = "a"
   191  		} else if tc.isMember {
   192  			ice.Comment.User.Login = "m"
   193  		}
   194  		for _, l := range tc.currentLabels {
   195  			ice.Issue.Labels = append(ice.Issue.Labels, github.Label{Name: l})
   196  		}
   197  		if err := handleComment(fc, logrus.WithField("plugin", PluginName), ice); err != nil {
   198  			t.Errorf("For case %s, did not expect error: %v", tc.name, err)
   199  		}
   200  		if tc.shouldComment && len(fc.IssueComments[5]) == 0 {
   201  			t.Errorf("For case %s, didn't comment but should have.", tc.name)
   202  		}
   203  		if len(fc.IssueLabelsAdded) > 1 {
   204  			t.Errorf("For case %s, added more than one label: %v", tc.name, fc.IssueLabelsAdded)
   205  		} else if len(fc.IssueLabelsAdded) == 0 && tc.addedLabel != "" {
   206  			t.Errorf("For case %s, should have added %s but didn't.", tc.name, tc.addedLabel)
   207  		} else if len(fc.IssueLabelsAdded) == 1 && fc.IssueLabelsAdded[0] != "/#5:"+tc.addedLabel {
   208  			t.Errorf("For case %s, added wrong label. Got %s, expected %s", tc.name, fc.IssueLabelsAdded[0], tc.addedLabel)
   209  		}
   210  
   211  		var expectedDeleted []string
   212  		for _, expect := range tc.deletedLabels {
   213  			expectedDeleted = append(expectedDeleted, "/#5:"+expect)
   214  		}
   215  		sort.Strings(expectedDeleted)
   216  		sort.Strings(fc.IssueLabelsRemoved)
   217  		if !reflect.DeepEqual(expectedDeleted, fc.IssueLabelsRemoved) {
   218  			t.Errorf(
   219  				"For case %s, expected %q labels to be deleted, but %q were deleted.",
   220  				tc.name,
   221  				expectedDeleted,
   222  				fc.IssueLabelsRemoved,
   223  			)
   224  		}
   225  	}
   226  }
   227  
   228  const lgtmLabel = labels.LGTM
   229  
   230  func formatLabels(num int, labels ...string) []string {
   231  	out := make([]string, 0, len(labels))
   232  	for _, l := range labels {
   233  		out = append(out, fmt.Sprintf("org/repo#%d:%s", num, l))
   234  	}
   235  	return out
   236  }
   237  
   238  func newFakeClient(body, branch string, initialLabels, comments []string, parentPRs map[int]string) (*fakegithub.FakeClient, *github.PullRequestEvent) {
   239  	formattedLabels := formatLabels(1, initialLabels...)
   240  	for parent, l := range parentPRs {
   241  		formattedLabels = append(formattedLabels, formatLabels(parent, l)...)
   242  	}
   243  	var issueComments []github.IssueComment
   244  	for _, comment := range comments {
   245  		issueComments = append(issueComments, github.IssueComment{Body: comment})
   246  	}
   247  	fghc := fakegithub.NewFakeClient()
   248  	fghc.IssueComments = map[int][]github.IssueComment{1: issueComments}
   249  	fghc.RepoLabelsExisting = []string{
   250  		lgtmLabel,
   251  		labels.ReleaseNote,
   252  		labels.ReleaseNoteLabelNeeded,
   253  		labels.ReleaseNoteNone,
   254  		labels.ReleaseNoteActionRequired,
   255  	}
   256  	fghc.IssueLabelsAdded = formattedLabels
   257  	fghc.IssueLabelsRemoved = []string{}
   258  	return fghc, &github.PullRequestEvent{
   259  		Action: github.PullRequestActionEdited,
   260  		Number: 1,
   261  		PullRequest: github.PullRequest{
   262  			Base:   github.PullRequestBranch{Ref: branch},
   263  			Number: 1,
   264  			Body:   body,
   265  			User:   github.User{Login: "cjwagner"},
   266  		},
   267  		Repo: github.Repo{
   268  			Owner: github.User{Login: "org"},
   269  			Name:  "repo",
   270  		},
   271  	}
   272  }
   273  
   274  func TestReleaseNotePR(t *testing.T) {
   275  	tests := []struct {
   276  		name               string
   277  		initialLabels      []string
   278  		body               string
   279  		branch             string // Defaults to master
   280  		parentPRs          map[int]string
   281  		issueComments      []string
   282  		IssueLabelsAdded   []string
   283  		IssueLabelsRemoved []string
   284  		merged             bool
   285  	}{
   286  		{
   287  			name:          "LGTM with release-note",
   288  			initialLabels: []string{lgtmLabel, labels.ReleaseNote},
   289  			body:          "```release-note\n note note note.\n```",
   290  		},
   291  		{
   292  			name:          "LGTM with release-note, arbitrary comment",
   293  			initialLabels: []string{lgtmLabel, labels.ReleaseNote},
   294  			body:          "```release-note\n note note note.\n```",
   295  			issueComments: []string{"Release notes are great fun."},
   296  		},
   297  		{
   298  			name:          "LGTM with release-note-none",
   299  			initialLabels: []string{lgtmLabel, labels.ReleaseNoteNone},
   300  			body:          "```release-note\nnone\n```",
   301  		},
   302  		{
   303  			name:          "LGTM with release-note-none, /release-note-none comment, empty block",
   304  			initialLabels: []string{lgtmLabel, labels.ReleaseNoteNone},
   305  			body:          "```release-note\n```",
   306  			issueComments: []string{"/release-note-none "},
   307  		},
   308  		{
   309  			name:          "LGTM with release-note-action-required",
   310  			initialLabels: []string{lgtmLabel, labels.ReleaseNoteActionRequired},
   311  			body:          "```release-note\n Action required.\n```",
   312  		},
   313  		{
   314  			name:          "LGTM with release-note-action-required, /release-note-none comment",
   315  			initialLabels: []string{lgtmLabel, labels.ReleaseNoteActionRequired},
   316  			body:          "```release-note\n Action required.\n```",
   317  			issueComments: []string{"Release notes are great fun.", "Especially \n/release-note-none"},
   318  		},
   319  		{
   320  			name:          "LGTM with do-not-merge/release-note-label-needed",
   321  			initialLabels: []string{lgtmLabel, labels.ReleaseNoteLabelNeeded},
   322  		},
   323  		{
   324  			name:               "LGTM with do-not-merge/release-note-label-needed, /release-note-none comment",
   325  			initialLabels:      []string{lgtmLabel, labels.ReleaseNoteLabelNeeded},
   326  			issueComments:      []string{"Release notes are great fun.", "Especially \n/release-note-none"},
   327  			IssueLabelsAdded:   []string{labels.ReleaseNoteNone},
   328  			IssueLabelsRemoved: []string{labels.ReleaseNoteLabelNeeded},
   329  		},
   330  		{
   331  			name:             "LGTM only",
   332  			initialLabels:    []string{lgtmLabel},
   333  			IssueLabelsAdded: []string{labels.ReleaseNoteLabelNeeded},
   334  		},
   335  		{
   336  			name:             "No labels",
   337  			initialLabels:    []string{},
   338  			IssueLabelsAdded: []string{labels.ReleaseNoteLabelNeeded},
   339  		},
   340  		{
   341  			name:          "release-note",
   342  			initialLabels: []string{labels.ReleaseNote},
   343  			body:          "```release-note normal note.```",
   344  		},
   345  		{
   346  			name:          "release-note, /release-note-none comment",
   347  			initialLabels: []string{labels.ReleaseNote},
   348  			body:          "```release-note normal note.```",
   349  			issueComments: []string{"/release-note-none "},
   350  		},
   351  		{
   352  			name:          "release-note-none",
   353  			initialLabels: []string{labels.ReleaseNoteNone},
   354  			body:          "```release-note\nnone\n```",
   355  		},
   356  		{
   357  			name:          "release-note-action-required",
   358  			initialLabels: []string{labels.ReleaseNoteActionRequired},
   359  			body:          "```release-note\n action required```",
   360  		},
   361  		{
   362  			name:               "release-note and do-not-merge/release-note-label-needed with no note",
   363  			initialLabels:      []string{labels.ReleaseNote, labels.ReleaseNoteLabelNeeded},
   364  			IssueLabelsRemoved: []string{labels.ReleaseNote},
   365  		},
   366  		{
   367  			name:               "release-note and do-not-merge/release-note-label-needed with note",
   368  			initialLabels:      []string{labels.ReleaseNote, labels.ReleaseNoteLabelNeeded},
   369  			body:               "```release-note note  ```",
   370  			IssueLabelsRemoved: []string{labels.ReleaseNoteLabelNeeded},
   371  		},
   372  		{
   373  			name:               "release-note-none and do-not-merge/release-note-label-needed",
   374  			initialLabels:      []string{labels.ReleaseNoteNone, labels.ReleaseNoteLabelNeeded},
   375  			body:               "```release-note\nnone\n```",
   376  			IssueLabelsRemoved: []string{labels.ReleaseNoteLabelNeeded},
   377  		},
   378  		{
   379  			name:               "release-note-action-required and do-not-merge/release-note-label-needed",
   380  			initialLabels:      []string{labels.ReleaseNoteActionRequired, labels.ReleaseNoteLabelNeeded},
   381  			body:               "```release-note\nSomething something dark side. Something something ACTION REQUIRED.```",
   382  			IssueLabelsRemoved: []string{labels.ReleaseNoteLabelNeeded},
   383  		},
   384  		{
   385  			name:          "do not add needs label when parent PR has releaseNote label",
   386  			branch:        "release-1.2",
   387  			initialLabels: []string{},
   388  			body:          "Cherry pick of #2 on release-1.2.",
   389  			parentPRs:     map[int]string{2: labels.ReleaseNote},
   390  		},
   391  		{
   392  			name:               "do not touch LGTM on non-master when parent PR has releaseNote label, but remove releaseNoteNeeded",
   393  			branch:             "release-1.2",
   394  			initialLabels:      []string{lgtmLabel, labels.ReleaseNoteLabelNeeded},
   395  			body:               "Cherry pick of #2 on release-1.2.",
   396  			parentPRs:          map[int]string{2: labels.ReleaseNote},
   397  			IssueLabelsRemoved: []string{labels.ReleaseNoteLabelNeeded},
   398  		},
   399  		{
   400  			name:          "do nothing when PR has releaseNoteActionRequired, but parent PR does not have releaseNote label",
   401  			branch:        "release-1.2",
   402  			initialLabels: []string{labels.ReleaseNoteActionRequired},
   403  			body:          "Cherry pick of #2 on release-1.2.\n```release-note note action required note\n```",
   404  			parentPRs:     map[int]string{2: labels.ReleaseNoteNone},
   405  		},
   406  		{
   407  			name:             "add releaseNoteNeeded on non-master when parent PR has releaseNoteNone label",
   408  			branch:           "release-1.2",
   409  			initialLabels:    []string{lgtmLabel},
   410  			body:             "Cherry pick of #2 on release-1.2.",
   411  			parentPRs:        map[int]string{2: labels.ReleaseNoteNone},
   412  			IssueLabelsAdded: []string{labels.ReleaseNoteLabelNeeded},
   413  		},
   414  		{
   415  			name:             "add releaseNoteNeeded on non-master when 1 of 2 parent PRs has releaseNoteNone",
   416  			branch:           "release-1.2",
   417  			initialLabels:    []string{lgtmLabel},
   418  			body:             "Other text.\nCherry pick of #2 on release-1.2.\nCherry pick of #4 on release-1.2.\n",
   419  			parentPRs:        map[int]string{2: labels.ReleaseNote, 4: labels.ReleaseNoteNone},
   420  			IssueLabelsAdded: []string{labels.ReleaseNoteLabelNeeded},
   421  		},
   422  		{
   423  			name:               "remove releaseNoteNeeded on non-master when both parent PRs have a release note",
   424  			branch:             "release-1.2",
   425  			initialLabels:      []string{lgtmLabel, labels.ReleaseNoteLabelNeeded},
   426  			body:               "Other text.\nCherry pick of #2 on release-1.2.\nCherry pick of #4 on release-1.2.\n",
   427  			parentPRs:          map[int]string{2: labels.ReleaseNote, 4: labels.ReleaseNoteActionRequired},
   428  			IssueLabelsRemoved: []string{labels.ReleaseNoteLabelNeeded},
   429  		},
   430  		{
   431  			name:               "add releaseNoteActionRequired on non-master when body contains note even though both parent PRs have a release note (non-mandatory RN)",
   432  			branch:             "release-1.2",
   433  			initialLabels:      []string{lgtmLabel, labels.ReleaseNoteLabelNeeded},
   434  			body:               "Other text.\nCherry pick of #2 on release-1.2.\nCherry pick of #4 on release-1.2.\n```release-note\nSome changes were made but there still is action required.\n```",
   435  			parentPRs:          map[int]string{2: labels.ReleaseNote, 4: labels.ReleaseNoteActionRequired},
   436  			IssueLabelsAdded:   []string{labels.ReleaseNoteActionRequired},
   437  			IssueLabelsRemoved: []string{labels.ReleaseNoteLabelNeeded},
   438  		},
   439  		{
   440  			name:               "add releaseNoteNeeded, remove release-note on non-master when release-note block is removed and parent PR has releaseNoteNone label",
   441  			branch:             "release-1.2",
   442  			initialLabels:      []string{lgtmLabel, labels.ReleaseNote},
   443  			body:               "Cherry pick of #2 on release-1.2.\n```release-note\n```\n/cc @cjwagner",
   444  			parentPRs:          map[int]string{2: labels.ReleaseNoteNone},
   445  			IssueLabelsAdded:   []string{labels.ReleaseNoteLabelNeeded},
   446  			IssueLabelsRemoved: []string{labels.ReleaseNote},
   447  		},
   448  		{
   449  			name:               "add ReleaseNoteLabelNeeded, remove release-note on non-master when release-note block is removed and parent PR has releaseNoteNone label",
   450  			branch:             "release-1.2",
   451  			initialLabels:      []string{lgtmLabel, labels.ReleaseNote},
   452  			body:               "Cherry pick of #2 on release-1.2.\n```release-note\n```\n/cc @cjwagner",
   453  			parentPRs:          map[int]string{2: labels.ReleaseNoteNone},
   454  			IssueLabelsAdded:   []string{labels.ReleaseNoteLabelNeeded},
   455  			IssueLabelsRemoved: []string{labels.ReleaseNote},
   456  		},
   457  		{
   458  			name:               "add ReleaseNoteLabelNeeded, remove ReleaseNoteNone when kind/deprecation label is added",
   459  			initialLabels:      []string{labels.DeprecationLabel, labels.ReleaseNoteNone},
   460  			body:               "```release-note\nnone\n```",
   461  			IssueLabelsAdded:   []string{labels.ReleaseNoteLabelNeeded},
   462  			IssueLabelsRemoved: []string{labels.ReleaseNoteNone},
   463  		},
   464  		{
   465  			name:             "release-note-none command cannot override deprecation label",
   466  			issueComments:    []string{"/release-note-none "},
   467  			initialLabels:    []string{labels.DeprecationLabel},
   468  			body:             "",
   469  			IssueLabelsAdded: []string{labels.ReleaseNoteLabelNeeded},
   470  		},
   471  		{
   472  			name:             "Add do-not-merge/release-note-label-needed",
   473  			body:             "```release-note\n```",
   474  			initialLabels:    []string{},
   475  			IssueLabelsAdded: []string{labels.ReleaseNoteLabelNeeded},
   476  		},
   477  		{
   478  			name:             "Release note edited after merge, do not add do-not-merge/release-note-label-needed",
   479  			body:             "```release-note\n```",
   480  			merged:           true,
   481  			initialLabels:    []string{},
   482  			IssueLabelsAdded: []string{},
   483  		},
   484  	}
   485  	for _, test := range tests {
   486  		if test.branch == "" {
   487  			test.branch = "master"
   488  		}
   489  		fc, pr := newFakeClient(test.body, test.branch, test.initialLabels, test.issueComments, test.parentPRs)
   490  		pr.PullRequest.Merged = test.merged
   491  
   492  		err := handlePR(fc, logrus.WithField("plugin", PluginName), pr)
   493  		if err != nil {
   494  			t.Fatalf("Unexpected error from handlePR: %v", err)
   495  		}
   496  
   497  		// Check that all the correct labels (and only the correct labels) were added.
   498  		expectAdded := formatLabels(1, append(test.initialLabels, test.IssueLabelsAdded...)...)
   499  		for parent, label := range test.parentPRs {
   500  			expectAdded = append(expectAdded, formatLabels(parent, label)...)
   501  		}
   502  		sort.Strings(expectAdded)
   503  		sort.Strings(fc.IssueLabelsAdded)
   504  		if !reflect.DeepEqual(expectAdded, fc.IssueLabelsAdded) {
   505  			t.Errorf("(%s): Expected labels to be added: %q, but got: %q.", test.name, expectAdded, fc.IssueLabelsAdded)
   506  		}
   507  		expectRemoved := formatLabels(1, test.IssueLabelsRemoved...)
   508  		sort.Strings(expectRemoved)
   509  		sort.Strings(fc.IssueLabelsRemoved)
   510  		if !reflect.DeepEqual(expectRemoved, fc.IssueLabelsRemoved) {
   511  			t.Errorf("(%s): Expected labels to be removed: %q, but got %q.", test.name, expectRemoved, fc.IssueLabelsRemoved)
   512  		}
   513  	}
   514  }
   515  
   516  func TestGetReleaseNote(t *testing.T) {
   517  	tests := []struct {
   518  		body                        string
   519  		labels                      sets.Set[string]
   520  		expectedReleaseNote         string
   521  		expectedReleaseNoteVariable string
   522  	}{
   523  		{
   524  			body:                        "**Release note**:  ```NONE```",
   525  			expectedReleaseNote:         "NONE",
   526  			expectedReleaseNoteVariable: labels.ReleaseNoteNone,
   527  		},
   528  		{
   529  			body:                        "**Release note**:\n\n ```\nNONE\n```",
   530  			expectedReleaseNote:         "NONE",
   531  			expectedReleaseNoteVariable: labels.ReleaseNoteNone,
   532  		},
   533  		{
   534  			body:                        "**Release note**:\n<!--  Steps to write your release note:\n...\n-->\n```NONE\n```",
   535  			expectedReleaseNote:         "NONE",
   536  			expectedReleaseNoteVariable: labels.ReleaseNoteNone,
   537  		},
   538  		{
   539  			body:                        "**Release note**:\n\n  ```This is a description of my feature```",
   540  			expectedReleaseNote:         "This is a description of my feature",
   541  			expectedReleaseNoteVariable: labels.ReleaseNote,
   542  		},
   543  		{
   544  			body:                        "**Release note**: ```This is my feature. There is some action required for my feature.```",
   545  			expectedReleaseNote:         "This is my feature. There is some action required for my feature.",
   546  			expectedReleaseNoteVariable: labels.ReleaseNoteActionRequired,
   547  		},
   548  		{
   549  			body:                        "```release-note\nsomething great.\n```",
   550  			expectedReleaseNote:         "something great.",
   551  			expectedReleaseNoteVariable: labels.ReleaseNote,
   552  		},
   553  		{
   554  			body:                        "```release-note\nNONE\n```",
   555  			expectedReleaseNote:         "NONE",
   556  			expectedReleaseNoteVariable: labels.ReleaseNoteNone,
   557  		},
   558  		{
   559  			body:                        "```release-note\n`NONE`\n```",
   560  			expectedReleaseNote:         "`NONE`",
   561  			expectedReleaseNoteVariable: labels.ReleaseNoteNone,
   562  		},
   563  		{
   564  			body:                        "```release-note\n`\"NONE\"`\n```",
   565  			expectedReleaseNote:         "`\"NONE\"`",
   566  			expectedReleaseNoteVariable: labels.ReleaseNoteNone,
   567  		},
   568  		{
   569  			body:                        "**Release note**:\n```release-note\nNONE\n```\n",
   570  			expectedReleaseNote:         "NONE",
   571  			expectedReleaseNoteVariable: labels.ReleaseNoteNone,
   572  		},
   573  		{
   574  			body:                        "",
   575  			expectedReleaseNote:         "",
   576  			expectedReleaseNoteVariable: labels.ReleaseNoteLabelNeeded,
   577  		},
   578  		{
   579  			body:                        "",
   580  			labels:                      sets.New[string](labels.ReleaseNoteNone),
   581  			expectedReleaseNote:         "",
   582  			expectedReleaseNoteVariable: labels.ReleaseNoteNone,
   583  		},
   584  		{
   585  			body:                        "",
   586  			labels:                      sets.New[string](labels.DeprecationLabel),
   587  			expectedReleaseNote:         "",
   588  			expectedReleaseNoteVariable: labels.ReleaseNoteLabelNeeded,
   589  		},
   590  		{
   591  			body:                        "",
   592  			labels:                      sets.New[string](labels.ReleaseNoteNone, labels.DeprecationLabel),
   593  			expectedReleaseNote:         "",
   594  			expectedReleaseNoteVariable: labels.ReleaseNoteLabelNeeded,
   595  		},
   596  		{
   597  			body:                        "```release-note\nNONE\n```",
   598  			labels:                      sets.New[string](labels.DeprecationLabel),
   599  			expectedReleaseNote:         "NONE",
   600  			expectedReleaseNoteVariable: labels.ReleaseNoteLabelNeeded,
   601  		},
   602  	}
   603  
   604  	for testNum, test := range tests {
   605  		calculatedReleaseNote := getReleaseNote(test.body)
   606  		if test.expectedReleaseNote != calculatedReleaseNote {
   607  			t.Errorf("Test %v: Expected %v as the release note, got %v", testNum, test.expectedReleaseNote, calculatedReleaseNote)
   608  		}
   609  		calculatedLabel := determineReleaseNoteLabel(test.body, test.labels)
   610  		if test.expectedReleaseNoteVariable != calculatedLabel {
   611  			t.Errorf("Test %v: Expected %v as the release note label, got %v", testNum, test.expectedReleaseNoteVariable, calculatedLabel)
   612  		}
   613  	}
   614  }
   615  
   616  func TestShouldHandlePR(t *testing.T) {
   617  	tests := []struct {
   618  		name           string
   619  		action         github.PullRequestEventAction
   620  		label          string
   621  		expectedResult bool
   622  	}{
   623  		{
   624  			name:           "Pull Request Action: Opened",
   625  			action:         github.PullRequestActionOpened,
   626  			label:          "",
   627  			expectedResult: true,
   628  		},
   629  		{
   630  			name:           "Pull Request Action: Edited",
   631  			action:         github.PullRequestActionEdited,
   632  			label:          "",
   633  			expectedResult: true,
   634  		},
   635  		{
   636  			name:           "Pull Request Action: Release Note label",
   637  			action:         github.PullRequestActionLabeled,
   638  			label:          labels.ReleaseNoteLabelNeeded,
   639  			expectedResult: true,
   640  		},
   641  		{
   642  			name:           "Pull Request Action: Non Release Note label",
   643  			action:         github.PullRequestActionLabeled,
   644  			label:          "do-not-merge/cherry-pick-not-approved",
   645  			expectedResult: false,
   646  		},
   647  	}
   648  
   649  	for _, test := range tests {
   650  		pr := github.PullRequestEvent{
   651  			Action: test.action,
   652  			Label: github.Label{
   653  				Name: test.label,
   654  			},
   655  		}
   656  		result := shouldHandlePR(&pr)
   657  
   658  		if test.expectedResult != result {
   659  			t.Errorf("(%s): Expected value to be: %t, but got %t.", test.name, test.expectedResult, result)
   660  		}
   661  	}
   662  }
   663  
   664  func Test_editReleaseNote(t *testing.T) {
   665  	issueNum := 5
   666  	ts := []struct {
   667  		name         string
   668  		event        github.IssueCommentEvent
   669  		expectError  bool
   670  		errorMessage string
   671  		comment      string
   672  		fcFunc       func(client *fakegithub.FakeClient)
   673  		expectedNote string
   674  	}{
   675  		{
   676  			name: "is not an org member",
   677  			event: github.IssueCommentEvent{
   678  				Action: github.IssueCommentActionCreated,
   679  				Issue:  github.Issue{Number: issueNum, User: github.User{Login: "user"}},
   680  				Comment: github.IssueComment{
   681  					Body: "/release-note-edit\r\n```release-note\r\nThe new note\r\n```\r\n",
   682  					User: github.User{Login: "user"},
   683  				},
   684  				Repo: github.Repo{Owner: github.User{Login: "org"}},
   685  			},
   686  			comment: "org member",
   687  			fcFunc: func(fc *fakegithub.FakeClient) {
   688  				fc.OrgMembers["org"] = []string{}
   689  			},
   690  		},
   691  		{
   692  			name: "no release note block",
   693  			event: github.IssueCommentEvent{
   694  				Action: github.IssueCommentActionCreated,
   695  				Issue:  github.Issue{Number: issueNum, User: github.User{Login: "user"}},
   696  				Comment: github.IssueComment{
   697  					Body: "/release-note-edit\r\nNew note",
   698  					User: github.User{Login: "user"},
   699  				},
   700  				Repo: github.Repo{Owner: github.User{Login: "org"}},
   701  			},
   702  			comment: "release note block",
   703  			fcFunc: func(fc *fakegithub.FakeClient) {
   704  				fc.OrgMembers["org"] = []string{"user"}
   705  			},
   706  		},
   707  		{
   708  			name: "multiple release note blocks",
   709  			event: github.IssueCommentEvent{
   710  				Action: github.IssueCommentActionCreated,
   711  				Issue:  github.Issue{Number: issueNum, User: github.User{Login: "user"}},
   712  				Comment: github.IssueComment{
   713  					Body: "/release-note-edit\r\n```release-note\r\nThe new note\r\n```\r\n```release-note\r\nThe second note\r\n```\r\n",
   714  					User: github.User{Login: "user"},
   715  				},
   716  				Repo: github.Repo{Owner: github.User{Login: "org"}},
   717  			},
   718  			comment: "single release note block",
   719  			fcFunc: func(fc *fakegithub.FakeClient) {
   720  				fc.OrgMembers["org"] = []string{"user"}
   721  			},
   722  		},
   723  		{
   724  			name: "happy path",
   725  			event: github.IssueCommentEvent{
   726  				Action: github.IssueCommentActionCreated,
   727  				Issue:  github.Issue{Number: issueNum, User: github.User{Login: "user"}, Body: "Top\r\n```release-note\r\nNONE\r\n```\r\nBelow\r\n"},
   728  				Comment: github.IssueComment{
   729  					Body: "/release-note-edit\r\n```release-note\r\nThe new note\r\n```\r\n",
   730  					User: github.User{Login: "user"},
   731  				},
   732  				Repo: github.Repo{Owner: github.User{Login: "org"}},
   733  			},
   734  			fcFunc: func(fc *fakegithub.FakeClient) {
   735  				fc.OrgMembers["org"] = []string{"user"}
   736  				fc.Issues[issueNum] = &github.Issue{
   737  					Number: issueNum,
   738  					User:   github.User{Login: "user"},
   739  					Body:   "Top level\r\n```release-note\r\nNONE\r\n```\r\n",
   740  				}
   741  			},
   742  			expectedNote: "Top\r\n```release-note\r\nThe new note\r\n```\r\nBelow\r\n",
   743  		},
   744  	}
   745  	for _, tc := range ts {
   746  		t.Run(tc.name, func(t *testing.T) {
   747  			fc := fakegithub.NewFakeClient()
   748  			if tc.fcFunc != nil {
   749  				tc.fcFunc(fc)
   750  			}
   751  			err := editReleaseNote(fc, logrus.WithField("plugin", PluginName), tc.event)
   752  			if err != nil {
   753  				if !tc.expectError {
   754  					t.Fatalf("unexpected error: %v", err)
   755  				}
   756  				if m := err.Error(); !strings.Contains(m, tc.errorMessage) {
   757  					t.Fatalf("expected error to contain: %s got: %v", tc.errorMessage, m)
   758  				}
   759  			}
   760  			if err == nil && tc.expectError {
   761  				t.Fatalf("expected error but did not produce")
   762  			}
   763  			if len(tc.comment) != 0 {
   764  				if cm, ok := fc.IssueComments[tc.event.Issue.Number]; ok {
   765  					if !strings.Contains(cm[0].Body, tc.comment) {
   766  						t.Fatalf("expected comment to contain: %s got: %s", tc.comment, cm[0].Body)
   767  					}
   768  				}
   769  			}
   770  			if len(tc.comment) == 0 && len(fc.IssueComments[issueNum]) != 0 {
   771  				t.Fatalf("unexpected comment: %v", fc.IssueComments[issueNum])
   772  			}
   773  			_, ok := fc.Issues[issueNum]
   774  			if ok && tc.expectedNote == "" {
   775  				t.Fatalf("unexpected issue exists: %v", fc.Issues[issueNum])
   776  			}
   777  			if tc.expectedNote != "" {
   778  				if !ok {
   779  					t.Fatalf("expected release note to be edited but issue does not exist")
   780  				}
   781  				if i := fc.Issues[issueNum]; i.Body != tc.expectedNote {
   782  					t.Fatalf("expected release note to be edited to: %v \n got: %v", tc.expectedNote, i.Body)
   783  				}
   784  			}
   785  		})
   786  	}
   787  }