github.com/muhammadn/cortex@v1.9.1-0.20220510110439-46bb7000d03d/pkg/configs/api/api_test.go (about) 1 package api 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "net/http" 7 "os" 8 "path" 9 "strings" 10 "testing" 11 12 "github.com/cortexproject/cortex/pkg/configs/userconfig" 13 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 ) 17 18 const ( 19 rulesEndpoint = "/api/prom/configs/rules" 20 rulesPrivateEndpoint = "/private/api/prom/configs/rules" 21 22 alertManagerConfigEndpoint = "/api/prom/configs/alertmanager" 23 alertManagerConfigPrivateEndpoint = "/private/api/prom/configs/alertmanager" 24 ) 25 26 var ( 27 rulesClient = configurable{rulesEndpoint, rulesPrivateEndpoint} 28 alertManagerConfigClient = configurable{alertManagerConfigEndpoint, alertManagerConfigPrivateEndpoint} 29 30 allClients = []configurable{rulesClient, alertManagerConfigClient} 31 ) 32 33 // The root page returns 200 OK. 34 func Test_Root_OK(t *testing.T) { 35 setup(t) 36 defer cleanup(t) 37 38 w := request(t, "GET", "/", nil) 39 assert.Equal(t, http.StatusOK, w.Code) 40 } 41 42 type configurable struct { 43 Endpoint string 44 PrivateEndpoint string 45 } 46 47 // post a config 48 func (c configurable) post(t *testing.T, userID string, config userconfig.Config) userconfig.View { 49 w := requestAsUser(t, userID, "POST", c.Endpoint, "", readerFromConfig(t, config)) 50 require.Equal(t, http.StatusNoContent, w.Code) 51 return c.get(t, userID) 52 } 53 54 // get a config 55 func (c configurable) get(t *testing.T, userID string) userconfig.View { 56 w := requestAsUser(t, userID, "GET", c.Endpoint, "", nil) 57 return parseView(t, w.Body.Bytes()) 58 } 59 60 // configs returns 401 to requests without authentication. 61 func Test_GetConfig_Anonymous(t *testing.T) { 62 setup(t) 63 defer cleanup(t) 64 65 for _, c := range allClients { 66 w := request(t, "GET", c.Endpoint, nil) 67 assert.Equal(t, http.StatusUnauthorized, w.Code) 68 } 69 } 70 71 // configs returns 404 if no config has been created yet. 72 func Test_GetConfig_NotFound(t *testing.T) { 73 setup(t) 74 defer cleanup(t) 75 76 userID := makeUserID() 77 for _, c := range allClients { 78 w := requestAsUser(t, userID, "GET", c.Endpoint, "", nil) 79 assert.Equal(t, http.StatusNotFound, w.Code) 80 } 81 } 82 83 // configs returns 401 to requests without authentication. 84 func Test_PostConfig_Anonymous(t *testing.T) { 85 setup(t) 86 defer cleanup(t) 87 88 for _, c := range allClients { 89 w := request(t, "POST", c.Endpoint, nil) 90 assert.Equal(t, http.StatusUnauthorized, w.Code) 91 } 92 } 93 94 // Posting to a configuration sets it so that you can get it again. 95 func Test_PostConfig_CreatesConfig(t *testing.T) { 96 setup(t) 97 defer cleanup(t) 98 99 userID := makeUserID() 100 config := makeConfig() 101 for _, c := range allClients { 102 { 103 w := requestAsUser(t, userID, "POST", c.Endpoint, "", readerFromConfig(t, config)) 104 assert.Equal(t, http.StatusNoContent, w.Code) 105 } 106 { 107 w := requestAsUser(t, userID, "GET", c.Endpoint, "", nil) 108 assert.Equal(t, config, parseView(t, w.Body.Bytes()).Config) 109 } 110 } 111 } 112 113 // Posting to a configuration sets it so that you can get it again. 114 func Test_PostConfig_UpdatesConfig(t *testing.T) { 115 setup(t) 116 defer cleanup(t) 117 118 userID := makeUserID() 119 for _, c := range allClients { 120 view1 := c.post(t, userID, makeConfig()) 121 config2 := makeConfig() 122 view2 := c.post(t, userID, config2) 123 assert.True(t, view2.ID > view1.ID, "%v > %v", view2.ID, view1.ID) 124 assert.Equal(t, config2, view2.Config) 125 } 126 } 127 128 // Different users can have different configurations. 129 func Test_PostConfig_MultipleUsers(t *testing.T) { 130 setup(t) 131 defer cleanup(t) 132 133 userID1 := makeUserID() 134 userID2 := makeUserID() 135 for _, c := range allClients { 136 config1 := c.post(t, userID1, makeConfig()) 137 config2 := c.post(t, userID2, makeConfig()) 138 foundConfig1 := c.get(t, userID1) 139 assert.Equal(t, config1, foundConfig1) 140 foundConfig2 := c.get(t, userID2) 141 assert.Equal(t, config2, foundConfig2) 142 assert.True(t, config2.ID > config1.ID, "%v > %v", config2.ID, config1.ID) 143 } 144 } 145 146 // GetAllConfigs returns an empty list of configs if there aren't any. 147 func Test_GetAllConfigs_Empty(t *testing.T) { 148 setup(t) 149 defer cleanup(t) 150 151 for _, c := range allClients { 152 w := request(t, "GET", c.PrivateEndpoint, nil) 153 assert.Equal(t, http.StatusOK, w.Code) 154 var found ConfigsView 155 err := json.Unmarshal(w.Body.Bytes(), &found) 156 assert.NoError(t, err, "Could not unmarshal JSON") 157 assert.Equal(t, ConfigsView{Configs: map[string]userconfig.View{}}, found) 158 } 159 } 160 161 // GetAllConfigs returns all created userconfig. 162 func Test_GetAllConfigs(t *testing.T) { 163 setup(t) 164 defer cleanup(t) 165 166 userID := makeUserID() 167 config := makeConfig() 168 169 for _, c := range allClients { 170 view := c.post(t, userID, config) 171 w := request(t, "GET", c.PrivateEndpoint, nil) 172 assert.Equal(t, http.StatusOK, w.Code) 173 var found ConfigsView 174 err := json.Unmarshal(w.Body.Bytes(), &found) 175 assert.NoError(t, err, "Could not unmarshal JSON") 176 assert.Equal(t, ConfigsView{Configs: map[string]userconfig.View{ 177 userID: view, 178 }}, found) 179 } 180 } 181 182 // GetAllConfigs returns the *newest* versions of all created userconfig. 183 func Test_GetAllConfigs_Newest(t *testing.T) { 184 setup(t) 185 defer cleanup(t) 186 187 userID := makeUserID() 188 189 for _, c := range allClients { 190 c.post(t, userID, makeConfig()) 191 c.post(t, userID, makeConfig()) 192 lastCreated := c.post(t, userID, makeConfig()) 193 194 w := request(t, "GET", c.PrivateEndpoint, nil) 195 assert.Equal(t, http.StatusOK, w.Code) 196 var found ConfigsView 197 err := json.Unmarshal(w.Body.Bytes(), &found) 198 assert.NoError(t, err, "Could not unmarshal JSON") 199 assert.Equal(t, ConfigsView{Configs: map[string]userconfig.View{ 200 userID: lastCreated, 201 }}, found) 202 } 203 } 204 205 func Test_GetConfigs_IncludesNewerConfigsAndExcludesOlder(t *testing.T) { 206 setup(t) 207 defer cleanup(t) 208 209 for _, c := range allClients { 210 c.post(t, makeUserID(), makeConfig()) 211 config2 := c.post(t, makeUserID(), makeConfig()) 212 userID3 := makeUserID() 213 config3 := c.post(t, userID3, makeConfig()) 214 215 w := request(t, "GET", fmt.Sprintf("%s?since=%d", c.PrivateEndpoint, config2.ID), nil) 216 assert.Equal(t, http.StatusOK, w.Code) 217 var found ConfigsView 218 err := json.Unmarshal(w.Body.Bytes(), &found) 219 assert.NoError(t, err, "Could not unmarshal JSON") 220 assert.Equal(t, ConfigsView{Configs: map[string]userconfig.View{ 221 userID3: config3, 222 }}, found) 223 } 224 } 225 226 var amCfgValidationTests = []struct { 227 config string 228 shouldFail bool 229 errContains string 230 }{ 231 { 232 config: "invalid config", 233 shouldFail: true, 234 errContains: "yaml", 235 }, { 236 config: ` 237 global: 238 smtp_smarthost: localhost:25 239 smtp_from: alertmanager@example.org 240 route: 241 receiver: noop 242 243 receivers: 244 - name: noop 245 email_configs: 246 - to: myteam@foobar.org`, 247 shouldFail: true, 248 errContains: ErrEmailNotificationsAreDisabled.Error(), 249 }, { 250 config: ` 251 global: 252 smtp_smarthost: localhost:25 253 smtp_from: alertmanager@example.org 254 route: 255 receiver: noop 256 257 receivers: 258 - name: noop 259 slack_configs: 260 - api_url: http://slack`, 261 shouldFail: false, 262 }, 263 } 264 265 func Test_ValidateAlertmanagerConfig(t *testing.T) { 266 setup(t) 267 defer cleanup(t) 268 269 userID := makeUserID() 270 for i, test := range amCfgValidationTests { 271 resp := requestAsUser(t, userID, "POST", "/api/prom/configs/alertmanager/validate", "", strings.NewReader(test.config)) 272 data := map[string]string{} 273 err := json.Unmarshal(resp.Body.Bytes(), &data) 274 assert.NoError(t, err, "test case %d", i) 275 276 success := map[string]string{ 277 "status": "success", 278 } 279 if !test.shouldFail { 280 assert.Equal(t, success, data, "test case %d", i) 281 assert.Equal(t, http.StatusOK, resp.Code, "test case %d", i) 282 continue 283 } 284 285 assert.Equal(t, "error", data["status"], "test case %d", i) 286 assert.Contains(t, data["error"], test.errContains, "test case %d", i) 287 } 288 } 289 290 func Test_SetConfig_ValidatesAlertmanagerConfig(t *testing.T) { 291 setup(t) 292 defer cleanup(t) 293 294 userID := makeUserID() 295 for i, test := range amCfgValidationTests { 296 cfg := userconfig.Config{AlertmanagerConfig: test.config} 297 resp := requestAsUser(t, userID, "POST", "/api/prom/configs/alertmanager", "", readerFromConfig(t, cfg)) 298 299 if !test.shouldFail { 300 assert.Equal(t, http.StatusNoContent, resp.Code, "test case %d", i) 301 continue 302 } 303 304 assert.Equal(t, http.StatusBadRequest, resp.Code, "test case %d", i) 305 assert.Contains(t, resp.Body.String(), test.errContains, "test case %d", i) 306 } 307 } 308 309 func Test_SetConfig_ValidatesAlertmanagerConfig_WithEmailEnabled(t *testing.T) { 310 config := ` 311 global: 312 smtp_smarthost: localhost:25 313 smtp_from: alertmanager@example.org 314 route: 315 receiver: noop 316 317 receivers: 318 - name: noop 319 email_configs: 320 - to: myteam@foobar.org` 321 setupWithEmailEnabled(t) 322 defer cleanup(t) 323 324 userID := makeUserID() 325 cfg := userconfig.Config{AlertmanagerConfig: config} 326 resp := requestAsUser(t, userID, "POST", "/api/prom/configs/alertmanager", "", readerFromConfig(t, cfg)) 327 328 assert.Equal(t, http.StatusNoContent, resp.Code) 329 } 330 331 func Test_SetConfig_BodyFormat(t *testing.T) { 332 setup(t) 333 defer cleanup(t) 334 for _, bodyFile := range []string{"testdata/config.yml", "testdata/config.json"} { 335 var contentType string 336 switch path.Ext(bodyFile) { 337 case ".yml": 338 contentType = "text/yaml" 339 default: 340 contentType = "text/json" 341 } 342 testSetConfigBodyFormat(bodyFile, contentType, t) 343 } 344 } 345 346 func testSetConfigBodyFormat(bodyFile string, contentType string, t *testing.T) { 347 userID := makeUserID() 348 file, err := os.Open(bodyFile) 349 require.NoError(t, err) 350 defer file.Close() 351 resp := requestAsUser(t, userID, "POST", "/api/prom/configs/alertmanager", contentType, file) 352 assert.Equal(t, http.StatusNoContent, resp.Code, "error body: %s Content-Type: %s", resp.Body.String(), contentType) 353 } 354 355 func TestParseConfigFormat(t *testing.T) { 356 tests := []struct { 357 name string 358 defaultFormat string 359 expected string 360 }{ 361 {"", FormatInvalid, FormatInvalid}, 362 {"", FormatJSON, FormatJSON}, 363 {"application/json", FormatInvalid, FormatJSON}, 364 {"application/yaml", FormatInvalid, FormatYAML}, 365 {"application/json, application/yaml", FormatInvalid, FormatJSON}, 366 {"application/yaml, application/json", FormatInvalid, FormatYAML}, 367 {"text/plain, application/yaml", FormatInvalid, FormatYAML}, 368 {"application/yaml; a=1", FormatInvalid, FormatYAML}, 369 } 370 for _, test := range tests { 371 t.Run(test.name+"_"+test.expected, func(t *testing.T) { 372 actual := parseConfigFormat(test.name, test.defaultFormat) 373 assert.Equal(t, test.expected, actual) 374 }) 375 } 376 } 377 378 func Test_SetConfig_ValidateTemplateFiles(t *testing.T) { 379 cfg := userconfig.Config{ 380 TemplateFiles: map[string]string{ 381 "mytemplate.tmpl": ` 382 {{ define "mytemplate" }} 383 ToUpper{{ .Value | toUpper }} 384 ToLower{{ .Value | toLower }} 385 Title{{ .Value | title }} 386 Join{{ .Values | join " " }} 387 Match{{ .Value | match "fir" }} 388 SafeHTML{{ .Value | safeHtml }} 389 ReReplaceAll{{ .Value | reReplaceAll "-" "_" }} 390 StringSlice{{ .Value | stringSlice }} 391 {{ end }} 392 `, 393 }, 394 } 395 396 err := validateTemplateFiles(cfg) 397 assert.Equal(t, nil, err) 398 }