fix/cancel-order (#120)

* fix: adds ability to cancel order

* passing tests

* chore: clean up unused code
This commit is contained in:
Sebastian Rindom
2020-10-06 14:00:47 +02:00
committed by GitHub
parent 4bbb2a2367
commit 11bedf8c6f
29 changed files with 240 additions and 97 deletions
@@ -1,4 +1,4 @@
import bcrypt from "bcrypt"
import Scrypt from "scrypt-kdf"
import { IdMap } from "medusa-test-utils"
export const CustomerServiceMock = {
@@ -43,9 +43,10 @@ export const CustomerServiceMock = {
})
}
if (email === "oliver@test.dk") {
return bcrypt
.hash("123456789", 10)
.then(hash => ({ email, password_hash: hash }))
return Scrypt.kdf("123456789", { logN: 1, r: 1, p: 1 }).then(hash => ({
email,
password_hash: hash.toString("base64"),
}))
}
return Promise.resolve(undefined)
}),
@@ -42,6 +42,9 @@ export const DiscountServiceMock = {
list: jest.fn().mockImplementation(data => {
return Promise.resolve([])
}),
decorate: jest.fn().mockImplementation(data => {
return Promise.resolve(data)
}),
addRegion: jest.fn().mockReturnValue(Promise.resolve()),
removeRegion: jest.fn().mockReturnValue(Promise.resolve()),
addValidVariant: jest.fn().mockReturnValue(Promise.resolve()),
@@ -13,6 +13,9 @@ export const DefaultProviderMock = {
return Promise.resolve(false)
}),
cancelFulfillment: jest.fn().mockImplementation(data => {
return {}
}),
calculatePrice: jest.fn().mockImplementation(data => {
return Promise.resolve()
}),
@@ -15,6 +15,7 @@ export const DefaultProviderMock = {
}),
capturePayment: jest.fn().mockReturnValue(Promise.resolve()),
refundPayment: jest.fn().mockReturnValue(Promise.resolve()),
cancelPayment: jest.fn().mockReturnValue(Promise.resolve({})),
}
export const PaymentProviderServiceMock = {
@@ -37,6 +37,7 @@ export const ProductServiceMock = {
return Promise.resolve({ ...data })
}),
count: jest.fn().mockReturnValue(4),
publish: jest.fn().mockImplementation(_ => {
return Promise.resolve({
_id: IdMap.getId("publish"),
@@ -126,6 +126,7 @@ export const ShippingProfileServiceMock = {
])
}
}),
decorate: jest.fn().mockImplementation(d => Promise.resolve(d)),
addShippingOption: jest.fn().mockImplementation(() => Promise.resolve()),
removeShippingOption: jest.fn().mockImplementation(() => Promise.resolve()),
addProduct: jest.fn().mockImplementation(() => Promise.resolve()),
@@ -1,4 +1,4 @@
import bcrypt from "bcrypt"
import Scrypt from "scrypt-kdf"
import { IdMap } from "medusa-test-utils"
import _ from "lodash"
@@ -92,9 +92,10 @@ export const UserServiceMock = {
})
}
if (email === "oliver@test.dk") {
return bcrypt
.hash("123456789", 10)
.then(hash => ({ email, password_hash: hash }))
return Scrypt.kdf("123456789", { logN: 1, r: 1, p: 1 }).then(hash => ({
email,
password_hash: hash.toString("base64"),
}))
}
return Promise.resolve(undefined)
}),
+21 -4
View File
@@ -228,12 +228,26 @@ describe("CartService", () => {
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
{
_id: IdMap.getId("cartWithLine"),
"items._id": IdMap.getId("existingLine"),
},
{
$set: {
"items.$.quantity": 20,
"items.$.has_shipping": false,
$push: {
items: {
title: "merge line",
description: "This is a new line",
thumbnail: "test-img-yeah.com/thumb",
has_shipping: false,
content: {
unit_price: 123,
variant: {
_id: IdMap.getId("can-cover"),
},
product: {
_id: IdMap.getId("product"),
},
quantity: 1,
},
quantity: 10,
},
},
}
)
@@ -736,6 +750,9 @@ describe("CartService", () => {
$set: {
region_id: IdMap.getId("region-us"),
shipping_methods: [],
shipping_address: {
country_code: "US",
},
items: [
{
_id: IdMap.getId("line"),
@@ -141,9 +141,7 @@ describe("CustomerService", () => {
first_name: "Oliver",
last_name: "Juhl",
has_account: true,
password_hash: expect.stringMatching(
/^\$2[aby]?\$[\d]+\$[./A-Za-z0-9]{53}$/
),
password_hash: expect.stringMatching(/^.{128}$/),
})
})
@@ -259,9 +257,7 @@ describe("CustomerService", () => {
{
$set: {
has_account: true,
password_hash: expect.stringMatching(
/^\$2[aby]?\$[\d]+\$[./A-Za-z0-9]{53}$/
),
password_hash: expect.stringMatching(/^.{128}$/),
},
},
{ runValidators: true }
@@ -23,7 +23,9 @@ describe("EventBusService", () => {
it("creates bull queue", () => {
expect(Bull).toHaveBeenCalledTimes(2)
expect(Bull).toHaveBeenCalledWith("EventBusService:queue", "testhost")
expect(Bull).toHaveBeenCalledWith("EventBusService:queue", {
createClient: expect.any(Function),
})
})
})
@@ -27,6 +27,7 @@ describe("LineItemService", () => {
title: "test",
description: "EUR10US-12",
thumbnail: "test.1234",
should_merge: true,
content: {
unit_price: 10,
variant: {
@@ -41,6 +42,7 @@ describe("LineItemService", () => {
quantity: 1,
},
quantity: 2,
metadata: {},
})
})
})
@@ -88,6 +90,7 @@ describe("LineItemService", () => {
name: "Test Name",
},
quantity: 1,
should_merge: true,
})
})
})
@@ -47,7 +47,7 @@ describe("OrderService", () => {
discountService: DiscountServiceMock,
regionService: RegionServiceMock,
eventBusService: EventBusServiceMock,
counterService: CounterServiceMock
counterService: CounterServiceMock,
})
beforeEach(async () => {
@@ -135,6 +135,7 @@ describe("OrderService", () => {
tax_rate: 0.25,
email: "test",
giftcard: expect.any(Object),
line_item: expect.any(Object),
}
)
@@ -328,6 +329,8 @@ describe("OrderService", () => {
describe("cancel", () => {
const orderService = new OrderService({
fulfillmentProviderService: FulfillmentProviderServiceMock,
paymentProviderService: PaymentProviderServiceMock,
orderModel: OrderModelMock,
eventBusService: EventBusServiceMock,
})
@@ -342,7 +345,24 @@ describe("OrderService", () => {
expect(OrderModelMock.updateOne).toHaveBeenCalledTimes(1)
expect(OrderModelMock.updateOne).toHaveBeenCalledWith(
{ _id: IdMap.getId("not-fulfilled-order") },
{ $set: { status: "cancelled" } }
{
$set: {
status: "canceled",
fulfillment_status: "canceled",
payment_status: "canceled",
fulfillments: [
{
data: {},
is_canceled: true,
provider_id: "default_provider",
},
],
payment_method: {
data: {},
provider_id: "default_provider",
},
},
}
)
})
@@ -359,7 +379,7 @@ describe("OrderService", () => {
await orderService.cancel(IdMap.getId("payed-order"))
} catch (error) {
expect(error.message).toEqual(
"Can't cancel an order with payment processed"
"Can't cancel an order with a processed payment"
)
}
})
@@ -435,9 +455,11 @@ describe("OrderService", () => {
},
quantity: 1,
},
fulfilled_quantity: 0,
quantity: 10,
},
]
],
orders.testOrder
)
expect(OrderModelMock.updateOne).toHaveBeenCalledTimes(1)
@@ -467,6 +489,7 @@ describe("OrderService", () => {
},
quantity: 1,
},
fulfilled_quantity: 0,
quantity: 10,
},
],
@@ -504,15 +527,15 @@ describe("OrderService", () => {
)
})
it("throws if payment is already processed", async () => {
it("throws if too many items are requested fulfilled", async () => {
await expect(
orderService.createFulfillment(IdMap.getId("fulfilled-order"), [
orderService.createFulfillment(IdMap.getId("test-order"), [
{
item_id: IdMap.getId("existingLine"),
quantity: 10,
quantity: 11,
},
])
).rejects.toThrow("Order is already fulfilled")
).rejects.toThrow("Cannot fulfill more items than have been purchased")
})
})
@@ -53,9 +53,7 @@ describe("UserService", () => {
expect(UserModelMock.create).toHaveBeenCalledWith({
email: "oliver@test.dk",
name: "Oliver",
password_hash: expect.stringMatching(
/^\$2[aby]?\$[\d]+\$[./A-Za-z0-9]{53}$/
),
password_hash: expect.stringMatching(/.{128}$/),
})
})
})
@@ -134,9 +132,7 @@ describe("UserService", () => {
$set: {
// Since bcrypt hashing always varies, we are testing the password
// match by using a regular expression.
password_hash: expect.stringMatching(
/^\$2[aby]?\$[\d]+\$[./A-Za-z0-9]{53}$/
),
password_hash: expect.stringMatching(/^.{128}$/),
},
}
)
+35 -12
View File
@@ -12,7 +12,7 @@ class OrderService extends BaseService {
REFUND_CREATED: "order.refund_created",
PLACED: "order.placed",
UPDATED: "order.updated",
CANCELLED: "order.cancelled",
CANCELED: "order.canceled",
COMPLETED: "order.completed",
}
@@ -542,21 +542,35 @@ class OrderService extends BaseService {
async cancel(orderId) {
const order = await this.retrieve(orderId)
if (order.fulfillment_status !== "not_fulfilled") {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Can't cancel a fulfilled order"
)
}
if (order.payment_status !== "awaiting") {
throw new MedusaError(
MedusaError.Types.NOT_ALLOWED,
"Can't cancel an order with payment processed"
"Can't cancel an order with a processed payment"
)
}
// TODO: cancel payment method
const fulfillments = await Promise.all(
order.fulfillments.map(async fulfillment => {
const { provider_id, data } = fulfillment
const provider = await this.fulfillmentProviderService_.retrieveProvider(
provider_id
)
const newData = await provider.cancelFulfillment(data)
return {
...fulfillment,
is_canceled: true,
data: newData,
}
})
)
const { provider_id, data } = order.payment_method
const paymentProvider = await this.paymentProviderService_.retrieveProvider(
provider_id
)
// Cancel payment with payment provider
const payData = await paymentProvider.cancelPayment(data)
return this.orderModel_
.updateOne(
@@ -564,12 +578,21 @@ class OrderService extends BaseService {
_id: orderId,
},
{
$set: { status: "cancelled" },
$set: {
status: "canceled",
fulfillment_status: "canceled",
payment_status: "canceled",
fulfillments,
payment_method: {
...order.payment_method,
data: payData,
},
},
}
)
.then(result => {
// Notify subscribers
this.eventBus_.emit(OrderService.Events.CANCELLED, result)
this.eventBus_.emit(OrderService.Events.CANCELED, result)
return result
})
.catch(err => {
+1 -1
View File
@@ -231,7 +231,7 @@ class UserService extends BaseService {
async setPassword(userId, password) {
const user = await this.retrieve(userId)
const hashedPassword = await bcrypt.hash(password, 10)
const hashedPassword = await this.hashPassword_(password)
if (!hashedPassword) {
throw new MedusaError(
MedusaError.Types.DB_ERROR,