feat: implement order workflow, metrics, and handler unit tests
- implement UpdateOrderStatus and GetOrdersReport service methods - implement all order, user handlers (UpdateMe, GetMyTrips, CancelOrder, GetOrder, etc.) - extract OrderServicer interface for testability - add Prometheus metrics middleware (requests total, duration) - fix GetAll storage flavor for PostgreSQL ($1 placeholders) - add 17 unit tests for order handlers via httptest - wire Grafana datasource and update Prometheus scrape config - update README with full API reference, pricing, roles, monitoring
This commit is contained in:
431
tests/order_handler_test.go
Normal file
431
tests/order_handler_test.go
Normal file
@@ -0,0 +1,431 @@
|
||||
package tests
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/anxi0uz/logiflow/internal/api"
|
||||
"github.com/anxi0uz/logiflow/internal/handler"
|
||||
"github.com/anxi0uz/logiflow/internal/models"
|
||||
"github.com/anxi0uz/logiflow/internal/services"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// --- Mock ---
|
||||
|
||||
type mockOrderService struct {
|
||||
createOrder func(ctx context.Context, req api.OrderCreate, userID uuid.UUID) (*services.CreateOrderResult, error)
|
||||
listOrders func(ctx context.Context, userID uuid.UUID, role string, params api.ListOrdersParams) ([]models.Order, error)
|
||||
getOrder func(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string) (*models.Order, error)
|
||||
cancelOrder func(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string) error
|
||||
updateOrderStatus func(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string, req api.OrderStatusUpdate) (*models.Order, error)
|
||||
getOrdersReport func(ctx context.Context, role string, params api.GetOrdersReportParams) ([]models.Order, error)
|
||||
}
|
||||
|
||||
func (m *mockOrderService) CreateOrder(ctx context.Context, req api.OrderCreate, userID uuid.UUID) (*services.CreateOrderResult, error) {
|
||||
return m.createOrder(ctx, req, userID)
|
||||
}
|
||||
func (m *mockOrderService) ListOrders(ctx context.Context, userID uuid.UUID, role string, params api.ListOrdersParams) ([]models.Order, error) {
|
||||
return m.listOrders(ctx, userID, role, params)
|
||||
}
|
||||
func (m *mockOrderService) GetOrder(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string) (*models.Order, error) {
|
||||
return m.getOrder(ctx, id, userID, role)
|
||||
}
|
||||
func (m *mockOrderService) CancelOrder(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string) error {
|
||||
return m.cancelOrder(ctx, id, userID, role)
|
||||
}
|
||||
func (m *mockOrderService) UpdateOrderStatus(ctx context.Context, id uuid.UUID, userID uuid.UUID, role string, req api.OrderStatusUpdate) (*models.Order, error) {
|
||||
return m.updateOrderStatus(ctx, id, userID, role, req)
|
||||
}
|
||||
func (m *mockOrderService) GetOrdersReport(ctx context.Context, role string, params api.GetOrdersReportParams) ([]models.Order, error) {
|
||||
return m.getOrdersReport(ctx, role, params)
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
func newTestServer(svc services.OrderServicer) *handler.Server {
|
||||
return &handler.Server{
|
||||
OrderSerice: svc,
|
||||
}
|
||||
}
|
||||
|
||||
func withClaims(r *http.Request, id uuid.UUID, role string) *http.Request {
|
||||
claims := &handler.Claims{ID: id, Role: role}
|
||||
ctx := context.WithValue(r.Context(), "user", claims)
|
||||
return r.WithContext(ctx)
|
||||
}
|
||||
|
||||
func jsonBody(t *testing.T, v any) *bytes.Buffer {
|
||||
t.Helper()
|
||||
b, err := json.Marshal(v)
|
||||
if err != nil {
|
||||
t.Fatalf("marshal body: %v", err)
|
||||
}
|
||||
return bytes.NewBuffer(b)
|
||||
}
|
||||
|
||||
// --- CreateOrder ---
|
||||
|
||||
func TestCreateOrder_Success(t *testing.T) {
|
||||
orderID := uuid.New()
|
||||
svc := &mockOrderService{
|
||||
createOrder: func(_ context.Context, _ api.OrderCreate, _ uuid.UUID) (*services.CreateOrderResult, error) {
|
||||
return &services.CreateOrderResult{
|
||||
Order: models.Order{ID: orderID, Status: "pending"},
|
||||
Route: models.Route{ID: uuid.New(), OrderID: orderID},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
r := httptest.NewRequest(http.MethodPost, "/orders", jsonBody(t, api.OrderCreate{DestinationAddress: "Moscow"}))
|
||||
r = withClaims(r, uuid.New(), "client")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.CreateOrder(w, r)
|
||||
|
||||
if w.Code != http.StatusCreated {
|
||||
t.Errorf("expected 201, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateOrder_InvalidBody(t *testing.T) {
|
||||
s := newTestServer(&mockOrderService{})
|
||||
r := httptest.NewRequest(http.MethodPost, "/orders", bytes.NewBufferString("not json"))
|
||||
r = withClaims(r, uuid.New(), "client")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.CreateOrder(w, r)
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateOrder_ServiceError(t *testing.T) {
|
||||
svc := &mockOrderService{
|
||||
createOrder: func(_ context.Context, _ api.OrderCreate, _ uuid.UUID) (*services.CreateOrderResult, error) {
|
||||
return nil, errors.New("geocode failed")
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
r := httptest.NewRequest(http.MethodPost, "/orders", jsonBody(t, api.OrderCreate{DestinationAddress: "Moscow"}))
|
||||
r = withClaims(r, uuid.New(), "client")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.CreateOrder(w, r)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Errorf("expected 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// --- ListOrders ---
|
||||
|
||||
func TestListOrders_Success(t *testing.T) {
|
||||
svc := &mockOrderService{
|
||||
listOrders: func(_ context.Context, _ uuid.UUID, _ string, _ api.ListOrdersParams) ([]models.Order, error) {
|
||||
return []models.Order{{ID: uuid.New(), Status: "pending"}}, nil
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
r := httptest.NewRequest(http.MethodGet, "/orders", nil)
|
||||
r = withClaims(r, uuid.New(), "client")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.ListOrders(w, r, api.ListOrdersParams{})
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListOrders_ServiceError(t *testing.T) {
|
||||
svc := &mockOrderService{
|
||||
listOrders: func(_ context.Context, _ uuid.UUID, _ string, _ api.ListOrdersParams) ([]models.Order, error) {
|
||||
return nil, errors.New("db error")
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
r := httptest.NewRequest(http.MethodGet, "/orders", nil)
|
||||
r = withClaims(r, uuid.New(), "manager")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.ListOrders(w, r, api.ListOrdersParams{})
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Errorf("expected 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// --- GetOrder ---
|
||||
|
||||
func TestGetOrder_Success(t *testing.T) {
|
||||
orderID := uuid.New()
|
||||
svc := &mockOrderService{
|
||||
getOrder: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string) (*models.Order, error) {
|
||||
return &models.Order{ID: orderID, Status: "pending"}, nil
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
r := httptest.NewRequest(http.MethodGet, "/orders/"+orderID.String(), nil)
|
||||
r = withClaims(r, uuid.New(), "client")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.GetOrder(w, r, orderID)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrder_Forbidden(t *testing.T) {
|
||||
orderID := uuid.New()
|
||||
svc := &mockOrderService{
|
||||
getOrder: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string) (*models.Order, error) {
|
||||
return nil, services.ErrForbidden
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
r := httptest.NewRequest(http.MethodGet, "/orders/"+orderID.String(), nil)
|
||||
r = withClaims(r, uuid.New(), "client")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.GetOrder(w, r, orderID)
|
||||
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Errorf("expected 403, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrder_ServiceError(t *testing.T) {
|
||||
orderID := uuid.New()
|
||||
svc := &mockOrderService{
|
||||
getOrder: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string) (*models.Order, error) {
|
||||
return nil, errors.New("db error")
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
r := httptest.NewRequest(http.MethodGet, "/orders/"+orderID.String(), nil)
|
||||
r = withClaims(r, uuid.New(), "manager")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.GetOrder(w, r, orderID)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Errorf("expected 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// --- CancelOrder ---
|
||||
|
||||
func TestCancelOrder_Success(t *testing.T) {
|
||||
orderID := uuid.New()
|
||||
svc := &mockOrderService{
|
||||
cancelOrder: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string) error {
|
||||
return nil
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
r := httptest.NewRequest(http.MethodDelete, "/orders/"+orderID.String()+"/cancel", nil)
|
||||
r = withClaims(r, uuid.New(), "client")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.CancelOrder(w, r, orderID)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelOrder_CannotCancel(t *testing.T) {
|
||||
orderID := uuid.New()
|
||||
svc := &mockOrderService{
|
||||
cancelOrder: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string) error {
|
||||
return services.ErrCannotCancel
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
r := httptest.NewRequest(http.MethodDelete, "/orders/"+orderID.String()+"/cancel", nil)
|
||||
r = withClaims(r, uuid.New(), "client")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.CancelOrder(w, r, orderID)
|
||||
|
||||
if w.Code != http.StatusConflict {
|
||||
t.Errorf("expected 409, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelOrder_Forbidden(t *testing.T) {
|
||||
orderID := uuid.New()
|
||||
svc := &mockOrderService{
|
||||
cancelOrder: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string) error {
|
||||
return services.ErrForbidden
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
r := httptest.NewRequest(http.MethodDelete, "/orders/"+orderID.String()+"/cancel", nil)
|
||||
r = withClaims(r, uuid.New(), "client")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.CancelOrder(w, r, orderID)
|
||||
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Errorf("expected 403, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCancelOrder_ServiceError(t *testing.T) {
|
||||
orderID := uuid.New()
|
||||
svc := &mockOrderService{
|
||||
cancelOrder: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string) error {
|
||||
return errors.New("db error")
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
r := httptest.NewRequest(http.MethodDelete, "/orders/"+orderID.String()+"/cancel", nil)
|
||||
r = withClaims(r, uuid.New(), "client")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.CancelOrder(w, r, orderID)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Errorf("expected 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// --- UpdateOrderStatus ---
|
||||
|
||||
func TestUpdateOrderStatus_Success(t *testing.T) {
|
||||
orderID := uuid.New()
|
||||
svc := &mockOrderService{
|
||||
updateOrderStatus: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string, _ api.OrderStatusUpdate) (*models.Order, error) {
|
||||
return &models.Order{ID: orderID, Status: "assigned"}, nil
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
body := jsonBody(t, api.OrderStatusUpdate{Status: api.OrderStatusUpdateStatusAssigned})
|
||||
r := httptest.NewRequest(http.MethodPatch, "/orders/"+orderID.String()+"/status", body)
|
||||
r = withClaims(r, uuid.New(), "manager")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.UpdateOrderStatus(w, r, orderID)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateOrderStatus_Forbidden(t *testing.T) {
|
||||
orderID := uuid.New()
|
||||
svc := &mockOrderService{
|
||||
updateOrderStatus: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string, _ api.OrderStatusUpdate) (*models.Order, error) {
|
||||
return nil, services.ErrForbidden
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
body := jsonBody(t, api.OrderStatusUpdate{Status: api.OrderStatusUpdateStatusAssigned})
|
||||
r := httptest.NewRequest(http.MethodPatch, "/orders/"+orderID.String()+"/status", body)
|
||||
r = withClaims(r, uuid.New(), "client")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.UpdateOrderStatus(w, r, orderID)
|
||||
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Errorf("expected 403, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateOrderStatus_InvalidBody(t *testing.T) {
|
||||
s := newTestServer(&mockOrderService{})
|
||||
r := httptest.NewRequest(http.MethodPatch, "/orders/"+uuid.New().String()+"/status", bytes.NewBufferString("bad json"))
|
||||
r = withClaims(r, uuid.New(), "manager")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.UpdateOrderStatus(w, r, uuid.New())
|
||||
|
||||
if w.Code != http.StatusBadRequest {
|
||||
t.Errorf("expected 400, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateOrderStatus_ServiceError(t *testing.T) {
|
||||
orderID := uuid.New()
|
||||
svc := &mockOrderService{
|
||||
updateOrderStatus: func(_ context.Context, _ uuid.UUID, _ uuid.UUID, _ string, _ api.OrderStatusUpdate) (*models.Order, error) {
|
||||
return nil, errors.New("db error")
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
body := jsonBody(t, api.OrderStatusUpdate{Status: api.OrderStatusUpdateStatusInTransit})
|
||||
r := httptest.NewRequest(http.MethodPatch, "/orders/"+orderID.String()+"/status", body)
|
||||
r = withClaims(r, uuid.New(), "driver")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.UpdateOrderStatus(w, r, orderID)
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Errorf("expected 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
// --- GetOrdersReport ---
|
||||
|
||||
func TestGetOrdersReport_Success(t *testing.T) {
|
||||
svc := &mockOrderService{
|
||||
getOrdersReport: func(_ context.Context, _ string, _ api.GetOrdersReportParams) ([]models.Order, error) {
|
||||
return []models.Order{{ID: uuid.New(), Status: "delivered"}}, nil
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
r := httptest.NewRequest(http.MethodGet, "/reports/orders", nil)
|
||||
r = withClaims(r, uuid.New(), "manager")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.GetOrdersReport(w, r, api.GetOrdersReportParams{})
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Errorf("expected 200, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrdersReport_Forbidden(t *testing.T) {
|
||||
svc := &mockOrderService{
|
||||
getOrdersReport: func(_ context.Context, _ string, _ api.GetOrdersReportParams) ([]models.Order, error) {
|
||||
return nil, services.ErrForbidden
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
r := httptest.NewRequest(http.MethodGet, "/reports/orders", nil)
|
||||
r = withClaims(r, uuid.New(), "client")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.GetOrdersReport(w, r, api.GetOrdersReportParams{})
|
||||
|
||||
if w.Code != http.StatusForbidden {
|
||||
t.Errorf("expected 403, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOrdersReport_ServiceError(t *testing.T) {
|
||||
svc := &mockOrderService{
|
||||
getOrdersReport: func(_ context.Context, _ string, _ api.GetOrdersReportParams) ([]models.Order, error) {
|
||||
return nil, errors.New("db error")
|
||||
},
|
||||
}
|
||||
s := newTestServer(svc)
|
||||
r := httptest.NewRequest(http.MethodGet, "/reports/orders", nil)
|
||||
r = withClaims(r, uuid.New(), "manager")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
s.GetOrdersReport(w, r, api.GetOrdersReportParams{})
|
||||
|
||||
if w.Code != http.StatusInternalServerError {
|
||||
t.Errorf("expected 500, got %d", w.Code)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user