func TestNotify(t *testing.T) { if evergreen.TestConfig().Notify.LogFile != "" { evergreen.SetLogger(evergreen.TestConfig().Notify.LogFile) } db.SetGlobalSessionProvider(db.SessionFactoryFromConfig(TestConfig)) emailSubjects = make([]string, 0) emailBodies = make([]string, 0) Convey("When running notification handlers", t, func() { ae, err := createEnvironment(TestConfig, map[string]interface{}{}) So(err, ShouldBeNil) Convey("Build-specific handlers should return the correct emails", func() { cleanupdb() timeNow := time.Now() // insert the test documents insertBuildDocs(timeNow) version := &version.Version{Id: "version"} So(version.Insert(), ShouldBeNil) Convey("BuildFailureHandler should return 1 email per failed build", func() { handler := BuildFailureHandler{} emails, err := handler.GetNotifications(ae, "config_test", &buildFailureNotificationKey) So(err, ShouldBeNil) // check that we only returned 2 failed notifications So(len(emails), ShouldEqual, 2) So(emails[0].GetSubject(), ShouldEqual, "[MCI-FAILURE ] Build #build1 failed on displayName") So(emails[1].GetSubject(), ShouldEqual, "[MCI-FAILURE ] Build #build9 failed on displayName") }) Convey("BuildSuccessHandler should return 1 email per successful build", func() { handler := BuildSuccessHandler{} emails, err := handler.GetNotifications(ae, "config_test", &buildSucceessNotificationKey) So(err, ShouldBeNil) // check that we only returned 2 success notifications So(len(emails), ShouldEqual, 2) So(emails[0].GetSubject(), ShouldEqual, "[MCI-SUCCESS ] Build #build3 succeeded on displayName") So(emails[1].GetSubject(), ShouldEqual, "[MCI-SUCCESS ] Build #build8 succeeded on displayName") }) Convey("BuildCompletionHandler should return 1 email per completed build", func() { handler := BuildCompletionHandler{} emails, err := handler.GetNotifications(ae, "config_test", &buildCompletionNotificationKey) So(err, ShouldBeNil) // check that we only returned 6 completed notifications So(len(emails), ShouldEqual, 4) So(emails[0].GetSubject(), ShouldEqual, "[MCI-COMPLETION ] Build #build1 completed on displayName") So(emails[1].GetSubject(), ShouldEqual, "[MCI-COMPLETION ] Build #build3 completed on displayName") So(emails[2].GetSubject(), ShouldEqual, "[MCI-COMPLETION ] Build #build8 completed on displayName") So(emails[3].GetSubject(), ShouldEqual, "[MCI-COMPLETION ] Build #build9 completed on displayName") }) Convey("BuildSuccessToFailureHandler should return 1 email per "+ "build success to failure transition", func() { handler := BuildSuccessToFailureHandler{} emails, err := handler.GetNotifications(ae, "config_test", &buildSuccessToFailureNotificationKey) So(err, ShouldBeNil) // check that we only returned 1 success_to_failure notifications So(len(emails), ShouldEqual, 1) So(emails[0].GetSubject(), ShouldEqual, "[MCI-FAILURE ] Build #build9 transitioned to failure on displayName") }) }) Convey("Task-specific handlers should return the correct emails", func() { cleanupdb() timeNow := time.Now() // insert the test documents insertTaskDocs(timeNow) v := &version.Version{Id: "version"} So(v.Insert(), ShouldBeNil) Convey("TaskFailureHandler should return 1 email per task failure", func() { handler := TaskFailureHandler{} emails, err := handler.GetNotifications(ae, "config_test", &taskFailureNotificationKey) So(err, ShouldBeNil) // check that we only returned 2 failed notifications So(len(emails), ShouldEqual, 2) So(emails[0].GetSubject(), ShouldEqual, "[MCI-FAILURE ] possible MCI failure in displayName (failed on build1)") So(emails[1].GetSubject(), ShouldEqual, "[MCI-FAILURE ] possible MCI failure in displayName (failed on build1)") }) Convey("TaskSuccessHandler should return 1 email per task success", func() { handler := TaskSuccessHandler{} emails, err := handler.GetNotifications(ae, "config_test", &taskSucceessNotificationKey) So(err, ShouldBeNil) // check that we only returned 2 success notifications So(len(emails), ShouldEqual, 2) So(emails[0].GetSubject(), ShouldEqual, "[MCI-SUCCESS ] possible MCI failure in displayName (succeeded on build1)") So(emails[1].GetSubject(), ShouldEqual, "[MCI-SUCCESS ] possible MCI failure in displayName (succeeded on build1)") }) Convey("TaskCompletionHandler should return 1 email per completed task", func() { handler := TaskCompletionHandler{} emails, err := handler.GetNotifications(ae, "config_test", &taskCompletionNotificationKey) So(err, ShouldBeNil) // check that we only returned 6 completion notifications So(len(emails), ShouldEqual, 4) So(emails[0].GetSubject(), ShouldEqual, "[MCI-COMPLETION ] possible MCI failure in displayName (completed on build1)") So(emails[1].GetSubject(), ShouldEqual, "[MCI-COMPLETION ] possible MCI failure in displayName (completed on build1)") So(emails[2].GetSubject(), ShouldEqual, "[MCI-COMPLETION ] possible MCI failure in displayName (completed on build1)") So(emails[3].GetSubject(), ShouldEqual, "[MCI-COMPLETION ] possible MCI failure in displayName (completed on build1)") }) Convey("TaskSuccessToFailureHandler should return 1 email per "+ "task success to failure transition", func() { handler := TaskSuccessToFailureHandler{} emails, err := handler.GetNotifications(ae, "config_test", &taskSuccessToFailureNotificationKey) So(err, ShouldBeNil) // check that we only returned 1 success to failure notifications So(len(emails), ShouldEqual, 1) So(emails[0].GetSubject(), ShouldEqual, "[MCI-FAILURE ] possible MCI failure in displayName (transitioned to "+ "failure on build1)") }) }) }) Convey("When running notifications pipeline", t, func() { cleanupdb() timeNow := time.Now() // insert the test documents insertTaskDocs(timeNow) v := &version.Version{Id: "version"} So(v.Insert(), ShouldBeNil) Convey("Should run the correct notification handlers for given "+ "notification keys", func() { notificationSettings := &MCINotification{} notificationSettings.Notifications = []Notification{ Notification{"task_failure", "project", []string{"user@mongodb"}, []string{}}, Notification{"task_success_to_failure", "project", []string{"user@mongodb"}, []string{}}, } notificationSettings.Teams = []Team{ Team{ "myteam", "*****@*****.**", []Subscription{Subscription{"task", []string{}, []string{"task_failure"}}}, }, } notificationSettings.PatchNotifications = []Subscription{ Subscription{"patch_project", []string{}, []string{}}, } notificationKeyFailure := NotificationKey{"project", "task_failure", "task", "gitter_request"} notificationKeyToFailure := NotificationKey{"project", "task_success_to_failure", "task", "gitter_request"} ae, err := createEnvironment(TestConfig, map[string]interface{}{}) So(err, ShouldBeNil) emails, err := ProcessNotifications(ae, "config_test", notificationSettings, false) So(err, ShouldBeNil) So(len(emails[notificationKeyFailure]), ShouldEqual, 2) So(emails[notificationKeyFailure][0].GetSubject(), ShouldEqual, "[MCI-FAILURE ] possible MCI failure in displayName (failed on build1)") So(emails[notificationKeyFailure][1].GetSubject(), ShouldEqual, "[MCI-FAILURE ] possible MCI failure in displayName (failed on build1)") So(len(emails[notificationKeyToFailure]), ShouldEqual, 1) So(emails[notificationKeyToFailure][0].GetSubject(), ShouldEqual, "[MCI-FAILURE ] possible MCI failure in displayName (transitioned to "+ "failure on build1)") }) Convey("SendNotifications should send emails correctly", func() { notificationSettings := &MCINotification{} notificationSettings.Notifications = []Notification{ Notification{"task_failure", "project", []string{"user@mongodb"}, []string{}}, } notificationSettings.Teams = []Team{ Team{ "myteam", "*****@*****.**", []Subscription{Subscription{"task", []string{}, []string{"task_failure"}}}, }, } notificationSettings.PatchNotifications = []Subscription{ Subscription{"patch_project", []string{}, []string{}}, } fakeTask, err := model.FindOneTask(bson.M{"_id": "task8"}, bson.M{}, []string{}) notificationKey := NotificationKey{"project", "task_failure", "task", "gitter_request"} triggeredNotification := TriggeredTaskNotification{ fakeTask, nil, []ChangeInfo{}, notificationKey, "[MCI-FAILURE]", "failed", } email := TaskEmail{ EmailBase{ "This is the email body", "This is the email subject", triggeredNotification.Info, }, triggeredNotification, } m := make(map[NotificationKey][]Email) m[notificationKey] = []Email{&email} mailer := MockMailer{} mockSettings := evergreen.Settings{Notify: evergreen.NotifyConfig{}} err = SendNotifications(&mockSettings, notificationSettings, m, mailer) So(err, ShouldBeNil) So(len(emailSubjects), ShouldEqual, 1) So(emailSubjects[0], ShouldEqual, "This is the email subject") So(emailBodies[0], ShouldEqual, "This is the email body") }) }) }
func (uis *UIServer) taskPage(w http.ResponseWriter, r *http.Request) { projCtx := MustHaveProjectContext(r) if projCtx.Task == nil { http.Error(w, "Not found", http.StatusNotFound) return } if projCtx.Build == nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("build not found")) return } if projCtx.Version == nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("version not found")) return } if projCtx.ProjectRef == nil { evergreen.Logger.Logf(slogger.ERROR, "Project ref is nil") uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("version not found")) return } executionStr := mux.Vars(r)["execution"] archived := false if executionStr != "" { // otherwise we can look in either tasks or old_tasks // where tasks are looked up in the old_tasks collection with key made up of // the original key and the execution number joined by an "_" // and the tasks are looked up in the tasks collection by key and execution // number, so that we avoid finding the wrong execution in the tasks // collection execution, err := strconv.Atoi(executionStr) if err != nil { http.Error(w, fmt.Sprintf("Bad execution number: %v", executionStr), http.StatusBadRequest) return } oldTaskId := fmt.Sprintf("%v_%v", projCtx.Task.Id, executionStr) taskFromDb, err := model.FindOneOldTask(bson.M{"_id": oldTaskId}, db.NoProjection, db.NoSort) if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, err) return } archived = true if taskFromDb == nil { // for backwards compatibility with tasks without an execution if execution == 0 { taskFromDb, err = model.FindOneTask(bson.M{ "$and": []bson.M{ bson.M{"_id": projCtx.Task.Id}, bson.M{"$or": []bson.M{bson.M{"execution": 0}, bson.M{"execution": nil}}}}}, db.NoProjection, db.NoSort) } else { taskFromDb, err = model.FindOneTask(bson.M{"_id": projCtx.Task.Id, "execution": execution}, db.NoProjection, db.NoSort) } if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, fmt.Errorf("Error finding old task: %v", err)) return } } projCtx.Task = taskFromDb } // Build a struct containing the subset of task data needed for display in the UI task := uiTaskData{ Id: projCtx.Task.Id, DisplayName: projCtx.Task.DisplayName, Revision: projCtx.Task.Revision, Status: projCtx.Task.Status, TaskEndDetails: projCtx.Task.Details, Distro: projCtx.Task.DistroId, BuildVariant: projCtx.Task.BuildVariant, BuildId: projCtx.Task.BuildId, Activated: projCtx.Task.Activated, Restarts: projCtx.Task.Restarts, Execution: projCtx.Task.Execution, Requester: projCtx.Task.Requester, StartTime: projCtx.Task.StartTime.UnixNano(), DispatchTime: projCtx.Task.DispatchTime.UnixNano(), FinishTime: projCtx.Task.FinishTime.UnixNano(), ExpectedDuration: projCtx.Task.ExpectedDuration, PushTime: projCtx.Task.PushTime, TimeTaken: projCtx.Task.TimeTaken, Priority: projCtx.Task.Priority, TestResults: projCtx.Task.TestResults, Aborted: projCtx.Task.Aborted, CurrentTime: time.Now().UnixNano(), BuildVariantDisplay: projCtx.Build.DisplayName, Message: projCtx.Version.Message, Project: projCtx.Version.Identifier, Author: projCtx.Version.Author, AuthorEmail: projCtx.Version.AuthorEmail, VersionId: projCtx.Version.Id, RepoOwner: projCtx.ProjectRef.Owner, Repo: projCtx.ProjectRef.Repo, Archived: archived, } deps, taskWaiting, err := getTaskDependencies(projCtx.Task) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } task.DependsOn = deps task.TaskWaiting = taskWaiting // Activating and deactivating tasks should clear out the // MinQueuePos but just in case, lets not show it if we shouldn't if projCtx.Task.Status == evergreen.TaskUndispatched && projCtx.Task.Activated { task.MinQueuePos = projCtx.Task.MinQueuePos } if projCtx.Task.HostId != "" { task.HostDNS = projCtx.Task.HostId task.HostId = projCtx.Task.HostId taskHost, err := host.FindOne(host.ById(projCtx.Task.HostId)) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } if taskHost != nil { task.HostDNS = taskHost.Host } } if projCtx.Patch != nil { taskOnBaseCommit, err := projCtx.Task.FindTaskOnBaseCommit() if err != nil { uis.LoggedError(w, r, http.StatusInternalServerError, err) return } taskPatch := &uiPatch{Patch: *projCtx.Patch} if taskOnBaseCommit != nil { taskPatch.BaseTaskId = taskOnBaseCommit.Id } taskPatch.StatusDiffs = model.StatusDiffTasks(taskOnBaseCommit, projCtx.Task).Tests task.PatchInfo = taskPatch } flashes := PopFlashes(uis.CookieStore, r, w) pluginContext := projCtx.ToPluginContext(uis.Settings, GetUser(r)) pluginContent := getPluginDataAndHTML(uis, plugin.TaskPage, pluginContext) uis.WriteHTML(w, http.StatusOK, struct { ProjectData projectContext User *user.DBUser Flashes []interface{} Task uiTaskData PluginContent pluginData }{projCtx, GetUser(r), flashes, task, pluginContent}, "base", "task.html", "base_angular.html", "menu.html") }