merge develop and resolve conflicts

This commit is contained in:
zakariaelas
2021-10-18 12:50:31 +01:00
205 changed files with 8686 additions and 4101 deletions
@@ -0,0 +1,17 @@
import _ from "lodash"
/**
* @oas [get] /auth
* operationId: "DeleteAuth"
* summary: "Delete Session"
* description: "Deletes the current session for the logged in user."
* tags:
* - Auth
* responses:
* "200":
* description: OK
*/
export default async (req, res) => {
req.session.destroy()
res.status(200).end()
}
@@ -13,5 +13,11 @@ export default app => {
)
route.post("/", middlewares.wrap(require("./create-session").default))
route.delete(
"/",
middlewares.authenticate(),
middlewares.wrap(require("./delete-session").default)
)
return app
}
@@ -46,6 +46,7 @@ describe("POST /admin/discounts/:discount_id/regions/:region_id", () => {
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
],
relations: ["rule", "parent_discount", "regions", "rule.valid_for"],
}
@@ -46,6 +46,7 @@ describe("POST /admin/discounts/:discount_id/variants/:variant_id", () => {
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
],
relations: ["rule", "parent_discount", "regions", "rule.valid_for"],
}
@@ -16,6 +16,8 @@ describe("POST /admin/discounts", () => {
value: 10,
allocation: "total",
},
starts_at: "02/02/2021 13:45",
ends_at: "03/14/2021 04:30",
},
adminSession: {
jwt: {
@@ -39,12 +41,99 @@ describe("POST /admin/discounts", () => {
value: 10,
allocation: "total",
},
starts_at: new Date("02/02/2021 13:45"),
ends_at: new Date("03/14/2021 04:30"),
is_disabled: false,
is_dynamic: false,
})
})
})
describe("unsuccessful creation with dynamic discount using an invalid iso8601 duration", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request("POST", "/admin/discounts", {
payload: {
code: "TEST",
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: "02/02/2021 13:45",
is_dynamic: true,
valid_duration: "PaMT2D",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"valid_duration" must be a valid ISO 8601 duration`
)
})
})
describe("successful creation with dynamic discount", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request("POST", "/admin/discounts", {
payload: {
code: "TEST",
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: "02/02/2021 13:45",
is_dynamic: true,
valid_duration: "P1Y2M03DT04H05M",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service create", () => {
expect(DiscountServiceMock.create).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.create).toHaveBeenCalledWith({
code: "TEST",
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: new Date("02/02/2021 13:45"),
is_disabled: false,
is_dynamic: true,
valid_duration: "P1Y2M03DT04H05M",
})
})
})
describe("fails on invalid data", () => {
let subject
@@ -74,4 +163,84 @@ describe("POST /admin/discounts", () => {
expect(subject.body.message[0].message).toEqual(`"rule.type" is required`)
})
})
describe("fails on invalid date intervals", () => {
let subject
beforeAll(async () => {
subject = await request("POST", "/admin/discounts", {
payload: {
code: "TEST",
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
ends_at: "02/02/2021",
starts_at: "03/14/2021",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"ends_at" must be greater than "ref:starts_at"`
)
})
})
describe("succesfully creates a dynamic discount without setting valid duration", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request("POST", "/admin/discounts", {
payload: {
code: "TEST",
is_dynamic: true,
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: "03/14/2021 14:30",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("returns error", () => {
expect(DiscountServiceMock.create).toHaveBeenCalledWith({
code: "TEST",
is_dynamic: true,
is_disabled: false,
rule: {
description: "Test",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: new Date("03/14/2021 14:30"),
})
})
})
})
@@ -17,6 +17,7 @@ const defaultFields = [
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
]
const defaultRelations = [
@@ -17,6 +17,7 @@ const defaultFields = [
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
]
const defaultRelations = [
@@ -17,6 +17,7 @@ const defaultFields = [
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
]
const defaultRelations = [
@@ -7,6 +7,7 @@ describe("POST /admin/discounts", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request(
"POST",
`/admin/discounts/${IdMap.getId("total10")}`,
@@ -50,4 +51,139 @@ describe("POST /admin/discounts", () => {
)
})
})
describe("unsuccessful update with dynamic discount using an invalid iso8601 duration", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request(
"POST",
`/admin/discounts/${IdMap.getId("total10")}`,
{
payload: {
code: "10TOTALOFF",
rule: {
id: "1234",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: "02/02/2021 13:45",
is_dynamic: true,
valid_duration: "PaMT2D",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"valid_duration" must be a valid ISO 8601 duration`
)
})
})
describe("successful update with dynamic discount", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request(
"POST",
`/admin/discounts/${IdMap.getId("total10")}`,
{
payload: {
code: "10TOTALOFF",
rule: {
id: "1234",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: "02/02/2021 13:45",
is_dynamic: true,
valid_duration: "P1Y2M03DT04H05M",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service update", () => {
expect(DiscountServiceMock.update).toHaveBeenCalledTimes(1)
expect(DiscountServiceMock.update).toHaveBeenCalledWith(
IdMap.getId("total10"),
{
code: "10TOTALOFF",
rule: {
id: "1234",
type: "fixed",
value: 10,
allocation: "total",
},
starts_at: new Date("02/02/2021 13:45"),
is_dynamic: true,
valid_duration: "P1Y2M03DT04H05M",
}
)
})
})
describe("fails on invalid date intervals", () => {
let subject
beforeAll(async () => {
jest.clearAllMocks()
subject = await request(
"POST",
`/admin/discounts/${IdMap.getId("total10")}`,
{
payload: {
code: "10TOTALOFF",
rule: {
id: "1234",
type: "fixed",
value: 10,
allocation: "total",
},
ends_at: "02/02/2021",
starts_at: "03/14/2021",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(
`"ends_at" must be greater than "ref:starts_at"`
)
})
})
})
@@ -1,4 +1,5 @@
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultRelations } from "."
/**
* @oas [post] /discounts
@@ -71,7 +72,13 @@ export default async (req, res) => {
.required(),
is_disabled: Validator.boolean().default(false),
starts_at: Validator.date().optional(),
ends_at: Validator.date().optional(),
ends_at: Validator.date()
.greater(Validator.ref("starts_at"))
.optional(),
valid_duration: Validator.string()
.isoDuration()
.allow(null)
.optional(),
usage_limit: Validator.number()
.positive()
.optional(),
@@ -90,11 +97,10 @@ export default async (req, res) => {
const discountService = req.scope.resolve("discountService")
const created = await discountService.create(value)
const discount = await discountService.retrieve(created.id, [
"rule",
"rule.valid_for",
"regions",
])
const discount = await discountService.retrieve(
created.id,
defaultRelations
)
res.status(200).json({ discount })
} catch (err) {
@@ -37,9 +37,9 @@ export default async (req, res) => {
try {
const discountService = req.scope.resolve("discountService")
await discountService.createDynamicCode(discount_id, value)
const created = await discountService.createDynamicCode(discount_id, value)
const discount = await discountService.retrieve(discount_id, {
const discount = await discountService.retrieve(created.id, {
relations: ["rule", "rule.valid_for", "regions"],
})
@@ -74,6 +74,7 @@ export const defaultFields = [
"updated_at",
"deleted_at",
"metadata",
"valid_duration",
]
export const defaultRelations = [
@@ -68,7 +68,16 @@ export default async (req, res) => {
.optional(),
is_disabled: Validator.boolean().optional(),
starts_at: Validator.date().optional(),
ends_at: Validator.date().optional(),
ends_at: Validator.when("starts_at", {
not: undefined,
then: Validator.date()
.greater(Validator.ref("starts_at"))
.optional(),
otherwise: Validator.date().optional(),
}),
valid_duration: Validator.string()
.isoDuration().allow(null)
.optional(),
usage_limit: Validator.number()
.positive()
.optional(),
@@ -78,6 +87,7 @@ export default async (req, res) => {
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
@@ -57,6 +57,7 @@ export const defaultCartRelations = [
"payment_sessions",
"shipping_methods.shipping_option",
"discounts",
"discounts.rule",
]
export const defaultCartFields = [
@@ -43,7 +43,14 @@ export default async (req, res) => {
.withTransaction(manager)
.retrieve(draftOrder.cart_id, {
select: ["total"],
relations: ["discounts", "shipping_methods", "region", "items"],
relations: [
"discounts",
"discounts.rule",
"discounts.rule.valid_for",
"shipping_methods",
"region",
"items",
],
})
await paymentProviderService
@@ -7,6 +7,8 @@ const defaultRelations = [
"billing_address",
"shipping_address",
"discounts",
"discounts.rule",
"discounts.rule.valid_for",
"shipping_methods",
"payments",
"fulfillments",
@@ -25,6 +27,7 @@ const defaultRelations = [
"claims.additional_items",
"claims.fulfillments",
"claims.claim_items",
"claims.claim_items.item",
"claims.claim_items.images",
"swaps",
"swaps.return_order",
@@ -54,6 +57,7 @@ const defaultFields = [
"metadata",
"items.refundable",
"swaps.additional_items.refundable",
"claims.additional_items.refundable",
"shipping_total",
"discount_total",
"tax_total",
@@ -202,7 +202,12 @@ export default async (req, res) => {
const order = await orderService
.withTransaction(manager)
.retrieve(id, {
relations: ["items", "discounts"],
relations: [
"items",
"cart",
"cart.discounts",
"cart.discounts.rule",
],
})
await claimService.withTransaction(manager).create({
@@ -45,6 +45,17 @@ import { defaultFields, defaultRelations } from "./"
* quantity:
* description: The quantity of the Product Variant to ship.
* type: integer
* custom_shipping_options:
* description: The custom shipping options to potentially create a Shipping Method from.
* type: array
* items:
* properties:
* option_id:
* description: The id of the Shipping Option to override with a custom price.
* type: string
* price:
* description: The custom price of the Shipping Option.
* type: integer
* no_notification:
* description: If set to true no notification will be send related to this Swap.
* type: boolean
@@ -85,6 +96,12 @@ export default async (req, res) => {
variant_id: Validator.string().required(),
quantity: Validator.number().required(),
}),
custom_shipping_options: Validator.array()
.items({
option_id: Validator.string().required(),
price: Validator.number().required(),
})
.default([]),
no_notification: Validator.boolean().optional(),
allow_backorder: Validator.boolean().default(true),
})
@@ -149,7 +166,9 @@ export default async (req, res) => {
}
)
await swapService.withTransaction(manager).createCart(swap.id)
await swapService
.withTransaction(manager)
.createCart(swap.id, value.custom_shipping_options)
const returnOrder = await returnService
.withTransaction(manager)
.retrieveBySwap(swap.id)
@@ -221,6 +221,8 @@ export const defaultRelations = [
"billing_address",
"shipping_address",
"discounts",
"discounts.rule",
"discounts.rule.valid_for",
"shipping_methods",
"payments",
"fulfillments",
@@ -239,6 +241,7 @@ export const defaultRelations = [
"claims.additional_items",
"claims.fulfillments",
"claims.claim_items",
"claims.claim_items.item",
"claims.claim_items.images",
// "claims.claim_items.tags",
"swaps",
@@ -269,6 +272,7 @@ export const defaultFields = [
"metadata",
"items.refundable",
"swaps.additional_items.refundable",
"claims.additional_items.refundable",
"shipping_total",
"discount_total",
"tax_total",
@@ -316,6 +320,8 @@ export const allowedRelations = [
"billing_address",
"shipping_address",
"discounts",
"discounts.rule",
"discounts.rule.valid_for",
"shipping_methods",
"payments",
"fulfillments",
@@ -46,7 +46,11 @@ export default app => {
)
route.get("/:id", middlewares.wrap(require("./get-product").default))
route.get("/", middlewares.wrap(require("./list-products").default))
route.get(
"/",
middlewares.normalizeQuery(),
middlewares.wrap(require("./list-products").default)
)
return app
}
@@ -121,3 +125,18 @@ export const allowedRelations = [
"type",
"collection",
]
export const filterableFields = [
"id",
"status",
"collection_id",
"tags",
"title",
"description",
"handle",
"is_giftcard",
"type",
"created_at",
"updated_at",
"deleted_at",
]
@@ -1,6 +1,6 @@
import _ from "lodash"
import { MedusaError, Validator } from "medusa-core-utils"
import { defaultFields, defaultRelations } from "./"
import { defaultFields, defaultRelations, filterableFields } from "./"
/**
* @oas [get] /products
@@ -31,6 +31,17 @@ import { defaultFields, defaultRelations } from "./"
* $ref: "#/components/schemas/product"
*/
export default async (req, res) => {
const schema = Validator.productFilter()
const { value, error } = schema.validate(req.query)
if (error) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
JSON.stringify(error.details)
)
}
try {
const productService = req.scope.resolve("productService")
@@ -53,21 +64,16 @@ export default async (req, res) => {
expandFields = req.query.expand.split(",")
}
if ("is_giftcard" in req.query) {
selector.is_giftcard = req.query.is_giftcard === "true"
for (const k of filterableFields) {
if (k in value) {
selector[k] = value[k]
}
}
if ("status" in req.query) {
const schema = Validator.array()
.items(
Validator.string().valid("proposed", "draft", "published", "rejected")
)
.single()
const { value, error } = schema.validate(req.query.status)
if (value && !error) {
selector.status = value
if (selector.status?.indexOf("null") > -1) {
selector.status.splice(selector.status.indexOf("null"), 1)
if (selector.status.length === 0) {
delete selector.status
}
}
@@ -23,7 +23,7 @@ describe("POST /store/carts/:id/shipping-methods", () => {
jest.clearAllMocks()
})
it("calls CartService addShipping", () => {
it("calls CartService addShippingMethod", () => {
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
IdMap.getId("fr-cart"),
@@ -45,6 +45,50 @@ describe("POST /store/carts/:id/shipping-methods", () => {
})
})
describe("successfully adds a shipping method", () => {
let subject
beforeAll(async () => {
const cartId = IdMap.getId("swap-cart")
subject = await request(
"POST",
`/store/carts/${cartId}/shipping-methods`,
{
payload: {
option_id: IdMap.getId("freeShipping"),
},
}
)
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls CartService addShippingMethod", () => {
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
IdMap.getId("swap-cart"),
IdMap.getId("freeShipping"),
{}
)
})
it("calls CartService retrieve", () => {
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(2)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("returns the cart", () => {
expect(subject.body.cart).toEqual(
expect.objectContaining({ type: "swap", id: IdMap.getId("test-swap") })
)
})
})
describe("successfully adds a shipping method with additional data", () => {
let subject
@@ -68,7 +112,7 @@ describe("POST /store/carts/:id/shipping-methods", () => {
jest.clearAllMocks()
})
it("calls CartService addShipping", () => {
it("calls CartService addShippingMethod", () => {
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
IdMap.getId("fr-cart"),
@@ -44,7 +44,9 @@ export default async (req, res) => {
await manager.transaction(async m => {
const txCartService = cartService.withTransaction(m)
await txCartService.addShippingMethod(id, value.option_id, value.data)
const updated = await txCartService.retrieve(id, {
relations: ["payment_sessions"],
})
@@ -54,12 +56,12 @@ export default async (req, res) => {
}
})
const cart = await cartService.retrieve(id, {
const updatedCart = await cartService.retrieve(id, {
select: defaultFields,
relations: defaultRelations,
})
res.status(200).json({ cart })
res.status(200).json({ cart: updatedCart })
} catch (err) {
throw err
}
@@ -82,6 +82,14 @@ export default async (req, res) => {
if (!value.region_id) {
const regionService = req.scope.resolve("regionService")
const regions = await regionService.withTransaction(manager).list({})
if (!regions?.length) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`A region is required to create a cart`
)
}
regionId = regions[0].id
}
@@ -116,4 +116,6 @@ export const defaultRelations = [
"payment_sessions",
"shipping_methods.shipping_option",
"discounts",
"discounts.rule",
"discounts.rule.valid_for",
]
@@ -50,7 +50,7 @@ export default async (req, res) => {
const id = req.user.customer_id
const schema = Validator.object().keys({
billing_address: Validator.address().optional(),
billing_address: Validator.address().optional().allow(null),
first_name: Validator.string().optional(),
last_name: Validator.string().optional(),
password: Validator.string().optional(),
@@ -66,9 +66,9 @@ export default async (req, res) => {
try {
const customerService = req.scope.resolve("customerService")
let customer = await customerService.update(id, value)
await customerService.update(id, value)
customer = await customerService.retrieve(customer.id, {
const customer = await customerService.retrieve(id, {
relations: defaultRelations,
select: defaultFields,
})
@@ -36,6 +36,8 @@ export const defaultRelations = [
"items.variant.product",
"shipping_methods",
"discounts",
"discounts.rule",
"discounts.rule.valid_for",
"customer",
"payments",
"region",
@@ -74,6 +76,8 @@ export const allowedRelations = [
"items.variant.product",
"shipping_methods",
"discounts",
"discounts.rule",
"discounts.rule.valid_for",
"customer",
"payments",
"region",
@@ -108,7 +108,7 @@ export default async (req, res) => {
case "started": {
const { key, error } = await idempotencyKeyService.workStage(
idempotencyKey.idempotency_key,
async (manager) => {
async manager => {
const order = await orderService
.withTransaction(manager)
.retrieve(value.order_id, {
@@ -163,7 +163,7 @@ export default async (req, res) => {
case "swap_created": {
const { key, error } = await idempotencyKeyService.workStage(
idempotencyKey.idempotency_key,
async (manager) => {
async manager => {
const swaps = await swapService.list({
idempotency_key: idempotencyKey.idempotency_key,
})
@@ -16,7 +16,7 @@ describe("Get variant by id", () => {
it("calls get variant from variantSerice", () => {
expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledWith("1", {
relations: ["prices"],
relations: ["prices", "options"],
})
})
@@ -12,4 +12,4 @@ export default app => {
return app
}
export const defaultRelations = ["prices"]
export const defaultRelations = ["prices", "options"]