cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.83.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/promapi/config_test.go

264lines · modecode

1package promapi_test
2
3import (
4 "net/http"
5 "net/http/httptest"
6 "strconv"
7 "testing"
8 "time"
9
10 "github.com/prometheus/client_golang/prometheus"
11 "github.com/stretchr/testify/assert"
12 "github.com/stretchr/testify/require"
13 "go.nhat.io/httpmock"
14
15 "github.com/cloudflare/pint/internal/promapi"
16)
17
18func TestConfig(t *testing.T) {
19 type testCaseT struct {
20 mock httpmock.Mocker
21 errCheck func(t *testing.T, err error)
22 name string
23 cfg promapi.PrometheusConfig
24 timeout time.Duration
25 useFailover bool
26 }
27
28 defaults := promapi.PrometheusConfig{
29 Global: promapi.ConfigSectionGlobal{
30 ScrapeInterval: time.Minute,
31 ScrapeTimeout: time.Second * 10,
32 EvaluationInterval: time.Minute,
33 ExternalLabels: nil,
34 },
35 }
36
37 testCases := []testCaseT{
38 {
39 name: "default",
40 timeout: time.Second,
41 cfg: defaults,
42 mock: httpmock.New(func(s *httpmock.Server) {
43 s.ExpectGet(promapi.APIPathConfig).
44 ReturnHeader("Content-Type", "application/json").
45 Return(`{"status":"success","data":{"yaml":"global:\n {}\n"}}`).
46 UnlimitedTimes()
47 }),
48 },
49 {
50 name: "1m",
51 timeout: time.Second,
52 cfg: defaults,
53 mock: httpmock.New(func(s *httpmock.Server) {
54 s.ExpectGet(promapi.APIPathConfig).
55 ReturnHeader("Content-Type", "application/json").
56 Return(`{"status":"success","data":{"yaml":"global:\n scrape_interval: 1m\n"}}`).
57 UnlimitedTimes()
58 }),
59 },
60 {
61 name: "30s",
62 timeout: time.Second,
63 cfg: promapi.PrometheusConfig{
64 Global: promapi.ConfigSectionGlobal{
65 ScrapeInterval: time.Second * 30,
66 ScrapeTimeout: time.Second * 10,
67 EvaluationInterval: time.Minute,
68 ExternalLabels: nil,
69 },
70 },
71 mock: httpmock.New(func(s *httpmock.Server) {
72 s.ExpectGet(promapi.APIPathConfig).
73 ReturnHeader("Content-Type", "application/json").
74 Return(`{"status":"success","data":{"yaml":"global:\n scrape_interval: 30s\n"}}`).
75 UnlimitedTimes()
76 }),
77 },
78 {
79 name: "slow",
80 timeout: time.Millisecond * 10,
81 errCheck: func(t *testing.T, err error) {
82 t.Helper()
83 require.EqualError(t, err, "connection timeout")
84 },
85 mock: httpmock.New(func(s *httpmock.Server) {
86 s.ExpectGet(promapi.APIPathConfig).
87 Run(func(_ *http.Request) ([]byte, error) {
88 time.Sleep(time.Second * 2)
89 return []byte(`{"status":"success","data":{"yaml":"global:\n {}\n"}}`), nil
90 }).
91 UnlimitedTimes()
92 }),
93 },
94 {
95 name: "error",
96 timeout: time.Second,
97 errCheck: func(t *testing.T, err error) {
98 t.Helper()
99 require.EqualError(t, err, "server_error: 500 Internal Server Error")
100 },
101 mock: httpmock.New(func(s *httpmock.Server) {
102 s.ExpectGet(promapi.APIPathConfig).
103 ReturnCode(http.StatusInternalServerError).
104 Return("fake error\n").
105 UnlimitedTimes()
106 }),
107 },
108 {
109 name: "badYaml",
110 timeout: time.Second,
111 errCheck: func(t *testing.T, err error) {
112 t.Helper()
113 require.ErrorContains(t, err, "failed to decode config data in")
114 },
115 mock: httpmock.New(func(s *httpmock.Server) {
116 s.ExpectGet(promapi.APIPathConfig).
117 ReturnHeader("Content-Type", "application/json").
118 Return(`{"status":"success","data":{"yaml":"invalid yaml"}}`).
119 UnlimitedTimes()
120 }),
121 },
122 {
123 name: "badJson",
124 timeout: time.Second,
125 errCheck: func(t *testing.T, err error) {
126 t.Helper()
127 require.EqualError(t, err, `bad_response: JSON parse error: jsontext: invalid character '}' after object name (expecting ':') within "/data/yaml" after offset 34`)
128 },
129 mock: httpmock.New(func(s *httpmock.Server) {
130 s.ExpectGet(promapi.APIPathConfig).
131 ReturnHeader("Content-Type", "application/json").
132 Return(`{"status":"success","data":{"yaml"}}`).
133 UnlimitedTimes()
134 }),
135 },
136 {
137 name: "apiError",
138 timeout: time.Second,
139 errCheck: func(t *testing.T, err error) {
140 t.Helper()
141 require.EqualError(t, err, "bad_data: custom error message")
142 },
143 mock: httpmock.New(func(s *httpmock.Server) {
144 s.ExpectGet(promapi.APIPathConfig).
145 ReturnHeader("Content-Type", "application/json").
146 Return(`{"status":"error","errorType":"bad_data","error":"custom error message"}`).
147 UnlimitedTimes()
148 }),
149 },
150 {
151 name: "emptyError",
152 timeout: time.Second,
153 errCheck: func(t *testing.T, err error) {
154 t.Helper()
155 require.EqualError(t, err, "bad_data: empty response object")
156 },
157 mock: httpmock.New(func(s *httpmock.Server) {
158 s.ExpectGet(promapi.APIPathConfig).
159 ReturnHeader("Content-Type", "application/json").
160 Return(`{"status":"error","errorType":"bad_data"}`).
161 UnlimitedTimes()
162 }),
163 },
164 // Verifies that FailoverGroup.Config returns a FailoverGroupError
165 // immediately when the server returns a non-unavailable error (bad_data).
166 {
167 name: "failover/non-unavailable error",
168 timeout: time.Second,
169 useFailover: true,
170 errCheck: func(t *testing.T, err error) {
171 t.Helper()
172 require.EqualError(t, err, "bad_data: custom error message")
173 },
174 mock: httpmock.New(func(s *httpmock.Server) {
175 s.ExpectGet(promapi.APIPathConfig).
176 ReturnHeader("Content-Type", "application/json").
177 Return(`{"status":"error","errorType":"bad_data","error":"custom error message"}`).
178 UnlimitedTimes()
179 }),
180 },
181 }
182
183 for _, tc := range testCases {
184 t.Run(tc.name, func(t *testing.T) {
185 srv := tc.mock(t)
186
187 prom := promapi.NewPrometheus("test", srv.URL(), "", nil, tc.timeout, 1, 100, nil)
188
189 var cfg *promapi.ConfigResult
190 var err error
191 if tc.useFailover {
192 fg := promapi.NewFailoverGroup("test", srv.URL(), []*promapi.Prometheus{prom}, true, "up", nil, nil, nil)
193 reg := prometheus.NewRegistry()
194 fg.StartWorkers(reg)
195 t.Cleanup(func() { fg.Close(reg) })
196 cfg, err = fg.Config(t.Context(), 0)
197 } else {
198 prom.StartWorkers()
199 t.Cleanup(prom.Close)
200 cfg, err = prom.Config(t.Context(), time.Minute)
201 }
202
203 if tc.errCheck != nil {
204 tc.errCheck(t, err)
205 } else {
206 require.NoError(t, err)
207 require.Equal(t, tc.cfg, cfg.Config)
208 }
209 })
210 }
211}
212
213func TestConfigHeaders(t *testing.T) {
214 type testCaseT struct {
215 config map[string]string
216 request map[string]string
217 shouldFail bool
218 }
219
220 testCases := []testCaseT{
221 {
222 config: nil,
223 request: nil,
224 },
225 {
226 config: nil,
227 request: map[string]string{"X-Foo": "bar"},
228 shouldFail: true,
229 },
230 {
231 config: map[string]string{"X-Foo": "bar", "X-Bar": "foo"},
232 request: map[string]string{"X-Foo": "bar", "X-Bar": "foo"},
233 },
234 }
235
236 for i, tc := range testCases {
237 t.Run(strconv.Itoa(i), func(t *testing.T) {
238 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
239 for k, v := range tc.request {
240 if tc.shouldFail {
241 assert.NotEqual(t, r.Header.Get(k), v)
242 } else {
243 assert.Equal(t, r.Header.Get(k), v)
244 }
245 }
246 w.WriteHeader(http.StatusOK)
247 w.Header().Set("Content-Type", "application/json")
248 _, _ = w.Write([]byte(`{"status":"success","data":{"yaml":"global:\n scrape_interval: 30s\n"}}`))
249 }))
250 t.Cleanup(srv.Close)
251
252 fg := promapi.NewFailoverGroup("test", srv.URL, []*promapi.Prometheus{
253 promapi.NewPrometheus("test", srv.URL, "", tc.config, time.Second, 1, 100, nil),
254 }, true, "up", nil, nil, nil)
255
256 reg := prometheus.NewRegistry()
257 fg.StartWorkers(reg)
258 defer fg.Close(reg)
259
260 _, err := fg.Config(t.Context(), 0)
261 require.NoError(t, err)
262 })
263 }
264}
265