feat(fulfillment): Separate list and context rules validation (#6674)

**What**

- Add method to validate fulfillment option from the provider
- Separate list/list and count from context rules validation and add listShippingOptionsForContext

FIXES CORE-1861
This commit is contained in:
Adrien de Peretti
2024-03-15 13:25:51 +00:00
committed by GitHub
parent 1956dce80a
commit 3188e703b3
16 changed files with 1011 additions and 326 deletions
@@ -1,4 +1,4 @@
import {Modules} from "@medusajs/modules-sdk"
import { Modules } from "@medusajs/modules-sdk"
import {
CreateFulfillmentSetDTO,
CreateServiceZoneDTO,
@@ -6,8 +6,8 @@ import {
ServiceZoneDTO,
UpdateFulfillmentSetDTO,
} from "@medusajs/types"
import {GeoZoneType} from "@medusajs/utils"
import {moduleIntegrationTestRunner, SuiteOptions} from "medusa-test-utils"
import { GeoZoneType } from "@medusajs/utils"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
jest.setTimeout(100000)
@@ -17,123 +17,123 @@ moduleIntegrationTestRunner({
describe("Fulfillment Module Service", () => {
describe("read", () => {
it("should list fulfillment sets with a filter", async function () {
const createdSet1 = await service.create({
name: "test",
type: "test-type",
})
const createdSet2 = await service.create({
name: "test2",
type: "test-type",
service_zones: [
{
name: "test",
geo_zones: [
{
type: GeoZoneType.COUNTRY,
country_code: "fr",
},
],
},
{
name: "test2",
geo_zones: [
{
type: GeoZoneType.COUNTRY,
country_code: "fr",
},
],
},
{
name: "_test",
geo_zones: [
{
type: GeoZoneType.COUNTRY,
country_code: "fr",
},
],
},
],
})
let listedSets = await service.list(
{
type: createdSet1.type,
},
{
relations: ["service_zones"],
}
)
const listedSets2 = await service.list(
{
type: createdSet1.type,
},
{
relations: ["service_zones"],
}
)
expect(listedSets).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdSet1.id }),
expect.objectContaining({ id: createdSet2.id }),
])
)
// Respecting order id by default
expect(listedSets[1].service_zones).toEqual([
expect.objectContaining({ name: "test" }),
expect.objectContaining({ name: "test2" }),
expect.objectContaining({ name: "_test" }),
])
expect(listedSets2).toEqual(listedSets2)
listedSets = await service.list({
name: createdSet2.name,
})
expect(listedSets).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdSet2.id }),
])
)
expect(listedSets).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdSet1.id }),
])
)
listedSets = await service.list({
service_zones: { name: "test" },
})
expect(listedSets).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdSet2.id }),
])
)
expect(listedSets).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdSet1.id }),
])
)
listedSets = await service.list({
service_zones: { geo_zones: { country_code: "fr" } },
})
expect(listedSets).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdSet2.id }),
])
)
expect(listedSets).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdSet1.id }),
])
)
const createdSet1 = await service.create({
name: "test",
type: "test-type",
})
const createdSet2 = await service.create({
name: "test2",
type: "test-type",
service_zones: [
{
name: "test",
geo_zones: [
{
type: GeoZoneType.COUNTRY,
country_code: "fr",
},
],
},
{
name: "test2",
geo_zones: [
{
type: GeoZoneType.COUNTRY,
country_code: "fr",
},
],
},
{
name: "_test",
geo_zones: [
{
type: GeoZoneType.COUNTRY,
country_code: "fr",
},
],
},
],
})
let listedSets = await service.list(
{
type: createdSet1.type,
},
{
relations: ["service_zones"],
}
)
const listedSets2 = await service.list(
{
type: createdSet1.type,
},
{
relations: ["service_zones"],
}
)
expect(listedSets).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdSet1.id }),
expect.objectContaining({ id: createdSet2.id }),
])
)
// Respecting order id by default
expect(listedSets[1].service_zones).toEqual([
expect.objectContaining({ name: "test" }),
expect.objectContaining({ name: "test2" }),
expect.objectContaining({ name: "_test" }),
])
expect(listedSets2).toEqual(listedSets2)
listedSets = await service.list({
name: createdSet2.name,
})
expect(listedSets).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdSet2.id }),
])
)
expect(listedSets).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdSet1.id }),
])
)
listedSets = await service.list({
service_zones: { name: "test" },
})
expect(listedSets).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdSet2.id }),
])
)
expect(listedSets).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdSet1.id }),
])
)
listedSets = await service.list({
service_zones: { geo_zones: { country_code: "fr" } },
})
expect(listedSets).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdSet2.id }),
])
)
expect(listedSets).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdSet1.id }),
])
)
})
})
describe("mutations", () => {
@@ -349,6 +349,7 @@ moduleIntegrationTestRunner({
{
type: GeoZoneType.CITY,
country_code: "fr",
province_code: "test",
city: "lyon",
},
],
@@ -400,6 +401,91 @@ moduleIntegrationTestRunner({
expect(err).toBeDefined()
expect(err.constraint).toBe("IDX_fulfillment_set_name_unique")
})
it("should fail on creating a new fulfillment set with new service zones and new geo zones that are not valid", async function () {
let data: CreateFulfillmentSetDTO = {
name: "test",
type: "test-type",
service_zones: [
{
name: "test",
geo_zones: [
{
type: GeoZoneType.PROVINCE,
country_code: "fr",
} as any,
],
},
],
}
let err = await service.create(data).catch((e) => e)
expect(err.message).toBe(
"Missing required property province_code for geo zone type province"
)
data = {
name: "test",
type: "test-type",
service_zones: [
{
name: "test",
geo_zones: [
{
type: GeoZoneType.CITY,
country_code: "fr",
province_code: "test",
} as any,
],
},
],
}
err = await service.create(data).catch((e) => e)
expect(err.message).toBe(
"Missing required property city for geo zone type city"
)
data = {
name: "test",
type: "test-type",
service_zones: [
{
name: "test",
geo_zones: [
{
type: GeoZoneType.ZIP,
postal_expression: "test",
} as any,
],
},
],
}
err = await service.create(data).catch((e) => e)
expect(err.message).toBe(
"Missing required property country_code for geo zone type zip"
)
data = {
name: "test",
type: "test-type",
service_zones: [
{
name: "test",
geo_zones: [
{
type: "unknown",
postal_expression: "test",
} as any,
],
},
],
}
err = await service.create(data).catch((e) => e)
expect(err.message).toBe(`Invalid geo zone type: unknown`)
})
})
describe("on update", () => {
@@ -1,11 +1,11 @@
import {Modules} from "@medusajs/modules-sdk"
import { Modules } from "@medusajs/modules-sdk"
import {
CreateGeoZoneDTO,
IFulfillmentModuleService,
UpdateGeoZoneDTO,
} from "@medusajs/types"
import {GeoZoneType} from "@medusajs/utils"
import {moduleIntegrationTestRunner, SuiteOptions} from "medusa-test-utils"
import { GeoZoneType } from "@medusajs/utils"
import { moduleIntegrationTestRunner, SuiteOptions } from "medusa-test-utils"
jest.setTimeout(100000)
@@ -15,52 +15,52 @@ moduleIntegrationTestRunner({
describe("Fulfillment Module Service", () => {
describe("read", () => {
it("should list geo zones with a filter", async function () {
const fulfillmentSet = await service.create({
name: "test",
type: "test-type",
})
const serviceZone = await service.createServiceZones({
name: "test",
fulfillment_set_id: fulfillmentSet.id,
})
const createdZone1 = await service.createGeoZones({
service_zone_id: serviceZone.id,
type: GeoZoneType.COUNTRY,
country_code: "fr",
})
const createdZone2 = await service.createGeoZones({
service_zone_id: serviceZone.id,
type: GeoZoneType.COUNTRY,
country_code: "us",
})
let listedZones = await service.listGeoZones({
type: createdZone1.type,
})
expect(listedZones).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdZone1.id }),
expect.objectContaining({ id: createdZone2.id }),
])
)
listedZones = await service.listGeoZones({
country_code: createdZone2.country_code,
})
expect(listedZones).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdZone2.id }),
])
)
expect(listedZones).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdZone1.id }),
])
)
const fulfillmentSet = await service.create({
name: "test",
type: "test-type",
})
const serviceZone = await service.createServiceZones({
name: "test",
fulfillment_set_id: fulfillmentSet.id,
})
const createdZone1 = await service.createGeoZones({
service_zone_id: serviceZone.id,
type: GeoZoneType.COUNTRY,
country_code: "fr",
})
const createdZone2 = await service.createGeoZones({
service_zone_id: serviceZone.id,
type: GeoZoneType.COUNTRY,
country_code: "us",
})
let listedZones = await service.listGeoZones({
type: createdZone1.type,
})
expect(listedZones).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdZone1.id }),
expect.objectContaining({ id: createdZone2.id }),
])
)
listedZones = await service.listGeoZones({
country_code: createdZone2.country_code,
})
expect(listedZones).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdZone2.id }),
])
)
expect(listedZones).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdZone1.id }),
])
)
})
})
describe("mutations", () => {
@@ -131,6 +131,60 @@ moduleIntegrationTestRunner({
++i
}
})
it("should fail to create new geo zones that are not valid", async function () {
const fulfillmentSet = await service.create({
name: "test",
type: "test-type",
})
const serviceZone = await service.createServiceZones({
name: "test",
fulfillment_set_id: fulfillmentSet.id,
})
let data: CreateGeoZoneDTO = {
service_zone_id: serviceZone.id,
type: GeoZoneType.PROVINCE,
country_code: "fr",
} as any
let err = await service.createGeoZones(data).catch((e) => e)
expect(err.message).toBe(
"Missing required property province_code for geo zone type province"
)
data = {
service_zone_id: serviceZone.id,
type: GeoZoneType.CITY,
country_code: "fr",
province_code: "test",
} as any
err = await service.createGeoZones(data).catch((e) => e)
expect(err.message).toBe(
"Missing required property city for geo zone type city"
)
data = {
service_zone_id: serviceZone.id,
type: GeoZoneType.ZIP,
postal_expression: "test",
} as any
err = await service.createGeoZones(data).catch((e) => e)
expect(err.message).toBe(
"Missing required property country_code for geo zone type zip"
)
data = {
service_zone_id: serviceZone.id,
type: "unknown",
postal_expression: "test",
} as any
err = await service.createGeoZones(data).catch((e) => e)
expect(err.message).toBe(`Invalid geo zone type: unknown`)
})
})
describe("on update", () => {
@@ -16,56 +16,56 @@ moduleIntegrationTestRunner({
describe("Fulfillment Module Service", () => {
describe("read", () => {
it("should list service zones with a filter", async function () {
const fulfillmentSet = await service.create({
name: "test",
type: "test-type",
})
const createdZone1 = await service.createServiceZones({
name: "test",
fulfillment_set_id: fulfillmentSet.id,
})
const createdZone2 = await service.createServiceZones({
name: "test2",
fulfillment_set_id: fulfillmentSet.id,
geo_zones: [
{
type: GeoZoneType.COUNTRY,
country_code: "fr",
},
],
})
let listedZones = await service.listServiceZones({
name: createdZone2.name,
})
expect(listedZones).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdZone2.id }),
])
)
expect(listedZones).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdZone1.id }),
])
)
listedZones = await service.listServiceZones({
geo_zones: { country_code: "fr" },
})
expect(listedZones).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdZone2.id }),
])
)
expect(listedZones).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdZone1.id }),
])
)
const fulfillmentSet = await service.create({
name: "test",
type: "test-type",
})
const createdZone1 = await service.createServiceZones({
name: "test",
fulfillment_set_id: fulfillmentSet.id,
})
const createdZone2 = await service.createServiceZones({
name: "test2",
fulfillment_set_id: fulfillmentSet.id,
geo_zones: [
{
type: GeoZoneType.COUNTRY,
country_code: "fr",
},
],
})
let listedZones = await service.listServiceZones({
name: createdZone2.name,
})
expect(listedZones).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdZone2.id }),
])
)
expect(listedZones).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdZone1.id }),
])
)
listedZones = await service.listServiceZones({
geo_zones: { country_code: "fr" },
})
expect(listedZones).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdZone2.id }),
])
)
expect(listedZones).not.toEqual(
expect.arrayContaining([
expect.objectContaining({ id: createdZone1.id }),
])
)
})
})
describe("mutations", () => {
@@ -189,6 +189,76 @@ moduleIntegrationTestRunner({
expect(err).toBeDefined()
expect(err.constraint).toBe("IDX_service_zone_name_unique")
})
it("should fail on creating a service zone and new geo zones that are not valid", async function () {
const fulfillmentSet = await service.create({
name: "test",
type: "test-type",
})
let data: CreateServiceZoneDTO = {
name: "test",
fulfillment_set_id: fulfillmentSet.id,
geo_zones: [
{
type: GeoZoneType.PROVINCE,
country_code: "fr",
} as any,
],
}
let err = await service.createServiceZones(data).catch((e) => e)
expect(err.message).toBe(
"Missing required property province_code for geo zone type province"
)
data = {
name: "test",
fulfillment_set_id: fulfillmentSet.id,
geo_zones: [
{
type: GeoZoneType.CITY,
country_code: "fr",
province_code: "test",
} as any,
],
}
err = await service.createServiceZones(data).catch((e) => e)
expect(err.message).toBe(
"Missing required property city for geo zone type city"
)
data = {
name: "test",
fulfillment_set_id: fulfillmentSet.id,
geo_zones: [
{
type: GeoZoneType.ZIP,
postal_expression: "test",
} as any,
],
}
err = await service.createServiceZones(data).catch((e) => e)
expect(err.message).toBe(
"Missing required property country_code for geo zone type zip"
)
data = {
name: "test",
fulfillment_set_id: fulfillmentSet.id,
geo_zones: [
{
type: "unknown",
postal_expression: "test",
} as any,
],
}
err = await service.createServiceZones(data).catch((e) => e)
expect(err.message).toBe(`Invalid geo zone type: unknown`)
})
})
describe("on update", () => {
@@ -8,6 +8,7 @@ import { generateCreateShippingOptionsData } from "../../__fixtures__"
import { resolve } from "path"
import { FulfillmentProviderService } from "@services"
import { FulfillmentProviderServiceFixtures } from "../../__fixtures__/providers"
import { GeoZoneType } from "@medusajs/utils"
jest.setTimeout(100000)
@@ -35,10 +36,7 @@ const providerId = FulfillmentProviderService.getRegistrationIdentifier(
moduleIntegrationTestRunner({
moduleName: Modules.FULFILLMENT,
moduleOptions,
testSuite: ({
MikroOrmWrapper,
service,
}: SuiteOptions<IFulfillmentModuleService>) => {
testSuite: ({ service }: SuiteOptions<IFulfillmentModuleService>) => {
describe("Fulfillment Module Service", () => {
describe("read", () => {
it("should list shipping options with a filter", async function () {
@@ -158,7 +156,7 @@ moduleIntegrationTestRunner({
}),
])
let listedOptions = await service.listShippingOptions({
let listedOptions = await service.listShippingOptionsForContext({
context: {
"test-attribute": "test",
"test-attribute2": {
@@ -175,8 +173,12 @@ moduleIntegrationTestRunner({
])
)
listedOptions = await service.listShippingOptions({
fulfillment_set_id: { $ne: fulfillmentSet.id },
listedOptions = await service.listShippingOptionsForContext({
service_zone: {
fulfillment_set: {
id: { $ne: fulfillmentSet.id },
},
},
context: {
"test-attribute": "test",
"test-attribute2": {
@@ -187,8 +189,12 @@ moduleIntegrationTestRunner({
expect(listedOptions).toHaveLength(0)
listedOptions = await service.listShippingOptions({
fulfillment_set_type: "non-existing-type",
listedOptions = await service.listShippingOptionsForContext({
service_zone: {
fulfillment_set: {
type: "non-existing-type",
},
},
context: {
"test-attribute": "test",
"test-attribute2": {
@@ -199,6 +205,207 @@ moduleIntegrationTestRunner({
expect(listedOptions).toHaveLength(0)
})
it(`should list the shipping options for a context with a specific address`, async function () {
const fulfillmentSet = await service.create({
name: "test",
type: "test-type",
service_zones: [
{
name: "test",
geo_zones: [
{
type: GeoZoneType.ZIP,
country_code: "fr",
province_code: "rhone",
city: "paris",
postal_expression: "75006",
},
],
},
],
})
const shippingProfile = await service.createShippingProfiles({
name: "test",
type: "default",
})
const [shippingOption1, , shippingOption3] =
await service.createShippingOptions([
generateCreateShippingOptionsData({
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: providerId,
rules: [
{
attribute: "test-attribute",
operator: "in",
value: ["test"],
},
],
}),
generateCreateShippingOptionsData({
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: providerId,
rules: [
{
attribute: "test-attribute",
operator: "in",
value: ["test-test"],
},
],
}),
generateCreateShippingOptionsData({
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: providerId,
rules: [
{
attribute: "test-attribute",
operator: "eq",
value: "test",
},
{
attribute: "test-attribute2.options",
operator: "in",
value: ["test", "test2"],
},
],
}),
])
let shippingOptions = await service.listShippingOptionsForContext({
address: {
country_code: "fr",
province_code: "rhone",
city: "paris",
postal_expression: "75006",
},
})
expect(shippingOptions).toHaveLength(3)
shippingOptions = await service.listShippingOptionsForContext({
address: {
country_code: "fr",
province_code: "rhone",
city: "paris",
postal_expression: "75001",
},
})
expect(shippingOptions).toHaveLength(0)
shippingOptions = await service.listShippingOptionsForContext({
address: {
country_code: "fr",
province_code: "rhone",
city: "paris",
postal_expression: "75006",
},
context: {
"test-attribute": "test",
"test-attribute2": {
options: "test2",
},
},
})
expect(shippingOptions).toHaveLength(2)
expect(shippingOptions).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: shippingOption1.id }),
expect.objectContaining({ id: shippingOption3.id }),
])
)
})
})
it("should validate if a shipping option is applicable to a context", async function () {
const fulfillmentSet = await service.create({
name: "test",
type: "test-type",
service_zones: [
{
name: "test",
},
],
})
const shippingProfile = await service.createShippingProfiles({
name: "test",
type: "default",
})
const [shippingOption1, shippingOption2, shippingOption3] =
await service.createShippingOptions([
generateCreateShippingOptionsData({
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: providerId,
rules: [
{
attribute: "test-attribute",
operator: "in",
value: ["test"],
},
],
}),
generateCreateShippingOptionsData({
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: providerId,
rules: [
{
attribute: "test-attribute",
operator: "in",
value: ["test-test"],
},
],
}),
generateCreateShippingOptionsData({
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
provider_id: providerId,
rules: [
{
attribute: "test-attribute",
operator: "eq",
value: "test",
},
{
attribute: "test-attribute2.options",
operator: "in",
value: ["test", "test2"],
},
],
}),
])
let listedOptions = await service.listShippingOptions()
expect(listedOptions).toHaveLength(3)
const context = {
"test-attribute": "test",
"test-attribute2": {
options: "test2",
},
}
const isShippingOption1Applicable =
await service.validateShippingOption(shippingOption1.id, context)
expect(isShippingOption1Applicable).toBeTruthy()
const isShippingOption2Applicable =
await service.validateShippingOption(shippingOption2.id, context)
expect(isShippingOption2Applicable).toBeFalsy()
const isShippingOption3Applicable =
await service.validateShippingOption(shippingOption3.id, context)
expect(isShippingOption3Applicable).toBeTruthy()
})
describe("mutations", () => {