Skip to content

Commit 3e85c35

Browse files
authored
Merge pull request #1759 from cloudflare/tests
Add more test coverage
2 parents 761a5d5 + 84d00cb commit 3e85c35

7 files changed

Lines changed: 1771 additions & 2280 deletions

File tree

internal/promapi/config_test.go

Lines changed: 113 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,27 @@
11
package promapi_test
22

33
import (
4-
"fmt"
54
"net/http"
65
"net/http/httptest"
76
"strconv"
8-
"strings"
97
"testing"
108
"time"
119

1210
"github.com/prometheus/client_golang/prometheus"
1311
"github.com/stretchr/testify/assert"
1412
"github.com/stretchr/testify/require"
13+
"go.nhat.io/httpmock"
1514

1615
"github.com/cloudflare/pint/internal/promapi"
1716
)
1817

1918
func TestConfig(t *testing.T) {
20-
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
21-
switch r.URL.Path {
22-
case "/30s" + promapi.APIPathConfig:
23-
w.WriteHeader(http.StatusOK)
24-
w.Header().Set("Content-Type", "application/json")
25-
_, _ = w.Write([]byte(`{"status":"success","data":{"yaml":"global:\n scrape_interval: 30s\n"}}`))
26-
case "/1m" + promapi.APIPathConfig:
27-
w.WriteHeader(http.StatusOK)
28-
w.Header().Set("Content-Type", "application/json")
29-
_, _ = w.Write([]byte(`{"status":"success","data":{"yaml":"global:\n scrape_interval: 1m\n"}}`))
30-
case "/default" + promapi.APIPathConfig:
31-
w.WriteHeader(http.StatusOK)
32-
w.Header().Set("Content-Type", "application/json")
33-
_, _ = w.Write([]byte(`{"status":"success","data":{"yaml":"global:\n {}\n"}}`))
34-
case "/once" + promapi.APIPathConfig:
35-
w.WriteHeader(http.StatusOK)
36-
w.Header().Set("Content-Type", "application/json")
37-
_, _ = w.Write([]byte(`{"status":"success","data":{"yaml":"global:\n {}\n"}}`))
38-
case "/slow" + promapi.APIPathConfig:
39-
w.WriteHeader(http.StatusOK)
40-
w.Header().Set("Content-Type", "application/json")
41-
time.Sleep(time.Second * 2)
42-
_, _ = w.Write([]byte(`{"status":"success","data":{"yaml":"global:\n {}\n"}}`))
43-
case "/error" + promapi.APIPathConfig:
44-
w.WriteHeader(http.StatusInternalServerError)
45-
_, _ = w.Write([]byte("fake error\n"))
46-
case "/badYaml" + promapi.APIPathConfig:
47-
w.WriteHeader(http.StatusOK)
48-
w.Header().Set("Content-Type", "application/json")
49-
_, _ = w.Write([]byte(`{"status":"success","data":{"yaml":"invalid yaml"}}`))
50-
case "/badJson" + promapi.APIPathConfig:
51-
w.WriteHeader(http.StatusOK)
52-
w.Header().Set("Content-Type", "application/json")
53-
_, _ = w.Write([]byte(`{"status":"success","data":{"yaml"}}`))
54-
case "/apiError" + promapi.APIPathConfig:
55-
w.WriteHeader(http.StatusOK)
56-
w.Header().Set("Content-Type", "application/json")
57-
_, _ = w.Write([]byte(`{"status":"error","errorType":"bad_data","error":"custom error message"}`))
58-
default:
59-
w.WriteHeader(http.StatusBadRequest)
60-
w.Header().Set("Content-Type", "application/json")
61-
_, _ = w.Write([]byte(`{"status":"error","errorType":"bad_data","error":"unhandled path"}`))
62-
}
63-
}))
64-
t.Cleanup(srv.Close)
65-
6619
type testCaseT struct {
67-
prefix string
68-
err string
69-
cfg promapi.ConfigResult
70-
timeout time.Duration
20+
mock httpmock.Mocker
21+
errCheck func(t *testing.T, err error)
22+
name string
23+
cfg promapi.PrometheusConfig
24+
timeout time.Duration
7125
}
7226

7327
defaults := promapi.PrometheusConfig{
@@ -81,75 +35,147 @@ func TestConfig(t *testing.T) {
8135

8236
testCases := []testCaseT{
8337
{
84-
prefix: "/default",
38+
name: "default",
8539
timeout: time.Second,
86-
cfg: promapi.ConfigResult{
87-
URI: srv.URL + "/default",
88-
Config: defaults,
89-
},
40+
cfg: defaults,
41+
mock: httpmock.New(func(s *httpmock.Server) {
42+
s.ExpectGet(promapi.APIPathConfig).
43+
ReturnHeader("Content-Type", "application/json").
44+
Return(`{"status":"success","data":{"yaml":"global:\n {}\n"}}`).
45+
UnlimitedTimes()
46+
}),
9047
},
9148
{
92-
prefix: "/1m",
49+
name: "1m",
9350
timeout: time.Second,
94-
cfg: promapi.ConfigResult{
95-
URI: srv.URL + "/1m",
96-
Config: defaults,
97-
},
51+
cfg: defaults,
52+
mock: httpmock.New(func(s *httpmock.Server) {
53+
s.ExpectGet(promapi.APIPathConfig).
54+
ReturnHeader("Content-Type", "application/json").
55+
Return(`{"status":"success","data":{"yaml":"global:\n scrape_interval: 1m\n"}}`).
56+
UnlimitedTimes()
57+
}),
9858
},
9959
{
100-
prefix: "/30s",
60+
name: "30s",
10161
timeout: time.Second,
102-
cfg: promapi.ConfigResult{
103-
URI: srv.URL + "/30s",
104-
Config: promapi.PrometheusConfig{
105-
Global: promapi.ConfigSectionGlobal{
106-
ScrapeInterval: time.Second * 30,
107-
ScrapeTimeout: time.Second * 10,
108-
EvaluationInterval: time.Minute,
109-
ExternalLabels: nil,
110-
},
62+
cfg: promapi.PrometheusConfig{
63+
Global: promapi.ConfigSectionGlobal{
64+
ScrapeInterval: time.Second * 30,
65+
ScrapeTimeout: time.Second * 10,
66+
EvaluationInterval: time.Minute,
67+
ExternalLabels: nil,
11168
},
11269
},
70+
mock: httpmock.New(func(s *httpmock.Server) {
71+
s.ExpectGet(promapi.APIPathConfig).
72+
ReturnHeader("Content-Type", "application/json").
73+
Return(`{"status":"success","data":{"yaml":"global:\n scrape_interval: 30s\n"}}`).
74+
UnlimitedTimes()
75+
}),
11376
},
11477
{
115-
prefix: "/slow",
78+
name: "slow",
11679
timeout: time.Millisecond * 10,
117-
err: "connection timeout",
80+
errCheck: func(t *testing.T, err error) {
81+
t.Helper()
82+
require.EqualError(t, err, "connection timeout")
83+
},
84+
mock: httpmock.New(func(s *httpmock.Server) {
85+
s.ExpectGet(promapi.APIPathConfig).
86+
Run(func(_ *http.Request) ([]byte, error) {
87+
time.Sleep(time.Second * 2)
88+
return []byte(`{"status":"success","data":{"yaml":"global:\n {}\n"}}`), nil
89+
}).
90+
UnlimitedTimes()
91+
}),
11892
},
11993
{
120-
prefix: "/error",
94+
name: "error",
12195
timeout: time.Second,
122-
err: "server_error: 500 Internal Server Error",
96+
errCheck: func(t *testing.T, err error) {
97+
t.Helper()
98+
require.EqualError(t, err, "server_error: 500 Internal Server Error")
99+
},
100+
mock: httpmock.New(func(s *httpmock.Server) {
101+
s.ExpectGet(promapi.APIPathConfig).
102+
ReturnCode(http.StatusInternalServerError).
103+
Return("fake error\n").
104+
UnlimitedTimes()
105+
}),
123106
},
124107
{
125-
prefix: "/badYaml",
108+
name: "badYaml",
126109
timeout: time.Second,
127-
err: fmt.Sprintf("failed to decode config data in %s/badYaml response: yaml: unmarshal errors:\n line 1: cannot unmarshal !!str `invalid...` into promapi.PrometheusConfig", srv.URL),
110+
errCheck: func(t *testing.T, err error) {
111+
t.Helper()
112+
require.ErrorContains(t, err, "failed to decode config data in")
113+
},
114+
mock: httpmock.New(func(s *httpmock.Server) {
115+
s.ExpectGet(promapi.APIPathConfig).
116+
ReturnHeader("Content-Type", "application/json").
117+
Return(`{"status":"success","data":{"yaml":"invalid yaml"}}`).
118+
UnlimitedTimes()
119+
}),
128120
},
129121
{
130-
prefix: "/badJson",
122+
name: "badJson",
131123
timeout: time.Second,
132-
err: `bad_response: JSON parse error: jsontext: invalid character '}' after object name (expecting ':') within "/data/yaml" after offset 34`,
124+
errCheck: func(t *testing.T, err error) {
125+
t.Helper()
126+
require.EqualError(t, err, `bad_response: JSON parse error: jsontext: invalid character '}' after object name (expecting ':') within "/data/yaml" after offset 34`)
127+
},
128+
mock: httpmock.New(func(s *httpmock.Server) {
129+
s.ExpectGet(promapi.APIPathConfig).
130+
ReturnHeader("Content-Type", "application/json").
131+
Return(`{"status":"success","data":{"yaml"}}`).
132+
UnlimitedTimes()
133+
}),
133134
},
134135
{
135-
prefix: "/apiError",
136+
name: "apiError",
136137
timeout: time.Second,
137-
err: "bad_data: custom error message",
138+
errCheck: func(t *testing.T, err error) {
139+
t.Helper()
140+
require.EqualError(t, err, "bad_data: custom error message")
141+
},
142+
mock: httpmock.New(func(s *httpmock.Server) {
143+
s.ExpectGet(promapi.APIPathConfig).
144+
ReturnHeader("Content-Type", "application/json").
145+
Return(`{"status":"error","errorType":"bad_data","error":"custom error message"}`).
146+
UnlimitedTimes()
147+
}),
148+
},
149+
{
150+
name: "emptyError",
151+
timeout: time.Second,
152+
errCheck: func(t *testing.T, err error) {
153+
t.Helper()
154+
require.EqualError(t, err, "bad_data: empty response object")
155+
},
156+
mock: httpmock.New(func(s *httpmock.Server) {
157+
s.ExpectGet(promapi.APIPathConfig).
158+
ReturnHeader("Content-Type", "application/json").
159+
Return(`{"status":"error","errorType":"bad_data"}`).
160+
UnlimitedTimes()
161+
}),
138162
},
139163
}
140164

141165
for _, tc := range testCases {
142-
t.Run(strings.TrimPrefix(tc.prefix, "/"), func(t *testing.T) {
143-
prom := promapi.NewPrometheus("test", srv.URL+tc.prefix, "", nil, tc.timeout, 1, 100, nil)
166+
t.Run(tc.name, func(t *testing.T) {
167+
srv := tc.mock(t)
168+
169+
prom := promapi.NewPrometheus("test", srv.URL(), "", nil, tc.timeout, 1, 100, nil)
144170
prom.StartWorkers()
145171
t.Cleanup(prom.Close)
146172

147173
cfg, err := prom.Config(t.Context(), time.Minute)
148-
if tc.err != "" {
149-
require.EqualError(t, err, tc.err, tc)
174+
if tc.errCheck != nil {
175+
tc.errCheck(t, err)
150176
} else {
151177
require.NoError(t, err)
152-
require.Equal(t, *cfg, tc.cfg)
178+
require.Equal(t, tc.cfg, cfg.Config)
153179
}
154180
})
155181
}

0 commit comments

Comments
 (0)