feat(fulfillment): List shipping options filtered by context anmd rules (#6507)

**What**
Should be able to list the shipping options with or without a context, when a context is provided all the rules of the shipping options must be valid for the shipping options to be returned.

FIXES CORE-1765
This commit is contained in:
Adrien de Peretti
2024-02-26 15:59:55 +01:00
committed by GitHub
parent b13c669528
commit ac829fc67f
7 changed files with 457 additions and 320 deletions

View File

@@ -0,0 +1,5 @@
---
"@medusajs/types": patch
---
feat(fulfillment): List shipping options filtered by context and rules

View File

@@ -0,0 +1,39 @@
import { CreateShippingOptionDTO } from "@medusajs/types"
export function generateCreateShippingOptionsData({
name,
service_zone_id,
shipping_profile_id,
service_provider_id,
price_type,
rules,
type,
data,
}: Omit<CreateShippingOptionDTO, "name" | "price_type" | "type"> & {
price_type?: CreateShippingOptionDTO["price_type"]
name?: string
type?: CreateShippingOptionDTO["type"]
}): Required<CreateShippingOptionDTO> {
return {
service_zone_id: service_zone_id,
shipping_profile_id: shipping_profile_id,
service_provider_id: service_provider_id,
type: type ?? {
code: "test-type",
description: "test-description",
label: "test-label",
},
data: data ?? {
amount: 1000,
},
name: name ?? Math.random().toString(36).substring(7),
price_type: price_type ?? "flat",
rules: rules ?? [
{
attribute: "weight",
operator: "eq",
value: "test",
},
],
}
}

View File

@@ -1,4 +1,4 @@
import { Modules } from "@medusajs/modules-sdk"
import {Modules} from "@medusajs/modules-sdk"
import {
CreateFulfillmentSetDTO,
CreateGeoZoneDTO,
@@ -12,11 +12,21 @@ import {
UpdateGeoZoneDTO,
UpdateServiceZoneDTO,
} 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"
import {generateCreateShippingOptionsData} from "../__fixtures__"
jest.setTimeout(100000)
// TODO: Temporary until the providers are sorted out
const createProvider = async (MikroOrmWrapper, providerId: string) => {
const [{ id }] = await MikroOrmWrapper.forkManager().execute(
`insert into service_provider (id) values ('${providerId}') returning id`
)
return id
}
moduleIntegrationTestRunner({
moduleName: Modules.FULFILLMENT,
testSuite: ({
@@ -249,6 +259,177 @@ moduleIntegrationTestRunner({
)
})
})
describe("shipping options", () => {
it("should list shipping options with a filter", 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 providerId = await createProvider(
MikroOrmWrapper,
"sp_jdafwfleiwuonl"
)
const [shippingOption1] = await service.createShippingOptions([
generateCreateShippingOptionsData({
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
service_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,
service_provider_id: providerId,
rules: [
{
attribute: "test-attribute",
operator: "eq",
value: "test",
},
{
attribute: "test-attribute2.options",
operator: "in",
value: ["test", "test2"],
},
],
}),
])
const listedOptions = await service.listShippingOptions({
name: shippingOption1.name,
})
expect(listedOptions).toHaveLength(1)
expect(listedOptions[0].id).toEqual(shippingOption1.id)
})
it("should list shipping options with 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 providerId = await createProvider(
MikroOrmWrapper,
"sp_jdafwfleiwuonl"
)
const [shippingOption1, , shippingOption3] =
await service.createShippingOptions([
generateCreateShippingOptionsData({
service_zone_id: fulfillmentSet.service_zones[0].id,
shipping_profile_id: shippingProfile.id,
service_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,
service_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,
service_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({
context: {
"test-attribute": "test",
"test-attribute2": {
options: "test2",
},
},
})
expect(listedOptions).toHaveLength(2)
expect(listedOptions).toEqual(
expect.arrayContaining([
expect.objectContaining({ id: shippingOption1.id }),
expect.objectContaining({ id: shippingOption3.id }),
])
)
listedOptions = await service.listShippingOptions({
fulfillment_set_id: { $ne: fulfillmentSet.id },
context: {
"test-attribute": "test",
"test-attribute2": {
options: "test2",
},
},
})
expect(listedOptions).toHaveLength(0)
listedOptions = await service.listShippingOptions({
fulfillment_set_type: "non-existing-type",
context: {
"test-attribute": "test",
"test-attribute2": {
options: "test2",
},
},
})
expect(listedOptions).toHaveLength(0)
})
})
})
describe("mutations", () => {
@@ -788,34 +969,16 @@ moduleIntegrationTestRunner({
fulfillment_set_id: fulfillmentSet.id,
})
// TODO: change that for a real provider instead of fake data manual inserted data
const [{ id: providerId }] =
await MikroOrmWrapper.forkManager().execute(
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
)
const providerId = await createProvider(
MikroOrmWrapper,
"sp_jdafwfleiwuonl"
)
const createData: CreateShippingOptionDTO = {
name: "test-option",
price_type: "flat",
const createData: CreateShippingOptionDTO = generateCreateShippingOptionsData({
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: providerId,
type: {
code: "test-type",
description: "test-description",
label: "test-label",
},
data: {
amount: 1000,
},
rules: [
{
attribute: "test-attribute",
operator: "in",
value: ["test-value"],
},
],
}
})
const createdShippingOption = await service.createShippingOptions(
createData
@@ -863,57 +1026,22 @@ moduleIntegrationTestRunner({
fulfillment_set_id: fulfillmentSet.id,
})
// TODO: change that for a real provider instead of fake data manual inserted data
const [{ id: providerId }] =
await MikroOrmWrapper.forkManager().execute(
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
)
const providerId = await createProvider(
MikroOrmWrapper,
"sp_jdafwfleiwuonl"
)
const createData: CreateShippingOptionDTO[] = [
{
name: "test-option",
price_type: "flat",
generateCreateShippingOptionsData({
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: providerId,
type: {
code: "test-type",
description: "test-description",
label: "test-label",
},
data: {
amount: 1000,
},
rules: [
{
attribute: "test-attribute",
operator: "eq",
value: "test-value",
},
],
},
{
name: "test-option-2",
price_type: "calculated",
}),
generateCreateShippingOptionsData({
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: providerId,
type: {
code: "test-type",
description: "test-description",
label: "test-label",
},
data: {
amount: 1000,
},
rules: [
{
attribute: "test-attribute",
operator: "eq",
value: "test-value",
},
],
},
})
]
const createdShippingOptions = await service.createShippingOptions(
@@ -968,34 +1096,23 @@ moduleIntegrationTestRunner({
fulfillment_set_id: fulfillmentSet.id,
})
// TODO: change that for a real provider instead of fake data manual inserted data
const [{ id: providerId }] =
await MikroOrmWrapper.forkManager().execute(
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
)
const providerId = await createProvider(
MikroOrmWrapper,
"sp_jdafwfleiwuonl"
)
const createData: CreateShippingOptionDTO = {
name: "test-option",
price_type: "flat",
const createData: CreateShippingOptionDTO = generateCreateShippingOptionsData({
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: providerId,
type: {
code: "test-type",
description: "test-description",
label: "test-label",
},
data: {
amount: 1000,
},
rules: [
{
attribute: "test-attribute",
operator: "invalid",
operator: "invalid" as any,
value: "test-value",
},
],
}
})
const err = await service
.createShippingOptions(createData)
@@ -1029,28 +1146,13 @@ moduleIntegrationTestRunner({
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
)
const shippingOption = await service.createShippingOptions({
name: "test-option",
price_type: "flat",
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: providerId,
type: {
code: "test-type",
description: "test-description",
label: "test-label",
},
data: {
amount: 1000,
},
rules: [
{
attribute: "test-attribute",
operator: "eq",
value: "test-value",
},
],
})
const shippingOption = await service.createShippingOptions(
generateCreateShippingOptionsData({
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: providerId,
})
)
const ruleData = {
attribute: "test-attribute",
@@ -1835,33 +1937,16 @@ moduleIntegrationTestRunner({
type: "default",
})
const [serviceProvider] =
await MikroOrmWrapper.forkManager().execute(
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
)
const providerId = await createProvider(
MikroOrmWrapper,
"sp_jdafwfleiwuonl"
)
const shippingOptionData = {
name: "test",
price_type: "flat",
const shippingOptionData = generateCreateShippingOptionsData({
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: serviceProvider.id,
type: {
code: "test",
description: "test",
label: "test",
},
data: {
amount: 1000,
},
rules: [
{
attribute: "test",
operator: "eq",
value: "test",
},
],
}
service_provider_id: providerId,
})
const shippingOption = await service.createShippingOptions(
shippingOptionData
@@ -1873,7 +1958,7 @@ moduleIntegrationTestRunner({
price_type: "calculated",
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: serviceProvider.id,
service_provider_id: providerId,
type: {
code: "updated-test",
description: "updated-test",
@@ -1955,33 +2040,16 @@ moduleIntegrationTestRunner({
type: "default",
})
const [serviceProvider] =
await MikroOrmWrapper.forkManager().execute(
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
)
const providerId = await createProvider(
MikroOrmWrapper,
"sp_jdafwfleiwuonl"
)
const shippingOptionData = {
name: "test",
price_type: "flat",
const shippingOptionData = generateCreateShippingOptionsData({
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: serviceProvider.id,
type: {
code: "test",
description: "test",
label: "test",
},
data: {
amount: 1000,
},
rules: [
{
attribute: "test",
operator: "eq",
value: "test",
},
],
}
service_provider_id: providerId,
})
const shippingOption = await service.createShippingOptions(
shippingOptionData
@@ -1993,7 +2061,7 @@ moduleIntegrationTestRunner({
price_type: "calculated",
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: serviceProvider.id,
service_provider_id: providerId,
data: {
amount: 2000,
},
@@ -2068,56 +2136,22 @@ moduleIntegrationTestRunner({
type: "default",
})
const [serviceProvider] =
await MikroOrmWrapper.forkManager().execute(
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
)
const providerId = await createProvider(
MikroOrmWrapper,
"sp_jdafwfleiwuonl"
)
const shippingOptionData = [
{
name: "test",
price_type: "flat",
generateCreateShippingOptionsData({
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: serviceProvider.id,
type: {
code: "test",
description: "test",
label: "test",
},
data: {
amount: 1000,
},
rules: [
{
attribute: "test",
operator: "eq",
value: "test",
},
],
},
{
name: "test2",
price_type: "calculated",
service_provider_id: providerId,
}),
generateCreateShippingOptionsData({
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: serviceProvider.id,
type: {
code: "test",
description: "test",
label: "test",
},
data: {
amount: 1000,
},
rules: [
{
attribute: "test",
operator: "eq",
value: "test",
},
],
},
service_provider_id: providerId,
})
]
const shippingOptions = await service.createShippingOptions(
@@ -2131,7 +2165,7 @@ moduleIntegrationTestRunner({
price_type: "calculated",
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: serviceProvider.id,
service_provider_id: providerId,
type: {
code: "updated-test",
description: "updated-test",
@@ -2154,7 +2188,7 @@ moduleIntegrationTestRunner({
price_type: "calculated",
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: serviceProvider.id,
service_provider_id: providerId,
type: {
code: "updated-test",
description: "updated-test",
@@ -2307,33 +2341,16 @@ moduleIntegrationTestRunner({
type: "default",
})
const [serviceProvider] =
await MikroOrmWrapper.forkManager().execute(
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
)
const providerId = await createProvider(
MikroOrmWrapper,
"sp_jdafwfleiwuonl"
)
const shippingOptionData = {
name: "test",
price_type: "flat",
const shippingOptionData = generateCreateShippingOptionsData({
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: serviceProvider.id,
type: {
code: "test",
description: "test",
label: "test",
},
data: {
amount: 1000,
},
rules: [
{
attribute: "test",
operator: "eq",
value: "test",
},
],
}
service_provider_id: providerId,
})
const shippingOption = await service.createShippingOptions(
shippingOptionData
@@ -2374,33 +2391,16 @@ moduleIntegrationTestRunner({
type: "default",
})
const [serviceProvider] =
await MikroOrmWrapper.forkManager().execute(
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
)
const providerId = await createProvider(
MikroOrmWrapper,
"sp_jdafwfleiwuonl"
)
const shippingOptionData = {
name: "test",
price_type: "flat",
const shippingOptionData = generateCreateShippingOptionsData({
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: serviceProvider.id,
type: {
code: "test",
description: "test",
label: "test",
},
data: {
amount: 1000,
},
rules: [
{
attribute: "test",
operator: "eq",
value: "test",
},
],
}
service_provider_id: providerId,
})
const shippingOption = await service.createShippingOptions(
shippingOptionData
@@ -2444,33 +2444,18 @@ moduleIntegrationTestRunner({
name: "test",
fulfillment_set_id: fulfillmentSet.id,
})
const [serviceProvider] =
await MikroOrmWrapper.forkManager().execute(
"insert into service_provider (id) values ('sp_jdafwfleiwuonl') returning id"
)
const providerId = await createProvider(
MikroOrmWrapper,
"sp_jdafwfleiwuonl"
)
const shippingOption = await service.createShippingOptions({
name: "test",
price_type: "flat",
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: serviceProvider.id,
type: {
code: "test",
description: "test",
label: "test",
},
data: {
amount: 1000,
},
rules: [
{
attribute: "test",
operator: "eq",
value: "test",
},
],
})
const shippingOption = await service.createShippingOptions(
generateCreateShippingOptionsData({
service_zone_id: serviceZone.id,
shipping_profile_id: shippingProfile.id,
service_provider_id: providerId,
})
)
const updateData = {
id: shippingOption.rules[0].id,

View File

@@ -1,11 +1,15 @@
import {
Context,
DAL,
FilterableShippingOptionProps,
FilterQuery,
FindConfig,
FulfillmentTypes,
IFulfillmentModuleService,
InternalModuleDeclaration,
ModuleJoinerConfig,
ModulesSdkTypes,
ShippingOptionDTO,
UpdateFulfillmentSetDTO,
} from "@medusajs/types"
import {
@@ -29,7 +33,7 @@ import {
ShippingOptionType,
ShippingProfile,
} from "@models"
import { validateRules } from "@utils"
import { isContextValid, validateRules } from "@utils"
const generateMethodForModels = [
ServiceZone,
@@ -113,6 +117,100 @@ export default class FulfillmentModuleService<
return joinerConfig
}
protected static normalizeShippingOptionsListParams(
filters: FilterableShippingOptionProps = {},
config: FindConfig<ShippingOptionDTO> = {}
) {
let { fulfillment_set_id, fulfillment_set_type, context, ...where } =
filters
const normalizedConfig = { ...config }
normalizedConfig.relations = [
"rules",
"type",
"shipping_profile",
"service_provider",
...(normalizedConfig.relations ?? []),
]
// The assumption is that there won't be an infinite amount of shipping options. So if a context filtering needs to be applied we can retrieve them all.
normalizedConfig.take =
normalizedConfig.take ?? (context ? null : undefined)
let normalizedFilters = { ...where } as FilterQuery
if (fulfillment_set_id || fulfillment_set_type) {
const fulfillmentSetConstraints = {}
if (fulfillment_set_id) {
fulfillmentSetConstraints["id"] = fulfillment_set_id
}
if (fulfillment_set_type) {
fulfillmentSetConstraints["type"] = fulfillment_set_type
}
normalizedFilters = {
...normalizedFilters,
service_zone: {
fulfillment_set: fulfillmentSetConstraints,
},
}
normalizedConfig.relations.push("service_zone.fulfillment_set")
}
normalizedConfig.relations = Array.from(new Set(normalizedConfig.relations))
return {
filters: normalizedFilters,
config: normalizedConfig,
context,
}
}
@InjectManager("baseRepository_")
// @ts-ignore
async listShippingOptions(
filters: FilterableShippingOptionProps = {},
config: FindConfig<ShippingOptionDTO> = {},
sharedContext?: Context
): Promise<FulfillmentTypes.ShippingOptionDTO[]> {
const {
filters: normalizedFilters,
config: normalizedConfig,
context,
} = FulfillmentModuleService.normalizeShippingOptionsListParams(
filters,
config
)
let shippingOptions = await this.shippingOptionService_.list(
normalizedFilters,
normalizedConfig,
sharedContext
)
// Apply rules context filtering
if (context) {
shippingOptions = shippingOptions.filter((shippingOption) => {
if (!shippingOption.rules?.length) {
return true
}
return isContextValid(
context,
shippingOption.rules.map((r) => r)
)
})
}
return await this.baseRepository_.serialize<
FulfillmentTypes.ShippingOptionDTO[]
>(shippingOptions, {
populate: true,
})
}
create(
data: FulfillmentTypes.CreateFulfillmentSetDTO[],
sharedContext?: Context

View File

@@ -1,4 +1,4 @@
import { isContextValidForRules, RuleOperator } from "../utils"
import { isContextValid, RuleOperator } from "../utils"
describe("isContextValidForRules", () => {
const context = {
@@ -19,37 +19,37 @@ describe("isContextValidForRules", () => {
value: "wrongValue",
}
it("returns true when all rules are valid and atLeastOneValidRule is false", () => {
it("returns true when all rules are valid", () => {
const rules = [validRule, validRule]
expect(isContextValidForRules(context, rules)).toBe(true)
expect(isContextValid(context, rules)).toBe(true)
})
it("returns true when all rules are valid and atLeastOneValidRule is true", () => {
it("returns true when some rules are valid", () => {
const rules = [validRule, validRule]
const options = { atLeastOneValidRule: true }
expect(isContextValidForRules(context, rules, options)).toBe(true)
const options = { someAreValid: true }
expect(isContextValid(context, rules, options)).toBe(true)
})
it("returns true when some rules are valid and atLeastOneValidRule is true", () => {
it("returns true when some rules are valid and someAreValid is true", () => {
const rules = [validRule, invalidRule]
const options = { atLeastOneValidRule: true }
expect(isContextValidForRules(context, rules, options)).toBe(true)
const options = { someAreValid: true }
expect(isContextValid(context, rules, options)).toBe(true)
})
it("returns false when some rules are valid and atLeastOneValidRule is false", () => {
it("returns false when some rules are valid", () => {
const rules = [validRule, invalidRule]
expect(isContextValidForRules(context, rules)).toBe(false)
expect(isContextValid(context, rules)).toBe(false)
})
it("returns false when no rules are valid and atLeastOneValidRule is true", () => {
it("returns false when no rules are valid and someAreValid is true", () => {
const rules = [invalidRule, invalidRule]
const options = { atLeastOneValidRule: true }
expect(isContextValidForRules(context, rules, options)).toBe(false)
const options = { someAreValid: true }
expect(isContextValid(context, rules, options)).toBe(false)
})
it("returns false when no rules are valid and atLeastOneValidRule is false", () => {
it("returns false when no rules are valid", () => {
const rules = [invalidRule, invalidRule]
expect(isContextValidForRules(context, rules)).toBe(false)
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'gt' operator is valid", () => {
@@ -61,7 +61,7 @@ describe("isContextValidForRules", () => {
},
]
const context = { attribute1: "2" }
expect(isContextValidForRules(context, rules)).toBe(true)
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'gt' operator is invalid", () => {
@@ -73,7 +73,7 @@ describe("isContextValidForRules", () => {
},
]
const context = { attribute1: "0" }
expect(isContextValidForRules(context, rules)).toBe(false)
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'gte' operator is valid", () => {
@@ -85,7 +85,7 @@ describe("isContextValidForRules", () => {
},
]
const context = { attribute1: "2" }
expect(isContextValidForRules(context, rules)).toBe(true)
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'gte' operator is invalid", () => {
@@ -97,7 +97,7 @@ describe("isContextValidForRules", () => {
},
]
const context = { attribute1: "2" }
expect(isContextValidForRules(context, rules)).toBe(false)
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'lt' operator is valid", () => {
@@ -109,7 +109,7 @@ describe("isContextValidForRules", () => {
},
]
const context = { attribute1: "2" }
expect(isContextValidForRules(context, rules)).toBe(true)
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'lt' operator is invalid", () => {
@@ -121,7 +121,7 @@ describe("isContextValidForRules", () => {
},
]
const context = { attribute1: "2" }
expect(isContextValidForRules(context, rules)).toBe(false)
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'lte' operator is valid", () => {
@@ -133,7 +133,7 @@ describe("isContextValidForRules", () => {
},
]
const context = { attribute1: "2" }
expect(isContextValidForRules(context, rules)).toBe(true)
expect(isContextValid(context, rules)).toBe(true)
})
// ... existing tests ...
@@ -147,7 +147,7 @@ describe("isContextValidForRules", () => {
},
]
const context = { attribute1: "2" }
expect(isContextValidForRules(context, rules)).toBe(false)
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'in' operator is valid", () => {
@@ -159,7 +159,7 @@ describe("isContextValidForRules", () => {
},
]
const context = { attribute1: "2" }
expect(isContextValidForRules(context, rules)).toBe(true)
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'in' operator is invalid", () => {
@@ -171,7 +171,7 @@ describe("isContextValidForRules", () => {
},
]
const context = { attribute1: "2" }
expect(isContextValidForRules(context, rules)).toBe(false)
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'nin' operator is valid", () => {
@@ -183,7 +183,7 @@ describe("isContextValidForRules", () => {
},
]
const context = { attribute1: "2" }
expect(isContextValidForRules(context, rules)).toBe(true)
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'nin' operator is invalid", () => {
@@ -195,7 +195,7 @@ describe("isContextValidForRules", () => {
},
]
const context = { attribute1: "2" }
expect(isContextValidForRules(context, rules)).toBe(false)
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'ne' operator is valid", () => {
@@ -207,7 +207,7 @@ describe("isContextValidForRules", () => {
},
]
const context = { attribute1: "2" }
expect(isContextValidForRules(context, rules)).toBe(true)
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'ne' operator is invalid", () => {
@@ -219,7 +219,7 @@ describe("isContextValidForRules", () => {
},
]
const context = { attribute1: "2" }
expect(isContextValidForRules(context, rules)).toBe(false)
expect(isContextValid(context, rules)).toBe(false)
})
it("returns true when the 'eq' operator is valid", () => {
@@ -231,7 +231,7 @@ describe("isContextValidForRules", () => {
},
]
const context = { attribute1: "2" }
expect(isContextValidForRules(context, rules)).toBe(true)
expect(isContextValid(context, rules)).toBe(true)
})
it("returns false when the 'eq' operator is invalid", () => {
@@ -243,6 +243,6 @@ describe("isContextValidForRules", () => {
},
]
const context = { attribute1: "2" }
expect(isContextValidForRules(context, rules)).toBe(false)
expect(isContextValid(context, rules)).toBe(false)
})
})

View File

@@ -10,8 +10,8 @@ import { isString, MedusaError, pickValueFromObject } from "@medusajs/utils"
export type Rule = {
attribute: string
operator: RuleOperator
value: string | string[]
operator: Lowercase<keyof typeof RuleOperator>
value: string | string[] | null
}
export enum RuleOperator {
@@ -71,18 +71,18 @@ const operatorsPredicate = {
* @param rules
* @param options
*/
export function isContextValidForRules(
export function isContextValid(
context: Record<string, any>,
rules: Rule[],
options: {
atLeastOneValidRule: boolean
someAreValid: boolean
} = {
atLeastOneValidRule: false,
someAreValid: false,
}
) {
const { atLeastOneValidRule } = options
const { someAreValid } = options
const loopComparator = atLeastOneValidRule ? rules.some : rules.every
const loopComparator = someAreValid ? rules.some : rules.every
const predicate = (rule) => {
const { attribute, operator, value } = rule
const contextValue = pickValueFromObject(attribute, context)
@@ -137,6 +137,13 @@ export function validateRule(rule: Record<string, unknown>): boolean {
"Rule value must be an array for in/nin operators"
)
}
} else {
if (!isString(rule.value)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Rule value must be a string for the selected operator ${rule.operator}`
)
}
}
return true

View File

@@ -37,6 +37,8 @@ export interface FilterableShippingOptionProps
extends BaseFilterable<FilterableShippingOptionProps> {
id?: string | string[] | OperatorMap<string | string[]>
name?: string | string[] | OperatorMap<string | string[]>
fulfillment_set_id?: string | string[] | OperatorMap<string | string[]>
fulfillment_set_type?: string | string[] | OperatorMap<string | string[]>
price_type?:
| ShippingOptionPriceType
| ShippingOptionPriceType[]
@@ -44,4 +46,5 @@ export interface FilterableShippingOptionProps
service_zone?: FilterableServiceZoneProps
shipping_option_type?: FilterableShippingOptionTypeProps
rules?: FilterableShippingOptionRuleProps
context?: Record<string, unknown>
}