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,24 @@
|
||||
import { FindOptions, RepositoryService } from "@medusajs/types"
|
||||
|
||||
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.")
|
||||
}
|
||||
}
|
||||
|
||||
CustomRepository.prototype.find = jest.fn().mockImplementation(async () => [])
|
||||
CustomRepository.prototype.findAndCount = jest
|
||||
.fn()
|
||||
.mockImplementation(async () => [])
|
||||
|
||||
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 {}
|
||||
@@ -0,0 +1,28 @@
|
||||
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"
|
||||
},
|
||||
]
|
||||
@@ -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 = await manager.create(ProductCategory, {
|
||||
...categoryDataClone,
|
||||
parent_category: parentCategory,
|
||||
})
|
||||
|
||||
categories.push(category)
|
||||
}
|
||||
|
||||
await manager.persistAndFlush(categories)
|
||||
|
||||
return categories
|
||||
}
|
||||
@@ -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"
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./categories"
|
||||
export * from "./products"
|
||||
@@ -0,0 +1,43 @@
|
||||
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",
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
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,
|
||||
},
|
||||
]
|
||||
@@ -0,0 +1,72 @@
|
||||
import { SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import {
|
||||
Product,
|
||||
ProductCategory,
|
||||
ProductCollection,
|
||||
ProductVariant,
|
||||
} from "@models"
|
||||
import ProductOption from "../../../src/models/product-option"
|
||||
|
||||
export async function createProductAndTags(
|
||||
manager: SqlEntityManager,
|
||||
data: any[]
|
||||
) {
|
||||
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: any[]
|
||||
) {
|
||||
const collections: any[] = collectionData.map((collectionData) => {
|
||||
return manager.create(ProductCollection, collectionData)
|
||||
})
|
||||
|
||||
await manager.persistAndFlush(collections)
|
||||
|
||||
return collections
|
||||
}
|
||||
|
||||
export async function createOptions(
|
||||
manager: SqlEntityManager,
|
||||
optionsData: any[]
|
||||
) {
|
||||
const options: any[] = optionsData.map((o) => {
|
||||
return manager.create(ProductOption, o)
|
||||
})
|
||||
|
||||
await manager.persistAndFlush(options)
|
||||
|
||||
return options
|
||||
}
|
||||
|
||||
export async function assignCategoriesToProduct(
|
||||
manager: SqlEntityManager,
|
||||
product: Product,
|
||||
categories: ProductCategory[]
|
||||
) {
|
||||
product.categories.add(categories)
|
||||
|
||||
await manager.persistAndFlush(product)
|
||||
|
||||
return product
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
if (typeof process.env.DB_TEMP_NAME === "undefined") {
|
||||
const tempName = parseInt(process.env.JEST_WORKER_ID || "1")
|
||||
process.env.DB_TEMP_NAME = `medusa-integration-${tempName}`
|
||||
}
|
||||
|
||||
process.env.MEDUSA_PRODUCT_DB_SCHEMA = "medusa-product"
|
||||
@@ -0,0 +1,22 @@
|
||||
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_NAME = process.env.DB_TEMP_NAME
|
||||
|
||||
const pgGodCredentials = {
|
||||
user: DB_USERNAME,
|
||||
password: DB_PASSWORD,
|
||||
host: DB_HOST,
|
||||
}
|
||||
|
||||
afterAll(async () => {
|
||||
try {
|
||||
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}`
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,95 @@
|
||||
import { TSMigrationGenerator } from "@mikro-orm/migrations"
|
||||
import { MikroORM, Options, SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
import * as ProductModels from "@models"
|
||||
import * as process from "process"
|
||||
|
||||
const DB_HOST = process.env.DB_HOST ?? "localhost"
|
||||
const DB_USERNAME = process.env.DB_USERNAME ?? ""
|
||||
const DB_PASSWORD = process.env.DB_PASSWORD
|
||||
const DB_NAME = process.env.DB_TEMP_NAME
|
||||
export const DB_URL = `postgres://${DB_USERNAME}${
|
||||
DB_PASSWORD ? `:${DB_PASSWORD}` : ""
|
||||
}@${DB_HOST}/${DB_NAME}`
|
||||
|
||||
const ORMConfig: Options = {
|
||||
type: "postgresql",
|
||||
clientUrl: DB_URL,
|
||||
entities: Object.values(ProductModels),
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
debug: false,
|
||||
migrations: {
|
||||
path: "../../src/migrations",
|
||||
pathTs: "../../src/migrations",
|
||||
glob: "!(*.d).{js,ts}",
|
||||
silent: true,
|
||||
dropTables: true,
|
||||
transactional: true,
|
||||
allOrNothing: true,
|
||||
safe: false,
|
||||
generator: TSMigrationGenerator,
|
||||
},
|
||||
}
|
||||
|
||||
interface TestDatabase {
|
||||
orm: MikroORM | null
|
||||
manager: SqlEntityManager | null
|
||||
|
||||
setupDatabase(): Promise<void>
|
||||
clearDatabase(): Promise<void>
|
||||
getManager(): SqlEntityManager
|
||||
forkManager(): Promise<SqlEntityManager>
|
||||
getORM(): MikroORM
|
||||
}
|
||||
|
||||
export const TestDatabase: TestDatabase = {
|
||||
orm: null,
|
||||
manager: null,
|
||||
|
||||
getManager() {
|
||||
if (this.manager === null) {
|
||||
throw new Error("manager entity not available")
|
||||
}
|
||||
|
||||
return this.manager
|
||||
},
|
||||
|
||||
async forkManager() {
|
||||
if (this.manager === null) {
|
||||
throw new Error("manager entity not available")
|
||||
}
|
||||
|
||||
return await this.manager.fork()
|
||||
},
|
||||
|
||||
getORM() {
|
||||
if (this.orm === null) {
|
||||
throw new Error("orm entity not available")
|
||||
}
|
||||
|
||||
return this.orm
|
||||
},
|
||||
|
||||
async setupDatabase() {
|
||||
// Initializing the ORM
|
||||
this.orm = await MikroORM.init(ORMConfig)
|
||||
|
||||
if (this.orm === null) {
|
||||
throw new Error("ORM not configured")
|
||||
}
|
||||
|
||||
this.manager = await this.orm.em
|
||||
|
||||
await this.orm.schema.refreshDatabase() // ensure db exists and is fresh
|
||||
},
|
||||
|
||||
async clearDatabase() {
|
||||
if (this.orm === null) {
|
||||
throw new Error("ORM not configured")
|
||||
}
|
||||
|
||||
await this.orm.close()
|
||||
|
||||
this.orm = null
|
||||
this.manager = null
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export * from "./database"
|
||||
Reference in new issue
Block a user