| /* | |
| 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 | |
| } |