cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.57.3

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/reporter/github_test.go

477lines · modecode

1package reporter_test
2
3import (
4 "fmt"
5 "io"
6 "log/slog"
7 "net/http"
8 "net/http/httptest"
9 "strings"
10 "testing"
11 "time"
12
13 "github.com/neilotoole/slogt"
14 "github.com/stretchr/testify/require"
15
16 "github.com/cloudflare/pint/internal/checks"
17 "github.com/cloudflare/pint/internal/git"
18 "github.com/cloudflare/pint/internal/parser"
19 "github.com/cloudflare/pint/internal/reporter"
20)
21
22func TestGithubReporter(t *testing.T) {
23 type testCaseT struct {
24 httpHandler http.Handler
25 error func(uri string) string
26 gitCmd git.CommandRunner
27
28 description string
29
30 owner string
31 repo string
32 token string
33 reports []reporter.Report
34 prNum int
35 maxComments int
36 timeout time.Duration
37 }
38
39 p := parser.NewParser()
40 mockRules, _ := p.Parse([]byte(`
41- record: target is down
42 expr: up == 0
43- record: sum errors
44 expr: sum(errors) by (job)
45`))
46
47 blameLine := func(sha string, line int, filename, content string) string {
48 return fmt.Sprintf(`%s %d %d 1
49filename %s
50 %s
51`, sha, line, line, filename, content)
52 }
53
54 for _, tc := range []testCaseT{
55 {
56 description: "timeout errors out",
57 owner: "foo",
58 repo: "bar",
59 token: "something",
60 prNum: 123,
61 maxComments: 50,
62 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
63 time.Sleep(1 * time.Second)
64 _, _ = w.Write([]byte("OK"))
65 }),
66 timeout: 100 * time.Millisecond,
67 gitCmd: func(args ...string) ([]byte, error) {
68 if args[0] == "rev-parse" {
69 return []byte("fake-commit-id"), nil
70 }
71 if args[0] == "blame" {
72 content := blameLine("fake-commit-id", 2, "foo.txt", "up == 0")
73 return []byte(content), nil
74 }
75 return nil, nil
76 },
77 error: func(_ string) string {
78 return "failed to list pull request reviews: context deadline exceeded"
79 },
80 reports: []reporter.Report{
81 {
82 SourcePath: "foo.txt",
83 ModifiedLines: []int{2},
84 Rule: mockRules[1],
85 Problem: checks.Problem{
86 Lines: parser.LineRange{
87 First: 2,
88 Last: 2,
89 },
90 Reporter: "mock",
91 Text: "syntax error",
92 Severity: checks.Fatal,
93 },
94 },
95 },
96 },
97 {
98 description: "list reviews error",
99 owner: "foo",
100 repo: "bar",
101 token: "something",
102 prNum: 123,
103 maxComments: 50,
104 timeout: time.Second,
105 gitCmd: func(args ...string) ([]byte, error) {
106 if args[0] == "rev-parse" {
107 return []byte("fake-commit-id"), nil
108 }
109 if args[0] == "blame" {
110 content := blameLine("fake-commit-id", 2, "foo.txt", "up == 0")
111 return []byte(content), nil
112 }
113 return nil, nil
114 },
115 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
116 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" {
117 w.WriteHeader(http.StatusBadRequest)
118 _, _ = w.Write([]byte("Error"))
119 return
120 }
121 _, _ = w.Write([]byte(""))
122 }),
123 error: func(uri string) string {
124 return fmt.Sprintf("failed to list pull request reviews: GET %s/api/v3/repos/foo/bar/pulls/123/reviews: 400 []", uri)
125 },
126 reports: []reporter.Report{
127 {
128 SourcePath: "foo.txt",
129 ModifiedLines: []int{2},
130 Rule: mockRules[1],
131 Problem: checks.Problem{
132 Lines: parser.LineRange{
133 First: 2,
134 Last: 2,
135 },
136 Reporter: "mock",
137 Text: "syntax error",
138 Details: "syntax details",
139 Severity: checks.Fatal,
140 },
141 },
142 },
143 },
144 {
145 description: "happy path",
146 owner: "foo",
147 repo: "bar",
148 token: "something",
149 prNum: 123,
150 maxComments: 50,
151 timeout: time.Second,
152 gitCmd: func(args ...string) ([]byte, error) {
153 if args[0] == "rev-parse" {
154 return []byte("fake-commit-id"), nil
155 }
156 if args[0] == "blame" {
157 content := blameLine("fake-commit-id", 2, "foo.txt", "up == 0")
158 return []byte(content), nil
159 }
160 return nil, nil
161 },
162 reports: []reporter.Report{
163 {
164 SourcePath: "foo.txt",
165 ModifiedLines: []int{2},
166 Rule: mockRules[1],
167 Problem: checks.Problem{
168 Lines: parser.LineRange{
169 First: 2,
170 Last: 2,
171 },
172 Reporter: "mock",
173 Text: "syntax error",
174 Details: "syntax details",
175 Severity: checks.Fatal,
176 },
177 },
178 },
179 },
180 {
181 description: "error crating review",
182 owner: "foo",
183 repo: "bar",
184 token: "something",
185 prNum: 123,
186 maxComments: 50,
187 timeout: time.Second,
188 gitCmd: func(args ...string) ([]byte, error) {
189 if args[0] == "rev-parse" {
190 return []byte("fake-commit-id"), nil
191 }
192 if args[0] == "blame" {
193 content := blameLine("fake-commit-id", 2, "foo.txt", "up == 0")
194 return []byte(content), nil
195 }
196 return nil, nil
197 },
198 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
199 if r.Method == http.MethodPost && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" {
200 w.WriteHeader(http.StatusBadGateway)
201 _, _ = w.Write([]byte("Error"))
202 return
203 }
204 _, _ = w.Write([]byte(""))
205 }),
206 error: func(uri string) string {
207 return fmt.Sprintf("failed to create pull request review: POST %s/api/v3/repos/foo/bar/pulls/123/reviews: 502 []", uri)
208 },
209 reports: []reporter.Report{
210 {
211 SourcePath: "foo.txt",
212 ModifiedLines: []int{2},
213 Rule: mockRules[1],
214 Problem: checks.Problem{
215 Lines: parser.LineRange{
216 First: 2,
217 Last: 2,
218 },
219 Reporter: "mock",
220 Text: "syntax error",
221 Details: "syntax details",
222 Severity: checks.Fatal,
223 },
224 },
225 },
226 },
227 {
228 description: "error updating existing review",
229 owner: "foo",
230 repo: "bar",
231 token: "something",
232 prNum: 123,
233 maxComments: 50,
234 timeout: time.Second,
235 gitCmd: func(args ...string) ([]byte, error) {
236 if args[0] == "rev-parse" {
237 return []byte("fake-commit-id"), nil
238 }
239 if args[0] == "blame" {
240 content := blameLine("fake-commit-id", 2, "foo.txt", "up == 0")
241 return []byte(content), nil
242 }
243 return nil, nil
244 },
245 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
246 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" {
247 _, _ = w.Write([]byte(`[{"id":1,"body":"### This pull request was validated by [pint](https://github.com/cloudflare/pint).\nxxxx"}]`))
248 return
249 }
250 if r.Method == http.MethodPut && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews/1" {
251 w.WriteHeader(http.StatusBadGateway)
252 _, _ = w.Write([]byte("Error"))
253 return
254 }
255 _, _ = w.Write([]byte(""))
256 }),
257 error: func(uri string) string {
258 return fmt.Sprintf("failed to update pull request review: PUT %s/api/v3/repos/foo/bar/pulls/123/reviews/1: 502 []", uri)
259 },
260 reports: []reporter.Report{
261 {
262 SourcePath: "foo.txt",
263 ModifiedLines: []int{2},
264 Rule: mockRules[1],
265 Problem: checks.Problem{
266 Lines: parser.LineRange{
267 First: 2,
268 Last: 2,
269 },
270 Reporter: "mock",
271 Text: "syntax error",
272 Details: "syntax details",
273 Severity: checks.Fatal,
274 },
275 },
276 },
277 },
278 {
279 description: "update existing review",
280 owner: "foo",
281 repo: "bar",
282 token: "something",
283 prNum: 123,
284 maxComments: 50,
285 timeout: time.Second,
286 gitCmd: func(args ...string) ([]byte, error) {
287 if args[0] == "rev-parse" {
288 return []byte("fake-commit-id"), nil
289 }
290 if args[0] == "blame" {
291 content := blameLine("fake-commit-id", 2, "foo.txt", "up == 0")
292 return []byte(content), nil
293 }
294 return nil, nil
295 },
296 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
297 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" {
298 _, _ = w.Write([]byte(`[{"id":1,"body":"### This pull request was validated by [pint](https://github.com/cloudflare/pint).\nxxxx"}]`))
299 return
300 }
301 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/comments" {
302 _, _ = w.Write([]byte(`[{"id":1,"commit_id":"fake-commit-id","path":"foo.txt","line":2,"body":":stop_sign: [mock](https://cloudflare.github.io/pint/checks/mock.html): syntax error\n\nsyntax details"}]`))
303 return
304 }
305 _, _ = w.Write([]byte(""))
306 }),
307 reports: []reporter.Report{
308 {
309 SourcePath: "foo.txt",
310 ModifiedLines: []int{2},
311 Rule: mockRules[1],
312 Problem: checks.Problem{
313 Lines: parser.LineRange{
314 First: 2,
315 Last: 2,
316 },
317 Reporter: "mock",
318 Text: "syntax error",
319 Details: "syntax details",
320 Severity: checks.Fatal,
321 },
322 },
323 },
324 },
325 {
326 description: "maxComments 2",
327 owner: "foo",
328 repo: "bar",
329 token: "something",
330 prNum: 123,
331 maxComments: 2,
332 timeout: time.Second,
333 gitCmd: func(args ...string) ([]byte, error) {
334 if args[0] == "rev-parse" {
335 return []byte("fake-commit-id"), nil
336 }
337 if args[0] == "blame" {
338 content := blameLine("fake-commit-id", 2, "foo.txt", "up == 0")
339 return []byte(content), nil
340 }
341 return nil, nil
342 },
343 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
344 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" {
345 _, _ = w.Write([]byte(`[{"id":1,"body":"### This pull request was validated by [pint](https://github.com/cloudflare/pint).\nxxxx"}]`))
346 return
347 }
348 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/comments" {
349 _, _ = w.Write([]byte(`[{"id":1,"commit_id":"fake-commit-id","path":"foo.txt","line":2,"body":":stop_sign: [mock](https://cloudflare.github.io/pint/checks/mock.html): syntax error\n\nsyntax details"}]`))
350 return
351 }
352 if r.Method == http.MethodPost && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/comments" {
353 body, _ := io.ReadAll(r.Body)
354 b := strings.TrimSpace(strings.TrimRight(string(body), "\n\t\r"))
355 switch b {
356 case `{"body":":stop_sign: [mock1](https://cloudflare.github.io/pint/checks/mock1.html): syntax error1\n\nsyntax details1","path":"","line":2,"side":"RIGHT","commit_id":"fake-commit-id"}`:
357 case `{"body":":stop_sign: [mock2](https://cloudflare.github.io/pint/checks/mock2.html): syntax error2\n\nsyntax details2","path":"","line":2,"side":"RIGHT","commit_id":"fake-commit-id"}`:
358 case `{"body":"This pint run would create 4 comment(s), which is more than 2 limit configured for pint.\n2 comments were skipped and won't be visibile on this PR."}`:
359 default:
360 t.Errorf("Unexpected comment: %s", b)
361 }
362 }
363 _, _ = w.Write([]byte(""))
364 }),
365 reports: []reporter.Report{
366 {
367 SourcePath: "foo.txt",
368 ModifiedLines: []int{2},
369 Rule: mockRules[1],
370 Problem: checks.Problem{
371 Lines: parser.LineRange{
372 First: 2,
373 Last: 2,
374 },
375 Reporter: "mock1",
376 Text: "syntax error1",
377 Details: "syntax details1",
378 Severity: checks.Bug,
379 },
380 },
381 {
382 SourcePath: "foo.txt",
383 ModifiedLines: []int{2},
384 Rule: mockRules[1],
385 Problem: checks.Problem{
386 Lines: parser.LineRange{
387 First: 2,
388 Last: 2,
389 },
390 Reporter: "mock2",
391 Text: "syntax error2",
392 Details: "syntax details2",
393 Severity: checks.Bug,
394 },
395 },
396 {
397 SourcePath: "foo.txt",
398 ModifiedLines: []int{2},
399 Rule: mockRules[1],
400 Problem: checks.Problem{
401 Lines: parser.LineRange{
402 First: 2,
403 Last: 2,
404 },
405 Reporter: "mock3",
406 Text: "syntax error3",
407 Details: "syntax details3",
408 Severity: checks.Fatal,
409 },
410 },
411 {
412 SourcePath: "foo.txt",
413 ModifiedLines: []int{2},
414 Rule: mockRules[1],
415 Problem: checks.Problem{
416 Lines: parser.LineRange{
417 First: 2,
418 Last: 2,
419 },
420 Reporter: "mock4",
421 Text: "syntax error4",
422 Details: "syntax details4",
423 Severity: checks.Fatal,
424 },
425 },
426 },
427 },
428 } {
429 t.Run(tc.description, func(t *testing.T) {
430 slog.SetDefault(slogt.New(t))
431
432 var handler http.Handler
433 if tc.httpHandler != nil {
434 handler = tc.httpHandler
435 } else {
436 // Handler that checks for token.
437 handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
438 auth := r.Header["Authorization"]
439 if len(auth) == 0 {
440 w.WriteHeader(500)
441 _, _ = w.Write([]byte("No token"))
442 t.Fatal("got a request with no token")
443 return
444 }
445 token := auth[0]
446 if token != fmt.Sprintf("Bearer %s", tc.token) {
447 w.WriteHeader(500)
448 _, _ = w.Write([]byte("Invalid token"))
449 t.Fatalf("got a request with invalid token (got %s)", token)
450 }
451 })
452 }
453 srv := httptest.NewServer(handler)
454 defer srv.Close()
455 r, err := reporter.NewGithubReporter(
456 "v0.0.0",
457 srv.URL,
458 srv.URL,
459 tc.timeout,
460 tc.token,
461 tc.owner,
462 tc.repo,
463 tc.prNum,
464 tc.maxComments,
465 tc.gitCmd,
466 )
467 require.NoError(t, err)
468
469 err = r.Submit(reporter.NewSummary(tc.reports))
470 if tc.error == nil {
471 require.NoError(t, err)
472 } else {
473 require.EqualError(t, err, tc.error(srv.URL))
474 }
475 })
476 }
477}
478