fix: Validate boolean query params (#8834)

This commit is contained in:
Oli Juhl
2024-08-28 13:14:15 +02:00
committed by GitHub
parent dbe931ab00
commit c2e7940f61
12 changed files with 109 additions and 36 deletions
@@ -1092,6 +1092,58 @@ medusaIntegrationTestRunner({
}), }),
]) ])
}) })
it("should get product variants filtered by manage_inventory", async () => {
const payload = {
title: "Test product - 1",
handle: "test-1",
variants: [
{
title: "Custom inventory 1",
prices: [{ currency_code: "usd", amount: 100 }],
manage_inventory: true,
inventory_items: [],
},
{
title: "Custom inventory 2",
prices: [{ currency_code: "usd", amount: 100 }],
manage_inventory: false,
},
],
}
const product = (
await api.post(`/admin/products`, payload, adminHeaders)
).data.product
let variants = (
await api.get(
`/admin/products/${product.id}/variants?manage_inventory=true`,
adminHeaders
)
).data.variants
expect(variants).toEqual([
expect.objectContaining({
title: "Custom inventory 1",
product_id: product.id,
}),
])
variants = (
await api.get(
`/admin/products/${product.id}/variants?manage_inventory=false`,
adminHeaders
)
).data.variants
expect(variants).toEqual([
expect.objectContaining({
title: "Custom inventory 2",
product_id: product.id,
}),
])
})
}) })
describe("POST /admin/products", () => { describe("POST /admin/products", () => {
@@ -1,5 +1,5 @@
import { z } from "zod" import { z } from "zod"
import { OptionalBooleanValidator } from "../../utils/common-validators" import { booleanString } from "../../utils/common-validators"
import { createFindParams } from "../../utils/validators" import { createFindParams } from "../../utils/validators"
export type AdminFulfillmentProvidersParamsType = z.infer< export type AdminFulfillmentProvidersParamsType = z.infer<
@@ -13,7 +13,7 @@ export const AdminFulfillmentProvidersParams = createFindParams({
z.object({ z.object({
id: z.union([z.string(), z.array(z.string())]).optional(), id: z.union([z.string(), z.array(z.string())]).optional(),
stock_location_id: z.union([z.string(), z.array(z.string())]).optional(), stock_location_id: z.union([z.string(), z.array(z.string())]).optional(),
is_enabled: OptionalBooleanValidator, is_enabled: booleanString().optional(),
q: z.string().optional(), q: z.string().optional(),
}) })
) )
@@ -1,5 +1,5 @@
import { z } from "zod" import { z } from "zod"
import { OptionalBooleanValidator } from "../../utils/common-validators" import { booleanString } from "../../utils/common-validators"
import { import {
createFindParams, createFindParams,
createOperatorMap, createOperatorMap,
@@ -27,7 +27,7 @@ export const AdminGetInventoryItemsParams = createFindParams({
mid_code: z.union([z.string(), z.array(z.string())]).optional(), mid_code: z.union([z.string(), z.array(z.string())]).optional(),
hs_code: z.union([z.string(), z.array(z.string())]).optional(), hs_code: z.union([z.string(), z.array(z.string())]).optional(),
material: z.union([z.string(), z.array(z.string())]).optional(), material: z.union([z.string(), z.array(z.string())]).optional(),
requires_shipping: OptionalBooleanValidator, requires_shipping: booleanString().optional(),
weight: createOperatorMap(z.number(), parseFloat).optional(), weight: createOperatorMap(z.number(), parseFloat).optional(),
length: createOperatorMap(z.number(), parseFloat).optional(), length: createOperatorMap(z.number(), parseFloat).optional(),
height: createOperatorMap(z.number(), parseFloat).optional(), height: createOperatorMap(z.number(), parseFloat).optional(),
@@ -1,4 +1,5 @@
import { z } from "zod" import { z } from "zod"
import { booleanString } from "../../utils/common-validators"
import { import {
createFindParams, createFindParams,
createOperatorMap, createOperatorMap,
@@ -34,7 +35,7 @@ export const AdminGetPaymentProvidersParams = createFindParams({
}).merge( }).merge(
z.object({ z.object({
id: z.union([z.string(), z.array(z.string())]).optional(), id: z.union([z.string(), z.array(z.string())]).optional(),
is_enabled: z.boolean().optional(), is_enabled: booleanString().optional(),
$and: z.lazy(() => AdminGetPaymentProvidersParams.array()).optional(), $and: z.lazy(() => AdminGetPaymentProvidersParams.array()).optional(),
$or: z.lazy(() => AdminGetPaymentProvidersParams.array()).optional(), $or: z.lazy(() => AdminGetPaymentProvidersParams.array()).optional(),
}) })
@@ -1,5 +1,5 @@
import { z } from "zod" import { z } from "zod"
import { OptionalBooleanValidator } from "../../utils/common-validators" import { booleanString } from "../../utils/common-validators"
import { import {
createFindParams, createFindParams,
createOperatorMap, createOperatorMap,
@@ -11,8 +11,8 @@ export type AdminProductCategoryParamsType = z.infer<
> >
export const AdminProductCategoryParams = createSelectParams().merge( export const AdminProductCategoryParams = createSelectParams().merge(
z.object({ z.object({
include_ancestors_tree: OptionalBooleanValidator, include_ancestors_tree: booleanString().optional(),
include_descendants_tree: OptionalBooleanValidator, include_descendants_tree: booleanString().optional(),
}) })
) )
@@ -29,10 +29,10 @@ export const AdminProductCategoriesParams = createFindParams({
description: z.union([z.string(), z.array(z.string())]).optional(), description: z.union([z.string(), z.array(z.string())]).optional(),
handle: z.union([z.string(), z.array(z.string())]).optional(), handle: z.union([z.string(), z.array(z.string())]).optional(),
parent_category_id: z.union([z.string(), z.array(z.string())]).optional(), parent_category_id: z.union([z.string(), z.array(z.string())]).optional(),
include_ancestors_tree: OptionalBooleanValidator, include_ancestors_tree: booleanString().optional(),
include_descendants_tree: OptionalBooleanValidator, include_descendants_tree: booleanString().optional(),
is_internal: OptionalBooleanValidator, is_internal: booleanString().optional(),
is_active: OptionalBooleanValidator, is_active: booleanString().optional(),
created_at: createOperatorMap().optional(), created_at: createOperatorMap().optional(),
updated_at: createOperatorMap().optional(), updated_at: createOperatorMap().optional(),
deleted_at: createOperatorMap().optional(), deleted_at: createOperatorMap().optional(),
@@ -1,4 +1,5 @@
import { z } from "zod" import { z } from "zod"
import { booleanString } from "../../utils/common-validators"
import { createFindParams, createOperatorMap } from "../../utils/validators" import { createFindParams, createOperatorMap } from "../../utils/validators"
export type AdminGetProductVariantsParamsType = z.infer< export type AdminGetProductVariantsParamsType = z.infer<
@@ -11,8 +12,8 @@ export const AdminGetProductVariantsParams = createFindParams({
z.object({ z.object({
q: z.string().optional(), q: z.string().optional(),
id: z.union([z.string(), z.array(z.string())]).optional(), id: z.union([z.string(), z.array(z.string())]).optional(),
manage_inventory: z.boolean().optional(), manage_inventory: booleanString().optional(),
allow_backorder: z.boolean().optional(), allow_backorder: booleanString().optional(),
created_at: createOperatorMap().optional(), created_at: createOperatorMap().optional(),
updated_at: createOperatorMap().optional(), updated_at: createOperatorMap().optional(),
deleted_at: createOperatorMap().optional(), deleted_at: createOperatorMap().optional(),
@@ -1,10 +1,11 @@
import { BatchMethodRequest } from "@medusajs/types" import { BatchMethodRequest } from "@medusajs/types"
import { ProductStatus } from "@medusajs/utils" import { ProductStatus } from "@medusajs/utils"
import { z } from "zod"
import { import {
booleanString,
GetProductsParams, GetProductsParams,
transformProductParams, transformProductParams,
} from "../../utils/common-validators" } from "../../utils/common-validators"
import { z } from "zod"
import { import {
createFindParams, createFindParams,
createOperatorMap, createOperatorMap,
@@ -28,8 +29,8 @@ export const AdminGetProductVariantsParams = createFindParams({
z.object({ z.object({
q: z.string().optional(), q: z.string().optional(),
id: z.union([z.string(), z.array(z.string())]).optional(), id: z.union([z.string(), z.array(z.string())]).optional(),
manage_inventory: z.boolean().optional(), manage_inventory: booleanString().optional(),
allow_backorder: z.boolean().optional(), allow_backorder: booleanString().optional(),
created_at: createOperatorMap().optional(), created_at: createOperatorMap().optional(),
updated_at: createOperatorMap().optional(), updated_at: createOperatorMap().optional(),
deleted_at: createOperatorMap().optional(), deleted_at: createOperatorMap().optional(),
@@ -140,8 +141,8 @@ export const CreateProductVariant = z
barcode: z.string().nullish(), barcode: z.string().nullish(),
hs_code: z.string().nullish(), hs_code: z.string().nullish(),
mid_code: z.string().nullish(), mid_code: z.string().nullish(),
allow_backorder: z.boolean().optional().default(false), allow_backorder: booleanString().optional().default(false),
manage_inventory: z.boolean().optional().default(true), manage_inventory: booleanString().optional().default(true),
variant_rank: z.number().optional(), variant_rank: z.number().optional(),
weight: z.number().nullish(), weight: z.number().nullish(),
length: z.number().nullish(), length: z.number().nullish(),
@@ -177,8 +178,8 @@ export const UpdateProductVariant = z
barcode: z.string().nullish(), barcode: z.string().nullish(),
hs_code: z.string().nullish(), hs_code: z.string().nullish(),
mid_code: z.string().nullish(), mid_code: z.string().nullish(),
allow_backorder: z.boolean().optional(), allow_backorder: booleanString().optional(),
manage_inventory: z.boolean().optional(), manage_inventory: booleanString().optional(),
variant_rank: z.number().optional(), variant_rank: z.number().optional(),
weight: z.number().nullish(), weight: z.number().nullish(),
length: z.number().nullish(), length: z.number().nullish(),
@@ -211,8 +212,8 @@ export const CreateProduct = z
title: z.string(), title: z.string(),
subtitle: z.string().nullish(), subtitle: z.string().nullish(),
description: z.string().nullish(), description: z.string().nullish(),
is_giftcard: z.boolean().optional().default(false), is_giftcard: booleanString().optional().default(false),
discountable: z.boolean().optional().default(true), discountable: booleanString().optional().default(true),
images: z.array(z.object({ url: z.string() })).optional(), images: z.array(z.object({ url: z.string() })).optional(),
thumbnail: z.string().nullish(), thumbnail: z.string().nullish(),
handle: z.string().optional(), handle: z.string().optional(),
@@ -242,8 +243,8 @@ export type AdminUpdateProductType = z.infer<typeof UpdateProduct>
export const UpdateProduct = z export const UpdateProduct = z
.object({ .object({
title: z.string().optional(), title: z.string().optional(),
discountable: z.boolean().optional(), discountable: booleanString().optional(),
is_giftcard: z.boolean().optional(), is_giftcard: booleanString().optional(),
options: z.array(UpdateProductOption).optional(), options: z.array(UpdateProductOption).optional(),
variants: z.array(UpdateProductVariant).optional(), variants: z.array(UpdateProductVariant).optional(),
status: statusEnum.optional(), status: statusEnum.optional(),
@@ -1,5 +1,5 @@
import { z } from "zod" import { z } from "zod"
import { OptionalBooleanValidator } from "../../utils/common-validators" import { booleanString } from "../../utils/common-validators"
import { import {
createFindParams, createFindParams,
createOperatorMap, createOperatorMap,
@@ -23,7 +23,7 @@ export const AdminGetSalesChannelsParams = createFindParams({
id: z.union([z.string(), z.array(z.string())]).optional(), id: z.union([z.string(), z.array(z.string())]).optional(),
name: z.union([z.string(), z.array(z.string())]).optional(), name: z.union([z.string(), z.array(z.string())]).optional(),
description: z.string().optional(), description: z.string().optional(),
is_disabled: OptionalBooleanValidator, is_disabled: booleanString().optional(),
created_at: createOperatorMap().optional(), created_at: createOperatorMap().optional(),
updated_at: createOperatorMap().optional(), updated_at: createOperatorMap().optional(),
deleted_at: createOperatorMap().optional(), deleted_at: createOperatorMap().optional(),
@@ -1,4 +1,5 @@
import { z } from "zod" import { z } from "zod"
import { booleanString } from "../../utils/common-validators"
import { createFindParams } from "../../utils/validators" import { createFindParams } from "../../utils/validators"
export type StoreGetPaymentProvidersParamsType = z.infer< export type StoreGetPaymentProvidersParamsType = z.infer<
@@ -11,7 +12,7 @@ export const StoreGetPaymentProvidersParams = createFindParams({
z.object({ z.object({
region_id: z.string(), region_id: z.string(),
id: z.union([z.string(), z.array(z.string())]).optional(), id: z.union([z.string(), z.array(z.string())]).optional(),
is_enabled: z.boolean().optional(), is_enabled: booleanString().optional(),
$and: z.lazy(() => StoreGetPaymentProvidersParams.array()).optional(), $and: z.lazy(() => StoreGetPaymentProvidersParams.array()).optional(),
$or: z.lazy(() => StoreGetPaymentProvidersParams.array()).optional(), $or: z.lazy(() => StoreGetPaymentProvidersParams.array()).optional(),
}) })
@@ -1,5 +1,5 @@
import { z } from "zod" import { z } from "zod"
import { OptionalBooleanValidator } from "../../utils/common-validators" import { booleanString } from "../../utils/common-validators"
import { import {
createFindParams, createFindParams,
createOperatorMap, createOperatorMap,
@@ -11,8 +11,8 @@ export type StoreProductCategoryParamsType = z.infer<
> >
export const StoreProductCategoryParams = createSelectParams().merge( export const StoreProductCategoryParams = createSelectParams().merge(
z.object({ z.object({
include_ancestors_tree: OptionalBooleanValidator, include_ancestors_tree: booleanString().optional(),
include_descendants_tree: OptionalBooleanValidator, include_descendants_tree: booleanString().optional(),
}) })
) )
@@ -29,8 +29,8 @@ export const StoreProductCategoriesParams = createFindParams({
description: z.union([z.string(), z.array(z.string())]).optional(), description: z.union([z.string(), z.array(z.string())]).optional(),
handle: z.union([z.string(), z.array(z.string())]).optional(), handle: z.union([z.string(), z.array(z.string())]).optional(),
parent_category_id: z.union([z.string(), z.array(z.string())]).optional(), parent_category_id: z.union([z.string(), z.array(z.string())]).optional(),
include_ancestors_tree: OptionalBooleanValidator, include_ancestors_tree: booleanString().optional(),
include_descendants_tree: OptionalBooleanValidator, include_descendants_tree: booleanString().optional(),
created_at: createOperatorMap().optional(), created_at: createOperatorMap().optional(),
updated_at: createOperatorMap().optional(), updated_at: createOperatorMap().optional(),
deleted_at: createOperatorMap().optional(), deleted_at: createOperatorMap().optional(),
@@ -32,7 +32,24 @@ const optionalBooleanMapper = new Map([
["false", false], ["false", false],
]) ])
/**
* @deprecated Use `booleanString` instead
* It support the chainable API of zod. Please note it does not come with `.optional()` by default
*/
export const OptionalBooleanValidator = z.preprocess( export const OptionalBooleanValidator = z.preprocess(
(val: any) => optionalBooleanMapper.get(val?.toLowerCase()), (val: any) => optionalBooleanMapper.get(val?.toLowerCase()),
z.boolean().optional() z.boolean().optional()
) )
/**
* Validates that a value is a boolean when it is passed as a string.
*/
export const booleanString = () =>
z
.union([z.boolean(), z.string()])
.refine((value) => {
return ["true", "false"].includes(value.toString().toLowerCase())
})
.transform((value) => {
return value.toString().toLowerCase() === "true"
})
@@ -1,8 +1,8 @@
import { FilterableProductProps } from "@medusajs/types"
import { ProductStatus } from "@medusajs/utils" import { ProductStatus } from "@medusajs/utils"
import { z } from "zod" import { z } from "zod"
import { createOperatorMap } from "../../validators" import { createOperatorMap } from "../../validators"
import { OptionalBooleanValidator } from "../common" import { booleanString } from "../common"
import { FilterableProductProps } from "@medusajs/types"
export const ProductStatusEnum = z.nativeEnum(ProductStatus) export const ProductStatusEnum = z.nativeEnum(ProductStatus)
@@ -11,7 +11,7 @@ export const GetProductsParams = z.object({
id: z.union([z.string(), z.array(z.string())]).optional(), id: z.union([z.string(), z.array(z.string())]).optional(),
title: z.string().optional(), title: z.string().optional(),
handle: z.string().optional(), handle: z.string().optional(),
is_giftcard: OptionalBooleanValidator, is_giftcard: booleanString().optional(),
category_id: z.union([z.string(), z.array(z.string())]).optional(), category_id: z.union([z.string(), z.array(z.string())]).optional(),
sales_channel_id: z.union([z.string(), z.array(z.string())]).optional(), sales_channel_id: z.union([z.string(), z.array(z.string())]).optional(),
collection_id: z.union([z.string(), z.array(z.string())]).optional(), collection_id: z.union([z.string(), z.array(z.string())]).optional(),