updates on the reverse logistics
This commit is contained in:
@@ -268,6 +268,9 @@ func RegisterRoutes(app *fiber.App, cfg *config.Config) {
|
||||
milerAuth.Post("/consignments/:id/start-delivery", middlewares.Idempotency(), controllers.MilerStartDelivery)
|
||||
milerAuth.Post("/consignments/:id/deliver", middlewares.Idempotency(), controllers.MilerDeliverConsignment)
|
||||
milerAuth.Post("/consignments/:id/skip", controllers.MilerSkipDelivery)
|
||||
// Rider hands a returned (RTO) parcel back to the sender. Refused unless
|
||||
// MILER_RTO_FLOW_ENABLED=true.
|
||||
milerAuth.Post("/consignments/:id/return-complete", middlewares.Idempotency(), controllers.MilerCompleteReturn)
|
||||
// Rider handover at a base. The authoritative record that a hub-routed parcel
|
||||
// physically changed hands; answers with the resulting state rather than a
|
||||
// bare 200, and carries the shared idempotency middleware because riders retry
|
||||
@@ -407,6 +410,12 @@ func RegisterRoutes(app *fiber.App, cfg *config.Config) {
|
||||
adminAuth.Get("/consignments/:id/logs", controllers.GetAdminConsignmentLogs)
|
||||
adminAuth.Get("/consignments/track/:trackingno", controllers.GetAdminConsignmentTracking)
|
||||
adminAuth.Put("/consignments/:id/status", controllers.AdminUpdateConsignmentStatus)
|
||||
// Reverse logistics (RTO). Starting, cancelling and closing a return are
|
||||
// Doormile-staff actions; a client login can read its own returns.
|
||||
adminAuth.Post("/consignments/:id/rto", middlewares.DoormileStaffOnly, controllers.InitiateConsignmentRTO)
|
||||
adminAuth.Post("/consignments/:id/rto/cancel", middlewares.DoormileStaffOnly, controllers.CancelConsignmentRTO)
|
||||
adminAuth.Post("/consignments/:id/rto/complete", middlewares.DoormileStaffOnly, controllers.CompleteConsignmentRTO)
|
||||
adminAuth.Get("/returns", controllers.GetReturns)
|
||||
|
||||
// Tripsheets
|
||||
adminAuth.Get("/tripsheets", controllers.GetTripsheets)
|
||||
|
||||
87
routes/routes_rto_test.go
Normal file
87
routes/routes_rto_test.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package routes_test
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Reverse logistics (RTO) gates, over real HTTP. Refusals happen in
|
||||
// middleware (or before any query), so no database is needed.
|
||||
|
||||
var rtoWrites = []struct{ method, path, body string }{
|
||||
{http.MethodPost, "/api/v1/admin/consignments/5/rto", `{"reason":"receiver_refused"}`},
|
||||
{http.MethodPost, "/api/v1/admin/consignments/5/rto/cancel", `{}`},
|
||||
{http.MethodPost, "/api/v1/admin/consignments/5/rto/complete", `{}`},
|
||||
}
|
||||
|
||||
func TestRTONeedsALogin(t *testing.T) {
|
||||
app := newApp()
|
||||
for _, w := range rtoWrites {
|
||||
if code, _ := do(t, app, w.method, w.path, "", w.body); code != http.StatusUnauthorized {
|
||||
t.Errorf("%s %s with no token = %d, want 401", w.method, w.path, code)
|
||||
}
|
||||
}
|
||||
if code, _ := do(t, app, http.MethodGet, "/api/v1/admin/returns", "", ""); code != http.StatusUnauthorized {
|
||||
t.Errorf("GET /admin/returns with no token = %d, want 401", code)
|
||||
}
|
||||
}
|
||||
|
||||
// A client login may read its returns but never start, cancel or close one.
|
||||
func TestRTOActionsAreDoormileStaffOnly(t *testing.T) {
|
||||
app := newApp()
|
||||
client := tenantToken(t, 50, 1, 7)
|
||||
for _, w := range rtoWrites {
|
||||
code, body := do(t, app, w.method, w.path, client, w.body)
|
||||
if code != http.StatusForbidden || !strings.Contains(body, "Doormile staff only") {
|
||||
t.Errorf("client %s %s = %d %s, want 403 staff only", w.method, w.path, code, body)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRTORefusesNonConsoleRoles(t *testing.T) {
|
||||
app := newApp()
|
||||
for _, role := range []int{5, 6, 9} {
|
||||
for _, w := range rtoWrites {
|
||||
if code, _ := do(t, app, w.method, w.path, token(t, 1, role), w.body); code != http.StatusForbidden {
|
||||
t.Errorf("role %d %s %s = %d, want 403", role, w.method, w.path, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Staff pass every gate (no database here, so the handler's first query
|
||||
// panics and recover answers 500 — proof nothing in front of it refused).
|
||||
// A missing reason is refused before any query.
|
||||
func TestRTOStaffPassTheGate(t *testing.T) {
|
||||
app := newApp()
|
||||
staff := token(t, 1, 1)
|
||||
for _, w := range rtoWrites {
|
||||
if code, _ := do(t, app, w.method, w.path, staff, w.body); code == 401 || code == 403 || code == 404 {
|
||||
t.Errorf("staff %s %s = %d; a gate refused or the route is missing", w.method, w.path, code)
|
||||
}
|
||||
}
|
||||
if code, body := do(t, app, http.MethodPost, "/api/v1/admin/consignments/5/rto", staff, `{"reason":"teleported"}`); code != http.StatusBadRequest {
|
||||
t.Errorf("unknown reason = %d %s, want 400", code, body)
|
||||
}
|
||||
if code, body := do(t, app, http.MethodPost, "/api/v1/admin/consignments/5/rto", staff, `{"reason":"other"}`); code != http.StatusBadRequest {
|
||||
t.Errorf("other without a note = %d %s, want 400", code, body)
|
||||
}
|
||||
if code, _ := do(t, app, http.MethodGet, "/api/v1/admin/returns?status=bogus", staff, ""); code != http.StatusBadRequest {
|
||||
t.Errorf("bad status filter = %d, want 400", code)
|
||||
}
|
||||
}
|
||||
|
||||
// The rider endpoint stays shut until MILER_RTO_FLOW_ENABLED=true — the
|
||||
// deployed rider app does not know returns yet.
|
||||
func TestRiderReturnIsOffByDefault(t *testing.T) {
|
||||
t.Setenv("MILER_RTO_FLOW_ENABLED", "")
|
||||
app := newApp()
|
||||
code, body := do(t, app, http.MethodPost, "/api/v1/miler/consignments/5/return-complete", token(t, 9, 5), `{}`)
|
||||
if code != http.StatusForbidden || !strings.Contains(body, "RTO_FLOW_DISABLED") {
|
||||
t.Fatalf("flag off = %d %s, want 403 RTO_FLOW_DISABLED", code, body)
|
||||
}
|
||||
if code, _ := do(t, app, http.MethodPost, "/api/v1/miler/consignments/5/return-complete", token(t, 1, 1), `{}`); code != http.StatusForbidden {
|
||||
t.Fatalf("a console token on the rider route = %d, want 403", code)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user