feat: Create product category flow (#7034)
* feat: Create product category * address PR comments
This commit is contained in:
@@ -1,62 +1,64 @@
|
|||||||
import path from "path"
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
import { Product, ProductCategory } from "@medusajs/medusa"
|
import { IProductModuleService } from "@medusajs/types"
|
||||||
|
import { medusaIntegrationTestRunner } from "medusa-test-utils"
|
||||||
import { In } from "typeorm"
|
import { In } from "typeorm"
|
||||||
|
import { breaking } from "../../../helpers/breaking"
|
||||||
import startServerWithEnvironment from "../../../environment-helpers/start-server-with-environment"
|
|
||||||
import { useApi } from "../../../environment-helpers/use-api"
|
|
||||||
import { useDb } from "../../../environment-helpers/use-db"
|
|
||||||
import adminSeeder from "../../../helpers/admin-seeder"
|
|
||||||
import {
|
import {
|
||||||
simpleProductCategoryFactory,
|
adminHeaders,
|
||||||
simpleProductFactory,
|
createAdminUser,
|
||||||
} from "../../../factories"
|
} from "../../../helpers/create-admin-user"
|
||||||
|
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
const adminHeaders = {
|
let { simpleProductCategoryFactory, simpleProductFactory } = {}
|
||||||
headers: {
|
let { Product } = {}
|
||||||
"x-medusa-access-token": "test_token",
|
|
||||||
|
medusaIntegrationTestRunner({
|
||||||
|
env: {
|
||||||
|
MEDUSA_FF_PRODUCT_CATEGORIES: true,
|
||||||
|
// MEDUSA_FF_MEDUSA_V2: true,
|
||||||
},
|
},
|
||||||
}
|
testSuite: ({ dbConnection, getContainer, api }) => {
|
||||||
|
let appContainer
|
||||||
|
let productCategory
|
||||||
|
let productCategory1
|
||||||
|
let productCategory2
|
||||||
|
let productCategoryChild
|
||||||
|
let productCategoryParent
|
||||||
|
let productCategoryChild0
|
||||||
|
let productCategoryChild1
|
||||||
|
let productCategoryChild2
|
||||||
|
let productCategoryChild3
|
||||||
|
|
||||||
describe("/admin/product-categories", () => {
|
let productModuleService: IProductModuleService
|
||||||
let medusaProcess
|
|
||||||
let dbConnection
|
|
||||||
let productCategory!: ProductCategory
|
|
||||||
let productCategory1!: ProductCategory
|
|
||||||
let productCategory2!: ProductCategory
|
|
||||||
let productCategoryChild!: ProductCategory
|
|
||||||
let productCategoryParent!: ProductCategory
|
|
||||||
let productCategoryChild0!: ProductCategory
|
|
||||||
let productCategoryChild1!: ProductCategory
|
|
||||||
let productCategoryChild2!: ProductCategory
|
|
||||||
let productCategoryChild3!: ProductCategory
|
|
||||||
|
|
||||||
beforeAll(async () => {
|
beforeAll(() => {
|
||||||
const cwd = path.resolve(path.join(__dirname, "..", ".."))
|
;({
|
||||||
const [process, connection] = await startServerWithEnvironment({
|
simpleProductCategoryFactory,
|
||||||
cwd,
|
simpleProductFactory,
|
||||||
env: { MEDUSA_FF_PRODUCT_CATEGORIES: true },
|
} = require("../../../factories"))
|
||||||
})
|
;({ Product } = require("@medusajs/medusa"))
|
||||||
dbConnection = connection
|
|
||||||
medusaProcess = process
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(async () => {
|
beforeEach(async () => {
|
||||||
const db = useDb()
|
appContainer = getContainer()
|
||||||
await db.shutdown()
|
|
||||||
|
|
||||||
medusaProcess.kill()
|
productModuleService = appContainer.resolve(
|
||||||
|
ModuleRegistrationName.PRODUCT
|
||||||
|
)
|
||||||
|
|
||||||
|
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("GET /admin/product-categories/:id", () => {
|
describe("GET /admin/product-categories/:id", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await adminSeeder(dbConnection)
|
productCategoryParent = await simpleProductCategoryFactory(
|
||||||
|
dbConnection,
|
||||||
productCategoryParent = await simpleProductCategoryFactory(dbConnection, {
|
{
|
||||||
name: "category parent",
|
name: "category parent",
|
||||||
handle: "category-parent",
|
handle: "category-parent",
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
productCategory = await simpleProductCategoryFactory(dbConnection, {
|
productCategory = await simpleProductCategoryFactory(dbConnection, {
|
||||||
name: "category",
|
name: "category",
|
||||||
@@ -64,42 +66,51 @@ describe("/admin/product-categories", () => {
|
|||||||
parent_category: productCategoryParent,
|
parent_category: productCategoryParent,
|
||||||
})
|
})
|
||||||
|
|
||||||
productCategoryChild = await simpleProductCategoryFactory(dbConnection, {
|
productCategoryChild = await simpleProductCategoryFactory(
|
||||||
|
dbConnection,
|
||||||
|
{
|
||||||
name: "category child",
|
name: "category child",
|
||||||
handle: "category-child",
|
handle: "category-child",
|
||||||
parent_category: productCategory,
|
parent_category: productCategory,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
productCategoryChild2 = await simpleProductCategoryFactory(dbConnection, {
|
productCategoryChild2 = await simpleProductCategoryFactory(
|
||||||
|
dbConnection,
|
||||||
|
{
|
||||||
name: "category child 2",
|
name: "category child 2",
|
||||||
handle: "category-child-2",
|
handle: "category-child-2",
|
||||||
parent_category: productCategoryChild,
|
parent_category: productCategoryChild,
|
||||||
})
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
const db = useDb()
|
|
||||||
return await db.teardown()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("gets product category with children tree and parent", async () => {
|
it("gets product category with children tree and parent", async () => {
|
||||||
const api = useApi()
|
const path = breaking(
|
||||||
|
() => `/admin/product-categories/${productCategory.id}`,
|
||||||
const response = await api.get(
|
() =>
|
||||||
`/admin/product-categories/${productCategory.id}`,
|
`/admin/product-categories/${productCategory.id}?include_descendants_tree=true`
|
||||||
adminHeaders
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const response = await api.get(path, adminHeaders)
|
||||||
|
|
||||||
expect(response.data.product_category).toEqual(
|
expect(response.data.product_category).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: productCategory.id,
|
id: productCategory.id,
|
||||||
name: productCategory.name,
|
name: productCategory.name,
|
||||||
handle: productCategory.handle,
|
handle: productCategory.handle,
|
||||||
|
...breaking(
|
||||||
|
() => ({
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: productCategoryParent.id,
|
id: productCategoryParent.id,
|
||||||
name: productCategoryParent.name,
|
name: productCategoryParent.name,
|
||||||
handle: productCategoryParent.handle,
|
handle: productCategoryParent.handle,
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
|
() => ({
|
||||||
|
parent_category_id: productCategoryParent.id,
|
||||||
|
})
|
||||||
|
),
|
||||||
category_children: [
|
category_children: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: productCategoryChild.id,
|
id: productCategoryChild.id,
|
||||||
@@ -124,12 +135,13 @@ describe("/admin/product-categories", () => {
|
|||||||
|
|
||||||
describe("GET /admin/product-categories", () => {
|
describe("GET /admin/product-categories", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await adminSeeder(dbConnection)
|
productCategoryParent = await simpleProductCategoryFactory(
|
||||||
|
dbConnection,
|
||||||
productCategoryParent = await simpleProductCategoryFactory(dbConnection, {
|
{
|
||||||
name: "Mens",
|
name: "Mens",
|
||||||
rank: 0,
|
rank: 0,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
productCategory = await simpleProductCategoryFactory(dbConnection, {
|
productCategory = await simpleProductCategoryFactory(dbConnection, {
|
||||||
name: "sweater",
|
name: "sweater",
|
||||||
@@ -138,63 +150,82 @@ describe("/admin/product-categories", () => {
|
|||||||
rank: 0,
|
rank: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
productCategoryChild = await simpleProductCategoryFactory(dbConnection, {
|
productCategoryChild = await simpleProductCategoryFactory(
|
||||||
|
dbConnection,
|
||||||
|
{
|
||||||
name: "cashmere",
|
name: "cashmere",
|
||||||
parent_category: productCategory,
|
parent_category: productCategory,
|
||||||
rank: 0,
|
rank: 0,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
productCategoryChild0 = await simpleProductCategoryFactory(dbConnection, {
|
productCategoryChild0 = await simpleProductCategoryFactory(
|
||||||
|
dbConnection,
|
||||||
|
{
|
||||||
name: "rank 2",
|
name: "rank 2",
|
||||||
parent_category: productCategoryChild,
|
parent_category: productCategoryChild,
|
||||||
rank: 2,
|
rank: 2,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
productCategoryChild1 = await simpleProductCategoryFactory(dbConnection, {
|
productCategoryChild1 = await simpleProductCategoryFactory(
|
||||||
|
dbConnection,
|
||||||
|
{
|
||||||
name: "rank 1",
|
name: "rank 1",
|
||||||
parent_category: productCategoryChild,
|
parent_category: productCategoryChild,
|
||||||
rank: 1,
|
rank: 1,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
productCategoryChild2 = await simpleProductCategoryFactory(dbConnection, {
|
productCategoryChild2 = await simpleProductCategoryFactory(
|
||||||
|
dbConnection,
|
||||||
|
{
|
||||||
name: "rank 0",
|
name: "rank 0",
|
||||||
parent_category: productCategoryChild,
|
parent_category: productCategoryChild,
|
||||||
rank: 0,
|
rank: 0,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
productCategoryChild3 = await simpleProductCategoryFactory(dbConnection, {
|
productCategoryChild3 = await simpleProductCategoryFactory(
|
||||||
|
dbConnection,
|
||||||
|
{
|
||||||
name: "rank 3",
|
name: "rank 3",
|
||||||
parent_category: productCategoryChild,
|
parent_category: productCategoryChild,
|
||||||
rank: 3,
|
rank: 3,
|
||||||
})
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
const db = useDb()
|
|
||||||
return await db.teardown()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("gets list of product category with immediate children and parents", async () => {
|
it("gets list of product category with immediate children and parents", async () => {
|
||||||
const api = useApi()
|
const path = breaking(
|
||||||
|
() => `/admin/product-categories?limit=7`,
|
||||||
const response = await api.get(
|
() =>
|
||||||
`/admin/product-categories?limit=7`,
|
`/admin/product-categories?include_descendants_tree=true&limit=7`
|
||||||
adminHeaders
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const response = await api.get(path, adminHeaders)
|
||||||
|
|
||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
expect(response.data.count).toEqual(7)
|
expect(response.data.count).toEqual(7)
|
||||||
expect(response.data.offset).toEqual(0)
|
expect(response.data.offset).toEqual(0)
|
||||||
expect(response.data.limit).toEqual(7)
|
expect(response.data.limit).toEqual(7)
|
||||||
|
|
||||||
expect(response.data.product_categories).toEqual([
|
expect(response.data.product_categories).toEqual(
|
||||||
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: productCategoryChild.id,
|
id: productCategoryChild.id,
|
||||||
|
...breaking(
|
||||||
|
() => ({
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: productCategory.id,
|
id: productCategory.id,
|
||||||
handle: productCategory.handle,
|
handle: productCategory.handle,
|
||||||
rank: 0,
|
rank: 0,
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
|
() => ({
|
||||||
|
parent_category_id: productCategory.id,
|
||||||
|
})
|
||||||
|
),
|
||||||
category_children: [
|
category_children: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: productCategoryChild2.id,
|
id: productCategoryChild2.id,
|
||||||
@@ -220,7 +251,10 @@ describe("/admin/product-categories", () => {
|
|||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: productCategoryParent.id,
|
id: productCategoryParent.id,
|
||||||
parent_category: null,
|
...breaking(
|
||||||
|
() => ({ parent_category: null }),
|
||||||
|
() => ({})
|
||||||
|
),
|
||||||
category_children: [
|
category_children: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: productCategory.id,
|
id: productCategory.id,
|
||||||
@@ -231,20 +265,34 @@ describe("/admin/product-categories", () => {
|
|||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: productCategoryChild2.id,
|
id: productCategoryChild2.id,
|
||||||
|
...breaking(
|
||||||
|
() => ({
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: productCategoryChild.id,
|
id: productCategoryChild.id,
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
|
() => ({
|
||||||
|
parent_category_id: productCategoryChild.id,
|
||||||
|
})
|
||||||
|
),
|
||||||
category_children: [],
|
category_children: [],
|
||||||
rank: 0,
|
rank: 0,
|
||||||
handle: productCategoryChild2.handle,
|
handle: productCategoryChild2.handle,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: productCategory.id,
|
id: productCategory.id,
|
||||||
|
...breaking(
|
||||||
|
() => ({
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: productCategoryParent.id,
|
id: productCategoryParent.id,
|
||||||
rank: 0,
|
rank: 0,
|
||||||
handle: productCategoryParent.handle,
|
handle: productCategoryParent.handle,
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
|
() => ({
|
||||||
|
parent_category_id: productCategoryParent.id,
|
||||||
|
})
|
||||||
|
),
|
||||||
category_children: [
|
category_children: [
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: productCategoryChild.id,
|
id: productCategoryChild.id,
|
||||||
@@ -255,43 +303,63 @@ describe("/admin/product-categories", () => {
|
|||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: productCategoryChild1.id,
|
id: productCategoryChild1.id,
|
||||||
|
...breaking(
|
||||||
|
() => ({
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: productCategoryChild.id,
|
id: productCategoryChild.id,
|
||||||
handle: productCategoryChild.handle,
|
handle: productCategoryChild.handle,
|
||||||
rank: 0,
|
rank: 0,
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
|
() => ({
|
||||||
|
parent_category_id: productCategoryChild.id,
|
||||||
|
})
|
||||||
|
),
|
||||||
category_children: [],
|
category_children: [],
|
||||||
handle: productCategoryChild1.handle,
|
handle: productCategoryChild1.handle,
|
||||||
rank: 1,
|
rank: 1,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: productCategoryChild0.id,
|
id: productCategoryChild0.id,
|
||||||
|
...breaking(
|
||||||
|
() => ({
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: productCategoryChild.id,
|
id: productCategoryChild.id,
|
||||||
handle: productCategoryChild.handle,
|
handle: productCategoryChild.handle,
|
||||||
rank: 0,
|
rank: 0,
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
|
() => ({
|
||||||
|
parent_category_id: productCategoryChild.id,
|
||||||
|
})
|
||||||
|
),
|
||||||
category_children: [],
|
category_children: [],
|
||||||
handle: productCategoryChild0.handle,
|
handle: productCategoryChild0.handle,
|
||||||
rank: 2,
|
rank: 2,
|
||||||
}),
|
}),
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: productCategoryChild3.id,
|
id: productCategoryChild3.id,
|
||||||
|
...breaking(
|
||||||
|
() => ({
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: productCategoryChild.id,
|
id: productCategoryChild.id,
|
||||||
handle: productCategoryChild.handle,
|
handle: productCategoryChild.handle,
|
||||||
rank: 0,
|
rank: 0,
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
|
() => ({
|
||||||
|
parent_category_id: productCategoryChild.id,
|
||||||
|
})
|
||||||
|
),
|
||||||
category_children: [],
|
category_children: [],
|
||||||
handle: productCategoryChild3.handle,
|
handle: productCategoryChild3.handle,
|
||||||
rank: 3,
|
rank: 3,
|
||||||
}),
|
}),
|
||||||
])
|
])
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("filters based on whitelisted attributes of the data model", async () => {
|
it("filters based on whitelisted attributes of the data model", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const response = await api.get(
|
const response = await api.get(
|
||||||
`/admin/product-categories?is_internal=true&limit=7`,
|
`/admin/product-categories?is_internal=true&limit=7`,
|
||||||
adminHeaders
|
adminHeaders
|
||||||
@@ -299,12 +367,12 @@ describe("/admin/product-categories", () => {
|
|||||||
|
|
||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
expect(response.data.count).toEqual(1)
|
expect(response.data.count).toEqual(1)
|
||||||
expect(response.data.product_categories[0].id).toEqual(productCategory.id)
|
expect(response.data.product_categories[0].id).toEqual(
|
||||||
|
productCategory.id
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("filters based on handle attribute of the data model", async () => {
|
it("filters based on handle attribute of the data model", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const response = await api.get(
|
const response = await api.get(
|
||||||
`/admin/product-categories?handle=${productCategory.handle}&limit=2`,
|
`/admin/product-categories?handle=${productCategory.handle}&limit=2`,
|
||||||
adminHeaders
|
adminHeaders
|
||||||
@@ -312,12 +380,12 @@ describe("/admin/product-categories", () => {
|
|||||||
|
|
||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
expect(response.data.count).toEqual(1)
|
expect(response.data.count).toEqual(1)
|
||||||
expect(response.data.product_categories[0].id).toEqual(productCategory.id)
|
expect(response.data.product_categories[0].id).toEqual(
|
||||||
|
productCategory.id
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("filters based on free text on name and handle columns", async () => {
|
it("filters based on free text on name and handle columns", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const response = await api.get(
|
const response = await api.get(
|
||||||
`/admin/product-categories?q=men&limit=1`,
|
`/admin/product-categories?q=men&limit=1`,
|
||||||
adminHeaders
|
adminHeaders
|
||||||
@@ -331,8 +399,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("filters based on parent category", async () => {
|
it("filters based on parent category", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const response = await api.get(
|
const response = await api.get(
|
||||||
`/admin/product-categories?parent_category_id=${productCategoryParent.id}&limit=7`,
|
`/admin/product-categories?parent_category_id=${productCategoryParent.id}&limit=7`,
|
||||||
adminHeaders
|
adminHeaders
|
||||||
@@ -340,10 +406,15 @@ describe("/admin/product-categories", () => {
|
|||||||
|
|
||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
expect(response.data.count).toEqual(1)
|
expect(response.data.count).toEqual(1)
|
||||||
expect(response.data.product_categories[0].id).toEqual(productCategory.id)
|
expect(response.data.product_categories[0].id).toEqual(
|
||||||
|
productCategory.id
|
||||||
|
)
|
||||||
|
|
||||||
const nullCategoryResponse = await api
|
const nullCategoryResponse = await api
|
||||||
.get(`/admin/product-categories?parent_category_id=null`, adminHeaders)
|
.get(
|
||||||
|
`/admin/product-categories?parent_category_id=null`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
.catch((e) => e)
|
.catch((e) => e)
|
||||||
|
|
||||||
expect(nullCategoryResponse.status).toEqual(200)
|
expect(nullCategoryResponse.status).toEqual(200)
|
||||||
@@ -354,8 +425,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("adds all descendants to categories in a nested way", async () => {
|
it("adds all descendants to categories in a nested way", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const response = await api.get(
|
const response = await api.get(
|
||||||
`/admin/product-categories?parent_category_id=null&include_descendants_tree=true&limit=7`,
|
`/admin/product-categories?parent_category_id=null&include_descendants_tree=true&limit=7`,
|
||||||
adminHeaders
|
adminHeaders
|
||||||
@@ -413,41 +482,51 @@ describe("/admin/product-categories", () => {
|
|||||||
|
|
||||||
describe("POST /admin/product-categories", () => {
|
describe("POST /admin/product-categories", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await adminSeeder(dbConnection)
|
productCategoryParent = await breaking(
|
||||||
|
async () =>
|
||||||
productCategoryParent = await simpleProductCategoryFactory(dbConnection, {
|
await simpleProductCategoryFactory(dbConnection, {
|
||||||
|
name: "category parent",
|
||||||
|
handle: "category-parent",
|
||||||
|
}),
|
||||||
|
async () =>
|
||||||
|
await productModuleService.createCategory({
|
||||||
name: "category parent",
|
name: "category parent",
|
||||||
handle: "category-parent",
|
handle: "category-parent",
|
||||||
})
|
})
|
||||||
|
)
|
||||||
|
|
||||||
productCategory = await simpleProductCategoryFactory(dbConnection, {
|
productCategory = await breaking(
|
||||||
|
async () =>
|
||||||
|
await simpleProductCategoryFactory(dbConnection, {
|
||||||
name: "category",
|
name: "category",
|
||||||
handle: "category",
|
handle: "category",
|
||||||
parent_category: productCategoryParent,
|
parent_category: productCategoryParent,
|
||||||
|
}),
|
||||||
|
async () =>
|
||||||
|
await productModuleService.createCategory({
|
||||||
|
name: "category",
|
||||||
|
handle: "category",
|
||||||
|
parent_category_id: productCategoryParent.id,
|
||||||
})
|
})
|
||||||
})
|
)
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
const db = useDb()
|
|
||||||
return await db.teardown()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("throws an error if required fields are missing", async () => {
|
it("throws an error if required fields are missing", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const error = await api
|
const error = await api
|
||||||
.post(`/admin/product-categories`, {}, adminHeaders)
|
.post(`/admin/product-categories`, {}, adminHeaders)
|
||||||
.catch((e) => e)
|
.catch((e) => e)
|
||||||
|
|
||||||
expect(error.response.status).toEqual(400)
|
expect(error.response.status).toEqual(400)
|
||||||
expect(error.response.data.type).toEqual("invalid_data")
|
expect(error.response.data.type).toEqual("invalid_data")
|
||||||
|
breaking(() => {
|
||||||
expect(error.response.data.message).toEqual(
|
expect(error.response.data.message).toEqual(
|
||||||
"name should not be empty, name must be a string"
|
"name should not be empty, name must be a string"
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: Remove in V2, unnecessary test
|
||||||
it("throws an error when description is not a string", async () => {
|
it("throws an error when description is not a string", async () => {
|
||||||
const api = useApi()
|
|
||||||
const payload = {
|
const payload = {
|
||||||
name: "test",
|
name: "test",
|
||||||
handle: "test",
|
handle: "test",
|
||||||
@@ -466,7 +545,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("successfully creates a product category", async () => {
|
it("successfully creates a product category", async () => {
|
||||||
const api = useApi()
|
|
||||||
const payload = {
|
const payload = {
|
||||||
name: "test",
|
name: "test",
|
||||||
handle: "test",
|
handle: "test",
|
||||||
@@ -492,9 +570,16 @@ describe("/admin/product-categories", () => {
|
|||||||
is_active: false,
|
is_active: false,
|
||||||
created_at: expect.any(String),
|
created_at: expect.any(String),
|
||||||
updated_at: expect.any(String),
|
updated_at: expect.any(String),
|
||||||
|
...breaking(
|
||||||
|
() => ({
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: payload.parent_category_id,
|
id: productCategory.id,
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
|
() => ({
|
||||||
|
parent_category_id: productCategory.id,
|
||||||
|
})
|
||||||
|
),
|
||||||
category_children: [],
|
category_children: [],
|
||||||
rank: 0,
|
rank: 0,
|
||||||
}),
|
}),
|
||||||
@@ -503,7 +588,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("successfully creates a product category with a rank", async () => {
|
it("successfully creates a product category with a rank", async () => {
|
||||||
const api = useApi()
|
|
||||||
const payload = {
|
const payload = {
|
||||||
name: "test",
|
name: "test",
|
||||||
handle: "test",
|
handle: "test",
|
||||||
@@ -527,9 +611,16 @@ describe("/admin/product-categories", () => {
|
|||||||
is_active: false,
|
is_active: false,
|
||||||
created_at: expect.any(String),
|
created_at: expect.any(String),
|
||||||
updated_at: expect.any(String),
|
updated_at: expect.any(String),
|
||||||
|
...breaking(
|
||||||
|
() => ({
|
||||||
parent_category: expect.objectContaining({
|
parent_category: expect.objectContaining({
|
||||||
id: productCategoryParent.id,
|
id: productCategoryParent.id,
|
||||||
}),
|
}),
|
||||||
|
}),
|
||||||
|
() => ({
|
||||||
|
parent_category_id: productCategoryParent.id,
|
||||||
|
})
|
||||||
|
),
|
||||||
category_children: [],
|
category_children: [],
|
||||||
rank: 1,
|
rank: 1,
|
||||||
}),
|
}),
|
||||||
@@ -538,8 +629,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("root parent returns children correctly on creating new category", async () => {
|
it("root parent returns children correctly on creating new category", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/admin/product-categories`,
|
`/admin/product-categories`,
|
||||||
{
|
{
|
||||||
@@ -550,11 +639,14 @@ describe("/admin/product-categories", () => {
|
|||||||
)
|
)
|
||||||
const lastDescendant = response.data.product_category
|
const lastDescendant = response.data.product_category
|
||||||
|
|
||||||
const parentResponse = await api.get(
|
const path = breaking(
|
||||||
`/admin/product-categories/${productCategoryParent.id}`,
|
() => `/admin/product-categories/${productCategoryParent.id}`,
|
||||||
adminHeaders
|
() =>
|
||||||
|
`/admin/product-categories/${productCategoryParent.id}?include_descendants_tree=true`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const parentResponse = await api.get(path, adminHeaders)
|
||||||
|
|
||||||
expect(parentResponse.data.product_category).toEqual(
|
expect(parentResponse.data.product_category).toEqual(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: productCategoryParent.id,
|
id: productCategoryParent.id,
|
||||||
@@ -576,12 +668,13 @@ describe("/admin/product-categories", () => {
|
|||||||
|
|
||||||
describe("DELETE /admin/product-categories/:id", () => {
|
describe("DELETE /admin/product-categories/:id", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await adminSeeder(dbConnection)
|
productCategoryParent = await simpleProductCategoryFactory(
|
||||||
|
dbConnection,
|
||||||
productCategoryParent = await simpleProductCategoryFactory(dbConnection, {
|
{
|
||||||
name: "category parent",
|
name: "category parent",
|
||||||
handle: "category-parent",
|
handle: "category-parent",
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
productCategory = await simpleProductCategoryFactory(dbConnection, {
|
productCategory = await simpleProductCategoryFactory(dbConnection, {
|
||||||
name: "category",
|
name: "category",
|
||||||
@@ -602,14 +695,7 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
const db = useDb()
|
|
||||||
return await db.teardown()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("returns successfully with an invalid ID", async () => {
|
it("returns successfully with an invalid ID", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const response = await api.delete(
|
const response = await api.delete(
|
||||||
`/admin/product-categories/invalid-id`,
|
`/admin/product-categories/invalid-id`,
|
||||||
adminHeaders
|
adminHeaders
|
||||||
@@ -622,8 +708,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("throws a not allowed error for a category with children", async () => {
|
it("throws a not allowed error for a category with children", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const error = await api
|
const error = await api
|
||||||
.delete(
|
.delete(
|
||||||
`/admin/product-categories/${productCategoryParent.id}`,
|
`/admin/product-categories/${productCategoryParent.id}`,
|
||||||
@@ -639,10 +723,11 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("deletes a product category with no children successfully", async () => {
|
it("deletes a product category with no children successfully", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const deleteResponse = await api
|
const deleteResponse = await api
|
||||||
.delete(`/admin/product-categories/${productCategory.id}`, adminHeaders)
|
.delete(
|
||||||
|
`/admin/product-categories/${productCategory.id}`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
.catch((e) => e)
|
.catch((e) => e)
|
||||||
|
|
||||||
expect(deleteResponse.status).toEqual(200)
|
expect(deleteResponse.status).toEqual(200)
|
||||||
@@ -658,10 +743,11 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("deleting a product category reorders siblings accurately", async () => {
|
it("deleting a product category reorders siblings accurately", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const deleteResponse = await api
|
const deleteResponse = await api
|
||||||
.delete(`/admin/product-categories/${productCategory.id}`, adminHeaders)
|
.delete(
|
||||||
|
`/admin/product-categories/${productCategory.id}`,
|
||||||
|
adminHeaders
|
||||||
|
)
|
||||||
.catch((e) => e)
|
.catch((e) => e)
|
||||||
|
|
||||||
expect(deleteResponse.status).toEqual(200)
|
expect(deleteResponse.status).toEqual(200)
|
||||||
@@ -688,11 +774,12 @@ describe("/admin/product-categories", () => {
|
|||||||
|
|
||||||
describe("POST /admin/product-categories/:id", () => {
|
describe("POST /admin/product-categories/:id", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await adminSeeder(dbConnection)
|
productCategoryParent = await simpleProductCategoryFactory(
|
||||||
|
dbConnection,
|
||||||
productCategoryParent = await simpleProductCategoryFactory(dbConnection, {
|
{
|
||||||
name: "category parent",
|
name: "category parent",
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
productCategory = await simpleProductCategoryFactory(dbConnection, {
|
productCategory = await simpleProductCategoryFactory(dbConnection, {
|
||||||
name: "category-0",
|
name: "category-0",
|
||||||
@@ -712,45 +799,53 @@ describe("/admin/product-categories", () => {
|
|||||||
rank: 2,
|
rank: 2,
|
||||||
})
|
})
|
||||||
|
|
||||||
productCategoryChild = await simpleProductCategoryFactory(dbConnection, {
|
productCategoryChild = await simpleProductCategoryFactory(
|
||||||
|
dbConnection,
|
||||||
|
{
|
||||||
name: "category child",
|
name: "category child",
|
||||||
parent_category: productCategory,
|
parent_category: productCategory,
|
||||||
rank: 0,
|
rank: 0,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
productCategoryChild0 = await simpleProductCategoryFactory(dbConnection, {
|
productCategoryChild0 = await simpleProductCategoryFactory(
|
||||||
|
dbConnection,
|
||||||
|
{
|
||||||
name: "category child 0",
|
name: "category child 0",
|
||||||
parent_category: productCategoryChild,
|
parent_category: productCategoryChild,
|
||||||
rank: 0,
|
rank: 0,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
productCategoryChild1 = await simpleProductCategoryFactory(dbConnection, {
|
productCategoryChild1 = await simpleProductCategoryFactory(
|
||||||
|
dbConnection,
|
||||||
|
{
|
||||||
name: "category child 1",
|
name: "category child 1",
|
||||||
parent_category: productCategoryChild,
|
parent_category: productCategoryChild,
|
||||||
rank: 1,
|
rank: 1,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
productCategoryChild2 = await simpleProductCategoryFactory(dbConnection, {
|
productCategoryChild2 = await simpleProductCategoryFactory(
|
||||||
|
dbConnection,
|
||||||
|
{
|
||||||
name: "category child 2",
|
name: "category child 2",
|
||||||
parent_category: productCategoryChild,
|
parent_category: productCategoryChild,
|
||||||
rank: 2,
|
rank: 2,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
productCategoryChild3 = await simpleProductCategoryFactory(dbConnection, {
|
productCategoryChild3 = await simpleProductCategoryFactory(
|
||||||
|
dbConnection,
|
||||||
|
{
|
||||||
name: "category child 3",
|
name: "category child 3",
|
||||||
parent_category: productCategoryChild,
|
parent_category: productCategoryChild,
|
||||||
rank: 3,
|
rank: 3,
|
||||||
})
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
const db = useDb()
|
|
||||||
return await db.teardown()
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("throws an error if invalid ID is sent", async () => {
|
it("throws an error if invalid ID is sent", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const error = await api
|
const error = await api
|
||||||
.post(
|
.post(
|
||||||
`/admin/product-categories/not-found-id`,
|
`/admin/product-categories/not-found-id`,
|
||||||
@@ -769,8 +864,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("throws an error if rank is negative", async () => {
|
it("throws an error if rank is negative", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const error = await api
|
const error = await api
|
||||||
.post(
|
.post(
|
||||||
`/admin/product-categories/not-found-id`,
|
`/admin/product-categories/not-found-id`,
|
||||||
@@ -789,8 +882,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("throws an error if invalid attribute is sent", async () => {
|
it("throws an error if invalid attribute is sent", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const error = await api
|
const error = await api
|
||||||
.post(
|
.post(
|
||||||
`/admin/product-categories/${productCategory.id}`,
|
`/admin/product-categories/${productCategory.id}`,
|
||||||
@@ -809,8 +900,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("successfully updates a product category", async () => {
|
it("successfully updates a product category", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/admin/product-categories/${productCategoryChild2.id}`,
|
`/admin/product-categories/${productCategoryChild2.id}`,
|
||||||
{
|
{
|
||||||
@@ -844,8 +933,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("updating properties other than rank should not change its rank", async () => {
|
it("updating properties other than rank should not change its rank", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
expect(productCategory.rank).toEqual(0)
|
expect(productCategory.rank).toEqual(0)
|
||||||
|
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
@@ -856,12 +943,12 @@ describe("/admin/product-categories", () => {
|
|||||||
adminHeaders
|
adminHeaders
|
||||||
)
|
)
|
||||||
expect(response.status).toEqual(200)
|
expect(response.status).toEqual(200)
|
||||||
expect(response.data.product_category.rank).toEqual(productCategory.rank)
|
expect(response.data.product_category.rank).toEqual(
|
||||||
|
productCategory.rank
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("root parent returns children correctly on updating new category", async () => {
|
it("root parent returns children correctly on updating new category", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/admin/product-categories/${productCategoryChild.id}`,
|
`/admin/product-categories/${productCategoryChild.id}`,
|
||||||
{
|
{
|
||||||
@@ -906,8 +993,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("when parent is updated, rank is updated to elements count + 1", async () => {
|
it("when parent is updated, rank is updated to elements count + 1", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/admin/product-categories/${productCategoryChild1.id}`,
|
`/admin/product-categories/${productCategoryChild1.id}`,
|
||||||
{
|
{
|
||||||
@@ -930,8 +1015,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("when parent is updated with rank, rank accurately updated", async () => {
|
it("when parent is updated with rank, rank accurately updated", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/admin/product-categories/${productCategoryChild1.id}`,
|
`/admin/product-categories/${productCategoryChild1.id}`,
|
||||||
{
|
{
|
||||||
@@ -955,8 +1038,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("when only rank is updated, rank should be updated", async () => {
|
it("when only rank is updated, rank should be updated", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/admin/product-categories/${productCategoryChild1.id}`,
|
`/admin/product-categories/${productCategoryChild1.id}`,
|
||||||
{
|
{
|
||||||
@@ -976,8 +1057,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("when rank is greater than list count, rank is updated to updated to elements count + 1", async () => {
|
it("when rank is greater than list count, rank is updated to updated to elements count + 1", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/admin/product-categories/${productCategoryChild1.id}`,
|
`/admin/product-categories/${productCategoryChild1.id}`,
|
||||||
{
|
{
|
||||||
@@ -996,8 +1075,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("when rank is updated, it accurately updates sibling ranks", async () => {
|
it("when rank is updated, it accurately updates sibling ranks", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const response = await api.post(
|
const response = await api.post(
|
||||||
`/admin/product-categories/${productCategoryChild2.id}`,
|
`/admin/product-categories/${productCategoryChild2.id}`,
|
||||||
{
|
{
|
||||||
@@ -1039,21 +1116,13 @@ describe("/admin/product-categories", () => {
|
|||||||
|
|
||||||
describe("POST /admin/product-categories/:id/products/batch", () => {
|
describe("POST /admin/product-categories/:id/products/batch", () => {
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await adminSeeder(dbConnection)
|
|
||||||
|
|
||||||
productCategory = await simpleProductCategoryFactory(dbConnection, {
|
productCategory = await simpleProductCategoryFactory(dbConnection, {
|
||||||
id: "test-category",
|
id: "test-category",
|
||||||
name: "test category",
|
name: "test category",
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
const db = useDb()
|
|
||||||
await db.teardown()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should add products to a product category", async () => {
|
it("should add products to a product category", async () => {
|
||||||
const api = useApi()
|
|
||||||
const testProduct1 = await simpleProductFactory(dbConnection, {
|
const testProduct1 = await simpleProductFactory(dbConnection, {
|
||||||
id: "test-product-1",
|
id: "test-product-1",
|
||||||
title: "test product 1",
|
title: "test product 1",
|
||||||
@@ -1102,8 +1171,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("throws error when product ID is invalid", async () => {
|
it("throws error when product ID is invalid", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
product_ids: [{ id: "product-id-invalid" }],
|
product_ids: [{ id: "product-id-invalid" }],
|
||||||
}
|
}
|
||||||
@@ -1125,7 +1192,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("throws error when category ID is invalid", async () => {
|
it("throws error when category ID is invalid", async () => {
|
||||||
const api = useApi()
|
|
||||||
const payload = { product_ids: [] }
|
const payload = { product_ids: [] }
|
||||||
|
|
||||||
const error = await api
|
const error = await api
|
||||||
@@ -1144,7 +1210,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("throws error trying to expand not allowed relations", async () => {
|
it("throws error trying to expand not allowed relations", async () => {
|
||||||
const api = useApi()
|
|
||||||
const payload = { product_ids: [] }
|
const payload = { product_ids: [] }
|
||||||
|
|
||||||
const error = await api
|
const error = await api
|
||||||
@@ -1167,8 +1232,6 @@ describe("/admin/product-categories", () => {
|
|||||||
let testProduct1, testProduct2
|
let testProduct1, testProduct2
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await adminSeeder(dbConnection)
|
|
||||||
|
|
||||||
testProduct1 = await simpleProductFactory(dbConnection, {
|
testProduct1 = await simpleProductFactory(dbConnection, {
|
||||||
id: "test-product-1",
|
id: "test-product-1",
|
||||||
title: "test product 1",
|
title: "test product 1",
|
||||||
@@ -1186,14 +1249,7 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(async () => {
|
|
||||||
const db = useDb()
|
|
||||||
await db.teardown()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("should remove products from a product category", async () => {
|
it("should remove products from a product category", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
product_ids: [{ id: testProduct2.id }],
|
product_ids: [{ id: testProduct2.id }],
|
||||||
}
|
}
|
||||||
@@ -1230,8 +1286,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("throws error when product ID is invalid", async () => {
|
it("throws error when product ID is invalid", async () => {
|
||||||
const api = useApi()
|
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
product_ids: [{ id: "product-id-invalid" }],
|
product_ids: [{ id: "product-id-invalid" }],
|
||||||
}
|
}
|
||||||
@@ -1255,7 +1309,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("throws error when category ID is invalid", async () => {
|
it("throws error when category ID is invalid", async () => {
|
||||||
const api = useApi()
|
|
||||||
const payload = { product_ids: [] }
|
const payload = { product_ids: [] }
|
||||||
|
|
||||||
const error = await api
|
const error = await api
|
||||||
@@ -1276,7 +1329,6 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("throws error trying to expand not allowed relations", async () => {
|
it("throws error trying to expand not allowed relations", async () => {
|
||||||
const api = useApi()
|
|
||||||
const payload = { product_ids: [] }
|
const payload = { product_ids: [] }
|
||||||
|
|
||||||
const error = await api
|
const error = await api
|
||||||
@@ -1296,4 +1348,5 @@ describe("/admin/product-categories", () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export * from "./customer-group"
|
|||||||
export * from "./defaults"
|
export * from "./defaults"
|
||||||
export * from "./definition"
|
export * from "./definition"
|
||||||
export * from "./definitions"
|
export * from "./definitions"
|
||||||
|
export * from "./file"
|
||||||
export * from "./fulfillment"
|
export * from "./fulfillment"
|
||||||
export * as Handlers from "./handlers"
|
export * as Handlers from "./handlers"
|
||||||
export * from "./inventory"
|
export * from "./inventory"
|
||||||
@@ -15,6 +16,7 @@ export * from "./payment"
|
|||||||
export * from "./price-list"
|
export * from "./price-list"
|
||||||
export * from "./pricing"
|
export * from "./pricing"
|
||||||
export * from "./product"
|
export * from "./product"
|
||||||
|
export * from "./product-category"
|
||||||
export * from "./promotion"
|
export * from "./promotion"
|
||||||
export * from "./reservation"
|
export * from "./reservation"
|
||||||
export * from "./region"
|
export * from "./region"
|
||||||
@@ -24,4 +26,3 @@ export * from "./stock-location"
|
|||||||
export * from "./store"
|
export * from "./store"
|
||||||
export * from "./tax"
|
export * from "./tax"
|
||||||
export * from "./user"
|
export * from "./user"
|
||||||
export * from "./file"
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export * from "./steps"
|
||||||
|
export * from "./workflows"
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import { ModuleRegistrationName } from "@medusajs/modules-sdk"
|
||||||
|
import {
|
||||||
|
CreateProductCategoryDTO,
|
||||||
|
IProductModuleService,
|
||||||
|
} from "@medusajs/types"
|
||||||
|
import { StepResponse, createStep } from "@medusajs/workflows-sdk"
|
||||||
|
|
||||||
|
type CreateProductCategoryStepInput = {
|
||||||
|
product_category: CreateProductCategoryDTO
|
||||||
|
}
|
||||||
|
|
||||||
|
export const createProductCategoryStepId = "create-product-category"
|
||||||
|
export const createProductCategoryStep = createStep(
|
||||||
|
createProductCategoryStepId,
|
||||||
|
async (data: CreateProductCategoryStepInput, { container }) => {
|
||||||
|
const service = container.resolve<IProductModuleService>(
|
||||||
|
ModuleRegistrationName.PRODUCT
|
||||||
|
)
|
||||||
|
|
||||||
|
const created = await service.createCategory(data.product_category)
|
||||||
|
|
||||||
|
return new StepResponse(created, created.id)
|
||||||
|
},
|
||||||
|
async (createdId, { container }) => {
|
||||||
|
if (!createdId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const service = container.resolve<IProductModuleService>(
|
||||||
|
ModuleRegistrationName.PRODUCT
|
||||||
|
)
|
||||||
|
|
||||||
|
await service.deleteCategory(createdId)
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./create-product-category"
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { ProductCategoryWorkflow } from "@medusajs/types"
|
||||||
|
import { WorkflowData, createWorkflow } from "@medusajs/workflows-sdk"
|
||||||
|
import { createProductCategoryStep } from "../steps"
|
||||||
|
|
||||||
|
type WorkflowInputData =
|
||||||
|
ProductCategoryWorkflow.CreateProductCategoryWorkflowInput
|
||||||
|
|
||||||
|
export const createProductCategoryWorkflowId = "create-product-category"
|
||||||
|
export const createProductCategoryWorkflow = createWorkflow(
|
||||||
|
createProductCategoryWorkflowId,
|
||||||
|
(input: WorkflowData<WorkflowInputData>) => {
|
||||||
|
const category = createProductCategoryStep(input)
|
||||||
|
|
||||||
|
return category
|
||||||
|
}
|
||||||
|
)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./create-product-category"
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
import { MiddlewareRoute } from "../../../loaders/helpers/routing/types"
|
||||||
import { authenticate } from "../../../utils/authenticate-middleware"
|
import { authenticate } from "../../../utils/authenticate-middleware"
|
||||||
|
import { validateAndTransformBody } from "../../utils/validate-body"
|
||||||
import { validateAndTransformQuery } from "../../utils/validate-query"
|
import { validateAndTransformQuery } from "../../utils/validate-query"
|
||||||
import * as QueryConfig from "./query-config"
|
import * as QueryConfig from "./query-config"
|
||||||
import {
|
import {
|
||||||
|
AdminCreateProductCategory,
|
||||||
AdminProductCategoriesParams,
|
AdminProductCategoriesParams,
|
||||||
AdminProductCategoryParams,
|
AdminProductCategoryParams,
|
||||||
} from "./validators"
|
} from "./validators"
|
||||||
@@ -33,4 +35,15 @@ export const adminProductCategoryRoutesMiddlewares: MiddlewareRoute[] = [
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
method: ["POST"],
|
||||||
|
matcher: "/admin/product-categories",
|
||||||
|
middlewares: [
|
||||||
|
validateAndTransformBody(AdminCreateProductCategory),
|
||||||
|
validateAndTransformQuery(
|
||||||
|
AdminProductCategoryParams,
|
||||||
|
QueryConfig.retrieveProductCategoryConfig
|
||||||
|
),
|
||||||
|
],
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -11,10 +11,24 @@ export const defaults = [
|
|||||||
"updated_at",
|
"updated_at",
|
||||||
"metadata",
|
"metadata",
|
||||||
|
|
||||||
"parent_category.id",
|
"*category_children",
|
||||||
"parent_category.name",
|
]
|
||||||
"category_children.id",
|
|
||||||
"category_children.name",
|
export const allowed = [
|
||||||
|
"id",
|
||||||
|
"name",
|
||||||
|
"description",
|
||||||
|
"handle",
|
||||||
|
"is_active",
|
||||||
|
"is_internal",
|
||||||
|
"rank",
|
||||||
|
"parent_category_id",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"metadata",
|
||||||
|
|
||||||
|
"*parent_category",
|
||||||
|
"*category_children",
|
||||||
]
|
]
|
||||||
|
|
||||||
export const retrieveProductCategoryConfig = {
|
export const retrieveProductCategoryConfig = {
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { AdminProductCategoryListResponse } from "@medusajs/types"
|
import { createProductCategoryWorkflow } from "@medusajs/core-flows"
|
||||||
|
import {
|
||||||
|
AdminProductCategoryListResponse,
|
||||||
|
AdminProductCategoryResponse,
|
||||||
|
} from "@medusajs/types"
|
||||||
import {
|
import {
|
||||||
ContainerRegistrationKeys,
|
ContainerRegistrationKeys,
|
||||||
remoteQueryObjectFromString,
|
remoteQueryObjectFromString,
|
||||||
@@ -7,7 +11,10 @@ import {
|
|||||||
AuthenticatedMedusaRequest,
|
AuthenticatedMedusaRequest,
|
||||||
MedusaResponse,
|
MedusaResponse,
|
||||||
} from "../../../types/routing"
|
} from "../../../types/routing"
|
||||||
import { AdminProductCategoriesParamsType } from "./validators"
|
import {
|
||||||
|
AdminCreateProductCategoryType,
|
||||||
|
AdminProductCategoriesParamsType,
|
||||||
|
} from "./validators"
|
||||||
|
|
||||||
export const GET = async (
|
export const GET = async (
|
||||||
req: AuthenticatedMedusaRequest<AdminProductCategoriesParamsType>,
|
req: AuthenticatedMedusaRequest<AdminProductCategoriesParamsType>,
|
||||||
@@ -33,3 +40,33 @@ export const GET = async (
|
|||||||
limit: metadata.take,
|
limit: metadata.take,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const POST = async (
|
||||||
|
req: AuthenticatedMedusaRequest<AdminCreateProductCategoryType>,
|
||||||
|
res: MedusaResponse<AdminProductCategoryResponse>
|
||||||
|
) => {
|
||||||
|
const { result, errors } = await createProductCategoryWorkflow(req.scope).run(
|
||||||
|
{
|
||||||
|
input: { product_category: req.validatedBody },
|
||||||
|
throwOnError: false,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (Array.isArray(errors) && errors[0]) {
|
||||||
|
throw errors[0].error
|
||||||
|
}
|
||||||
|
|
||||||
|
const remoteQuery = req.scope.resolve(ContainerRegistrationKeys.REMOTE_QUERY)
|
||||||
|
|
||||||
|
const queryObject = remoteQueryObjectFromString({
|
||||||
|
entryPoint: "product_category",
|
||||||
|
variables: {
|
||||||
|
filters: { id: result.id },
|
||||||
|
},
|
||||||
|
fields: req.remoteQueryConfig.fields,
|
||||||
|
})
|
||||||
|
|
||||||
|
const [product_category] = await remoteQuery(queryObject)
|
||||||
|
|
||||||
|
res.status(200).json({ product_category })
|
||||||
|
}
|
||||||
|
|||||||
@@ -43,6 +43,14 @@ export const AdminProductCategoriesParams = createFindParams({
|
|||||||
(val: any) => optionalBooleanMapper.get(val?.toLowerCase()),
|
(val: any) => optionalBooleanMapper.get(val?.toLowerCase()),
|
||||||
z.boolean().optional()
|
z.boolean().optional()
|
||||||
),
|
),
|
||||||
|
is_internal: z.preprocess(
|
||||||
|
(val: any) => optionalBooleanMapper.get(val?.toLowerCase()),
|
||||||
|
z.boolean().optional()
|
||||||
|
),
|
||||||
|
is_active: z.preprocess(
|
||||||
|
(val: any) => optionalBooleanMapper.get(val?.toLowerCase()),
|
||||||
|
z.boolean().optional()
|
||||||
|
),
|
||||||
created_at: createOperatorMap().optional(),
|
created_at: createOperatorMap().optional(),
|
||||||
updated_at: createOperatorMap().optional(),
|
updated_at: createOperatorMap().optional(),
|
||||||
deleted_at: createOperatorMap().optional(),
|
deleted_at: createOperatorMap().optional(),
|
||||||
@@ -50,3 +58,19 @@ export const AdminProductCategoriesParams = createFindParams({
|
|||||||
$or: z.lazy(() => AdminProductCategoriesParams.array()).optional(),
|
$or: z.lazy(() => AdminProductCategoriesParams.array()).optional(),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
export const AdminCreateProductCategory = z
|
||||||
|
.object({
|
||||||
|
name: z.string(),
|
||||||
|
description: z.string().optional(),
|
||||||
|
handle: z.string().optional(),
|
||||||
|
is_internal: z.boolean().optional(),
|
||||||
|
is_active: z.boolean().optional(),
|
||||||
|
parent_category_id: z.string().optional(),
|
||||||
|
metadata: z.record(z.unknown()).optional(),
|
||||||
|
})
|
||||||
|
.strict()
|
||||||
|
|
||||||
|
export type AdminCreateProductCategoryType = z.infer<
|
||||||
|
typeof AdminCreateProductCategory
|
||||||
|
>
|
||||||
|
|||||||
+1
-2
@@ -1,10 +1,9 @@
|
|||||||
import { Modules } from "@medusajs/modules-sdk"
|
import { Modules } from "@medusajs/modules-sdk"
|
||||||
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
||||||
import { Product, ProductCategory } from "@models"
|
import { Product, ProductCategory } from "@models"
|
||||||
import { MockEventBusService } from "medusa-test-utils"
|
import { MockEventBusService, SuiteOptions, moduleIntegrationTestRunner } from "medusa-test-utils"
|
||||||
import { createProductCategories } from "../../../__fixtures__/product-category"
|
import { createProductCategories } from "../../../__fixtures__/product-category"
|
||||||
import { productCategoriesRankData } from "../../../__fixtures__/product-category/data"
|
import { productCategoriesRankData } from "../../../__fixtures__/product-category/data"
|
||||||
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
|
|
||||||
|
|
||||||
jest.setTimeout(30000)
|
jest.setTimeout(30000)
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,7 @@ import {
|
|||||||
ProductCategoryTransformOptions,
|
ProductCategoryTransformOptions,
|
||||||
ProductTypes,
|
ProductTypes,
|
||||||
} from "@medusajs/types"
|
} from "@medusajs/types"
|
||||||
import {
|
import { DALUtils, MedusaError, isDefined } from "@medusajs/utils"
|
||||||
DALUtils,
|
|
||||||
MedusaError,
|
|
||||||
isDefined
|
|
||||||
} from "@medusajs/utils"
|
|
||||||
import {
|
import {
|
||||||
LoadStrategy,
|
LoadStrategy,
|
||||||
FilterQuery as MikroFilterQuery,
|
FilterQuery as MikroFilterQuery,
|
||||||
|
|||||||
@@ -915,11 +915,21 @@ export default class ProductModuleService<
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@InjectTransactionManager("baseRepository_")
|
@InjectManager("baseRepository_")
|
||||||
async createCategory(
|
async createCategory(
|
||||||
data: ProductTypes.CreateProductCategoryDTO,
|
data: ProductTypes.CreateProductCategoryDTO,
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<ProductTypes.ProductCategoryDTO> {
|
): Promise<ProductTypes.ProductCategoryDTO> {
|
||||||
|
const result = await this.createCategory_(data, sharedContext)
|
||||||
|
|
||||||
|
return await this.baseRepository_.serialize(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
@InjectTransactionManager("baseRepository_")
|
||||||
|
async createCategory_(
|
||||||
|
data: ProductTypes.CreateProductCategoryDTO,
|
||||||
|
@MedusaContext() sharedContext: Context = {}
|
||||||
|
): Promise<ProductCategory> {
|
||||||
const productCategory = await this.productCategoryService_.create(
|
const productCategory = await this.productCategoryService_.create(
|
||||||
data,
|
data,
|
||||||
sharedContext
|
sharedContext
|
||||||
@@ -930,9 +940,7 @@ export default class ProductModuleService<
|
|||||||
{ id: productCategory.id }
|
{ id: productCategory.id }
|
||||||
)
|
)
|
||||||
|
|
||||||
return await this.baseRepository_.serialize(productCategory, {
|
return productCategory
|
||||||
populate: true,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@InjectTransactionManager("baseRepository_")
|
@InjectTransactionManager("baseRepository_")
|
||||||
@@ -1115,8 +1123,10 @@ export default class ProductModuleService<
|
|||||||
data: ProductTypes.CreateProductDTO[],
|
data: ProductTypes.CreateProductDTO[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<TProduct[]> {
|
): Promise<TProduct[]> {
|
||||||
const normalizedInput = await Promise.all(
|
const normalizedInput = await promiseAll(
|
||||||
data.map((d) => this.normalizeCreateProductInput(d, sharedContext))
|
data.map(
|
||||||
|
async (d) => await this.normalizeCreateProductInput(d, sharedContext)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const productData = await this.productService_.upsertWithReplace(
|
const productData = await this.productService_.upsertWithReplace(
|
||||||
@@ -1171,8 +1181,10 @@ export default class ProductModuleService<
|
|||||||
data: UpdateProductInput[],
|
data: UpdateProductInput[],
|
||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<TProduct[]> {
|
): Promise<TProduct[]> {
|
||||||
const normalizedInput = await Promise.all(
|
const normalizedInput = await promiseAll(
|
||||||
data.map((d) => this.normalizeUpdateProductInput(d, sharedContext))
|
data.map(
|
||||||
|
async (d) => await this.normalizeUpdateProductInput(d, sharedContext)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
const productData = await this.productService_.upsertWithReplace(
|
const productData = await this.productService_.upsertWithReplace(
|
||||||
@@ -1258,7 +1270,8 @@ export default class ProductModuleService<
|
|||||||
@MedusaContext() sharedContext: Context = {}
|
@MedusaContext() sharedContext: Context = {}
|
||||||
): Promise<ProductTypes.CreateProductDTO> {
|
): Promise<ProductTypes.CreateProductDTO> {
|
||||||
const productData = (await this.normalizeUpdateProductInput(
|
const productData = (await this.normalizeUpdateProductInput(
|
||||||
product as UpdateProductInput
|
product as UpdateProductInput,
|
||||||
|
sharedContext
|
||||||
)) as ProductTypes.CreateProductDTO
|
)) as ProductTypes.CreateProductDTO
|
||||||
|
|
||||||
if (!productData.handle && productData.title) {
|
if (!productData.handle && productData.title) {
|
||||||
|
|||||||
@@ -358,11 +358,8 @@ export interface CreateProductCategoryDTO {
|
|||||||
rank?: number
|
rank?: number
|
||||||
/**
|
/**
|
||||||
* The ID of the parent product category, if it has any.
|
* The ID of the parent product category, if it has any.
|
||||||
*
|
|
||||||
* @privateRemarks
|
|
||||||
* Shouldn't this be optional?
|
|
||||||
*/
|
*/
|
||||||
parent_category_id: string | null
|
parent_category_id?: string | null
|
||||||
/**
|
/**
|
||||||
* Holds custom data in key-value pairs.
|
* Holds custom data in key-value pairs.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
export * as CartWorkflow from "./cart"
|
export * as CartWorkflow from "./cart"
|
||||||
export * as CommonWorkflow from "./common"
|
export * as CommonWorkflow from "./common"
|
||||||
export * as ProductWorkflow from "./product"
|
|
||||||
export * as InventoryWorkflow from "./inventory"
|
|
||||||
export * as PriceListWorkflow from "./price-list"
|
|
||||||
export * as UserWorkflow from "./user"
|
|
||||||
export * as RegionWorkflow from "./region"
|
|
||||||
export * as InviteWorkflow from "./invite"
|
|
||||||
export * as FulfillmentWorkflow from "./fulfillment"
|
export * as FulfillmentWorkflow from "./fulfillment"
|
||||||
|
export * as InventoryWorkflow from "./inventory"
|
||||||
|
export * as InviteWorkflow from "./invite"
|
||||||
|
export * as PriceListWorkflow from "./price-list"
|
||||||
|
export * as ProductWorkflow from "./product"
|
||||||
|
export * as ProductCategoryWorkflow from "./product-category"
|
||||||
|
export * as RegionWorkflow from "./region"
|
||||||
export * as ReservationWorkflow from "./reservation"
|
export * as ReservationWorkflow from "./reservation"
|
||||||
|
export * as UserWorkflow from "./user"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { CreateProductCategoryDTO } from "../../product"
|
||||||
|
|
||||||
|
export interface CreateProductCategoryWorkflowInput {
|
||||||
|
product_category: CreateProductCategoryDTO
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user