chore(): Reorganize modules (#7210)

**What**
Move all modules to the modules directory
This commit is contained in:
Adrien de Peretti
2024-05-02 17:33:34 +02:00
committed by GitHub
parent 7a351eef09
commit 4eae25e1ef
870 changed files with 91 additions and 62 deletions

View File

@@ -0,0 +1,52 @@
import { Context } from "@medusajs/types"
import { DALUtils } from "@medusajs/utils"
class CustomRepository extends DALUtils.MikroOrmBaseRepository {
constructor({ manager }) {
// @ts-ignore
super(...arguments)
}
find = jest.fn().mockImplementation(async () => [])
findAndCount = jest.fn().mockImplementation(async () => [])
create = jest.fn()
update = jest.fn()
delete = jest.fn()
softDelete = jest.fn()
restore = jest.fn()
async transaction<TManager = unknown>(
task: (transactionManager: TManager) => Promise<any>,
options: {
isolationLevel?: string
enableNestedTransactions?: boolean
transaction?: TManager
} = {}
): Promise<any> {
return super.transaction(task, options)
}
getActiveManager<TManager = unknown>({
transactionManager,
manager,
}: Context = {}): TManager {
return super.getActiveManager({ transactionManager, manager })
}
getFreshManager<TManager = unknown>(): TManager {
return super.getFreshManager()
}
async serialize<TOutput extends object | object[]>(
data: any,
options?: any
): Promise<TOutput> {
return super.serialize(data, options)
}
}
export class ProductRepository extends CustomRepository {}
export class ProductTagRepository extends CustomRepository {}
export class ProductCollectionRepository extends CustomRepository {}
export class ProductVariantRepository extends CustomRepository {}
export class ProductCategoryRepository extends CustomRepository {}

View File

@@ -0,0 +1,191 @@
export const productCategoriesData = [
{
id: "category-0",
name: "category 0",
parent_category_id: null,
},
{
id: "category-1",
name: "category 1",
parent_category_id: "category-0",
},
{
id: "category-1-a",
name: "category 1 a",
parent_category_id: "category-1",
},
{
id: "category-1-b",
name: "category 1 b",
parent_category_id: "category-1",
is_internal: true,
},
{
id: "category-1-b-1",
name: "category 1 b 1",
parent_category_id: "category-1-b",
},
]
export const productCategoriesRankData = [
{
id: "category-0-0",
name: "category 0 0",
parent_category_id: null,
rank: 0,
},
{
id: "category-0-1",
name: "category 0 1",
parent_category_id: null,
rank: 1,
},
{
id: "category-0-2",
name: "category 0 2",
parent_category_id: null,
rank: 2,
},
{
id: "category-0-0-0",
name: "category 0 0-0",
parent_category_id: "category-0-0",
rank: 0,
},
{
id: "category-0-0-1",
name: "category 0 0-1",
parent_category_id: "category-0-0",
rank: 1,
},
{
id: "category-0-0-2",
name: "category 0 0-2",
parent_category_id: "category-0-0",
rank: 2,
},
]
export const eletronicsCategoriesData = eval(`[
{
id: "electronics",
name: "Electronics",
parent_category_id: null,
},
{
id: "computers",
name: "Computers & Accessories",
parent_category_id: "electronics",
},
{
id: "desktops",
name: "Desktops",
parent_category_id: "computers",
},
{
id: "gaming-desktops",
name: "Gaming Desktops",
parent_category_id: "desktops",
},
{
id: "office-desktops",
name: "Office Desktops",
parent_category_id: "desktops",
},
{
id: "laptops",
name: "Laptops",
parent_category_id: "computers",
},
{
id: "gaming-laptops",
name: "Gaming Laptops",
parent_category_id: "laptops",
},
{
id: "budget-gaming",
name: "Budget Gaming Laptops",
parent_category_id: "gaming-laptops",
},
{
id: "high-performance",
name: "High Performance Gaming Laptops",
parent_category_id: "gaming-laptops",
},
{
id: "vr-ready",
name: "VR-Ready High Performance Gaming Laptops",
parent_category_id: "high-performance",
},
{
id: "4k-gaming",
name: "4K Gaming Laptops",
parent_category_id: "high-performance",
},
{
id: "ultrabooks",
name: "Ultrabooks",
parent_category_id: "laptops",
},
{
id: "thin-light",
name: "Thin & Light Ultrabooks",
parent_category_id: "ultrabooks",
},
{
id: "convertible-ultrabooks",
name: "Convertible Ultrabooks",
parent_category_id: "ultrabooks",
},
{
id: "touchscreen-ultrabooks",
name: "Touchscreen Ultrabooks",
parent_category_id: "convertible-ultrabooks",
},
{
id: "detachable-ultrabooks",
name: "Detachable Ultrabooks",
parent_category_id: "convertible-ultrabooks",
},
{
id: "mobile",
name: "Mobile Phones & Accessories",
parent_category_id: "electronics",
},
{
id: "smartphones",
name: "Smartphones",
parent_category_id: "mobile",
},
{
id: "android-phones",
name: "Android Phones",
parent_category_id: "smartphones",
},
{
id: "flagship-phones",
name: "Flagship Smartphones",
parent_category_id: "android-phones",
},
{
id: "budget-phones",
name: "Budget Smartphones",
parent_category_id: "android-phones",
},
{
id: "iphones",
name: "iPhones",
parent_category_id: "smartphones",
},
{
id: "pro-phones",
name: "Pro Models",
parent_category_id: "iphones",
},
{
id: "mini-phones",
name: "Mini Models",
parent_category_id: "iphones",
},
]`)

View File

@@ -0,0 +1,31 @@
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { ProductCategory } from "@models"
export async function createProductCategories(
manager: SqlEntityManager,
categoriesData: any[]
): Promise<ProductCategory[]> {
const categories: ProductCategory[] = []
for (let categoryData of categoriesData) {
let categoryDataClone = { ...categoryData }
let parentCategory: ProductCategory | null = null
const parentCategoryId = categoryDataClone.parent_category_id as string
delete categoryDataClone.parent_category_id
if (parentCategoryId) {
parentCategory = await manager.findOne(ProductCategory, parentCategoryId)
}
const category = manager.create(ProductCategory, {
...categoryDataClone,
parent_category: parentCategory,
})
categories.push(category)
}
await manager.persistAndFlush(categories)
return categories
}

View File

@@ -0,0 +1,17 @@
export const categoriesData = [
{
id: "category-0",
name: "category 0",
parent_category_id: null,
},
{
id: "category-1",
name: "category 1",
parent_category_id: "category-0",
},
{
id: "category-1-a",
name: "category 1 a",
parent_category_id: "category-1",
},
]

View File

@@ -0,0 +1,83 @@
import { ProductTypes } from "@medusajs/types"
import { Image } from "@models"
import faker from "faker"
export const buildProductOnlyData = ({
title,
description,
subtitle,
is_giftcard,
discountable,
thumbnail,
images,
status,
}: {
title?: string
description?: string
subtitle?: string
is_giftcard?: boolean
discountable?: boolean
thumbnail?: string
images?: { id?: string; url: string }[]
status?: ProductTypes.ProductStatus
} = {}) => {
return {
title: title ?? faker.commerce.productName(),
description: description ?? faker.commerce.productName(),
subtitle: subtitle ?? faker.commerce.productName(),
is_giftcard: is_giftcard ?? false,
discountable: discountable ?? true,
thumbnail: thumbnail as string,
status: status ?? ProductTypes.ProductStatus.PUBLISHED,
images: (images ?? []) as Image[],
}
}
export const buildProductAndRelationsData = ({
title,
description,
subtitle,
is_giftcard,
discountable,
thumbnail,
images,
status,
type_id,
tags,
options,
variants,
collection_id,
}: Partial<ProductTypes.CreateProductDTO>) => {
const defaultOptionTitle = "test-option"
const defaultOptionValue = "test-value"
return {
title: title ?? faker.commerce.productName(),
description: description ?? faker.commerce.productName(),
subtitle: subtitle ?? faker.commerce.productName(),
is_giftcard: is_giftcard ?? false,
discountable: discountable ?? true,
thumbnail: thumbnail as string,
status: status ?? ProductTypes.ProductStatus.PUBLISHED,
images: (images ?? []) as Image[],
type_id,
tags: tags ?? [{ value: "tag-1" }],
collection_id,
options: options ?? [
{
title: defaultOptionTitle,
values: [defaultOptionValue],
},
],
variants: variants ?? [
{
title: faker.commerce.productName(),
sku: faker.commerce.productName(),
options: {
[defaultOptionTitle]: defaultOptionValue,
},
},
],
// TODO: add categories, must be created first
}
}

View File

@@ -0,0 +1,2 @@
export * from "./categories"
export * from "./products"

View File

@@ -0,0 +1,54 @@
import { ProductTypes } from "@medusajs/types"
export const productsData = [
{
id: "test-1",
title: "product 1",
status: ProductTypes.ProductStatus.PUBLISHED,
tags: [
{
id: "tag-1",
value: "France",
},
],
},
{
id: "test-2",
title: "product",
status: ProductTypes.ProductStatus.PUBLISHED,
tags: [
{
id: "tag-2",
value: "Germany",
},
],
},
{
id: "test-3",
title: "product 3",
status: ProductTypes.ProductStatus.PUBLISHED,
tags: [
{
id: "tag-3",
value: "Netherlands",
},
],
},
]
export const variantsData = [
{
id: "test-1",
title: "variant title",
sku: "sku 1",
product: { id: productsData[0].id },
inventory_quantity: 10,
},
{
id: "test-2",
title: "variant title",
sku: "sku 2",
product: { id: productsData[1].id },
inventory_quantity: 10,
},
]

View File

@@ -0,0 +1,146 @@
import { ProductTypes } from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import {
Image,
Product,
ProductCategory,
ProductCollection,
ProductType,
ProductVariant,
} from "@models"
import ProductOption from "../../../src/models/product-option"
export * from "./data/create-product"
export async function createProductAndTags(
manager: SqlEntityManager,
data: {
id?: string
title: string
status: ProductTypes.ProductStatus
tags?: { id: string; value: string }[]
collection_id?: string
}[]
) {
const products: any[] = data.map((productData) => {
return manager.create(Product, productData)
})
await manager.persistAndFlush(products)
return products
}
export async function createProductAndTypes(
manager: SqlEntityManager,
data: {
id?: string
title: string
status: ProductTypes.ProductStatus
type?: { id: string; value: string }
}[]
) {
const products: any[] = data.map((productData) => {
return manager.create(Product, productData)
})
await manager.persistAndFlush(products)
return products
}
export async function createProductVariants(
manager: SqlEntityManager,
data: any[]
) {
const variants: any[] = data.map((variantsData) => {
return manager.create(ProductVariant, variantsData)
})
await manager.persistAndFlush(variants)
return variants
}
export async function createCollections(
manager: SqlEntityManager,
collectionData: {
id?: string
title: string
handle?: string
}[]
) {
const collections: any[] = collectionData.map((collectionData) => {
return manager.create(ProductCollection, collectionData)
})
await manager.persistAndFlush(collections)
return collections
}
export async function createTypes(
manager: SqlEntityManager,
typesData: {
id?: string
value: string
}[]
) {
const types: any[] = typesData.map((typesData) => {
return manager.create(ProductType, typesData)
})
await manager.persistAndFlush(types)
return types
}
export async function createOptions(
manager: SqlEntityManager,
optionsData: {
id?: string
product: { id: string }
title: string
value?: string
values?: {
id?: string
value: string
variant?: { id: string } & any
}[]
variant?: { id: string } & any
}[]
) {
const options: any[] = optionsData.map((option) => {
return manager.create(ProductOption, option)
})
await manager.persistAndFlush(options)
return options
}
export async function createImages(
manager: SqlEntityManager,
imagesData: string[]
) {
const images: any[] = imagesData.map((img) => {
return manager.create(Image, { url: img })
})
await manager.persistAndFlush(images)
return images
}
export async function assignCategoriesToProduct(
manager: SqlEntityManager,
product: Product,
categories: ProductCategory[]
) {
product.categories.add(categories)
await manager.persistAndFlush(product)
return product
}

View File

@@ -0,0 +1,44 @@
import { ProductTypes } from "@medusajs/types"
import faker from "faker"
export const buildProductVariantOnlyData = ({
title,
sku,
barcode,
ean,
upc,
allow_backorder,
inventory_quantity,
manage_inventory,
hs_code,
origin_country,
mid_code,
material,
weight,
length,
height,
width,
options,
metadata,
}: Partial<ProductTypes.CreateProductVariantOnlyDTO>) => {
return {
title: title ?? faker.commerce.productName(),
sku: sku ?? faker.commerce.productName(),
barcode,
ean,
upc,
allow_backorder,
inventory_quantity,
manage_inventory,
hs_code,
origin_country,
mid_code,
material,
weight,
length,
height,
width,
options,
metadata,
}
}

View File

@@ -0,0 +1 @@
export * from "./data/create-variant"

View File

@@ -0,0 +1,370 @@
import { ProductCollectionService } from "@services"
import { createCollections } from "../../../__fixtures__/product"
import { Modules } from "@medusajs/modules-sdk"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import { IProductModuleService } from "@medusajs/types"
jest.setTimeout(30000)
moduleIntegrationTestRunner({
moduleName: Modules.PRODUCT,
testSuite: ({
MikroOrmWrapper,
medusaApp,
}: SuiteOptions<IProductModuleService>) => {
describe("Product collection Service", () => {
let service: ProductCollectionService
beforeEach(() => {
service = medusaApp.modules["productService"].productCollectionService_
})
describe("list", () => {
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 () => {
await createCollections(MikroOrmWrapper.forkManager(), data)
})
it("list product collections", async () => {
const productCollectionResults = await service.list()
expect(productCollectionResults).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("list product collections by id", async () => {
const productCollectionResults = await service.list({
id: data![0].id,
})
expect(productCollectionResults).toEqual([
expect.objectContaining({
id: "test-1",
title: "col 1",
}),
])
})
it("list product collections by title matching string", async () => {
const productCollectionResults = await service.list({
title: "col 3 extra",
})
expect(productCollectionResults).toEqual([
expect.objectContaining({
id: "test-3",
title: "col 3 extra",
}),
])
})
})
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 () => {
await createCollections(MikroOrmWrapper.forkManager(), 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",
handle: "col-2",
products: [],
},
])
})
})
describe("retrieve", () => {
const collectionData = {
id: "collection-1",
title: "collection 1",
}
beforeEach(async () => {
await createCollections(MikroOrmWrapper.forkManager(), [
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(
"productCollection - id 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,
handle: "collection-1",
})
})
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,
handle: "collection-1",
products: [],
})
})
})
describe("delete", () => {
const collectionId = "collection-1"
const collectionData = {
id: collectionId,
title: "collection 1",
}
beforeEach(async () => {
await createCollections(MikroOrmWrapper.forkManager(), [
collectionData,
])
})
it("should delete the product collection given an ID successfully", async () => {
await service.delete([collectionId])
const collections = await service.list({
id: collectionId,
})
expect(collections).toHaveLength(0)
})
})
describe("update", () => {
const collectionId = "collection-1"
const collectionData = {
id: collectionId,
title: "collection 1",
}
beforeEach(async () => {
await createCollections(MikroOrmWrapper.forkManager(), [
collectionData,
])
})
it("should update the value of the collection successfully", async () => {
await service.update([
{
id: collectionId,
title: "New Collection",
},
])
const productCollection = await service.retrieve(collectionId)
expect(productCollection.title).toEqual("New Collection")
})
it("should throw an error when an id does not exist", async () => {
let error
try {
await service.update([
{
id: "does-not-exist",
title: "New Collection",
},
])
} catch (e) {
error = e
}
expect(error.message).toEqual(
'ProductCollection with id "does-not-exist" not found'
)
})
})
describe("create", () => {
it("should create a collection successfully", async () => {
await service.create([
{
title: "New Collection",
},
])
const [productCollection] = await service.list({
title: "New Collection",
})
expect(productCollection.title).toEqual("New Collection")
})
})
})
},
})

View File

@@ -0,0 +1,614 @@
import { Modules } from "@medusajs/modules-sdk"
import { IProductModuleService, ProductTypes } from "@medusajs/types"
import { Product, ProductCategory } from "@models"
import {
MockEventBusService,
SuiteOptions,
moduleIntegrationTestRunner,
} from "medusa-test-utils"
import { createProductCategories } from "../../../__fixtures__/product-category"
import { productCategoriesRankData } from "../../../__fixtures__/product-category/data"
jest.setTimeout(30000)
moduleIntegrationTestRunner({
moduleName: Modules.PRODUCT,
injectedDependencies: {
eventBusModuleService: new MockEventBusService(),
},
testSuite: ({
MikroOrmWrapper,
service,
}: SuiteOptions<IProductModuleService>) => {
describe("ProductModuleService product categories", () => {
let productOne: Product
let productTwo: Product
let productCategoryOne: ProductCategory
let productCategoryTwo: ProductCategory
let productCategories: ProductCategory[]
beforeEach(async () => {
const testManager = await MikroOrmWrapper.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 () => {
jest.clearAllMocks()
})
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, {
select: ["id", "name"],
})
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"
)
})
})
describe("createCategory", () => {
it("should create a category successfully", async () => {
await service.createCategory({
name: "New Category",
parent_category_id: productCategoryOne.id,
})
const [productCategory] = await service.listCategories(
{
name: "New Category",
},
{
select: ["name", "rank"],
}
)
expect(productCategory).toEqual(
expect.objectContaining({
name: "New Category",
rank: "0",
})
)
})
it("should emit events through event bus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
const category = await service.createCategory({
name: "New Category",
parent_category_id: productCategoryOne.id,
})
expect(eventBusSpy).toHaveBeenCalledTimes(1)
expect(eventBusSpy).toHaveBeenCalledWith("product-category.created", {
id: category.id,
})
})
it("should append rank from an existing category depending on parent", async () => {
await service.createCategory({
name: "New Category",
parent_category_id: productCategoryOne.id,
rank: 0,
})
await service.createCategory({
name: "New Category 2",
parent_category_id: productCategoryOne.id,
})
const [productCategoryNew] = await service.listCategories(
{
name: "New Category 2",
},
{
select: ["name", "rank"],
}
)
expect(productCategoryNew).toEqual(
expect.objectContaining({
name: "New Category 2",
rank: "1",
})
)
await service.createCategory({
name: "New Category 2.1",
parent_category_id: productCategoryNew.id,
})
const [productCategoryWithParent] = await service.listCategories(
{
name: "New Category 2.1",
},
{
select: ["name", "rank", "parent_category_id"],
}
)
expect(productCategoryWithParent).toEqual(
expect.objectContaining({
name: "New Category 2.1",
parent_category_id: productCategoryNew.id,
rank: "0",
})
)
})
})
describe("updateCategory", () => {
let productCategoryZero
let productCategoryOne
let productCategoryTwo
let productCategoryZeroZero
let productCategoryZeroOne
let productCategoryZeroTwo
let categories
beforeEach(async () => {
const testManager = await MikroOrmWrapper.forkManager()
categories = await createProductCategories(
testManager,
productCategoriesRankData
)
productCategoryZero = categories[0]
productCategoryOne = categories[1]
productCategoryTwo = categories[2]
productCategoryZeroZero = categories[3]
productCategoryZeroOne = categories[4]
productCategoryZeroTwo = categories[5]
})
it("should emit events through event bus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
await service.updateCategory(productCategoryZero.id, {
name: "New Category",
})
expect(eventBusSpy).toHaveBeenCalledTimes(1)
expect(eventBusSpy).toHaveBeenCalledWith("product-category.updated", {
id: productCategoryZero.id,
})
})
it("should update the name of the category successfully", async () => {
await service.updateCategory(productCategoryZero.id, {
name: "New Category",
})
const productCategory = await service.retrieveCategory(
productCategoryZero.id,
{
select: ["name"],
}
)
expect(productCategory.name).toEqual("New Category")
})
it("should throw an error when an id does not exist", async () => {
let error
try {
await service.updateCategory("does-not-exist", {
name: "New Category",
})
} catch (e) {
error = e
}
expect(error.message).toEqual(
`ProductCategory not found ({ id: 'does-not-exist' })`
)
})
it("should reorder rank successfully in the same parent", async () => {
await service.updateCategory(productCategoryTwo.id, {
rank: 0,
})
const productCategories = await service.listCategories(
{
parent_category_id: null,
},
{
select: ["name", "rank"],
}
)
expect(productCategories).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: productCategoryTwo.id,
rank: "0",
}),
expect.objectContaining({
id: productCategoryZero.id,
rank: "1",
}),
expect.objectContaining({
id: productCategoryOne.id,
rank: "2",
}),
])
)
})
it("should reorder rank successfully when changing parent", async () => {
await service.updateCategory(productCategoryTwo.id, {
rank: 0,
parent_category_id: productCategoryZero.id,
})
const productCategories = await service.listCategories(
{
parent_category_id: productCategoryZero.id,
},
{
select: ["name", "rank"],
}
)
expect(productCategories).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: productCategoryTwo.id,
rank: "0",
}),
expect.objectContaining({
id: productCategoryZeroZero.id,
rank: "1",
}),
expect.objectContaining({
id: productCategoryZeroOne.id,
rank: "2",
}),
expect.objectContaining({
id: productCategoryZeroTwo.id,
rank: "3",
}),
])
)
})
it("should reorder rank successfully when changing parent and in first position", async () => {
await service.updateCategory(productCategoryTwo.id, {
rank: 0,
parent_category_id: productCategoryZero.id,
})
const productCategories = await service.listCategories(
{
parent_category_id: productCategoryZero.id,
},
{
select: ["name", "rank"],
}
)
expect(productCategories).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: productCategoryTwo.id,
rank: "0",
}),
expect.objectContaining({
id: productCategoryZeroZero.id,
rank: "1",
}),
expect.objectContaining({
id: productCategoryZeroOne.id,
rank: "2",
}),
expect.objectContaining({
id: productCategoryZeroTwo.id,
rank: "3",
}),
])
)
})
})
describe("deleteCategory", () => {
let productCategoryZero
let productCategoryOne
let productCategoryTwo
let categories
beforeEach(async () => {
const testManager = await MikroOrmWrapper.forkManager()
categories = await createProductCategories(
testManager,
productCategoriesRankData
)
productCategoryZero = categories[0]
productCategoryOne = categories[1]
productCategoryTwo = categories[2]
})
it("should emit events through event bus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
await service.deleteCategory(productCategoryOne.id)
expect(eventBusSpy).toHaveBeenCalledTimes(1)
expect(eventBusSpy).toHaveBeenCalledWith("product-category.deleted", {
id: productCategoryOne.id,
})
})
it("should throw an error when an id does not exist", async () => {
let error
try {
await service.deleteCategory("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual(
`ProductCategory not found ({ id: 'does-not-exist' })`
)
})
it("should throw an error when it has children", async () => {
let error
try {
await service.deleteCategory(productCategoryZero.id)
} catch (e) {
error = e
}
expect(error.message).toEqual(
`Deleting ProductCategory (category-0-0) with category children is not allowed`
)
})
it("should reorder siblings rank successfully on deleting", async () => {
await service.deleteCategory(productCategoryOne.id)
const productCategories = await service.listCategories(
{
parent_category_id: null,
},
{
select: ["id", "rank"],
}
)
expect(productCategories).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: productCategoryZero.id,
rank: "0",
}),
expect.objectContaining({
id: productCategoryTwo.id,
rank: "1",
}),
])
)
})
})
})
},
})

View File

@@ -0,0 +1,440 @@
import { IProductModuleService, ProductTypes } from "@medusajs/types"
import { Product, ProductCollection } from "@models"
import { MockEventBusService } from "medusa-test-utils"
import { createCollections } from "../../../__fixtures__/product"
import { Modules } from "@medusajs/modules-sdk"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
jest.setTimeout(30000)
moduleIntegrationTestRunner({
moduleName: Modules.PRODUCT,
injectedDependencies: {
eventBusModuleService: new MockEventBusService(),
},
testSuite: ({
MikroOrmWrapper,
service,
}: SuiteOptions<IProductModuleService>) => {
describe("ProductModuleService product collections", () => {
let productOne: Product
let productTwo: Product
let productCollectionOne: ProductCollection
let productCollectionTwo: ProductCollection
let productCollections: ProductCollection[]
beforeEach(async () => {
const testManager = await MikroOrmWrapper.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]
})
afterEach(async () => {
jest.clearAllMocks()
})
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"
)
})
})
describe("deleteCollections", () => {
const collectionId = "test-1"
it("should delete the product collection given an ID successfully", async () => {
await service.deleteCollections([collectionId])
const collections = await service.listCollections({
id: collectionId,
})
expect(collections).toHaveLength(0)
})
it("should emit events through event bus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
await service.deleteCollections([collectionId])
expect(eventBusSpy).toHaveBeenCalledTimes(1)
expect(eventBusSpy).toHaveBeenCalledWith([
{
eventName: "product-collection.deleted",
data: { id: collectionId },
},
])
})
})
describe("updateCollections", () => {
const collectionId = "test-1"
it("should emit events through event bus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
await service.upsertCollections([
{
id: collectionId,
title: "New Collection",
},
])
expect(eventBusSpy).toHaveBeenCalledTimes(1)
expect(eventBusSpy).toHaveBeenCalledWith([
{
eventName: "product-collection.updated",
data: { id: collectionId },
},
])
})
it("should update the value of the collection successfully", async () => {
await service.upsertCollections([
{
id: collectionId,
title: "New Collection",
},
])
const productCollection = await service.retrieveCollection(
collectionId
)
expect(productCollection.title).toEqual("New Collection")
})
it("should add products to a collection successfully", async () => {
await service.upsertCollections([
{
id: collectionId,
product_ids: [productOne.id, productTwo.id],
},
])
const productCollection = await service.retrieveCollection(
collectionId,
{
select: ["products.id"],
relations: ["products"],
}
)
expect(productCollection.products).toHaveLength(2)
expect(productCollection).toEqual(
expect.objectContaining({
products: expect.arrayContaining([
expect.objectContaining({
id: productOne.id,
}),
expect.objectContaining({
id: productTwo.id,
}),
]),
})
)
})
it("should throw an error when an id does not exist", async () => {
let error
try {
await service.updateCollections("does-not-exist", {
title: "New Collection",
})
} catch (e) {
error = e
}
expect(error.message).toEqual(
"ProductCollection with id: does-not-exist was not found"
)
})
})
describe("createCollections", () => {
it("should create a collection successfully", async () => {
const res = await service.createCollections([
{
title: "New Collection",
},
])
const [productCollection] = await service.listCollections({
title: "New Collection",
})
expect(productCollection.title).toEqual("New Collection")
})
it("should create collection with products successfully", async () => {
await service.createCollections([
{
title: "New Collection with products",
handle: "new-collection-with-products",
product_ids: [productOne.id, productTwo.id],
},
])
const [productCollection] = await service.listCollections(
{
handle: "new-collection-with-products",
},
{
select: ["title", "handle", "products.id"],
relations: ["products"],
}
)
expect(productCollection).toEqual(
expect.objectContaining({
title: "New Collection with products",
handle: "new-collection-with-products",
products: [
expect.objectContaining({
id: productOne.id,
}),
expect.objectContaining({
id: productTwo.id,
}),
],
})
)
})
it("should emit events through event bus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
const collections = await service.createCollections([
{
title: "New Collection",
},
])
expect(eventBusSpy).toHaveBeenCalledTimes(1)
expect(eventBusSpy).toHaveBeenCalledWith([
{
eventName: "product-collection.created",
data: { id: collections[0].id },
},
])
})
})
})
},
})

View File

@@ -0,0 +1,312 @@
import { IProductModuleService, ProductTypes } from "@medusajs/types"
import { Product, ProductOption } from "@models"
import { Modules } from "@medusajs/modules-sdk"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
jest.setTimeout(30000)
moduleIntegrationTestRunner({
moduleName: Modules.PRODUCT,
testSuite: ({
MikroOrmWrapper,
service,
}: SuiteOptions<IProductModuleService>) => {
describe("ProductModuleService product options", () => {
let optionOne: ProductOption
let optionTwo: ProductOption
let productOne: Product
let productTwo: Product
beforeEach(async () => {
const testManager = await MikroOrmWrapper.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,
})
optionOne = testManager.create(ProductOption, {
id: "option-1",
title: "option 1",
product: productOne,
})
optionTwo = testManager.create(ProductOption, {
id: "option-2",
title: "option 1",
product: productTwo,
})
await testManager.persistAndFlush([optionOne, optionTwo])
})
describe("listOptions", () => {
it("should return options and count queried by ID", async () => {
const options = await service.listOptions({
id: optionOne.id,
})
expect(options).toEqual([
expect.objectContaining({
id: optionOne.id,
}),
])
})
it("should return options and count based on the options and filter parameter", async () => {
let options = await service.listOptions(
{
id: optionOne.id,
},
{
take: 1,
}
)
expect(options).toEqual([
expect.objectContaining({
id: optionOne.id,
}),
])
options = await service.listOptions({}, { take: 1, skip: 1 })
expect(options).toEqual([
expect.objectContaining({
id: optionTwo.id,
}),
])
})
it("should return only requested fields and relations for options", async () => {
const options = await service.listOptions(
{
id: optionOne.id,
},
{
select: ["title", "product.id"],
relations: ["product"],
take: 1,
}
)
expect(options).toEqual([
{
id: optionOne.id,
title: optionOne.title,
product_id: productOne.id,
product: {
id: productOne.id,
type_id: null,
collection_id: null,
},
},
])
})
})
describe("listAndCountOptions", () => {
it("should return options and count queried by ID", async () => {
const [options, count] = await service.listAndCountOptions({
id: optionOne.id,
})
expect(count).toEqual(1)
expect(options).toEqual([
expect.objectContaining({
id: optionOne.id,
}),
])
})
it("should return options and count based on the options and filter parameter", async () => {
let [options, count] = await service.listAndCountOptions(
{
id: optionOne.id,
},
{
take: 1,
}
)
expect(count).toEqual(1)
expect(options).toEqual([
expect.objectContaining({
id: optionOne.id,
}),
])
;[options, count] = await service.listAndCountOptions({}, { take: 1 })
expect(count).toEqual(2)
;[options, count] = await service.listAndCountOptions(
{},
{ take: 1, skip: 1 }
)
expect(count).toEqual(2)
expect(options).toEqual([
expect.objectContaining({
id: optionTwo.id,
}),
])
})
it("should return only requested fields and relations for options", async () => {
const [options, count] = await service.listAndCountOptions(
{
id: optionOne.id,
},
{
select: ["title", "product.id"],
relations: ["product"],
take: 1,
}
)
expect(count).toEqual(1)
expect(options).toEqual([
{
id: optionOne.id,
title: optionOne.title,
product_id: productOne.id,
product: {
id: productOne.id,
type_id: null,
collection_id: null,
},
},
])
})
})
describe("retrieveOption", () => {
it("should return the requested option", async () => {
const option = await service.retrieveOption(optionOne.id)
expect(option).toEqual(
expect.objectContaining({
id: optionOne.id,
})
)
})
it("should return requested attributes when requested through config", async () => {
const option = await service.retrieveOption(optionOne.id, {
select: ["id", "product.title"],
relations: ["product"],
})
expect(option).toEqual(
expect.objectContaining({
id: optionOne.id,
product: {
id: "product-1",
handle: "product-1",
title: "product 1",
type_id: null,
collection_id: null,
},
product_id: "product-1",
})
)
})
it("should throw an error when a option with ID does not exist", async () => {
let error
try {
await service.retrieveOption("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual(
`ProductOption with id: does-not-exist was not found`
)
})
})
describe("deleteOptions", () => {
const optionId = "option-1"
it("should delete the product option given an ID successfully", async () => {
await service.deleteOptions([optionId])
const options = await service.listOptions({
id: optionId,
})
expect(options).toHaveLength(0)
})
})
describe("updateOptions", () => {
const optionId = "option-1"
it("should update the title of the option successfully", async () => {
await service.upsertOptions([
{
id: optionId,
title: "new test",
},
])
const productOption = await service.retrieveOption(optionId)
expect(productOption.title).toEqual("new test")
})
it("should throw an error when an id does not exist", async () => {
let error
try {
await service.updateOptions("does-not-exist", {})
} catch (e) {
error = e
}
expect(error.message).toEqual(
`ProductOption with id: does-not-exist was not found`
)
})
})
describe("createOptions", () => {
it("should create a option successfully", async () => {
const res = await service.createOptions([
{
title: "test",
values: [],
product_id: productOne.id,
},
])
const [productOption] = await service.listOptions(
{
title: "test",
},
{
select: ["id", "title", "product.id"],
relations: ["product"],
}
)
expect(productOption).toEqual(
expect.objectContaining({
title: "test",
product: expect.objectContaining({
id: productOne.id,
}),
})
)
})
})
})
},
})

View File

@@ -0,0 +1,302 @@
import { Modules } from "@medusajs/modules-sdk"
import { IProductModuleService, ProductTypes } from "@medusajs/types"
import { Product, ProductTag } from "@models"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
jest.setTimeout(30000)
moduleIntegrationTestRunner({
moduleName: Modules.PRODUCT,
testSuite: ({
MikroOrmWrapper,
service,
}: SuiteOptions<IProductModuleService>) => {
describe("ProductModuleService product tags", () => {
let tagOne: ProductTag
let tagTwo: ProductTag
let productOne: Product
let productTwo: Product
beforeEach(async () => {
const testManager = await MikroOrmWrapper.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,
})
tagOne = testManager.create(ProductTag, {
id: "tag-1",
value: "tag 1",
products: [productOne],
})
tagTwo = testManager.create(ProductTag, {
id: "tag-2",
value: "tag",
products: [productTwo],
})
await testManager.persistAndFlush([tagOne, tagTwo])
})
describe("listTags", () => {
it("should return tags and count queried by ID", async () => {
const tags = await service.listTags({
id: tagOne.id,
})
expect(tags).toEqual([
expect.objectContaining({
id: tagOne.id,
}),
])
})
it("should return tags and count based on the options and filter parameter", async () => {
let tags = await service.listTags(
{
id: tagOne.id,
},
{
take: 1,
}
)
expect(tags).toEqual([
expect.objectContaining({
id: tagOne.id,
}),
])
tags = await service.listTags({}, { take: 1, skip: 1 })
expect(tags).toEqual([
expect.objectContaining({
id: tagTwo.id,
}),
])
})
it("should return only requested fields and relations for tags", async () => {
const tags = await service.listTags(
{
id: tagOne.id,
},
{
select: ["value", "products.id"],
relations: ["products"],
take: 1,
}
)
expect(tags).toEqual([
{
id: tagOne.id,
value: tagOne.value,
products: [
{
collection_id: null,
type_id: null,
id: productOne.id,
},
],
},
])
})
})
describe("listAndCountTags", () => {
it("should return tags and count queried by ID", async () => {
const [tags, count] = await service.listAndCountTags({
id: tagOne.id,
})
expect(count).toEqual(1)
expect(tags).toEqual([
expect.objectContaining({
id: tagOne.id,
}),
])
})
it("should return tags and count based on the options and filter parameter", async () => {
let [tags, count] = await service.listAndCountTags(
{
id: tagOne.id,
},
{
take: 1,
}
)
expect(count).toEqual(1)
expect(tags).toEqual([
expect.objectContaining({
id: tagOne.id,
}),
])
;[tags, count] = await service.listAndCountTags({}, { take: 1 })
expect(count).toEqual(2)
;[tags, count] = await service.listAndCountTags(
{},
{ take: 1, skip: 1 }
)
expect(count).toEqual(2)
expect(tags).toEqual([
expect.objectContaining({
id: tagTwo.id,
}),
])
})
it("should return only requested fields and relations for tags", async () => {
const [tags, count] = await service.listAndCountTags(
{
id: tagOne.id,
},
{
select: ["value", "products.id"],
relations: ["products"],
take: 1,
}
)
expect(count).toEqual(1)
expect(tags).toEqual([
{
id: tagOne.id,
value: tagOne.value,
products: [
{
collection_id: null,
type_id: null,
id: productOne.id,
},
],
},
])
})
})
describe("retrieveTag", () => {
it("should return the requested tag", async () => {
const tag = await service.retrieveTag(tagOne.id)
expect(tag).toEqual(
expect.objectContaining({
id: tagOne.id,
})
)
})
it("should return requested attributes when requested through config", async () => {
const tag = await service.retrieveTag(tagOne.id, {
select: ["id", "value", "products.title"],
relations: ["products"],
})
expect(tag).toEqual(
expect.objectContaining({
id: tagOne.id,
value: tagOne.value,
products: [
expect.objectContaining({
title: "product 1",
}),
],
})
)
})
it("should throw an error when a tag with ID does not exist", async () => {
let error
try {
await service.retrieveTag("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual(
"ProductTag with id: does-not-exist was not found"
)
})
})
describe("deleteTags", () => {
const tagId = "tag-1"
it("should delete the product tag given an ID successfully", async () => {
await service.deleteTags([tagId])
const tags = await service.listTags({
id: tagId,
})
expect(tags).toHaveLength(0)
})
})
describe("updateTags", () => {
const tagId = "tag-1"
it("should update the value of the tag successfully", async () => {
await service.updateTags([
{
id: tagId,
value: "UK",
},
])
const productTag = await service.retrieveTag(tagId)
expect(productTag.value).toEqual("UK")
})
it("should throw an error when an id does not exist", async () => {
let error
try {
await service.updateTags([
{
id: "does-not-exist",
value: "UK",
},
])
} catch (e) {
error = e
}
expect(error.message).toEqual(
'ProductTag with id "does-not-exist" not found'
)
})
})
describe("createTags", () => {
it("should create a tag successfully", async () => {
const res = await service.createTags([
{
value: "UK",
},
])
const productTag = await service.listTags({
value: "UK",
})
expect(productTag[0]?.value).toEqual("UK")
})
})
})
},
})

View File

@@ -0,0 +1,257 @@
import { Modules } from "@medusajs/modules-sdk"
import { IProductModuleService } from "@medusajs/types"
import { ProductType } from "@models"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
jest.setTimeout(30000)
moduleIntegrationTestRunner({
moduleName: Modules.PRODUCT,
testSuite: ({
MikroOrmWrapper,
service,
}: SuiteOptions<IProductModuleService>) => {
describe("ProductModuleService product types", () => {
let typeOne: ProductType
let typeTwo: ProductType
beforeEach(async () => {
const testManager = await MikroOrmWrapper.forkManager()
typeOne = testManager.create(ProductType, {
id: "type-1",
value: "type 1",
})
typeTwo = testManager.create(ProductType, {
id: "type-2",
value: "type",
})
await testManager.persistAndFlush([typeOne, typeTwo])
})
describe("listTypes", () => {
it("should return types and count queried by ID", async () => {
const types = await service.listTypes({
id: typeOne.id,
})
expect(types).toEqual([
expect.objectContaining({
id: typeOne.id,
}),
])
})
it("should return types and count based on the options and filter parameter", async () => {
let types = await service.listTypes(
{
id: typeOne.id,
},
{
take: 1,
}
)
expect(types).toEqual([
expect.objectContaining({
id: typeOne.id,
}),
])
types = await service.listTypes({}, { take: 1, skip: 1 })
expect(types).toEqual([
expect.objectContaining({
id: typeTwo.id,
}),
])
})
it("should return only requested fields for types", async () => {
const types = await service.listTypes(
{
id: typeOne.id,
},
{
select: ["value"],
take: 1,
}
)
expect(types).toEqual([
{
id: typeOne.id,
value: typeOne.value,
},
])
})
})
describe("listAndCountTypes", () => {
it("should return types and count queried by ID", async () => {
const [types, count] = await service.listAndCountTypes({
id: typeOne.id,
})
expect(count).toEqual(1)
expect(types).toEqual([
expect.objectContaining({
id: typeOne.id,
}),
])
})
it("should return types and count based on the options and filter parameter", async () => {
let [types, count] = await service.listAndCountTypes(
{
id: typeOne.id,
},
{
take: 1,
}
)
expect(count).toEqual(1)
expect(types).toEqual([
expect.objectContaining({
id: typeOne.id,
}),
])
;[types, count] = await service.listAndCountTypes({}, { take: 1 })
expect(count).toEqual(2)
;[types, count] = await service.listAndCountTypes(
{},
{ take: 1, skip: 1 }
)
expect(count).toEqual(2)
expect(types).toEqual([
expect.objectContaining({
id: typeTwo.id,
}),
])
})
it("should return only requested fields for types", async () => {
const [types, count] = await service.listAndCountTypes(
{
id: typeOne.id,
},
{
select: ["value"],
take: 1,
}
)
expect(count).toEqual(1)
expect(types).toEqual([
{
id: typeOne.id,
value: typeOne.value,
},
])
})
})
describe("retrieveType", () => {
it("should return the requested type", async () => {
const type = await service.retrieveType(typeOne.id)
expect(type).toEqual(
expect.objectContaining({
id: typeOne.id,
})
)
})
it("should return requested attributes when requested through config", async () => {
const type = await service.retrieveType(typeOne.id, {
select: ["id", "value"],
})
expect(type).toEqual({
id: typeOne.id,
value: typeOne.value,
})
})
it("should throw an error when a type with ID does not exist", async () => {
let error
try {
await service.retrieveType("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual(
"ProductType with id: does-not-exist was not found"
)
})
})
describe("deleteTypes", () => {
const typeId = "type-1"
it("should delete the product type given an ID successfully", async () => {
await service.deleteTypes([typeId])
const types = await service.listTypes({
id: typeId,
})
expect(types).toHaveLength(0)
})
})
describe("updateTypes", () => {
const typeId = "type-1"
it("should update the value of the type successfully", async () => {
await service.updateTypes(typeId, {
value: "UK",
})
const productType = await service.retrieveType(typeId)
expect(productType.value).toEqual("UK")
})
it("should throw an error when an id does not exist", async () => {
let error
try {
await service.updateTypes("does-not-exist", {
value: "UK",
})
} catch (e) {
error = e
}
expect(error.message).toEqual(
"ProductType with id: does-not-exist was not found"
)
})
})
describe("createTypes", () => {
it("should create a type successfully", async () => {
const res = await service.createTypes([
{
value: "UK",
},
])
const productType = await service.listTypes({
value: "UK",
})
expect(productType[0]?.value).toEqual("UK")
})
})
})
},
})

View File

@@ -0,0 +1,273 @@
import { Modules } from "@medusajs/modules-sdk"
import { IProductModuleService, ProductTypes } from "@medusajs/types"
import { Product, ProductVariant } from "@models"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
jest.setTimeout(30000)
moduleIntegrationTestRunner({
moduleName: Modules.PRODUCT,
testSuite: ({
MikroOrmWrapper,
service,
}: SuiteOptions<IProductModuleService>) => {
describe("ProductModuleService product variants", () => {
let variantOne: ProductVariant
let variantTwo: ProductVariant
let productOne: Product
let productTwo: Product
beforeEach(async () => {
productOne = await service.create({
id: "product-1",
title: "product 1",
status: ProductTypes.ProductStatus.PUBLISHED,
options: [
{
title: "size",
values: ["large", "small"],
},
{
title: "color",
values: ["red", "blue"],
},
],
})
productTwo = await service.create({
id: "product-2",
title: "product 2",
status: ProductTypes.ProductStatus.PUBLISHED,
})
variantOne = await service.createVariants({
id: "test-1",
title: "variant 1",
inventory_quantity: 10,
product_id: productOne.id,
options: { size: "large" },
})
variantTwo = await service.createVariants({
id: "test-2",
title: "variant",
inventory_quantity: 10,
product_id: productTwo.id,
})
})
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",
// 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: 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"
)
})
})
describe("updateVariants", () => {
it("should update the title of the variant successfully", async () => {
await service.upsertVariants([
{
id: variantOne.id,
title: "new test",
},
])
const productVariant = await service.retrieveVariant(variantOne.id)
expect(productVariant.title).toEqual("new test")
})
it("should upsert the options of a variant successfully", async () => {
await service.upsertVariants([
{
id: variantOne.id,
options: { size: "small" },
},
])
const productVariant = await service.retrieveVariant(variantOne.id, {
relations: ["options"],
})
expect(productVariant.options).toEqual(
expect.arrayContaining([
expect.objectContaining({
value: "small",
}),
])
)
})
it("should do a partial update on the options of a variant successfully", async () => {
await service.updateVariants(variantOne.id, {
options: { size: "small", color: "red" },
})
const productVariant = await service.retrieveVariant(variantOne.id, {
relations: ["options"],
})
expect(productVariant.options).toEqual(
expect.arrayContaining([
expect.objectContaining({
value: "small",
}),
expect.objectContaining({
value: "red",
}),
])
)
})
it("should throw an error when an id does not exist", async () => {
let error
try {
await service.updateVariants("does-not-exist", {})
} catch (e) {
error = e
}
expect(error.message).toEqual(
`Cannot update non-existing variants with ids: does-not-exist`
)
})
})
describe("softDelete variant", () => {
it("should soft delete a variant and its relations", async () => {
const beforeDeletedVariants = await service.listVariants(
{ id: variantOne.id },
{
relations: ["options"],
}
)
await service.softDeleteVariants([variantOne.id])
const deletedVariants = await service.listVariants(
{ id: variantOne.id },
{
relations: ["options"],
withDeleted: true,
}
)
expect(deletedVariants).toHaveLength(1)
expect(deletedVariants[0].deleted_at).not.toBeNull()
for (const variantOption of deletedVariants[0].options) {
expect(variantOption?.deleted_at).toBeNull()
}
})
})
})
},
})

View File

@@ -0,0 +1,869 @@
import { Modules } from "@medusajs/modules-sdk"
import { IProductModuleService, ProductTypes } from "@medusajs/types"
import { kebabCase } from "@medusajs/utils"
import {
Product,
ProductCategory,
ProductCollection,
ProductType,
ProductVariant,
} from "@models"
import {
MockEventBusService,
moduleIntegrationTestRunner,
SuiteOptions,
} from "medusa-test-utils"
import { createCollections, createTypes } from "../../../__fixtures__/product"
import { createProductCategories } from "../../../__fixtures__/product-category"
import { buildProductAndRelationsData } from "../../../__fixtures__/product/data/create-product"
import { UpdateProductInput } from "@types"
jest.setTimeout(300000)
moduleIntegrationTestRunner({
moduleName: Modules.PRODUCT,
injectedDependencies: {
eventBusModuleService: new MockEventBusService(),
},
testSuite: ({
MikroOrmWrapper,
service,
}: SuiteOptions<IProductModuleService>) => {
describe("ProductModuleService products", function () {
let productCollectionOne: ProductCollection
let productCollectionTwo: ProductCollection
const productCollectionsData = [
{
id: "test-1",
title: "col 1",
},
{
id: "test-2",
title: "col 2",
},
]
afterEach(() => {
jest.clearAllMocks()
})
describe("update", function () {
let productOne: Product
let productTwo: Product
let productCategoryOne: ProductCategory
let productCategoryTwo: ProductCategory
let productTypeOne: ProductType
let productTypeTwo: ProductType
let images = [{ url: "image-1" }]
const productCategoriesData = [
{
id: "test-1",
name: "category 1",
},
{
id: "test-2",
name: "category 2",
},
]
const productTypesData = [
{
id: "type-1",
value: "type 1",
},
{
id: "type-2",
value: "type 2",
},
]
const tagsData = [
{
id: "tag-1",
value: "tag 1",
},
]
beforeEach(async () => {
const testManager = MikroOrmWrapper.forkManager()
const collections = await createCollections(
testManager,
productCollectionsData
)
productCollectionOne = collections[0]
productCollectionTwo = collections[1]
const types = await createTypes(testManager, productTypesData)
productTypeOne = types[0]
productTypeTwo = types[1]
const categories = await createProductCategories(
testManager,
productCategoriesData
)
productCategoryOne = categories[0]
productCategoryTwo = categories[1]
productOne = service.create({
id: "product-1",
title: "product 1",
status: ProductTypes.ProductStatus.PUBLISHED,
variants: [
{
id: "variant-1",
title: "variant 1",
inventory_quantity: 10,
},
],
})
productTwo = service.create({
id: "product-2",
title: "product 2",
status: ProductTypes.ProductStatus.PUBLISHED,
categories: [{ id: productCategoryOne.id }],
collection_id: productCollectionOne.id,
tags: tagsData,
options: [
{
title: "size",
values: ["large", "small"],
},
{
title: "color",
values: ["red", "blue"],
},
],
variants: [
{
id: "variant-2",
title: "variant 2",
inventory_quantity: 10,
},
{
id: "variant-3",
title: "variant 3",
inventory_quantity: 10,
options: {
size: "small",
color: "red",
},
},
],
})
const res = await Promise.all([productOne, productTwo])
productOne = res[0]
productTwo = res[1]
})
it("should update a product and upsert relations that are not created yet", async () => {
const data = buildProductAndRelationsData({
images,
thumbnail: images[0].url,
})
const variantTitle = data.variants[0].title
const productBefore = (await service.retrieve(productOne.id, {
relations: [
"images",
"variants",
"options",
"options.values",
"variants.options",
"tags",
"type",
],
})) as unknown as UpdateProductInput
productBefore.title = "updated title"
productBefore.variants = [
...productBefore.variants!,
...data.variants,
]
productBefore.options = data.options
productBefore.images = data.images
productBefore.thumbnail = data.thumbnail
productBefore.tags = data.tags
const updatedProducts = await service.upsert([productBefore])
expect(updatedProducts).toHaveLength(1)
const product = await service.retrieve(productBefore.id, {
relations: [
"images",
"variants",
"options",
"options.values",
"variants.options",
"tags",
"type",
],
})
const createdVariant = product.variants.find(
(v) => v.title === variantTitle
)!
expect(product.images).toHaveLength(1)
expect(createdVariant?.options).toHaveLength(1)
expect(product.tags).toHaveLength(1)
expect(product.variants).toHaveLength(2)
expect(product).toEqual(
expect.objectContaining({
id: expect.any(String),
title: "updated title",
description: productBefore.description,
subtitle: productBefore.subtitle,
is_giftcard: productBefore.is_giftcard,
discountable: productBefore.discountable,
thumbnail: images[0].url,
status: productBefore.status,
images: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
url: images[0].url,
}),
]),
options: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
title: productBefore.options?.[0].title,
values: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
value: data.options[0].values[0],
}),
]),
}),
]),
tags: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
value: productBefore.tags?.[0].value,
}),
]),
variants: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
title: createdVariant.title,
sku: createdVariant.sku,
allow_backorder: false,
manage_inventory: true,
inventory_quantity: 100,
variant_rank: 0,
options: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
value: data.options[0].values[0],
}),
]),
}),
]),
})
)
})
it("should emit events through event bus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
const data = buildProductAndRelationsData({
images,
thumbnail: images[0].url,
})
const updateData = {
...data,
options: data.options,
id: productOne.id,
title: "updated title",
}
await service.upsert([updateData])
expect(eventBusSpy).toHaveBeenCalledTimes(1)
expect(eventBusSpy).toHaveBeenCalledWith([
{
eventName: "product.updated",
data: { id: productOne.id },
},
])
})
it("should add relationships to a product", async () => {
const updateData = {
id: productOne.id,
categories: [
{
id: productCategoryOne.id,
},
],
collection_id: productCollectionOne.id,
type_id: productTypeOne.id,
}
await service.upsert([updateData])
const product = await service.retrieve(updateData.id, {
relations: ["categories", "collection", "type"],
})
expect(product).toEqual(
expect.objectContaining({
id: productOne.id,
categories: [
expect.objectContaining({
id: productCategoryOne.id,
}),
],
collection: expect.objectContaining({
id: productCollectionOne.id,
}),
type: expect.objectContaining({
id: productTypeOne.id,
}),
})
)
})
it("should upsert a product type when type object is passed", async () => {
let updateData = {
id: productTwo.id,
type_id: productTypeOne.id,
}
await service.upsert([updateData])
let product = await service.retrieve(updateData.id, {
relations: ["type"],
})
expect(product).toEqual(
expect.objectContaining({
id: productTwo.id,
type: expect.objectContaining({
id: productTypeOne.id,
}),
})
)
})
it("should replace relationships of a product", async () => {
const newTagData = {
id: "tag-2",
value: "tag 2",
}
const updateData = {
id: productTwo.id,
categories: [
{
id: productCategoryTwo.id,
},
],
collection_id: productCollectionTwo.id,
type_id: productTypeTwo.id,
tags: [newTagData],
}
await service.upsert([updateData])
const product = await service.retrieve(updateData.id, {
relations: ["categories", "collection", "tags", "type"],
})
expect(product).toEqual(
expect.objectContaining({
id: productTwo.id,
categories: [
expect.objectContaining({
id: productCategoryTwo.id,
}),
],
collection: expect.objectContaining({
id: productCollectionTwo.id,
}),
tags: [
expect.objectContaining({
id: newTagData.id,
value: newTagData.value,
}),
],
type: expect.objectContaining({
id: productTypeTwo.id,
}),
})
)
})
it("should remove relationships of a product", async () => {
const updateData = {
id: productTwo.id,
categories: [],
collection_id: null,
type_id: null,
tags: [],
}
await service.upsert([updateData])
const product = await service.retrieve(updateData.id, {
relations: ["categories", "collection", "tags"],
})
expect(product).toEqual(
expect.objectContaining({
id: productTwo.id,
categories: [],
tags: [],
collection: null,
type: null,
})
)
})
it("should throw an error when product ID does not exist", async () => {
let error
try {
await service.update("does-not-exist", { title: "test" })
} catch (e) {
error = e.message
}
expect(error).toEqual(`Product with id: does-not-exist was not found`)
})
it("should update, create and delete variants", async () => {
const updateData = {
id: productTwo.id,
// Note: VariantThree is already assigned to productTwo, that should be deleted
variants: [
{
id: productTwo.variants[0].id,
title: "updated-variant",
},
{
title: "created-variant",
},
],
}
await service.upsert([updateData])
const product = await service.retrieve(updateData.id, {
relations: ["variants"],
})
expect(product.variants).toHaveLength(2)
expect(product).toEqual(
expect.objectContaining({
id: expect.any(String),
variants: expect.arrayContaining([
expect.objectContaining({
id: productTwo.variants[0].id,
title: "updated-variant",
}),
expect.objectContaining({
id: expect.any(String),
title: "created-variant",
}),
]),
})
)
})
it("should do a partial update on the options of a variant successfully", async () => {
await service.update(productTwo.id, {
variants: [
{
id: "variant-3",
options: { size: "small", color: "blue" },
},
],
})
const fetchedProduct = await service.retrieve(productTwo.id, {
relations: ["variants", "variants.options"],
})
expect(fetchedProduct.variants[0].options).toEqual(
expect.arrayContaining([
expect.objectContaining({
value: "small",
}),
expect.objectContaining({
value: "blue",
}),
])
)
})
it("should createa variant with id that was passed if it does not exist", async () => {
const updateData = {
id: productTwo.id,
// Note: VariantThree is already assigned to productTwo, that should be deleted
variants: [
{
id: "passed-id",
title: "updated-variant",
},
{
title: "created-variant",
},
],
}
await service.upsert([updateData])
const retrieved = await service.retrieve(updateData.id, {
relations: ["variants"],
})
expect(retrieved.variants).toHaveLength(2)
expect(retrieved.variants).toEqual(
expect.arrayContaining([
expect.objectContaining({
id: "passed-id",
title: "updated-variant",
}),
expect.objectContaining({
id: expect.any(String),
title: "created-variant",
}),
])
)
})
})
describe("create", function () {
let images = [{ url: "image-1" }]
it("should create a product", async () => {
const data = buildProductAndRelationsData({
images,
thumbnail: images[0].url,
})
const productsCreated = await service.create([data])
const products = await service.list(
{ id: productsCreated[0].id },
{
relations: [
"images",
"categories",
"variants",
"variants.options",
"options",
"options.values",
"tags",
],
}
)
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].url,
status: data.status,
images: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
url: images[0].url,
}),
]),
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.options[0].values[0],
}),
]),
}),
]),
tags: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
value: data.tags[0].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.options[0].values[0],
}),
]),
}),
]),
})
)
})
it("should emit events through eventBus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
const data = buildProductAndRelationsData({
images,
thumbnail: images[0].url,
})
const products = await service.create([data])
expect(eventBusSpy).toHaveBeenCalledTimes(1)
expect(eventBusSpy).toHaveBeenCalledWith([
{
eventName: "product.created",
data: { id: products[0].id },
},
])
})
})
describe("softDelete", function () {
let images = [{ url: "image-1" }]
it("should soft delete a product and its cascaded relations", async () => {
const data = buildProductAndRelationsData({
images,
thumbnail: images[0].url,
})
const products = await service.create([data])
await service.softDelete([products[0].id])
const deletedProducts = await service.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()
}
})
it("should retrieve soft-deleted products if filtered on deleted_at", async () => {
const data = buildProductAndRelationsData({
images,
thumbnail: images[0].url,
})
const products = await service.create([data])
await service.softDelete([products[0].id])
const softDeleted = await service.list({
deleted_at: { $gt: "01-01-2022" },
})
expect(softDeleted).toHaveLength(1)
})
it("should emit events through eventBus", async () => {
const eventBusSpy = jest.spyOn(MockEventBusService.prototype, "emit")
const data = buildProductAndRelationsData({
images,
thumbnail: images[0].url,
})
const products = await service.create([data])
await service.softDelete([products[0].id])
expect(eventBusSpy).toHaveBeenCalledWith([
{
eventName: "product.created",
data: { id: products[0].id },
},
])
})
})
describe("restore", function () {
let images = [{ url: "image-1" }]
it("should restore a soft deleted product and its cascaded relations", async () => {
const data = buildProductAndRelationsData({
images,
thumbnail: images[0].url,
})
const products = await service.create([data])
let retrievedProducts = await service.list({ id: products[0].id })
expect(retrievedProducts).toHaveLength(1)
expect(retrievedProducts[0].deleted_at).toBeNull()
await service.softDelete([products[0].id])
retrievedProducts = await service.list(
{ id: products[0].id },
{
withDeleted: true,
}
)
expect(retrievedProducts).toHaveLength(1)
expect(retrievedProducts[0].deleted_at).not.toBeNull()
await service.restore([products[0].id])
const deletedProducts = await service.list(
{ id: products[0].id },
{
relations: [
"variants",
"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()
}
})
})
describe("list", function () {
beforeEach(async () => {
const collections = await createCollections(
MikroOrmWrapper.forkManager(),
productCollectionsData
)
productCollectionOne = collections[0]
productCollectionTwo = collections[1]
const productOneData = buildProductAndRelationsData({
collection_id: productCollectionOne.id,
})
const productTwoData = buildProductAndRelationsData({
collection_id: productCollectionTwo.id,
tags: [],
})
await service.create([productOneData, productTwoData])
})
it("should return a list of products scoped by collection id", async () => {
const productsWithCollectionOne = await service.list(
{ collection_id: productCollectionOne.id },
{
relations: ["collection"],
}
)
expect(productsWithCollectionOne).toHaveLength(1)
expect([
expect.objectContaining({
collection: expect.objectContaining({
id: productCollectionOne.id,
}),
}),
])
})
it("should return empty array when querying for a collection that doesnt exist", async () => {
const products = await service.list(
{
categories: { id: ["collection-doesnt-exist-id"] },
},
{
select: ["title", "collection.title"],
relations: ["collection"],
}
)
expect(products).toEqual([])
})
})
})
},
})

View File

@@ -0,0 +1,371 @@
import { ProductOptionService } from "@services"
import { Product } from "@models"
import { createOptions } from "../../../__fixtures__/product"
import { ProductTypes } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import { IProductModuleService } from "@medusajs/types"
jest.setTimeout(30000)
moduleIntegrationTestRunner({
moduleName: Modules.PRODUCT,
testSuite: ({
MikroOrmWrapper,
medusaApp,
}: SuiteOptions<IProductModuleService>) => {
describe("ProductOption Service", () => {
let service: ProductOptionService
beforeEach(() => {
service = medusaApp.modules["productService"].productOptionService_
})
let productOne: Product
let productTwo: Product
const productOneData = {
id: "product-1",
title: "product 1",
status: ProductTypes.ProductStatus.PUBLISHED,
}
const productTwoData = {
id: "product-2",
title: "product 2",
status: ProductTypes.ProductStatus.PUBLISHED,
}
beforeEach(async () => {
const testManager = MikroOrmWrapper.forkManager()
productOne = testManager.create(Product, productOneData)
productTwo = testManager.create(Product, productTwoData)
await createOptions(testManager, [
{
id: "option-1",
title: "Option 1",
product: productOne,
},
{
id: "option-2",
title: "Option 2",
product: productOne,
},
])
})
describe("list", () => {
it("list product option", async () => {
const optionResults = await service.list()
expect(optionResults).toEqual([
expect.objectContaining({
id: "option-1",
title: "Option 1",
}),
expect.objectContaining({
id: "option-2",
title: "Option 2",
}),
])
})
it("list product option by id", async () => {
const optionResults = await service.list({ id: "option-2" })
expect(optionResults).toEqual([
expect.objectContaining({
id: "option-2",
title: "Option 2",
}),
])
})
it("list product option by title matching string", async () => {
const optionResults = await service.list({ title: "Option 1" })
expect(optionResults).toEqual([
expect.objectContaining({
id: "option-1",
title: "Option 1",
}),
])
})
})
describe("listAndCount", () => {
it("should return product option and count", async () => {
const [optionResults, count] = await service.listAndCount()
expect(count).toEqual(2)
expect(optionResults).toEqual([
expect.objectContaining({
id: "option-1",
title: "Option 1",
}),
expect.objectContaining({
id: "option-2",
title: "Option 2",
}),
])
})
it("should return product option and count when filtered", async () => {
const [optionResults, count] = await service.listAndCount({
id: "option-2",
})
expect(count).toEqual(1)
expect(optionResults).toEqual([
expect.objectContaining({
id: "option-2",
}),
])
})
it("should return product option and count when using skip and take", async () => {
const [optionResults, count] = await service.listAndCount(
{},
{ skip: 1, take: 1 }
)
expect(count).toEqual(2)
expect(optionResults).toEqual([
expect.objectContaining({
id: "option-2",
}),
])
})
it("should return requested fields", async () => {
const [optionResults, count] = await service.listAndCount(
{},
{
take: 1,
select: ["title"],
}
)
const serialized = JSON.parse(JSON.stringify(optionResults))
expect(count).toEqual(2)
expect(serialized).toEqual([
expect.objectContaining({
id: "option-1",
}),
])
})
})
describe("retrieve", () => {
const optionId = "option-1"
const optionValue = "Option 1"
it("should return option for the given id", async () => {
const option = await service.retrieve(optionId)
expect(option).toEqual(
expect.objectContaining({
id: optionId,
})
)
})
it("should throw an error when option with id does not exist", async () => {
let error
try {
await service.retrieve("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual(
"ProductOption 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("productOption - id must be defined")
})
it("should return option based on config select param", async () => {
const option = await service.retrieve(optionId, {
select: ["id", "title"],
})
const serialized = JSON.parse(JSON.stringify(option))
expect(serialized).toEqual({
id: optionId,
title: optionValue,
product_id: null,
})
})
})
describe("delete", () => {
const optionId = "option-1"
it("should delete the product option given an ID successfully", async () => {
await service.delete([optionId])
const options = await service.list({
id: optionId,
})
expect(options).toHaveLength(0)
})
})
describe("update", () => {
const optionId = "option-1"
it("should update the title of the option successfully", async () => {
await service.update([
{
id: optionId,
title: "UK",
},
])
const productOption = await service.retrieve(optionId)
expect(productOption.title).toEqual("UK")
})
it("should update the relationship of the option successfully", async () => {
await service.update([
{
id: optionId,
product_id: productTwo.id,
},
])
const productOption = await service.retrieve(optionId, {
relations: ["product"],
})
expect(productOption).toEqual(
expect.objectContaining({
id: optionId,
product: expect.objectContaining({
id: productTwo.id,
}),
})
)
})
it("should throw an error when an id does not exist", async () => {
let error
try {
await service.update([
{
id: "does-not-exist",
title: "UK",
},
])
} catch (e) {
error = e
}
expect(error.message).toEqual(
'ProductOption with id "does-not-exist" not found'
)
})
})
describe("create", () => {
it("should create a option successfully", async () => {
await service.create([
{
title: "UK",
product: productOne,
},
])
const [productOption] = await service.list(
{
title: "UK",
},
{
relations: ["product"],
}
)
expect(productOption).toEqual(
expect.objectContaining({
title: "UK",
product: expect.objectContaining({
id: productOne.id,
}),
})
)
})
})
describe("upsert", function () {
it("should create an option and update another option successfully", async () => {
const productOption = (
await service.create([
{
title: "UK",
product: productOne,
},
])
)[0]
const optionToUpdate = {
id: productOption.id,
title: "US",
}
const newOption = {
title: "US2",
product_id: productOne.id,
}
await service.upsert([optionToUpdate, newOption])
const productOptions = await service.list(
{
q: "US%",
},
{
relations: ["product"],
}
)
expect(JSON.parse(JSON.stringify(productOptions))).toEqual(
expect.arrayContaining([
expect.objectContaining({
title: "US",
product: expect.objectContaining({
id: productOne.id,
}),
}),
expect.objectContaining({
title: newOption.title,
product: expect.objectContaining({
id: productOne.id,
}),
}),
])
)
})
})
})
},
})

View File

@@ -0,0 +1,338 @@
import { Product } from "@models"
import { ProductTagService } from "@services"
import { ProductTypes } from "@medusajs/types"
import { createProductAndTags } from "../../../__fixtures__/product"
import { Modules } from "@medusajs/modules-sdk"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import { IProductModuleService } from "@medusajs/types"
jest.setTimeout(30000)
moduleIntegrationTestRunner({
moduleName: Modules.PRODUCT,
testSuite: ({
MikroOrmWrapper,
medusaApp,
}: SuiteOptions<IProductModuleService>) => {
describe("ProductTag Service", () => {
let data!: Product[]
let service: ProductTagService
beforeEach(() => {
service = medusaApp.modules["productService"].productTagService_
})
const productsData = [
{
id: "test-1",
title: "product 1",
status: ProductTypes.ProductStatus.PUBLISHED,
tags: [
{
id: "tag-1",
value: "France",
},
],
},
{
id: "test-2",
title: "product",
status: ProductTypes.ProductStatus.PUBLISHED,
tags: [
{
id: "tag-2",
value: "Germany",
},
{
id: "tag-3",
value: "United States",
},
{
id: "tag-4",
value: "United Kingdom",
},
],
},
]
beforeEach(async () => {
data = await createProductAndTags(
MikroOrmWrapper.forkManager(),
productsData
)
})
describe("list", () => {
it("list product tags", async () => {
const tagsResults = await service.list()
expect(tagsResults).toEqual([
expect.objectContaining({
id: "tag-1",
value: "France",
}),
expect.objectContaining({
id: "tag-2",
value: "Germany",
}),
expect.objectContaining({
id: "tag-3",
value: "United States",
}),
expect.objectContaining({
id: "tag-4",
value: "United Kingdom",
}),
])
})
it("list product tags by id", async () => {
const tagsResults = await service.list({ id: data[0].tags![0].id })
expect(tagsResults).toEqual([
expect.objectContaining({
id: "tag-1",
value: "France",
}),
])
})
it("list product tags by value matching string", async () => {
const tagsResults = await service.list({ q: "united kingdom" })
expect(tagsResults).toEqual([
expect.objectContaining({
id: "tag-4",
value: "United Kingdom",
}),
])
})
})
describe("listAndCount", () => {
it("should return product tags and count", async () => {
const [tagsResults, count] = await service.listAndCount()
expect(count).toEqual(4)
expect(tagsResults).toEqual([
expect.objectContaining({
id: "tag-1",
value: "France",
}),
expect.objectContaining({
id: "tag-2",
value: "Germany",
}),
expect.objectContaining({
id: "tag-3",
value: "United States",
}),
expect.objectContaining({
id: "tag-4",
value: "United Kingdom",
}),
])
})
it("should return product tags and count when filtered", async () => {
const [tagsResults, count] = await service.listAndCount({
id: data[0].tags![0].id,
})
expect(count).toEqual(1)
expect(tagsResults).toEqual([
expect.objectContaining({
id: "tag-1",
}),
])
})
it("should return product tags and count when using skip and take", async () => {
const [tagsResults, count] = await service.listAndCount(
{},
{ skip: 1, take: 2 }
)
expect(count).toEqual(4)
expect(tagsResults).toEqual([
expect.objectContaining({
id: "tag-2",
}),
expect.objectContaining({
id: "tag-3",
}),
])
})
it("should return requested fields and relations", async () => {
const [tagsResults, count] = await service.listAndCount(
{},
{
take: 1,
select: ["value", "products.id"],
relations: ["products"],
}
)
const serialized = JSON.parse(JSON.stringify(tagsResults))
expect(count).toEqual(4)
expect(serialized).toEqual([
expect.objectContaining({
id: "tag-1",
products: [
{
id: "test-1",
collection_id: null,
type_id: null,
},
],
value: "France",
}),
])
})
})
describe("retrieve", () => {
const tagId = "tag-1"
const tagValue = "France"
const productId = "test-1"
it("should return tag for the given id", async () => {
const tag = await service.retrieve(tagId)
expect(tag).toEqual(
expect.objectContaining({
id: tagId,
})
)
})
it("should throw an error when tag with id does not exist", async () => {
let error
try {
await service.retrieve("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual(
"ProductTag 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("productTag - id must be defined")
})
it("should return tag based on config select param", async () => {
const tag = await service.retrieve(tagId, {
select: ["id", "value"],
})
const serialized = JSON.parse(JSON.stringify(tag))
expect(serialized).toEqual({
id: tagId,
value: tagValue,
})
})
it("should return tag based on config relation param", async () => {
const tag = await service.retrieve(tagId, {
select: ["id", "value", "products.id"],
relations: ["products"],
})
const serialized = JSON.parse(JSON.stringify(tag))
expect(serialized).toEqual({
id: tagId,
value: tagValue,
products: [
expect.objectContaining({
id: productId,
}),
],
})
})
})
describe("delete", () => {
const tagId = "tag-1"
it("should delete the product tag given an ID successfully", async () => {
await service.delete([tagId])
const tags = await service.list({
id: tagId,
})
expect(tags).toHaveLength(0)
})
})
describe("update", () => {
const tagId = "tag-1"
it("should update the value of the tag successfully", async () => {
await service.update([
{
id: tagId,
value: "UK",
},
])
const productTag = await service.retrieve(tagId)
expect(productTag.value).toEqual("UK")
})
it("should throw an error when an id does not exist", async () => {
let error
try {
await service.update([
{
id: "does-not-exist",
value: "UK",
},
])
} catch (e) {
error = e
}
expect(error.message).toEqual(
'ProductTag with id "does-not-exist" not found'
)
})
})
describe("create", () => {
it("should create a tag successfully", async () => {
await service.create([
{
value: "UK",
},
])
const [productTag] = await service.list({
value: "UK",
})
expect(productTag.value).toEqual("UK")
})
})
})
},
})

View File

@@ -0,0 +1,277 @@
import { ProductTypeService } from "@services"
import { Product } from "@models"
import { createProductAndTypes } from "../../../__fixtures__/product"
import { ProductTypes } from "@medusajs/types"
import { Modules } from "@medusajs/modules-sdk"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import { IProductModuleService } from "@medusajs/types"
jest.setTimeout(30000)
moduleIntegrationTestRunner({
moduleName: Modules.PRODUCT,
testSuite: ({
MikroOrmWrapper,
medusaApp,
}: SuiteOptions<IProductModuleService>) => {
describe("ProductType Service", () => {
let data!: Product[]
let service: ProductTypeService
beforeEach(() => {
service = medusaApp.modules["productService"].productTypeService_
})
const productsData = [
{
id: "product-1",
title: "product 1",
status: ProductTypes.ProductStatus.PUBLISHED,
type: {
id: "type-1",
value: "Type 1",
},
},
{
id: "product-2",
title: "product",
status: ProductTypes.ProductStatus.PUBLISHED,
type: {
id: "type-2",
value: "Type 2",
},
},
]
beforeEach(async () => {
data = await createProductAndTypes(
MikroOrmWrapper.forkManager(),
productsData
)
})
describe("list", () => {
it("list product type", async () => {
const typeResults = await service.list()
expect(typeResults).toEqual([
expect.objectContaining({
id: "type-1",
value: "Type 1",
}),
expect.objectContaining({
id: "type-2",
value: "Type 2",
}),
])
})
it("list product type by id", async () => {
const typeResults = await service.list({ id: data[0].type.id })
expect(typeResults).toEqual([
expect.objectContaining({
id: "type-1",
value: "Type 1",
}),
])
})
it("list product type by value matching string", async () => {
const typeResults = await service.list({ value: "Type 1" })
expect(typeResults).toEqual([
expect.objectContaining({
id: "type-1",
value: "Type 1",
}),
])
})
})
describe("listAndCount", () => {
it("should return product type and count", async () => {
const [typeResults, count] = await service.listAndCount()
expect(count).toEqual(2)
expect(typeResults).toEqual([
expect.objectContaining({
id: "type-1",
value: "Type 1",
}),
expect.objectContaining({
id: "type-2",
value: "Type 2",
}),
])
})
it("should return product type and count when filtered", async () => {
const [typeResults, count] = await service.listAndCount({
id: data[0].type.id,
})
expect(count).toEqual(1)
expect(typeResults).toEqual([
expect.objectContaining({
id: "type-1",
}),
])
})
it("should return product type and count when using skip and take", async () => {
const [typeResults, count] = await service.listAndCount(
{},
{ skip: 1, take: 1 }
)
expect(count).toEqual(2)
expect(typeResults).toEqual([
expect.objectContaining({
id: "type-2",
}),
])
})
it("should return requested fields", async () => {
const [typeResults, count] = await service.listAndCount(
{},
{
take: 1,
select: ["value"],
}
)
const serialized = JSON.parse(JSON.stringify(typeResults))
expect(count).toEqual(2)
expect(serialized).toEqual([
expect.objectContaining({
id: "type-1",
}),
])
})
})
describe("retrieve", () => {
const typeId = "type-1"
const typeValue = "Type 1"
it("should return type for the given id", async () => {
const type = await service.retrieve(typeId)
expect(type).toEqual(
expect.objectContaining({
id: typeId,
})
)
})
it("should throw an error when type with id does not exist", async () => {
let error
try {
await service.retrieve("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual(
"ProductType 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("productType - id must be defined")
})
it("should return type based on config select param", async () => {
const type = await service.retrieve(typeId, {
select: ["id", "value"],
})
const serialized = JSON.parse(JSON.stringify(type))
expect(serialized).toEqual({
id: typeId,
value: typeValue,
})
})
})
describe("delete", () => {
const typeId = "type-1"
it("should delete the product type given an ID successfully", async () => {
await service.delete([typeId])
const types = await service.list({
id: typeId,
})
expect(types).toHaveLength(0)
})
})
describe("update", () => {
const typeId = "type-1"
it("should update the value of the type successfully", async () => {
await service.update([
{
id: typeId,
value: "UK",
},
])
const productType = await service.retrieve(typeId)
expect(productType.value).toEqual("UK")
})
it("should throw an error when an id does not exist", async () => {
let error
try {
await service.update([
{
id: "does-not-exist",
value: "UK",
},
])
} catch (e) {
error = e
}
expect(error.message).toEqual(
'ProductType with id "does-not-exist" not found'
)
})
})
describe("create", () => {
it("should create a type successfully", async () => {
await service.create([
{
value: "UK",
},
])
const [productType] = await service.list({
value: "UK",
})
expect(productType.value).toEqual("UK")
})
})
})
},
})

View File

@@ -0,0 +1,328 @@
import { ProductOption } from "@medusajs/client-types"
import { ProductTypes } from "@medusajs/types"
import { Collection } from "@mikro-orm/core"
import { Product, ProductTag, ProductVariant } from "@models"
import { ProductVariantService } from "@services"
import {
createOptions,
createProductAndTags,
createProductVariants,
} from "../../../__fixtures__/product"
import { productsData, variantsData } from "../../../__fixtures__/product/data"
import { buildProductVariantOnlyData } from "../../../__fixtures__/variant/data/create-variant"
import { Modules } from "@medusajs/modules-sdk"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import { IProductModuleService } from "@medusajs/types"
jest.setTimeout(30000)
moduleIntegrationTestRunner({
moduleName: Modules.PRODUCT,
testSuite: ({
MikroOrmWrapper,
medusaApp,
}: SuiteOptions<IProductModuleService>) => {
describe.skip("ProductVariant Service", () => {
let variantOne: ProductVariant
let variantTwo: ProductVariant
let productOne: Product
const productVariantTestOne = "test-1"
let service: ProductVariantService
beforeEach(() => {
service = medusaApp.modules["productService"].productVariantService_
})
describe("list", () => {
beforeEach(async () => {
const testManager = await MikroOrmWrapper.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,
})
variantTwo = testManager.create(ProductVariant, {
id: "test-2",
title: "variant",
inventory_quantity: 10,
product: productOne,
})
await testManager.persistAndFlush([variantOne, variantTwo])
})
it("selecting by properties, scopes out the results", async () => {
const results = await service.list({
id: variantOne.id,
})
expect(results).toEqual([
expect.objectContaining({
id: variantOne.id,
title: "variant 1",
inventory_quantity: "10",
}),
])
})
it("passing a limit, scopes the result to the limit", async () => {
const results = await service.list(
{},
{
take: 1,
}
)
expect(results).toEqual([
expect.objectContaining({
id: variantOne.id,
}),
])
})
it("passing populate, scopes the results of the response", async () => {
const results = await service.list(
{
id: productVariantTestOne,
},
{
select: ["id", "title", "product.title"] as any,
relations: ["product"],
}
)
expect(results).toEqual([
expect.objectContaining({
id: productVariantTestOne,
title: "variant 1",
product: expect.objectContaining({
id: "product-1",
title: "product 1",
tags: expect.any(Collection<ProductTag>),
variants: expect.any(Collection<ProductVariant>),
}),
}),
])
expect(JSON.parse(JSON.stringify(results))).toEqual([
{
id: productVariantTestOne,
title: "variant 1",
product: {
id: "product-1",
title: "product 1",
},
},
])
})
})
describe("relation: options", () => {
let products: Product[]
let variants: ProductVariant[]
let options: ProductOption[]
beforeEach(async () => {
const testManager = await MikroOrmWrapper.forkManager()
products = (await createProductAndTags(
testManager,
productsData
)) as Product[]
variants = (await createProductVariants(
testManager,
variantsData
)) as ProductVariant[]
options = await createOptions(testManager, [
{
id: "test-option-1",
title: "size",
product: products[0],
values: [
{
id: "value-1",
value: "XS",
variant: products[0].variants[0],
},
{
id: "value-1",
value: "XL",
variant: products[0].variants[0],
},
],
},
{
id: "test-option-2",
title: "color",
product: products[0],
value: "blue",
variant: products[0].variants[0],
},
])
})
it("filter by options relation", async () => {
const variants = await service.list(
{ options: { id: ["value-1"] } },
{ relations: ["options"] }
)
expect(JSON.parse(JSON.stringify(variants))).toEqual([
expect.objectContaining({
id: productVariantTestOne,
title: "variant title",
sku: "sku 1",
}),
])
})
})
describe("create", function () {
let products: Product[]
let productOptions!: ProductOption[]
beforeEach(async () => {
const testManager = await MikroOrmWrapper.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 () => {
const testManager = await MikroOrmWrapper.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("productVariant - id must be defined")
})
})
})
},
})

View File

@@ -0,0 +1,693 @@
import { Image, Product, ProductCategory, ProductCollection } from "@models"
import {
assignCategoriesToProduct,
buildProductOnlyData,
createCollections,
createImages,
createProductAndTags,
createProductVariants,
} from "../../../__fixtures__/product"
import {
categoriesData,
productsData,
variantsData,
} from "../../../__fixtures__/product/data"
import { ProductService } from "@services"
import {
IProductModuleService,
ProductDTO,
ProductTypes,
} from "@medusajs/types"
import { kebabCase } from "@medusajs/utils"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { createProductCategories } from "../../../__fixtures__/product-category"
import { Modules } from "@medusajs/modules-sdk"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
import { ProductTag } from "../../../../src/models"
jest.setTimeout(30000)
moduleIntegrationTestRunner({
moduleName: Modules.PRODUCT,
testSuite: ({
MikroOrmWrapper,
medusaApp,
}: SuiteOptions<IProductModuleService>) => {
let service: ProductService
beforeEach(() => {
service = medusaApp.modules["productService"].productService_
})
describe("Product Service", () => {
let testManager: SqlEntityManager
let products!: Product[]
let productOne: Product
let categories!: ProductCategory[]
describe("retrieve", () => {
beforeEach(async () => {
testManager = await MikroOrmWrapper.forkManager()
productOne = testManager.create(Product, {
id: "product-1",
title: "product 1",
status: ProductTypes.ProductStatus.PUBLISHED,
})
await testManager.persistAndFlush([productOne])
})
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("product - id must be defined")
})
it("should throw an error when product with id does not exist", async () => {
let error
try {
await service.retrieve("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual(
"Product with id: does-not-exist was not found"
)
})
it("should return a product when product with an id exists", async () => {
const result = await service.retrieve(productOne.id)
expect(result).toEqual(
expect.objectContaining({
id: productOne.id,
})
)
})
})
describe("create", function () {
let images: Image[] = []
beforeEach(async () => {
testManager = await MikroOrmWrapper.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("update", function () {
let images: Image[] = []
beforeEach(async () => {
testManager = await MikroOrmWrapper.forkManager()
images = await createImages(testManager, ["image-1", "image-2"])
productOne = testManager.create(Product, {
id: "product-1",
title: "product 1",
status: ProductTypes.ProductStatus.PUBLISHED,
})
await testManager.persistAndFlush([productOne])
})
it("should update a product and its allowed relations", async () => {
const updateData = [
{
id: productOne.id,
title: "update test 1",
images: images,
thumbnail: images[0].url,
},
]
const products = await service.update(updateData)
expect(products.length).toEqual(1)
let result = await service.retrieve(productOne.id, {
relations: ["images", "thumbnail"],
})
let serialized = JSON.parse(JSON.stringify(result))
expect(serialized).toEqual(
expect.objectContaining({
id: productOne.id,
title: "update test 1",
thumbnail: images[0].url,
images: [
expect.objectContaining({
url: images[0].url,
}),
expect.objectContaining({
url: images[1].url,
}),
],
})
)
})
it("should throw an error when id is not present", async () => {
let error
const updateData = [
{
id: productOne.id,
title: "update test 1",
},
{
id: undefined as unknown as string,
title: "update test 2",
},
]
try {
await service.update(updateData)
} catch (e) {
error = e
}
expect(error.message).toEqual(`Product with id "" not found`)
let result = await service.retrieve(productOne.id)
expect(result.title).not.toBe("update test 1")
})
it("should throw an error when product with id does not exist", async () => {
let error
const updateData = [
{
id: "does-not-exist",
title: "update test 1",
},
]
try {
await service.update(updateData)
} catch (e) {
error = e
}
expect(error.message).toEqual(
`Product with id "does-not-exist" not found`
)
})
})
describe("list", () => {
it("should list all product that match the free text search", async () => {
const data = buildProductOnlyData({
title: "test product",
})
const data2 = buildProductOnlyData({
title: "space X",
})
const products = await service.create([data, data2])
const result = await service.list({
q: "test",
})
expect(result).toHaveLength(1)
expect(result[0].title).toEqual("test product")
const result2 = await service.list({
q: "space",
})
expect(result2).toHaveLength(1)
expect(result2[0].title).toEqual("space X")
})
describe("soft deleted", function () {
let product
beforeEach(async () => {
testManager = await MikroOrmWrapper.forkManager()
const products = await createProductAndTags(
testManager,
productsData
)
product = products[1]
await service.softDelete([products[0].id])
})
it("should list all products that are not deleted", async () => {
const products = await service.list()
expect(products).toHaveLength(2)
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(3)
})
})
describe("relation: tags", () => {
beforeEach(async () => {
testManager = await MikroOrmWrapper.forkManager()
products = await createProductAndTags(testManager, productsData)
})
it("should filter by id and including relations", async () => {
const productsResult = await service.list(
{
id: products[0].id,
},
{
relations: ["tags"],
}
)
productsResult.forEach((product, index) => {
const tags = product.tags.toArray()
expect(product).toEqual(
expect.objectContaining({
id: productsData[index].id,
title: productsData[index].title,
})
)
tags.forEach((tag, tagIndex) => {
expect(tag).toEqual(
expect.objectContaining({
...productsData[index].tags[tagIndex],
})
)
})
})
})
it("should filter by id and without relations", async () => {
const productsResult = await service.list({
id: products[0].id,
})
productsResult.forEach((product, index) => {
const tags = product.tags.getItems(false)
expect(product).toEqual(
expect.objectContaining({
id: productsData[index].id,
title: productsData[index].title,
})
)
expect(tags.length).toBe(0)
})
})
})
describe("relation: categories", () => {
let workingProduct: Product
let workingCategory: ProductCategory
beforeEach(async () => {
testManager = await MikroOrmWrapper.forkManager()
products = await createProductAndTags(testManager, productsData)
workingProduct = products.find((p) => p.id === "test-1") as Product
categories = await createProductCategories(
testManager,
categoriesData
)
workingCategory = (await testManager.findOne(
ProductCategory,
"category-1"
)) as ProductCategory
workingProduct = await assignCategoriesToProduct(
testManager,
workingProduct,
categories
)
})
it("should filter by categories relation and scope fields", async () => {
const products = await service.list(
{
id: workingProduct.id,
categories: { id: [workingCategory.id] },
},
{
select: [
"title",
"categories.name",
"categories.handle",
"categories.mpath",
] as (keyof ProductDTO)[],
relations: ["categories"],
}
)
const product = products.find(
(p) => p.id === workingProduct.id
) as unknown as Product
expect(product).toEqual(
expect.objectContaining({
id: workingProduct.id,
title: workingProduct.title,
})
)
expect(product.categories.toArray()).toEqual([
{
id: "category-0",
name: "category 0",
handle: "category-0",
mpath: "category-0.",
parent_category_id: null,
},
{
id: "category-1",
name: "category 1",
handle: "category-1",
mpath: "category-0.category-1.",
parent_category_id: null,
},
{
id: "category-1-a",
name: "category 1 a",
handle: "category-1-a",
mpath: "category-0.category-1.category-1-a.",
parent_category_id: null,
},
])
})
it("should returns empty array when querying for a category that doesnt exist", async () => {
const products = await service.list(
{
id: workingProduct.id,
categories: { id: ["category-doesnt-exist-id"] },
},
{
select: [
"title",
"categories.name",
"categories.handle",
] as (keyof ProductDTO)[],
relations: ["categories"],
}
)
expect(products).toEqual([])
})
})
describe("relation: collections", () => {
let workingProduct: Product
let workingProductTwo: Product
let workingCollection: ProductCollection
let workingCollectionTwo: ProductCollection
const collectionData = [
{
id: "test-1",
title: "col 1",
},
{
id: "test-2",
title: "col 2",
},
]
beforeEach(async () => {
testManager = await MikroOrmWrapper.forkManager()
await createCollections(testManager, collectionData)
workingCollection = (await testManager.findOne(
ProductCollection,
"test-1"
)) as ProductCollection
workingCollectionTwo = (await testManager.findOne(
ProductCollection,
"test-2"
)) as ProductCollection
products = await createProductAndTags(testManager, [
{
...productsData[0],
collection_id: workingCollection.id,
},
{
...productsData[1],
collection_id: workingCollectionTwo.id,
},
{
...productsData[2],
},
])
workingProduct = products.find((p) => p.id === "test-1") as Product
workingProductTwo = products.find(
(p) => p.id === "test-2"
) as Product
})
it("should filter by collection relation and scope fields", async () => {
const products = await service.list(
{
id: workingProduct.id,
collection_id: workingCollection.id,
},
{
select: ["title", "collection.title"],
relations: ["collection"],
}
)
const serialized = JSON.parse(JSON.stringify(products))
expect(serialized.length).toEqual(1)
expect(serialized).toEqual([
{
id: workingProduct.id,
title: workingProduct.title,
handle: "product-1",
collection_id: workingCollection.id,
type_id: null,
collection: {
handle: "col-1",
id: workingCollection.id,
title: workingCollection.title,
},
},
])
})
it("should filter by collection when multiple collection ids are passed", async () => {
const products = await service.list(
{
collection_id: [workingCollection.id, workingCollectionTwo.id],
},
{
select: ["title", "collection.title"],
relations: ["collection"],
}
)
const serialized = JSON.parse(JSON.stringify(products))
expect(serialized.length).toEqual(2)
expect(serialized).toEqual([
{
id: workingProduct.id,
title: workingProduct.title,
handle: "product-1",
type_id: null,
collection_id: workingCollection.id,
collection: {
handle: "col-1",
id: workingCollection.id,
title: workingCollection.title,
},
},
{
id: workingProductTwo.id,
title: workingProductTwo.title,
handle: "product",
type_id: null,
collection_id: workingCollectionTwo.id,
collection: {
handle: "col-2",
id: workingCollectionTwo.id,
title: workingCollectionTwo.title,
},
},
])
})
it("should returns empty array when querying for a collection that doesnt exist", async () => {
const products = await service.list(
{
id: workingProduct.id,
collection_id: "collection-doesnt-exist-id",
},
{
select: ["title", "collection.title"] as (keyof ProductDTO)[],
relations: ["collection"],
}
)
expect(products).toEqual([])
})
})
describe("relation: variants", () => {
beforeEach(async () => {
testManager = await MikroOrmWrapper.forkManager()
products = await createProductAndTags(testManager, productsData)
await createProductVariants(testManager, variantsData)
})
it("should filter by id and including relations", async () => {
const productsResult = await service.list(
{
id: products[0].id,
},
{
relations: ["variants"],
}
)
productsResult.forEach((product, index) => {
const variants = product.variants.toArray()
expect(product).toEqual(
expect.objectContaining({
id: productsData[index].id,
title: productsData[index].title,
})
)
variants.forEach((variant, variantIndex) => {
const expectedVariant = variantsData.filter(
(d) => d.product.id === product.id
)[variantIndex]
const variantProduct = variant.product
expect(variant).toEqual(
expect.objectContaining({
id: expectedVariant.id,
sku: expectedVariant.sku,
title: expectedVariant.title,
})
)
})
})
})
})
})
describe("softDelete", function () {
let images: Image[] = []
beforeEach(async () => {
testManager = await MikroOrmWrapper.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])
await service.softDelete(products.map((p) => p.id))
const deleteProducts = await service.list(
{ id: products.map((p) => p.id) },
{
relations: [
"variants",
"variants.options",
"options",
"options.values",
],
withDeleted: true,
}
)
expect(deleteProducts).toHaveLength(1)
expect(deleteProducts[0].deleted_at).not.toBeNull()
})
})
describe("restore", function () {
let images: Image[] = []
beforeEach(async () => {
testManager = await MikroOrmWrapper.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()
})
})
})
},
})