feat(): Sync order translations (#14267)

* feat(): Sync order translations

* feat(): Sync order translations

* tests

* Create tender-melons-develop.md

* fix tests

* cleanup

* cleanup
This commit is contained in:
Adrien de Peretti
2025-12-11 15:40:11 +01:00
committed by GitHub
parent fe314ab5bc
commit f13c23a4b7
30 changed files with 2271 additions and 74 deletions

View File

@@ -0,0 +1,485 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { MedusaContainer } from "@medusajs/types"
import { Modules, ProductStatus } from "@medusajs/utils"
import {
adminHeaders,
createAdminUser,
} from "../../../../helpers/create-admin-user"
import { setupTaxStructure } from "../../../../modules/__tests__/fixtures"
jest.setTimeout(300000)
process.env.MEDUSA_FF_TRANSLATION = "true"
medusaIntegrationTestRunner({
testSuite: ({ dbConnection, getContainer, api }) => {
describe("Admin Draft Order Translation API", () => {
let appContainer: MedusaContainer
let region: { id: string }
let product: { id: string; variants: { id: string; title: string }[] }
let salesChannel: { id: string }
let shippingProfile: { id: string }
let stockLocation: { id: string }
let shippingOption: { id: string }
beforeAll(async () => {
appContainer = getContainer()
})
beforeEach(async () => {
await setupTaxStructure(appContainer.resolve(Modules.TAX))
await createAdminUser(dbConnection, adminHeaders, appContainer)
salesChannel = (
await api.post(
"/admin/sales-channels",
{ name: "Webshop", description: "channel" },
adminHeaders
)
).data.sales_channel
const storeModule = appContainer.resolve(Modules.STORE)
const [defaultStore] = await storeModule.listStores(
{},
{ select: ["id"], take: 1 }
)
await storeModule.updateStores(defaultStore.id, {
supported_locales: [
{ locale_code: "en-US", is_default: true },
{ locale_code: "fr-FR" },
{ locale_code: "de-DE" },
],
})
region = (
await api.post(
"/admin/regions",
{ name: "US", currency_code: "usd", countries: ["us"] },
adminHeaders
)
).data.region
shippingProfile = (
await api.post(
`/admin/shipping-profiles`,
{ name: "default", type: "default" },
adminHeaders
)
).data.shipping_profile
stockLocation = (
await api.post(
`/admin/stock-locations`,
{ name: "test location" },
adminHeaders
)
).data.stock_location
await api.post(
`/admin/stock-locations/${stockLocation.id}/sales-channels`,
{ add: [salesChannel.id] },
adminHeaders
)
product = (
await api.post(
"/admin/products",
{
title: "Medusa T-Shirt",
description: "A comfortable cotton t-shirt",
handle: "t-shirt",
status: ProductStatus.PUBLISHED,
shipping_profile_id: shippingProfile.id,
options: [{ title: "Size", values: ["S", "M"] }],
variants: [
{
title: "Small",
sku: "SHIRT-S",
options: { Size: "S" },
manage_inventory: false,
prices: [{ amount: 1500, currency_code: "usd" }],
},
{
title: "Medium",
sku: "SHIRT-M",
options: { Size: "M" },
manage_inventory: false,
prices: [{ amount: 1500, currency_code: "usd" }],
},
],
},
adminHeaders
)
).data.product
const variantSmall = product.variants.find((v) => v.title === "Small")
const variantMedium = product.variants.find((v) => v.title === "Medium")
product.variants = [variantSmall!, variantMedium!]
const fulfillmentSets = (
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`,
{ name: "Test", type: "test-type" },
adminHeaders
)
).data.stock_location.fulfillment_sets
const fulfillmentSet = (
await api.post(
`/admin/fulfillment-sets/${fulfillmentSets[0].id}/service-zones`,
{
name: "Test",
geo_zones: [{ type: "country", country_code: "us" }],
},
adminHeaders
)
).data.fulfillment_set
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-providers`,
{ add: ["manual_test-provider"] },
adminHeaders
)
shippingOption = (
await api.post(
`/admin/shipping-options`,
{
name: "Test shipping option",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [{ currency_code: "usd", amount: 1000 }],
rules: [],
},
adminHeaders
)
).data.shipping_option
await api.post(
"/admin/translations/batch",
{
create: [
{
reference_id: product.id,
reference: "product",
locale_code: "fr-FR",
translations: {
title: "T-Shirt Medusa",
description: "Un t-shirt en coton confortable",
},
},
{
reference_id: product.id,
reference: "product",
locale_code: "de-DE",
translations: {
title: "Medusa T-Shirt DE",
description: "Ein bequemes Baumwoll-T-Shirt",
},
},
{
reference_id: product.variants[0].id,
reference: "product_variant",
locale_code: "fr-FR",
translations: { title: "Petit" },
},
{
reference_id: product.variants[0].id,
reference: "product_variant",
locale_code: "de-DE",
translations: { title: "Klein" },
},
{
reference_id: product.variants[1].id,
reference: "product_variant",
locale_code: "fr-FR",
translations: { title: "Moyen" },
},
{
reference_id: product.variants[1].id,
reference: "product_variant",
locale_code: "de-DE",
translations: { title: "Mittel" },
},
],
},
adminHeaders
)
})
describe("POST /admin/draft-orders/:id/edit/items (add items to draft order)", () => {
it("should translate items when adding to draft order with locale", async () => {
const draftOrder = (
await api.post(
"/admin/draft-orders",
{
email: "test@test.com",
region_id: region.id,
sales_channel_id: salesChannel.id,
locale: "fr-FR",
shipping_address: {
address_1: "123 Main St",
city: "Anytown",
country_code: "us",
postal_code: "12345",
first_name: "John",
},
},
adminHeaders
)
).data.draft_order
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit`,
{},
adminHeaders
)
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit/items`,
{
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
},
adminHeaders
)
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit/confirm`,
{},
adminHeaders
)
const updatedDraftOrder = (
await api.get(`/admin/draft-orders/${draftOrder.id}`, adminHeaders)
).data.draft_order
expect(updatedDraftOrder.items[0]).toEqual(
expect.objectContaining({
product_title: "T-Shirt Medusa",
product_description: "Un t-shirt en coton confortable",
variant_title: "Petit",
})
)
})
it("should have original values when draft order has no locale", async () => {
const draftOrder = (
await api.post(
"/admin/draft-orders",
{
email: "test@test.com",
region_id: region.id,
sales_channel_id: salesChannel.id,
shipping_address: {
address_1: "123 Main St",
city: "Anytown",
country_code: "us",
postal_code: "12345",
first_name: "John",
},
},
adminHeaders
)
).data.draft_order
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit`,
{},
adminHeaders
)
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit/items`,
{
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
},
adminHeaders
)
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit/confirm`,
{},
adminHeaders
)
const updatedDraftOrder = (
await api.get(`/admin/draft-orders/${draftOrder.id}`, adminHeaders)
).data.draft_order
expect(updatedDraftOrder.items[0]).toEqual(
expect.objectContaining({
product_title: "Medusa T-Shirt",
product_description: "A comfortable cotton t-shirt",
variant_title: "Small",
})
)
})
it("should translate multiple items added to draft order", async () => {
const draftOrder = (
await api.post(
"/admin/draft-orders",
{
email: "test@test.com",
region_id: region.id,
sales_channel_id: salesChannel.id,
locale: "de-DE",
shipping_address: {
address_1: "123 Main St",
city: "Anytown",
country_code: "us",
postal_code: "12345",
first_name: "John",
},
},
adminHeaders
)
).data.draft_order
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit`,
{},
adminHeaders
)
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit/items`,
{
items: [
{ variant_id: product.variants[0].id, quantity: 1 },
{ variant_id: product.variants[1].id, quantity: 2 },
],
},
adminHeaders
)
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit/confirm`,
{},
adminHeaders
)
const updatedDraftOrder = (
await api.get(`/admin/draft-orders/${draftOrder.id}`, adminHeaders)
).data.draft_order
expect(updatedDraftOrder.items).toHaveLength(2)
const smallItem = updatedDraftOrder.items.find(
(item) => item.variant_id === product.variants[0].id
)
const mediumItem = updatedDraftOrder.items.find(
(item) => item.variant_id === product.variants[1].id
)
expect(smallItem).toEqual(
expect.objectContaining({
product_title: "Medusa T-Shirt DE",
variant_title: "Klein",
})
)
expect(mediumItem).toEqual(
expect.objectContaining({
product_title: "Medusa T-Shirt DE",
variant_title: "Mittel",
})
)
})
})
describe("POST /admin/draft-orders/:id (update draft order locale)", () => {
it("should re-translate all items when locale is updated", async () => {
const draftOrder = (
await api.post(
"/admin/draft-orders",
{
email: "test@test.com",
region_id: region.id,
sales_channel_id: salesChannel.id,
locale: "fr-FR",
shipping_address: {
address_1: "123 Main St",
city: "Anytown",
country_code: "us",
postal_code: "12345",
first_name: "John",
},
},
adminHeaders
)
).data.draft_order
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit`,
{},
adminHeaders
)
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit/items`,
{
items: [
{ variant_id: product.variants[0].id, quantity: 1 },
{ variant_id: product.variants[1].id, quantity: 1 },
],
},
adminHeaders
)
await api.post(
`/admin/draft-orders/${draftOrder.id}/edit/confirm`,
{},
adminHeaders
)
let updatedDraftOrder = (
await api.get(`/admin/draft-orders/${draftOrder.id}`, adminHeaders)
).data.draft_order
const frenchSmallItem = updatedDraftOrder.items.find(
(item) => item.variant_id === product.variants[0].id
)
expect(frenchSmallItem.variant_title).toEqual("Petit")
await api.post(
`/admin/draft-orders/${draftOrder.id}`,
{ locale: "de-DE" },
adminHeaders
)
updatedDraftOrder = (
await api.get(`/admin/draft-orders/${draftOrder.id}`, adminHeaders)
).data.draft_order
const germanSmallItem = updatedDraftOrder.items.find(
(item) => item.variant_id === product.variants[0].id
)
const germanMediumItem = updatedDraftOrder.items.find(
(item) => item.variant_id === product.variants[1].id
)
expect(germanSmallItem).toEqual(
expect.objectContaining({
product_title: "Medusa T-Shirt DE",
product_description: "Ein bequemes Baumwoll-T-Shirt",
variant_title: "Klein",
})
)
expect(germanMediumItem).toEqual(
expect.objectContaining({
product_title: "Medusa T-Shirt DE",
variant_title: "Mittel",
})
)
})
})
})
},
})

View File

@@ -0,0 +1,527 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { MedusaContainer } from "@medusajs/types"
import { Modules, ProductStatus, RuleOperator } from "@medusajs/utils"
import {
adminHeaders,
createAdminUser,
generatePublishableKey,
generateStoreHeaders,
} from "../../../helpers/create-admin-user"
import { setupTaxStructure } from "../../../modules/__tests__/fixtures"
jest.setTimeout(300000)
process.env.MEDUSA_FF_TRANSLATION = "true"
const shippingAddressData = {
address_1: "test address 1",
address_2: "test address 2",
city: "SF",
country_code: "us",
province: "CA",
postal_code: "94016",
}
medusaIntegrationTestRunner({
testSuite: ({ dbConnection, getContainer, api }) => {
describe("Exchange Translation API", () => {
let appContainer: MedusaContainer
let storeHeaders: { headers: { [key: string]: string } }
let region: { id: string }
let product: { id: string; variants: { id: string; title: string }[] }
let salesChannel: { id: string }
let shippingProfile: { id: string }
let stockLocation: { id: string }
let shippingOption: { id: string }
let outboundShippingOption: { id: string }
let inventoryItem: { id: string }
beforeAll(async () => {
appContainer = getContainer()
})
beforeEach(async () => {
await setupTaxStructure(appContainer.resolve(Modules.TAX))
await createAdminUser(dbConnection, adminHeaders, appContainer)
const publishableKey = await generatePublishableKey(appContainer)
storeHeaders = generateStoreHeaders({ publishableKey })
salesChannel = (
await api.post(
"/admin/sales-channels",
{ name: "Webshop", description: "channel" },
adminHeaders
)
).data.sales_channel
const storeModule = appContainer.resolve(Modules.STORE)
const [defaultStore] = await storeModule.listStores(
{},
{ select: ["id"], take: 1 }
)
await storeModule.updateStores(defaultStore.id, {
supported_locales: [
{ locale_code: "en-US", is_default: true },
{ locale_code: "fr-FR" },
{ locale_code: "de-DE" },
],
})
region = (
await api.post(
"/admin/regions",
{ name: "US", currency_code: "usd", countries: ["us"] },
adminHeaders
)
).data.region
shippingProfile = (
await api.post(
`/admin/shipping-profiles`,
{ name: "default", type: "default" },
adminHeaders
)
).data.shipping_profile
stockLocation = (
await api.post(
`/admin/stock-locations`,
{ name: "test location" },
adminHeaders
)
).data.stock_location
inventoryItem = (
await api.post(
`/admin/inventory-items`,
{ sku: "test-variant" },
adminHeaders
)
).data.inventory_item
await api.post(
`/admin/inventory-items/${inventoryItem.id}/location-levels`,
{ location_id: stockLocation.id, stocked_quantity: 100 },
adminHeaders
)
await api.post(
`/admin/stock-locations/${stockLocation.id}/sales-channels`,
{ add: [salesChannel.id] },
adminHeaders
)
product = (
await api.post(
"/admin/products",
{
title: "Medusa T-Shirt",
description: "A comfortable cotton t-shirt",
handle: "t-shirt",
status: ProductStatus.PUBLISHED,
shipping_profile_id: shippingProfile.id,
options: [{ title: "Size", values: ["S", "M"] }],
variants: [
{
title: "Small",
sku: "SHIRT-S",
options: { Size: "S" },
inventory_items: [
{
inventory_item_id: inventoryItem.id,
required_quantity: 1,
},
],
prices: [{ amount: 1500, currency_code: "usd" }],
},
{
title: "Medium",
sku: "SHIRT-M",
options: { Size: "M" },
manage_inventory: false,
prices: [{ amount: 1500, currency_code: "usd" }],
},
],
},
adminHeaders
)
).data.product
const variantSmall = product.variants.find((v) => v.title === "Small")
const variantMedium = product.variants.find((v) => v.title === "Medium")
product.variants = [variantSmall!, variantMedium!]
const fulfillmentSets = (
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`,
{ name: "Test", type: "test-type" },
adminHeaders
)
).data.stock_location.fulfillment_sets
const fulfillmentSet = (
await api.post(
`/admin/fulfillment-sets/${fulfillmentSets[0].id}/service-zones`,
{
name: "Test",
geo_zones: [{ type: "country", country_code: "us" }],
},
adminHeaders
)
).data.fulfillment_set
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-providers`,
{ add: ["manual_test-provider"] },
adminHeaders
)
shippingOption = (
await api.post(
`/admin/shipping-options`,
{
name: "Test shipping option",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [{ currency_code: "usd", amount: 1000 }],
rules: [],
},
adminHeaders
)
).data.shipping_option
outboundShippingOption = (
await api.post(
`/admin/shipping-options`,
{
name: "Outbound shipping",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [{ currency_code: "usd", amount: 0 }],
rules: [
{
operator: RuleOperator.EQ,
attribute: "is_return",
value: "false",
},
],
},
adminHeaders
)
).data.shipping_option
await api.post(
"/admin/translations/batch",
{
create: [
{
reference_id: product.id,
reference: "product",
locale_code: "fr-FR",
translations: {
title: "T-Shirt Medusa",
description: "Un t-shirt en coton confortable",
},
},
{
reference_id: product.id,
reference: "product",
locale_code: "de-DE",
translations: {
title: "Medusa T-Shirt DE",
description: "Ein bequemes Baumwoll-T-Shirt",
},
},
{
reference_id: product.variants[0].id,
reference: "product_variant",
locale_code: "fr-FR",
translations: { title: "Petit" },
},
{
reference_id: product.variants[0].id,
reference: "product_variant",
locale_code: "de-DE",
translations: { title: "Klein" },
},
{
reference_id: product.variants[1].id,
reference: "product_variant",
locale_code: "fr-FR",
translations: { title: "Moyen" },
},
{
reference_id: product.variants[1].id,
reference: "product_variant",
locale_code: "de-DE",
translations: { title: "Mittel" },
},
],
},
adminHeaders
)
})
const createOrderFromCart = async (locale?: string) => {
const cart = (
await api.post(
`/store/carts`,
{
currency_code: "usd",
email: "test@example.com",
region_id: region.id,
sales_channel_id: salesChannel.id,
locale,
shipping_address: shippingAddressData,
billing_address: shippingAddressData,
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
},
storeHeaders
)
).data.cart
await api.post(
`/store/carts/${cart.id}/shipping-methods`,
{ option_id: shippingOption.id },
storeHeaders
)
const paymentCollection = (
await api.post(
`/store/payment-collections`,
{ cart_id: cart.id },
storeHeaders
)
).data.payment_collection
await api.post(
`/store/payment-collections/${paymentCollection.id}/payment-sessions`,
{ provider_id: "pp_system_default" },
storeHeaders
)
const order = (
await api.post(`/store/carts/${cart.id}/complete`, {}, storeHeaders)
).data.order
return (await api.get(`/admin/orders/${order.id}`, adminHeaders)).data
.order
}
describe("Exchange items translation", () => {
it("should translate new items in exchange using order locale", async () => {
const order = await createOrderFromCart("fr-FR")
await api.post(
`/admin/orders/${order.id}/fulfillments`,
{
location_id: stockLocation.id,
items: [{ id: order.items[0].id, quantity: 1 }],
},
adminHeaders
)
const exchange = (
await api.post(
"/admin/exchanges",
{ order_id: order.id, description: "Test exchange" },
adminHeaders
)
).data.exchange
// Add inbound item (item being returned)
await api.post(
`/admin/exchanges/${exchange.id}/inbound/items`,
{
items: [{ id: order.items[0].id, quantity: 1 }],
},
adminHeaders
)
// Add outbound item (new item being sent)
await api.post(
`/admin/exchanges/${exchange.id}/outbound/items`,
{
items: [{ variant_id: product.variants[1].id, quantity: 1 }],
},
adminHeaders
)
await api.post(
`/admin/exchanges/${exchange.id}/outbound/shipping-method`,
{ shipping_option_id: outboundShippingOption.id },
adminHeaders
)
await api.post(
`/admin/exchanges/${exchange.id}/request`,
{},
adminHeaders
)
const updatedOrder = (
await api.get(`/admin/orders/${order.id}`, adminHeaders)
).data.order
const newItem = updatedOrder.items.find(
(item: any) => item.variant_id === product.variants[1].id
)
expect(newItem).toEqual(
expect.objectContaining({
product_title: "T-Shirt Medusa",
variant_title: "Moyen",
})
)
})
it("should translate exchange items using German locale", async () => {
const order = await createOrderFromCart("de-DE")
await api.post(
`/admin/orders/${order.id}/fulfillments`,
{
location_id: stockLocation.id,
items: [{ id: order.items[0].id, quantity: 1 }],
},
adminHeaders
)
const exchange = (
await api.post(
"/admin/exchanges",
{ order_id: order.id, description: "Test exchange" },
adminHeaders
)
).data.exchange
// Add inbound item (item being returned)
await api.post(
`/admin/exchanges/${exchange.id}/inbound/items`,
{
items: [{ id: order.items[0].id, quantity: 1 }],
},
adminHeaders
)
// Add outbound item (new item being sent)
await api.post(
`/admin/exchanges/${exchange.id}/outbound/items`,
{
items: [{ variant_id: product.variants[1].id, quantity: 1 }],
},
adminHeaders
)
await api.post(
`/admin/exchanges/${exchange.id}/outbound/shipping-method`,
{ shipping_option_id: outboundShippingOption.id },
adminHeaders
)
await api.post(
`/admin/exchanges/${exchange.id}/request`,
{},
adminHeaders
)
const updatedOrder = (
await api.get(`/admin/orders/${order.id}`, adminHeaders)
).data.order
const newItem = updatedOrder.items.find(
(item: any) => item.variant_id === product.variants[1].id
)
expect(newItem).toEqual(
expect.objectContaining({
product_title: "Medusa T-Shirt DE",
product_description: "Ein bequemes Baumwoll-T-Shirt",
variant_title: "Mittel",
})
)
})
it("should have original values when order has no locale", async () => {
const order = await createOrderFromCart()
await api.post(
`/admin/orders/${order.id}/fulfillments`,
{
location_id: stockLocation.id,
items: [{ id: order.items[0].id, quantity: 1 }],
},
adminHeaders
)
const exchange = (
await api.post(
"/admin/exchanges",
{ order_id: order.id, description: "Test exchange" },
adminHeaders
)
).data.exchange
await api.post(
`/admin/exchanges/${exchange.id}/inbound/items`,
{
items: [{ id: order.items[0].id, quantity: 1 }],
},
adminHeaders
)
// Add outbound item (new item being sent)
await api.post(
`/admin/exchanges/${exchange.id}/outbound/items`,
{
items: [{ variant_id: product.variants[1].id, quantity: 1 }],
},
adminHeaders
)
await api.post(
`/admin/exchanges/${exchange.id}/outbound/shipping-method`,
{ shipping_option_id: outboundShippingOption.id },
adminHeaders
)
await api.post(
`/admin/exchanges/${exchange.id}/request`,
{},
adminHeaders
)
const updatedOrder = (
await api.get(`/admin/orders/${order.id}`, adminHeaders)
).data.order
const newItem = updatedOrder.items.find(
(item: any) => item.variant_id === product.variants[1].id
)
expect(newItem).toEqual(
expect.objectContaining({
product_title: "Medusa T-Shirt",
product_description: "A comfortable cotton t-shirt",
variant_title: "Medium",
})
)
})
})
})
},
})

View File

@@ -0,0 +1,419 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { MedusaContainer } from "@medusajs/types"
import { Modules, ProductStatus } from "@medusajs/utils"
import {
adminHeaders,
createAdminUser,
generatePublishableKey,
generateStoreHeaders,
} from "../../../helpers/create-admin-user"
import { setupTaxStructure } from "../../../modules/__tests__/fixtures"
jest.setTimeout(300000)
process.env.MEDUSA_FF_TRANSLATION = "true"
const shippingAddressData = {
address_1: "test address 1",
address_2: "test address 2",
city: "SF",
country_code: "us",
province: "CA",
postal_code: "94016",
}
medusaIntegrationTestRunner({
testSuite: ({ dbConnection, getContainer, api }) => {
describe("Order Edit Translation API", () => {
let appContainer: MedusaContainer
let storeHeaders: { headers: { [key: string]: string } }
let region: { id: string }
let product: { id: string; variants: { id: string; title: string }[] }
let salesChannel: { id: string }
let shippingProfile: { id: string }
let stockLocation: { id: string }
let shippingOption: { id: string }
let inventoryItem: { id: string }
beforeAll(async () => {
appContainer = getContainer()
})
beforeEach(async () => {
await setupTaxStructure(appContainer.resolve(Modules.TAX))
await createAdminUser(dbConnection, adminHeaders, appContainer)
const publishableKey = await generatePublishableKey(appContainer)
storeHeaders = generateStoreHeaders({ publishableKey })
salesChannel = (
await api.post(
"/admin/sales-channels",
{ name: "Webshop", description: "channel" },
adminHeaders
)
).data.sales_channel
const storeModule = appContainer.resolve(Modules.STORE)
const [defaultStore] = await storeModule.listStores(
{},
{ select: ["id"], take: 1 }
)
await storeModule.updateStores(defaultStore.id, {
supported_locales: [
{ locale_code: "en-US", is_default: true },
{ locale_code: "fr-FR" },
{ locale_code: "de-DE" },
],
})
region = (
await api.post(
"/admin/regions",
{ name: "US", currency_code: "usd", countries: ["us"] },
adminHeaders
)
).data.region
shippingProfile = (
await api.post(
`/admin/shipping-profiles`,
{ name: "default", type: "default" },
adminHeaders
)
).data.shipping_profile
stockLocation = (
await api.post(
`/admin/stock-locations`,
{ name: "test location" },
adminHeaders
)
).data.stock_location
inventoryItem = (
await api.post(
`/admin/inventory-items`,
{ sku: "test-variant" },
adminHeaders
)
).data.inventory_item
await api.post(
`/admin/inventory-items/${inventoryItem.id}/location-levels`,
{ location_id: stockLocation.id, stocked_quantity: 100 },
adminHeaders
)
await api.post(
`/admin/stock-locations/${stockLocation.id}/sales-channels`,
{ add: [salesChannel.id] },
adminHeaders
)
product = (
await api.post(
"/admin/products",
{
title: "Medusa T-Shirt",
description: "A comfortable cotton t-shirt",
handle: "t-shirt",
status: ProductStatus.PUBLISHED,
shipping_profile_id: shippingProfile.id,
options: [{ title: "Size", values: ["S", "M"] }],
variants: [
{
title: "Small",
sku: "SHIRT-S",
options: { Size: "S" },
inventory_items: [
{
inventory_item_id: inventoryItem.id,
required_quantity: 1,
},
],
prices: [{ amount: 1500, currency_code: "usd" }],
},
{
title: "Medium",
sku: "SHIRT-M",
options: { Size: "M" },
manage_inventory: false,
prices: [{ amount: 1500, currency_code: "usd" }],
},
],
},
adminHeaders
)
).data.product
const variantSmall = product.variants.find((v) => v.title === "Small")
const variantMedium = product.variants.find((v) => v.title === "Medium")
product.variants = [variantSmall!, variantMedium!]
const fulfillmentSets = (
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`,
{ name: "Test", type: "test-type" },
adminHeaders
)
).data.stock_location.fulfillment_sets
const fulfillmentSet = (
await api.post(
`/admin/fulfillment-sets/${fulfillmentSets[0].id}/service-zones`,
{
name: "Test",
geo_zones: [{ type: "country", country_code: "us" }],
},
adminHeaders
)
).data.fulfillment_set
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-providers`,
{ add: ["manual_test-provider"] },
adminHeaders
)
shippingOption = (
await api.post(
`/admin/shipping-options`,
{
name: "Test shipping option",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [{ currency_code: "usd", amount: 1000 }],
rules: [],
},
adminHeaders
)
).data.shipping_option
await api.post(
"/admin/translations/batch",
{
create: [
{
reference_id: product.id,
reference: "product",
locale_code: "fr-FR",
translations: {
title: "T-Shirt Medusa",
description: "Un t-shirt en coton confortable",
},
},
{
reference_id: product.id,
reference: "product",
locale_code: "de-DE",
translations: {
title: "Medusa T-Shirt DE",
description: "Ein bequemes Baumwoll-T-Shirt",
},
},
{
reference_id: product.variants[0].id,
reference: "product_variant",
locale_code: "fr-FR",
translations: { title: "Petit" },
},
{
reference_id: product.variants[0].id,
reference: "product_variant",
locale_code: "de-DE",
translations: { title: "Klein" },
},
{
reference_id: product.variants[1].id,
reference: "product_variant",
locale_code: "fr-FR",
translations: { title: "Moyen" },
},
{
reference_id: product.variants[1].id,
reference: "product_variant",
locale_code: "de-DE",
translations: { title: "Mittel" },
},
],
},
adminHeaders
)
})
const createOrderFromCart = async (locale?: string) => {
const cart = (
await api.post(
`/store/carts`,
{
currency_code: "usd",
email: "test@example.com",
region_id: region.id,
sales_channel_id: salesChannel.id,
locale,
shipping_address: shippingAddressData,
billing_address: shippingAddressData,
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
},
storeHeaders
)
).data.cart
await api.post(
`/store/carts/${cart.id}/shipping-methods`,
{ option_id: shippingOption.id },
storeHeaders
)
const paymentCollection = (
await api.post(
`/store/payment-collections`,
{ cart_id: cart.id },
storeHeaders
)
).data.payment_collection
await api.post(
`/store/payment-collections/${paymentCollection.id}/payment-sessions`,
{ provider_id: "pp_system_default" },
storeHeaders
)
const order = (
await api.post(`/store/carts/${cart.id}/complete`, {}, storeHeaders)
).data.order
return (await api.get(`/admin/orders/${order.id}`, adminHeaders)).data
.order
}
describe("POST /admin/order-edits/:id/items (add items during order edit)", () => {
it("should translate new items added during order edit using order locale", async () => {
const order = await createOrderFromCart("fr-FR")
await api.post(
"/admin/order-edits",
{ order_id: order.id },
adminHeaders
)
await api.post(
`/admin/order-edits/${order.id}/items`,
{
items: [{ variant_id: product.variants[1].id, quantity: 1 }],
},
adminHeaders
)
await api.post(
`/admin/order-edits/${order.id}/confirm`,
{},
adminHeaders
)
const updatedOrder = (
await api.get(`/admin/orders/${order.id}`, adminHeaders)
).data.order
const newItem = updatedOrder.items.find(
(item) => item.variant_id === product.variants[1].id
)
expect(newItem).toEqual(
expect.objectContaining({
product_title: "T-Shirt Medusa",
variant_title: "Moyen",
})
)
})
it("should have original values when order has no locale", async () => {
const order = await createOrderFromCart()
await api.post(
"/admin/order-edits",
{ order_id: order.id },
adminHeaders
)
await api.post(
`/admin/order-edits/${order.id}/items`,
{
items: [{ variant_id: product.variants[1].id, quantity: 1 }],
},
adminHeaders
)
await api.post(
`/admin/order-edits/${order.id}/confirm`,
{},
adminHeaders
)
const updatedOrder = (
await api.get(`/admin/orders/${order.id}`, adminHeaders)
).data.order
const newItem = updatedOrder.items.find(
(item) => item.variant_id === product.variants[1].id
)
expect(newItem).toEqual(
expect.objectContaining({
product_title: "Medusa T-Shirt",
product_description: "A comfortable cotton t-shirt",
variant_title: "Medium",
})
)
})
it("should translate items using German locale", async () => {
const order = await createOrderFromCart("de-DE")
await api.post(
"/admin/order-edits",
{ order_id: order.id },
adminHeaders
)
await api.post(
`/admin/order-edits/${order.id}/items`,
{
items: [{ variant_id: product.variants[1].id, quantity: 1 }],
},
adminHeaders
)
await api.post(
`/admin/order-edits/${order.id}/confirm`,
{},
adminHeaders
)
const updatedOrder = (
await api.get(`/admin/orders/${order.id}`, adminHeaders)
).data.order
const newItem = updatedOrder.items.find(
(item) => item.variant_id === product.variants[1].id
)
expect(newItem).toEqual(
expect.objectContaining({
product_title: "Medusa T-Shirt DE",
product_description: "Ein bequemes Baumwoll-T-Shirt",
variant_title: "Mittel",
})
)
})
})
})
},
})

View File

@@ -0,0 +1,435 @@
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
import { MedusaContainer } from "@medusajs/types"
import { Modules, ProductStatus, RuleOperator } from "@medusajs/utils"
import {
adminHeaders,
createAdminUser,
generatePublishableKey,
generateStoreHeaders,
} from "../../../../helpers/create-admin-user"
import { setupTaxStructure } from "../../../../modules/__tests__/fixtures"
jest.setTimeout(300000)
process.env.MEDUSA_FF_TRANSLATION = "true"
const shippingAddressData = {
address_1: "test address 1",
address_2: "test address 2",
city: "SF",
country_code: "us",
province: "CA",
postal_code: "94016",
}
medusaIntegrationTestRunner({
testSuite: ({ dbConnection, getContainer, api }) => {
describe("Admin Order Translation API", () => {
let appContainer: MedusaContainer
let storeHeaders: { headers: { [key: string]: string } }
let region: { id: string }
let product: { id: string; variants: { id: string; title: string }[] }
let salesChannel: { id: string }
let shippingProfile: { id: string }
let stockLocation: { id: string }
let shippingOption: { id: string }
let returnShippingOption: { id: string }
let outboundShippingOption: { id: string }
let inventoryItem: { id: string }
beforeAll(async () => {
appContainer = getContainer()
})
beforeEach(async () => {
await setupTaxStructure(appContainer.resolve(Modules.TAX))
await createAdminUser(dbConnection, adminHeaders, appContainer)
const publishableKey = await generatePublishableKey(appContainer)
storeHeaders = generateStoreHeaders({ publishableKey })
salesChannel = (
await api.post(
"/admin/sales-channels",
{ name: "Webshop", description: "channel" },
adminHeaders
)
).data.sales_channel
const storeModule = appContainer.resolve(Modules.STORE)
const [defaultStore] = await storeModule.listStores(
{},
{ select: ["id"], take: 1 }
)
await storeModule.updateStores(defaultStore.id, {
supported_locales: [
{ locale_code: "en-US", is_default: true },
{ locale_code: "fr-FR" },
{ locale_code: "de-DE" },
],
})
region = (
await api.post(
"/admin/regions",
{ name: "US", currency_code: "usd", countries: ["us"] },
adminHeaders
)
).data.region
shippingProfile = (
await api.post(
`/admin/shipping-profiles`,
{ name: "default", type: "default" },
adminHeaders
)
).data.shipping_profile
stockLocation = (
await api.post(
`/admin/stock-locations`,
{ name: "test location" },
adminHeaders
)
).data.stock_location
inventoryItem = (
await api.post(
`/admin/inventory-items`,
{ sku: "test-variant" },
adminHeaders
)
).data.inventory_item
await api.post(
`/admin/inventory-items/${inventoryItem.id}/location-levels`,
{ location_id: stockLocation.id, stocked_quantity: 100 },
adminHeaders
)
await api.post(
`/admin/stock-locations/${stockLocation.id}/sales-channels`,
{ add: [salesChannel.id] },
adminHeaders
)
// Create product with description for translation
product = (
await api.post(
"/admin/products",
{
title: "Medusa T-Shirt",
description: "A comfortable cotton t-shirt",
handle: "t-shirt",
status: ProductStatus.PUBLISHED,
shipping_profile_id: shippingProfile.id,
options: [{ title: "Size", values: ["S", "M"] }],
variants: [
{
title: "Small",
sku: "SHIRT-S",
options: { Size: "S" },
inventory_items: [
{
inventory_item_id: inventoryItem.id,
required_quantity: 1,
},
],
prices: [{ amount: 1500, currency_code: "usd" }],
},
{
title: "Medium",
sku: "SHIRT-M",
options: { Size: "M" },
manage_inventory: false,
prices: [{ amount: 1500, currency_code: "usd" }],
},
],
},
adminHeaders
)
).data.product
// Maintain predictable variants order
const variantSmall = product.variants.find((v) => v.title === "Small")
const variantMedium = product.variants.find((v) => v.title === "Medium")
product.variants = [variantSmall!, variantMedium!]
// Setup fulfillment
const fulfillmentSets = (
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-sets?fields=*fulfillment_sets`,
{ name: "Test", type: "test-type" },
adminHeaders
)
).data.stock_location.fulfillment_sets
const fulfillmentSet = (
await api.post(
`/admin/fulfillment-sets/${fulfillmentSets[0].id}/service-zones`,
{
name: "Test",
geo_zones: [{ type: "country", country_code: "us" }],
},
adminHeaders
)
).data.fulfillment_set
await api.post(
`/admin/stock-locations/${stockLocation.id}/fulfillment-providers`,
{ add: ["manual_test-provider"] },
adminHeaders
)
shippingOption = (
await api.post(
`/admin/shipping-options`,
{
name: "Test shipping option",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [{ currency_code: "usd", amount: 1000 }],
rules: [],
},
adminHeaders
)
).data.shipping_option
returnShippingOption = (
await api.post(
`/admin/shipping-options`,
{
name: "Return shipping",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [{ currency_code: "usd", amount: 500 }],
rules: [
{
operator: RuleOperator.EQ,
attribute: "is_return",
value: "true",
},
],
},
adminHeaders
)
).data.shipping_option
outboundShippingOption = (
await api.post(
`/admin/shipping-options`,
{
name: "Outbound shipping",
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: "manual_test-provider",
price_type: "flat",
type: {
label: "Test type",
description: "Test description",
code: "test-code",
},
prices: [{ currency_code: "usd", amount: 0 }],
rules: [
{
operator: RuleOperator.EQ,
attribute: "is_return",
value: "false",
},
],
},
adminHeaders
)
).data.shipping_option
// Create translations for product and variants
await api.post(
"/admin/translations/batch",
{
create: [
{
reference_id: product.id,
reference: "product",
locale_code: "fr-FR",
translations: {
title: "T-Shirt Medusa",
description: "Un t-shirt en coton confortable",
},
},
{
reference_id: product.id,
reference: "product",
locale_code: "de-DE",
translations: {
title: "Medusa T-Shirt DE",
description: "Ein bequemes Baumwoll-T-Shirt",
},
},
{
reference_id: product.variants[0].id,
reference: "product_variant",
locale_code: "fr-FR",
translations: { title: "Petit" },
},
{
reference_id: product.variants[0].id,
reference: "product_variant",
locale_code: "de-DE",
translations: { title: "Klein" },
},
{
reference_id: product.variants[1].id,
reference: "product_variant",
locale_code: "fr-FR",
translations: { title: "Moyen" },
},
{
reference_id: product.variants[1].id,
reference: "product_variant",
locale_code: "de-DE",
translations: { title: "Mittel" },
},
],
},
adminHeaders
)
})
const createOrderFromCart = async (locale?: string) => {
const cart = (
await api.post(
`/store/carts`,
{
currency_code: "usd",
email: "test@example.com",
region_id: region.id,
sales_channel_id: salesChannel.id,
locale,
shipping_address: shippingAddressData,
billing_address: shippingAddressData,
items: [{ variant_id: product.variants[0].id, quantity: 1 }],
},
storeHeaders
)
).data.cart
await api.post(
`/store/carts/${cart.id}/shipping-methods`,
{ option_id: shippingOption.id },
storeHeaders
)
const paymentCollection = (
await api.post(
`/store/payment-collections`,
{ cart_id: cart.id },
storeHeaders
)
).data.payment_collection
await api.post(
`/store/payment-collections/${paymentCollection.id}/payment-sessions`,
{ provider_id: "pp_system_default" },
storeHeaders
)
const order = (
await api.post(`/store/carts/${cart.id}/complete`, {}, storeHeaders)
).data.order
return (await api.get(`/admin/orders/${order.id}`, adminHeaders)).data
.order
}
describe("Order creation from cart with locale", () => {
it("should preserve locale and translated items when order is created from cart", async () => {
const order = await createOrderFromCart("fr-FR")
expect(order.items[0]).toEqual(
expect.objectContaining({
product_title: "T-Shirt Medusa",
product_description: "Un t-shirt en coton confortable",
variant_title: "Petit",
})
)
})
it("should have original values when order is created without locale", async () => {
const order = await createOrderFromCart()
expect(order.items[0]).toEqual(
expect.objectContaining({
product_title: "Medusa T-Shirt",
product_description: "A comfortable cotton t-shirt",
variant_title: "Small",
})
)
})
})
describe("POST /admin/orders/:id (update order locale)", () => {
it("should re-translate all items when locale is updated", async () => {
const order = await createOrderFromCart("fr-FR")
expect(order.items[0].variant_title).toEqual("Petit")
await api.post(
`/admin/orders/${order.id}`,
{ locale: "de-DE" },
adminHeaders
)
const updatedOrder = (
await api.get(`/admin/orders/${order.id}`, adminHeaders)
).data.order
expect(updatedOrder.items[0]).toEqual(
expect.objectContaining({
product_title: "Medusa T-Shirt DE",
product_description: "Ein bequemes Baumwoll-T-Shirt",
variant_title: "Klein",
})
)
})
it("should not re-translate items when updating other fields", async () => {
const order = await createOrderFromCart("fr-FR")
await api.post(
`/admin/orders/${order.id}`,
{ email: "updated@example.com" },
adminHeaders
)
const updatedOrder = (
await api.get(
`/admin/orders/${order.id}?fields=+email`,
adminHeaders
)
).data.order
expect(updatedOrder.email).toEqual("updated@example.com")
expect(updatedOrder.items[0]).toEqual(
expect.objectContaining({
product_title: "T-Shirt Medusa",
variant_title: "Petit",
})
)
})
})
})
},
})