diff --git a/integration-tests/api/__tests__/admin/price-list.js b/integration-tests/api/__tests__/admin/price-list.js deleted file mode 100644 index 9e06fe5128..0000000000 --- a/integration-tests/api/__tests__/admin/price-list.js +++ /dev/null @@ -1,1495 +0,0 @@ -const path = require("path") -const { Region } = require("@medusajs/medusa") - -const setupServer = require("../../../environment-helpers/setup-server") -const startServerWithEnvironment = - require("../../../environment-helpers/start-server-with-environment").default -const { useApi } = require("../../../environment-helpers/use-api") -const { useDb, initDb } = require("../../../environment-helpers/use-db") - -const { - simpleProductFactory, - simplePriceListFactory, - simpleRegionFactory, -} = require("../../../factories") -const adminSeeder = require("../../../helpers/admin-seeder") -const customerSeeder = require("../../../helpers/customer-seeder") -const priceListSeeder = require("../../../helpers/price-list-seeder") -const productSeeder = require("../../../helpers/product-seeder") - -const adminReqConfig = { - headers: { - "x-medusa-access-token": "test_token", - }, -} - -jest.setTimeout(50000) - -describe("/admin/price-lists", () => { - let medusaProcess - let dbConnection - - beforeAll(async () => { - const cwd = path.resolve(path.join(__dirname, "..", "..")) - dbConnection = await initDb({ cwd }) - medusaProcess = await setupServer({ cwd }) - }) - - afterAll(async () => { - const db = useDb() - await db.shutdown() - medusaProcess.kill() - }) - - describe("POST /admin/price-list", () => { - beforeEach(async () => { - await adminSeeder(dbConnection) - await customerSeeder(dbConnection) - await productSeeder(dbConnection) - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("creates a price list", async () => { - const api = useApi() - - const region = await simpleRegionFactory(dbConnection, { - id: "region-pl-infer-currency", - currency_code: "hrk", - }) - - const payload = { - name: "VIP Summer sale", - description: "Summer sale for VIP customers. 25% off selected items.", - type: "sale", - status: "active", - starts_at: "2022-07-01T00:00:00.000Z", - ends_at: "2022-07-31T00:00:00.000Z", - customer_groups: [ - { - id: "customer-group-1", - }, - ], - prices: [ - { - amount: 85, - currency_code: "usd", - variant_id: "test-variant", - }, - { - amount: 105, - region_id: region.id, - variant_id: "test-variant", - }, - ], - } - - const response = await api - .post("/admin/price-lists", payload, adminReqConfig) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data.price_list).toEqual( - expect.objectContaining({ - id: expect.any(String), - name: "VIP Summer sale", - description: "Summer sale for VIP customers. 25% off selected items.", - type: "sale", - status: "active", - starts_at: "2022-07-01T00:00:00.000Z", - ends_at: "2022-07-31T00:00:00.000Z", - customer_groups: [ - expect.objectContaining({ - id: "customer-group-1", - }), - ], - prices: [ - expect.objectContaining({ - id: expect.any(String), - amount: 85, - currency_code: "usd", - variant_id: "test-variant", - }), - expect.objectContaining({ - id: expect.any(String), - amount: 105, - currency_code: region.currency_code, - variant_id: "test-variant", - }), - ], - }) - ) - }) - }) - - describe("GET /admin/price-lists", () => { - beforeEach(async () => { - await adminSeeder(dbConnection) - await productSeeder(dbConnection) - await priceListSeeder(dbConnection) - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("returns a price list by :id", async () => { - const api = useApi() - - const response = await api - .get("/admin/price-lists/pl_no_customer_groups", adminReqConfig) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data.price_list).toMatchSnapshot({ - id: expect.any(String), - name: "VIP winter sale", - description: "Winter sale for VIP customers. 25% off selected items.", - type: "sale", - status: "active", - starts_at: "2022-07-01T00:00:00.000Z", - ends_at: "2022-07-31T00:00:00.000Z", - prices: [ - { - id: expect.any(String), - amount: 100, - currency_code: "usd", - min_quantity: 1, - max_quantity: 100, - variant_id: "test-variant", - price_list_id: "pl_no_customer_groups", - created_at: expect.any(String), - updated_at: expect.any(String), - variant: expect.any(Object), - variants: expect.any(Array), - }, - { - id: expect.any(String), - amount: 80, - currency_code: "usd", - min_quantity: 101, - max_quantity: 500, - variant_id: "test-variant", - price_list_id: "pl_no_customer_groups", - created_at: expect.any(String), - updated_at: expect.any(String), - variant: expect.any(Object), - variants: expect.any(Array), - }, - { - id: expect.any(String), - amount: 50, - currency_code: "usd", - min_quantity: 501, - max_quantity: 1000, - variant_id: "test-variant", - price_list_id: "pl_no_customer_groups", - created_at: expect.any(String), - updated_at: expect.any(String), - variant: expect.any(Object), - variants: expect.any(Array), - }, - ], - created_at: expect.any(String), - updated_at: expect.any(String), - }) - }) - - it("returns a list of price lists", async () => { - const api = useApi() - - const response = await api - .get("/admin/price-lists", adminReqConfig) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data.price_lists).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: "pl_no_customer_groups", - }), - ]) - ) - }) - - it("given a search query, returns matching results by name", async () => { - const api = useApi() - - const response = await api - .get("/admin/price-lists?q=winter", adminReqConfig) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data.price_lists).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - name: "VIP winter sale", - }), - ]) - ) - expect(response.data.count).toEqual(1) - }) - - it("given a search query, returns matching results by description", async () => { - const api = useApi() - - const response = await api - .get("/admin/price-lists?q=25%", adminReqConfig) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data.price_lists).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - name: "VIP winter sale", - description: - "Winter sale for VIP customers. 25% off selected items.", - }), - ]) - ) - expect(response.data.count).toEqual(1) - }) - - it("given a search query, returns empty list when does not exist", async () => { - const api = useApi() - - const response = await api - .get("/admin/price-lists?q=blablabla", adminReqConfig) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data.price_lists).toEqual([]) - expect(response.data.count).toEqual(0) - }) - - it("given a search query and a status filter not matching any price list, returns an empty set", async () => { - const api = useApi() - - const response = await api - .get("/admin/price-lists?q=vip&status[]=draft", adminReqConfig) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data.price_lists).toEqual([]) - expect(response.data.count).toEqual(0) - }) - - it("given a search query and a status filter matching a price list, returns a price list", async () => { - const api = useApi() - - const response = await api - .get("/admin/price-lists?q=vip&status[]=active", adminReqConfig) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data.price_lists).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - name: "VIP winter sale", - status: "active", - }), - ]) - ) - expect(response.data.count).toEqual(1) - }) - - it("lists only price lists with customer_group", async () => { - await customerSeeder(dbConnection) - - await simplePriceListFactory(dbConnection, { - id: "test-list-cgroup-1", - customer_groups: ["customer-group-1"], - }) - await simplePriceListFactory(dbConnection, { - id: "test-list-cgroup-2", - customer_groups: ["customer-group-2"], - }) - await simplePriceListFactory(dbConnection, { - id: "test-list-cgroup-3", - customer_groups: ["customer-group-3"], - }) - await simplePriceListFactory(dbConnection, { - id: "test-list-no-cgroup", - }) - - const api = useApi() - - const response = await api - .get( - `/admin/price-lists?customer_groups[]=customer-group-1,customer-group-2`, - adminReqConfig - ) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data.price_lists.length).toEqual(2) - expect(response.data.price_lists).toHaveLength(2) - expect(response.data.price_lists).toEqual( - expect.arrayContaining([ - expect.objectContaining({ id: "test-list-cgroup-1" }), - expect.objectContaining({ id: "test-list-cgroup-2" }), - ]) - ) - }) - }) - - describe("POST /admin/price-lists/:id", () => { - beforeEach(async () => { - await adminSeeder(dbConnection) - await customerSeeder(dbConnection) - await productSeeder(dbConnection) - await priceListSeeder(dbConnection) - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("removes configuration with update", async () => { - const priceList = await simplePriceListFactory(dbConnection, { - ends_at: new Date(), - starts_at: new Date(), - customer_groups: ["customer-group-1"], - }) - - const api = useApi() - const getResult = await api.get( - `/admin/price-lists/${priceList.id}`, - adminReqConfig - ) - - expect(getResult.status).toEqual(200) - expect(getResult.data.price_list.starts_at).toBeTruthy() - expect(getResult.data.price_list.ends_at).toBeTruthy() - expect(getResult.data.price_list.customer_groups.length).toEqual(1) - - const updateResult = await api.post( - `/admin/price-lists/${priceList.id}`, - { ends_at: null, starts_at: null, customer_groups: [] }, - adminReqConfig - ) - - expect(updateResult.status).toEqual(200) - expect(updateResult.data.price_list.starts_at).toBeFalsy() - expect(updateResult.data.price_list.ends_at).toBeFalsy() - expect(updateResult.data.price_list.customer_groups.length).toEqual(0) - }) - - it("updates a price list", async () => { - const api = useApi() - - const payload = { - name: "Loyalty Reward - Winter Sale", - description: "Winter sale for our most loyal customers", - type: "sale", - status: "draft", - starts_at: "2022-09-01T00:00:00.000Z", - ends_at: "2022-12-31T00:00:00.000Z", - customer_groups: [ - { - id: "customer-group-1", - }, - ], - prices: [ - { - amount: 85, - currency_code: "usd", - variant_id: "test-variant_1", - }, - { - amount: 10, - currency_code: "usd", - variant_id: "test-variant", - }, - ], - } - - const response = await api - .post( - "/admin/price-lists/pl_no_customer_groups", - payload, - adminReqConfig - ) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data.price_list).toMatchSnapshot({ - id: "pl_no_customer_groups", - name: "Loyalty Reward - Winter Sale", - description: "Winter sale for our most loyal customers", - type: "sale", - status: "draft", - starts_at: "2022-09-01T00:00:00.000Z", - ends_at: "2022-12-31T00:00:00.000Z", - prices: expect.arrayContaining([ - { - id: expect.any(String), - amount: 100, - currency_code: "usd", - min_quantity: 1, - max_quantity: 100, - variant_id: "test-variant", - price_list_id: "pl_no_customer_groups", - region_id: null, - created_at: expect.any(String), - updated_at: expect.any(String), - variant: expect.any(Object), - variants: expect.any(Array), - deleted_at: null, - }, - { - id: expect.any(String), - amount: 80, - currency_code: "usd", - min_quantity: 101, - max_quantity: 500, - variant_id: "test-variant", - variant: expect.any(Object), - variants: expect.any(Array), - price_list_id: "pl_no_customer_groups", - region_id: null, - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - }, - { - id: expect.any(String), - amount: 50, - currency_code: "usd", - min_quantity: 501, - max_quantity: 1000, - variant_id: "test-variant", - variant: expect.any(Object), - variants: expect.any(Array), - price_list_id: "pl_no_customer_groups", - region_id: null, - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - }, - { - id: expect.any(String), - amount: 85, - currency_code: "usd", - variant_id: "test-variant_1", - variant: expect.any(Object), - variants: expect.any(Array), - price_list_id: "pl_no_customer_groups", - min_quantity: null, - max_quantity: null, - region_id: null, - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - }, - { - id: expect.any(String), - amount: 10, - currency_code: "usd", - variant_id: "test-variant", - variant: expect.any(Object), - variants: expect.any(Array), - price_list_id: "pl_no_customer_groups", - min_quantity: null, - max_quantity: null, - region_id: null, - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - }, - ]), - customer_groups: [ - { - id: "customer-group-1", - created_at: expect.any(String), - updated_at: expect.any(String), - }, - ], - created_at: expect.any(String), - updated_at: expect.any(String), - }) - }) - - it("updates the amount and currency of a price in the price list", async () => { - const api = useApi() - - const payload = { - prices: [ - { - id: "ma_test_1", - amount: 250, - currency_code: "eur", - variant_id: "test-variant", - }, - ], - } - - const response = await api - .post( - "/admin/price-lists/pl_no_customer_groups", - payload, - adminReqConfig - ) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - - const updatedPrice = response.data.price_list.prices.find( - (p) => p.id === "ma_test_1" - ) - - expect(updatedPrice).toMatchSnapshot({ - id: "ma_test_1", - amount: 250, - currency_code: "eur", - min_quantity: 1, - max_quantity: 100, - variant_id: "test-variant", - price_list_id: "pl_no_customer_groups", - region_id: null, - created_at: expect.any(String), - updated_at: expect.any(String), - variant: expect.any(Object), - variants: expect.any(Array), - }) - }) - - it("updates price list prices (inser a new MA for a specific region)", async () => { - const api = useApi() - - const payload = { - prices: [ - // update MA - { - id: "ma_test_4", - amount: 1001, - currency_code: "usd", - variant_id: "test-variant", - }, - // create MA - { - amount: 101, - variant_id: "test-variant", - region_id: "region-pl", - }, - ], - } - - const response = await api - .post("/admin/price-lists/pl_with_some_ma", payload, adminReqConfig) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - - expect(response.data.price_list.prices.length).toEqual(2) - expect(response.data.price_list.prices).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: expect.any(String), - currency_code: "eur", - amount: 101, - min_quantity: null, - max_quantity: null, - price_list_id: "pl_with_some_ma", - variant_id: "test-variant", - region_id: "region-pl", - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - }), - expect.objectContaining({ - id: "ma_test_4", - currency_code: "usd", - amount: 1001, - price_list_id: "pl_with_some_ma", - variant_id: "test-variant", - region_id: null, - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - }), - ]) - ) - }) - }) - - describe("POST /admin/price-lists/:id/prices/batch", () => { - beforeEach(async () => { - await adminSeeder(dbConnection) - await productSeeder(dbConnection) - await priceListSeeder(dbConnection) - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("Adds a batch of new prices to a price list without overriding existing prices", async () => { - const api = useApi() - - const payload = { - prices: [ - { - amount: 45, - currency_code: "usd", - variant_id: "test-variant", - min_quantity: 1001, - max_quantity: 2000, - }, - { - amount: 35, - currency_code: "usd", - variant_id: "test-variant", - min_quantity: 2001, - max_quantity: 3000, - }, - { - amount: 25, - currency_code: "usd", - variant_id: "test-variant", - min_quantity: 3001, - max_quantity: 4000, - }, - ], - } - - const response = await api - .post( - "/admin/price-lists/pl_no_customer_groups/prices/batch", - payload, - adminReqConfig - ) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data.price_list.prices.length).toEqual(6) - expect( - response.data.price_list.prices.sort((a, b) => b.amount - a.amount) - ).toMatchSnapshot([ - { - id: expect.any(String), - price_list_id: "pl_no_customer_groups", - amount: 100, - currency_code: "usd", - min_quantity: 1, - max_quantity: 100, - variant_id: "test-variant", - created_at: expect.any(String), - updated_at: expect.any(String), - variant: expect.any(Object), - variants: expect.any(Array), - }, - { - id: expect.any(String), - price_list_id: "pl_no_customer_groups", - amount: 80, - currency_code: "usd", - min_quantity: 101, - max_quantity: 500, - variant_id: "test-variant", - created_at: expect.any(String), - updated_at: expect.any(String), - variant: expect.any(Object), - variants: expect.any(Array), - }, - { - id: expect.any(String), - price_list_id: "pl_no_customer_groups", - amount: 50, - currency_code: "usd", - min_quantity: 501, - max_quantity: 1000, - variant_id: "test-variant", - created_at: expect.any(String), - updated_at: expect.any(String), - variant: expect.any(Object), - variants: expect.any(Array), - }, - { - id: expect.any(String), - price_list_id: "pl_no_customer_groups", - amount: 45, - currency_code: "usd", - variant_id: "test-variant", - min_quantity: 1001, - max_quantity: 2000, - created_at: expect.any(String), - updated_at: expect.any(String), - variant: expect.any(Object), - variants: expect.any(Array), - }, - { - id: expect.any(String), - price_list_id: "pl_no_customer_groups", - amount: 35, - currency_code: "usd", - variant_id: "test-variant", - min_quantity: 2001, - max_quantity: 3000, - created_at: expect.any(String), - updated_at: expect.any(String), - variant: expect.any(Object), - variants: expect.any(Array), - }, - { - id: expect.any(String), - price_list_id: "pl_no_customer_groups", - amount: 25, - currency_code: "usd", - variant_id: "test-variant", - min_quantity: 3001, - max_quantity: 4000, - created_at: expect.any(String), - updated_at: expect.any(String), - variant: expect.any(Object), - variants: expect.any(Array), - }, - ]) - }) - - it("Adds a batch of new prices to a price list overriding existing prices", async () => { - const api = useApi() - - const payload = { - prices: [ - { - amount: 45, - currency_code: "usd", - variant_id: "test-variant", - min_quantity: 1001, - max_quantity: 2000, - }, - { - amount: 35, - currency_code: "usd", - variant_id: "test-variant", - min_quantity: 2001, - max_quantity: 3000, - }, - { - amount: 25, - currency_code: "usd", - variant_id: "test-variant", - min_quantity: 3001, - max_quantity: 4000, - }, - ], - override: true, - } - - const response = await api - .post( - "/admin/price-lists/pl_no_customer_groups/prices/batch", - payload, - adminReqConfig - ) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data.price_list.prices.length).toEqual(3) - expect(response.data.price_list.prices).toMatchSnapshot([ - { - id: expect.any(String), - price_list_id: "pl_no_customer_groups", - amount: 45, - currency_code: "usd", - variant_id: "test-variant", - min_quantity: 1001, - max_quantity: 2000, - created_at: expect.any(String), - updated_at: expect.any(String), - variant: expect.any(Object), - variants: expect.any(Array), - }, - { - id: expect.any(String), - price_list_id: "pl_no_customer_groups", - amount: 35, - currency_code: "usd", - variant_id: "test-variant", - min_quantity: 2001, - max_quantity: 3000, - created_at: expect.any(String), - updated_at: expect.any(String), - variant: expect.any(Object), - variants: expect.any(Array), - }, - { - id: expect.any(String), - price_list_id: "pl_no_customer_groups", - amount: 25, - currency_code: "usd", - variant_id: "test-variant", - min_quantity: 3001, - max_quantity: 4000, - created_at: expect.any(String), - updated_at: expect.any(String), - variant: expect.any(Object), - variants: expect.any(Array), - }, - ]) - }) - - it("Adds a batch of new prices where a MA record have a `region_id` instead of `currency_code`", async () => { - const api = useApi() - - const payload = { - prices: [ - { - amount: 100, - variant_id: "test-variant", - region_id: "region-pl", - }, - { - amount: 200, - variant_id: "test-variant", - currency_code: "usd", - }, - ], - } - - const response = await api - .post( - "/admin/price-lists/pl_with_some_ma/prices/batch", - payload, - adminReqConfig - ) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - - expect(response.data.price_list.prices.length).toEqual(3) // initially this PL has 1 MA record - expect(response.data.price_list.prices).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: "ma_test_4", - currency_code: "usd", - amount: 70, - price_list_id: "pl_with_some_ma", - variant_id: "test-variant", - region_id: null, - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - }), - expect.objectContaining({ - id: expect.any(String), - currency_code: "usd", - amount: 200, - min_quantity: null, - max_quantity: null, - price_list_id: "pl_with_some_ma", - variant_id: "test-variant", - region_id: null, - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - }), - expect.objectContaining({ - id: expect.any(String), - currency_code: "eur", - amount: 100, - min_quantity: null, - max_quantity: null, - price_list_id: "pl_with_some_ma", - variant_id: "test-variant", - region_id: "region-pl", - created_at: expect.any(String), - updated_at: expect.any(String), - deleted_at: null, - }), - ]) - ) - }) - }) - - describe("DELETE /admin/price-lists/:id", () => { - beforeEach(async () => { - await adminSeeder(dbConnection) - await productSeeder(dbConnection) - await priceListSeeder(dbConnection) - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("Deletes a price list", async () => { - const api = useApi() - - const response = await api - .delete("/admin/price-lists/pl_no_customer_groups", adminReqConfig) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data).toEqual({ - id: "pl_no_customer_groups", - object: "price-list", - deleted: true, - }) - - try { - await api.get( - "/admin/price-lists/pl_no_customer_groups", - adminReqConfig - ) - } catch (error) { - expect(error.response.status).toBe(404) - expect(error.response.data.message).toEqual( - "Price list with id: pl_no_customer_groups was not found" - ) - } - }) - }) - - describe("tests cascade on delete", () => { - beforeEach(async () => { - await adminSeeder(dbConnection) - await productSeeder(dbConnection) - await priceListSeeder(dbConnection) - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("Deletes a variant and ensures that prices associated with the variant are deleted from PriceList", async () => { - const api = useApi() - - await api - .delete( - "/admin/products/test-product/variants/test-variant", - adminReqConfig - ) - .catch((err) => { - console.warn(err.response.data) - }) - - const response = await api.get( - "/admin/price-lists/pl_no_customer_groups", - adminReqConfig - ) - - expect(response.status).toEqual(200) - expect(response.data.price_list.prices.length).toEqual(0) - }) - }) - - describe("DELETE /admin/price-lists/:id/prices/batch", () => { - beforeEach(async () => { - await adminSeeder(dbConnection) - await productSeeder(dbConnection) - await priceListSeeder(dbConnection) - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("Deletes several prices associated with a price list", async () => { - const api = useApi() - - const response = await api - .delete("/admin/price-lists/pl_no_customer_groups/prices/batch", { - ...adminReqConfig, - data: { - price_ids: ["ma_test_1", "ma_test_2"], - }, - }) - .catch((err) => { - console.warn(err.response.data) - }) - - const getPriceListResponse = await api - .get("/admin/price-lists/pl_no_customer_groups", adminReqConfig) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data).toEqual({ - ids: ["ma_test_1", "ma_test_2"], - object: "money-amount", - deleted: true, - }) - expect(getPriceListResponse.data.price_list.prices.length).toEqual(1) - expect(getPriceListResponse.data.price_list.prices[0].id).toEqual( - "ma_test_3" - ) - }) - }) - - describe("GET /admin/price-lists/:id/products", () => { - let tag - beforeEach(async () => { - await adminSeeder(dbConnection) - - await simpleProductFactory( - dbConnection, - { - id: "test-prod-1", - title: "MedusaHeadphones", - variants: [{ id: "test-variant-1" }, { id: "test-variant-2" }], - }, - 1 - ) - - const prod = await simpleProductFactory( - dbConnection, - { - id: "test-prod-2", - title: "MedusaShoes", - tags: ["test-tag"], - variants: [{ id: "test-variant-3" }, { id: "test-variant-4" }], - }, - 2 - ) - - tag = prod.tags[0].id - - await simpleProductFactory( - dbConnection, - { - id: "test-prod-3", - title: "MedusaShirt", - variants: [{ id: "test-variant-5" }], - }, - 3 - ) - - // Used to validate that products that are not associated with the price list are not returned - await simpleProductFactory( - dbConnection, - { - id: "test-prod-4", - title: "OtherHeadphones", - variants: [{ id: "test-variant-6" }], - }, - 4 - ) - - await simplePriceListFactory(dbConnection, { - id: "test-list", - customer_groups: ["test-group"], - prices: [ - { variant_id: "test-variant-1", currency_code: "usd", amount: 150 }, - { variant_id: "test-variant-4", currency_code: "usd", amount: 150 }, - ], - }) - await simplePriceListFactory(dbConnection, { - id: "test-list-2", - prices: [ - { variant_id: "test-variant-1", currency_code: "usd", amount: 200 }, - { variant_id: "test-variant-4", currency_code: "usd", amount: 200 }, - ], - }) - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("lists only product 1, 2 with price list prices", async () => { - const api = useApi() - - const response = await api - .get( - `/admin/price-lists/test-list/products?order=-created_at`, - adminReqConfig - ) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data.count).toEqual(2) - expect(response.data.products).toHaveLength(2) - expect(response.data.products).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: "test-prod-1", - variants: expect.arrayContaining([ - expect.objectContaining({ - id: "test-variant-1", - prices: expect.arrayContaining([ - expect.objectContaining({ - currency_code: "usd", - amount: 100, - }), - expect.objectContaining({ - currency_code: "usd", - amount: 150, - price_list_id: "test-list", - }), - ]), - }), - expect.objectContaining({ - id: "test-variant-2", - prices: expect.arrayContaining([ - expect.objectContaining({ - currency_code: "usd", - amount: 100, - }), - ]), - }), - ]), - }), - expect.objectContaining({ - id: "test-prod-2", - variants: expect.arrayContaining([ - expect.objectContaining({ - id: "test-variant-3", - prices: expect.arrayContaining([ - expect.objectContaining({ - currency_code: "usd", - amount: 100, - }), - ]), - }), - expect.objectContaining({ - id: "test-variant-4", - prices: expect.arrayContaining([ - expect.objectContaining({ - currency_code: "usd", - amount: 100, - }), - expect.objectContaining({ - currency_code: "usd", - amount: 150, - price_list_id: "test-list", - }), - ]), - }), - ]), - }), - ]) - ) - }) - - it("lists only product 2", async () => { - const api = useApi() - - const response = await api - .get( - `/admin/price-lists/test-list/products?tags[]=${tag}`, - adminReqConfig - ) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data.count).toEqual(1) - expect(response.data.products).toHaveLength(1) - expect(response.data.products).toEqual( - expect.arrayContaining([expect.objectContaining({ id: "test-prod-2" })]) - ) - }) - - it("lists products using free text search", async () => { - const api = useApi() - - const response = await api - .get( - `/admin/price-lists/test-list/products?q=Headphones`, - adminReqConfig - ) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data.count).toEqual(1) - expect(response.data.products).toHaveLength(1) - expect(response.data.products).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - id: "test-prod-1", - title: "MedusaHeadphones", - }), - ]) - ) - }) - }) - - describe("delete prices from price list related to the specified product or variant", () => { - let product1 - let product2 - - function getCustomPriceIdFromVariant(variantId, index) { - return "ma_" + index + "_" + variantId - } - - beforeEach(async () => { - await adminSeeder(dbConnection) - - product1 = await simpleProductFactory( - dbConnection, - { - id: "test-prod-1", - title: "some product", - variants: [ - { - id: `simple-test-variant-${Math.random() * 1000}`, - title: "Test", - prices: [{ currency: "usd", amount: 100 }], - }, - { - id: `simple-test-variant-${Math.random() * 1000}`, - title: "Test 2", - prices: [{ currency: "usd", amount: 200 }], - }, - ], - }, - 1 - ) - - product2 = await simpleProductFactory( - dbConnection, - { - id: "test-prod-2", - title: "some product 2", - }, - 2 - ) - - await simplePriceListFactory(dbConnection, { - id: "test-list", - customer_groups: ["test-group"], - prices: [ - ...product1.variants.map((variant, i) => ({ - id: getCustomPriceIdFromVariant(variant.id, i), - variant_id: variant.id, - currency_code: "usd", - amount: (i + 1) * 150, - })), - ...product2.variants.map((variant, i) => ({ - id: getCustomPriceIdFromVariant(variant.id, i), - variant_id: variant.id, - currency_code: "usd", - amount: (i + 1) * 150, - })), - ], - }) - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("should delete all the prices that are part of the price list for the specified product", async () => { - const api = useApi() - - let response = await api.get( - "/admin/price-lists/test-list", - adminReqConfig - ) - - expect(response.status).toBe(200) - expect(response.data.price_list.prices.length).toBe(3) - - response = await api.delete( - `/admin/price-lists/test-list/products/${product1.id}/prices`, - adminReqConfig - ) - - expect(response.status).toBe(200) - expect(response.data).toEqual({ - ids: expect.arrayContaining( - product1.variants.map((variant, i) => { - return getCustomPriceIdFromVariant(variant.id, i) - }) - ), - object: "money-amount", - deleted: true, - }) - - response = await api.get("/admin/price-lists/test-list", adminReqConfig) - - expect(response.status).toBe(200) - expect(response.data.price_list.prices.length).toBe(1) - }) - - it("should delete all the prices that are part of the price list for the specified variant", async () => { - const api = useApi() - - let response = await api.get( - "/admin/price-lists/test-list", - adminReqConfig - ) - - expect(response.status).toBe(200) - expect(response.data.price_list.prices.length).toBe(3) - - const variant = product2.variants[0] - response = await api.delete( - `/admin/price-lists/test-list/variants/${variant.id}/prices`, - adminReqConfig - ) - - expect(response.status).toBe(200) - expect(response.data).toEqual({ - ids: [getCustomPriceIdFromVariant(variant.id, 0)], - object: "money-amount", - deleted: true, - }) - - response = await api.get("/admin/price-lists/test-list", adminReqConfig) - - expect(response.status).toBe(200) - expect(response.data.price_list.prices.length).toBe(2) - }) - }) -}) - -describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/price-lists", () => { - let medusaProcess - let dbConnection - - beforeAll(async () => { - const cwd = path.resolve(path.join(__dirname, "..", "..")) - const [process, connection] = await startServerWithEnvironment({ - cwd, - env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true }, - }) - dbConnection = connection - medusaProcess = process - }) - - afterAll(async () => { - const db = useDb() - await db.shutdown() - - medusaProcess.kill() - }) - - describe("POST /admin/price-list", () => { - const priceListIncludesTaxId = "price-list-1-includes-tax" - - beforeEach(async () => { - try { - await adminSeeder(dbConnection) - await customerSeeder(dbConnection) - await productSeeder(dbConnection) - await simplePriceListFactory(dbConnection, { - id: priceListIncludesTaxId, - }) - } catch (err) { - console.log(err) - throw err - } - }) - - afterEach(async () => { - const db = useDb() - await db.teardown() - }) - - it("should creates a price list that includes tax", async () => { - const api = useApi() - - const payload = { - name: "VIP Summer sale", - description: "Summer sale for VIP customers. 25% off selected items.", - type: "sale", - status: "active", - starts_at: "2022-07-01T00:00:00.000Z", - ends_at: "2022-07-31T00:00:00.000Z", - customer_groups: [ - { - id: "customer-group-1", - }, - ], - prices: [ - { - amount: 85, - currency_code: "usd", - variant_id: "test-variant", - }, - ], - includes_tax: true, - } - - const response = await api - .post("/admin/price-lists", payload, adminReqConfig) - .catch((err) => { - console.warn(err.response.data) - }) - - expect(response.status).toEqual(200) - expect(response.data.price_list).toEqual( - expect.objectContaining({ - id: expect.any(String), - includes_tax: true, - }) - ) - }) - - it("should update a price list that include_tax", async () => { - const api = useApi() - - let response = await api - .get(`/admin/price-lists/${priceListIncludesTaxId}`, adminReqConfig) - .catch((err) => { - console.log(err) - }) - - expect(response.data.price_list.includes_tax).toBe(false) - - response = await api - .post( - `/admin/price-lists/${priceListIncludesTaxId}`, - { includes_tax: true }, - adminReqConfig - ) - .catch((err) => { - console.log(err) - }) - - expect(response.data.price_list.includes_tax).toBe(true) - }) - }) -}) diff --git a/integration-tests/helpers/admin-variants-seeder.js b/integration-tests/helpers/admin-variants-seeder.js deleted file mode 100644 index 1f1153a399..0000000000 --- a/integration-tests/helpers/admin-variants-seeder.js +++ /dev/null @@ -1,241 +0,0 @@ -const { ProductVariantMoneyAmount } = require("@medusajs/medusa") -const { MoneyAmount } = require("@medusajs/medusa") -const { - Region, - Product, - ProductVariant, - PriceList, - CustomerGroup, - Customer, - Image, - ShippingProfile, - ProductCollection, - ProductOption, -} = require("@medusajs/medusa") - -module.exports = async (dataSource, data = {}) => { - const manager = dataSource.manager - - const yesterday = ((today) => new Date(today.setDate(today.getDate() - 1)))( - new Date() - ) - - const tenDaysAgo = ((today) => new Date(today.setDate(today.getDate() - 10)))( - new Date() - ) - - const defaultProfile = await manager.findOne(ShippingProfile, { - where: { type: ShippingProfile.default }, - }) - - const collection = manager.create(ProductCollection, { - id: "test-collection", - handle: "test-collection", - title: "Test collection", - }) - - await manager.save(collection) - - await manager.insert(Region, { - id: "reg-europe", - name: "Test Region Europe", - currency_code: "eur", - tax_rate: 0, - }) - - await manager.insert(Region, { - id: "reg-us", - name: "Test Region US", - currency_code: "usd", - tax_rate: 0, - }) - - const customer = manager.create(Customer, { - id: "test-customer", - email: "john@doe.com", - first_name: "John", - last_name: "Doe", - password_hash: - "c2NyeXB0AAEAAAABAAAAAVMdaddoGjwU1TafDLLlBKnOTQga7P2dbrfgf3fB+rCD/cJOMuGzAvRdKutbYkVpuJWTU39P7OpuWNkUVoEETOVLMJafbI8qs8Qx/7jMQXkN", // password matching "test" - has_account: true, - }) - - const customerGroup = manager.create(CustomerGroup, { - id: "test-group", - name: "test-group", - }) - - await manager.save(customerGroup) - customer.groups = [customerGroup] - await manager.save(customer) - - const priceListActive = manager.create(PriceList, { - id: "pl", - name: "VIP sale", - description: "All year sale for VIP customers.", - type: "sale", - status: "active", - }) - - await manager.save(priceListActive) - - const priceListExpired = manager.create(PriceList, { - id: "pl_expired", - name: "VIP summer sale", - description: "Summer sale for VIP customers.", - type: "sale", - status: "active", - starts_at: tenDaysAgo, - ends_at: yesterday, - }) - - await manager.save(priceListExpired) - - const priceListWithCustomers = manager.create(PriceList, { - id: "pl_with_customers", - name: "VIP winter sale", - description: "Winter sale for VIP customers.", - type: "sale", - status: "active", - }) - - priceListWithCustomers.customer_groups = [customerGroup] - - await manager.save(priceListWithCustomers) - - const productMultiReg = manager.create(Product, { - id: "test-product", - handle: "test-product-reg", - title: "Multi Reg Test product", - profile_id: defaultProfile.id, - profiles: [{ id: defaultProfile.id }], - description: "test-product-description", - status: "published", - collection_id: "test-collection", - }) - - const image = manager.create(Image, { - id: "test-image", - url: "test-image.png", - }) - - productMultiReg.images = [image] - - await manager.save(productMultiReg) - - await manager.save(ProductOption, { - id: "test-option", - title: "test-option", - product_id: "test-product", - }) - - const variantMultiReg = manager.create(ProductVariant, { - id: "test-variant", - inventory_quantity: 10, - title: "Test variant", - variant_rank: 0, - sku: "test-sku", - ean: "test-ean", - upc: "test-upc", - barcode: "test-barcode", - product_id: "test-product", - options: [ - { - id: "test-variant-option-reg", - value: "Default variant", - option_id: "test-option", - }, - ], - }) - - await manager.insert(MoneyAmount, { - id: "test-price-multi-usd", - currency_code: "usd", - amount: 100, - }) - - await manager.insert(ProductVariantMoneyAmount, { - id: "pvma", - money_amount_id: "test-price-multi-usd", - variant_id: "test-variant", - }) - - await manager.insert(MoneyAmount, { - id: "test-price-discount-multi-usd", - currency_code: "usd", - amount: 80, - price_list_id: "pl", - }) - - await manager.insert(ProductVariantMoneyAmount, { - id: "pvma1", - money_amount_id: "test-price-discount-multi-usd", - variant_id: "test-variant", - }) - - await manager.insert(MoneyAmount, { - id: "test-price-discount-expired-multi-usd", - currency_code: "usd", - amount: 70, - price_list_id: "pl_expired", - }) - - await manager.insert(ProductVariantMoneyAmount, { - id: "pvma2", - money_amount_id: "test-price-discount-expired-multi-usd", - variant_id: "test-variant", - }) - - await manager.insert(MoneyAmount, { - id: "test-price-multi-eur", - currency_code: "eur", - amount: 100, - }) - - await manager.insert(ProductVariantMoneyAmount, { - id: "pvma3", - money_amount_id: "test-price-multi-eur", - variant_id: "test-variant", - }) - - await manager.insert(MoneyAmount, { - id: "test-price-discount-multi-eur", - currency_code: "eur", - amount: 80, - price_list_id: "pl", - }) - - await manager.insert(ProductVariantMoneyAmount, { - id: "pvma4", - money_amount_id: "test-price-discount-multi-eur", - variant_id: "test-variant", - }) - - await manager.insert(MoneyAmount, { - id: "test-price-discount-multi-eur-with-customer", - currency_code: "eur", - amount: 40, - price_list_id: "pl_with_customers", - }) - - await manager.insert(ProductVariantMoneyAmount, { - id: "pvma5", - money_amount_id: "test-price-discount-multi-eur-with-customer", - variant_id: "test-variant", - }) - - await manager.insert(MoneyAmount, { - id: "test-price-discount-expired-multi-eur", - currency_code: "eur", - amount: 70, - price_list_id: "pl_expired", - }) - - await manager.insert(ProductVariantMoneyAmount, { - id: "pvma6", - money_amount_id: "test-price-discount-expired-multi-eur", - variant_id: "test-variant", - }) - - await manager.save(variantMultiReg) -} diff --git a/integration-tests/helpers/customer-seeder.js b/integration-tests/helpers/customer-seeder.js deleted file mode 100644 index ab7ef6dddc..0000000000 --- a/integration-tests/helpers/customer-seeder.js +++ /dev/null @@ -1,113 +0,0 @@ -const { Customer, Address, CustomerGroup } = require("@medusajs/medusa") - -module.exports = async (dataSource, data = {}) => { - const manager = dataSource.manager - - const testAddr = manager.create(Address, { - id: "test-address", - first_name: "Lebron", - last_name: "James", - }) - - await manager.save(testAddr) - - const customer = manager.create(Customer, { - id: "test-customer-1", - email: "test1@email.com", - }) - - customer.billing_address = testAddr - customer.shipping_addresses = [testAddr] - await manager.save(customer) - - await manager.insert(Customer, { - id: "test-customer-2", - email: "test2@email.com", - }) - - await manager.insert(Customer, { - id: "test-customer-3", - email: "test3@email.com", - }) - - await manager.insert(Customer, { - id: "test-customer-has_account", - email: "test4@email.com", - has_account: true, - }) - - const customer5 = manager.create(Customer, { - id: "test-customer-5", - email: "test5@email.com", - }) - await manager.save(customer5) - - const customer6 = manager.create(Customer, { - id: "test-customer-6", - email: "test6@email.com", - }) - await manager.save(customer6) - - const customer7 = manager.create(Customer, { - id: "test-customer-7", - email: "test7@email.com", - }) - await manager.save(customer7) - - const deletionCustomer = manager.create(Customer, { - id: "test-customer-delete-cg", - email: "test-deletetion-cg@email.com", - }) - await manager.save(deletionCustomer) - - await manager.insert(CustomerGroup, { - id: "customer-group-1", - name: "vip-customers", - }) - - await manager.insert(CustomerGroup, { - id: "customer-group-2", - name: "test-group-2", - metadata: { data1: "value1" }, - }) - - await manager.insert(CustomerGroup, { - id: "customer-group-3", - name: "test-group-3", - }) - - await manager.insert(CustomerGroup, { - id: "test-group-4", - name: "test-group-4", - }) - - const c_group_5 = manager.create(CustomerGroup, { - id: "test-group-5", - name: "test-group-5", - }) - await manager.save(c_group_5) - - const c_group_6 = manager.create(CustomerGroup, { - id: "test-group-6", - name: "test-group-6", - }) - await manager.save(c_group_6) - - customer5.groups = [c_group_5] - await manager.save(customer5) - - customer6.groups = [c_group_5] - await manager.save(customer6) - - customer7.groups = [c_group_5, c_group_6] - await manager.save(customer7) - - const c_group_delete = manager.create(CustomerGroup, { - id: "test-group-delete", - name: "test-group-delete", - }) - await manager.save(c_group_delete) - - deletionCustomer.groups = [c_group_delete] - await manager.save(deletionCustomer) -} diff --git a/integration-tests/helpers/fixtures.ts b/integration-tests/helpers/fixtures.ts index cd05c87dd5..1f1c2ae4a8 100644 --- a/integration-tests/helpers/fixtures.ts +++ b/integration-tests/helpers/fixtures.ts @@ -40,3 +40,20 @@ export const getProductFixture = ( ], ...(overrides ?? {}), }) + +export const getPricelistFixture = (overrides: Partial) => { + return { + // BREAKING: `name` field was renamed to `title` + title: "Winter sales", + description: "Winter sale. 25% off selected items.", + type: "sale", + status: "active", + starts_at: "2022-07-01T00:00:00.000Z", + ends_at: "2022-07-31T00:00:00.000Z", + // BREAKING: There is no explicit customer_groups field in v2, we use rules instead + // BREAKING: Variant ID is required in prices + // BREAKING: Currency code is required in prices + prices: [], + ...overrides, + } +} diff --git a/integration-tests/helpers/price-list-seeder.js b/integration-tests/helpers/price-list-seeder.js deleted file mode 100644 index 459c405bab..0000000000 --- a/integration-tests/helpers/price-list-seeder.js +++ /dev/null @@ -1,101 +0,0 @@ -const { ProductVariantMoneyAmount } = require("@medusajs/medusa") -const { Region, PriceList, MoneyAmount } = require("@medusajs/medusa") - -module.exports = async (dataSource, data = {}) => { - const manager = dataSource.manager - - const priceListNoCustomerGroups = manager.create(PriceList, { - id: "pl_no_customer_groups", - name: "VIP winter sale", - description: "Winter sale for VIP customers. 25% off selected items.", - type: "sale", - status: "active", - starts_at: "2022-07-01T00:00:00.000Z", - ends_at: "2022-07-31T00:00:00.000Z", - }) - - await manager.save(priceListNoCustomerGroups) - - await manager.insert(Region, { - id: "region-pl", - name: "Test Region", - currency_code: "eur", - tax_rate: 0, - }) - - const moneyAmount1 = manager.create(MoneyAmount, { - id: "ma_test_1", - amount: 100, - currency_code: "usd", - min_quantity: 1, - max_quantity: 100, - price_list_id: "pl_no_customer_groups", - }) - - await manager.save(moneyAmount1) - - const moneyAmount2 = manager.create(MoneyAmount, { - id: "ma_test_2", - amount: 80, - currency_code: "usd", - min_quantity: 101, - max_quantity: 500, - price_list_id: "pl_no_customer_groups", - }) - - await manager.save(moneyAmount2) - - const moneyAmount3 = manager.create(MoneyAmount, { - id: "ma_test_3", - amount: 50, - currency_code: "usd", - min_quantity: 501, - max_quantity: 1000, - price_list_id: "pl_no_customer_groups", - }) - - await manager.save(moneyAmount3) - - const moneyAmount4 = manager.create(MoneyAmount, { - id: "ma_test_4", - amount: 70, - currency_code: "usd", - }) - - await manager.save(moneyAmount4) - - await manager.insert(ProductVariantMoneyAmount, { - id: "pvma1-pl", - money_amount_id: "ma_test_1", - variant_id: "test-variant", - }) - await manager.insert(ProductVariantMoneyAmount, { - id: "pvma2-pl", - money_amount_id: "ma_test_2", - variant_id: "test-variant", - }) - await manager.insert(ProductVariantMoneyAmount, { - id: "pvma3-pl", - money_amount_id: "ma_test_3", - variant_id: "test-variant", - }) - await manager.insert(ProductVariantMoneyAmount, { - id: "pvma4-pl", - money_amount_id: "ma_test_4", - variant_id: "test-variant", - }) - - const priceListWithMA = manager.create(PriceList, { - id: "pl_with_some_ma", - name: "Weeken sale", - description: "Desc. of the list", - type: "sale", - status: "active", - starts_at: "2022-07-01T00:00:00.000Z", - ends_at: "2022-07-31T00:00:00.000Z", - }) - - priceListWithMA.prices = [moneyAmount4] - - await manager.save(priceListWithMA) -} diff --git a/integration-tests/helpers/shipping-option-seeder.js b/integration-tests/helpers/shipping-option-seeder.js deleted file mode 100644 index 79b9ce6130..0000000000 --- a/integration-tests/helpers/shipping-option-seeder.js +++ /dev/null @@ -1,84 +0,0 @@ -const { - Region, - ShippingProfile, - ShippingOption, - ShippingOptionRequirement, - ShippingProfileType, -} = require("@medusajs/medusa") - -module.exports = async (dataSource, data = {}) => { - const manager = dataSource.manager - - await manager.insert(Region, { - id: "region", - name: "Test Region", - currency_code: "usd", - tax_rate: 0, - }) - - const defaultProfile = await manager.findOne(ShippingProfile, { - where: { type: ShippingProfileType.DEFAULT }, - }) - - await manager.insert(ShippingOption, { - id: "test-out", - name: "Test out", - profile_id: defaultProfile.id, - region_id: "region", - provider_id: "test-ful", - data: {}, - price_type: "flat_rate", - amount: 2000, - is_return: false, - }) - - await manager.insert(ShippingOption, { - id: "test-option-req", - name: "With req", - profile_id: defaultProfile.id, - region_id: "region", - provider_id: "test-ful", - data: {}, - price_type: "flat_rate", - amount: 2000, - is_return: false, - }) - - await manager.insert(ShippingOption, { - id: "test-option-req-admin-only", - name: "With req", - profile_id: defaultProfile.id, - region_id: "region", - admin_only: true, - provider_id: "test-ful", - data: {}, - price_type: "flat_rate", - amount: 2000, - is_return: false, - }) - await manager.insert(ShippingOption, { - id: "test-option-req-return", - name: "With req", - profile_id: defaultProfile.id, - region_id: "region", - is_return: true, - provider_id: "test-ful", - data: {}, - price_type: "flat_rate", - amount: 2000, - }) - - await manager.insert(ShippingOptionRequirement, { - id: "option-req", - shipping_option_id: "test-option-req", - type: "min_subtotal", - amount: 5, - }) - - await manager.insert(ShippingOptionRequirement, { - id: "option-req-2", - shipping_option_id: "test-option-req", - type: "max_subtotal", - amount: 10, - }) -} diff --git a/integration-tests/http/__tests__/price-list/admin/price-list.spec.ts b/integration-tests/http/__tests__/price-list/admin/price-list.spec.ts new file mode 100644 index 0000000000..b57eeb84eb --- /dev/null +++ b/integration-tests/http/__tests__/price-list/admin/price-list.spec.ts @@ -0,0 +1,855 @@ +import { medusaIntegrationTestRunner } from "medusa-test-utils" +import { + createAdminUser, + adminHeaders, +} from "../../../../helpers/create-admin-user" +import { + getPricelistFixture, + getProductFixture, +} from "../../../../helpers/fixtures" + +jest.setTimeout(30000) + +medusaIntegrationTestRunner({ + env: {}, + testSuite: ({ dbConnection, getContainer, api }) => { + let pricelist1 + let pricelist2 + let region1 + let product1 + let customerGroup1 + + beforeEach(async () => { + const container = getContainer() + await createAdminUser(dbConnection, adminHeaders, container) + + region1 = ( + await api.post( + "/admin/regions", + { + name: "Test Region", + currency_code: "usd", + }, + adminHeaders + ) + ).data.region + + product1 = ( + await api.post( + "/admin/products", + getProductFixture({ title: "Test product" }), + adminHeaders + ) + ).data.product + + customerGroup1 = ( + await api.post( + "/admin/customer-groups", + { + name: "vip-customers", + metadata: { + data1: "value1", + }, + }, + adminHeaders + ) + ).data.customer_group + + pricelist1 = ( + await api.post( + "/admin/price-lists", + getPricelistFixture({ + title: "New sale", + prices: [ + { + amount: 100, + currency_code: "usd", + variant_id: product1.variants[0].id, + min_quantity: 1, + max_quantity: 100, + }, + { + amount: 80, + currency_code: "usd", + variant_id: product1.variants[0].id, + min_quantity: 101, + max_quantity: 500, + }, + ], + }), + adminHeaders + ) + ).data.price_list + + pricelist2 = ( + await api.post( + "/admin/price-lists", + + getPricelistFixture({ + title: "Another sale", + description: "50% off on all items", + }), + adminHeaders + ) + ).data.price_list + + // BREAKING: You need to register rule types before you can use them + await api.post( + "/admin/pricing/rule-types", + { name: "Region ID", rule_attribute: "region_id", default_priority: 0 }, + adminHeaders + ) + await api.post( + "/admin/pricing/rule-types", + { + name: "Customer Group ID", + rule_attribute: "customer_group_id", + default_priority: 0, + }, + adminHeaders + ) + }) + + describe("/admin/price-lists", () => { + describe("POST /admin/price-list", () => { + it("creates a price list", async () => { + const payload = getPricelistFixture({ + title: "VIP Summer sale", + description: + "Summer sale for VIP customers. 25% off selected items.", + rules: { + customer_group_id: [customerGroup1.id], + }, + prices: [ + { + amount: 85, + currency_code: "usd", + variant_id: product1.variants[0].id, + }, + { + amount: 105, + currency_code: region1.currency_code, + region_id: region1.id, + variant_id: product1.variants[0].id, + }, + ], + }) + + const response = await api.post( + "/admin/price-lists", + payload, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_list).toEqual( + expect.objectContaining({ + id: expect.any(String), + title: "VIP Summer sale", + description: + "Summer sale for VIP customers. 25% off selected items.", + type: "sale", + status: "active", + starts_at: "2022-07-01T00:00:00.000Z", + ends_at: "2022-07-31T00:00:00.000Z", + rules: { + customer_group_id: [expect.stringContaining("cusgroup_")], + }, + prices: [ + expect.objectContaining({ + id: expect.any(String), + amount: 85, + currency_code: "usd", + variant_id: product1.variants[0].id, + }), + expect.objectContaining({ + id: expect.any(String), + amount: 105, + currency_code: region1.currency_code, + variant_id: product1.variants[0].id, + }), + ], + }) + ) + }) + }) + + describe("GET /admin/price-lists", () => { + it("returns a price list by :id", async () => { + const response = await api.get( + `/admin/price-lists/${pricelist1.id}`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_list).toEqual( + expect.objectContaining({ + id: expect.any(String), + title: pricelist1.title, + description: pricelist1.description, + type: pricelist1.type, + status: pricelist1.status, + starts_at: pricelist1.starts_at, + ends_at: pricelist1.ends_at, + prices: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + amount: 100, + currency_code: "usd", + // BREAKING: Min and max quantity are returned as string + min_quantity: "1", + max_quantity: "100", + variant_id: product1.variants[0].id, + created_at: expect.any(String), + updated_at: expect.any(String), + // BREAKING: `variant` and `variants` are not returned as part of the prices + }), + expect.objectContaining({ + id: expect.any(String), + amount: 80, + currency_code: "usd", + min_quantity: "101", + max_quantity: "500", + variant_id: product1.variants[0].id, + created_at: expect.any(String), + updated_at: expect.any(String), + }), + ]), + created_at: expect.any(String), + updated_at: expect.any(String), + }) + ) + }) + + it("returns a list of price lists", async () => { + const response = await api.get("/admin/price-lists", adminHeaders) + + expect(response.status).toEqual(200) + expect(response.data.price_lists).toHaveLength(2) + expect(response.data.price_lists).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: pricelist1.id, + }), + expect.objectContaining({ + id: pricelist2.id, + }), + ]) + ) + }) + + it("given a search query, returns matching results by name", async () => { + const response = await api.get( + "/admin/price-lists?q=new", + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_lists).toEqual([ + expect.objectContaining({ + title: pricelist1.title, + }), + ]) + expect(response.data.count).toEqual(1) + }) + + it("given a search query, returns matching results by description", async () => { + const response = await api.get( + "/admin/price-lists?q=50%", + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_lists).toEqual([ + expect.objectContaining({ + id: pricelist2.id, + }), + ]) + expect(response.data.count).toEqual(1) + }) + + it("given a search query, returns empty list when does not exist", async () => { + const response = await api.get( + "/admin/price-lists?q=blablabla", + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_lists).toEqual([]) + expect(response.data.count).toEqual(0) + }) + + it("given a search query and a status filter not matching any price list, returns an empty set", async () => { + const response = await api.get( + "/admin/price-lists?q=new&status[]=draft", + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_lists).toEqual([]) + expect(response.data.count).toEqual(0) + }) + + it("given a search query and a status filter matching a price list, returns a price list", async () => { + const response = await api.get( + "/admin/price-lists?q=new&status[]=active", + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_lists).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + title: "New sale", + status: "active", + }), + ]) + ) + expect(response.data.count).toEqual(1) + }) + + it.skip("lists only price lists with customer_group", async () => { + // BREAKING: You can no longer filter by customer groups + const response = await api.get( + `/admin/price-lists?customer_groups[]=customer-group-1,customer-group-2`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_lists.length).toEqual(2) + expect(response.data.price_lists).toHaveLength(2) + expect(response.data.price_lists).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: "test-list-cgroup-1" }), + expect.objectContaining({ id: "test-list-cgroup-2" }), + ]) + ) + }) + }) + + describe("POST /admin/price-lists/:id", () => { + it("removes configuration with update", async () => { + const updateResult = await api.post( + `/admin/price-lists/${pricelist1.id}`, + { ends_at: null, starts_at: null, rules: {} }, + adminHeaders + ) + expect(updateResult.status).toEqual(200) + expect(updateResult.data.price_list.starts_at).toBeFalsy() + expect(updateResult.data.price_list.ends_at).toBeFalsy() + expect(updateResult.data.price_list.rules).toEqual({}) + }) + + it("updates a price list", async () => { + const payload = { + title: "Loyalty Reward - Winter Sale", + description: "Winter sale for our most loyal customers", + type: "sale", + status: "draft", + starts_at: "2022-09-01T00:00:00.000Z", + ends_at: "2022-12-31T00:00:00.000Z", + rules: { + customer_group_id: [customerGroup1.id], + }, + } + + const response = await api.post( + `/admin/price-lists/${pricelist1.id}`, + payload, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_list).toEqual( + expect.objectContaining({ + id: pricelist1.id, + title: "Loyalty Reward - Winter Sale", + description: "Winter sale for our most loyal customers", + type: "sale", + status: "draft", + starts_at: "2022-09-01T00:00:00.000Z", + ends_at: "2022-12-31T00:00:00.000Z", + prices: expect.arrayContaining([ + expect.objectContaining({ + amount: 100, + currency_code: "usd", + id: expect.any(String), + max_quantity: "100", + min_quantity: "1", + }), + expect.objectContaining({ + amount: 80, + currency_code: "usd", + id: expect.any(String), + max_quantity: "500", + min_quantity: "101", + }), + ]), + rules: { + customer_group_id: [customerGroup1.id], + }, + created_at: expect.any(String), + updated_at: expect.any(String), + }) + ) + }) + + it("updates the amount and currency of a price in the price list", async () => { + const payload = { + // BREAKING: Updates of prices happen through the batch endpoint, and doing it through the price list update endpoint is no longer supported + update: [ + { + id: pricelist1.prices.find((p) => p.amount === 80).id, + amount: 250, + currency_code: "eur", + variant_id: product1.variants[0].id, + }, + ], + } + + await api.post( + `/admin/price-lists/${pricelist1.id}/prices/batch`, + payload, + adminHeaders + ) + const response = await api.get( + `/admin/price-lists/${pricelist1.id}`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_list.prices).toEqual([ + expect.objectContaining({ + amount: 100, + currency_code: "usd", + id: expect.any(String), + max_quantity: "100", + min_quantity: "1", + }), + expect.objectContaining({ + amount: 250, + currency_code: "eur", + id: expect.any(String), + max_quantity: "500", + min_quantity: "101", + }), + ]) + }) + }) + + describe("POST /admin/price-lists/:id/prices/batch", () => { + it("Adds a batch of new prices to a price list without overriding existing prices", async () => { + // BREAKING: The payload of the batch request changed + const payload = { + create: [ + { + amount: 45, + currency_code: "usd", + variant_id: product1.variants[0].id, + min_quantity: 1001, + max_quantity: 2000, + }, + { + amount: 35, + currency_code: "usd", + variant_id: product1.variants[0].id, + min_quantity: 2001, + max_quantity: 3000, + }, + { + amount: 25, + currency_code: "usd", + variant_id: product1.variants[0].id, + min_quantity: 3001, + max_quantity: 4000, + }, + ], + } + + await api.post( + `/admin/price-lists/${pricelist1.id}/prices/batch`, + payload, + adminHeaders + ) + const response = await api.get( + `/admin/price-lists/${pricelist1.id}`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_list.prices.length).toEqual(5) + expect(response.data.price_list.prices).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + amount: 100, + currency_code: "usd", + min_quantity: "1", + max_quantity: "100", + variant_id: product1.variants[0].id, + }), + expect.objectContaining({ + id: expect.any(String), + amount: 80, + currency_code: "usd", + min_quantity: "101", + max_quantity: "500", + variant_id: product1.variants[0].id, + }), + expect.objectContaining({ + id: expect.any(String), + amount: 45, + currency_code: "usd", + variant_id: product1.variants[0].id, + min_quantity: "1001", + max_quantity: "2000", + }), + expect.objectContaining({ + id: expect.any(String), + amount: 35, + currency_code: "usd", + variant_id: product1.variants[0].id, + min_quantity: "2001", + max_quantity: "3000", + }), + expect.objectContaining({ + id: expect.any(String), + amount: 25, + currency_code: "usd", + variant_id: product1.variants[0].id, + min_quantity: "3001", + max_quantity: "4000", + }), + ]) + ) + }) + + it.skip("Adds a batch of new prices to a price list overriding existing prices", async () => { + // BREAKING: There is no support for overriding configuration + const payload = { + prices: [ + { + amount: 45, + currency_code: "usd", + variant_id: "test-variant", + min_quantity: 1001, + max_quantity: 2000, + }, + { + amount: 35, + currency_code: "usd", + variant_id: "test-variant", + min_quantity: 2001, + max_quantity: 3000, + }, + { + amount: 25, + currency_code: "usd", + variant_id: "test-variant", + min_quantity: 3001, + max_quantity: 4000, + }, + ], + override: true, + } + + const response = await api.post( + "/admin/price-lists/pl_no_customer_groups/prices/batch", + payload, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_list.prices.length).toEqual(3) + expect(response.data.price_list.prices).toMatchSnapshot([ + { + id: expect.any(String), + price_list_id: "pl_no_customer_groups", + amount: 45, + currency_code: "usd", + variant_id: "test-variant", + min_quantity: 1001, + max_quantity: 2000, + created_at: expect.any(String), + updated_at: expect.any(String), + variant: expect.any(Object), + variants: expect.any(Array), + }, + { + id: expect.any(String), + price_list_id: "pl_no_customer_groups", + amount: 35, + currency_code: "usd", + variant_id: "test-variant", + min_quantity: 2001, + max_quantity: 3000, + created_at: expect.any(String), + updated_at: expect.any(String), + variant: expect.any(Object), + variants: expect.any(Array), + }, + { + id: expect.any(String), + price_list_id: "pl_no_customer_groups", + amount: 25, + currency_code: "usd", + variant_id: "test-variant", + min_quantity: 3001, + max_quantity: 4000, + created_at: expect.any(String), + updated_at: expect.any(String), + variant: expect.any(Object), + variants: expect.any(Array), + }, + ]) + }) + + it("Adds a batch of new prices where a MA record have a `region_id` instead of `currency_code`", async () => { + // BREAKING: As mentioned, currency code is required now + const payload = { + create: [ + { + amount: 100, + variant_id: product1.variants[0].id, + currency_code: "eur", + region_id: region1.id, + }, + { + amount: 200, + variant_id: product1.variants[0].id, + currency_code: "eur", + }, + ], + } + + await api.post( + `/admin/price-lists/${pricelist1.id}/prices/batch`, + payload, + adminHeaders + ) + const response = await api.get( + `/admin/price-lists/${pricelist1.id}`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_list.prices.length).toEqual(4) + expect(response.data.price_list.prices).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + amount: 100, + currency_code: "usd", + min_quantity: "1", + max_quantity: "100", + variant_id: product1.variants[0].id, + }), + expect.objectContaining({ + id: expect.any(String), + amount: 80, + currency_code: "usd", + min_quantity: "101", + max_quantity: "500", + variant_id: product1.variants[0].id, + }), + expect.objectContaining({ + id: expect.any(String), + amount: 100, + currency_code: "eur", + // region_id: region1.id, + variant_id: product1.variants[0].id, + }), + expect.objectContaining({ + id: expect.any(String), + amount: 200, + currency_code: "eur", + variant_id: product1.variants[0].id, + }), + ]) + ) + }) + }) + + describe("DELETE /admin/price-lists/:id", () => { + it("Deletes a price list", async () => { + const response = await api.delete( + `/admin/price-lists/${pricelist1.id}`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data).toEqual({ + id: pricelist1.id, + object: "price_list", + deleted: true, + }) + + const err = await api + .get(`/admin/price-lists/${pricelist1.id}`, adminHeaders) + .catch((e) => e) + + expect(err.response.status).toBe(404) + expect(err.response.data.message).toEqual( + `Price list with id: ${pricelist1.id} was not found` + ) + }) + }) + + describe("tests cascade on delete", () => { + it("Deletes a variant and ensures that prices associated with the variant are deleted from PriceList", async () => { + await api.delete( + `/admin/products/${product1.id}/variants/${product1.variants[0].id}`, + adminHeaders + ) + + const response = await api.get( + `/admin/price-lists/${pricelist1.id}`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_list.prices.length).toEqual(0) + }) + }) + + describe("DELETE /admin/price-lists/:id/prices/batch", () => { + // BREAKING: The batch method signature changed + it("Deletes several prices associated with a price list", async () => { + await api.post( + `/admin/price-lists/${pricelist1.id}/prices/batch`, + { + delete: [pricelist1.prices[0].id], + }, + adminHeaders + ) + + const response = await api.get( + `/admin/price-lists/${pricelist1.id}`, + adminHeaders + ) + + expect(response.status).toEqual(200) + expect(response.data.price_list.prices).toEqual([ + expect.objectContaining({ + amount: 80, + currency_code: "usd", + min_quantity: "101", + max_quantity: "500", + variant_id: product1.variants[0].id, + }), + ]) + }) + }) + + // BREAKING: There is no longer a GET /admin/price-lists/:id/products endpoint. Get products through `/admin/products` endpoint instead + // BREAKING: There is no longer a DELETE /admin/price-lists/test-list/products/${product.id}/prices endpoint + // BREAKING: There is no longer a DELETE /admin/price-lists/test-list/variants/${variant.id}/prices endpoint + }) + }, +}) + +// TODO: Revisit tax inclusive pricing +// describe("[MEDUSA_FF_TAX_INCLUSIVE_PRICING] /admin/price-lists", () => { +// let medusaProcess +// let dbConnection + +// beforeAll(async () => { +// const cwd = path.resolve(path.join(__dirname, "..", "..")) +// const [process, connection] = await startServerWithEnvironment({ +// cwd, +// env: { MEDUSA_FF_TAX_INCLUSIVE_PRICING: true }, +// }) +// dbConnection = connection +// medusaProcess = process +// }) + +// afterAll(async () => { +// const db = useDb() +// await db.shutdown() + +// medusaProcess.kill() +// }) + +// describe("POST /admin/price-list", () => { +// const priceListIncludesTaxId = "price-list-1-includes-tax" + +// beforeEach(async () => { +// try { +// await adminSeeder(dbConnection) +// await customerSeeder(dbConnection) +// await productSeeder(dbConnection) +// await simplePriceListFactory(dbConnection, { +// id: priceListIncludesTaxId, +// }) +// } catch (err) { +// console.log(err) +// throw err +// } +// }) + +// afterEach(async () => { +// const db = useDb() +// await db.teardown() +// }) + +// it("should creates a price list that includes tax", async () => { +// const payload = { +// name: "VIP Summer sale", +// description: "Summer sale for VIP customers. 25% off selected items.", +// type: "sale", +// status: "active", +// starts_at: "2022-07-01T00:00:00.000Z", +// ends_at: "2022-07-31T00:00:00.000Z", +// customer_groups: [ +// { +// id: "customer-group-1", +// }, +// ], +// prices: [ +// { +// amount: 85, +// currency_code: "usd", +// variant_id: "test-variant", +// }, +// ], +// includes_tax: true, +// } + +// const response = await api +// .post("/admin/price-lists", payload, adminHeaders) +// .catch((err) => { +// console.warn(err.response.data) +// }) + +// expect(response.status).toEqual(200) +// expect(response.data.price_list).toEqual( +// expect.objectContaining({ +// id: expect.any(String), +// includes_tax: true, +// }) +// ) +// }) + +// it("should update a price list that include_tax", async () => { +// let response = await api +// .get(`/admin/price-lists/${priceListIncludesTaxId}`, adminHeaders) +// .catch((err) => { +// console.log(err) +// }) + +// expect(response.data.price_list.includes_tax).toBe(false) + +// response = await api +// .post( +// `/admin/price-lists/${priceListIncludesTaxId}`, +// { includes_tax: true }, +// adminHeaders +// ) +// .catch((err) => { +// console.log(err) +// }) + +// expect(response.data.price_list.includes_tax).toBe(true) +// }) +// }) +// }) diff --git a/packages/medusa/src/api/admin/price-lists/validators.ts b/packages/medusa/src/api/admin/price-lists/validators.ts index 1171103817..9862718286 100644 --- a/packages/medusa/src/api/admin/price-lists/validators.ts +++ b/packages/medusa/src/api/admin/price-lists/validators.ts @@ -68,8 +68,8 @@ export type AdminCreatePriceListType = z.infer export const AdminUpdatePriceList = z.object({ title: z.string().optional(), description: z.string().optional(), - starts_at: z.string().optional(), - ends_at: z.string().optional(), + starts_at: z.string().optional().nullable(), + ends_at: z.string().optional().nullable(), status: z.nativeEnum(PriceListStatus).optional(), type: z.nativeEnum(PriceListType).optional(), rules: z.record(z.string(), z.array(z.string())).optional(),