feat(medusa): Nested Categories Admin List Endpoint (#2973)
* chore: added get route for admin categories API * chore: add tree method to mock repository * chore: added changeset to the PR * chore: rename id to productCategoryId in service * chore: switch cli option to string * chore: lint fixes, tests for parent category * chore: move Nested Categories behind feature flag * chore: use transformQuery hook in api * chore: add feature flag in migrations * chore: remove migration FF, fix FF name * chore: add free text search + count repo function * chore: added list endpoint for admin * chore: added changeset for feature * chore: address pr review comments * chore: change oas comment * chore: add nullable parent category filter + test
This commit is contained in:
@@ -111,4 +111,126 @@ describe("/admin/product-categories", () => {
|
||||
expect(response.status).toEqual(200)
|
||||
})
|
||||
})
|
||||
|
||||
describe("GET /admin/product-categories", () => {
|
||||
beforeEach(async () => {
|
||||
await adminSeeder(dbConnection)
|
||||
|
||||
productCategoryParent = await simpleProductCategoryFactory(dbConnection, {
|
||||
name: "Mens",
|
||||
handle: "mens",
|
||||
})
|
||||
|
||||
productCategory = await simpleProductCategoryFactory(dbConnection, {
|
||||
name: "sweater",
|
||||
handle: "sweater",
|
||||
parent_category: productCategoryParent,
|
||||
is_internal: true,
|
||||
})
|
||||
|
||||
productCategoryChild = await simpleProductCategoryFactory(dbConnection, {
|
||||
name: "cashmere",
|
||||
handle: "cashmere",
|
||||
parent_category: productCategory,
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
const db = useDb()
|
||||
return await db.teardown()
|
||||
})
|
||||
|
||||
it("gets list of product category with immediate children and parents", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api.get(
|
||||
`/admin/product-categories`,
|
||||
adminHeaders
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.count).toEqual(3)
|
||||
expect(response.data.offset).toEqual(0)
|
||||
expect(response.data.limit).toEqual(100)
|
||||
expect(response.data.product_categories).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: productCategoryParent.id,
|
||||
parent_category: null,
|
||||
category_children: [
|
||||
expect.objectContaining({
|
||||
id: productCategory.id,
|
||||
})
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategory.id,
|
||||
parent_category: expect.objectContaining({
|
||||
id: productCategoryParent.id,
|
||||
}),
|
||||
category_children: [
|
||||
expect.objectContaining({
|
||||
id: productCategoryChild.id,
|
||||
})
|
||||
],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: productCategoryChild.id,
|
||||
parent_category: expect.objectContaining({
|
||||
id: productCategory.id,
|
||||
}),
|
||||
category_children: [],
|
||||
}),
|
||||
])
|
||||
)
|
||||
})
|
||||
|
||||
it("filters based on whitelisted attributes of the data model", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api.get(
|
||||
`/admin/product-categories?is_internal=true`,
|
||||
adminHeaders,
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.count).toEqual(1)
|
||||
expect(response.data.product_categories[0].id).toEqual(productCategory.id)
|
||||
})
|
||||
|
||||
it("filters based on free text on name and handle columns", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api.get(
|
||||
`/admin/product-categories?q=men`,
|
||||
adminHeaders,
|
||||
)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.count).toEqual(1)
|
||||
expect(response.data.product_categories[0].id).toEqual(productCategoryParent.id)
|
||||
})
|
||||
|
||||
it("filters based on parent category", async () => {
|
||||
const api = useApi()
|
||||
|
||||
const response = await api.get(
|
||||
`/admin/product-categories?parent_category_id=${productCategoryParent.id}`,
|
||||
adminHeaders,
|
||||
).catch(e => e)
|
||||
|
||||
expect(response.status).toEqual(200)
|
||||
expect(response.data.count).toEqual(1)
|
||||
expect(response.data.product_categories[0].id).toEqual(productCategory.id)
|
||||
|
||||
const nullCategoryResponse = await api.get(
|
||||
`/admin/product-categories?parent_category_id=null`,
|
||||
adminHeaders,
|
||||
).catch(e => e)
|
||||
|
||||
expect(nullCategoryResponse.status).toEqual(200)
|
||||
expect(nullCategoryResponse.data.count).toEqual(1)
|
||||
expect(nullCategoryResponse.data.product_categories[0].id).toEqual(productCategoryParent.id)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -2,6 +2,7 @@ import path from "path"
|
||||
import { ProductCategory } from "@medusajs/medusa"
|
||||
import { initDb, useDb } from "../../../helpers/use-db"
|
||||
import { simpleProductCategoryFactory } from '../../factories'
|
||||
import { ProductCategoryRepository } from "@medusajs/medusa/dist/repositories/product-category"
|
||||
|
||||
describe("Product Categories", () => {
|
||||
let dbConnection
|
||||
@@ -30,7 +31,8 @@ describe("Product Categories", () => {
|
||||
a11 = await simpleProductCategoryFactory(dbConnection, { name: 'a11', handle: 'a11', parent_category: a1 })
|
||||
a111 = await simpleProductCategoryFactory(dbConnection, { name: 'a111', handle: 'a111', parent_category: a11 })
|
||||
a12 = await simpleProductCategoryFactory(dbConnection, { name: 'a12', handle: 'a12', parent_category: a1 })
|
||||
productCategoryRepository = dbConnection.getTreeRepository(ProductCategory)
|
||||
|
||||
productCategoryRepository = dbConnection.manager.getCustomRepository(ProductCategoryRepository)
|
||||
})
|
||||
|
||||
it("can fetch all root categories", async () => {
|
||||
@@ -56,7 +58,6 @@ describe("Product Categories", () => {
|
||||
])
|
||||
})
|
||||
|
||||
|
||||
it("can fetch all root descendants of a category", async () => {
|
||||
const a1Children = await productCategoryRepository.findDescendants(a1)
|
||||
|
||||
@@ -76,4 +77,131 @@ describe("Product Categories", () => {
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("getFreeTextSearchResultsAndCount", () => {
|
||||
let a1, a11, a111, a12
|
||||
let productCategoryRepository
|
||||
|
||||
beforeEach(async () => {
|
||||
a1 = await simpleProductCategoryFactory(
|
||||
dbConnection, {
|
||||
name: 'skinny jeans',
|
||||
handle: 'skinny-jeans',
|
||||
is_active: true
|
||||
}
|
||||
)
|
||||
|
||||
a11 = await simpleProductCategoryFactory(
|
||||
dbConnection, {
|
||||
name: 'winter shirts',
|
||||
handle: 'winter-shirts',
|
||||
parent_category: a1,
|
||||
is_active: true
|
||||
}
|
||||
)
|
||||
|
||||
a111 = await simpleProductCategoryFactory(
|
||||
dbConnection, {
|
||||
name: 'running shoes',
|
||||
handle: 'running-shoes',
|
||||
parent_category: a11
|
||||
}
|
||||
)
|
||||
|
||||
a12 = await simpleProductCategoryFactory(
|
||||
dbConnection, {
|
||||
name: 'casual shoes',
|
||||
handle: 'casual-shoes',
|
||||
parent_category: a1,
|
||||
is_internal: true
|
||||
}
|
||||
)
|
||||
|
||||
productCategoryRepository = dbConnection.manager.getCustomRepository(ProductCategoryRepository)
|
||||
})
|
||||
|
||||
it("fetches all active categories", async () => {
|
||||
const [ categories, count ] = await productCategoryRepository.getFreeTextSearchResultsAndCount(
|
||||
{ where: { is_active: true } },
|
||||
)
|
||||
|
||||
expect(count).toEqual(2)
|
||||
expect(categories).toEqual([
|
||||
expect.objectContaining({
|
||||
name: a1.name,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: a11.name,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("fetches all internal categories", async () => {
|
||||
const [ categories, count ] = await productCategoryRepository.getFreeTextSearchResultsAndCount(
|
||||
{ where: { is_internal: true } },
|
||||
)
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(categories).toEqual([
|
||||
expect.objectContaining({
|
||||
name: a12.name,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("fetches all categories with query shoes", async () => {
|
||||
const [ categories, count ] = await productCategoryRepository.getFreeTextSearchResultsAndCount(
|
||||
{ where: {} },
|
||||
'shoes'
|
||||
)
|
||||
|
||||
expect(count).toEqual(2)
|
||||
expect(categories).toEqual([
|
||||
expect.objectContaining({
|
||||
name: a111.name,
|
||||
}),
|
||||
expect.objectContaining({
|
||||
name: a12.name,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("fetches all categories with query casual-", async () => {
|
||||
const [ categories, count ] = await productCategoryRepository.getFreeTextSearchResultsAndCount(
|
||||
{ where: {} },
|
||||
'casual-'
|
||||
)
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(categories).toEqual([
|
||||
expect.objectContaining({
|
||||
name: a12.name,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("builds relations for categories", async () => {
|
||||
const [ categories, count ] = await productCategoryRepository.getFreeTextSearchResultsAndCount(
|
||||
{
|
||||
where: { id: a11.id },
|
||||
relations: ['parent_category', 'category_children']
|
||||
},
|
||||
)
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(categories[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: a11.id,
|
||||
parent_category: expect.objectContaining({
|
||||
id: a1.id,
|
||||
}),
|
||||
category_children: [
|
||||
expect.objectContaining({
|
||||
id: a111.id,
|
||||
})
|
||||
]
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user