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