blob: 00bd46956eb6523e3d84b43c9f800c973fdd3332 [file] [log] [blame]
package notify
import (
"context"
"errors"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/stretchr/testify/require"
"go.skia.org/infra/email/go/emailclient"
"go.skia.org/infra/go/now"
"go.skia.org/infra/go/paramtools"
"go.skia.org/infra/go/testutils"
"go.skia.org/infra/perf/go/alerts"
"go.skia.org/infra/perf/go/clustering2"
"go.skia.org/infra/perf/go/config"
"go.skia.org/infra/perf/go/dataframe"
"go.skia.org/infra/perf/go/git/provider"
"go.skia.org/infra/perf/go/notify/mocks"
"go.skia.org/infra/perf/go/stepfit"
"go.skia.org/infra/perf/go/ui/frame"
)
const (
newHTMLMessage = "<b>Alert</b><br><br>\n<p>\n\tA Perf Regression (High) has been found at:\n</p>\n<p style=\"padding: 1em;\">\n\t<a href=\"https://perf.skia.org/g/t/d261e1075a93677442fdf7fe72aba7e583863664\">https://perf.skia.org/g/t/d261e1075a93677442fdf7fe72aba7e583863664</a>\n</p>\n<p>\n For:\n</p>\n<p style=\"padding: 1em;\">\n <a href=\"https://skia.googlesource.com/skia/&#43;show/d261e1075a93677442fdf7fe72aba7e583863664\">https://skia.googlesource.com/skia/&#43;show/d261e1075a93677442fdf7fe72aba7e583863664</a>\n</p>\n<p>\n\tWith 10 matching traces.\n</p>\n<p>\n And direction High.\n</p>\n<p>\n\tFrom Alert <a href=\"https://perf.skia.org/a/?123\">MyAlert</a>\n</p>\n"
newHTMLSubject = "MyAlert - Regression found for d261e10 - 2y 40w - An example commit use for testing."
missingHTMLMessage = "<b>Alert</b><br><br>\n<p>\n\tA Perf Regression (High) can no longer be found at:\n</p>\n<p style=\"padding: 1em;\">\n\t<a href=\"https://perf.skia.org/g/t/d261e1075a93677442fdf7fe72aba7e583863664\">https://perf.skia.org/g/t/d261e1075a93677442fdf7fe72aba7e583863664</a>\n</p>\n<p>\n\tFor:\n</p>\n<p style=\"padding: 1em;\">\n\t<a href=\"https://skia.googlesource.com/skia/&#43;show/d261e1075a93677442fdf7fe72aba7e583863664\">https://skia.googlesource.com/skia/&#43;show/d261e1075a93677442fdf7fe72aba7e583863664</a>\n</p>\n<p>\n\tWith 10 matching traces.\n</p>\n<p>\n\tAnd direction High.\n</p>\n<p>\n\tFrom Alert <a href=\"https://perf.skia.org/a/?123\">MyAlert</a>\n</p>\n"
missingHTMLSubject = "MyAlert - Regression no longer found for d261e10 - 2y 40w - An example commit use for testing."
newMarkdownMessage = "A Perf Regression (High) has been found at:\n\n https://perf.skia.org/g/t/d261e1075a93677442fdf7fe72aba7e583863664\n\nFor:\n\n Commit https://skia.googlesource.com/skia/+show/d261e1075a93677442fdf7fe72aba7e583863664\n\nWith:\n\n - 10 matching traces.\n - Direction High.\n\nFrom Alert [MyAlert](https://perf.skia.org/a/?123)\n"
newMarkdownSubject = "MyAlert - Regression found for An example commit use for testing."
missingMarkdownMessage = "The Perf Regression can no longer be detected. This issue is being automatically closed.\n"
missingMarkdownSubject = "MyAlert - Regression no longer found for An example commit use for testing."
newMarkdownMessageWithCommitRangeURLTemplate = "A Perf Regression (High) has been found at:\n\n https://perf.skia.org/g/t/d261e1075a93677442fdf7fe72aba7e583863664\n\nFor:\n\n Commit https://example.com/fb49909acafba5e031b90a265a6ce059cda85019/d261e1075a93677442fdf7fe72aba7e583863664/\n\nWith:\n\n - 10 matching traces.\n - Direction High.\n\nFrom Alert [MyAlert](https://perf.skia.org/a/?123)\n"
)
const (
mockThreadingID = "123456789"
instanceURL = "https://perf.skia.org"
)
var (
alertForTest = &alerts.Alert{
IDAsString: "123",
Alert: "someone@example.org, someother@example.com ",
DisplayName: "MyAlert",
}
errMock = errors.New("my mock error")
commitForTestingBuildID = provider.Commit{
Subject: "https://android-build.googleplex.com/builds/jump-to-build/10768667 ",
}
previousCommitForTestingBuildID = provider.Commit{
Subject: "https://android-build.googleplex.com/builds/jump-to-build/10768666 ",
}
cl = &clustering2.ClusterSummary{
Num: 10,
StepFit: &stepfit.StepFit{
Status: stepfit.HIGH,
},
StepPoint: &dataframe.ColumnHeader{
Offset: 2,
},
}
frameResponse = &frame.FrameResponse{
DataFrame: &dataframe.DataFrame{
Header: []*dataframe.ColumnHeader{
{Offset: 1, Timestamp: 1687824470},
{Offset: 2, Timestamp: 1498176000},
},
ParamSet: paramtools.ReadOnlyParamSet{
"device_name": []string{"sailfish", "sargo", "wembley"},
},
},
}
)
func TestExampleSendWithHTMLFormatter_HappyPath(t *testing.T) {
tr := mocks.NewTransport(t)
tr.On("SendNewRegression", testutils.AnyContext, alertForTest, newHTMLMessage, newHTMLSubject).Return(mockThreadingID, nil)
tr.On("SendRegressionMissing", testutils.AnyContext, mockThreadingID, alertForTest, missingHTMLMessage, missingHTMLSubject).Return(nil)
n := newNotifier(NewHTMLFormatter(""), tr, instanceURL)
ctx := context.WithValue(context.Background(), now.ContextKey, time.Date(2020, 04, 01, 0, 0, 0, 0, time.UTC))
err := n.ExampleSend(ctx, alertForTest)
require.NoError(t, err)
}
func TestExampleSendWithMarkdownFormatter_HappyPath(t *testing.T) {
tr := mocks.NewTransport(t)
tr.On("SendNewRegression", testutils.AnyContext, alertForTest, newMarkdownMessage, newMarkdownSubject).Return(mockThreadingID, nil)
tr.On("SendRegressionMissing", testutils.AnyContext, mockThreadingID, alertForTest, missingMarkdownMessage, missingMarkdownSubject).Return(nil)
f, err := NewMarkdownFormatter("", &config.NotifyConfig{})
require.NoError(t, err)
n := newNotifier(f, tr, instanceURL)
ctx := context.WithValue(context.Background(), now.ContextKey, time.Date(2020, 04, 01, 0, 0, 0, 0, time.UTC))
err = n.ExampleSend(ctx, alertForTest)
require.NoError(t, err)
}
func TestExampleSendWithMarkdownFormatterWithCommitRangeURLTemplate_HappyPath(t *testing.T) {
tr := mocks.NewTransport(t)
tr.On("SendNewRegression", testutils.AnyContext, alertForTest, newMarkdownMessageWithCommitRangeURLTemplate, newMarkdownSubject).Return(mockThreadingID, nil)
tr.On("SendRegressionMissing", testutils.AnyContext, mockThreadingID, alertForTest, missingMarkdownMessage, missingMarkdownSubject).Return(nil)
f, err := NewMarkdownFormatter("https://example.com/{begin}/{end}/", &config.NotifyConfig{})
require.NoError(t, err)
n := newNotifier(f, tr, instanceURL)
ctx := context.WithValue(context.Background(), now.ContextKey, time.Date(2020, 04, 01, 0, 0, 0, 0, time.UTC))
err = n.ExampleSend(ctx, alertForTest)
require.NoError(t, err)
}
func TestExampleSendWithMarkdownFormatterWithCommitRangeURLTemplateAndCustomizedNotifierFormats_HappyPath(t *testing.T) {
tr := mocks.NewTransport(t)
tr.On(
"SendNewRegression",
testutils.AnyContext,
alertForTest,
"body MyAlert - https://example.com/fb49909acafba5e031b90a265a6ce059cda85019/d261e1075a93677442fdf7fe72aba7e583863664/",
"subject fb49909acafba5e031b90a265a6ce059cda85019",
).Return(mockThreadingID, nil)
tr.On(
"SendRegressionMissing",
testutils.AnyContext,
mockThreadingID,
alertForTest,
"missing-body MyAlert - https://example.com/fb49909acafba5e031b90a265a6ce059cda85019/d261e1075a93677442fdf7fe72aba7e583863664/",
"missing-subject fb49909acafba5e031b90a265a6ce059cda85019",
).Return(nil)
f, err := NewMarkdownFormatter("https://example.com/{begin}/{end}/", &config.NotifyConfig{
Subject: "subject {{ .PreviousCommit.GitHash }}",
Body: []string{"body {{ .Alert.DisplayName }} - {{ .CommitURL }}"},
MissingSubject: "missing-subject {{ .PreviousCommit.GitHash }}",
MissingBody: []string{"missing-body {{ .Alert.DisplayName }} - {{ .CommitURL }}"},
})
require.NoError(t, err)
n := newNotifier(f, tr, instanceURL)
ctx := context.WithValue(context.Background(), now.ContextKey, time.Date(2020, 04, 01, 0, 0, 0, 0, time.UTC))
err = n.ExampleSend(ctx, alertForTest)
require.NoError(t, err)
}
func TestExampleSendWithHTMLFormatter_SendRegressionMissingReturnsError_ReturnsError(t *testing.T) {
tr := mocks.NewTransport(t)
tr.On("SendNewRegression", testutils.AnyContext, alertForTest, newHTMLMessage, newHTMLSubject).Return(mockThreadingID, nil)
tr.On("SendRegressionMissing", testutils.AnyContext, mockThreadingID, alertForTest, missingHTMLMessage, missingHTMLSubject).Return(errMock)
n := newNotifier(NewHTMLFormatter(""), tr, instanceURL)
ctx := context.WithValue(context.Background(), now.ContextKey, time.Date(2020, 04, 01, 0, 0, 0, 0, time.UTC))
err := n.ExampleSend(ctx, alertForTest)
require.ErrorIs(t, err, errMock)
require.Contains(t, err.Error(), "sending regression missing message")
}
func TestExampleSendWithHTMLFormatter_SendNewRegressionReturnsError_ReturnsError(t *testing.T) {
tr := mocks.NewTransport(t)
tr.On("SendNewRegression", testutils.AnyContext, alertForTest, newHTMLMessage, newHTMLSubject).Return("", errMock)
n := newNotifier(NewHTMLFormatter(""), tr, instanceURL)
ctx := context.WithValue(context.Background(), now.ContextKey, time.Date(2020, 04, 01, 0, 0, 0, 0, time.UTC))
err := n.ExampleSend(ctx, alertForTest)
require.ErrorIs(t, err, errMock)
require.Contains(t, err.Error(), "sending new regression message")
}
func TestExampleSendWithHTMLFormatterAndEMailTransport_HappyPath(t *testing.T) {
const expectedMessageID = "<the-actual-message-id>"
subjects := []string{newHTMLSubject, missingHTMLSubject}
subjectIndex := 0
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
b, err := ioutil.ReadAll(r.Body)
require.NoError(t, err)
require.Contains(t, string(b), "<b>Alert</b>")
require.Contains(t, string(b), subjects[subjectIndex])
subjectIndex++
w.Header().Add("x-message-id", mockThreadingID)
w.WriteHeader(http.StatusOK)
}))
tr := NewEmailTransport()
emailClient := emailclient.NewAt(s.URL)
tr.client = emailClient
n := newNotifier(NewHTMLFormatter(""), tr, instanceURL)
ctx := context.WithValue(context.Background(), now.ContextKey, time.Date(2020, 04, 01, 0, 0, 0, 0, time.UTC))
err := n.ExampleSend(ctx, alertForTest)
require.NoError(t, err)
}
func TestMarkdownFormatter_CallsBuildIDFromSubjectButSubjectDoesntContainLink_ReturnsEmptyString(t *testing.T) {
f, err := NewMarkdownFormatter("", &config.NotifyConfig{
Subject: "From {{ buildIDFromSubject .PreviousCommit.Subject}} To {{ buildIDFromSubject .Commit.Subject}}",
})
require.NoError(t, err)
_, subject, err := f.FormatNewRegression(context.Background(), commit, previousCommit, alertForTest, cl, "", frameResponse)
require.NoError(t, err)
require.Equal(t, "From To ", subject)
}
func TestMarkdownFormatter_CallsBuildIDFromSubject_Success(t *testing.T) {
f, err := NewMarkdownFormatter("", &config.NotifyConfig{
Subject: "From {{ buildIDFromSubject .PreviousCommit.Subject}} To {{ buildIDFromSubject .Commit.Subject}}",
})
require.NoError(t, err)
_, subject, err := f.FormatNewRegression(context.Background(), commitForTestingBuildID, previousCommitForTestingBuildID, alertForTest, cl, "", frameResponse)
require.NoError(t, err)
require.Equal(t, "From 10768666 To 10768667", subject)
}
func TestMarkdownFormatter_ForLoopOnParamSet_Success(t *testing.T) {
f, err := NewMarkdownFormatter("", &config.NotifyConfig{
Subject: "devices: {{ range index .ParamSet \"device_name\" }} {{ . }} | {{ end }}",
})
require.NoError(t, err)
_, subject, err := f.FormatNewRegression(context.Background(), commitForTestingBuildID, previousCommitForTestingBuildID, alertForTest, cl, "", frameResponse)
require.NoError(t, err)
require.Equal(t, "devices: sailfish | sargo | wembley | ", subject)
}