cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.66.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/reporter/github_test.go

918lines · modecode

1package reporter_test
2
3import (
4 "context"
5 "fmt"
6 "io"
7 "log/slog"
8 "net/http"
9 "net/http/httptest"
10 "strings"
11 "testing"
12 "time"
13
14 "github.com/neilotoole/slogt"
15 "github.com/stretchr/testify/require"
16
17 "github.com/cloudflare/pint/internal/checks"
18 "github.com/cloudflare/pint/internal/discovery"
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
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(false)
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 for _, tc := range []testCaseT{
48 {
49 description: "list files error",
50 owner: "foo",
51 repo: "bar",
52 token: "something",
53 prNum: 123,
54 maxComments: 50,
55 timeout: time.Second,
56 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
57 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/files" {
58 w.WriteHeader(http.StatusBadRequest)
59 _, _ = w.Write([]byte("Error"))
60 return
61 }
62 _, _ = w.Write([]byte(""))
63 }),
64 error: func(uri string) string {
65 return fmt.Sprintf("failed to list pull request files: GET %s/api/v3/repos/foo/bar/pulls/123/files: 400 []", uri)
66 },
67 reports: []reporter.Report{
68 {
69 Path: discovery.Path{
70 Name: "foo.txt",
71 SymlinkTarget: "foo.txt",
72 },
73
74 ModifiedLines: []int{2},
75 Rule: mockRules[1],
76 Problem: checks.Problem{
77 Lines: parser.LineRange{
78 First: 2,
79 Last: 2,
80 },
81 Reporter: "mock",
82 Text: "syntax error",
83 Details: "syntax details",
84 Severity: checks.Fatal,
85 },
86 },
87 },
88 },
89 {
90 description: "list pull reviews timeout",
91 owner: "foo",
92 repo: "bar",
93 token: "something",
94 prNum: 123,
95 maxComments: 50,
96 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
97 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/files" {
98 w.WriteHeader(http.StatusOK)
99 _, _ = w.Write([]byte("[]"))
100 return
101 }
102 time.Sleep(1 * time.Second)
103 _, _ = w.Write([]byte("OK"))
104 }),
105 timeout: 100 * time.Millisecond,
106 error: func(_ string) string {
107 return "failed to list pull request reviews: context deadline exceeded"
108 },
109 reports: []reporter.Report{
110 {
111 Path: discovery.Path{
112 Name: "$1",
113 SymlinkTarget: "$1",
114 },
115
116 ModifiedLines: []int{2},
117 Rule: mockRules[1],
118 Problem: checks.Problem{
119 Lines: parser.LineRange{
120 First: 2,
121 Last: 2,
122 },
123 Reporter: "mock",
124 Text: "syntax error",
125 Severity: checks.Fatal,
126 },
127 },
128 },
129 },
130 {
131 description: "list reviews error",
132 owner: "foo",
133 repo: "bar",
134 token: "something",
135 prNum: 123,
136 maxComments: 50,
137 timeout: time.Second,
138 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
139 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" {
140 w.WriteHeader(http.StatusBadRequest)
141 _, _ = w.Write([]byte("Error"))
142 return
143 }
144 _, _ = w.Write([]byte(""))
145 }),
146 error: func(uri string) string {
147 return fmt.Sprintf("failed to list pull request reviews: GET %s/api/v3/repos/foo/bar/pulls/123/reviews: 400 []", uri)
148 },
149 reports: []reporter.Report{
150 {
151 Path: discovery.Path{
152 Name: "foo.txt",
153 SymlinkTarget: "foo.txt",
154 },
155
156 ModifiedLines: []int{2},
157 Rule: mockRules[1],
158 Problem: checks.Problem{
159 Lines: parser.LineRange{
160 First: 2,
161 Last: 2,
162 },
163 Reporter: "mock",
164 Text: "syntax error",
165 Details: "syntax details",
166 Severity: checks.Fatal,
167 },
168 },
169 },
170 },
171 {
172 description: "no problems",
173 owner: "foo",
174 repo: "bar",
175 token: "something",
176 prNum: 123,
177 maxComments: 50,
178 timeout: time.Second,
179 reports: []reporter.Report{},
180 },
181 {
182 description: "happy path",
183 owner: "foo",
184 repo: "bar",
185 token: "something",
186 prNum: 123,
187 maxComments: 50,
188 timeout: time.Second,
189 reports: []reporter.Report{
190 {
191 Path: discovery.Path{
192 Name: "foo.txt",
193 SymlinkTarget: "foo.txt",
194 },
195
196 ModifiedLines: []int{2},
197 Rule: mockRules[1],
198 Problem: checks.Problem{
199 Lines: parser.LineRange{
200 First: 2,
201 Last: 2,
202 },
203 Reporter: "mock",
204 Text: "syntax error",
205 Details: "syntax details",
206 Severity: checks.Fatal,
207 },
208 },
209 },
210 },
211 {
212 description: "error crating review",
213 owner: "foo",
214 repo: "bar",
215 token: "something",
216 prNum: 123,
217 maxComments: 50,
218 timeout: time.Second,
219 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
220 if r.Method == http.MethodPost && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" {
221 w.WriteHeader(http.StatusBadGateway)
222 _, _ = w.Write([]byte("Error"))
223 return
224 }
225 _, _ = w.Write([]byte(""))
226 }),
227 error: func(uri string) string {
228 return fmt.Sprintf("failed to create pull request review: POST %s/api/v3/repos/foo/bar/pulls/123/reviews: 502 []", uri)
229 },
230 reports: []reporter.Report{
231 {
232 Path: discovery.Path{
233 Name: "foo.txt",
234 SymlinkTarget: "foo.txt",
235 },
236
237 ModifiedLines: []int{2},
238 Rule: mockRules[1],
239 Problem: checks.Problem{
240 Lines: parser.LineRange{
241 First: 2,
242 Last: 2,
243 },
244 Reporter: "mock",
245 Text: "syntax error",
246 Details: "syntax details",
247 Severity: checks.Fatal,
248 },
249 },
250 },
251 },
252 {
253 description: "error updating existing review",
254 owner: "foo",
255 repo: "bar",
256 token: "something",
257 prNum: 123,
258 maxComments: 50,
259 timeout: time.Second,
260 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
261 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" {
262 _, _ = w.Write([]byte(`[{"id":1,"body":"### This pull request was validated by [pint](https://github.com/cloudflare/pint).\nxxxx"}]`))
263 return
264 }
265 if r.Method == http.MethodPut && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews/1" {
266 w.WriteHeader(http.StatusBadGateway)
267 _, _ = w.Write([]byte("Error"))
268 return
269 }
270 _, _ = w.Write([]byte(""))
271 }),
272 error: func(uri string) string {
273 return fmt.Sprintf("failed to update pull request review: PUT %s/api/v3/repos/foo/bar/pulls/123/reviews/1: 502 []", uri)
274 },
275 reports: []reporter.Report{
276 {
277 Path: discovery.Path{
278 Name: "foo.txt",
279 SymlinkTarget: "foo.txt",
280 },
281
282 ModifiedLines: []int{2},
283 Rule: mockRules[1],
284 Problem: checks.Problem{
285 Lines: parser.LineRange{
286 First: 2,
287 Last: 2,
288 },
289 Reporter: "mock",
290 Text: "syntax error",
291 Details: "syntax details",
292 Severity: checks.Fatal,
293 },
294 },
295 },
296 },
297 {
298 description: "update existing review",
299 owner: "foo",
300 repo: "bar",
301 token: "something",
302 prNum: 123,
303 maxComments: 50,
304 timeout: time.Second,
305 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
306 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" {
307 _, _ = w.Write([]byte(`[{"id":1,"body":"### This pull request was validated by [pint](https://github.com/cloudflare/pint).\nxxxx"}]`))
308 return
309 }
310 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/comments" {
311 _, _ = 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"}]`))
312 return
313 }
314 _, _ = w.Write([]byte(""))
315 }),
316 reports: []reporter.Report{
317 {
318 Path: discovery.Path{
319 Name: "foo.txt",
320 SymlinkTarget: "foo.txt",
321 },
322
323 ModifiedLines: []int{2},
324 Rule: mockRules[1],
325 Problem: checks.Problem{
326 Lines: parser.LineRange{
327 First: 2,
328 Last: 2,
329 },
330 Reporter: "mock",
331 Text: "syntax error",
332 Details: "syntax details",
333 Severity: checks.Fatal,
334 },
335 },
336 },
337 },
338 {
339 description: "maxComments 2",
340 owner: "foo",
341 repo: "bar",
342 token: "something",
343 prNum: 123,
344 maxComments: 2,
345 timeout: time.Second,
346 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
347 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" {
348 _, _ = w.Write([]byte(`[{"id":1,"body":"### This pull request was validated by [pint](https://github.com/cloudflare/pint).\nxxxx"}]`))
349 return
350 }
351 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/comments" {
352 _, _ = 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"}]`))
353 return
354 }
355 if r.Method == http.MethodPost && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/comments" {
356 body, _ := io.ReadAll(r.Body)
357 b := strings.TrimSpace(strings.TrimRight(string(body), "\n\t\r"))
358 switch b {
359 case `{"body":":stop_sign: **Bug** reported by [pint](https://cloudflare.github.io/pint/) **mock1** check.\n\n------\n\nsyntax error1\n\nsyntax details1\n\n------\n\n:information_source: To see documentation covering this check and instructions on how to resolve it [click here](https://cloudflare.github.io/pint/checks/mock1.html).\n","path":"foo.txt","line":2,"side":"RIGHT","commit_id":"HEAD"}`:
360 case `{"body":":stop_sign: **Bug** reported by [pint](https://cloudflare.github.io/pint/) **mock2** check.\n\n------\n\nsyntax error2\n\nsyntax details2\n\n------\n\n:information_source: To see documentation covering this check and instructions on how to resolve it [click here](https://cloudflare.github.io/pint/checks/mock2.html).\n","path":"foo.txt","line":2,"side":"RIGHT","commit_id":"HEAD"}`:
361 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."}`:
362 default:
363 t.Errorf("Unexpected comment: %s", b)
364 }
365 }
366 _, _ = w.Write([]byte(""))
367 }),
368 reports: []reporter.Report{
369 {
370 Path: discovery.Path{
371 Name: "foo.txt",
372 SymlinkTarget: "foo.txt",
373 },
374
375 ModifiedLines: []int{2},
376 Rule: mockRules[1],
377 Problem: checks.Problem{
378 Lines: parser.LineRange{
379 First: 2,
380 Last: 2,
381 },
382 Reporter: "mock1",
383 Text: "syntax error1",
384 Details: "syntax details1",
385 Severity: checks.Bug,
386 },
387 },
388 {
389 Path: discovery.Path{
390 Name: "foo.txt",
391 SymlinkTarget: "foo.txt",
392 },
393
394 ModifiedLines: []int{2},
395 Rule: mockRules[1],
396 Problem: checks.Problem{
397 Lines: parser.LineRange{
398 First: 2,
399 Last: 2,
400 },
401 Reporter: "mock2",
402 Text: "syntax error2",
403 Details: "syntax details2",
404 Severity: checks.Bug,
405 },
406 },
407 {
408 Path: discovery.Path{
409 Name: "foo.txt",
410 SymlinkTarget: "foo.txt",
411 },
412
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: "mock3",
421 Text: "syntax error3",
422 Details: "syntax details3",
423 Severity: checks.Fatal,
424 },
425 },
426 {
427 Path: discovery.Path{
428 Name: "foo.txt",
429 SymlinkTarget: "foo.txt",
430 },
431
432 ModifiedLines: []int{2},
433 Rule: mockRules[1],
434 Problem: checks.Problem{
435 Lines: parser.LineRange{
436 First: 2,
437 Last: 2,
438 },
439 Reporter: "mock4",
440 Text: "syntax error4",
441 Details: "syntax details4",
442 Severity: checks.Fatal,
443 },
444 },
445 },
446 },
447 {
448 description: "maxComments 2, too many comments comment error",
449 owner: "foo",
450 repo: "bar",
451 token: "something",
452 prNum: 123,
453 maxComments: 2,
454 timeout: time.Second,
455 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
456 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" {
457 _, _ = w.Write([]byte(`[{"id":1,"body":"### This pull request was validated by [pint](https://github.com/cloudflare/pint).\nxxxx"}]`))
458 return
459 }
460 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/comments" {
461 _, _ = 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"}]`))
462 return
463 }
464 if r.Method == http.MethodPost && r.URL.Path == "/api/v3/repos/foo/bar/issues/123/comments" {
465 body, _ := io.ReadAll(r.Body)
466 b := strings.TrimSpace(strings.TrimRight(string(body), "\n\t\r"))
467 if b == `{"body":"This pint run would create 4 comment(s), which is more than the limit configured for pint (2).\n2 comment(s) were skipped and won't be visibile on this PR."}` {
468 w.WriteHeader(http.StatusInternalServerError)
469 _, _ = w.Write([]byte("Cannot create issue comment"))
470 return
471 }
472 }
473 _, _ = w.Write([]byte(""))
474 }),
475 reports: []reporter.Report{
476 {
477 Path: discovery.Path{
478 Name: "foo.txt",
479 SymlinkTarget: "foo.txt",
480 },
481
482 ModifiedLines: []int{2},
483 Rule: mockRules[1],
484 Problem: checks.Problem{
485 Lines: parser.LineRange{
486 First: 2,
487 Last: 2,
488 },
489 Reporter: "mock1",
490 Text: "syntax error1",
491 Details: "syntax details1",
492 Severity: checks.Bug,
493 },
494 },
495 {
496 Path: discovery.Path{
497 Name: "foo.txt",
498 SymlinkTarget: "foo.txt",
499 },
500
501 ModifiedLines: []int{2},
502 Rule: mockRules[1],
503 Problem: checks.Problem{
504 Lines: parser.LineRange{
505 First: 2,
506 Last: 2,
507 },
508 Reporter: "mock2",
509 Text: "syntax error2",
510 Details: "syntax details2",
511 Severity: checks.Bug,
512 },
513 },
514 {
515 Path: discovery.Path{
516 Name: "foo.txt",
517 SymlinkTarget: "foo.txt",
518 },
519
520 ModifiedLines: []int{2},
521 Rule: mockRules[1],
522 Problem: checks.Problem{
523 Lines: parser.LineRange{
524 First: 2,
525 Last: 2,
526 },
527 Reporter: "mock3",
528 Text: "syntax error3",
529 Details: "syntax details3",
530 Severity: checks.Fatal,
531 },
532 },
533 {
534 Path: discovery.Path{
535 Name: "foo.txt",
536 SymlinkTarget: "foo.txt",
537 },
538
539 ModifiedLines: []int{2},
540 Rule: mockRules[1],
541 Problem: checks.Problem{
542 Lines: parser.LineRange{
543 First: 2,
544 Last: 2,
545 },
546 Reporter: "mock4",
547 Text: "syntax error4",
548 Details: "syntax details4",
549 Severity: checks.Fatal,
550 },
551 },
552 },
553 },
554 {
555 description: "general comment error",
556 owner: "foo",
557 repo: "bar",
558 token: "something",
559 prNum: 123,
560 maxComments: 2,
561 timeout: time.Second,
562 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
563 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/files" {
564 w.WriteHeader(http.StatusOK)
565 _, _ = w.Write([]byte(`[{"filename":"foo.txt"}]`))
566 return
567 }
568 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" {
569 _, _ = w.Write([]byte(`[{"id":1,"body":"### This pull request was validated by [pint](https://github.com/cloudflare/pint).\nxxxx"}]`))
570 return
571 }
572 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/comments" {
573 _, _ = 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"}]`))
574 return
575 }
576 if r.Method == http.MethodPost && r.URL.Path == "/api/v3/repos/foo/bar/issues/123/comments" {
577 w.WriteHeader(http.StatusInternalServerError)
578 _, _ = w.Write([]byte("Cannot create issue comment"))
579 return
580 }
581 _, _ = w.Write([]byte(""))
582 }),
583 error: func(uri string) string {
584 return fmt.Sprintf("failed to create general comment: POST %s/api/v3/repos/foo/bar/issues/123/comments: 500 []", uri)
585 },
586 reports: []reporter.Report{
587 {
588 Path: discovery.Path{
589 Name: "foo.txt",
590 SymlinkTarget: "foo.txt",
591 },
592
593 ModifiedLines: []int{2},
594 Rule: mockRules[1],
595 Problem: checks.Problem{
596 Lines: parser.LineRange{
597 First: 2,
598 Last: 2,
599 },
600 Reporter: "mock1",
601 Text: "syntax error1",
602 Details: "syntax details1",
603 Severity: checks.Bug,
604 },
605 },
606 {
607 Path: discovery.Path{
608 Name: "foo.txt",
609 SymlinkTarget: "foo.txt",
610 },
611
612 ModifiedLines: []int{2},
613 Rule: mockRules[1],
614 Problem: checks.Problem{
615 Lines: parser.LineRange{
616 First: 2,
617 Last: 2,
618 },
619 Reporter: "mock2",
620 Text: "syntax error2",
621 Details: "syntax details2",
622 Severity: checks.Bug,
623 },
624 },
625 {
626 Path: discovery.Path{
627 Name: "foo.txt",
628 SymlinkTarget: "foo.txt",
629 },
630
631 ModifiedLines: []int{2},
632 Rule: mockRules[1],
633 Problem: checks.Problem{
634 Lines: parser.LineRange{
635 First: 2,
636 Last: 2,
637 },
638 Reporter: "mock3",
639 Text: "syntax error3",
640 Details: "syntax details3",
641 Severity: checks.Fatal,
642 },
643 },
644 {
645 Path: discovery.Path{
646 Name: "foo.txt",
647 SymlinkTarget: "foo.txt",
648 },
649
650 ModifiedLines: []int{2},
651 Rule: mockRules[1],
652 Problem: checks.Problem{
653 Lines: parser.LineRange{
654 First: 2,
655 Last: 2,
656 },
657 Reporter: "mock4",
658 Text: "syntax error4",
659 Details: "syntax details4",
660 Severity: checks.Fatal,
661 },
662 },
663 },
664 },
665 {
666 description: "modified line",
667 owner: "foo",
668 repo: "bar",
669 token: "something",
670 prNum: 123,
671 maxComments: 50,
672 timeout: time.Second,
673 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
674 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" {
675 _, _ = w.Write([]byte(`[]`))
676 return
677 }
678 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/comments" {
679 _, _ = w.Write([]byte(`[]`))
680 return
681 }
682 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/files" {
683 w.WriteHeader(http.StatusOK)
684 _, _ = w.Write([]byte(`[{"filename":"foo.txt", "patch": "@@ -1,4 +1,4 @@ - record: target is down\n- expr: up == 1\n+ expr: up == 0\n - record: sum errors\n expr: sum(errors) by (job)"}]`))
685 return
686 }
687 if r.Method == http.MethodPost && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/comments" {
688 body, _ := io.ReadAll(r.Body)
689 b := strings.TrimSpace(strings.TrimRight(string(body), "\n\t\r"))
690 if b != `{"body":":stop_sign: **Fatal** reported by [pint](https://cloudflare.github.io/pint/) **mock** check.\n\n------\n\nsyntax error\n\nsyntax details\n\n------\n\n:information_source: To see documentation covering this check and instructions on how to resolve it [click here](https://cloudflare.github.io/pint/checks/mock.html).\n","path":"foo.txt","line":1,"side":"RIGHT","commit_id":"HEAD"}` {
691 t.Errorf("Unexpected comment: %s", b)
692 t.FailNow()
693 }
694 }
695 _, _ = w.Write([]byte(""))
696 }),
697 reports: []reporter.Report{
698 {
699 Path: discovery.Path{
700 Name: "foo.txt",
701 SymlinkTarget: "foo.txt",
702 },
703
704 ModifiedLines: []int{1},
705 Rule: mockRules[0],
706 Problem: checks.Problem{
707 Lines: parser.LineRange{
708 First: 1,
709 Last: 1,
710 },
711 Reporter: "mock",
712 Text: "syntax error",
713 Details: "syntax details",
714 Severity: checks.Fatal,
715 },
716 },
717 },
718 },
719 {
720 description: "unmodified line",
721 owner: "foo",
722 repo: "bar",
723 token: "something",
724 prNum: 123,
725 maxComments: 50,
726 timeout: time.Second,
727 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
728 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" {
729 _, _ = w.Write([]byte(`[]`))
730 return
731 }
732 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/comments" {
733 _, _ = w.Write([]byte(`[]`))
734 return
735 }
736 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/files" {
737 w.WriteHeader(http.StatusOK)
738 _, _ = w.Write([]byte(`[{"filename":"foo.txt", "patch": "@@ -1,4 +1,4 @@ - record: target is down\n- expr: up == 1\n+ expr: up == 0\n - record: sum errors\n expr: sum(errors) by (job)"}]`))
739 return
740 }
741 if r.Method == http.MethodPost && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/comments" {
742 body, _ := io.ReadAll(r.Body)
743 b := strings.TrimSpace(strings.TrimRight(string(body), "\n\t\r"))
744 if b != `{"body":":stop_sign: **Fatal** reported by [pint](https://cloudflare.github.io/pint/) **mock** check.\n\n------\n\nsyntax error\n\nsyntax details\n\n------\n\n:information_source: To see documentation covering this check and instructions on how to resolve it [click here](https://cloudflare.github.io/pint/checks/mock.html).\n","path":"foo.txt","line":1,"side":"RIGHT","commit_id":"HEAD"}` {
745 t.Errorf("Unexpected comment: %s", b)
746 t.FailNow()
747 }
748 }
749 _, _ = w.Write([]byte(""))
750 }),
751 reports: []reporter.Report{
752 {
753 Path: discovery.Path{
754 Name: "foo.txt",
755 SymlinkTarget: "foo.txt",
756 },
757
758 ModifiedLines: []int{2},
759 Rule: mockRules[1],
760 Problem: checks.Problem{
761 Lines: parser.LineRange{
762 First: 2,
763 Last: 2,
764 },
765 Reporter: "mock",
766 Text: "syntax error",
767 Details: "syntax details",
768 Severity: checks.Fatal,
769 },
770 },
771 },
772 },
773 {
774 description: "removed line",
775 owner: "foo",
776 repo: "bar",
777 token: "something",
778 prNum: 123,
779 maxComments: 50,
780 timeout: time.Second,
781 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
782 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" {
783 _, _ = w.Write([]byte(`[]`))
784 return
785 }
786 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/comments" {
787 _, _ = w.Write([]byte(`[]`))
788 return
789 }
790 if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/files" {
791 w.WriteHeader(http.StatusOK)
792 _, _ = w.Write([]byte(`[{"filename":"foo.txt", "patch": "@@ -1,5 +1,4 @@\n - record: target is down\n expr: up == 0\n- labels: {}\n - record: sum errors\n expr: sum(errors) by (job)"}]`))
793 return
794 }
795 if r.Method == http.MethodPost && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/comments" {
796 body, _ := io.ReadAll(r.Body)
797 b := strings.TrimSpace(strings.TrimRight(string(body), "\n\t\r"))
798 if b != `{"body":":stop_sign: **Fatal** reported by [pint](https://cloudflare.github.io/pint/) **mock** check.\n\n------\n\nsyntax error\n\nsyntax details\n\n------\n\n:information_source: To see documentation covering this check and instructions on how to resolve it [click here](https://cloudflare.github.io/pint/checks/mock.html).\n","path":"foo.txt","line":3,"side":"LEFT","commit_id":"HEAD"}` {
799 t.Errorf("Unexpected comment: %s", b)
800 t.FailNow()
801 }
802 }
803 _, _ = w.Write([]byte(""))
804 }),
805 reports: []reporter.Report{
806 {
807 Path: discovery.Path{
808 Name: "foo.txt",
809 SymlinkTarget: "foo.txt",
810 },
811
812 ModifiedLines: []int{3},
813 Rule: mockRules[0],
814 Problem: checks.Problem{
815 Lines: parser.LineRange{
816 First: 3,
817 Last: 3,
818 },
819 Reporter: "mock",
820 Text: "syntax error",
821 Details: "syntax details",
822 Severity: checks.Fatal,
823 Anchor: checks.AnchorBefore,
824 },
825 },
826 },
827 },
828 {
829 description: "review comment",
830 owner: "foo",
831 repo: "bar",
832 token: "something",
833 prNum: 123,
834 maxComments: 50,
835 timeout: time.Second,
836 httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
837 if r.Method == http.MethodPost && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" {
838 body, _ := io.ReadAll(r.Body)
839 b := strings.TrimSpace(strings.TrimRight(string(body), "\n\t\r"))
840 if b != `{"commit_id":"HEAD","body":"### This pull request was validated by [pint](https://github.com/cloudflare/pint).\n:heavy_exclamation_mark:\tProblems found.\n| Severity | Number of problems |\n| --- | --- |\n| Fatal | 1 |\n<details><summary>Stats</summary>\n<p>\n\n| Stat | Value |\n| --- | --- |\n| Version | v0.0.0 |\n| Number of rules parsed | 0 |\n| Number of rules checked | 0 |\n| Number of problems found | 1 |\n| Number of offline checks | 0 |\n| Number of online checks | 0 |\n| Checks duration | 0 |\n\n</p>\n</details>\n\n<details><summary>Problems</summary>\n<p>\n\nFailed to generate list of problems: open foo.txt: no such file or directory\n</p>\n</details>\n\n","event":"COMMENT"}` {
841 t.Errorf("Unexpected comment: %s", b)
842 t.FailNow()
843 }
844 }
845 _, _ = w.Write([]byte(""))
846 }),
847 reports: []reporter.Report{
848 {
849 Path: discovery.Path{
850 Name: "foo.txt",
851 SymlinkTarget: "foo.txt",
852 },
853
854 ModifiedLines: []int{1},
855 Rule: mockRules[0],
856 Problem: checks.Problem{
857 Lines: parser.LineRange{
858 First: 1,
859 Last: 1,
860 },
861 Reporter: "mock",
862 Text: "syntax error",
863 Details: "syntax details",
864 Severity: checks.Fatal,
865 },
866 },
867 },
868 },
869 } {
870 t.Run(tc.description, func(t *testing.T) {
871 slog.SetDefault(slogt.New(t))
872
873 var handler http.Handler
874 if tc.httpHandler != nil {
875 handler = tc.httpHandler
876 } else {
877 // Handler that checks for token.
878 handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
879 auth := r.Header["Authorization"]
880 if len(auth) == 0 {
881 w.WriteHeader(500)
882 _, _ = w.Write([]byte("No token"))
883 t.Fatal("got a request with no token")
884 return
885 }
886 token := auth[0]
887 if token != "Bearer "+tc.token {
888 w.WriteHeader(500)
889 _, _ = w.Write([]byte("Invalid token"))
890 t.Fatalf("got a request with invalid token (got %s)", token)
891 }
892 })
893 }
894 srv := httptest.NewServer(handler)
895 defer srv.Close()
896 r, err := reporter.NewGithubReporter(
897 "v0.0.0",
898 srv.URL,
899 srv.URL,
900 tc.timeout,
901 tc.token,
902 tc.owner,
903 tc.repo,
904 tc.prNum,
905 tc.maxComments,
906 "HEAD",
907 )
908 require.NoError(t, err)
909
910 err = reporter.Submit(context.Background(), reporter.NewSummary(tc.reports), r)
911 if tc.error == nil {
912 require.NoError(t, err)
913 } else {
914 require.EqualError(t, err, tc.error(srv.URL))
915 }
916 })
917 }
918}