From 5d2d34c9dec059e58a5c34b4de032c88cf2e9cac Mon Sep 17 00:00:00 2001 From: Evgeniy Fedorov Date: Thu, 24 Aug 2023 10:52:55 +0300 Subject: [PATCH 1/5] Covered 5 functions/methods with tests. --- actions/createOrder.go | 11 +++--- actions/createOrder_test.go | 37 ++++++++++++++++++++ actions/retrieveOrder.go | 12 ++++--- actions/retrieveOrder_test.go | 53 ++++++++++++++++++++++++++++ actions/transferToCheckout_test.go | 21 +++++++++++ cmd/{ => fixtures}/fixtures.go | 2 +- cmd/{ => migrations}/migrations.go | 2 +- cmd/{ => webserver}/webserver.go | 8 ++--- helpers/sqlNullConvert_test.go | 16 +++++++++ model/read/order.go | 4 +++ rest/retrieveOrder.go | 23 +++++++----- rest/retrieveOrder_test.go | 56 ++++++++++++++++++++++++++++++ 12 files changed, 223 insertions(+), 22 deletions(-) create mode 100644 actions/createOrder_test.go create mode 100644 actions/retrieveOrder_test.go create mode 100644 actions/transferToCheckout_test.go rename cmd/{ => fixtures}/fixtures.go (98%) rename cmd/{ => migrations}/migrations.go (96%) rename cmd/{ => webserver}/webserver.go (95%) create mode 100644 helpers/sqlNullConvert_test.go create mode 100644 rest/retrieveOrder_test.go diff --git a/actions/createOrder.go b/actions/createOrder.go index 43d2756..8ed4e6a 100644 --- a/actions/createOrder.go +++ b/actions/createOrder.go @@ -9,15 +9,18 @@ import ( // NewOrderRequest. type NewOrderRequest struct{} +// Save order function. +type SaveOrder func(order *write.Order) error + // CreateOrder action. type CreateOrder struct { - repo write.IOrderSaver + saveOrder SaveOrder } // Constructor. -func NewCreateOrder(repo write.IOrderSaver) *CreateOrder { +func NewCreateOrder(saveOrder SaveOrder) *CreateOrder { return &CreateOrder{ - repo: repo, + saveOrder: saveOrder, } } @@ -28,7 +31,7 @@ func (action *CreateOrder) Create(r NewOrderRequest) (*write.Order, error) { CreatedAt: time.Now(), } - err := action.repo.Save(order) + err := action.saveOrder(order) if err != nil { return nil, err diff --git a/actions/createOrder_test.go b/actions/createOrder_test.go new file mode 100644 index 0000000..22bee11 --- /dev/null +++ b/actions/createOrder_test.go @@ -0,0 +1,37 @@ +package actions_test + +import ( + // "orders/actions" + "errors" + "orders/actions" + "orders/model/write" + "testing" +) + +func Test_CreateOrder_Success(t *testing.T) { + // SaveOrder function type mock. + saveOrder:=func(order *write.Order) error { + return nil + } + + action:=actions.NewCreateOrder(saveOrder) + _,err:=action.Create(actions.NewOrderRequest{}) + + if err!=nil{ + t.Logf("Cannot create new order") + } +} + +func Test_CreateOrder_Failure(t *testing.T) { + // SaveOrder function type mock. + saveOrder:=func(order *write.Order) error { + return errors.New("could not save order") + } + + action:=actions.NewCreateOrder(saveOrder) + _,err:=action.Create(actions.NewOrderRequest{}) + + if err==nil{ + t.Logf("save order error not handled") + } +} diff --git a/actions/retrieveOrder.go b/actions/retrieveOrder.go index 7879fa1..84abe5c 100644 --- a/actions/retrieveOrder.go +++ b/actions/retrieveOrder.go @@ -1,26 +1,30 @@ package actions import ( + "errors" "orders/model/read" ) // RetrieveOrder action. type RetrieveOrder struct { - finder read.OrderFinderById + findOrder read.FindOrder } // Constructor. -func NewRetrieveOrder(finder read.OrderFinderById) *RetrieveOrder { +func NewRetrieveOrder(findOrder read.FindOrder) *RetrieveOrder { return &RetrieveOrder{ - finder: finder, + findOrder: findOrder, } } func (action *RetrieveOrder) Retrieve(uuid string) (*read.Order, error) { - order, err := action.finder.Find(uuid) + order, err := action.findOrder(uuid) if err != nil { return nil, err } + if order == nil { + return nil, errors.New("order is nil") + } return order, nil } diff --git a/actions/retrieveOrder_test.go b/actions/retrieveOrder_test.go new file mode 100644 index 0000000..3e45d86 --- /dev/null +++ b/actions/retrieveOrder_test.go @@ -0,0 +1,53 @@ +package actions_test + +import ( + "errors" + "orders/actions" + "orders/model/read" + "reflect" + "testing" +) + +func Test_RetrieveOrder(t *testing.T) { + order := &read.Order{} + + tests := []struct { + testName string + findOrder read.FindOrder + orderResult *read.Order + errResult error + }{ + {"success", + func(uuid string) (*read.Order, error) { + return order, nil + }, + order, + nil, + }, + {"failure - order finder error", + func(uuid string) (*read.Order, error) { + return nil, errors.New("order not found") + }, + nil, + errors.New("order not found"), + }, + {"failure - order is nil", + func(uuid string) (*read.Order, error) { + return nil, nil + }, + nil, + errors.New("order is nil"), + }, + } + + // Loop through each test case + for _, tt := range tests { + t.Run(tt.testName, func(t *testing.T) { + action := actions.NewRetrieveOrder(tt.findOrder) + order, err := action.Retrieve("someuuid") + if order != tt.orderResult || reflect.TypeOf(err) != reflect.TypeOf(tt.errResult) { + t.Errorf(tt.testName, order, err, tt.orderResult, tt.errResult) + } + }) + } +} diff --git a/actions/transferToCheckout_test.go b/actions/transferToCheckout_test.go new file mode 100644 index 0000000..8ee7ca1 --- /dev/null +++ b/actions/transferToCheckout_test.go @@ -0,0 +1,21 @@ +package actions_test + +import ( + "database/sql" + "orders/actions" + "orders/model/read" + "strconv" + "testing" +) + +func Test_TransferToCheckout(t *testing.T) { + order := &read.Order{Uuid: sql.NullString{String: "someuuid"}, Total: 100} + url := "http://checkout.url" + action := actions.NewCheckoutTransfer(url) + got := action.Url(order) + + wanted := url + "?cart=" + order.Uuid.String + "&total=" + strconv.Itoa(order.Total) + if got != wanted { + t.Fatalf("wanted %s, got %s", wanted, got) + } +} diff --git a/cmd/fixtures.go b/cmd/fixtures/fixtures.go similarity index 98% rename from cmd/fixtures.go rename to cmd/fixtures/fixtures.go index 9e363c9..da99b4a 100644 --- a/cmd/fixtures.go +++ b/cmd/fixtures/fixtures.go @@ -1,4 +1,4 @@ -package main +package fixtures import ( "github.com/joho/godotenv" diff --git a/cmd/migrations.go b/cmd/migrations/migrations.go similarity index 96% rename from cmd/migrations.go rename to cmd/migrations/migrations.go index 128045f..fb5995a 100644 --- a/cmd/migrations.go +++ b/cmd/migrations/migrations.go @@ -1,4 +1,4 @@ -package main +package migrations import ( "github.com/joho/godotenv" diff --git a/cmd/webserver.go b/cmd/webserver/webserver.go similarity index 95% rename from cmd/webserver.go rename to cmd/webserver/webserver.go index fa4b349..86a5908 100644 --- a/cmd/webserver.go +++ b/cmd/webserver/webserver.go @@ -1,4 +1,4 @@ -package main +package webserver import ( "database/sql" @@ -50,11 +50,11 @@ func main() { orderActiveFinder := read.NewOrderFinderActiveById(mysqlDb) // Actions. - retrieveOrderAction := actions.NewRetrieveOrder(read.NewOrderFinderById(mysqlDb, read.NewOrderItemFinderById(mysqlDb))) + retrieveOrderAction := actions.NewRetrieveOrder(read.NewOrderFinderById(mysqlDb, read.NewOrderItemFinderById(mysqlDb)).Find) // Controllers. - createOrderCntrlr := rest.NewCreateOrder(actions.NewCreateOrder(repo), respndr) - retrieveOrderCntrlr := rest.NewRetrieveOrder(retrieveOrderAction, respndr) + createOrderCntrlr := rest.NewCreateOrder(actions.NewCreateOrder(repo.Save), respndr) + retrieveOrderCntrlr := rest.NewRetrieveOrder(retrieveOrderAction.Retrieve, respndr) modifyOrderCntrlr := rest.NewDeleteProduct(actions.NewProductDeleter(orderModifier, orderItemFinder), respndr) addProductCntrl := rest.NewAddProduct(actions.NewProductAdder(orderModifier, orderActiveFinder), respndr) checkoutCntrlr := rest.NewCheckoutTransfer(actions.NewCheckoutTransfer("http://checkout.url"), retrieveOrderAction, respndr) diff --git a/helpers/sqlNullConvert_test.go b/helpers/sqlNullConvert_test.go new file mode 100644 index 0000000..d0fc2f5 --- /dev/null +++ b/helpers/sqlNullConvert_test.go @@ -0,0 +1,16 @@ +package helpers_test + +import ( + "database/sql" + "orders/helpers" + "testing" +) + +func Test_SqlNullConvert(t *testing.T) { + want := "a string" + nlstr := sql.NullString{String: want, Valid: true} + got := helpers.NullStringToString(nlstr) + if want != got { + t.Fatalf("%s != %s", want, got) + } +} diff --git a/model/read/order.go b/model/read/order.go index 3e11338..3e6211c 100644 --- a/model/read/order.go +++ b/model/read/order.go @@ -16,6 +16,9 @@ type Order struct { CreatedAt sql.NullTime } +// FindOrder repository function type. +type FindOrder func(uuid string) (*Order, error) +// Same as above, but in interface type OrderFinderById interface { Find(uuid string) (*Order, error) } @@ -113,3 +116,4 @@ AND uuid = ?; return &order, nil } + diff --git a/rest/retrieveOrder.go b/rest/retrieveOrder.go index 44e7271..5c33d5e 100644 --- a/rest/retrieveOrder.go +++ b/rest/retrieveOrder.go @@ -1,22 +1,23 @@ package rest import ( - "github.com/gorilla/mux" "net/http" - "orders/actions" + "orders/model/read" + + "github.com/gorilla/mux" ) // Create order controller. type RetrieveOrder struct { - action *actions.RetrieveOrder - rspndr *Responder + findOrder read.FindOrder + rspndr *Responder } // Constructor. -func NewRetrieveOrder(action *actions.RetrieveOrder, rspndr *Responder) *RetrieveOrder { +func NewRetrieveOrder(findOrder read.FindOrder, rspndr *Responder) *RetrieveOrder { return &RetrieveOrder{ - action: action, - rspndr: rspndr, + findOrder: findOrder, + rspndr: rspndr, } } @@ -24,10 +25,16 @@ func (c *RetrieveOrder) Retrieve(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) uuid := vars["uuid"] - order, err := c.action.Retrieve(uuid) + order, err := c.findOrder(uuid) if err != nil { c.rspndr.Error(w, http.StatusInternalServerError, err.Error()) + return + } + + if order == nil { + c.rspndr.Error(w, http.StatusInternalServerError, "order is nil") + return } c.rspndr.Success(w, order) diff --git a/rest/retrieveOrder_test.go b/rest/retrieveOrder_test.go new file mode 100644 index 0000000..6a68ef4 --- /dev/null +++ b/rest/retrieveOrder_test.go @@ -0,0 +1,56 @@ +package rest_test + +import ( + "errors" + "net/http" + "net/http/httptest" + "orders/model/read" + "orders/rest" + "testing" +) + +func Test_RetrieveOrder(t *testing.T) { + + tests := []struct { + name string + findOrder read.FindOrder + wantHttpStatus int + }{ + {"success", + func(uuid string) (*read.Order, error) { + return &read.Order{}, nil + }, + http.StatusOK, + }, + {"failure - order finder error", + func(uuid string) (*read.Order, error) { + return nil, errors.New("order not found") + }, + http.StatusInternalServerError, + }, + {"failure - order is nil", + func(uuid string) (*read.Order, error) { + return nil, nil + }, + http.StatusInternalServerError, + }, + } + + // Loop through each test case + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Init http testing server. + r := httptest.NewRequest(http.MethodGet, "http://localhost", nil) + w := httptest.NewRecorder() + // Init our controller. + rspndr := rest.NewResponder("2006-01-02 15:04:05") + cntrlr := rest.NewRetrieveOrder(tt.findOrder, rspndr) + cntrlr.Retrieve(w, r) + + gotHttpStatus := w.Result().StatusCode + if gotHttpStatus != tt.wantHttpStatus { + t.Errorf(tt.name, gotHttpStatus, tt.wantHttpStatus) + } + }) + } +} From 453b2ab5c0e392e30a438fd8b519d82afcd0f5a1 Mon Sep 17 00:00:00 2001 From: Evgeniy Fedorov Date: Sun, 27 Aug 2023 13:06:31 +0300 Subject: [PATCH 2/5] Updated todos --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index f84904e..75348b6 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ Prjctr Go course. Mock order service project. - confirms order payment from payment gateway - "/order/{uuid}/payment/{paymentUuid}" - DONE! - provides order status (as requested from user service, product service, others) - "/retrieve/{uuid}" - DONE! +- investigage using GORM raw url for read model retrieveal (null values?) +- implement html ui with htmx + ## Technical implementation - REST API - uses repository pattern for entity persistence From 03db256c3d4c227596f26c2df231d945fedee197 Mon Sep 17 00:00:00 2001 From: Evgeniy Fedorov Date: Wed, 30 Aug 2023 20:41:10 +0300 Subject: [PATCH 3/5] Updated readme. --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 75348b6..1cee1c9 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ Prjctr Go course. Mock order service project. - confirms order payment from payment gateway - "/order/{uuid}/payment/{paymentUuid}" - DONE! - provides order status (as requested from user service, product service, others) - "/retrieve/{uuid}" - DONE! +- implement DI with Wire (function type injection, ftw) - investigage using GORM raw url for read model retrieveal (null values?) - implement html ui with htmx From e6ba5ff62cfa39ca966cc7fb3141a1c49732aceb Mon Sep 17 00:00:00 2001 From: Evgeniy Fedorov Date: Wed, 30 Aug 2023 22:34:05 +0300 Subject: [PATCH 4/5] Updated TODO list --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1cee1c9..2dc3a48 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,11 @@ Prjctr Go course. Mock order service project. - confirms order payment from payment gateway - "/order/{uuid}/payment/{paymentUuid}" - DONE! - provides order status (as requested from user service, product service, others) - "/retrieve/{uuid}" - DONE! +## TODO - implement DI with Wire (function type injection, ftw) - investigage using GORM raw url for read model retrieveal (null values?) - implement html ui with htmx +- use type receivers (read-only) for write model ## Technical implementation - REST API From 5a8a3c7919a672c57b780cd27d6e0c30d28a550e Mon Sep 17 00:00:00 2001 From: Evgeniy Fedorov Date: Wed, 6 Sep 2023 11:15:31 +0300 Subject: [PATCH 5/5] Updated README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 2dc3a48..cf4c811 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Prjctr Go course. Mock order service project. - investigage using GORM raw url for read model retrieveal (null values?) - implement html ui with htmx - use type receivers (read-only) for write model +- implement backend data admin (https://go-admin.com, https://github.com/LyricTian/gin-admin) ## Technical implementation - REST API