github.com/willyham/dosa@v2.3.1-0.20171024181418-1e446d37ee71+incompatible/entity_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 dosa_test
    22  
    23  import (
    24  	"testing"
    25  
    26  	"github.com/stretchr/testify/assert"
    27  	"github.com/uber-go/dosa"
    28  )
    29  
    30  func TestEntityDefinitionEnsureValid(t *testing.T) {
    31  	type testData struct {
    32  		e     *dosa.EntityDefinition
    33  		valid bool
    34  		msg   string
    35  	}
    36  
    37  	invalidName := getValidEntityDefinition()
    38  	invalidName.Name = "foo=bar"
    39  
    40  	nilColumn := getValidEntityDefinition()
    41  	nilColumn.Columns = append(nilColumn.Columns, nil)
    42  
    43  	invalidColumnName := getValidEntityDefinition()
    44  	invalidColumnName.Columns[0].Name = "AAA"
    45  
    46  	dupColumnNames := getValidEntityDefinition()
    47  	dupColumnNames.Columns = append(dupColumnNames.Columns, &dosa.ColumnDefinition{Name: "bar", Type: dosa.Int64})
    48  
    49  	invalidColumnType := getValidEntityDefinition()
    50  	invalidColumnType.Columns[0].Type = dosa.Invalid
    51  
    52  	nilPK := getValidEntityDefinition()
    53  	nilPK.Key = nil
    54  
    55  	noPartitionKey := getValidEntityDefinition()
    56  	noPartitionKey.Key.PartitionKeys = []string{}
    57  
    58  	invalidPartitionKeyName := getValidEntityDefinition()
    59  	invalidPartitionKeyName.Key.PartitionKeys[0] = "fox"
    60  
    61  	dupParitionKeyNames := getValidEntityDefinition()
    62  	dupParitionKeyNames.Key.PartitionKeys = append(dupParitionKeyNames.Key.PartitionKeys, "foo")
    63  
    64  	nilClusteringKey := getValidEntityDefinition()
    65  	nilClusteringKey.Key.ClusteringKeys = append(nilClusteringKey.Key.ClusteringKeys, nil)
    66  
    67  	invalidClusteringKeyName := getValidEntityDefinition()
    68  	invalidClusteringKeyName.Key.ClusteringKeys[0].Name = "fox"
    69  
    70  	dupClusteringKeyName := getValidEntityDefinition()
    71  	dupClusteringKeyName.Key.ClusteringKeys[0].Name = "foo"
    72  
    73  	noClusteringKey := getValidEntityDefinition()
    74  	noClusteringKey.Key.ClusteringKeys = []*dosa.ClusteringKey{}
    75  
    76  	data := []testData{
    77  		{
    78  			e:     nil,
    79  			valid: false,
    80  			msg:   "EntityDefinition is nil",
    81  		},
    82  		{
    83  			e:     invalidName,
    84  			valid: false,
    85  			msg:   "name must contain only",
    86  		},
    87  		{
    88  			e:     nilColumn,
    89  			valid: false,
    90  			msg:   "has nil column",
    91  		},
    92  		{
    93  			e:     invalidColumnName,
    94  			valid: false,
    95  			msg:   "has invalid column name",
    96  		},
    97  		{
    98  			e:     dupColumnNames,
    99  			valid: false,
   100  			msg:   "duplicated column found",
   101  		},
   102  		{
   103  			e:     invalidColumnType,
   104  			valid: false,
   105  			msg:   "invalid type for column",
   106  		},
   107  		{
   108  			e:     invalidColumnType,
   109  			valid: false,
   110  			msg:   "\"foo\"",
   111  		},
   112  		{
   113  			e:     nilPK,
   114  			valid: false,
   115  			msg:   "nil primary key",
   116  		},
   117  		{
   118  			e:     noPartitionKey,
   119  			valid: false,
   120  			msg:   "does not have partition key",
   121  		},
   122  		{
   123  			e:     invalidPartitionKeyName,
   124  			valid: false,
   125  			msg:   "partition key does not refer to a column",
   126  		},
   127  		{
   128  			e:     invalidPartitionKeyName,
   129  			valid: false,
   130  			msg:   "\"fox\"",
   131  		},
   132  		{
   133  			e:     dupParitionKeyNames,
   134  			valid: false,
   135  			msg:   "a column cannot be used twice in key",
   136  		},
   137  		{
   138  			e:     dupParitionKeyNames,
   139  			valid: false,
   140  			msg:   "\"foo\"",
   141  		},
   142  		{
   143  			e:     invalidClusteringKeyName,
   144  			valid: false,
   145  			msg:   "\"fox\"",
   146  		},
   147  		{
   148  			e:     invalidClusteringKeyName,
   149  			valid: false,
   150  			msg:   "does not refer to",
   151  		},
   152  		{
   153  			e:     dupClusteringKeyName,
   154  			valid: false,
   155  			msg:   "a column cannot be used twice in key",
   156  		},
   157  		{
   158  			e:     getValidEntityDefinition(),
   159  			valid: true,
   160  			msg:   "should be a valid EntityDefinition",
   161  		},
   162  		{
   163  			e:     noClusteringKey,
   164  			valid: true,
   165  			msg:   "no clustering key is ok",
   166  		},
   167  		{
   168  			e:     nilClusteringKey,
   169  			valid: false,
   170  			msg:   "nil clustering key",
   171  		},
   172  	}
   173  
   174  	for _, entry := range data {
   175  		err := entry.e.EnsureValid()
   176  		if entry.valid {
   177  			assert.NoError(t, err, entry.msg)
   178  		} else {
   179  			assert.Error(t, err, entry.msg)
   180  			assert.Contains(t, err.Error(), entry.msg)
   181  		}
   182  	}
   183  }
   184  
   185  func TestEntityDefinitionEnsureValidForIndex(t *testing.T) {
   186  	type testData struct {
   187  		e     *dosa.EntityDefinition
   188  		valid bool
   189  		msg   string
   190  	}
   191  
   192  	invalidName := getValidEntityDefinition()
   193  	invalidName.Indexes["index3=1123"] = invalidName.Indexes["index1"]
   194  
   195  	nilPK := getValidEntityDefinition()
   196  	nilPK.Indexes["index1"].Key = nil
   197  
   198  	nilIndex := getValidEntityDefinition()
   199  	nilIndex.Indexes["index1"] = nil
   200  
   201  	noPartitionKey := getValidEntityDefinition()
   202  	noPartitionKey.Indexes["index1"].Key.PartitionKeys = []string{}
   203  
   204  	invalidPartitionKeyName := getValidEntityDefinition()
   205  	invalidPartitionKeyName.Indexes["index1"].Key.PartitionKeys[0] = "fox"
   206  
   207  	dupParitionKeyNames := getValidEntityDefinition()
   208  	dupParitionKeyNames.Indexes["index1"].Key.PartitionKeys = append(dupParitionKeyNames.Key.PartitionKeys, "foo")
   209  
   210  	nilClusteringKey := getValidEntityDefinition()
   211  	nilClusteringKey.Indexes["index1"].Key.ClusteringKeys = append(nilClusteringKey.Key.ClusteringKeys, nil)
   212  
   213  	invalidClusteringKeyName := getValidEntityDefinition()
   214  	invalidClusteringKeyName.Indexes["index1"].Key.ClusteringKeys[0].Name = "fox"
   215  
   216  	dupClusteringKeyName := getValidEntityDefinition()
   217  	dupClusteringKeyName.Indexes["index1"].Key.ClusteringKeys[0].Name = "qux"
   218  
   219  	noClusteringKey := getValidEntityDefinition()
   220  	noClusteringKey.Indexes["index1"].Key.ClusteringKeys = []*dosa.ClusteringKey{}
   221  
   222  	data := []testData{
   223  		{
   224  			e:     invalidName,
   225  			valid: false,
   226  			msg:   "name must contain only",
   227  		},
   228  		{
   229  			e:     nilPK,
   230  			valid: false,
   231  			msg:   "nil key",
   232  		},
   233  		{
   234  			e:     nilIndex,
   235  			valid: false,
   236  			msg:   "is nil",
   237  		},
   238  		{
   239  			e:     noPartitionKey,
   240  			valid: false,
   241  			msg:   "does not have partition key",
   242  		},
   243  		{
   244  			e:     invalidPartitionKeyName,
   245  			valid: false,
   246  			msg:   "partition key does not refer to a column",
   247  		},
   248  		{
   249  			e:     invalidPartitionKeyName,
   250  			valid: false,
   251  			msg:   "\"fox\"",
   252  		},
   253  		{
   254  			e:     dupParitionKeyNames,
   255  			valid: false,
   256  			msg:   "a column cannot be used twice in index key",
   257  		},
   258  		{
   259  			e:     dupParitionKeyNames,
   260  			valid: false,
   261  			msg:   "\"foo\"",
   262  		},
   263  		{
   264  			e:     invalidClusteringKeyName,
   265  			valid: false,
   266  			msg:   "\"fox\"",
   267  		},
   268  		{
   269  			e:     invalidClusteringKeyName,
   270  			valid: false,
   271  			msg:   "does not refer to",
   272  		},
   273  		{
   274  			e:     dupClusteringKeyName,
   275  			valid: false,
   276  			msg:   "a column cannot be used twice in index key",
   277  		},
   278  		{
   279  			e:     noClusteringKey,
   280  			valid: true,
   281  			msg:   "no clustering key is ok",
   282  		},
   283  		{
   284  			e:     nilClusteringKey,
   285  			valid: false,
   286  			msg:   "nil clustering key",
   287  		},
   288  	}
   289  
   290  	for _, entry := range data {
   291  		err := entry.e.EnsureValid()
   292  		if entry.valid {
   293  			assert.NoError(t, err, entry.msg)
   294  		} else {
   295  			assert.Error(t, err, entry.msg)
   296  			assert.Contains(t, err.Error(), entry.msg)
   297  		}
   298  	}
   299  }
   300  
   301  func TestEntityDefinitionHelpers(t *testing.T) {
   302  	ed := getValidEntityDefinition()
   303  
   304  	expectedColumnTypes := map[string]dosa.Type{
   305  		"foo": dosa.TUUID,
   306  		"bar": dosa.Int64,
   307  		"qux": dosa.Blob,
   308  	}
   309  	assert.Equal(t, expectedColumnTypes, ed.ColumnTypes())
   310  
   311  	expectedPartitionKeySet := map[string]struct{}{"foo": {}}
   312  	assert.Equal(t, expectedPartitionKeySet, ed.PartitionKeySet())
   313  	assert.Equal(t, expectedPartitionKeySet, ed.Key.PartitionKeySet())
   314  
   315  	expectedClusteringKeySet := map[string]struct{}{"bar": {}}
   316  	assert.Equal(t, expectedClusteringKeySet, ed.Key.ClusteringKeySet())
   317  
   318  	expectedKeySet := map[string]struct{}{"foo": {}, "bar": {}}
   319  	assert.Equal(t, expectedKeySet, ed.KeySet())
   320  }
   321  
   322  func getValidEntityDefinition() *dosa.EntityDefinition {
   323  	return &dosa.EntityDefinition{
   324  		Name: "testentity",
   325  		Key: &dosa.PrimaryKey{
   326  			PartitionKeys: []string{"foo"},
   327  			ClusteringKeys: []*dosa.ClusteringKey{
   328  				{
   329  					Name:       "bar",
   330  					Descending: true,
   331  				},
   332  			},
   333  		},
   334  		Indexes: map[string]*dosa.IndexDefinition{
   335  			"index1": {
   336  				Key: &dosa.PrimaryKey{
   337  					PartitionKeys: []string{"qux"},
   338  					ClusteringKeys: []*dosa.ClusteringKey{
   339  						{
   340  							Name:       "bar",
   341  							Descending: true,
   342  						},
   343  					},
   344  				},
   345  			},
   346  
   347  			"index2": {
   348  				Key: &dosa.PrimaryKey{
   349  					PartitionKeys: []string{"bar"},
   350  				},
   351  			},
   352  		},
   353  		Columns: []*dosa.ColumnDefinition{
   354  			{
   355  				Name: "foo",
   356  				Type: dosa.TUUID,
   357  			},
   358  			{
   359  				Name: "bar",
   360  				Type: dosa.Int64,
   361  			},
   362  			{
   363  				Name: "qux",
   364  				Type: dosa.Blob,
   365  			},
   366  		},
   367  	}
   368  }
   369  
   370  func TestEntityDefinitionIsCompatible(t *testing.T) {
   371  	validEd := getValidEntityDefinition()
   372  	// entity name not match
   373  	errEd := getValidEntityDefinition()
   374  	errEd.Name = errEd.Name + "error"
   375  	err := validEd.IsCompatible(errEd)
   376  	assert.Error(t, err)
   377  	assert.Contains(t, err.Error(), "entity name")
   378  
   379  	// partition key's size doesn't match
   380  	// less
   381  	errEd = getValidEntityDefinition()
   382  	errEd.Key.PartitionKeys = []string{}
   383  	err = validEd.IsCompatible(errEd)
   384  	assert.Error(t, err)
   385  	assert.Contains(t, err.Error(), "partition")
   386  
   387  	// more
   388  	errEd = getValidEntityDefinition()
   389  	errEd.Key.PartitionKeys = append(errEd.Key.PartitionKeys, "bar")
   390  	err = validEd.IsCompatible(errEd)
   391  	assert.Error(t, err)
   392  	assert.Contains(t, err.Error(), "partition")
   393  
   394  	// not same partition key
   395  	errEd = getValidEntityDefinition()
   396  	errEd.Key.PartitionKeys = []string{"bar"}
   397  	err = validEd.IsCompatible(errEd)
   398  	assert.Error(t, err)
   399  	assert.Contains(t, err.Error(), "partition")
   400  
   401  	// clustering key's size doesn't match
   402  	// less
   403  	errEd = getValidEntityDefinition()
   404  	errEd.Key.ClusteringKeys = nil
   405  	err = validEd.IsCompatible(errEd)
   406  	assert.Error(t, err)
   407  	assert.Contains(t, err.Error(), "clustering")
   408  
   409  	// more
   410  	errEd = getValidEntityDefinition()
   411  	errEd.Key.ClusteringKeys = append(errEd.Key.ClusteringKeys, &dosa.ClusteringKey{Name: "qux", Descending: false})
   412  	err = validEd.IsCompatible(errEd)
   413  	assert.Error(t, err)
   414  	assert.Contains(t, err.Error(), "clustering")
   415  
   416  	// empty clustering key
   417  	errEd = getValidEntityDefinition()
   418  	errEd.Key.ClusteringKeys = nil
   419  
   420  	errEd1 := getValidEntityDefinition()
   421  	errEd1.Key.ClusteringKeys = make([]*dosa.ClusteringKey, 0)
   422  	err = errEd.IsCompatible(errEd1)
   423  	assert.NoError(t, err)
   424  
   425  	// not same clustering key
   426  	// name not match
   427  	errEd = getValidEntityDefinition()
   428  	errEd.Key.ClusteringKeys[0].Name = "qux"
   429  	err = validEd.IsCompatible(errEd)
   430  	assert.Error(t, err)
   431  	assert.Contains(t, err.Error(), "clustering")
   432  
   433  	// descending not match
   434  	errEd = getValidEntityDefinition()
   435  	errEd.Key.ClusteringKeys[0].Descending = !errEd.Key.ClusteringKeys[0].Descending
   436  	err = validEd.IsCompatible(errEd)
   437  	assert.Error(t, err)
   438  	assert.Contains(t, err.Error(), "clustering")
   439  
   440  	// column size is less
   441  	errEd = getValidEntityDefinition()
   442  	errEd.Columns = append(errEd.Columns, &dosa.ColumnDefinition{Name: "abc", Type: dosa.Bool})
   443  	err = validEd.IsCompatible(errEd)
   444  	assert.Error(t, err)
   445  	assert.Contains(t, err.Error(), "column")
   446  
   447  	// column not match
   448  	// name not match
   449  	errEd = getValidEntityDefinition()
   450  	errEd.Columns[0].Name = errEd.Columns[0].Name + "error"
   451  	err = validEd.IsCompatible(errEd)
   452  	assert.Error(t, err)
   453  	assert.Contains(t, err.Error(), "column")
   454  
   455  	// type not match
   456  	errEd = getValidEntityDefinition()
   457  	errEd.Columns[0].Type = dosa.Invalid
   458  	err = validEd.IsCompatible(errEd)
   459  	assert.Error(t, err)
   460  	assert.Contains(t, err.Error(), "type")
   461  
   462  	// index not match
   463  	errEd = getValidEntityDefinition()
   464  	errEd.Indexes["index1"] = &dosa.IndexDefinition{
   465  		Key: &dosa.PrimaryKey{
   466  			PartitionKeys: []string{"bar, qux"},
   467  		},
   468  	}
   469  	err = validEd.IsCompatible(errEd)
   470  	assert.Error(t, err)
   471  	assert.Contains(t, err.Error(), "index")
   472  
   473  	// same entity
   474  	// name not match
   475  	aEd := getValidEntityDefinition()
   476  	err = validEd.IsCompatible(aEd)
   477  	assert.NoError(t, err)
   478  	// reverse
   479  	err = aEd.IsCompatible(validEd)
   480  	assert.NoError(t, err)
   481  
   482  	// add new column
   483  	aEd = getValidEntityDefinition()
   484  	aEd.Columns = append(aEd.Columns, &dosa.ColumnDefinition{Name: "col", Type: dosa.Bool})
   485  	err = aEd.IsCompatible(validEd)
   486  	assert.NoError(t, err)
   487  
   488  	// reverse
   489  	err = validEd.IsCompatible(aEd)
   490  	assert.Error(t, err)
   491  
   492  	// add new index
   493  	aEd = getValidEntityDefinition()
   494  	aEd.Indexes["newindex"] = &dosa.IndexDefinition{
   495  		Key: &dosa.PrimaryKey{
   496  			PartitionKeys: []string{"bar, qux"},
   497  		},
   498  	}
   499  
   500  	err = aEd.IsCompatible(validEd)
   501  	assert.NoError(t, err)
   502  }
   503  
   504  func TestEntityDefinition_FindColumnDefinition(t *testing.T) {
   505  	ed := getValidEntityDefinition()
   506  
   507  	// for each known column, make sure we find the column definition with the same name
   508  	for _, name := range []string{"foo", "bar", "qux"} {
   509  		assert.Equal(t, name, ed.FindColumnDefinition(name).Name)
   510  	}
   511  
   512  	assert.Nil(t, ed.FindColumnDefinition("notacolumn"))
   513  }
   514  
   515  func TestClone(t *testing.T) {
   516  	ed := getValidEntityDefinition()
   517  	ed1 := ed.Clone()
   518  	assert.Equal(t, ed, ed1)
   519  }