sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/bugzilla/fake.go (about)

     1  /*
     2  Copyright 2019 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 bugzilla
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"net/http"
    23  
    24  	"github.com/sirupsen/logrus"
    25  	"k8s.io/apimachinery/pkg/util/sets"
    26  )
    27  
    28  // Fake is a fake Bugzilla client with injectable fields
    29  type Fake struct {
    30  	EndpointString   string
    31  	Bugs             map[int]Bug
    32  	BugComments      map[int][]Comment
    33  	BugErrors        sets.Set[int]
    34  	BugErrorMessages map[int]string
    35  	BugCreateErrors  sets.Set[string]
    36  	ExternalBugs     map[int][]ExternalBug
    37  	SubComponents    map[int]map[string][]string
    38  	SearchedBugs     []*Bug
    39  }
    40  
    41  // Endpoint returns the endpoint for this fake
    42  func (c *Fake) Endpoint() string {
    43  	return c.EndpointString
    44  }
    45  
    46  func (c *Fake) bugErrorMsg(bug int, def string) error {
    47  	if c.BugErrorMessages[bug] != "" {
    48  		return errors.New(c.BugErrorMessages[bug])
    49  	}
    50  	return errors.New(def)
    51  }
    52  
    53  // GetBug retrieves the bug, if registered, or an error, if set,
    54  // or responds with an error that matches IsNotFound
    55  func (c *Fake) GetBug(id int) (*Bug, error) {
    56  	if c.BugErrors.Has(id) {
    57  		return nil, c.bugErrorMsg(id, "injected error getting bug")
    58  	}
    59  	if bug, exists := c.Bugs[id]; exists {
    60  		return &bug, nil
    61  	}
    62  	return nil, &requestError{statusCode: http.StatusNotFound, message: "bug not registered in the fake"}
    63  }
    64  
    65  // GetBug retrieves the external bugs for the Bugzilla bug,
    66  // if registered, or an error, if set, or responds with an
    67  // error that matches IsNotFound
    68  func (c *Fake) GetExternalBugPRsOnBug(id int) ([]ExternalBug, error) {
    69  	if c.BugErrors.Has(id) {
    70  		return nil, c.bugErrorMsg(id, "injected error adding external bug to bug")
    71  	}
    72  	if _, exists := c.Bugs[id]; exists {
    73  		return c.ExternalBugs[id], nil
    74  	}
    75  	return nil, &requestError{statusCode: http.StatusNotFound, message: "bug not registered in the fake"}
    76  }
    77  
    78  // UpdateBug updates the bug, if registered, or an error, if set,
    79  // or responds with an error that matches IsNotFound
    80  func (c *Fake) UpdateBug(id int, update BugUpdate) error {
    81  	if c.BugErrors.Has(id) {
    82  		return c.bugErrorMsg(id, "injected error updating bug")
    83  	}
    84  	bug, exists := c.Bugs[id]
    85  	if !exists {
    86  		return &requestError{statusCode: http.StatusNotFound, message: "bug not registered in the fake"}
    87  	}
    88  	bug.Status = update.Status
    89  	bug.Resolution = update.Resolution
    90  	if update.Version != "" {
    91  		bug.Version = []string{update.Version}
    92  	}
    93  	if update.TargetRelease != nil {
    94  		bug.TargetRelease = update.TargetRelease
    95  	}
    96  	if update.DependsOn != nil {
    97  		if len(update.DependsOn.Set) > 0 {
    98  			bug.DependsOn = update.DependsOn.Set
    99  		} else {
   100  			bug.DependsOn = sets.List(sets.New[int](bug.DependsOn...).Insert(update.DependsOn.Add...).Delete(update.DependsOn.Remove...))
   101  		}
   102  		for _, blockerID := range bug.DependsOn {
   103  			blockerBug := c.Bugs[blockerID]
   104  			blockerBug.Blocks = append(blockerBug.Blocks, id)
   105  			c.Bugs[blockerID] = blockerBug
   106  		}
   107  	}
   108  	if update.Blocks != nil {
   109  		if len(update.Blocks.Set) > 0 {
   110  			bug.Blocks = update.Blocks.Set
   111  		} else {
   112  			bug.Blocks = sets.List(sets.New[int](bug.Blocks...).Insert(update.Blocks.Add...).Delete(update.Blocks.Remove...))
   113  		}
   114  		for _, blockerID := range bug.Blocks {
   115  			blockerBug := c.Bugs[blockerID]
   116  			blockerBug.DependsOn = append(blockerBug.DependsOn, id)
   117  			c.Bugs[blockerID] = blockerBug
   118  		}
   119  	}
   120  	c.Bugs[id] = bug
   121  	return nil
   122  }
   123  
   124  // AddPullRequestAsExternalBug adds an external bug to the Bugzilla bug,
   125  // if registered, or an error, if set, or responds with an error that
   126  // matches IsNotFound
   127  func (c *Fake) AddPullRequestAsExternalBug(id int, org, repo string, num int) (bool, error) {
   128  	if c.BugErrors.Has(id) {
   129  		return false, c.bugErrorMsg(id, "injected error adding external bug to bug")
   130  	}
   131  	if _, exists := c.Bugs[id]; exists {
   132  		pullIdentifier := IdentifierForPull(org, repo, num)
   133  		for _, bug := range c.ExternalBugs[id] {
   134  			if bug.BugzillaBugID == id && bug.ExternalBugID == pullIdentifier {
   135  				return false, nil
   136  			}
   137  		}
   138  		c.ExternalBugs[id] = append(c.ExternalBugs[id], ExternalBug{
   139  			BugzillaBugID: id,
   140  			ExternalBugID: pullIdentifier,
   141  		})
   142  		return true, nil
   143  	}
   144  	return false, &requestError{statusCode: http.StatusNotFound, message: "bug not registered in the fake"}
   145  }
   146  
   147  // RemovePullRequestAsExternalBug removes an external bug from the Bugzilla bug,
   148  // if registered, or an error, if set, or responds with an error that
   149  // matches IsNotFound
   150  func (c *Fake) RemovePullRequestAsExternalBug(id int, org, repo string, num int) (bool, error) {
   151  	if c.BugErrors.Has(id) {
   152  		return false, c.bugErrorMsg(id, "injected error removing external bug from bug")
   153  	}
   154  	if _, exists := c.Bugs[id]; exists {
   155  		pullIdentifier := IdentifierForPull(org, repo, num)
   156  		toRemove := -1
   157  		for i, bug := range c.ExternalBugs[id] {
   158  			if bug.BugzillaBugID == id && bug.ExternalBugID == pullIdentifier {
   159  				toRemove = i
   160  				break
   161  			}
   162  		}
   163  		if toRemove != -1 {
   164  			c.ExternalBugs[id] = append(c.ExternalBugs[id][:toRemove], c.ExternalBugs[id][toRemove+1:]...)
   165  			return true, nil
   166  		}
   167  		return false, nil
   168  	}
   169  	return false, &requestError{statusCode: http.StatusNotFound, message: "bug not registered in the fake"}
   170  }
   171  
   172  // CreateBug creates a new bug and associated description comment given a BugCreate or and error
   173  // if description is in BugCreateErrors set
   174  func (c *Fake) CreateBug(bug *BugCreate) (int, error) {
   175  	if c.BugCreateErrors.Has(bug.Description) {
   176  		return 0, errors.New("injected error creating new bug")
   177  	}
   178  	// add new bug one ID newer than highest existing BugID
   179  	newID := 0
   180  	for k := range c.Bugs {
   181  		if k >= newID {
   182  			newID = k + 1
   183  		}
   184  	}
   185  	newBug := Bug{
   186  		Alias:           bug.Alias,
   187  		AssignedTo:      bug.AssignedTo,
   188  		CC:              bug.CC,
   189  		Component:       bug.Component,
   190  		Flags:           bug.Flags,
   191  		Groups:          bug.Groups,
   192  		ID:              newID,
   193  		Keywords:        bug.Keywords,
   194  		OperatingSystem: bug.OperatingSystem,
   195  		Platform:        bug.Platform,
   196  		Priority:        bug.Priority,
   197  		Product:         bug.Product,
   198  		QAContact:       bug.QAContact,
   199  		Resolution:      bug.Resolution,
   200  		Severity:        bug.Severity,
   201  		Status:          bug.Status,
   202  		Summary:         bug.Summary,
   203  		TargetMilestone: bug.TargetMilestone,
   204  		Version:         bug.Version,
   205  		TargetRelease:   bug.TargetRelease,
   206  	}
   207  	c.Bugs[newID] = newBug
   208  	// add new comment one ID newer than highest existing CommentID
   209  	newCommentID := 0
   210  	for _, comments := range c.BugComments {
   211  		for _, comment := range comments {
   212  			if comment.ID >= newCommentID {
   213  				newCommentID = comment.ID + 1
   214  			}
   215  		}
   216  	}
   217  	newComments := []Comment{{
   218  		ID:         newCommentID,
   219  		BugID:      newID,
   220  		Count:      0,
   221  		Text:       bug.Description,
   222  		IsMarkdown: bug.IsMarkdown,
   223  		IsPrivate:  bug.CommentIsPrivate,
   224  		Tags:       bug.CommentTags,
   225  	}}
   226  	c.BugComments[newID] = newComments
   227  	if bug.SubComponents != nil {
   228  		c.SubComponents[newID] = bug.SubComponents
   229  	}
   230  	return newID, nil
   231  }
   232  
   233  func (c *Fake) CreateComment(comment *CommentCreate) (int, error) {
   234  	// add new comment one ID newer than highest existing CommentID
   235  	newCommentID := 0
   236  	for _, comments := range c.BugComments {
   237  		for _, comment := range comments {
   238  			if comment.ID >= newCommentID {
   239  				newCommentID = comment.ID + 1
   240  			}
   241  		}
   242  	}
   243  	newComment := Comment{
   244  		ID:        newCommentID,
   245  		BugID:     comment.ID,
   246  		Count:     len(c.BugComments[comment.ID]),
   247  		Text:      comment.Comment,
   248  		IsPrivate: comment.IsPrivate,
   249  	}
   250  	c.BugComments[comment.ID] = append(c.BugComments[comment.ID], newComment)
   251  	return newCommentID, nil
   252  }
   253  
   254  // GetComments retrieves the bug comments, if registered, or an error, if set,
   255  // or responds with an error that matches IsNotFound
   256  func (c *Fake) GetComments(id int) ([]Comment, error) {
   257  	if c.BugErrors.Has(id) {
   258  		return nil, c.bugErrorMsg(id, "injected error getting bug comments")
   259  	}
   260  	if comments, exists := c.BugComments[id]; exists {
   261  		return comments, nil
   262  	}
   263  	return nil, &requestError{statusCode: http.StatusNotFound, message: fmt.Sprintf("bug comments for id %d not registered in the fake", id)}
   264  }
   265  
   266  // CloneBug clones a bug by creating a new bug with the same fields, copying the description, and updating the bug to depend on the original bug
   267  func (c *Fake) CloneBug(bug *Bug, mutations ...func(bug *BugCreate)) (int, error) {
   268  	return clone(c, bug, mutations)
   269  }
   270  
   271  func (c *Fake) GetSubComponentsOnBug(id int) (map[string][]string, error) {
   272  	if c.BugErrors.Has(id) {
   273  		return nil, errors.New("injected error getting bug subcomponents")
   274  	}
   275  	return c.SubComponents[id], nil
   276  }
   277  
   278  func (c *Fake) GetClones(bug *Bug) ([]*Bug, error) {
   279  	if c.BugErrors.Has(bug.ID) {
   280  		return nil, errors.New("injected error getting subcomponents")
   281  	}
   282  	return getClones(c, bug)
   283  }
   284  
   285  // GetAllClones gets all clones including its parents and children spanning multiple levels
   286  func (c *Fake) GetAllClones(bug *Bug) ([]*Bug, error) {
   287  	if c.BugErrors.Has(bug.ID) {
   288  		return nil, errors.New("injected error getting subcomponents")
   289  	}
   290  	bugCache := newBugDetailsCache()
   291  	return getAllClones(c, bug, bugCache)
   292  }
   293  
   294  // GetRootForClone gets the original bug.
   295  func (c *Fake) GetRootForClone(bug *Bug) (*Bug, error) {
   296  	if c.BugErrors.Has(bug.ID) {
   297  		return nil, errors.New("injected error getting bug")
   298  	}
   299  	return getRootForClone(c, bug)
   300  }
   301  
   302  func (c *Fake) SearchBugs(filters map[string]string) ([]*Bug, error) {
   303  	return c.SearchedBugs, nil
   304  }
   305  
   306  // SetRoundTripper sets the Transport in http.Client to a custom RoundTripper
   307  func (c *Fake) SetRoundTripper(t http.RoundTripper) {
   308  	// Do nothing here
   309  }
   310  
   311  func (c *Fake) ForPlugin(plugin string) Client             { return c }
   312  func (c *Fake) ForSubcomponent(subcomponent string) Client { return c }
   313  func (c *Fake) WithFields(fields logrus.Fields) Client     { return c }
   314  func (c *Fake) Used() bool                                 { return true }
   315  
   316  // the Fake is a Client
   317  var _ Client = &Fake{}