feat: re integrate transform middleware lost files and features (#7477)
**What** Re integrate the transform query tests + missing features. It was originally mistekenly removed from one the team pr, also, I have adjusted the API and tests **NOTE** It does not include a full clean up of the typings and the backward compatibility remains for now
This commit is contained in:
committed by
GitHub
parent
7b059562d7
commit
d2b5768c02
@@ -190,10 +190,8 @@ export interface CustomFindOptions<TModel, InKeys extends keyof TModel> {
|
||||
* @ignore
|
||||
*/
|
||||
export type QueryConfig<TEntity extends BaseEntity> = {
|
||||
defaultFields?: (keyof TEntity | string)[]
|
||||
defaultRelations?: string[]
|
||||
allowedFields?: string[]
|
||||
allowedRelations?: string[]
|
||||
deafults?: (keyof TEntity | string)[]
|
||||
allowed?: (keyof TEntity | string)[]
|
||||
defaultLimit?: number
|
||||
isList?: boolean
|
||||
}
|
||||
|
||||
695
packages/medusa/src/api/utils/__tests__/validate-query.spec.ts
Normal file
695
packages/medusa/src/api/utils/__tests__/validate-query.spec.ts
Normal file
@@ -0,0 +1,695 @@
|
||||
import { NextFunction, Request, Response } from "express"
|
||||
import { createFindParams } from "../validators"
|
||||
import { validateAndTransformQuery } from "../validate-query"
|
||||
import { MedusaError } from "@medusajs/utils"
|
||||
|
||||
describe("validateAndTransformQuery", () => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("should transform the input query", async () => {
|
||||
let mockRequest = {
|
||||
query: {},
|
||||
} as Request
|
||||
const mockResponse = {} as Response
|
||||
const nextFunction: NextFunction = jest.fn()
|
||||
|
||||
const expectations = ({
|
||||
offset,
|
||||
limit,
|
||||
inputOrder,
|
||||
transformedOrder,
|
||||
}: {
|
||||
offset: number
|
||||
limit: number
|
||||
inputOrder: string | undefined
|
||||
transformedOrder?: Record<string, "ASC" | "DESC">
|
||||
relations?: string[]
|
||||
}) => {
|
||||
expect(mockRequest.validatedQuery).toEqual({
|
||||
offset,
|
||||
limit,
|
||||
order: inputOrder,
|
||||
})
|
||||
expect(mockRequest.filterableFields).toEqual({})
|
||||
expect(mockRequest.listConfig).toEqual({
|
||||
take: limit,
|
||||
skip: offset,
|
||||
select: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
relations: [
|
||||
"metadata",
|
||||
"metadata.parent",
|
||||
"metadata.children",
|
||||
"metadata.product",
|
||||
],
|
||||
order: transformedOrder,
|
||||
})
|
||||
expect(mockRequest.remoteQueryConfig).toEqual({
|
||||
fields: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
pagination: {
|
||||
order: transformedOrder,
|
||||
skip: offset,
|
||||
take: limit,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
let queryConfig: any = {
|
||||
defaultFields: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
defaultRelations: [
|
||||
"metadata",
|
||||
"metadata.parent",
|
||||
"metadata.children",
|
||||
"metadata.product",
|
||||
],
|
||||
isList: true,
|
||||
}
|
||||
|
||||
let middleware = validateAndTransformQuery(createFindParams(), queryConfig)
|
||||
|
||||
await middleware(mockRequest, mockResponse, nextFunction)
|
||||
|
||||
expectations({
|
||||
limit: 20,
|
||||
offset: 0,
|
||||
inputOrder: undefined,
|
||||
})
|
||||
|
||||
//////////////////////////////
|
||||
|
||||
mockRequest = {
|
||||
query: {
|
||||
limit: "10",
|
||||
offset: "5",
|
||||
order: "created_at",
|
||||
},
|
||||
} as unknown as Request
|
||||
|
||||
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
|
||||
|
||||
await middleware(mockRequest, mockResponse, nextFunction)
|
||||
|
||||
expectations({
|
||||
limit: 10,
|
||||
offset: 5,
|
||||
inputOrder: "created_at",
|
||||
transformedOrder: { created_at: "ASC" },
|
||||
})
|
||||
|
||||
//////////////////////////////
|
||||
|
||||
mockRequest = {
|
||||
query: {
|
||||
limit: "10",
|
||||
offset: "5",
|
||||
order: "created_at",
|
||||
},
|
||||
} as unknown as Request
|
||||
|
||||
queryConfig = {
|
||||
defaults: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
isList: true,
|
||||
}
|
||||
|
||||
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
|
||||
|
||||
await middleware(mockRequest, mockResponse, nextFunction)
|
||||
|
||||
expectations({
|
||||
limit: 10,
|
||||
offset: 5,
|
||||
inputOrder: "created_at",
|
||||
transformedOrder: { created_at: "ASC" },
|
||||
})
|
||||
})
|
||||
|
||||
it("should transform the input query taking into account the fields symbols (+,- or no symbol)", async () => {
|
||||
let mockRequest = {
|
||||
query: {
|
||||
fields: "id",
|
||||
},
|
||||
} as unknown as Request
|
||||
const mockResponse = {} as Response
|
||||
const nextFunction: NextFunction = jest.fn()
|
||||
|
||||
let queryConfig: any = {
|
||||
defaultFields: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
defaultRelations: [
|
||||
"metadata",
|
||||
"metadata.parent",
|
||||
"metadata.children",
|
||||
"metadata.product",
|
||||
],
|
||||
isList: true,
|
||||
}
|
||||
|
||||
let middleware = validateAndTransformQuery(createFindParams(), queryConfig)
|
||||
|
||||
await middleware(mockRequest, mockResponse, nextFunction)
|
||||
|
||||
expect(mockRequest.listConfig).toEqual(
|
||||
expect.objectContaining({
|
||||
select: ["id", "created_at"],
|
||||
})
|
||||
)
|
||||
|
||||
//////////////////////////////
|
||||
|
||||
mockRequest = {
|
||||
query: {
|
||||
fields: "+test_prop,-prop-test-something",
|
||||
},
|
||||
} as unknown as Request
|
||||
|
||||
queryConfig = {
|
||||
defaultFields: [
|
||||
"id",
|
||||
"prop-test-something",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
defaultRelations: [
|
||||
"metadata",
|
||||
"metadata.parent",
|
||||
"metadata.children",
|
||||
"metadata.product",
|
||||
],
|
||||
isList: true,
|
||||
}
|
||||
|
||||
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
|
||||
|
||||
await middleware(mockRequest, mockResponse, nextFunction)
|
||||
|
||||
expect(mockRequest.listConfig).toEqual(
|
||||
expect.objectContaining({
|
||||
select: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
"test_prop",
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
//////////////////////////////
|
||||
|
||||
mockRequest = {
|
||||
query: {
|
||||
fields: "+test_prop,-updated_at",
|
||||
},
|
||||
} as unknown as Request
|
||||
|
||||
queryConfig = {
|
||||
defaults: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
isList: true,
|
||||
}
|
||||
|
||||
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
|
||||
|
||||
await middleware(mockRequest, mockResponse, nextFunction)
|
||||
|
||||
expect(mockRequest.listConfig).toEqual(
|
||||
expect.objectContaining({
|
||||
select: [
|
||||
"id",
|
||||
"created_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
"test_prop",
|
||||
],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it(`should transform the input and manage the allowed fields and relations properly without error`, async () => {
|
||||
let mockRequest = {
|
||||
query: {
|
||||
fields: "*product.variants,+product.id",
|
||||
},
|
||||
} as unknown as Request
|
||||
const mockResponse = {} as Response
|
||||
const nextFunction: NextFunction = jest.fn()
|
||||
|
||||
let queryConfig: any = {
|
||||
defaults: [
|
||||
"id",
|
||||
"created_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
allowed: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
"product",
|
||||
"product.variants",
|
||||
],
|
||||
isList: true,
|
||||
}
|
||||
|
||||
let middleware = validateAndTransformQuery(createFindParams(), queryConfig)
|
||||
|
||||
await middleware(mockRequest, mockResponse, nextFunction)
|
||||
|
||||
expect(mockRequest.listConfig).toEqual(
|
||||
expect.objectContaining({
|
||||
select: [
|
||||
"id",
|
||||
"created_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
"product.id",
|
||||
],
|
||||
relations: [
|
||||
"metadata",
|
||||
"metadata.parent",
|
||||
"metadata.children",
|
||||
"metadata.product",
|
||||
"product",
|
||||
"product.variants",
|
||||
],
|
||||
})
|
||||
)
|
||||
expect(mockRequest.remoteQueryConfig).toEqual(
|
||||
expect.objectContaining({
|
||||
fields: [
|
||||
"id",
|
||||
"created_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
"product.id",
|
||||
"product.variants.*",
|
||||
],
|
||||
})
|
||||
)
|
||||
|
||||
//////////////////////////////
|
||||
|
||||
mockRequest = {
|
||||
query: {
|
||||
fields: "store.name",
|
||||
},
|
||||
} as unknown as Request
|
||||
|
||||
queryConfig = {
|
||||
defaultFields: [
|
||||
"id",
|
||||
"created_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
allowed: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
"product",
|
||||
"product.variants",
|
||||
"store.name",
|
||||
],
|
||||
isList: true,
|
||||
}
|
||||
|
||||
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
|
||||
|
||||
await middleware(mockRequest, mockResponse, nextFunction)
|
||||
|
||||
expect(mockRequest.listConfig).toEqual(
|
||||
expect.objectContaining({
|
||||
select: ["store.name", "created_at", "id"],
|
||||
relations: ["store"],
|
||||
})
|
||||
)
|
||||
expect(mockRequest.remoteQueryConfig).toEqual(
|
||||
expect.objectContaining({
|
||||
fields: ["store.name", "created_at", "id"],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw when attempting to transform the input if disallowed fields are requested", async () => {
|
||||
let mockRequest = {
|
||||
query: {
|
||||
fields: "+test_prop",
|
||||
},
|
||||
} as unknown as Request
|
||||
const mockResponse = {} as Response
|
||||
const nextFunction: NextFunction = jest.fn()
|
||||
|
||||
let queryConfig: any = {
|
||||
defaultFields: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
defaultRelations: [
|
||||
"metadata",
|
||||
"metadata.parent",
|
||||
"metadata.children",
|
||||
"metadata.product",
|
||||
],
|
||||
allowed: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
isList: true,
|
||||
}
|
||||
|
||||
let middleware = validateAndTransformQuery(createFindParams(), queryConfig)
|
||||
|
||||
await middleware(mockRequest, mockResponse, nextFunction)
|
||||
|
||||
expect(nextFunction).toHaveBeenLastCalledWith(
|
||||
new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Requested fields [test_prop] are not valid`
|
||||
)
|
||||
)
|
||||
|
||||
//////////////////////////////
|
||||
|
||||
mockRequest = {
|
||||
query: {
|
||||
fields: "product",
|
||||
},
|
||||
} as unknown as Request
|
||||
|
||||
queryConfig = {
|
||||
defaultFields: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
defaultRelations: [
|
||||
"metadata",
|
||||
"metadata.parent",
|
||||
"metadata.children",
|
||||
"metadata.product",
|
||||
],
|
||||
allowed: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
isList: true,
|
||||
}
|
||||
|
||||
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
|
||||
|
||||
await middleware(mockRequest, mockResponse, nextFunction)
|
||||
|
||||
expect(nextFunction).toHaveBeenLastCalledWith(
|
||||
new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Requested fields [product] are not valid`
|
||||
)
|
||||
)
|
||||
|
||||
//////////////////////////////
|
||||
|
||||
mockRequest = {
|
||||
query: {
|
||||
fields: "store",
|
||||
},
|
||||
} as unknown as Request
|
||||
|
||||
queryConfig = {
|
||||
defaultFields: [
|
||||
"id",
|
||||
"created_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
allowed: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
"product",
|
||||
"product.variants",
|
||||
"store.name",
|
||||
],
|
||||
isList: true,
|
||||
}
|
||||
|
||||
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
|
||||
|
||||
await middleware(mockRequest, mockResponse, nextFunction)
|
||||
|
||||
expect(nextFunction).toHaveBeenLastCalledWith(
|
||||
new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Requested fields [store] are not valid`
|
||||
)
|
||||
)
|
||||
|
||||
//////////////////////////////
|
||||
|
||||
mockRequest = {
|
||||
query: {
|
||||
fields: "*product",
|
||||
},
|
||||
} as unknown as Request
|
||||
|
||||
queryConfig = {
|
||||
defaults: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
allowed: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
isList: true,
|
||||
}
|
||||
|
||||
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
|
||||
|
||||
await middleware(mockRequest, mockResponse, nextFunction)
|
||||
|
||||
expect(nextFunction).toHaveBeenLastCalledWith(
|
||||
new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Requested fields [product] are not valid`
|
||||
)
|
||||
)
|
||||
|
||||
//////////////////////////////
|
||||
|
||||
mockRequest = {
|
||||
query: {
|
||||
fields: "*product.variants",
|
||||
},
|
||||
} as unknown as Request
|
||||
|
||||
queryConfig = {
|
||||
defaults: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
allowed: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
"product",
|
||||
],
|
||||
isList: true,
|
||||
}
|
||||
|
||||
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
|
||||
|
||||
await middleware(mockRequest, mockResponse, nextFunction)
|
||||
|
||||
expect(nextFunction).toHaveBeenLastCalledWith(
|
||||
new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Requested fields [product.variants] are not valid`
|
||||
)
|
||||
)
|
||||
|
||||
//////////////////////////////
|
||||
|
||||
mockRequest = {
|
||||
query: {
|
||||
fields: "*product",
|
||||
},
|
||||
} as unknown as Request
|
||||
|
||||
queryConfig = {
|
||||
defaults: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
],
|
||||
allowed: [
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"deleted_at",
|
||||
"metadata.id",
|
||||
"metadata.parent.id",
|
||||
"metadata.children.id",
|
||||
"metadata.product.id",
|
||||
"product.title",
|
||||
],
|
||||
isList: true,
|
||||
}
|
||||
|
||||
middleware = validateAndTransformQuery(createFindParams(), queryConfig)
|
||||
|
||||
await middleware(mockRequest, mockResponse, nextFunction)
|
||||
|
||||
expect(nextFunction).toHaveBeenLastCalledWith(
|
||||
new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Requested fields [product] are not valid`
|
||||
)
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
prepareRetrieveQuery,
|
||||
} from "../../utils/get-query-config"
|
||||
import { zodValidator } from "./validate-body"
|
||||
|
||||
/**
|
||||
* Normalize an input query, especially from array like query params to an array type
|
||||
* e.g: /admin/orders/?fields[]=id,status,cart_id becomes { fields: ["id", "status", "cart_id"] }
|
||||
@@ -43,11 +44,14 @@ export function validateAndTransformQuery<TEntity extends BaseEntity>(
|
||||
) => Promise<void> {
|
||||
return async (req: MedusaRequest, _: MedusaResponse, next: NextFunction) => {
|
||||
try {
|
||||
const allowed = (req.allowed ?? queryConfig.allowed ?? []) as string[]
|
||||
delete req.allowed
|
||||
|
||||
const query = normalizeQuery(req)
|
||||
const validated = await zodValidator(zodSchema, query)
|
||||
const cnf = queryConfig.isList
|
||||
? prepareListQuery(validated, queryConfig)
|
||||
: prepareRetrieveQuery(validated, queryConfig)
|
||||
? prepareListQuery(validated, { ...queryConfig, allowed })
|
||||
: prepareRetrieveQuery(validated, { ...queryConfig, allowed })
|
||||
|
||||
req.validatedQuery = validated
|
||||
req.filterableFields = getFilterableFields(req.validatedQuery)
|
||||
|
||||
Reference in New Issue
Block a user