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  }