feat(fulfillment): Soft deletes (#6630)
**What** - Ensure soft delete works properly according to the soft delete configuration and validate all relation of the entire data model - Add is_enabled to the providers in order to manage new providers to enabled or disabled - include joiner config update FIXES CORE-1853 FIXES CORE-1830 FIXES CORE-1719
This commit is contained in:
@@ -6,6 +6,8 @@ export function generateCreateFulfillmentData(
|
||||
shipping_option_id: string
|
||||
}
|
||||
) {
|
||||
const randomString = Math.random().toString(36).substring(7)
|
||||
|
||||
return {
|
||||
location_id: "test-location",
|
||||
packed_at: null,
|
||||
@@ -17,28 +19,28 @@ export function generateCreateFulfillmentData(
|
||||
shipping_option_id: data.shipping_option_id,
|
||||
metadata: data.metadata ?? null,
|
||||
delivery_address: data.delivery_address ?? {
|
||||
address_1: "test-address",
|
||||
address_2: "test-address",
|
||||
city: "test-city",
|
||||
postal_code: "test-postal-code",
|
||||
country_code: "test-country-code",
|
||||
province: "test-province",
|
||||
phone: "test-phone",
|
||||
full_name: "test-full-name",
|
||||
address_1: "test-address_" + randomString,
|
||||
address_2: "test-address_" + randomString,
|
||||
city: "test-city_" + randomString,
|
||||
postal_code: "test-postal-code_" + randomString,
|
||||
country_code: "test-country-code_" + randomString,
|
||||
province: "test-province_" + randomString,
|
||||
phone: "test-phone_" + randomString,
|
||||
full_name: "test-full-name_" + randomString,
|
||||
},
|
||||
items: data.items ?? [
|
||||
{
|
||||
title: "test-title",
|
||||
sku: "test-sku",
|
||||
title: "test-title_" + randomString,
|
||||
sku: "test-sku_" + randomString,
|
||||
quantity: 1,
|
||||
barcode: "test-barcode",
|
||||
barcode: "test-barcode_" + randomString,
|
||||
},
|
||||
],
|
||||
labels: data.labels ?? [
|
||||
{
|
||||
tracking_number: "test-tracking-number",
|
||||
tracking_url: "test-tracking-url",
|
||||
label_url: "test-label-url",
|
||||
tracking_number: "test-tracking-number_" + randomString,
|
||||
tracking_url: "test-tracking-url_" + randomString,
|
||||
label_url: "test-label-url_" + randomString,
|
||||
},
|
||||
],
|
||||
order: data.order ?? {},
|
||||
|
||||
@@ -1,2 +1,52 @@
|
||||
import { generateCreateShippingOptionsData } from "./shipping-options"
|
||||
import { generateCreateFulfillmentData } from "./fulfillment"
|
||||
import { IFulfillmentModuleService } from "@medusajs/types"
|
||||
|
||||
export * from "./shipping-options"
|
||||
export * from "./fulfillment"
|
||||
|
||||
export async function createFullDataStructure(
|
||||
service: IFulfillmentModuleService,
|
||||
{
|
||||
providerId,
|
||||
}: {
|
||||
providerId: string
|
||||
}
|
||||
) {
|
||||
const randomString = Math.random().toString(36).substring(7)
|
||||
|
||||
const shippingProfile = await service.createShippingProfiles({
|
||||
// generate random string
|
||||
name: "test_" + randomString,
|
||||
type: "default",
|
||||
})
|
||||
const fulfillmentSet = await service.create({
|
||||
name: "test_" + randomString,
|
||||
type: "test-type",
|
||||
})
|
||||
const serviceZone = await service.createServiceZones({
|
||||
name: "test_" + randomString,
|
||||
fulfillment_set_id: fulfillmentSet.id,
|
||||
geo_zones: [
|
||||
{
|
||||
type: "country",
|
||||
country_code: "US_" + randomString,
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
const shippingOption = await service.createShippingOptions(
|
||||
generateCreateShippingOptionsData({
|
||||
fulfillment_provider_id: providerId,
|
||||
service_zone_id: serviceZone.id,
|
||||
shipping_profile_id: shippingProfile.id,
|
||||
})
|
||||
)
|
||||
|
||||
await service.createFulfillment(
|
||||
generateCreateFulfillmentData({
|
||||
provider_id: providerId,
|
||||
shipping_option_id: shippingOption.id,
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
@@ -14,14 +14,16 @@ export function generateCreateShippingOptionsData({
|
||||
name?: string
|
||||
type?: CreateShippingOptionDTO["type"]
|
||||
}): Required<CreateShippingOptionDTO> {
|
||||
const randomString = Math.random().toString(36).substring(7)
|
||||
|
||||
return {
|
||||
service_zone_id: service_zone_id,
|
||||
shipping_profile_id: shipping_profile_id,
|
||||
fulfillment_provider_id: fulfillment_provider_id,
|
||||
type: type ?? {
|
||||
code: "test-type",
|
||||
description: "test-description",
|
||||
label: "test-label",
|
||||
code: "test-type_" + randomString,
|
||||
description: "test-description_" + randomString,
|
||||
label: "test-label_" + randomString,
|
||||
},
|
||||
data: data ?? {
|
||||
amount: 1000,
|
||||
|
||||
+2
-71
@@ -1,14 +1,11 @@
|
||||
import { resolve } from "path"
|
||||
import { Modules, ModulesDefinition } from "@medusajs/modules-sdk"
|
||||
import { Modules } from "@medusajs/modules-sdk"
|
||||
import { IFulfillmentModuleService } from "@medusajs/types"
|
||||
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
|
||||
import {
|
||||
generateCreateFulfillmentData,
|
||||
generateCreateShippingOptionsData,
|
||||
} from "../../__fixtures__"
|
||||
import { initModules } from "medusa-test-utils/dist"
|
||||
import { FulfillmentProviderService } from "@services"
|
||||
import { FulfillmentProviderServiceFixtures } from "../../__fixtures__/providers"
|
||||
|
||||
jest.setTimeout(100000)
|
||||
|
||||
@@ -33,73 +30,7 @@ const providerId = "fixtures-fulfillment-provider_test-provider"
|
||||
moduleIntegrationTestRunner({
|
||||
moduleName: Modules.FULFILLMENT,
|
||||
moduleOptions: moduleOptions,
|
||||
testSuite: ({
|
||||
MikroOrmWrapper,
|
||||
service,
|
||||
}: SuiteOptions<IFulfillmentModuleService>) => {
|
||||
describe("Fulfillment Module Service", () => {
|
||||
it("should load and save all the providers on bootstrap", async () => {
|
||||
const databaseConfig = {
|
||||
schema: "public",
|
||||
clientUrl: MikroOrmWrapper.clientUrl,
|
||||
}
|
||||
|
||||
const providersConfig = {}
|
||||
for (let i = 0; i < 10; i++) {
|
||||
providersConfig[`provider-${i}`] = {}
|
||||
}
|
||||
|
||||
const moduleOptions = {
|
||||
databaseConfig,
|
||||
modulesConfig: {
|
||||
[Modules.FULFILLMENT]: {
|
||||
definition: ModulesDefinition[Modules.FULFILLMENT],
|
||||
options: {
|
||||
databaseConfig,
|
||||
providers: [
|
||||
{
|
||||
resolve: resolve(
|
||||
process.cwd() +
|
||||
"/integration-tests/__fixtures__/providers/default-provider"
|
||||
),
|
||||
options: {
|
||||
config: providersConfig,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const { shutdown } = await initModules(moduleOptions)
|
||||
|
||||
const fulfillmentProviderrs =
|
||||
await MikroOrmWrapper.forkManager().execute(
|
||||
`SELECT * FROM fulfillment_provider`
|
||||
)
|
||||
|
||||
expect(fulfillmentProviderrs).toHaveLength(
|
||||
Object.keys(providersConfig).length + 1 // +1 for the default provider
|
||||
)
|
||||
|
||||
for (const [name] of Object.entries(providersConfig)) {
|
||||
const provider = fulfillmentProviderrs.find((p) => {
|
||||
return (
|
||||
p.id ===
|
||||
FulfillmentProviderService.getRegistrationIdentifier(
|
||||
FulfillmentProviderServiceFixtures,
|
||||
name
|
||||
)
|
||||
)
|
||||
})
|
||||
expect(provider).toBeDefined()
|
||||
}
|
||||
|
||||
await shutdown()
|
||||
})
|
||||
})
|
||||
|
||||
testSuite: ({ service }: SuiteOptions<IFulfillmentModuleService>) => {
|
||||
describe("Fulfillment Module Service", () => {
|
||||
describe("read", () => {
|
||||
it("should list fulfillment", async () => {
|
||||
|
||||
+264
@@ -0,0 +1,264 @@
|
||||
import { Modules, ModulesDefinition } from "@medusajs/modules-sdk"
|
||||
import { FulfillmentSetDTO, IFulfillmentModuleService } from "@medusajs/types"
|
||||
import {
|
||||
initModules,
|
||||
moduleIntegrationTestRunner,
|
||||
SuiteOptions,
|
||||
} from "medusa-test-utils/dist"
|
||||
import { resolve } from "path"
|
||||
import { createFullDataStructure } from "../../__fixtures__"
|
||||
import { FulfillmentProviderService } from "@services"
|
||||
import { FulfillmentProviderServiceFixtures } from "../../__fixtures__/providers"
|
||||
|
||||
let moduleOptions = {
|
||||
providers: [
|
||||
{
|
||||
resolve: resolve(
|
||||
process.cwd() +
|
||||
"/integration-tests/__fixtures__/providers/default-provider"
|
||||
),
|
||||
options: {
|
||||
config: {
|
||||
"test-provider": {},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
let providerId = "fixtures-fulfillment-provider_test-provider"
|
||||
|
||||
async function list(
|
||||
service: IFulfillmentModuleService,
|
||||
...args: Parameters<IFulfillmentModuleService["list"]>
|
||||
) {
|
||||
const [filters = {}, config = {}] = args
|
||||
|
||||
const finalConfig = {
|
||||
relations: [
|
||||
"service_zones.geo_zones",
|
||||
"service_zones.shipping_options.shipping_profile",
|
||||
"service_zones.shipping_options.fulfillment_provider",
|
||||
"service_zones.shipping_options.type",
|
||||
"service_zones.shipping_options.rules",
|
||||
"service_zones.shipping_options.fulfillments.labels",
|
||||
"service_zones.shipping_options.fulfillments.items",
|
||||
"service_zones.shipping_options.fulfillments.delivery_address",
|
||||
],
|
||||
...config,
|
||||
}
|
||||
|
||||
return await service.list(filters, finalConfig)
|
||||
}
|
||||
|
||||
function expectSoftDeleted(
|
||||
fulfillmentSets: FulfillmentSetDTO[],
|
||||
{ softDeleted = false } = {}
|
||||
) {
|
||||
expect(fulfillmentSets).toHaveLength(1)
|
||||
|
||||
let fulfillmentSet = fulfillmentSets[0]
|
||||
expect(!!fulfillmentSet.deleted_at).toEqual(softDeleted)
|
||||
expect(fulfillmentSet.service_zones).toHaveLength(1)
|
||||
|
||||
let serviceZone = fulfillmentSet.service_zones[0]
|
||||
expect(!!serviceZone.deleted_at).toEqual(softDeleted)
|
||||
expect(serviceZone.geo_zones).toHaveLength(1)
|
||||
expect(serviceZone.shipping_options).toHaveLength(1)
|
||||
|
||||
let geoZone = serviceZone.geo_zones[0]
|
||||
expect(!!geoZone.deleted_at).toEqual(softDeleted)
|
||||
|
||||
let shippingOption = serviceZone.shipping_options[0]
|
||||
expect(!!shippingOption.deleted_at).toEqual(softDeleted)
|
||||
expect(!!shippingOption.shipping_profile.deleted_at).toEqual(false)
|
||||
expect(!!shippingOption.type.deleted_at).toEqual(softDeleted)
|
||||
expect(shippingOption.fulfillments).toHaveLength(1)
|
||||
expect(shippingOption.rules).toHaveLength(1)
|
||||
|
||||
let rule = shippingOption.rules[0]
|
||||
expect(!!rule.deleted_at).toEqual(softDeleted)
|
||||
|
||||
/**
|
||||
* We do not expect the fulfillment to be soft deleted when soft deleting parents entities
|
||||
*/
|
||||
|
||||
let fulfillment = shippingOption.fulfillments[0]
|
||||
expect(!!fulfillment.deleted_at).toEqual(false)
|
||||
expect(fulfillment.labels).toHaveLength(1)
|
||||
expect(fulfillment.items).toHaveLength(1)
|
||||
|
||||
let label = fulfillment.labels[0]
|
||||
expect(!!label.deleted_at).toEqual(false)
|
||||
|
||||
let item = fulfillment.items[0]
|
||||
expect(!!item.deleted_at).toEqual(false)
|
||||
|
||||
let deliveryAddress = fulfillment.delivery_address
|
||||
expect(!!deliveryAddress.deleted_at).toEqual(false)
|
||||
}
|
||||
|
||||
moduleIntegrationTestRunner({
|
||||
moduleName: Modules.FULFILLMENT,
|
||||
moduleOptions,
|
||||
testSuite: ({
|
||||
MikroOrmWrapper,
|
||||
service,
|
||||
}: SuiteOptions<IFulfillmentModuleService>) =>
|
||||
describe("Fulfillment Module Service", () => {
|
||||
it("should load and save all the providers on bootstrap with the correct is_enabled value", async () => {
|
||||
const databaseConfig = {
|
||||
schema: "public",
|
||||
clientUrl: MikroOrmWrapper.clientUrl,
|
||||
}
|
||||
|
||||
const providersConfig = {}
|
||||
for (let i = 0; i < 10; i++) {
|
||||
providersConfig[`provider-${i}`] = {}
|
||||
}
|
||||
|
||||
let moduleOptions = {
|
||||
databaseConfig,
|
||||
modulesConfig: {
|
||||
[Modules.FULFILLMENT]: {
|
||||
definition: ModulesDefinition[Modules.FULFILLMENT],
|
||||
options: {
|
||||
databaseConfig,
|
||||
providers: [
|
||||
{
|
||||
resolve: resolve(
|
||||
process.cwd() +
|
||||
"/integration-tests/__fixtures__/providers/default-provider"
|
||||
),
|
||||
options: {
|
||||
config: providersConfig,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
let { shutdown } = await initModules(moduleOptions)
|
||||
|
||||
let fulfillmentProviders = await MikroOrmWrapper.forkManager().execute(
|
||||
`SELECT * FROM fulfillment_provider`
|
||||
)
|
||||
|
||||
expect(fulfillmentProviders).toHaveLength(
|
||||
Object.keys(providersConfig).length + 1 // +1 for the default provider
|
||||
)
|
||||
|
||||
for (const [name] of Object.entries(providersConfig)) {
|
||||
const provider = fulfillmentProviders.find((p) => {
|
||||
return (
|
||||
p.id ===
|
||||
FulfillmentProviderService.getRegistrationIdentifier(
|
||||
FulfillmentProviderServiceFixtures,
|
||||
name
|
||||
)
|
||||
)
|
||||
})
|
||||
expect(provider).toBeDefined()
|
||||
expect(provider.is_enabled).toBeTruthy()
|
||||
}
|
||||
|
||||
await shutdown()
|
||||
|
||||
const providersConfig2 = {}
|
||||
for (let i = 10; i < 20; i++) {
|
||||
providersConfig2[`provider-${i}`] = {}
|
||||
}
|
||||
|
||||
moduleOptions = {
|
||||
databaseConfig,
|
||||
modulesConfig: {
|
||||
[Modules.FULFILLMENT]: {
|
||||
definition: ModulesDefinition[Modules.FULFILLMENT],
|
||||
options: {
|
||||
databaseConfig,
|
||||
providers: [
|
||||
{
|
||||
resolve: resolve(
|
||||
process.cwd() +
|
||||
"/integration-tests/__fixtures__/providers/default-provider"
|
||||
),
|
||||
options: {
|
||||
config: providersConfig2,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const medusaApp = await initModules(moduleOptions)
|
||||
shutdown = medusaApp.shutdown
|
||||
|
||||
fulfillmentProviders = await MikroOrmWrapper.forkManager().execute(
|
||||
`SELECT * FROM fulfillment_provider`
|
||||
)
|
||||
|
||||
expect(fulfillmentProviders).toHaveLength(
|
||||
Object.keys(providersConfig2).length +
|
||||
Object.keys(providersConfig).length +
|
||||
1 // +1 for the default provider
|
||||
)
|
||||
|
||||
const allProviders = Object.assign(
|
||||
{},
|
||||
providersConfig,
|
||||
providersConfig2
|
||||
)
|
||||
|
||||
for (const [name] of Object.entries(allProviders)) {
|
||||
const provider = fulfillmentProviders.find((p) => {
|
||||
return (
|
||||
p.id ===
|
||||
FulfillmentProviderService.getRegistrationIdentifier(
|
||||
FulfillmentProviderServiceFixtures,
|
||||
name
|
||||
)
|
||||
)
|
||||
})
|
||||
expect(provider).toBeDefined()
|
||||
|
||||
const isEnabled = !!providersConfig2[name]
|
||||
expect(provider.is_enabled).toEqual(isEnabled)
|
||||
}
|
||||
|
||||
await shutdown().catch(() => void 0)
|
||||
})
|
||||
|
||||
it("should soft delete and restore the data respecting the configured cascade", async () => {
|
||||
await createFullDataStructure(service, { providerId })
|
||||
|
||||
let fulfillmentSets = await list(service)
|
||||
expectSoftDeleted(fulfillmentSets)
|
||||
|
||||
/**
|
||||
* Soft delete the fulfillment set
|
||||
*/
|
||||
|
||||
await service.softDelete(fulfillmentSets[0].id)
|
||||
const deletedFulfillmentSets = await list(
|
||||
service,
|
||||
{},
|
||||
{
|
||||
withDeleted: true,
|
||||
}
|
||||
)
|
||||
expectSoftDeleted(deletedFulfillmentSets, { softDeleted: true })
|
||||
|
||||
/**
|
||||
* Restore the fulfillment set
|
||||
*/
|
||||
|
||||
await service.restore(fulfillmentSets[0].id)
|
||||
const restoredFulfillmentSets = await list(service)
|
||||
expectSoftDeleted(restoredFulfillmentSets)
|
||||
})
|
||||
}),
|
||||
})
|
||||
Reference in New Issue
Block a user