go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/service/datastore/properties_test.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package datastore
    16  
    17  import (
    18  	"fmt"
    19  	"math"
    20  	"sort"
    21  	"testing"
    22  	"time"
    23  
    24  	"go.chromium.org/luci/gae/service/blobstore"
    25  
    26  	. "github.com/smartystreets/goconvey/convey"
    27  )
    28  
    29  type mybool bool
    30  type mystring string
    31  type myfloat float32
    32  
    33  func TestProperties(t *testing.T) {
    34  	t.Parallel()
    35  
    36  	Convey("Test Property", t, func() {
    37  		Convey("Construction", func() {
    38  			Convey("empty", func() {
    39  				pv := Property{}
    40  				So(pv.Value(), ShouldBeNil)
    41  				So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
    42  				So(pv.Type().String(), ShouldEqual, "PTNull")
    43  			})
    44  			Convey("set", func() {
    45  				pv := MkPropertyNI(100)
    46  				So(pv.Value(), ShouldHaveSameTypeAs, int64(100))
    47  				So(pv.Value(), ShouldEqual, 100)
    48  				So(pv.IndexSetting(), ShouldEqual, NoIndex)
    49  				So(pv.Type().String(), ShouldEqual, "PTInt")
    50  
    51  				So(pv.SetValue(nil, ShouldIndex), ShouldBeNil)
    52  				So(pv.Value(), ShouldBeNil)
    53  				So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
    54  				So(pv.Type().String(), ShouldEqual, "PTNull")
    55  			})
    56  			Convey("derived types", func() {
    57  				Convey("int", func() {
    58  					pv := MkProperty(19)
    59  					So(pv.Value(), ShouldHaveSameTypeAs, int64(19))
    60  					So(pv.Value(), ShouldEqual, 19)
    61  					So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
    62  					So(pv.Type().String(), ShouldEqual, "PTInt")
    63  				})
    64  				Convey("int32", func() {
    65  					pv := MkProperty(int32(32))
    66  					So(pv.Value(), ShouldHaveSameTypeAs, int64(32))
    67  					So(pv.Value(), ShouldEqual, 32)
    68  					So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
    69  					So(pv.Type().String(), ShouldEqual, "PTInt")
    70  				})
    71  				Convey("uint32", func() {
    72  					pv := MkProperty(uint32(32))
    73  					So(pv.Value(), ShouldHaveSameTypeAs, int64(32))
    74  					So(pv.Value(), ShouldEqual, 32)
    75  					So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
    76  					So(pv.Type().String(), ShouldEqual, "PTInt")
    77  				})
    78  				Convey("byte", func() {
    79  					pv := MkProperty(byte(32))
    80  					So(pv.Value(), ShouldHaveSameTypeAs, int64(32))
    81  					So(pv.Value(), ShouldEqual, 32)
    82  					So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
    83  					So(pv.Type().String(), ShouldEqual, "PTInt")
    84  				})
    85  				Convey("bool (true)", func() {
    86  					pv := MkProperty(mybool(true))
    87  					So(pv.Value(), ShouldBeTrue)
    88  					So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
    89  					So(pv.Type().String(), ShouldEqual, "PTBool")
    90  				})
    91  				Convey("string", func() {
    92  					pv := MkProperty(mystring("sup"))
    93  					So(pv.Value(), ShouldEqual, "sup")
    94  					So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
    95  					So(pv.Type().String(), ShouldEqual, "PTString")
    96  				})
    97  				Convey("blobstore.Key is distinquished", func() {
    98  					pv := MkProperty(blobstore.Key("sup"))
    99  					So(pv.Value(), ShouldEqual, blobstore.Key("sup"))
   100  					So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
   101  					So(pv.Type().String(), ShouldEqual, "PTBlobKey")
   102  				})
   103  				Convey("datastore Key is distinguished", func() {
   104  					k := MkKeyContext("appid", "ns").MakeKey("kind", "1")
   105  					pv := MkProperty(k)
   106  					So(pv.Value(), ShouldHaveSameTypeAs, k)
   107  					So(pv.Value().(*Key).Equal(k), ShouldBeTrue)
   108  					So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
   109  					So(pv.Type().String(), ShouldEqual, "PTKey")
   110  
   111  					pv = MkProperty((*Key)(nil))
   112  					So(pv.Value(), ShouldBeNil)
   113  					So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
   114  					So(pv.Type().String(), ShouldEqual, "PTNull")
   115  				})
   116  				Convey("float", func() {
   117  					pv := Property{}
   118  					So(pv.SetValue(myfloat(19.7), ShouldIndex), ShouldBeNil)
   119  					So(pv.Value(), ShouldHaveSameTypeAs, float64(19.7))
   120  					So(pv.Value(), ShouldEqual, float32(19.7))
   121  					So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
   122  					So(pv.Type().String(), ShouldEqual, "PTFloat")
   123  				})
   124  				Convey("property map", func() {
   125  					pv := Property{}
   126  					So(pv.SetValue(PropertyMap{
   127  						"key": Property{
   128  							indexSetting: true,
   129  							propType:     PTString,
   130  							value:        stringByteSequence("val"),
   131  						},
   132  						"nested": Property{
   133  							indexSetting: false,
   134  							propType:     PTPropertyMap,
   135  							value: PropertyMap{
   136  								"key": Property{
   137  									indexSetting: false,
   138  									propType:     PTInt,
   139  									value:        1,
   140  								},
   141  							},
   142  						},
   143  					}, NoIndex), ShouldBeNil)
   144  					So(pv.Value(), ShouldResemble, PropertyMap{
   145  						"key": Property{
   146  							indexSetting: true,
   147  							propType:     PTString,
   148  							value:        stringByteSequence("val"),
   149  						},
   150  						"nested": Property{
   151  							indexSetting: false,
   152  							propType:     PTPropertyMap,
   153  							value: PropertyMap{
   154  								"key": Property{
   155  									indexSetting: false,
   156  									propType:     PTInt,
   157  									value:        1,
   158  								},
   159  							},
   160  						},
   161  					})
   162  					So(pv.IndexSetting(), ShouldEqual, NoIndex)
   163  					So(pv.Type().String(), ShouldEqual, "PTPropertyMap")
   164  				})
   165  			})
   166  			Convey("bad type", func() {
   167  				pv := Property{}
   168  				err := pv.SetValue(complex(100, 29), ShouldIndex)
   169  				So(err.Error(), ShouldContainSubstring, "has bad type complex")
   170  				So(pv.Value(), ShouldBeNil)
   171  				So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
   172  				So(pv.Type().String(), ShouldEqual, "PTNull")
   173  			})
   174  			Convey("invalid GeoPoint", func() {
   175  				pv := Property{}
   176  				err := pv.SetValue(GeoPoint{-1000, 0}, ShouldIndex)
   177  				So(err.Error(), ShouldContainSubstring, "invalid GeoPoint value")
   178  				So(pv.Value(), ShouldBeNil)
   179  				So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
   180  				So(pv.Type().String(), ShouldEqual, "PTNull")
   181  			})
   182  			Convey("invalid time", func() {
   183  				pv := Property{}
   184  				loc, err := time.LoadLocation("America/Los_Angeles")
   185  				So(err, ShouldBeNil)
   186  				t := time.Date(1970, 1, 1, 0, 0, 0, 0, loc)
   187  
   188  				err = pv.SetValue(t, ShouldIndex)
   189  				So(err.Error(), ShouldContainSubstring, "time value has wrong Location")
   190  
   191  				err = pv.SetValue(time.Unix(math.MaxInt64, 0).UTC(), ShouldIndex)
   192  				So(err.Error(), ShouldContainSubstring, "time value out of range")
   193  				So(pv.Value(), ShouldBeNil)
   194  				So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
   195  				So(pv.Type().String(), ShouldEqual, "PTNull")
   196  			})
   197  			Convey("time gets rounded", func() {
   198  				pv := Property{}
   199  				now := time.Now().In(time.UTC)
   200  				now = now.Round(time.Microsecond).Add(time.Nanosecond * 313)
   201  				So(pv.SetValue(now, ShouldIndex), ShouldBeNil)
   202  				So(pv.Value(), ShouldHappenBefore, now)
   203  				So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
   204  				So(pv.Type().String(), ShouldEqual, "PTTime")
   205  			})
   206  			Convey("zero time", func() {
   207  				now := time.Time{}
   208  				So(now.IsZero(), ShouldBeTrue)
   209  
   210  				pv := Property{}
   211  				So(pv.SetValue(now, ShouldIndex), ShouldBeNil)
   212  				So(pv.Value(), ShouldResemble, now)
   213  				v, err := pv.Project(PTInt)
   214  				So(err, ShouldBeNil)
   215  				So(v, ShouldEqual, 0)
   216  
   217  				So(pv.SetValue(0, ShouldIndex), ShouldBeNil)
   218  				So(pv.Value(), ShouldEqual, 0)
   219  				v, err = pv.Project(PTTime)
   220  				So(err, ShouldBeNil)
   221  				So(v.(time.Time).IsZero(), ShouldBeTrue)
   222  			})
   223  			Convey("[]byte allows IndexSetting", func() {
   224  				pv := Property{}
   225  				So(pv.SetValue([]byte("hello"), ShouldIndex), ShouldBeNil)
   226  				So(pv.Value(), ShouldResemble, []byte("hello"))
   227  				So(pv.IndexSetting(), ShouldEqual, ShouldIndex)
   228  				So(pv.Type().String(), ShouldEqual, "PTBytes")
   229  			})
   230  		})
   231  
   232  		Convey("Comparison", func() {
   233  			Convey(`A []byte property should equal a string property with the same value.`, func() {
   234  				a := MkProperty([]byte("ohaithere"))
   235  				b := MkProperty("ohaithere")
   236  				So(a.Equal(&b), ShouldBeTrue)
   237  			})
   238  		})
   239  	})
   240  }
   241  
   242  func TestDSPropertyMapImpl(t *testing.T) {
   243  	t.Parallel()
   244  
   245  	Convey("Test PropertyMap", t, func() {
   246  		Convey("load/save err conditions", func() {
   247  			Convey("empty", func() {
   248  				pm := PropertyMap{}
   249  				err := pm.Load(PropertyMap{"hello": Property{}})
   250  				So(err, ShouldBeNil)
   251  				So(pm, ShouldResemble, PropertyMap{"hello": Property{}})
   252  
   253  				npm, _ := pm.Save(false)
   254  				So(npm, ShouldResemble, pm)
   255  			})
   256  			Convey("meta", func() {
   257  				Convey("working", func() {
   258  					pm := PropertyMap{"": MkProperty("trap!")}
   259  					_, ok := pm.GetMeta("foo")
   260  					So(ok, ShouldBeFalse)
   261  
   262  					So(pm.SetMeta("foo", 100), ShouldBeTrue)
   263  
   264  					v, ok := pm.GetMeta("foo")
   265  					So(ok, ShouldBeTrue)
   266  					So(v, ShouldEqual, 100)
   267  
   268  					So(GetMetaDefault(pm, "foo", 100), ShouldEqual, 100)
   269  
   270  					So(GetMetaDefault(pm, "bar", 100), ShouldEqual, 100)
   271  
   272  					npm, err := pm.Save(false)
   273  					So(err, ShouldBeNil)
   274  					So(len(npm), ShouldEqual, 0)
   275  				})
   276  
   277  				Convey("too many values picks the first one", func() {
   278  					pm := PropertyMap{
   279  						"$thing": PropertySlice{MkProperty(100), MkProperty(200)},
   280  					}
   281  					v, ok := pm.GetMeta("thing")
   282  					So(ok, ShouldBeTrue)
   283  					So(v, ShouldEqual, 100)
   284  				})
   285  
   286  				Convey("errors", func() {
   287  
   288  					Convey("weird value", func() {
   289  						pm := PropertyMap{}
   290  						So(pm.SetMeta("sup", complex(100, 20)), ShouldBeFalse)
   291  					})
   292  				})
   293  			})
   294  		})
   295  		Convey("disable indexing on entire map", func() {
   296  			pm := PropertyMap{
   297  				"single": MkProperty("foo"),
   298  				"slice":  PropertySlice{MkProperty(100), MkProperty(200)},
   299  			}
   300  			pm.TurnOffIdx()
   301  			So(pm["single"].Slice()[0].indexSetting, ShouldEqual, NoIndex)
   302  			So(pm["slice"].Slice()[0].indexSetting, ShouldEqual, NoIndex)
   303  			So(pm["slice"].Slice()[1].indexSetting, ShouldEqual, NoIndex)
   304  		})
   305  	})
   306  }
   307  
   308  func TestByteSequences(t *testing.T) {
   309  	t.Parallel()
   310  
   311  	conversions := []struct {
   312  		desc string
   313  		conv func(v string) (byteSequence, any)
   314  	}{
   315  		{"string", func(v string) (byteSequence, any) { return stringByteSequence(v), v }},
   316  		{"[]byte", func(v string) (byteSequence, any) { return bytesByteSequence(v), []byte(v) }},
   317  	}
   318  
   319  	testCases := map[string][]struct {
   320  		assertion func(any, ...any) string
   321  		cmpS      string
   322  	}{
   323  		"": {
   324  			{ShouldEqual, ""},
   325  			{ShouldBeLessThan, "foo"},
   326  		},
   327  		"bar": {
   328  			{ShouldEqual, "bar"},
   329  			{ShouldBeGreaterThan, "ba"},
   330  		},
   331  		"ba": {
   332  			{ShouldBeLessThan, "bar"},
   333  			{ShouldBeLessThan, "z"},
   334  		},
   335  		"foo": {
   336  			{ShouldBeGreaterThan, ""},
   337  		},
   338  		"bz": {
   339  			{ShouldBeGreaterThan, "bar"},
   340  		},
   341  		"qux": {
   342  			{ShouldBeGreaterThan, "bar"},
   343  		},
   344  	}
   345  
   346  	keys := make([]string, 0, len(testCases))
   347  	for k := range testCases {
   348  		keys = append(keys, k)
   349  	}
   350  	sort.Strings(keys)
   351  
   352  	Convey(`When testing byte sequences`, t, func() {
   353  		for _, s := range keys {
   354  			for _, c := range conversions {
   355  				Convey(fmt.Sprintf(`A %s sequence with test data %q`, c.desc, s), func() {
   356  					bs, effectiveValue := c.conv(s)
   357  
   358  					Convey(`Basic stuff works.`, func() {
   359  						So(bs.len(), ShouldEqual, len(s))
   360  						for i, c := range s {
   361  							So(bs.get(i), ShouldEqual, c)
   362  						}
   363  						So(bs.value(), ShouldResemble, effectiveValue)
   364  						So(bs.string(), ShouldEqual, s)
   365  						So(bs.bytes(), ShouldResemble, []byte(s))
   366  					})
   367  
   368  					// Test comparison with other byteSequence types.
   369  					for _, tc := range testCases[s] {
   370  						for _, c := range conversions {
   371  							Convey(fmt.Sprintf(`Compares properly with %s %q`, c.desc, tc.cmpS), func() {
   372  								cmpBS, _ := c.conv(tc.cmpS)
   373  								So(cmpByteSequence(bs, cmpBS), tc.assertion, 0)
   374  							})
   375  						}
   376  					}
   377  				})
   378  			}
   379  		}
   380  	})
   381  }