| // Copyright 2017 Google Inc. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| // A runner for the cross-language tests. |
| |
| package firestore |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "io/ioutil" |
| "path" |
| "path/filepath" |
| "strings" |
| "testing" |
| |
| pb "cloud.google.com/go/firestore/genproto" |
| "github.com/golang/protobuf/proto" |
| "github.com/golang/protobuf/ptypes" |
| ts "github.com/golang/protobuf/ptypes/timestamp" |
| "golang.org/x/net/context" |
| fspb "google.golang.org/genproto/googleapis/firestore/v1beta1" |
| ) |
| |
| func TestCrossLanguageTests(t *testing.T) { |
| const dir = "testdata" |
| fis, err := ioutil.ReadDir(dir) |
| if err != nil { |
| t.Fatal(err) |
| } |
| n := 0 |
| for _, fi := range fis { |
| if strings.HasSuffix(fi.Name(), ".textproto") { |
| runTestFromFile(t, filepath.Join(dir, fi.Name())) |
| n++ |
| } |
| } |
| t.Logf("ran %d cross-language tests", n) |
| } |
| |
| func runTestFromFile(t *testing.T, filename string) { |
| bytes, err := ioutil.ReadFile(filename) |
| if err != nil { |
| t.Fatalf("%s: %v", filename, err) |
| } |
| var test pb.Test |
| if err := proto.UnmarshalText(string(bytes), &test); err != nil { |
| t.Fatalf("unmarshalling %s: %v", filename, err) |
| } |
| msg := fmt.Sprintf("%s (file %s)", test.Description, filepath.Base(filename)) |
| runTest(t, msg, &test) |
| } |
| |
| func runTest(t *testing.T, msg string, test *pb.Test) { |
| check := func(gotErr error, wantErr bool) { |
| if wantErr && gotErr == nil { |
| t.Errorf("%s: got nil, want error", msg) |
| } else if !wantErr && gotErr != nil { |
| t.Errorf("%s: %v", msg, gotErr) |
| } |
| } |
| |
| ctx := context.Background() |
| c, srv := newMock(t) |
| |
| switch tt := test.Test.(type) { |
| case *pb.Test_Get: |
| srv.addRPC(tt.Get.Request, &fspb.Document{ |
| CreateTime: &ts.Timestamp{}, |
| UpdateTime: &ts.Timestamp{}, |
| }) |
| ref := docRefFromPath(tt.Get.DocRefPath, c) |
| _, err := ref.Get(ctx) |
| if err != nil { |
| t.Errorf("%s: %v", msg, err) |
| return |
| } |
| // Checking response would just be testing the function converting a Document |
| // proto to a DocumentSnapshot, hence uninteresting. |
| |
| case *pb.Test_Create: |
| srv.addRPC(tt.Create.Request, commitResponseForSet) |
| ref := docRefFromPath(tt.Create.DocRefPath, c) |
| data, err := convertData(tt.Create.JsonData) |
| if err != nil { |
| t.Errorf("%s: %v", msg, err) |
| return |
| } |
| _, err = ref.Create(ctx, data) |
| check(err, tt.Create.IsError) |
| |
| case *pb.Test_Set: |
| srv.addRPC(tt.Set.Request, commitResponseForSet) |
| ref := docRefFromPath(tt.Set.DocRefPath, c) |
| data, err := convertData(tt.Set.JsonData) |
| if err != nil { |
| t.Errorf("%s: %v", msg, err) |
| return |
| } |
| var opts []SetOption |
| if tt.Set.Option != nil { |
| opts = []SetOption{convertSetOption(tt.Set.Option)} |
| } |
| _, err = ref.Set(ctx, data, opts...) |
| check(err, tt.Set.IsError) |
| |
| case *pb.Test_Update: |
| // Ignore Update test because we only support UpdatePaths. |
| // Not to worry, every Update test has a corresponding UpdatePaths test. |
| |
| case *pb.Test_UpdatePaths: |
| srv.addRPC(tt.UpdatePaths.Request, commitResponseForSet) |
| ref := docRefFromPath(tt.UpdatePaths.DocRefPath, c) |
| preconds := convertPrecondition(t, tt.UpdatePaths.Precondition) |
| paths := convertFieldPaths(tt.UpdatePaths.FieldPaths) |
| var ups []Update |
| for i, path := range paths { |
| jsonValue := tt.UpdatePaths.JsonValues[i] |
| var val interface{} |
| if err := json.Unmarshal([]byte(jsonValue), &val); err != nil { |
| t.Fatalf("%s: %q: %v", msg, jsonValue, err) |
| } |
| ups = append(ups, Update{ |
| FieldPath: path, |
| Value: convertTestValue(val), |
| }) |
| } |
| _, err := ref.Update(ctx, ups, preconds...) |
| check(err, tt.UpdatePaths.IsError) |
| |
| case *pb.Test_Delete: |
| srv.addRPC(tt.Delete.Request, commitResponseForSet) |
| ref := docRefFromPath(tt.Delete.DocRefPath, c) |
| preconds := convertPrecondition(t, tt.Delete.Precondition) |
| _, err := ref.Delete(ctx, preconds...) |
| check(err, tt.Delete.IsError) |
| |
| default: |
| t.Fatalf("unknown test type %T", tt) |
| } |
| } |
| |
| func docRefFromPath(p string, c *Client) *DocumentRef { |
| return &DocumentRef{ |
| Path: p, |
| ID: path.Base(p), |
| Parent: &CollectionRef{c: c}, |
| } |
| } |
| |
| func convertData(jsonData string) (map[string]interface{}, error) { |
| var m map[string]interface{} |
| if err := json.Unmarshal([]byte(jsonData), &m); err != nil { |
| return nil, err |
| } |
| return convertTestMap(m), nil |
| } |
| |
| func convertTestMap(m map[string]interface{}) map[string]interface{} { |
| for k, v := range m { |
| m[k] = convertTestValue(v) |
| } |
| return m |
| } |
| |
| func convertTestValue(v interface{}) interface{} { |
| switch v := v.(type) { |
| case string: |
| switch v { |
| case "ServerTimestamp": |
| return ServerTimestamp |
| case "Delete": |
| return Delete |
| default: |
| return v |
| } |
| case float64: |
| if v == float64(int(v)) { |
| return int(v) |
| } |
| return v |
| case []interface{}: |
| for i, e := range v { |
| v[i] = convertTestValue(e) |
| } |
| return v |
| case map[string]interface{}: |
| return convertTestMap(v) |
| default: |
| return v |
| } |
| } |
| |
| func convertSetOption(opt *pb.SetOption) SetOption { |
| if opt.All { |
| return MergeAll |
| } |
| return Merge(convertFieldPaths(opt.Fields)...) |
| } |
| |
| func convertFieldPaths(fps []*pb.FieldPath) []FieldPath { |
| var res []FieldPath |
| for _, fp := range fps { |
| res = append(res, fp.Field) |
| } |
| return res |
| } |
| |
| func convertPrecondition(t *testing.T, fp *fspb.Precondition) []Precondition { |
| if fp == nil { |
| return nil |
| } |
| var pc Precondition |
| switch fp := fp.ConditionType.(type) { |
| case *fspb.Precondition_Exists: |
| pc = exists(fp.Exists) |
| case *fspb.Precondition_UpdateTime: |
| tm, err := ptypes.Timestamp(fp.UpdateTime) |
| if err != nil { |
| t.Fatal(err) |
| } |
| pc = LastUpdateTime(tm) |
| default: |
| t.Fatalf("unknown precondition type %T", fp) |
| } |
| return []Precondition{pc} |
| } |