fix: Several fixes to store product endpoints, moved several test suites to HTTP (#7601)
* chore: Move publishable api key tests to HTTP * chore: Move store tests to HTTP folder * fix: Add tests for store products, fix several bugs around publishable keys
This commit is contained in:
@@ -0,0 +1,291 @@
|
||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||
import {
|
||||
adminHeaders,
|
||||
createAdminUser,
|
||||
} from "../../../../helpers/create-admin-user"
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
medusaIntegrationTestRunner({
|
||||
env: {},
|
||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||
describe("Publishable Keys - Admin", () => {
|
||||
let pubKey1
|
||||
let pubKey2
|
||||
|
||||
beforeEach(async () => {
|
||||
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
||||
|
||||
// BREAKING: Before the ID of the token was used in request headers, now there is a separate `token` field that should be used
|
||||
pubKey1 = (
|
||||
await api.post(
|
||||
"/admin/api-keys",
|
||||
{ title: "sample key", type: "publishable" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.api_key
|
||||
pubKey2 = (
|
||||
await api.post(
|
||||
"/admin/api-keys",
|
||||
// BREAKING: The type field is now required
|
||||
{ title: "just a title", type: "publishable" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.api_key
|
||||
})
|
||||
|
||||
// BREAKING: The URL changed from /admin/publishable-api-keys to /admin/api-keys, as well as the response field
|
||||
describe("GET /admin/api-keys/:id", () => {
|
||||
it("retrieve a publishable key by id ", async () => {
|
||||
const response = await api.get(
|
||||
`/admin/api-keys/${pubKey1.id}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
|
||||
expect(response.data.api_key).toMatchObject({
|
||||
id: pubKey1.id,
|
||||
created_at: expect.any(String),
|
||||
created_by: expect.stringContaining("user_"),
|
||||
revoked_by: null,
|
||||
revoked_at: null,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("GET /admin/api-keys", () => {
|
||||
it("list publishable keys", async () => {
|
||||
const response = await api.get(
|
||||
`/admin/api-keys?limit=2`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.data.count).toBe(2)
|
||||
expect(response.data.limit).toBe(2)
|
||||
expect(response.data.offset).toBe(0)
|
||||
expect(response.data.api_keys).toHaveLength(2)
|
||||
})
|
||||
|
||||
it("list publishable keys with query search", async () => {
|
||||
const response = await api.get(
|
||||
`/admin/api-keys?q=sample`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.data.count).toBe(1)
|
||||
expect(response.data.limit).toBe(20)
|
||||
expect(response.data.offset).toBe(0)
|
||||
expect(response.data.api_keys).toHaveLength(1)
|
||||
expect(response.data.api_keys).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
title: "sample key",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/api-keys", () => {
|
||||
it("crete a publishable keys", async () => {
|
||||
const response = await api.post(
|
||||
`/admin/api-keys`,
|
||||
{ title: "Store api key", type: "publishable" },
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data.api_key).toMatchObject({
|
||||
created_by: expect.any(String),
|
||||
id: expect.any(String),
|
||||
title: "Store api key",
|
||||
revoked_by: null,
|
||||
revoked_at: null,
|
||||
created_at: expect.any(String),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/api-keys/:id", () => {
|
||||
it("update a publishable key", async () => {
|
||||
const response = await api.post(
|
||||
`/admin/api-keys/${pubKey1.id}`,
|
||||
{ title: "Changed title" },
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data.api_key).toMatchObject({
|
||||
id: pubKey1.id,
|
||||
title: "Changed title",
|
||||
revoked_by: null,
|
||||
revoked_at: null,
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("DELETE /admin/api-keys/:id", () => {
|
||||
it("delete a publishable key", async () => {
|
||||
const response1 = await api.delete(
|
||||
`/admin/api-keys/${pubKey1.id}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response1.status).toBe(200)
|
||||
expect(response1.data).toEqual({
|
||||
id: pubKey1.id,
|
||||
object: "api_key",
|
||||
deleted: true,
|
||||
})
|
||||
|
||||
const err = await api
|
||||
.get(`/admin/api-keys/${pubKey1.id}`, adminHeaders)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(err.response.status).toBe(404)
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/api-keys/:id/revoke", () => {
|
||||
it("revoke a publishable key", async () => {
|
||||
const response = await api.post(
|
||||
`/admin/api-keys/${pubKey1.id}/revoke`,
|
||||
{},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
|
||||
expect(response.data.api_key).toMatchObject({
|
||||
id: pubKey1.id,
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
revoked_by: expect.stringContaining("user_"),
|
||||
revoked_at: expect.any(String),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
// BREAKING: The GET /admin/api-keys/:id/sales-channels endpoint was removed.
|
||||
// It was replaced by the GET /admin/sales-channels endpoint where you can filter by publishable key
|
||||
// BREAKING: Batch route and input changed (no more batch suffix, and the input takes ids to add and remove)
|
||||
describe("Add /admin/api-keys/:id/sales-channels", () => {
|
||||
let salesChannel1
|
||||
let salesChannel2
|
||||
|
||||
beforeEach(async () => {
|
||||
salesChannel1 = (
|
||||
await api.post(
|
||||
"/admin/sales-channels",
|
||||
{
|
||||
name: "test name",
|
||||
description: "test description",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.sales_channel
|
||||
|
||||
salesChannel2 = (
|
||||
await api.post(
|
||||
"/admin/sales-channels",
|
||||
{
|
||||
name: "test name 2",
|
||||
description: "test description 2",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.sales_channel
|
||||
})
|
||||
|
||||
it("add sales channels to the publishable api key scope", async () => {
|
||||
const response = await api.post(
|
||||
`/admin/api-keys/${pubKey1.id}/sales-channels`,
|
||||
{
|
||||
add: [salesChannel1.id, salesChannel2.id],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
const keyWithChannels = (
|
||||
await api.get(`/admin/api-keys/${pubKey1.id}`, adminHeaders)
|
||||
).data.api_key
|
||||
|
||||
expect(keyWithChannels.sales_channels).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: salesChannel1.id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: salesChannel2.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("remove sales channels from the publishable api key scope", async () => {
|
||||
await api.post(
|
||||
`/admin/api-keys/${pubKey1.id}/sales-channels`,
|
||||
{
|
||||
add: [salesChannel1.id, salesChannel2.id],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/api-keys/${pubKey1.id}/sales-channels`,
|
||||
{
|
||||
remove: [salesChannel1.id],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
const keyWithChannels = (
|
||||
await api.get(`/admin/api-keys/${pubKey1.id}`, adminHeaders)
|
||||
).data.api_key
|
||||
|
||||
expect(keyWithChannels.sales_channels).toEqual([
|
||||
expect.objectContaining({
|
||||
id: salesChannel2.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("list sales channels from the publishable api key", async () => {
|
||||
await api.post(
|
||||
`/admin/api-keys/${pubKey1.id}/sales-channels`,
|
||||
{
|
||||
add: [salesChannel1.id, salesChannel2.id],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const response = await api.get(
|
||||
`/admin/api-keys/${pubKey1.id}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const salesChannels = response.data.api_key.sales_channels
|
||||
expect(response.status).toBe(200)
|
||||
expect(salesChannels.length).toEqual(2)
|
||||
expect(salesChannels).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: salesChannel1.id,
|
||||
name: "test name",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: salesChannel2.id,
|
||||
name: "test name 2",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
212
integration-tests/http/__tests__/cart/store/cart.spec.ts
Normal file
212
integration-tests/http/__tests__/cart/store/cart.spec.ts
Normal file
@@ -0,0 +1,212 @@
|
||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||
import {
|
||||
adminHeaders,
|
||||
createAdminUser,
|
||||
} from "../../../../helpers/create-admin-user"
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
medusaIntegrationTestRunner({
|
||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||
beforeEach(async () => {
|
||||
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
||||
})
|
||||
|
||||
describe("noop", () => {
|
||||
it("noop", () => {})
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
// TODO: Implement the tests below, which were migrated from v1.
|
||||
// describe("POST /store/carts/:id", () => {
|
||||
// let product
|
||||
// const pubKeyId = IdMap.getId("pubkey-get-id")
|
||||
|
||||
// beforeEach(async () => {
|
||||
// await adminSeeder(dbConnection)
|
||||
|
||||
// await simpleRegionFactory(dbConnection, {
|
||||
// id: "test-region",
|
||||
// })
|
||||
|
||||
// await simplePublishableApiKeyFactory(dbConnection, {
|
||||
// id: pubKeyId,
|
||||
// created_by: adminUserId,
|
||||
// })
|
||||
|
||||
// product = await simpleProductFactory(dbConnection, {
|
||||
// sales_channels: [
|
||||
// {
|
||||
// id: "sales-channel",
|
||||
// name: "Sales channel",
|
||||
// description: "Sales channel",
|
||||
// },
|
||||
// {
|
||||
// id: "sales-channel2",
|
||||
// name: "Sales channel2",
|
||||
// description: "Sales channel2",
|
||||
// },
|
||||
// ],
|
||||
// })
|
||||
// })
|
||||
|
||||
// afterEach(async () => {
|
||||
// const db = useDb()
|
||||
// return await db.teardown()
|
||||
// })
|
||||
|
||||
// it("should assign sales channel to order on cart completion if PK is present in the header", async () => {
|
||||
// const api = useApi()
|
||||
|
||||
// await api.post(
|
||||
// `/admin/api-keys/${pubKeyId}/sales-channels/batch`,
|
||||
// {
|
||||
// sales_channel_ids: [{ id: "sales-channel" }],
|
||||
// },
|
||||
// adminHeaders
|
||||
// )
|
||||
|
||||
// const customerRes = await api.post("/store/customers", customerData, {
|
||||
// withCredentials: true,
|
||||
// })
|
||||
|
||||
// const createCartRes = await api.post(
|
||||
// "/store/carts",
|
||||
// {
|
||||
// region_id: "test-region",
|
||||
// items: [
|
||||
// {
|
||||
// variant_id: product.variants[0].id,
|
||||
// quantity: 1,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// headers: {
|
||||
// "x-medusa-access-token": "test_token",
|
||||
// "x-publishable-api-key": pubKeyId,
|
||||
// },
|
||||
// }
|
||||
// )
|
||||
|
||||
// const cart = createCartRes.data.cart
|
||||
|
||||
// await api.post(`/store/carts/${cart.id}`, {
|
||||
// customer_id: customerRes.data.customer.id,
|
||||
// })
|
||||
|
||||
// await api.post(`/store/carts/${cart.id}/payment-sessions`)
|
||||
|
||||
// const createdOrder = await api.post(
|
||||
// `/store/carts/${cart.id}/complete-cart`
|
||||
// )
|
||||
|
||||
// expect(createdOrder.data.type).toEqual("order")
|
||||
// expect(createdOrder.status).toEqual(200)
|
||||
// expect(createdOrder.data.data).toEqual(
|
||||
// expect.objectContaining({
|
||||
// sales_channel_id: "sales-channel",
|
||||
// })
|
||||
// )
|
||||
// })
|
||||
|
||||
// it("SC from params defines where product is assigned (passed SC still has to be in the scope of PK from the header)", async () => {
|
||||
// const api = useApi()
|
||||
|
||||
// await api.post(
|
||||
// `/admin/api-keys/${pubKeyId}/sales-channels/batch`,
|
||||
// {
|
||||
// sales_channel_ids: [
|
||||
// { id: "sales-channel" },
|
||||
// { id: "sales-channel2" },
|
||||
// ],
|
||||
// },
|
||||
// adminHeaders
|
||||
// )
|
||||
|
||||
// const customerRes = await api.post("/store/customers", customerData, {
|
||||
// withCredentials: true,
|
||||
// })
|
||||
|
||||
// const createCartRes = await api.post(
|
||||
// "/store/carts",
|
||||
// {
|
||||
// sales_channel_id: "sales-channel2",
|
||||
// region_id: "test-region",
|
||||
// items: [
|
||||
// {
|
||||
// variant_id: product.variants[0].id,
|
||||
// quantity: 1,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// headers: {
|
||||
// "x-medusa-access-token": "test_token",
|
||||
// "x-publishable-api-key": pubKeyId,
|
||||
// },
|
||||
// }
|
||||
// )
|
||||
|
||||
// const cart = createCartRes.data.cart
|
||||
|
||||
// await api.post(`/store/carts/${cart.id}`, {
|
||||
// customer_id: customerRes.data.customer.id,
|
||||
// })
|
||||
|
||||
// await api.post(`/store/carts/${cart.id}/payment-sessions`)
|
||||
|
||||
// const createdOrder = await api.post(
|
||||
// `/store/carts/${cart.id}/complete-cart`
|
||||
// )
|
||||
|
||||
// expect(createdOrder.data.type).toEqual("order")
|
||||
// expect(createdOrder.status).toEqual(200)
|
||||
// expect(createdOrder.data.data).toEqual(
|
||||
// expect.objectContaining({
|
||||
// sales_channel_id: "sales-channel2",
|
||||
// })
|
||||
// )
|
||||
// })
|
||||
|
||||
// it("should throw because SC id in the body is not in the scope of PK from the header", async () => {
|
||||
// const api = useApi()
|
||||
|
||||
// await api.post(
|
||||
// `/admin/api-keys/${pubKeyId}/sales-channels/batch`,
|
||||
// {
|
||||
// sales_channel_ids: [{ id: "sales-channel" }],
|
||||
// },
|
||||
// adminHeaders
|
||||
// )
|
||||
|
||||
// try {
|
||||
// await api.post(
|
||||
// "/store/carts",
|
||||
// {
|
||||
// sales_channel_id: "sales-channel2", // SC not in the PK scope
|
||||
// region_id: "test-region",
|
||||
// items: [
|
||||
// {
|
||||
// variant_id: product.variants[0].id,
|
||||
// quantity: 1,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// {
|
||||
// headers: {
|
||||
// "x-medusa-access-token": "test_token",
|
||||
// "x-publishable-api-key": pubKeyId,
|
||||
// },
|
||||
// }
|
||||
// )
|
||||
// } catch (error) {
|
||||
// expect(error.response.status).toEqual(400)
|
||||
// expect(error.response.data.errors[0]).toEqual(
|
||||
// `Provided sales channel id param: sales-channel2 is not associated with the Publishable API Key passed in the header of the request.`
|
||||
// )
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
// })
|
||||
@@ -3,48 +3,10 @@ import {
|
||||
adminHeaders,
|
||||
createAdminUser,
|
||||
} from "../../../../helpers/create-admin-user"
|
||||
import { getProductFixture } from "../../../../helpers/fixtures"
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
const getProductFixture = (overrides) => ({
|
||||
title: "Test fixture",
|
||||
description: "test-product-description",
|
||||
status: "draft",
|
||||
// BREAKING: Images input changed from string[] to {url: string}[]
|
||||
images: [{ url: "test-image.png" }, { url: "test-image-2.png" }],
|
||||
tags: [{ value: "123" }, { value: "456" }],
|
||||
// BREAKING: Options input changed from {title: string}[] to {title: string, values: string[]}[]
|
||||
options: [
|
||||
{ title: "size", values: ["large", "small"] },
|
||||
{ title: "color", values: ["green"] },
|
||||
],
|
||||
variants: [
|
||||
{
|
||||
title: "Test variant",
|
||||
prices: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
},
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 45,
|
||||
},
|
||||
{
|
||||
currency_code: "dkk",
|
||||
amount: 30,
|
||||
},
|
||||
],
|
||||
// BREAKING: Options input changed from {value: string}[] to {[optionTitle]: optionValue} map
|
||||
options: {
|
||||
size: "large",
|
||||
color: "green",
|
||||
},
|
||||
},
|
||||
],
|
||||
...(overrides ?? {}),
|
||||
})
|
||||
|
||||
medusaIntegrationTestRunner({
|
||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||
let baseProduct
|
||||
@@ -56,7 +18,6 @@ medusaIntegrationTestRunner({
|
||||
let publishedCollection
|
||||
|
||||
let baseType
|
||||
let baseRegion
|
||||
|
||||
beforeEach(async () => {
|
||||
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
||||
@@ -85,18 +46,6 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
).data.product_type
|
||||
|
||||
// BREAKING: Creating a region no longer takes tax_rate, payment_providers, fulfillment_providers, countriesr
|
||||
baseRegion = (
|
||||
await api.post(
|
||||
"/admin/regions",
|
||||
{
|
||||
name: "Test region",
|
||||
currency_code: "USD",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.region
|
||||
|
||||
baseProduct = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
@@ -1947,527 +1896,6 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
})
|
||||
|
||||
describe("GET /admin/products/:id/variants", () => {
|
||||
it("should return the variants related to the requested product", async () => {
|
||||
const res = await api
|
||||
.get(`/admin/products/${baseProduct.id}/variants`, adminHeaders)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.variants.length).toBe(1)
|
||||
expect(res.data.variants).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: baseProduct.variants[0].id,
|
||||
product_id: baseProduct.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should allow searching of variants", async () => {
|
||||
const newProduct = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
getProductFixture({
|
||||
variants: [
|
||||
{ title: "First variant", prices: [] },
|
||||
{ title: "Second variant", prices: [] },
|
||||
],
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
|
||||
const res = await api
|
||||
.get(
|
||||
`/admin/products/${newProduct.id}/variants?q=first`,
|
||||
adminHeaders
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.variants).toHaveLength(1)
|
||||
expect(res.data.variants).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
title: "First variant",
|
||||
product_id: newProduct.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("updates a variant's default prices (ignores prices associated with a Price List)", () => {
|
||||
it("successfully updates a variant's default prices by changing an existing price (currency_code)", async () => {
|
||||
const data = {
|
||||
prices: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 1500,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}`,
|
||||
data,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(
|
||||
baseProduct.variants[0].prices.find(
|
||||
(p) => p.currency_code === "usd"
|
||||
).amount
|
||||
).toEqual(100)
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data).toEqual({
|
||||
product: expect.objectContaining({
|
||||
id: baseProduct.id,
|
||||
variants: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: baseProduct.variants[0].id,
|
||||
prices: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 1500,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: Do we want to add support for region prices through the product APIs?
|
||||
it.skip("successfully updates a variant's price by changing an existing price (given a region_id)", async () => {
|
||||
const data = {
|
||||
prices: [
|
||||
{
|
||||
region_id: "test-region",
|
||||
amount: 1500,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/admin/products/test-product1/variants/test-variant_3",
|
||||
data,
|
||||
adminHeaders
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
expect(response.data.product).toEqual(
|
||||
expect.objectContaining({
|
||||
variants: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: "test-variant_3",
|
||||
prices: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 1500,
|
||||
currency_code: "usd",
|
||||
region_id: "test-region",
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully updates a variant's prices by adding a new price", async () => {
|
||||
const usdPrice = baseProduct.variants[0].prices.find(
|
||||
(p) => p.currency_code === "usd"
|
||||
)
|
||||
const data = {
|
||||
title: "Test variant prices",
|
||||
prices: [
|
||||
{
|
||||
id: usdPrice.id,
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
},
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 4500,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}`,
|
||||
data,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
expect(response.data).toEqual(
|
||||
expect.objectContaining({
|
||||
product: expect.objectContaining({
|
||||
id: baseProduct.id,
|
||||
variants: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: baseProduct.variants[0].id,
|
||||
prices: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 100,
|
||||
currency_code: "usd",
|
||||
id: usdPrice.id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 4500,
|
||||
currency_code: "eur",
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully updates a variant's prices by deleting a price and adding another price", async () => {
|
||||
const data = {
|
||||
prices: [
|
||||
{
|
||||
currency_code: "dkk",
|
||||
amount: 8000,
|
||||
},
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 900,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}`,
|
||||
data,
|
||||
adminHeaders
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
const variant = response.data.product.variants[0]
|
||||
expect(variant.prices.length).toEqual(2)
|
||||
|
||||
expect(variant.prices).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 8000,
|
||||
currency_code: "dkk",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 900,
|
||||
currency_code: "eur",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
// TODO: Similarly we need to decide how to handle regions
|
||||
it.skip("successfully updates a variant's prices by updating an existing price (using region_id) and adding another price", async () => {
|
||||
const data = {
|
||||
prices: [
|
||||
{
|
||||
region_id: "test-region",
|
||||
amount: 8000,
|
||||
},
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 900,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const variantId = "test-variant_3"
|
||||
const response = await api
|
||||
.post(
|
||||
`/admin/products/test-product1/variants/${variantId}`,
|
||||
data,
|
||||
adminHeaders
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
const variant = response.data.product.variants.find(
|
||||
(v) => v.id === variantId
|
||||
)
|
||||
expect(variant.prices.length).toEqual(data.prices.length)
|
||||
|
||||
expect(variant.prices).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 8000,
|
||||
currency_code: "usd",
|
||||
region_id: "test-region",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 900,
|
||||
currency_code: "eur",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
// TODO: Similarly we need to decide how to handle regions
|
||||
it.skip("successfully deletes a region price", async () => {
|
||||
const createRegionPricePayload = {
|
||||
prices: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 1000,
|
||||
},
|
||||
{
|
||||
region_id: "test-region",
|
||||
amount: 8000,
|
||||
},
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 900,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const variantId = "test-variant_3"
|
||||
|
||||
const createRegionPriceResponse = await api.post(
|
||||
"/admin/products/test-product1/variants/test-variant_3",
|
||||
createRegionPricePayload,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const initialPriceArray =
|
||||
createRegionPriceResponse.data.product.variants.find(
|
||||
(v) => v.id === variantId
|
||||
).prices
|
||||
|
||||
expect(createRegionPriceResponse.status).toEqual(200)
|
||||
expect(initialPriceArray).toHaveLength(3)
|
||||
expect(initialPriceArray).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 1000,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 8000,
|
||||
currency_code: "usd",
|
||||
region_id: "test-region",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 900,
|
||||
currency_code: "eur",
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
const deleteRegionPricePayload = {
|
||||
prices: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 1000,
|
||||
},
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 900,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const deleteRegionPriceResponse = await api.post(
|
||||
"/admin/products/test-product1/variants/test-variant_3",
|
||||
deleteRegionPricePayload,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const finalPriceArray =
|
||||
deleteRegionPriceResponse.data.product.variants.find(
|
||||
(v) => v.id === variantId
|
||||
).prices
|
||||
|
||||
expect(deleteRegionPriceResponse.status).toEqual(200)
|
||||
expect(finalPriceArray).toHaveLength(2)
|
||||
expect(finalPriceArray).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 1000,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 900,
|
||||
currency_code: "eur",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
// TODO: Similarly we need to decide how to handle regions
|
||||
it.skip("successfully updates a variants prices by deleting both a currency and region price", async () => {
|
||||
// await Promise.all(
|
||||
// ["reg_1", "reg_2", "reg_3"].map(async (regionId) => {
|
||||
// return await simpleRegionFactory(dbConnection, {
|
||||
// id: regionId,
|
||||
// currency_code: regionId === "reg_1" ? "eur" : "usd",
|
||||
// })
|
||||
// })
|
||||
// )
|
||||
|
||||
const createPrices = {
|
||||
prices: [
|
||||
{
|
||||
region_id: "reg_1",
|
||||
amount: 1,
|
||||
},
|
||||
{
|
||||
region_id: "reg_2",
|
||||
amount: 2,
|
||||
},
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 3,
|
||||
},
|
||||
{
|
||||
region_id: "reg_3",
|
||||
amount: 4,
|
||||
},
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 5,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const variantId = "test-variant_3"
|
||||
|
||||
await api
|
||||
.post(
|
||||
`/admin/products/test-product1/variants/${variantId}`,
|
||||
createPrices,
|
||||
adminHeaders
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
const updatePrices = {
|
||||
prices: [
|
||||
{
|
||||
region_id: "reg_1",
|
||||
amount: 100,
|
||||
},
|
||||
{
|
||||
region_id: "reg_2",
|
||||
amount: 200,
|
||||
},
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 300,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/products/test-product1/variants/${variantId}`,
|
||||
updatePrices,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const finalPriceArray = response.data.product.variants.find(
|
||||
(v) => v.id === variantId
|
||||
).prices
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(finalPriceArray).toHaveLength(3)
|
||||
expect(finalPriceArray).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 100,
|
||||
region_id: "reg_1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 200,
|
||||
region_id: "reg_2",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 300,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("variant creation", () => {
|
||||
it("create a product variant with prices (regional and currency)", async () => {
|
||||
const payload = {
|
||||
title: "Created variant",
|
||||
sku: "new-sku",
|
||||
ean: "new-ean",
|
||||
upc: "new-upc",
|
||||
barcode: "new-barcode",
|
||||
prices: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
},
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 200,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const res = await api
|
||||
.post(
|
||||
`/admin/products/${baseProduct.id}/variants`,
|
||||
payload,
|
||||
adminHeaders
|
||||
)
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
const insertedVariant = res.data.product.variants.find(
|
||||
(v) => v.sku === "new-sku"
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
|
||||
expect(insertedVariant.prices).toHaveLength(2)
|
||||
expect(insertedVariant.prices).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
variant_id: insertedVariant.id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
currency_code: "eur",
|
||||
amount: 200,
|
||||
variant_id: insertedVariant.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("testing for soft-deletion + uniqueness on handles, collection and variant properties", () => {
|
||||
it("successfully deletes a product", async () => {
|
||||
const response = await api
|
||||
@@ -3029,314 +2457,6 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/products/:id/variants/:variant_id/inventory-items", () => {
|
||||
it("should throw an error when required attributes are not passed", async () => {
|
||||
const { response } = await api
|
||||
.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}/inventory-items`,
|
||||
{},
|
||||
adminHeaders
|
||||
)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(response.status).toEqual(400)
|
||||
expect(response.data).toEqual({
|
||||
type: "invalid_data",
|
||||
message:
|
||||
"Invalid request: Field 'required_quantity' is required; Field 'inventory_item_id' is required",
|
||||
})
|
||||
})
|
||||
|
||||
it("successfully adds inventory item to a variant", async () => {
|
||||
const inventoryItem = (
|
||||
await api.post(
|
||||
`/admin/inventory-items`,
|
||||
{ sku: "12345" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.inventory_item
|
||||
|
||||
const res = await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}/inventory-items?fields=inventory_items.inventory.*,inventory_items.*`,
|
||||
{
|
||||
inventory_item_id: inventoryItem.id,
|
||||
required_quantity: 5,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.variant.inventory_items).toHaveLength(2)
|
||||
expect(res.data.variant.inventory_items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
required_quantity: 5,
|
||||
inventory_item_id: inventoryItem.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/products/:id/variants/:variant_id/inventory-items/:inventory_id", () => {
|
||||
let inventoryItem
|
||||
|
||||
beforeEach(async () => {
|
||||
inventoryItem = (
|
||||
await api.post(
|
||||
`/admin/inventory-items`,
|
||||
{ sku: "12345" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.inventory_item
|
||||
|
||||
await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}/inventory-items`,
|
||||
{
|
||||
inventory_item_id: inventoryItem.id,
|
||||
required_quantity: 5,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when required attributes are not passed", async () => {
|
||||
const { response } = await api
|
||||
.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}/inventory-items/${inventoryItem.id}`,
|
||||
{},
|
||||
adminHeaders
|
||||
)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(response.status).toEqual(400)
|
||||
expect(response.data).toEqual({
|
||||
type: "invalid_data",
|
||||
message: "Invalid request: Field 'required_quantity' is required",
|
||||
})
|
||||
})
|
||||
|
||||
it("successfully updates an inventory item link to a variant", async () => {
|
||||
const res = await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}/inventory-items/${inventoryItem.id}?fields=inventory_items.inventory.*,inventory_items.*`,
|
||||
{ required_quantity: 10 },
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.variant.inventory_items).toHaveLength(2)
|
||||
expect(res.data.variant.inventory_items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
required_quantity: 10,
|
||||
inventory_item_id: inventoryItem.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("DELETE /admin/products/:id/variants/:variant_id/inventory-items/:inventory_id", () => {
|
||||
let inventoryItem
|
||||
|
||||
beforeEach(async () => {
|
||||
inventoryItem = (
|
||||
await api.post(
|
||||
`/admin/inventory-items`,
|
||||
{ sku: "12345" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.inventory_item
|
||||
|
||||
await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}/inventory-items`,
|
||||
{
|
||||
inventory_item_id: inventoryItem.id,
|
||||
required_quantity: 5,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully deletes an inventory item link from a variant", async () => {
|
||||
await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}/inventory-items`,
|
||||
{ inventory_item_id: inventoryItem.id, required_quantity: 5 },
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const res = await api.delete(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}/inventory-items/${inventoryItem.id}?fields=inventory_items.inventory.*,inventory_items.*`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.parent.inventory_items).toHaveLength(1)
|
||||
expect(res.data.parent.inventory_items[0].id).not.toBe(
|
||||
inventoryItem.id
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/products/:id/variants/:variant_id/inventory-items/batch", () => {
|
||||
let inventoryItemToUpdate
|
||||
let inventoryItemToDelete
|
||||
let inventoryItemToCreate
|
||||
let inventoryProduct
|
||||
let inventoryVariant1
|
||||
let inventoryVariant2
|
||||
let inventoryVariant3
|
||||
|
||||
beforeEach(async () => {
|
||||
inventoryProduct = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
{
|
||||
title: "product 1",
|
||||
variants: [
|
||||
{
|
||||
title: "variant 1",
|
||||
prices: [{ currency_code: "usd", amount: 100 }],
|
||||
},
|
||||
{
|
||||
title: "variant 2",
|
||||
prices: [{ currency_code: "usd", amount: 100 }],
|
||||
},
|
||||
{
|
||||
title: "variant 3",
|
||||
prices: [{ currency_code: "usd", amount: 100 }],
|
||||
},
|
||||
],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
|
||||
inventoryVariant1 = inventoryProduct.variants[0]
|
||||
inventoryVariant2 = inventoryProduct.variants[1]
|
||||
inventoryVariant3 = inventoryProduct.variants[2]
|
||||
|
||||
inventoryItemToCreate = (
|
||||
await api.post(
|
||||
`/admin/inventory-items`,
|
||||
{ sku: "to-create" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.inventory_item
|
||||
|
||||
inventoryItemToUpdate = (
|
||||
await api.post(
|
||||
`/admin/inventory-items`,
|
||||
{ sku: "to-update" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.inventory_item
|
||||
|
||||
inventoryItemToDelete = (
|
||||
await api.post(
|
||||
`/admin/inventory-items`,
|
||||
{ sku: "to-delete" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.inventory_item
|
||||
|
||||
await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${inventoryVariant1.id}/inventory-items`,
|
||||
{
|
||||
inventory_item_id: inventoryItemToUpdate.id,
|
||||
required_quantity: 5,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${inventoryVariant2.id}/inventory-items`,
|
||||
{
|
||||
inventory_item_id: inventoryItemToDelete.id,
|
||||
required_quantity: 10,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully creates, updates and deletes an inventory item link from a variant", async () => {
|
||||
const res = await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/inventory-items/batch`,
|
||||
{
|
||||
create: [
|
||||
{
|
||||
required_quantity: 15,
|
||||
inventory_item_id: inventoryItemToCreate.id,
|
||||
variant_id: inventoryVariant3.id,
|
||||
},
|
||||
],
|
||||
update: [
|
||||
{
|
||||
required_quantity: 25,
|
||||
inventory_item_id: inventoryItemToUpdate.id,
|
||||
variant_id: inventoryVariant1.id,
|
||||
},
|
||||
],
|
||||
delete: [
|
||||
{
|
||||
inventory_item_id: inventoryItemToDelete.id,
|
||||
variant_id: inventoryVariant2.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
|
||||
const createdLinkVariant = (
|
||||
await api.get(
|
||||
`/admin/products/${baseProduct.id}/variants/${inventoryVariant3.id}?fields=inventory_items.inventory.*,inventory_items.*`,
|
||||
adminHeaders
|
||||
)
|
||||
).data.variant
|
||||
|
||||
expect(createdLinkVariant.inventory_items).toHaveLength(2)
|
||||
expect(createdLinkVariant.inventory_items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
required_quantity: 15,
|
||||
inventory_item_id: inventoryItemToCreate.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
const updatedLinkVariant = (
|
||||
await api.get(
|
||||
`/admin/products/${baseProduct.id}/variants/${inventoryVariant1.id}?fields=inventory_items.inventory.*,inventory_items.*`,
|
||||
adminHeaders
|
||||
)
|
||||
).data.variant
|
||||
|
||||
expect(updatedLinkVariant.inventory_items).toHaveLength(2)
|
||||
expect(updatedLinkVariant.inventory_items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
required_quantity: 25,
|
||||
inventory_item_id: inventoryItemToUpdate.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
const deletedLinkVariant = (
|
||||
await api.get(
|
||||
`/admin/products/${baseProduct.id}/variants/${inventoryVariant2.id}?fields=inventory_items.inventory.*,inventory_items.*`,
|
||||
adminHeaders
|
||||
)
|
||||
).data.variant
|
||||
|
||||
expect(deletedLinkVariant.inventory_items).toHaveLength(1)
|
||||
expect(deletedLinkVariant.inventory_items[0].id).not.toEqual(
|
||||
inventoryItemToDelete.id
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
943
integration-tests/http/__tests__/product/admin/variant.spec.ts
Normal file
943
integration-tests/http/__tests__/product/admin/variant.spec.ts
Normal file
@@ -0,0 +1,943 @@
|
||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||
import {
|
||||
adminHeaders,
|
||||
createAdminUser,
|
||||
} from "../../../../helpers/create-admin-user"
|
||||
import { getProductFixture } from "../../../../helpers/fixtures"
|
||||
|
||||
jest.setTimeout(50000)
|
||||
|
||||
medusaIntegrationTestRunner({
|
||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||
let baseProduct
|
||||
let baseRegion
|
||||
|
||||
beforeEach(async () => {
|
||||
await createAdminUser(dbConnection, adminHeaders, getContainer())
|
||||
// BREAKING: Creating a region no longer takes tax_rate, payment_providers, fulfillment_providers, countriesr
|
||||
baseRegion = (
|
||||
await api.post(
|
||||
"/admin/regions",
|
||||
{
|
||||
name: "Test region",
|
||||
currency_code: "USD",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.region
|
||||
|
||||
baseProduct = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
getProductFixture({
|
||||
title: "Base product",
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
})
|
||||
|
||||
// BREAKING: We no longer have `/admin/variants` endpoint. Instead, variant is scoped by product ID, `/admin/products/:id/variants`
|
||||
describe("GET /admin/products/:id/variants", () => {
|
||||
it("should return the variants related to the requested product", async () => {
|
||||
const res = await api
|
||||
.get(`/admin/products/${baseProduct.id}/variants`, adminHeaders)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.variants.length).toBe(1)
|
||||
expect(res.data.variants).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: baseProduct.variants[0].id,
|
||||
product_id: baseProduct.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("should allow searching of variants", async () => {
|
||||
const newProduct = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
getProductFixture({
|
||||
variants: [
|
||||
{ title: "First variant", prices: [] },
|
||||
{ title: "Second variant", prices: [] },
|
||||
],
|
||||
}),
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
|
||||
const res = await api
|
||||
.get(
|
||||
`/admin/products/${newProduct.id}/variants?q=first`,
|
||||
adminHeaders
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.variants).toHaveLength(1)
|
||||
expect(res.data.variants).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
title: "First variant",
|
||||
product_id: newProduct.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("updates a variant's default prices (ignores prices associated with a Price List)", () => {
|
||||
it("successfully updates a variant's default prices by changing an existing price (currency_code)", async () => {
|
||||
const data = {
|
||||
prices: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 1500,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}`,
|
||||
data,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(
|
||||
baseProduct.variants[0].prices.find((p) => p.currency_code === "usd")
|
||||
.amount
|
||||
).toEqual(100)
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data).toEqual({
|
||||
product: expect.objectContaining({
|
||||
id: baseProduct.id,
|
||||
variants: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: baseProduct.variants[0].id,
|
||||
prices: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 1500,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: Do we want to add support for region prices through the product APIs?
|
||||
it.skip("successfully updates a variant's price by changing an existing price (given a region_id)", async () => {
|
||||
const data = {
|
||||
prices: [
|
||||
{
|
||||
region_id: "test-region",
|
||||
amount: 1500,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
"/admin/products/test-product1/variants/test-variant_3",
|
||||
data,
|
||||
adminHeaders
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
expect(response.data.product).toEqual(
|
||||
expect.objectContaining({
|
||||
variants: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: "test-variant_3",
|
||||
prices: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 1500,
|
||||
currency_code: "usd",
|
||||
region_id: "test-region",
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully updates a variant's prices by adding a new price", async () => {
|
||||
const usdPrice = baseProduct.variants[0].prices.find(
|
||||
(p) => p.currency_code === "usd"
|
||||
)
|
||||
const data = {
|
||||
title: "Test variant prices",
|
||||
prices: [
|
||||
{
|
||||
id: usdPrice.id,
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
},
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 4500,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}`,
|
||||
data,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
expect(response.data).toEqual(
|
||||
expect.objectContaining({
|
||||
product: expect.objectContaining({
|
||||
id: baseProduct.id,
|
||||
variants: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: baseProduct.variants[0].id,
|
||||
prices: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 100,
|
||||
currency_code: "usd",
|
||||
id: usdPrice.id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 4500,
|
||||
currency_code: "eur",
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully updates a variant's prices by deleting a price and adding another price", async () => {
|
||||
const data = {
|
||||
prices: [
|
||||
{
|
||||
currency_code: "dkk",
|
||||
amount: 8000,
|
||||
},
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 900,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const response = await api
|
||||
.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}`,
|
||||
data,
|
||||
adminHeaders
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
const variant = response.data.product.variants[0]
|
||||
expect(variant.prices.length).toEqual(2)
|
||||
|
||||
expect(variant.prices).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 8000,
|
||||
currency_code: "dkk",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 900,
|
||||
currency_code: "eur",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
// TODO: Similarly we need to decide how to handle regions
|
||||
it.skip("successfully updates a variant's prices by updating an existing price (using region_id) and adding another price", async () => {
|
||||
const data = {
|
||||
prices: [
|
||||
{
|
||||
region_id: "test-region",
|
||||
amount: 8000,
|
||||
},
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 900,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const variantId = "test-variant_3"
|
||||
const response = await api
|
||||
.post(
|
||||
`/admin/products/test-product1/variants/${variantId}`,
|
||||
data,
|
||||
adminHeaders
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
const variant = response.data.product.variants.find(
|
||||
(v) => v.id === variantId
|
||||
)
|
||||
expect(variant.prices.length).toEqual(data.prices.length)
|
||||
|
||||
expect(variant.prices).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 8000,
|
||||
currency_code: "usd",
|
||||
region_id: "test-region",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 900,
|
||||
currency_code: "eur",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
// TODO: Similarly we need to decide how to handle regions
|
||||
it.skip("successfully deletes a region price", async () => {
|
||||
const createRegionPricePayload = {
|
||||
prices: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 1000,
|
||||
},
|
||||
{
|
||||
region_id: "test-region",
|
||||
amount: 8000,
|
||||
},
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 900,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const variantId = "test-variant_3"
|
||||
|
||||
const createRegionPriceResponse = await api.post(
|
||||
"/admin/products/test-product1/variants/test-variant_3",
|
||||
createRegionPricePayload,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const initialPriceArray =
|
||||
createRegionPriceResponse.data.product.variants.find(
|
||||
(v) => v.id === variantId
|
||||
).prices
|
||||
|
||||
expect(createRegionPriceResponse.status).toEqual(200)
|
||||
expect(initialPriceArray).toHaveLength(3)
|
||||
expect(initialPriceArray).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 1000,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 8000,
|
||||
currency_code: "usd",
|
||||
region_id: "test-region",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 900,
|
||||
currency_code: "eur",
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
const deleteRegionPricePayload = {
|
||||
prices: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 1000,
|
||||
},
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 900,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const deleteRegionPriceResponse = await api.post(
|
||||
"/admin/products/test-product1/variants/test-variant_3",
|
||||
deleteRegionPricePayload,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const finalPriceArray =
|
||||
deleteRegionPriceResponse.data.product.variants.find(
|
||||
(v) => v.id === variantId
|
||||
).prices
|
||||
|
||||
expect(deleteRegionPriceResponse.status).toEqual(200)
|
||||
expect(finalPriceArray).toHaveLength(2)
|
||||
expect(finalPriceArray).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 1000,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 900,
|
||||
currency_code: "eur",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
// TODO: Similarly we need to decide how to handle regions
|
||||
it.skip("successfully updates a variants prices by deleting both a currency and region price", async () => {
|
||||
// await Promise.all(
|
||||
// ["reg_1", "reg_2", "reg_3"].map(async (regionId) => {
|
||||
// return await simpleRegionFactory(dbConnection, {
|
||||
// id: regionId,
|
||||
// currency_code: regionId === "reg_1" ? "eur" : "usd",
|
||||
// })
|
||||
// })
|
||||
// )
|
||||
|
||||
const createPrices = {
|
||||
prices: [
|
||||
{
|
||||
region_id: "reg_1",
|
||||
amount: 1,
|
||||
},
|
||||
{
|
||||
region_id: "reg_2",
|
||||
amount: 2,
|
||||
},
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 3,
|
||||
},
|
||||
{
|
||||
region_id: "reg_3",
|
||||
amount: 4,
|
||||
},
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 5,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const variantId = "test-variant_3"
|
||||
|
||||
await api
|
||||
.post(
|
||||
`/admin/products/test-product1/variants/${variantId}`,
|
||||
createPrices,
|
||||
adminHeaders
|
||||
)
|
||||
.catch((err) => {
|
||||
console.log(err)
|
||||
})
|
||||
|
||||
const updatePrices = {
|
||||
prices: [
|
||||
{
|
||||
region_id: "reg_1",
|
||||
amount: 100,
|
||||
},
|
||||
{
|
||||
region_id: "reg_2",
|
||||
amount: 200,
|
||||
},
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 300,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/products/test-product1/variants/${variantId}`,
|
||||
updatePrices,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const finalPriceArray = response.data.product.variants.find(
|
||||
(v) => v.id === variantId
|
||||
).prices
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(finalPriceArray).toHaveLength(3)
|
||||
expect(finalPriceArray).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
amount: 100,
|
||||
region_id: "reg_1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 200,
|
||||
region_id: "reg_2",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
amount: 300,
|
||||
currency_code: "usd",
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: Do we want to support price calculation on the admin endpoints? Enable this suite if we do, otherwise remove it
|
||||
describe.skip("variant pricing calculations", () => {
|
||||
it("selects prices based on the passed currency code", async () => {
|
||||
const response = await api.get(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}?fields=calculated_price¤cy_code=usd`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.data.variant).toEqual({
|
||||
id: baseProduct.variants[0].id,
|
||||
original_price: 100,
|
||||
calculated_price: 80,
|
||||
calculated_price_type: "sale",
|
||||
original_price_incl_tax: null,
|
||||
calculated_price_incl_tax: null,
|
||||
original_tax: null,
|
||||
calculated_tax: null,
|
||||
options: expect.any(Array),
|
||||
prices: expect.any(Array),
|
||||
product: expect.any(Object),
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
})
|
||||
})
|
||||
|
||||
it("selects prices based on the passed region id", async () => {
|
||||
const response = await api.get(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}?fields=calculated_price®ion_id=${baseRegion.id}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.data.variant).toEqual({
|
||||
id: "test-variant",
|
||||
original_price: 100,
|
||||
calculated_price: 80,
|
||||
calculated_price_type: "sale",
|
||||
original_price_incl_tax: 100,
|
||||
calculated_price_incl_tax: 80,
|
||||
original_tax: 0,
|
||||
calculated_tax: 0,
|
||||
options: expect.any(Array),
|
||||
prices: expect.any(Array),
|
||||
product: expect.any(Object),
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
})
|
||||
})
|
||||
|
||||
it("selects prices based on the passed region id and customer id", async () => {
|
||||
const response = await api.get(
|
||||
`/admin/products/${baseProduct.id}/variants/${
|
||||
baseProduct.variants[0].id
|
||||
}?fields=calculated_price®ion_id=${
|
||||
baseRegion.id
|
||||
}&customer_id=${""}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.data.variant).toEqual({
|
||||
id: "test-variant",
|
||||
original_price: 100,
|
||||
calculated_price: 40,
|
||||
calculated_price_type: "sale",
|
||||
original_price_incl_tax: 100,
|
||||
calculated_price_incl_tax: 40,
|
||||
original_tax: 0,
|
||||
calculated_tax: 0,
|
||||
prices: expect.any(Array),
|
||||
options: expect.any(Array),
|
||||
product: expect.any(Object),
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("variant creation", () => {
|
||||
it("create a product variant with prices (regional and currency)", async () => {
|
||||
const payload = {
|
||||
title: "Created variant",
|
||||
sku: "new-sku",
|
||||
ean: "new-ean",
|
||||
upc: "new-upc",
|
||||
barcode: "new-barcode",
|
||||
prices: [
|
||||
{
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
},
|
||||
{
|
||||
currency_code: "eur",
|
||||
amount: 200,
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const res = await api
|
||||
.post(
|
||||
`/admin/products/${baseProduct.id}/variants`,
|
||||
payload,
|
||||
adminHeaders
|
||||
)
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
const insertedVariant = res.data.product.variants.find(
|
||||
(v) => v.sku === "new-sku"
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
|
||||
expect(insertedVariant.prices).toHaveLength(2)
|
||||
expect(insertedVariant.prices).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
currency_code: "usd",
|
||||
amount: 100,
|
||||
variant_id: insertedVariant.id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
currency_code: "eur",
|
||||
amount: 200,
|
||||
variant_id: insertedVariant.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/products/:id/variants/:variant_id/inventory-items", () => {
|
||||
it("should throw an error when required attributes are not passed", async () => {
|
||||
const { response } = await api
|
||||
.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}/inventory-items`,
|
||||
{},
|
||||
adminHeaders
|
||||
)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(response.status).toEqual(400)
|
||||
expect(response.data).toEqual({
|
||||
type: "invalid_data",
|
||||
message:
|
||||
"Invalid request: Field 'required_quantity' is required; Field 'inventory_item_id' is required",
|
||||
})
|
||||
})
|
||||
|
||||
it("successfully adds inventory item to a variant", async () => {
|
||||
const inventoryItem = (
|
||||
await api.post(
|
||||
`/admin/inventory-items`,
|
||||
{ sku: "12345" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.inventory_item
|
||||
|
||||
const res = await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}/inventory-items?fields=inventory_items.inventory.*,inventory_items.*`,
|
||||
{
|
||||
inventory_item_id: inventoryItem.id,
|
||||
required_quantity: 5,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.variant.inventory_items).toHaveLength(2)
|
||||
expect(res.data.variant.inventory_items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
required_quantity: 5,
|
||||
inventory_item_id: inventoryItem.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/products/:id/variants/:variant_id/inventory-items/:inventory_id", () => {
|
||||
let inventoryItem
|
||||
|
||||
beforeEach(async () => {
|
||||
inventoryItem = (
|
||||
await api.post(
|
||||
`/admin/inventory-items`,
|
||||
{ sku: "12345" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.inventory_item
|
||||
|
||||
await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}/inventory-items`,
|
||||
{
|
||||
inventory_item_id: inventoryItem.id,
|
||||
required_quantity: 5,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when required attributes are not passed", async () => {
|
||||
const { response } = await api
|
||||
.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}/inventory-items/${inventoryItem.id}`,
|
||||
{},
|
||||
adminHeaders
|
||||
)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(response.status).toEqual(400)
|
||||
expect(response.data).toEqual({
|
||||
type: "invalid_data",
|
||||
message: "Invalid request: Field 'required_quantity' is required",
|
||||
})
|
||||
})
|
||||
|
||||
it("successfully updates an inventory item link to a variant", async () => {
|
||||
const res = await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}/inventory-items/${inventoryItem.id}?fields=inventory_items.inventory.*,inventory_items.*`,
|
||||
{ required_quantity: 10 },
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.variant.inventory_items).toHaveLength(2)
|
||||
expect(res.data.variant.inventory_items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
required_quantity: 10,
|
||||
inventory_item_id: inventoryItem.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("DELETE /admin/products/:id/variants/:variant_id/inventory-items/:inventory_id", () => {
|
||||
let inventoryItem
|
||||
|
||||
beforeEach(async () => {
|
||||
inventoryItem = (
|
||||
await api.post(
|
||||
`/admin/inventory-items`,
|
||||
{ sku: "12345" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.inventory_item
|
||||
|
||||
await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}/inventory-items`,
|
||||
{
|
||||
inventory_item_id: inventoryItem.id,
|
||||
required_quantity: 5,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully deletes an inventory item link from a variant", async () => {
|
||||
await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}/inventory-items`,
|
||||
{ inventory_item_id: inventoryItem.id, required_quantity: 5 },
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const res = await api.delete(
|
||||
`/admin/products/${baseProduct.id}/variants/${baseProduct.variants[0].id}/inventory-items/${inventoryItem.id}?fields=inventory_items.inventory.*,inventory_items.*`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
expect(res.data.parent.inventory_items).toHaveLength(1)
|
||||
expect(res.data.parent.inventory_items[0].id).not.toBe(inventoryItem.id)
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/products/:id/variants/:variant_id/inventory-items/batch", () => {
|
||||
let inventoryItemToUpdate
|
||||
let inventoryItemToDelete
|
||||
let inventoryItemToCreate
|
||||
let inventoryProduct
|
||||
let inventoryVariant1
|
||||
let inventoryVariant2
|
||||
let inventoryVariant3
|
||||
|
||||
beforeEach(async () => {
|
||||
inventoryProduct = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
{
|
||||
title: "product 1",
|
||||
variants: [
|
||||
{
|
||||
title: "variant 1",
|
||||
prices: [{ currency_code: "usd", amount: 100 }],
|
||||
},
|
||||
{
|
||||
title: "variant 2",
|
||||
prices: [{ currency_code: "usd", amount: 100 }],
|
||||
},
|
||||
{
|
||||
title: "variant 3",
|
||||
prices: [{ currency_code: "usd", amount: 100 }],
|
||||
},
|
||||
],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
|
||||
inventoryVariant1 = inventoryProduct.variants[0]
|
||||
inventoryVariant2 = inventoryProduct.variants[1]
|
||||
inventoryVariant3 = inventoryProduct.variants[2]
|
||||
|
||||
inventoryItemToCreate = (
|
||||
await api.post(
|
||||
`/admin/inventory-items`,
|
||||
{ sku: "to-create" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.inventory_item
|
||||
|
||||
inventoryItemToUpdate = (
|
||||
await api.post(
|
||||
`/admin/inventory-items`,
|
||||
{ sku: "to-update" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.inventory_item
|
||||
|
||||
inventoryItemToDelete = (
|
||||
await api.post(
|
||||
`/admin/inventory-items`,
|
||||
{ sku: "to-delete" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.inventory_item
|
||||
|
||||
await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${inventoryVariant1.id}/inventory-items`,
|
||||
{
|
||||
inventory_item_id: inventoryItemToUpdate.id,
|
||||
required_quantity: 5,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/${inventoryVariant2.id}/inventory-items`,
|
||||
{
|
||||
inventory_item_id: inventoryItemToDelete.id,
|
||||
required_quantity: 10,
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully creates, updates and deletes an inventory item link from a variant", async () => {
|
||||
const res = await api.post(
|
||||
`/admin/products/${baseProduct.id}/variants/inventory-items/batch`,
|
||||
{
|
||||
create: [
|
||||
{
|
||||
required_quantity: 15,
|
||||
inventory_item_id: inventoryItemToCreate.id,
|
||||
variant_id: inventoryVariant3.id,
|
||||
},
|
||||
],
|
||||
update: [
|
||||
{
|
||||
required_quantity: 25,
|
||||
inventory_item_id: inventoryItemToUpdate.id,
|
||||
variant_id: inventoryVariant1.id,
|
||||
},
|
||||
],
|
||||
delete: [
|
||||
{
|
||||
inventory_item_id: inventoryItemToDelete.id,
|
||||
variant_id: inventoryVariant2.id,
|
||||
},
|
||||
],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(res.status).toEqual(200)
|
||||
|
||||
const createdLinkVariant = (
|
||||
await api.get(
|
||||
`/admin/products/${baseProduct.id}/variants/${inventoryVariant3.id}?fields=inventory_items.inventory.*,inventory_items.*`,
|
||||
adminHeaders
|
||||
)
|
||||
).data.variant
|
||||
|
||||
expect(createdLinkVariant.inventory_items).toHaveLength(2)
|
||||
expect(createdLinkVariant.inventory_items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
required_quantity: 15,
|
||||
inventory_item_id: inventoryItemToCreate.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
const updatedLinkVariant = (
|
||||
await api.get(
|
||||
`/admin/products/${baseProduct.id}/variants/${inventoryVariant1.id}?fields=inventory_items.inventory.*,inventory_items.*`,
|
||||
adminHeaders
|
||||
)
|
||||
).data.variant
|
||||
|
||||
expect(updatedLinkVariant.inventory_items).toHaveLength(2)
|
||||
expect(updatedLinkVariant.inventory_items).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
required_quantity: 25,
|
||||
inventory_item_id: inventoryItemToUpdate.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
|
||||
const deletedLinkVariant = (
|
||||
await api.get(
|
||||
`/admin/products/${baseProduct.id}/variants/${inventoryVariant2.id}?fields=inventory_items.inventory.*,inventory_items.*`,
|
||||
adminHeaders
|
||||
)
|
||||
).data.variant
|
||||
|
||||
expect(deletedLinkVariant.inventory_items).toHaveLength(1)
|
||||
expect(deletedLinkVariant.inventory_items[0].id).not.toEqual(
|
||||
inventoryItemToDelete.id
|
||||
)
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
405
integration-tests/http/__tests__/product/store/product.spec.ts
Normal file
405
integration-tests/http/__tests__/product/store/product.spec.ts
Normal file
@@ -0,0 +1,405 @@
|
||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||
import {
|
||||
adminHeaders,
|
||||
createAdminUser,
|
||||
} from "../../../../helpers/create-admin-user"
|
||||
import { getProductFixture } from "../../../../helpers/fixtures"
|
||||
import { ModuleRegistrationName } from "@medusajs/utils"
|
||||
import { IStoreModuleService } from "@medusajs/types"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
medusaIntegrationTestRunner({
|
||||
testSuite: ({ dbConnection, api, getContainer }) => {
|
||||
let store
|
||||
let product1
|
||||
let product2
|
||||
let product3
|
||||
|
||||
beforeEach(async () => {
|
||||
const appContainer = getContainer()
|
||||
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
||||
|
||||
const storeModule: IStoreModuleService = appContainer.resolve(
|
||||
ModuleRegistrationName.STORE
|
||||
)
|
||||
// A default store is created when the app is started, so we want to delete that one and create one specifically for our tests.
|
||||
const defaultId = (await api.get("/admin/stores", adminHeaders)).data
|
||||
.stores?.[0]?.id
|
||||
if (defaultId) {
|
||||
storeModule.delete(defaultId)
|
||||
}
|
||||
|
||||
store = await storeModule.create({
|
||||
name: "New store",
|
||||
supported_currency_codes: ["usd", "dkk"],
|
||||
default_currency_code: "usd",
|
||||
})
|
||||
|
||||
product1 = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
getProductFixture({ title: "test1", status: "published" }),
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
product2 = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
getProductFixture({ title: "test2", status: "published" }),
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
product3 = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
getProductFixture({ title: "test3", status: "published" }),
|
||||
adminHeaders
|
||||
)
|
||||
).data.product
|
||||
})
|
||||
|
||||
describe("Get products based on publishable key", () => {
|
||||
let pubKey1
|
||||
let salesChannel1
|
||||
let salesChannel2
|
||||
|
||||
beforeEach(async () => {
|
||||
pubKey1 = (
|
||||
await api.post(
|
||||
"/admin/api-keys",
|
||||
{ title: "sample key", type: "publishable" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.api_key
|
||||
|
||||
salesChannel1 = (
|
||||
await api.post(
|
||||
"/admin/sales-channels",
|
||||
{
|
||||
name: "test name",
|
||||
description: "test description",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.sales_channel
|
||||
|
||||
salesChannel2 = (
|
||||
await api.post(
|
||||
"/admin/sales-channels",
|
||||
{
|
||||
name: "test name 2",
|
||||
description: "test description 2",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.sales_channel
|
||||
|
||||
await api.post(
|
||||
`/admin/sales-channels/${salesChannel1.id}/products`,
|
||||
{ add: [product1.id] },
|
||||
adminHeaders
|
||||
)
|
||||
await api.post(
|
||||
`/admin/sales-channels/${salesChannel2.id}/products`,
|
||||
{ add: [product2.id] },
|
||||
adminHeaders
|
||||
)
|
||||
await api.post(
|
||||
`/admin/stores/${store.id}`,
|
||||
{ default_sales_channel_id: salesChannel1.id },
|
||||
adminHeaders
|
||||
)
|
||||
})
|
||||
|
||||
it("returns products from a specific channel associated with a publishable key", async () => {
|
||||
await api.post(
|
||||
`/admin/api-keys/${pubKey1.id}/sales-channels`,
|
||||
{
|
||||
add: [salesChannel1.id],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const response = await api.get(`/store/products`, {
|
||||
headers: {
|
||||
...adminHeaders.headers,
|
||||
"x-publishable-api-key": pubKey1.token,
|
||||
},
|
||||
})
|
||||
|
||||
expect(response.data.products.length).toBe(1)
|
||||
expect(response.data.products).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: product1.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("returns products from multiples sales channels associated with a publishable key", async () => {
|
||||
await api.post(
|
||||
`/admin/api-keys/${pubKey1.id}/sales-channels`,
|
||||
{
|
||||
add: [salesChannel1.id, salesChannel2.id],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const response = await api.get(`/store/products`, {
|
||||
headers: {
|
||||
...adminHeaders.headers,
|
||||
"x-publishable-api-key": pubKey1.token,
|
||||
},
|
||||
})
|
||||
|
||||
expect(response.data.products.length).toBe(2)
|
||||
expect(response.data.products).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: product2.id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: product1.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("SC param overrides PK channels (but SK still needs to be in the PK's scope", async () => {
|
||||
await api.post(
|
||||
`/admin/api-keys/${pubKey1.id}/sales-channels`,
|
||||
{
|
||||
add: [salesChannel1.id, salesChannel2.id],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const response = await api.get(
|
||||
`/store/products?sales_channel_id[0]=${salesChannel2.id}`,
|
||||
{
|
||||
headers: {
|
||||
...adminHeaders.headers,
|
||||
"x-publishable-api-key": pubKey1.token,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expect(response.data.products.length).toBe(1)
|
||||
expect(response.data.products).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: product2.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("returns default product from default sales channel if PK is not passed", async () => {
|
||||
await api.post(
|
||||
`/admin/stores/${store.id}`,
|
||||
{ default_sales_channel_id: salesChannel2.id },
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
await api.post(
|
||||
`/admin/api-keys/${pubKey1.id}/sales-channels`,
|
||||
{
|
||||
add: [salesChannel1.id, salesChannel2.id],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const response = await api.get(`/store/products`, {
|
||||
adminHeaders,
|
||||
})
|
||||
|
||||
expect(response.data.products.length).toBe(1)
|
||||
expect(response.data.products).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: product2.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
// TODO: Decide if this is the behavior we want to keep in v2, as it seems a bit strange
|
||||
it.skip("returns all products if passed PK doesn't have associated channels", async () => {
|
||||
const response = await api.get(`/store/products`, {
|
||||
headers: {
|
||||
...adminHeaders.headers,
|
||||
"x-publishable-api-key": pubKey1.token,
|
||||
},
|
||||
})
|
||||
|
||||
expect(response.data.products.length).toBe(3)
|
||||
expect(response.data.products).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: product1.id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: product2.id,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: product3.id,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("throws because sales channel param is not in the scope of passed PK", async () => {
|
||||
await api.post(
|
||||
`/admin/api-keys/${pubKey1.id}/sales-channels`,
|
||||
{
|
||||
add: [salesChannel1.id],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const err = await api
|
||||
.get(`/store/products?sales_channel_id[]=${salesChannel2.id}`, {
|
||||
headers: {
|
||||
...adminHeaders.headers,
|
||||
"x-publishable-api-key": pubKey1.token,
|
||||
},
|
||||
})
|
||||
.catch((e) => e)
|
||||
|
||||
expect(err.response.status).toEqual(400)
|
||||
expect(err.response.data.message).toEqual(
|
||||
`Requested sales channel is not part of the publishable key mappings`
|
||||
)
|
||||
})
|
||||
|
||||
it("retrieve a product from a specific channel associated with a publishable key", async () => {
|
||||
await api.post(
|
||||
`/admin/api-keys/${pubKey1.id}/sales-channels`,
|
||||
{
|
||||
add: [salesChannel1.id],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const response = await api.get(`/store/products/${product1.id}`, {
|
||||
headers: {
|
||||
...adminHeaders.headers,
|
||||
"x-publishable-api-key": pubKey1.token,
|
||||
},
|
||||
})
|
||||
|
||||
expect(response.data.product).toEqual(
|
||||
expect.objectContaining({
|
||||
id: product1.id,
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
// BREAKING: If product not in sales channel we used to return 400, we return 404 instead.
|
||||
it("return 404 because requested product is not in the SC associated with a publishable key", async () => {
|
||||
await api.post(
|
||||
`/admin/api-keys/${pubKey1.id}/sales-channels`,
|
||||
{
|
||||
add: [salesChannel1.id],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const err = await api
|
||||
.get(`/store/products/${product2.id}`, {
|
||||
headers: {
|
||||
...adminHeaders.headers,
|
||||
"x-publishable-api-key": pubKey1.token,
|
||||
},
|
||||
})
|
||||
.catch((e) => e)
|
||||
|
||||
expect(err.response.status).toEqual(404)
|
||||
})
|
||||
|
||||
// TODO: Add variant endpoints to the store API (if that is what we want)
|
||||
it.skip("should return 404 when the requested variant doesn't exist", async () => {
|
||||
await api.post(
|
||||
`/admin/api-keys/${pubKey1.id}/sales-channels`,
|
||||
{
|
||||
add: [salesChannel1.id],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const response = await api
|
||||
.get(`/store/variants/does-not-exist`, {
|
||||
headers: {
|
||||
...adminHeaders.headers,
|
||||
"x-publishable-api-key": pubKey1.token,
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
return err.response
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(404)
|
||||
expect(response.data.message).toEqual(
|
||||
"Variant with id: does-not-exist was not found"
|
||||
)
|
||||
})
|
||||
|
||||
it("should return 404 when the requested product doesn't exist", async () => {
|
||||
await api.post(
|
||||
`/admin/api-keys/${pubKey1.id}/sales-channels`,
|
||||
{
|
||||
add: [salesChannel1.id],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
const response = await api
|
||||
.get(`/store/products/does-not-exist`, {
|
||||
headers: {
|
||||
...adminHeaders.headers,
|
||||
"x-publishable-api-key": pubKey1.token,
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
return err.response
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(404)
|
||||
expect(response.data.message).toEqual(
|
||||
"Product with id: does-not-exist was not found"
|
||||
)
|
||||
})
|
||||
|
||||
// TODO: Similar to above, decide what the behavior should be in v2
|
||||
it.skip("correctly returns a product if passed PK has no associated SCs", async () => {
|
||||
let response = await api
|
||||
.get(`/store/products/${product1.id}`, {
|
||||
headers: {
|
||||
...adminHeaders.headers,
|
||||
"x-publishable-api-key": pubKey1.token,
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
return err.response
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
|
||||
response = await api
|
||||
.get(`/store/products/${product2.id}`, {
|
||||
headers: {
|
||||
...adminHeaders.headers,
|
||||
"x-publishable-api-key": pubKey1.token,
|
||||
},
|
||||
})
|
||||
.catch((err) => {
|
||||
return err.response
|
||||
})
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
@@ -1,39 +1,47 @@
|
||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||
import {
|
||||
adminHeaders,
|
||||
createAdminUser,
|
||||
adminHeaders,
|
||||
createAdminUser,
|
||||
} from "../../../../helpers/create-admin-user"
|
||||
|
||||
jest.setTimeout(60000)
|
||||
|
||||
medusaIntegrationTestRunner({
|
||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||
beforeAll(() => {})
|
||||
let salesChannel1
|
||||
let salesChannel2
|
||||
|
||||
beforeEach(async () => {
|
||||
const container = getContainer()
|
||||
await createAdminUser(dbConnection, adminHeaders, container)
|
||||
|
||||
salesChannel1 = (
|
||||
await api.post(
|
||||
"/admin/sales-channels",
|
||||
{
|
||||
name: "test name",
|
||||
description: "test description",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.sales_channel
|
||||
|
||||
salesChannel2 = (
|
||||
await api.post(
|
||||
"/admin/sales-channels",
|
||||
{
|
||||
name: "test name 2",
|
||||
description: "test description 2",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.sales_channel
|
||||
})
|
||||
|
||||
describe("GET /admin/sales-channels/:id", () => {
|
||||
let salesChannel
|
||||
|
||||
beforeEach(async () => {
|
||||
salesChannel = (
|
||||
await api.post(
|
||||
"/admin/sales-channels",
|
||||
{
|
||||
name: "test name",
|
||||
description: "test description",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.sales_channel
|
||||
})
|
||||
|
||||
it("should retrieve the requested sales channel", async () => {
|
||||
const response = await api.get(
|
||||
`/admin/sales-channels/${salesChannel.id}`,
|
||||
`/admin/sales-channels/${salesChannel1.id}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
@@ -42,8 +50,8 @@ medusaIntegrationTestRunner({
|
||||
expect(response.data.sales_channel).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
name: salesChannel.name,
|
||||
description: salesChannel.description,
|
||||
name: salesChannel1.name,
|
||||
description: salesChannel1.description,
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
})
|
||||
@@ -52,33 +60,6 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
|
||||
describe("GET /admin/sales-channels", () => {
|
||||
let salesChannel1
|
||||
let salesChannel2
|
||||
|
||||
beforeEach(async () => {
|
||||
salesChannel1 = (
|
||||
await api.post(
|
||||
"/admin/sales-channels",
|
||||
{
|
||||
name: "test name",
|
||||
description: "test description",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.sales_channel
|
||||
|
||||
salesChannel2 = (
|
||||
await api.post(
|
||||
"/admin/sales-channels",
|
||||
{
|
||||
name: "test name 2",
|
||||
description: "test description 2",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.sales_channel
|
||||
})
|
||||
|
||||
it("should list the sales channel", async () => {
|
||||
const response = await api.get(`/admin/sales-channels`, adminHeaders)
|
||||
|
||||
@@ -183,21 +164,6 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
|
||||
describe("POST /admin/sales-channels/:id", () => {
|
||||
let sc
|
||||
|
||||
beforeEach(async () => {
|
||||
sc = (
|
||||
await api.post(
|
||||
"/admin/sales-channels",
|
||||
{
|
||||
name: "test name",
|
||||
description: "test description",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.sales_channel
|
||||
})
|
||||
|
||||
it("updates sales channel properties", async () => {
|
||||
const payload = {
|
||||
name: "updated name",
|
||||
@@ -206,7 +172,7 @@ medusaIntegrationTestRunner({
|
||||
}
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/sales-channels/${sc.id}`,
|
||||
`/admin/sales-channels/${salesChannel1.id}`,
|
||||
payload,
|
||||
adminHeaders
|
||||
)
|
||||
@@ -279,46 +245,19 @@ medusaIntegrationTestRunner({
|
||||
})
|
||||
|
||||
describe("DELETE /admin/sales-channels/:id", () => {
|
||||
let salesChannel
|
||||
let salesChannel2
|
||||
|
||||
beforeEach(async () => {
|
||||
salesChannel = (
|
||||
await api.post(
|
||||
"/admin/sales-channels",
|
||||
{
|
||||
name: "test name",
|
||||
description: "test description",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.sales_channel
|
||||
|
||||
salesChannel2 = (
|
||||
await api.post(
|
||||
"/admin/sales-channels",
|
||||
{
|
||||
name: "test name 2",
|
||||
description: "test description 2",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.sales_channel
|
||||
})
|
||||
|
||||
it("should delete the requested sales channel", async () => {
|
||||
const toDelete = (
|
||||
await api.get(
|
||||
`/admin/sales-channels/${salesChannel.id}`,
|
||||
`/admin/sales-channels/${salesChannel1.id}`,
|
||||
adminHeaders
|
||||
)
|
||||
).data.sales_channel
|
||||
|
||||
expect(toDelete.id).toEqual(salesChannel.id)
|
||||
expect(toDelete.id).toEqual(salesChannel1.id)
|
||||
expect(toDelete.deleted_at).toEqual(null)
|
||||
|
||||
const response = await api.delete(
|
||||
`/admin/sales-channels/${salesChannel.id}`,
|
||||
`/admin/sales-channels/${salesChannel1.id}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
@@ -331,13 +270,13 @@ medusaIntegrationTestRunner({
|
||||
|
||||
await api
|
||||
.get(
|
||||
`/admin/sales-channels/${salesChannel.id}?fields=id,deleted_at`,
|
||||
`/admin/sales-channels/${salesChannel1.id}?fields=id,deleted_at`,
|
||||
adminHeaders
|
||||
)
|
||||
.catch((err) => {
|
||||
expect(err.response.data.type).toEqual("not_found")
|
||||
expect(err.response.data.message).toEqual(
|
||||
`Sales channel with id: ${salesChannel.id} not found`
|
||||
`Sales channel with id: ${salesChannel1.id} not found`
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -356,13 +295,13 @@ medusaIntegrationTestRunner({
|
||||
await api.post(
|
||||
`/admin/stock-locations/${location.id}/sales-channels`,
|
||||
{
|
||||
add: [salesChannel.id, salesChannel2.id],
|
||||
add: [salesChannel1.id, salesChannel2.id],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
await api.delete(
|
||||
`/admin/sales-channels/${salesChannel.id}`,
|
||||
`/admin/sales-channels/${salesChannel1.id}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
@@ -381,21 +320,8 @@ medusaIntegrationTestRunner({
|
||||
// BREAKING CHANGE: Endpoint has changed
|
||||
// from: /admin/sales-channels/:id/products/batch
|
||||
// to: /admin/sales-channels/:id/products
|
||||
|
||||
let salesChannel
|
||||
let product
|
||||
beforeEach(async () => {
|
||||
salesChannel = (
|
||||
await api.post(
|
||||
"/admin/sales-channels",
|
||||
{
|
||||
name: "test name",
|
||||
description: "test description",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
).data.sales_channel
|
||||
|
||||
product = (
|
||||
await api.post(
|
||||
"/admin/products",
|
||||
@@ -409,7 +335,7 @@ medusaIntegrationTestRunner({
|
||||
|
||||
it("should add products to a sales channel", async () => {
|
||||
const response = await api.post(
|
||||
`/admin/sales-channels/${salesChannel.id}/products`,
|
||||
`/admin/sales-channels/${salesChannel1.id}/products`,
|
||||
{ add: [product.id] },
|
||||
adminHeaders
|
||||
)
|
||||
@@ -449,7 +375,7 @@ medusaIntegrationTestRunner({
|
||||
|
||||
it("should remove products from a sales channel", async () => {
|
||||
await api.post(
|
||||
`/admin/sales-channels/${salesChannel.id}/products`,
|
||||
`/admin/sales-channels/${salesChannel1.id}/products`,
|
||||
{ add: [product.id] },
|
||||
adminHeaders
|
||||
)
|
||||
@@ -474,7 +400,7 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
|
||||
const response = await api.post(
|
||||
`/admin/sales-channels/${salesChannel.id}/products`,
|
||||
`/admin/sales-channels/${salesChannel1.id}/products`,
|
||||
{ remove: [product.id] },
|
||||
adminHeaders
|
||||
)
|
||||
@@ -499,7 +425,50 @@ medusaIntegrationTestRunner({
|
||||
expect(product.sales_channels.length).toBe(0)
|
||||
})
|
||||
})
|
||||
// DELETED TESTS:
|
||||
|
||||
describe("Sales channels with publishable key", () => {
|
||||
let pubKey1
|
||||
beforeEach(async () => {
|
||||
pubKey1 = (
|
||||
await api.post(
|
||||
"/admin/api-keys",
|
||||
{ title: "sample key", type: "publishable" },
|
||||
adminHeaders
|
||||
)
|
||||
).data.api_key
|
||||
|
||||
await api.post(
|
||||
`/admin/api-keys/${pubKey1.id}/sales-channels`,
|
||||
{
|
||||
add: [salesChannel1.id, salesChannel2.id],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
})
|
||||
|
||||
it("list sales channels from the publishable api key with free text search filter", async () => {
|
||||
const response = await api.get(
|
||||
`/admin/sales-channels?q=2&publishable_api_key=${pubKey1.id}`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toBe(200)
|
||||
expect(response.data.sales_channels.length).toEqual(1)
|
||||
expect(response.data.sales_channels).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: salesChannel2.id,
|
||||
deleted_at: null,
|
||||
name: "test name 2",
|
||||
description: "test description 2",
|
||||
is_disabled: false,
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
// BREAKING: DELETED TESTS:
|
||||
// - POST /admin/products/:id
|
||||
// - Mutation sales channels on products
|
||||
// - POST /admin/products
|
||||
|
||||
194
integration-tests/http/__tests__/store/admin/store.spec.ts
Normal file
194
integration-tests/http/__tests__/store/admin/store.spec.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||
import {
|
||||
adminHeaders,
|
||||
createAdminUser,
|
||||
} from "../../../../helpers/create-admin-user"
|
||||
import { ModuleRegistrationName } from "@medusajs/utils"
|
||||
import { IStoreModuleService } from "@medusajs/types"
|
||||
|
||||
jest.setTimeout(90000)
|
||||
|
||||
medusaIntegrationTestRunner({
|
||||
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||
describe("/admin/stores", () => {
|
||||
let store
|
||||
let container
|
||||
|
||||
beforeEach(async () => {
|
||||
container = getContainer()
|
||||
const storeModule: IStoreModuleService = container.resolve(
|
||||
ModuleRegistrationName.STORE
|
||||
)
|
||||
await createAdminUser(dbConnection, adminHeaders, container)
|
||||
|
||||
// A default store is created when the app is started, so we want to delete that one and create one specifically for our tests.
|
||||
const defaultId = (await api.get("/admin/stores", adminHeaders)).data
|
||||
.stores?.[0]?.id
|
||||
if (defaultId) {
|
||||
storeModule.delete(defaultId)
|
||||
}
|
||||
|
||||
store = await storeModule.create({
|
||||
name: "New store",
|
||||
supported_currency_codes: ["usd", "dkk"],
|
||||
default_currency_code: "usd",
|
||||
default_sales_channel_id: "sc_12345",
|
||||
})
|
||||
})
|
||||
|
||||
// BREAKING: The URL changed from `GET /admin/store` to `GET /admin/stores`
|
||||
describe("Store creation", () => {
|
||||
it("has created store with default currency", async () => {
|
||||
const resStore = (
|
||||
await api.get("/admin/stores", adminHeaders)
|
||||
).data.stores.find((s) => s.id === store.id)
|
||||
|
||||
// BREAKING: The store response contained currencies, modules, and feature flags, which are not present anymore
|
||||
expect(resStore).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
name: "New store",
|
||||
default_currency_code: "usd",
|
||||
default_sales_channel_id: expect.any(String),
|
||||
supported_currency_codes: ["usd", "dkk"],
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("POST /admin/stores", () => {
|
||||
it("fails to update default currency if not in store currencies", async () => {
|
||||
const err = await api
|
||||
.post(
|
||||
`/admin/stores/${store.id}`,
|
||||
{
|
||||
default_currency_code: "eur",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(err.response.status).toBe(400)
|
||||
expect(err.response.data).toEqual(
|
||||
expect.objectContaining({
|
||||
type: "invalid_data",
|
||||
message: "Store does not have currency: eur",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
// BREAKING: `currencies` was renamed to `supported_currency_codes`
|
||||
it("fails to remove default currency from currencies without replacing it", async () => {
|
||||
const err = await api
|
||||
.post(
|
||||
`/admin/stores/${store.id}`,
|
||||
{ supported_currency_codes: ["dkk"] },
|
||||
adminHeaders
|
||||
)
|
||||
.catch((e) => e)
|
||||
|
||||
expect(err.response.status).toBe(400)
|
||||
expect(err.response.data).toEqual(
|
||||
expect.objectContaining({
|
||||
type: "invalid_data",
|
||||
message:
|
||||
"You are not allowed to remove default currency from store currencies without replacing it as well",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully updates default currency code", async () => {
|
||||
const response = await api
|
||||
.post(
|
||||
`/admin/stores/${store.id}`,
|
||||
{
|
||||
default_currency_code: "dkk",
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
.catch((err) => console.log(err))
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.store).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
name: "New store",
|
||||
default_currency_code: "dkk",
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully updates default currency and store currencies", async () => {
|
||||
const response = await api.post(
|
||||
`/admin/stores/${store.id}`,
|
||||
{
|
||||
default_currency_code: "jpy",
|
||||
supported_currency_codes: ["jpy", "usd"],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.store).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
name: "New store",
|
||||
default_sales_channel_id: expect.any(String),
|
||||
supported_currency_codes: ["jpy", "usd"],
|
||||
default_currency_code: "jpy",
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully updates and store currencies", async () => {
|
||||
const response = await api.post(
|
||||
`/admin/stores/${store.id}`,
|
||||
{
|
||||
supported_currency_codes: ["jpy", "usd"],
|
||||
},
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.store).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
default_sales_channel_id: expect.any(String),
|
||||
name: "New store",
|
||||
supported_currency_codes: ["jpy", "usd"],
|
||||
default_currency_code: "usd",
|
||||
created_at: expect.any(String),
|
||||
updated_at: expect.any(String),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("GET /admin/stores", () => {
|
||||
it("supports searching of stores", async () => {
|
||||
const response = await api.get(
|
||||
"/admin/stores?q=nonexistent",
|
||||
adminHeaders
|
||||
)
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.stores).toHaveLength(0)
|
||||
|
||||
const response2 = await api.get("/admin/stores?q=store", adminHeaders)
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response2.data.stores).toEqual([
|
||||
expect.objectContaining({
|
||||
id: store.id,
|
||||
name: "New store",
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
},
|
||||
})
|
||||
Reference in New Issue
Block a user