github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/syz-cluster/email-reporter/handler_test.go (about) 1 // Copyright 2025 syzkaller project authors. All rights reserved. 2 // Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. 3 4 package main 5 6 import ( 7 "context" 8 "testing" 9 10 "github.com/google/syzkaller/pkg/email" 11 "github.com/google/syzkaller/syz-cluster/pkg/api" 12 "github.com/google/syzkaller/syz-cluster/pkg/app" 13 "github.com/google/syzkaller/syz-cluster/pkg/controller" 14 "github.com/google/syzkaller/syz-cluster/pkg/emailclient" 15 "github.com/google/syzkaller/syz-cluster/pkg/reporter" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 ) 19 20 var testEmailConfig = emailclient.TestEmailConfig() 21 22 func TestModerationReportFlow(t *testing.T) { 23 env, ctx := app.TestEnvironment(t) 24 testSeries := controller.DummySeries() 25 handler, _, emailServer := setupHandlerTest(t, env, ctx, testSeries) 26 27 report, err := handler.PollAndReport(ctx) 28 assert.NoError(t, err) 29 30 receivedEmail := emailServer.email() 31 assert.NotNil(t, receivedEmail, "a moderation email must be sent") 32 receivedEmail.Body = nil // for now don't validate the body 33 assert.Equal(t, &emailclient.Email{ 34 To: []string{testEmailConfig.ModerationList}, 35 Cc: []string{testEmailConfig.ArchiveList}, 36 Subject: "[moderation/CI] Re: " + testSeries.Title, 37 BugID: report.ID, 38 // Note that InReplyTo and Cc are nil. 39 }, receivedEmail) 40 41 // Emulate an "upstream" command. 42 err = handler.IncomingEmail(ctx, &email.Email{ 43 BugIDs: []string{report.ID}, 44 Commands: []*email.SingleCommand{ 45 { 46 Command: email.CmdUpstream, 47 }, 48 }, 49 }) 50 assert.NoError(t, err) 51 52 // The report must be sent upstream. 53 report, err = handler.PollAndReport(ctx) 54 assert.NoError(t, err) 55 56 receivedEmail = emailServer.email() 57 assert.NotNil(t, receivedEmail, "an email must be sent upstream") 58 receivedEmail.Body = nil 59 assert.Equal(t, &emailclient.Email{ 60 To: testSeries.Cc, 61 Cc: append([]string{testEmailConfig.ArchiveList}, testEmailConfig.ReportCC...), 62 Subject: "[name] Re: " + testSeries.Title, 63 InReplyTo: testSeries.ExtID, 64 BugID: report.ID, 65 }, receivedEmail) 66 } 67 68 func TestReportInvalidationFlow(t *testing.T) { 69 env, ctx := app.TestEnvironment(t) 70 testSeries := controller.DummySeries() 71 handler, _, emailServer := setupHandlerTest(t, env, ctx, testSeries) 72 73 report, err := handler.PollAndReport(ctx) 74 require.NoError(t, err) 75 76 receivedEmail := emailServer.email() 77 require.NotNil(t, receivedEmail, "a moderation email must be sent") 78 receivedEmail.Body = nil // for now don't validate the body 79 80 // Emulate an "upstream" command. 81 err = handler.IncomingEmail(ctx, &email.Email{ 82 BugIDs: []string{report.ID}, 83 Commands: []*email.SingleCommand{ 84 { 85 Command: email.CmdInvalid, 86 }, 87 }, 88 }) 89 require.NoError(t, err) 90 91 // The report must be not sent upstream. 92 report, err = handler.PollAndReport(ctx) 93 require.NoError(t, err) 94 assert.Nil(t, report) 95 96 receivedEmail = emailServer.email() 97 assert.Nil(t, receivedEmail, "an email must not be sent upstream") 98 } 99 100 func TestInvalidReply(t *testing.T) { 101 env, ctx := app.TestEnvironment(t) 102 testSeries := controller.DummySeries() 103 handler, _, emailServer := setupHandlerTest(t, env, ctx, testSeries) 104 105 report, err := handler.PollAndReport(ctx) 106 assert.NoError(t, err) 107 108 receivedEmail := emailServer.email() 109 assert.NotNil(t, receivedEmail, "a moderation email must be sent") 110 receivedEmail.Body = nil 111 112 t.Run("unrelated email", func(t *testing.T) { 113 err = handler.IncomingEmail(ctx, &email.Email{ 114 Commands: []*email.SingleCommand{ 115 { 116 Command: email.CmdUpstream, 117 }, 118 }, 119 }) 120 assert.NoError(t, err) 121 _, err = handler.PollAndReport(ctx) 122 assert.NoError(t, err) 123 // No email must be sent in reply. 124 assert.Nil(t, emailServer.email()) 125 }) 126 127 t.Run("unsupported command", func(t *testing.T) { 128 err := handler.IncomingEmail(ctx, &email.Email{ 129 Author: "user@email.com", 130 Subject: "Command", 131 BugIDs: []string{report.ID}, 132 Cc: []string{"a@a.com", "b@b.com"}, 133 MessageID: "user-reply-msg-id", 134 Commands: []*email.SingleCommand{ 135 { 136 Command: email.CmdFix, 137 }, 138 }, 139 Body: `#syz fix: abcd`, 140 }) 141 assert.NoError(t, err) 142 reply := emailServer.email() 143 assert.NotNil(t, reply) 144 assert.Equal(t, &emailclient.Email{ 145 To: []string{"user@email.com"}, 146 Cc: []string{"a@a.com", "b@b.com"}, 147 Subject: "Re: Command", 148 InReplyTo: "user-reply-msg-id", 149 Body: []byte(`> #syz fix: abcd 150 151 Unknown command 152 153 `), 154 }, reply) 155 }) 156 157 t.Run("own email", func(t *testing.T) { 158 err = handler.IncomingEmail(ctx, &email.Email{ 159 OwnEmail: true, 160 BugIDs: []string{report.ID}, 161 Commands: []*email.SingleCommand{ 162 { 163 Command: email.CmdUpstream, 164 }, 165 }, 166 }) 167 assert.NoError(t, err) 168 _, err = handler.PollAndReport(ctx) 169 assert.NoError(t, err) 170 // No email must be sent in reply. 171 assert.Nil(t, emailServer.email()) 172 }) 173 174 t.Run("forwarded email", func(t *testing.T) { 175 err = handler.IncomingEmail(ctx, &email.Email{ 176 Subject: email.ForwardedPrefix + "abcd", 177 OwnEmail: true, 178 BugIDs: []string{report.ID}, 179 Commands: []*email.SingleCommand{ 180 { 181 Command: email.CmdUpstream, 182 }, 183 }, 184 }) 185 assert.NoError(t, err) 186 _, err = handler.PollAndReport(ctx) 187 assert.NoError(t, err) 188 assert.NotNil(t, emailServer.email()) 189 }) 190 } 191 192 func setupHandlerTest(t *testing.T, env *app.AppEnvironment, ctx context.Context, 193 series *api.Series) (*Handler, *api.ReporterClient, *fakeSender) { 194 client := controller.TestServer(t, env) 195 controller.FakeSeriesWithFindings(t, ctx, env, client, series) 196 197 generator := reporter.NewGenerator(env) 198 err := generator.Process(ctx, 1) 199 assert.NoError(t, err) 200 201 emailServer := makeFakeSender() 202 reporterClient := reporter.TestServer(t, env) 203 handler := &Handler{ 204 reporter: api.LKMLReporter, 205 apiClient: reporterClient, 206 emailConfig: testEmailConfig, 207 sender: emailServer.send, 208 } 209 return handler, reporterClient, emailServer 210 } 211 212 type fakeSender struct { 213 ch chan *emailclient.Email 214 } 215 216 func makeFakeSender() *fakeSender { 217 return &fakeSender{ 218 ch: make(chan *emailclient.Email, 16), 219 } 220 } 221 222 func (f *fakeSender) send(ctx context.Context, e *emailclient.Email) (string, error) { 223 f.ch <- e 224 return "email-id", nil 225 } 226 227 func (f *fakeSender) email() *emailclient.Email { 228 select { 229 case e := <-f.ch: 230 return e 231 default: 232 return nil 233 } 234 }