cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.62.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/reporter/github_test.go

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