github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/ctl/service/r2/routes_test.go (about)

     1  // Copyright (c) 2017 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package r2
    22  
    23  import (
    24  	"bytes"
    25  	"context"
    26  	"encoding/json"
    27  	"fmt"
    28  	"net/http"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/m3db/m3/src/ctl/auth"
    33  	"github.com/m3db/m3/src/ctl/service/r2/store"
    34  	"github.com/m3db/m3/src/metrics/rules"
    35  	"github.com/m3db/m3/src/metrics/rules/view"
    36  	"github.com/m3db/m3/src/metrics/rules/view/changes"
    37  	"github.com/m3db/m3/src/x/clock"
    38  	"github.com/m3db/m3/src/x/instrument"
    39  
    40  	"github.com/golang/mock/gomock"
    41  	"github.com/gorilla/mux"
    42  	"github.com/stretchr/testify/require"
    43  	"github.com/uber-go/tally"
    44  )
    45  
    46  func TestHandleRoute(t *testing.T) {
    47  	s := newTestService(nil)
    48  	r := newTestGetRequest()
    49  	expected := view.Namespaces{}
    50  	actual, err := s.handleRoute(fetchNamespaces, r, newTestInstrumentMethodMetrics())
    51  	require.NoError(t, err)
    52  	require.Equal(t, expected, actual)
    53  }
    54  
    55  func TestHandleRouteNilRequest(t *testing.T) {
    56  	s := newTestService(nil)
    57  	_, err := s.handleRoute(fetchNamespaces, nil, newTestInstrumentMethodMetrics())
    58  	require.EqualError(t, err, errNilRequest.Error())
    59  }
    60  func TestFetchNamespacesSuccess(t *testing.T) {
    61  	expected := view.Namespaces{}
    62  	actual, err := fetchNamespaces(newTestService(nil), newTestGetRequest())
    63  	require.NoError(t, err)
    64  	require.Equal(t, expected, actual)
    65  }
    66  
    67  func TestFetchNamespaceSuccess(t *testing.T) {
    68  	expected := view.RuleSet{}
    69  	actual, err := fetchNamespace(newTestService(nil), newTestGetRequest())
    70  	require.NoError(t, err)
    71  	require.Equal(t, expected, actual)
    72  }
    73  
    74  func TestValidateRuleSetSuccess(t *testing.T) {
    75  	expected := "Ruleset is valid"
    76  	actual, err := validateRuleSet(newTestService(nil), newTestPostRequest([]byte(`{}`)))
    77  	require.NoError(t, err)
    78  	require.Equal(t, expected, actual)
    79  }
    80  
    81  func TestCreateNamespaceSuccess(t *testing.T) {
    82  	expected := view.Namespace{}
    83  	actual, err := createNamespace(newTestService(nil), newTestPostRequest([]byte(`{"id": "id"}`)))
    84  	require.NoError(t, err)
    85  	require.Equal(t, expected, actual)
    86  }
    87  
    88  func TestDeleteNamespaceSuccess(t *testing.T) {
    89  	expected := fmt.Sprintf("Deleted namespace %s", "")
    90  	actual, err := deleteNamespace(newTestService(nil), newTestDeleteRequest())
    91  	require.NoError(t, err)
    92  	require.Equal(t, expected, actual)
    93  }
    94  
    95  func TestFetchMappingRuleSuccess(t *testing.T) {
    96  	expected := view.MappingRule{}
    97  	actual, err := fetchMappingRule(newTestService(nil), newTestGetRequest())
    98  	require.NoError(t, err)
    99  	require.Equal(t, expected, actual)
   100  }
   101  
   102  func TestCreateMappingRuleSuccess(t *testing.T) {
   103  	expected := view.MappingRule{}
   104  	actual, err := createMappingRule(newTestService(nil), newTestPostRequest(
   105  		[]byte(`{"filter": "key:val", "name": "name", "policies": []}`),
   106  	))
   107  	require.NoError(t, err)
   108  	require.Equal(t, expected, actual)
   109  }
   110  
   111  func TestUpdateMappingRuleSuccess(t *testing.T) {
   112  	expected := view.MappingRule{}
   113  	actual, err := updateMappingRule(newTestService(nil), newTestPutRequest(
   114  		[]byte(`{"filter": "key:val", "name": "name", "policies": []}`),
   115  	))
   116  	require.NoError(t, err)
   117  	require.Equal(t, expected, actual)
   118  }
   119  
   120  func TestDeleteMappingRuleSuccess(t *testing.T) {
   121  	expected := fmt.Sprintf("Deleted mapping rule: %s in namespace %s", "", "")
   122  	actual, err := deleteMappingRule(newTestService(nil), newTestDeleteRequest())
   123  	require.NoError(t, err)
   124  	require.Equal(t, expected, actual)
   125  }
   126  
   127  func TestFetchMappingRuleHistorySuccess(t *testing.T) {
   128  	expected := view.MappingRuleSnapshots{MappingRules: make([]view.MappingRule, 0)}
   129  	actual, err := fetchMappingRuleHistory(newTestService(nil), newTestGetRequest())
   130  	require.NoError(t, err)
   131  	require.Equal(t, expected, actual)
   132  }
   133  
   134  func TestFetchRollupRuleSuccess(t *testing.T) {
   135  	expected := view.RollupRule{}
   136  	actual, err := fetchRollupRule(newTestService(nil), newTestGetRequest())
   137  	require.NoError(t, err)
   138  	require.Equal(t, expected, actual)
   139  }
   140  
   141  func TestCreateRollupRuleSuccess(t *testing.T) {
   142  	expected := view.RollupRule{}
   143  	actual, err := createRollupRule(newTestService(nil), newTestPostRequest(
   144  		[]byte(`{"filter": "key:val", "name": "name", "targets": []}`),
   145  	))
   146  	require.NoError(t, err)
   147  	require.Equal(t, expected, actual)
   148  }
   149  
   150  func TestUpdateRollupRuleSuccess(t *testing.T) {
   151  	expected := view.RollupRule{}
   152  	actual, err := updateRollupRule(newTestService(nil), newTestPutRequest(
   153  		[]byte(`{"filter": "key:val", "name": "name", "targets": []}`),
   154  	))
   155  	require.NoError(t, err)
   156  	require.Equal(t, expected, actual)
   157  }
   158  
   159  func TestDeleteRollupRuleSuccess(t *testing.T) {
   160  	expected := fmt.Sprintf("Deleted rollup rule: %s in namespace %s", "", "")
   161  	actual, err := deleteRollupRule(newTestService(nil), newTestDeleteRequest())
   162  	require.NoError(t, err)
   163  	require.Equal(t, expected, actual)
   164  }
   165  
   166  func TestFetchRollupRuleHistorySuccess(t *testing.T) {
   167  	expected := view.RollupRuleSnapshots{RollupRules: []view.RollupRule{}}
   168  	actual, err := fetchRollupRuleHistory(newTestService(nil), newTestGetRequest())
   169  	require.NoError(t, err)
   170  	require.Equal(t, expected, actual)
   171  }
   172  
   173  func TestRulesetUpdateRuleSet(t *testing.T) {
   174  	namespaceID := "testNamespace"
   175  	bulkReqBody := newTestBulkReqBody()
   176  	bodyBytes, err := json.Marshal(bulkReqBody)
   177  	require.NoError(t, err)
   178  	req, err := http.NewRequest(
   179  		http.MethodPost,
   180  		fmt.Sprintf("/namespaces/%s/ruleset/update", namespaceID),
   181  		bytes.NewBuffer(bodyBytes),
   182  	)
   183  	require.NoError(t, err)
   184  	req = mux.SetURLVars(
   185  		req,
   186  		map[string]string{
   187  			"namespaceID": namespaceID,
   188  		},
   189  	)
   190  
   191  	ctrl := gomock.NewController(t)
   192  	defer ctrl.Finish()
   193  	storeMock := store.NewMockStore(ctrl)
   194  	storeMock.EXPECT().UpdateRuleSet(gomock.Any(), 1, gomock.Any()).Return(
   195  		view.RuleSet{
   196  			Version: 2,
   197  		},
   198  		nil,
   199  	)
   200  
   201  	service := newTestService(storeMock)
   202  	resp, err := updateRuleSet(service, req)
   203  	require.NoError(t, err)
   204  	typedResp := resp.(view.RuleSet)
   205  	require.Equal(t, typedResp.Version, 2)
   206  }
   207  
   208  func TestUpdateRuleSetStoreUpdateFailure(t *testing.T) {
   209  	namespaceID := "testNamespace"
   210  	bulkReqBody := newTestBulkReqBody()
   211  	bodyBytes, err := json.Marshal(bulkReqBody)
   212  	require.NoError(t, err)
   213  	req, err := http.NewRequest(
   214  		http.MethodPost,
   215  		fmt.Sprintf("/namespaces/%s/ruleset/bulk", namespaceID),
   216  		bytes.NewBuffer(bodyBytes),
   217  	)
   218  	require.NoError(t, err)
   219  	req = mux.SetURLVars(
   220  		req,
   221  		map[string]string{
   222  			"namespaceID": namespaceID,
   223  		},
   224  	)
   225  
   226  	ctrl := gomock.NewController(t)
   227  	defer ctrl.Finish()
   228  	storeMock := store.NewMockStore(ctrl)
   229  	storeMock.EXPECT().UpdateRuleSet(gomock.Any(), 1, gomock.Any()).Return(
   230  		view.RuleSet{},
   231  		NewConflictError("something horrible has happened"),
   232  	)
   233  
   234  	service := newTestService(storeMock)
   235  	resp, err := updateRuleSet(service, req)
   236  	require.Equal(t, view.RuleSet{}, resp)
   237  	require.Error(t, err)
   238  	require.IsType(t, NewConflictError(""), err)
   239  }
   240  
   241  func TestUpdateRuleSetInvalidJSON(t *testing.T) {
   242  	namespaceID := "testNamespace"
   243  	req, err := http.NewRequest(
   244  		http.MethodPost,
   245  		fmt.Sprintf("/namespaces/%s/ruleset/bulk", namespaceID),
   246  		bytes.NewBuffer([]byte("invalid josn")),
   247  	)
   248  	require.NoError(t, err)
   249  	req = mux.SetURLVars(
   250  		req,
   251  		map[string]string{
   252  			"namespaceID": namespaceID,
   253  		},
   254  	)
   255  
   256  	ctrl := gomock.NewController(t)
   257  	defer ctrl.Finish()
   258  	storeMock := store.NewMockStore(ctrl)
   259  	service := newTestService(storeMock)
   260  	resp, err := updateRuleSet(service, req)
   261  	require.Nil(t, resp)
   262  	require.Error(t, err)
   263  	require.IsType(t, NewBadInputError(""), err)
   264  }
   265  
   266  func TestUpdateRuleSetEmptyRequest(t *testing.T) {
   267  	namespaceID := "testNamespace"
   268  	body := &updateRuleSetRequest{
   269  		RuleSetChanges: changes.RuleSetChanges{},
   270  	}
   271  	bodyBytes, err := json.Marshal(body)
   272  	require.NoError(t, err)
   273  	req, err := http.NewRequest(
   274  		http.MethodPost,
   275  		fmt.Sprintf("/namespaces/%s/ruleset/bulk", namespaceID),
   276  		bytes.NewBuffer(bodyBytes),
   277  	)
   278  	require.NoError(t, err)
   279  	req = mux.SetURLVars(
   280  		req,
   281  		map[string]string{
   282  			"namespaceID": namespaceID,
   283  		},
   284  	)
   285  
   286  	ctrl := gomock.NewController(t)
   287  	defer ctrl.Finish()
   288  	storeMock := store.NewMockStore(ctrl)
   289  	service := newTestService(storeMock)
   290  	resp, err := updateRuleSet(service, req)
   291  	require.Nil(t, resp)
   292  	require.Error(t, err)
   293  	require.IsType(t, NewBadInputError(""), err)
   294  	println(err.Error())
   295  }
   296  
   297  func newTestService(store store.Store) *service {
   298  	if store == nil {
   299  		store = newMockStore()
   300  	}
   301  	iOpts := instrument.NewOptions()
   302  	return &service{
   303  		metrics:     newServiceMetrics(iOpts.MetricsScope(), iOpts.TimerOptions()),
   304  		nowFn:       clock.NewOptions().NowFn(),
   305  		store:       store,
   306  		authService: auth.NewNoopAuth(),
   307  		logger:      iOpts.Logger(),
   308  	}
   309  }
   310  
   311  func newTestGetRequest() *http.Request {
   312  	req, _ := http.NewRequest("GET", "/route", nil)
   313  	return req.WithContext(context.Background())
   314  }
   315  
   316  func newTestPostRequest(bodyBuff []byte) *http.Request {
   317  	req, _ := http.NewRequest("POST", "/route", bytes.NewReader(bodyBuff))
   318  	return req.WithContext(context.Background())
   319  }
   320  
   321  func newTestPutRequest(bodyBuff []byte) *http.Request {
   322  	req, _ := http.NewRequest("PUT", "/route", bytes.NewReader(bodyBuff))
   323  	return req.WithContext(context.Background())
   324  }
   325  
   326  func newTestDeleteRequest() *http.Request {
   327  	req, _ := http.NewRequest("DELETE", "/route", nil)
   328  	return req.WithContext(context.Background())
   329  }
   330  
   331  func newTestInstrumentMethodMetrics() instrument.MethodMetrics {
   332  	return instrument.NewMethodMetrics(tally.NoopScope, "testRoute", instrument.TimerOptions{})
   333  }
   334  
   335  func newTestBulkReqBody() updateRuleSetRequest {
   336  	return updateRuleSetRequest{
   337  		RuleSetVersion: 1,
   338  		RuleSetChanges: changes.RuleSetChanges{
   339  			Namespace: "testNamespace",
   340  			RollupRuleChanges: []changes.RollupRuleChange{
   341  				changes.RollupRuleChange{
   342  					Op: changes.AddOp,
   343  					RuleData: &view.RollupRule{
   344  						Name: "rollupRule3",
   345  					},
   346  				},
   347  			},
   348  			MappingRuleChanges: []changes.MappingRuleChange{
   349  				changes.MappingRuleChange{
   350  					Op: changes.AddOp,
   351  					RuleData: &view.MappingRule{
   352  						Name: "mappingRule3",
   353  					},
   354  				},
   355  			},
   356  		},
   357  	}
   358  }
   359  
   360  // nolint: unparam
   361  func newTestRuleSet(version int) rules.RuleSet {
   362  	helper := rules.NewRuleSetUpdateHelper(time.Minute)
   363  	// For testing all updates happen at the 0 epoch
   364  	meta := helper.NewUpdateMetadata(0, "originalAuthor")
   365  
   366  	mrs := rules.NewEmptyRuleSet("testNamespace", rules.UpdateMetadata{})
   367  	mrs.AddRollupRule(
   368  		view.RollupRule{
   369  			Name: "rollupRule1",
   370  		},
   371  		meta,
   372  	)
   373  	mrs.AddRollupRule(
   374  		view.RollupRule{
   375  			Name: "rollupRule2",
   376  		},
   377  		meta,
   378  	)
   379  	mrs.AddMappingRule(
   380  		view.MappingRule{
   381  			Name: "mappingRule1",
   382  		},
   383  		meta,
   384  	)
   385  	mrs.AddMappingRule(
   386  		view.MappingRule{
   387  			Name: "mappingRule2",
   388  		},
   389  		meta,
   390  	)
   391  	proto, _ := mrs.Proto()
   392  	rs, _ := rules.NewRuleSetFromProto(version, proto, rules.NewOptions())
   393  	return rs
   394  }
   395  
   396  // TODO(jskelcy): Migrate to use mockgen mock for testing.
   397  type mockStore struct{}
   398  
   399  func newMockStore() store.Store {
   400  	return mockStore{}
   401  }
   402  
   403  func (s mockStore) FetchNamespaces() (view.Namespaces, error) {
   404  	return view.Namespaces{}, nil
   405  }
   406  
   407  func (s mockStore) ValidateRuleSet(rs view.RuleSet) error {
   408  	return nil
   409  }
   410  
   411  func (s mockStore) UpdateRuleSet(rsChanges changes.RuleSetChanges, version int, uOpts store.UpdateOptions) (view.RuleSet, error) {
   412  	return view.RuleSet{}, nil
   413  }
   414  
   415  func (s mockStore) CreateNamespace(namespaceID string, uOpts store.UpdateOptions) (view.Namespace, error) {
   416  	return view.Namespace{}, nil
   417  }
   418  
   419  func (s mockStore) DeleteNamespace(namespaceID string, uOpts store.UpdateOptions) error {
   420  	return nil
   421  }
   422  
   423  //nolint: unparam
   424  func (s mockStore) FetchRuleSet(namespaceID string) (rules.RuleSet, error) {
   425  	return newTestRuleSet(1), nil
   426  }
   427  
   428  func (s mockStore) FetchRuleSetSnapshot(namespaceID string) (view.RuleSet, error) {
   429  	return view.RuleSet{}, nil
   430  }
   431  
   432  func (s mockStore) FetchMappingRule(namespaceID, mappingRuleID string) (view.MappingRule, error) {
   433  	return view.MappingRule{}, nil
   434  }
   435  
   436  func (s mockStore) CreateMappingRule(namespaceID string, mrv view.MappingRule, uOpts store.UpdateOptions) (view.MappingRule, error) {
   437  	return view.MappingRule{}, nil
   438  }
   439  
   440  func (s mockStore) UpdateMappingRule(namespaceID, mappingRuleID string, mrv view.MappingRule, uOpts store.UpdateOptions) (view.MappingRule, error) {
   441  	return view.MappingRule{}, nil
   442  }
   443  
   444  func (s mockStore) DeleteMappingRule(namespaceID, mappingRuleID string, uOpts store.UpdateOptions) error {
   445  	return nil
   446  }
   447  
   448  func (s mockStore) FetchMappingRuleHistory(namespaceID, mappingRuleID string) ([]view.MappingRule, error) {
   449  	return make([]view.MappingRule, 0), nil
   450  }
   451  
   452  func (s mockStore) FetchRollupRule(namespaceID, rollupRuleID string) (view.RollupRule, error) {
   453  	return view.RollupRule{}, nil
   454  }
   455  
   456  func (s mockStore) CreateRollupRule(namespaceID string, rrv view.RollupRule, uOpts store.UpdateOptions) (view.RollupRule, error) {
   457  	return view.RollupRule{}, nil
   458  }
   459  
   460  func (s mockStore) UpdateRollupRule(namespaceID, rollupRuleID string, rrv view.RollupRule, uOpts store.UpdateOptions) (view.RollupRule, error) {
   461  	return view.RollupRule{}, nil
   462  }
   463  
   464  func (s mockStore) DeleteRollupRule(namespaceID, rollupRuleID string, uOpts store.UpdateOptions) error {
   465  	return nil
   466  }
   467  
   468  func (s mockStore) FetchRollupRuleHistory(namespaceID, rollupRuleID string) ([]view.RollupRule, error) {
   469  	return make([]view.RollupRule, 0), nil
   470  }
   471  
   472  func (s mockStore) Close() {}