feat(product): Create (+ workflow), delete, restore (#4459)
* Feat: create product with product module * feat: create product wip * feat: create product wip * feat: update product relation and generate image migration * lint * conitnue implementation * continue implementation and add integration tests for produceService.create * Add integration tests for product creation at the module level for the complete flow * only use persist since write operations are always wrapped in a transaction which will be committed and flushed * simplify the transaction wrapper to make future changes easier * feat: move some utils to the utils package to simplify its usage * tests: fix unit tests * feat: create variants along side the product * Add more integration tests an update migrations * chore: Update actions workflow to include packages integration tests * small types and utils cleanup * chore: Add support for database debug option * chore: Add missing types in package.json from types and util, validate that all the models are sync with medusa * expose retrieve method * fix types issues * fix unit tests and move integration tests workflow with the plugins integration tests * chore: remove migration function export from the definition to prevent them to be ran by the medusa cli just in case * fix package.json script * chore: workflows * feat: start creating the create product workflow * feat: add empty step for prices and sales channel * tests: update scripts and action envs * fix imports * feat: Add proper soft deleted support + add product deletion service public api * chore: update migrations * chore: update migrations * chore: update todo * feat: Add product deletion to the create-product workflow as compensation * chore: cleanup product utils * feat: Add support for cascade soft-remove * feat: refactor repository to take into account withDeleted * fix integration tests * Add support for force delete -> delete, cleanup repositories and improvements * Add support for restoring a product and add integration tests * cleaup + tests * types * fix integration tests * remove unnecessary comments * move specific mikro orm usage to the DAL * Cleanup workflow functions * Make deleted_at optional at the property level and add url index for the images * address feedback + cleanup * fix export * merge migrations into one * feat(product, types): added missing product variant methods (#4475) * chore: added missing product variant methods * chore: address PR feedback * chore: catch undefined case for retrieve + specs for variant service * chore: align TEntity + add changeset * chore: revert changeset, TEntity to ProductVariant * chore: write tests for pagination, unskip the test * Create chilled-mice-deliver.md * update integration fixtuers * update pipeline node version * rename github action * fix pipeline * feat(medusa, types): added missing category tests and service methods (#4499) * chore: added missing category tests and service methods * chore: added type changes to module service * chore: address pr feedback * update repositories manager usage and serialisation from the write public API * move serializisation to the DAL * rename template args * chore: added collection methods for module and collection service (#4505) * chore: added collection methods for module and collection service * Create fresh-islands-teach.md * chore: move retrieve entity to utils package * chore: make products optional in DTO type --------- Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com> * feat(product): Apply transaction decorators to the services (#4512) --------- Co-authored-by: Riqwan Thamir <rmthamir@gmail.com> Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com> Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
This commit is contained in:
98 files changed
+5444
-688
No files matched your search
@@ -1,11 +1,13 @@
|
||||
import { MedusaModule } from "@medusajs/modules-sdk"
|
||||
import { Product } from "@models"
|
||||
import { initialize } from "../../src"
|
||||
import * as CustomRepositories from "../__fixtures__/module"
|
||||
import { ProductRepository } from "../__fixtures__/module"
|
||||
import { createProductAndTags } from "../__fixtures__/product"
|
||||
import { productsData } from "../__fixtures__/product/data"
|
||||
import { DB_URL, TestDatabase } from "../utils"
|
||||
import { buildProductAndRelationsData } from "../__fixtures__/product/data/create-product"
|
||||
import { kebabCase } from "@medusajs/utils"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
|
||||
const beforeEach_ = async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
@@ -18,12 +20,11 @@ const afterEach_ = async () => {
|
||||
|
||||
describe("Product module", function () {
|
||||
describe("Using built-in data access layer", function () {
|
||||
let module
|
||||
let products: Product[]
|
||||
let module: IProductModuleService
|
||||
|
||||
beforeEach(async () => {
|
||||
const testManager = await beforeEach_()
|
||||
products = await createProductAndTags(testManager, productsData)
|
||||
await createProductAndTags(testManager, productsData)
|
||||
|
||||
module = await initialize({
|
||||
database: {
|
||||
@@ -46,13 +47,12 @@ describe("Product module", function () {
|
||||
})
|
||||
|
||||
describe("Using custom data access layer", function () {
|
||||
let module
|
||||
let products: Product[]
|
||||
let module: IProductModuleService
|
||||
|
||||
beforeEach(async () => {
|
||||
const testManager = await beforeEach_()
|
||||
|
||||
products = await createProductAndTags(testManager, productsData)
|
||||
await createProductAndTags(testManager, productsData)
|
||||
|
||||
module = await initialize({
|
||||
database: {
|
||||
@@ -78,12 +78,11 @@ describe("Product module", function () {
|
||||
})
|
||||
|
||||
describe("Using custom data access layer and connection", function () {
|
||||
let module
|
||||
let products: Product[]
|
||||
let module: IProductModuleService
|
||||
|
||||
beforeEach(async () => {
|
||||
const testManager = await beforeEach_()
|
||||
products = await createProductAndTags(testManager, productsData)
|
||||
await createProductAndTags(testManager, productsData)
|
||||
|
||||
MedusaModule.clearInstances()
|
||||
|
||||
@@ -106,4 +105,244 @@ describe("Product module", function () {
|
||||
expect(products).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("create", function () {
|
||||
let module: IProductModuleService
|
||||
let images = ["image-1"]
|
||||
|
||||
beforeEach(async () => {
|
||||
await beforeEach_()
|
||||
|
||||
MedusaModule.clearInstances()
|
||||
|
||||
module = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(afterEach_)
|
||||
|
||||
it("should create a product", async () => {
|
||||
const data = buildProductAndRelationsData({
|
||||
images,
|
||||
thumbnail: images[0],
|
||||
})
|
||||
|
||||
const products = await module.create([data])
|
||||
|
||||
expect(products).toHaveLength(1)
|
||||
|
||||
expect(products[0].images).toHaveLength(1)
|
||||
expect(products[0].options).toHaveLength(1)
|
||||
expect(products[0].tags).toHaveLength(1)
|
||||
expect(products[0].categories).toHaveLength(0)
|
||||
expect(products[0].variants).toHaveLength(1)
|
||||
|
||||
expect(products[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
title: data.title,
|
||||
handle: kebabCase(data.title),
|
||||
description: data.description,
|
||||
subtitle: data.subtitle,
|
||||
is_giftcard: data.is_giftcard,
|
||||
discountable: data.discountable,
|
||||
thumbnail: images[0],
|
||||
status: data.status,
|
||||
images: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
url: images[0],
|
||||
}),
|
||||
]),
|
||||
options: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
title: data.options[0].title,
|
||||
values: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
value: data.variants[0].options?.[0].value,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
tags: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
value: data.tags[0].value,
|
||||
}),
|
||||
]),
|
||||
type: expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
value: data.type.value,
|
||||
}),
|
||||
variants: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
title: data.variants[0].title,
|
||||
sku: data.variants[0].sku,
|
||||
allow_backorder: false,
|
||||
manage_inventory: true,
|
||||
inventory_quantity: 100,
|
||||
variant_rank: 0,
|
||||
options: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
value: data.variants[0].options?.[0].value,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("softDelete", function () {
|
||||
let module: IProductModuleService
|
||||
let images = ["image-1"]
|
||||
|
||||
beforeEach(async () => {
|
||||
await beforeEach_()
|
||||
|
||||
MedusaModule.clearInstances()
|
||||
|
||||
module = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(afterEach_)
|
||||
|
||||
it("should soft delete a product and its cascaded relations", async () => {
|
||||
const data = buildProductAndRelationsData({
|
||||
images,
|
||||
thumbnail: images[0],
|
||||
})
|
||||
|
||||
const products = await module.create([data])
|
||||
|
||||
await module.softDelete([products[0].id])
|
||||
|
||||
const deletedProducts = await module.list(
|
||||
{ id: products[0].id },
|
||||
{
|
||||
relations: [
|
||||
"variants",
|
||||
"variants.options",
|
||||
"options",
|
||||
"options.values",
|
||||
],
|
||||
withDeleted: true,
|
||||
}
|
||||
)
|
||||
|
||||
expect(deletedProducts).toHaveLength(1)
|
||||
expect(deletedProducts[0].deleted_at).not.toBeNull()
|
||||
|
||||
for (const option of deletedProducts[0].options) {
|
||||
expect(option.deleted_at).not.toBeNull()
|
||||
}
|
||||
|
||||
const productOptionsValues = deletedProducts[0].options
|
||||
.map((o) => o.values)
|
||||
.flat()
|
||||
|
||||
for (const optionValue of productOptionsValues) {
|
||||
expect(optionValue.deleted_at).not.toBeNull()
|
||||
}
|
||||
|
||||
for (const variant of deletedProducts[0].variants) {
|
||||
expect(variant.deleted_at).not.toBeNull()
|
||||
}
|
||||
|
||||
const variantsOptions = deletedProducts[0].options
|
||||
.map((o) => o.values)
|
||||
.flat()
|
||||
|
||||
for (const option of variantsOptions) {
|
||||
expect(option.deleted_at).not.toBeNull()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("restore", function () {
|
||||
let module: IProductModuleService
|
||||
let images = ["image-1"]
|
||||
|
||||
beforeEach(async () => {
|
||||
await beforeEach_()
|
||||
|
||||
MedusaModule.clearInstances()
|
||||
|
||||
module = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(afterEach_)
|
||||
|
||||
it("should restore a soft deleted product and its cascaded relations", async () => {
|
||||
const data = buildProductAndRelationsData({
|
||||
images,
|
||||
thumbnail: images[0],
|
||||
})
|
||||
|
||||
const products = await module.create([data])
|
||||
|
||||
await module.softDelete([products[0].id])
|
||||
await module.restore([products[0].id])
|
||||
|
||||
const deletedProducts = await module.list(
|
||||
{ id: products[0].id },
|
||||
{
|
||||
relations: [
|
||||
"variants",
|
||||
"variants.options",
|
||||
"variants.options",
|
||||
"options",
|
||||
"options.values",
|
||||
],
|
||||
withDeleted: true,
|
||||
}
|
||||
)
|
||||
|
||||
expect(deletedProducts).toHaveLength(1)
|
||||
expect(deletedProducts[0].deleted_at).toBeNull()
|
||||
|
||||
for (const option of deletedProducts[0].options) {
|
||||
expect(option.deleted_at).toBeNull()
|
||||
}
|
||||
|
||||
const productOptionsValues = deletedProducts[0].options
|
||||
.map((o) => o.values)
|
||||
.flat()
|
||||
|
||||
for (const optionValue of productOptionsValues) {
|
||||
expect(optionValue.deleted_at).toBeNull()
|
||||
}
|
||||
|
||||
for (const variant of deletedProducts[0].variants) {
|
||||
expect(variant.deleted_at).toBeNull()
|
||||
}
|
||||
|
||||
const variantsOptions = deletedProducts[0].options
|
||||
.map((o) => o.values)
|
||||
.flat()
|
||||
|
||||
for (const option of variantsOptions) {
|
||||
expect(option.deleted_at).toBeNull()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -10,7 +10,7 @@ import { productCategoriesData } from "../../../__fixtures__/product-category/da
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("ProductCategory Service", () => {
|
||||
describe("Product category Service", () => {
|
||||
let service: ProductCategoryService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
@@ -47,7 +47,7 @@ describe("ProductCategory Service", () => {
|
||||
const productCategoryResults = await service.list(
|
||||
{},
|
||||
{
|
||||
select: ["id", "parent_category_id"] as any,
|
||||
select: ["id", "parent_category_id"],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -128,7 +128,7 @@ describe("ProductCategory Service", () => {
|
||||
include_descendants_tree: true,
|
||||
},
|
||||
{
|
||||
select: ["id", "handle"] as any,
|
||||
select: ["id", "handle"],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -192,7 +192,7 @@ describe("ProductCategory Service", () => {
|
||||
is_internal: false,
|
||||
},
|
||||
{
|
||||
select: ["id", "handle"] as any,
|
||||
select: ["id", "handle"],
|
||||
}
|
||||
)
|
||||
|
||||
@@ -230,4 +230,334 @@ describe("ProductCategory Service", () => {
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieve", () => {
|
||||
const categoryOneId = "category-1"
|
||||
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
productCategories = await createProductCategories(
|
||||
testManager,
|
||||
productCategoriesData
|
||||
)
|
||||
})
|
||||
|
||||
it("should return category for the given id", async () => {
|
||||
const productCategoryResults = await service.retrieve(
|
||||
categoryOneId,
|
||||
)
|
||||
|
||||
expect(productCategoryResults).toEqual(
|
||||
expect.objectContaining({
|
||||
id: categoryOneId
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when category with id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieve("does-not-exist")
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('ProductCategory with id: does-not-exist was not found')
|
||||
})
|
||||
|
||||
it("should throw an error when an id is not provided", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieve(undefined as unknown as string)
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('"productCategoryId" must be defined')
|
||||
})
|
||||
|
||||
it("should return category based on config select param", async () => {
|
||||
const productCategoryResults = await service.retrieve(
|
||||
categoryOneId,
|
||||
{
|
||||
select: ["id", "parent_category_id"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(productCategoryResults).toEqual(
|
||||
expect.objectContaining({
|
||||
id: categoryOneId,
|
||||
parent_category_id: "category-0",
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should return category based on config relation param", async () => {
|
||||
const productCategoryResults = await service.retrieve(
|
||||
categoryOneId,
|
||||
{
|
||||
select: ["id", "parent_category_id"],
|
||||
relations: ["parent_category"]
|
||||
}
|
||||
)
|
||||
|
||||
expect(productCategoryResults).toEqual(
|
||||
expect.objectContaining({
|
||||
id: categoryOneId,
|
||||
category_children: [
|
||||
expect.objectContaining({
|
||||
id: 'category-1-a',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: 'category-1-b',
|
||||
})
|
||||
],
|
||||
parent_category: expect.objectContaining({
|
||||
id: "category-0"
|
||||
})
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("listAndCount", () => {
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
productCategories = await createProductCategories(
|
||||
testManager,
|
||||
productCategoriesData
|
||||
)
|
||||
})
|
||||
|
||||
it("should return categories and count based on take and skip", async () => {
|
||||
let results = await service.listAndCount(
|
||||
{},
|
||||
{
|
||||
take: 1,
|
||||
}
|
||||
)
|
||||
|
||||
expect(results[1]).toEqual(5)
|
||||
expect(results[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "category-0",
|
||||
}),
|
||||
])
|
||||
|
||||
results = await service.listAndCount(
|
||||
{},
|
||||
{
|
||||
take: 1,
|
||||
skip: 1
|
||||
}
|
||||
)
|
||||
|
||||
expect(results[1]).toEqual(5)
|
||||
expect(results[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "category-1",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return all product categories and count", async () => {
|
||||
const productCategoryResults = await service.listAndCount(
|
||||
{},
|
||||
{
|
||||
select: ["id", "parent_category_id"],
|
||||
relations: ["parent_category"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(productCategoryResults[1]).toEqual(5)
|
||||
expect(productCategoryResults[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "category-0",
|
||||
parent_category: null
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "category-1",
|
||||
parent_category: expect.objectContaining({
|
||||
id: "category-0",
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "category-1-a",
|
||||
parent_category: expect.objectContaining({
|
||||
id: "category-1",
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "category-1-b",
|
||||
parent_category: expect.objectContaining({
|
||||
id: "category-1",
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "category-1-b-1",
|
||||
parent_category: expect.objectContaining({
|
||||
id: "category-1-b",
|
||||
}),
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should only return categories that are scoped by parent_category_id", async () => {
|
||||
let productCategoryResults = await service.listAndCount(
|
||||
{ parent_category_id: null },
|
||||
{
|
||||
select: ["id"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(productCategoryResults[1]).toEqual(1)
|
||||
expect(productCategoryResults[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "category-0",
|
||||
}),
|
||||
])
|
||||
|
||||
productCategoryResults = await service.listAndCount({
|
||||
parent_category_id: "category-0",
|
||||
})
|
||||
|
||||
expect(productCategoryResults[1]).toEqual(1)
|
||||
expect(productCategoryResults[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "category-1",
|
||||
}),
|
||||
])
|
||||
|
||||
productCategoryResults = await service.listAndCount({
|
||||
parent_category_id: ["category-1-b", "category-0"],
|
||||
})
|
||||
|
||||
expect(productCategoryResults[1]).toEqual(2)
|
||||
expect(productCategoryResults[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "category-1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "category-1-b-1",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should includes descendants when include_descendants_tree is true", async () => {
|
||||
const productCategoryResults = await service.listAndCount(
|
||||
{
|
||||
parent_category_id: null,
|
||||
include_descendants_tree: true,
|
||||
},
|
||||
{
|
||||
select: ["id", "handle"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(productCategoryResults[1]).toEqual(1)
|
||||
|
||||
const serializedObject = JSON.parse(
|
||||
JSON.stringify(productCategoryResults[0])
|
||||
)
|
||||
|
||||
expect(serializedObject).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "category-0",
|
||||
handle: "category-0",
|
||||
mpath: "category-0.",
|
||||
parent_category_id: null,
|
||||
parent_category: null,
|
||||
category_children: [
|
||||
expect.objectContaining({
|
||||
id: "category-1",
|
||||
handle: "category-1",
|
||||
mpath: "category-0.category-1.",
|
||||
parent_category_id: "category-0",
|
||||
parent_category: "category-0",
|
||||
category_children: [
|
||||
expect.objectContaining({
|
||||
id: "category-1-a",
|
||||
handle: "category-1-a",
|
||||
mpath: "category-0.category-1.category-1-a.",
|
||||
parent_category_id: "category-1",
|
||||
parent_category: "category-1",
|
||||
category_children: [],
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "category-1-b",
|
||||
handle: "category-1-b",
|
||||
mpath: "category-0.category-1.category-1-b.",
|
||||
parent_category_id: "category-1",
|
||||
parent_category: "category-1",
|
||||
category_children: [
|
||||
expect.objectContaining({
|
||||
id: "category-1-b-1",
|
||||
handle: "category-1-b-1",
|
||||
mpath:
|
||||
"category-0.category-1.category-1-b.category-1-b-1.",
|
||||
parent_category_id: "category-1-b",
|
||||
parent_category: "category-1-b",
|
||||
category_children: [],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should filter out children when include_descendants_tree is true", async () => {
|
||||
const productCategoryResults = await service.listAndCount(
|
||||
{
|
||||
parent_category_id: null,
|
||||
include_descendants_tree: true,
|
||||
is_internal: false,
|
||||
},
|
||||
{
|
||||
select: ["id", "handle"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(productCategoryResults[1]).toEqual(1)
|
||||
|
||||
const serializedObject = JSON.parse(
|
||||
JSON.stringify(productCategoryResults[0])
|
||||
)
|
||||
|
||||
expect(serializedObject).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "category-0",
|
||||
handle: "category-0",
|
||||
mpath: "category-0.",
|
||||
parent_category_id: null,
|
||||
parent_category: null,
|
||||
category_children: [
|
||||
expect.objectContaining({
|
||||
id: "category-1",
|
||||
handle: "category-1",
|
||||
mpath: "category-0.category-1.",
|
||||
parent_category_id: "category-0",
|
||||
parent_category: "category-0",
|
||||
category_children: [
|
||||
expect.objectContaining({
|
||||
id: "category-1-a",
|
||||
handle: "category-1-a",
|
||||
mpath: "category-0.category-1.category-1-a.",
|
||||
parent_category_id: "category-1",
|
||||
parent_category: "category-1",
|
||||
category_children: [],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -9,7 +9,7 @@ import { createCollections } from "../../../__fixtures__/product"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("Product Service", () => {
|
||||
describe("Product collection Service", () => {
|
||||
let service: ProductCollectionService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
@@ -59,9 +59,9 @@ describe("Product Service", () => {
|
||||
})
|
||||
|
||||
it("list product collections", async () => {
|
||||
const tagsResults = await service.list()
|
||||
const productCollectionResults = await service.list()
|
||||
|
||||
expect(tagsResults).toEqual([
|
||||
expect(productCollectionResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
title: "col 1",
|
||||
@@ -82,9 +82,9 @@ describe("Product Service", () => {
|
||||
})
|
||||
|
||||
it("list product collections by id", async () => {
|
||||
const tagsResults = await service.list({ id: data![0].id })
|
||||
const productCollectionResults = await service.list({ id: data![0].id })
|
||||
|
||||
expect(tagsResults).toEqual([
|
||||
expect(productCollectionResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
title: "col 1",
|
||||
@@ -93,9 +93,9 @@ describe("Product Service", () => {
|
||||
})
|
||||
|
||||
it("list product collections by title matching string", async () => {
|
||||
const tagsResults = await service.list({ title: "col 3 extra" })
|
||||
const productCollectionResults = await service.list({ title: "col 3 extra" })
|
||||
|
||||
expect(tagsResults).toEqual([
|
||||
expect(productCollectionResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-3",
|
||||
title: "col 3 extra",
|
||||
@@ -103,4 +103,174 @@ describe("Product Service", () => {
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("listAndCount", () => {
|
||||
const data = [
|
||||
{
|
||||
id: "test-1",
|
||||
title: "col 1",
|
||||
},
|
||||
{
|
||||
id: "test-2",
|
||||
title: "col 2",
|
||||
},
|
||||
{
|
||||
id: "test-3",
|
||||
title: "col 3 extra",
|
||||
},
|
||||
{
|
||||
id: "test-4",
|
||||
title: "col 4 extra",
|
||||
},
|
||||
]
|
||||
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
collectionsData = await createCollections(testManager, data)
|
||||
})
|
||||
|
||||
it("should return all collections and count", async () => {
|
||||
const [productCollectionResults, count] = await service.listAndCount()
|
||||
const serialized = JSON.parse(JSON.stringify(productCollectionResults))
|
||||
|
||||
expect(serialized).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
title: "col 1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "test-2",
|
||||
title: "col 2",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "test-3",
|
||||
title: "col 3 extra",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "test-4",
|
||||
title: "col 4 extra",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return count and collections based on filter data", async () => {
|
||||
const [productCollectionResults, count] = await service.listAndCount({ id: data![0].id })
|
||||
const serialized = JSON.parse(JSON.stringify(productCollectionResults))
|
||||
|
||||
expect(count).toEqual(1)
|
||||
expect(serialized).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
title: "col 1",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return count and collections based on config data", async () => {
|
||||
const [productCollectionResults, count] = await service.listAndCount({}, {
|
||||
relations: ['products'],
|
||||
select: ['title'],
|
||||
take: 1,
|
||||
skip: 1,
|
||||
})
|
||||
const serialized = JSON.parse(JSON.stringify(productCollectionResults))
|
||||
|
||||
expect(count).toEqual(4)
|
||||
expect(serialized).toEqual([
|
||||
{
|
||||
id: "test-2",
|
||||
title: "col 2",
|
||||
products: []
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieve", () => {
|
||||
const collectionData = {
|
||||
id: "collection-1",
|
||||
title: "collection 1",
|
||||
}
|
||||
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
await createCollections(testManager, [collectionData])
|
||||
})
|
||||
|
||||
it("should return collection for the given id", async () => {
|
||||
const productCollectionResults = await service.retrieve(
|
||||
collectionData.id,
|
||||
)
|
||||
|
||||
expect(productCollectionResults).toEqual(
|
||||
expect.objectContaining({
|
||||
id: collectionData.id
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when collection with id does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieve("does-not-exist")
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('ProductCollection with id: does-not-exist was not found')
|
||||
})
|
||||
|
||||
it("should throw an error when an id is not provided", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieve(undefined as unknown as string)
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('"productCollectionId" must be defined')
|
||||
})
|
||||
|
||||
it("should return collection based on config select param", async () => {
|
||||
const productCollectionResults = await service.retrieve(
|
||||
collectionData.id,
|
||||
{
|
||||
select: ["id", "title"],
|
||||
}
|
||||
)
|
||||
|
||||
const serialized = JSON.parse(JSON.stringify(productCollectionResults))
|
||||
|
||||
expect(serialized).toEqual(
|
||||
{
|
||||
id: collectionData.id,
|
||||
title: collectionData.title,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should return collection based on config relation param", async () => {
|
||||
const productCollectionResults = await service.retrieve(
|
||||
collectionData.id,
|
||||
{
|
||||
select: ["id", "title"],
|
||||
relations: ["products"]
|
||||
}
|
||||
)
|
||||
|
||||
const serialized = JSON.parse(JSON.stringify(productCollectionResults))
|
||||
|
||||
expect(serialized).toEqual(
|
||||
{
|
||||
id: collectionData.id,
|
||||
title: collectionData.title,
|
||||
products: []
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { Product, ProductCategory } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
|
||||
import { initialize } from "../../../../src"
|
||||
import { DB_URL, TestDatabase } from "../../../utils"
|
||||
import { createProductCategories } from "../../../__fixtures__/product-category"
|
||||
|
||||
describe("ProductModuleService product categories", () => {
|
||||
let service: IProductModuleService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
let productOne: Product
|
||||
let productTwo: Product
|
||||
let productCategoryOne: ProductCategory
|
||||
let productCategoryTwo: ProductCategory
|
||||
let productCategories: ProductCategory[]
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
repositoryManager = await TestDatabase.forkManager()
|
||||
|
||||
service = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
})
|
||||
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
productOne = testManager.create(Product, {
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
})
|
||||
|
||||
productTwo = testManager.create(Product, {
|
||||
id: "product-2",
|
||||
title: "product 2",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
})
|
||||
|
||||
const productCategoriesData = [{
|
||||
id: "test-1",
|
||||
name: "category 1",
|
||||
products: [productOne],
|
||||
},{
|
||||
id: "test-2",
|
||||
name: "category",
|
||||
products: [productTwo],
|
||||
}]
|
||||
|
||||
productCategories = await createProductCategories(
|
||||
testManager,
|
||||
productCategoriesData
|
||||
)
|
||||
|
||||
productCategoryOne = productCategories[0]
|
||||
productCategoryTwo = productCategories[1]
|
||||
|
||||
await testManager.persistAndFlush([productCategoryOne, productCategoryTwo])
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
})
|
||||
|
||||
describe("listCategories", () => {
|
||||
it("should return categories queried by ID", async () => {
|
||||
const results = await service.listCategories({
|
||||
id: productCategoryOne.id,
|
||||
})
|
||||
|
||||
expect(results).toEqual([
|
||||
expect.objectContaining({
|
||||
id: productCategoryOne.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return categories based on the options and filter parameter", async () => {
|
||||
let results = await service.listCategories(
|
||||
{
|
||||
id: productCategoryOne.id,
|
||||
},
|
||||
{
|
||||
take: 1,
|
||||
}
|
||||
)
|
||||
|
||||
expect(results).toEqual([
|
||||
expect.objectContaining({
|
||||
id: productCategoryOne.id,
|
||||
}),
|
||||
])
|
||||
|
||||
results = await service.listCategories({}, { take: 1, skip: 1 })
|
||||
|
||||
expect(results).toEqual([
|
||||
expect.objectContaining({
|
||||
id: productCategoryTwo.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return only requested fields and relations for categories", async () => {
|
||||
const results = await service.listCategories(
|
||||
{
|
||||
id: productCategoryOne.id,
|
||||
},
|
||||
{
|
||||
select: ["id", "name", "products.title"],
|
||||
relations: ["products"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(results).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
name: "category 1",
|
||||
products: [expect.objectContaining({
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
})],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("listAndCountCategories", () => {
|
||||
it("should return categories and count queried by ID", async () => {
|
||||
const results = await service.listAndCountCategories({
|
||||
id: productCategoryOne.id,
|
||||
})
|
||||
|
||||
expect(results[1]).toEqual(1)
|
||||
expect(results[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: productCategoryOne.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return categories and count based on the options and filter parameter", async () => {
|
||||
let results = await service.listAndCountCategories(
|
||||
{
|
||||
id: productCategoryOne.id,
|
||||
},
|
||||
{
|
||||
take: 1,
|
||||
}
|
||||
)
|
||||
|
||||
expect(results[1]).toEqual(1)
|
||||
expect(results[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: productCategoryOne.id,
|
||||
}),
|
||||
])
|
||||
|
||||
results = await service.listAndCountCategories({}, { take: 1 })
|
||||
|
||||
expect(results[1]).toEqual(2)
|
||||
|
||||
results = await service.listAndCountCategories({}, { take: 1, skip: 1 })
|
||||
|
||||
expect(results[1]).toEqual(2)
|
||||
expect(results[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: productCategoryTwo.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return only requested fields and relations for categories", async () => {
|
||||
const results = await service.listAndCountCategories(
|
||||
{
|
||||
id: productCategoryOne.id,
|
||||
},
|
||||
{
|
||||
select: ["id", "name", "products.title"],
|
||||
relations: ["products"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(results[1]).toEqual(1)
|
||||
expect(results[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
name: "category 1",
|
||||
products: [expect.objectContaining({
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
})],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieveCategory", () => {
|
||||
it("should return the requested category", async () => {
|
||||
const result = await service.retrieveCategory(productCategoryOne.id)
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
name: "category 1",
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should return requested attributes when requested through config", async () => {
|
||||
const result = await service.retrieveCategory(
|
||||
productCategoryOne.id,
|
||||
{
|
||||
select: ["id", "name", "products.title"],
|
||||
relations: ["products"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
name: "category 1",
|
||||
products: [expect.objectContaining({
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
})],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when a category with ID does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieveCategory("does-not-exist")
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual("ProductCategory with id: does-not-exist was not found")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+250
@@ -0,0 +1,250 @@
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { Product, ProductCollection } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
|
||||
import { initialize } from "../../../../src"
|
||||
import { DB_URL, TestDatabase } from "../../../utils"
|
||||
import { createCollections } from "../../../__fixtures__/product"
|
||||
|
||||
describe("ProductModuleService product collections", () => {
|
||||
let service: IProductModuleService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
let productOne: Product
|
||||
let productTwo: Product
|
||||
let productCollectionOne: ProductCollection
|
||||
let productCollectionTwo: ProductCollection
|
||||
let productCollections: ProductCollection[]
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
repositoryManager = await TestDatabase.forkManager()
|
||||
|
||||
service = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
})
|
||||
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
productOne = testManager.create(Product, {
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
})
|
||||
|
||||
productTwo = testManager.create(Product, {
|
||||
id: "product-2",
|
||||
title: "product 2",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
})
|
||||
|
||||
const productCollectionsData = [{
|
||||
id: "test-1",
|
||||
title: "collection 1",
|
||||
products: [productOne],
|
||||
},{
|
||||
id: "test-2",
|
||||
title: "collection",
|
||||
products: [productTwo],
|
||||
}]
|
||||
|
||||
productCollections = await createCollections(
|
||||
testManager,
|
||||
productCollectionsData
|
||||
)
|
||||
|
||||
productCollectionOne = productCollections[0]
|
||||
productCollectionTwo = productCollections[1]
|
||||
|
||||
await testManager.persistAndFlush([productCollectionOne, productCollectionTwo])
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
})
|
||||
|
||||
describe("listCollections", () => {
|
||||
it("should return collections queried by ID", async () => {
|
||||
const results = await service.listCollections({
|
||||
id: productCollectionOne.id,
|
||||
})
|
||||
|
||||
expect(results).toEqual([
|
||||
expect.objectContaining({
|
||||
id: productCollectionOne.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return collections based on the options and filter parameter", async () => {
|
||||
let results = await service.listCollections(
|
||||
{
|
||||
id: productCollectionOne.id,
|
||||
},
|
||||
{
|
||||
take: 1,
|
||||
}
|
||||
)
|
||||
|
||||
expect(results).toEqual([
|
||||
expect.objectContaining({
|
||||
id: productCollectionOne.id,
|
||||
}),
|
||||
])
|
||||
|
||||
results = await service.listCollections({}, { take: 1, skip: 1 })
|
||||
|
||||
expect(results).toEqual([
|
||||
expect.objectContaining({
|
||||
id: productCollectionTwo.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return only requested fields and relations for collections", async () => {
|
||||
const results = await service.listCollections(
|
||||
{
|
||||
id: productCollectionOne.id,
|
||||
},
|
||||
{
|
||||
select: ["id", "title", "products.title"],
|
||||
relations: ["products"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(results).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
title: "collection 1",
|
||||
products: [
|
||||
expect.objectContaining({
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
})
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("listAndCountCollections", () => {
|
||||
it("should return collections and count queried by ID", async () => {
|
||||
const results = await service.listAndCountCollections({
|
||||
id: productCollectionOne.id,
|
||||
})
|
||||
|
||||
expect(results[1]).toEqual(1)
|
||||
expect(results[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: productCollectionOne.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return collections and count based on the options and filter parameter", async () => {
|
||||
let results = await service.listAndCountCollections(
|
||||
{
|
||||
id: productCollectionOne.id,
|
||||
},
|
||||
{
|
||||
take: 1,
|
||||
}
|
||||
)
|
||||
|
||||
expect(results[1]).toEqual(1)
|
||||
expect(results[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: productCollectionOne.id,
|
||||
}),
|
||||
])
|
||||
|
||||
results = await service.listAndCountCollections({}, { take: 1 })
|
||||
|
||||
expect(results[1]).toEqual(2)
|
||||
|
||||
results = await service.listAndCountCollections({}, { take: 1, skip: 1 })
|
||||
|
||||
expect(results[1]).toEqual(2)
|
||||
expect(results[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: productCollectionTwo.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return only requested fields and relations for collections", async () => {
|
||||
const results = await service.listAndCountCollections(
|
||||
{
|
||||
id: productCollectionOne.id,
|
||||
},
|
||||
{
|
||||
select: ["id", "title", "products.title"],
|
||||
relations: ["products"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(results[1]).toEqual(1)
|
||||
expect(results[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
title: "collection 1",
|
||||
products: [expect.objectContaining({
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
})],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieveCollection", () => {
|
||||
it("should return the requested collection", async () => {
|
||||
const result = await service.retrieveCollection(productCollectionOne.id)
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
title: "collection 1",
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should return requested attributes when requested through config", async () => {
|
||||
const result = await service.retrieveCollection(
|
||||
productCollectionOne.id,
|
||||
{
|
||||
select: ["id", "title", "products.title"],
|
||||
relations: ["products"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
title: "collection 1",
|
||||
products: [expect.objectContaining({
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
})],
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when a collection with ID does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieveCollection("does-not-exist")
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual("ProductCollection with id: does-not-exist was not found")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
import { initialize } from "../../../../src"
|
||||
import { DB_URL, TestDatabase } from "../../../utils"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
import { Product, ProductVariant } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
|
||||
describe("ProductModuleService product variants", () => {
|
||||
let service: IProductModuleService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
let variantOne: ProductVariant
|
||||
let variantTwo: ProductVariant
|
||||
let productOne: Product
|
||||
let productTwo: Product
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
repositoryManager = await TestDatabase.forkManager()
|
||||
|
||||
service = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
})
|
||||
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
productOne = testManager.create(Product, {
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
})
|
||||
|
||||
productTwo = testManager.create(Product, {
|
||||
id: "product-2",
|
||||
title: "product 2",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
})
|
||||
|
||||
variantOne = testManager.create(ProductVariant, {
|
||||
id: "test-1",
|
||||
title: "variant 1",
|
||||
inventory_quantity: 10,
|
||||
product: productOne,
|
||||
})
|
||||
|
||||
variantTwo = testManager.create(ProductVariant, {
|
||||
id: "test-2",
|
||||
title: "variant",
|
||||
inventory_quantity: 10,
|
||||
product: productTwo,
|
||||
})
|
||||
|
||||
await testManager.persistAndFlush([variantOne, variantTwo])
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
})
|
||||
|
||||
describe("listAndCountVariants", () => {
|
||||
it("should return variants and count queried by ID", async () => {
|
||||
const results = await service.listAndCountVariants({
|
||||
id: variantOne.id,
|
||||
})
|
||||
|
||||
expect(results[1]).toEqual(1)
|
||||
expect(results[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: variantOne.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return variants and count based on the options and filter parameter", async () => {
|
||||
let results = await service.listAndCountVariants(
|
||||
{
|
||||
id: variantOne.id,
|
||||
},
|
||||
{
|
||||
take: 1,
|
||||
}
|
||||
)
|
||||
|
||||
expect(results[1]).toEqual(1)
|
||||
expect(results[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: variantOne.id,
|
||||
}),
|
||||
])
|
||||
|
||||
results = await service.listAndCountVariants({}, { take: 1 })
|
||||
|
||||
expect(results[1]).toEqual(2)
|
||||
|
||||
results = await service.listAndCountVariants({}, { take: 1, skip: 1 })
|
||||
|
||||
expect(results[1]).toEqual(2)
|
||||
expect(results[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: variantTwo.id,
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("should return only requested fields and relations for variants", async () => {
|
||||
const results = await service.listAndCountVariants(
|
||||
{
|
||||
id: variantOne.id,
|
||||
},
|
||||
{
|
||||
select: ["id", "title", "product.title"] as any,
|
||||
relations: ["product"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(results[1]).toEqual(1)
|
||||
expect(results[0]).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
title: "variant 1",
|
||||
product_id: "product-1",
|
||||
// TODO: investigate why this is returning more than the expected results
|
||||
product: expect.objectContaining({
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
}),
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieveVariant", () => {
|
||||
it("should return the requested variant", async () => {
|
||||
const result = await service.retrieveVariant(variantOne.id)
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
title: "variant 1",
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should return requested attributes when requested through config", async () => {
|
||||
const result = await service.retrieveVariant(
|
||||
variantOne.id,
|
||||
{
|
||||
select: ["id", "title", "product.title"] as any,
|
||||
relations: ["product"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
title: "variant 1",
|
||||
product_id: "product-1",
|
||||
product: expect.objectContaining({
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when a variant with ID does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieveVariant("does-not-exist")
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual("ProductVariant with id: does-not-exist was not found")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -10,7 +10,7 @@ import { ProductTypes } from "@medusajs/types"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("Product Service", () => {
|
||||
describe("Product tag Service", () => {
|
||||
let service: ProductTagService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { TestDatabase } from "../../../utils"
|
||||
import { ProductVariantService } from "@services"
|
||||
import { ProductVariantRepository } from "@repositories"
|
||||
import { ProductService, ProductVariantService } from "@services"
|
||||
import { ProductRepository, ProductVariantRepository } from "@repositories"
|
||||
import { Product, ProductTag, ProductVariant } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { Collection } from "@mikro-orm/core"
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
createProductVariants,
|
||||
} from "../../../__fixtures__/product"
|
||||
import { productsData, variantsData } from "../../../__fixtures__/product/data"
|
||||
import { buildProductVariantOnlyData } from "../../../__fixtures__/variant/data/create-variant"
|
||||
|
||||
describe("ProductVariant Service", () => {
|
||||
let service: ProductVariantService
|
||||
@@ -20,6 +21,7 @@ describe("ProductVariant Service", () => {
|
||||
let variantOne: ProductVariant
|
||||
let variantTwo: ProductVariant
|
||||
let productOne: Product
|
||||
const productVariantTestOne = "test-1"
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
@@ -28,8 +30,17 @@ describe("ProductVariant Service", () => {
|
||||
const productVariantRepository = new ProductVariantRepository({
|
||||
manager: repositoryManager,
|
||||
})
|
||||
const productRepository = new ProductRepository({
|
||||
manager: repositoryManager,
|
||||
})
|
||||
|
||||
service = new ProductVariantService({ productVariantRepository })
|
||||
const productService = new ProductService({
|
||||
productRepository,
|
||||
})
|
||||
service = new ProductVariantService({
|
||||
productService,
|
||||
productVariantRepository,
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
@@ -47,7 +58,7 @@ describe("ProductVariant Service", () => {
|
||||
})
|
||||
|
||||
variantOne = testManager.create(ProductVariant, {
|
||||
id: "test-1",
|
||||
id: productVariantTestOne,
|
||||
title: "variant 1",
|
||||
inventory_quantity: 10,
|
||||
product: productOne,
|
||||
@@ -95,7 +106,7 @@ describe("ProductVariant Service", () => {
|
||||
it("passing populate, scopes the results of the response", async () => {
|
||||
const results = await service.list(
|
||||
{
|
||||
id: "test-1",
|
||||
id: productVariantTestOne,
|
||||
},
|
||||
{
|
||||
select: ["id", "title", "product.title"] as any,
|
||||
@@ -105,7 +116,7 @@ describe("ProductVariant Service", () => {
|
||||
|
||||
expect(results).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
id: productVariantTestOne,
|
||||
title: "variant 1",
|
||||
product: expect.objectContaining({
|
||||
id: "product-1",
|
||||
@@ -118,7 +129,7 @@ describe("ProductVariant Service", () => {
|
||||
|
||||
expect(JSON.parse(JSON.stringify(results))).toEqual([
|
||||
{
|
||||
id: "test-1",
|
||||
id: productVariantTestOne,
|
||||
title: "variant 1",
|
||||
product_id: "product-1",
|
||||
product: {
|
||||
@@ -175,11 +186,148 @@ describe("ProductVariant Service", () => {
|
||||
|
||||
expect(JSON.parse(JSON.stringify(variants))).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
id: productVariantTestOne,
|
||||
title: "variant title",
|
||||
sku: "sku 1",
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("create", function () {
|
||||
let products: Product[]
|
||||
let productOptions!: ProductOption[]
|
||||
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
products = (await createProductAndTags(
|
||||
testManager,
|
||||
productsData
|
||||
)) as Product[]
|
||||
|
||||
productOptions = await createOptions(testManager, [
|
||||
{
|
||||
id: "test-option-1",
|
||||
title: "size",
|
||||
product: products[0],
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("should create a variant", async () => {
|
||||
const data = buildProductVariantOnlyData({
|
||||
options: [
|
||||
{
|
||||
option: productOptions[0],
|
||||
value: "XS",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const variants = await service.create(products[0].id, [data])
|
||||
|
||||
expect(variants).toHaveLength(1)
|
||||
expect(variants[0].options).toHaveLength(1)
|
||||
|
||||
expect(JSON.parse(JSON.stringify(variants[0]))).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
title: data.title,
|
||||
sku: data.sku,
|
||||
inventory_quantity: 100,
|
||||
allow_backorder: false,
|
||||
manage_inventory: true,
|
||||
variant_rank: 0,
|
||||
product: expect.objectContaining({
|
||||
id: products[0].id,
|
||||
}),
|
||||
options: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
value: data.options![0].value,
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("retrieve", () => {
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
productOne = testManager.create(Product, {
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
status: ProductTypes.ProductStatus.PUBLISHED,
|
||||
})
|
||||
|
||||
variantOne = testManager.create(ProductVariant, {
|
||||
id: productVariantTestOne,
|
||||
title: "variant 1",
|
||||
inventory_quantity: 10,
|
||||
product: productOne,
|
||||
})
|
||||
|
||||
await testManager.persistAndFlush([variantOne])
|
||||
})
|
||||
|
||||
it("should return the requested variant", async () => {
|
||||
const result = await service.retrieve(variantOne.id)
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
id: productVariantTestOne,
|
||||
title: "variant 1",
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should return requested attributes when requested through config", async () => {
|
||||
const result = await service.retrieve(
|
||||
variantOne.id,
|
||||
{
|
||||
select: ["id", "title", "product.title"] as any,
|
||||
relations: ["product"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(result).toEqual(
|
||||
expect.objectContaining({
|
||||
id: productVariantTestOne,
|
||||
title: "variant 1",
|
||||
product_id: "product-1",
|
||||
product: expect.objectContaining({
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when a variant with ID does not exist", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieve("does-not-exist")
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual("ProductVariant with id: does-not-exist was not found")
|
||||
})
|
||||
|
||||
it("should throw an error when an id is not provided", async () => {
|
||||
let error
|
||||
|
||||
try {
|
||||
await service.retrieve(undefined as unknown as string)
|
||||
} catch (e) {
|
||||
error = e
|
||||
}
|
||||
|
||||
expect(error.message).toEqual('"productVariantId" must be defined')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,17 +1,14 @@
|
||||
import { TestDatabase } from "../../../utils"
|
||||
import {
|
||||
ProductService,
|
||||
ProductTagService,
|
||||
ProductVariantService,
|
||||
} from "@services"
|
||||
import { ProductService } from "@services"
|
||||
import { ProductRepository } from "@repositories"
|
||||
import { Product, ProductCategory, ProductVariant } from "@models"
|
||||
import { Image, Product, ProductCategory, ProductVariant } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { ProductDTO } from "@medusajs/types"
|
||||
|
||||
import { createProductCategories } from "../../../__fixtures__/product-category"
|
||||
import {
|
||||
assignCategoriesToProduct,
|
||||
createImages,
|
||||
createProductAndTags,
|
||||
createProductVariants,
|
||||
} from "../../../__fixtures__/product"
|
||||
@@ -20,13 +17,8 @@ import {
|
||||
productsData,
|
||||
variantsData,
|
||||
} from "../../../__fixtures__/product/data"
|
||||
|
||||
const productVariantService = {
|
||||
list: jest.fn(),
|
||||
} as unknown as ProductVariantService
|
||||
const productTagService = {
|
||||
list: jest.fn(),
|
||||
} as unknown as ProductTagService
|
||||
import { buildProductOnlyData } from "../../../__fixtures__/product/data/create-product"
|
||||
import { kebabCase } from "@medusajs/utils"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
@@ -48,8 +40,6 @@ describe("Product Service", () => {
|
||||
|
||||
service = new ProductService({
|
||||
productRepository,
|
||||
productVariantService,
|
||||
productTagService,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -57,7 +47,74 @@ describe("Product Service", () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
})
|
||||
|
||||
describe("create", function () {
|
||||
let images: Image[] = []
|
||||
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
images = await createImages(testManager, ["image-1"])
|
||||
})
|
||||
|
||||
it("should create a product", async () => {
|
||||
const data = buildProductOnlyData({
|
||||
images,
|
||||
thumbnail: images[0].url,
|
||||
})
|
||||
|
||||
const products = await service.create([data])
|
||||
|
||||
expect(products).toHaveLength(1)
|
||||
expect(JSON.parse(JSON.stringify(products[0]))).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
title: data.title,
|
||||
handle: kebabCase(data.title),
|
||||
description: data.description,
|
||||
subtitle: data.subtitle,
|
||||
is_giftcard: data.is_giftcard,
|
||||
discountable: data.discountable,
|
||||
thumbnail: images[0].url,
|
||||
status: data.status,
|
||||
images: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: images[0].id,
|
||||
url: images[0].url,
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("list", () => {
|
||||
describe("soft deleted", function () {
|
||||
let deletedProduct
|
||||
let product
|
||||
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
const products = await createProductAndTags(testManager, productsData)
|
||||
|
||||
product = products[1]
|
||||
deletedProduct = await service.softDelete([products[0].id])
|
||||
})
|
||||
|
||||
it("should list all products that are not deleted", async () => {
|
||||
const products = await service.list()
|
||||
|
||||
expect(products).toHaveLength(1)
|
||||
expect(products[0].id).toEqual(product.id)
|
||||
})
|
||||
|
||||
it("should list all products including the deleted", async () => {
|
||||
const products = await service.list({}, { withDeleted: true })
|
||||
|
||||
expect(products).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe("relation: tags", () => {
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
@@ -65,7 +122,7 @@ describe("Product Service", () => {
|
||||
products = await createProductAndTags(testManager, productsData)
|
||||
})
|
||||
|
||||
it("filter by id and including relations", async () => {
|
||||
it("should filter by id and including relations", async () => {
|
||||
const productsResult = await service.list(
|
||||
{
|
||||
id: products[0].id,
|
||||
@@ -95,7 +152,7 @@ describe("Product Service", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("filter by id and without relations", async () => {
|
||||
it("should filter by id and without relations", async () => {
|
||||
const productsResult = await service.list({
|
||||
id: products[0].id,
|
||||
})
|
||||
@@ -137,7 +194,7 @@ describe("Product Service", () => {
|
||||
)
|
||||
})
|
||||
|
||||
it("filter by categories relation and scope fields", async () => {
|
||||
it("should filter by categories relation and scope fields", async () => {
|
||||
const products = await service.list(
|
||||
{
|
||||
id: workingProduct.id,
|
||||
@@ -187,7 +244,7 @@ describe("Product Service", () => {
|
||||
])
|
||||
})
|
||||
|
||||
it("returns empty array when querying for a category that doesnt exist", async () => {
|
||||
it("should returns empty array when querying for a category that doesnt exist", async () => {
|
||||
const products = await service.list(
|
||||
{
|
||||
id: workingProduct.id,
|
||||
@@ -215,7 +272,7 @@ describe("Product Service", () => {
|
||||
variants = await createProductVariants(testManager, variantsData)
|
||||
})
|
||||
|
||||
it("filter by id and including relations", async () => {
|
||||
it("should filter by id and including relations", async () => {
|
||||
const productsResult = await service.list(
|
||||
{
|
||||
id: products[0].id,
|
||||
@@ -254,4 +311,52 @@ describe("Product Service", () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("softDelete", function () {
|
||||
let images: Image[] = []
|
||||
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
images = await createImages(testManager, ["image-1"])
|
||||
})
|
||||
|
||||
it("should soft delete a product", async () => {
|
||||
const data = buildProductOnlyData({
|
||||
images,
|
||||
thumbnail: images[0].url,
|
||||
})
|
||||
|
||||
const products = await service.create([data])
|
||||
const deleteProducts = await service.softDelete(products.map((p) => p.id))
|
||||
|
||||
expect(deleteProducts).toHaveLength(1)
|
||||
expect(deleteProducts[0].deleted_at).not.toBeNull()
|
||||
})
|
||||
})
|
||||
|
||||
describe("restore", function () {
|
||||
let images: Image[] = []
|
||||
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
images = await createImages(testManager, ["image-1"])
|
||||
})
|
||||
|
||||
it("should restore a soft deleted product", async () => {
|
||||
const data = buildProductOnlyData({
|
||||
images,
|
||||
thumbnail: images[0].url,
|
||||
})
|
||||
|
||||
const products = await service.create([data])
|
||||
const product = products[0]
|
||||
await service.softDelete([product.id])
|
||||
const restoreProducts = await service.restore([product.id])
|
||||
|
||||
expect(restoreProducts).toHaveLength(1)
|
||||
expect(restoreProducts[0].deleted_at).toBeNull()
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user