From 6eb8c2be76fb8a9cf6fe7d8514bccd4c688a0980 Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Sat, 9 Jun 2018 14:01:55 -0600 Subject: [PATCH 01/21] split into different offerings packages --- README.md | 8 ++++++-- client/README.md | 1 + cloud/README.md | 1 + engine/README.md | 1 + {hotelws => engine/hotelws}/hotel_avail.go | 4 ++-- {hotelws => engine/hotelws}/hotel_avail_test.go | 4 ++-- {hotelws => engine/hotelws}/hotel_prop_desc.go | 4 ++-- {hotelws => engine/hotelws}/hotel_prop_desc_test.go | 2 +- {hotelws => engine/hotelws}/hotel_rate_desc.go | 4 ++-- {hotelws => engine/hotelws}/hotel_rate_desc_test.go | 2 +- {hotelws => engine/hotelws}/hotel_res.go | 4 ++-- {hotelws => engine/hotelws}/hotel_res_test.go | 0 {hotelws => engine/hotelws}/hotel_search_criteria.go | 2 +- {hotelws => engine/hotelws}/hotelws.go | 2 +- {hotelws => engine/hotelws}/hotelws_helper_test.go | 0 {hotelws => engine/hotelws}/test_data/coverage.out | 0 {hotelws => engine/hotelws}/test_data/hotel_avail_rq.xml | 0 {hotelws => engine/hotelws}/test_data/hotel_avail_rs.xml | 0 .../hotelws}/test_data/hotel_avail_rs_biz_logic_error.xml | 0 .../hotelws}/test_data/hotel_property_desc_rq.xml | 0 .../hotelws}/test_data/hotel_property_desc_rs.xml | 0 .../hotelws}/test_data/hotel_rate_desc_rq.xml | 0 .../hotelws}/test_data/hotel_rate_desc_rs.xml | 0 .../test_data/hotel_res_direct_connect_not_proc_rs.xml | 0 .../hotelws}/test_data/hotel_res_format_err_rq.xml | 0 .../hotelws}/test_data/hotel_res_not_proc_format_rs.xml | 0 {hotelws => engine/hotelws}/test_data/hotel_res_rq.xml | 0 {hotelws => engine/hotelws}/test_data/hotel_res_rs.xml | 0 .../hotelws}/test_data/hotel_res_usg_invalid_rs.xml | 0 {itin => engine/itin}/pnr_details.go | 4 ++-- {itin => engine/itin}/pnr_details_test.go | 2 +- {itin => engine/itin}/pnr_helper_test.go | 0 {itin => engine/itin}/test_data/psngr_details_rq.xml | 0 {itin => engine/itin}/test_data/psngr_details_rs.xml | 0 .../test_data/psngr_details_warning_business_logic.xml | 0 {sbrerr => engine/sbrerr}/sbrerr.go | 0 {sbrerr => engine/sbrerr}/sbrerr_test.go | 0 {srvc => engine/srvc}/session_pool.go | 1 - {srvc => engine/srvc}/session_pool_test.go | 0 {srvc => engine/srvc}/soap_base.go | 2 +- {srvc => engine/srvc}/soap_base_test.go | 2 +- .../srvc}/test_data/close_session_invalid_token_resp.xml | 0 {srvc => engine/srvc}/test_data/close_session_req.xml | 0 .../srvc}/test_data/close_session_success_resp.xml | 0 {srvc => engine/srvc}/test_data/coverage.out | 0 .../srvc}/test_data/create_session_success_req.xml | 0 .../srvc}/test_data/create_session_success_resp.xml | 0 .../srvc}/test_data/session_create_auth_fail_resp.xml | 0 .../test_data/validate_session_invalid_token_resp.xml | 0 .../srvc}/test_data/validate_session_success_req.xml | 0 .../srvc}/test_data/validate_session_success_resp.xml | 0 51 files changed, 28 insertions(+), 22 deletions(-) create mode 100644 client/README.md create mode 100644 cloud/README.md create mode 100644 engine/README.md rename {hotelws => engine/hotelws}/hotel_avail.go (98%) rename {hotelws => engine/hotelws}/hotel_avail_test.go (99%) rename {hotelws => engine/hotelws}/hotel_prop_desc.go (98%) rename {hotelws => engine/hotelws}/hotel_prop_desc_test.go (99%) rename {hotelws => engine/hotelws}/hotel_rate_desc.go (98%) rename {hotelws => engine/hotelws}/hotel_rate_desc_test.go (99%) rename {hotelws => engine/hotelws}/hotel_res.go (99%) rename {hotelws => engine/hotelws}/hotel_res_test.go (100%) rename {hotelws => engine/hotelws}/hotel_search_criteria.go (98%) rename {hotelws => engine/hotelws}/hotelws.go (99%) rename {hotelws => engine/hotelws}/hotelws_helper_test.go (100%) rename {hotelws => engine/hotelws}/test_data/coverage.out (100%) rename {hotelws => engine/hotelws}/test_data/hotel_avail_rq.xml (100%) rename {hotelws => engine/hotelws}/test_data/hotel_avail_rs.xml (100%) rename {hotelws => engine/hotelws}/test_data/hotel_avail_rs_biz_logic_error.xml (100%) rename {hotelws => engine/hotelws}/test_data/hotel_property_desc_rq.xml (100%) rename {hotelws => engine/hotelws}/test_data/hotel_property_desc_rs.xml (100%) rename {hotelws => engine/hotelws}/test_data/hotel_rate_desc_rq.xml (100%) rename {hotelws => engine/hotelws}/test_data/hotel_rate_desc_rs.xml (100%) rename {hotelws => engine/hotelws}/test_data/hotel_res_direct_connect_not_proc_rs.xml (100%) rename {hotelws => engine/hotelws}/test_data/hotel_res_format_err_rq.xml (100%) rename {hotelws => engine/hotelws}/test_data/hotel_res_not_proc_format_rs.xml (100%) rename {hotelws => engine/hotelws}/test_data/hotel_res_rq.xml (100%) rename {hotelws => engine/hotelws}/test_data/hotel_res_rs.xml (100%) rename {hotelws => engine/hotelws}/test_data/hotel_res_usg_invalid_rs.xml (100%) rename {itin => engine/itin}/pnr_details.go (99%) rename {itin => engine/itin}/pnr_details_test.go (99%) rename {itin => engine/itin}/pnr_helper_test.go (100%) rename {itin => engine/itin}/test_data/psngr_details_rq.xml (100%) rename {itin => engine/itin}/test_data/psngr_details_rs.xml (100%) rename {itin => engine/itin}/test_data/psngr_details_warning_business_logic.xml (100%) rename {sbrerr => engine/sbrerr}/sbrerr.go (100%) rename {sbrerr => engine/sbrerr}/sbrerr_test.go (100%) rename {srvc => engine/srvc}/session_pool.go (99%) rename {srvc => engine/srvc}/session_pool_test.go (100%) rename {srvc => engine/srvc}/soap_base.go (99%) rename {srvc => engine/srvc}/soap_base_test.go (99%) rename {srvc => engine/srvc}/test_data/close_session_invalid_token_resp.xml (100%) rename {srvc => engine/srvc}/test_data/close_session_req.xml (100%) rename {srvc => engine/srvc}/test_data/close_session_success_resp.xml (100%) rename {srvc => engine/srvc}/test_data/coverage.out (100%) rename {srvc => engine/srvc}/test_data/create_session_success_req.xml (100%) rename {srvc => engine/srvc}/test_data/create_session_success_resp.xml (100%) rename {srvc => engine/srvc}/test_data/session_create_auth_fail_resp.xml (100%) rename {srvc => engine/srvc}/test_data/validate_session_invalid_token_resp.xml (100%) rename {srvc => engine/srvc}/test_data/validate_session_success_req.xml (100%) rename {srvc => engine/srvc}/test_data/validate_session_success_resp.xml (100%) diff --git a/README.md b/README.md index 39e940b..88c85c7 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,11 @@ #sbrweb -Connects to Sabre Web services endpoints, both SOAP and REST. +Connects to Sabre Web services endpoints, both SOAP and REST. It is built around three offerings: -## Big'n +1. BookingEngine (BEN): `engine` +1. BookingClient (bClient): `client` +1. BookingCloud (bCloud): `cloud` + +## Engine This is a large project with organized subprojects. To get a sense of the number of lines of code, tests, and other files you can `wc -l 'find sbrweb/hotelws -type f'`. 1. `srvc` (service) diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000..516b281 --- /dev/null +++ b/client/README.md @@ -0,0 +1 @@ +# Client (BookingClient, bClient) \ No newline at end of file diff --git a/cloud/README.md b/cloud/README.md new file mode 100644 index 0000000..216b095 --- /dev/null +++ b/cloud/README.md @@ -0,0 +1 @@ +# Cloud (BookingCloud, bCloud) \ No newline at end of file diff --git a/engine/README.md b/engine/README.md new file mode 100644 index 0000000..e60f26f --- /dev/null +++ b/engine/README.md @@ -0,0 +1 @@ +# Engine (BookingENgine, BEN) \ No newline at end of file diff --git a/hotelws/hotel_avail.go b/engine/hotelws/hotel_avail.go similarity index 98% rename from hotelws/hotel_avail.go rename to engine/hotelws/hotel_avail.go index 88484cb..9f20fd3 100644 --- a/hotelws/hotel_avail.go +++ b/engine/hotelws/hotel_avail.go @@ -6,8 +6,8 @@ import ( "io" "net/http" - "github.com/ailgroup/sbrweb/sbrerr" - "github.com/ailgroup/sbrweb/srvc" + "github.com/ailgroup/sbrweb/engine/sbrerr" + "github.com/ailgroup/sbrweb/engine/srvc" ) // HotelAvailRequest for soap package on OTA_HotelAvailRQ service diff --git a/hotelws/hotel_avail_test.go b/engine/hotelws/hotel_avail_test.go similarity index 99% rename from hotelws/hotel_avail_test.go rename to engine/hotelws/hotel_avail_test.go index 73af5ca..b72b638 100644 --- a/hotelws/hotel_avail_test.go +++ b/engine/hotelws/hotel_avail_test.go @@ -6,8 +6,8 @@ import ( "strings" "testing" - "github.com/ailgroup/sbrweb/sbrerr" - "github.com/ailgroup/sbrweb/srvc" + "github.com/ailgroup/sbrweb/engine/sbrerr" + "github.com/ailgroup/sbrweb/engine/srvc" ) func TestAddressSearchReturnError(t *testing.T) { diff --git a/hotelws/hotel_prop_desc.go b/engine/hotelws/hotel_prop_desc.go similarity index 98% rename from hotelws/hotel_prop_desc.go rename to engine/hotelws/hotel_prop_desc.go index c1dd8a9..c1d87bc 100644 --- a/hotelws/hotel_prop_desc.go +++ b/engine/hotelws/hotel_prop_desc.go @@ -6,8 +6,8 @@ import ( "io" "net/http" - "github.com/ailgroup/sbrweb/sbrerr" - "github.com/ailgroup/sbrweb/srvc" + "github.com/ailgroup/sbrweb/engine/sbrerr" + "github.com/ailgroup/sbrweb/engine/srvc" ) // HotelPropDescRequest for soap package on HotelPropertyDescriptionRQ service diff --git a/hotelws/hotel_prop_desc_test.go b/engine/hotelws/hotel_prop_desc_test.go similarity index 99% rename from hotelws/hotel_prop_desc_test.go rename to engine/hotelws/hotel_prop_desc_test.go index 586e125..2af67e0 100644 --- a/hotelws/hotel_prop_desc_test.go +++ b/engine/hotelws/hotel_prop_desc_test.go @@ -4,7 +4,7 @@ import ( "encoding/xml" "testing" - "github.com/ailgroup/sbrweb/sbrerr" + "github.com/ailgroup/sbrweb/engine/sbrerr" ) func TestPropDescValidReqCityCode(t *testing.T) { diff --git a/hotelws/hotel_rate_desc.go b/engine/hotelws/hotel_rate_desc.go similarity index 98% rename from hotelws/hotel_rate_desc.go rename to engine/hotelws/hotel_rate_desc.go index 2327ef5..d13674c 100644 --- a/hotelws/hotel_rate_desc.go +++ b/engine/hotelws/hotel_rate_desc.go @@ -6,8 +6,8 @@ import ( "io" "net/http" - "github.com/ailgroup/sbrweb/sbrerr" - "github.com/ailgroup/sbrweb/srvc" + "github.com/ailgroup/sbrweb/engine/sbrerr" + "github.com/ailgroup/sbrweb/engine/srvc" ) // HotelRateDescRequest for soap package on HotelRateDescRequest service diff --git a/hotelws/hotel_rate_desc_test.go b/engine/hotelws/hotel_rate_desc_test.go similarity index 99% rename from hotelws/hotel_rate_desc_test.go rename to engine/hotelws/hotel_rate_desc_test.go index 60ebab7..c742d5c 100644 --- a/hotelws/hotel_rate_desc_test.go +++ b/engine/hotelws/hotel_rate_desc_test.go @@ -4,7 +4,7 @@ import ( "encoding/xml" "testing" - "github.com/ailgroup/sbrweb/sbrerr" + "github.com/ailgroup/sbrweb/engine/sbrerr" ) func TestHotelRateDescMarshal(t *testing.T) { diff --git a/hotelws/hotel_res.go b/engine/hotelws/hotel_res.go similarity index 99% rename from hotelws/hotel_res.go rename to engine/hotelws/hotel_res.go index fbb40b5..cae98d3 100644 --- a/hotelws/hotel_res.go +++ b/engine/hotelws/hotel_res.go @@ -7,8 +7,8 @@ import ( "io" "net/http" - "github.com/ailgroup/sbrweb/sbrerr" - "github.com/ailgroup/sbrweb/srvc" + "github.com/ailgroup/sbrweb/engine/sbrerr" + "github.com/ailgroup/sbrweb/engine/srvc" ) /* diff --git a/hotelws/hotel_res_test.go b/engine/hotelws/hotel_res_test.go similarity index 100% rename from hotelws/hotel_res_test.go rename to engine/hotelws/hotel_res_test.go diff --git a/hotelws/hotel_search_criteria.go b/engine/hotelws/hotel_search_criteria.go similarity index 98% rename from hotelws/hotel_search_criteria.go rename to engine/hotelws/hotel_search_criteria.go index b32440e..8da44b9 100644 --- a/hotelws/hotel_search_criteria.go +++ b/engine/hotelws/hotel_search_criteria.go @@ -8,7 +8,7 @@ import ( "fmt" "strings" - "github.com/ailgroup/sbrweb/sbrerr" + "github.com/ailgroup/sbrweb/engine/sbrerr" ) // SetRateParams helper to create a slice of rate plans to append on a an Avail Segement diff --git a/hotelws/hotelws.go b/engine/hotelws/hotelws.go similarity index 99% rename from hotelws/hotelws.go rename to engine/hotelws/hotelws.go index d1f3eeb..13b7bb7 100644 --- a/hotelws/hotelws.go +++ b/engine/hotelws/hotelws.go @@ -27,7 +27,7 @@ import ( "time" "unicode" - "github.com/ailgroup/sbrweb/sbrerr" + "github.com/ailgroup/sbrweb/engine/sbrerr" ) const ( diff --git a/hotelws/hotelws_helper_test.go b/engine/hotelws/hotelws_helper_test.go similarity index 100% rename from hotelws/hotelws_helper_test.go rename to engine/hotelws/hotelws_helper_test.go diff --git a/hotelws/test_data/coverage.out b/engine/hotelws/test_data/coverage.out similarity index 100% rename from hotelws/test_data/coverage.out rename to engine/hotelws/test_data/coverage.out diff --git a/hotelws/test_data/hotel_avail_rq.xml b/engine/hotelws/test_data/hotel_avail_rq.xml similarity index 100% rename from hotelws/test_data/hotel_avail_rq.xml rename to engine/hotelws/test_data/hotel_avail_rq.xml diff --git a/hotelws/test_data/hotel_avail_rs.xml b/engine/hotelws/test_data/hotel_avail_rs.xml similarity index 100% rename from hotelws/test_data/hotel_avail_rs.xml rename to engine/hotelws/test_data/hotel_avail_rs.xml diff --git a/hotelws/test_data/hotel_avail_rs_biz_logic_error.xml b/engine/hotelws/test_data/hotel_avail_rs_biz_logic_error.xml similarity index 100% rename from hotelws/test_data/hotel_avail_rs_biz_logic_error.xml rename to engine/hotelws/test_data/hotel_avail_rs_biz_logic_error.xml diff --git a/hotelws/test_data/hotel_property_desc_rq.xml b/engine/hotelws/test_data/hotel_property_desc_rq.xml similarity index 100% rename from hotelws/test_data/hotel_property_desc_rq.xml rename to engine/hotelws/test_data/hotel_property_desc_rq.xml diff --git a/hotelws/test_data/hotel_property_desc_rs.xml b/engine/hotelws/test_data/hotel_property_desc_rs.xml similarity index 100% rename from hotelws/test_data/hotel_property_desc_rs.xml rename to engine/hotelws/test_data/hotel_property_desc_rs.xml diff --git a/hotelws/test_data/hotel_rate_desc_rq.xml b/engine/hotelws/test_data/hotel_rate_desc_rq.xml similarity index 100% rename from hotelws/test_data/hotel_rate_desc_rq.xml rename to engine/hotelws/test_data/hotel_rate_desc_rq.xml diff --git a/hotelws/test_data/hotel_rate_desc_rs.xml b/engine/hotelws/test_data/hotel_rate_desc_rs.xml similarity index 100% rename from hotelws/test_data/hotel_rate_desc_rs.xml rename to engine/hotelws/test_data/hotel_rate_desc_rs.xml diff --git a/hotelws/test_data/hotel_res_direct_connect_not_proc_rs.xml b/engine/hotelws/test_data/hotel_res_direct_connect_not_proc_rs.xml similarity index 100% rename from hotelws/test_data/hotel_res_direct_connect_not_proc_rs.xml rename to engine/hotelws/test_data/hotel_res_direct_connect_not_proc_rs.xml diff --git a/hotelws/test_data/hotel_res_format_err_rq.xml b/engine/hotelws/test_data/hotel_res_format_err_rq.xml similarity index 100% rename from hotelws/test_data/hotel_res_format_err_rq.xml rename to engine/hotelws/test_data/hotel_res_format_err_rq.xml diff --git a/hotelws/test_data/hotel_res_not_proc_format_rs.xml b/engine/hotelws/test_data/hotel_res_not_proc_format_rs.xml similarity index 100% rename from hotelws/test_data/hotel_res_not_proc_format_rs.xml rename to engine/hotelws/test_data/hotel_res_not_proc_format_rs.xml diff --git a/hotelws/test_data/hotel_res_rq.xml b/engine/hotelws/test_data/hotel_res_rq.xml similarity index 100% rename from hotelws/test_data/hotel_res_rq.xml rename to engine/hotelws/test_data/hotel_res_rq.xml diff --git a/hotelws/test_data/hotel_res_rs.xml b/engine/hotelws/test_data/hotel_res_rs.xml similarity index 100% rename from hotelws/test_data/hotel_res_rs.xml rename to engine/hotelws/test_data/hotel_res_rs.xml diff --git a/hotelws/test_data/hotel_res_usg_invalid_rs.xml b/engine/hotelws/test_data/hotel_res_usg_invalid_rs.xml similarity index 100% rename from hotelws/test_data/hotel_res_usg_invalid_rs.xml rename to engine/hotelws/test_data/hotel_res_usg_invalid_rs.xml diff --git a/itin/pnr_details.go b/engine/itin/pnr_details.go similarity index 99% rename from itin/pnr_details.go rename to engine/itin/pnr_details.go index 24b1bbe..4cf1a60 100644 --- a/itin/pnr_details.go +++ b/engine/itin/pnr_details.go @@ -7,8 +7,8 @@ import ( "io" "net/http" - "github.com/ailgroup/sbrweb/sbrerr" - "github.com/ailgroup/sbrweb/srvc" + "github.com/ailgroup/sbrweb/engine/sbrerr" + "github.com/ailgroup/sbrweb/engine/srvc" ) /* PNRDetailsRequest root level struct for dealing with an PNR. Taken from Sabre docs: diff --git a/itin/pnr_details_test.go b/engine/itin/pnr_details_test.go similarity index 99% rename from itin/pnr_details_test.go rename to engine/itin/pnr_details_test.go index b50018a..3c17cab 100644 --- a/itin/pnr_details_test.go +++ b/engine/itin/pnr_details_test.go @@ -5,7 +5,7 @@ import ( "strings" "testing" - "github.com/ailgroup/sbrweb/sbrerr" + "github.com/ailgroup/sbrweb/engine/sbrerr" ) func TestPNRSet(t *testing.T) { diff --git a/itin/pnr_helper_test.go b/engine/itin/pnr_helper_test.go similarity index 100% rename from itin/pnr_helper_test.go rename to engine/itin/pnr_helper_test.go diff --git a/itin/test_data/psngr_details_rq.xml b/engine/itin/test_data/psngr_details_rq.xml similarity index 100% rename from itin/test_data/psngr_details_rq.xml rename to engine/itin/test_data/psngr_details_rq.xml diff --git a/itin/test_data/psngr_details_rs.xml b/engine/itin/test_data/psngr_details_rs.xml similarity index 100% rename from itin/test_data/psngr_details_rs.xml rename to engine/itin/test_data/psngr_details_rs.xml diff --git a/itin/test_data/psngr_details_warning_business_logic.xml b/engine/itin/test_data/psngr_details_warning_business_logic.xml similarity index 100% rename from itin/test_data/psngr_details_warning_business_logic.xml rename to engine/itin/test_data/psngr_details_warning_business_logic.xml diff --git a/sbrerr/sbrerr.go b/engine/sbrerr/sbrerr.go similarity index 100% rename from sbrerr/sbrerr.go rename to engine/sbrerr/sbrerr.go diff --git a/sbrerr/sbrerr_test.go b/engine/sbrerr/sbrerr_test.go similarity index 100% rename from sbrerr/sbrerr_test.go rename to engine/sbrerr/sbrerr_test.go diff --git a/srvc/session_pool.go b/engine/srvc/session_pool.go similarity index 99% rename from srvc/session_pool.go rename to engine/srvc/session_pool.go index fb44919..6401ffe 100644 --- a/srvc/session_pool.go +++ b/engine/srvc/session_pool.go @@ -4,7 +4,6 @@ import ( "fmt" "math/rand" "time" - //"github.com/pkg/profile" ) // Session holds sabre session data and other fields for handling in the SessionPool diff --git a/srvc/session_pool_test.go b/engine/srvc/session_pool_test.go similarity index 100% rename from srvc/session_pool_test.go rename to engine/srvc/session_pool_test.go diff --git a/srvc/soap_base.go b/engine/srvc/soap_base.go similarity index 99% rename from srvc/soap_base.go rename to engine/srvc/soap_base.go index 8abfc01..b14eb1a 100644 --- a/srvc/soap_base.go +++ b/engine/srvc/soap_base.go @@ -12,7 +12,7 @@ import ( "regexp" "time" - "github.com/ailgroup/sbrweb/sbrerr" + "github.com/ailgroup/sbrweb/engine/sbrerr" ) const ( diff --git a/srvc/soap_base_test.go b/engine/srvc/soap_base_test.go similarity index 99% rename from srvc/soap_base_test.go rename to engine/srvc/soap_base_test.go index 8c790d3..b949be7 100644 --- a/srvc/soap_base_test.go +++ b/engine/srvc/soap_base_test.go @@ -18,7 +18,7 @@ import ( "regexp" "testing" - "github.com/ailgroup/sbrweb/sbrerr" + "github.com/ailgroup/sbrweb/engine/sbrerr" ) var ( diff --git a/srvc/test_data/close_session_invalid_token_resp.xml b/engine/srvc/test_data/close_session_invalid_token_resp.xml similarity index 100% rename from srvc/test_data/close_session_invalid_token_resp.xml rename to engine/srvc/test_data/close_session_invalid_token_resp.xml diff --git a/srvc/test_data/close_session_req.xml b/engine/srvc/test_data/close_session_req.xml similarity index 100% rename from srvc/test_data/close_session_req.xml rename to engine/srvc/test_data/close_session_req.xml diff --git a/srvc/test_data/close_session_success_resp.xml b/engine/srvc/test_data/close_session_success_resp.xml similarity index 100% rename from srvc/test_data/close_session_success_resp.xml rename to engine/srvc/test_data/close_session_success_resp.xml diff --git a/srvc/test_data/coverage.out b/engine/srvc/test_data/coverage.out similarity index 100% rename from srvc/test_data/coverage.out rename to engine/srvc/test_data/coverage.out diff --git a/srvc/test_data/create_session_success_req.xml b/engine/srvc/test_data/create_session_success_req.xml similarity index 100% rename from srvc/test_data/create_session_success_req.xml rename to engine/srvc/test_data/create_session_success_req.xml diff --git a/srvc/test_data/create_session_success_resp.xml b/engine/srvc/test_data/create_session_success_resp.xml similarity index 100% rename from srvc/test_data/create_session_success_resp.xml rename to engine/srvc/test_data/create_session_success_resp.xml diff --git a/srvc/test_data/session_create_auth_fail_resp.xml b/engine/srvc/test_data/session_create_auth_fail_resp.xml similarity index 100% rename from srvc/test_data/session_create_auth_fail_resp.xml rename to engine/srvc/test_data/session_create_auth_fail_resp.xml diff --git a/srvc/test_data/validate_session_invalid_token_resp.xml b/engine/srvc/test_data/validate_session_invalid_token_resp.xml similarity index 100% rename from srvc/test_data/validate_session_invalid_token_resp.xml rename to engine/srvc/test_data/validate_session_invalid_token_resp.xml diff --git a/srvc/test_data/validate_session_success_req.xml b/engine/srvc/test_data/validate_session_success_req.xml similarity index 100% rename from srvc/test_data/validate_session_success_req.xml rename to engine/srvc/test_data/validate_session_success_req.xml diff --git a/srvc/test_data/validate_session_success_resp.xml b/engine/srvc/test_data/validate_session_success_resp.xml similarity index 100% rename from srvc/test_data/validate_session_success_resp.xml rename to engine/srvc/test_data/validate_session_success_resp.xml From 4764d375984c43b7dd9d5a5cc4fa61d531e65f8b Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Sat, 9 Jun 2018 20:01:35 -0600 Subject: [PATCH 02/21] experiemnt wtih booking client --- client/client.go | 18 ++++++++++++++++++ client/common/common.go | 20 ++++++++++++++++++++ client/common/datastore/datastore.go | 26 ++++++++++++++++++++++++++ client/common/datastore/memcache.go | 16 ++++++++++++++++ client/handlers/hotel_avail.go | 20 ++++++++++++++++++++ 5 files changed, 100 insertions(+) create mode 100644 client/client.go create mode 100644 client/common/common.go create mode 100644 client/common/datastore/datastore.go create mode 100644 client/common/datastore/memcache.go create mode 100644 client/handlers/hotel_avail.go diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..05e8d5f --- /dev/null +++ b/client/client.go @@ -0,0 +1,18 @@ +package main + +import ( + "github.com/ailgroup/sbrweb/client/common" + "github.com/ailgroup/sbrweb/client/handlers" + "github.com/gorilla/mux" +) + +// registerRoutes is responsible for regisetering the server-side request handlers +func registerRoutes(env *common.Env, r *mux.Router) { + r.Handle("/avail", handlers.HotelAvailHandler(env)).Methods("GET") +} + +func main() { + env := common.Env{} + r := mux.NewRouter() + registerRoutes(&env, r) +} diff --git a/client/common/common.go b/client/common/common.go new file mode 100644 index 0000000..b022050 --- /dev/null +++ b/client/common/common.go @@ -0,0 +1,20 @@ +package common + +//"github.com/ailgroup/sbrweb/client/datastore" +//"github.com/gorilla/sessions" +//"github.com/isomorphicgo/isokit" +//"github.com/isomorphicgo/isokit" +import ( + "github.com/julienschmidt/httprouter" +) + +type Env struct { + Router *httprouter.Router + //DStore datastore.Datastore + //FStore *sessions.FilesystemStore + //Router *isokit.Router + //TemplateSet *isokit.TemplateSet + //logging... + //tracking.... + //analytics... +} diff --git a/client/common/datastore/datastore.go b/client/common/datastore/datastore.go new file mode 100644 index 0000000..4266e84 --- /dev/null +++ b/client/common/datastore/datastore.go @@ -0,0 +1,26 @@ +package datastore + +import "errors" + +const ( + MEMCACHED = iota +) + +type Datastore interface { + Ping() DataStoreStatus +} + +type DataStoreStatus struct { + Code int + Status string + Error error +} + +func NewDataStore(dstoreType int, conn string) (Datastore, error) { + switch dstoreType { + case MEMCACHED: + return NewMemcacheDatastore(conn) + default: + return nil, errors.New("Datastore not registered") + } +} diff --git a/client/common/datastore/memcache.go b/client/common/datastore/memcache.go new file mode 100644 index 0000000..6b3e8af --- /dev/null +++ b/client/common/datastore/memcache.go @@ -0,0 +1,16 @@ +package datastore + +type MemcacheDatastore struct { +} + +func NewMemcacheDatastore(address string) (*MemcacheDatastore, error) { + return &MemcacheDatastore{}, nil +} + +func (m *MemcacheDatastore) Ping() DataStoreStatus { + return DataStoreStatus{ + Code: 201, + Status: "ok", + Error: nil, + } +} diff --git a/client/handlers/hotel_avail.go b/client/handlers/hotel_avail.go new file mode 100644 index 0000000..4b2cf59 --- /dev/null +++ b/client/handlers/hotel_avail.go @@ -0,0 +1,20 @@ +package handlers + +import ( + "fmt" + "net/http" + + "github.com/ailgroup/sbrweb/client/common" +) + +//https://stackoverflow.com/questions/15407719/in-gos-http-package-how-do-i-get-the-query-string-on-a-post-request +func HotelAvailHandler(env *common.Env) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + //1 way: + //param1 := r.URL.Query().Get("param1") + + //2 way: + //value := FormValue("field") + fmt.Fprint(w, "hello hotel availability") + }) +} From 58425b99aaafcfd418b1fe692205829b4d051b17 Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Fri, 22 Jun 2018 07:03:06 -0600 Subject: [PATCH 03/21] Common environment router, config, session pool Gelling around patterns for dependency injection with viper and configuration settings, as well as the chi muxer for dealing with http handlers --- client/client.go | 53 +++++++++++++++++++++++---- client/common/common.go | 9 ++++- client/config.toml | 23 ++++++++++++ client/handlers/handle_hotel_avail.go | 24 ++++++++++++ client/handlers/hotel_avail.go | 20 ---------- 5 files changed, 100 insertions(+), 29 deletions(-) create mode 100644 client/config.toml create mode 100644 client/handlers/handle_hotel_avail.go delete mode 100644 client/handlers/hotel_avail.go diff --git a/client/client.go b/client/client.go index 05e8d5f..24da30b 100644 --- a/client/client.go +++ b/client/client.go @@ -1,18 +1,57 @@ package main import ( + "fmt" + "net/http" + "github.com/ailgroup/sbrweb/client/common" "github.com/ailgroup/sbrweb/client/handlers" - "github.com/gorilla/mux" + "github.com/fsnotify/fsnotify" + "github.com/go-chi/chi" + "github.com/go-chi/chi/middleware" + "github.com/spf13/viper" ) -// registerRoutes is responsible for regisetering the server-side request handlers -func registerRoutes(env *common.Env, r *mux.Router) { - r.Handle("/avail", handlers.HotelAvailHandler(env)).Methods("GET") +func setConfig() *viper.Viper { + conf := viper.GetViper() + + conf.SetConfigName("config") + conf.AddConfigPath("$HOME") + conf.AddConfigPath(".") + conf.BindEnv("SABRE_USERNAME") + conf.BindEnv("SABRE_PASSWORD") + conf.BindEnv("SABRE_PCC") + + err := conf.ReadInConfig() + if err != nil { + panic(fmt.Errorf("Fatal error config file: %s \n", err)) + } + conf.WatchConfig() + conf.OnConfigChange(func(e fsnotify.Event) { + fmt.Println("Config file changed:", e.Name) + }) + + return conf +} + +// registerRoutes is responsible for registering the server-side request handlers +func registerRoutes(env *common.Env) { + env.Mux.Get("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hi")) }) + env.Mux.Handle("/avail", handlers.HotelAvailHandler(env)) } func main() { - env := common.Env{} - r := mux.NewRouter() - registerRoutes(&env, r) + // deal with context?? + r := chi.NewRouter() + //Logger Logs the start and end of each request with the elapsed processing time + r.Use(middleware.Logger) + // Heartbeat Monitoring endpoint to check the servers pulse + r.Use(middleware.Heartbeat("/heartbeat")) + + env := common.Env{ + Config: setConfig(), + Mux: r, + } + registerRoutes(&env) + http.ListenAndServe(":8080", r) } diff --git a/client/common/common.go b/client/common/common.go index b022050..7b1e353 100644 --- a/client/common/common.go +++ b/client/common/common.go @@ -5,11 +5,16 @@ package common //"github.com/isomorphicgo/isokit" //"github.com/isomorphicgo/isokit" import ( - "github.com/julienschmidt/httprouter" + "github.com/ailgroup/sbrweb/engine/srvc" + "github.com/go-chi/chi" + "github.com/spf13/viper" ) type Env struct { - Router *httprouter.Router + //Router *httprouter.Router + Mux *chi.Mux + SessionPool *srvc.SessionPool + Config *viper.Viper //DStore datastore.Datastore //FStore *sessions.FilesystemStore //Router *isokit.Router diff --git a/client/config.toml b/client/config.toml new file mode 100644 index 0000000..1089006 --- /dev/null +++ b/client/config.toml @@ -0,0 +1,23 @@ +# This is a TOML document. + +title = "Sample Client Config" + +[owner] +name = "Client" +version = 0.1 + + +# ENV are stored on the server +#sabre_password == ENV +#sabre_username == ENV +#sabre_pcc == ENV +[sessions] +sabre_url = "https://webservices.sabre.com/websvc/" + [sessions.client] + url = "www.z.com" + pool_size = 10 + + [sessions.expire] + # min/max are in minutes + min = 10 + max = 28 \ No newline at end of file diff --git a/client/handlers/handle_hotel_avail.go b/client/handlers/handle_hotel_avail.go new file mode 100644 index 0000000..48f5368 --- /dev/null +++ b/client/handlers/handle_hotel_avail.go @@ -0,0 +1,24 @@ +package handlers + +import ( + "fmt" + "net/http" + + "github.com/ailgroup/sbrweb/client/common" +) + +// HotelAvailHandler wraps SOAP call to sabre hotel availability service +func HotelAvailHandler(env *common.Env) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + //1 way: + //param1 := r.URL.Query().Get("param1") + + //2 way: + //value := FormValue("field") + pcc := env.Config.GetString("SABRE_PCC") + clientURL := env.Config.GetString("sessions.client.url") + sessExpireMin := env.Config.GetInt("sessions.expire.min") + msg := fmt.Sprintf("PCC:%s\n From:%s\n SessionExpireMin:%d\n \n %s", pcc, clientURL, sessExpireMin, "hello hotel availability") + fmt.Fprint(w, msg) + }) +} diff --git a/client/handlers/hotel_avail.go b/client/handlers/hotel_avail.go deleted file mode 100644 index 4b2cf59..0000000 --- a/client/handlers/hotel_avail.go +++ /dev/null @@ -1,20 +0,0 @@ -package handlers - -import ( - "fmt" - "net/http" - - "github.com/ailgroup/sbrweb/client/common" -) - -//https://stackoverflow.com/questions/15407719/in-gos-http-package-how-do-i-get-the-query-string-on-a-post-request -func HotelAvailHandler(env *common.Env) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - //1 way: - //param1 := r.URL.Query().Get("param1") - - //2 way: - //value := FormValue("field") - fmt.Fprint(w, "hello hotel availability") - }) -} From 335f03fc51511d29badfe26afb9e42ffa1eb4eb5 Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Fri, 22 Jun 2018 23:17:58 -0600 Subject: [PATCH 04/21] refactor client for cleaner impementation --- client/app/handlers.go | 31 ++++++ client/app/routes.go | 11 ++ client/{common/common.go => app/server.go} | 11 +- client/client.go | 57 ----------- client/{common => }/datastore/datastore.go | 0 client/{common => }/datastore/memcache.go | 0 client/handlers/handle_hotel_avail.go | 24 ----- client/main.go | 111 +++++++++++++++++++++ engine/srvc/session_pool.go | 15 +-- engine/srvc/soap_base.go | 9 +- engine/srvc/soap_base_test.go | 16 ++- 11 files changed, 183 insertions(+), 102 deletions(-) create mode 100644 client/app/handlers.go create mode 100644 client/app/routes.go rename client/{common/common.go => app/server.go} (52%) delete mode 100644 client/client.go rename client/{common => }/datastore/datastore.go (100%) rename client/{common => }/datastore/memcache.go (100%) delete mode 100644 client/handlers/handle_hotel_avail.go create mode 100644 client/main.go diff --git a/client/app/handlers.go b/client/app/handlers.go new file mode 100644 index 0000000..c4aa0cb --- /dev/null +++ b/client/app/handlers.go @@ -0,0 +1,31 @@ +package app + +import ( + "fmt" + "net/http" +) + +// HotelAvailHandler wraps SOAP call to sabre hotel availability service +func (s *Server) HotelAvailHandler() http.HandlerFunc { + pcc := s.Config.GetString("SABRE_PCC") + clientURL := s.Config.GetString("sessions.client.url") + sessExpireMin := s.Config.GetInt("sessions.expire.min") + msg := fmt.Sprintf("PCC:%s\n From:%s\n SessionExpireMin:%d\n \n %s", pcc, clientURL, sessExpireMin, "hello hotel availability") + return func(w http.ResponseWriter, r *http.Request) { + sess := s.SessionPool.Pick() + defer s.SessionPool.Put(sess) + info := fmt.Sprintf("%s \n\n SessionID: %s\n ExpireTime: %v\n Started: %v\n ConvID: %s\n", + msg, + sess.ID, + sess.ExpireTime, + sess.TimeStarted, + sess.Sabre.Body.SessionCreateRS.ConversationID, + ) + //1 way: + //param1 := r.URL.Query().Get("param1") + + //2 way: + //value := FormValue("field") + fmt.Fprint(w, info) + } +} diff --git a/client/app/routes.go b/client/app/routes.go new file mode 100644 index 0000000..2f7f3b7 --- /dev/null +++ b/client/app/routes.go @@ -0,0 +1,11 @@ +package app + +import ( + "net/http" +) + +// registerRoutes is responsible for registering the server-side request handlers +func (s *Server) RegisterRoutes() { + s.Mux.Get("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hi")) }) + s.Mux.Handle("/avail", s.HotelAvailHandler()) +} diff --git a/client/common/common.go b/client/app/server.go similarity index 52% rename from client/common/common.go rename to client/app/server.go index 7b1e353..73ab471 100644 --- a/client/common/common.go +++ b/client/app/server.go @@ -1,24 +1,17 @@ -package common +package app -//"github.com/ailgroup/sbrweb/client/datastore" -//"github.com/gorilla/sessions" -//"github.com/isomorphicgo/isokit" -//"github.com/isomorphicgo/isokit" import ( "github.com/ailgroup/sbrweb/engine/srvc" "github.com/go-chi/chi" "github.com/spf13/viper" ) -type Env struct { - //Router *httprouter.Router +type Server struct { Mux *chi.Mux SessionPool *srvc.SessionPool Config *viper.Viper //DStore datastore.Datastore //FStore *sessions.FilesystemStore - //Router *isokit.Router - //TemplateSet *isokit.TemplateSet //logging... //tracking.... //analytics... diff --git a/client/client.go b/client/client.go deleted file mode 100644 index 24da30b..0000000 --- a/client/client.go +++ /dev/null @@ -1,57 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - - "github.com/ailgroup/sbrweb/client/common" - "github.com/ailgroup/sbrweb/client/handlers" - "github.com/fsnotify/fsnotify" - "github.com/go-chi/chi" - "github.com/go-chi/chi/middleware" - "github.com/spf13/viper" -) - -func setConfig() *viper.Viper { - conf := viper.GetViper() - - conf.SetConfigName("config") - conf.AddConfigPath("$HOME") - conf.AddConfigPath(".") - conf.BindEnv("SABRE_USERNAME") - conf.BindEnv("SABRE_PASSWORD") - conf.BindEnv("SABRE_PCC") - - err := conf.ReadInConfig() - if err != nil { - panic(fmt.Errorf("Fatal error config file: %s \n", err)) - } - conf.WatchConfig() - conf.OnConfigChange(func(e fsnotify.Event) { - fmt.Println("Config file changed:", e.Name) - }) - - return conf -} - -// registerRoutes is responsible for registering the server-side request handlers -func registerRoutes(env *common.Env) { - env.Mux.Get("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hi")) }) - env.Mux.Handle("/avail", handlers.HotelAvailHandler(env)) -} - -func main() { - // deal with context?? - r := chi.NewRouter() - //Logger Logs the start and end of each request with the elapsed processing time - r.Use(middleware.Logger) - // Heartbeat Monitoring endpoint to check the servers pulse - r.Use(middleware.Heartbeat("/heartbeat")) - - env := common.Env{ - Config: setConfig(), - Mux: r, - } - registerRoutes(&env) - http.ListenAndServe(":8080", r) -} diff --git a/client/common/datastore/datastore.go b/client/datastore/datastore.go similarity index 100% rename from client/common/datastore/datastore.go rename to client/datastore/datastore.go diff --git a/client/common/datastore/memcache.go b/client/datastore/memcache.go similarity index 100% rename from client/common/datastore/memcache.go rename to client/datastore/memcache.go diff --git a/client/handlers/handle_hotel_avail.go b/client/handlers/handle_hotel_avail.go deleted file mode 100644 index 48f5368..0000000 --- a/client/handlers/handle_hotel_avail.go +++ /dev/null @@ -1,24 +0,0 @@ -package handlers - -import ( - "fmt" - "net/http" - - "github.com/ailgroup/sbrweb/client/common" -) - -// HotelAvailHandler wraps SOAP call to sabre hotel availability service -func HotelAvailHandler(env *common.Env) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - //1 way: - //param1 := r.URL.Query().Get("param1") - - //2 way: - //value := FormValue("field") - pcc := env.Config.GetString("SABRE_PCC") - clientURL := env.Config.GetString("sessions.client.url") - sessExpireMin := env.Config.GetInt("sessions.expire.min") - msg := fmt.Sprintf("PCC:%s\n From:%s\n SessionExpireMin:%d\n \n %s", pcc, clientURL, sessExpireMin, "hello hotel availability") - fmt.Fprint(w, msg) - }) -} diff --git a/client/main.go b/client/main.go new file mode 100644 index 0000000..46b6d33 --- /dev/null +++ b/client/main.go @@ -0,0 +1,111 @@ +package main + +import ( + "fmt" + "net/http" + "os" + "os/signal" + "time" + + "github.com/ailgroup/sbrweb/client/app" + "github.com/ailgroup/sbrweb/engine/srvc" + "github.com/fsnotify/fsnotify" + "github.com/go-chi/chi" + "github.com/go-chi/chi/middleware" + "github.com/spf13/viper" +) + +const ( + ConfSabreUsername = "SABRE_USERNAME" + ConfSabrePassword = "SABRE_PASSWORD" + ConfSabrePCC = "SABRE_PCC" + ConfFile = "config" + ConfExpireMin = "sessions.expire.min" + ConfExpireMax = "sessions.expire.max" + ConfPoolSize = "sessions.client.pool_size" + ConfSabreURL = "sessions.sabre_url" + ConfClientURL = "sessions.client.url" +) + +var ( + vRepeatEvery = time.Minute * 3 + //vEndAfter = time.Minute * 6 +) + +func setConfig() *viper.Viper { + conf := viper.GetViper() + + conf.SetConfigName(ConfFile) + conf.AddConfigPath("$HOME") + conf.AddConfigPath(".") + conf.BindEnv(ConfSabreUsername) + conf.BindEnv(ConfSabrePassword) + conf.BindEnv(ConfSabrePCC) + + err := conf.ReadInConfig() + if err != nil { + panic(fmt.Errorf("Fatal error config file: %s \n", err)) + } + conf.WatchConfig() + conf.OnConfigChange(func(e fsnotify.Event) { + fmt.Println("Config file changed:", e.Name) + }) + + return conf +} + +func main() { + c := setConfig() + scheme := srvc.ExpireScheme{ + Min: c.GetInt(ConfExpireMin), + Max: c.GetInt(ConfExpireMax), + } + p := srvc.NewPool( + scheme, + c.GetInt(ConfPoolSize), + c.GetString(ConfSabreURL), + c.GetString(ConfClientURL), + c.GetString(ConfSabrePCC), + srvc.GenerateConversationID(c.GetString(ConfClientURL)), + srvc.GenerateMessageID(), + srvc.SabreTimeFormat(), + c.GetString(ConfSabreUsername), + c.GetString(ConfSabrePassword), + ) + + // background populating the pool + // and setting up the teh keepvalid worker + go func() { + err := p.Populate() + if err != nil { + panic(err) + } + //srvc.Keepalive(p, vRepeatEvery, vEndAfter) + srvc.Keepalive(p, vRepeatEvery) + }() + // close down session pool properly, validates against + // sabre and re-allocates on sabre side + cls := make(chan os.Signal) + signal.Notify(cls, os.Interrupt) + go func() { + sig := <-cls + fmt.Printf("Got %s signal. Closing down...\n", sig) + p.Close() + os.Exit(1) + }() + + // deal with context?? + m := chi.NewRouter() + //Logger Logs the start and end of each request with the elapsed processing time + m.Use(middleware.Logger) + // Heartbeat Monitoring endpoint to check the servers pulse + m.Use(middleware.Heartbeat("/heartbeat")) + + server := app.Server{ + Config: c, + Mux: m, + SessionPool: p, + } + server.RegisterRoutes() + http.ListenAndServe(":8080", m) +} diff --git a/engine/srvc/session_pool.go b/engine/srvc/session_pool.go index 6401ffe..ece242a 100644 --- a/engine/srvc/session_pool.go +++ b/engine/srvc/session_pool.go @@ -118,8 +118,8 @@ func (p *SessionPool) newSession() (Session, error) { Sabre: createRS, TimeStarted: now, TimeValidated: now, - //ExpireTime: now.Add(time.Minute * time.Duration(RandomInt(3, 14))), - ExpireTime: now.Add(time.Minute * time.Duration(1)), + ExpireTime: now.Add(time.Minute * time.Duration(RandomInt(p.Expire.Min, p.Expire.Max))), + //ExpireTime: now.Add(time.Minute * time.Duration(1)), FaultError: faultErr, } logSession.Printf( @@ -163,7 +163,7 @@ func (p *SessionPool) Pick() Session { return sess } -// Put session back onto the buffered queue +// Put session back onto the buffered queue, perhaps return error here for better signalling if ever deferring these func (p *SessionPool) Put(sess Session) { p.Sessions <- sess p.logReport("Put-" + sess.ID) @@ -217,7 +217,7 @@ func (p *SessionPool) RangeKeepalive(keepaliveID string) { newSess, err := p.newSession() if err != nil { logSession.Printf("Network ERROR for ID=%s, expire and retry", newSess.ID) - newSess.ExpireTime = time.Now().Add(time.Second * 1) + newSess.ExpireTime = time.Now().Add(time.Second * 5) } logSession.Printf("NewSession-%s ID=%s token=%s\n", keepaliveID, @@ -262,10 +262,11 @@ func generateKeepAliveID() string { return "kid:" + randStr + "|" + nowtime } -//Keepalive sessions in the session pool validated -func Keepalive(p *SessionPool, repeatEvery, endAfter time.Duration) { +//Keepalive sessions by RangeKeepalive over all sessions in pool +func Keepalive(p *SessionPool, repeatEvery time.Duration) { //defer profile.Start(profile.MemProfile).Stop() - doneChan := time.NewTimer(endAfter).C + //endAfter time.Duration + doneChan := time.NewTimer(time.Minute * 4).C started := time.Now() keepAliveID := generateKeepAliveID() logSession.Println("Starting KEEPALIVE...", keepAliveID) diff --git a/engine/srvc/soap_base.go b/engine/srvc/soap_base.go index b14eb1a..243f93e 100644 --- a/engine/srvc/soap_base.go +++ b/engine/srvc/soap_base.go @@ -350,9 +350,12 @@ func randStringBytesMaskImprSrc(n int) string { // GenerateMessageID returns 'mid:20060102-15:04:05|urioe' func GenerateMessageID() string { - randStr := randStringBytesMaskImprSrc(5) - nowtime := time.Now().Format("20060102-15:04:05.99") - return "mid:" + nowtime + "|" + randStr + return "mid:" + time.Now().Format("20060102-15:04:05.99") + "|" + randStringBytesMaskImprSrc(5) +} + +// GenerateConversationID returns 'cid:1Fv0Oq65|www.z.com' +func GenerateConversationID(from string) string { + return "cid:" + randStringBytesMaskImprSrc(8) + "|" + from } // SessionCreateRQ for session create request diff --git a/engine/srvc/soap_base_test.go b/engine/srvc/soap_base_test.go index b949be7..73d4467 100644 --- a/engine/srvc/soap_base_test.go +++ b/engine/srvc/soap_base_test.go @@ -25,8 +25,8 @@ var ( samplerandStr = regexp.MustCompile(`\w*`) samplerfc333pString = "2017-11-27T09:58:31Z" samplerfc333pReg = regexp.MustCompile(`\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`) - samplemidStr = "mid:20180207-20:19:07.25|QVbg0" samplemidReg = regexp.MustCompile(`mid:\d{8}-\d{2}:\d{2}:\d{2}\.\d{1,2}\|\w{5}`) + sampleconvReg = regexp.MustCompile(`cid:\w{8}\|.{5,}`) samplemid = "mid:20180216-07:18:42.3|14oUa" sampletime = "2018-02-16T07:18:42Z" samplepcc = "7TZA" @@ -210,7 +210,7 @@ func BenchmarkRandomStringGen(b *testing.B) { func TestGenerateMessageID(t *testing.T) { mid := GenerateMessageID() if !samplemidReg.MatchString(mid) { - t.Errorf("MessageID formt wrong. Example: '%s', got '%s'", samplemidStr, mid) + t.Errorf("MessageID format wrong. Example: '%s', got '%s'", samplemid, mid) } } func BenchmarkGenerateMessageID(b *testing.B) { @@ -219,6 +219,18 @@ func BenchmarkGenerateMessageID(b *testing.B) { } } +func TestGenerateConversationID(t *testing.T) { + conv := GenerateConversationID(samplefrom) + if !sampleconvReg.MatchString(conv) { + t.Errorf("ConversaionID format wrong. Example: '%s', got '%s'", sampleconvid, conv) + } +} +func BenchmarkGenerateConversationID(b *testing.B) { + for n := 0; n < b.N; n++ { + GenerateConversationID(samplefrom) + } +} + func TestSabreTokenParse(t *testing.T) { tok := SabreTokenParse(samplebinsectoken) if tok != samplebintokensplit { From 93f5701e70104c054bd734cf64310a9409123330 Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Sat, 23 Jun 2018 22:02:48 -0600 Subject: [PATCH 05/21] add session conf, update tests, refactor --- client/app/handlers.go | 15 +- engine/hotelws/hotel_avail.go | 14 +- engine/hotelws/hotel_avail_test.go | 39 +---- engine/hotelws/hotel_prop_desc.go | 14 +- engine/hotelws/hotel_prop_desc_test.go | 20 +-- engine/hotelws/hotel_rate_desc.go | 14 +- engine/hotelws/hotel_rate_desc_test.go | 15 +- engine/hotelws/hotel_res.go | 18 +-- engine/hotelws/hotel_res_test.go | 21 ++- engine/hotelws/hotelws_helper_test.go | 16 ++ engine/srvc/session_pool.go | 37 ++--- engine/srvc/session_pool_test.go | 31 ++-- engine/srvc/soap_base.go | 44 +++-- engine/srvc/soap_base_test.go | 213 +++++++++++++------------ 14 files changed, 242 insertions(+), 269 deletions(-) diff --git a/client/app/handlers.go b/client/app/handlers.go index c4aa0cb..91426b9 100644 --- a/client/app/handlers.go +++ b/client/app/handlers.go @@ -7,25 +7,16 @@ import ( // HotelAvailHandler wraps SOAP call to sabre hotel availability service func (s *Server) HotelAvailHandler() http.HandlerFunc { - pcc := s.Config.GetString("SABRE_PCC") - clientURL := s.Config.GetString("sessions.client.url") - sessExpireMin := s.Config.GetInt("sessions.expire.min") - msg := fmt.Sprintf("PCC:%s\n From:%s\n SessionExpireMin:%d\n \n %s", pcc, clientURL, sessExpireMin, "hello hotel availability") + msg := "this" return func(w http.ResponseWriter, r *http.Request) { sess := s.SessionPool.Pick() defer s.SessionPool.Put(sess) - info := fmt.Sprintf("%s \n\n SessionID: %s\n ExpireTime: %v\n Started: %v\n ConvID: %s\n", - msg, - sess.ID, - sess.ExpireTime, - sess.TimeStarted, - sess.Sabre.Body.SessionCreateRS.ConversationID, - ) + //1 way: //param1 := r.URL.Query().Get("param1") //2 way: //value := FormValue("field") - fmt.Fprint(w, info) + fmt.Fprint(w, msg) } } diff --git a/engine/hotelws/hotel_avail.go b/engine/hotelws/hotel_avail.go index 9f20fd3..27b313c 100644 --- a/engine/hotelws/hotel_avail.go +++ b/engine/hotelws/hotel_avail.go @@ -72,7 +72,7 @@ func SetHotelAvailBody(guestCount int, query *HotelSearchCriteria, arrive, depar } // BuildHotelAvailRequest to make hotel availability request. -func BuildHotelAvailRequest(from, pcc, binsectoken, convid, mid, time string, otaHotelAvail HotelAvailBody) HotelAvailRequest { +func BuildHotelAvailRequest(c *srvc.SessionConf, otaHotelAvail HotelAvailBody) HotelAvailRequest { return HotelAvailRequest{ Envelope: srvc.CreateEnvelope(), Header: srvc.SessionHeader{ @@ -80,24 +80,24 @@ func BuildHotelAvailRequest(from, pcc, binsectoken, convid, mid, time string, ot MustUnderstand: srvc.SabreMustUnderstand, EbVersion: srvc.SabreEBVersion, From: srvc.FromElem{ - PartyID: srvc.CreatePartyID(from, srvc.PartyIDTypeURN), + PartyID: srvc.CreatePartyID(c.From, srvc.PartyIDTypeURN), }, To: srvc.ToElem{ PartyID: srvc.CreatePartyID(srvc.SabreToBase, srvc.PartyIDTypeURN), }, - CPAID: pcc, - ConversationID: convid, + CPAID: c.PCC, + ConversationID: c.Convid, Service: srvc.ServiceElem{Value: "OTA_HotelAvailRQ", Type: "sabreXML"}, Action: "OTA_HotelAvailLLSRQ", MessageData: srvc.MessageDataElem{ - MessageID: mid, - Timestamp: time, + MessageID: c.Msgid, + Timestamp: c.Timestr, }, }, Security: srvc.Security{ XMLNSWsseBase: srvc.BaseWsse, XMLNSWsu: srvc.BaseWsuNameSpace, - BinarySecurityToken: binsectoken, + BinarySecurityToken: c.Binsectok, }, }, Body: otaHotelAvail, diff --git a/engine/hotelws/hotel_avail_test.go b/engine/hotelws/hotel_avail_test.go index b72b638..3d03bb7 100644 --- a/engine/hotelws/hotel_avail_test.go +++ b/engine/hotelws/hotel_avail_test.go @@ -23,7 +23,6 @@ func TestAddressSearchCriteria(t *testing.T) { a, err := NewHotelSearchCriteria( AddressSearch(addr), ) - if err != nil { t.Errorf("NewHotelSearchCriteria with AddressSearch error %v", err) } @@ -85,7 +84,6 @@ func TestHotelRefSearchCityCodeCriteria(t *testing.T) { if r.Criterion.HotelRefs[i].HotelCityCode != code { t.Errorf("HotelRef[%d].HotelCityCode city expect: %s, got: %s", i, code, r.Criterion.HotelRefs[i].HotelCityCode) } - } } @@ -131,11 +129,9 @@ func TestMultipleHotelCriteria(t *testing.T) { PackageSearch(samplePackages), PropertyTypeSearch(samplePropertyTypes), ) - if err != nil { t.Errorf("NewHotelSearchCriteria with basic criteria error %v", err) } - counter := 0 for _, code := range sampleHotelCode { if r.Criterion.HotelRefs[counter].HotelCode != code { @@ -149,7 +145,6 @@ func TestMultipleHotelCriteria(t *testing.T) { } counter++ } - if r.Criterion.Address.Street != sampleStreet { t.Error("buildAddress street not correct") } @@ -179,7 +174,6 @@ func TestSetHotelAvailRqStructMarshal(t *testing.T) { availBody := SetHotelAvailBody(sampleGuestCount, &HotelSearchCriteria{}, sampleArrive, sampleDepart) avail := availBody.OTAHotelAvailRQ avail.addCorporateID(sampleCID) - if avail.XMLNSXsi != srvc.BaseXSINamespace { t.Errorf("SetHotelAvailRqStruct XMLNSXsi expect: %s, got %s", srvc.BaseXSINamespace, avail.XMLNSXsi) } @@ -192,7 +186,6 @@ func TestSetHotelAvailRqStructMarshal(t *testing.T) { if avail.Avail.Customer.Corporate.ID != sampleCID { t.Errorf("SetHotelAvailRqStruct Customer.Corporate.ID expect: %s, got %s", sampleCID, avail.Avail.Customer.Corporate.ID) } - _, err := xml.Marshal(avail) if err != nil { t.Error("Error marshaling get hotel content", err) @@ -203,16 +196,13 @@ func TestSetHotelAvailRqStructCorpID(t *testing.T) { availBody := SetHotelAvailBody(sampleGuestCount, &HotelSearchCriteria{}, sampleArrive, sampleDepart) avail := availBody.OTAHotelAvailRQ avail.addCorporateID(sampleCID) - if avail.Avail.Customer.Corporate.ID != sampleCID { t.Errorf("SetHotelAvailRqStruct Corporate.ID expect: %s, got %s", sampleCID, avail.Avail.Customer.Corporate.ID) } - avail.addCustomerID(sampleCID) if avail.Avail.Customer.CustomerID.Number != sampleCID { t.Errorf("SetHotelAvailRqStruct CustomerID.Number expect: %s, got %s", sampleCID, avail.Avail.Customer.Corporate.ID) } - } func TestAvailIdsMarshal(t *testing.T) { @@ -221,19 +211,15 @@ func TestAvailIdsMarshal(t *testing.T) { ) gcount := 4 availBody := SetHotelAvailBody(gcount, q, sampleArrive, sampleDepart) - avail := availBody.OTAHotelAvailRQ avail.addCorporateID(sampleCID) avail.Avail.RatePlanCandidates = SetRateParams([]RatePlan{RatePlan{CurrencyCode: "USD", DCA_ProductCode: "I7A"}}) - if avail.Avail.GuestCounts.Count != gcount { t.Errorf("SetHotelAvailRqStruct GuestCounts.Count expect: %d, got %d", gcount, avail.Avail.GuestCounts.Count) } - if len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs) != len(hqids[hotelidQueryField]) { t.Error("HotelRefs shoudl be same length as params", len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs), len(hqids[hotelidQueryField])) } - b, err := xml.Marshal(avail) if err != nil { t.Error("Error marshaling get hotel content", err) @@ -241,7 +227,6 @@ func TestAvailIdsMarshal(t *testing.T) { if string(b) != string(sampleAvailRQHotelIDSCoprIDRatePlans) { t.Errorf("Expected marshal hotel avail for hotel ids \n sample: %s \n result: %s", string(sampleAvailRQHotelIDSCoprIDRatePlans), string(b)) } - //fmt.Printf("content marshal \n%s\n", b) } func TestAvailCitiesMarshal(t *testing.T) { @@ -252,15 +237,12 @@ func TestAvailCitiesMarshal(t *testing.T) { availBody := SetHotelAvailBody(gcount, q, sampleArrive, sampleDepart) avail := availBody.OTAHotelAvailRQ avail.addCustomerID(sampleCID) - if avail.Avail.GuestCounts.Count != gcount { t.Errorf("BuildHotelAvailRq GuestCounts.Count expect: %d, got %d", gcount, avail.Avail.GuestCounts.Count) } - if len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs) != len(hqcity[cityQueryField]) { t.Error("HotelRefs shoudl be same length as params", len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs), len(hqcity[cityQueryField])) } - b, err := xml.Marshal(avail) if err != nil { t.Error("Error marshaling get hotel content", err) @@ -268,7 +250,6 @@ func TestAvailCitiesMarshal(t *testing.T) { if string(b) != string(sampleAvailRQCitiesCustNumber) { t.Errorf("Expected marshal hotel avail for hotel cities \n sample: %s \n result: %s", string(sampleAvailRQCitiesCustNumber), string(b)) } - //fmt.Printf("content marshal \n%s\n", b) } func TestAvailLatLngMarshal(t *testing.T) { @@ -277,15 +258,12 @@ func TestAvailLatLngMarshal(t *testing.T) { ) availBody := SetHotelAvailBody(sampleGuestCount, q, sampleArrive, sampleDepart) avail := availBody.OTAHotelAvailRQ - if avail.Avail.GuestCounts.Count != sampleGuestCount { t.Errorf("BuildHotelAvailRq GuestCounts.Count expect: %d, got %d", sampleGuestCount, avail.Avail.GuestCounts.Count) } - if len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs) != len(hqltln[latlngQueryField]) { t.Error("HotelRefs shoudl be same length as params", len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs), len(hqltln[latlngQueryField])) } - b, err := xml.Marshal(avail) if err != nil { t.Error("Error marshaling get hotel content", err) @@ -293,7 +271,6 @@ func TestAvailLatLngMarshal(t *testing.T) { if string(b) != string(sampleAvailRQLatLng) { t.Errorf("Expected marshal set hotel avail for hotel lat/lng \n sample: %s \n result: %s", string(sampleAvailRQLatLng), string(b)) } - //fmt.Printf("content marshal \n%s\n", b) } func TestAvailPropertyTypesPackagesMarshal(t *testing.T) { @@ -303,7 +280,6 @@ func TestAvailPropertyTypesPackagesMarshal(t *testing.T) { ) availBody := SetHotelAvailBody(sampleGuestCount, q, sampleArrive, sampleDepart) avail := availBody.OTAHotelAvailRQ - b, err := xml.Marshal(avail) if err != nil { t.Error("Error marshaling get hotel content", err) @@ -311,7 +287,6 @@ func TestAvailPropertyTypesPackagesMarshal(t *testing.T) { if string(b) != string(sampleAvailRQPropPackages) { t.Errorf("Expected marshal set hotel avail for hotel packages and property types \n sample: %s \n result: %s", string(sampleAvailRQLatLng), string(b)) } - //fmt.Printf("content marshal \n%s\n", b) } func TestBuildHotelAvailRequestMarshal(t *testing.T) { @@ -319,8 +294,7 @@ func TestBuildHotelAvailRequestMarshal(t *testing.T) { HotelRefSearch(hqids), ) avail := SetHotelAvailBody(sampleGuestCount, q, sampleArrive, sampleDepart) - req := BuildHotelAvailRequest(samplesite, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, avail) - + req := BuildHotelAvailRequest(sconf, avail) b, err := xml.Marshal(req) if err != nil { t.Error("Error marshaling get hotel content", err) @@ -329,7 +303,6 @@ func TestBuildHotelAvailRequestMarshal(t *testing.T) { if string(b) != string(sampleAvailRQHotelIDS) { t.Errorf("Expected marshal SOAP hotel avail for hotel ids \n sample: %s \n result: %s", string(sampleAvailRQHotelIDS), string(b)) } - //fmt.Printf("content marshal \n%s\n", b) } func TestHotelAvailUnmarshal(t *testing.T) { @@ -369,10 +342,6 @@ func TestHotelAvailUnmarshal(t *testing.T) { if rateRange.Min != "134.00" { t.Errorf("RateRange Min should be %s, got %s", "USD", rateRange.Min) } - - //fmt.Printf("SAMPLE: %s\n", sampleEnvelope) - //fmt.Printf("CURRENT: %+v\n", success) - //fmt.Printf("CURRENT: %+v\n", avail) } func TestHotelAvailCallByIDs(t *testing.T) { @@ -380,7 +349,7 @@ func TestHotelAvailCallByIDs(t *testing.T) { HotelRefSearch(hqids), ) avail := SetHotelAvailBody(sampleGuestCount, q, sampleArrive, sampleDepart) - req := BuildHotelAvailRequest(samplesite, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, avail) + req := BuildHotelAvailRequest(sconf, avail) resp, err := CallHotelAvail(serverHotelAvailability.URL, req) if err != nil { t.Error("Error making request CallHotelAvail", err) @@ -405,7 +374,7 @@ func TestHotelAvailCallDown(t *testing.T) { HotelRefSearch(hqids), ) avail := SetHotelAvailBody(sampleGuestCount, q, sampleArrive, sampleDepart) - req := BuildHotelAvailRequest(samplesite, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, avail) + req := BuildHotelAvailRequest(sconf, avail) resp, err := CallHotelAvail(serverHotelDown.URL, req) if err == nil { t.Error("Expected error making request to serverHotelDown") @@ -426,7 +395,7 @@ func TestHotelAvailCallBadResponseBody(t *testing.T) { HotelRefSearch(hqids), ) avail := SetHotelAvailBody(sampleGuestCount, q, sampleArrive, sampleDepart) - req := BuildHotelAvailRequest(samplesite, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, avail) + req := BuildHotelAvailRequest(sconf, avail) resp, err := CallHotelAvail(serverBadBody.URL, req) if err == nil { t.Error("Expected error making request to sserverBadBody") diff --git a/engine/hotelws/hotel_prop_desc.go b/engine/hotelws/hotel_prop_desc.go index c1d87bc..1163477 100644 --- a/engine/hotelws/hotel_prop_desc.go +++ b/engine/hotelws/hotel_prop_desc.go @@ -76,7 +76,7 @@ func SetHotelPropDescBody(guestCount int, query *HotelSearchCriteria, arrive, de } // BuildHotelPropDescRequest to make hotel property description request, which will have rate availability information on the response. -func BuildHotelPropDescRequest(from, pcc, binsectoken, convid, mid, time string, propDesc HotelPropDescBody) HotelPropDescRequest { +func BuildHotelPropDescRequest(c *srvc.SessionConf, propDesc HotelPropDescBody) HotelPropDescRequest { return HotelPropDescRequest{ Envelope: srvc.CreateEnvelope(), Header: srvc.SessionHeader{ @@ -84,24 +84,24 @@ func BuildHotelPropDescRequest(from, pcc, binsectoken, convid, mid, time string, MustUnderstand: srvc.SabreMustUnderstand, EbVersion: srvc.SabreEBVersion, From: srvc.FromElem{ - PartyID: srvc.CreatePartyID(from, srvc.PartyIDTypeURN), + PartyID: srvc.CreatePartyID(c.From, srvc.PartyIDTypeURN), }, To: srvc.ToElem{ PartyID: srvc.CreatePartyID(srvc.SabreToBase, srvc.PartyIDTypeURN), }, - CPAID: pcc, - ConversationID: convid, + CPAID: c.PCC, + ConversationID: c.Convid, Service: srvc.ServiceElem{Value: "HotelPropertyDescription", Type: "sabreXML"}, Action: "HotelPropertyDescriptionLLSRQ", MessageData: srvc.MessageDataElem{ - MessageID: mid, - Timestamp: time, + MessageID: c.Msgid, + Timestamp: c.Timestr, }, }, Security: srvc.Security{ XMLNSWsseBase: srvc.BaseWsse, XMLNSWsu: srvc.BaseWsuNameSpace, - BinarySecurityToken: binsectoken, + BinarySecurityToken: c.Binsectok, }, }, Body: propDesc, diff --git a/engine/hotelws/hotel_prop_desc_test.go b/engine/hotelws/hotel_prop_desc_test.go index 2af67e0..bf95364 100644 --- a/engine/hotelws/hotel_prop_desc_test.go +++ b/engine/hotelws/hotel_prop_desc_test.go @@ -50,8 +50,7 @@ func TestPropDescBuildHotelPropDescMarshal(t *testing.T) { if err != nil { t.Error("Error SetHotelPropDescRqStruct: ", err) } - req := BuildHotelPropDescRequest(samplesite, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, prop) - + req := BuildHotelPropDescRequest(sconf, prop) b, err := xml.Marshal(req) if err != nil { t.Error("Error marshaling get hotel content", err) @@ -60,18 +59,15 @@ func TestPropDescBuildHotelPropDescMarshal(t *testing.T) { if string(b) != string(samplePropRQIDs) { t.Errorf("Expected marshal SOAP hotel property description for hotel ids \n sample: %s \n result: %s", string(samplePropRQIDs), string(b)) } - //fmt.Printf("content marshal \n%s\n", b) } func TestSetHotelPropDescRqStructCorpID(t *testing.T) { body, _ := SetHotelPropDescBody(sampleGuestCount, &HotelSearchCriteria{}, sampleArrive, sampleDepart) prop := body.HotelPropDescRQ prop.addCorporateID(sampleCID) - if prop.Avail.Customer.Corporate.ID != sampleCID { t.Errorf("SetHotelPropDescRqStruct Corporate.ID expect: %s, got %s", sampleCID, prop.Avail.Customer.Corporate.ID) } - prop.addCustomerID(sampleCID) if prop.Avail.Customer.CustomerID.Number != sampleCID { t.Errorf("SetHotelPropDescRqStruct CustomerID.Number expect: %s, got %s", sampleCID, prop.Avail.Customer.Corporate.ID) @@ -98,7 +94,6 @@ func TestPropDescUnmarshal(t *testing.T) { if numRates != 16 { t.Error("Number of rates is wrong") } - rate0 := roomStayRates[0] sample0 := rateSamples[0] if rate0.RPH != sample0.rph { @@ -141,8 +136,6 @@ func TestPropDescUnmarshal(t *testing.T) { if taxes0 != sampleTaxes0 { t.Errorf("TotalTaxes expected %s, got %s", sampleTaxes0, taxes0) } - //fmt.Printf("CURRENT: %+v\n", prop) - //fmt.Printf("RATES COUNT: %d\n", len(prop.Body.HotelDesc.RoomStay.RoomRates)) } func TestPropDescCall(t *testing.T) { @@ -152,8 +145,7 @@ func TestPropDescCall(t *testing.T) { HotelRefSearch(hotelid), ) prop, _ := SetHotelPropDescBody(sampleGuestCount, q, sampleArrive, sampleDepart) - req := BuildHotelPropDescRequest(samplesite, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, prop) - + req := BuildHotelPropDescRequest(sconf, prop) resp, err := CallHotelPropDesc(serverHotelPropertyDesc.URL, req) if err != nil { t.Error("Error making request CallHotelProperty", err) @@ -166,7 +158,6 @@ func TestPropDescCall(t *testing.T) { if numRoomRates != 16 { t.Error("Number of rates is wrong") } - for idx, rr := range roomStayRates { if rr.IATA_Character != iataCharSample[idx] { t.Errorf("IATA_Character %d expected %s, got %s", idx, iataCharSample[idx], rr.IATA_Character) @@ -183,7 +174,6 @@ func TestPropDescCall(t *testing.T) { t.Errorf("RoomRate %d expected cancel policy option %s, got %s", idx, "D", copt) } } - indexRoomRate := numRoomRates - 1 numRates := len(roomStayRates[indexRoomRate].Rates) if numRates != 1 { @@ -220,8 +210,7 @@ func TestHotelPropDescCallDown(t *testing.T) { HotelRefSearch(hotelid), ) prop, _ := SetHotelPropDescBody(sampleGuestCount, q, sampleArrive, sampleDepart) - req := BuildHotelPropDescRequest(samplesite, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, prop) - + req := BuildHotelPropDescRequest(sconf, prop) resp, err := CallHotelPropDesc(serverHotelDown.URL, req) if err == nil { t.Error("Expected error making request to serverHotelDown") @@ -244,8 +233,7 @@ func TestHotelPropDescCallBadResponseBody(t *testing.T) { HotelRefSearch(hotelid), ) prop, _ := SetHotelPropDescBody(sampleGuestCount, q, sampleArrive, sampleDepart) - req := BuildHotelPropDescRequest(samplesite, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, prop) - + req := BuildHotelPropDescRequest(sconf, prop) resp, err := CallHotelPropDesc(serverBadBody.URL, req) if err == nil { t.Error("Expected error making request to sserverBadBody") diff --git a/engine/hotelws/hotel_rate_desc.go b/engine/hotelws/hotel_rate_desc.go index d13674c..ef73f45 100644 --- a/engine/hotelws/hotel_rate_desc.go +++ b/engine/hotelws/hotel_rate_desc.go @@ -51,7 +51,7 @@ func SetHotelRateDescBody(rpc *RatePlanCandidates) (HotelRateDescBody, error) { } // BuildHotelRateDescRequest to make hotel property description request, done after hotel property description iff HRD_RequiredForSell=true. -func BuildHotelRateDescRequest(from, pcc, binsectoken, convid, mid, time string, body HotelRateDescBody) HotelRateDescRequest { +func BuildHotelRateDescRequest(c *srvc.SessionConf, body HotelRateDescBody) HotelRateDescRequest { return HotelRateDescRequest{ Envelope: srvc.CreateEnvelope(), Header: srvc.SessionHeader{ @@ -59,24 +59,24 @@ func BuildHotelRateDescRequest(from, pcc, binsectoken, convid, mid, time string, MustUnderstand: srvc.SabreMustUnderstand, EbVersion: srvc.SabreEBVersion, From: srvc.FromElem{ - PartyID: srvc.CreatePartyID(from, srvc.PartyIDTypeURN), + PartyID: srvc.CreatePartyID(c.From, srvc.PartyIDTypeURN), }, To: srvc.ToElem{ PartyID: srvc.CreatePartyID(srvc.SabreToBase, srvc.PartyIDTypeURN), }, - CPAID: pcc, - ConversationID: convid, + CPAID: c.PCC, + ConversationID: c.Convid, Service: srvc.ServiceElem{Value: "HotelRateDescriptionLLSRQ", Type: "sabreXML"}, Action: "HotelRateDescriptionLLSRQ", MessageData: srvc.MessageDataElem{ - MessageID: mid, - Timestamp: time, + MessageID: c.Msgid, + Timestamp: c.Timestr, }, }, Security: srvc.Security{ XMLNSWsseBase: srvc.BaseWsse, XMLNSWsu: srvc.BaseWsuNameSpace, - BinarySecurityToken: binsectoken, + BinarySecurityToken: c.Binsectok, }, }, Body: body, diff --git a/engine/hotelws/hotel_rate_desc_test.go b/engine/hotelws/hotel_rate_desc_test.go index c742d5c..fadb5d0 100644 --- a/engine/hotelws/hotel_rate_desc_test.go +++ b/engine/hotelws/hotel_rate_desc_test.go @@ -19,17 +19,14 @@ func TestHotelRateDescMarshal(t *testing.T) { if err != nil { t.Error("Error SetHotelRateDescRqStruct:", err) } - req := BuildHotelRateDescRequest(samplesite, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, rate) - + req := BuildHotelRateDescRequest(sconf, rate) b, err := xml.Marshal(req) if err != nil { t.Error("Error marshaling get hotel content", err) } - if string(b) != string(sampleHotelRateDescRQRPH) { t.Errorf("Expected marshal SOAP hotel rate description for rph \n sample: %s \n result: %s", string(sampleHotelRateDescRQRPH), string(b)) } - //fmt.Printf("content marshal \n%s\n", b) } var additionalCards = []string{"DS", "CA", "MC", "CB", "VI", "VS", "AX", "JC", "DC"} @@ -56,9 +53,7 @@ func TestRateDescCall(t *testing.T) { }, ) raterq, _ := SetHotelRateDescBody(rpc) - - req := BuildHotelRateDescRequest(samplesite, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, raterq) - + req := BuildHotelRateDescRequest(sconf, raterq) resp, err := CallHotelRateDesc(serverHotelRateDesc.URL, req) if err != nil { t.Error("Error making request CallHotelRateDesc", err) @@ -151,7 +146,7 @@ func TestHotelRateDesCallDown(t *testing.T) { }, ) raterq, _ := SetHotelRateDescBody(rpc) - req := BuildHotelRateDescRequest(samplesite, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, raterq) + req := BuildHotelRateDescRequest(sconf, raterq) resp, err := CallHotelRateDesc(serverHotelDown.URL, req) if err == nil { t.Error("Expected error making request to serverHotelDown") @@ -176,9 +171,7 @@ func TestHotelRateDescCallBadResponseBody(t *testing.T) { }, ) raterq, _ := SetHotelRateDescBody(rpc) - - req := BuildHotelRateDescRequest(samplesite, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, raterq) - + req := BuildHotelRateDescRequest(sconf, raterq) resp, err := CallHotelRateDesc(serverBadBody.URL, req) if err == nil { t.Error("Expected error making request to sserverBadBody") diff --git a/engine/hotelws/hotel_res.go b/engine/hotelws/hotel_res.go index cae98d3..cefbe2e 100644 --- a/engine/hotelws/hotel_res.go +++ b/engine/hotelws/hotel_res.go @@ -193,14 +193,14 @@ func (h *HotelRsrvBody) AddTimeSpan(timesp TimeSpan) { } // SetHotelResBody for basic payload, other functions can append optional data -func SetHotelResBody(units int) HotelRsrvBody { +func SetHotelResBody(units int, timestr string) HotelRsrvBody { return HotelRsrvBody{ OTAHotelResRQ: OTAHotelResRQ{ XMLNS: srvc.BaseWebServicesNS, XMLNSXs: srvc.BaseXSDNameSpace, XMLNSXsi: srvc.BaseXSINamespace, ReturnHostCommand: true, - TimeStamp: srvc.SabreTimeFormat(), + TimeStamp: timestr, Version: "2.2.0", Hotel: HotelRequest{ RoomType: RoomType{ @@ -212,7 +212,7 @@ func SetHotelResBody(units int) HotelRsrvBody { } // BuildHotelResRequest build request body for SOAP reservation service -func BuildHotelResRequest(from, pcc, binsectoken, convid, mid, time string, body HotelRsrvBody) HotelRsrvRequest { +func BuildHotelResRequest(c *srvc.SessionConf, body HotelRsrvBody) HotelRsrvRequest { return HotelRsrvRequest{ Envelope: srvc.CreateEnvelope(), Header: srvc.SessionHeader{ @@ -220,24 +220,24 @@ func BuildHotelResRequest(from, pcc, binsectoken, convid, mid, time string, body MustUnderstand: srvc.SabreMustUnderstand, EbVersion: srvc.SabreEBVersion, From: srvc.FromElem{ - PartyID: srvc.CreatePartyID(from, srvc.PartyIDTypeURN), + PartyID: srvc.CreatePartyID(c.From, srvc.PartyIDTypeURN), }, To: srvc.ToElem{ PartyID: srvc.CreatePartyID(srvc.SabreToBase, srvc.PartyIDTypeURN), }, - CPAID: pcc, - ConversationID: convid, + CPAID: c.PCC, + ConversationID: c.Convid, Service: srvc.ServiceElem{Value: "OTA_HotelRes", Type: "sabreXML"}, Action: "OTA_HotelResLLSRQ", MessageData: srvc.MessageDataElem{ - MessageID: mid, - Timestamp: time, + MessageID: c.Msgid, + Timestamp: c.Timestr, }, }, Security: srvc.Security{ XMLNSWsseBase: srvc.BaseWsse, XMLNSWsu: srvc.BaseWsuNameSpace, - BinarySecurityToken: binsectoken, + BinarySecurityToken: c.Binsectok, }, }, Body: body, diff --git a/engine/hotelws/hotel_res_test.go b/engine/hotelws/hotel_res_test.go index 7bc021b..41009bc 100644 --- a/engine/hotelws/hotel_res_test.go +++ b/engine/hotelws/hotel_res_test.go @@ -2,12 +2,11 @@ package hotelws import ( "encoding/xml" - "fmt" "testing" ) func TestHotelResSet(t *testing.T) { - body := SetHotelResBody(1) + body := SetHotelResBody(1, sconf.Timestr) body.NewPropertyResByRPH("12") body.NewGuaranteeRes("Testlast", "G", "MC", "2012-12", "1234567890") @@ -47,7 +46,7 @@ func TestHotelResSet(t *testing.T) { } func TestHotelResByHotel(t *testing.T) { - body := SetHotelResBody(1) + body := SetHotelResBody(1, sconf.Timestr) body.NewPropertyResByHotel("SL", "00004") b := body.OTAHotelResRQ if b.Hotel.BasicPropertyRes.ChainCode != "SL" { @@ -59,15 +58,15 @@ func TestHotelResByHotel(t *testing.T) { } func TestHotelResBuild(t *testing.T) { - body := SetHotelResBody(1) - //body.NewPropertyResByHotel("SL", "00004") - body.NewPropertyResByRPH("004") - body.NewGuaranteeRes("Testlast", "GDPST", "MC", "2012-12", "1234567890") - - req := BuildHotelResRequest(samplesite, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, body) + body := SetHotelResBody(1, sconf.Timestr) + body.NewPropertyResByRPH("007") + body.NewGuaranteeRes("Booking", "G", "MC", "2019-12", "5105105105105100") + req := BuildHotelResRequest(sconf, body) b, err := xml.Marshal(req) if err != nil { - t.Error("Error marshaling hotel resercation request", err) + t.Error("Error marshaling hotel reservation request", err) + } + if string(b) != string(sampleHotelResRQgood) { + t.Errorf("Expected marshal SOAP hotel reservation by RPH \n sample: '%s'\n result: '%s'\n", string(sampleHotelResRQgood), string(b)) } - fmt.Printf("%s\n", b) } diff --git a/engine/hotelws/hotelws_helper_test.go b/engine/hotelws/hotelws_helper_test.go index 0083a47..66bea52 100644 --- a/engine/hotelws/hotelws_helper_test.go +++ b/engine/hotelws/hotelws_helper_test.go @@ -3,6 +3,8 @@ package hotelws import ( "net/http" "net/http/httptest" + + "github.com/ailgroup/sbrweb/engine/srvc" ) /* @@ -113,6 +115,16 @@ var ( sampleconvid = "fds8789h|dev@z.com" samplemid = "mid:20180207-20:19:07.25|QVbg0" sampletime = "2018-02-16T07:18:42Z" + sconf = &srvc.SessionConf{ + From: samplesite, + PCC: samplepcc, + Convid: sampleconvid, + Msgid: samplemid, + Timestr: sampletime, + Binsectok: samplebinsectoken, + //Username: sampleusername, + //Password: samplepassword, + } ) var iataCharSample = []string{"P1KRAC", "D1KRAC", "L1KRAC", "P1KBRF", "T1KRAC", "P2TRAC", "K1KRAC", "L2TRAC", "E2DRAC", "E1KRAC", "D2DRAC", "C1KRAC", "U1QRAC", "A2TRAC", "N1KRAC", "N1QRAC"} @@ -1642,4 +1654,8 @@ var ( `) + + sampleHotelResRQgood = []byte(`www.z.comwebservices.sabre.com7TZAfds8789h|dev@z.comOTA_HotelResOTA_HotelResLLSRQmid:20180207-20:19:07.25|QVbg02018-02-16T07:18:42ZShared/IDL:IceSess\/SessMgr:1\.0.IDL/Common/!ICESMS\/RESE!ICESMSLB\/RES.LB!-3177016070087638144!110012!0Booking`) + + //sampleHotelResRSgood = []byte(``) ) diff --git a/engine/srvc/session_pool.go b/engine/srvc/session_pool.go index ece242a..f29f186 100644 --- a/engine/srvc/session_pool.go +++ b/engine/srvc/session_pool.go @@ -16,17 +16,6 @@ type Session struct { FaultError error } -// SessionConfig holds info to create and manage session -type SessionConfig struct { - from string - pcc string - convid string - mid string - timeStamp string - username string - password string -} - // ExpireScheme for when to expire sessions type ExpireScheme struct { Max int @@ -44,27 +33,19 @@ type SessionPool struct { NetworkErrors []error FaultErrors []error Expire ExpireScheme - Conf SessionConfig + Conf *SessionConf } // NewPool initializes a new pool with the given tasks and at the given // concurrency. -func NewPool(expire ExpireScheme, size int, serviceURL, from, pcc, convid, mid, timeStamp, username, password string) *SessionPool { +func NewPool(expire ExpireScheme, cred *SessionConf, size int) *SessionPool { return &SessionPool{ ConfigPoolSize: size, Sessions: make(chan Session, size), //buffered channel blocks! InitializedTime: time.Now(), - ServiceURL: serviceURL, + ServiceURL: cred.ServiceURL, Expire: expire, - Conf: SessionConfig{ - from: from, - pcc: pcc, - convid: convid, - mid: mid, - timeStamp: timeStamp, - username: username, - password: password, - }, + Conf: cred, } } @@ -92,7 +73,7 @@ func RandomInt(min, max int) int { } func (p *SessionPool) newSession() (Session, error) { - createRQ := BuildSessionCreateRequest(p.Conf.from, p.Conf.pcc, p.Conf.convid, p.Conf.mid, p.Conf.timeStamp, p.Conf.username, p.Conf.password) + createRQ := BuildSessionCreateRequest(p.Conf) createRS, err := CallSessionCreate(p.ServiceURL, createRQ) if err != nil { p.NetworkErrors = append(p.NetworkErrors, err) @@ -266,7 +247,7 @@ func generateKeepAliveID() string { func Keepalive(p *SessionPool, repeatEvery time.Duration) { //defer profile.Start(profile.MemProfile).Stop() //endAfter time.Duration - doneChan := time.NewTimer(time.Minute * 4).C + //doneChan := time.NewTimer(time.Minute * 15).C started := time.Now() keepAliveID := generateKeepAliveID() logSession.Println("Starting KEEPALIVE...", keepAliveID) @@ -279,9 +260,9 @@ func Keepalive(p *SessionPool, repeatEvery time.Duration) { p.RangeKeepalive(keepAliveID) logSession.Printf("KEEPALIVE run(InMin=%.2f, InHour=%.2f)", time.Since(started).Minutes(), time.Since(started).Hours()) p.logReport(keepAliveID + "-KeepAlive") - case <-doneChan: - logSession.Println("KEEPALIVE killed, total time:", time.Since(started)) - return + //case <-doneChan: + // logSession.Println("KEEPALIVE killed, total time:", time.Since(started)) + // return } } } diff --git a/engine/srvc/session_pool_test.go b/engine/srvc/session_pool_test.go index 23aa106..ed29df0 100644 --- a/engine/srvc/session_pool_test.go +++ b/engine/srvc/session_pool_test.go @@ -19,7 +19,8 @@ var ( func TestSessionPoolEmpty(t *testing.T) { poolSize := 3 - p := NewPool(sampleExpireScheme, poolSize, serverCreateRQ.URL, samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) + sampleSessionConf.ServiceURL = serverCreateRQ.URL + p := NewPool(sampleExpireScheme, sampleSessionConf, poolSize) if p.ServiceURL != serverCreateRQ.URL { t.Errorf("SessionPool.ServiceURL expect: %s, got: %s", serverCreateRQ.URL, p.ServiceURL) } @@ -40,7 +41,8 @@ func TestSessionPoolEmpty(t *testing.T) { // NOTE: this helps test case where we could not get any valid sessions, so we close the buffered channel to prevent indefinite blocking. func TestSessionPoolPopluateServerDown(t *testing.T) { poolSize := 3 - p := NewPool(sampleExpireScheme, poolSize, serverDown.URL, samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) + sampleSessionConf.ServiceURL = serverDown.URL + p := NewPool(sampleExpireScheme, sampleSessionConf, poolSize) p.Populate() if len(p.Sessions) != 0 { @@ -66,7 +68,8 @@ func TestSessionPoolPopluateServerDown(t *testing.T) { func TestSessionPoolPopluateBadBody(t *testing.T) { poolSize := 2 - p := NewPool(sampleExpireScheme, poolSize, serverBadBody.URL, samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) + sampleSessionConf.ServiceURL = serverBadBody.URL + p := NewPool(sampleExpireScheme, sampleSessionConf, poolSize) p.Populate() if len(p.NetworkErrors) <= 0 { t.Fail() @@ -78,13 +81,11 @@ func TestSessionPoolPopluateBadBody(t *testing.T) { func TestSessionPoolCloseOnDownServer(t *testing.T) { poolSize := 2 //must be a good server or no sessions and buffer blocks... - p := NewPool(sampleExpireScheme, poolSize, serverCreateRQ.URL, samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) + sampleSessionConf.ServiceURL = serverCreateRQ.URL + p := NewPool(sampleExpireScheme, sampleSessionConf, poolSize) p.Populate() - //reroute to unavailable server p.ServiceURL = serverDown.URL - //p.ServiceURL = serverBadBody.URL - p.Close() if len(p.NetworkErrors) <= 0 { t.Fail() @@ -93,7 +94,8 @@ func TestSessionPoolCloseOnDownServer(t *testing.T) { func TestSessionPoolPopluateUnauth(t *testing.T) { poolSize := 2 - p := NewPool(sampleExpireScheme, poolSize, serverCreateRSUnauth.URL, samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) + sampleSessionConf.ServiceURL = serverCreateRSUnauth.URL + p := NewPool(sampleExpireScheme, sampleSessionConf, poolSize) p.Populate() if len(p.NetworkErrors) != 0 { t.Fail() @@ -115,7 +117,8 @@ func TestSessionPoolPopluateUnauth(t *testing.T) { func TestSessionPoolCloseInvalidToken(t *testing.T) { poolSize := 2 - p := NewPool(sampleExpireScheme, poolSize, serverCreateRQ.URL, samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) + sampleSessionConf.ServiceURL = serverCreateRQ.URL + p := NewPool(sampleExpireScheme, sampleSessionConf, poolSize) p.Populate() if len(p.NetworkErrors) != 0 { t.Fail() @@ -123,11 +126,9 @@ func TestSessionPoolCloseInvalidToken(t *testing.T) { if p.AllowPoolSize != poolSize { t.Error("AllowPoolSize should be more than zero even with SOAP Fault") } - //reroute serivce to server with close invalid response... p.ServiceURL = serverCloseRSInvalid.URL p.Close() - if len(p.NetworkErrors) != 0 { t.Error("Network errors should not exist") } @@ -141,7 +142,8 @@ func TestSessionPoolCloseInvalidToken(t *testing.T) { func TestSessionPoolPopluatePickPut(t *testing.T) { poolSize := 5 - p := NewPool(sampleExpireScheme, poolSize, serverCreateRQ.URL, samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) + sampleSessionConf.ServiceURL = serverCreateRQ.URL + p := NewPool(sampleExpireScheme, sampleSessionConf, poolSize) p.Populate() if poolSize != p.AllowPoolSize { t.Errorf("given poolSize: %d should equal AllowPoolSize on Populate: %d", poolSize, p.AllowPoolSize) @@ -181,7 +183,8 @@ func TestSessionPoolPopluatePickPut(t *testing.T) { func TestSessionPoolBlocking(t *testing.T) { poolSize := 5 blockingSize := 3 - p := NewPool(sampleExpireScheme, poolSize, serverCreateRQ.URL, samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) + sampleSessionConf.ServiceURL = serverCreateRQ.URL + p := NewPool(sampleExpireScheme, sampleSessionConf, poolSize) p.Populate() sessions := []Session{} @@ -254,7 +257,7 @@ func TestSessionPoolBlocking(t *testing.T) { func TestSessionPoolSafeBlocking(t *testing.T) { poolSize := 3 - p := NewPool(sampleExpireScheme, poolSize, serverCreateRQ.URL, samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) + p := NewPool(sampleExpireScheme, sampleSessionConf, poolSize) if p.ConfigPoolSize == p.AllowPoolSize { t.Errorf("ConfigPoolSize: %d not equal AllowPoolSize: %d", p.ConfigPoolSize, p.AllowPoolSize) } diff --git a/engine/srvc/soap_base.go b/engine/srvc/soap_base.go index 243f93e..44e6bba 100644 --- a/engine/srvc/soap_base.go +++ b/engine/srvc/soap_base.go @@ -380,33 +380,52 @@ type SessionCreateRequest struct { Body SessionCreateRQBody } +type SessionConf struct { + ServiceURL string + From string + PCC string + Binsectok string + Convid string + Msgid string + Timestr string + Username string + Password string +} + +// SetTime updates the timestamp. Pass around SessionConf and update the timestamp for any new request +func (s *SessionConf) SetTime() *SessionConf { + s.Timestr = SabreTimeFormat() + return s +} + // BuildSessionCreateRequest build session create envelope for request // CPAID, Organization, and PseudoCityCode all use the PCC/iPCC. ConversationID is typically a contact email address with unique identifier to the request. MessageID is typically a timestamped identifier to locate specific queries: it should contai a company identifier. -func BuildSessionCreateRequest(from, pcc, convid, mid, time, username, password string) SessionCreateRequest { +//func BuildSessionCreateRequest(from, pcc, convid, mid, time, username, password string) SessionCreateRequest { +func BuildSessionCreateRequest(c *SessionConf) SessionCreateRequest { return SessionCreateRequest{ Envelope: CreateEnvelope(), Header: SessionHeader{ MessageHeader: MessageHeader{ MustUnderstand: SabreMustUnderstand, EbVersion: SabreEBVersion, - From: FromElem{PartyID: CreatePartyID(from, PartyIDTypeURN)}, + From: FromElem{PartyID: CreatePartyID(c.From, PartyIDTypeURN)}, To: ToElem{PartyID: CreatePartyID(SabreToBase, PartyIDTypeURN)}, - CPAID: pcc, - ConversationID: convid, + CPAID: c.PCC, + ConversationID: c.Convid, Service: ServiceElem{"SessionCreateRQ", "OTA"}, Action: "SessionCreateRQ", MessageData: MessageDataElem{ - MessageID: mid, - Timestamp: time, + MessageID: c.Msgid, + Timestamp: c.Timestr, }, }, Security: Security{ XMLNSWsseBase: BaseWsse, XMLNSWsu: BaseWsuNameSpace, UserNameToken: &UsernameTokenElem{ - Username: username, - Password: password, - Organization: pcc, + Username: c.Username, + Password: c.Password, + Organization: c.PCC, Domain: sabreDefaultDomain, }, }, @@ -417,7 +436,7 @@ func BuildSessionCreateRequest(from, pcc, convid, mid, time, username, password XMLNS: baseOTANameSpace, POS: POSElem{ Source: SourceElem{ - PseudoCityCode: pcc, + PseudoCityCode: c.PCC, }, }, }, @@ -440,6 +459,11 @@ type SessionCreateResponse struct { } } +func (s *SessionCreateResponse) ParseBinSecToken(c SessionConf) SessionConf { + c.Binsectok = s.Header.Security.BinarySecurityToken.Value + return c +} + // CallSessionCreate to sabre web services. func CallSessionCreate(serviceURL string, req SessionCreateRequest) (SessionCreateResponse, error) { sessionResponse := SessionCreateResponse{} diff --git a/engine/srvc/soap_base_test.go b/engine/srvc/soap_base_test.go index 73d4467..702ccf8 100644 --- a/engine/srvc/soap_base_test.go +++ b/engine/srvc/soap_base_test.go @@ -22,6 +22,20 @@ import ( ) var ( + //serverDown mocks an unreachable service + serverDown = &httptest.Server{} + //serverBadBody mocks a server that returns malformed body + serverBadBody = &httptest.Server{} + //serverCreateRSUnauth for testing session create not authorized + serverCreateRSUnauth = &httptest.Server{} + //serverCloseRSInvalid for testing session create not authorized + serverCloseRSInvalid = &httptest.Server{} + //serverCreateRQ for testing session create + serverCreateRQ = &httptest.Server{} + //serverCloseRQ for testing session close + serverCloseRQ = &httptest.Server{} + //serverValidateRQ for testing session validate + serverValidateRQ = &httptest.Server{} samplerandStr = regexp.MustCompile(`\w*`) samplerfc333pString = "2017-11-27T09:58:31Z" samplerfc333pReg = regexp.MustCompile(`\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z`) @@ -38,11 +52,17 @@ var ( sampleusername = "773400" samplepassword = "PASSWORD_GOES_HER" sampledomain = "DEFAULT" - - samplebinsectoken = string([]byte(`Shared/IDL:IceSess\/SessMgr:1\.0.IDL/Common/!ICESMS\/RESE!ICESMSLB\/RES.LB!-3177016070087638144!110012!0`)) - - samplebintokensplit = "-3177016070087638144!110012!0" - + sampleSessionConf = &SessionConf{ + From: samplefrom, + PCC: samplepcc, + Convid: sampleconvid, + Msgid: samplemid, + Timestr: sampletime, + Username: sampleusername, + Password: samplepassword, + } + samplebinsectoken = string([]byte(`Shared/IDL:IceSess\/SessMgr:1\.0.IDL/Common/!ICESMS\/RESE!ICESMSLB\/RES.LB!-3177016070087638144!110012!0`)) + samplebintokensplit = "-3177016070087638144!110012!0" sampleSessionNoAuthFaultCode = "soap-env:Client.AuthenticationFailed" sampleSessionNoAuthFaultString = " Authentication failed " sampleSessionNoAuthStackTrace = "com.sabre.universalservices.base.security.AuthenticationException: errors.authentication.USG_AUTHENTICATION_FAILED" @@ -96,6 +116,75 @@ var ( webservices.sabre.comwww.z.com7TZAfds8789h|dev@z.comSessionValidateRQErrorRS42158374688379005532018-02-16T07:18:42Zmid:20180216-07:18:42.3|14oUaShared/IDL:IceSess\/SessMgr:1\.0.IDL/Common/!ICESMS\/RESE!ICESMSLB\/RES.LB!-3177016070087638144!110012!0soap-env:Client.InvalidSecurityTokenInvalid or Expired binary security token: Shared/IDL:IceSess\/SessMgr:1\.0.IDL/Common/!ICESMS\/RESE!ICESMSLB\/RES.LB!-3177016070087638144!110012!0com.sabre.universalservices.base.session.SessionException: errors.session.USG_INVALID_SECURITY_TOKEN`) ) +//Initialize Mock Sabre Web Servers +func init() { + serverDown = httptest.NewServer( + http.HandlerFunc( + func(rs http.ResponseWriter, rq *http.Request) { + //rs.WriteHeader(500) + }, + )) + serverDown.Close() + + serverBadBody = httptest.NewServer( + http.HandlerFunc( + func(rs http.ResponseWriter, rq *http.Request) { + //rs.Header() + //rs.WriteHeader(500) + //rs.Write(sampleBadBody) + rs.Write([]byte(`!#BAD_/_BODY_.*__\\fhji(*&^%^%$%^&Y*(J)OPKL:`)) + }, + ), + ) + //defer func() { serverBadBody.Close() }() + + serverCreateRQ = httptest.NewServer( + http.HandlerFunc( + func(rs http.ResponseWriter, rq *http.Request) { + rs.Write(sampleSessionSuccessResponse) + }, + ), + ) + //defer func() { serverCreateRQ.Close() }() + + serverCreateRSUnauth = httptest.NewServer( + http.HandlerFunc( + func(rs http.ResponseWriter, rq *http.Request) { + rs.Write(sampleSessionUnAuth) + }, + ), + ) + //defer func() { serverCreateRSInvalid.Close() }() + + serverCloseRQ = httptest.NewServer( + http.HandlerFunc( + func(rs http.ResponseWriter, rq *http.Request) { + rs.Write(sampleSessionCloseRespSuccess) + }, + ), + ) + //defer func() { serverCloseRQ.Close() }() + + serverCloseRSInvalid = httptest.NewServer( + http.HandlerFunc( + func(rs http.ResponseWriter, rq *http.Request) { + rs.Write(sampleSessionCloseRespNoValidToken) + }, + ), + ) + //defer func() { serverCloseRSInvalid.Close() }() + + serverValidateRQ = httptest.NewServer( + http.HandlerFunc( + func(rs http.ResponseWriter, rq *http.Request) { + rs.Write(sampleSessionValidateRespSuccess) + }, + ), + ) + //defer func() { serverValidateRQ.Close() }() + +} + func TestLogSetup(t *testing.T) { setUpLogging() if _, err := os.Stat("sabre_web_soap.log"); os.IsNotExist(err) { @@ -176,6 +265,15 @@ func BenchmarkTimeFormat(b *testing.B) { } } +func TestSessionConfSetTime(t *testing.T) { + conf := &SessionConf{ + Timestr: sampletime, + } + if conf.SetTime().Timestr != SabreTimeFormat() { + t.Error("SessionConf SetTime() should be SabreTimeFormat()") + } +} + var randstrtests = []struct { size int }{ @@ -674,7 +772,7 @@ func BenchmarkSessionCreateRequestUnmarshal(b *testing.B) { } func TestBuildSessionCreateRequest(t *testing.T) { - sess := BuildSessionCreateRequest(samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) + sess := BuildSessionCreateRequest(sampleSessionConf) if sess.Header.MessageHeader.From.PartyID.Value != samplefrom { t.Errorf("Header.MessageHeader.From.PartyID.Value expect: %s, got %s", samplefrom, sess.Header.MessageHeader.From.PartyID.Value) @@ -691,12 +789,11 @@ func TestBuildSessionCreateRequest(t *testing.T) { } func BenchmarkBuildSessionCreateRequest(b *testing.B) { for n := 0; n < b.N; n++ { - BuildSessionCreateRequest(samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) + BuildSessionCreateRequest(sampleSessionConf) } } func TestBuildSessionCreateRequestMarshal(t *testing.T) { - sess := BuildSessionCreateRequest(samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) - + sess := BuildSessionCreateRequest(sampleSessionConf) b, err := xml.Marshal(&sess) if err != nil { t.Error("Error marshalling session envelope", err) @@ -704,11 +801,9 @@ func TestBuildSessionCreateRequestMarshal(t *testing.T) { if string(b) != string(sampleSessionEnvelopeWithValues) { t.Error("Session envelope with values does not match test sample") } - //fmt.Printf("SAMPLE: %v\n", string(sampleSessionEnvelopeWithValues)) - //fmt.Printf("CURREN: %v\n", string(b)) } func BenchmarkBuildSessionCreateRequestMarshal(b *testing.B) { - s := BuildSessionCreateRequest(samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) + s := BuildSessionCreateRequest(sampleSessionConf) for n := 0; n < b.N; n++ { xml.Marshal(&s) } @@ -743,8 +838,6 @@ func TestSessionCreateResponse(t *testing.T) { if resp.Body.SessionCreateRS.Status != "Approved" { t.Errorf("resp.SessionRSBody.SessionCreateRS.Status expect %s, got %s", "Approved", resp.Body.SessionCreateRS.Status) } - //fmt.Printf("SAMPLE: %s\n\n", sampleSessionSuccessResponse) - //fmt.Printf("CURRENT: %+v\n", resp) } func TestSessionCreateResponseUnAuth(t *testing.T) { @@ -984,92 +1077,6 @@ func BenchmarkBuildSessionValidateRequestMarshal(b *testing.B) { } } -var ( - //serverDown mocks an unreachable service - serverDown = &httptest.Server{} - //serverBadBody mocks a server that returns malformed body - serverBadBody = &httptest.Server{} - //serverCreateRSUnauth for testing session create not authorized - serverCreateRSUnauth = &httptest.Server{} - //serverCloseRSInvalid for testing session create not authorized - serverCloseRSInvalid = &httptest.Server{} - //serverCreateRQ for testing session create - serverCreateRQ = &httptest.Server{} - //serverCloseRQ for testing session close - serverCloseRQ = &httptest.Server{} - //serverValidateRQ for testing session validate - serverValidateRQ = &httptest.Server{} -) - -//Initialize Mock Sabre Web Servers -func init() { - serverDown = httptest.NewServer( - http.HandlerFunc( - func(rs http.ResponseWriter, rq *http.Request) { - //rs.WriteHeader(500) - }, - )) - serverDown.Close() - - serverBadBody = httptest.NewServer( - http.HandlerFunc( - func(rs http.ResponseWriter, rq *http.Request) { - //rs.Header() - //rs.WriteHeader(500) - //rs.Write(sampleBadBody) - rs.Write([]byte(`!#BAD_/_BODY_.*__\\fhji(*&^%^%$%^&Y*(J)OPKL:`)) - }, - ), - ) - //defer func() { serverBadBody.Close() }() - - serverCreateRQ = httptest.NewServer( - http.HandlerFunc( - func(rs http.ResponseWriter, rq *http.Request) { - rs.Write(sampleSessionSuccessResponse) - }, - ), - ) - //defer func() { serverCreateRQ.Close() }() - - serverCreateRSUnauth = httptest.NewServer( - http.HandlerFunc( - func(rs http.ResponseWriter, rq *http.Request) { - rs.Write(sampleSessionUnAuth) - }, - ), - ) - //defer func() { serverCreateRSInvalid.Close() }() - - serverCloseRQ = httptest.NewServer( - http.HandlerFunc( - func(rs http.ResponseWriter, rq *http.Request) { - rs.Write(sampleSessionCloseRespSuccess) - }, - ), - ) - //defer func() { serverCloseRQ.Close() }() - - serverCloseRSInvalid = httptest.NewServer( - http.HandlerFunc( - func(rs http.ResponseWriter, rq *http.Request) { - rs.Write(sampleSessionCloseRespNoValidToken) - }, - ), - ) - //defer func() { serverCloseRSInvalid.Close() }() - - serverValidateRQ = httptest.NewServer( - http.HandlerFunc( - func(rs http.ResponseWriter, rq *http.Request) { - rs.Write(sampleSessionValidateRespSuccess) - }, - ), - ) - //defer func() { serverValidateRQ.Close() }() - -} - func TestCallSessionCreateServiceNoExist(t *testing.T) { resp, err := CallSessionCreate(serverDown.URL, SessionCreateRequest{}) if err == nil { @@ -1158,7 +1165,8 @@ func TestCallSessionValidateBadBody(t *testing.T) { func TestCallSessionCreateSuccess(t *testing.T) { //Mock Sabre Web Services - req := BuildSessionCreateRequest(samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) + req := BuildSessionCreateRequest(sampleSessionConf) + //req := BuildSessionCreateRequest(samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) resp, err := CallSessionCreate(serverCreateRQ.URL, req) if err != nil { t.Error("Error making request CallSessionCreate", err) @@ -1187,7 +1195,8 @@ func TestCallSessionCreateSuccess(t *testing.T) { } } func BenchmarkCallSessionCreate(b *testing.B) { - req := BuildSessionCreateRequest(samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) + req := BuildSessionCreateRequest(sampleSessionConf) + //req := BuildSessionCreateRequest(samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) for n := 0; n < b.N; n++ { CallSessionCreate(serverCreateRQ.URL, req) } From d7df5fc50020c936b3bcacf3a2aa0d304d8ea626 Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Sun, 24 Jun 2018 08:27:27 -0600 Subject: [PATCH 06/21] refactor itin for session config --- engine/itin/pnr_details.go | 14 +++++++------- engine/itin/pnr_details_test.go | 18 +++++------------- engine/itin/pnr_helper_test.go | 10 ++++++++++ engine/srvc/session_pool.go | 9 +++------ 4 files changed, 25 insertions(+), 26 deletions(-) diff --git a/engine/itin/pnr_details.go b/engine/itin/pnr_details.go index 4cf1a60..cc43025 100644 --- a/engine/itin/pnr_details.go +++ b/engine/itin/pnr_details.go @@ -221,7 +221,7 @@ func SetPNRDetailBody(phone string, person PersonName) PassengerDetailBody { } // BuildPNRDetailsRequest passenger details for booking -func BuildPNRDetailsRequest(from, pcc, binsectoken, convid, mid, time string, body PassengerDetailBody) PNRDetailsRequest { +func BuildPNRDetailsRequest(c *srvc.SessionConf, body PassengerDetailBody) PNRDetailsRequest { return PNRDetailsRequest{ Envelope: srvc.CreateEnvelope(), Header: srvc.SessionHeader{ @@ -229,24 +229,24 @@ func BuildPNRDetailsRequest(from, pcc, binsectoken, convid, mid, time string, bo MustUnderstand: srvc.SabreMustUnderstand, EbVersion: srvc.SabreEBVersion, From: srvc.FromElem{ - PartyID: srvc.CreatePartyID(from, srvc.PartyIDTypeURN), + PartyID: srvc.CreatePartyID(c.From, srvc.PartyIDTypeURN), }, To: srvc.ToElem{ PartyID: srvc.CreatePartyID(srvc.SabreToBase, srvc.PartyIDTypeURN), }, - CPAID: pcc, - ConversationID: convid, + CPAID: c.PCC, + ConversationID: c.Convid, Service: srvc.ServiceElem{Value: "PassengerDetailsRQ", Type: "sabreXML"}, Action: "PassengerDetailsRQ", MessageData: srvc.MessageDataElem{ - MessageID: mid, - Timestamp: time, + MessageID: c.Msgid, + Timestamp: c.Timestr, }, }, Security: srvc.Security{ XMLNSWsseBase: srvc.BaseWsse, XMLNSWsu: srvc.BaseWsuNameSpace, - BinarySecurityToken: binsectoken, + BinarySecurityToken: c.Binsectok, }, }, Body: body, diff --git a/engine/itin/pnr_details_test.go b/engine/itin/pnr_details_test.go index 3c17cab..7d2d8bf 100644 --- a/engine/itin/pnr_details_test.go +++ b/engine/itin/pnr_details_test.go @@ -61,8 +61,7 @@ func TestPNRSet(t *testing.T) { func TestPNRBuildMarshal(t *testing.T) { p := CreatePersonName(sampleFirstName, sampleLastName) body := SetPNRDetailBody(samplePhoneReq, p) - req := BuildPNRDetailsRequest(samplefrom, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, body) - + req := BuildPNRDetailsRequest(sampleConf, body) b, err := xml.Marshal(req) if err != nil { t.Error("Error marshaling passenger details request", err) @@ -74,13 +73,11 @@ func TestPNRBuildMarshal(t *testing.T) { func TestPNRDetailCall(t *testing.T) { body := SetPNRDetailBody(samplePhoneReq, CreatePersonName(sampleFirstName, sampleLastName)) - req := BuildPNRDetailsRequest(samplefrom, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, body) - + req := BuildPNRDetailsRequest(sampleConf, body) resp, err := CallPNRDetail(serverPNRDetails.URL, req) if err != nil { t.Error("Error making request CallPNRDetailsRequest", err) } - appres := resp.Body.PassengerDetailsRS.AppResults if appres.Status != "Complete" { t.Errorf("AppResults.Status expect: %s, got %s", "Complete", appres.Status) @@ -91,7 +88,6 @@ func TestPNRDetailCall(t *testing.T) { if len(appres.Warnings) != 0 { t.Errorf("AppResults.Warnings expect: %d, got %d", 0, len(appres.Warnings)) } - travelItin := resp.Body.PassengerDetailsRS.TravelItineraryReadRS.TravelItinerary customer := travelItin.Customer @@ -108,7 +104,6 @@ func TestPNRDetailCall(t *testing.T) { if numbersOne.RPH != 1 { t.Errorf("customer.ContactNumbers[0].RPH expect: %d, got %d", 1, numbersOne.RPH) } - person := customer.PersonName if person.WithInfant { t.Error("PersonName.WithInfant should be false") @@ -151,8 +146,7 @@ func TestPNRDetailCall(t *testing.T) { func TestPNRDetailCallWarn(t *testing.T) { body := SetPNRDetailBody(samplePhoneReq, CreatePersonName(sampleFirstName, sampleLastName)) - req := BuildPNRDetailsRequest(samplefrom, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, body) - + req := BuildPNRDetailsRequest(sampleConf, body) resp, err := CallPNRDetail(serverBizLogic.URL, req) if err == nil { t.Error("CallPNRDetailsRequest Should have errors", err) @@ -172,9 +166,8 @@ func TestPNRDetailCallWarn(t *testing.T) { func TestPNRCallBadBodyResponseBody(t *testing.T) { p := CreatePersonName(sampleFirstName, sampleLastName) body := SetPNRDetailBody(samplePhoneReq, p) - req := BuildPNRDetailsRequest(samplefrom, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, body) + req := BuildPNRDetailsRequest(sampleConf, body) resp, err := CallPNRDetail(serverBadBody.URL, req) - if err == nil { t.Error("Expected error making request to serverBadBody") } @@ -191,9 +184,8 @@ func TestPNRCallBadBodyResponseBody(t *testing.T) { func TestPNRDetailsCallDown(t *testing.T) { body := SetPNRDetailBody(samplePhoneReq, CreatePersonName(sampleFirstName, sampleLastName)) - req := BuildPNRDetailsRequest(samplefrom, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime, body) + req := BuildPNRDetailsRequest(sampleConf, body) resp, err := CallPNRDetail(serverDown.URL, req) - if err == nil { t.Error("Expected error making request to serverHotelDown") } diff --git a/engine/itin/pnr_helper_test.go b/engine/itin/pnr_helper_test.go index 1a6a429..c10d6c2 100644 --- a/engine/itin/pnr_helper_test.go +++ b/engine/itin/pnr_helper_test.go @@ -3,6 +3,8 @@ package itin import ( "net/http" "net/http/httptest" + + "github.com/ailgroup/sbrweb/engine/srvc" ) var ( @@ -70,6 +72,14 @@ var ( sampleLastName = "Babbage" samplePhoneRes = "123-456-7890-H.1.1" samplePhoneReq = "123-456-7890" + sampleConf = &srvc.SessionConf{ + From: samplefrom, + PCC: samplepcc, + Convid: sampleconvid, + Msgid: samplemid, + Binsectok: samplebinsectoken, + Timestr: sampletime, + } ) var ( diff --git a/engine/srvc/session_pool.go b/engine/srvc/session_pool.go index f29f186..4e933b1 100644 --- a/engine/srvc/session_pool.go +++ b/engine/srvc/session_pool.go @@ -246,13 +246,10 @@ func generateKeepAliveID() string { //Keepalive sessions by RangeKeepalive over all sessions in pool func Keepalive(p *SessionPool, repeatEvery time.Duration) { //defer profile.Start(profile.MemProfile).Stop() - //endAfter time.Duration - //doneChan := time.NewTimer(time.Minute * 15).C started := time.Now() keepAliveID := generateKeepAliveID() logSession.Println("Starting KEEPALIVE...", keepAliveID) - //keep validating until exhaust endAfter for { select { case <-time.After(repeatEvery): @@ -260,9 +257,9 @@ func Keepalive(p *SessionPool, repeatEvery time.Duration) { p.RangeKeepalive(keepAliveID) logSession.Printf("KEEPALIVE run(InMin=%.2f, InHour=%.2f)", time.Since(started).Minutes(), time.Since(started).Hours()) p.logReport(keepAliveID + "-KeepAlive") - //case <-doneChan: - // logSession.Println("KEEPALIVE killed, total time:", time.Since(started)) - // return + default: + logSession.Println("KEEPALIVE killed, total time:", time.Since(started)) + return } } } From 7fb1c150e9e4e72a80096f04c8bc8a0da34c39be Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Sun, 24 Jun 2018 10:03:01 -0600 Subject: [PATCH 07/21] Client config handling, update session close for config --- client/app/handlers.go | 9 ++++- client/app/server.go | 3 +- client/config.toml | 2 +- client/main.go | 71 ++++++++++++++++++----------------- engine/srvc/session_pool.go | 14 ++----- engine/srvc/soap_base.go | 16 ++++---- engine/srvc/soap_base_test.go | 40 +++++++------------- 7 files changed, 73 insertions(+), 82 deletions(-) diff --git a/client/app/handlers.go b/client/app/handlers.go index 91426b9..9ed0ec9 100644 --- a/client/app/handlers.go +++ b/client/app/handlers.go @@ -7,10 +7,17 @@ import ( // HotelAvailHandler wraps SOAP call to sabre hotel availability service func (s *Server) HotelAvailHandler() http.HandlerFunc { - msg := "this" + //msg := "this" return func(w http.ResponseWriter, r *http.Request) { sess := s.SessionPool.Pick() defer s.SessionPool.Put(sess) + msg := fmt.Sprintf("\nID: %v\n Expire: %v\n Started: %v\n Validated: %v\n Status: %v\n", + sess.ID, + sess.ExpireTime, + sess.TimeStarted, + sess.TimeValidated, + sess.Sabre.Body.SessionCreateRS.Status, + ) //1 way: //param1 := r.URL.Query().Get("param1") diff --git a/client/app/server.go b/client/app/server.go index 73ab471..2ff23f8 100644 --- a/client/app/server.go +++ b/client/app/server.go @@ -9,7 +9,8 @@ import ( type Server struct { Mux *chi.Mux SessionPool *srvc.SessionPool - Config *viper.Viper + VConfig *viper.Viper + SConfig *srvc.SessionConf //DStore datastore.Datastore //FStore *sessions.FilesystemStore //logging... diff --git a/client/config.toml b/client/config.toml index 1089006..7374628 100644 --- a/client/config.toml +++ b/client/config.toml @@ -15,7 +15,7 @@ version = 0.1 sabre_url = "https://webservices.sabre.com/websvc/" [sessions.client] url = "www.z.com" - pool_size = 10 + pool_size = 3 [sessions.expire] # min/max are in minutes diff --git a/client/main.go b/client/main.go index 46b6d33..2037244 100644 --- a/client/main.go +++ b/client/main.go @@ -29,62 +29,63 @@ const ( var ( vRepeatEvery = time.Minute * 3 - //vEndAfter = time.Minute * 6 + sessConf = &srvc.SessionConf{} + vipConf = &viper.Viper{} ) +func init() { + vipConf = setConfig() + sessConf = &srvc.SessionConf{ + ServiceURL: vipConf.GetString(ConfSabreURL), + From: vipConf.GetString(ConfClientURL), + PCC: vipConf.GetString(ConfSabrePCC), + Convid: srvc.GenerateConversationID(vipConf.GetString(ConfClientURL)), + Msgid: srvc.GenerateMessageID(), + Timestr: srvc.SabreTimeFormat(), + Username: vipConf.GetString(ConfSabreUsername), + Password: vipConf.GetString(ConfSabrePassword), + } +} + func setConfig() *viper.Viper { - conf := viper.GetViper() + vip := viper.GetViper() - conf.SetConfigName(ConfFile) - conf.AddConfigPath("$HOME") - conf.AddConfigPath(".") - conf.BindEnv(ConfSabreUsername) - conf.BindEnv(ConfSabrePassword) - conf.BindEnv(ConfSabrePCC) + vip.SetConfigName(ConfFile) + vip.AddConfigPath("$HOME") + vip.AddConfigPath(".") + vip.BindEnv(ConfSabreUsername) + vip.BindEnv(ConfSabrePassword) + vip.BindEnv(ConfSabrePCC) - err := conf.ReadInConfig() + err := vip.ReadInConfig() if err != nil { panic(fmt.Errorf("Fatal error config file: %s \n", err)) } - conf.WatchConfig() - conf.OnConfigChange(func(e fsnotify.Event) { + vip.WatchConfig() + vip.OnConfigChange(func(e fsnotify.Event) { fmt.Println("Config file changed:", e.Name) }) - return conf + return vip } func main() { - c := setConfig() + // create scheme for session expirey scheme := srvc.ExpireScheme{ - Min: c.GetInt(ConfExpireMin), - Max: c.GetInt(ConfExpireMax), + Min: vipConf.GetInt(ConfExpireMin), + Max: vipConf.GetInt(ConfExpireMax), } - p := srvc.NewPool( - scheme, - c.GetInt(ConfPoolSize), - c.GetString(ConfSabreURL), - c.GetString(ConfClientURL), - c.GetString(ConfSabrePCC), - srvc.GenerateConversationID(c.GetString(ConfClientURL)), - srvc.GenerateMessageID(), - srvc.SabreTimeFormat(), - c.GetString(ConfSabreUsername), - c.GetString(ConfSabrePassword), - ) - // background populating the pool - // and setting up the teh keepvalid worker + // create pool, background populate pool and sett up keepalive + p := srvc.NewPool(scheme, sessConf, vipConf.GetInt(ConfPoolSize)) go func() { err := p.Populate() if err != nil { panic(err) } - //srvc.Keepalive(p, vRepeatEvery, vEndAfter) srvc.Keepalive(p, vRepeatEvery) }() - // close down session pool properly, validates against - // sabre and re-allocates on sabre side + // close down session pool properly against sabre re-allocates useable sessiosn cls := make(chan os.Signal) signal.Notify(cls, os.Interrupt) go func() { @@ -94,7 +95,7 @@ func main() { os.Exit(1) }() - // deal with context?? + // pass context through handlers?? m := chi.NewRouter() //Logger Logs the start and end of each request with the elapsed processing time m.Use(middleware.Logger) @@ -102,10 +103,12 @@ func main() { m.Use(middleware.Heartbeat("/heartbeat")) server := app.Server{ - Config: c, + VConfig: vipConf, + SConfig: sessConf, Mux: m, SessionPool: p, } server.RegisterRoutes() + fmt.Println("Begin on port:", ":8080") http.ListenAndServe(":8080", m) } diff --git a/engine/srvc/session_pool.go b/engine/srvc/session_pool.go index 4e933b1..8aad3a8 100644 --- a/engine/srvc/session_pool.go +++ b/engine/srvc/session_pool.go @@ -100,8 +100,7 @@ func (p *SessionPool) newSession() (Session, error) { TimeStarted: now, TimeValidated: now, ExpireTime: now.Add(time.Minute * time.Duration(RandomInt(p.Expire.Min, p.Expire.Max))), - //ExpireTime: now.Add(time.Minute * time.Duration(1)), - FaultError: faultErr, + FaultError: faultErr, } logSession.Printf( "Status='%s' created session ID=%s with Expirey='%s' for token=%s", sess.Sabre.Body.SessionCreateRS.Status, @@ -268,15 +267,8 @@ func Keepalive(p *SessionPool, repeatEvery time.Duration) { func (p *SessionPool) Close() { networkErrors := []error{} faultErrors := []error{} - for createRS := range p.Sessions { - closeRQ := BuildSessionCloseRequest( - createRS.Sabre.Header.MessageHeader.To.PartyID.Value, - createRS.Sabre.Header.MessageHeader.CPAID, - createRS.Sabre.Header.Security.BinarySecurityToken.Value, - createRS.Sabre.Header.MessageHeader.ConversationID, - createRS.Sabre.Header.MessageHeader.MessageData.RefToMessageID, - SabreTimeFormat(), - ) + for range p.Sessions { + closeRQ := BuildSessionCloseRequest(p.Conf) closeRS, err := CallSessionClose(p.ServiceURL, closeRQ) if err != nil { diff --git a/engine/srvc/soap_base.go b/engine/srvc/soap_base.go index 44e6bba..d073dff 100644 --- a/engine/srvc/soap_base.go +++ b/engine/srvc/soap_base.go @@ -511,35 +511,35 @@ type SessionCloseRequest struct { // BuildSessionCloseRequest build session Close envelope for request. // CPAID, Organization, and PseudoCityCode all use the PCC/iPCC. ConversationID, MessageID, BinarySecurityToken must be from the existing session you wish to close. -func BuildSessionCloseRequest(from, pcc, binsectoken, convid, mid, time string) SessionCloseRequest { +func BuildSessionCloseRequest(c *SessionConf) SessionCloseRequest { return SessionCloseRequest{ Envelope: CreateEnvelope(), Header: SessionHeader{ MessageHeader: MessageHeader{ MustUnderstand: SabreMustUnderstand, EbVersion: SabreEBVersion, - From: FromElem{PartyID: CreatePartyID(from, PartyIDTypeURN)}, + From: FromElem{PartyID: CreatePartyID(c.From, PartyIDTypeURN)}, To: ToElem{PartyID: CreatePartyID(SabreToBase, PartyIDTypeURN)}, - CPAID: pcc, - ConversationID: convid, + CPAID: c.PCC, + ConversationID: c.Convid, Service: ServiceElem{"SessionCloseRQ", "OTA"}, Action: "SessionCloseRQ", MessageData: MessageDataElem{ - MessageID: mid, - Timestamp: time, + MessageID: c.Msgid, + Timestamp: c.Timestr, }, }, Security: Security{ XMLNSWsseBase: BaseWsse, XMLNSWsu: BaseWsuNameSpace, - BinarySecurityToken: binsectoken, + BinarySecurityToken: c.Binsectok, }, }, Body: SessionCloseRQBody{ SessionCloseRQ: SessionCloseRQ{ POS: POSElem{ Source: SourceElem{ - PseudoCityCode: pcc, + PseudoCityCode: c.PCC, }, }, }, diff --git a/engine/srvc/soap_base_test.go b/engine/srvc/soap_base_test.go index 702ccf8..05b90b8 100644 --- a/engine/srvc/soap_base_test.go +++ b/engine/srvc/soap_base_test.go @@ -53,13 +53,14 @@ var ( samplepassword = "PASSWORD_GOES_HER" sampledomain = "DEFAULT" sampleSessionConf = &SessionConf{ - From: samplefrom, - PCC: samplepcc, - Convid: sampleconvid, - Msgid: samplemid, - Timestr: sampletime, - Username: sampleusername, - Password: samplepassword, + From: samplefrom, + PCC: samplepcc, + Convid: sampleconvid, + Msgid: samplemid, + Timestr: sampletime, + Username: sampleusername, + Password: samplepassword, + Binsectok: samplebinsectoken, } samplebinsectoken = string([]byte(`Shared/IDL:IceSess\/SessMgr:1\.0.IDL/Common/!ICESMS\/RESE!ICESMSLB\/RES.LB!-3177016070087638144!110012!0`)) samplebintokensplit = "-3177016070087638144!110012!0" @@ -941,23 +942,22 @@ func TestSessionCloseRequest(t *testing.T) { } func TestBuildSessionCloseRequestMarshal(t *testing.T) { - close := BuildSessionCloseRequest(samplefrom, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime) - + close := BuildSessionCloseRequest(sampleSessionConf) b, err := xml.Marshal(&close) if err != nil { t.Error("Error marshalling session envelope", err) } if string(b) != string(sampleSessionCloseRQ) { - t.Error("Session envelope with values does not match test sample") + t.Errorf("Close request marshal \n sample: %s \n result: %s", string(sampleSessionCloseRQ), string(b)) } } func BenchmarkBuildSessionCloseRequest(b *testing.B) { for n := 0; n < b.N; n++ { - BuildSessionCloseRequest(samplefrom, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime) + BuildSessionCloseRequest(sampleSessionConf) } } func BenchmarkBuildSessionCloseRequestMarshal(b *testing.B) { - close := BuildSessionCloseRequest(samplefrom, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime) + close := BuildSessionCloseRequest(sampleSessionConf) for n := 0; n < b.N; n++ { xml.Marshal(&close) } @@ -976,8 +976,6 @@ func TestSessionCloseResponse(t *testing.T) { if resp.Body.Fault.String != "" { t.Errorf("Body.Fault.String expect empty: '%s', got: %s", "", resp.Body.Fault.String) } - //fmt.Printf("SAMPLE: %s\n\n", sampleSessionCloseRespSuccess) - //fmt.Printf("CURREN: %+v\n", resp) } func TestSessionCloseResponseInvalidToken(t *testing.T) { @@ -999,8 +997,6 @@ func TestSessionCloseResponseInvalidToken(t *testing.T) { if resp.Header.Security.BinarySecurityToken.Value != samplebinsectoken { t.Errorf("Header.Security.BinarySecurityToken.Value expect: %s, got: %s", samplebinsectoken, resp.Header.Security.BinarySecurityToken.Value) } - //fmt.Printf("SAMPLE: %s\n\n", sampleSessionCloseRespNoValidToken) - //fmt.Printf("CURREN: %+v\n", resp) } func TestSessionValidateResponse(t *testing.T) { @@ -1024,8 +1020,6 @@ func TestSessionValidateResponse(t *testing.T) { if resp.Header.MessageHeader.MessageData.Timestamp != sampletime { t.Errorf("Header.MessageHeader.MessageData.Timestamp expect: %s, got: %s", sampletime, resp.Header.MessageHeader.MessageData.Timestamp) } - //fmt.Printf("SAMPLE: %s\n\n", sampleSessionValidateRS) - //fmt.Printf("CURREN: %+v\n", resp) } func TestSessionValidateResponseInvalidToken(t *testing.T) { @@ -1049,8 +1043,6 @@ func TestSessionValidateResponseInvalidToken(t *testing.T) { if resp.Body.Fault.Detail.StackTrace != sampleSessionInvalidTokenStackTrace { t.Errorf("Body.Fault.Detail.StackTrace expect: %s, got: %s", sampleSessionInvalidTokenStackTrace, resp.Body.Fault.Detail.StackTrace) } - //fmt.Printf("SAMPLE: %s\n\n", sampleSessionValidateRSInvalidTokenRS) - //fmt.Printf("CURREN: %+v\n", resp) } func TestBuildSessionValidateRequestMarshal(t *testing.T) { close := BuildSessionValidateRequest(samplefrom, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime) @@ -1062,8 +1054,6 @@ func TestBuildSessionValidateRequestMarshal(t *testing.T) { if string(b) != string(sampleSessionValidateRQ) { t.Error("Session envelope with values does not match test sample") } - //fmt.Printf("SAMPLE: %v\n", string(sampleSessionValidateRQ)) - //fmt.Printf("CURREN: %v\n", string(b)) } func BenchmarkBuildSessionValidateRequest(b *testing.B) { for n := 0; n < b.N; n++ { @@ -1166,7 +1156,6 @@ func TestCallSessionValidateBadBody(t *testing.T) { func TestCallSessionCreateSuccess(t *testing.T) { //Mock Sabre Web Services req := BuildSessionCreateRequest(sampleSessionConf) - //req := BuildSessionCreateRequest(samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) resp, err := CallSessionCreate(serverCreateRQ.URL, req) if err != nil { t.Error("Error making request CallSessionCreate", err) @@ -1196,14 +1185,13 @@ func TestCallSessionCreateSuccess(t *testing.T) { } func BenchmarkCallSessionCreate(b *testing.B) { req := BuildSessionCreateRequest(sampleSessionConf) - //req := BuildSessionCreateRequest(samplefrom, samplepcc, sampleconvid, samplemid, sampletime, sampleusername, samplepassword) for n := 0; n < b.N; n++ { CallSessionCreate(serverCreateRQ.URL, req) } } func TestCallSessionClose(t *testing.T) { - req := BuildSessionCloseRequest(samplefrom, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime) + req := BuildSessionCloseRequest(sampleSessionConf) resp, err := CallSessionClose(serverCloseRQ.URL, req) if err != nil { t.Error("Error making request CallSessionClose", err) @@ -1216,7 +1204,7 @@ func TestCallSessionClose(t *testing.T) { } } func BenchmarkCallSessionClose(b *testing.B) { - req := BuildSessionCloseRequest(samplefrom, samplepcc, samplebinsectoken, sampleconvid, samplemid, sampletime) + req := BuildSessionCloseRequest(sampleSessionConf) for n := 0; n < b.N; n++ { CallSessionClose(serverCloseRQ.URL, req) } From 06de26f44c2a13672df0a61e70b9ace0eceefe95 Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Sun, 24 Jun 2018 20:26:44 -0600 Subject: [PATCH 08/21] start validation for client --- client/app/handlers.go | 90 ++++++++++++++++++++----- client/app/routes.go | 4 +- client/app/validation.go | 19 ++++++ client/main.go | 25 +++---- engine/hotelws/hotel_avail.go | 1 + engine/hotelws/hotel_avail_test.go | 12 ++-- engine/hotelws/hotel_prop_desc_test.go | 8 +-- engine/hotelws/hotel_search_criteria.go | 14 ++-- engine/hotelws/hotelws.go | 12 ++-- engine/hotelws/hotelws_helper_test.go | 14 ++-- engine/srvc/session_pool.go | 12 ++-- engine/srvc/soap_base.go | 11 +-- 12 files changed, 153 insertions(+), 69 deletions(-) create mode 100644 client/app/validation.go diff --git a/client/app/handlers.go b/client/app/handlers.go index 9ed0ec9..e76bc9c 100644 --- a/client/app/handlers.go +++ b/client/app/handlers.go @@ -2,28 +2,86 @@ package app import ( "fmt" + "log" "net/http" + + "github.com/ailgroup/sbrweb/engine/hotelws" +) + +/* +HOTELEREF: + hqids = make(HotelRefCriterion) + hqltln = make(HotelRefCriterion) + hqcity = make(HotelRefCriterion) + + search[hotelws.HotelidQueryField] = []string{"0012", "19876", "1109", "445098", "000034"} + search[hotelws.LatlngQueryField] = []string{"32.78,-96.81", "54.87,-102.96"} + search[hotelws.CountryCodeQueryField] = []string{"DFW", "CHC", "LA"} + +ADDRESS: + addr = make(AddressCriterion) + addr[StreetQueryField] = "2031 N. 100 W" + addr[CityQueryField] = "Aneheim" + addr[PostalQueryField] = "90458" + addr[CountryCodeQueryField] = "US" +*/ +var ( + search = hotelws.HotelRefCriterion{} + addr = hotelws.AddressCriterion{} ) // HotelAvailHandler wraps SOAP call to sabre hotel availability service -func (s *Server) HotelAvailHandler() http.HandlerFunc { - //msg := "this" +// http://localhost:8080/avail?arrive=06-25&depart=06-26&guest_count=2&hotel_id=10&hotel_id=12&hotel_id=13&city_code=DWF&city_code=CHC&city_code=LA&lat_lng=32.78,-96.81&lat_lng=54.87,-102.96 + +// curl --request GET --url 'http://localhost:8080/avail?arrive=06-25&depart=06-26&guest_count=2&hotel_id=10&hotel_id=12&hotel_id=13&city_code=DWF&city_code=CHC&city_code=LA&lat_lng=32.78,-96.81&lat_lng=54.87,-102.96' +func (s *Server) HotelAvailIDsHandler() http.HandlerFunc { + //define hotel search criterion + search = make(hotelws.HotelRefCriterion) + addr = make(hotelws.AddressCriterion) + //closure to execute return func(w http.ResponseWriter, r *http.Request) { + //get session sess := s.SessionPool.Pick() + //defer close on session defer s.SessionPool.Put(sess) - msg := fmt.Sprintf("\nID: %v\n Expire: %v\n Started: %v\n Validated: %v\n Status: %v\n", - sess.ID, - sess.ExpireTime, - sess.TimeStarted, - sess.TimeValidated, - sess.Sabre.Body.SessionCreateRS.Status, - ) - - //1 way: - //param1 := r.URL.Query().Get("param1") - - //2 way: - //value := FormValue("field") - fmt.Fprint(w, msg) + //get binary security token + s.SConfig.SetBinSec(sess.Sabre) + // parse incoming params + params := r.URL.Query() + if ids, ok := params["hotel_id"]; ok { + search[hotelws.HotelidQueryField] = ids + } + if latlng, ok := params["lat_lng"]; ok { + search[hotelws.LatlngQueryField] = latlng + } + if cities, ok := params["city_code"]; ok { + search[hotelws.CountryCodeQueryField] = cities + } + + if len(search) > 1 || len(search) < 1 { + log.Printf("Search Validation ERROR: %v", search) + fmt.Fprint(w, ErrInvalidSearchCriterion) + } + + /* + + arrive := params.Get("arrive") + depart := params.Get("depart") + guests, _ := strconv.Atoi(params.Get("guest_count")) + ids := params["hotel_ids"] + search[hotelws.HotelidQueryField] = ids + q, _ := hotelws.NewHotelSearchCriteria( + hotelws.HotelRefSearch(search), + ) + availBody := hotelws.SetHotelAvailBody(guests, q, arrive, depart) + req := hotelws.BuildHotelAvailRequest(s.SConfig, availBody) + resp, err := hotelws.CallHotelAvail(s.SConfig.ServiceURL, req) + if err != nil { + fmt.Printf("CallHotelAvail ERROR: %v", err) + fmt.Fprint(w, err) + return + } + */ + fmt.Fprint(w, search) } } diff --git a/client/app/routes.go b/client/app/routes.go index 2f7f3b7..f8b7c03 100644 --- a/client/app/routes.go +++ b/client/app/routes.go @@ -7,5 +7,7 @@ import ( // registerRoutes is responsible for registering the server-side request handlers func (s *Server) RegisterRoutes() { s.Mux.Get("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hi")) }) - s.Mux.Handle("/avail", s.HotelAvailHandler()) + s.Mux.Handle("/avail", s.HotelAvailIDsHandler()) + //s.Mux.Handle("/avail/latlong", s.HotelAvailIDsHandler()) + //s.Mux.Handle("/avail/address", s.HotelAvailIDsHandler()) } diff --git a/client/app/validation.go b/client/app/validation.go new file mode 100644 index 0000000..2d59b9a --- /dev/null +++ b/client/app/validation.go @@ -0,0 +1,19 @@ +package app + +import ( + "errors" + "net/http" +) + +type InputValidator interface { + Validate(r *http.Request) error +} + +var ( + ErrInvalidArrive = errors.New("invalid arrive") + ErrInvalidDepart = errors.New("invalid depart") + ErrInvalidGuestCount = errors.New("invalid guest_count") + ErrInvalidSearchCriterion = errors.New("invalid search criterion, verify only one kind of criterion (hotel_id, lat_lng, city_code) was sent") + ErrInvalidHotelID = errors.New("invalid hotel_id") + ErrInvalidCityCode = errors.New("invalid city_code") +) diff --git a/client/main.go b/client/main.go index 2037244..2fe6a3b 100644 --- a/client/main.go +++ b/client/main.go @@ -31,6 +31,7 @@ var ( vRepeatEvery = time.Minute * 3 sessConf = &srvc.SessionConf{} vipConf = &viper.Viper{} + port = ":8080" ) func init() { @@ -76,21 +77,21 @@ func main() { Max: vipConf.GetInt(ConfExpireMax), } - // create pool, background populate pool and sett up keepalive + // create and populate pool, start keepalive, watch for signal close down p := srvc.NewPool(scheme, sessConf, vipConf.GetInt(ConfPoolSize)) + keepKill := make(chan os.Signal, 1) + signal.Notify(keepKill, os.Interrupt) + shutDown := make(chan os.Signal, 1) + signal.Notify(shutDown, os.Interrupt) go func() { err := p.Populate() if err != nil { - panic(err) + fmt.Printf("Error popluating session pool %v\n", err) + os.Exit(1) } - srvc.Keepalive(p, vRepeatEvery) - }() - // close down session pool properly against sabre re-allocates useable sessiosn - cls := make(chan os.Signal) - signal.Notify(cls, os.Interrupt) - go func() { - sig := <-cls - fmt.Printf("Got %s signal. Closing down...\n", sig) + srvc.Keepalive(p, vRepeatEvery, keepKill) + sig := <-shutDown + fmt.Printf("\nGot '%s' SIGNAL. Shutdown keepalive, session pool, and exit program...\n", sig) p.Close() os.Exit(1) }() @@ -109,6 +110,6 @@ func main() { SessionPool: p, } server.RegisterRoutes() - fmt.Println("Begin on port:", ":8080") - http.ListenAndServe(":8080", m) + fmt.Println("Begin on port:", port) + http.ListenAndServe(port, m) } diff --git a/engine/hotelws/hotel_avail.go b/engine/hotelws/hotel_avail.go index 27b313c..4ae51d1 100644 --- a/engine/hotelws/hotel_avail.go +++ b/engine/hotelws/hotel_avail.go @@ -143,6 +143,7 @@ type HotelAvailResponse struct { // CallHotelAvail to sabre web services func CallHotelAvail(serviceURL string, req HotelAvailRequest) (HotelAvailResponse, error) { + //allocate return types availResp := HotelAvailResponse{} //construct payload byteReq, _ := xml.Marshal(req) diff --git a/engine/hotelws/hotel_avail_test.go b/engine/hotelws/hotel_avail_test.go index 3d03bb7..bb86c8f 100644 --- a/engine/hotelws/hotel_avail_test.go +++ b/engine/hotelws/hotel_avail_test.go @@ -217,8 +217,8 @@ func TestAvailIdsMarshal(t *testing.T) { if avail.Avail.GuestCounts.Count != gcount { t.Errorf("SetHotelAvailRqStruct GuestCounts.Count expect: %d, got %d", gcount, avail.Avail.GuestCounts.Count) } - if len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs) != len(hqids[hotelidQueryField]) { - t.Error("HotelRefs shoudl be same length as params", len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs), len(hqids[hotelidQueryField])) + if len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs) != len(hqids[HotelidQueryField]) { + t.Error("HotelRefs shoudl be same length as params", len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs), len(hqids[HotelidQueryField])) } b, err := xml.Marshal(avail) if err != nil { @@ -240,8 +240,8 @@ func TestAvailCitiesMarshal(t *testing.T) { if avail.Avail.GuestCounts.Count != gcount { t.Errorf("BuildHotelAvailRq GuestCounts.Count expect: %d, got %d", gcount, avail.Avail.GuestCounts.Count) } - if len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs) != len(hqcity[cityQueryField]) { - t.Error("HotelRefs shoudl be same length as params", len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs), len(hqcity[cityQueryField])) + if len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs) != len(hqcity[CityQueryField]) { + t.Error("HotelRefs shoudl be same length as params", len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs), len(hqcity[CityQueryField])) } b, err := xml.Marshal(avail) if err != nil { @@ -261,8 +261,8 @@ func TestAvailLatLngMarshal(t *testing.T) { if avail.Avail.GuestCounts.Count != sampleGuestCount { t.Errorf("BuildHotelAvailRq GuestCounts.Count expect: %d, got %d", sampleGuestCount, avail.Avail.GuestCounts.Count) } - if len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs) != len(hqltln[latlngQueryField]) { - t.Error("HotelRefs shoudl be same length as params", len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs), len(hqltln[latlngQueryField])) + if len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs) != len(hqltln[LatlngQueryField]) { + t.Error("HotelRefs shoudl be same length as params", len(avail.Avail.HotelSearchCriteria.Criterion.HotelRefs), len(hqltln[LatlngQueryField])) } b, err := xml.Marshal(avail) if err != nil { diff --git a/engine/hotelws/hotel_prop_desc_test.go b/engine/hotelws/hotel_prop_desc_test.go index bf95364..6826870 100644 --- a/engine/hotelws/hotel_prop_desc_test.go +++ b/engine/hotelws/hotel_prop_desc_test.go @@ -42,7 +42,7 @@ func TestPropDescValidMultiHotelRefs(t *testing.T) { func TestPropDescBuildHotelPropDescMarshal(t *testing.T) { var hotelid = make(HotelRefCriterion) - hotelid[hotelidQueryField] = []string{"10"} + hotelid[HotelidQueryField] = []string{"10"} q, _ := NewHotelSearchCriteria( HotelRefSearch(hotelid), ) @@ -140,7 +140,7 @@ func TestPropDescUnmarshal(t *testing.T) { func TestPropDescCall(t *testing.T) { var hotelid = make(HotelRefCriterion) - hotelid[hotelidQueryField] = []string{"10"} + hotelid[HotelidQueryField] = []string{"10"} q, _ := NewHotelSearchCriteria( HotelRefSearch(hotelid), ) @@ -205,7 +205,7 @@ func TestPropDescCall(t *testing.T) { func TestHotelPropDescCallDown(t *testing.T) { var hotelid = make(HotelRefCriterion) - hotelid[hotelidQueryField] = []string{"10"} + hotelid[HotelidQueryField] = []string{"10"} q, _ := NewHotelSearchCriteria( HotelRefSearch(hotelid), ) @@ -228,7 +228,7 @@ func TestHotelPropDescCallDown(t *testing.T) { func TestHotelPropDescCallBadResponseBody(t *testing.T) { var hotelid = make(HotelRefCriterion) - hotelid[hotelidQueryField] = []string{"10"} + hotelid[HotelidQueryField] = []string{"10"} q, _ := NewHotelSearchCriteria( HotelRefSearch(hotelid), ) diff --git a/engine/hotelws/hotel_search_criteria.go b/engine/hotelws/hotel_search_criteria.go index 8da44b9..a35d945 100644 --- a/engine/hotelws/hotel_search_criteria.go +++ b/engine/hotelws/hotel_search_criteria.go @@ -79,13 +79,13 @@ func AddressSearch(params AddressCriterion) func(q *HotelSearchCriteria) error { } for k, v := range params { switch k { - case streetQueryField: + case StreetQueryField: a.Street = v - case cityQueryField: + case CityQueryField: a.City = v - case postalQueryField: + case PostalQueryField: a.Postal = v - case countryCodeQueryField: + case CountryCodeQueryField: a.CountryCode = v } } @@ -103,15 +103,15 @@ func HotelRefSearch(params HotelRefCriterion) func(q *HotelSearchCriteria) error } for k, v := range params { switch k { - case cityQueryField: + case CityQueryField: for _, city := range v { q.Criterion.HotelRefs = append(q.Criterion.HotelRefs, &HotelRef{HotelCityCode: city}) } - case hotelidQueryField: + case HotelidQueryField: for _, code := range v { q.Criterion.HotelRefs = append(q.Criterion.HotelRefs, &HotelRef{HotelCode: code}) } - case latlngQueryField: + case LatlngQueryField: for _, l := range v { latlng := strings.Split(l, ",") q.Criterion.HotelRefs = append(q.Criterion.HotelRefs, &HotelRef{Latitude: latlng[0], Longitude: latlng[1]}) diff --git a/engine/hotelws/hotelws.go b/engine/hotelws/hotelws.go index 13b7bb7..1f7d336 100644 --- a/engine/hotelws/hotelws.go +++ b/engine/hotelws/hotelws.go @@ -35,12 +35,12 @@ const ( TimeFormatMD = "01-02" TimeFormatMDTHM = "01-02T15:04" TimeFormatMDHM = "01-02 15:04" - streetQueryField = "street_qf" - cityQueryField = "city_qf" - postalQueryField = "postal_qf" - countryCodeQueryField = "countryCode_qf" - latlngQueryField = "latlng_qf" - hotelidQueryField = "hotelID_qf" + StreetQueryField = "street_qf" + CityQueryField = "city_qf" + PostalQueryField = "postal_qf" + CountryCodeQueryField = "countryCode_qf" + LatlngQueryField = "latlng_qf" + HotelidQueryField = "hotelID_qf" returnHostCommand = true ESA = "\u0087" //UNICODE: End of Selected Area CrossLorraine = "\u2628" //UNICODE Cross of Lorraine diff --git a/engine/hotelws/hotelws_helper_test.go b/engine/hotelws/hotelws_helper_test.go index 66bea52..2fdaaf2 100644 --- a/engine/hotelws/hotelws_helper_test.go +++ b/engine/hotelws/hotelws_helper_test.go @@ -31,14 +31,14 @@ var ( //Initialize Mock Sabre Web Servers and test data func init() { // init data chunks... - hqcity[cityQueryField] = sampleHotelCityCode - hqids[hotelidQueryField] = sampleHotelCode - hqltln[latlngQueryField] = sampleLatLang + hqcity[CityQueryField] = sampleHotelCityCode + hqids[HotelidQueryField] = sampleHotelCode + hqltln[LatlngQueryField] = sampleLatLang - addr[streetQueryField] = sampleStreet - addr[cityQueryField] = sampleCity - addr[postalQueryField] = samplePostal - addr[countryCodeQueryField] = sampleCountryCode + addr[StreetQueryField] = sampleStreet + addr[CityQueryField] = sampleCity + addr[PostalQueryField] = samplePostal + addr[CountryCodeQueryField] = sampleCountryCode // init test servers... serverHotelDown = httptest.NewServer( diff --git a/engine/srvc/session_pool.go b/engine/srvc/session_pool.go index 8aad3a8..8fc22fb 100644 --- a/engine/srvc/session_pool.go +++ b/engine/srvc/session_pool.go @@ -3,6 +3,7 @@ package srvc import ( "fmt" "math/rand" + "os" "time" ) @@ -243,8 +244,8 @@ func generateKeepAliveID() string { } //Keepalive sessions by RangeKeepalive over all sessions in pool -func Keepalive(p *SessionPool, repeatEvery time.Duration) { - //defer profile.Start(profile.MemProfile).Stop() +func Keepalive(p *SessionPool, repeatEvery time.Duration, doneChan chan os.Signal) { + //create done channel to close down on sigint started := time.Now() keepAliveID := generateKeepAliveID() logSession.Println("Starting KEEPALIVE...", keepAliveID) @@ -256,8 +257,8 @@ func Keepalive(p *SessionPool, repeatEvery time.Duration) { p.RangeKeepalive(keepAliveID) logSession.Printf("KEEPALIVE run(InMin=%.2f, InHour=%.2f)", time.Since(started).Minutes(), time.Since(started).Hours()) p.logReport(keepAliveID + "-KeepAlive") - default: - logSession.Println("KEEPALIVE killed, total time:", time.Since(started)) + case <-doneChan: + logSession.Println("KEEPALIVE done, total lifetime:", time.Since(started)) return } } @@ -267,7 +268,8 @@ func Keepalive(p *SessionPool, repeatEvery time.Duration) { func (p *SessionPool) Close() { networkErrors := []error{} faultErrors := []error{} - for range p.Sessions { + for createRS := range p.Sessions { + p.Conf.SetBinSec(createRS.Sabre) closeRQ := BuildSessionCloseRequest(p.Conf) closeRS, err := CallSessionClose(p.ServiceURL, closeRQ) diff --git a/engine/srvc/soap_base.go b/engine/srvc/soap_base.go index d073dff..dcd356f 100644 --- a/engine/srvc/soap_base.go +++ b/engine/srvc/soap_base.go @@ -398,6 +398,12 @@ func (s *SessionConf) SetTime() *SessionConf { return s } +// SetBinSec updates the timestamp. Pass around SessionConf and update the timestamp for any new request +func (s *SessionConf) SetBinSec(session SessionCreateResponse) *SessionConf { + s.Binsectok = session.Header.Security.BinarySecurityToken.Value + return s +} + // BuildSessionCreateRequest build session create envelope for request // CPAID, Organization, and PseudoCityCode all use the PCC/iPCC. ConversationID is typically a contact email address with unique identifier to the request. MessageID is typically a timestamped identifier to locate specific queries: it should contai a company identifier. //func BuildSessionCreateRequest(from, pcc, convid, mid, time, username, password string) SessionCreateRequest { @@ -459,11 +465,6 @@ type SessionCreateResponse struct { } } -func (s *SessionCreateResponse) ParseBinSecToken(c SessionConf) SessionConf { - c.Binsectok = s.Header.Security.BinarySecurityToken.Value - return c -} - // CallSessionCreate to sabre web services. func CallSessionCreate(serviceURL string, req SessionCreateRequest) (SessionCreateResponse, error) { sessionResponse := SessionCreateResponse{} From 174d3654c0ff575ffe06916b4880150ce6cc56f5 Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Fri, 29 Jun 2018 21:16:53 -0600 Subject: [PATCH 09/21] validation, errors for client --- apperr/aperr.go | 83 ++++++++++++ client/app/handlers.go | 275 ++++++++++++++++++++++++++++++--------- client/app/validation.go | 57 ++++++-- client/main.go | 24 ++-- 4 files changed, 357 insertions(+), 82 deletions(-) create mode 100644 apperr/aperr.go diff --git a/apperr/aperr.go b/apperr/aperr.go new file mode 100644 index 0000000..0341161 --- /dev/null +++ b/apperr/aperr.go @@ -0,0 +1,83 @@ +package apperr + +type AppStatus int + +// List statuses for common error handling and parsing. +const ( + Unknown AppStatus = iota + 1 //1 + BadInput //2 + Invalid //3 +) + +var ( + appStatuses = [...]string{ + "0", + "Unknown", + "BadInput", + "Invalid", + } +) + +func StatusInvalid() string { + return appStatuses[Invalid] +} +func StatusBadInput() string { + return appStatuses[BadInput] +} +func (code AppStatus) String() string { + if code < Unknown || code > Invalid { + return "Unknown" + } + return appStatuses[code] +} +func AppStatusCode(input string) AppStatus { + if input == "0" { + return Unknown + } + switch input { + case Invalid.String(): + return Invalid + case BadInput.String(): + return BadInput + default: + return Unknown + } +} + +// ErrorInvalid container for validation errors +type ErrorInvalid struct { + ErrMessage string `json:"err_invalid,omitempty"` + AppMessage string `json:"app_invalid,omitempty"` + Code AppStatus `json:"app_status"` + HTTPStatus uint `json:"http_status"` +} + +// NewErrorInvalid for validation errors +func NewErrorInvalid(errIn, appIn string, code AppStatus, httpstatus uint) ErrorInvalid { + //err = strings.Replace(err, "\n", "", -1) + return ErrorInvalid{ErrMessage: errIn, AppMessage: appIn, Code: code, HTTPStatus: httpstatus} +} + +// Error for ErrorInvalid implements std lib error interface +func (e ErrorInvalid) Error() string { + return e.ErrMessage +} + +// ErrorBadInput container for bad input or json parsing +type ErrorBadInput struct { + ErrMessage string `json:"err_badinput,omitempty"` + AppMessage string `json:"app_badinput,omitempty"` + Code AppStatus `json:"app_status"` + HTTPStatus uint `json:"http_status"` +} + +// NewErrorBadInput for json parsing or other bad input +func NewErrorBadInput(errIn, appIn string, code AppStatus, httpstatus uint) ErrorBadInput { + //err = strings.Replace(err, "\n", "", -1) + return ErrorBadInput{ErrMessage: errIn, AppMessage: appIn, Code: code, HTTPStatus: httpstatus} +} + +// Error for ErrorBadInput implements std lib error interface +func (e ErrorBadInput) Error() string { + return e.ErrMessage +} diff --git a/client/app/handlers.go b/client/app/handlers.go index e76bc9c..a362c65 100644 --- a/client/app/handlers.go +++ b/client/app/handlers.go @@ -1,13 +1,224 @@ package app import ( + "encoding/json" "fmt" - "log" "net/http" - "github.com/ailgroup/sbrweb/engine/hotelws" + "github.com/ailgroup/sbrweb/apperr" + "github.com/go-playground/form" ) +/* +type AvailParamsBase struct { + GuestCount int `json:"guest_count" form:"guest_count" validate:"gt=0,lt=5"` + Arrive string `json:"arrive" form:"arrive" validate:"required"` + Depart string `json:"depart" form:"depart" validate:"required"` +} +type AvailParamsIDs struct { + AvailParamsBase + HotelIDs []string `json:"hotel_ids" form:"hotel_ids" validate:"required,dive,min=1,max=10"` +} +*/ +type AvailParamsBase struct { + GuestCount int `json:"guest_count" form:"guest_count"` + Arrive string `json:"arrive" form:"arrive"` + Depart string `json:"depart" form:"depart"` +} +type AvailParamsIDs struct { + AvailParamsBase + HotelIDs []string `json:"hotel_ids" form:"hotel_ids"` +} +type AvailParamsCityCodes struct { + Base AvailParamsBase + CityCodes []string +} +type AvailParamsLatLng struct { + Base AvailParamsBase + LatLng []string +} + +// Validate AvailParamsBase fields arrive, depart, guest_count looking at date formats and counts. +func (b AvailParamsBase) Validate() error { + tArrive, ok, err := ValidArriveDepart(b.Arrive) + if !ok { + return ErrStayFormat(ErrArriveFmtMsg, err.Error(), b.Arrive, timeShortForm) + } + + tDepart, ok, err := ValidArriveDepart(b.Depart) + if !ok { + return ErrStayFormat(ErrDepartFmtMsg, err.Error(), b.Depart, timeShortForm) + } + + if !startBeforeEnd(tArrive, tDepart) { + return ErrStayRange(ErrStayRangeMsg, b.Arrive, b.Depart) + } + + if b.GuestCount == 0 { + return ErrGuestCountNullOrZero + } + if Gt(b.GuestCount, GuestGTE) { + return ErrLtGt(ErrGuestLTEMsg, b.GuestCount, GuestGTE) + } + if Lt(b.GuestCount, GuestLTE) { + return ErrLtGt(ErrGuestGTEMsg, b.GuestCount, GuestLTE) + } + return nil +} + +func (a AvailParamsIDs) Validate() error { + if err := a.AvailParamsBase.Validate(); err != nil { + return err + } + fmt.Printf("%v %d\n", a.HotelIDs, len(a.HotelIDs)) + if len(a.HotelIDs) == 1 { + if (a.HotelIDs[0] == "") || (a.HotelIDs[0] == "\"") || (a.HotelIDs[0] == "\"\"") { + return ErrHotelIDNullOrZero + } + } + + if Gt(len(a.HotelIDs), HotelIDsLTE) { + return ErrLtGt(ErrHotelIDsLTEMsg, len(a.HotelIDs), HotelIDsLTE) + } + return nil +} + +/* +HotelAvailHandler wraps SOAP call to sabre hotel availability service +https://github.com/tidwall/gjson + +curl -H "Accept: application/json" -X GET -d '{"arrive":"06-28","depart":"06-29","guest_count":"2", "hotel_ids":["007"]}' http://localhost:8080/avail | jq +*/ +func (s *Server) HotelAvailIDsHandler() http.HandlerFunc { + var availPIDs AvailParamsIDs + var decoder *form.Decoder + //closure to execute + return func(w http.ResponseWriter, r *http.Request) { + availPIDs = AvailParamsIDs{} + decoder = form.NewDecoder() + if err := decoder.Decode(&availPIDs, r.URL.Query()); err != nil { + appMsg := fmt.Sprintf("Cannot Decode Query: %v", r.URL.Query()) + b, _ := json.Marshal( + apperr.NewErrorBadInput( + err.Error(), + appMsg, + apperr.BadInput, + http.StatusBadRequest, + ), + ) + w.WriteHeader(http.StatusBadRequest) + w.Write(b) + return + } + fmt.Printf("\n%+v\n", availPIDs) + if err := availPIDs.Validate(); err != nil { + b, _ := json.Marshal( + apperr.NewErrorInvalid( + err.Error(), + "Invalid", + apperr.Invalid, + http.StatusUnprocessableEntity, + ), + ) + w.WriteHeader(http.StatusUnprocessableEntity) + w.Write(b) + return + } + /* + // decode, validate params + if err := decoder.Decode(¶ms, r.URL.Query()); err != nil { + appMsg := fmt.Sprintf("Cannot Decode Query: %v", r.URL.Query()) + b, _ := json.Marshal( + apperr.NewErrorBadInput(err.Error(), appMsg, apperr.BadInput, http.StatusBadRequest), + ) + w.WriteHeader(http.StatusBadRequest) + w.Write(b) + return + } + if err := params.Validate(); err != nil { + b, _ := json.Marshal( + apperr.NewErrorInvalid(err.Error(), "Invalid Request", apperr.BadInput, http.StatusUnprocessableEntity), + ) + w.WriteHeader(http.StatusUnprocessableEntity) + w.Write(b) + return + } + */ + /* + //get session + sess := s.SessionPool.Pick() + //defer close on session + defer s.SessionPool.Put(sess) + //get binary security token + s.SConfig.SetBinSec(sess.Sabre) + // parse incoming params as JSON + searchids := make(hotelws.HotelRefCriterion) + searchids[hotelws.HotelidQueryField] = params.HotelIDs + + q, _ := hotelws.NewHotelSearchCriteria( + hotelws.HotelRefSearch(searchids), + ) + g, _ := strconv.Atoi(params.Base.GuestCount) + availBody := hotelws.SetHotelAvailBody(g, q, params.Base.Arrive, params.Base.Depart) + req := hotelws.BuildHotelAvailRequest(s.SConfig, availBody) + resp, _ := hotelws.CallHotelAvail(s.SConfig.ServiceURL, req) + + b, _ := json.Marshal(resp) + w.WriteHeader(http.StatusOK) + w.Write(b) + */ + //b, _ := json.Marshal(params) + //w.WriteHeader(http.StatusOK) + //w.Write(b) + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(availPIDs) + } +} + +/* + + arrive := params.Get("arrive") + depart := params.Get("depart") + guests, _ := strconv.Atoi(params.Get("guest_count")) + ids := params["hotel_ids"] + search[hotelws.HotelidQueryField] = ids + q, _ := hotelws.NewHotelSearchCriteria( + hotelws.HotelRefSearch(search), + ) + availBody := hotelws.SetHotelAvailBody(guests, q, arrive, depart) + req := hotelws.BuildHotelAvailRequest(s.SConfig, availBody) + resp, err := hotelws.CallHotelAvail(s.SConfig.ServiceURL, req) + if err != nil { + fmt.Printf("CallHotelAvail ERROR: %v", err) + fmt.Fprint(w, err) + return + } +*/ + +/* +func (b AvailParamsBase) Validate() error { + if govalidator.IsNull(b.GuestCount) { + return ErrGuestCountNull + } + if govalidator.IsInt(b.GuestCount) { + return ErrGuestCountNotNumber + } + if govalidator.InRangeInt(b.GuestCount, "1", "4") { + return ErrGuestCountRange + } + return nil +} + +func (a AvailParamsIDs) Validate() error { + //if len(a.HotelIDs) + if len(a.HotelIDs) < 1 || len(a.HotelIDs) > 10 { + return ErrHotelIDRange + } + return nil +} +*/ + /* HOTELEREF: hqids = make(HotelRefCriterion) @@ -25,63 +236,3 @@ ADDRESS: addr[PostalQueryField] = "90458" addr[CountryCodeQueryField] = "US" */ -var ( - search = hotelws.HotelRefCriterion{} - addr = hotelws.AddressCriterion{} -) - -// HotelAvailHandler wraps SOAP call to sabre hotel availability service -// http://localhost:8080/avail?arrive=06-25&depart=06-26&guest_count=2&hotel_id=10&hotel_id=12&hotel_id=13&city_code=DWF&city_code=CHC&city_code=LA&lat_lng=32.78,-96.81&lat_lng=54.87,-102.96 - -// curl --request GET --url 'http://localhost:8080/avail?arrive=06-25&depart=06-26&guest_count=2&hotel_id=10&hotel_id=12&hotel_id=13&city_code=DWF&city_code=CHC&city_code=LA&lat_lng=32.78,-96.81&lat_lng=54.87,-102.96' -func (s *Server) HotelAvailIDsHandler() http.HandlerFunc { - //define hotel search criterion - search = make(hotelws.HotelRefCriterion) - addr = make(hotelws.AddressCriterion) - //closure to execute - return func(w http.ResponseWriter, r *http.Request) { - //get session - sess := s.SessionPool.Pick() - //defer close on session - defer s.SessionPool.Put(sess) - //get binary security token - s.SConfig.SetBinSec(sess.Sabre) - // parse incoming params - params := r.URL.Query() - if ids, ok := params["hotel_id"]; ok { - search[hotelws.HotelidQueryField] = ids - } - if latlng, ok := params["lat_lng"]; ok { - search[hotelws.LatlngQueryField] = latlng - } - if cities, ok := params["city_code"]; ok { - search[hotelws.CountryCodeQueryField] = cities - } - - if len(search) > 1 || len(search) < 1 { - log.Printf("Search Validation ERROR: %v", search) - fmt.Fprint(w, ErrInvalidSearchCriterion) - } - - /* - - arrive := params.Get("arrive") - depart := params.Get("depart") - guests, _ := strconv.Atoi(params.Get("guest_count")) - ids := params["hotel_ids"] - search[hotelws.HotelidQueryField] = ids - q, _ := hotelws.NewHotelSearchCriteria( - hotelws.HotelRefSearch(search), - ) - availBody := hotelws.SetHotelAvailBody(guests, q, arrive, depart) - req := hotelws.BuildHotelAvailRequest(s.SConfig, availBody) - resp, err := hotelws.CallHotelAvail(s.SConfig.ServiceURL, req) - if err != nil { - fmt.Printf("CallHotelAvail ERROR: %v", err) - fmt.Fprint(w, err) - return - } - */ - fmt.Fprint(w, search) - } -} diff --git a/client/app/validation.go b/client/app/validation.go index 2d59b9a..3246812 100644 --- a/client/app/validation.go +++ b/client/app/validation.go @@ -2,18 +2,55 @@ package app import ( "errors" - "net/http" + "fmt" + "time" ) -type InputValidator interface { - Validate(r *http.Request) error -} +const ( + GuestGTE = 4 + GuestLTE = 1 + HotelIDsLTE = 5 + timeShortForm = "01-02" +) var ( - ErrInvalidArrive = errors.New("invalid arrive") - ErrInvalidDepart = errors.New("invalid depart") - ErrInvalidGuestCount = errors.New("invalid guest_count") - ErrInvalidSearchCriterion = errors.New("invalid search criterion, verify only one kind of criterion (hotel_id, lat_lng, city_code) was sent") - ErrInvalidHotelID = errors.New("invalid hotel_id") - ErrInvalidCityCode = errors.New("invalid city_code") + ErrHotelIDNullOrZero = errors.New("invalid hotel_id: query not present or value not defined") + ErrGuestCountNullOrZero = errors.New("invalid guest_count: query not present or value is 0") + + ErrSearchCriterion = errors.New("invalid search criterion: verify only one kind of criterion (hotel_id, lat_lng, city_code)") ) + +var ( + ErrGuestLTEMsg = "invalid guest_count '%d': must be less than or equal '%d'" + ErrGuestGTEMsg = "invalid guest_count '%d': must be greater than or equal '%d'" + ErrHotelIDsLTEMsg = "invalid hotel_ids '%d': must be less than or equal '%d'" + ErrArriveFmtMsg = "invalid arrive '%v': format (MM-DD '%s'). %v" + ErrDepartFmtMsg = "invalid depart '%v': format (MM-DD '%s'). %v" + ErrStayRangeMsg = "invalid range: arrive '%v' must be before depart '%v'" +) + +func ErrLtGt(msgFormat string, given, expect int) error { + return fmt.Errorf(msgFormat, given, expect) +} +func ErrStayFormat(msgFormat, errMsg, given, expect string) error { + return fmt.Errorf(msgFormat, given, expect, errMsg) +} +func ErrStayRange(msgFormat, given, expect string) error { + return fmt.Errorf(msgFormat, given, expect) +} +func Lt(current, compare int) bool { + return current < compare +} +func Gt(current, compare int) bool { + return current > compare +} +func startBeforeEnd(start, end time.Time) bool { + return start.Before(end) +} +func ValidArriveDepart(ts string) (time.Time, bool, error) { + t, err := time.Parse(timeShortForm, ts) + if err != nil { + return t, false, err + } + return t, true, nil +} diff --git a/client/main.go b/client/main.go index 2fe6a3b..7a12147 100644 --- a/client/main.go +++ b/client/main.go @@ -70,15 +70,7 @@ func setConfig() *viper.Viper { return vip } -func main() { - // create scheme for session expirey - scheme := srvc.ExpireScheme{ - Min: vipConf.GetInt(ConfExpireMin), - Max: vipConf.GetInt(ConfExpireMax), - } - - // create and populate pool, start keepalive, watch for signal close down - p := srvc.NewPool(scheme, sessConf, vipConf.GetInt(ConfPoolSize)) +func runPool(p *srvc.SessionPool) { keepKill := make(chan os.Signal, 1) signal.Notify(keepKill, os.Interrupt) shutDown := make(chan os.Signal, 1) @@ -95,6 +87,18 @@ func main() { p.Close() os.Exit(1) }() +} + +func main() { + // create scheme for session expirey + scheme := srvc.ExpireScheme{ + Min: vipConf.GetInt(ConfExpireMin), + Max: vipConf.GetInt(ConfExpireMax), + } + + // create and populate pool, start keepalive, watch for signal close down + pool := srvc.NewPool(scheme, sessConf, vipConf.GetInt(ConfPoolSize)) + //runPool(pool) // pass context through handlers?? m := chi.NewRouter() @@ -107,7 +111,7 @@ func main() { VConfig: vipConf, SConfig: sessConf, Mux: m, - SessionPool: p, + SessionPool: pool, } server.RegisterRoutes() fmt.Println("Begin on port:", port) From 962bef1e36b5982257fe001a931ec30e907de49f Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Sat, 30 Jun 2018 17:43:42 -0600 Subject: [PATCH 10/21] update client errors and validations --- apperr/aperr.go | 54 ++++++++++++--- client/app/handlers.go | 140 +++++++++++++++------------------------ client/app/validation.go | 47 ++++++++----- client/main.go | 31 +++++---- engine/srvc/soap_base.go | 19 +++--- 5 files changed, 162 insertions(+), 129 deletions(-) diff --git a/apperr/aperr.go b/apperr/aperr.go index 0341161..85c717d 100644 --- a/apperr/aperr.go +++ b/apperr/aperr.go @@ -1,5 +1,12 @@ package apperr +import ( + "encoding/json" + "fmt" + "net/url" + "time" +) + type AppStatus int // List statuses for common error handling and parsing. @@ -18,6 +25,9 @@ var ( } ) +func StatusUnknown() string { + return appStatuses[Unknown] +} func StatusInvalid() string { return appStatuses[Invalid] } @@ -35,10 +45,10 @@ func AppStatusCode(input string) AppStatus { return Unknown } switch input { - case Invalid.String(): - return Invalid case BadInput.String(): return BadInput + case Invalid.String(): + return Invalid default: return Unknown } @@ -50,12 +60,13 @@ type ErrorInvalid struct { AppMessage string `json:"app_invalid,omitempty"` Code AppStatus `json:"app_status"` HTTPStatus uint `json:"http_status"` + ServerTime time.Time `json:"server_time"` } // NewErrorInvalid for validation errors -func NewErrorInvalid(errIn, appIn string, code AppStatus, httpstatus uint) ErrorInvalid { +func NewErrorInvalid(errIn, appIn string, code AppStatus, hts uint) ErrorInvalid { //err = strings.Replace(err, "\n", "", -1) - return ErrorInvalid{ErrMessage: errIn, AppMessage: appIn, Code: code, HTTPStatus: httpstatus} + return ErrorInvalid{ErrMessage: errIn, AppMessage: appIn, Code: code, HTTPStatus: hts, ServerTime: time.Now()} } // Error for ErrorInvalid implements std lib error interface @@ -63,21 +74,48 @@ func (e ErrorInvalid) Error() string { return e.ErrMessage } +// DecodeInvalid builds an ErrorInvalid response given a set of url.Values +func DecodeInvalid(handlerMsg string, err error, httpstatus uint) []byte { + b, _ := json.Marshal( + NewErrorInvalid( + err.Error(), + fmt.Sprintf("%s: %s.", "Invalid", handlerMsg), + Invalid, + httpstatus, + ), + ) + return b +} + // ErrorBadInput container for bad input or json parsing type ErrorBadInput struct { - ErrMessage string `json:"err_badinput,omitempty"` - AppMessage string `json:"app_badinput,omitempty"` + ErrMessage string `json:"err_bad_input,omitempty"` + AppMessage string `json:"app_bad_input,omitempty"` Code AppStatus `json:"app_status"` HTTPStatus uint `json:"http_status"` + ServerTime time.Time `json:"server_time"` } // NewErrorBadInput for json parsing or other bad input -func NewErrorBadInput(errIn, appIn string, code AppStatus, httpstatus uint) ErrorBadInput { +func NewErrorBadInput(errIn, appIn string, code AppStatus, hts uint) ErrorBadInput { //err = strings.Replace(err, "\n", "", -1) - return ErrorBadInput{ErrMessage: errIn, AppMessage: appIn, Code: code, HTTPStatus: httpstatus} + return ErrorBadInput{ErrMessage: errIn, AppMessage: appIn, Code: code, HTTPStatus: hts, ServerTime: time.Now()} } // Error for ErrorBadInput implements std lib error interface func (e ErrorBadInput) Error() string { return e.ErrMessage } + +// DecodeBadInput builds an ErrorBadInput response given a set of url.Values +func DecodeBadInput(handlerName string, val url.Values, err error, httpstatus uint) []byte { + b, _ := json.Marshal( + NewErrorBadInput( + err.Error(), + fmt.Sprintf("%s. Cannot Decode Query: %v", handlerName, val), + BadInput, + httpstatus, + ), + ) + return b +} diff --git a/client/app/handlers.go b/client/app/handlers.go index a362c65..2478180 100644 --- a/client/app/handlers.go +++ b/client/app/handlers.go @@ -2,24 +2,13 @@ package app import ( "encoding/json" - "fmt" "net/http" + "time" "github.com/ailgroup/sbrweb/apperr" "github.com/go-playground/form" ) -/* -type AvailParamsBase struct { - GuestCount int `json:"guest_count" form:"guest_count" validate:"gt=0,lt=5"` - Arrive string `json:"arrive" form:"arrive" validate:"required"` - Depart string `json:"depart" form:"depart" validate:"required"` -} -type AvailParamsIDs struct { - AvailParamsBase - HotelIDs []string `json:"hotel_ids" form:"hotel_ids" validate:"required,dive,min=1,max=10"` -} -*/ type AvailParamsBase struct { GuestCount int `json:"guest_count" form:"guest_count"` Arrive string `json:"arrive" form:"arrive"` @@ -38,47 +27,60 @@ type AvailParamsLatLng struct { LatLng []string } -// Validate AvailParamsBase fields arrive, depart, guest_count looking at date formats and counts. -func (b AvailParamsBase) Validate() error { - tArrive, ok, err := ValidArriveDepart(b.Arrive) - if !ok { - return ErrStayFormat(ErrArriveFmtMsg, err.Error(), b.Arrive, timeShortForm) +// Validate AvailParamsBase fields. Time date formats arrive/depart are using app timezone location aware validations. Integer guest_count checks against min/max. +func (b AvailParamsBase) Validate(loc *time.Location) error { + //check for null or empty values first + if b.Arrive == "" { + return ErrArriveNull + } + if b.Depart == "" { + return ErrDepartNull + } + if b.GuestCount == 0 { + return ErrGuestCountNullOrZero } - tDepart, ok, err := ValidArriveDepart(b.Depart) + tArrive, ok, err := StayFormat(b.Arrive, loc) if !ok { - return ErrStayFormat(ErrDepartFmtMsg, err.Error(), b.Depart, timeShortForm) + return ErrStayFormat(ErrArriveFmtMsg, err.Error(), tArrive.String(), timeShortForm) } - - if !startBeforeEnd(tArrive, tDepart) { - return ErrStayRange(ErrStayRangeMsg, b.Arrive, b.Depart) + //get app time zone location + today := BeginOfDay(time.Now().In(loc)) + if ArriveNotInPast(tArrive, today) { + return ErrArriveInPast(ErrStayInPastMsg, tArrive.String(), today) } - - if b.GuestCount == 0 { - return ErrGuestCountNullOrZero + tDepart, ok, err := StayFormat(b.Depart, loc) + if !ok { + return ErrStayFormat(ErrDepartFmtMsg, err.Error(), tDepart.String(), timeShortForm) } - if Gt(b.GuestCount, GuestGTE) { - return ErrLtGt(ErrGuestLTEMsg, b.GuestCount, GuestGTE) + if DepartBeforeArrive(tDepart, tArrive) { + return ErrStayRange(ErrStayRangeMsg, tDepart.String(), tArrive.String()) } - if Lt(b.GuestCount, GuestLTE) { - return ErrLtGt(ErrGuestGTEMsg, b.GuestCount, GuestLTE) + if Gt(b.GuestCount, GuestMax) { + return ErrLtGt(ErrGuestMaxMsg, b.GuestCount, GuestMax) + } + if Lt(b.GuestCount, GuestMin) { + return ErrLtGt(ErrGuestMinMsg, b.GuestCount, GuestMin) } return nil } -func (a AvailParamsIDs) Validate() error { - if err := a.AvailParamsBase.Validate(); err != nil { +// Validate AvailParamsIDs runs params base validations and for hotel_ids +func (a AvailParamsIDs) Validate(loc *time.Location) error { + if err := a.AvailParamsBase.Validate(loc); err != nil { return err } - fmt.Printf("%v %d\n", a.HotelIDs, len(a.HotelIDs)) + //defense against no param or weird values like 'hotel_ids=', 'hotel_ids="', 'hotel_ids=""' + if len(a.HotelIDs) == 0 { + return ErrHotelIDNullOrZero + } if len(a.HotelIDs) == 1 { if (a.HotelIDs[0] == "") || (a.HotelIDs[0] == "\"") || (a.HotelIDs[0] == "\"\"") { return ErrHotelIDNullOrZero } } - - if Gt(len(a.HotelIDs), HotelIDsLTE) { - return ErrLtGt(ErrHotelIDsLTEMsg, len(a.HotelIDs), HotelIDsLTE) + if Gt(len(a.HotelIDs), HotelIDsMax) { + return ErrLtGt(ErrHotelIDsMaxsg, len(a.HotelIDs), HotelIDsMax) } return nil } @@ -90,60 +92,24 @@ https://github.com/tidwall/gjson curl -H "Accept: application/json" -X GET -d '{"arrive":"06-28","depart":"06-29","guest_count":"2", "hotel_ids":["007"]}' http://localhost:8080/avail | jq */ func (s *Server) HotelAvailIDsHandler() http.HandlerFunc { - var availPIDs AvailParamsIDs + var params AvailParamsIDs var decoder *form.Decoder //closure to execute return func(w http.ResponseWriter, r *http.Request) { - availPIDs = AvailParamsIDs{} + params = AvailParamsIDs{} decoder = form.NewDecoder() - if err := decoder.Decode(&availPIDs, r.URL.Query()); err != nil { - appMsg := fmt.Sprintf("Cannot Decode Query: %v", r.URL.Query()) - b, _ := json.Marshal( - apperr.NewErrorBadInput( - err.Error(), - appMsg, - apperr.BadInput, - http.StatusBadRequest, - ), - ) + // decode params, check errors + if err := decoder.Decode(¶ms, r.URL.Query()); err != nil { w.WriteHeader(http.StatusBadRequest) - w.Write(b) + w.Write(apperr.DecodeBadInput("HotelAvailIDsHandler", r.URL.Query(), err, http.StatusBadRequest)) return } - fmt.Printf("\n%+v\n", availPIDs) - if err := availPIDs.Validate(); err != nil { - b, _ := json.Marshal( - apperr.NewErrorInvalid( - err.Error(), - "Invalid", - apperr.Invalid, - http.StatusUnprocessableEntity, - ), - ) + // validate query params + if err := params.Validate(s.SConfig.AppTimeZone); err != nil { w.WriteHeader(http.StatusUnprocessableEntity) - w.Write(b) + w.Write(apperr.DecodeInvalid("HotelAvailIDsHandler", err, http.StatusUnprocessableEntity)) return } - /* - // decode, validate params - if err := decoder.Decode(¶ms, r.URL.Query()); err != nil { - appMsg := fmt.Sprintf("Cannot Decode Query: %v", r.URL.Query()) - b, _ := json.Marshal( - apperr.NewErrorBadInput(err.Error(), appMsg, apperr.BadInput, http.StatusBadRequest), - ) - w.WriteHeader(http.StatusBadRequest) - w.Write(b) - return - } - if err := params.Validate(); err != nil { - b, _ := json.Marshal( - apperr.NewErrorInvalid(err.Error(), "Invalid Request", apperr.BadInput, http.StatusUnprocessableEntity), - ) - w.WriteHeader(http.StatusUnprocessableEntity) - w.Write(b) - return - } - */ /* //get session sess := s.SessionPool.Pick() @@ -158,21 +124,25 @@ func (s *Server) HotelAvailIDsHandler() http.HandlerFunc { q, _ := hotelws.NewHotelSearchCriteria( hotelws.HotelRefSearch(searchids), ) - g, _ := strconv.Atoi(params.Base.GuestCount) - availBody := hotelws.SetHotelAvailBody(g, q, params.Base.Arrive, params.Base.Depart) + availBody := hotelws.SetHotelAvailBody( + params.AvailParamsBase.GuestCount, + q, + params.AvailParamsBase.Arrive, + params.AvailParamsBase.Depart, + ) req := hotelws.BuildHotelAvailRequest(s.SConfig, availBody) resp, _ := hotelws.CallHotelAvail(s.SConfig.ServiceURL, req) + */ + /* b, _ := json.Marshal(resp) w.WriteHeader(http.StatusOK) w.Write(b) */ - //b, _ := json.Marshal(params) - //w.WriteHeader(http.StatusOK) - //w.Write(b) w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(availPIDs) + //json.NewEncoder(w).Encode(resp) + json.NewEncoder(w).Encode(params) } } diff --git a/client/app/validation.go b/client/app/validation.go index 3246812..b9579a4 100644 --- a/client/app/validation.go +++ b/client/app/validation.go @@ -7,26 +7,29 @@ import ( ) const ( - GuestGTE = 4 - GuestLTE = 1 - HotelIDsLTE = 5 - timeShortForm = "01-02" + GuestMax = 4 + GuestMin = 1 + HotelIDsMax = 5 + timeShortForm = "2006-01-02" ) var ( - ErrHotelIDNullOrZero = errors.New("invalid hotel_id: query not present or value not defined") + ErrArriveNull = errors.New("invalid arrive: query not present or value not defined") + ErrDepartNull = errors.New("invalid depart: query not present or value not defined") + ErrHotelIDNullOrZero = errors.New("invalid hotel_ids: query not present or value not defined") ErrGuestCountNullOrZero = errors.New("invalid guest_count: query not present or value is 0") ErrSearchCriterion = errors.New("invalid search criterion: verify only one kind of criterion (hotel_id, lat_lng, city_code)") ) var ( - ErrGuestLTEMsg = "invalid guest_count '%d': must be less than or equal '%d'" - ErrGuestGTEMsg = "invalid guest_count '%d': must be greater than or equal '%d'" - ErrHotelIDsLTEMsg = "invalid hotel_ids '%d': must be less than or equal '%d'" - ErrArriveFmtMsg = "invalid arrive '%v': format (MM-DD '%s'). %v" - ErrDepartFmtMsg = "invalid depart '%v': format (MM-DD '%s'). %v" - ErrStayRangeMsg = "invalid range: arrive '%v' must be before depart '%v'" + ErrGuestMaxMsg = "invalid guest_count '%d': must be less than or equal '%d'" + ErrGuestMinMsg = "invalid guest_count '%d': must be greater than or equal '%d'" + ErrHotelIDsMaxsg = "invalid hotel_ids '%d': must be less than or equal '%d'" + ErrArriveFmtMsg = "invalid arrive '%s': format (YYYY-MM-DD '%s'). %s" + ErrStayInPastMsg = "invalid arrive '%s': cannot be before today '%s'" + ErrDepartFmtMsg = "invalid depart '%s': format (YYYY-MM-DD '%s'). %s" + ErrStayRangeMsg = "invalid range: depart '%s' must be after arrive '%s'" ) func ErrLtGt(msgFormat string, given, expect int) error { @@ -38,19 +41,33 @@ func ErrStayFormat(msgFormat, errMsg, given, expect string) error { func ErrStayRange(msgFormat, given, expect string) error { return fmt.Errorf(msgFormat, given, expect) } +func ErrArriveInPast(msgFormat, given string, now time.Time) error { + return fmt.Errorf(msgFormat, given, now) +} func Lt(current, compare int) bool { return current < compare } func Gt(current, compare int) bool { return current > compare } -func startBeforeEnd(start, end time.Time) bool { - return start.Before(end) +func DepartBeforeArrive(d, a time.Time) bool { + return d.Before(a) +} +func ArriveNotInPast(a, n time.Time) bool { + return a.Before(n) } -func ValidArriveDepart(ts string) (time.Time, bool, error) { - t, err := time.Parse(timeShortForm, ts) + +// StayFormat validates format of incoming params, returning boolean for conditional checking, error for creating an informative validation error message, and parsed time in the app time zone location in order to check other before/after validations. +func StayFormat(stay string, loc *time.Location) (time.Time, bool, error) { + t, err := time.ParseInLocation(timeShortForm, stay, loc) if err != nil { return t, false, err } return t, true, nil } + +// BeginOfDay sets the beginning of the day for time +func BeginOfDay(t time.Time) time.Time { + year, month, day := t.Date() + return time.Date(year, month, day, 0, 0, 0, 0, t.Location()) +} diff --git a/client/main.go b/client/main.go index 7a12147..05dcdb1 100644 --- a/client/main.go +++ b/client/main.go @@ -20,6 +20,7 @@ const ( ConfSabrePassword = "SABRE_PASSWORD" ConfSabrePCC = "SABRE_PCC" ConfFile = "config" + ConfTimeZone = "app_timezone" ConfExpireMin = "sessions.expire.min" ConfExpireMax = "sessions.expire.max" ConfPoolSize = "sessions.client.pool_size" @@ -28,23 +29,26 @@ const ( ) var ( - vRepeatEvery = time.Minute * 3 - sessConf = &srvc.SessionConf{} - vipConf = &viper.Viper{} - port = ":8080" + vRepeatEvery = time.Minute * 3 + sessConf = &srvc.SessionConf{} + vipConf = &viper.Viper{} + port = ":8080" + ClientAppTimeZone = &time.Location{} ) func init() { + ClientAppTimeZone = time.UTC vipConf = setConfig() sessConf = &srvc.SessionConf{ - ServiceURL: vipConf.GetString(ConfSabreURL), - From: vipConf.GetString(ConfClientURL), - PCC: vipConf.GetString(ConfSabrePCC), - Convid: srvc.GenerateConversationID(vipConf.GetString(ConfClientURL)), - Msgid: srvc.GenerateMessageID(), - Timestr: srvc.SabreTimeFormat(), - Username: vipConf.GetString(ConfSabreUsername), - Password: vipConf.GetString(ConfSabrePassword), + ServiceURL: vipConf.GetString(ConfSabreURL), + From: vipConf.GetString(ConfClientURL), + PCC: vipConf.GetString(ConfSabrePCC), + Convid: srvc.GenerateConversationID(vipConf.GetString(ConfClientURL)), + Msgid: srvc.GenerateMessageID(), + Timestr: srvc.SabreTimeFormat(), + Username: vipConf.GetString(ConfSabreUsername), + Password: vipConf.GetString(ConfSabrePassword), + AppTimeZone: ClientAppTimeZone, } } @@ -58,6 +62,9 @@ func setConfig() *viper.Viper { vip.BindEnv(ConfSabrePassword) vip.BindEnv(ConfSabrePCC) + //loc, _ := time.LoadLocation("Europe/Berlin") + vip.SetDefault("app_timezone", "UTC") + err := vip.ReadInConfig() if err != nil { panic(fmt.Errorf("Fatal error config file: %s \n", err)) diff --git a/engine/srvc/soap_base.go b/engine/srvc/soap_base.go index dcd356f..9fb5314 100644 --- a/engine/srvc/soap_base.go +++ b/engine/srvc/soap_base.go @@ -381,15 +381,16 @@ type SessionCreateRequest struct { } type SessionConf struct { - ServiceURL string - From string - PCC string - Binsectok string - Convid string - Msgid string - Timestr string - Username string - Password string + ServiceURL string + From string + PCC string + Binsectok string + Convid string + Msgid string + Timestr string + Username string + Password string + AppTimeZone *time.Location } // SetTime updates the timestamp. Pass around SessionConf and update the timestamp for any new request From 837b20cd8e34685514c6d5056bb3aae8a9d58145 Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Wed, 11 Jul 2018 20:20:33 -0600 Subject: [PATCH 11/21] standardize erors, update hotel id handler --- apperr/aperr.go | 52 +++++++++++++--- client/app/handlers.go | 114 +++++++++++++++++++--------------- client/main.go | 2 +- engine/hotelws/hotel_avail.go | 2 +- engine/hotelws/hotelws.go | 6 +- engine/itin/pnr_details.go | 2 +- engine/sbrerr/sbrerr.go | 60 +++++++++--------- engine/sbrerr/sbrerr_test.go | 8 +-- 8 files changed, 146 insertions(+), 100 deletions(-) diff --git a/apperr/aperr.go b/apperr/aperr.go index 85c717d..4b13e54 100644 --- a/apperr/aperr.go +++ b/apperr/aperr.go @@ -54,13 +54,45 @@ func AppStatusCode(input string) AppStatus { } } +// ErrorUnknown container for validation errors +type ErrorUnknown struct { + ErrMessage string `json:",omitempty"` + AppMessage string `json:",omitempty"` + Code AppStatus + HTTPStatus uint + ServerTime time.Time +} + +// NewErrorUnknown for validation errors +func NewErrorUnknown(errIn, appIn string, code AppStatus, hts uint) ErrorUnknown { + return ErrorUnknown{ErrMessage: errIn, AppMessage: appIn, Code: code, HTTPStatus: hts, ServerTime: time.Now()} +} + +// Error for ErrorUnknown implements std lib error interface +func (e ErrorUnknown) Error() string { + return e.ErrMessage +} + +// DecodeInvalid builds an ErrorInvalid response given a set of url.Values +func DecodeUnknown(handlerMsg string, val url.Values, err error, httpstatus uint) []byte { + b, _ := json.Marshal( + NewErrorUnknown( + err.Error(), + fmt.Sprintf("%s. Unknown for Query: %v", handlerMsg, val), + Unknown, + httpstatus, + ), + ) + return b +} + // ErrorInvalid container for validation errors type ErrorInvalid struct { - ErrMessage string `json:"err_invalid,omitempty"` - AppMessage string `json:"app_invalid,omitempty"` - Code AppStatus `json:"app_status"` - HTTPStatus uint `json:"http_status"` - ServerTime time.Time `json:"server_time"` + ErrMessage string `json:",omitempty"` + AppMessage string `json:",omitempty"` + Code AppStatus + HTTPStatus uint + ServerTime time.Time } // NewErrorInvalid for validation errors @@ -89,11 +121,11 @@ func DecodeInvalid(handlerMsg string, err error, httpstatus uint) []byte { // ErrorBadInput container for bad input or json parsing type ErrorBadInput struct { - ErrMessage string `json:"err_bad_input,omitempty"` - AppMessage string `json:"app_bad_input,omitempty"` - Code AppStatus `json:"app_status"` - HTTPStatus uint `json:"http_status"` - ServerTime time.Time `json:"server_time"` + ErrMessage string `json:",omitempty"` + AppMessage string `json:",omitempty"` + Code AppStatus + HTTPStatus uint + ServerTime time.Time } // NewErrorBadInput for json parsing or other bad input diff --git a/client/app/handlers.go b/client/app/handlers.go index 2478180..5273137 100644 --- a/client/app/handlers.go +++ b/client/app/handlers.go @@ -6,18 +6,23 @@ import ( "time" "github.com/ailgroup/sbrweb/apperr" + "github.com/ailgroup/sbrweb/engine/hotelws" "github.com/go-playground/form" ) type AvailParamsBase struct { - GuestCount int `json:"guest_count" form:"guest_count"` - Arrive string `json:"arrive" form:"arrive"` - Depart string `json:"depart" form:"depart"` + GuestCount int `form:"guest_count"` + InputArrive string `form:"arrive"` + InputDepart string `form:"depart"` + OutArrive string + OutDepart string } type AvailParamsIDs struct { - AvailParamsBase - HotelIDs []string `json:"hotel_ids" form:"hotel_ids"` + *AvailParamsBase + HotelIDs []string `form:"hotel_ids"` } + +/* TODO... type AvailParamsCityCodes struct { Base AvailParamsBase CityCodes []string @@ -26,21 +31,28 @@ type AvailParamsLatLng struct { Base AvailParamsBase LatLng []string } +*/ -// Validate AvailParamsBase fields. Time date formats arrive/depart are using app timezone location aware validations. Integer guest_count checks against min/max. -func (b AvailParamsBase) Validate(loc *time.Location) error { +type AvailabilityResponse struct { + RequestParams AvailParamsIDs + SabreEngineErrors interface{} `json:",omitempty"` + HotelAvail hotelws.AvailabilityOptions +} + +// Validate AvailParamsBase fields. Time date formats arrive/depart are using app timezone location aware validations and set the outgoing arrive/depart formats for sabre. Integer guest_count checks against min/max. +func (b *AvailParamsBase) ValidateAndFormat(loc *time.Location) error { //check for null or empty values first - if b.Arrive == "" { + if b.InputArrive == "" { return ErrArriveNull } - if b.Depart == "" { + if b.InputDepart == "" { return ErrDepartNull } if b.GuestCount == 0 { return ErrGuestCountNullOrZero } - tArrive, ok, err := StayFormat(b.Arrive, loc) + tArrive, ok, err := StayFormat(b.InputArrive, loc) if !ok { return ErrStayFormat(ErrArriveFmtMsg, err.Error(), tArrive.String(), timeShortForm) } @@ -49,13 +61,16 @@ func (b AvailParamsBase) Validate(loc *time.Location) error { if ArriveNotInPast(tArrive, today) { return ErrArriveInPast(ErrStayInPastMsg, tArrive.String(), today) } - tDepart, ok, err := StayFormat(b.Depart, loc) + tDepart, ok, err := StayFormat(b.InputDepart, loc) if !ok { return ErrStayFormat(ErrDepartFmtMsg, err.Error(), tDepart.String(), timeShortForm) } if DepartBeforeArrive(tDepart, tArrive) { return ErrStayRange(ErrStayRangeMsg, tDepart.String(), tArrive.String()) } + b.OutArrive = tArrive.Format(hotelws.TimeFormatMD) + b.OutDepart = tDepart.Format(hotelws.TimeFormatMD) + if Gt(b.GuestCount, GuestMax) { return ErrLtGt(ErrGuestMaxMsg, b.GuestCount, GuestMax) } @@ -67,7 +82,7 @@ func (b AvailParamsBase) Validate(loc *time.Location) error { // Validate AvailParamsIDs runs params base validations and for hotel_ids func (a AvailParamsIDs) Validate(loc *time.Location) error { - if err := a.AvailParamsBase.Validate(loc); err != nil { + if err := a.AvailParamsBase.ValidateAndFormat(loc); err != nil { return err } //defense against no param or weird values like 'hotel_ids=', 'hotel_ids="', 'hotel_ids=""' @@ -87,17 +102,17 @@ func (a AvailParamsIDs) Validate(loc *time.Location) error { /* HotelAvailHandler wraps SOAP call to sabre hotel availability service -https://github.com/tidwall/gjson -curl -H "Accept: application/json" -X GET -d '{"arrive":"06-28","depart":"06-29","guest_count":"2", "hotel_ids":["007"]}' http://localhost:8080/avail | jq + Example: + curl -H "Accept: application/json" -X GET 'http://localhost:8080/avail?guest_count=4&arrive=2018-07-17&depart=2018-07-18&hotel_ids=10' */ func (s *Server) HotelAvailIDsHandler() http.HandlerFunc { - var params AvailParamsIDs - var decoder *form.Decoder //closure to execute return func(w http.ResponseWriter, r *http.Request) { - params = AvailParamsIDs{} - decoder = form.NewDecoder() + w.Header().Set("Content-Type", "application/json") + params := &AvailParamsIDs{} + decoder := form.NewDecoder() + response := AvailabilityResponse{} // decode params, check errors if err := decoder.Decode(¶ms, r.URL.Query()); err != nil { w.WriteHeader(http.StatusBadRequest) @@ -110,39 +125,38 @@ func (s *Server) HotelAvailIDsHandler() http.HandlerFunc { w.Write(apperr.DecodeInvalid("HotelAvailIDsHandler", err, http.StatusUnprocessableEntity)) return } - /* - //get session - sess := s.SessionPool.Pick() - //defer close on session - defer s.SessionPool.Put(sess) - //get binary security token - s.SConfig.SetBinSec(sess.Sabre) - // parse incoming params as JSON - searchids := make(hotelws.HotelRefCriterion) - searchids[hotelws.HotelidQueryField] = params.HotelIDs - - q, _ := hotelws.NewHotelSearchCriteria( - hotelws.HotelRefSearch(searchids), - ) - availBody := hotelws.SetHotelAvailBody( - params.AvailParamsBase.GuestCount, - q, - params.AvailParamsBase.Arrive, - params.AvailParamsBase.Depart, - ) - req := hotelws.BuildHotelAvailRequest(s.SConfig, availBody) - resp, _ := hotelws.CallHotelAvail(s.SConfig.ServiceURL, req) - */ - - /* - b, _ := json.Marshal(resp) - w.WriteHeader(http.StatusOK) - w.Write(b) - */ + response.RequestParams = *params + //get session, defer close + sess := s.SessionPool.Pick() + defer s.SessionPool.Put(sess) + //get binary security token + s.SConfig.SetBinSec(sess.Sabre) + // parse incoming params as JSON + searchids := make(hotelws.HotelRefCriterion) + searchids[hotelws.HotelidQueryField] = params.HotelIDs - w.Header().Set("Content-Type", "application/json") - //json.NewEncoder(w).Encode(resp) - json.NewEncoder(w).Encode(params) + q, _ := hotelws.NewHotelSearchCriteria( + hotelws.HotelRefSearch(searchids), + ) + availBody := hotelws.SetHotelAvailBody( + params.AvailParamsBase.GuestCount, + q, + params.AvailParamsBase.OutArrive, + params.AvailParamsBase.OutDepart, + ) + + req := hotelws.BuildHotelAvailRequest(s.SConfig, availBody) + call, err := hotelws.CallHotelAvail(s.SConfig.ServiceURL, req) + if err != nil { + w.WriteHeader(http.StatusFailedDependency) + w.Write(apperr.DecodeUnknown("CallHotelAvail::HotelAvailIDsHandler", r.URL.Query(), err, http.StatusFailedDependency)) + return + } + + response.HotelAvail = call.Body.HotelAvail.AvailOpts + + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) } } diff --git a/client/main.go b/client/main.go index 05dcdb1..e49d4a1 100644 --- a/client/main.go +++ b/client/main.go @@ -105,7 +105,7 @@ func main() { // create and populate pool, start keepalive, watch for signal close down pool := srvc.NewPool(scheme, sessConf, vipConf.GetInt(ConfPoolSize)) - //runPool(pool) + runPool(pool) // pass context through handlers?? m := chi.NewRouter() diff --git a/engine/hotelws/hotel_avail.go b/engine/hotelws/hotel_avail.go index 4ae51d1..f1400cf 100644 --- a/engine/hotelws/hotel_avail.go +++ b/engine/hotelws/hotel_avail.go @@ -105,7 +105,7 @@ func BuildHotelAvailRequest(c *srvc.SessionConf, otaHotelAvail HotelAvailBody) H } type AvailabilityOptions struct { - XMLName xml.Name `xml:"AvailabilityOptions"` + XMLName xml.Name `xml:"AvailabilityOptions" json:"-"` AvailableOptions []AvailabilityOption `xml:"AvailabilityOption"` } diff --git a/engine/hotelws/hotelws.go b/engine/hotelws/hotelws.go index 1f7d336..bf76b98 100644 --- a/engine/hotelws/hotelws.go +++ b/engine/hotelws/hotelws.go @@ -94,7 +94,7 @@ func (s SystemResults) Translate() string { */ func (result ApplicationResults) ErrFormat() sbrerr.ErrorSabreResult { return sbrerr.ErrorSabreResult{ - Code: sbrerr.AppStatusCode(result.Status), + Code: sbrerr.SabreEngineStatusCode(result.Status), AppMessage: fmt.Sprintf( "%s because %s. %s. HostCommand[LNIATA: %s Cryptic: %s]", result.Status, @@ -271,7 +271,7 @@ type PaymentCard struct { } type RoomRate struct { - XMLName xml.Name `xml:"RoomRate"` + XMLName xml.Name `xml:"RoomRate" json:"-"` DirectConnect string `xml:"RDirectConnect,attr"` GuaranteeSurcharge string `xml:"GuaranteeSurchargeRequired,attr"` GuaranteedRate string `xml:"GuaranteedRateProgram,attr"` @@ -313,7 +313,7 @@ type AdditionalInfo struct { // BasicPropertyInfo contains all info relevant to property. It is the root-level element after service element for hotel_avail; and is embedded in the RoomStay element. type BasicPropertyInfo struct { - XMLName xml.Name `xml:"BasicPropertyInfo"` + XMLName xml.Name `xml:"BasicPropertyInfo" json:"-"` AreadID string `xml:"AreadID,attr"` ChainCode string `xml:"ChainCode,attr"` Distance string `xml:"Distance,attr"` diff --git a/engine/itin/pnr_details.go b/engine/itin/pnr_details.go index cc43025..f94512f 100644 --- a/engine/itin/pnr_details.go +++ b/engine/itin/pnr_details.go @@ -300,7 +300,7 @@ func (result ApplicationResults) ErrFormat() sbrerr.ErrorSabreResult { wmsg += fmt.Sprintf("Warning-%d:Type-%s Results: %s", i, w.Type, msg) } return sbrerr.ErrorSabreResult{ - Code: sbrerr.AppStatusCode(result.Status), + Code: sbrerr.SabreEngineStatusCode(result.Status), AppMessage: wmsg, } } diff --git a/engine/sbrerr/sbrerr.go b/engine/sbrerr/sbrerr.go index a91976c..37491b1 100644 --- a/engine/sbrerr/sbrerr.go +++ b/engine/sbrerr/sbrerr.go @@ -4,17 +4,17 @@ import ( "errors" ) -type AppStatus int +type SabreStatus int // List statuses for common error handling and parsing. Generally, the lower number are more serious. const ( - Unknown AppStatus = iota + 1 //1 - BadService //2 - BadParse //3 - SoapFault //4 - NotProcessed //5 - Approved //6 - Complete //7 + Unknown SabreStatus = iota + 1 //1 + BadService //2 + BadParse //3 + SoapFault //4 + NotProcessed //5 + Approved //6 + Complete //7 ) const ( @@ -33,7 +33,7 @@ var ( ErrPropDescLatLng = errors.New("Latitude or Longitude not allowed in HotelPropDesc") ErrPropDescHotelRefs = errors.New("Criterion.HotelRef cannot be greater than 1, can only search using one criterion") - appStatuses = [...]string{ + sabreEngineStatuses = [...]string{ "0", "Unknown", "BadService", @@ -46,21 +46,21 @@ var ( ) func StatusNotProcess() string { - return appStatuses[NotProcessed] + return sabreEngineStatuses[NotProcessed] } func StatusApproved() string { - return appStatuses[Approved] + return sabreEngineStatuses[Approved] } func StatusComplete() string { - return appStatuses[Complete] + return sabreEngineStatuses[Complete] } -func (code AppStatus) String() string { +func (code SabreStatus) String() string { if code < Unknown || code > Complete { return "Unknown" } - return appStatuses[code] + return sabreEngineStatuses[code] } -func AppStatusCode(input string) AppStatus { +func SabreEngineStatusCode(input string) SabreStatus { if input == "0" { return Unknown } @@ -84,13 +84,13 @@ func AppStatusCode(input string) AppStatus { // ErrorSabreService container for network issues type ErrorSabreService struct { - ErrMessage string `json:"err_message_sabre_service,omitempty"` - AppMessage string `json:"app_message_sabre_service,omitempty"` - Code AppStatus `json:"app_status"` + ErrMessage string `json:",omitempty"` + AppMessage string `json:",omitempty"` + Code SabreStatus } // NewErrorSabreService for http or sabre web services networking problems -func NewErrorSabreService(errIn, appIn string, code AppStatus) ErrorSabreService { +func NewErrorSabreService(errIn, appIn string, code SabreStatus) ErrorSabreService { //err = strings.Replace(err, "\n", "", -1) return ErrorSabreService{ErrMessage: errIn, AppMessage: appIn, Code: code} } @@ -102,13 +102,13 @@ func (e ErrorSabreService) Error() string { // ErrorSabreXML container for xml issues type ErrorSabreXML struct { - ErrMessage string `json:"err_message_sabre_xml,omitempty"` - AppMessage string `json:"app_message_sabre_xml,omitempty"` - Code AppStatus `json:"app_status"` + ErrMessage string `json:",omitempty"` + AppMessage string `json:",omitempty"` + Code SabreStatus } // ErrorSabreXML for parsing web services responses -func NewErrorSabreXML(errIn, appIn string, code AppStatus) ErrorSabreXML { +func NewErrorSabreXML(errIn, appIn string, code SabreStatus) ErrorSabreXML { //err = strings.Replace(err, "\n", "", -1) return ErrorSabreXML{ErrMessage: errIn, AppMessage: appIn, Code: code} } @@ -120,12 +120,12 @@ func (e ErrorSabreXML) Error() string { // ErrorSabreResult for results issues type ErrorSabreResult struct { - AppMessage string `json:"app_message_sabre_results,omitempty"` - Code AppStatus `json:"app_status"` + AppMessage string `json:",omitempty"` + Code SabreStatus } // NewErrorSabreResult for response results with errors(bad dates, credit card, etc...) -func NewErrorSabreResult(appIn string, code AppStatus) ErrorSabreResult { +func NewErrorSabreResult(appIn string, code SabreStatus) ErrorSabreResult { //err = strings.Replace(err, "\n", "", -1) return ErrorSabreResult{AppMessage: appIn, Code: code} } @@ -137,10 +137,10 @@ func (e ErrorSabreResult) Error() string { // ErrorSoapFault for results issues type ErrorSoapFault struct { - ErrMessage string `json:"err_message_soap_fault_string,omitempty"` - FaultCode string `json:"soap_fault_code,omitempty"` - StackTrace string `json:"soap_fault_stacktrace,omitempty"` - Code AppStatus `json:"app_status"` + ErrMessage string `json:",omitempty"` + FaultCode string `json:",omitempty"` + StackTrace string `json:",omitempty"` + Code SabreStatus } // NewErrorSoapFault for response results with errors(bad dates, credit card, etc...) diff --git a/engine/sbrerr/sbrerr_test.go b/engine/sbrerr/sbrerr_test.go index cf0292c..9f20596 100644 --- a/engine/sbrerr/sbrerr_test.go +++ b/engine/sbrerr/sbrerr_test.go @@ -23,7 +23,7 @@ func TestStatusHelperFuncs(t *testing.T) { } var sampleStatuses = []struct { - code AppStatus + code SabreStatus expect string }{ {-100, "Unknown"}, @@ -52,7 +52,7 @@ func TestAppStatus(t *testing.T) { } var sampleCodes = []struct { - expect AppStatus + expect SabreStatus input string }{ {1, "Unknown"}, @@ -73,8 +73,8 @@ var sampleCodes = []struct { func TestGetStatus(t *testing.T) { for i, c := range sampleCodes { - if AppStatusCode(c.input) != c.expect { - t.Errorf("sampleCodes: %d for input %s expect %d got %d", i, c.input, c.expect, AppStatusCode(c.input)) + if SabreEngineStatusCode(c.input) != c.expect { + t.Errorf("sampleCodes: %d for input %s expect %d got %d", i, c.input, c.expect, SabreEngineStatusCode(c.input)) } } } From 6231942f791db269607b2577e69061a4dbdfdf7c Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Thu, 12 Jul 2018 20:25:45 -0600 Subject: [PATCH 12/21] hotel property rates with hotel id --- client/app/handlers.go | 221 +++++++++++++++++++++++--------------- client/app/routes.go | 10 +- engine/hotelws/hotelws.go | 2 +- 3 files changed, 140 insertions(+), 93 deletions(-) diff --git a/client/app/handlers.go b/client/app/handlers.go index 5273137..f08ba8e 100644 --- a/client/app/handlers.go +++ b/client/app/handlers.go @@ -10,37 +10,81 @@ import ( "github.com/go-playground/form" ) -type AvailParamsBase struct { +/* + + TODO... implement hotel availability requests by city_code, lat/long, and address + + +HOTELEREF: + hqids = make(HotelRefCriterion) + hqltln = make(HotelRefCriterion) + hqcity = make(HotelRefCriterion) + + search[hotelws.HotelidQueryField] = []string{"0012", "19876", "1109", "445098", "000034"} + search[hotelws.LatlngQueryField] = []string{"32.78,-96.81", "54.87,-102.96"} + search[hotelws.CountryCodeQueryField] = []string{"DFW", "CHC", "LA"} + + //TODO... +type HotelParamsLatLng struct { + Base AvailParamsBase + LatLng []string +} + //TODO... +type HotelParamsCityCodes struct { + Base AvailParamsBase + CityCodes []string +} + + +ADDRESS: + addr = make(AddressCriterion) + addr[StreetQueryField] = "2031 N. 100 W" + addr[CityQueryField] = "Aneheim" + addr[PostalQueryField] = "90458" + addr[CountryCodeQueryField] = "US" + + //TODO... +type HotelParamsAddress struct { + Base AvailParamsBase + Street string + City string + PostalCode string + CountryCode string +} +*/ + +// HotelParamsBase hold guest, arrive, depart that are needed for any hotel request. Distinguish between incoming and outgoing params to allow mutliple time formats; that is, sabre onlyl accepts month-day formats but we want client API to force using the year as well. +type HotelParamsBase struct { GuestCount int `form:"guest_count"` InputArrive string `form:"arrive"` InputDepart string `form:"depart"` OutArrive string OutDepart string } -type AvailParamsIDs struct { - *AvailParamsBase + +// HotelParamsIDs holds 1..n hotel ids for making queries +type HotelParamsIDs struct { + *HotelParamsBase + Max int HotelIDs []string `form:"hotel_ids"` } -/* TODO... -type AvailParamsCityCodes struct { - Base AvailParamsBase - CityCodes []string +// AvailHotelIDSResponse for sabre hotel availability +type AvailHotelIDSResponse struct { + RequestParams HotelParamsIDs + SabreEngineErrors interface{} `json:",omitempty"` + AvailabilityOptions hotelws.AvailabilityOptions } -type AvailParamsLatLng struct { - Base AvailParamsBase - LatLng []string -} -*/ -type AvailabilityResponse struct { - RequestParams AvailParamsIDs +// PropertyHotelIDResponse for sabre property description +type PropertyHotelIDResponse struct { + RequestParams HotelParamsIDs SabreEngineErrors interface{} `json:",omitempty"` - HotelAvail hotelws.AvailabilityOptions + RoomStay hotelws.RoomStay } // Validate AvailParamsBase fields. Time date formats arrive/depart are using app timezone location aware validations and set the outgoing arrive/depart formats for sabre. Integer guest_count checks against min/max. -func (b *AvailParamsBase) ValidateAndFormat(loc *time.Location) error { +func (b *HotelParamsBase) ValidateAndFormat(loc *time.Location) error { //check for null or empty values first if b.InputArrive == "" { return ErrArriveNull @@ -81,48 +125,47 @@ func (b *AvailParamsBase) ValidateAndFormat(loc *time.Location) error { } // Validate AvailParamsIDs runs params base validations and for hotel_ids -func (a AvailParamsIDs) Validate(loc *time.Location) error { - if err := a.AvailParamsBase.ValidateAndFormat(loc); err != nil { +func (a HotelParamsIDs) Validate(loc *time.Location) error { + if err := a.HotelParamsBase.ValidateAndFormat(loc); err != nil { return err } - //defense against no param or weird values like 'hotel_ids=', 'hotel_ids="', 'hotel_ids=""' if len(a.HotelIDs) == 0 { return ErrHotelIDNullOrZero } + if Gt(len(a.HotelIDs), a.Max) { + return ErrLtGt(ErrHotelIDsMaxsg, len(a.HotelIDs), HotelIDsMax) + } + //defense against no param or weird values like 'hotel_ids=', 'hotel_ids="', 'hotel_ids=""' if len(a.HotelIDs) == 1 { if (a.HotelIDs[0] == "") || (a.HotelIDs[0] == "\"") || (a.HotelIDs[0] == "\"\"") { return ErrHotelIDNullOrZero } } - if Gt(len(a.HotelIDs), HotelIDsMax) { - return ErrLtGt(ErrHotelIDsMaxsg, len(a.HotelIDs), HotelIDsMax) - } return nil } /* -HotelAvailHandler wraps SOAP call to sabre hotel availability service +PropertyDescriptionIDsHandler wraps SOAP call to sabre property description service. This SOAP service is the primary service for returning room rates. It accepts one hotel ref criterion and returns one hotel with one room stay object containing 0..n room rates. Example: - curl -H "Accept: application/json" -X GET 'http://localhost:8080/avail?guest_count=4&arrive=2018-07-17&depart=2018-07-18&hotel_ids=10' */ -func (s *Server) HotelAvailIDsHandler() http.HandlerFunc { +func (s *Server) PropertyDescriptionIDsHandler() http.HandlerFunc { //closure to execute return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - params := &AvailParamsIDs{} + params := &HotelParamsIDs{Max: 1} decoder := form.NewDecoder() - response := AvailabilityResponse{} + response := PropertyHotelIDResponse{} // decode params, check errors if err := decoder.Decode(¶ms, r.URL.Query()); err != nil { w.WriteHeader(http.StatusBadRequest) - w.Write(apperr.DecodeBadInput("HotelAvailIDsHandler", r.URL.Query(), err, http.StatusBadRequest)) + w.Write(apperr.DecodeBadInput("PropertyDescriptionIDsHandler", r.URL.Query(), err, http.StatusBadRequest)) return } // validate query params if err := params.Validate(s.SConfig.AppTimeZone); err != nil { w.WriteHeader(http.StatusUnprocessableEntity) - w.Write(apperr.DecodeInvalid("HotelAvailIDsHandler", err, http.StatusUnprocessableEntity)) + w.Write(apperr.DecodeInvalid("PropertyDescriptionIDsHandler", err, http.StatusUnprocessableEntity)) return } response.RequestParams = *params @@ -132,28 +175,29 @@ func (s *Server) HotelAvailIDsHandler() http.HandlerFunc { //get binary security token s.SConfig.SetBinSec(sess.Sabre) // parse incoming params as JSON - searchids := make(hotelws.HotelRefCriterion) - searchids[hotelws.HotelidQueryField] = params.HotelIDs + hotelid := make(hotelws.HotelRefCriterion) + hotelid[hotelws.HotelidQueryField] = params.HotelIDs + //no need to handle error in these two functions since api validations give similar guarantee q, _ := hotelws.NewHotelSearchCriteria( - hotelws.HotelRefSearch(searchids), + hotelws.HotelRefSearch(hotelid), ) - availBody := hotelws.SetHotelAvailBody( - params.AvailParamsBase.GuestCount, + body, _ := hotelws.SetHotelPropDescBody( + params.HotelParamsBase.GuestCount, q, - params.AvailParamsBase.OutArrive, - params.AvailParamsBase.OutDepart, + params.HotelParamsBase.OutArrive, + params.HotelParamsBase.OutDepart, ) - req := hotelws.BuildHotelAvailRequest(s.SConfig, availBody) - call, err := hotelws.CallHotelAvail(s.SConfig.ServiceURL, req) + req := hotelws.BuildHotelPropDescRequest(s.SConfig, body) + call, err := hotelws.CallHotelPropDesc(s.SConfig.ServiceURL, req) if err != nil { w.WriteHeader(http.StatusFailedDependency) - w.Write(apperr.DecodeUnknown("CallHotelAvail::HotelAvailIDsHandler", r.URL.Query(), err, http.StatusFailedDependency)) + w.Write(apperr.DecodeUnknown("CallHotelPropDesc::PropertyDescriptionIDsHandler", r.URL.Query(), err, http.StatusFailedDependency)) return } - response.HotelAvail = call.Body.HotelAvail.AvailOpts + response.RoomStay = call.Body.HotelDesc.RoomStay w.WriteHeader(http.StatusOK) json.NewEncoder(w).Encode(response) @@ -161,62 +205,63 @@ func (s *Server) HotelAvailIDsHandler() http.HandlerFunc { } /* +HotelAvailIDsHandler wraps SOAP call to sabre hotel availability service. This SOAP service does not return rates for rooms. It accepts many hotel ref criteria and returns many hotel options. + + Example: + curl -H "Accept: application/json" -X GET 'http://localhost:8080/avail/hotel/id?guest_count=4&arrive=2018-07-17&depart=2018-07-18&hotel_ids=10' + curl -H "Accept: application/json" -X GET 'http://localhost:8080/avail/hotel/id?guest_count=4&arrive=2018-07-17&depart=2018-07-18&hotel_ids=10,12' +*/ +func (s *Server) HotelAvailIDsHandler() http.HandlerFunc { + //closure to execute + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + params := &HotelParamsIDs{Max: HotelIDsMax} + decoder := form.NewDecoder() + response := AvailHotelIDSResponse{} + // decode params, check errors + if err := decoder.Decode(¶ms, r.URL.Query()); err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write(apperr.DecodeBadInput("HotelAvailIDsHandler", r.URL.Query(), err, http.StatusBadRequest)) + return + } + // validate query params + if err := params.Validate(s.SConfig.AppTimeZone); err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + w.Write(apperr.DecodeInvalid("HotelAvailIDsHandler", err, http.StatusUnprocessableEntity)) + return + } + response.RequestParams = *params + //get session, defer close + sess := s.SessionPool.Pick() + defer s.SessionPool.Put(sess) + //get binary security token + s.SConfig.SetBinSec(sess.Sabre) + // parse incoming params as JSON + searchids := make(hotelws.HotelRefCriterion) + searchids[hotelws.HotelidQueryField] = params.HotelIDs - arrive := params.Get("arrive") - depart := params.Get("depart") - guests, _ := strconv.Atoi(params.Get("guest_count")) - ids := params["hotel_ids"] - search[hotelws.HotelidQueryField] = ids + //no need to handle error in these two functions since api validations give similar guarantee q, _ := hotelws.NewHotelSearchCriteria( - hotelws.HotelRefSearch(search), + hotelws.HotelRefSearch(searchids), + ) + availBody := hotelws.SetHotelAvailBody( + params.HotelParamsBase.GuestCount, + q, + params.HotelParamsBase.OutArrive, + params.HotelParamsBase.OutDepart, ) - availBody := hotelws.SetHotelAvailBody(guests, q, arrive, depart) + req := hotelws.BuildHotelAvailRequest(s.SConfig, availBody) - resp, err := hotelws.CallHotelAvail(s.SConfig.ServiceURL, req) + call, err := hotelws.CallHotelAvail(s.SConfig.ServiceURL, req) if err != nil { - fmt.Printf("CallHotelAvail ERROR: %v", err) - fmt.Fprint(w, err) + w.WriteHeader(http.StatusFailedDependency) + w.Write(apperr.DecodeUnknown("CallHotelAvail::HotelAvailIDsHandler", r.URL.Query(), err, http.StatusFailedDependency)) return } -*/ -/* -func (b AvailParamsBase) Validate() error { - if govalidator.IsNull(b.GuestCount) { - return ErrGuestCountNull - } - if govalidator.IsInt(b.GuestCount) { - return ErrGuestCountNotNumber - } - if govalidator.InRangeInt(b.GuestCount, "1", "4") { - return ErrGuestCountRange - } - return nil -} + response.AvailabilityOptions = call.Body.HotelAvail.AvailOpts -func (a AvailParamsIDs) Validate() error { - //if len(a.HotelIDs) - if len(a.HotelIDs) < 1 || len(a.HotelIDs) > 10 { - return ErrHotelIDRange + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(response) } - return nil } -*/ - -/* -HOTELEREF: - hqids = make(HotelRefCriterion) - hqltln = make(HotelRefCriterion) - hqcity = make(HotelRefCriterion) - - search[hotelws.HotelidQueryField] = []string{"0012", "19876", "1109", "445098", "000034"} - search[hotelws.LatlngQueryField] = []string{"32.78,-96.81", "54.87,-102.96"} - search[hotelws.CountryCodeQueryField] = []string{"DFW", "CHC", "LA"} - -ADDRESS: - addr = make(AddressCriterion) - addr[StreetQueryField] = "2031 N. 100 W" - addr[CityQueryField] = "Aneheim" - addr[PostalQueryField] = "90458" - addr[CountryCodeQueryField] = "US" -*/ diff --git a/client/app/routes.go b/client/app/routes.go index f8b7c03..106b14b 100644 --- a/client/app/routes.go +++ b/client/app/routes.go @@ -6,8 +6,10 @@ import ( // registerRoutes is responsible for registering the server-side request handlers func (s *Server) RegisterRoutes() { - s.Mux.Get("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte("hi")) }) - s.Mux.Handle("/avail", s.HotelAvailIDsHandler()) - //s.Mux.Handle("/avail/latlong", s.HotelAvailIDsHandler()) - //s.Mux.Handle("/avail/address", s.HotelAvailIDsHandler()) + s.Mux.Get("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`{"ping":"p0ng"}`)) }) + s.Mux.Handle("/avail/hotel/id", s.HotelAvailIDsHandler()) + s.Mux.Handle("/rates/hotel/id", s.PropertyDescriptionIDsHandler()) + //s.Mux.Handle("/avail/hotel/latlong", s.HotelAvailIDsHandler()) + //s.Mux.Handle("/avail/hotel/address", s.HotelAvailIDsHandler()) + //s.Mux.Handle("/avail/hotel/citycode", s.HotelAvailIDsHandler()) } diff --git a/engine/hotelws/hotelws.go b/engine/hotelws/hotelws.go index bf76b98..78e3819 100644 --- a/engine/hotelws/hotelws.go +++ b/engine/hotelws/hotelws.go @@ -288,7 +288,7 @@ type RoomRate struct { HotelRateCode string `xml:"HotelRateCode"` } type AdditionalInfo struct { - XMLName xml.Name `xml:"AdditionalInfo"` + XMLName xml.Name `xml:"AdditionalInfo" json:"-"` Commission struct { NonCommission string `xml:"NonCommission,attr"` Val string `xml:",char"` From 40cc1f94aa287df416fc14b74c424e64a30e8f21 Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Tue, 31 Jul 2018 07:08:13 -0600 Subject: [PATCH 13/21] add start of book room params, updates to other handlers --- client/app/handlers.go | 229 ++++++++++++++++++++++++---------- client/app/routes.go | 11 +- client/app/validation.go | 18 ++- client/main.go | 2 +- engine/srvc/session_pool.go | 2 +- engine/srvc/soap_base.go | 6 +- engine/srvc/soap_base_test.go | 6 +- 7 files changed, 188 insertions(+), 86 deletions(-) diff --git a/client/app/handlers.go b/client/app/handlers.go index f08ba8e..2e7b9c8 100644 --- a/client/app/handlers.go +++ b/client/app/handlers.go @@ -7,53 +7,11 @@ import ( "github.com/ailgroup/sbrweb/apperr" "github.com/ailgroup/sbrweb/engine/hotelws" + "github.com/ailgroup/sbrweb/engine/itin" "github.com/go-playground/form" ) -/* - - TODO... implement hotel availability requests by city_code, lat/long, and address - - -HOTELEREF: - hqids = make(HotelRefCriterion) - hqltln = make(HotelRefCriterion) - hqcity = make(HotelRefCriterion) - - search[hotelws.HotelidQueryField] = []string{"0012", "19876", "1109", "445098", "000034"} - search[hotelws.LatlngQueryField] = []string{"32.78,-96.81", "54.87,-102.96"} - search[hotelws.CountryCodeQueryField] = []string{"DFW", "CHC", "LA"} - - //TODO... -type HotelParamsLatLng struct { - Base AvailParamsBase - LatLng []string -} - //TODO... -type HotelParamsCityCodes struct { - Base AvailParamsBase - CityCodes []string -} - - -ADDRESS: - addr = make(AddressCriterion) - addr[StreetQueryField] = "2031 N. 100 W" - addr[CityQueryField] = "Aneheim" - addr[PostalQueryField] = "90458" - addr[CountryCodeQueryField] = "US" - - //TODO... -type HotelParamsAddress struct { - Base AvailParamsBase - Street string - City string - PostalCode string - CountryCode string -} -*/ - -// HotelParamsBase hold guest, arrive, depart that are needed for any hotel request. Distinguish between incoming and outgoing params to allow mutliple time formats; that is, sabre onlyl accepts month-day formats but we want client API to force using the year as well. +// HotelParamsBase hold guest, arrive, depart that are needed for any hotel request. Distinguish between incoming and outgoing params to allow mutliple time formats; that is, sabre only accepts month-day formats but we want client API to force using the year as well. type HotelParamsBase struct { GuestCount int `form:"guest_count"` InputArrive string `form:"arrive"` @@ -69,6 +27,24 @@ type HotelParamsIDs struct { HotelIDs []string `form:"hotel_ids"` } +// HotelParamsID holds 1 hotel id for making queries +type HotelParamsID struct { + *HotelParamsBase + HotelID string `form:"hotel_id"` +} + +// BookRoomParams hold params for creating pnr and executing a reservation. CCCode is the credit card type code (MC, AMX, etc...); RoomRPH is the is the sabre reference place holder of the room. RPH is gotten from a previous request for room rates through RatesHotelIDHandler. +type BookRoomParams struct { + FirstName string `form:"first_name"` + LastName string `form:"last_name"` + Phone string `form:"phone"` + NumRooms string `form:"num_rooms"` + RoomRPH string `form:"room_rph"` + CCCode string `form:"cc_code"` + CCExpire string `form:"cc_expire"` + CCNumber string `form:"cc_number"` +} + // AvailHotelIDSResponse for sabre hotel availability type AvailHotelIDSResponse struct { RequestParams HotelParamsIDs @@ -78,7 +54,7 @@ type AvailHotelIDSResponse struct { // PropertyHotelIDResponse for sabre property description type PropertyHotelIDResponse struct { - RequestParams HotelParamsIDs + RequestParams HotelParamsID SabreEngineErrors interface{} `json:",omitempty"` RoomStay hotelws.RoomStay } @@ -86,6 +62,9 @@ type PropertyHotelIDResponse struct { // Validate AvailParamsBase fields. Time date formats arrive/depart are using app timezone location aware validations and set the outgoing arrive/depart formats for sabre. Integer guest_count checks against min/max. func (b *HotelParamsBase) ValidateAndFormat(loc *time.Location) error { //check for null or empty values first + if b == nil { + return ErrBaseParamsNull + } if b.InputArrive == "" { return ErrArriveNull } @@ -124,7 +103,22 @@ func (b *HotelParamsBase) ValidateAndFormat(loc *time.Location) error { return nil } -// Validate AvailParamsIDs runs params base validations and for hotel_ids +// Validate HotelParamsID runs params base validations and for hotel_id +func (a HotelParamsID) Validate(loc *time.Location) error { + if err := a.HotelParamsBase.ValidateAndFormat(loc); err != nil { + return err + } + if a.HotelID == "" { + return ErrHotelIDNullOrZero + } + //defense against no param or weird values like 'hotel_id=', 'hotel_id="', 'hotel_id=""' + if (a.HotelID == "") || (a.HotelID == "\"") || (a.HotelID == "\"\"") { + return ErrHotelIDNullOrZero + } + return nil +} + +// Validate HotelParamsIDs runs params base validations and for hotel_ids func (a HotelParamsIDs) Validate(loc *time.Location) error { if err := a.HotelParamsBase.ValidateAndFormat(loc); err != nil { return err @@ -136,36 +130,102 @@ func (a HotelParamsIDs) Validate(loc *time.Location) error { return ErrLtGt(ErrHotelIDsMaxsg, len(a.HotelIDs), HotelIDsMax) } //defense against no param or weird values like 'hotel_ids=', 'hotel_ids="', 'hotel_ids=""' - if len(a.HotelIDs) == 1 { - if (a.HotelIDs[0] == "") || (a.HotelIDs[0] == "\"") || (a.HotelIDs[0] == "\"\"") { + //if len(a.HotelIDs) >= 1 { + for _, id := range a.HotelIDs { + if (id == "") || (id == "\"") || (id == "\"\"") { return ErrHotelIDNullOrZero + } } + //} + return nil +} + +// Validate AvailParamsIDs runs params base validations and for hotel_ids +func (b BookRoomParams) Validate() error { return nil } +// first,last,phone, number of units, rph, ccCode, ccExpire, ccNumber +func (s *Server) BookRoomHandler() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + params := &BookRoomParams{} + decoder := form.NewDecoder() + + // decode params, check errors + if err := decoder.Decode(¶ms, r.URL.Query()); err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write(apperr.DecodeBadInput("BookRoomHandler", r.URL.Query(), err, http.StatusBadRequest)) + return + } + + // validate query params + if err := params.Validate(); err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + w.Write(apperr.DecodeInvalid("BookRoomHandler", err, http.StatusUnprocessableEntity)) + return + } + + //PNR + //build person + person := itin.CreatePersonName(params.FirstName, params.LastName) + //build pnr + pnrBody := itin.SetPNRDetailBody(params.Phone, person) + pnrReq := itin.BuildPNRDetailsRequest(s.SConfig, pnrBody) + //call pnr + pnrResp, err := itin.CallPNRDetail(s.SConfig.ServiceURL, pnrReq) + if err != nil { + w.WriteHeader(http.StatusFailedDependency) + w.Write(apperr.DecodeUnknown("CallPNRDetail::BookRoomHandler", r.URL.Query(), err, http.StatusFailedDependency)) + return + } + + json.NewEncoder(w).Encode(pnrResp) + + //Hotel Reservation + /* + reservationBody := hotelws.SetHotelResBody(1, srvc.SabreTimeNowFmt()) + reservationBody.NewGuaranteeRes( + person.Last.Val, + rr.GuaranteeSurcharge, //gtype string, + "MC", //ccCode string, + "2019-07", //ccExpire string, + "5105105105105100", //ccNumber string, + ) + reservationReq := hotelws.BuildHotelResRequest(s.SConfig, reservationBody) + resResp, err := hotelws.CallHotelRes(prodURL, reservationReq) + if err != nil { + return err + } + */ + } +} + /* -PropertyDescriptionIDsHandler wraps SOAP call to sabre property description service. This SOAP service is the primary service for returning room rates. It accepts one hotel ref criterion and returns one hotel with one room stay object containing 0..n room rates. +RatesHotelIDHandler wraps SOAP call to sabre property description service. This SOAP service is the primary service for returning room rates. It accepts one hotel ref criterion and returns one hotel with one room stay object containing 0..n room rates. Example: + curl -H "Accept: application/json" -X GET 'http://localhost:8080/rates/hotel/id?guest_count=4&arrive=2018-07-17&depart=2018-07-18&hotel_id=10' + curl -H "Accept: application/json" -X GET 'http://localhost:8080/rates/hotel/id?guest_count=4&arrive=2018-07-17&depart=2018-07-18&hotel_ids=12' */ -func (s *Server) PropertyDescriptionIDsHandler() http.HandlerFunc { +func (s *Server) RatesHotelIDHandler() http.HandlerFunc { //closure to execute return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") - params := &HotelParamsIDs{Max: 1} + params := &HotelParamsID{} decoder := form.NewDecoder() response := PropertyHotelIDResponse{} // decode params, check errors if err := decoder.Decode(¶ms, r.URL.Query()); err != nil { w.WriteHeader(http.StatusBadRequest) - w.Write(apperr.DecodeBadInput("PropertyDescriptionIDsHandler", r.URL.Query(), err, http.StatusBadRequest)) + w.Write(apperr.DecodeBadInput("RatesHotelIDHandler", r.URL.Query(), err, http.StatusBadRequest)) return } // validate query params if err := params.Validate(s.SConfig.AppTimeZone); err != nil { w.WriteHeader(http.StatusUnprocessableEntity) - w.Write(apperr.DecodeInvalid("PropertyDescriptionIDsHandler", err, http.StatusUnprocessableEntity)) + w.Write(apperr.DecodeInvalid("RatesHotelIDHandler", err, http.StatusUnprocessableEntity)) return } response.RequestParams = *params @@ -174,9 +234,9 @@ func (s *Server) PropertyDescriptionIDsHandler() http.HandlerFunc { defer s.SessionPool.Put(sess) //get binary security token s.SConfig.SetBinSec(sess.Sabre) - // parse incoming params as JSON + // setup hotel ref criterion for 1 hotel id hotelid := make(hotelws.HotelRefCriterion) - hotelid[hotelws.HotelidQueryField] = params.HotelIDs + hotelid[hotelws.HotelidQueryField] = []string{params.HotelID} //no need to handle error in these two functions since api validations give similar guarantee q, _ := hotelws.NewHotelSearchCriteria( @@ -193,7 +253,7 @@ func (s *Server) PropertyDescriptionIDsHandler() http.HandlerFunc { call, err := hotelws.CallHotelPropDesc(s.SConfig.ServiceURL, req) if err != nil { w.WriteHeader(http.StatusFailedDependency) - w.Write(apperr.DecodeUnknown("CallHotelPropDesc::PropertyDescriptionIDsHandler", r.URL.Query(), err, http.StatusFailedDependency)) + w.Write(apperr.DecodeUnknown("CallHotelPropDesc::RatesHotelIDHandler", r.URL.Query(), err, http.StatusFailedDependency)) return } @@ -205,13 +265,13 @@ func (s *Server) PropertyDescriptionIDsHandler() http.HandlerFunc { } /* -HotelAvailIDsHandler wraps SOAP call to sabre hotel availability service. This SOAP service does not return rates for rooms. It accepts many hotel ref criteria and returns many hotel options. +HotelIDsHandler wraps SOAP call to sabre hotel availability service. This SOAP service does not return rates for rooms. Instead it shows basic availability options along with property information. It accepts many hotel ref criteria and returns many hotel options. Example: - curl -H "Accept: application/json" -X GET 'http://localhost:8080/avail/hotel/id?guest_count=4&arrive=2018-07-17&depart=2018-07-18&hotel_ids=10' - curl -H "Accept: application/json" -X GET 'http://localhost:8080/avail/hotel/id?guest_count=4&arrive=2018-07-17&depart=2018-07-18&hotel_ids=10,12' + curl -H "Accept: application/json" -X GET 'http://localhost:8080/hotel/ids?guest_count=4&arrive=2018-07-17&depart=2018-07-18&hotel_ids=10' + curl -H "Accept: application/json" -X GET 'http://localhost:8080/hotel/ids?guest_count=4&arrive=2018-07-17&depart=2018-07-18&hotel_ids=10,12' */ -func (s *Server) HotelAvailIDsHandler() http.HandlerFunc { +func (s *Server) HotelIDsHandler() http.HandlerFunc { //closure to execute return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -221,13 +281,13 @@ func (s *Server) HotelAvailIDsHandler() http.HandlerFunc { // decode params, check errors if err := decoder.Decode(¶ms, r.URL.Query()); err != nil { w.WriteHeader(http.StatusBadRequest) - w.Write(apperr.DecodeBadInput("HotelAvailIDsHandler", r.URL.Query(), err, http.StatusBadRequest)) + w.Write(apperr.DecodeBadInput("HotelIDsHandler", r.URL.Query(), err, http.StatusBadRequest)) return } // validate query params if err := params.Validate(s.SConfig.AppTimeZone); err != nil { w.WriteHeader(http.StatusUnprocessableEntity) - w.Write(apperr.DecodeInvalid("HotelAvailIDsHandler", err, http.StatusUnprocessableEntity)) + w.Write(apperr.DecodeInvalid("HotelIDsHandler", err, http.StatusUnprocessableEntity)) return } response.RequestParams = *params @@ -236,7 +296,7 @@ func (s *Server) HotelAvailIDsHandler() http.HandlerFunc { defer s.SessionPool.Put(sess) //get binary security token s.SConfig.SetBinSec(sess.Sabre) - // parse incoming params as JSON + // setup hotel ref serch for hotel ids... searchids := make(hotelws.HotelRefCriterion) searchids[hotelws.HotelidQueryField] = params.HotelIDs @@ -265,3 +325,46 @@ func (s *Server) HotelAvailIDsHandler() http.HandlerFunc { json.NewEncoder(w).Encode(response) } } + +/* + + TODO... implement hotel availability requests by city_code, lat/long, and address + + +HOTELEREF: + hqids = make(HotelRefCriterion) + hqltln = make(HotelRefCriterion) + hqcity = make(HotelRefCriterion) + + search[hotelws.HotelidQueryField] = []string{"0012", "19876", "1109", "445098", "000034"} + search[hotelws.LatlngQueryField] = []string{"32.78,-96.81", "54.87,-102.96"} + search[hotelws.CountryCodeQueryField] = []string{"DFW", "CHC", "LA"} + + //TODO... +type HotelParamsLatLng struct { + Base AvailParamsBase + LatLng []string +} + //TODO... +type HotelParamsCityCodes struct { + Base AvailParamsBase + CityCodes []string +} + + +ADDRESS: + addr = make(AddressCriterion) + addr[StreetQueryField] = "2031 N. 100 W" + addr[CityQueryField] = "Aneheim" + addr[PostalQueryField] = "90458" + addr[CountryCodeQueryField] = "US" + + //TODO... +type HotelParamsAddress struct { + Base AvailParamsBase + Street string + City string + PostalCode string + CountryCode string +} +*/ diff --git a/client/app/routes.go b/client/app/routes.go index 106b14b..a4e9b1c 100644 --- a/client/app/routes.go +++ b/client/app/routes.go @@ -7,9 +7,10 @@ import ( // registerRoutes is responsible for registering the server-side request handlers func (s *Server) RegisterRoutes() { s.Mux.Get("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`{"ping":"p0ng"}`)) }) - s.Mux.Handle("/avail/hotel/id", s.HotelAvailIDsHandler()) - s.Mux.Handle("/rates/hotel/id", s.PropertyDescriptionIDsHandler()) - //s.Mux.Handle("/avail/hotel/latlong", s.HotelAvailIDsHandler()) - //s.Mux.Handle("/avail/hotel/address", s.HotelAvailIDsHandler()) - //s.Mux.Handle("/avail/hotel/citycode", s.HotelAvailIDsHandler()) + s.Mux.Handle("/hotel/ids", s.HotelIDsHandler()) + //s.Mux.Handle("/hotel/latlong", s.Handler()) + //s.Mux.Handle("/hotel/address", s.Handler()) + //s.Mux.Handle("/hotel/citycodes", s.Handler()) + s.Mux.Handle("/rates/hotel/id", s.RatesHotelIDHandler()) + s.Mux.Handle("/book/hotel/room", s.BookRoomHandler()) } diff --git a/client/app/validation.go b/client/app/validation.go index b9579a4..1ea4038 100644 --- a/client/app/validation.go +++ b/client/app/validation.go @@ -14,22 +14,20 @@ const ( ) var ( + ErrBaseParamsNull = errors.New("invalid params: arrive, depart, guest_count not present") ErrArriveNull = errors.New("invalid arrive: query not present or value not defined") ErrDepartNull = errors.New("invalid depart: query not present or value not defined") ErrHotelIDNullOrZero = errors.New("invalid hotel_ids: query not present or value not defined") ErrGuestCountNullOrZero = errors.New("invalid guest_count: query not present or value is 0") ErrSearchCriterion = errors.New("invalid search criterion: verify only one kind of criterion (hotel_id, lat_lng, city_code)") -) - -var ( - ErrGuestMaxMsg = "invalid guest_count '%d': must be less than or equal '%d'" - ErrGuestMinMsg = "invalid guest_count '%d': must be greater than or equal '%d'" - ErrHotelIDsMaxsg = "invalid hotel_ids '%d': must be less than or equal '%d'" - ErrArriveFmtMsg = "invalid arrive '%s': format (YYYY-MM-DD '%s'). %s" - ErrStayInPastMsg = "invalid arrive '%s': cannot be before today '%s'" - ErrDepartFmtMsg = "invalid depart '%s': format (YYYY-MM-DD '%s'). %s" - ErrStayRangeMsg = "invalid range: depart '%s' must be after arrive '%s'" + ErrGuestMaxMsg = "invalid guest_count '%d': must be less than or equal '%d'" + ErrGuestMinMsg = "invalid guest_count '%d': must be greater than or equal '%d'" + ErrHotelIDsMaxsg = "invalid hotel_ids '%d': must be less than or equal '%d'" + ErrArriveFmtMsg = "invalid arrive '%s': format (YYYY-MM-DD '%s'). %s" + ErrStayInPastMsg = "invalid arrive '%s': cannot be before today '%s'" + ErrDepartFmtMsg = "invalid depart '%s': format (YYYY-MM-DD '%s'). %s" + ErrStayRangeMsg = "invalid range: depart '%s' must be after arrive '%s'" ) func ErrLtGt(msgFormat string, given, expect int) error { diff --git a/client/main.go b/client/main.go index e49d4a1..3e77af4 100644 --- a/client/main.go +++ b/client/main.go @@ -45,7 +45,7 @@ func init() { PCC: vipConf.GetString(ConfSabrePCC), Convid: srvc.GenerateConversationID(vipConf.GetString(ConfClientURL)), Msgid: srvc.GenerateMessageID(), - Timestr: srvc.SabreTimeFormat(), + Timestr: srvc.SabreTimeNowFmt(), Username: vipConf.GetString(ConfSabreUsername), Password: vipConf.GetString(ConfSabrePassword), AppTimeZone: ClientAppTimeZone, diff --git a/engine/srvc/session_pool.go b/engine/srvc/session_pool.go index 8fc22fb..bd8eba7 100644 --- a/engine/srvc/session_pool.go +++ b/engine/srvc/session_pool.go @@ -179,7 +179,7 @@ func (p *SessionPool) RangeKeepalive(keepaliveID string) { sess.Sabre.Header.Security.BinarySecurityToken.Value, sess.Sabre.Header.MessageHeader.ConversationID, sess.Sabre.Header.MessageHeader.MessageData.RefToMessageID, - SabreTimeFormat(), + SabreTimeNowFmt(), ) validateRS, err := CallSessionValidate(p.ServiceURL, validateRQ) if err != nil { diff --git a/engine/srvc/soap_base.go b/engine/srvc/soap_base.go index 9fb5314..dc69fe5 100644 --- a/engine/srvc/soap_base.go +++ b/engine/srvc/soap_base.go @@ -324,8 +324,8 @@ func SabreTokenParse(tok string) string { return binaryTokenMatcher.FindAllString(tok, -1)[0] } -// SabreTimeFormat returns '2017-11-27T09:58:31Z' -func SabreTimeFormat() string { +// SabreTimeNowFmt returns time.Now in format: '2017-11-27T09:58:31Z' +func SabreTimeNowFmt() string { return time.Now().Format(StandardTimeFormatter) } @@ -395,7 +395,7 @@ type SessionConf struct { // SetTime updates the timestamp. Pass around SessionConf and update the timestamp for any new request func (s *SessionConf) SetTime() *SessionConf { - s.Timestr = SabreTimeFormat() + s.Timestr = SabreTimeNowFmt() return s } diff --git a/engine/srvc/soap_base_test.go b/engine/srvc/soap_base_test.go index 05b90b8..a593414 100644 --- a/engine/srvc/soap_base_test.go +++ b/engine/srvc/soap_base_test.go @@ -255,14 +255,14 @@ func BenchmarkEnvelopeUnmarshal(b *testing.B) { } func TestTimeFormat(t *testing.T) { - format := SabreTimeFormat() + format := SabreTimeNowFmt() if !samplerfc333pReg.MatchString(format) { t.Errorf("Timestamp formt needs to be '%s' but got '%s'", samplerfc333pString, format) } } func BenchmarkTimeFormat(b *testing.B) { for n := 0; n < b.N; n++ { - SabreTimeFormat() + SabreTimeNowFmt() } } @@ -270,7 +270,7 @@ func TestSessionConfSetTime(t *testing.T) { conf := &SessionConf{ Timestr: sampletime, } - if conf.SetTime().Timestr != SabreTimeFormat() { + if conf.SetTime().Timestr != SabreTimeNowFmt() { t.Error("SessionConf SetTime() should be SabreTimeFormat()") } } From 563f5e6b97f5b8137004bcded0d147b59b1c69ba Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Tue, 31 Jul 2018 18:40:16 -0600 Subject: [PATCH 14/21] add more json specs for hotel rate parsing --- engine/hotelws/hotelws.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/engine/hotelws/hotelws.go b/engine/hotelws/hotelws.go index 78e3819..bd55ffd 100644 --- a/engine/hotelws/hotelws.go +++ b/engine/hotelws/hotelws.go @@ -209,14 +209,14 @@ type Corporate struct { // Timepsan for arrival and departure params type TimeSpan struct { - XMLName xml.Name `xml:"TimeSpan"` + XMLName xml.Name `xml:"TimeSpan" json:"-"` Duration int `xml:"Duration,omitempty"` Depart string `xml:"End,attr,omitempty"` Arrive string `xml:"Start,attr,omitempty"` } type RatePlan struct { - XMLName xml.Name `xml:"RatePlanCandidate"` + XMLName xml.Name `xml:"RatePlanCandidate" json:"-"` CurrencyCode string `xml:"CurrencyCode,attr,omitempty"` DCA_ProductCode string `xml:"DCA_ProductCode,attr,omitempty"` DecodeAll string `xml:"DecodeAll,attr,omitempty"` @@ -242,7 +242,7 @@ type AvailRequestSegment struct { // RoomStay contains all info relevant to the property's available rooms. It is the root-level element after service element for hotel_rate_desc and hotel_property_desc. type RoomStay struct { - XMLName xml.Name `xml:"RoomStay"` + XMLName xml.Name `xml:"RoomStay" json:"-"` BasicPropertyInfo BasicPropertyInfo Guarantee Guarantee RoomRates []RoomRate `xml:"RoomRates>RoomRate"` @@ -251,16 +251,16 @@ type RoomStay struct { // Guarantee shows forms of payment accepted by property type Guarantee struct { - XMLName xml.Name `xml:"Guarantee"` + XMLName xml.Name `xml:"Guarantee" json:"-"` GuaranteesAccepted GuaranteesAccepted DepositsAccepted DepositsAccepted } type GuaranteesAccepted struct { - XMLName xml.Name `xml:"GuaranteesAccepted"` + XMLName xml.Name `xml:"GuaranteesAccepted" json:"-"` PaymentCards []PaymentCard `xml:"PaymentCard"` } type DepositsAccepted struct { - XMLName xml.Name `xml:"DepositsAccepted"` + XMLName xml.Name `xml:"DepositsAccepted" json:"-"` PaymentCards []PaymentCard `xml:"PaymentCard"` } type PaymentCard struct { @@ -370,18 +370,18 @@ type Charge struct { } type AdditionalGuestAmount struct { - XMLName xml.Name `xml:"AdditionalGuestAmount"` + XMLName xml.Name `xml:"AdditionalGuestAmount" json:"-"` MaxExtraPerson int `xml:"MaxExtraPersonsAllowed,attr"` NumCribs int `xml:"NumCribs,attr"` Charges []Charge `xml:"Charges"` } type TotalSurcharges struct { - XMLName xml.Name `xml:"TotalSurcharges"` + XMLName xml.Name `xml:"TotalSurcharges" json:"-"` Amount string `xml:"Amount,attr"` } type TotalTaxes struct { - XMLName xml.Name `xml:"TotalTaxes"` + XMLName xml.Name `xml:"TotalTaxes" json:"-"` Amount string `xml:"Amount,attr"` TaxFieldOne string `xml:"TaxFieldOne"` TaxFieldTwo string `xml:"TaxFieldTwo"` @@ -389,7 +389,7 @@ type TotalTaxes struct { } type HotelPricing struct { - XMLName xml.Name `xml:"HotelTotalPricing"` + XMLName xml.Name `xml:"HotelTotalPricing" json:"-"` Amount string `xml:"Amount,attr"` Disclaimer string `xml:"Disclaimer"` TotalSurcharges TotalSurcharges @@ -397,7 +397,7 @@ type HotelPricing struct { } type Rate struct { - XMLName xml.Name `xml:"Rate"` + XMLName xml.Name `xml:"Rate" json:"-"` Amount string `xml:"Amount,attr"` ChangeIndicator string `xml:"ChangeIndicator,attr"` CurrencyCode string `xml:"CurrencyCode,attr"` @@ -411,7 +411,7 @@ type Rate struct { } type VendorMessages struct { - XMLName xml.Name `xml:"VendorMessages"` + XMLName xml.Name `xml:"VendorMessages" json:"-"` Attractions Attractions `xml:"Attractions"` AdditionalAttractions AdditionalAttractions `xml:"AdditionalAttractions"` Awards Awards `xml:"Awards"` @@ -494,7 +494,7 @@ type AdditionalAttractions struct { } type IndexD struct { - XMLName xml.Name `xml:"Index"` + XMLName xml.Name `xml:"Index" json:"-"` CountryState string `xml:"CountryState,attr"` DistanceDirection string `xml:"DistanceDirection,attr"` LocationCode string `xml:"LocationCode,attr"` @@ -526,7 +526,7 @@ type ContactNumber struct { } type ApplicationResults struct { - XMLName xml.Name `xml:"ApplicationResults"` + XMLName xml.Name `xml:"ApplicationResults" json:"-"` Status string `xml:"status,attr"` Success ReqSuccess `xml:"Success"` Error ReqError `xml:"Error"` From 55050f525e61bc07015a68aa687c5a2b1fe35ad1 Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Tue, 31 Jul 2018 20:06:27 -0600 Subject: [PATCH 15/21] add url safe base64 encoding and decoding for hotel desc rates and room rates --- engine/hotelws/hotel_rate_desc.go | 14 ++++++++++++++ engine/hotelws/hotelws.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/engine/hotelws/hotel_rate_desc.go b/engine/hotelws/hotel_rate_desc.go index ef73f45..8ccad72 100644 --- a/engine/hotelws/hotel_rate_desc.go +++ b/engine/hotelws/hotel_rate_desc.go @@ -3,8 +3,10 @@ package hotelws import ( "bytes" "encoding/xml" + "fmt" "io" "net/http" + "strings" "github.com/ailgroup/sbrweb/engine/sbrerr" "github.com/ailgroup/sbrweb/engine/srvc" @@ -107,6 +109,18 @@ type HotelRateDescResponse struct { ErrorSabreXML sbrerr.ErrorSabreXML } +func (r *HotelRateDescResponse) SetTrackedEncode() { + for i, rate := range r.Body.HotelDesc.RoomStay.RoomRates { + strslc := []string{} + strslc = append(strslc, fmt.Sprintf("%s:%d", TrackEncIndex, i)) + strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncRPH, rate.RPH)) + strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncIATAChar, rate.IATA_Character)) + strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncTotal, rate.Rates[0].HotelPricing.Amount)) + + rate.TrackedEncoding = B64Enc(strings.Join(strslc, TrackEncDelimiter)) + } +} + // CallHotelRateDesc to sabre web services retrieve hotel rates using HotelRateDescriptionLLSRQ. This call only supports requests that contain an RPH from a previous hotel_property_desc call, see BuildHotelRateDescRequest. func CallHotelRateDesc(serviceURL string, req HotelRateDescRequest) (HotelRateDescResponse, error) { rateResp := HotelRateDescResponse{} diff --git a/engine/hotelws/hotelws.go b/engine/hotelws/hotelws.go index bd55ffd..6455d75 100644 --- a/engine/hotelws/hotelws.go +++ b/engine/hotelws/hotelws.go @@ -21,6 +21,7 @@ One may implement Sabre hotel searching through building various criteria functi package hotelws import ( + b64 "encoding/base64" "encoding/xml" "fmt" "strings" @@ -41,6 +42,11 @@ const ( CountryCodeQueryField = "countryCode_qf" LatlngQueryField = "latlng_qf" HotelidQueryField = "hotelID_qf" + TrackEncDelimiter = "|" + TrackEncIndex = "idx" + TrackEncRPH = "rph" + TrackEncIATAChar = "iatachar" + TrackEncTotal = "total" returnHostCommand = true ESA = "\u0087" //UNICODE: End of Selected Area CrossLorraine = "\u2628" //UNICODE Cross of Lorraine @@ -48,6 +54,17 @@ const ( var hostCommandReplacer = strings.NewReplacer("\\", "", "/", "", ESA, "") +// B64Enc base64 encode a string +func B64Enc(str string) string { + return b64.URLEncoding.EncodeToString([]byte(str)) +} + +// B64Dec decode a base64 string +func B64Dec(b64str string) (string, error) { + uDec, err := b64.URLEncoding.DecodeString(b64str) + return string(uDec), err +} + // TimeSpanFormatter parse string data value into time value. func TimeSpanFormatter(arrive, depart, formIn, formOut string) TimeSpan { a, _ := time.Parse(formIn, arrive) @@ -286,7 +303,19 @@ type RoomRate struct { Rates []Rate `xml:"Rates>Rate"` AdditionalInfo AdditionalInfo HotelRateCode string `xml:"HotelRateCode"` + TrackedEncoding string `json:"tracked_encoding"` } + +func (r *RoomRate) DecodeTrackedEncoding() ([]string, error) { + res := []string{} + bytEnc, err := B64Dec(r.TrackedEncoding) + if err != nil { + return res, err + } + res = strings.Split(string(bytEnc), TrackEncDelimiter) + return res, nil +} + type AdditionalInfo struct { XMLName xml.Name `xml:"AdditionalInfo" json:"-"` Commission struct { From ba0b066c8c58a3f08749e65873dc77c68d2f9b12 Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Wed, 1 Aug 2018 17:57:35 -0600 Subject: [PATCH 16/21] tests for room tracking and sanatize --- engine/hotelws/hotel_rate_desc.go | 3 +- engine/hotelws/hotel_rate_desc_test.go | 45 +++++++++++++++++++++++- engine/hotelws/hotelws.go | 8 +++-- engine/hotelws/hotelws_helper_test.go | 48 +++++++++++++++++++++++++- engine/hotelws/hotelws_test.go | 20 +++++++++++ 5 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 engine/hotelws/hotelws_test.go diff --git a/engine/hotelws/hotel_rate_desc.go b/engine/hotelws/hotel_rate_desc.go index 8ccad72..f4cc7ab 100644 --- a/engine/hotelws/hotel_rate_desc.go +++ b/engine/hotelws/hotel_rate_desc.go @@ -116,8 +116,7 @@ func (r *HotelRateDescResponse) SetTrackedEncode() { strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncRPH, rate.RPH)) strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncIATAChar, rate.IATA_Character)) strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncTotal, rate.Rates[0].HotelPricing.Amount)) - - rate.TrackedEncoding = B64Enc(strings.Join(strslc, TrackEncDelimiter)) + r.Body.HotelDesc.RoomStay.RoomRates[i].TrackedEncoding = B64Enc(strings.Join(strslc, TrackEncDelimiter)) } } diff --git a/engine/hotelws/hotel_rate_desc_test.go b/engine/hotelws/hotel_rate_desc_test.go index fadb5d0..d609c67 100644 --- a/engine/hotelws/hotel_rate_desc_test.go +++ b/engine/hotelws/hotel_rate_desc_test.go @@ -73,7 +73,7 @@ func TestRateDescCall(t *testing.T) { roomStayRates := resp.Body.HotelDesc.RoomStay.RoomRates numRoomRates := len(roomStayRates) - if numRoomRates != 1 { + if numRoomRates != 2 { t.Error("Number of room rates is wrong") } @@ -134,7 +134,50 @@ func TestRateDescCall(t *testing.T) { if hprice.TotalTaxes.TaxFieldTwo != "13.73" { t.Errorf("TaxFieldTwo expected %s, got %s", "13.73", hprice.TotalTaxes.TaxFieldTwo) } +} + +var trackenc = []struct { + b64str string + input []string +}{ + {"aWR4OjB8cnBoOjAwMXxpYXRhY2hhcjpKMUtBMTZ8dG90YWw6MzA3LjUw", []string{"idx:0", "rph:001", "iatachar:J1KA16", "total:307.50"}}, + {"aWR4OjF8cnBoOjAwMnxpYXRhY2hhcjpGVzhNTlVUfHRvdGFsOjE3Mi45NQ==", []string{"idx:1", "rph:002", "iatachar:FW8MNUT", "total:172.95"}}, +} +func TestSetTrackedEncode(t *testing.T) { + // assume RPH is from previous hotel property description call + rpc := SetRateParams( + []RatePlan{ + RatePlan{ + RPH: "12", + }, + }, + ) + raterq, _ := SetHotelRateDescBody(rpc) + req := BuildHotelRateDescRequest(sconf, raterq) + resp, _ := CallHotelRateDesc(serverHotelRateDesc.URL, req) + resp.SetTrackedEncode() + for i, rate := range resp.Body.HotelDesc.RoomStay.RoomRates { + if rate.TrackedEncoding != trackenc[i].b64str { + t.Errorf("TrackedEncoding expect: '%s', got '%s'", trackenc[i].b64str, rate.TrackedEncoding) + } + res, err := rate.DecodeTrackedEncoding() + if err != nil { + t.Errorf("Error on DecodeTrackedEncoding() %v", err) + } + if res[0] != trackenc[i].input[0] { + t.Errorf("epxected %s, got %s", trackenc[i].input[0], res[0]) + } + if res[1] != trackenc[i].input[1] { + t.Errorf("epxected %s, got %s", trackenc[i].input[1], res[1]) + } + if res[2] != trackenc[i].input[2] { + t.Errorf("epxected %s, got %s", trackenc[i].input[2], res[2]) + } + if res[3] != trackenc[i].input[3] { + t.Errorf("epxected %s, got %s", trackenc[i].input[3], res[3]) + } + } } func TestHotelRateDesCallDown(t *testing.T) { diff --git a/engine/hotelws/hotelws.go b/engine/hotelws/hotelws.go index 6455d75..7021e78 100644 --- a/engine/hotelws/hotelws.go +++ b/engine/hotelws/hotelws.go @@ -75,7 +75,7 @@ func TimeSpanFormatter(arrive, depart, formIn, formOut string) TimeSpan { } } -// sanatize cleans up filtered string terms for file names. removes whitespace and slashes as these either get in the +// sanatize cleans up filtered string terms for file names. removes whitespace and slashes func sanatize(str string) string { // use a NewReplacer to clean this up and make it easy to use with multiple replacers //trim := strings.Trim(str, " ") @@ -289,16 +289,20 @@ type PaymentCard struct { type RoomRate struct { XMLName xml.Name `xml:"RoomRate" json:"-"` - DirectConnect string `xml:"RDirectConnect,attr"` + ClientID string `xml:"ClientID,attr"` + DirectConnect string `xml:"DirectConnect,attr"` GuaranteeSurcharge string `xml:"GuaranteeSurchargeRequired,attr"` GuaranteedRate string `xml:"GuaranteedRateProgram,attr"` IATA_Character string `xml:"IATA_CharacteristicIdentification,attr"` IATA_Product string `xml:"IATA_ProductIdentification,attr"` LowInventory string `xml:"LowInventoryThreshold,attr"` + RateAccessCode string `xml:"RateAccessCode,attr"` + RateCategory string `xml:"RateCategory,attr"` RateLevelCode string `xml:"RateLevelCode,attr"` RPH string `xml:"RPH,attr"` RateChangeInd string `xml:"RateChangeInd,attr"` RateConversionInd string `xml:"RateConversionInd,attr"` + RoomLocationCode string `xml:"RoomLocationCode,attr"` SpecialOffer string `xml:"SpecialOffer,attr"` Rates []Rate `xml:"Rates>Rate"` AdditionalInfo AdditionalInfo diff --git a/engine/hotelws/hotelws_helper_test.go b/engine/hotelws/hotelws_helper_test.go index 2fdaaf2..8f059e3 100644 --- a/engine/hotelws/hotelws_helper_test.go +++ b/engine/hotelws/hotelws_helper_test.go @@ -1604,7 +1604,7 @@ var ( - + 10.00 PERCENT COMMISSION @@ -1650,6 +1650,52 @@ var ( + + + + 10.00 PERCENT COMMISSION + + 2 DAYS-PRIOR 1 NTS PENALTY + + + GUARANTEE REQRD- MAJOR CREDIT CARDS. + + + + + + + + + + + TAXES NOT INCLUDED IN ROOM RAT + AAA OR CAA MEMBERSHIP ID REQUIRED AT CHECK-IN. + NON SMOKING WATERVIEW: HIGH FLOOR ROOM: LED + SMART TV: FRIDGE / COMP BOTTLED WATER: + HEAVENLY + AAA OR CAA MEMBERSHIP REQUIRED AT BE SHOWN AT + CHECK-IN. + + + + + + + + + + INCLUDES TAXES AND SURCHARGES + + 19.22 + 13.73 + CITY TAX + OCCUPANCY TAX + + + + + diff --git a/engine/hotelws/hotelws_test.go b/engine/hotelws/hotelws_test.go new file mode 100644 index 0000000..3570d6b --- /dev/null +++ b/engine/hotelws/hotelws_test.go @@ -0,0 +1,20 @@ +package hotelws + +import "testing" + +var dirtyStrings = []struct { + dirty string + clean string +}{ + {"slash/ whitespace", "slashwhitespace"}, + {"boo/this/bad boo", "boothisbadboo"}, +} + +func TestSanatizeString(t *testing.T) { + for _, ds := range dirtyStrings { + clean := sanatize(ds.dirty) + if clean != ds.clean { + t.Errorf("expected %s, got %s", ds.clean, clean) + } + } +} From 1fec8a4ac211e71ff4e448f8161bbf6861cf89df Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Wed, 1 Aug 2018 20:45:37 -0600 Subject: [PATCH 17/21] base 64 metadata string for room data --- client/app/handlers.go | 3 ++ engine/hotelws/hotel_prop_desc.go | 14 ++++++++ engine/hotelws/hotel_prop_desc_test.go | 45 ++++++++++++++++++++++++++ engine/hotelws/hotel_rate_desc.go | 3 +- engine/hotelws/hotel_rate_desc_test.go | 24 +++++++------- engine/hotelws/hotelws.go | 38 +++++++++++----------- 6 files changed, 95 insertions(+), 32 deletions(-) diff --git a/client/app/handlers.go b/client/app/handlers.go index 2e7b9c8..0338737 100644 --- a/client/app/handlers.go +++ b/client/app/handlers.go @@ -198,6 +198,8 @@ func (s *Server) BookRoomHandler() http.HandlerFunc { if err != nil { return err } + + call.SetTrackedEncode() */ } } @@ -257,6 +259,7 @@ func (s *Server) RatesHotelIDHandler() http.HandlerFunc { return } + call.SetTrackedEncode() response.RoomStay = call.Body.HotelDesc.RoomStay w.WriteHeader(http.StatusOK) diff --git a/engine/hotelws/hotel_prop_desc.go b/engine/hotelws/hotel_prop_desc.go index 1163477..15d85b4 100644 --- a/engine/hotelws/hotel_prop_desc.go +++ b/engine/hotelws/hotel_prop_desc.go @@ -3,8 +3,10 @@ package hotelws import ( "bytes" "encoding/xml" + "fmt" "io" "net/http" + "strings" "github.com/ailgroup/sbrweb/engine/sbrerr" "github.com/ailgroup/sbrweb/engine/srvc" @@ -132,6 +134,17 @@ type HotelPropDescResponse struct { ErrorSabreXML sbrerr.ErrorSabreXML } +func (r *HotelPropDescResponse) SetTrackedEncode() { + for i, rate := range r.Body.HotelDesc.RoomStay.RoomRates { + strslc := []string{} + strslc = append(strslc, fmt.Sprintf("%s:%d", TrackEncIndex, i)) + strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncRPH, rate.RPH)) + strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncIATAChar, rate.IATA_Character)) + strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncTotal, rate.Rates[0].HotelPricing.Amount)) + r.Body.HotelDesc.RoomStay.RoomRates[i].B64RoomMetaData = B64Enc(strings.Join(strslc, TrackEncDelimiter)) + } +} + // CallHotelPropDesc to sabre web services retrieve hotel rates using HotelPropertyDescriptionLLSRQ. func CallHotelPropDesc(serviceURL string, req HotelPropDescRequest) (HotelPropDescResponse, error) { propResp := HotelPropDescResponse{} @@ -161,5 +174,6 @@ func CallHotelPropDesc(serviceURL string, req HotelPropDescRequest) (HotelPropDe if !propResp.Body.HotelDesc.Result.Ok() { return propResp, propResp.Body.HotelDesc.Result.ErrFormat() } + //propResp.SetTrackedEncode() return propResp, nil } diff --git a/engine/hotelws/hotel_prop_desc_test.go b/engine/hotelws/hotel_prop_desc_test.go index 6826870..82dc6ef 100644 --- a/engine/hotelws/hotel_prop_desc_test.go +++ b/engine/hotelws/hotel_prop_desc_test.go @@ -203,6 +203,51 @@ func TestPropDescCall(t *testing.T) { } } +var proptrack = []struct { + b64str string + input []string +}{ + {"aWR4OjB8cnBoOjAwMXxpYXRhY2hhcjpQMUtSQUN8dG90YWw6MzM1LjQ1", []string{"idx:0", "rph:001", "iatachar:P1KRAC", "total:335.45"}}, + {"aWR4OjF8cnBoOjAwMnxpYXRhY2hhcjpEMUtSQUN8dG90YWw6MzM1LjQ1", []string{"idx:1", "rph:002", "iatachar:D1KRAC", "total:335.45"}}, +} + +func TestSetTrackedEncodePropDesc(t *testing.T) { + var hotelid = make(HotelRefCriterion) + hotelid[HotelidQueryField] = []string{"10"} + q, _ := NewHotelSearchCriteria( + HotelRefSearch(hotelid), + ) + prop, _ := SetHotelPropDescBody(sampleGuestCount, q, sampleArrive, sampleDepart) + req := BuildHotelPropDescRequest(sconf, prop) + resp, _ := CallHotelPropDesc(serverHotelPropertyDesc.URL, req) + resp.SetTrackedEncode() + for i, rate := range resp.Body.HotelDesc.RoomStay.RoomRates { + //only test the first 2 + if i > 1 { + break + } + if rate.B64RoomMetaData != proptrack[i].b64str { + t.Errorf("TrackedEncoding expect: '%s', got '%s'", proptrack[i].b64str, rate.B64RoomMetaData) + } + res, err := rate.DecodeTrackedEncoding() + if err != nil { + t.Errorf("Error on DecodeTrackedEncoding() %v", err) + } + if res[0] != proptrack[i].input[0] { + t.Errorf("epxected %s, got %s", proptrack[i].input[0], res[0]) + } + if res[1] != proptrack[i].input[1] { + t.Errorf("epxected %s, got %s", proptrack[i].input[1], res[1]) + } + if res[2] != proptrack[i].input[2] { + t.Errorf("epxected %s, got %s", proptrack[i].input[2], res[2]) + } + if res[3] != proptrack[i].input[3] { + t.Errorf("epxected %s, got %s", proptrack[i].input[3], res[3]) + } + } +} + func TestHotelPropDescCallDown(t *testing.T) { var hotelid = make(HotelRefCriterion) hotelid[HotelidQueryField] = []string{"10"} diff --git a/engine/hotelws/hotel_rate_desc.go b/engine/hotelws/hotel_rate_desc.go index f4cc7ab..e258b7d 100644 --- a/engine/hotelws/hotel_rate_desc.go +++ b/engine/hotelws/hotel_rate_desc.go @@ -116,7 +116,7 @@ func (r *HotelRateDescResponse) SetTrackedEncode() { strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncRPH, rate.RPH)) strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncIATAChar, rate.IATA_Character)) strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncTotal, rate.Rates[0].HotelPricing.Amount)) - r.Body.HotelDesc.RoomStay.RoomRates[i].TrackedEncoding = B64Enc(strings.Join(strslc, TrackEncDelimiter)) + r.Body.HotelDesc.RoomStay.RoomRates[i].B64RoomMetaData = B64Enc(strings.Join(strslc, TrackEncDelimiter)) } } @@ -149,5 +149,6 @@ func CallHotelRateDesc(serviceURL string, req HotelRateDescRequest) (HotelRateDe if !rateResp.Body.HotelDesc.Result.Ok() { return rateResp, rateResp.Body.HotelDesc.Result.ErrFormat() } + //rateResp.SetTrackedEncode() return rateResp, nil } diff --git a/engine/hotelws/hotel_rate_desc_test.go b/engine/hotelws/hotel_rate_desc_test.go index d609c67..3eaa6a1 100644 --- a/engine/hotelws/hotel_rate_desc_test.go +++ b/engine/hotelws/hotel_rate_desc_test.go @@ -136,7 +136,7 @@ func TestRateDescCall(t *testing.T) { } } -var trackenc = []struct { +var ratetrack = []struct { b64str string input []string }{ @@ -144,7 +144,7 @@ var trackenc = []struct { {"aWR4OjF8cnBoOjAwMnxpYXRhY2hhcjpGVzhNTlVUfHRvdGFsOjE3Mi45NQ==", []string{"idx:1", "rph:002", "iatachar:FW8MNUT", "total:172.95"}}, } -func TestSetTrackedEncode(t *testing.T) { +func TestSetTrackedEncodeRateDesc(t *testing.T) { // assume RPH is from previous hotel property description call rpc := SetRateParams( []RatePlan{ @@ -158,24 +158,24 @@ func TestSetTrackedEncode(t *testing.T) { resp, _ := CallHotelRateDesc(serverHotelRateDesc.URL, req) resp.SetTrackedEncode() for i, rate := range resp.Body.HotelDesc.RoomStay.RoomRates { - if rate.TrackedEncoding != trackenc[i].b64str { - t.Errorf("TrackedEncoding expect: '%s', got '%s'", trackenc[i].b64str, rate.TrackedEncoding) + if rate.B64RoomMetaData != ratetrack[i].b64str { + t.Errorf("TrackedEncoding expect: '%s', got '%s'", ratetrack[i].b64str, rate.B64RoomMetaData) } res, err := rate.DecodeTrackedEncoding() if err != nil { t.Errorf("Error on DecodeTrackedEncoding() %v", err) } - if res[0] != trackenc[i].input[0] { - t.Errorf("epxected %s, got %s", trackenc[i].input[0], res[0]) + if res[0] != ratetrack[i].input[0] { + t.Errorf("epxected %s, got %s", ratetrack[i].input[0], res[0]) } - if res[1] != trackenc[i].input[1] { - t.Errorf("epxected %s, got %s", trackenc[i].input[1], res[1]) + if res[1] != ratetrack[i].input[1] { + t.Errorf("epxected %s, got %s", ratetrack[i].input[1], res[1]) } - if res[2] != trackenc[i].input[2] { - t.Errorf("epxected %s, got %s", trackenc[i].input[2], res[2]) + if res[2] != ratetrack[i].input[2] { + t.Errorf("epxected %s, got %s", ratetrack[i].input[2], res[2]) } - if res[3] != trackenc[i].input[3] { - t.Errorf("epxected %s, got %s", trackenc[i].input[3], res[3]) + if res[3] != ratetrack[i].input[3] { + t.Errorf("epxected %s, got %s", ratetrack[i].input[3], res[3]) } } } diff --git a/engine/hotelws/hotelws.go b/engine/hotelws/hotelws.go index 7021e78..f163971 100644 --- a/engine/hotelws/hotelws.go +++ b/engine/hotelws/hotelws.go @@ -289,30 +289,30 @@ type PaymentCard struct { type RoomRate struct { XMLName xml.Name `xml:"RoomRate" json:"-"` - ClientID string `xml:"ClientID,attr"` - DirectConnect string `xml:"DirectConnect,attr"` - GuaranteeSurcharge string `xml:"GuaranteeSurchargeRequired,attr"` - GuaranteedRate string `xml:"GuaranteedRateProgram,attr"` - IATA_Character string `xml:"IATA_CharacteristicIdentification,attr"` - IATA_Product string `xml:"IATA_ProductIdentification,attr"` - LowInventory string `xml:"LowInventoryThreshold,attr"` - RateAccessCode string `xml:"RateAccessCode,attr"` - RateCategory string `xml:"RateCategory,attr"` - RateLevelCode string `xml:"RateLevelCode,attr"` - RPH string `xml:"RPH,attr"` - RateChangeInd string `xml:"RateChangeInd,attr"` - RateConversionInd string `xml:"RateConversionInd,attr"` - RoomLocationCode string `xml:"RoomLocationCode,attr"` - SpecialOffer string `xml:"SpecialOffer,attr"` - Rates []Rate `xml:"Rates>Rate"` + ClientID string `xml:"ClientID,attr,omitempty"` + DirectConnect string `xml:"DirectConnect,attr,omitempty"` + GuaranteeSurcharge string `xml:"GuaranteeSurchargeRequired,attr,omitempty"` + GuaranteedRate string `xml:"GuaranteedRateProgram,attr,omitempty"` + IATA_Character string `xml:"IATA_CharacteristicIdentification,attr,omitempty"` + IATA_Product string `xml:"IATA_ProductIdentification,attr,omitempty"` + LowInventory string `xml:"LowInventoryThreshold,attr,omitempty"` + RateAccessCode string `xml:"RateAccessCode,attr,omitempty"` + RateCategory string `xml:"RateCategory,attr,omitempty"` + RateLevelCode string `xml:"RateLevelCode,attr,omitempty"` + RPH string `xml:"RPH,attr,omitempty"` + RateChangeInd string `xml:"RateChangeInd,attr,omitempty"` + RateConversionInd string `xml:"RateConversionInd,attr,omitempty"` + RoomLocationCode string `xml:"RoomLocationCode,attr,omitempty"` + SpecialOffer string `xml:"SpecialOffer,attr,omitempty"` + Rates []Rate `xml:"Rates>Rate,omitempty"` AdditionalInfo AdditionalInfo - HotelRateCode string `xml:"HotelRateCode"` - TrackedEncoding string `json:"tracked_encoding"` + HotelRateCode string `xml:"HotelRateCode,omitempty"` + B64RoomMetaData string } func (r *RoomRate) DecodeTrackedEncoding() ([]string, error) { res := []string{} - bytEnc, err := B64Dec(r.TrackedEncoding) + bytEnc, err := B64Dec(r.B64RoomMetaData) if err != nil { return res, err } From 91dcd2d0aae8cda294f7ac02578093cf0dcf76c4 Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Thu, 2 Aug 2018 14:02:12 -0600 Subject: [PATCH 18/21] update b64 str set and tests --- client/app/handlers.go | 2 +- engine/hotelws/hotel_prop_desc.go | 13 +++---- engine/hotelws/hotel_prop_desc_test.go | 23 +++++------- engine/hotelws/hotel_rate_desc.go | 11 +++--- engine/hotelws/hotel_rate_desc_test.go | 21 ++++------- engine/hotelws/hotelws.go | 48 ++++++++++++++------------ 6 files changed, 54 insertions(+), 64 deletions(-) diff --git a/client/app/handlers.go b/client/app/handlers.go index 0338737..7a1c156 100644 --- a/client/app/handlers.go +++ b/client/app/handlers.go @@ -259,7 +259,7 @@ func (s *Server) RatesHotelIDHandler() http.HandlerFunc { return } - call.SetTrackedEncode() + call.SetRoomMetaData() response.RoomStay = call.Body.HotelDesc.RoomStay w.WriteHeader(http.StatusOK) diff --git a/engine/hotelws/hotel_prop_desc.go b/engine/hotelws/hotel_prop_desc.go index 15d85b4..3d7cb7f 100644 --- a/engine/hotelws/hotel_prop_desc.go +++ b/engine/hotelws/hotel_prop_desc.go @@ -134,14 +134,15 @@ type HotelPropDescResponse struct { ErrorSabreXML sbrerr.ErrorSabreXML } -func (r *HotelPropDescResponse) SetTrackedEncode() { +func (r *HotelPropDescResponse) SetRoomMetaData() { for i, rate := range r.Body.HotelDesc.RoomStay.RoomRates { strslc := []string{} - strslc = append(strslc, fmt.Sprintf("%s:%d", TrackEncIndex, i)) - strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncRPH, rate.RPH)) - strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncIATAChar, rate.IATA_Character)) - strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncTotal, rate.Rates[0].HotelPricing.Amount)) - r.Body.HotelDesc.RoomStay.RoomRates[i].B64RoomMetaData = B64Enc(strings.Join(strslc, TrackEncDelimiter)) + strslc = append(strslc, fmt.Sprintf("%s:%s", RoomMetaRPHKey, rate.RPH)) + strslc = append(strslc, fmt.Sprintf("%s:%s", RoomMetaIATACharKey, rate.IATA_Character)) + for ri, rr := range rate.Rates { + strslc = append(strslc, fmt.Sprintf("%s:%d-%s:%s-%s:%s", RoomMetaRatesIdxKey, ri, RoomMetaTotalKey, rr.HotelPricing.Amount, RoomMetaRateNextKey, rr.HRD_RequiredForSell)) + } + r.Body.HotelDesc.RoomStay.RoomRates[i].B64RoomMetaData = B64Enc(strings.Join(strslc, RoomMetaDelimiter)) } } diff --git a/engine/hotelws/hotel_prop_desc_test.go b/engine/hotelws/hotel_prop_desc_test.go index 82dc6ef..8069c7c 100644 --- a/engine/hotelws/hotel_prop_desc_test.go +++ b/engine/hotelws/hotel_prop_desc_test.go @@ -207,11 +207,11 @@ var proptrack = []struct { b64str string input []string }{ - {"aWR4OjB8cnBoOjAwMXxpYXRhY2hhcjpQMUtSQUN8dG90YWw6MzM1LjQ1", []string{"idx:0", "rph:001", "iatachar:P1KRAC", "total:335.45"}}, - {"aWR4OjF8cnBoOjAwMnxpYXRhY2hhcjpEMUtSQUN8dG90YWw6MzM1LjQ1", []string{"idx:1", "rph:002", "iatachar:D1KRAC", "total:335.45"}}, + {"cnBoOjAwMXxybXQ6UDFLUkFDfHJ0eDowLXR0bDozMzUuNDUtbnh0OmZhbHNl", []string{"rph:001", "rmt:P1KRAC", "rtx:0-ttl:335.45-nxt:false"}}, + {"cnBoOjAwMnxybXQ6RDFLUkFDfHJ0eDowLXR0bDozMzUuNDUtbnh0OmZhbHNl", []string{"rph:002", "rmt:D1KRAC", "rtx:0-ttl:335.45-nxt:false"}}, } -func TestSetTrackedEncodePropDesc(t *testing.T) { +func TestSetRoomMetaDataPropDesc(t *testing.T) { var hotelid = make(HotelRefCriterion) hotelid[HotelidQueryField] = []string{"10"} q, _ := NewHotelSearchCriteria( @@ -220,7 +220,7 @@ func TestSetTrackedEncodePropDesc(t *testing.T) { prop, _ := SetHotelPropDescBody(sampleGuestCount, q, sampleArrive, sampleDepart) req := BuildHotelPropDescRequest(sconf, prop) resp, _ := CallHotelPropDesc(serverHotelPropertyDesc.URL, req) - resp.SetTrackedEncode() + resp.SetRoomMetaData() for i, rate := range resp.Body.HotelDesc.RoomStay.RoomRates { //only test the first 2 if i > 1 { @@ -233,17 +233,10 @@ func TestSetTrackedEncodePropDesc(t *testing.T) { if err != nil { t.Errorf("Error on DecodeTrackedEncoding() %v", err) } - if res[0] != proptrack[i].input[0] { - t.Errorf("epxected %s, got %s", proptrack[i].input[0], res[0]) - } - if res[1] != proptrack[i].input[1] { - t.Errorf("epxected %s, got %s", proptrack[i].input[1], res[1]) - } - if res[2] != proptrack[i].input[2] { - t.Errorf("epxected %s, got %s", proptrack[i].input[2], res[2]) - } - if res[3] != proptrack[i].input[3] { - t.Errorf("epxected %s, got %s", proptrack[i].input[3], res[3]) + for ix, b64 := range res { + if b64 != proptrack[i].input[ix] { + t.Errorf("epxected %s, got %s", proptrack[i].input[ix], b64) + } } } } diff --git a/engine/hotelws/hotel_rate_desc.go b/engine/hotelws/hotel_rate_desc.go index e258b7d..9136e74 100644 --- a/engine/hotelws/hotel_rate_desc.go +++ b/engine/hotelws/hotel_rate_desc.go @@ -112,11 +112,12 @@ type HotelRateDescResponse struct { func (r *HotelRateDescResponse) SetTrackedEncode() { for i, rate := range r.Body.HotelDesc.RoomStay.RoomRates { strslc := []string{} - strslc = append(strslc, fmt.Sprintf("%s:%d", TrackEncIndex, i)) - strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncRPH, rate.RPH)) - strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncIATAChar, rate.IATA_Character)) - strslc = append(strslc, fmt.Sprintf("%s:%s", TrackEncTotal, rate.Rates[0].HotelPricing.Amount)) - r.Body.HotelDesc.RoomStay.RoomRates[i].B64RoomMetaData = B64Enc(strings.Join(strslc, TrackEncDelimiter)) + strslc = append(strslc, fmt.Sprintf("%s:%s", RoomMetaRPHKey, rate.RPH)) + strslc = append(strslc, fmt.Sprintf("%s:%s", RoomMetaIATACharKey, rate.IATA_Character)) + for ri, rr := range rate.Rates { + strslc = append(strslc, fmt.Sprintf("%s:%d-%s:%s", RoomMetaRatesIdxKey, ri, RoomMetaTotalKey, rr.HotelPricing.Amount)) + } + r.Body.HotelDesc.RoomStay.RoomRates[i].B64RoomMetaData = B64Enc(strings.Join(strslc, RoomMetaDelimiter)) } } diff --git a/engine/hotelws/hotel_rate_desc_test.go b/engine/hotelws/hotel_rate_desc_test.go index 3eaa6a1..eb252fa 100644 --- a/engine/hotelws/hotel_rate_desc_test.go +++ b/engine/hotelws/hotel_rate_desc_test.go @@ -140,11 +140,11 @@ var ratetrack = []struct { b64str string input []string }{ - {"aWR4OjB8cnBoOjAwMXxpYXRhY2hhcjpKMUtBMTZ8dG90YWw6MzA3LjUw", []string{"idx:0", "rph:001", "iatachar:J1KA16", "total:307.50"}}, - {"aWR4OjF8cnBoOjAwMnxpYXRhY2hhcjpGVzhNTlVUfHRvdGFsOjE3Mi45NQ==", []string{"idx:1", "rph:002", "iatachar:FW8MNUT", "total:172.95"}}, + {"cnBoOjAwMXxybXQ6SjFLQTE2fHJ0eDowLXR0bDozMDcuNTA=", []string{"rph:001", "rmt:J1KA16", "rtx:0-ttl:307.50"}}, + {"cnBoOjAwMnxybXQ6Rlc4TU5VVHxydHg6MC10dGw6MTcyLjk1", []string{"rph:002", "rmt:FW8MNUT", "rtx:0-ttl:172.95"}}, } -func TestSetTrackedEncodeRateDesc(t *testing.T) { +func TestSetRoomMetaDataRateDesc(t *testing.T) { // assume RPH is from previous hotel property description call rpc := SetRateParams( []RatePlan{ @@ -165,17 +165,10 @@ func TestSetTrackedEncodeRateDesc(t *testing.T) { if err != nil { t.Errorf("Error on DecodeTrackedEncoding() %v", err) } - if res[0] != ratetrack[i].input[0] { - t.Errorf("epxected %s, got %s", ratetrack[i].input[0], res[0]) - } - if res[1] != ratetrack[i].input[1] { - t.Errorf("epxected %s, got %s", ratetrack[i].input[1], res[1]) - } - if res[2] != ratetrack[i].input[2] { - t.Errorf("epxected %s, got %s", ratetrack[i].input[2], res[2]) - } - if res[3] != ratetrack[i].input[3] { - t.Errorf("epxected %s, got %s", ratetrack[i].input[3], res[3]) + for ix, b64 := range res { + if b64 != ratetrack[i].input[ix] { + t.Errorf("epxected %s, got %s", ratetrack[i].input[ix], b64) + } } } } diff --git a/engine/hotelws/hotelws.go b/engine/hotelws/hotelws.go index f163971..0a66ec4 100644 --- a/engine/hotelws/hotelws.go +++ b/engine/hotelws/hotelws.go @@ -42,11 +42,13 @@ const ( CountryCodeQueryField = "countryCode_qf" LatlngQueryField = "latlng_qf" HotelidQueryField = "hotelID_qf" - TrackEncDelimiter = "|" - TrackEncIndex = "idx" - TrackEncRPH = "rph" - TrackEncIATAChar = "iatachar" - TrackEncTotal = "total" + RoomMetaDelimiter = "|" + RoomMetaIdxKey = "idx" + RoomMetaRPHKey = "rph" //reference place holder + RoomMetaIATACharKey = "rmt" //room type + RoomMetaRatesIdxKey = "rtx" //rate index + RoomMetaTotalKey = "ttl" //total + RoomMetaRateNextKey = "nxt" //next returnHostCommand = true ESA = "\u0087" //UNICODE: End of Selected Area CrossLorraine = "\u2628" //UNICODE Cross of Lorraine @@ -289,24 +291,24 @@ type PaymentCard struct { type RoomRate struct { XMLName xml.Name `xml:"RoomRate" json:"-"` - ClientID string `xml:"ClientID,attr,omitempty"` - DirectConnect string `xml:"DirectConnect,attr,omitempty"` - GuaranteeSurcharge string `xml:"GuaranteeSurchargeRequired,attr,omitempty"` - GuaranteedRate string `xml:"GuaranteedRateProgram,attr,omitempty"` - IATA_Character string `xml:"IATA_CharacteristicIdentification,attr,omitempty"` - IATA_Product string `xml:"IATA_ProductIdentification,attr,omitempty"` - LowInventory string `xml:"LowInventoryThreshold,attr,omitempty"` - RateAccessCode string `xml:"RateAccessCode,attr,omitempty"` - RateCategory string `xml:"RateCategory,attr,omitempty"` - RateLevelCode string `xml:"RateLevelCode,attr,omitempty"` - RPH string `xml:"RPH,attr,omitempty"` - RateChangeInd string `xml:"RateChangeInd,attr,omitempty"` - RateConversionInd string `xml:"RateConversionInd,attr,omitempty"` - RoomLocationCode string `xml:"RoomLocationCode,attr,omitempty"` - SpecialOffer string `xml:"SpecialOffer,attr,omitempty"` - Rates []Rate `xml:"Rates>Rate,omitempty"` + ClientID string `xml:"ClientID,attr"` + DirectConnect string `xml:"DirectConnect,attr"` + GuaranteeSurcharge string `xml:"GuaranteeSurchargeRequired,attr"` + GuaranteedRate string `xml:"GuaranteedRateProgram,attr"` + IATA_Character string `xml:"IATA_CharacteristicIdentification,attr"` + IATA_Product string `xml:"IATA_ProductIdentification,attr"` + LowInventory string `xml:"LowInventoryThreshold,attr"` + RateAccessCode string `xml:"RateAccessCode,attr"` + RateCategory string `xml:"RateCategory,attr"` + RateLevelCode string `xml:"RateLevelCode,attr"` + RPH string `xml:"RPH,attr"` + RateChangeInd string `xml:"RateChangeInd,attr"` + RateConversionInd string `xml:"RateConversionInd,attr"` + RoomLocationCode string `xml:"RoomLocationCode,attr"` + SpecialOffer string `xml:"SpecialOffer,attr"` + Rates []Rate `xml:"Rates>Rate"` AdditionalInfo AdditionalInfo - HotelRateCode string `xml:"HotelRateCode,omitempty"` + HotelRateCode string `xml:"HotelRateCode"` B64RoomMetaData string } @@ -316,7 +318,7 @@ func (r *RoomRate) DecodeTrackedEncoding() ([]string, error) { if err != nil { return res, err } - res = strings.Split(string(bytEnc), TrackEncDelimiter) + res = strings.Split(string(bytEnc), RoomMetaDelimiter) return res, nil } From 80441e959a4cadb5e20f4b4e4033e235e5bea8a2 Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Tue, 7 Aug 2018 19:46:43 -0600 Subject: [PATCH 19/21] finish tests for room stay rate cached values --- client/app/handlers.go | 19 +-- engine/hotelws/hotel_prop_desc.go | 22 +++- engine/hotelws/hotel_prop_desc_test.go | 84 +++++++++++-- engine/hotelws/hotel_rate_desc.go | 14 --- engine/hotelws/hotel_rate_desc_test.go | 37 ------ engine/hotelws/hotelws.go | 168 +++++++++++++++++++------ engine/hotelws/hotelws_helper_test.go | 16 ++- 7 files changed, 248 insertions(+), 112 deletions(-) diff --git a/client/app/handlers.go b/client/app/handlers.go index 7a1c156..4452e63 100644 --- a/client/app/handlers.go +++ b/client/app/handlers.go @@ -37,12 +37,12 @@ type HotelParamsID struct { type BookRoomParams struct { FirstName string `form:"first_name"` LastName string `form:"last_name"` - Phone string `form:"phone"` NumRooms string `form:"num_rooms"` - RoomRPH string `form:"room_rph"` + CCPhone string `form:"cc_phone"` CCCode string `form:"cc_code"` CCExpire string `form:"cc_expire"` CCNumber string `form:"cc_number"` + RoomMeta string `form:"room_meta"` } // AvailHotelIDSResponse for sabre hotel availability @@ -146,7 +146,12 @@ func (b BookRoomParams) Validate() error { return nil } -// first,last,phone, number of units, rph, ccCode, ccExpire, ccNumber +/* + BookRoomHandler creates a pnr, fetches rate, books room, ends transaction. It accepts room meta data generated from previous requests. Required params: last_name, num_rooms, rph, cc_phone, cc_code, cc_expire, cc_number + + curl -H "Accept: application/json" -X GET 'http://localhost:8080/book/hotel/room?' +*/ + func (s *Server) BookRoomHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -171,7 +176,7 @@ func (s *Server) BookRoomHandler() http.HandlerFunc { //build person person := itin.CreatePersonName(params.FirstName, params.LastName) //build pnr - pnrBody := itin.SetPNRDetailBody(params.Phone, person) + pnrBody := itin.SetPNRDetailBody(params.CCPhone, person) pnrReq := itin.BuildPNRDetailsRequest(s.SConfig, pnrBody) //call pnr pnrResp, err := itin.CallPNRDetail(s.SConfig.ServiceURL, pnrReq) @@ -208,8 +213,8 @@ func (s *Server) BookRoomHandler() http.HandlerFunc { RatesHotelIDHandler wraps SOAP call to sabre property description service. This SOAP service is the primary service for returning room rates. It accepts one hotel ref criterion and returns one hotel with one room stay object containing 0..n room rates. Example: - curl -H "Accept: application/json" -X GET 'http://localhost:8080/rates/hotel/id?guest_count=4&arrive=2018-07-17&depart=2018-07-18&hotel_id=10' - curl -H "Accept: application/json" -X GET 'http://localhost:8080/rates/hotel/id?guest_count=4&arrive=2018-07-17&depart=2018-07-18&hotel_ids=12' + curl -H "Accept: application/json" -X GET 'http://localhost:8080/rates/hotel/id?guest_count=2&arrive=2018-07-17&depart=2018-07-18&hotel_id=10' + curl -H "Accept: application/json" -X GET 'http://localhost:8080/rates/hotel/id?guest_count=4&arrive=2018-07-17&depart=2018-07-18&hotel_id=12' */ func (s *Server) RatesHotelIDHandler() http.HandlerFunc { //closure to execute @@ -259,7 +264,7 @@ func (s *Server) RatesHotelIDHandler() http.HandlerFunc { return } - call.SetRoomMetaData() + call.SetRoomMetaData(params.GuestCount, params.OutArrive, params.OutDepart, params.HotelID) response.RoomStay = call.Body.HotelDesc.RoomStay w.WriteHeader(http.StatusOK) diff --git a/engine/hotelws/hotel_prop_desc.go b/engine/hotelws/hotel_prop_desc.go index 3d7cb7f..84a9f57 100644 --- a/engine/hotelws/hotel_prop_desc.go +++ b/engine/hotelws/hotel_prop_desc.go @@ -134,15 +134,27 @@ type HotelPropDescResponse struct { ErrorSabreXML sbrerr.ErrorSabreXML } -func (r *HotelPropDescResponse) SetRoomMetaData() { +// SetRoomMetaData builds a b64 encoded string cache of rate request for later retrieval. See NewParsedRoomMeta for this data is parsed. +func (r *HotelPropDescResponse) SetRoomMetaData(guest int, arrive, depart, hotelid string) { for i, rate := range r.Body.HotelDesc.RoomStay.RoomRates { strslc := []string{} - strslc = append(strslc, fmt.Sprintf("%s:%s", RoomMetaRPHKey, rate.RPH)) - strslc = append(strslc, fmt.Sprintf("%s:%s", RoomMetaIATACharKey, rate.IATA_Character)) + rrates := "" + strslc = append(strslc, fmt.Sprintf("%s:%s", RoomMetaArvKey, arrive)) + strslc = append(strslc, fmt.Sprintf("%s:%s", RoomMetaDptKey, depart)) + strslc = append(strslc, fmt.Sprintf("%s:%d", RoomMetaGstKey, guest)) + strslc = append(strslc, fmt.Sprintf("%s:%s", RoomMetaHcKey, r.Body.HotelDesc.Result.Success.System.HostCommand.Cryptic)) + strslc = append(strslc, fmt.Sprintf("%s:%s", RoomMetaHidKey, hotelid)) + strslc = append(strslc, fmt.Sprintf("%s:%s", RoomMetaRphKey, rate.RPH)) + strslc = append(strslc, fmt.Sprintf("%s:%s", RoomMetaRmtKey, rate.IATA_Character)) + for ri, rr := range rate.Rates { - strslc = append(strslc, fmt.Sprintf("%s:%d-%s:%s-%s:%s", RoomMetaRatesIdxKey, ri, RoomMetaTotalKey, rr.HotelPricing.Amount, RoomMetaRateNextKey, rr.HRD_RequiredForSell)) + rrates += fmt.Sprintf("%s:%s-%s:%s-%s:%s", RrateMetaCurKey, rr.CurrencyCode, RrateMetaRqsKey, rr.HRD_RequiredForSell, RrateMetaAmtKey, rr.HotelPricing.Amount) + if ((len(rate.Rates) - 1) - ri) != 0 { + rrates += SColDelim + } } - r.Body.HotelDesc.RoomStay.RoomRates[i].B64RoomMetaData = B64Enc(strings.Join(strslc, RoomMetaDelimiter)) + strslc = append(strslc, fmt.Sprintf("%s%s%s", RBrackDelim, rrates, LBrackDelim)) + r.Body.HotelDesc.RoomStay.RoomRates[i].B64RoomMetaData = B64Enc(strings.Join(strslc, PipeDelim)) } } diff --git a/engine/hotelws/hotel_prop_desc_test.go b/engine/hotelws/hotel_prop_desc_test.go index 8069c7c..8d334c3 100644 --- a/engine/hotelws/hotel_prop_desc_test.go +++ b/engine/hotelws/hotel_prop_desc_test.go @@ -203,12 +203,52 @@ func TestPropDescCall(t *testing.T) { } } +//[]string{"hc:HOD4/11MAY-12MAY2", "rph:002", "rmt:D1KRAC", "rtx:0-ttl:190.45-nxt:false"} + var proptrack = []struct { b64str string - input []string + expect ParsedRoomMeta }{ - {"cnBoOjAwMXxybXQ6UDFLUkFDfHJ0eDowLXR0bDozMzUuNDUtbnh0OmZhbHNl", []string{"rph:001", "rmt:P1KRAC", "rtx:0-ttl:335.45-nxt:false"}}, - {"cnBoOjAwMnxybXQ6RDFLUkFDfHJ0eDowLXR0bDozMzUuNDUtbnh0OmZhbHNl", []string{"rph:002", "rmt:D1KRAC", "rtx:0-ttl:335.45-nxt:false"}}, + {"YXJ2OjA0LTAyfGRwdDowNC0wNXxnc3Q6MnxoYzpIT0Q0LzExTUFZLTEyTUFZMnxoaWQ6MTB8cnBoOjAwMXxybXQ6UDFLUkFDfFtjdXI6U0dELXJxczpmYWxzZS1hbXQ6MzM1LjQ1O2N1cjpVU0QtcnFzOmZhbHNlLWFtdDo0MzUuNDVd", ParsedRoomMeta{ + Arrive: "04-02", + Depart: "04-05", + Guest: "2", + Hc: "HOD4/11MAY-12MAY2", + HotelID: "10", + Rmt: "P1KRAC", + Rph: "001", + StayRatesCache: []string{"cur:SGD-rqs:false-amt:335.45", "cur:USD-rqs:false-amt:435.45"}, + ParsedStayRatesCache: []parsedStayRateCache{ + parsedStayRateCache{ + Amt: "335.45", + Cur: "SGD", + Rqs: false, + }, + parsedStayRateCache{ + Amt: "435.45", + Cur: "USD", + Rqs: false, + }, + }, + }}, + {"YXJ2OjA0LTAyfGRwdDowNC0wNXxnc3Q6MnxoYzpIT0Q0LzExTUFZLTEyTUFZMnxoaWQ6MTB8cnBoOjAwMnxybXQ6RDFLUkFDfFtjdXI6U0dELXJxczpmYWxzZS1hbXQ6MTkwLjQ1XQ==", + ParsedRoomMeta{ + Arrive: "04-02", + Depart: "04-05", + Guest: "2", + Hc: "HOD4/11MAY-12MAY2", + HotelID: "10", + Rmt: "D1KRAC", + Rph: "002", + StayRatesCache: []string{"rtx:0-ttl:190.45-nxt:false"}, + ParsedStayRatesCache: []parsedStayRateCache{ + parsedStayRateCache{ + Amt: "190.45", + Cur: "SGD", + Rqs: false, + }, + }, + }}, } func TestSetRoomMetaDataPropDesc(t *testing.T) { @@ -220,24 +260,50 @@ func TestSetRoomMetaDataPropDesc(t *testing.T) { prop, _ := SetHotelPropDescBody(sampleGuestCount, q, sampleArrive, sampleDepart) req := BuildHotelPropDescRequest(sconf, prop) resp, _ := CallHotelPropDesc(serverHotelPropertyDesc.URL, req) - resp.SetRoomMetaData() + resp.SetRoomMetaData(sampleGuestCount, sampleArrive, sampleDepart, "10") for i, rate := range resp.Body.HotelDesc.RoomStay.RoomRates { //only test the first 2 if i > 1 { break } if rate.B64RoomMetaData != proptrack[i].b64str { - t.Errorf("TrackedEncoding expect: '%s', got '%s'", proptrack[i].b64str, rate.B64RoomMetaData) + t.Errorf("B64RoomMetaData expect: '%s', got '%s'", proptrack[i].b64str, rate.B64RoomMetaData) } - res, err := rate.DecodeTrackedEncoding() + prm, err := rate.NewParsedRoomMeta() if err != nil { t.Errorf("Error on DecodeTrackedEncoding() %v", err) } - for ix, b64 := range res { - if b64 != proptrack[i].input[ix] { - t.Errorf("epxected %s, got %s", proptrack[i].input[ix], b64) + if prm.Arrive != proptrack[i].expect.Arrive { + t.Errorf("Arrive expect %s, got %s", proptrack[i].expect.Arrive, prm.Arrive) + } + if prm.Depart != proptrack[i].expect.Depart { + t.Errorf("Depart expect %s, got %s", proptrack[i].expect.Depart, prm.Depart) + } + if prm.Guest != proptrack[i].expect.Guest { + t.Errorf("Guest expect %s, got %s", proptrack[i].expect.Guest, prm.Guest) + } + if prm.Hc != proptrack[i].expect.Hc { + t.Errorf("Hc expect %s, got %s", proptrack[i].expect.Hc, prm.Hc) + } + if prm.HotelID != proptrack[i].expect.HotelID { + t.Errorf("HotelID expect %s, got %s", proptrack[i].expect.HotelID, prm.HotelID) + } + + for idx, psrc := range prm.ParsedStayRatesCache { + amt := proptrack[i].expect.ParsedStayRatesCache[idx].Amt + cur := proptrack[i].expect.ParsedStayRatesCache[idx].Cur + rqs := proptrack[i].expect.ParsedStayRatesCache[idx].Rqs + if psrc.Amt != amt { + t.Errorf("Amt expect %s, got %s", amt, psrc.Amt) + } + if psrc.Cur != cur { + t.Errorf("Cur expect %s, got %s", cur, psrc.Cur) + } + if psrc.Rqs != rqs { + t.Errorf("Amt expect %v, got %v", rqs, psrc.Rqs) } } + } } diff --git a/engine/hotelws/hotel_rate_desc.go b/engine/hotelws/hotel_rate_desc.go index 9136e74..849c2de 100644 --- a/engine/hotelws/hotel_rate_desc.go +++ b/engine/hotelws/hotel_rate_desc.go @@ -3,10 +3,8 @@ package hotelws import ( "bytes" "encoding/xml" - "fmt" "io" "net/http" - "strings" "github.com/ailgroup/sbrweb/engine/sbrerr" "github.com/ailgroup/sbrweb/engine/srvc" @@ -109,18 +107,6 @@ type HotelRateDescResponse struct { ErrorSabreXML sbrerr.ErrorSabreXML } -func (r *HotelRateDescResponse) SetTrackedEncode() { - for i, rate := range r.Body.HotelDesc.RoomStay.RoomRates { - strslc := []string{} - strslc = append(strslc, fmt.Sprintf("%s:%s", RoomMetaRPHKey, rate.RPH)) - strslc = append(strslc, fmt.Sprintf("%s:%s", RoomMetaIATACharKey, rate.IATA_Character)) - for ri, rr := range rate.Rates { - strslc = append(strslc, fmt.Sprintf("%s:%d-%s:%s", RoomMetaRatesIdxKey, ri, RoomMetaTotalKey, rr.HotelPricing.Amount)) - } - r.Body.HotelDesc.RoomStay.RoomRates[i].B64RoomMetaData = B64Enc(strings.Join(strslc, RoomMetaDelimiter)) - } -} - // CallHotelRateDesc to sabre web services retrieve hotel rates using HotelRateDescriptionLLSRQ. This call only supports requests that contain an RPH from a previous hotel_property_desc call, see BuildHotelRateDescRequest. func CallHotelRateDesc(serviceURL string, req HotelRateDescRequest) (HotelRateDescResponse, error) { rateResp := HotelRateDescResponse{} diff --git a/engine/hotelws/hotel_rate_desc_test.go b/engine/hotelws/hotel_rate_desc_test.go index eb252fa..aca0a60 100644 --- a/engine/hotelws/hotel_rate_desc_test.go +++ b/engine/hotelws/hotel_rate_desc_test.go @@ -136,43 +136,6 @@ func TestRateDescCall(t *testing.T) { } } -var ratetrack = []struct { - b64str string - input []string -}{ - {"cnBoOjAwMXxybXQ6SjFLQTE2fHJ0eDowLXR0bDozMDcuNTA=", []string{"rph:001", "rmt:J1KA16", "rtx:0-ttl:307.50"}}, - {"cnBoOjAwMnxybXQ6Rlc4TU5VVHxydHg6MC10dGw6MTcyLjk1", []string{"rph:002", "rmt:FW8MNUT", "rtx:0-ttl:172.95"}}, -} - -func TestSetRoomMetaDataRateDesc(t *testing.T) { - // assume RPH is from previous hotel property description call - rpc := SetRateParams( - []RatePlan{ - RatePlan{ - RPH: "12", - }, - }, - ) - raterq, _ := SetHotelRateDescBody(rpc) - req := BuildHotelRateDescRequest(sconf, raterq) - resp, _ := CallHotelRateDesc(serverHotelRateDesc.URL, req) - resp.SetTrackedEncode() - for i, rate := range resp.Body.HotelDesc.RoomStay.RoomRates { - if rate.B64RoomMetaData != ratetrack[i].b64str { - t.Errorf("TrackedEncoding expect: '%s', got '%s'", ratetrack[i].b64str, rate.B64RoomMetaData) - } - res, err := rate.DecodeTrackedEncoding() - if err != nil { - t.Errorf("Error on DecodeTrackedEncoding() %v", err) - } - for ix, b64 := range res { - if b64 != ratetrack[i].input[ix] { - t.Errorf("epxected %s, got %s", ratetrack[i].input[ix], b64) - } - } - } -} - func TestHotelRateDesCallDown(t *testing.T) { rpc := SetRateParams( []RatePlan{ diff --git a/engine/hotelws/hotelws.go b/engine/hotelws/hotelws.go index 0a66ec4..321b477 100644 --- a/engine/hotelws/hotelws.go +++ b/engine/hotelws/hotelws.go @@ -24,6 +24,7 @@ import ( b64 "encoding/base64" "encoding/xml" "fmt" + "regexp" "strings" "time" "unicode" @@ -32,40 +33,45 @@ import ( ) const ( - hotelRQVersion = "2.3.0" - TimeFormatMD = "01-02" - TimeFormatMDTHM = "01-02T15:04" - TimeFormatMDHM = "01-02 15:04" + hotelRQVersion = "2.3.0" + TimeFormatMD = "01-02" + TimeFormatMDTHM = "01-02T15:04" + TimeFormatMDHM = "01-02 15:04" + StreetQueryField = "street_qf" CityQueryField = "city_qf" - PostalQueryField = "postal_qf" CountryCodeQueryField = "countryCode_qf" - LatlngQueryField = "latlng_qf" HotelidQueryField = "hotelID_qf" - RoomMetaDelimiter = "|" - RoomMetaIdxKey = "idx" - RoomMetaRPHKey = "rph" //reference place holder - RoomMetaIATACharKey = "rmt" //room type - RoomMetaRatesIdxKey = "rtx" //rate index - RoomMetaTotalKey = "ttl" //total - RoomMetaRateNextKey = "nxt" //next - returnHostCommand = true - ESA = "\u0087" //UNICODE: End of Selected Area - CrossLorraine = "\u2628" //UNICODE Cross of Lorraine -) - -var hostCommandReplacer = strings.NewReplacer("\\", "", "/", "", ESA, "") + LatlngQueryField = "latlng_qf" + PostalQueryField = "postal_qf" -// B64Enc base64 encode a string -func B64Enc(str string) string { - return b64.URLEncoding.EncodeToString([]byte(str)) -} + ColDelim = ":" + DashDelim = "-" + LBrackDelim = "]" + PipeDelim = "|" + RBrackDelim = "[" + SColDelim = ";" + + RoomMetaArvKey = "arv" //arrival + RoomMetaDptKey = "dpt" //depart + RoomMetaGstKey = "gst" //guest count + RoomMetaHcKey = "hc" //host command + RoomMetaHidKey = "hid" //hotel id + RoomMetaRmtKey = "rmt" //room type + RoomMetaRphKey = "rph" //reference place holder + RrateMetaAmtKey = "amt" //total + RrateMetaCurKey = "cur" //total + RrateMetaRqsKey = "rqs" //next + + returnHostCommand = true + ESA = "\u0087" //UNICODE: End of Selected Area + CrossLorraine = "\u2628" //UNICODE Cross of Lorraine +) -// B64Dec decode a base64 string -func B64Dec(b64str string) (string, error) { - uDec, err := b64.URLEncoding.DecodeString(b64str) - return string(uDec), err -} +var ( + hostCommandReplacer = strings.NewReplacer("\\", "", "/", "", ESA, "") + ratesMetaMatch = regexp.MustCompile(`^\[.*\]$`) +) // TimeSpanFormatter parse string data value into time value. func TimeSpanFormatter(arrive, depart, formIn, formOut string) TimeSpan { @@ -89,6 +95,7 @@ func sanatize(str string) string { }, hostCommandReplacer.Replace(strings.ToLower(str))) } +// Translate sabre SystemResults messages into human readable. func (s SystemResults) Translate() string { clean := sanatize(s.Message) switch { @@ -99,6 +106,7 @@ func (s SystemResults) Translate() string { } } +// ErrFormat formatter on ApplicationResults for printing error string from sabre soap calls. /* OTHER ERRORS --see hotel_res_direct_connect.xml, credit card??? @@ -125,6 +133,7 @@ func (result ApplicationResults) ErrFormat() sbrerr.ErrorSabreResult { } } +// Ok for ApplicationResults returns boolean check for sbrerr on SOAP requests func (result ApplicationResults) Ok() bool { switch result.Status { case sbrerr.StatusNotProcess(): //queries @@ -138,6 +147,99 @@ func (result ApplicationResults) Ok() bool { } } +// B64Enc base64 encode a string +func B64Enc(str string) string { + return b64.URLEncoding.EncodeToString([]byte(str)) +} + +// B64Dec decode a base64 string +func B64Dec(b64str string) (string, error) { + uDec, err := b64.URLEncoding.DecodeString(b64str) + return string(uDec), err +} + +// parseB64DecodeRates parses cached room stay rates +func (p *ParsedRoomMeta) parseB64DecodeRates() { + for _, r := range p.StayRatesCache { + mr := parsedStayRateCache{} + dash := strings.Split(r, DashDelim) + for _, d := range dash { + rs := strings.Split(d, ColDelim) + if len(rs) < 2 { + continue + } + switch rs[0] { + case RrateMetaAmtKey: + mr.Amt = rs[1] + case RrateMetaCurKey: + mr.Cur = rs[1] + case RrateMetaRqsKey: + if rs[1] == "true" { + mr.Rqs = true + } + } + } + p.ParsedStayRatesCache = append(p.ParsedStayRatesCache, mr) + } +} + +// NewParsedRoomMeta builds a strcut from parsing b64 encoded string cache of previous rate request. See SetRoomMetaData for how this data is constucted. +func (r *RoomRate) NewParsedRoomMeta() (ParsedRoomMeta, error) { + rmp := ParsedRoomMeta{} + b64Str, err := B64Dec(r.B64RoomMetaData) + if err != nil { + return rmp, err + } + for _, p := range strings.Split(b64Str, PipeDelim) { + if ratesMetaMatch.MatchString(p) { + b := strings.TrimPrefix(p, RBrackDelim) + b = strings.TrimSuffix(b, LBrackDelim) + rmp.StayRatesCache = strings.Split(b, SColDelim) + } else { + c := strings.Split(p, ColDelim) + if len(c) < 2 { + continue + } + switch c[0] { + case RoomMetaArvKey: + rmp.Arrive = c[1] + case RoomMetaDptKey: + rmp.Depart = c[1] + case RoomMetaGstKey: + rmp.Guest = c[1] + case RoomMetaHcKey: + rmp.Hc = c[1] + case RoomMetaHidKey: + rmp.HotelID = c[1] + case RoomMetaRphKey: + rmp.Rph = c[1] + case RoomMetaRmtKey: + rmp.Rmt = c[1] + } + } + } + rmp.parseB64DecodeRates() + return rmp, nil +} + +type parsedStayRateCache struct { + Amt string + Cur string + Rqs bool +} + +type ParsedRoomMeta struct { + Arrive string // arrival + Depart string // departure + Guest string // guest count + Hc string // host command + HotelID string // hotel id + Rmt string // room type;;iata characteristic + Rph string // reference place holder + StayRatesCache []string // room_stay.room_rates + ParsedStayRatesCache []parsedStayRateCache +} + // HotelSearchCriteria top level element for criterion type HotelSearchCriteria struct { XMLName xml.Name `xml:"HotelSearchCriteria"` @@ -312,16 +414,6 @@ type RoomRate struct { B64RoomMetaData string } -func (r *RoomRate) DecodeTrackedEncoding() ([]string, error) { - res := []string{} - bytEnc, err := B64Dec(r.B64RoomMetaData) - if err != nil { - return res, err - } - res = strings.Split(string(bytEnc), RoomMetaDelimiter) - return res, nil -} - type AdditionalInfo struct { XMLName xml.Name `xml:"AdditionalInfo" json:"-"` Commission struct { diff --git a/engine/hotelws/hotelws_helper_test.go b/engine/hotelws/hotelws_helper_test.go index 8f059e3..d91db2a 100644 --- a/engine/hotelws/hotelws_helper_test.go +++ b/engine/hotelws/hotelws_helper_test.go @@ -848,6 +848,18 @@ var ( + + + + + + + + INCLUDES TAXES AND SURCHARGES + + + + @@ -858,13 +870,13 @@ var ( PRIVATE BALCONY, FREE WIFI - + - + INCLUDES TAXES AND SURCHARGES From fb775dc135aa35de39321a7e64e587a479aececc Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Thu, 9 Aug 2018 19:21:41 -0600 Subject: [PATCH 20/21] room meta on booking, more tests set parsed room metadata on booking handler response, write tests for b64 encode and decode, add result and err format conditions to hotel property description tests --- client/app/handlers.go | 66 ++++++++++++++++---------- engine/hotelws/hotel_prop_desc_test.go | 41 +++++++++++++++- engine/hotelws/hotelws.go | 4 +- engine/hotelws/hotelws_test.go | 53 +++++++++++++++++++-- 4 files changed, 131 insertions(+), 33 deletions(-) diff --git a/client/app/handlers.go b/client/app/handlers.go index 4452e63..ab4c187 100644 --- a/client/app/handlers.go +++ b/client/app/handlers.go @@ -7,7 +7,6 @@ import ( "github.com/ailgroup/sbrweb/apperr" "github.com/ailgroup/sbrweb/engine/hotelws" - "github.com/ailgroup/sbrweb/engine/itin" "github.com/go-playground/form" ) @@ -35,14 +34,15 @@ type HotelParamsID struct { // BookRoomParams hold params for creating pnr and executing a reservation. CCCode is the credit card type code (MC, AMX, etc...); RoomRPH is the is the sabre reference place holder of the room. RPH is gotten from a previous request for room rates through RatesHotelIDHandler. type BookRoomParams struct { - FirstName string `form:"first_name"` - LastName string `form:"last_name"` - NumRooms string `form:"num_rooms"` - CCPhone string `form:"cc_phone"` - CCCode string `form:"cc_code"` - CCExpire string `form:"cc_expire"` - CCNumber string `form:"cc_number"` - RoomMeta string `form:"room_meta"` + FirstName string `form:"first_name"` + LastName string `form:"last_name"` + NumRooms string `form:"num_rooms"` + CCPhone string `form:"cc_phone"` + CCCode string `form:"cc_code"` + CCExpire string `form:"cc_expire"` + CCNumber string `form:"cc_number"` + RoomMeta string `form:"room_meta"` + ParsedRoomMeta hotelws.ParsedRoomMeta } // AvailHotelIDSResponse for sabre hotel availability @@ -59,6 +59,12 @@ type PropertyHotelIDResponse struct { RoomStay hotelws.RoomStay } +// PropertyHotelIDResponse for sabre property description +type BookHotelResResponse struct { + RequestParams BookRoomParams + SabreEngineErrors interface{} `json:",omitempty"` +} + // Validate AvailParamsBase fields. Time date formats arrive/depart are using app timezone location aware validations and set the outgoing arrive/depart formats for sabre. Integer guest_count checks against min/max. func (b *HotelParamsBase) ValidateAndFormat(loc *time.Location) error { //check for null or empty values first @@ -147,7 +153,7 @@ func (b BookRoomParams) Validate() error { } /* - BookRoomHandler creates a pnr, fetches rate, books room, ends transaction. It accepts room meta data generated from previous requests. Required params: last_name, num_rooms, rph, cc_phone, cc_code, cc_expire, cc_number + BookRoomHandler creates a pnr, fetches rate, books room, ends transaction. It accepts room meta data generated from previous requests. Required params: last_name, num_rooms, room_meta, cc_phone, cc_code, cc_expire, cc_number curl -H "Accept: application/json" -X GET 'http://localhost:8080/book/hotel/room?' */ @@ -157,36 +163,44 @@ func (s *Server) BookRoomHandler() http.HandlerFunc { w.Header().Set("Content-Type", "application/json") params := &BookRoomParams{} decoder := form.NewDecoder() - + response := BookHotelResResponse{} // decode params, check errors if err := decoder.Decode(¶ms, r.URL.Query()); err != nil { w.WriteHeader(http.StatusBadRequest) w.Write(apperr.DecodeBadInput("BookRoomHandler", r.URL.Query(), err, http.StatusBadRequest)) return } - // validate query params if err := params.Validate(); err != nil { w.WriteHeader(http.StatusUnprocessableEntity) w.Write(apperr.DecodeInvalid("BookRoomHandler", err, http.StatusUnprocessableEntity)) return } - - //PNR - //build person - person := itin.CreatePersonName(params.FirstName, params.LastName) - //build pnr - pnrBody := itin.SetPNRDetailBody(params.CCPhone, person) - pnrReq := itin.BuildPNRDetailsRequest(s.SConfig, pnrBody) - //call pnr - pnrResp, err := itin.CallPNRDetail(s.SConfig.ServiceURL, pnrReq) + prm, err := hotelws.NewParsedRoomMeta(params.RoomMeta) if err != nil { - w.WriteHeader(http.StatusFailedDependency) - w.Write(apperr.DecodeUnknown("CallPNRDetail::BookRoomHandler", r.URL.Query(), err, http.StatusFailedDependency)) + w.WriteHeader(http.StatusUnprocessableEntity) + w.Write(apperr.DecodeInvalid("BookRoomHandler::RoomMetaData", err, http.StatusUnprocessableEntity)) return } + params.ParsedRoomMeta = prm + response.RequestParams = *params + /* + //PNR + //build person + person := itin.CreatePersonName(params.FirstName, params.LastName) + //build pnr + pnrBody := itin.SetPNRDetailBody(params.CCPhone, person) + pnrReq := itin.BuildPNRDetailsRequest(s.SConfig, pnrBody) + //call pnr + pnrResp, err := itin.CallPNRDetail(s.SConfig.ServiceURL, pnrReq) + if err != nil { + w.WriteHeader(http.StatusFailedDependency) + w.Write(apperr.DecodeUnknown("CallPNRDetail::BookRoomHandler", r.URL.Query(), err, http.StatusFailedDependency)) + return + } - json.NewEncoder(w).Encode(pnrResp) + json.NewEncoder(w).Encode(pnrResp) + */ //Hotel Reservation /* @@ -203,9 +217,9 @@ func (s *Server) BookRoomHandler() http.HandlerFunc { if err != nil { return err } - - call.SetTrackedEncode() */ + + json.NewEncoder(w).Encode(response) } } diff --git a/engine/hotelws/hotel_prop_desc_test.go b/engine/hotelws/hotel_prop_desc_test.go index 8d334c3..79356b1 100644 --- a/engine/hotelws/hotel_prop_desc_test.go +++ b/engine/hotelws/hotel_prop_desc_test.go @@ -2,6 +2,7 @@ package hotelws import ( "encoding/xml" + "errors" "testing" "github.com/ailgroup/sbrweb/engine/sbrerr" @@ -150,6 +151,15 @@ func TestPropDescCall(t *testing.T) { if err != nil { t.Error("Error making request CallHotelProperty", err) } + + if !resp.Body.HotelDesc.Result.Ok() { + t.Error("CallHotelPropDesc Ok should be true") + } + sabreErrFmt := resp.Body.HotelDesc.Result.ErrFormat() + if sabreErrFmt.Code.String() != "Complete" { + t.Errorf("CallHotelPropDesc code expected %s, got %s", "Complete", sabreErrFmt.Code.String()) + } + if resp.Body.Fault.String != "" { t.Errorf("Body.Fault.String expect empty: '%s', got: %s", "", resp.Body.Fault.String) } @@ -203,7 +213,19 @@ func TestPropDescCall(t *testing.T) { } } -//[]string{"hc:HOD4/11MAY-12MAY2", "rph:002", "rmt:D1KRAC", "rtx:0-ttl:190.45-nxt:false"} +func TestNewParsedRoomMeta(t *testing.T) { + //NotUrlSafeString := "some data with \x00 and \ufeff" + //NotUrlSafeStringExpected := "some data with and " + errExpect := errors.New("illegal base64 data at input byte 31") + b64NotUrlSafe := "c29tZSBkYXRhIHdpdGggACBhbmQg77u/" + _, err := NewParsedRoomMeta(b64NotUrlSafe) + if err == nil { + t.Errorf("NewParsedRoomMeta expected error") + } + if err.Error() != errExpect.Error() { + t.Errorf("NewParsedRoomMeta expected error %v, got %v", errExpect, err) + } +} var proptrack = []struct { b64str string @@ -269,7 +291,7 @@ func TestSetRoomMetaDataPropDesc(t *testing.T) { if rate.B64RoomMetaData != proptrack[i].b64str { t.Errorf("B64RoomMetaData expect: '%s', got '%s'", proptrack[i].b64str, rate.B64RoomMetaData) } - prm, err := rate.NewParsedRoomMeta() + prm, err := NewParsedRoomMeta(rate.B64RoomMetaData) if err != nil { t.Errorf("Error on DecodeTrackedEncoding() %v", err) } @@ -319,6 +341,14 @@ func TestHotelPropDescCallDown(t *testing.T) { if err == nil { t.Error("Expected error making request to serverHotelDown") } + if !resp.Body.HotelDesc.Result.Ok() { + t.Error("CallHotelPropDesc Ok should be true") + } + sabreErrFmt := resp.Body.HotelDesc.Result.ErrFormat() + if sabreErrFmt.Code.String() != "Unknown" { + t.Errorf("CallHotelPropDesc code expected %s, got %s", "Unknown", sabreErrFmt.Code.String()) + } + if err.Error() != resp.ErrorSabreService.ErrMessage { t.Error("Error() message should match resp.ErrorSabreService.ErrMessage") } @@ -342,6 +372,13 @@ func TestHotelPropDescCallBadResponseBody(t *testing.T) { if err == nil { t.Error("Expected error making request to sserverBadBody") } + if !resp.Body.HotelDesc.Result.Ok() { + t.Error("CallHotelPropDesc Ok should be true") + } + sabreErrFmt := resp.Body.HotelDesc.Result.ErrFormat() + if sabreErrFmt.Code.String() != "Unknown" { + t.Errorf("CallHotelPropDesc code expected %s, got %s", "Unknown", sabreErrFmt.Code.String()) + } if err.Error() != resp.ErrorSabreXML.ErrMessage { t.Error("Error() message should match resp.ErrorSabreService.ErrMessage") } diff --git a/engine/hotelws/hotelws.go b/engine/hotelws/hotelws.go index 321b477..d3468f4 100644 --- a/engine/hotelws/hotelws.go +++ b/engine/hotelws/hotelws.go @@ -184,9 +184,9 @@ func (p *ParsedRoomMeta) parseB64DecodeRates() { } // NewParsedRoomMeta builds a strcut from parsing b64 encoded string cache of previous rate request. See SetRoomMetaData for how this data is constucted. -func (r *RoomRate) NewParsedRoomMeta() (ParsedRoomMeta, error) { +func NewParsedRoomMeta(b64Str string) (ParsedRoomMeta, error) { rmp := ParsedRoomMeta{} - b64Str, err := B64Dec(r.B64RoomMetaData) + b64Str, err := B64Dec(b64Str) if err != nil { return rmp, err } diff --git a/engine/hotelws/hotelws_test.go b/engine/hotelws/hotelws_test.go index 3570d6b..daadfa6 100644 --- a/engine/hotelws/hotelws_test.go +++ b/engine/hotelws/hotelws_test.go @@ -1,8 +1,11 @@ package hotelws -import "testing" +import ( + "errors" + "testing" +) -var dirtyStrings = []struct { +var dirtyStringsSample = []struct { dirty string clean string }{ @@ -11,10 +14,54 @@ var dirtyStrings = []struct { } func TestSanatizeString(t *testing.T) { - for _, ds := range dirtyStrings { + for _, ds := range dirtyStringsSample { clean := sanatize(ds.dirty) if clean != ds.clean { t.Errorf("expected %s, got %s", ds.clean, clean) } } } + +var b64EncodeStringsSample = []struct { + in string + enc string +}{ + {"hello smiley face", "aGVsbG8gc21pbGV5IGZhY2U="}, + {"1234 {\"this\"} and-/that", "MTIzNCB7InRoaXMifSBhbmQtL3RoYXQ="}, +} + +func TestB64Enc(t *testing.T) { + for idx, sample := range b64EncodeStringsSample { + encoded := B64Enc(sample.in) + if encoded != sample.enc { + t.Errorf("for sample %d: expected %s, got %s", idx, sample.enc, encoded) + } + } +} + +var b64DecodeStringsSample = []struct { + in string + dec string + err error +}{ + {"", "", nil}, + {"MTIzNCB7InRoaXMifSBhbmQtL3RoYXQ=", "1234 {\"this\"} and-/that", nil}, + {"aGVsbG8gc21pbGV5IGZhY2U=", "hello smiley face", nil}, + //not url safe! + {"c29tZSBkYXRhIHdpdGggACBhbmQg77u/", "some data with \x00 and \ufeff", errors.New("illegal base64 data at input byte 31")}, +} + +func TestB64Dec(t *testing.T) { + for idx, sample := range b64DecodeStringsSample { + decoded, err := B64Dec(sample.in) + if err != nil { + if err.Error() != sample.err.Error() { + t.Errorf("for sample %d: expected %s, got %s", idx, sample.err, err) + } + continue + } + if decoded != sample.dec { + t.Errorf("for sample %d: expected %s, got %s", idx, sample.dec, decoded) + } + } +} From 499d4f38d5cef28c167e252992715f74e9075902 Mon Sep 17 00:00:00 2001 From: Joshua Bowles Date: Fri, 2 Nov 2018 15:10:31 -0600 Subject: [PATCH 21/21] update a few function doc --- client/app/routes.go | 2 +- engine/hotelws/hotelws.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/app/routes.go b/client/app/routes.go index a4e9b1c..5fa7345 100644 --- a/client/app/routes.go +++ b/client/app/routes.go @@ -4,7 +4,7 @@ import ( "net/http" ) -// registerRoutes is responsible for registering the server-side request handlers +// RegisterRoutes is responsible for registering the server-side request handlers func (s *Server) RegisterRoutes() { s.Mux.Get("/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(`{"ping":"p0ng"}`)) }) s.Mux.Handle("/hotel/ids", s.HotelIDsHandler()) diff --git a/engine/hotelws/hotelws.go b/engine/hotelws/hotelws.go index d3468f4..41bba6e 100644 --- a/engine/hotelws/hotelws.go +++ b/engine/hotelws/hotelws.go @@ -183,7 +183,7 @@ func (p *ParsedRoomMeta) parseB64DecodeRates() { } } -// NewParsedRoomMeta builds a strcut from parsing b64 encoded string cache of previous rate request. See SetRoomMetaData for how this data is constucted. +// NewParsedRoomMeta builds a struct from parsing b64 encoded string cache of previous rate request. See SetRoomMetaData for how this data is constucted. func NewParsedRoomMeta(b64Str string) (ParsedRoomMeta, error) { rmp := ParsedRoomMeta{} b64Str, err := B64Dec(b64Str)