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 }