feat(product): Create (+ workflow), delete, restore (#4459)

* Feat: create product with product module

* feat: create product wip

* feat: create product wip

* feat: update product relation and generate image migration

* lint

* conitnue implementation

* continue implementation and add integration tests for produceService.create

* Add integration tests for product creation at the module level for the complete flow

* only use persist since write operations are always wrapped in a transaction which will be committed and flushed

* simplify the transaction wrapper to make future changes easier

* feat: move some utils to the utils package to simplify its usage

* tests: fix unit tests

* feat: create variants along side the product

* Add more integration tests an update migrations

* chore: Update actions workflow to include packages integration tests

* small types and utils cleanup

* chore: Add support for database debug option

* chore: Add missing types in package.json from types and util, validate that all the models are sync with medusa

* expose retrieve method

* fix types issues

* fix unit tests and move integration tests workflow with the plugins integration tests

* chore: remove migration function export from the definition to prevent them to be ran by the medusa cli just in case

* fix package.json script

* chore: workflows

* feat: start creating the create product workflow

* feat: add empty step for prices and sales channel

* tests: update scripts and action envs

* fix imports

* feat: Add proper soft deleted support + add product deletion service public api

* chore: update migrations

* chore: update migrations

* chore: update todo

* feat: Add product deletion to the create-product workflow as compensation

* chore: cleanup product utils

* feat: Add support for cascade soft-remove

* feat: refactor repository to take into account withDeleted

* fix integration tests

* Add support for force delete -> delete, cleanup repositories and improvements

* Add support for restoring a product and add integration tests

* cleaup + tests

* types

* fix integration tests

* remove unnecessary comments

* move specific mikro orm usage to the DAL

* Cleanup workflow functions

* Make deleted_at optional at the property level and add url index for the images

* address feedback + cleanup

* fix export

* merge migrations into one

* feat(product, types): added missing product variant methods (#4475)

* chore: added missing product variant methods

* chore: address PR feedback

* chore: catch undefined case for retrieve + specs for variant service

* chore: align TEntity + add changeset

* chore: revert changeset, TEntity to ProductVariant

* chore: write tests for pagination, unskip the test

* Create chilled-mice-deliver.md

* update integration fixtuers

* update pipeline node version

* rename github action

* fix pipeline

* feat(medusa, types): added missing category tests and service methods (#4499)

* chore: added missing category tests and service methods

* chore: added type changes to module service

* chore: address pr feedback

* update repositories manager usage and serialisation from the write public API

* move serializisation to the DAL

* rename template args

* chore: added collection methods for module and collection service (#4505)

* chore: added collection methods for module and collection service

* Create fresh-islands-teach.md

* chore: move retrieve entity to utils package

* chore: make products optional in DTO type

---------

Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>

* feat(product): Apply transaction decorators to the services (#4512)

---------

Co-authored-by: Riqwan Thamir <rmthamir@gmail.com>
Co-authored-by: Oliver Windall Juhl <59018053+olivermrbl@users.noreply.github.com>
Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
This commit is contained in:
Adrien de Peretti
2023-07-16 20:19:23 +02:00
committed by GitHub
co-authored by Oliver Windall Juhl Riqwan Thamir Carlos R. L. Rodrigues
parent 5b91a3503a
commit befc2f1c80
98 changed files with 5444 additions and 688 deletions
@@ -1,14 +1,9 @@
import { FindOptions, RepositoryService } from "@medusajs/types"
import { BaseRepository } from "../../src/repositories/base"
class CustomRepository implements RepositoryService {
constructor() {}
find(options?: FindOptions): Promise<any[]> {
throw new Error("Method not implemented.")
}
findAndCount(options?: FindOptions): Promise<[any[], number]> {
throw new Error("Method not implemented.")
class CustomRepository extends BaseRepository {
constructor({ manager }) {
// @ts-ignore
super(...arguments)
}
}
@@ -0,0 +1,80 @@
import { ProductTypes } from "@medusajs/types"
import faker from "faker"
import { Image } from "@models"
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,
tags,
options,
variants,
}: Partial<ProductTypes.CreateProductDTO>) => {
const defaultOptionTitle = faker.commerce.productName()
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: type ? { value: type } : { value: faker.commerce.productName() },
tags: tags ?? [{ value: "tag-1" }],
options: options ?? [
{
title: defaultOptionTitle,
},
],
variants: variants ?? [
{
title: faker.commerce.productName(),
sku: faker.commerce.productName(),
options: [
{
value: defaultOptionTitle + faker.commerce.productName(),
},
],
},
],
// TODO: add categories, must be created first
}
}
@@ -1,5 +1,7 @@
import { ProductTypes } from "@medusajs/types"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import {
Image,
Product,
ProductCategory,
ProductCollection,
@@ -7,9 +9,16 @@ import {
} from "@models"
import ProductOption from "../../../src/models/product-option"
export * from "./data/create-product"
export async function createProductAndTags(
manager: SqlEntityManager,
data: any[]
data: {
id?: string
title: string
status: ProductTypes.ProductStatus
tags?: { id: string; value: string }[]
}[]
) {
const products: any[] = data.map((productData) => {
return manager.create(Product, productData)
@@ -35,7 +44,11 @@ export async function createProductVariants(
export async function createCollections(
manager: SqlEntityManager,
collectionData: any[]
collectionData: {
id?: string
title: string
handle?: string
}[]
) {
const collections: any[] = collectionData.map((collectionData) => {
return manager.create(ProductCollection, collectionData)
@@ -48,10 +61,21 @@ export async function createCollections(
export async function createOptions(
manager: SqlEntityManager,
optionsData: any[]
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((o) => {
return manager.create(ProductOption, o)
const options: any[] = optionsData.map((option) => {
return manager.create(ProductOption, option)
})
await manager.persistAndFlush(options)
@@ -59,6 +83,19 @@ export async function createOptions(
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,
@@ -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,
}
}
@@ -0,0 +1 @@
export * from "./data/create-variant"
@@ -1,11 +1,13 @@
import { MedusaModule } from "@medusajs/modules-sdk"
import { Product } from "@models"
import { initialize } from "../../src"
import * as CustomRepositories from "../__fixtures__/module"
import { ProductRepository } from "../__fixtures__/module"
import { createProductAndTags } from "../__fixtures__/product"
import { productsData } from "../__fixtures__/product/data"
import { DB_URL, TestDatabase } from "../utils"
import { buildProductAndRelationsData } from "../__fixtures__/product/data/create-product"
import { kebabCase } from "@medusajs/utils"
import { IProductModuleService } from "@medusajs/types"
const beforeEach_ = async () => {
await TestDatabase.setupDatabase()
@@ -18,12 +20,11 @@ const afterEach_ = async () => {
describe("Product module", function () {
describe("Using built-in data access layer", function () {
let module
let products: Product[]
let module: IProductModuleService
beforeEach(async () => {
const testManager = await beforeEach_()
products = await createProductAndTags(testManager, productsData)
await createProductAndTags(testManager, productsData)
module = await initialize({
database: {
@@ -46,13 +47,12 @@ describe("Product module", function () {
})
describe("Using custom data access layer", function () {
let module
let products: Product[]
let module: IProductModuleService
beforeEach(async () => {
const testManager = await beforeEach_()
products = await createProductAndTags(testManager, productsData)
await createProductAndTags(testManager, productsData)
module = await initialize({
database: {
@@ -78,12 +78,11 @@ describe("Product module", function () {
})
describe("Using custom data access layer and connection", function () {
let module
let products: Product[]
let module: IProductModuleService
beforeEach(async () => {
const testManager = await beforeEach_()
products = await createProductAndTags(testManager, productsData)
await createProductAndTags(testManager, productsData)
MedusaModule.clearInstances()
@@ -106,4 +105,244 @@ describe("Product module", function () {
expect(products).toHaveLength(0)
})
})
describe("create", function () {
let module: IProductModuleService
let images = ["image-1"]
beforeEach(async () => {
await beforeEach_()
MedusaModule.clearInstances()
module = await initialize({
database: {
clientUrl: DB_URL,
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
},
})
})
afterEach(afterEach_)
it("should create a product", async () => {
const data = buildProductAndRelationsData({
images,
thumbnail: images[0],
})
const products = await module.create([data])
expect(products).toHaveLength(1)
expect(products[0].images).toHaveLength(1)
expect(products[0].options).toHaveLength(1)
expect(products[0].tags).toHaveLength(1)
expect(products[0].categories).toHaveLength(0)
expect(products[0].variants).toHaveLength(1)
expect(products[0]).toEqual(
expect.objectContaining({
id: expect.any(String),
title: data.title,
handle: kebabCase(data.title),
description: data.description,
subtitle: data.subtitle,
is_giftcard: data.is_giftcard,
discountable: data.discountable,
thumbnail: images[0],
status: data.status,
images: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
url: images[0],
}),
]),
options: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
title: data.options[0].title,
values: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
value: data.variants[0].options?.[0].value,
}),
]),
}),
]),
tags: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
value: data.tags[0].value,
}),
]),
type: expect.objectContaining({
id: expect.any(String),
value: data.type.value,
}),
variants: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
title: data.variants[0].title,
sku: data.variants[0].sku,
allow_backorder: false,
manage_inventory: true,
inventory_quantity: 100,
variant_rank: 0,
options: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
value: data.variants[0].options?.[0].value,
}),
]),
}),
]),
})
)
})
})
describe("softDelete", function () {
let module: IProductModuleService
let images = ["image-1"]
beforeEach(async () => {
await beforeEach_()
MedusaModule.clearInstances()
module = await initialize({
database: {
clientUrl: DB_URL,
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
},
})
})
afterEach(afterEach_)
it("should soft delete a product and its cascaded relations", async () => {
const data = buildProductAndRelationsData({
images,
thumbnail: images[0],
})
const products = await module.create([data])
await module.softDelete([products[0].id])
const deletedProducts = await module.list(
{ id: products[0].id },
{
relations: [
"variants",
"variants.options",
"options",
"options.values",
],
withDeleted: true,
}
)
expect(deletedProducts).toHaveLength(1)
expect(deletedProducts[0].deleted_at).not.toBeNull()
for (const option of deletedProducts[0].options) {
expect(option.deleted_at).not.toBeNull()
}
const productOptionsValues = deletedProducts[0].options
.map((o) => o.values)
.flat()
for (const optionValue of productOptionsValues) {
expect(optionValue.deleted_at).not.toBeNull()
}
for (const variant of deletedProducts[0].variants) {
expect(variant.deleted_at).not.toBeNull()
}
const variantsOptions = deletedProducts[0].options
.map((o) => o.values)
.flat()
for (const option of variantsOptions) {
expect(option.deleted_at).not.toBeNull()
}
})
})
describe("restore", function () {
let module: IProductModuleService
let images = ["image-1"]
beforeEach(async () => {
await beforeEach_()
MedusaModule.clearInstances()
module = await initialize({
database: {
clientUrl: DB_URL,
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
},
})
})
afterEach(afterEach_)
it("should restore a soft deleted product and its cascaded relations", async () => {
const data = buildProductAndRelationsData({
images,
thumbnail: images[0],
})
const products = await module.create([data])
await module.softDelete([products[0].id])
await module.restore([products[0].id])
const deletedProducts = await module.list(
{ id: products[0].id },
{
relations: [
"variants",
"variants.options",
"variants.options",
"options",
"options.values",
],
withDeleted: true,
}
)
expect(deletedProducts).toHaveLength(1)
expect(deletedProducts[0].deleted_at).toBeNull()
for (const option of deletedProducts[0].options) {
expect(option.deleted_at).toBeNull()
}
const productOptionsValues = deletedProducts[0].options
.map((o) => o.values)
.flat()
for (const optionValue of productOptionsValues) {
expect(optionValue.deleted_at).toBeNull()
}
for (const variant of deletedProducts[0].variants) {
expect(variant.deleted_at).toBeNull()
}
const variantsOptions = deletedProducts[0].options
.map((o) => o.values)
.flat()
for (const option of variantsOptions) {
expect(option.deleted_at).toBeNull()
}
})
})
})
@@ -10,7 +10,7 @@ import { productCategoriesData } from "../../../__fixtures__/product-category/da
jest.setTimeout(30000)
describe("ProductCategory Service", () => {
describe("Product category Service", () => {
let service: ProductCategoryService
let testManager: SqlEntityManager
let repositoryManager: SqlEntityManager
@@ -47,7 +47,7 @@ describe("ProductCategory Service", () => {
const productCategoryResults = await service.list(
{},
{
select: ["id", "parent_category_id"] as any,
select: ["id", "parent_category_id"],
}
)
@@ -128,7 +128,7 @@ describe("ProductCategory Service", () => {
include_descendants_tree: true,
},
{
select: ["id", "handle"] as any,
select: ["id", "handle"],
}
)
@@ -192,7 +192,7 @@ describe("ProductCategory Service", () => {
is_internal: false,
},
{
select: ["id", "handle"] as any,
select: ["id", "handle"],
}
)
@@ -230,4 +230,334 @@ describe("ProductCategory Service", () => {
])
})
})
describe("retrieve", () => {
const categoryOneId = "category-1"
beforeEach(async () => {
testManager = await TestDatabase.forkManager()
productCategories = await createProductCategories(
testManager,
productCategoriesData
)
})
it("should return category for the given id", async () => {
const productCategoryResults = await service.retrieve(
categoryOneId,
)
expect(productCategoryResults).toEqual(
expect.objectContaining({
id: categoryOneId
})
)
})
it("should throw an error when category with id does not exist", async () => {
let error
try {
await service.retrieve("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual('ProductCategory with id: does-not-exist was not found')
})
it("should throw an error when an id is not provided", async () => {
let error
try {
await service.retrieve(undefined as unknown as string)
} catch (e) {
error = e
}
expect(error.message).toEqual('"productCategoryId" must be defined')
})
it("should return category based on config select param", async () => {
const productCategoryResults = await service.retrieve(
categoryOneId,
{
select: ["id", "parent_category_id"],
}
)
expect(productCategoryResults).toEqual(
expect.objectContaining({
id: categoryOneId,
parent_category_id: "category-0",
})
)
})
it("should return category based on config relation param", async () => {
const productCategoryResults = await service.retrieve(
categoryOneId,
{
select: ["id", "parent_category_id"],
relations: ["parent_category"]
}
)
expect(productCategoryResults).toEqual(
expect.objectContaining({
id: categoryOneId,
category_children: [
expect.objectContaining({
id: 'category-1-a',
}),
expect.objectContaining({
id: 'category-1-b',
})
],
parent_category: expect.objectContaining({
id: "category-0"
})
})
)
})
})
describe("listAndCount", () => {
beforeEach(async () => {
testManager = await TestDatabase.forkManager()
productCategories = await createProductCategories(
testManager,
productCategoriesData
)
})
it("should return categories and count based on take and skip", async () => {
let results = await service.listAndCount(
{},
{
take: 1,
}
)
expect(results[1]).toEqual(5)
expect(results[0]).toEqual([
expect.objectContaining({
id: "category-0",
}),
])
results = await service.listAndCount(
{},
{
take: 1,
skip: 1
}
)
expect(results[1]).toEqual(5)
expect(results[0]).toEqual([
expect.objectContaining({
id: "category-1",
}),
])
})
it("should return all product categories and count", async () => {
const productCategoryResults = await service.listAndCount(
{},
{
select: ["id", "parent_category_id"],
relations: ["parent_category"],
}
)
expect(productCategoryResults[1]).toEqual(5)
expect(productCategoryResults[0]).toEqual([
expect.objectContaining({
id: "category-0",
parent_category: null
}),
expect.objectContaining({
id: "category-1",
parent_category: expect.objectContaining({
id: "category-0",
}),
}),
expect.objectContaining({
id: "category-1-a",
parent_category: expect.objectContaining({
id: "category-1",
}),
}),
expect.objectContaining({
id: "category-1-b",
parent_category: expect.objectContaining({
id: "category-1",
}),
}),
expect.objectContaining({
id: "category-1-b-1",
parent_category: expect.objectContaining({
id: "category-1-b",
}),
}),
])
})
it("should only return categories that are scoped by parent_category_id", async () => {
let productCategoryResults = await service.listAndCount(
{ parent_category_id: null },
{
select: ["id"],
}
)
expect(productCategoryResults[1]).toEqual(1)
expect(productCategoryResults[0]).toEqual([
expect.objectContaining({
id: "category-0",
}),
])
productCategoryResults = await service.listAndCount({
parent_category_id: "category-0",
})
expect(productCategoryResults[1]).toEqual(1)
expect(productCategoryResults[0]).toEqual([
expect.objectContaining({
id: "category-1",
}),
])
productCategoryResults = await service.listAndCount({
parent_category_id: ["category-1-b", "category-0"],
})
expect(productCategoryResults[1]).toEqual(2)
expect(productCategoryResults[0]).toEqual([
expect.objectContaining({
id: "category-1",
}),
expect.objectContaining({
id: "category-1-b-1",
}),
])
})
it("should includes descendants when include_descendants_tree is true", async () => {
const productCategoryResults = await service.listAndCount(
{
parent_category_id: null,
include_descendants_tree: true,
},
{
select: ["id", "handle"],
}
)
expect(productCategoryResults[1]).toEqual(1)
const serializedObject = JSON.parse(
JSON.stringify(productCategoryResults[0])
)
expect(serializedObject).toEqual([
expect.objectContaining({
id: "category-0",
handle: "category-0",
mpath: "category-0.",
parent_category_id: null,
parent_category: null,
category_children: [
expect.objectContaining({
id: "category-1",
handle: "category-1",
mpath: "category-0.category-1.",
parent_category_id: "category-0",
parent_category: "category-0",
category_children: [
expect.objectContaining({
id: "category-1-a",
handle: "category-1-a",
mpath: "category-0.category-1.category-1-a.",
parent_category_id: "category-1",
parent_category: "category-1",
category_children: [],
}),
expect.objectContaining({
id: "category-1-b",
handle: "category-1-b",
mpath: "category-0.category-1.category-1-b.",
parent_category_id: "category-1",
parent_category: "category-1",
category_children: [
expect.objectContaining({
id: "category-1-b-1",
handle: "category-1-b-1",
mpath:
"category-0.category-1.category-1-b.category-1-b-1.",
parent_category_id: "category-1-b",
parent_category: "category-1-b",
category_children: [],
}),
],
}),
],
}),
],
}),
])
})
it("should filter out children when include_descendants_tree is true", async () => {
const productCategoryResults = await service.listAndCount(
{
parent_category_id: null,
include_descendants_tree: true,
is_internal: false,
},
{
select: ["id", "handle"],
}
)
expect(productCategoryResults[1]).toEqual(1)
const serializedObject = JSON.parse(
JSON.stringify(productCategoryResults[0])
)
expect(serializedObject).toEqual([
expect.objectContaining({
id: "category-0",
handle: "category-0",
mpath: "category-0.",
parent_category_id: null,
parent_category: null,
category_children: [
expect.objectContaining({
id: "category-1",
handle: "category-1",
mpath: "category-0.category-1.",
parent_category_id: "category-0",
parent_category: "category-0",
category_children: [
expect.objectContaining({
id: "category-1-a",
handle: "category-1-a",
mpath: "category-0.category-1.category-1-a.",
parent_category_id: "category-1",
parent_category: "category-1",
category_children: [],
}),
],
}),
],
}),
])
})
})
})
@@ -9,7 +9,7 @@ import { createCollections } from "../../../__fixtures__/product"
jest.setTimeout(30000)
describe("Product Service", () => {
describe("Product collection Service", () => {
let service: ProductCollectionService
let testManager: SqlEntityManager
let repositoryManager: SqlEntityManager
@@ -59,9 +59,9 @@ describe("Product Service", () => {
})
it("list product collections", async () => {
const tagsResults = await service.list()
const productCollectionResults = await service.list()
expect(tagsResults).toEqual([
expect(productCollectionResults).toEqual([
expect.objectContaining({
id: "test-1",
title: "col 1",
@@ -82,9 +82,9 @@ describe("Product Service", () => {
})
it("list product collections by id", async () => {
const tagsResults = await service.list({ id: data![0].id })
const productCollectionResults = await service.list({ id: data![0].id })
expect(tagsResults).toEqual([
expect(productCollectionResults).toEqual([
expect.objectContaining({
id: "test-1",
title: "col 1",
@@ -93,9 +93,9 @@ describe("Product Service", () => {
})
it("list product collections by title matching string", async () => {
const tagsResults = await service.list({ title: "col 3 extra" })
const productCollectionResults = await service.list({ title: "col 3 extra" })
expect(tagsResults).toEqual([
expect(productCollectionResults).toEqual([
expect.objectContaining({
id: "test-3",
title: "col 3 extra",
@@ -103,4 +103,174 @@ describe("Product Service", () => {
])
})
})
describe("listAndCount", () => {
const data = [
{
id: "test-1",
title: "col 1",
},
{
id: "test-2",
title: "col 2",
},
{
id: "test-3",
title: "col 3 extra",
},
{
id: "test-4",
title: "col 4 extra",
},
]
beforeEach(async () => {
testManager = await TestDatabase.forkManager()
collectionsData = await createCollections(testManager, data)
})
it("should return all collections and count", async () => {
const [productCollectionResults, count] = await service.listAndCount()
const serialized = JSON.parse(JSON.stringify(productCollectionResults))
expect(serialized).toEqual([
expect.objectContaining({
id: "test-1",
title: "col 1",
}),
expect.objectContaining({
id: "test-2",
title: "col 2",
}),
expect.objectContaining({
id: "test-3",
title: "col 3 extra",
}),
expect.objectContaining({
id: "test-4",
title: "col 4 extra",
}),
])
})
it("should return count and collections based on filter data", async () => {
const [productCollectionResults, count] = await service.listAndCount({ id: data![0].id })
const serialized = JSON.parse(JSON.stringify(productCollectionResults))
expect(count).toEqual(1)
expect(serialized).toEqual([
expect.objectContaining({
id: "test-1",
title: "col 1",
}),
])
})
it("should return count and collections based on config data", async () => {
const [productCollectionResults, count] = await service.listAndCount({}, {
relations: ['products'],
select: ['title'],
take: 1,
skip: 1,
})
const serialized = JSON.parse(JSON.stringify(productCollectionResults))
expect(count).toEqual(4)
expect(serialized).toEqual([
{
id: "test-2",
title: "col 2",
products: []
},
])
})
})
describe("retrieve", () => {
const collectionData = {
id: "collection-1",
title: "collection 1",
}
beforeEach(async () => {
testManager = await TestDatabase.forkManager()
await createCollections(testManager, [collectionData])
})
it("should return collection for the given id", async () => {
const productCollectionResults = await service.retrieve(
collectionData.id,
)
expect(productCollectionResults).toEqual(
expect.objectContaining({
id: collectionData.id
})
)
})
it("should throw an error when collection with id does not exist", async () => {
let error
try {
await service.retrieve("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual('ProductCollection with id: does-not-exist was not found')
})
it("should throw an error when an id is not provided", async () => {
let error
try {
await service.retrieve(undefined as unknown as string)
} catch (e) {
error = e
}
expect(error.message).toEqual('"productCollectionId" must be defined')
})
it("should return collection based on config select param", async () => {
const productCollectionResults = await service.retrieve(
collectionData.id,
{
select: ["id", "title"],
}
)
const serialized = JSON.parse(JSON.stringify(productCollectionResults))
expect(serialized).toEqual(
{
id: collectionData.id,
title: collectionData.title,
}
)
})
it("should return collection based on config relation param", async () => {
const productCollectionResults = await service.retrieve(
collectionData.id,
{
select: ["id", "title"],
relations: ["products"]
}
)
const serialized = JSON.parse(JSON.stringify(productCollectionResults))
expect(serialized).toEqual(
{
id: collectionData.id,
title: collectionData.title,
products: []
}
)
})
})
})
@@ -0,0 +1,248 @@
import { IProductModuleService } from "@medusajs/types"
import { Product, ProductCategory } from "@models"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { ProductTypes } from "@medusajs/types"
import { initialize } from "../../../../src"
import { DB_URL, TestDatabase } from "../../../utils"
import { createProductCategories } from "../../../__fixtures__/product-category"
describe("ProductModuleService product categories", () => {
let service: IProductModuleService
let testManager: SqlEntityManager
let repositoryManager: SqlEntityManager
let productOne: Product
let productTwo: Product
let productCategoryOne: ProductCategory
let productCategoryTwo: ProductCategory
let productCategories: ProductCategory[]
beforeEach(async () => {
await TestDatabase.setupDatabase()
repositoryManager = await TestDatabase.forkManager()
service = await initialize({
database: {
clientUrl: DB_URL,
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
},
})
testManager = await TestDatabase.forkManager()
productOne = testManager.create(Product, {
id: "product-1",
title: "product 1",
status: ProductTypes.ProductStatus.PUBLISHED,
})
productTwo = testManager.create(Product, {
id: "product-2",
title: "product 2",
status: ProductTypes.ProductStatus.PUBLISHED,
})
const productCategoriesData = [{
id: "test-1",
name: "category 1",
products: [productOne],
},{
id: "test-2",
name: "category",
products: [productTwo],
}]
productCategories = await createProductCategories(
testManager,
productCategoriesData
)
productCategoryOne = productCategories[0]
productCategoryTwo = productCategories[1]
await testManager.persistAndFlush([productCategoryOne, productCategoryTwo])
})
afterEach(async () => {
await TestDatabase.clearDatabase()
})
describe("listCategories", () => {
it("should return categories queried by ID", async () => {
const results = await service.listCategories({
id: productCategoryOne.id,
})
expect(results).toEqual([
expect.objectContaining({
id: productCategoryOne.id,
}),
])
})
it("should return categories based on the options and filter parameter", async () => {
let results = await service.listCategories(
{
id: productCategoryOne.id,
},
{
take: 1,
}
)
expect(results).toEqual([
expect.objectContaining({
id: productCategoryOne.id,
}),
])
results = await service.listCategories({}, { take: 1, skip: 1 })
expect(results).toEqual([
expect.objectContaining({
id: productCategoryTwo.id,
}),
])
})
it("should return only requested fields and relations for categories", async () => {
const results = await service.listCategories(
{
id: productCategoryOne.id,
},
{
select: ["id", "name", "products.title"],
relations: ["products"],
}
)
expect(results).toEqual([
expect.objectContaining({
id: "test-1",
name: "category 1",
products: [expect.objectContaining({
id: "product-1",
title: "product 1",
})],
}),
])
})
})
describe("listAndCountCategories", () => {
it("should return categories and count queried by ID", async () => {
const results = await service.listAndCountCategories({
id: productCategoryOne.id,
})
expect(results[1]).toEqual(1)
expect(results[0]).toEqual([
expect.objectContaining({
id: productCategoryOne.id,
}),
])
})
it("should return categories and count based on the options and filter parameter", async () => {
let results = await service.listAndCountCategories(
{
id: productCategoryOne.id,
},
{
take: 1,
}
)
expect(results[1]).toEqual(1)
expect(results[0]).toEqual([
expect.objectContaining({
id: productCategoryOne.id,
}),
])
results = await service.listAndCountCategories({}, { take: 1 })
expect(results[1]).toEqual(2)
results = await service.listAndCountCategories({}, { take: 1, skip: 1 })
expect(results[1]).toEqual(2)
expect(results[0]).toEqual([
expect.objectContaining({
id: productCategoryTwo.id,
}),
])
})
it("should return only requested fields and relations for categories", async () => {
const results = await service.listAndCountCategories(
{
id: productCategoryOne.id,
},
{
select: ["id", "name", "products.title"],
relations: ["products"],
}
)
expect(results[1]).toEqual(1)
expect(results[0]).toEqual([
expect.objectContaining({
id: "test-1",
name: "category 1",
products: [expect.objectContaining({
id: "product-1",
title: "product 1",
})],
}),
])
})
})
describe("retrieveCategory", () => {
it("should return the requested category", async () => {
const result = await service.retrieveCategory(productCategoryOne.id)
expect(result).toEqual(
expect.objectContaining({
id: "test-1",
name: "category 1",
}),
)
})
it("should return requested attributes when requested through config", async () => {
const result = await service.retrieveCategory(
productCategoryOne.id,
{
select: ["id", "name", "products.title"],
relations: ["products"],
}
)
expect(result).toEqual(
expect.objectContaining({
id: "test-1",
name: "category 1",
products: [expect.objectContaining({
id: "product-1",
title: "product 1",
})],
}),
)
})
it("should throw an error when a category with ID does not exist", async () => {
let error
try {
await service.retrieveCategory("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual("ProductCategory with id: does-not-exist was not found")
})
})
})
@@ -0,0 +1,250 @@
import { IProductModuleService } from "@medusajs/types"
import { Product, ProductCollection } from "@models"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { ProductTypes } from "@medusajs/types"
import { initialize } from "../../../../src"
import { DB_URL, TestDatabase } from "../../../utils"
import { createCollections } from "../../../__fixtures__/product"
describe("ProductModuleService product collections", () => {
let service: IProductModuleService
let testManager: SqlEntityManager
let repositoryManager: SqlEntityManager
let productOne: Product
let productTwo: Product
let productCollectionOne: ProductCollection
let productCollectionTwo: ProductCollection
let productCollections: ProductCollection[]
beforeEach(async () => {
await TestDatabase.setupDatabase()
repositoryManager = await TestDatabase.forkManager()
service = await initialize({
database: {
clientUrl: DB_URL,
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
},
})
testManager = await TestDatabase.forkManager()
productOne = testManager.create(Product, {
id: "product-1",
title: "product 1",
status: ProductTypes.ProductStatus.PUBLISHED,
})
productTwo = testManager.create(Product, {
id: "product-2",
title: "product 2",
status: ProductTypes.ProductStatus.PUBLISHED,
})
const productCollectionsData = [{
id: "test-1",
title: "collection 1",
products: [productOne],
},{
id: "test-2",
title: "collection",
products: [productTwo],
}]
productCollections = await createCollections(
testManager,
productCollectionsData
)
productCollectionOne = productCollections[0]
productCollectionTwo = productCollections[1]
await testManager.persistAndFlush([productCollectionOne, productCollectionTwo])
})
afterEach(async () => {
await TestDatabase.clearDatabase()
})
describe("listCollections", () => {
it("should return collections queried by ID", async () => {
const results = await service.listCollections({
id: productCollectionOne.id,
})
expect(results).toEqual([
expect.objectContaining({
id: productCollectionOne.id,
}),
])
})
it("should return collections based on the options and filter parameter", async () => {
let results = await service.listCollections(
{
id: productCollectionOne.id,
},
{
take: 1,
}
)
expect(results).toEqual([
expect.objectContaining({
id: productCollectionOne.id,
}),
])
results = await service.listCollections({}, { take: 1, skip: 1 })
expect(results).toEqual([
expect.objectContaining({
id: productCollectionTwo.id,
}),
])
})
it("should return only requested fields and relations for collections", async () => {
const results = await service.listCollections(
{
id: productCollectionOne.id,
},
{
select: ["id", "title", "products.title"],
relations: ["products"],
}
)
expect(results).toEqual([
expect.objectContaining({
id: "test-1",
title: "collection 1",
products: [
expect.objectContaining({
id: "product-1",
title: "product 1",
})
],
}),
])
})
})
describe("listAndCountCollections", () => {
it("should return collections and count queried by ID", async () => {
const results = await service.listAndCountCollections({
id: productCollectionOne.id,
})
expect(results[1]).toEqual(1)
expect(results[0]).toEqual([
expect.objectContaining({
id: productCollectionOne.id,
}),
])
})
it("should return collections and count based on the options and filter parameter", async () => {
let results = await service.listAndCountCollections(
{
id: productCollectionOne.id,
},
{
take: 1,
}
)
expect(results[1]).toEqual(1)
expect(results[0]).toEqual([
expect.objectContaining({
id: productCollectionOne.id,
}),
])
results = await service.listAndCountCollections({}, { take: 1 })
expect(results[1]).toEqual(2)
results = await service.listAndCountCollections({}, { take: 1, skip: 1 })
expect(results[1]).toEqual(2)
expect(results[0]).toEqual([
expect.objectContaining({
id: productCollectionTwo.id,
}),
])
})
it("should return only requested fields and relations for collections", async () => {
const results = await service.listAndCountCollections(
{
id: productCollectionOne.id,
},
{
select: ["id", "title", "products.title"],
relations: ["products"],
}
)
expect(results[1]).toEqual(1)
expect(results[0]).toEqual([
expect.objectContaining({
id: "test-1",
title: "collection 1",
products: [expect.objectContaining({
id: "product-1",
title: "product 1",
})],
}),
])
})
})
describe("retrieveCollection", () => {
it("should return the requested collection", async () => {
const result = await service.retrieveCollection(productCollectionOne.id)
expect(result).toEqual(
expect.objectContaining({
id: "test-1",
title: "collection 1",
}),
)
})
it("should return requested attributes when requested through config", async () => {
const result = await service.retrieveCollection(
productCollectionOne.id,
{
select: ["id", "title", "products.title"],
relations: ["products"],
}
)
expect(result).toEqual(
expect.objectContaining({
id: "test-1",
title: "collection 1",
products: [expect.objectContaining({
id: "product-1",
title: "product 1",
})],
}),
)
})
it("should throw an error when a collection with ID does not exist", async () => {
let error
try {
await service.retrieveCollection("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual("ProductCollection with id: does-not-exist was not found")
})
})
})
@@ -0,0 +1,182 @@
import { initialize } from "../../../../src"
import { DB_URL, TestDatabase } from "../../../utils"
import { IProductModuleService } from "@medusajs/types"
import { Product, ProductVariant } from "@models"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { ProductTypes } from "@medusajs/types"
describe("ProductModuleService product variants", () => {
let service: IProductModuleService
let testManager: SqlEntityManager
let repositoryManager: SqlEntityManager
let variantOne: ProductVariant
let variantTwo: ProductVariant
let productOne: Product
let productTwo: Product
beforeEach(async () => {
await TestDatabase.setupDatabase()
repositoryManager = await TestDatabase.forkManager()
service = await initialize({
database: {
clientUrl: DB_URL,
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
},
})
testManager = await TestDatabase.forkManager()
productOne = testManager.create(Product, {
id: "product-1",
title: "product 1",
status: ProductTypes.ProductStatus.PUBLISHED,
})
productTwo = testManager.create(Product, {
id: "product-2",
title: "product 2",
status: ProductTypes.ProductStatus.PUBLISHED,
})
variantOne = testManager.create(ProductVariant, {
id: "test-1",
title: "variant 1",
inventory_quantity: 10,
product: productOne,
})
variantTwo = testManager.create(ProductVariant, {
id: "test-2",
title: "variant",
inventory_quantity: 10,
product: productTwo,
})
await testManager.persistAndFlush([variantOne, variantTwo])
})
afterEach(async () => {
await TestDatabase.clearDatabase()
})
describe("listAndCountVariants", () => {
it("should return variants and count queried by ID", async () => {
const results = await service.listAndCountVariants({
id: variantOne.id,
})
expect(results[1]).toEqual(1)
expect(results[0]).toEqual([
expect.objectContaining({
id: variantOne.id,
}),
])
})
it("should return variants and count based on the options and filter parameter", async () => {
let results = await service.listAndCountVariants(
{
id: variantOne.id,
},
{
take: 1,
}
)
expect(results[1]).toEqual(1)
expect(results[0]).toEqual([
expect.objectContaining({
id: variantOne.id,
}),
])
results = await service.listAndCountVariants({}, { take: 1 })
expect(results[1]).toEqual(2)
results = await service.listAndCountVariants({}, { take: 1, skip: 1 })
expect(results[1]).toEqual(2)
expect(results[0]).toEqual([
expect.objectContaining({
id: variantTwo.id,
}),
])
})
it("should return only requested fields and relations for variants", async () => {
const results = await service.listAndCountVariants(
{
id: variantOne.id,
},
{
select: ["id", "title", "product.title"] as any,
relations: ["product"],
}
)
expect(results[1]).toEqual(1)
expect(results[0]).toEqual([
expect.objectContaining({
id: "test-1",
title: "variant 1",
product_id: "product-1",
// TODO: investigate why this is returning more than the expected results
product: expect.objectContaining({
id: "product-1",
title: "product 1",
}),
}),
])
})
})
describe("retrieveVariant", () => {
it("should return the requested variant", async () => {
const result = await service.retrieveVariant(variantOne.id)
expect(result).toEqual(
expect.objectContaining({
id: "test-1",
title: "variant 1",
}),
)
})
it("should return requested attributes when requested through config", async () => {
const result = await service.retrieveVariant(
variantOne.id,
{
select: ["id", "title", "product.title"] as any,
relations: ["product"],
}
)
expect(result).toEqual(
expect.objectContaining({
id: "test-1",
title: "variant 1",
product_id: "product-1",
product: expect.objectContaining({
id: "product-1",
title: "product 1",
}),
}),
)
})
it("should throw an error when a variant with ID does not exist", async () => {
let error
try {
await service.retrieveVariant("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual("ProductVariant with id: does-not-exist was not found")
})
})
})
@@ -10,7 +10,7 @@ import { ProductTypes } from "@medusajs/types"
jest.setTimeout(30000)
describe("Product Service", () => {
describe("Product tag Service", () => {
let service: ProductTagService
let testManager: SqlEntityManager
let repositoryManager: SqlEntityManager
@@ -1,6 +1,6 @@
import { TestDatabase } from "../../../utils"
import { ProductVariantService } from "@services"
import { ProductVariantRepository } from "@repositories"
import { ProductService, ProductVariantService } from "@services"
import { ProductRepository, ProductVariantRepository } from "@repositories"
import { Product, ProductTag, ProductVariant } from "@models"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { Collection } from "@mikro-orm/core"
@@ -12,6 +12,7 @@ import {
createProductVariants,
} from "../../../__fixtures__/product"
import { productsData, variantsData } from "../../../__fixtures__/product/data"
import { buildProductVariantOnlyData } from "../../../__fixtures__/variant/data/create-variant"
describe("ProductVariant Service", () => {
let service: ProductVariantService
@@ -20,6 +21,7 @@ describe("ProductVariant Service", () => {
let variantOne: ProductVariant
let variantTwo: ProductVariant
let productOne: Product
const productVariantTestOne = "test-1"
beforeEach(async () => {
await TestDatabase.setupDatabase()
@@ -28,8 +30,17 @@ describe("ProductVariant Service", () => {
const productVariantRepository = new ProductVariantRepository({
manager: repositoryManager,
})
const productRepository = new ProductRepository({
manager: repositoryManager,
})
service = new ProductVariantService({ productVariantRepository })
const productService = new ProductService({
productRepository,
})
service = new ProductVariantService({
productService,
productVariantRepository,
})
})
afterEach(async () => {
@@ -47,7 +58,7 @@ describe("ProductVariant Service", () => {
})
variantOne = testManager.create(ProductVariant, {
id: "test-1",
id: productVariantTestOne,
title: "variant 1",
inventory_quantity: 10,
product: productOne,
@@ -95,7 +106,7 @@ describe("ProductVariant Service", () => {
it("passing populate, scopes the results of the response", async () => {
const results = await service.list(
{
id: "test-1",
id: productVariantTestOne,
},
{
select: ["id", "title", "product.title"] as any,
@@ -105,7 +116,7 @@ describe("ProductVariant Service", () => {
expect(results).toEqual([
expect.objectContaining({
id: "test-1",
id: productVariantTestOne,
title: "variant 1",
product: expect.objectContaining({
id: "product-1",
@@ -118,7 +129,7 @@ describe("ProductVariant Service", () => {
expect(JSON.parse(JSON.stringify(results))).toEqual([
{
id: "test-1",
id: productVariantTestOne,
title: "variant 1",
product_id: "product-1",
product: {
@@ -175,11 +186,148 @@ describe("ProductVariant Service", () => {
expect(JSON.parse(JSON.stringify(variants))).toEqual([
expect.objectContaining({
id: "test-1",
id: productVariantTestOne,
title: "variant title",
sku: "sku 1",
}),
])
})
})
describe("create", function () {
let products: Product[]
let productOptions!: ProductOption[]
beforeEach(async () => {
testManager = await TestDatabase.forkManager()
products = (await createProductAndTags(
testManager,
productsData
)) as Product[]
productOptions = await createOptions(testManager, [
{
id: "test-option-1",
title: "size",
product: products[0],
},
])
})
it("should create a variant", async () => {
const data = buildProductVariantOnlyData({
options: [
{
option: productOptions[0],
value: "XS",
},
],
})
const variants = await service.create(products[0].id, [data])
expect(variants).toHaveLength(1)
expect(variants[0].options).toHaveLength(1)
expect(JSON.parse(JSON.stringify(variants[0]))).toEqual(
expect.objectContaining({
id: expect.any(String),
title: data.title,
sku: data.sku,
inventory_quantity: 100,
allow_backorder: false,
manage_inventory: true,
variant_rank: 0,
product: expect.objectContaining({
id: products[0].id,
}),
options: expect.arrayContaining([
expect.objectContaining({
id: expect.any(String),
value: data.options![0].value,
}),
]),
})
)
})
})
describe("retrieve", () => {
beforeEach(async () => {
testManager = await TestDatabase.forkManager()
productOne = testManager.create(Product, {
id: "product-1",
title: "product 1",
status: ProductTypes.ProductStatus.PUBLISHED,
})
variantOne = testManager.create(ProductVariant, {
id: productVariantTestOne,
title: "variant 1",
inventory_quantity: 10,
product: productOne,
})
await testManager.persistAndFlush([variantOne])
})
it("should return the requested variant", async () => {
const result = await service.retrieve(variantOne.id)
expect(result).toEqual(
expect.objectContaining({
id: productVariantTestOne,
title: "variant 1",
}),
)
})
it("should return requested attributes when requested through config", async () => {
const result = await service.retrieve(
variantOne.id,
{
select: ["id", "title", "product.title"] as any,
relations: ["product"],
}
)
expect(result).toEqual(
expect.objectContaining({
id: productVariantTestOne,
title: "variant 1",
product_id: "product-1",
product: expect.objectContaining({
id: "product-1",
title: "product 1",
}),
}),
)
})
it("should throw an error when a variant with ID does not exist", async () => {
let error
try {
await service.retrieve("does-not-exist")
} catch (e) {
error = e
}
expect(error.message).toEqual("ProductVariant with id: does-not-exist was not found")
})
it("should throw an error when an id is not provided", async () => {
let error
try {
await service.retrieve(undefined as unknown as string)
} catch (e) {
error = e
}
expect(error.message).toEqual('"productVariantId" must be defined')
})
})
})
@@ -1,17 +1,14 @@
import { TestDatabase } from "../../../utils"
import {
ProductService,
ProductTagService,
ProductVariantService,
} from "@services"
import { ProductService } from "@services"
import { ProductRepository } from "@repositories"
import { Product, ProductCategory, ProductVariant } from "@models"
import { Image, Product, ProductCategory, ProductVariant } from "@models"
import { SqlEntityManager } from "@mikro-orm/postgresql"
import { ProductDTO } from "@medusajs/types"
import { createProductCategories } from "../../../__fixtures__/product-category"
import {
assignCategoriesToProduct,
createImages,
createProductAndTags,
createProductVariants,
} from "../../../__fixtures__/product"
@@ -20,13 +17,8 @@ import {
productsData,
variantsData,
} from "../../../__fixtures__/product/data"
const productVariantService = {
list: jest.fn(),
} as unknown as ProductVariantService
const productTagService = {
list: jest.fn(),
} as unknown as ProductTagService
import { buildProductOnlyData } from "../../../__fixtures__/product/data/create-product"
import { kebabCase } from "@medusajs/utils"
jest.setTimeout(30000)
@@ -48,8 +40,6 @@ describe("Product Service", () => {
service = new ProductService({
productRepository,
productVariantService,
productTagService,
})
})
@@ -57,7 +47,74 @@ describe("Product Service", () => {
await TestDatabase.clearDatabase()
})
describe("create", function () {
let images: Image[] = []
beforeEach(async () => {
testManager = await TestDatabase.forkManager()
images = await createImages(testManager, ["image-1"])
})
it("should create a product", async () => {
const data = buildProductOnlyData({
images,
thumbnail: images[0].url,
})
const products = await service.create([data])
expect(products).toHaveLength(1)
expect(JSON.parse(JSON.stringify(products[0]))).toEqual(
expect.objectContaining({
id: expect.any(String),
title: data.title,
handle: kebabCase(data.title),
description: data.description,
subtitle: data.subtitle,
is_giftcard: data.is_giftcard,
discountable: data.discountable,
thumbnail: images[0].url,
status: data.status,
images: expect.arrayContaining([
expect.objectContaining({
id: images[0].id,
url: images[0].url,
}),
]),
})
)
})
})
describe("list", () => {
describe("soft deleted", function () {
let deletedProduct
let product
beforeEach(async () => {
testManager = await TestDatabase.forkManager()
const products = await createProductAndTags(testManager, productsData)
product = products[1]
deletedProduct = await service.softDelete([products[0].id])
})
it("should list all products that are not deleted", async () => {
const products = await service.list()
expect(products).toHaveLength(1)
expect(products[0].id).toEqual(product.id)
})
it("should list all products including the deleted", async () => {
const products = await service.list({}, { withDeleted: true })
expect(products).toHaveLength(2)
})
})
describe("relation: tags", () => {
beforeEach(async () => {
testManager = await TestDatabase.forkManager()
@@ -65,7 +122,7 @@ describe("Product Service", () => {
products = await createProductAndTags(testManager, productsData)
})
it("filter by id and including relations", async () => {
it("should filter by id and including relations", async () => {
const productsResult = await service.list(
{
id: products[0].id,
@@ -95,7 +152,7 @@ describe("Product Service", () => {
})
})
it("filter by id and without relations", async () => {
it("should filter by id and without relations", async () => {
const productsResult = await service.list({
id: products[0].id,
})
@@ -137,7 +194,7 @@ describe("Product Service", () => {
)
})
it("filter by categories relation and scope fields", async () => {
it("should filter by categories relation and scope fields", async () => {
const products = await service.list(
{
id: workingProduct.id,
@@ -187,7 +244,7 @@ describe("Product Service", () => {
])
})
it("returns empty array when querying for a category that doesnt exist", async () => {
it("should returns empty array when querying for a category that doesnt exist", async () => {
const products = await service.list(
{
id: workingProduct.id,
@@ -215,7 +272,7 @@ describe("Product Service", () => {
variants = await createProductVariants(testManager, variantsData)
})
it("filter by id and including relations", async () => {
it("should filter by id and including relations", async () => {
const productsResult = await service.list(
{
id: products[0].id,
@@ -254,4 +311,52 @@ describe("Product Service", () => {
})
})
})
describe("softDelete", function () {
let images: Image[] = []
beforeEach(async () => {
testManager = await TestDatabase.forkManager()
images = await createImages(testManager, ["image-1"])
})
it("should soft delete a product", async () => {
const data = buildProductOnlyData({
images,
thumbnail: images[0].url,
})
const products = await service.create([data])
const deleteProducts = await service.softDelete(products.map((p) => p.id))
expect(deleteProducts).toHaveLength(1)
expect(deleteProducts[0].deleted_at).not.toBeNull()
})
})
describe("restore", function () {
let images: Image[] = []
beforeEach(async () => {
testManager = await TestDatabase.forkManager()
images = await createImages(testManager, ["image-1"])
})
it("should restore a soft deleted product", async () => {
const data = buildProductOnlyData({
images,
thumbnail: images[0].url,
})
const products = await service.create([data])
const product = products[0]
await service.softDelete([product.id])
const restoreProducts = await service.restore([product.id])
expect(restoreProducts).toHaveLength(1)
expect(restoreProducts[0].deleted_at).toBeNull()
})
})
})
+4 -4
View File
@@ -1,8 +1,8 @@
const { dropDatabase } = require("pg-god")
const DB_HOST = process.env.DB_HOST
const DB_USERNAME = process.env.DB_USERNAME
const DB_PASSWORD = process.env.DB_PASSWORD
const DB_HOST = process.env.DB_HOST ?? "localhost"
const DB_USERNAME = process.env.DB_USERNAME ?? "postgres"
const DB_PASSWORD = process.env.DB_PASSWORD ?? ""
const DB_NAME = process.env.DB_TEMP_NAME
const pgGodCredentials = {
@@ -16,7 +16,7 @@ afterAll(async () => {
await dropDatabase({ databaseName: DB_NAME }, pgGodCredentials)
} catch (e) {
console.error(
`This might fail if it is run during the unit tests since there is no database to drop. Otherwise, please check what is the issue. ${e}`
`This might fail if it is run during the unit tests since there is no database to drop. Otherwise, please check what is the issue. ${e.message}`
)
}
})