Skip to content

Commit c913231

Browse files
committed
Capture more HTTP snapshots
1 parent 969355d commit c913231

26 files changed

+2674
-88
lines changed

cmd/pint/main_test.go

Lines changed: 81 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"bytes"
45
"context"
56
"crypto/rand"
67
"crypto/rsa"
@@ -9,18 +10,21 @@ import (
910
"encoding/pem"
1011
"errors"
1112
"fmt"
13+
"io"
1214
"math/big"
1315
"net"
1416
"net/http"
1517
"os"
1618
"path"
1719
"regexp"
20+
"slices"
1821
"strconv"
1922
"strings"
2023
"sync"
2124
"testing"
2225
"time"
2326

27+
"github.com/itchyny/json2yaml"
2428
"github.com/rogpeppe/go-internal/testscript"
2529
)
2630

@@ -54,7 +58,6 @@ func httpServer(ts *testscript.TestScript, _ bool, args []string) {
5458
cmd := args[0]
5559

5660
switch cmd {
57-
// http response name /200 200 OK
5861
case "response":
5962
if len(args) < 5 {
6063
ts.Fatalf("! http response command requires '$NAME $PATH $CODE $BODY' args, got [%s]", strings.Join(args, " "))
@@ -64,9 +67,10 @@ func httpServer(ts *testscript.TestScript, _ bool, args []string) {
6467
code, err := strconv.Atoi(args[3])
6568
ts.Check(err)
6669
body := strings.Join(args[4:], " ")
67-
mocks.add(name, httpMock{pattern: path, handler: func(w http.ResponseWriter, _ *http.Request) {
70+
mocks.add(name, httpMock{pattern: path, handler: func(w http.ResponseWriter, r *http.Request) {
71+
snapshotRequest(ts, r, name, path)
6872
w.WriteHeader(code)
69-
_, err := w.Write([]byte(body))
73+
_, err = w.Write([]byte(body))
7074
ts.Check(err)
7175
}})
7276
case "method":
@@ -79,7 +83,8 @@ func httpServer(ts *testscript.TestScript, _ bool, args []string) {
7983
code, err := strconv.Atoi(args[4])
8084
ts.Check(err)
8185
body := strings.Join(args[5:], " ")
82-
mocks.add(name, httpMock{pattern: path, method: meth, handler: func(w http.ResponseWriter, _ *http.Request) {
86+
mocks.add(name, httpMock{pattern: path, method: meth, handler: func(w http.ResponseWriter, r *http.Request) {
87+
snapshotRequest(ts, r, name, path)
8388
w.WriteHeader(code)
8489
_, err := w.Write([]byte(body))
8590
ts.Check(err)
@@ -99,6 +104,7 @@ func httpServer(ts *testscript.TestScript, _ bool, args []string) {
99104
mocks.add(name, httpMock{pattern: path, handler: func(w http.ResponseWriter, r *http.Request) {
100105
username, password, ok := r.BasicAuth()
101106
if ok && username == user && password == pass {
107+
snapshotRequest(ts, r, name, path)
102108
w.WriteHeader(code)
103109
_, err := w.Write([]byte(body))
104110
ts.Check(err)
@@ -118,7 +124,8 @@ func httpServer(ts *testscript.TestScript, _ bool, args []string) {
118124
code, err := strconv.Atoi(args[4])
119125
ts.Check(err)
120126
body := strings.Join(args[5:], " ")
121-
mocks.add(name, httpMock{pattern: path, handler: func(w http.ResponseWriter, _ *http.Request) {
127+
mocks.add(name, httpMock{pattern: path, handler: func(w http.ResponseWriter, r *http.Request) {
128+
snapshotRequest(ts, r, name, path)
122129
time.Sleep(delay)
123130
w.WriteHeader(code)
124131
_, err := w.Write([]byte(body))
@@ -132,7 +139,8 @@ func httpServer(ts *testscript.TestScript, _ bool, args []string) {
132139
name := args[1]
133140
srcpath := regexp.MustCompile(args[2])
134141
dstpath := args[3]
135-
mocks.add(name, httpMock{pattern: srcpath, handler: func(w http.ResponseWriter, _ *http.Request) {
142+
mocks.add(name, httpMock{pattern: srcpath, handler: func(w http.ResponseWriter, r *http.Request) {
143+
snapshotRequest(ts, r, name, srcpath)
136144
w.Header().Set("Location", dstpath)
137145
w.WriteHeader(http.StatusFound)
138146
}})
@@ -151,6 +159,7 @@ func httpServer(ts *testscript.TestScript, _ bool, args []string) {
151159
tlsKey = args[4]
152160
}
153161

162+
var mtx sync.Mutex
154163
mux := http.NewServeMux()
155164
mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
156165
var done bool
@@ -163,8 +172,10 @@ func httpServer(ts *testscript.TestScript, _ bool, args []string) {
163172
if !mock.pattern.MatchString(r.URL.Path) {
164173
continue
165174
}
175+
mtx.Lock()
166176
mock.handler(w, r)
167177
done = true
178+
mtx.Unlock()
168179
break
169180
}
170181
break
@@ -331,3 +342,67 @@ func writeCert(ts *testscript.TestScript, dirname, filename string, block *pem.B
331342

332343
ts.Logf("Wrote PEM file to %s", filename)
333344
}
345+
346+
func snapshotRequest(ts *testscript.TestScript, r *http.Request, name string, path *regexp.Regexp) {
347+
payload, err := io.ReadAll(r.Body)
348+
ts.Check(err)
349+
r.Body.Close()
350+
351+
var buf strings.Builder
352+
buf.WriteString(r.Method)
353+
buf.WriteRune(' ')
354+
buf.WriteString(path.String())
355+
buf.WriteRune('\n')
356+
357+
hKeys := make([]string, 0, len(r.Header))
358+
for k := range r.Header {
359+
if k == "Content-Length" || k == "User-Agent" {
360+
continue
361+
}
362+
hKeys = append(hKeys, k)
363+
}
364+
slices.Sort(hKeys)
365+
for _, k := range hKeys {
366+
buf.WriteString(" ")
367+
buf.WriteString(k)
368+
buf.WriteRune(':')
369+
buf.WriteRune(' ')
370+
buf.WriteString(strings.Join(r.Header.Values(k), ";"))
371+
buf.WriteRune('\n')
372+
}
373+
374+
if len(payload) > 0 {
375+
payload = sanitizePayload(payload)
376+
buf.WriteString("--- BODY ---\n")
377+
buf.Write(payload)
378+
if !bytes.HasSuffix(payload, []byte("\n")) {
379+
buf.WriteRune('\n')
380+
}
381+
buf.WriteString("--- END ---\n")
382+
}
383+
buf.WriteRune('\n')
384+
385+
f, err := os.OpenFile(ts.MkAbs(name+".got"), os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0o644)
386+
ts.Check(err)
387+
_, err = f.WriteString(buf.String())
388+
ts.Check(err)
389+
ts.Check(f.Close())
390+
}
391+
392+
func sanitizePayload(payload []byte) []byte {
393+
bbDurationRe := regexp.MustCompile(`\{"value":([0-9]+),"title":"Checks duration","type":"DURATION"\}`)
394+
payload = bbDurationRe.ReplaceAll(payload, []byte(`{"value":0,"title":"Checks duration","type":"DURATION"}`))
395+
396+
ghDurationRe := regexp.MustCompile(`\| Checks duration \| ([0-9]+[a-z]+) \|`)
397+
payload = ghDurationRe.ReplaceAll(payload, []byte(`| Checks duration | 0 |`))
398+
399+
commitIDRe := regexp.MustCompile(`"commit_id":"([0-9a-zA-Z]{40})"`)
400+
payload = commitIDRe.ReplaceAll(payload, []byte(`"commit_id":"<COMMIT ID>"`))
401+
402+
var output bytes.Buffer
403+
if err := json2yaml.Convert(&output, bytes.NewReader(payload)); err != nil {
404+
return payload
405+
}
406+
407+
return output.Bytes()
408+
}

cmd/pint/tests/0031_ci_bitbucket.txt

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,7 @@ exec git commit -am 'v2'
2222
env BITBUCKET_AUTH_TOKEN="12345"
2323
exec pint -l debug --no-color ci
2424
! stdout .
25-
stderr 'msg="Sending a request to BitBucket" method=PUT'
26-
stderr 'msg="BitBucket request completed" status=200'
27-
stderr 'msg="Sending a request to BitBucket" method=DELETE'
28-
stderr 'msg="BitBucket request completed" status=200'
25+
cmp bitbucket.got ../bitbucket.expected
2926

3027
-- src/v1.yml --
3128
- alert: rule1
@@ -56,3 +53,74 @@ repository {
5653
repository = "rules"
5754
}
5855
}
56+
57+
-- bitbucket.expected --
58+
DELETE /rest/insights/1.0/projects/prometheus/repos/rules/commits/.*/reports/pint
59+
Accept-Encoding: gzip
60+
Authorization: Bearer "12345"
61+
Content-Type: application/json
62+
63+
PUT /rest/insights/1.0/projects/prometheus/repos/rules/commits/.*/reports/pint
64+
Accept-Encoding: gzip
65+
Authorization: Bearer "12345"
66+
Content-Type: application/json
67+
--- BODY ---
68+
reporter: Prometheus rule linter
69+
title: pint unknown
70+
result: PASS
71+
details: |-
72+
pint is a Prometheus rule linter/validator.
73+
It will inspect all Prometheus recording and alerting rules for problems that could prevent these from working correctly.
74+
Checks can be either offline (static checks using only rule definition) or online (validate rule against live Prometheus server).
75+
link: https://cloudflare.github.io/pint/
76+
data:
77+
- value: 2
78+
title: Number of rules parsed
79+
type: NUMBER
80+
- value: 2
81+
title: Number of rules checked
82+
type: NUMBER
83+
- value: 2
84+
title: Number of problems found
85+
type: NUMBER
86+
- value: 7
87+
title: Number of offline checks
88+
type: NUMBER
89+
- value: 0
90+
title: Number of online checks
91+
type: NUMBER
92+
- value: 0
93+
title: Checks duration
94+
type: DURATION
95+
--- END ---
96+
97+
GET /rest/api/1.0/projects/prometheus/repos/rules/commits/.*/pull-requests
98+
Accept-Encoding: gzip
99+
Authorization: Bearer "12345"
100+
Content-Type: application/json
101+
102+
DELETE /rest/insights/1.0/projects/prometheus/repos/rules/commits/.*/reports/pint
103+
Accept-Encoding: gzip
104+
Authorization: Bearer "12345"
105+
Content-Type: application/json
106+
107+
POST /rest/insights/1.0/projects/prometheus/repos/rules/commits/.*/reports/pint
108+
Accept-Encoding: gzip
109+
Authorization: Bearer "12345"
110+
Content-Type: application/json
111+
--- BODY ---
112+
annotations:
113+
- path: rules.yml
114+
message: "Problem reported on unmodified line 2, annotation moved here: alerts/comparison: always firing alert"
115+
severity: LOW
116+
type: CODE_SMELL
117+
link: https://cloudflare.github.io/pint/checks/alerts/comparison.html
118+
line: 3
119+
- path: rules.yml
120+
message: "alerts/for: redundant field with default value"
121+
severity: LOW
122+
type: CODE_SMELL
123+
link: https://cloudflare.github.io/pint/checks/alerts/for.html
124+
line: 3
125+
--- END ---
126+

0 commit comments

Comments
 (0)