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:
Adrien de Peretti
2024-03-11 15:56:08 +00:00
committed by GitHub
parent 78e5ec459a
commit d9d5afc3cf
23 changed files with 985 additions and 696 deletions
@@ -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,
@@ -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 () => {
@@ -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)
})
}),
})