github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/syz-cluster/pkg/reporter/api_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 reporter 5 6 import ( 7 "testing" 8 "time" 9 10 "github.com/google/syzkaller/syz-cluster/pkg/api" 11 "github.com/google/syzkaller/syz-cluster/pkg/app" 12 "github.com/google/syzkaller/syz-cluster/pkg/controller" 13 "github.com/google/syzkaller/syz-cluster/pkg/service" 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 ) 17 18 func TestAPIReportFlow(t *testing.T) { 19 env, ctx := app.TestEnvironment(t) 20 client := controller.TestServer(t, env) 21 22 // Create series/session/test/findings. 23 testSeries := controller.DummySeries() 24 ids := controller.FakeSeriesWithFindings(t, ctx, env, client, testSeries) 25 26 generator := NewGenerator(env) 27 err := generator.Process(ctx, 1) 28 assert.NoError(t, err) 29 30 reportClient := TestServer(t, env) 31 // The same report will be returned multiple times. 32 nextResp, err := reportClient.GetNextReport(ctx, api.LKMLReporter) 33 assert.NoError(t, err) 34 nextResp2, err := reportClient.GetNextReport(ctx, api.LKMLReporter) 35 assert.NoError(t, err) 36 assert.Equal(t, nextResp2, nextResp) 37 // We don't know IDs in advance. 38 nextResp.Report.ID = "" 39 nextResp.Report.Series.ID = "" 40 // For URLs, just check if they are in place. 41 for _, finding := range nextResp.Report.Findings { 42 assert.NotEmpty(t, finding.LogURL, "%q's LogURL is empty", finding.Title) 43 finding.LogURL = "" 44 assert.NotEmpty(t, finding.LinkCRepro, "%q's LinkCRepro is empty", finding.Title) 45 finding.LinkCRepro = "" 46 assert.NotEmpty(t, finding.LinkSyzRepro, "%q's LinkSyzRepro is empty", finding.Title) 47 finding.LinkSyzRepro = "" 48 assert.NotEmpty(t, finding.Build.ConfigLink, "%q's ConfigLink is empty", finding.Title) 49 finding.Build.ConfigLink = "" 50 } 51 52 assert.Equal(t, &api.SessionReport{ 53 Moderation: true, 54 Link: env.URLs.Series(ids.SeriesID), 55 Series: &api.Series{ 56 ExtID: testSeries.ExtID, 57 Title: testSeries.Title, 58 Link: "http://link/to/series", 59 Cc: []string{"first@user.com", "second@user.com"}, 60 Patches: []api.SeriesPatch{ 61 { 62 Seq: 1, 63 Title: "first patch title", 64 // Body is empty. 65 }, 66 }, 67 }, 68 // These findings relate to controller.DummyFindings(). 69 Findings: []*api.Finding{ 70 { 71 Title: "finding 0", 72 Report: "report 0", 73 Build: api.BuildInfo{ 74 TreeName: "mainline", 75 TreeURL: "https://git/repo", 76 BaseCommit: "abcd", 77 Arch: "amd64", 78 Compiler: "compiler", 79 }, 80 }, 81 { 82 Title: "finding 1", 83 Report: "report 1", 84 Build: api.BuildInfo{ 85 TreeName: "mainline", 86 TreeURL: "https://git/repo", 87 BaseCommit: "abcd", 88 Arch: "amd64", 89 Compiler: "compiler", 90 }, 91 }, 92 }, 93 }, nextResp.Report) 94 95 // Now confirm it. 96 reportID := nextResp2.Report.ID 97 err = reportClient.ConfirmReport(ctx, reportID) 98 assert.NoError(t, err) 99 100 // It should no longer appear in Next(). 101 emptyNext, err := reportClient.GetNextReport(ctx, api.LKMLReporter) 102 assert.NoError(t, err) 103 assert.Nil(t, emptyNext.Report) 104 105 // "Upstream" it. 106 err = reportClient.UpstreamReport(ctx, reportID, &api.UpstreamReportReq{ 107 User: "name", 108 }) 109 assert.NoError(t, err) 110 111 // It should appear again, now with Moderation=false. 112 nextResp3, err := reportClient.GetNextReport(ctx, api.LKMLReporter) 113 assert.NoError(t, err) 114 assert.False(t, nextResp3.Report.Moderation) 115 assert.Equal(t, nextResp2.Report.Series, nextResp3.Report.Series) 116 } 117 118 func TestReplyReporting(t *testing.T) { 119 env, ctx := app.TestEnvironment(t) 120 client := controller.TestServer(t, env) 121 122 // Create series/session/test/findings. 123 testSeries := controller.DummySeries() 124 controller.FakeSeriesWithFindings(t, ctx, env, client, testSeries) 125 126 generator := NewGenerator(env) 127 err := generator.Process(ctx, 1) 128 assert.NoError(t, err) 129 130 // Create a report. 131 reportClient := TestServer(t, env) 132 nextResp, err := reportClient.GetNextReport(ctx, api.LKMLReporter) 133 assert.NoError(t, err) 134 135 // Confirm the report and set its message ID. 136 reportID := nextResp.Report.ID 137 err = reportClient.ConfirmReport(ctx, reportID) 138 assert.NoError(t, err) 139 140 const reportMessageID = "message-id" 141 _, err = reportClient.RecordReply(ctx, &api.RecordReplyReq{ 142 MessageID: reportMessageID, 143 ReportID: reportID, 144 Reporter: api.LKMLReporter, 145 }) 146 assert.NoError(t, err) 147 148 // Direct reply to the report. 149 resp, err := reportClient.RecordReply(ctx, &api.RecordReplyReq{ 150 MessageID: "direct-reply-id", 151 InReplyTo: reportMessageID, 152 Reporter: api.LKMLReporter, 153 Time: time.Now(), 154 }) 155 assert.NoError(t, err) 156 assert.Equal(t, &api.RecordReplyResp{ 157 New: true, 158 ReportID: reportID, 159 }, resp) 160 161 // Reply to the reply. 162 replyToReply := &api.RecordReplyReq{ 163 MessageID: "reply-to-reply-id", 164 InReplyTo: "direct-reply-id", 165 Reporter: api.LKMLReporter, 166 Time: time.Now(), 167 } 168 resp, err = reportClient.RecordReply(ctx, replyToReply) 169 assert.NoError(t, err) 170 assert.Equal(t, &api.RecordReplyResp{ 171 New: true, 172 ReportID: reportID, 173 }, resp) 174 175 t.Run("dup-report", func(t *testing.T) { 176 resp, err := reportClient.RecordReply(ctx, replyToReply) 177 assert.NoError(t, err) 178 assert.Equal(t, &api.RecordReplyResp{ 179 New: false, 180 ReportID: reportID, 181 }, resp) 182 }) 183 184 t.Run("unknown-message", func(t *testing.T) { 185 resp, err := reportClient.RecordReply(ctx, &api.RecordReplyReq{ 186 MessageID: "whatever", 187 InReplyTo: "unknown-id", 188 Reporter: api.LKMLReporter, 189 }) 190 assert.NoError(t, err) 191 assert.Equal(t, &api.RecordReplyResp{ 192 New: false, 193 ReportID: "", 194 }, resp) 195 }) 196 } 197 198 func TestInvalidate(t *testing.T) { 199 env, ctx := app.TestEnvironment(t) 200 client := controller.TestServer(t, env) 201 testSeries := controller.DummySeries() 202 ids := controller.FakeSeriesWithFindings(t, ctx, env, client, testSeries) 203 204 generator := NewGenerator(env) 205 err := generator.Process(ctx, 1) 206 require.NoError(t, err) 207 208 // Create a report. 209 reportClient := TestServer(t, env) 210 nextResp, err := reportClient.GetNextReport(ctx, api.LKMLReporter) 211 require.NoError(t, err) 212 reportID := nextResp.Report.ID 213 err = reportClient.ConfirmReport(ctx, reportID) 214 require.NoError(t, err) 215 216 // Invalidate the findings. 217 err = reportClient.InvalidateReport(ctx, reportID) 218 require.NoError(t, err) 219 220 // Report should not appear in Next(). 221 emptyNext, err := reportClient.GetNextReport(ctx, api.LKMLReporter) 222 require.NoError(t, err) 223 assert.Nil(t, emptyNext.Report) 224 225 // All findings must be invalidated. 226 findingService := service.NewFindingService(env) 227 list, err := findingService.List(ctx, ids.SessionID, 0) 228 require.NoError(t, err) 229 assert.Len(t, list, 2) 230 for i, finding := range list { 231 assert.True(t, finding.Invalidated, "finding %d must be invalidated", i) 232 } 233 }