Syncing between source head and git
diff --git a/api/api.go b/api/api.go
index d419c40..61e0129 100644
--- a/api/api.go
+++ b/api/api.go
@@ -1,5 +1,5 @@
/*
-Copyright 2017 Google Inc.
+Copyright 2019 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
import (
"bytes"
+ "context"
"crypto/tls"
"crypto/x509"
"encoding/base64"
@@ -33,33 +34,27 @@
"strings"
"time"
+ epb "github.com/golang/protobuf/ptypes/empty"
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
- "github.com/golang/protobuf/ptypes/empty"
"github.com/google/go-cmp/cmp"
+
"github.com/maps-booking-v3/utils"
fpb "github.com/maps-booking-v3/feeds"
mpb "github.com/maps-booking-v3/v3"
- wpb "github.com/maps-booking-v3/waitlist"
+ wpb "github.com/maps-booking-v3/v3waitlist"
)
const (
- userID = "0"
- firstName = "Jane"
- lastName = "Doe"
- telephone = "+1 800-789-7890"
- email = "test@example.com"
+ userID = "0"
+ firstName = "Jane"
+ lastName = "Doe"
+ telephone = "+1 800-789-7890"
+ email = "test@example.com"
+ reqTimeout = 10 * time.Second
)
-// HTTPConnection is a convenience struct for holding connection-related objects.
-type HTTPConnection struct {
- client *http.Client
- credentials string
- marshaler *jsonpb.Marshaler
- baseURL string
-}
-
func setupCertConfig(caFile string, fullServerName string) (*tls.Config, error) {
if caFile == "" {
return nil, nil
@@ -78,9 +73,9 @@
}, nil
}
-// InitHTTPConnection creates and returns a new HTTPConnection object
+// InitHTTPConnection creates and returns a new utils.HTTPConnection object
// with a given server address and username/password.
-func InitHTTPConnection(serverAddr string, credentialsFile string, caFile string, fullServerName string) (*HTTPConnection, error) {
+func InitHTTPConnection(serverAddr string, credentialsFile string, caFile string, fullServerName string) (*utils.HTTPConnection, error) {
// Set up username/password.
var credentials string
if credentialsFile != "" {
@@ -98,24 +93,16 @@
if config != nil {
protocol = "https"
}
- return &HTTPConnection{
- client: &http.Client{
- Timeout: 10 * time.Second,
+ return &utils.HTTPConnection{
+ Client: &http.Client{
Transport: &http.Transport{TLSClientConfig: config},
},
- credentials: credentials,
- marshaler: &jsonpb.Marshaler{OrigName: true},
- baseURL: protocol + "://" + serverAddr,
+ Credentials: credentials,
+ Marshaler: &jsonpb.Marshaler{OrigName: true},
+ BaseURL: protocol + "://" + serverAddr,
}, nil
}
-func (h HTTPConnection) getURL(rpcName string) string {
- if rpcName != "" {
- return h.baseURL + "/v3/" + rpcName
- }
- return h.baseURL
-}
-
// Bookings is a convenience type for a booking array.
type Bookings []*mpb.Booking
@@ -132,32 +119,58 @@
}
// HealthCheck performs a health check.
-func HealthCheck(conn *HTTPConnection) error {
- utils.LogFlow("Health Check", "Start")
- defer utils.LogFlow("Health Check", "End")
+func HealthCheck(ctx context.Context, logger *log.Logger, conn *utils.HTTPConnection) error {
+ utils.LogFlow(logger, "Health Check", "Start")
+ defer utils.LogFlow(logger, "Health Check", "End")
- httpReq, err := http.NewRequest("GET", conn.getURL("HealthCheck"), nil)
- httpReq.Header.Set("Authorization", conn.credentials)
+ fmt.Println(conn.GetURL("HealthCheck"))
+ httpReq, err := http.NewRequest("GET", conn.GetURL("HealthCheck"), nil)
+ reqCtx, cancel := context.WithTimeout(ctx, reqTimeout)
+ defer cancel()
+ httpReq = httpReq.WithContext(reqCtx)
+ httpReq.Header.Set("Authorization", conn.Credentials)
// See if we get a response.
- resp, err := conn.client.Do(httpReq)
+ resp, err := conn.Client.Do(httpReq)
+ if err := ctx.Err(); err != nil {
+ panic(fmt.Sprintf("Encountered context error: %s", err.Error()))
+ }
if err != nil {
return fmt.Errorf("Health check failed to connect to server: %v", err)
} else if resp.StatusCode != 200 {
return fmt.Errorf("Health check returned unhealthy status: %s", resp.Status)
}
- log.Printf("health check success! Got status: %s", resp.Status)
+ logger.Printf("health check success! Got status: %s", resp.Status)
return nil
}
-// sendRequest sets up and sends the relevant HTTP request to the server and returns the HTTP response.
-func sendRequest(rpcName string, req string, conn *HTTPConnection) (string, error) {
- httpReq, err := http.NewRequest("POST", conn.getURL(rpcName), bytes.NewBuffer([]byte(req)))
- httpReq.Header.Set("Content-Type", "application/json")
- httpReq.Header.Set("Authorization", conn.credentials)
- log.Printf("%v Request. Sent(unix): %s, Url: %v, Method: %v, Header: %v, Body: %v\n", rpcName, time.Now().UTC().Format(time.RFC850), httpReq.URL, httpReq.Method, httpReq.Header, httpReq.Body)
+func getLogSafeHeader(header http.Header) http.Header {
+ logHeader := make(http.Header)
+ for k, v := range header {
+ if k == "Authorization" {
+ logHeader.Set("Authorization", "VALUE REDACTED.")
+ continue
+ }
+ logHeader[k] = v
+ }
+ return logHeader
+}
- httpResp, err := conn.client.Do(httpReq)
+// sendRequest sets up and sends the relevant HTTP request to the server and returns the HTTP response.
+func sendRequest(ctx context.Context, logger *log.Logger, rpcName string, req string, conn *utils.HTTPConnection) (string, error) {
+ httpReq, err := http.NewRequest("POST", conn.GetURL(rpcName), bytes.NewBuffer([]byte(req)))
+ reqCtx, cancel := context.WithTimeout(ctx, reqTimeout)
+ defer cancel()
+ httpReq = httpReq.WithContext(reqCtx)
+ httpReq.Header.Set("Content-Type", "application/json")
+ httpReq.Header.Set("Authorization", conn.Credentials)
+ logHeader := getLogSafeHeader(httpReq.Header)
+ logger.Printf("%v Request. Sent(unix): %s, Url: %v, Method: %v, Header: %v, Body: %v\n", rpcName, time.Now().UTC().Format(time.RFC850), httpReq.URL, httpReq.Method, logHeader, httpReq.Body)
+
+ httpResp, err := conn.Client.Do(httpReq)
+ if err := ctx.Err(); err != nil {
+ panic(fmt.Sprintf("Encountered context error: %s", err.Error()))
+ }
if err != nil {
return "", fmt.Errorf("invalid response. %s yielded error: %v", rpcName, err)
}
@@ -167,13 +180,13 @@
return "", fmt.Errorf("Could not read http response body")
}
bodyString := string(bodyBytes)
- log.Printf("%v Response. Received(unix): %s, Response %v\n", rpcName, time.Now().UTC().Format(time.RFC850), bodyString)
+ logger.Printf("%v Response. Received(unix): %s, Response %v\n", rpcName, time.Now().UTC().Format(time.RFC850), bodyString)
return bodyString, nil
}
// CheckAvailability performs a maps booking availability check on all supplied availability slots. This function
// returns any errors when trying to call the CheckAvailability RPC.
-func CheckAvailability(a *fpb.Availability, conn *HTTPConnection) error {
+func CheckAvailability(ctx context.Context, logger *log.Logger, a *fpb.Availability, conn *utils.HTTPConnection) error {
slot, err := utils.BuildSlotFrom(a)
if err != nil {
return fmt.Errorf("unable to build request for check availability flow. err: %v, availability record: %v", err, a.String())
@@ -181,11 +194,11 @@
reqPB := &mpb.CheckAvailabilityRequest{
Slot: slot,
}
- req, err := conn.marshaler.MarshalToString(reqPB)
+ req, err := conn.Marshaler.MarshalToString(reqPB)
if err != nil {
return fmt.Errorf("Could not convert pb3 to json: %v", reqPB)
}
- httpResp, err := sendRequest("CheckAvailability", req, conn)
+ httpResp, err := sendRequest(ctx, logger, "CheckAvailability", req, conn)
if err != nil {
return fmt.Errorf("invalid response. CheckAvailability yielded error: %v", err)
}
@@ -207,16 +220,16 @@
// BatchAvailabilityLookup performs a maps booking batch availability lookup on all supplied availability slots. This function
// returns any errors when trying to call the BatchAvailabilityLookup RPC.
-func BatchAvailabilityLookup(av []*fpb.Availability, conn *HTTPConnection) error {
+func BatchAvailabilityLookup(ctx context.Context, logger *log.Logger, av []*fpb.Availability, conn *utils.HTTPConnection) error {
reqPB, err := utils.BuildBatchAvailabilityLookupRequestFrom(av)
if err != nil {
return fmt.Errorf("unable to build request for batch availability lookup flow. err: %v, availability records: %v", err, av)
}
- req, err := conn.marshaler.MarshalToString(reqPB)
+ req, err := conn.Marshaler.MarshalToString(reqPB)
if err != nil {
return fmt.Errorf("Could not convert pb3 to json: %v", reqPB)
}
- httpResp, err := sendRequest("BatchAvailabilityLookup", req, conn)
+ httpResp, err := sendRequest(ctx, logger, "BatchAvailabilityLookup", req, conn)
if err != nil {
return fmt.Errorf("invalid response. BatchAvailabilityLookup yielded error: %v", err)
}
@@ -232,7 +245,7 @@
slotTimeReq := reqPB.GetSlotTime()[i]
slotTimeResp := resp.GetSlotTimeAvailability()[i].GetSlotTime()
if diff := cmp.Diff(slotTimeReq, slotTimeResp, cmp.Comparer(proto.Equal)); diff != "" {
- log.Printf("Slot %v differs: req=%v, resp=%v", i, slotTimeReq, slotTimeResp)
+ logger.Printf("Slot %v differs: req=%v, resp=%v", i, slotTimeReq, slotTimeResp)
diffCount++
}
}
@@ -243,7 +256,7 @@
}
// CreateBooking attempts to create bookings from availability slots.
-func CreateBooking(a *fpb.Availability, conn *HTTPConnection) (*mpb.Booking, error) {
+func CreateBooking(ctx context.Context, logger *log.Logger, a *fpb.Availability, conn *utils.HTTPConnection) (*mpb.Booking, error) {
slot, err := utils.BuildSlotFrom(a)
if err != nil {
return nil, fmt.Errorf("unable to build request for check availability flow. err: %v, availability record: %v", err, a.String())
@@ -265,12 +278,12 @@
},
IdempotencyToken: strconv.Itoa(gen.Intn(1000000)),
}
- req, err := conn.marshaler.MarshalToString(reqPB)
+ req, err := conn.Marshaler.MarshalToString(reqPB)
if err != nil {
return nil, fmt.Errorf("Could not convert pb3 to json: %v", reqPB)
}
- httpResp, err := sendRequest("CreateBooking", req, conn)
+ httpResp, err := sendRequest(ctx, logger, "CreateBooking", req, conn)
if err != nil {
return nil, fmt.Errorf("invalid response. CreateBooking yielded error: %v", err)
}
@@ -293,8 +306,8 @@
}
// Perform idempotency test.
- log.Printf("Idempotency check -- CreateBooking Request. Sent(unix): %s, Request %s", time.Now().UTC().Format(time.RFC850), reqPB.String())
- idemHTTPResp, err := sendRequest("CreateBooking", req, conn)
+ logger.Printf("Idempotency check -- CreateBooking Request. Sent(unix): %s, Request %s", time.Now().UTC().Format(time.RFC850), reqPB.String())
+ idemHTTPResp, err := sendRequest(ctx, logger, "CreateBooking", req, conn)
if err != nil {
return nil, fmt.Errorf("invalid response. Idempotency check yielded error: %v", err)
}
@@ -311,15 +324,15 @@
}
// ListBookings calls the maps booking ListBookings rpc and compares the return with all input bookings.
-func ListBookings(tB Bookings, conn *HTTPConnection) (Bookings, error) {
+func ListBookings(ctx context.Context, logger *log.Logger, tB Bookings, conn *utils.HTTPConnection) (Bookings, error) {
reqPB := &mpb.ListBookingsRequest{
UserId: userID,
}
- req, err := conn.marshaler.MarshalToString(reqPB)
+ req, err := conn.Marshaler.MarshalToString(reqPB)
if err != nil {
return nil, fmt.Errorf("Could not convert pb3 to json: %v", reqPB)
}
- httpResp, err := sendRequest("ListBookings", req, conn)
+ httpResp, err := sendRequest(ctx, logger, "ListBookings", req, conn)
if err != nil {
return nil, fmt.Errorf("invalid response. ListBookings yielded error: %v. Abandoning all booking from this flow", err)
}
@@ -330,37 +343,37 @@
gB := Bookings(resp.GetBookings())
if len(tB) == 0 {
- log.Printf("ListBookings returning %d found bookings", len(gB))
+ logger.Printf("ListBookings returning %d found bookings", len(gB))
return gB, nil
}
if len(gB) != len(tB) {
- log.Printf("ListBookings number of bookings differed unexpectedly. Got: %d, Want: %d.", len(gB), len(tB))
+ logger.Printf("ListBookings number of bookings differed unexpectedly. Got: %d, Want: %d.", len(gB), len(tB))
}
sort.Sort(gB)
sort.Sort(tB)
var out Bookings
for i := 0; i < len(tB); i++ {
if iE := utils.ValidateBooking(gB[i], tB[i]); iE != nil {
- log.Printf("ListBookings invalid, %s, abandoning slot %d/%d", iE.Error(), i, len(tB))
+ logger.Printf("ListBookings invalid, %s, abandoning slot %d/%d", iE.Error(), i, len(tB))
continue
}
out = append(out, tB[i])
}
- log.Printf("ListBookings returning %d bookings", len(out))
+ logger.Printf("ListBookings returning %d bookings", len(out))
return out, nil
}
// GetBookingStatus checks that all input bookings are in an acceptable state.
-func GetBookingStatus(b *mpb.Booking, conn *HTTPConnection) error {
+func GetBookingStatus(ctx context.Context, logger *log.Logger, b *mpb.Booking, conn *utils.HTTPConnection) error {
reqPB := &mpb.GetBookingStatusRequest{
BookingId: b.GetBookingId(),
}
- req, err := conn.marshaler.MarshalToString(reqPB)
+ req, err := conn.Marshaler.MarshalToString(reqPB)
if err != nil {
return fmt.Errorf("Could not convert pb3 to json: %v", reqPB)
}
- httpResp, err := sendRequest("GetBookingStatus", req, conn)
+ httpResp, err := sendRequest(ctx, logger, "GetBookingStatus", req, conn)
if err != nil {
return fmt.Errorf("invalid response. GetBookingStatus yielded error: %v", err)
}
@@ -377,18 +390,18 @@
}
// CancelBooking is a clean up method that cancels all supplied bookings.
-func CancelBooking(bookingID string, conn *HTTPConnection) error {
+func CancelBooking(ctx context.Context, logger *log.Logger, bookingID string, conn *utils.HTTPConnection) error {
reqPB := &mpb.UpdateBookingRequest{
Booking: &mpb.Booking{
BookingId: bookingID,
Status: mpb.BookingStatus_CANCELED,
},
}
- req, err := conn.marshaler.MarshalToString(reqPB)
+ req, err := conn.Marshaler.MarshalToString(reqPB)
if err != nil {
return fmt.Errorf("Could not convert pb3 to json: %v", reqPB)
}
- httpResp, err := sendRequest("UpdateBooking", req, conn)
+ httpResp, err := sendRequest(ctx, logger, "UpdateBooking", req, conn)
if err != nil {
return fmt.Errorf("invalid response. UpdateBooking yielded error: %v", err)
}
@@ -397,14 +410,20 @@
return fmt.Errorf("CancelBooking: Could not parse HTTP response to pb3: %v", err)
}
- if iE := utils.ValidateBooking(resp.GetBooking(), reqPB.GetBooking()); iE != nil {
+ // We only care that the correct booking was cancelled. The rest of the response content
+ // is not relevant.
+ compareBooking := &mpb.Booking{
+ BookingId: resp.GetBooking().GetBookingId(),
+ Status: resp.GetBooking().GetStatus(),
+ }
+ if iE := utils.ValidateBooking(compareBooking, reqPB.GetBooking()); iE != nil {
return fmt.Errorf("invalid response. UpdateBooking: %s", iE.Error())
}
return nil
}
// Rescheduling will attempt to create a booking, update the booking, then cancel.
-func Rescheduling(av []*fpb.Availability, conn *HTTPConnection) error {
+func Rescheduling(ctx context.Context, logger *log.Logger, av []*fpb.Availability, conn *utils.HTTPConnection) error {
var slots []*fpb.Availability
for _, v := range utils.BuildMerchantServiceMap(av) {
// Need at least two slots for reschedule.
@@ -419,7 +438,7 @@
return errors.New("no suitable availability for rescheduling flow. exiting")
}
// Book first slot.
- newBooking, err := CreateBooking(slots[0], conn)
+ newBooking, err := CreateBooking(ctx, logger, slots[0], conn)
if err != nil {
return fmt.Errorf("could not complete booking, abandoning rescheduling flow: %v", err)
}
@@ -436,11 +455,11 @@
},
},
}
- req, err := conn.marshaler.MarshalToString(reqPB)
+ req, err := conn.Marshaler.MarshalToString(reqPB)
if err != nil {
return fmt.Errorf("Rescheduling UpdateBooking: Could not convert pb3 to json: %v", reqPB)
}
- updateHTTPResp, err := sendRequest("UpdateBooking", req, conn)
+ updateHTTPResp, err := sendRequest(ctx, logger, "UpdateBooking", req, conn)
var resp mpb.CreateBookingResponse
if err := jsonpb.UnmarshalString(updateHTTPResp, &resp); err != nil {
return fmt.Errorf("Rescheduling UpdateBooking: Could not parse HTTP response to pb3: %v", err)
@@ -453,21 +472,21 @@
if iE := utils.ValidateBooking(resp.GetBooking(), newBooking); iE != nil {
return fmt.Errorf("invalid response. UpdateBooking: %s, abandoning slot 1/1", iE.Error())
}
- return CancelBooking(resp.GetBooking().GetBookingId(), conn)
+ return CancelBooking(ctx, logger, resp.GetBooking().GetBookingId(), conn)
}
// CheckOrderFulfillability attempts to send a CheckOrderFulfillabilityRequest
// to the connection endpoint and diff the results with what are expected.
-func CheckOrderFulfillability(merchantID string, lineItems []*mpb.LineItem, conn *HTTPConnection) error {
+func CheckOrderFulfillability(ctx context.Context, logger *log.Logger, merchantID string, lineItems []*mpb.LineItem, conn *utils.HTTPConnection) error {
reqPB := &mpb.CheckOrderFulfillabilityRequest{
MerchantId: merchantID,
Item: lineItems,
}
- req, err := conn.marshaler.MarshalToString(reqPB)
+ req, err := conn.Marshaler.MarshalToString(reqPB)
if err != nil {
return fmt.Errorf("Could not convert pb3 to json: %v", reqPB)
}
- httpResp, err := sendRequest("CheckOrderFulfillability", req, conn)
+ httpResp, err := sendRequest(ctx, logger, "CheckOrderFulfillability", req, conn)
if err != nil {
return fmt.Errorf("invalid response. CheckOrderFulfillability yielded error: %v", err)
}
@@ -477,7 +496,6 @@
}
orderFulfillability := resp.GetFulfillability()
- // TODO(ccawdrey): Add validation cases for other OrderFulFillability enums.
if diff := cmp.Diff(orderFulfillability.GetResult(), mpb.OrderFulfillability_CAN_FULFILL); diff != "" {
return fmt.Errorf("invalid response. CheckOrderFulfillability.Fulfillability.OrderFulfillabilityResult differ (-got +want)\n%s", diff)
}
@@ -505,7 +523,7 @@
}
// CreateOrder will attempt to build an order from a merchant id and array of line orders.
-func CreateOrder(merchantID string, lineItems []*mpb.LineItem, conn *HTTPConnection) (*mpb.Order, error) {
+func CreateOrder(ctx context.Context, logger *log.Logger, merchantID string, lineItems []*mpb.LineItem, conn *utils.HTTPConnection) (*mpb.Order, error) {
gen := rand.New(rand.NewSource(time.Now().UnixNano()))
reqOrder := &mpb.Order{
@@ -527,11 +545,11 @@
IdempotencyToken: strconv.Itoa(gen.Intn(1000000)),
}
- req, err := conn.marshaler.MarshalToString(reqPB)
+ req, err := conn.Marshaler.MarshalToString(reqPB)
if err != nil {
return nil, fmt.Errorf("Could not convert pb3 to json: %v", reqPB)
}
- httpResp, err := sendRequest("CreateOrder", req, conn)
+ httpResp, err := sendRequest(ctx, logger, "CreateOrder", req, conn)
if err != nil {
return nil, fmt.Errorf("invalid response. CreateOrder yielded error: %v", err)
}
@@ -549,8 +567,8 @@
}
// Perform idempotency test.
- log.Printf("Idempotency check")
- idemHTTPResp, err := sendRequest("CreateOrder", req, conn)
+ logger.Printf("Idempotency check")
+ idemHTTPResp, err := sendRequest(ctx, logger, "CreateOrder", req, conn)
if err != nil {
return nil, fmt.Errorf("invalid response. Idempotency check yielded error: %v", err)
}
@@ -569,12 +587,12 @@
return resp.GetOrder(), nil
}
-func sendListOrdersRequest(reqPB *mpb.ListOrdersRequest, conn *HTTPConnection) (mpb.ListOrdersResponse, error) {
- req, err := conn.marshaler.MarshalToString(reqPB)
+func sendListOrdersRequest(ctx context.Context, logger *log.Logger, reqPB *mpb.ListOrdersRequest, conn *utils.HTTPConnection) (mpb.ListOrdersResponse, error) {
+ req, err := conn.Marshaler.MarshalToString(reqPB)
if err != nil {
return mpb.ListOrdersResponse{}, fmt.Errorf("Could not convert pb3 to json: %v", reqPB)
}
- httpResp, err := sendRequest("ListOrders", req, conn)
+ httpResp, err := sendRequest(ctx, logger, "ListOrders", req, conn)
if err != nil {
return mpb.ListOrdersResponse{}, fmt.Errorf("invalid response. ListOrders yielded error: %v", err)
}
@@ -588,7 +606,7 @@
// ListOrders first checks that the number and contents of the server's order state
// are consistent with what the test client assumes is present.
-func ListOrders(orders []*mpb.Order, conn *HTTPConnection) error {
+func ListOrders(ctx context.Context, logger *log.Logger, orders []*mpb.Order, conn *utils.HTTPConnection) error {
if len(orders) == 0 {
return errors.New("at least one order must be present for ListOrders to succeed")
}
@@ -597,7 +615,7 @@
reqPB := &mpb.ListOrdersRequest{
Ids: &mpb.ListOrdersRequest_UserId{userID},
}
- respUser, err := sendListOrdersRequest(reqPB, conn)
+ respUser, err := sendListOrdersRequest(ctx, logger, reqPB, conn)
if err != nil {
return err
}
@@ -613,7 +631,7 @@
}
reqPB.Ids = &mpb.ListOrdersRequest_OrderIds_{&orderIDs}
- respOrder, err := sendListOrdersRequest(reqPB, conn)
+ respOrder, err := sendListOrdersRequest(ctx, logger, reqPB, conn)
if err != nil {
return err
}
@@ -625,7 +643,7 @@
}
// BatchGetWaitEstimates calls the partners API and verifies the returned WaitEstimates.
-func BatchGetWaitEstimates(s *fpb.Service, conn *HTTPConnection) error {
+func BatchGetWaitEstimates(ctx context.Context, logger *log.Logger, s *fpb.Service, conn *utils.HTTPConnection) error {
rules := s.GetWaitlistRules()
ps := make([]int32, rules.GetMaxPartySize()-rules.GetMinPartySize())
@@ -637,11 +655,11 @@
ServiceId: s.GetServiceId(),
PartySize: ps,
}
- req, err := conn.marshaler.MarshalToString(reqPB)
+ req, err := conn.Marshaler.MarshalToString(reqPB)
if err != nil {
return fmt.Errorf("Could not convert pb3 to json: %v", reqPB)
}
- httpResp, err := sendRequest("BatchGetWaitEstimates", req, conn)
+ httpResp, err := sendRequest(ctx, logger, "BatchGetWaitEstimates", req, conn)
if err != nil {
return fmt.Errorf("invalid response. BatchGetWaitEstimates yielded error: %v", err)
}
@@ -677,7 +695,7 @@
// CreateWaitlistEntry attempts to create waitlist entries from a service.
// The max party size listed for the service's waitlist rules is used for
// the waitlist entry's party size.
-func CreateWaitlistEntry(s *fpb.Service, conn *HTTPConnection) (string, error) {
+func CreateWaitlistEntry(ctx context.Context, logger *log.Logger, s *fpb.Service, conn *utils.HTTPConnection) (string, error) {
gen := rand.New(rand.NewSource(time.Now().UnixNano()))
reqPB := &wpb.CreateWaitlistEntryRequest{
@@ -696,12 +714,12 @@
if s.GetWaitlistRules().GetSupportsAdditionalRequest() {
reqPB.AdditionalRequest = "test additional request"
}
- req, err := conn.marshaler.MarshalToString(reqPB)
+ req, err := conn.Marshaler.MarshalToString(reqPB)
if err != nil {
return "", fmt.Errorf("Could not convert pb3 to json: %v", reqPB)
}
- httpResp, err := sendRequest("CreateWaitlistEntry", req, conn)
+ httpResp, err := sendRequest(ctx, logger, "CreateWaitlistEntry", req, conn)
if err != nil {
return "", fmt.Errorf("invalid response. CreateWaitlistEntry yielded error: %v", err)
}
@@ -721,8 +739,8 @@
}
// Perform idempotency test.
- log.Printf("Idempotency check -- CreateWaitlistEntry Request. Sent(unix): %s, Request %s", time.Now().UTC().Format(time.RFC850), reqPB.String())
- idemHTTPResp, err := sendRequest("CreateWaitlistEntry", req, conn)
+ logger.Printf("Idempotency check -- CreateWaitlistEntry Request. Sent(unix): %s, Request %s", time.Now().UTC().Format(time.RFC850), reqPB.String())
+ idemHTTPResp, err := sendRequest(ctx, logger, "CreateWaitlistEntry", req, conn)
if err != nil {
return "", fmt.Errorf("invalid response. Idempotency check yielded error: %v", err)
}
@@ -740,16 +758,16 @@
// GetWaitlistEntry retrieves and validates the booking for the specified
// waitlist entry id.
-func GetWaitlistEntry(id string, conn *HTTPConnection) (*wpb.WaitlistEntry, error) {
+func GetWaitlistEntry(ctx context.Context, logger *log.Logger, id string, conn *utils.HTTPConnection) (*wpb.WaitlistEntry, error) {
reqPB := &wpb.GetWaitlistEntryRequest{
WaitlistEntryId: id,
}
- req, err := conn.marshaler.MarshalToString(reqPB)
+ req, err := conn.Marshaler.MarshalToString(reqPB)
if err != nil {
return nil, fmt.Errorf("Could not convert pb3 to json: %v", reqPB)
}
- httpResp, err := sendRequest("GetWaitlistEntry", req, conn)
+ httpResp, err := sendRequest(ctx, logger, "GetWaitlistEntry", req, conn)
if err != nil {
return nil, fmt.Errorf("invalid response. GetWaitlistEntry yielded error: %v", err)
}
@@ -766,22 +784,22 @@
}
// DeleteWaitlistEntry makes a request to delete the waitlist entry.
-func DeleteWaitlistEntry(id string, conn *HTTPConnection) error {
+func DeleteWaitlistEntry(ctx context.Context, logger *log.Logger, id string, conn *utils.HTTPConnection) error {
reqPB := &wpb.DeleteWaitlistEntryRequest{
WaitlistEntryId: id,
}
- req, err := conn.marshaler.MarshalToString(reqPB)
+ req, err := conn.Marshaler.MarshalToString(reqPB)
if err != nil {
return fmt.Errorf("Could not convert pb3 to json: %v", reqPB)
}
- httpResp, err := sendRequest("DeleteWaitlistEntry", req, conn)
+ httpResp, err := sendRequest(ctx, logger, "DeleteWaitlistEntry", req, conn)
if err != nil {
return fmt.Errorf("invalid response. DeleteWaitlistEntry yielded error: %v", err)
}
- var resp empty.Empty
+ var resp epb.Empty
if err := jsonpb.UnmarshalString(httpResp, &resp); err != nil {
return fmt.Errorf("DeleteWaitlistEntry: Could not parse HTTP response to pb3: %v. Body should be: {}", err)
}
diff --git a/booking/bookingTests.go b/booking/bookingTests.go
new file mode 100644
index 0000000..b44ec05
--- /dev/null
+++ b/booking/bookingTests.go
@@ -0,0 +1,270 @@
+/*
+Copyright 2019 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 booking contains test logic for booking related endpoints.
+package booking
+
+import (
+ "context"
+ "log"
+
+ "github.com/maps-booking-v3/api"
+ "github.com/maps-booking-v3/utils"
+
+ fpb "github.com/maps-booking-v3/feeds"
+ mpb "github.com/maps-booking-v3/v3"
+)
+
+// GenerateBookings creates bookings from an availability feed.
+func GenerateBookings(ctx context.Context, logger *log.Logger, av []*fpb.Availability, stats *utils.TestSummary, conn *utils.HTTPConnection, config *utils.Config) api.Bookings {
+ logger.Println("no previous bookings to use, acquiring new inventory")
+ utils.LogFlow(logger, "Generate Fresh Inventory", "Start")
+ defer utils.LogFlow(logger, "Generate Fresh Inventory", "End")
+
+ var out api.Bookings
+ totalSlots := len(av)
+ for i, a := range av {
+ if config.BookingUseBal {
+ if err := api.BatchAvailabilityLookup(ctx, logger, []*fpb.Availability{a}, conn); err != nil {
+ logger.Printf("BAL error: %s. skipping slot %d/%d", err.Error(), i, totalSlots)
+ stats.BookingBatchAvailabilityLookupErrors++
+ continue
+ }
+ stats.BookingBatchAvailabilityLookupSuccess++
+ } else {
+ if err := api.CheckAvailability(ctx, logger, a, conn); err != nil {
+ logger.Printf("%s. skipping slot %d/%d", err.Error(), i, totalSlots)
+ stats.BookingCheckAvailabilityErrors++
+ continue
+ }
+ stats.BookingCheckAvailabilitySuccess++
+ }
+
+ booking, err := api.CreateBooking(ctx, logger, a, conn)
+ if err != nil {
+ logger.Printf("%s. skipping slot %d/%d", err.Error(), i, totalSlots)
+ stats.BookingCreateBookingErrors++
+ continue
+ }
+ out = append(out, booking)
+ stats.BookingCreateBookingSuccess++
+ }
+ return out
+}
+
+func logStats(stats *utils.TestSummary, logger *log.Logger, config *utils.Config) {
+ logger.Println("\n************* Begin Stats *************\n")
+ var totalErrors int
+ if config.BookingHealthFlow || config.BookingAllFlows {
+ if stats.BookingHealthCheckSuccess {
+ logger.Println("HealthCheck Succeeded")
+ } else {
+ totalErrors++
+ logger.Println("HealthCheck Failed")
+ }
+ }
+ if config.BookingCheckFlow || config.BookingAllFlows {
+ if config.BookingUseBal {
+ totalErrors += stats.BookingBatchAvailabilityLookupErrors
+ logger.Printf("BatchAvailabilityLookup Errors: %d/%d", stats.BookingBatchAvailabilityLookupErrors, stats.BookingBatchAvailabilityLookupErrors+stats.BookingBatchAvailabilityLookupSuccess)
+ } else {
+ totalErrors += stats.BookingCheckAvailabilityErrors
+ logger.Printf("CheckAvailability Errors: %d/%d", stats.BookingCheckAvailabilityErrors, stats.BookingCheckAvailabilityErrors+stats.BookingCheckAvailabilitySuccess)
+ }
+ }
+ if config.BookingBookFlow || config.BookingAllFlows {
+ totalErrors += stats.BookingCreateBookingErrors
+ logger.Printf("CreateBooking Errors: %d/%d", stats.BookingCreateBookingErrors, stats.BookingCreateBookingErrors+stats.BookingCreateBookingSuccess)
+ }
+ if config.BookingListFlow || config.BookingAllFlows || config.BookingCancelAllBookings {
+ if stats.BookingListBookingsSuccess {
+ logger.Println("ListBookings Succeeded")
+ } else {
+ totalErrors++
+ logger.Println("ListBookings Failed")
+ }
+ }
+ if config.BookingStatusFlow || config.BookingAllFlows {
+ totalErrors += stats.BookingGetBookingStatusErrors
+ logger.Printf("GetBookingStatus Errors: %d/%d", stats.BookingGetBookingStatusErrors, stats.BookingGetBookingStatusErrors+stats.BookingGetBookingStatusSuccess)
+ }
+ if config.BookingRescheduleFlow || config.BookingAllFlows {
+ if stats.BookingReschedulingSuccess {
+ logger.Println("Rescheduling Succeeded")
+ } else {
+ totalErrors++
+ logger.Println("Rescheduling Failed")
+ }
+ }
+
+ logger.Println("\n\n\n")
+ if totalErrors == 0 {
+ logger.Println("All Tests Pass!")
+ } else {
+ logger.Printf("Found %d Errors", totalErrors)
+ }
+
+ logger.Println("\n************* End Stats *************\n")
+}
+
+// RunTests runs booking tests.
+func RunTests(ctx context.Context, logger *log.Logger, config *utils.Config, av []*fpb.Availability, avForRescheduling []*fpb.Availability, stats *utils.TestSummary) {
+ conn := config.Conn
+ // HealthCheck Flow
+ if config.BookingHealthFlow || config.BookingAllFlows {
+ stats.BookingHealthCheckSuccess = true
+ if err := api.HealthCheck(ctx, logger, conn); err != nil {
+ stats.BookingHealthCheckSuccess = false
+ logger.Println(err.Error())
+ }
+ stats.BookingHealthCheckCompleted = true
+ if !config.BookingAllFlows && !config.BookingCheckFlow && !config.BookingBookFlow &&
+ !config.BookingListFlow && !config.BookingStatusFlow && !config.BookingRescheduleFlow {
+ logStats(stats, logger, config)
+ return
+ }
+ }
+ if config.BookingCheckFlow || config.BookingAllFlows {
+ if config.BookingUseBal {
+ utils.LogFlow(logger, "Batch Availability Lookup", "Start")
+ for _, a := range utils.SplitAvailabilityByMerchant(av) {
+ if err := api.BatchAvailabilityLookup(ctx, logger, a, conn); err != nil {
+ logger.Printf("BatchAvailabilityLookup returned error: %v", err)
+ stats.BookingBatchAvailabilityLookupErrors++
+ } else {
+ stats.BookingBatchAvailabilityLookupSuccess++
+ }
+ }
+ utils.LogFlow(logger, "Batch Availability Lookup", "End")
+ stats.BookingBatchAvailabilityLookupCompleted = true
+ } else {
+ // AvailabilityCheck Flow
+ utils.LogFlow(logger, "Availability Check", "Start")
+ totalSlots := len(av)
+
+ for i, a := range av {
+ if err := api.CheckAvailability(ctx, logger, a, conn); err != nil {
+ logger.Printf("%s. skipping slot %d/%d", err.Error(), i, totalSlots)
+ stats.BookingCheckAvailabilityErrors++
+ continue
+ }
+ stats.BookingCheckAvailabilitySuccess++
+ }
+ utils.LogFlow(logger, "Availability Check", "End")
+ stats.BookingCheckAvailabilityCompleted = true
+ }
+ }
+
+ // CreateBooking Flow.
+ var b []*mpb.Booking
+ if config.BookingBookFlow || config.BookingAllFlows {
+ utils.LogFlow(logger, "Booking", "Start")
+ totalSlots := len(av)
+ logger.Printf("total slots %d", totalSlots)
+ for i, a := range av {
+ logger.Printf("creating booking")
+ booking, err := api.CreateBooking(ctx, logger, a, conn)
+ if err != nil {
+ logger.Printf("%s. skipping slot %d/%d", err.Error(), i, totalSlots)
+ stats.BookingCreateBookingErrors++
+ continue
+ }
+ b = append(b, booking)
+ stats.BookingCreateBookingSuccess++
+ }
+ utils.LogFlow(logger, "Booking", "End")
+ stats.BookingCreateBookingCompleted = true
+ }
+ // ListBookings Flow
+ if config.BookingListFlow || config.BookingAllFlows || config.BookingCancelAllBookings {
+ if len(b) == 0 && !config.BookingCancelAllBookings {
+ b = GenerateBookings(ctx, logger, av, stats, conn, config)
+ }
+ utils.LogFlow(logger, "List Bookings", "Start")
+ if len(b) > 0 || config.BookingCancelAllBookings {
+ var err error
+ b, err = api.ListBookings(ctx, logger, b, conn)
+ stats.BookingListBookingsSuccess = true
+ if err != nil {
+ stats.BookingListBookingsSuccess = false
+ logger.Println(err.Error())
+ }
+ } else {
+ logger.Println("Could not create bookings to test ListBookings flow with.")
+ stats.BookingListBookingsSuccess = false
+ }
+ utils.LogFlow(logger, "List Bookings", "End")
+ stats.BookingListBookingsCompleted = true
+ }
+
+ // GetBookingStatus Flow
+ if config.BookingStatusFlow || config.BookingAllFlows {
+ if len(b) == 0 {
+ b = GenerateBookings(ctx, logger, av, stats, conn, config)
+ }
+
+ utils.LogFlow(logger, "BookingStatus", "Start")
+ totalBookings := len(b)
+
+ if totalBookings > 0 {
+ j := 0
+ for i, booking := range b {
+ if err := api.GetBookingStatus(ctx, logger, booking, conn); err != nil {
+ logger.Printf("%s. abandoning booking %d/%d", err.Error(), i, totalBookings)
+ stats.BookingGetBookingStatusErrors++
+ continue
+ }
+ stats.BookingGetBookingStatusSuccess++
+ b[j] = booking
+ j++
+ }
+ b = b[:j]
+ } else {
+ logger.Println("Could not create bookings to test GetBookingStatus flow with.")
+ }
+ utils.LogFlow(logger, "BookingStatus", "End")
+ stats.BookingGetBookingStatusCompleted = true
+ }
+ // CancelBooking Flow
+ if len(b) > 0 {
+ utils.LogFlow(logger, "Cancel Booking", "Start")
+ for i, booking := range b {
+ if err := api.CancelBooking(ctx, logger, booking.GetBookingId(), conn); err != nil {
+ logger.Printf("%s. abandoning booking %d/%d", err.Error(), i, len(b))
+ stats.BookingCancelBookingsErrors++
+ continue
+ }
+ stats.BookingCancelBookingsSuccess++
+ }
+ utils.LogFlow(logger, "Cancel Booking", "End")
+ stats.BookingCancelBookingsCompleted = true
+ }
+
+ // Rescheduling is nuanced and can be isolated
+ // from the rest of the tests.
+ if config.BookingRescheduleFlow || config.BookingAllFlows {
+ utils.LogFlow(logger, "Rescheduling", "Start")
+ stats.BookingReschedulingSuccess = true
+ if err := api.Rescheduling(ctx, logger, avForRescheduling, conn); err != nil {
+ logger.Println(err.Error())
+ stats.BookingReschedulingSuccess = false
+ }
+ utils.LogFlow(logger, "Rescheduling", "End")
+ stats.BookingReschedulingCompleted = true
+ }
+
+ logStats(stats, logger, config)
+}
diff --git a/order/orderTests.go b/order/orderTests.go
new file mode 100644
index 0000000..23c689d
--- /dev/null
+++ b/order/orderTests.go
@@ -0,0 +1,155 @@
+/*
+Copyright 2019 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 order contains test logic for order related endpoints.
+package order
+
+import (
+ "context"
+ "log"
+
+ fpb "github.com/maps-booking-v3/feeds"
+ "github.com/maps-booking-v3/api"
+ mpb "github.com/maps-booking-v3/v3"
+ "github.com/maps-booking-v3/utils"
+)
+
+func logStats(stats *utils.TestSummary, logger *log.Logger, config *utils.Config) {
+ logger.Println("\n************* Begin Stats *************\n")
+ var totalErrors int
+ if config.OrderHealthFlow || config.OrderAllFlows {
+ if stats.OrderHealthCheckSuccess {
+ logger.Println("HealthCheck Succeeded")
+ } else {
+ totalErrors++
+ logger.Println("HealthCheck Failed")
+ }
+ stats.OrderHealthCheckCompleted = true
+ }
+ if config.OrderCheckFlow || config.OrderAllFlows {
+ totalErrors += stats.OrderCheckOrderFulfillabilityErrors
+ logger.Printf("CheckOrderFulfillability Errors: %d/%d", stats.OrderCheckOrderFulfillabilityErrors, stats.OrderCheckOrderFulfillabilityErrors+stats.OrderCheckOrderFulfillabilitySuccess)
+ }
+ if config.OrderOrderFlow || config.OrderAllFlows {
+ totalErrors += stats.OrderCreateOrderErrors
+ logger.Printf("CreateOrder Errors: %d/%d", stats.OrderCreateOrderErrors, stats.OrderCreateOrderErrors+stats.OrderCreateOrderSuccess)
+ }
+ if config.OrderAllFlows {
+ if stats.OrderListOrdersSuccess {
+ logger.Println("ListOrders Succeeded")
+ } else {
+ totalErrors++
+ logger.Println("ListOrders Failed")
+ }
+ }
+ if config.OrderCheckFlow || config.OrderOrderFlow || config.OrderAllFlows {
+ logger.Printf("Total Slots Processed: %d", stats.OrderTotalSlotsProcessed)
+ }
+
+ logger.Println("\n\n\n")
+ if totalErrors == 0 {
+ logger.Println("All Tests Pass!")
+ } else {
+ logger.Printf("Found %d Errors", totalErrors)
+ }
+
+ logger.Println("\n************* End Stats *************\n")
+}
+
+// RunTests runs order tests.
+func RunTests(ctx context.Context, logger *log.Logger, config *utils.Config, av []*fpb.Availability, services []*fpb.Service, stats *utils.TestSummary) {
+ conn := config.Conn
+ // HealthCheck Flow
+ if config.OrderHealthFlow || config.OrderAllFlows {
+ stats.OrderHealthCheckSuccess = true
+ if err := api.HealthCheck(ctx, logger, conn); err != nil {
+ stats.OrderHealthCheckSuccess = false
+ logger.Println(err.Error())
+ }
+ stats.OrderHealthCheckCompleted = true
+ if !config.OrderAllFlows && !config.OrderCheckFlow && !config.OrderOrderFlow {
+ logStats(stats, logger, config)
+ return
+ }
+ }
+ testInventory, err := utils.BuildLineItemMap(services, av)
+ if err != nil {
+ logger.Printf("Remaining tests cannot run due to error building line items: %s\n", err)
+ return
+ }
+
+ // CheckOrderFulfillability Flow
+ if config.OrderCheckFlow || config.OrderAllFlows {
+ utils.LogFlow(logger, "CheckOrderFulfillability", "Start")
+ for _, value := range testInventory {
+ stats.OrderTotalSlotsProcessed += len(value)
+ }
+
+ i := 0
+ for merchantID, lineItems := range testInventory {
+ if err := api.CheckOrderFulfillability(ctx, logger, merchantID, lineItems, conn); err != nil {
+ logger.Printf("%s. skipping slots %d-%d/%d", err.Error(), i, i+len(lineItems)-1, stats.OrderTotalSlotsProcessed)
+ stats.OrderCheckOrderFulfillabilityErrors += len(lineItems)
+ delete(testInventory, merchantID)
+ i += len(lineItems)
+ continue
+ }
+ stats.OrderCheckOrderFulfillabilitySuccess += len(lineItems)
+ i += len(lineItems)
+ }
+ stats.OrderCheckOrderFulfillabilityCompleted = true
+ utils.LogFlow(logger, "CheckOrderFulfillability", "End")
+ }
+ // CreateOrder Flow.
+ var orders []*mpb.Order
+ if config.OrderOrderFlow || config.OrderAllFlows {
+ utils.LogFlow(logger, "CreateOrder", "Start")
+ if stats.OrderTotalSlotsProcessed == 0 {
+ for _, value := range testInventory {
+ stats.OrderTotalSlotsProcessed += len(value)
+ }
+ }
+
+ i := 0
+ for merchantID, lineItems := range testInventory {
+ order, err := api.CreateOrder(ctx, logger, merchantID, lineItems, conn)
+ if err != nil {
+ logger.Printf("%s. skipping slot %d-%d/%d", err.Error(), i, i+len(lineItems)-1, stats.OrderTotalSlotsProcessed)
+ stats.OrderCreateOrderErrors += len(lineItems)
+ delete(testInventory, merchantID)
+ i += len(lineItems)
+ continue
+ }
+ orders = append(orders, order)
+ stats.OrderCreateOrderSuccess += len(lineItems)
+ i += len(lineItems)
+ }
+ stats.OrderCreateOrderCompleted = true
+ utils.LogFlow(logger, "CreateOrder", "End")
+ }
+ // ListOrders Flow
+ if config.OrderAllFlows || config.OrderListFlow {
+ utils.LogFlow(logger, "ListOrders", "Start")
+ stats.OrderListOrdersSuccess = true
+ if err := api.ListOrders(ctx, logger, orders, conn); err != nil {
+ stats.OrderListOrdersSuccess = false
+ logger.Println(err.Error())
+ }
+ stats.OrderListOrdersCompleted = true
+ utils.LogFlow(logger, "ListOrders", "End")
+ }
+ logStats(stats, logger, config)
+}
diff --git a/proto/waitlist.proto b/proto/waitlist.proto
index 218e868..d432edc 100644
--- a/proto/waitlist.proto
+++ b/proto/waitlist.proto
@@ -100,6 +100,17 @@
EstimatedSeatTimeRange estimated_seat_time_range = 2;
}
+// The confirmation modes used when joining the waitlist.
+enum WaitlistConfirmationMode {
+ // The confirmation mode was not specified.
+ // Synchronous confirmation will be assumed.
+ WAITLIST_CONFIRMATION_MODE_UNSPECIFIED = 0;
+ // Waitlist entries will be confirmed synchronously.
+ WAITLIST_CONFIRMATION_MODE_SYNCHRONOUS = 1;
+ // Waitlist entries will be confirmed asynchronously.
+ WAITLIST_CONFIRMATION_MODE_ASYNCHRONOUS = 2;
+}
+
// The wait estimate for a particular party size, merchant and service.
message WaitEstimate {
// Required. The party size this wait estimate applies to.
@@ -108,6 +119,11 @@
// Required. Contains fields measuring how long (in time or # of people) until
// the user is ready to leave the waitlist and be seated.
WaitLength wait_length = 2;
+
+ // Required. Indicates whether waitlist entries for this wait estimate will be
+ // confirmed synchronously or asynchronously. An UNSPECIFIED value will be
+ // interpreted as synchronous.
+ WaitlistConfirmationMode waitlist_confirmation_mode = 3;
}
// CreateWaitlistEntry method
@@ -186,6 +202,8 @@
// The waitlist entry was created and the user is currently waiting in the
// waitlist.
WAITING = 1;
+ // The waitlist entry is awaiting confirmation by the merchant.
+ PENDING_MERCHANT_CONFIRMATION = 8;
// The waitlist entry has been canceled by the user. Cancellation for no-shows
// should use the NO_SHOW state.
CANCELED = 2;
diff --git a/testclient/.bookingClient.go.swp b/testclient/.bookingClient.go.swp
deleted file mode 100644
index b3ae8ec..0000000
--- a/testclient/.bookingClient.go.swp
+++ /dev/null
Binary files differ
diff --git a/testclient/bookingClient.go b/testclient/bookingClient.go
index eb23a84..8462716 100644
--- a/testclient/bookingClient.go
+++ b/testclient/bookingClient.go
@@ -1,5 +1,5 @@
/*
-Copyright 2017 Google Inc.
+Copyright 2019 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,29 +16,23 @@
package main
import (
+ "context"
"flag"
- "fmt"
"log"
- "os"
- "path/filepath"
- "time"
"github.com/maps-booking-v3/api"
- "github.com/maps-booking-v3/utils"
-
+ "github.com/maps-booking-v3/booking"
fpb "github.com/maps-booking-v3/feeds"
- mpb "github.com/maps-booking-v3/v3"
+ "github.com/maps-booking-v3/utils"
)
-const logFile = "http_test_client_log_"
-
var (
serverAddr = flag.String("server_addr", "example.com:80", "Your http server's address in the format of host:port")
credentialsFile = flag.String("credentials_file", "", "File containing credentials for your server. Leave blank to bypass authentication. File should have exactly one line of the form 'username:password'.")
testSlots = flag.Int("num_test_slots", 10, "Maximum number of slots to test from availability_feed. Slots will be selected randomly")
allFlows = flag.Bool("all_tests", false, "Whether to test all endpoints.")
healthFlow = flag.Bool("health_check_test", false, "Whether to test the Health endpoint.")
- checkFlow = flag.Bool("check_availability_test", false, "Whether to test the CheckAvailability endpoint.")
+ checkFlow = flag.Bool("check_availability_test", false, "Whether to test availability lookup. Will use BatchAvailabilityLookup or CheckAvailability endpoint depending on value of the use_batch_availability_lookup flag.")
bookFlow = flag.Bool("booking_test", false, "Whether to test the CreateBooking endpoint.")
listFlow = flag.Bool("list_bookings_test", false, "Whether to test the ListBookings endpoint")
statusFlow = flag.Bool("booking_status_test", false, "Whether to test the GetBookingStatus endpoint.")
@@ -52,297 +46,61 @@
useBal = flag.Bool("use_batch_availability_lookup", false, "Whether to use the BatchAvailabilityLookup RPC (as opposed to the deprecated CheckAvailability)")
)
-type counters struct {
- TotalSlotsProcessed int
- HealthCheckSuccess bool
- BatchAvailabilityLookupErrors int
- BatchAvailabilityLookupSuccess int
- CheckAvailabilitySuccess int
- CheckAvailabilityErrors int
- CreateBookingSuccess int
- CreateBookingErrors int
- ListBookingsSuccess bool
- GetBookingStatusSuccess int
- GetBookingStatusErrors int
- CancelBookingsSuccess int
- CancelBookingsErrors int
- ReschedulingSuccess bool
-}
-
-// GenerateBookings creates bookings from an availability feed.
-func GenerateBookings(av []*fpb.Availability, stats *counters, conn *api.HTTPConnection) api.Bookings {
- log.Println("no previous bookings to use, acquiring new inventory")
- utils.LogFlow("Generate Fresh Inventory", "Start")
- defer utils.LogFlow("Generate Fresh Inventory", "End")
-
- var out api.Bookings
- totalSlots := len(av)
- for i, a := range av {
- if *useBal {
- if err := api.BatchAvailabilityLookup([]*fpb.Availability{a}, conn); err != nil {
- log.Printf("BAL error: %s. skipping slot %d/%d", err.Error(), i, totalSlots)
- stats.BatchAvailabilityLookupErrors++
- continue
- }
- stats.BatchAvailabilityLookupSuccess++
- } else {
- if err := api.CheckAvailability(a, conn); err != nil {
- log.Printf("%s. skipping slot %d/%d", err.Error(), i, totalSlots)
- stats.CheckAvailabilityErrors++
- continue
- }
- stats.CheckAvailabilitySuccess++
- }
-
- booking, err := api.CreateBooking(a, conn)
- if err != nil {
- log.Printf("%s. skipping slot %d/%d", err.Error(), i, totalSlots)
- stats.CreateBookingErrors++
- continue
- }
- out = append(out, booking)
- stats.CreateBookingSuccess++
+func makeConfig(logger *log.Logger) *utils.Config {
+ conn, err := api.InitHTTPConnection(*serverAddr, *credentialsFile, *caFile, *fullServerName)
+ if err != nil {
+ logger.Fatalf("Failed to init http connection %v", err)
}
- return out
-}
-
-func createLogFile() (*os.File, error) {
- var err error
- outPath := *outputDir
- if outPath == "" {
- outPath, err = os.Getwd()
- if err != nil {
- return nil, err
- }
- }
-
- now := time.Now().UTC()
- nowString := fmt.Sprintf("%d-%02d-%02d_%02d-%02d-%02d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
- outFile := filepath.Join(outPath, fmt.Sprintf("%s%s", logFile, nowString))
-
- return os.Create(outFile)
-}
-
-func logStats(stats counters) {
- log.Println("\n************* Begin Stats *************\n")
- var totalErrors int
- if *healthFlow || *allFlows {
- if stats.HealthCheckSuccess {
- log.Println("HealthCheck Succeeded")
- } else {
- totalErrors++
- log.Println("HealthCheck Failed")
- }
- }
- if *useBal {
- totalErrors += stats.BatchAvailabilityLookupErrors
- log.Printf("BatchAvailabilityLookup Errors: %d/%d", stats.BatchAvailabilityLookupErrors, stats.BatchAvailabilityLookupErrors+stats.BatchAvailabilityLookupSuccess)
- } else if *checkFlow || *allFlows {
- totalErrors += stats.CheckAvailabilityErrors
- log.Printf("CheckAvailability Errors: %d/%d", stats.CheckAvailabilityErrors, stats.CheckAvailabilityErrors+stats.CheckAvailabilitySuccess)
- }
- if *bookFlow || *allFlows {
- totalErrors += stats.CreateBookingErrors
- log.Printf("CreateBooking Errors: %d/%d", stats.CreateBookingErrors, stats.CreateBookingErrors+stats.CreateBookingSuccess)
- }
- if *listFlow || *allFlows || *cancelAllBookings {
- if stats.ListBookingsSuccess {
- log.Println("ListBookings Succeeded")
- } else {
- totalErrors++
- log.Println("ListBookings Failed")
- }
- }
- if *statusFlow || *allFlows {
- totalErrors += stats.GetBookingStatusErrors
- log.Printf("GetBookingStatus Errors: %d/%d", stats.GetBookingStatusErrors, stats.GetBookingStatusErrors+stats.GetBookingStatusSuccess)
- }
- if *rescheduleFlow || *allFlows {
- if stats.ReschedulingSuccess {
- log.Println("Rescheduling Succeeded")
- } else {
- totalErrors++
- log.Println("Rescheduling Failed")
- }
- }
-
- log.Println("\n\n\n")
- if totalErrors == 0 {
- log.Println("All Tests Pass!")
- } else {
- log.Printf("Found %d Errors", totalErrors)
- }
-
- log.Println("\n************* End Stats *************\n")
- os.Exit(totalErrors)
+ return &utils.Config{Conn: conn,
+ BookingAllFlows: *allFlows,
+ BookingHealthFlow: *healthFlow,
+ BookingCheckFlow: *checkFlow,
+ BookingBookFlow: *bookFlow,
+ BookingListFlow: *listFlow,
+ BookingStatusFlow: *statusFlow,
+ BookingRescheduleFlow: *rescheduleFlow,
+ BookingCancelAllBookings: *cancelAllBookings,
+ BookingUseBal: *useBal}
}
func main() {
flag.Parse()
- var stats counters
- if !*outputToTerminal {
- // Set up logging before continuing with flows
- f, err := createLogFile()
- if err != nil {
- log.Fatalf("Failed to create log file %v", err)
- }
- defer f.Close()
- log.SetOutput(f)
- }
-
- conn, err := api.InitHTTPConnection(*serverAddr, *credentialsFile, *caFile, *fullServerName)
+ logger, f, err := utils.MakeLogger(*outputToTerminal, *outputDir)
if err != nil {
- log.Fatalf("Failed to init http connection %v", err)
+ log.Fatal("Could not create logger: ", err)
}
+ if f != nil {
+ defer f.Close()
+ }
+
+ config := makeConfig(logger)
// Health check doesn't affect the cancel booking flow so we let it through.
if *cancelAllBookings && (*allFlows || *checkFlow || *bookFlow || *listFlow || *statusFlow || *rescheduleFlow) {
- log.Fatal("cancel_all_bookings is not supported with other test flows")
+ logger.Fatal("cancel_all_bookings is not supported with other test flows")
}
- // HealthCheck Flow
- if *healthFlow || *allFlows {
- stats.HealthCheckSuccess = true
- if err := api.HealthCheck(conn); err != nil {
- stats.HealthCheckSuccess = false
- log.Println(err.Error())
- }
- if !*allFlows && !*checkFlow && !*bookFlow &&
- !*listFlow && !*statusFlow && !*rescheduleFlow {
- logStats(stats)
- }
- }
-
+ var summary utils.TestSummary
var av []*fpb.Availability
var avForRescheduling []*fpb.Availability
- if !*cancelAllBookings {
+
+ needAvailability := !*cancelAllBookings || *allFlows || *checkFlow || *bookFlow || *listFlow || *statusFlow || *rescheduleFlow
+
+ if needAvailability {
// Build availablility records.
if *availabilityFeed == "" {
- log.Fatal("please set availability_feed flag if you wish to test additional flows")
+ logger.Fatal("Please set availability_feed flag if you wish to test anything except health check.")
}
- av, err = utils.AvailabilityFrom(*availabilityFeed, *testSlots, false)
+ av, err = utils.AvailabilityFrom(logger, *availabilityFeed, *testSlots, false)
if err != nil {
- log.Fatalf("Failed to get availability: %v", err.Error())
+ logger.Fatalf("Failed to get availability: %v", err.Error())
}
- stats.TotalSlotsProcessed += len(av)
-
- avForRescheduling, err = utils.AvailabilityFrom(*availabilityFeed, *testSlots, true)
+ summary.BookingTotalSlotsProcessed += len(av)
+ avForRescheduling, err = utils.AvailabilityFrom(logger, *availabilityFeed, *testSlots, true)
if err != nil {
- log.Fatalf("Failed to get availability for rescheduling test: %v", err.Error())
+ logger.Fatalf("Failed to get availability for rescheduling test: %v", err.Error())
}
}
-
- if *useBal {
- utils.LogFlow("Batch Availability Lookup", "Start")
- for _, a := range utils.SplitAvailabilityByMerchant(av) {
- if err = api.BatchAvailabilityLookup(a, conn); err != nil {
- log.Printf("BatchAvailabilityLookup returned error: %v", err)
- stats.BatchAvailabilityLookupErrors++
- } else {
- stats.BatchAvailabilityLookupSuccess++
- }
- }
- utils.LogFlow("Batch Availability Lookup", "End")
- } else if *checkFlow || *allFlows {
- // AvailabilityCheck Flow
- utils.LogFlow("Availability Check", "Start")
- totalSlots := len(av)
-
- j := 0
- for i, a := range av {
- if err = api.CheckAvailability(a, conn); err != nil {
- log.Printf("%s. skipping slot %d/%d", err.Error(), i, totalSlots)
- stats.CheckAvailabilityErrors++
- continue
- }
- stats.CheckAvailabilitySuccess++
- av[j] = a
- j++
- }
- av = av[:j]
- utils.LogFlow("Availability Check", "End")
- }
- // CreateBooking Flow.
- var b []*mpb.Booking
- if *bookFlow || *allFlows {
- utils.LogFlow("Booking", "Start")
- totalSlots := len(av)
- for i, a := range av {
- booking, err := api.CreateBooking(a, conn)
- if err != nil {
- log.Printf("%s. skipping slot %d/%d", err.Error(), i, totalSlots)
- stats.CreateBookingErrors++
- continue
- }
- b = append(b, booking)
- stats.CreateBookingSuccess++
- }
- utils.LogFlow("Booking", "End")
- }
- // ListBookings Flow
- if *listFlow || *allFlows || *cancelAllBookings {
- if len(b) == 0 && !*cancelAllBookings {
- b = GenerateBookings(av, &stats, conn)
- }
- utils.LogFlow("List Bookings", "Start")
- b, err = api.ListBookings(b, conn)
- stats.ListBookingsSuccess = true
- if err != nil {
- stats.ListBookingsSuccess = false
- log.Println(err.Error())
- }
- utils.LogFlow("List Bookings", "End")
- }
-
- // GetBookingStatus Flow
- if *statusFlow || *allFlows {
- if len(b) == 0 {
- b = GenerateBookings(av, &stats, conn)
- }
-
- utils.LogFlow("BookingStatus", "Start")
- totalBookings := len(b)
-
- j := 0
- for i, booking := range b {
- if err = api.GetBookingStatus(booking, conn); err != nil {
- log.Printf("%s. abandoning booking %d/%d", err.Error(), i, totalBookings)
- stats.GetBookingStatusErrors++
- continue
- }
- stats.GetBookingStatusSuccess++
- b[j] = booking
- j++
- }
- b = b[:j]
- utils.LogFlow("BookingStatus", "End")
- }
- // CancelBooking Flow
- if len(b) > 0 {
- utils.LogFlow("Cancel Booking", "Start")
- for i, booking := range b {
- if err = api.CancelBooking(booking.GetBookingId(), conn); err != nil {
- log.Printf("%s. abandoning booking %d/%d", err.Error(), i, len(b))
- stats.CancelBookingsErrors++
- continue
- }
- stats.CancelBookingsSuccess++
- }
- utils.LogFlow("Cancel Booking", "End")
- }
-
- // Rescheduling is nuanced and can be isolated
- // from the rest of the tests.
- if *rescheduleFlow || *allFlows {
- utils.LogFlow("Rescheduling", "Start")
- stats.ReschedulingSuccess = true
- if err = api.Rescheduling(avForRescheduling, conn); err != nil {
- log.Println(err.Error())
- stats.ReschedulingSuccess = false
- }
- utils.LogFlow("Rescheduling", "End")
- }
-
- logStats(stats)
+ booking.RunTests(context.Background(), logger, config, av, avForRescheduling, &summary)
}
diff --git a/testclient/orderClient.go b/testclient/orderClient.go
index 718ff23..a3ee63d 100644
--- a/testclient/orderClient.go
+++ b/testclient/orderClient.go
@@ -1,5 +1,5 @@
/*
-Copyright 2017 Google Inc.
+Copyright 2019 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,21 +16,17 @@
package main
import (
+ "context"
"flag"
- "fmt"
"log"
- "os"
- "path/filepath"
- "time"
+
+ fpb "github.com/maps-booking-v3/feeds"
"github.com/maps-booking-v3/api"
+ "github.com/maps-booking-v3/order"
"github.com/maps-booking-v3/utils"
-
- mpb "github.com/maps-booking-v3/v3"
)
-const logFile = "http_test_client_log_"
-
var (
serverAddr = flag.String("server_addr", "example.com:80", "Your http server's address in the format of host:port")
credentialsFile = flag.String("credentials_file", "", "File containing credentials for your server. Leave blank to bypass authentication. File should have exactly one line of the form 'username:password'.")
@@ -47,172 +43,48 @@
outputToTerminal = flag.Bool("output_to_terminal", false, "Output to terminal rather than a file.")
)
-type counters struct {
- TotalSlotsProcessed int
- HealthCheckSuccess bool
- CheckOrderFulfillabilitySuccess int
- CheckOrderFulfillabilityErrors int
- CreateOrderSuccess int
- CreateOrderErrors int
- ListOrdersSuccess bool
-}
-
-func createLogFile() (*os.File, error) {
- var err error
- outPath := *outputDir
- if outPath == "" {
- outPath, err = os.Getwd()
- if err != nil {
- return nil, err
- }
+func makeConfig(logger *log.Logger) *utils.Config {
+ conn, err := api.InitHTTPConnection(*serverAddr, *credentialsFile, *caFile, *fullServerName)
+ if err != nil {
+ logger.Fatalf("Failed to init http connection %v", err)
}
-
- now := time.Now().UTC()
- nowString := fmt.Sprintf("%d-%02d-%02d_%02d-%02d-%02d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
- outFile := filepath.Join(outPath, fmt.Sprintf("%s%s", logFile, nowString))
-
- return os.Create(outFile)
-}
-
-func logStats(stats counters) {
- log.Println("\n************* Begin Stats *************\n")
- var totalErrors int
- if *healthFlow || *allFlows {
- if stats.HealthCheckSuccess {
- log.Println("HealthCheck Succeeded")
- } else {
- totalErrors++
- log.Println("HealthCheck Failed")
- }
+ return &utils.Config{Conn: conn,
+ OrderAllFlows: *allFlows,
+ OrderHealthFlow: *healthFlow,
+ OrderCheckFlow: *checkFlow,
+ OrderOrderFlow: *orderFlow,
}
- if *checkFlow || *allFlows {
- totalErrors += stats.CheckOrderFulfillabilityErrors
- log.Printf("CheckOrderFulfillability Errors: %d/%d", stats.CheckOrderFulfillabilityErrors, stats.CheckOrderFulfillabilityErrors+stats.CheckOrderFulfillabilitySuccess)
- }
- if *orderFlow || *allFlows {
- totalErrors += stats.CreateOrderErrors
- log.Printf("CreateOrder Errors: %d/%d", stats.CreateOrderErrors, stats.CreateOrderErrors+stats.CreateOrderSuccess)
- }
- if *allFlows {
- if stats.ListOrdersSuccess {
- log.Println("ListOrders Succeeded")
- } else {
- totalErrors++
- log.Println("ListOrders Failed")
- }
- }
- if *checkFlow || *orderFlow || *allFlows {
- log.Printf("Total Slots Processed: %d", stats.TotalSlotsProcessed)
- }
-
- log.Println("\n\n\n")
- if totalErrors == 0 {
- log.Println("All Tests Pass!")
- } else {
- log.Printf("Found %d Errors", totalErrors)
- }
-
- log.Println("\n************* End Stats *************\n")
- os.Exit(totalErrors)
}
func main() {
flag.Parse()
- var stats counters
- if !*outputToTerminal {
- // Set up logging before continuing with flows
- f, err := createLogFile()
- if err != nil {
- log.Fatalf("Failed to create log file %v", err)
- }
+ logger, f, err := utils.MakeLogger(*outputToTerminal, *outputDir)
+ if err != nil {
+ log.Fatal("Could not create logger: ", err)
+ }
+ if f != nil {
defer f.Close()
- log.SetOutput(f)
}
- conn, err := api.InitHTTPConnection(*serverAddr, *credentialsFile, *caFile, *fullServerName)
- if err != nil {
- log.Fatalf("Failed to init http connection %v", err)
- }
-
- // HealthCheck Flow
- if *healthFlow || *allFlows {
- stats.HealthCheckSuccess = true
- if err := api.HealthCheck(conn); err != nil {
- stats.HealthCheckSuccess = false
- log.Println(err.Error())
- }
- if !*allFlows && !*checkFlow && !*orderFlow {
- logStats(stats)
- }
- }
-
- if *availabilityFeed == "" || *serviceFeed == "" {
- log.Fatal("please set both availability_feed and service_feed flags")
- }
-
- testInventory, err := utils.MerchantLineItemMapFrom(*serviceFeed, *availabilityFeed, *testSlots)
- if err != nil {
- log.Fatal(err.Error())
- }
-
- // CheckOrderFulfillability Flow
- if *checkFlow || *allFlows {
- utils.LogFlow("CheckOrderFulfillability", "Start")
- for _, value := range testInventory {
- stats.TotalSlotsProcessed += len(value)
+ config := makeConfig(logger)
+ var summary utils.TestSummary
+ var services []*fpb.Service
+ var av []*fpb.Availability
+ if config.OrderAllFlows || config.OrderCheckFlow || config.OrderOrderFlow {
+ if *availabilityFeed == "" || *serviceFeed == "" {
+ log.Fatal("please set both availability_feed and service_feed flags")
}
- i := 0
- for merchantID, lineItems := range testInventory {
- if err = api.CheckOrderFulfillability(merchantID, lineItems, conn); err != nil {
- log.Printf("%s. skipping slots %d-%d/%d", err.Error(), i, i+len(lineItems)-1, stats.TotalSlotsProcessed)
- stats.CheckOrderFulfillabilityErrors += len(lineItems)
- delete(testInventory, merchantID)
- i += len(lineItems)
- continue
- }
- stats.CheckOrderFulfillabilitySuccess += len(lineItems)
- i += len(lineItems)
- }
- utils.LogFlow("CheckOrderFulfillability", "End")
- }
- // CreateOrder Flow.
- var orders []*mpb.Order
- if *orderFlow || *allFlows {
- utils.LogFlow("CreateOrder", "Start")
- if stats.TotalSlotsProcessed == 0 {
- for _, value := range testInventory {
- stats.TotalSlotsProcessed += len(value)
- }
+ services, err = utils.ParseServiceFeed(*serviceFeed)
+ if err != nil {
+ log.Fatal(err.Error())
}
- i := 0
- for merchantID, lineItems := range testInventory {
- order, err := api.CreateOrder(merchantID, lineItems, conn)
- if err != nil {
- log.Printf("%s. skipping slot %d-%d/%d", err.Error(), i, i+len(lineItems)-1, stats.TotalSlotsProcessed)
- stats.CreateOrderErrors += len(lineItems)
- delete(testInventory, merchantID)
- i += len(lineItems)
- continue
- }
- orders = append(orders, order)
- stats.CreateOrderSuccess += len(lineItems)
- i += len(lineItems)
+ av, err = utils.AvailabilityFrom(logger, *availabilityFeed, *testSlots, false)
+ if err != nil {
+ log.Fatal(err.Error())
}
- utils.LogFlow("CreateOrder", "End")
}
- // ListOrders Flow
- if *allFlows {
- utils.LogFlow("ListOrders", "Start")
- stats.ListOrdersSuccess = true
- if err = api.ListOrders(orders, conn); err != nil {
- stats.ListOrdersSuccess = false
- log.Println(err.Error())
- }
- utils.LogFlow("ListOrders", "End")
- }
-
- logStats(stats)
+ order.RunTests(context.Background(), logger, config, av, services, &summary)
}
diff --git a/testclient/waitlistClient.go b/testclient/waitlistClient.go
index 4f673e7..5260a74 100644
--- a/testclient/waitlistClient.go
+++ b/testclient/waitlistClient.go
@@ -1,5 +1,5 @@
/*
-Copyright 2017 Google Inc.
+Copyright 2019 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -16,21 +16,16 @@
package main
import (
+ "context"
"flag"
- "fmt"
"log"
- "os"
- "path/filepath"
- "time"
"github.com/maps-booking-v3/api"
- "github.com/maps-booking-v3/utils"
-
fpb "github.com/maps-booking-v3/feeds"
+ "github.com/maps-booking-v3/utils"
+ "github.com/maps-booking-v3/waitlist"
)
-const logFile = "http_test_client_log_"
-
var (
serverAddr = flag.String("server_addr", "example.com:80", "Your http server's address in the format of host:port")
credentialsFile = flag.String("credentials_file", "", "File containing credentials for your server. Leave blank to bypass authentication. File should have exactly one line of the form 'username:password'.")
@@ -48,206 +43,64 @@
outputToTerminal = flag.Bool("output_to_terminal", false, "Output to terminal rather than a file.")
)
-type counters struct {
- TotalServicesProcessed int
- HealthCheckSuccess bool
- BatchGetWaitEstimatesSuccess int
- BatchGetWaitEstimatesErrors int
- CreateWaitlistEntrySuccess int
- CreateWaitlistEntryErrors int
- GetWaitlistEntrySuccess int
- GetWaitlistEntryErrors int
- DeleteWaitlistEntrySuccess int
- DeleteWaitlistEntryErrors int
-}
-
-// GenerateWaitlistEntries creates a waitlist entry for each provided service.
-func GenerateWaitlistEntries(services []*fpb.Service, stats *counters, conn *api.HTTPConnection) []string {
- log.Println("no previous waitlist entries to use, acquiring new inventory")
- utils.LogFlow("Generate Fresh Entries", "Start")
- defer utils.LogFlow("Generate Fresh Entries", "End")
-
- var out []string
- totalServices := len(services)
- for i, s := range services {
- id, err := api.CreateWaitlistEntry(s, conn)
- if err != nil {
- log.Printf("%s. skipping waitlistEntry %d/%d, serviceID: %s",
- err.Error(), i, totalServices, s.GetServiceId())
- stats.CreateWaitlistEntryErrors++
- continue
- }
- out = append(out, id)
- stats.CreateWaitlistEntrySuccess++
+func makeConfig(logger *log.Logger) *utils.Config {
+ conn, err := api.InitHTTPConnection(*serverAddr, *credentialsFile, *caFile, *fullServerName)
+ if err != nil {
+ logger.Fatalf("Failed to init http connection %v", err)
}
- return out
-}
-
-func createLogFile() (*os.File, error) {
- var err error
- outPath := *outputDir
- if outPath == "" {
- outPath, err = os.Getwd()
- if err != nil {
- return nil, err
- }
+ return &utils.Config{Conn: conn,
+ WaitlistAllFlows: *allFlows,
+ WaitlistHealthFlow: *healthFlow,
+ WaitlistBatchGetWaitEstimatesFlow: *batchGetWaitEstimatesFlow,
+ WaitlistCreateWaitlistEntryFlow: *createWaitlistEntryFlow,
+ WaitlistGetWaitlistEntryFlow: *getWaitlistEntryFlow,
+ WaitlistDeleteWaitlistEntryFlow: *deleteWaitlistEntryFlow,
}
-
- now := time.Now().UTC()
- nowString := fmt.Sprintf("%d-%02d-%02d_%02d-%02d-%02d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
- outFile := filepath.Join(outPath, fmt.Sprintf("%s%s", logFile, nowString))
-
- return os.Create(outFile)
-}
-
-func logStats(stats counters) {
- log.Println("\n************* Begin Stats *************\n")
- var totalErrors int
- if *healthFlow || *allFlows {
- if stats.HealthCheckSuccess {
- log.Println("HealthCheck Succeeded")
- } else {
- totalErrors++
- log.Println("HealthCheck Failed")
- }
- }
- if *batchGetWaitEstimatesFlow || *allFlows {
- totalErrors += stats.BatchGetWaitEstimatesErrors
- log.Printf("BatchGetWaitEstimates Errors: %d/%d", stats.BatchGetWaitEstimatesErrors, stats.BatchGetWaitEstimatesErrors+stats.BatchGetWaitEstimatesSuccess)
- }
- if *createWaitlistEntryFlow || *allFlows {
- totalErrors += stats.CreateWaitlistEntryErrors
- log.Printf("CreateWaitlistEntry Errors: %d/%d", stats.CreateWaitlistEntryErrors, stats.CreateWaitlistEntryErrors+stats.CreateWaitlistEntrySuccess)
- }
- if *getWaitlistEntryFlow || *allFlows {
- totalErrors += stats.GetWaitlistEntryErrors
- log.Printf("GetWaitlistEntry Errors: %d/%d", stats.GetWaitlistEntryErrors, stats.GetWaitlistEntryErrors+stats.GetWaitlistEntrySuccess)
- }
- if *deleteWaitlistEntryFlow || *allFlows {
- totalErrors += stats.DeleteWaitlistEntryErrors
- log.Printf("DeleteWaitlistEntry Errors: %d/%d", stats.DeleteWaitlistEntryErrors, stats.DeleteWaitlistEntryErrors+stats.DeleteWaitlistEntrySuccess)
- }
-
- log.Println("\n\n\n")
- if totalErrors == 0 {
- log.Println("All Tests Pass!")
- } else {
- log.Printf("Found %d Errors", totalErrors)
- }
-
- log.Println("\n************* End Stats *************\n")
- os.Exit(totalErrors)
}
func main() {
flag.Parse()
- var stats counters
- if !*outputToTerminal {
- // Set up logging before continuing with flows
- f, err := createLogFile()
- if err != nil {
- log.Fatalf("Failed to create log file %v", err)
- }
- defer f.Close()
- log.SetOutput(f)
- }
-
- conn, err := api.InitHTTPConnection(*serverAddr, *credentialsFile, *caFile, *fullServerName)
+ logger, f, err := utils.MakeLogger(*outputToTerminal, *outputDir)
if err != nil {
- log.Fatalf("Failed to init http connection %v", err)
+ log.Fatal("Could not create logger: ", err)
}
+ if f != nil {
+ defer f.Close()
+ }
+ config := makeConfig(logger)
+ var summary utils.TestSummary
- // HealthCheck Flow
- if *healthFlow || *allFlows {
- stats.HealthCheckSuccess = true
- if err := api.HealthCheck(conn); err != nil {
- stats.HealthCheckSuccess = false
- log.Println(err.Error())
- }
- if !*allFlows && !*batchGetWaitEstimatesFlow && !*createWaitlistEntryFlow &&
- !*getWaitlistEntryFlow && !*deleteWaitlistEntryFlow {
- logStats(stats)
- }
- }
+ needService := config.WaitlistAllFlows || config.WaitlistBatchGetWaitEstimatesFlow || config.WaitlistCreateWaitlistEntryFlow ||
+ config.WaitlistGetWaitlistEntryFlow || config.WaitlistDeleteWaitlistEntryFlow
// Build services.
- if *serviceFeed == "" {
- log.Fatal("please set service_feed flag if you wish to test additional flows")
- }
-
- var services []*fpb.Service
- services, err = utils.ParseServiceFeed(*serviceFeed)
- if err != nil {
- log.Fatalf("Failed to get services: %v", err.Error())
- }
- // Remove services without waitlist rules.
- waitlistServices := services[:0]
- for _, s := range services {
- if s.GetWaitlistRules() != nil {
- waitlistServices = append(waitlistServices, s)
+ var reducedServices []*fpb.Service
+ if needService {
+ if *serviceFeed == "" {
+ logger.Fatal("please set service_feed flag if you wish to test additional flows")
}
- }
- if len(waitlistServices) == 0 {
- log.Fatal("no services have waitlist rules")
- }
- reducedServices := utils.ReduceServices(waitlistServices, *numTestServices)
- stats.TotalServicesProcessed += len(reducedServices)
-
- // BatchGetWaitEstimates Flow
- if *batchGetWaitEstimatesFlow || *allFlows {
- utils.LogFlow("BatchGetWaitEstimates", "Start")
-
- for i, s := range reducedServices {
- if err = api.BatchGetWaitEstimates(s, conn); err != nil {
- log.Printf("%s. BatchGerWaitEstimates failed for service %d/%d. Service_id:",
- err.Error(), i, stats.TotalServicesProcessed, s.GetServiceId())
- stats.BatchGetWaitEstimatesErrors++
- continue
+ var services []*fpb.Service
+ services, err = utils.ParseServiceFeed(*serviceFeed)
+ if err != nil {
+ logger.Fatalf("Failed to get services: %v", err.Error())
+ }
+ // Remove services without waitlist rules.
+ waitlistServices := services[:0]
+ for _, s := range services {
+ if s.GetWaitlistRules() != nil {
+ waitlistServices = append(waitlistServices, s)
}
- stats.BatchGetWaitEstimatesSuccess++
}
- utils.LogFlow("BatchGetWaitEstimates", "End")
- }
- // CreateWaitlistEntry Flow.
- var ids []string
- if *createWaitlistEntryFlow || *getWaitlistEntryFlow ||
- *deleteWaitlistEntryFlow || *allFlows {
- utils.LogFlow("CreateWaitlistEntry", "Start")
- ids = GenerateWaitlistEntries(reducedServices, &stats, conn)
- utils.LogFlow("CreateWaitlistEntry", "End")
- }
- // GetWaitlistEntry Flow
- if *getWaitlistEntryFlow || *allFlows {
- utils.LogFlow("GetWaitlistEntry", "Start")
- for _, id := range ids {
- if _, err = api.GetWaitlistEntry(id, conn); err != nil {
- log.Printf("%s. get waitlist entry failed for waitlist entry id: %s",
- err.Error(), id)
- stats.GetWaitlistEntryErrors++
- continue
- }
- stats.GetWaitlistEntrySuccess++
+
+ if len(waitlistServices) == 0 {
+ logger.Fatal("no services have waitlist rules")
}
- utils.LogFlow("GetWaitlistEntry", "End")
+ reducedServices = utils.ReduceServices(logger, waitlistServices, *numTestServices)
+ } else {
+ reducedServices = []*fpb.Service{}
}
-
- // DeleteWaitlistentry Flow
- if *deleteWaitlistEntryFlow || *allFlows {
- utils.LogFlow("DeleteWaitlistEntry", "Start")
-
- for _, id := range ids {
- if err = api.DeleteWaitlistEntry(id, conn); err != nil {
- log.Printf("%s. Delete waitlist entry failed for waitlist entry id: %s",
- err.Error(), id)
- stats.DeleteWaitlistEntryErrors++
- continue
- }
- stats.DeleteWaitlistEntrySuccess++
- }
- utils.LogFlow("DeleteWaitlistEntry", "End")
- }
-
- logStats(stats)
+ summary.WaitlistTotalServicesProcessed += len(reducedServices)
+ waitlist.RunTests(context.Background(), logger, config, reducedServices, &summary)
}
diff --git a/utils/structs.go b/utils/structs.go
new file mode 100644
index 0000000..689c35e
--- /dev/null
+++ b/utils/structs.go
@@ -0,0 +1,152 @@
+/*
+Copyright 2019 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
+
+import (
+ "net/http"
+
+ "github.com/golang/protobuf/jsonpb"
+)
+
+// Config holds all test configuration options.
+type Config struct {
+ BookingAllFlows bool
+ BookingHealthFlow bool
+ BookingCheckFlow bool
+ BookingBookFlow bool
+ BookingListFlow bool
+ BookingStatusFlow bool
+ BookingRescheduleFlow bool
+ BookingCancelAllBookings bool
+ BookingUseBal bool
+
+ OrderAllFlows bool
+ OrderHealthFlow bool
+ OrderCheckFlow bool
+ OrderOrderFlow bool
+ OrderListFlow bool
+
+ WaitlistAllFlows bool
+ WaitlistHealthFlow bool
+ WaitlistBatchGetWaitEstimatesFlow bool
+ WaitlistCreateWaitlistEntryFlow bool
+ WaitlistGetWaitlistEntryFlow bool
+ WaitlistDeleteWaitlistEntryFlow bool
+
+ Conn *HTTPConnection
+}
+
+// ShouldTestBookings determines whether any booking tests need to be run.
+func (c *Config) ShouldTestBookings() bool {
+ return c.BookingAllFlows ||
+ c.BookingHealthFlow ||
+ c.BookingCheckFlow ||
+ c.BookingBookFlow ||
+ c.BookingListFlow ||
+ c.BookingStatusFlow ||
+ c.BookingRescheduleFlow ||
+ c.BookingCancelAllBookings
+}
+
+// ShouldTestOrders determines whether any orders tests need to be run.
+func (c *Config) ShouldTestOrders() bool {
+ return c.OrderAllFlows ||
+ c.OrderHealthFlow ||
+ c.OrderCheckFlow ||
+ c.OrderOrderFlow ||
+ c.OrderListFlow
+}
+
+// ShouldTestWaitlists determines whether any waitlist tests need to be run.
+func (c *Config) ShouldTestWaitlists() bool {
+ return c.WaitlistAllFlows ||
+ c.WaitlistHealthFlow ||
+ c.WaitlistBatchGetWaitEstimatesFlow ||
+ c.WaitlistCreateWaitlistEntryFlow ||
+ c.WaitlistGetWaitlistEntryFlow ||
+ c.WaitlistDeleteWaitlistEntryFlow
+}
+
+// TestSummary contains a summary of all test results.
+type TestSummary struct {
+ BookingTotalSlotsProcessed int
+ BookingHealthCheckSuccess bool
+ BookingHealthCheckCompleted bool
+ BookingBatchAvailabilityLookupErrors int
+ BookingBatchAvailabilityLookupSuccess int
+ BookingBatchAvailabilityLookupCompleted bool
+ BookingCheckAvailabilitySuccess int
+ BookingCheckAvailabilityErrors int
+ BookingCheckAvailabilityCompleted bool
+ BookingCreateBookingSuccess int
+ BookingCreateBookingErrors int
+ BookingCreateBookingCompleted bool
+ BookingListBookingsSuccess bool
+ BookingListBookingsCompleted bool
+ BookingGetBookingStatusSuccess int
+ BookingGetBookingStatusErrors int
+ BookingGetBookingStatusCompleted bool
+ BookingCancelBookingsSuccess int
+ BookingCancelBookingsErrors int
+ BookingCancelBookingsCompleted bool
+ BookingReschedulingSuccess bool
+ BookingReschedulingCompleted bool
+
+ OrderTotalSlotsProcessed int
+ OrderHealthCheckSuccess bool
+ OrderHealthCheckCompleted bool
+ OrderCheckOrderFulfillabilitySuccess int
+ OrderCheckOrderFulfillabilityErrors int
+ OrderCheckOrderFulfillabilityCompleted bool
+ OrderCreateOrderSuccess int
+ OrderCreateOrderErrors int
+ OrderCreateOrderCompleted bool
+ OrderListOrdersSuccess bool
+ OrderListOrdersCompleted bool
+
+ WaitlistTotalServicesProcessed int
+ WaitlistHealthCheckSuccess bool
+ WaitlistHealthCheckCompleted bool
+ WaitlistBatchGetWaitEstimatesSuccess int
+ WaitlistBatchGetWaitEstimatesErrors int
+ WaitlistBatchGetWaitEstimatesCompleted bool
+ WaitlistCreateWaitlistEntrySuccess int
+ WaitlistCreateWaitlistEntryErrors int
+ WaitlistCreateWaitlistEntryCompleted bool
+ WaitlistGetWaitlistEntrySuccess int
+ WaitlistGetWaitlistEntryErrors int
+ WaitlistGetWaitlistEntryCompleted bool
+ WaitlistDeleteWaitlistEntrySuccess int
+ WaitlistDeleteWaitlistEntryErrors int
+ WaitlistDeleteWaitlistEntryCompleted bool
+}
+
+// HTTPConnection is a convenience struct for holding connection-related objects.
+type HTTPConnection struct {
+ Client *http.Client
+ Credentials string
+ Marshaler *jsonpb.Marshaler
+ BaseURL string
+}
+
+// GetURL computes the URL for an RPC.
+func (h HTTPConnection) GetURL(rpcName string) string {
+ if rpcName != "" {
+ return h.BaseURL + "/v3/" + rpcName
+ }
+ return h.BaseURL
+}
diff --git a/utils/utils.go b/utils/utils.go
index c5e102f..03984b2 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -1,5 +1,5 @@
/*
-Copyright 2017 Google Inc.
+Copyright 2019 Google Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -18,25 +18,32 @@
package utils
import (
- "crypto/md5"
+ "crypto/sha256"
"errors"
"fmt"
"io/ioutil"
"log"
"math/rand"
+ "os"
"path"
+ "path/filepath"
"sort"
"strings"
+ "time"
"github.com/golang/protobuf/jsonpb"
"github.com/golang/protobuf/proto"
"github.com/google/go-cmp/cmp"
+
fpb "github.com/maps-booking-v3/feeds"
+
mpb "github.com/maps-booking-v3/v3"
- wpb "github.com/maps-booking-v3/waitlist"
+ wpb "github.com/maps-booking-v3/v3waitlist"
)
+const logFile = "http_test_client_log_"
+
// SlotKey is a struct representing a unique service.
type SlotKey struct {
MerchantID string
@@ -45,13 +52,45 @@
RoomID string
}
+func createLogFile(outPath string) (*os.File, error) {
+ var err error
+ if outPath == "" {
+ outPath, err = os.Getwd()
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ now := time.Now().UTC()
+ nowString := fmt.Sprintf("%d-%02d-%02d_%02d-%02d-%02d", now.Year(), now.Month(), now.Day(), now.Hour(), now.Minute(), now.Second())
+ outFile := filepath.Join(outPath, fmt.Sprintf("%s%s", logFile, nowString))
+
+ return os.Create(outFile)
+}
+
+// MakeLogger creates a logger that either logs to terminal or to a file. If outputToTerminal
+// is true, outputDir will be ignored.
+func MakeLogger(outputToTerminal bool, outputDir string) (*log.Logger, *os.File, error) {
+ var logger *log.Logger
+ if outputToTerminal {
+ logger = log.New(os.Stderr, "", log.Flags())
+ return logger, nil, nil
+ }
+ f, err := createLogFile(outputDir)
+ if err != nil {
+ return nil, nil, err
+ }
+ logger = log.New(f, "", log.Flags())
+ return logger, f, nil
+}
+
// 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##########"}, " "))
+func LogFlow(logger *log.Logger, f string, status string) {
+ logger.Println(strings.Join([]string{"\n##########\n", status, f, "Flow", "\n##########"}, " "))
}
// ReduceServices randomly selects and returns numTestServices from the provided services.
-func ReduceServices(allServices []*fpb.Service, numTestServices int) []*fpb.Service {
+func ReduceServices(logger *log.Logger, allServices []*fpb.Service, numTestServices int) []*fpb.Service {
reducedServices := make([]*fpb.Service, 0, numTestServices)
if len(allServices) <= numTestServices {
@@ -61,7 +100,7 @@
reducedServices = append(reducedServices, allServices[n])
}
}
- log.Printf("Selected %d services out of a possible %d", len(reducedServices), len(allServices))
+ logger.Printf("Selected %d services out of a possible %d", len(reducedServices), len(allServices))
return reducedServices
}
@@ -152,13 +191,8 @@
return lineItem
}
-// MerchantLineItemMapFrom attempts to build a collection of LineItems from a service and availability feed.
-func MerchantLineItemMapFrom(serviceFeed, availabilityFeed string, testSlots int) (map[string][]*mpb.LineItem, error) {
- services, err := ParseServiceFeed(serviceFeed)
- if err != nil {
- return nil, err
- }
-
+// BuildLineItemMap creats a collection of LineItems from slices of services and availabilities.
+func BuildLineItemMap(services []*fpb.Service, availabilities []*fpb.Availability) (map[string][]*mpb.LineItem, error) {
feedHasTicketType := false
serviceTicketTypeMap := make(map[string][]*fpb.TicketType)
for _, service := range services {
@@ -178,12 +212,7 @@
}
if !feedHasTicketType {
- return nil, errors.New("no valid ticket types found in service feed, please update service feed and retry")
- }
-
- availabilities, err := AvailabilityFrom(availabilityFeed, testSlots, false)
- if err != nil {
- return nil, err
+ return nil, errors.New("no valid ticket types found in services, please update services and retry")
}
merchantLineItemMap := make(map[string][]*mpb.LineItem)
@@ -198,14 +227,16 @@
merchantLineItemMap[availability.GetMerchantId()] = append(merchantLineItemMap[availability.GetMerchantId()], lineItem)
}
}
-
+ if len(merchantLineItemMap) == 0 {
+ return nil, errors.New("no availability slots with ticket type IDs matching those in the service were found")
+ }
return merchantLineItemMap, nil
}
// 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, forRescheduling bool) ([]*fpb.Availability, error) {
- LogFlow("Parse Input Feed", "Start")
- defer LogFlow("Parse Input Feed", "End")
+func AvailabilityFrom(logger *log.Logger, availabilityFeed string, testSlots int, forRescheduling bool) ([]*fpb.Availability, error) {
+ LogFlow(logger, "Parse Input Feed", "Start")
+ defer LogFlow(logger, "Parse Input Feed", "End")
var feed fpb.AvailabilityFeed
content, err := ioutil.ReadFile(availabilityFeed)
@@ -248,16 +279,40 @@
}
}
}
- log.Printf("Selected %d slots out of a possible %d", len(finalAvailability), len(rawAvailability))
+ logger.Printf("Selected %d slots out of a possible %d", len(finalAvailability), len(rawAvailability))
return finalAvailability, nil
}
+func slotDiff(got, want *mpb.Slot) string {
+ // CONFIRMATION_MODE_SYNCHRONOUS and CONFIRMATION_MODE_UNSPECIFIED should be treated as equivalent.
+ var gotConfirmationMode, wantConfirmationMode mpb.ConfirmationMode
+ var gotComp, wantComp mpb.Slot
+ if got != nil {
+ gotComp = *got
+ gotConfirmationMode = got.GetConfirmationMode()
+ gotComp.ConfirmationMode = mpb.ConfirmationMode_CONFIRMATION_MODE_UNSPECIFIED
+ }
+ if want != nil {
+ wantComp = *want
+ wantConfirmationMode = want.GetConfirmationMode()
+ wantComp.ConfirmationMode = mpb.ConfirmationMode_CONFIRMATION_MODE_UNSPECIFIED
+ }
+ if diff := cmp.Diff(gotComp, wantComp, cmp.Comparer(proto.Equal)); diff != "" {
+ return diff
+ }
+ if (wantConfirmationMode == mpb.ConfirmationMode_CONFIRMATION_MODE_ASYNCHRONOUS) !=
+ (gotConfirmationMode == mpb.ConfirmationMode_CONFIRMATION_MODE_ASYNCHRONOUS) {
+ return cmp.Diff(gotConfirmationMode, wantConfirmationMode)
+ }
+ return ""
+}
+
// ValidateBooking performs granular comparisons between all got and want Bookings.
func ValidateBooking(got, 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 != "" {
+ if diff := slotDiff(got.GetSlot(), want.GetSlot()); diff != "" {
return fmt.Errorf("slots differ (-got +want)\n%s", diff)
}
// UserId is the only required field for the partner to return.
@@ -362,7 +417,7 @@
for _, ticket := range l.GetTickets() {
uID = append(uID, ticket.GetTicketId())
}
- return fmt.Sprintf("%x", md5.Sum([]byte(strings.Join(uID, `|`))))
+ return fmt.Sprintf("%x", sha256.Sum256([]byte(strings.Join(uID, `|`))))
}
// Orders is a convenience type for an Orders array
@@ -500,20 +555,20 @@
}
waitLength := waitEstimate.GetWaitLength()
if waitLength == nil {
- return errors.New("wait estimate not specified")
+ return errors.New("wait length not specified")
}
if waitLength.GetPartiesAheadCount() < 0 {
return errors.New("parties ahead count < 0")
}
estimatedSeatTimeRange := waitLength.GetEstimatedSeatTimeRange()
if estimatedSeatTimeRange == nil {
- return errors.New("estimated seat time range not specified")
+ return nil
}
if estimatedSeatTimeRange.GetStartSeconds() <= 0 {
- return errors.New("start seconds <= 0")
+ return errors.New("estimated seat time range provided and start seconds <= 0")
}
if estimatedSeatTimeRange.GetEndSeconds() <= 0 {
- return errors.New("end seconds <= 0")
+ return errors.New("estimated seat time range provided and end seconds <= 0")
}
return nil
}
diff --git a/waitlist/waitlist.pb.go b/v3waitlist/v3waitlist.pb.go
similarity index 100%
rename from waitlist/waitlist.pb.go
rename to v3waitlist/v3waitlist.pb.go
diff --git a/waitlist/waitlistTests.go b/waitlist/waitlistTests.go
new file mode 100644
index 0000000..d67c6ef
--- /dev/null
+++ b/waitlist/waitlistTests.go
@@ -0,0 +1,165 @@
+/*
+Copyright 2019 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 waitlist contains test logic for waitlist related endpoints.
+package waitlist
+
+import (
+ "context"
+ "log"
+
+ fpb "github.com/maps-booking-v3/feeds"
+ "github.com/maps-booking-v3/api"
+ "github.com/maps-booking-v3/utils"
+)
+
+func logStats(stats *utils.TestSummary, logger *log.Logger, config *utils.Config) {
+ logger.Println("\n************* Begin Stats *************\n")
+ var totalErrors int
+ if config.WaitlistHealthFlow || config.WaitlistAllFlows {
+ if stats.WaitlistHealthCheckSuccess {
+ logger.Println("HealthCheck Succeeded")
+ } else {
+ totalErrors++
+ logger.Println("HealthCheck Failed")
+ }
+ }
+ if config.WaitlistBatchGetWaitEstimatesFlow || config.WaitlistAllFlows {
+ totalErrors += stats.WaitlistBatchGetWaitEstimatesErrors
+ logger.Printf("BatchGetWaitEstimates Errors: %d/%d", stats.WaitlistBatchGetWaitEstimatesErrors, stats.WaitlistBatchGetWaitEstimatesErrors+stats.WaitlistBatchGetWaitEstimatesSuccess)
+ }
+ if config.WaitlistCreateWaitlistEntryFlow || config.WaitlistAllFlows {
+ totalErrors += stats.WaitlistCreateWaitlistEntryErrors
+ logger.Printf("CreateWaitlistEntry Errors: %d/%d", stats.WaitlistCreateWaitlistEntryErrors, stats.WaitlistCreateWaitlistEntryErrors+stats.WaitlistCreateWaitlistEntrySuccess)
+ }
+ if config.WaitlistGetWaitlistEntryFlow || config.WaitlistAllFlows {
+ totalErrors += stats.WaitlistGetWaitlistEntryErrors
+ logger.Printf("GetWaitlistEntry Errors: %d/%d", stats.WaitlistGetWaitlistEntryErrors, stats.WaitlistGetWaitlistEntryErrors+stats.WaitlistGetWaitlistEntrySuccess)
+ }
+ if config.WaitlistDeleteWaitlistEntryFlow || config.WaitlistAllFlows {
+ totalErrors += stats.WaitlistDeleteWaitlistEntryErrors
+ logger.Printf("DeleteWaitlistEntry Errors: %d/%d", stats.WaitlistDeleteWaitlistEntryErrors, stats.WaitlistDeleteWaitlistEntryErrors+stats.WaitlistDeleteWaitlistEntrySuccess)
+ }
+
+ logger.Println("\n\n\n")
+ if totalErrors == 0 {
+ logger.Println("All Tests Pass!")
+ } else {
+ logger.Printf("Found %d Errors", totalErrors)
+ }
+
+ logger.Println("\n************* End Stats *************\n")
+}
+
+// generateWaitlistEntries creates a waitlist entry for each provided service.
+func generateWaitlistEntries(ctx context.Context, logger *log.Logger, services []*fpb.Service, stats *utils.TestSummary, conn *utils.HTTPConnection) []string {
+ logger.Println("no previous waitlist entries to use, acquiring new inventory")
+ utils.LogFlow(logger, "Generate Fresh Entries", "Start")
+ defer utils.LogFlow(logger, "Generate Fresh Entries", "End")
+
+ var out []string
+ totalServices := len(services)
+ for i, s := range services {
+ id, err := api.CreateWaitlistEntry(ctx, logger, s, conn)
+ if err != nil {
+ logger.Printf("%s. skipping waitlistEntry %d/%d, serviceID: %s",
+ err.Error(), i, totalServices, s.GetServiceId())
+ stats.WaitlistCreateWaitlistEntryErrors++
+ continue
+ }
+ out = append(out, id)
+ stats.WaitlistCreateWaitlistEntrySuccess++
+ }
+ return out
+}
+
+// RunTests runs waitlist tests.
+func RunTests(ctx context.Context, logger *log.Logger, config *utils.Config, reducedServices []*fpb.Service, stats *utils.TestSummary) {
+ // HealthCheck Flow
+ conn := config.Conn
+ if config.WaitlistHealthFlow || config.WaitlistAllFlows {
+ stats.WaitlistHealthCheckSuccess = true
+ if err := api.HealthCheck(ctx, logger, conn); err != nil {
+ stats.WaitlistHealthCheckSuccess = false
+ logger.Println(err.Error())
+ }
+ stats.WaitlistHealthCheckCompleted = true
+ if !config.WaitlistAllFlows && !config.WaitlistBatchGetWaitEstimatesFlow && !config.WaitlistCreateWaitlistEntryFlow &&
+ !config.WaitlistGetWaitlistEntryFlow && !config.WaitlistDeleteWaitlistEntryFlow {
+ logStats(stats, logger, config)
+ return
+ }
+ }
+
+ // BatchGetWaitEstimates Flow
+ if config.WaitlistBatchGetWaitEstimatesFlow || config.WaitlistAllFlows {
+ utils.LogFlow(logger, "BatchGetWaitEstimates", "Start")
+
+ for i, s := range reducedServices {
+ if err := api.BatchGetWaitEstimates(ctx, logger, s, conn); err != nil {
+ logger.Printf("%s. BatchGerWaitEstimates failed for service %d/%d. Service_id: %s",
+ err.Error(), i, stats.WaitlistTotalServicesProcessed, s.GetServiceId())
+ stats.WaitlistBatchGetWaitEstimatesErrors++
+ continue
+ }
+ stats.WaitlistBatchGetWaitEstimatesSuccess++
+ }
+ stats.WaitlistBatchGetWaitEstimatesCompleted = true
+ utils.LogFlow(logger, "BatchGetWaitEstimates", "End")
+ }
+ // CreateWaitlistEntry Flow.
+ var ids []string
+ if config.WaitlistCreateWaitlistEntryFlow || config.WaitlistGetWaitlistEntryFlow ||
+ config.WaitlistDeleteWaitlistEntryFlow || config.WaitlistAllFlows {
+ utils.LogFlow(logger, "CreateWaitlistEntry", "Start")
+ ids = generateWaitlistEntries(ctx, logger, reducedServices, stats, conn)
+ stats.WaitlistCreateWaitlistEntryCompleted = true
+ utils.LogFlow(logger, "CreateWaitlistEntry", "End")
+ }
+ // GetWaitlistEntry Flow
+ if config.WaitlistGetWaitlistEntryFlow || config.WaitlistAllFlows {
+ utils.LogFlow(logger, "GetWaitlistEntry", "Start")
+ for _, id := range ids {
+ if _, err := api.GetWaitlistEntry(ctx, logger, id, conn); err != nil {
+ logger.Printf("%s. get waitlist entry failed for waitlist entry id: %s",
+ err.Error(), id)
+ stats.WaitlistGetWaitlistEntryErrors++
+ continue
+ }
+ stats.WaitlistGetWaitlistEntrySuccess++
+ }
+ stats.WaitlistGetWaitlistEntryCompleted = true
+ utils.LogFlow(logger, "GetWaitlistEntry", "End")
+ }
+
+ // DeleteWaitlistentry Flow
+ if config.WaitlistDeleteWaitlistEntryFlow || config.WaitlistAllFlows {
+ utils.LogFlow(logger, "DeleteWaitlistEntry", "Start")
+
+ for _, id := range ids {
+ if err := api.DeleteWaitlistEntry(ctx, logger, id, conn); err != nil {
+ logger.Printf("%s. Delete waitlist entry failed for waitlist entry id: %s",
+ err.Error(), id)
+ stats.WaitlistDeleteWaitlistEntryErrors++
+ continue
+ }
+ stats.WaitlistDeleteWaitlistEntrySuccess++
+ }
+ stats.WaitlistDeleteWaitlistEntryCompleted = true
+ utils.LogFlow(logger, "DeleteWaitlistEntry", "End")
+ }
+ logStats(stats, logger, config)
+}