What: - Added query planning to the Remote Joiner, enabling phased and parallel execution of data aggregation. - Replaced object deletes with non-enumerable property hiding to improve performance.
555 lines
17 KiB
TypeScript
555 lines
17 KiB
TypeScript
import { medusaIntegrationTestRunner } from "@medusajs/test-utils"
|
|
import path from "path"
|
|
|
|
jest.setTimeout(100000)
|
|
|
|
import { createProductsWorkflow } from "@medusajs/core-flows"
|
|
import { Modules } from "@medusajs/utils"
|
|
import { TranslationModule } from "../__fixtures__/translation-test/src/modules/translation/service"
|
|
|
|
const createTranslations = async (container, inputs) => {
|
|
const translationModule: any = container.resolve("translation")
|
|
|
|
const created = await translationModule.createTranslations(inputs as any)
|
|
return Array.isArray(created) ? created : [created]
|
|
}
|
|
|
|
const attachTranslationToProduct = async (
|
|
container,
|
|
{ productId, translation }
|
|
) => {
|
|
const [created] = await createTranslations(container, [translation])
|
|
|
|
const remoteLink: any = container.resolve("remoteLink")
|
|
await remoteLink.create({
|
|
[Modules.PRODUCT]: { product_id: productId },
|
|
translation: { translation_id: created.id },
|
|
})
|
|
|
|
return created
|
|
}
|
|
|
|
const attachTranslationToVariant = async (
|
|
container,
|
|
{ variantId, translation }
|
|
) => {
|
|
const [created] = await createTranslations(container, [translation])
|
|
|
|
const remoteLink: any = container.resolve("remoteLink")
|
|
await remoteLink.create({
|
|
[Modules.PRODUCT]: { product_variant_id: variantId },
|
|
translation: { translation_id: created.id },
|
|
})
|
|
|
|
return created
|
|
}
|
|
|
|
const attachTranslationToOption = async (
|
|
container,
|
|
{ optionId, translation }
|
|
) => {
|
|
const [created] = await createTranslations(container, [translation])
|
|
|
|
const remoteLink: any = container.resolve("remoteLink")
|
|
await remoteLink.create({
|
|
[Modules.PRODUCT]: { product_option_id: optionId },
|
|
translation: { translation_id: created.id },
|
|
})
|
|
|
|
return created
|
|
}
|
|
|
|
const attachTranslationToProductCategory = async (
|
|
container,
|
|
{ categoryId, translation }
|
|
) => {
|
|
const [created] = await createTranslations(container, [translation])
|
|
|
|
const remoteLink: any = container.resolve("remoteLink")
|
|
await remoteLink.create({
|
|
[Modules.PRODUCT]: { product_category_id: categoryId },
|
|
translation: { translation_id: created.id },
|
|
})
|
|
|
|
return created
|
|
}
|
|
|
|
medusaIntegrationTestRunner({
|
|
cwd: path.join(__dirname, "../__fixtures__/translation-test"),
|
|
testSuite: ({ getContainer }) => {
|
|
describe("query.graph()", () => {
|
|
beforeEach(async () => {
|
|
const container = getContainer()
|
|
const productService: any = container.resolve("product")
|
|
|
|
const categories = await Promise.all(
|
|
[1, 2, 3].map((i) =>
|
|
productService.createProductCategories({
|
|
name: `Category ${i}`,
|
|
})
|
|
)
|
|
)
|
|
|
|
const buildProduct = (i: number, categoryId: string) => ({
|
|
title: `Product ${i}`,
|
|
category_ids: [categoryId],
|
|
options: [
|
|
{
|
|
title: "size",
|
|
values: ["small", "large"],
|
|
},
|
|
],
|
|
variants: [
|
|
{
|
|
title: `P${i} Variant 1`,
|
|
options: { size: "small" },
|
|
prices: [
|
|
{
|
|
amount: 10,
|
|
currency_code: "usd",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
title: `P${i} Variant 2`,
|
|
options: { size: "large" },
|
|
prices: [
|
|
{
|
|
amount: 20,
|
|
currency_code: "usd",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
})
|
|
|
|
const createdProducts = await Promise.all(
|
|
[1, 2, 3].map(
|
|
async (i) =>
|
|
await createProductsWorkflow(container).run({
|
|
input: {
|
|
products: [buildProduct(i, categories[i - 1].id)],
|
|
},
|
|
})
|
|
)
|
|
)
|
|
|
|
const productsWithRels = await Promise.all(
|
|
createdProducts.map((p) =>
|
|
productService.retrieveProduct(p.result[0].id, {
|
|
relations: [
|
|
"variants",
|
|
"options",
|
|
"options.values",
|
|
"categories",
|
|
],
|
|
})
|
|
)
|
|
)
|
|
|
|
await Promise.all(
|
|
productsWithRels.map(async (p, idx) => {
|
|
const i = idx + 1
|
|
await attachTranslationToProduct(getContainer(), {
|
|
productId: p.id,
|
|
translation: {
|
|
key: p.id,
|
|
value: {
|
|
pt: { title: `Produto ${i}` },
|
|
fr: { title: `Produit ${i}` },
|
|
},
|
|
},
|
|
})
|
|
|
|
const cat = p.categories?.[0]
|
|
if (cat) {
|
|
await attachTranslationToProductCategory(getContainer(), {
|
|
categoryId: cat.id,
|
|
translation: {
|
|
key: cat.id,
|
|
value: {
|
|
pt: { name: `Categoria ${i}` },
|
|
fr: { name: `Catégorie ${i}` },
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
const opt = p.options?.[0]
|
|
if (opt) {
|
|
await attachTranslationToOption(getContainer(), {
|
|
optionId: opt.id,
|
|
translation: {
|
|
key: opt.id,
|
|
value: {
|
|
pt: { title: "Tamanho" },
|
|
fr: { title: "Taille" },
|
|
},
|
|
},
|
|
})
|
|
}
|
|
|
|
await Promise.all(
|
|
(p.variants || []).map((v, vi) => {
|
|
const variantNumber = v.title.split("").pop()
|
|
return attachTranslationToVariant(getContainer(), {
|
|
variantId: v.id,
|
|
translation: {
|
|
key: v.id,
|
|
value: {
|
|
pt: { title: `Variante ${variantNumber}` },
|
|
fr: { title: `Variante ${variantNumber}` },
|
|
},
|
|
},
|
|
})
|
|
})
|
|
)
|
|
})
|
|
)
|
|
})
|
|
|
|
it("should call same entity in different levels (variant)", async () => {
|
|
const container = getContainer()
|
|
const query = container.resolve("query")
|
|
const productService = container.resolve(Modules.PRODUCT)
|
|
const inventoryService = container.resolve(Modules.INVENTORY)
|
|
|
|
const productServiceSpy = jest.spyOn(
|
|
productService,
|
|
"listProductVariants"
|
|
)
|
|
const inventoryServiceSpy = jest.spyOn(
|
|
inventoryService,
|
|
"listInventoryItems"
|
|
)
|
|
|
|
const result = await query.graph({
|
|
entity: "variants",
|
|
fields: [
|
|
"id",
|
|
"manage_inventory",
|
|
"inventory.id",
|
|
"inventory.variants.id",
|
|
],
|
|
})
|
|
|
|
expect(productServiceSpy).toHaveBeenCalledTimes(2)
|
|
expect(inventoryServiceSpy).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it("should call services in correct order with parallel execution where possible", async () => {
|
|
const container = getContainer()
|
|
|
|
const query = container.resolve("query")
|
|
const productService = container.resolve(Modules.PRODUCT)
|
|
const priceService = container.resolve(Modules.PRICING)
|
|
const translationService = container.resolve(
|
|
"translation"
|
|
) as TranslationModule
|
|
|
|
const productServiceSpy = jest.spyOn(productService, "listProducts")
|
|
const translationServiceSpy = jest.spyOn(
|
|
translationService,
|
|
"listTranslations"
|
|
)
|
|
const priceServiceSpy = jest.spyOn(priceService, "listPriceSets")
|
|
|
|
// Execute the query
|
|
const result = await query.graph({
|
|
entity: "product",
|
|
fields: [
|
|
"sales_channels.name",
|
|
"title",
|
|
"translation.*",
|
|
"categories.name",
|
|
"categories.translation.*",
|
|
"variants.title",
|
|
"variants.translation.*",
|
|
"options.title",
|
|
"options.translation.*",
|
|
"variants.prices.amount",
|
|
"variants.prices.currency_code",
|
|
],
|
|
})
|
|
|
|
expect(productServiceSpy.mock.calls[0][1]).toEqual({
|
|
select: [
|
|
"title",
|
|
"variants_id",
|
|
"id",
|
|
"categories.name",
|
|
"categories.id",
|
|
"variants.title",
|
|
"variants.id",
|
|
"options.title",
|
|
"options.id",
|
|
],
|
|
relations: ["categories", "variants", "options"],
|
|
args: {},
|
|
})
|
|
|
|
expect(translationServiceSpy.mock.calls[0][0].id).toHaveLength(3)
|
|
expect(translationServiceSpy.mock.calls[1][0].id).toHaveLength(12)
|
|
expect(priceServiceSpy.mock.calls[0][0].id).toHaveLength(6)
|
|
|
|
expect(result.data).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
title: "Product 3",
|
|
categories: [
|
|
expect.objectContaining({
|
|
name: "Category 3",
|
|
translation: expect.objectContaining({
|
|
value: {
|
|
fr: {
|
|
name: "Catégorie 3",
|
|
},
|
|
pt: {
|
|
name: "Categoria 3",
|
|
},
|
|
},
|
|
}),
|
|
}),
|
|
],
|
|
variants: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
title: "P3 Variant 2",
|
|
translation: expect.objectContaining({
|
|
value: {
|
|
fr: {
|
|
title: "Variante 2",
|
|
},
|
|
pt: {
|
|
title: "Variante 2",
|
|
},
|
|
},
|
|
}),
|
|
prices: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
amount: 20,
|
|
currency_code: "usd",
|
|
}),
|
|
]),
|
|
}),
|
|
expect.objectContaining({
|
|
title: "P3 Variant 1",
|
|
translation: expect.objectContaining({
|
|
value: {
|
|
fr: {
|
|
title: "Variante 1",
|
|
},
|
|
pt: {
|
|
title: "Variante 1",
|
|
},
|
|
},
|
|
}),
|
|
prices: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
amount: 10,
|
|
currency_code: "usd",
|
|
}),
|
|
]),
|
|
}),
|
|
]),
|
|
options: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
title: "size",
|
|
translation: expect.objectContaining({
|
|
value: {
|
|
fr: {
|
|
title: "Taille",
|
|
},
|
|
pt: {
|
|
title: "Tamanho",
|
|
},
|
|
},
|
|
}),
|
|
}),
|
|
]),
|
|
sales_channels: [],
|
|
translation: expect.objectContaining({
|
|
value: {
|
|
fr: {
|
|
title: "Produit 3",
|
|
},
|
|
pt: {
|
|
title: "Produto 3",
|
|
},
|
|
},
|
|
}),
|
|
}),
|
|
expect.objectContaining({
|
|
title: "Product 1",
|
|
categories: [
|
|
expect.objectContaining({
|
|
name: "Category 1",
|
|
translation: expect.objectContaining({
|
|
value: {
|
|
fr: {
|
|
name: "Catégorie 1",
|
|
},
|
|
pt: {
|
|
name: "Categoria 1",
|
|
},
|
|
},
|
|
}),
|
|
}),
|
|
],
|
|
variants: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
title: "P1 Variant 2",
|
|
translation: expect.objectContaining({
|
|
value: {
|
|
fr: {
|
|
title: "Variante 2",
|
|
},
|
|
pt: {
|
|
title: "Variante 2",
|
|
},
|
|
},
|
|
}),
|
|
prices: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
amount: 20,
|
|
currency_code: "usd",
|
|
}),
|
|
]),
|
|
}),
|
|
expect.objectContaining({
|
|
title: "P1 Variant 1",
|
|
translation: expect.objectContaining({
|
|
value: {
|
|
fr: {
|
|
title: "Variante 1",
|
|
},
|
|
pt: {
|
|
title: "Variante 1",
|
|
},
|
|
},
|
|
}),
|
|
prices: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
amount: 10,
|
|
currency_code: "usd",
|
|
}),
|
|
]),
|
|
}),
|
|
]),
|
|
options: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
title: "size",
|
|
translation: expect.objectContaining({
|
|
value: {
|
|
fr: {
|
|
title: "Taille",
|
|
},
|
|
pt: {
|
|
title: "Tamanho",
|
|
},
|
|
},
|
|
}),
|
|
}),
|
|
]),
|
|
sales_channels: [],
|
|
translation: expect.objectContaining({
|
|
value: {
|
|
fr: {
|
|
title: "Produit 1",
|
|
},
|
|
pt: {
|
|
title: "Produto 1",
|
|
},
|
|
},
|
|
}),
|
|
}),
|
|
expect.objectContaining({
|
|
title: "Product 2",
|
|
categories: [
|
|
expect.objectContaining({
|
|
name: "Category 2",
|
|
translation: expect.objectContaining({
|
|
value: {
|
|
fr: {
|
|
name: "Catégorie 2",
|
|
},
|
|
pt: {
|
|
name: "Categoria 2",
|
|
},
|
|
},
|
|
}),
|
|
}),
|
|
],
|
|
variants: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
title: "P2 Variant 1",
|
|
translation: expect.objectContaining({
|
|
value: {
|
|
fr: {
|
|
title: "Variante 1",
|
|
},
|
|
pt: {
|
|
title: "Variante 1",
|
|
},
|
|
},
|
|
}),
|
|
prices: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
amount: 10,
|
|
currency_code: "usd",
|
|
}),
|
|
]),
|
|
}),
|
|
expect.objectContaining({
|
|
title: "P2 Variant 2",
|
|
translation: expect.objectContaining({
|
|
value: {
|
|
fr: {
|
|
title: "Variante 2",
|
|
},
|
|
pt: {
|
|
title: "Variante 2",
|
|
},
|
|
},
|
|
}),
|
|
prices: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
amount: 20,
|
|
currency_code: "usd",
|
|
}),
|
|
]),
|
|
}),
|
|
]),
|
|
options: expect.arrayContaining([
|
|
expect.objectContaining({
|
|
title: "size",
|
|
translation: expect.objectContaining({
|
|
value: {
|
|
fr: {
|
|
title: "Taille",
|
|
},
|
|
pt: {
|
|
title: "Tamanho",
|
|
},
|
|
},
|
|
}),
|
|
}),
|
|
]),
|
|
sales_channels: [],
|
|
translation: expect.objectContaining({
|
|
value: {
|
|
fr: {
|
|
title: "Produit 2",
|
|
},
|
|
pt: {
|
|
title: "Produto 2",
|
|
},
|
|
},
|
|
}),
|
|
}),
|
|
])
|
|
)
|
|
})
|
|
})
|
|
},
|
|
})
|