| Index: service/datastore/datastore_test.go
 | 
| diff --git a/service/datastore/datastore_test.go b/service/datastore/datastore_test.go
 | 
| index 3dcf68f4759f78a4a9d7b1ed6b55d3bc46a63e94..c9bba1dc86a47d5aa4b008265e8ccc0de8c25508 100644
 | 
| --- a/service/datastore/datastore_test.go
 | 
| +++ b/service/datastore/datastore_test.go
 | 
| @@ -7,7 +7,12 @@
 | 
|  package datastore
 | 
|  
 | 
|  import (
 | 
| +	"bytes"
 | 
|  	"fmt"
 | 
| +	"io/ioutil"
 | 
| +	"os"
 | 
| +	"path/filepath"
 | 
| +	"runtime"
 | 
|  	"testing"
 | 
|  
 | 
|  	"github.com/luci/gae/service/info"
 | 
| @@ -1291,3 +1296,183 @@ func TestSchemaChange(t *testing.T) {
 | 
|  		})
 | 
|  	})
 | 
|  }
 | 
| +
 | 
| +func TestParseIndexYAML(t *testing.T) {
 | 
| +	t.Parallel()
 | 
| +
 | 
| +	Convey("parses properly formatted YAML", t, func() {
 | 
| +		yaml := `
 | 
| +indexes:
 | 
| +
 | 
| +- kind: Cat
 | 
| +  ancestor: no
 | 
| +  properties:
 | 
| +  - name: name
 | 
| +  - name: age
 | 
| +    direction: desc
 | 
| +
 | 
| +- kind: Cat
 | 
| +  properties:
 | 
| +  - name: name
 | 
| +    direction: asc
 | 
| +  - name: whiskers
 | 
| +    direction: desc
 | 
| +
 | 
| +- kind: Store
 | 
| +  ancestor: yes
 | 
| +  properties:
 | 
| +  - name: business
 | 
| +    direction: asc
 | 
| +  - name: owner
 | 
| +    direction: asc
 | 
| +`
 | 
| +		ids, err := ParseIndexYAML(bytes.NewBuffer([]byte(yaml)))
 | 
| +		So(err, ShouldBeNil)
 | 
| +
 | 
| +		expected := []*IndexDefinition{
 | 
| +			{
 | 
| +				Kind:     "Cat",
 | 
| +				Ancestor: false,
 | 
| +				SortBy: []IndexColumn{
 | 
| +					{
 | 
| +						Property:   "name",
 | 
| +						Descending: false,
 | 
| +					},
 | 
| +					{
 | 
| +						Property:   "age",
 | 
| +						Descending: true,
 | 
| +					},
 | 
| +				},
 | 
| +			},
 | 
| +			{
 | 
| +				Kind:     "Cat",
 | 
| +				Ancestor: false,
 | 
| +				SortBy: []IndexColumn{
 | 
| +					{
 | 
| +						Property:   "name",
 | 
| +						Descending: false,
 | 
| +					},
 | 
| +					{
 | 
| +						Property:   "whiskers",
 | 
| +						Descending: true,
 | 
| +					},
 | 
| +				},
 | 
| +			},
 | 
| +			{
 | 
| +				Kind:     "Store",
 | 
| +				Ancestor: true,
 | 
| +				SortBy: []IndexColumn{
 | 
| +					{
 | 
| +						Property:   "business",
 | 
| +						Descending: false,
 | 
| +					},
 | 
| +					{
 | 
| +						Property:   "owner",
 | 
| +						Descending: false,
 | 
| +					},
 | 
| +				},
 | 
| +			},
 | 
| +		}
 | 
| +		So(ids, ShouldResembleV, expected)
 | 
| +	})
 | 
| +
 | 
| +	Convey("returns non-nil error for incorrectly formatted YAML", t, func() {
 | 
| +
 | 
| +		Convey("missing top level `indexes` key", func() {
 | 
| +			yaml := `
 | 
| +- kind: Cat
 | 
| +  properties:
 | 
| +  - name: name
 | 
| +  - name: age
 | 
| +    direction: desc
 | 
| +`
 | 
| +			_, err := ParseIndexYAML(bytes.NewBuffer([]byte(yaml)))
 | 
| +			So(err, ShouldNotBeNil)
 | 
| +		})
 | 
| +
 | 
| +		Convey("missing `name` key in property", func() {
 | 
| +			yaml := `
 | 
| +indexes:
 | 
| +
 | 
| +- kind: Cat
 | 
| +  ancestor: no
 | 
| +  properties:
 | 
| +  - name: name
 | 
| +  - direction: desc
 | 
| +`
 | 
| +			_, err := ParseIndexYAML(bytes.NewBuffer([]byte(yaml)))
 | 
| +			So(err, ShouldNotBeNil)
 | 
| +		})
 | 
| +	})
 | 
| +}
 | 
| +
 | 
| +func TestFindAndParseIndexYAML(t *testing.T) {
 | 
| +	t.Parallel()
 | 
| +
 | 
| +	Convey("returns parsed index definitions for existing index YAML files", t, func() {
 | 
| +		// YAML content to write temporarily to disk
 | 
| +		yaml1 := `
 | 
| +indexes:
 | 
| +
 | 
| +- kind: Same Level
 | 
| +  properties:
 | 
| +  - name: name
 | 
| +  - name: age
 | 
| +    direction: desc
 | 
| +`
 | 
| +		yaml2 := `
 | 
| +indexes:
 | 
| +
 | 
| +- kind: Higher Level
 | 
| +  properties:
 | 
| +  - name: name
 | 
| +  - name: age
 | 
| +    direction: desc
 | 
| +`
 | 
| +		// determine the directory of this test file
 | 
| +		_, path, _, ok := runtime.Caller(0)
 | 
| +		if !ok {
 | 
| +			panic(fmt.Errorf("failed to determine test file path"))
 | 
| +		}
 | 
| +		sameLevelDir := filepath.Dir(path)
 | 
| +
 | 
| +		Convey("picks YAML file at same level as test file", func() {
 | 
| +			writePath1 := filepath.Join(sameLevelDir, "index.yml")
 | 
| +			writePath2 := filepath.Join(filepath.Dir(sameLevelDir), "index.yaml")
 | 
| +
 | 
| +			setup := func() {
 | 
| +				ioutil.WriteFile(writePath1, []byte(yaml1), 0600)
 | 
| +				ioutil.WriteFile(writePath2, []byte(yaml2), 0600)
 | 
| +			}
 | 
| +
 | 
| +			cleanup := func() {
 | 
| +				os.Remove(writePath1)
 | 
| +				os.Remove(writePath2)
 | 
| +			}
 | 
| +
 | 
| +			setup()
 | 
| +			defer cleanup()
 | 
| +			ids, err := FindAndParseIndexYAML()
 | 
| +			So(err, ShouldBeNil)
 | 
| +			So(ids[0].Kind, ShouldEqual, "Same Level")
 | 
| +		})
 | 
| +
 | 
| +		Convey("picks YAML file two levels up", func() {
 | 
| +			writePath := filepath.Join(filepath.Dir(filepath.Dir(sameLevelDir)), "index.yaml")
 | 
| +
 | 
| +			setup := func() {
 | 
| +				ioutil.WriteFile(writePath, []byte(yaml2), 0600)
 | 
| +			}
 | 
| +
 | 
| +			cleanup := func() {
 | 
| +				os.Remove(writePath)
 | 
| +			}
 | 
| +
 | 
| +			setup()
 | 
| +			defer cleanup()
 | 
| +			ids, err := FindAndParseIndexYAML()
 | 
| +			So(err, ShouldBeNil)
 | 
| +			So(ids[0].Kind, ShouldEqual, "Higher Level")
 | 
| +		})
 | 
| +	})
 | 
| +}
 | 
| 
 |