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 +}