feat: Product Module (#4161)
* chore: boilerplate setup * wip: add Product, ProductTag, ProductType, ProductCollection models * wip: `IProductService` definition * wip: test function in index, build passing * fix: where condition * chore: get boilerplate working with modules sdk, create a boilerplate test, create product variant model, register services properly * chore: added variant to model * chore: changed definition details + add migrator * cleanup and update product entity * Update product unique index to include soft deleted * Migrations tests * generated migration * update dev orm config * add path aliases * WIP * chore: added boilerplate integration test + database helper + product variant migraiton + model * chore: remove old test utils * update ts and jest config to include path aliases * tweak config * WIP migrations variant * Migrations round * integration tests migrations polishing * chore: fixed issues with test db * fix path aliases when published * use ts-alias * fix connection loader * fixes * wip: product list * (WIP): Data access layer * (WIP): DAL cleanup services * wip: `ProductTag` DAL * wip: `ProductTag` expose list in product service * (WIP): Continue DAL and test list product filtering/populate * WIP: unit tests * chore: added tests for service - productvariant * chore: WIP finding issues with orm manager fork * WIP fix fields selection * chore: make text fixes work * (WIP) product integration * (WIP) product integration * chore: create a product in variant test * list product with relations * wip: `ProductTag` service + integrations * chore: added with and without serialization example * chore: remove only in spec * wip: `ProductCollection` service + integrations * uncomment product.variants * Update type IProductService * (WIP) type work * (WIP) Product variants relation * WIP: replacable data layer * (WIP): Use bundle types * WIP: update type * WIP upadte tests * WIP * wip: options/option values entites * (WIP): cleanup * wip: add option value to variant, fix collection * Integration tests for custom data access layer * update tests * chore: merge with latest branch * chore: scope tests to relations and add category to models/index * chore: ignore dist folders for jest * chore: modularize spec data file * improve DX * module fixture naming * chore: added category tests + fix model * chore: use kebab case * chore: allow scoping products by category id * chore: replace `kebabCase` import * feat: add `deleted_at` to options * improve typings * chore: wip * fix query util * revert webpack * fix: update option models, create option DTOs, tests wip, fix `deduplicateIfNecessary` returning `undefined` * fix: merge conflict * WIP connection * rm unsues deps * fix migrations * fix query util * chore: adds mpath on creation * WIP update types * improve typings * WIP typeings improvement * WIP * deps * chore: package medusa/product ot medusa-commerce/product * chore: added product categories service + descendants filter * add missing index * Add support for strict categories not in * Add support for strict categories not in * lint * rename module * rename module * Create small-ducks-doubt.md * yarn lock * update initialise * chore: fix/finalise DTOs * fix: wrong types in `IProductService` * fix type * Load database config from env if present (#4175) * Load database config from env if present * Load database config from env if present * options optionnal * update util * add defaults * improve filterable interfaces * fix import * fix types * remove medusa-telemetry from modules-sdk * WIP fixing webpack issues when bootstraping module * cleanup * improve loading driver options * cleanup * yarn lock * fix import * improve sdk types and naming * align orther modules initialise method * fix module tests with singleton module * fix module tests with singleton module * add up/down migration scripts * update types * scripts * cleanup migrations and scripts * hash module singleton * cleanup migration * cleanup * fix stringifyCircular usage * improvements * fix deps * fix deps * improve load config utils * improve load config utils * fix deps * add declaration to the build * update yarn * Do not resolve a module path if the exports are explicitly given * fix module registration resolution path when exports are provided. Explicitly check for false and assign an empty string in this scenario for segregation purpose * add comment * fix migration options to prevent set replica errors * chore: update types to a proper depedency * chore: update type package * add seed scripts * Add descriptive error during database config loading * use MedusaError * chore: added lodash to package * add more test to the database config loader util * create bin scripts * add bin * update argv retrieval * update package.json * chore: add product category to injected deps * chore: replace with product category service * move dotenv usage to the functions * do not load db if there is custom manager * chore: fix some tests on products repo * chore: fixed product spec * chore: skip products module on modules register * stringifyCircular update * chore: fix incorrect module resolution * fix: circular stringify and non required module loading * yarn lock * target es5 * chore: mikro-orm back to 5.7.4 * revert module registry * skip external modules * es2020 * update indexes, migration and integration tests * rm only * unit test script should only run unit tests * Exclude product integration from the unit tests and make use of the global integration script to run all packages integration tests * fix integration tests * improve setup * cleanup * log error on setup fail * Create enum like for package names * chore: remove EOL * chore: review part 2 * renamve gateway to productModuleService * chore: added filters and collections to productmoduleservice * chore: add collection to the singleton instance * chore: remove skipped test + add todo * fix indexes on fields and relations + update migration * update yarn lock * update idx * add foreign key * rename interface and add listCategories * rename product module definition --------- Co-authored-by: fPolic <frane@medusajs.com> Co-authored-by: Riqwan Thamir <rmthamir@gmail.com> Co-authored-by: Carlos R. L. Rodrigues <rodrigolr@gmail.com>
This commit is contained in:
104 files changed
+6472
-176
No files matched your search
@@ -0,0 +1,109 @@
|
||||
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"
|
||||
|
||||
const beforeEach_ = async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
return await TestDatabase.forkManager()
|
||||
}
|
||||
|
||||
const afterEach_ = async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
}
|
||||
|
||||
describe("Product module", function () {
|
||||
describe("Using built-in data access layer", function () {
|
||||
let module
|
||||
let products: Product[]
|
||||
|
||||
beforeEach(async () => {
|
||||
const testManager = await beforeEach_()
|
||||
products = await createProductAndTags(testManager, productsData)
|
||||
|
||||
module = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(afterEach_)
|
||||
|
||||
it("should initialize", async () => {
|
||||
expect(module).toBeDefined()
|
||||
})
|
||||
|
||||
it("should return a list of product", async () => {
|
||||
const products = await module.list()
|
||||
expect(products).toHaveLength(2)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Using custom data access layer", function () {
|
||||
let module
|
||||
let products: Product[]
|
||||
|
||||
beforeEach(async () => {
|
||||
const testManager = await beforeEach_()
|
||||
|
||||
products = await createProductAndTags(testManager, productsData)
|
||||
|
||||
module = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
repositories: CustomRepositories,
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(afterEach_)
|
||||
|
||||
it("should initialize", async () => {
|
||||
expect(module).toBeDefined()
|
||||
})
|
||||
|
||||
it("should return a list of product", async () => {
|
||||
const products = await module.list()
|
||||
|
||||
expect(ProductRepository.prototype.find).toHaveBeenCalled()
|
||||
expect(products).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
|
||||
describe("Using custom data access layer and connection", function () {
|
||||
let module
|
||||
let products: Product[]
|
||||
|
||||
beforeEach(async () => {
|
||||
const testManager = await beforeEach_()
|
||||
products = await createProductAndTags(testManager, productsData)
|
||||
|
||||
MedusaModule.clearInstances()
|
||||
|
||||
module = await initialize({
|
||||
manager: testManager,
|
||||
repositories: CustomRepositories,
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(afterEach_)
|
||||
|
||||
it("should initialize and return a list of product", async () => {
|
||||
expect(module).toBeDefined()
|
||||
})
|
||||
|
||||
it("should return a list of product", async () => {
|
||||
const products = await module.list()
|
||||
|
||||
expect(ProductRepository.prototype.find).toHaveBeenCalled()
|
||||
expect(products).toHaveLength(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,233 @@
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
import { ProductCategoryService } from "@services"
|
||||
import { ProductCategoryRepository } from "@repositories"
|
||||
import { ProductCategory } from "@models"
|
||||
|
||||
import { TestDatabase } from "../../../utils"
|
||||
import { createProductCategories } from "../../../__fixtures__/product-category"
|
||||
import { productCategoriesData } from "../../../__fixtures__/product-category/data"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("ProductCategory Service", () => {
|
||||
let service: ProductCategoryService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
let productCategories!: ProductCategory[]
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
repositoryManager = await TestDatabase.forkManager()
|
||||
|
||||
const productCategoryRepository = new ProductCategoryRepository({
|
||||
manager: repositoryManager,
|
||||
})
|
||||
|
||||
service = new ProductCategoryService({
|
||||
productCategoryRepository,
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
})
|
||||
|
||||
describe("list", () => {
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
productCategories = await createProductCategories(
|
||||
testManager,
|
||||
productCategoriesData
|
||||
)
|
||||
})
|
||||
|
||||
it("lists all product categories", async () => {
|
||||
const productCategoryResults = await service.list(
|
||||
{},
|
||||
{
|
||||
select: ["id", "parent_category_id"] as any,
|
||||
}
|
||||
)
|
||||
|
||||
expect(productCategoryResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "category-0",
|
||||
parent_category_id: 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("scopes product categories by parent_category_id", async () => {
|
||||
let productCategoryResults = await service.list(
|
||||
{ parent_category_id: null },
|
||||
{
|
||||
select: ["id"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(productCategoryResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "category-0",
|
||||
}),
|
||||
])
|
||||
|
||||
productCategoryResults = await service.list({
|
||||
parent_category_id: "category-0",
|
||||
})
|
||||
|
||||
expect(productCategoryResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "category-1",
|
||||
}),
|
||||
])
|
||||
|
||||
productCategoryResults = await service.list({
|
||||
parent_category_id: ["category-1-b", "category-0"],
|
||||
})
|
||||
|
||||
expect(productCategoryResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "category-1",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: "category-1-b-1",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("includes the entire list of descendants when include_descendants_tree is true", async () => {
|
||||
const productCategoryResults = await service.list(
|
||||
{
|
||||
parent_category_id: null,
|
||||
include_descendants_tree: true,
|
||||
},
|
||||
{
|
||||
select: ["id", "handle"] as any,
|
||||
}
|
||||
)
|
||||
|
||||
const serializedObject = JSON.parse(
|
||||
JSON.stringify(productCategoryResults)
|
||||
)
|
||||
|
||||
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("scopes children when include_descendants_tree is true", async () => {
|
||||
const productCategoryResults = await service.list(
|
||||
{
|
||||
parent_category_id: null,
|
||||
include_descendants_tree: true,
|
||||
is_internal: false,
|
||||
},
|
||||
{
|
||||
select: ["id", "handle"] as any,
|
||||
}
|
||||
)
|
||||
|
||||
const serializedObject = JSON.parse(
|
||||
JSON.stringify(productCategoryResults)
|
||||
)
|
||||
|
||||
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: [],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
],
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,106 @@
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
import { ProductCollection } from "@models"
|
||||
import { ProductCollectionService } from "@services"
|
||||
import { ProductCollectionRepository } from "@repositories"
|
||||
|
||||
import { TestDatabase } from "../../../utils"
|
||||
import { createCollections } from "../../../__fixtures__/product"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("Product Service", () => {
|
||||
let service: ProductCollectionService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
let collectionsData: ProductCollection[] = []
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
repositoryManager = await TestDatabase.forkManager()
|
||||
|
||||
const productCollectionRepository = new ProductCollectionRepository({
|
||||
manager: repositoryManager,
|
||||
})
|
||||
|
||||
service = new ProductCollectionService({
|
||||
productCollectionRepository,
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
})
|
||||
|
||||
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 () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
collectionsData = await createCollections(testManager, data)
|
||||
})
|
||||
|
||||
it("list product collections", async () => {
|
||||
const tagsResults = await service.list()
|
||||
|
||||
expect(tagsResults).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 tagsResults = await service.list({ id: data![0].id })
|
||||
|
||||
expect(tagsResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
title: "col 1",
|
||||
}),
|
||||
])
|
||||
})
|
||||
|
||||
it("list product collections by title matching string", async () => {
|
||||
const tagsResults = await service.list({ title: "col 3 extra" })
|
||||
|
||||
expect(tagsResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-3",
|
||||
title: "col 3 extra",
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,121 @@
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
import { ProductTagService } from "@services"
|
||||
import { ProductTagRepository } from "@repositories"
|
||||
import { Product } from "@models"
|
||||
|
||||
import { TestDatabase } from "../../../utils"
|
||||
import { createProductAndTags } from "../../../__fixtures__/product"
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("Product Service", () => {
|
||||
let service: ProductTagService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
let data!: Product[]
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
repositoryManager = await TestDatabase.forkManager()
|
||||
|
||||
const productTagRepository = new ProductTagRepository({
|
||||
manager: repositoryManager,
|
||||
})
|
||||
|
||||
service = new ProductTagService({
|
||||
productTagRepository,
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
})
|
||||
|
||||
describe("list", () => {
|
||||
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 () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
data = await createProductAndTags(testManager, productsData)
|
||||
})
|
||||
|
||||
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({ value: "united kingdom" })
|
||||
|
||||
expect(tagsResults).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "tag-4",
|
||||
value: "United Kingdom",
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,185 @@
|
||||
import { TestDatabase } from "../../../utils"
|
||||
import { ProductVariantService } from "@services"
|
||||
import { ProductVariantRepository } from "@repositories"
|
||||
import { Product, ProductTag, ProductVariant } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { Collection } from "@mikro-orm/core"
|
||||
import { ProductTypes } from "@medusajs/types"
|
||||
import { ProductOption } from "@medusajs/client-types"
|
||||
import {
|
||||
createOptions,
|
||||
createProductAndTags,
|
||||
createProductVariants,
|
||||
} from "../../../__fixtures__/product"
|
||||
import { productsData, variantsData } from "../../../__fixtures__/product/data"
|
||||
|
||||
describe("ProductVariant Service", () => {
|
||||
let service: ProductVariantService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
let variantOne: ProductVariant
|
||||
let variantTwo: ProductVariant
|
||||
let productOne: Product
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
repositoryManager = await TestDatabase.forkManager()
|
||||
|
||||
const productVariantRepository = new ProductVariantRepository({
|
||||
manager: repositoryManager,
|
||||
})
|
||||
|
||||
service = new ProductVariantService({ productVariantRepository })
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
})
|
||||
|
||||
describe("list", () => {
|
||||
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: "test-1",
|
||||
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: "test-1",
|
||||
},
|
||||
{
|
||||
select: ["id", "title", "product.title"] as any,
|
||||
relations: ["product"],
|
||||
}
|
||||
)
|
||||
|
||||
expect(results).toEqual([
|
||||
expect.objectContaining({
|
||||
id: "test-1",
|
||||
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: "test-1",
|
||||
title: "variant 1",
|
||||
product_id: "product-1",
|
||||
product: {
|
||||
id: "product-1",
|
||||
title: "product 1",
|
||||
},
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe("relation: options", () => {
|
||||
let products: Product[]
|
||||
let variants: ProductVariant[]
|
||||
let options: ProductOption[]
|
||||
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.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: "test-1",
|
||||
title: "variant title",
|
||||
sku: "sku 1",
|
||||
}),
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,257 @@
|
||||
import { TestDatabase } from "../../../utils"
|
||||
import {
|
||||
ProductService,
|
||||
ProductTagService,
|
||||
ProductVariantService,
|
||||
} from "@services"
|
||||
import { ProductRepository } from "@repositories"
|
||||
import { Product, ProductCategory, ProductVariant } from "@models"
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import { ProductDTO } from "@medusajs/types"
|
||||
|
||||
import { createProductCategories } from "../../../__fixtures__/product-category"
|
||||
import {
|
||||
assignCategoriesToProduct,
|
||||
createProductAndTags,
|
||||
createProductVariants,
|
||||
} from "../../../__fixtures__/product"
|
||||
import {
|
||||
categoriesData,
|
||||
productsData,
|
||||
variantsData,
|
||||
} from "../../../__fixtures__/product/data"
|
||||
|
||||
const productVariantService = {
|
||||
list: jest.fn(),
|
||||
} as unknown as ProductVariantService
|
||||
const productTagService = {
|
||||
list: jest.fn(),
|
||||
} as unknown as ProductTagService
|
||||
|
||||
jest.setTimeout(30000)
|
||||
|
||||
describe("Product Service", () => {
|
||||
let service: ProductService
|
||||
let testManager: SqlEntityManager
|
||||
let repositoryManager: SqlEntityManager
|
||||
let products!: Product[]
|
||||
let variants!: ProductVariant[]
|
||||
let categories!: ProductCategory[]
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
repositoryManager = await TestDatabase.forkManager()
|
||||
|
||||
const productRepository = new ProductRepository({
|
||||
manager: repositoryManager,
|
||||
})
|
||||
|
||||
service = new ProductService({
|
||||
productRepository,
|
||||
productVariantService,
|
||||
productTagService,
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
})
|
||||
|
||||
describe("list", () => {
|
||||
describe("relation: tags", () => {
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
products = await createProductAndTags(testManager, productsData)
|
||||
})
|
||||
|
||||
it("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("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 TestDatabase.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("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.",
|
||||
},
|
||||
{
|
||||
id: "category-1",
|
||||
name: "category 1",
|
||||
handle: "category-1",
|
||||
mpath: "category-0.category-1.",
|
||||
},
|
||||
{
|
||||
id: "category-1-a",
|
||||
name: "category 1 a",
|
||||
handle: "category-1-a",
|
||||
mpath: "category-0.category-1.category-1-a.",
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it("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: variants", () => {
|
||||
beforeEach(async () => {
|
||||
testManager = await TestDatabase.forkManager()
|
||||
|
||||
products = await createProductAndTags(testManager, productsData)
|
||||
variants = await createProductVariants(testManager, variantsData)
|
||||
})
|
||||
|
||||
it("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,
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user