github.com/justinjmoses/evergreen@v0.0.0-20170530173719-1d50e381ff0d/alerts/jira_test.go (about)

     1  package alerts
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/evergreen-ci/evergreen"
     7  	"github.com/evergreen-ci/evergreen/apimodels"
     8  	"github.com/evergreen-ci/evergreen/model"
     9  	"github.com/evergreen-ci/evergreen/model/alert"
    10  	"github.com/evergreen-ci/evergreen/model/alertrecord"
    11  	"github.com/evergreen-ci/evergreen/model/build"
    12  	"github.com/evergreen-ci/evergreen/model/host"
    13  	"github.com/evergreen-ci/evergreen/model/task"
    14  	"github.com/evergreen-ci/evergreen/model/version"
    15  	"github.com/evergreen-ci/evergreen/thirdparty"
    16  	"github.com/pkg/errors"
    17  	. "github.com/smartystreets/goconvey/convey"
    18  )
    19  
    20  func TestJIRASummary(t *testing.T) {
    21  	Convey("With failed task alert types:", t, func() {
    22  		ctx := AlertContext{
    23  			AlertRequest: &alert.AlertRequest{
    24  				Trigger: alertrecord.TaskFailedId,
    25  			},
    26  			ProjectRef: &model.ProjectRef{
    27  				DisplayName: ProjectName,
    28  				Owner:       ProjectOwner,
    29  			},
    30  			Task: &task.Task{
    31  				DisplayName: TaskName,
    32  				Details:     apimodels.TaskEndDetail{},
    33  			},
    34  			Build:   &build.Build{DisplayName: BuildName},
    35  			Version: &version.Version{Revision: VersionRevision},
    36  		}
    37  
    38  		Convey("a task that timed out should return a subject", func() {
    39  			ctx.Task.Details.TimedOut = true
    40  			subj := getSummary(ctx)
    41  			So(subj, ShouldNotEqual, "")
    42  			Convey("denoting the time out and showing the task name", func() {
    43  				So(subj, ShouldContainSubstring, "Timed Out")
    44  				So(subj, ShouldContainSubstring, TaskName)
    45  				So(subj, ShouldContainSubstring, BuildName)
    46  				So(subj, ShouldContainSubstring, VersionRevision[0:8])
    47  				So(subj, ShouldNotContainSubstring, VersionRevision[0:9])
    48  				So(subj, ShouldContainSubstring, ProjectName)
    49  			})
    50  		})
    51  		Convey("a task that failed on a system command should return a subject", func() {
    52  			ctx.Task.Details.Type = model.SystemCommandType
    53  			subj := getSummary(ctx)
    54  			So(subj, ShouldNotEqual, "")
    55  			Convey("denoting the system failure and showing the task name", func() {
    56  				So(subj, ShouldContainSubstring, "System")
    57  				So(subj, ShouldContainSubstring, TaskName)
    58  				So(subj, ShouldContainSubstring, BuildName)
    59  				So(subj, ShouldContainSubstring, VersionRevision[0:8])
    60  				So(subj, ShouldContainSubstring, ProjectName)
    61  			})
    62  		})
    63  		Convey("a task that has a hearbeat failure should return a subject", func() {
    64  			ctx.Task.Details.Description = task.AgentHeartbeat
    65  			subj := getSummary(ctx)
    66  			So(subj, ShouldNotEqual, "")
    67  			Convey("denoting the system failure and showing the task name", func() {
    68  				So(subj, ShouldContainSubstring, "System Failure")
    69  				So(subj, ShouldContainSubstring, TaskName)
    70  				So(subj, ShouldContainSubstring, BuildName)
    71  				So(subj, ShouldContainSubstring, VersionRevision[0:8])
    72  				So(subj, ShouldNotContainSubstring, VersionRevision[0:9])
    73  				So(subj, ShouldContainSubstring, ProjectName)
    74  			})
    75  		})
    76  		Convey("a task that failed on a normal command with no tests should return a subject", func() {
    77  			subj := getSummary(ctx)
    78  			So(subj, ShouldNotEqual, "")
    79  			Convey("denoting the failure and showing the task name", func() {
    80  				So(subj, ShouldContainSubstring, "Failed")
    81  				So(subj, ShouldContainSubstring, TaskName)
    82  				So(subj, ShouldContainSubstring, BuildName)
    83  				So(subj, ShouldContainSubstring, VersionRevision[0:8])
    84  				So(subj, ShouldContainSubstring, ProjectName)
    85  			})
    86  		})
    87  		Convey("a task with two failed tests should return a subject", func() {
    88  			ctx.Task.TestResults = []task.TestResult{
    89  				{TestFile: TestName1, Status: evergreen.TestFailedStatus},
    90  				{TestFile: TestName2, Status: evergreen.TestFailedStatus},
    91  				{TestFile: TestName3, Status: evergreen.TestSucceededStatus},
    92  				{TestFile: TestName3, Status: evergreen.TestSucceededStatus},
    93  				{TestFile: TestName3, Status: evergreen.TestSucceededStatus},
    94  				{TestFile: TestName3, Status: evergreen.TestSucceededStatus},
    95  			}
    96  			subj := getSummary(ctx)
    97  			So(subj, ShouldNotEqual, "")
    98  			Convey("denoting the failure and showing the task name and failed tests", func() {
    99  				So(subj, ShouldContainSubstring, "Failures")
   100  				So(subj, ShouldContainSubstring, TaskName)
   101  				So(subj, ShouldContainSubstring, BuildName)
   102  				So(subj, ShouldContainSubstring, VersionRevision[0:8])
   103  				So(subj, ShouldContainSubstring, ProjectName)
   104  				So(subj, ShouldContainSubstring, "big_test.js")
   105  				So(subj, ShouldContainSubstring, "FunUnitTest")
   106  				So(subj, ShouldNotContainSubstring, "cool.exe")
   107  				Convey("with test names properly truncated", func() {
   108  					So(subj, ShouldNotContainSubstring, "local")
   109  					So(subj, ShouldNotContainSubstring, "jstest")
   110  				})
   111  			})
   112  		})
   113  		Convey("a task with failing tests should return a subject omitting any silently failing tests", func() {
   114  			ctx.Task.TestResults = []task.TestResult{
   115  				{TestFile: TestName1, Status: evergreen.TestFailedStatus},
   116  				{TestFile: TestName2, Status: evergreen.TestFailedStatus},
   117  				{TestFile: TestName3, Status: evergreen.TestSilentlyFailedStatus},
   118  			}
   119  			subj := getSummary(ctx)
   120  			So(subj, ShouldNotEqual, "")
   121  			Convey("denoting the failure and showing the task name and failed tests", func() {
   122  				So(subj, ShouldContainSubstring, "Failures")
   123  				So(subj, ShouldContainSubstring, TaskName)
   124  				So(subj, ShouldContainSubstring, BuildName)
   125  				So(subj, ShouldContainSubstring, VersionRevision[0:8])
   126  				So(subj, ShouldContainSubstring, ProjectName)
   127  				So(subj, ShouldContainSubstring, "big_test.js")
   128  				So(subj, ShouldContainSubstring, "FunUnitTest")
   129  				So(subj, ShouldNotContainSubstring, "cool.exe")
   130  				Convey("with test names properly truncated", func() {
   131  					So(subj, ShouldNotContainSubstring, "local")
   132  					So(subj, ShouldNotContainSubstring, "jstest")
   133  				})
   134  			})
   135  		})
   136  		Convey("a task with five failed tests should return a subject", func() {
   137  			ctx.Task.TestResults = []task.TestResult{
   138  				{TestFile: TestName1, Status: evergreen.TestFailedStatus},
   139  				{TestFile: TestName2, Status: evergreen.TestFailedStatus},
   140  				{TestFile: TestName3, Status: evergreen.TestFailedStatus},
   141  				{TestFile: TestName3, Status: evergreen.TestFailedStatus},
   142  				{TestFile: TestName3, Status: evergreen.TestFailedStatus},
   143  			}
   144  			subj := getSummary(ctx)
   145  			So(subj, ShouldNotEqual, "")
   146  			Convey("denoting two test failures but hiding the rest", func() {
   147  				So(subj, ShouldContainSubstring, "Failures")
   148  				So(subj, ShouldContainSubstring, TaskName)
   149  				So(subj, ShouldContainSubstring, BuildName)
   150  				So(subj, ShouldContainSubstring, VersionRevision[0:8])
   151  				So(subj, ShouldContainSubstring, ProjectName)
   152  				So(subj, ShouldContainSubstring, "big_test.js")
   153  				So(subj, ShouldContainSubstring, "FunUnitTest")
   154  				So(subj, ShouldNotContainSubstring, "cool.exe")
   155  				So(subj, ShouldContainSubstring, "+3 more")
   156  			})
   157  		})
   158  		Convey("a failed task with passing tests should return a subject", func() {
   159  			ctx.Task.TestResults = []task.TestResult{
   160  				{TestFile: TestName1, Status: evergreen.TestSucceededStatus},
   161  				{TestFile: TestName2, Status: evergreen.TestSucceededStatus},
   162  				{TestFile: TestName3, Status: evergreen.TestSucceededStatus},
   163  			}
   164  			subj := getSummary(ctx)
   165  			So(subj, ShouldNotEqual, "")
   166  			Convey("denoting a task failure without a parenthetical", func() {
   167  				So(subj, ShouldContainSubstring, "Failed")
   168  				So(subj, ShouldContainSubstring, TaskName)
   169  				So(subj, ShouldContainSubstring, BuildName)
   170  				So(subj, ShouldContainSubstring, VersionRevision[0:8])
   171  				So(subj, ShouldContainSubstring, ProjectName)
   172  				So(subj, ShouldNotContainSubstring, "big_test.js")
   173  				So(subj, ShouldNotContainSubstring, "FunUnitTest")
   174  				So(subj, ShouldNotContainSubstring, "cool.exe")
   175  				So(subj, ShouldNotContainSubstring, "(")
   176  				So(subj, ShouldNotContainSubstring, ")")
   177  			})
   178  		})
   179  		Convey("a failed task with only passing or silently failing tests should return a subject", func() {
   180  			ctx.Task.TestResults = []task.TestResult{
   181  				{TestFile: TestName1, Status: evergreen.TestSilentlyFailedStatus},
   182  				{TestFile: TestName2, Status: evergreen.TestSucceededStatus},
   183  				{TestFile: TestName3, Status: evergreen.TestSilentlyFailedStatus},
   184  			}
   185  			subj := getSummary(ctx)
   186  			So(subj, ShouldNotEqual, "")
   187  			Convey("denoting a task failure without a parenthetical", func() {
   188  				So(subj, ShouldContainSubstring, "Failed")
   189  				So(subj, ShouldContainSubstring, TaskName)
   190  				So(subj, ShouldContainSubstring, BuildName)
   191  				So(subj, ShouldContainSubstring, VersionRevision[0:8])
   192  				So(subj, ShouldContainSubstring, ProjectName)
   193  				So(subj, ShouldNotContainSubstring, "big_test.js")
   194  				So(subj, ShouldNotContainSubstring, "FunUnitTest")
   195  				So(subj, ShouldNotContainSubstring, "cool.exe")
   196  				So(subj, ShouldNotContainSubstring, "(")
   197  				So(subj, ShouldNotContainSubstring, ")")
   198  			})
   199  		})
   200  	})
   201  }
   202  
   203  const (
   204  	HostId  = "h1"
   205  	HostDNS = "h1.net"
   206  )
   207  
   208  func TestJIRADescription(t *testing.T) {
   209  	Convey("With a failed task context", t, func() {
   210  		ui := "http://evergreen.ui"
   211  		ctx := AlertContext{
   212  			AlertRequest: &alert.AlertRequest{
   213  				Trigger: alertrecord.TaskFailedId,
   214  			},
   215  			ProjectRef: &model.ProjectRef{
   216  				DisplayName: ProjectName,
   217  				Identifier:  ProjectId,
   218  				Owner:       ProjectOwner,
   219  			},
   220  			Task: &task.Task{
   221  				Id:          TaskId,
   222  				DisplayName: TaskName,
   223  				Details:     apimodels.TaskEndDetail{},
   224  				Project:     ProjectId,
   225  				TestResults: []task.TestResult{
   226  					{TestFile: TestName1, Status: evergreen.TestFailedStatus, URL: "direct_link"},
   227  					{TestFile: TestName2, Status: evergreen.TestFailedStatus, LogId: "123"},
   228  					{TestFile: TestName3, Status: evergreen.TestSucceededStatus},
   229  				},
   230  			},
   231  			Host:  &host.Host{Id: HostId, Host: HostDNS},
   232  			Build: &build.Build{DisplayName: BuildName, Id: BuildId},
   233  			Version: &version.Version{
   234  				Revision: VersionRevision,
   235  				Message:  VersionMessage,
   236  			},
   237  		}
   238  		Convey("the description should be successfully generated", func() {
   239  			d, err := getDescription(ctx, ui)
   240  			So(err, ShouldBeNil)
   241  			So(d, ShouldNotEqual, "")
   242  
   243  			Convey("the task, host, project, and build names should be present", func() {
   244  				So(d, ShouldContainSubstring, TaskName)
   245  				So(d, ShouldContainSubstring, HostDNS)
   246  				So(d, ShouldContainSubstring, ProjectName)
   247  				So(d, ShouldContainSubstring, BuildName)
   248  				So(d, ShouldContainSubstring, ProjectOwner)
   249  				So(d, ShouldContainSubstring, VersionRevision)
   250  				So(d, ShouldContainSubstring, VersionMessage)
   251  				So(d, ShouldContainSubstring, "diff|https://github.com/")
   252  			})
   253  			Convey("with links to the task, host, project", func() {
   254  				So(d, ShouldContainSubstring, TaskId)
   255  				So(d, ShouldContainSubstring, HostId)
   256  				So(d, ShouldContainSubstring, ProjectId)
   257  			})
   258  			Convey("and the failed tasks should be listed with links", func() {
   259  				So(d, ShouldContainSubstring, cleanTestName(TestName1))
   260  				So(d, ShouldContainSubstring, "direct_link")
   261  				So(d, ShouldContainSubstring, cleanTestName(TestName2))
   262  				So(d, ShouldContainSubstring, "test_log/123")
   263  				Convey("but passing tasks should not be present", func() {
   264  					So(d, ShouldNotContainSubstring, cleanTestName(TestName3))
   265  				})
   266  			})
   267  		})
   268  	})
   269  }
   270  
   271  type mockJIRAHandler struct {
   272  	err     error
   273  	proj    string
   274  	desc    string
   275  	summary string
   276  }
   277  
   278  func (mj *mockJIRAHandler) CreateTicket(fields map[string]interface{}) (
   279  	*thirdparty.JiraCreateTicketResponse, error) {
   280  	if mj.err != nil {
   281  		return nil, mj.err
   282  	}
   283  	// if these lookups panic then that's fine -- the test will fail
   284  	mj.proj = fields["project"].(map[string]string)["key"]
   285  	mj.summary = fields["summary"].(string)
   286  	mj.desc = fields["description"].(string)
   287  	return &thirdparty.JiraCreateTicketResponse{Key: mj.proj + "-1"}, nil
   288  }
   289  
   290  func (mj *mockJIRAHandler) JiraHost() string { return "mock" }
   291  
   292  func TestJIRADelivery(t *testing.T) {
   293  	Convey("With a failed task context and alertConf", t, func() {
   294  		ui := "http://evergreen.ui"
   295  		ctx := AlertContext{
   296  			AlertRequest: &alert.AlertRequest{
   297  				Trigger: alertrecord.TaskFailedId,
   298  			},
   299  			ProjectRef: &model.ProjectRef{DisplayName: ProjectName, Identifier: ProjectId},
   300  			Task: &task.Task{
   301  				Id:          TaskId,
   302  				DisplayName: TaskName,
   303  				Details:     apimodels.TaskEndDetail{},
   304  				Project:     ProjectId,
   305  			},
   306  			Host:    &host.Host{Id: HostId, Host: HostDNS},
   307  			Build:   &build.Build{DisplayName: BuildName, Id: BuildId},
   308  			Version: &version.Version{Revision: VersionRevision},
   309  		}
   310  		alertConf := model.AlertConfig{Provider: JiraProvider}
   311  		Convey("and a JIRA Deliverer with a valid mock jiraCreator", func() {
   312  			mjc := &mockJIRAHandler{}
   313  			jd := &jiraDeliverer{
   314  				project: "COOL",
   315  				handler: mjc,
   316  				uiRoot:  ui,
   317  			}
   318  			Convey("a populated ticket should be created successfully", func() {
   319  				So(jd.Deliver(ctx, alertConf), ShouldBeNil)
   320  				So(mjc.desc, ShouldNotEqual, "")
   321  				So(mjc.summary, ShouldNotEqual, "")
   322  				So(mjc.proj, ShouldEqual, "COOL")
   323  			})
   324  		})
   325  		Convey("and a JIRA Deliverer with an erroring jiraCreator", func() {
   326  			mjc := &mockJIRAHandler{err: errors.New("bad internet uh-oh")}
   327  			jd := &jiraDeliverer{
   328  				project: "COOL",
   329  				handler: mjc,
   330  				uiRoot:  ui,
   331  			}
   332  			Convey("should return an error", func() {
   333  				So(jd.Deliver(ctx, alertConf), ShouldNotBeNil)
   334  			})
   335  		})
   336  	})
   337  }
   338  
   339  func TestGetJIRADeliverer(t *testing.T) {
   340  	Convey("With a JIRA alertConf and QueueProcessor", t, func() {
   341  		alertConf := model.AlertConfig{
   342  			Provider: JiraProvider,
   343  			Settings: map[string]interface{}{"project": "TEST", "issue": "Bug"},
   344  		}
   345  		qp := &QueueProcessor{}
   346  		Convey("a QueueProcessor with full settings should return the JIRA deliverer", func() {
   347  			qp.config = &evergreen.Settings{
   348  				Jira: evergreen.JiraConfig{
   349  					Username: "u1",
   350  					Password: "pw",
   351  					Host:     "www.example.com",
   352  				},
   353  				Ui: evergreen.UIConfig{
   354  					Url: "root",
   355  				},
   356  			}
   357  			d, err := qp.getDeliverer(alertConf)
   358  			So(err, ShouldBeNil)
   359  			So(d, ShouldNotBeNil)
   360  			jd, ok := d.(*jiraDeliverer)
   361  			So(ok, ShouldBeTrue)
   362  			So(jd.handler, ShouldNotBeNil)
   363  			So(jd.project, ShouldEqual, "TEST")
   364  			So(jd.uiRoot, ShouldEqual, "root")
   365  		})
   366  
   367  		Convey("a QueueProcessor with a malformed alertConf should error", func() {
   368  			alertConf.Settings = map[string]interface{}{"NOTproject": "TEST"}
   369  			d, err := qp.getDeliverer(alertConf)
   370  			So(err, ShouldNotBeNil)
   371  			So(d, ShouldBeNil)
   372  			alertConf.Settings = map[string]interface{}{"project": 1000}
   373  			d, err = qp.getDeliverer(alertConf)
   374  			So(err, ShouldNotBeNil)
   375  			So(d, ShouldBeNil)
   376  		})
   377  
   378  		Convey("a QueueProcessor with missing JIRA settings should error", func() {
   379  			qp.config = &evergreen.Settings{
   380  				Ui: evergreen.UIConfig{
   381  					Url: "root",
   382  				},
   383  			}
   384  			d, err := qp.getDeliverer(alertConf)
   385  			So(err, ShouldNotBeNil)
   386  			So(d, ShouldBeNil)
   387  		})
   388  		Convey("a QueueProcessor with missing UI settings should error", func() {
   389  			qp.config = &evergreen.Settings{
   390  				Jira: evergreen.JiraConfig{
   391  					Username: "u1",
   392  					Password: "pw",
   393  					Host:     "www.example.com",
   394  				},
   395  			}
   396  			d, err := qp.getDeliverer(alertConf)
   397  			So(err, ShouldNotBeNil)
   398  			So(d, ShouldBeNil)
   399  		})
   400  	})
   401  }