Skip to content

Commit

Permalink
fix: Retrieve ancestors and/or descendants on product categories (#7226)
Browse files Browse the repository at this point in the history
  • Loading branch information
olivermrbl committed May 3, 2024
1 parent 520867b commit 1366e2e
Show file tree
Hide file tree
Showing 11 changed files with 150 additions and 382 deletions.
309 changes: 38 additions & 271 deletions integration-tests/api/__tests__/admin/product-category.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
import { IProductModuleService } from "@medusajs/types"
import { medusaIntegrationTestRunner } from "medusa-test-utils"
import { In } from "typeorm"
import { breaking } from "../../../helpers/breaking"
import {
adminHeaders,
Expand All @@ -16,7 +15,7 @@ let { Product } = {}
medusaIntegrationTestRunner({
env: {
MEDUSA_FF_PRODUCT_CATEGORIES: true,
// MEDUSA_FF_MEDUSA_V2: true,
MEDUSA_FF_MEDUSA_V2: true,
},
testSuite: ({ dbConnection, getContainer, api }) => {
let appContainer
Expand Down Expand Up @@ -478,6 +477,37 @@ medusaIntegrationTestRunner({
}),
])
})

it("adds all ancestors to categories in a nested way", async () => {
const response = await api.get(
`/admin/product-categories/${productCategoryChild1.id}?include_ancestors_tree=true`,
adminHeaders
)

expect(response.status).toEqual(200)
expect(response.data.product_category).toEqual(
expect.objectContaining({
id: productCategoryChild1.id,
name: "rank 1",
rank: 1,
parent_category: expect.objectContaining({
id: productCategoryChild.id,
name: "cashmere",
rank: 0,
parent_category: expect.objectContaining({
id: productCategory.id,
name: "sweater",
rank: 0,
parent_category: expect.objectContaining({
id: productCategoryParent.id,
name: "Mens",
rank: 0,
}),
}),
}),
})
)
})
})

describe("POST /admin/product-categories", () => {
Expand Down Expand Up @@ -525,25 +555,6 @@ medusaIntegrationTestRunner({
})
})

// TODO: Remove in V2, unnecessary test
it("throws an error when description is not a string", async () => {
const payload = {
name: "test",
handle: "test",
description: null,
}

const error = await api
.post(`/admin/product-categories`, payload, adminHeaders)
.catch((e) => e)

expect(error.response.status).toEqual(400)
expect(error.response.data.type).toEqual("invalid_data")
// expect(error.response.data.message).toEqual(
// "description must be a string"
// )
})

it("successfully creates a product category", async () => {
const payload = {
name: "test",
Expand All @@ -570,16 +581,9 @@ medusaIntegrationTestRunner({
is_active: false,
created_at: expect.any(String),
updated_at: expect.any(String),
...breaking(
() => ({
parent_category: expect.objectContaining({
id: productCategory.id,
}),
}),
() => ({
parent_category_id: productCategory.id,
})
),
parent_category: expect.objectContaining({
id: productCategory.id,
}),
category_children: [],
rank: 0,
}),
Expand Down Expand Up @@ -671,7 +675,8 @@ medusaIntegrationTestRunner({
})
})

describe("DELETE /admin/product-categories/:id", () => {
// TODO: Should be migrate to V2
describe.skip("DELETE /admin/product-categories/:id", () => {
beforeEach(async () => {
productCategoryParent = await simpleProductCategoryFactory(
dbConnection,
Expand Down Expand Up @@ -1221,245 +1226,7 @@ medusaIntegrationTestRunner({
})
})

// TODO: Remove in V2, endpoint changed
describe("POST /admin/product-categories/:id/products/batch", () => {
beforeEach(async () => {
productCategory = await simpleProductCategoryFactory(dbConnection, {
id: "test-category",
name: "test category",
})
})

it("should add products to a product category", async () => {
const testProduct1 = await simpleProductFactory(dbConnection, {
id: "test-product-1",
title: "test product 1",
})

const testProduct2 = await simpleProductFactory(dbConnection, {
id: "test-product-2",
title: "test product 2",
})

const payload = {
product_ids: [{ id: testProduct1.id }, { id: testProduct2.id }],
}

const response = await api.post(
`/admin/product-categories/${productCategory.id}/products/batch`,
payload,
adminHeaders
)

expect(response.status).toEqual(200)
expect(response.data.product_category).toEqual(
expect.objectContaining({
id: productCategory.id,
created_at: expect.any(String),
updated_at: expect.any(String),
})
)

const products = await dbConnection.manager.find(Product, {
where: { id: In([testProduct1.id, testProduct2.id]) },
relations: ["categories"],
})

expect(products[0].categories).toEqual([
expect.objectContaining({
id: productCategory.id,
}),
])

expect(products[1].categories).toEqual([
expect.objectContaining({
id: productCategory.id,
}),
])
})

it("throws error when product ID is invalid", async () => {
const payload = {
product_ids: [{ id: "product-id-invalid" }],
}

const error = await api
.post(
`/admin/product-categories/${productCategory.id}/products/batch`,
payload,
adminHeaders
)
.catch((e) => e)

expect(error.response.status).toEqual(400)
expect(error.response.data).toEqual({
errors: ["Products product-id-invalid do not exist"],
message:
"Provided request body contains errors. Please check the data and retry the request",
})
})

it("throws error when category ID is invalid", async () => {
const payload = { product_ids: [] }

const error = await api
.post(
`/admin/product-categories/invalid-category-id/products/batch`,
payload,
adminHeaders
)
.catch((e) => e)

expect(error.response.status).toEqual(404)
expect(error.response.data).toEqual({
message: "ProductCategory with id: invalid-category-id was not found",
type: "not_found",
})
})

it("throws error trying to expand not allowed relations", async () => {
const payload = { product_ids: [] }

const error = await api
.post(
`/admin/product-categories/invalid-category-id/products/batch?expand=products`,
payload,
adminHeaders
)
.catch((e) => e)

expect(error.response.status).toEqual(400)
expect(error.response.data).toEqual({
message: "Requested fields [products] are not valid",
type: "invalid_data",
})
})
})

// TODO: Remove in v2, endpoint changed
describe("DELETE /admin/product-categories/:id/products/batch", () => {
let testProduct1, testProduct2

beforeEach(async () => {
testProduct1 = await simpleProductFactory(dbConnection, {
id: "test-product-1",
title: "test product 1",
})

testProduct2 = await simpleProductFactory(dbConnection, {
id: "test-product-2",
title: "test product 2",
})

productCategory = await simpleProductCategoryFactory(dbConnection, {
id: "test-category",
name: "test category",
products: [testProduct1, testProduct2],
})
})

it("should remove products from a product category", async () => {
const payload = {
product_ids: [{ id: testProduct2.id }],
}

const response = await api.delete(
`/admin/product-categories/${productCategory.id}/products/batch`,
{
...adminHeaders,
data: payload,
}
)

expect(response.status).toEqual(200)
expect(response.data.product_category).toEqual(
expect.objectContaining({
id: productCategory.id,
created_at: expect.any(String),
updated_at: expect.any(String),
})
)

const products = await dbConnection.manager.find(Product, {
where: { id: In([testProduct1.id, testProduct2.id]) },
relations: ["categories"],
})

expect(products[0].categories).toEqual([
expect.objectContaining({
id: productCategory.id,
}),
])

expect(products[1].categories).toEqual([])
})

it("throws error when product ID is invalid", async () => {
const payload = {
product_ids: [{ id: "product-id-invalid" }],
}

const error = await api
.delete(
`/admin/product-categories/${productCategory.id}/products/batch`,
{
...adminHeaders,
data: payload,
}
)
.catch((e) => e)

expect(error.response.status).toEqual(400)
expect(error.response.data).toEqual({
errors: ["Products product-id-invalid do not exist"],
message:
"Provided request body contains errors. Please check the data and retry the request",
})
})

it("throws error when category ID is invalid", async () => {
const payload = { product_ids: [] }

const error = await api
.delete(
`/admin/product-categories/invalid-category-id/products/batch`,
{
...adminHeaders,
data: payload,
}
)
.catch((e) => e)

expect(error.response.status).toEqual(404)
expect(error.response.data).toEqual({
message: "ProductCategory with id: invalid-category-id was not found",
type: "not_found",
})
})

it("throws error trying to expand not allowed relations", async () => {
const payload = { product_ids: [] }

const error = await api
.delete(
`/admin/product-categories/invalid-category-id/products/batch?expand=products`,
{
...adminHeaders,
data: payload,
}
)
.catch((e) => e)

expect(error.response.status).toEqual(400)
expect(error.response.data).toEqual({
message: "Requested fields [products] are not valid",
type: "invalid_data",
})
})
})

// Skipping because the test is for V2 only
describe.skip("POST /admin/product-categories/:id/products", () => {
describe("POST /admin/product-categories/:id/products", () => {
beforeEach(async () => {
productCategory = await productModuleService.createCategory({
name: "category parent",
Expand Down
1 change: 1 addition & 0 deletions packages/core/orchestration/src/joiner/remote-joiner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,7 @@ export class RemoteJoiner {
uniqueIds,
relationship
)

const isObj = isDefined(response.path)
let resData = isObj ? response.data[response.path!] : response.data

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
AuthenticatedMedusaRequest,
MedusaResponse,
} from "../../../../../types/routing"
import { refetchCategory } from "../../helpers"
import { refetchEntity } from "../../../../utils/refetch-entity"

export const POST = async (
req: AuthenticatedMedusaRequest<LinkMethodRequest>,
Expand All @@ -24,11 +24,11 @@ export const POST = async (
throw errors[0].error
}

const category = await refetchCategory(
req.params.id,
const category = await refetchEntity(
"product_category",
id,
req.scope,
req.remoteQueryConfig.fields,
req.filterableFields
req.remoteQueryConfig.fields
)

res.status(200).json({ product_category: category })
Expand Down
Loading

0 comments on commit 1366e2e

Please sign in to comment.