feat: Add DiscountConditions (#1230)
* feat: Add DiscountCondition entity + Join table per relation (#1146) * feat: Convert DiscountService to TypeScript (#1149) * feat: Add DiscountRepository + bulk insert and remove (#1156) * feat: Add `conditions` to payload in `POST /discounts` and `POST /discounts/:id` (#1170) * feat: Add DiscountRuleCondition entity * fix relation * fix join key * Add discount rule condition repo * add join table per relation * Convert DiscountService to TypeScript * feat: Add DiscountConditionRepository * Add migration + remove use of valid_for * revert changes to files, not done yet * init work on create discount endpoint * Add conditions to create discount endpoint * Add conditions to update discount endpoint * Add unique constraint to discount condition * integration tests passing * fix imports of models * fix tests (excluding totals calculations) * Fix commented code * add unique constraint on discount condition * Add generic way of generating retrieve configs * Requested changes + ExactlyOne validator * Remove isLocal flag from error handler * Use postgres error constant * remove commented code * feat: Add `isValidForProduct` to check if Discount is valid for a given Product (#1172) * feat: Add `canApplyForCustomer` to check if Discount is valid for customer groups (#1212) * feat: Add `calculateDiscountForLineItem` (#1224) * feat: Adds discount condition test factory (#1228) * Remove use of valid_for * Tests passing * Remove valid_for form relations * Add integration tests for applying discounts to cart
This commit is contained in:
committed by
GitHub
parent
b7f699654b
commit
a610805917
@@ -1,5 +1,11 @@
|
||||
const path = require("path")
|
||||
const { Region, DiscountRule, Discount } = require("@medusajs/medusa")
|
||||
const {
|
||||
Region,
|
||||
DiscountRule,
|
||||
Discount,
|
||||
Customer,
|
||||
CustomerGroup,
|
||||
} = require("@medusajs/medusa")
|
||||
|
||||
const setupServer = require("../../../helpers/setup-server")
|
||||
const { useApi } = require("../../../helpers/use-api")
|
||||
@@ -7,6 +13,10 @@ const { initDb, useDb } = require("../../../helpers/use-db")
|
||||
const adminSeeder = require("../../helpers/admin-seeder")
|
||||
const discountSeeder = require("../../helpers/discount-seeder")
|
||||
const { exportAllDeclaration } = require("@babel/types")
|
||||
const { simpleProductFactory } = require("../../factories")
|
||||
const {
|
||||
simpleDiscountFactory,
|
||||
} = require("../../factories/simple-discount-factory")
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
@@ -26,6 +36,153 @@ describe("/admin/discounts", () => {
|
||||
medusaProcess.kill()
|
||||
})
|
||||
|
||||
describe("GET /admin/discounts/:id", () => {
|
||||
beforeEach(async () => {
|
||||
const manager = dbConnection.manager
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
await manager.insert(DiscountRule, {
|
||||
id: "test-discount-rule-fixed",
|
||||
description: "Test discount rule",
|
||||
type: "fixed",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
})
|
||||
|
||||
const prod = await simpleProductFactory(dbConnection, { type: "pants" })
|
||||
|
||||
await simpleDiscountFactory(dbConnection, {
|
||||
id: "test-discount",
|
||||
code: "TEST",
|
||||
rule: {
|
||||
type: "percentage",
|
||||
value: "10",
|
||||
allocation: "total",
|
||||
conditions: [
|
||||
{
|
||||
type: "products",
|
||||
operator: "in",
|
||||
products: [prod.id],
|
||||
},
|
||||
{
|
||||
type: "product_types",
|
||||
operator: "not_in",
|
||||
product_types: [prod.type_id],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
await db.teardown()
|
||||
})
|
||||
|
||||
it("should retrieve discount with customer conditions created with factory", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const group = await dbConnection.manager.insert(CustomerGroup, {
|
||||
id: "customer-group-1",
|
||||
name: "vip-customers",
|
||||
})
|
||||
|
||||
await dbConnection.manager.insert(Customer, {
|
||||
id: "cus_1234",
|
||||
email: "oli@email.com",
|
||||
groups: [group],
|
||||
})
|
||||
|
||||
await simpleDiscountFactory(dbConnection, {
|
||||
id: "test-discount",
|
||||
code: "TEST",
|
||||
rule: {
|
||||
type: "percentage",
|
||||
value: "10",
|
||||
allocation: "total",
|
||||
conditions: [
|
||||
{
|
||||
type: "customer_groups",
|
||||
operator: "in",
|
||||
customer_groups: ["customer-group-1"],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
|
||||
const response = await api
|
||||
.get(
|
||||
"/admin/discounts/test-discount?expand=rule,rule.conditions,rule.conditions.customer_groups",
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
const disc = response.data.discount
|
||||
expect(response.status).toEqual(200)
|
||||
expect(disc).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "test-discount",
|
||||
code: "TEST",
|
||||
})
|
||||
)
|
||||
expect(disc.rule.conditions).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: "customer_groups",
|
||||
operator: "in",
|
||||
discount_rule_id: disc.rule.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should retrieve discount with product conditions created with factory", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api
|
||||
.get(
|
||||
"/admin/discounts/test-discount?expand=rule,rule.conditions,rule.conditions.products,rule.conditions.product_types",
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
const disc = response.data.discount
|
||||
expect(response.status).toEqual(200)
|
||||
expect(disc).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "test-discount",
|
||||
code: "TEST",
|
||||
})
|
||||
)
|
||||
expect(disc.rule.conditions).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: "products",
|
||||
operator: "in",
|
||||
discount_rule_id: disc.rule.id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: "product_types",
|
||||
operator: "not_in",
|
||||
discount_rule_id: disc.rule.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("GET /admin/discounts", () => {
|
||||
beforeEach(async () => {
|
||||
const manager = dbConnection.manager
|
||||
@@ -267,25 +424,398 @@ describe("/admin/discounts", () => {
|
||||
usage_limit: 10,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
const test = await api.get(
|
||||
`/admin/discounts/${response.data.discount.id}`,
|
||||
{ headers: { Authorization: "Bearer test_token" } }
|
||||
)
|
||||
it("creates a discount with conditions", async () => {
|
||||
const api = useApi()
|
||||
|
||||
expect(test.status).toEqual(200)
|
||||
expect(test.data.discount).toEqual(
|
||||
expect.objectContaining({
|
||||
code: "HELLOWORLD",
|
||||
usage_limit: 10,
|
||||
rule: expect.objectContaining({
|
||||
value: 10,
|
||||
type: "percentage",
|
||||
description: "test",
|
||||
allocation: "total",
|
||||
}),
|
||||
const product = await simpleProductFactory(dbConnection, {
|
||||
type: "pants",
|
||||
tags: ["ss22"],
|
||||
})
|
||||
|
||||
const anotherProduct = await simpleProductFactory(dbConnection, {
|
||||
type: "blouses",
|
||||
tags: ["ss23"],
|
||||
})
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/admin/discounts",
|
||||
{
|
||||
code: "HELLOWORLD",
|
||||
rule: {
|
||||
description: "test",
|
||||
type: "percentage",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
conditions: [
|
||||
{
|
||||
products: [product.id],
|
||||
operator: "in",
|
||||
},
|
||||
{
|
||||
products: [anotherProduct.id],
|
||||
operator: "not_in",
|
||||
},
|
||||
{
|
||||
product_types: [product.type_id],
|
||||
operator: "not_in",
|
||||
},
|
||||
{
|
||||
product_types: [anotherProduct.type_id],
|
||||
operator: "in",
|
||||
},
|
||||
{
|
||||
product_tags: [product.tags[0].id],
|
||||
operator: "not_in",
|
||||
},
|
||||
{
|
||||
product_tags: [anotherProduct.tags[0].id],
|
||||
operator: "in",
|
||||
},
|
||||
],
|
||||
},
|
||||
usage_limit: 10,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.discount.rule.conditions).toEqual([
|
||||
expect.objectContaining({
|
||||
type: "products",
|
||||
operator: "in",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: "products",
|
||||
operator: "not_in",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: "product_types",
|
||||
operator: "not_in",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: "product_types",
|
||||
operator: "in",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: "product_tags",
|
||||
operator: "not_in",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: "product_tags",
|
||||
operator: "in",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("creates a discount with conditions and updates said conditions", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const product = await simpleProductFactory(dbConnection, {
|
||||
type: "pants",
|
||||
})
|
||||
|
||||
const anotherProduct = await simpleProductFactory(dbConnection, {
|
||||
type: "pants",
|
||||
})
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/admin/discounts?expand=rule,rule.conditions",
|
||||
{
|
||||
code: "HELLOWORLD",
|
||||
rule: {
|
||||
description: "test",
|
||||
type: "percentage",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
conditions: [
|
||||
{
|
||||
products: [product.id],
|
||||
operator: "in",
|
||||
},
|
||||
{
|
||||
product_types: [product.type_id],
|
||||
operator: "not_in",
|
||||
},
|
||||
],
|
||||
},
|
||||
usage_limit: 10,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.discount.rule.conditions).toEqual([
|
||||
expect.objectContaining({
|
||||
type: "products",
|
||||
operator: "in",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: "product_types",
|
||||
operator: "not_in",
|
||||
}),
|
||||
])
|
||||
|
||||
const createdRule = response.data.discount.rule
|
||||
const condsToUpdate = createdRule.conditions[0]
|
||||
|
||||
const updated = await api
|
||||
.post(
|
||||
`/admin/discounts/${response.data.discount.id}?expand=rule,rule.conditions,rule.conditions.products`,
|
||||
{
|
||||
rule: {
|
||||
id: createdRule.id,
|
||||
type: createdRule.type,
|
||||
value: createdRule.value,
|
||||
allocation: createdRule.allocation,
|
||||
conditions: [
|
||||
{
|
||||
id: condsToUpdate.id,
|
||||
products: [product.id, anotherProduct.id],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(updated.status).toEqual(200)
|
||||
expect(updated.data.discount.rule.conditions).toEqual([
|
||||
expect.objectContaining({
|
||||
type: "products",
|
||||
operator: "in",
|
||||
products: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: product.id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: anotherProduct.id,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: "product_types",
|
||||
operator: "not_in",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("fails to add condition on rule with existing comb. of type and operator", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const product = await simpleProductFactory(dbConnection, {
|
||||
type: "pants",
|
||||
})
|
||||
|
||||
const anotherProduct = await simpleProductFactory(dbConnection, {
|
||||
type: "pants",
|
||||
})
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/admin/discounts",
|
||||
{
|
||||
code: "HELLOWORLD",
|
||||
rule: {
|
||||
description: "test",
|
||||
type: "percentage",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
conditions: [
|
||||
{
|
||||
products: [product.id],
|
||||
operator: "in",
|
||||
},
|
||||
],
|
||||
},
|
||||
usage_limit: 10,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
const createdRule = response.data.discount.rule
|
||||
|
||||
try {
|
||||
await api.post(
|
||||
`/admin/discounts/${response.data.discount.id}?expand=rule,rule.conditions,rule.conditions.products`,
|
||||
{
|
||||
rule: {
|
||||
id: createdRule.id,
|
||||
type: createdRule.type,
|
||||
value: createdRule.value,
|
||||
allocation: createdRule.allocation,
|
||||
conditions: [
|
||||
{
|
||||
products: [anotherProduct.id],
|
||||
operator: "in",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
expect(error.response.data.type).toEqual("duplicate_error")
|
||||
expect(error.response.data.message).toEqual(
|
||||
`Discount Condition with operator 'in' and type 'products' already exist on a Discount Rule`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("fails if multiple types of resources are provided on create", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const product = await simpleProductFactory(dbConnection, {
|
||||
type: "pants",
|
||||
})
|
||||
|
||||
try {
|
||||
await api.post(
|
||||
"/admin/discounts",
|
||||
{
|
||||
code: "HELLOWORLD",
|
||||
rule: {
|
||||
description: "test",
|
||||
type: "percentage",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
conditions: [
|
||||
{
|
||||
products: [product.id],
|
||||
product_types: [product.type_id],
|
||||
operator: "in",
|
||||
},
|
||||
],
|
||||
},
|
||||
usage_limit: 10,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
expect(error.response.data.type).toEqual("invalid_data")
|
||||
expect(error.response.data.message).toEqual(
|
||||
"Only one of products, product_types is allowed, Only one of product_types, products is allowed"
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("fails if multiple types of resources are provided on update", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const product = await simpleProductFactory(dbConnection, {
|
||||
type: "pants",
|
||||
})
|
||||
|
||||
const anotherProduct = await simpleProductFactory(dbConnection, {
|
||||
type: "pants",
|
||||
})
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/admin/discounts",
|
||||
{
|
||||
code: "HELLOWORLD",
|
||||
rule: {
|
||||
description: "test",
|
||||
type: "percentage",
|
||||
value: 10,
|
||||
allocation: "total",
|
||||
conditions: [
|
||||
{
|
||||
products: [product.id],
|
||||
operator: "in",
|
||||
},
|
||||
],
|
||||
},
|
||||
usage_limit: 10,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
const createdRule = response.data.discount.rule
|
||||
|
||||
try {
|
||||
await api.post(
|
||||
`/admin/discounts/${response.data.discount.id}?expand=rule,rule.conditions,rule.conditions.products`,
|
||||
{
|
||||
rule: {
|
||||
id: createdRule.id,
|
||||
type: createdRule.type,
|
||||
value: createdRule.value,
|
||||
allocation: createdRule.allocation,
|
||||
conditions: [
|
||||
{
|
||||
products: [anotherProduct.id],
|
||||
product_types: [product.type_id],
|
||||
operator: "in",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
Authorization: "Bearer test_token",
|
||||
},
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
expect(error.response.data.type).toEqual("invalid_data")
|
||||
expect(error.response.data.message).toEqual(
|
||||
`Only one of products, product_types is allowed, Only one of product_types, products is allowed`
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it("creates a discount and updates it", async () => {
|
||||
|
||||
Reference in New Issue
Block a user