Initial push of API v3 test utility.
diff --git a/utils/utils.go b/utils/utils.go
new file mode 100644
index 0000000..ea9f1fa
--- /dev/null
+++ b/utils/utils.go
@@ -0,0 +1,147 @@
+/*
+Copyright 2017 Google Inc.
+
+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
+
+https://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.
+*/
+
+// Package utils contains common BookingService based helper functions.
+package utils
+
+import (
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "log"
+ "math/rand"
+ "path"
+ "strings"
+
+ "github.com/golang/protobuf/jsonpb"
+ "github.com/golang/protobuf/proto"
+ "github.com/google/go-cmp/cmp"
+
+ fpb "github.com/maps-booking/feeds"
+ mpb "github.com/maps-booking/v3"
+)
+
+// SlotKey is a struct representing a unique service.
+type SlotKey struct {
+ MerchantID string
+ ServiceID string
+ StaffID string
+ RoomID string
+}
+
+// LogFlow is a convenience function for logging common flows..
+func LogFlow(f string, status string) {
+ log.Println(strings.Join([]string{"\n##########\n", status, f, "Flow", "\n##########"}, " "))
+}
+
+// AvailabilityFrom parses the file specified in availabilityFeed, returning a random permutation of availability data, maximum entries specified in testSlots
+func AvailabilityFrom(availabilityFeed string, testSlots int) ([]*fpb.Availability, error) {
+ LogFlow("Parse Input Feed", "Start")
+ defer LogFlow("Parse Input Feed", "End")
+
+ var feed fpb.AvailabilityFeed
+ content, err := ioutil.ReadFile(availabilityFeed)
+ if err != nil {
+ return nil, fmt.Errorf("unable to read input file: %v", err)
+ }
+ if path.Ext(availabilityFeed) == ".json" {
+ if err := jsonpb.UnmarshalString(string(content), &feed); err != nil {
+ return nil, fmt.Errorf("unable to parse feed as json: %v", err)
+ }
+ }
+ if path.Ext(availabilityFeed) == ".pb3" {
+ if err := proto.Unmarshal(content, &feed); err != nil {
+ return nil, fmt.Errorf("unable to parse feed as pb3: %v", err)
+ }
+ }
+
+ var finalAvailability []*fpb.Availability
+ var rawAvailability []*fpb.Availability
+ for _, sa := range feed.GetServiceAvailability() {
+ rawAvailability = append(rawAvailability, sa.GetAvailability()...)
+ }
+ if len(rawAvailability) == 0 || testSlots == 0 {
+ return finalAvailability, errors.New("no valid availability in feed, exiting workflows")
+ }
+ if len(rawAvailability) <= testSlots {
+ finalAvailability = rawAvailability
+ } else {
+ nums := rand.Perm(len(rawAvailability))[0:testSlots]
+ for _, n := range nums {
+ finalAvailability = append(finalAvailability, rawAvailability[n])
+ }
+ }
+ log.Printf("Selected %d slots out of a possible %d", len(finalAvailability), len(rawAvailability))
+ return finalAvailability, nil
+}
+
+// ValidateBooking performs granular comparisons between all got and want Bookings.
+func ValidateBooking(got *mpb.Booking, want *mpb.Booking) error {
+ if got.GetBookingId() == "" {
+ return errors.New("booking_id is empty")
+ }
+ if diff := cmp.Diff(got.GetSlot(), want.GetSlot(), cmp.Comparer(proto.Equal)); diff != "" {
+ return fmt.Errorf("slots differ (-got +want)\n%s", diff)
+ }
+ // UserId is the only required field for the partner to return.
+ if diff := cmp.Diff(got.GetUserInformation().GetUserId(), want.GetUserInformation().GetUserId()); diff != "" {
+ return fmt.Errorf("users differ (-got +want)\n%s", diff)
+ }
+ if diff := cmp.Diff(got.GetPaymentInformation(), want.GetPaymentInformation(), cmp.Comparer(proto.Equal)); diff != "" {
+ return fmt.Errorf("payment information differs (-got +want)\n%s", diff)
+ }
+ // BookingStatus_CONFIRMED is the default case unless want overrides it.
+ wantStatus := mpb.BookingStatus_CONFIRMED
+ if want.GetStatus() != mpb.BookingStatus_BOOKING_STATUS_UNSPECIFIED {
+ wantStatus = want.GetStatus()
+ }
+ if diff := cmp.Diff(got.GetStatus(), wantStatus); diff != "" {
+ return fmt.Errorf("status differs (-got +want)\n%s", diff)
+ }
+ return nil
+}
+
+// BuildSlotFrom creates a bookingservice slot from an feed availability record.
+func BuildSlotFrom(a *fpb.Availability) (*mpb.Slot, error) {
+ r := a.GetResources()
+ return &mpb.Slot{
+ MerchantId: a.GetMerchantId(),
+ ServiceId: a.GetServiceId(),
+ StartSec: a.GetStartSec(),
+ DurationSec: a.GetDurationSec(),
+ AvailabilityTag: a.GetAvailabilityTag(),
+ Resources: &mpb.ResourceIds{
+ StaffId: r.GetStaffId(),
+ RoomId: r.GetRoomId(),
+ PartySize: r.GetPartySize(),
+ },
+ }, nil
+}
+
+// BuildMerchantServiceMap creates a key value pair of unique services to all of their availability slots.
+func BuildMerchantServiceMap(av []*fpb.Availability) map[SlotKey][]*fpb.Availability {
+ m := make(map[SlotKey][]*fpb.Availability)
+ for _, a := range av {
+ key := SlotKey{
+ MerchantID: a.GetMerchantId(),
+ ServiceID: a.GetServiceId(),
+ StaffID: a.GetResources().GetStaffId(),
+ RoomID: a.GetResources().GetRoomId(),
+ }
+ m[key] = append(m[key], a)
+ }
+ return m
+}