github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/metrics/rules/mapping_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 rules
    22  
    23  import (
    24  	"strings"
    25  	"testing"
    26  	"time"
    27  
    28  	"github.com/m3db/m3/src/metrics/aggregation"
    29  	"github.com/m3db/m3/src/metrics/errors"
    30  	"github.com/m3db/m3/src/metrics/filters"
    31  	"github.com/m3db/m3/src/metrics/generated/proto/aggregationpb"
    32  	"github.com/m3db/m3/src/metrics/generated/proto/metricpb"
    33  	"github.com/m3db/m3/src/metrics/generated/proto/policypb"
    34  	"github.com/m3db/m3/src/metrics/generated/proto/rulepb"
    35  	"github.com/m3db/m3/src/metrics/policy"
    36  	"github.com/m3db/m3/src/metrics/rules/view"
    37  	"github.com/m3db/m3/src/query/models"
    38  	xtime "github.com/m3db/m3/src/x/time"
    39  
    40  	"github.com/google/go-cmp/cmp"
    41  	"github.com/google/go-cmp/cmp/cmpopts"
    42  	"github.com/stretchr/testify/require"
    43  )
    44  
    45  var (
    46  	testMappingRuleSnapshot1V1Proto = &rulepb.MappingRuleSnapshot{
    47  		Name:         "foo",
    48  		Tombstoned:   false,
    49  		CutoverNanos: 12345000000,
    50  		Filter:       "tag1:value1 tag2:value2",
    51  		Policies: []*policypb.Policy{
    52  			&policypb.Policy{
    53  				StoragePolicy: &policypb.StoragePolicy{
    54  					Resolution: policypb.Resolution{
    55  						WindowSize: int64(10 * time.Second),
    56  						Precision:  int64(time.Second),
    57  					},
    58  					Retention: policypb.Retention{
    59  						Period: int64(24 * time.Hour),
    60  					},
    61  				},
    62  			},
    63  		},
    64  		DropPolicy:         policypb.DropPolicy_NONE,
    65  		LastUpdatedAtNanos: 12345000000,
    66  		LastUpdatedBy:      "someone",
    67  	}
    68  	testMappingRuleSnapshot2V1Proto = &rulepb.MappingRuleSnapshot{
    69  		Name:         "bar",
    70  		Tombstoned:   true,
    71  		CutoverNanos: 67890000000,
    72  		Filter:       "tag3:value3 tag4:value4",
    73  		Policies: []*policypb.Policy{
    74  			&policypb.Policy{
    75  				StoragePolicy: &policypb.StoragePolicy{
    76  					Resolution: policypb.Resolution{
    77  						WindowSize: int64(time.Minute),
    78  						Precision:  int64(time.Minute),
    79  					},
    80  					Retention: policypb.Retention{
    81  						Period: int64(24 * time.Hour),
    82  					},
    83  				},
    84  				AggregationTypes: []aggregationpb.AggregationType{
    85  					aggregationpb.AggregationType_MEAN,
    86  				},
    87  			},
    88  			&policypb.Policy{
    89  				StoragePolicy: &policypb.StoragePolicy{
    90  					Resolution: policypb.Resolution{
    91  						WindowSize: int64(5 * time.Minute),
    92  						Precision:  int64(time.Minute),
    93  					},
    94  					Retention: policypb.Retention{
    95  						Period: int64(48 * time.Hour),
    96  					},
    97  				},
    98  				AggregationTypes: []aggregationpb.AggregationType{
    99  					aggregationpb.AggregationType_MEAN,
   100  				},
   101  			},
   102  		},
   103  		DropPolicy:         policypb.DropPolicy_NONE,
   104  		LastUpdatedAtNanos: 67890000000,
   105  		LastUpdatedBy:      "someone-else",
   106  	}
   107  	testMappingRuleSnapshot3V2Proto = &rulepb.MappingRuleSnapshot{
   108  		Name:               "foo",
   109  		Tombstoned:         false,
   110  		CutoverNanos:       12345000000,
   111  		Filter:             "tag1:value1 tag2:value2",
   112  		LastUpdatedAtNanos: 12345000000,
   113  		LastUpdatedBy:      "someone",
   114  		StoragePolicies: []*policypb.StoragePolicy{
   115  			&policypb.StoragePolicy{
   116  				Resolution: policypb.Resolution{
   117  					WindowSize: 10 * time.Second.Nanoseconds(),
   118  					Precision:  time.Second.Nanoseconds(),
   119  				},
   120  				Retention: policypb.Retention{
   121  					Period: 24 * time.Hour.Nanoseconds(),
   122  				},
   123  			},
   124  			&policypb.StoragePolicy{
   125  				Resolution: policypb.Resolution{
   126  					WindowSize: time.Minute.Nanoseconds(),
   127  					Precision:  time.Minute.Nanoseconds(),
   128  				},
   129  				Retention: policypb.Retention{
   130  					Period: 720 * time.Hour.Nanoseconds(),
   131  				},
   132  			},
   133  			&policypb.StoragePolicy{
   134  				Resolution: policypb.Resolution{
   135  					WindowSize: time.Hour.Nanoseconds(),
   136  					Precision:  time.Hour.Nanoseconds(),
   137  				},
   138  				Retention: policypb.Retention{
   139  					Period: 365 * 24 * time.Hour.Nanoseconds(),
   140  				},
   141  			},
   142  		},
   143  		DropPolicy: policypb.DropPolicy_NONE,
   144  		Tags:       []*metricpb.Tag{},
   145  	}
   146  	testMappingRuleSnapshot4V2Proto = &rulepb.MappingRuleSnapshot{
   147  		Name:               "bar",
   148  		Tombstoned:         true,
   149  		CutoverNanos:       67890000000,
   150  		Filter:             "tag3:value3 tag4:value4",
   151  		LastUpdatedAtNanos: 67890000000,
   152  		LastUpdatedBy:      "someone-else",
   153  		AggregationTypes: []aggregationpb.AggregationType{
   154  			aggregationpb.AggregationType_MIN,
   155  			aggregationpb.AggregationType_MAX,
   156  		},
   157  		StoragePolicies: []*policypb.StoragePolicy{
   158  			&policypb.StoragePolicy{
   159  				Resolution: policypb.Resolution{
   160  					WindowSize: 10 * time.Minute.Nanoseconds(),
   161  					Precision:  time.Minute.Nanoseconds(),
   162  				},
   163  				Retention: policypb.Retention{
   164  					Period: 1800 * time.Hour.Nanoseconds(),
   165  				},
   166  			},
   167  		},
   168  		DropPolicy: policypb.DropPolicy_NONE,
   169  		Tags:       []*metricpb.Tag{},
   170  	}
   171  	testMappingRuleSnapshot5V2Proto = &rulepb.MappingRuleSnapshot{
   172  		Name:               "foo",
   173  		Tombstoned:         false,
   174  		CutoverNanos:       12345000000,
   175  		Filter:             "tag1:value1 tag2:value2",
   176  		LastUpdatedAtNanos: 12345000000,
   177  		LastUpdatedBy:      "someone",
   178  		StoragePolicies:    []*policypb.StoragePolicy{},
   179  		DropPolicy:         policypb.DropPolicy_DROP_MUST,
   180  		Tags:               []*metricpb.Tag{},
   181  	}
   182  	testMappingRuleSnapshot6V2Proto = &rulepb.MappingRuleSnapshot{
   183  		Name:               "foo",
   184  		Tombstoned:         false,
   185  		CutoverNanos:       67890000000,
   186  		Filter:             "tag1:value1 tag2:value2",
   187  		LastUpdatedAtNanos: 67890000000,
   188  		LastUpdatedBy:      "someone-else",
   189  		StoragePolicies:    []*policypb.StoragePolicy{},
   190  		DropPolicy:         policypb.DropPolicy_DROP_IF_ONLY_MATCH,
   191  		Tags:               []*metricpb.Tag{},
   192  	}
   193  	testMappingRule1V1Proto = &rulepb.MappingRule{
   194  		Uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
   195  		Snapshots: []*rulepb.MappingRuleSnapshot{
   196  			testMappingRuleSnapshot1V1Proto,
   197  			testMappingRuleSnapshot2V1Proto,
   198  		},
   199  	}
   200  	testMappingRule2V2Proto = &rulepb.MappingRule{
   201  		Uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
   202  		Snapshots: []*rulepb.MappingRuleSnapshot{
   203  			testMappingRuleSnapshot3V2Proto,
   204  			testMappingRuleSnapshot4V2Proto,
   205  		},
   206  	}
   207  	testMappingRule3V2Proto = &rulepb.MappingRule{
   208  		Uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
   209  		Snapshots: []*rulepb.MappingRuleSnapshot{
   210  			testMappingRuleSnapshot5V2Proto,
   211  			testMappingRuleSnapshot6V2Proto,
   212  		},
   213  	}
   214  	testMappingRuleSnapshot1 = &mappingRuleSnapshot{
   215  		name:          "foo",
   216  		tombstoned:    false,
   217  		cutoverNanos:  12345000000,
   218  		rawFilter:     "tag1:value1 tag2:value2",
   219  		aggregationID: aggregation.DefaultID,
   220  		storagePolicies: policy.StoragePolicies{
   221  			policy.NewStoragePolicy(10*time.Second, xtime.Second, 24*time.Hour),
   222  		},
   223  		dropPolicy:         policy.DropNone,
   224  		lastUpdatedAtNanos: 12345000000,
   225  		lastUpdatedBy:      "someone",
   226  		tags:               []models.Tag{},
   227  	}
   228  	testMappingRuleSnapshot2 = &mappingRuleSnapshot{
   229  		name:          "bar",
   230  		tombstoned:    true,
   231  		cutoverNanos:  67890000000,
   232  		rawFilter:     "tag3:value3 tag4:value4",
   233  		aggregationID: aggregation.MustCompressTypes(aggregation.Mean),
   234  		storagePolicies: policy.StoragePolicies{
   235  			policy.NewStoragePolicy(time.Minute, xtime.Minute, 24*time.Hour),
   236  			policy.NewStoragePolicy(5*time.Minute, xtime.Minute, 48*time.Hour),
   237  		},
   238  		dropPolicy:         policy.DropNone,
   239  		lastUpdatedAtNanos: 67890000000,
   240  		lastUpdatedBy:      "someone-else",
   241  		tags:               []models.Tag{},
   242  	}
   243  	testMappingRuleSnapshot3 = &mappingRuleSnapshot{
   244  		name:          "foo",
   245  		tombstoned:    false,
   246  		cutoverNanos:  12345000000,
   247  		rawFilter:     "tag1:value1 tag2:value2",
   248  		aggregationID: aggregation.DefaultID,
   249  		storagePolicies: policy.StoragePolicies{
   250  			policy.NewStoragePolicy(10*time.Second, xtime.Second, 24*time.Hour),
   251  			policy.NewStoragePolicy(time.Minute, xtime.Minute, 720*time.Hour),
   252  			policy.NewStoragePolicy(time.Hour, xtime.Hour, 365*24*time.Hour),
   253  		},
   254  		dropPolicy:         policy.DropNone,
   255  		lastUpdatedAtNanos: 12345000000,
   256  		lastUpdatedBy:      "someone",
   257  		tags:               []models.Tag{},
   258  	}
   259  	testMappingRuleSnapshot4 = &mappingRuleSnapshot{
   260  		name:          "bar",
   261  		tombstoned:    true,
   262  		cutoverNanos:  67890000000,
   263  		rawFilter:     "tag3:value3 tag4:value4",
   264  		aggregationID: aggregation.MustCompressTypes(aggregation.Min, aggregation.Max),
   265  		storagePolicies: policy.StoragePolicies{
   266  			policy.NewStoragePolicy(10*time.Minute, xtime.Minute, 1800*time.Hour),
   267  		},
   268  		dropPolicy:         policy.DropNone,
   269  		lastUpdatedAtNanos: 67890000000,
   270  		lastUpdatedBy:      "someone-else",
   271  		tags:               []models.Tag{},
   272  	}
   273  	testMappingRuleSnapshot5 = &mappingRuleSnapshot{
   274  		name:               "foo",
   275  		tombstoned:         false,
   276  		cutoverNanos:       12345000000,
   277  		rawFilter:          "tag1:value1 tag2:value2",
   278  		aggregationID:      aggregation.DefaultID,
   279  		storagePolicies:    policy.StoragePolicies{},
   280  		dropPolicy:         policy.DropMust,
   281  		lastUpdatedAtNanos: 12345000000,
   282  		lastUpdatedBy:      "someone",
   283  		tags:               []models.Tag{},
   284  	}
   285  	testMappingRuleSnapshot6 = &mappingRuleSnapshot{
   286  		name:               "foo",
   287  		tombstoned:         false,
   288  		cutoverNanos:       67890000000,
   289  		rawFilter:          "tag1:value1 tag2:value2",
   290  		aggregationID:      aggregation.DefaultID,
   291  		storagePolicies:    policy.StoragePolicies{},
   292  		dropPolicy:         policy.DropIfOnlyMatch,
   293  		lastUpdatedAtNanos: 67890000000,
   294  		lastUpdatedBy:      "someone-else",
   295  		tags:               []models.Tag{},
   296  	}
   297  	testMappingRule1 = &mappingRule{
   298  		uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
   299  		snapshots: []*mappingRuleSnapshot{
   300  			testMappingRuleSnapshot1,
   301  			testMappingRuleSnapshot2,
   302  		},
   303  	}
   304  	testMappingRule2 = &mappingRule{
   305  		uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
   306  		snapshots: []*mappingRuleSnapshot{
   307  			testMappingRuleSnapshot3,
   308  			testMappingRuleSnapshot4,
   309  		},
   310  	}
   311  	testMappingRule3 = &mappingRule{
   312  		uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
   313  		snapshots: []*mappingRuleSnapshot{
   314  			testMappingRuleSnapshot5,
   315  			testMappingRuleSnapshot6,
   316  		},
   317  	}
   318  	testMappingRuleSnapshotCmpOpts = []cmp.Option{
   319  		cmp.AllowUnexported(mappingRuleSnapshot{}),
   320  		cmpopts.IgnoreInterfaces(struct{ filters.TagsFilter }{}),
   321  	}
   322  	testMappingRuleCmpOpts = []cmp.Option{
   323  		cmp.AllowUnexported(mappingRule{}),
   324  		cmp.AllowUnexported(mappingRuleSnapshot{}),
   325  		cmpopts.IgnoreInterfaces(struct{ filters.TagsFilter }{}),
   326  	}
   327  )
   328  
   329  func TestNewMappingRuleSnapshotFromProtoNilProto(t *testing.T) {
   330  	_, err := newMappingRuleSnapshotFromProto(nil, testTagsFilterOptions())
   331  	require.Equal(t, errNilMappingRuleSnapshotProto, err)
   332  }
   333  
   334  func TestNewMappingRuleSnapshotFromV1ProtoInvalidProto(t *testing.T) {
   335  	proto := &rulepb.MappingRuleSnapshot{
   336  		Policies: []*policypb.Policy{
   337  			&policypb.Policy{},
   338  		},
   339  	}
   340  	_, err := newMappingRuleSnapshotFromProto(proto, testTagsFilterOptions())
   341  	require.Error(t, err)
   342  }
   343  
   344  func TestNewMappingRuleSnapshotFromV1Proto(t *testing.T) {
   345  	filterOpts := testTagsFilterOptions()
   346  	inputs := []*rulepb.MappingRuleSnapshot{
   347  		testMappingRuleSnapshot1V1Proto,
   348  		testMappingRuleSnapshot2V1Proto,
   349  	}
   350  	expected := []*mappingRuleSnapshot{
   351  		testMappingRuleSnapshot1,
   352  		testMappingRuleSnapshot2,
   353  	}
   354  	for i, input := range inputs {
   355  		res, err := newMappingRuleSnapshotFromProto(input, filterOpts)
   356  		require.NoError(t, err)
   357  		require.True(t, cmp.Equal(expected[i], res, testMappingRuleSnapshotCmpOpts...))
   358  		require.NotNil(t, res.filter)
   359  	}
   360  }
   361  
   362  func TestNewMappingRuleSnapshotFromV2ProtoInvalidProto(t *testing.T) {
   363  	filterOpts := testTagsFilterOptions()
   364  	proto := &rulepb.MappingRuleSnapshot{
   365  		AggregationTypes: []aggregationpb.AggregationType{
   366  			aggregationpb.AggregationType_UNKNOWN,
   367  		},
   368  	}
   369  	_, err := newMappingRuleSnapshotFromProto(proto, filterOpts)
   370  	require.Error(t, err)
   371  }
   372  
   373  func TestNewMappingRuleSnapshotFromV2Proto(t *testing.T) {
   374  	filterOpts := testTagsFilterOptions()
   375  	inputs := []*rulepb.MappingRuleSnapshot{
   376  		testMappingRuleSnapshot3V2Proto,
   377  		testMappingRuleSnapshot4V2Proto,
   378  		testMappingRuleSnapshot5V2Proto,
   379  		testMappingRuleSnapshot6V2Proto,
   380  	}
   381  	expected := []*mappingRuleSnapshot{
   382  		testMappingRuleSnapshot3,
   383  		testMappingRuleSnapshot4,
   384  		testMappingRuleSnapshot5,
   385  		testMappingRuleSnapshot6,
   386  	}
   387  	for i, input := range inputs {
   388  		res, err := newMappingRuleSnapshotFromProto(input, filterOpts)
   389  		require.NoError(t, err)
   390  		require.True(t, cmp.Equal(expected[i], res, testMappingRuleSnapshotCmpOpts...))
   391  		require.NotNil(t, res.filter)
   392  	}
   393  }
   394  
   395  func TestNewMappingRuleSnapshotFromProtoTombstoned(t *testing.T) {
   396  	filterOpts := testTagsFilterOptions()
   397  	input := &rulepb.MappingRuleSnapshot{
   398  		Name:               "foo",
   399  		Tombstoned:         true,
   400  		CutoverNanos:       12345000000,
   401  		Filter:             "tag1:value1 tag2:value2",
   402  		LastUpdatedAtNanos: 12345000000,
   403  		LastUpdatedBy:      "someone",
   404  		Tags:               []*metricpb.Tag{},
   405  	}
   406  	res, err := newMappingRuleSnapshotFromProto(input, filterOpts)
   407  	require.NoError(t, err)
   408  
   409  	expected := &mappingRuleSnapshot{
   410  		name:               "foo",
   411  		tombstoned:         true,
   412  		cutoverNanos:       12345000000,
   413  		rawFilter:          "tag1:value1 tag2:value2",
   414  		aggregationID:      aggregation.DefaultID,
   415  		lastUpdatedAtNanos: 12345000000,
   416  		lastUpdatedBy:      "someone",
   417  		tags:               []models.Tag{},
   418  	}
   419  	require.True(t, cmp.Equal(expected, res, testMappingRuleSnapshotCmpOpts...))
   420  	require.NotNil(t, res.filter)
   421  }
   422  
   423  func TestNewMappingRuleSnapshotNoStoragePoliciesAndDropPolicy(t *testing.T) {
   424  	proto := &rulepb.MappingRuleSnapshot{}
   425  	_, err := newMappingRuleSnapshotFromProto(proto, testTagsFilterOptions())
   426  	require.Equal(t, errNoStoragePoliciesAndDropPolicyInMappingRuleSnapshot, err)
   427  }
   428  
   429  func TestNewMappingRuleSnapshotStoragePoliciesAndDropPolicy(t *testing.T) {
   430  	proto := &rulepb.MappingRuleSnapshot{
   431  		StoragePolicies: []*policypb.StoragePolicy{
   432  			&policypb.StoragePolicy{
   433  				Resolution: policypb.Resolution{
   434  					WindowSize: 10 * time.Second.Nanoseconds(),
   435  					Precision:  time.Second.Nanoseconds(),
   436  				},
   437  				Retention: policypb.Retention{
   438  					Period: 24 * time.Hour.Nanoseconds(),
   439  				},
   440  			},
   441  		},
   442  		DropPolicy: policypb.DropPolicy_DROP_MUST,
   443  	}
   444  	_, err := newMappingRuleSnapshotFromProto(proto, testTagsFilterOptions())
   445  	require.Equal(t, errStoragePoliciesAndDropPolicyInMappingRuleSnapshot, err)
   446  }
   447  
   448  func TestNewMappingRuleSnapshotInvalidDropPolicy(t *testing.T) {
   449  	proto := &rulepb.MappingRuleSnapshot{
   450  		DropPolicy: policypb.DropPolicy(-1),
   451  	}
   452  	_, err := newMappingRuleSnapshotFromProto(proto, testTagsFilterOptions())
   453  	require.Equal(t, errInvalidDropPolicyInMappRuleSnapshot, err)
   454  }
   455  
   456  func TestNewMappingRuleSnapshotFromFields(t *testing.T) {
   457  	res, err := newMappingRuleSnapshotFromFields(
   458  		testMappingRuleSnapshot3.name,
   459  		testMappingRuleSnapshot3.cutoverNanos,
   460  		testMappingRuleSnapshot3.filter,
   461  		testMappingRuleSnapshot3.rawFilter,
   462  		testMappingRuleSnapshot3.aggregationID,
   463  		testMappingRuleSnapshot3.storagePolicies,
   464  		testMappingRuleSnapshot3.dropPolicy,
   465  		testMappingRuleSnapshot3.tags,
   466  		testMappingRuleSnapshot3.lastUpdatedAtNanos,
   467  		testMappingRuleSnapshot3.lastUpdatedBy,
   468  	)
   469  	require.NoError(t, err)
   470  	require.True(t, cmp.Equal(testMappingRuleSnapshot3, res, testMappingRuleSnapshotCmpOpts...))
   471  }
   472  
   473  func TestNewMappingRuleSnapshotFromFieldsValidationError(t *testing.T) {
   474  	badFilters := []string{
   475  		"tag3:",
   476  		"tag3:*a*b*c*d",
   477  		"ab[cd",
   478  	}
   479  
   480  	for _, f := range badFilters {
   481  		_, err := newMappingRuleSnapshotFromFields(
   482  			"bar",
   483  			12345000000,
   484  			nil,
   485  			f,
   486  			aggregation.DefaultID,
   487  			nil,
   488  			policy.DropNone,
   489  			nil,
   490  			1234,
   491  			"test_user",
   492  		)
   493  		require.Error(t, err)
   494  		_, ok := err.(errors.ValidationError)
   495  		require.True(t, ok)
   496  	}
   497  }
   498  
   499  func TestMappingRuleSnapshotProto(t *testing.T) {
   500  	snapshots := []*mappingRuleSnapshot{
   501  		testMappingRuleSnapshot3,
   502  		testMappingRuleSnapshot4,
   503  	}
   504  	expected := []*rulepb.MappingRuleSnapshot{
   505  		testMappingRuleSnapshot3V2Proto,
   506  		testMappingRuleSnapshot4V2Proto,
   507  	}
   508  	for i, snapshot := range snapshots {
   509  		proto, err := snapshot.proto()
   510  		require.NoError(t, err)
   511  		require.Equal(t, expected[i], proto)
   512  	}
   513  }
   514  
   515  func TestNewMappingRuleFromProtoNilProto(t *testing.T) {
   516  	_, err := newMappingRuleFromProto(nil, testTagsFilterOptions())
   517  	require.Equal(t, errNilMappingRuleProto, err)
   518  }
   519  
   520  func TestNewMappingRuleFromProtoValidProto(t *testing.T) {
   521  	filterOpts := testTagsFilterOptions()
   522  	inputs := []*rulepb.MappingRule{
   523  		testMappingRule1V1Proto,
   524  		testMappingRule2V2Proto,
   525  	}
   526  	expected := []*mappingRule{
   527  		testMappingRule1,
   528  		testMappingRule2,
   529  	}
   530  	for i, input := range inputs {
   531  		res, err := newMappingRuleFromProto(input, filterOpts)
   532  		require.NoError(t, err)
   533  		require.True(t, cmp.Equal(expected[i], res, testMappingRuleCmpOpts...))
   534  	}
   535  }
   536  
   537  func TestMappingRuleClone(t *testing.T) {
   538  	inputs := []*mappingRule{
   539  		testMappingRule1,
   540  		testMappingRule2,
   541  		testMappingRule3,
   542  	}
   543  	for _, input := range inputs {
   544  		cloned := input.clone()
   545  		require.True(t, cmp.Equal(&cloned, input, testMappingRuleCmpOpts...))
   546  
   547  		// Asserting that modifying the clone doesn't modify the original mapping rule.
   548  		cloned2 := input.clone()
   549  		require.True(t, cmp.Equal(&cloned2, input, testMappingRuleCmpOpts...))
   550  		cloned2.snapshots[0].tombstoned = true
   551  		require.False(t, cmp.Equal(&cloned2, input, testMappingRuleCmpOpts...))
   552  		require.True(t, cmp.Equal(&cloned, input, testMappingRuleCmpOpts...))
   553  	}
   554  }
   555  
   556  func TestMappingRuleProto(t *testing.T) {
   557  	inputs := []*mappingRule{
   558  		testMappingRule2,
   559  		testMappingRule3,
   560  	}
   561  	expected := []*rulepb.MappingRule{
   562  		testMappingRule2V2Proto,
   563  		testMappingRule3V2Proto,
   564  	}
   565  	for i, input := range inputs {
   566  		res, err := input.proto()
   567  		require.NoError(t, err)
   568  		require.Equal(t, expected[i], res)
   569  	}
   570  }
   571  
   572  func TestMappingRuleActiveSnapshotNotFound(t *testing.T) {
   573  	require.Nil(t, testMappingRule2.activeSnapshot(0))
   574  }
   575  
   576  func TestMappingRuleActiveSnapshotFound(t *testing.T) {
   577  	require.Equal(t, testMappingRule2.snapshots[1], testMappingRule2.activeSnapshot(100000000000))
   578  }
   579  
   580  func TestMappingRuleActiveRuleNotFound(t *testing.T) {
   581  	require.Equal(t, testMappingRule2, testMappingRule2.activeRule(0))
   582  }
   583  
   584  func TestMappingRuleActiveRuleFound(t *testing.T) {
   585  	expected := &mappingRule{
   586  		uuid:      testMappingRule2.uuid,
   587  		snapshots: testMappingRule2.snapshots[1:],
   588  	}
   589  	require.Equal(t, expected, testMappingRule2.activeRule(100000000000))
   590  }
   591  
   592  func TestMappingNameNoSnapshot(t *testing.T) {
   593  	rr := mappingRule{
   594  		uuid:      "blah",
   595  		snapshots: []*mappingRuleSnapshot{},
   596  	}
   597  	_, err := rr.name()
   598  	require.Equal(t, errNoRuleSnapshots, err)
   599  }
   600  
   601  func TestMappingTombstonedNoSnapshot(t *testing.T) {
   602  	rr := mappingRule{
   603  		uuid:      "blah",
   604  		snapshots: []*mappingRuleSnapshot{},
   605  	}
   606  	require.True(t, rr.tombstoned())
   607  }
   608  
   609  func TestMappingTombstoned(t *testing.T) {
   610  	require.True(t, testMappingRule2.tombstoned())
   611  }
   612  
   613  func TestMappingRuleMarkTombstoned(t *testing.T) {
   614  	proto := &rulepb.MappingRule{
   615  		Uuid: "12669817-13ae-40e6-ba2f-33087b262c68",
   616  		Snapshots: []*rulepb.MappingRuleSnapshot{
   617  			testMappingRuleSnapshot3V2Proto,
   618  		},
   619  	}
   620  	rr, err := newMappingRuleFromProto(proto, testTagsFilterOptions())
   621  	require.NoError(t, err)
   622  
   623  	meta := UpdateMetadata{
   624  		cutoverNanos:   67890000000,
   625  		updatedAtNanos: 10000,
   626  		updatedBy:      "john",
   627  	}
   628  	require.NoError(t, rr.markTombstoned(meta))
   629  	require.Equal(t, 2, len(rr.snapshots))
   630  	require.True(t, cmp.Equal(testMappingRuleSnapshot3, rr.snapshots[0], testMappingRuleSnapshotCmpOpts...))
   631  
   632  	expected := &mappingRuleSnapshot{
   633  		name:               "foo",
   634  		tombstoned:         true,
   635  		cutoverNanos:       67890000000,
   636  		rawFilter:          "tag1:value1 tag2:value2",
   637  		lastUpdatedAtNanos: 10000,
   638  		lastUpdatedBy:      "john",
   639  		tags:               []models.Tag{},
   640  	}
   641  	require.True(t, cmp.Equal(expected, rr.snapshots[1], testMappingRuleSnapshotCmpOpts...))
   642  }
   643  
   644  func TestMappingRuleMarkTombstonedNoSnapshots(t *testing.T) {
   645  	rr := &mappingRule{}
   646  	require.Error(t, rr.markTombstoned(UpdateMetadata{}))
   647  }
   648  
   649  func TestMappingRuleMarkTombstonedAlreadyTombstoned(t *testing.T) {
   650  	err := testMappingRule2.markTombstoned(UpdateMetadata{})
   651  	require.Error(t, err)
   652  	require.True(t, strings.Contains(err.Error(), "bar is already tombstoned"))
   653  }
   654  
   655  func TestMappingRuleMappingRuleView(t *testing.T) {
   656  	res, err := testMappingRule2.mappingRuleView(1)
   657  	require.NoError(t, err)
   658  
   659  	expected := view.MappingRule{
   660  		ID:            "12669817-13ae-40e6-ba2f-33087b262c68",
   661  		Name:          "bar",
   662  		Tombstoned:    true,
   663  		CutoverMillis: 67890,
   664  		Filter:        "tag3:value3 tag4:value4",
   665  		AggregationID: aggregation.MustCompressTypes(aggregation.Min, aggregation.Max),
   666  		StoragePolicies: policy.StoragePolicies{
   667  			policy.NewStoragePolicy(10*time.Minute, xtime.Minute, 1800*time.Hour),
   668  		},
   669  		LastUpdatedAtMillis: 67890,
   670  		LastUpdatedBy:       "someone-else",
   671  		DropPolicy:          res.DropPolicy,
   672  		Tags:                []models.Tag{},
   673  	}
   674  	require.Equal(t, expected, res)
   675  }
   676  
   677  func TestNewMappingRuleViewError(t *testing.T) {
   678  	badIndices := []int{-2, 2, 30}
   679  	for _, i := range badIndices {
   680  		_, err := testMappingRule2.mappingRuleView(i)
   681  		require.Equal(t, errMappingRuleSnapshotIndexOutOfRange, err)
   682  	}
   683  }
   684  
   685  func TestNewMappingRuleHistory(t *testing.T) {
   686  	history, err := testMappingRule2.history()
   687  	require.NoError(t, err)
   688  
   689  	expected := []view.MappingRule{
   690  		{
   691  			ID:            "12669817-13ae-40e6-ba2f-33087b262c68",
   692  			Name:          "bar",
   693  			Tombstoned:    true,
   694  			CutoverMillis: 67890,
   695  			Filter:        "tag3:value3 tag4:value4",
   696  			AggregationID: aggregation.MustCompressTypes(aggregation.Min, aggregation.Max),
   697  			StoragePolicies: policy.StoragePolicies{
   698  				policy.NewStoragePolicy(10*time.Minute, xtime.Minute, 1800*time.Hour),
   699  			},
   700  			LastUpdatedAtMillis: 67890,
   701  			LastUpdatedBy:       "someone-else",
   702  			Tags:                []models.Tag{},
   703  		},
   704  		{
   705  			ID:            "12669817-13ae-40e6-ba2f-33087b262c68",
   706  			Name:          "foo",
   707  			Tombstoned:    false,
   708  			CutoverMillis: 12345,
   709  			Filter:        "tag1:value1 tag2:value2",
   710  			AggregationID: aggregation.DefaultID,
   711  			StoragePolicies: policy.StoragePolicies{
   712  				policy.NewStoragePolicy(10*time.Second, xtime.Second, 24*time.Hour),
   713  				policy.NewStoragePolicy(time.Minute, xtime.Minute, 720*time.Hour),
   714  				policy.NewStoragePolicy(time.Hour, xtime.Hour, 365*24*time.Hour),
   715  			},
   716  			LastUpdatedAtMillis: 12345,
   717  			LastUpdatedBy:       "someone",
   718  			Tags:                []models.Tag{},
   719  		},
   720  	}
   721  	require.Equal(t, expected, history)
   722  }