fix: Several fixes to store product endpoints, moved several test suites to HTTP (#7601)

* chore: Move publishable api key tests to HTTP

* chore: Move store tests to HTTP folder

* fix: Add tests for store products, fix several bugs around publishable keys
This commit is contained in:
Stevche Radevski authored and GitHub committed 2024-06-04 21:00:07 +02:00
1 parent d104d1a256
commit e44fe78b96
24 files changed
+2301 -2763

No files matched your search

@@ -9,6 +9,7 @@ import {
import { refetchApiKey } from "../helpers"
import { AdminUpdateApiKeyType } from "../validators"
import { MedusaError } from "@medusajs/utils"
export const GET = async (
req: AuthenticatedMedusaRequest,
@@ -20,6 +21,13 @@ export const GET = async (
req.remoteQueryConfig.fields
)
if (!apiKey) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`API Key with id: ${req.params.id} was not found`
)
}
res.status(200).json({ api_key: apiKey })
}
@@ -5,6 +5,7 @@ export const defaultAdminApiKeyFields = [
"redacted",
"type",
"last_used_at",
"updated_at",
"created_at",
"created_by",
"revoked_at",
@@ -11,7 +11,7 @@ export const AdminGetApiKeyParams = createSelectParams()
export type AdminGetApiKeysParamsType = z.infer<typeof AdminGetApiKeysParams>
export const AdminGetApiKeysParams = createFindParams({
offset: 0,
limit: 50,
limit: 20,
}).merge(
z.object({
q: z.string().optional(),
@@ -18,6 +18,7 @@ import {
remapProductResponse,
} from "../helpers"
import { AdminUpdateProductType } from "../validators"
import { MedusaError } from "@medusajs/utils"
export const GET = async (
req: AuthenticatedMedusaRequest,
@@ -35,6 +36,9 @@ export const GET = async (
})
const [product] = await remoteQuery(queryObject)
if (!product) {
throw new MedusaError(MedusaError.Types.NOT_FOUND, "Product not found")
}
res.status(200).json({ product: remapProductResponse(product) })
}
@@ -2,6 +2,7 @@ import { isPresent } from "@medusajs/utils"
import { MedusaRequest, MedusaResponse } from "../../../../types/routing"
import { refetchProduct, wrapVariantsWithInventoryQuantity } from "../helpers"
import { StoreGetProductsParamsType } from "../validators"
import { MedusaError } from "@medusajs/utils"
export const GET = async (
req: MedusaRequest<StoreGetProductsParamsType>,
@@ -34,6 +35,13 @@ export const GET = async (
req.remoteQueryConfig.fields
)
if (!product) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Product with id: ${req.params.id} was not found`
)
}
if (withInventoryQuantity) {
await wrapVariantsWithInventoryQuantity(req, product.variants || [])
}
@@ -14,6 +14,7 @@ import {
StoreGetProductsParams,
StoreGetProductsParamsType,
} from "./validators"
import { applyParamsAsFilters } from "../../utils/middlewares/common/apply-params-as-filters"
export const storeProductRoutesMiddlewares: MiddlewareRoute[] = [
{
@@ -57,6 +58,7 @@ export const storeProductRoutesMiddlewares: MiddlewareRoute[] = [
StoreGetProductsParams,
QueryConfig.retrieveProductQueryConfig
),
applyParamsAsFilters({ id: "id" }),
filterByValidSalesChannels(),
setContext({
stock_location_id: maybeApplyStockLocationId,
@@ -0,0 +1,16 @@
import { NextFunction } from "express"
import { MedusaRequest } from "../../../../types/routing"
export function applyParamsAsFilters<TFilter extends object>(mappings: {
[param: string]: string
}) {
return async (req: MedusaRequest, _, next: NextFunction) => {
for (const [param, paramValue] of Object.entries(req.params)) {
if (mappings[param]) {
req.filterableFields[mappings[param]] = paramValue
}
}
return next()
}
}
@@ -1,8 +1,13 @@
import { isPresent, MedusaError } from "@medusajs/utils"
import { MedusaError } from "@medusajs/utils"
import { NextFunction } from "express"
import { AuthenticatedMedusaRequest } from "../../../../types/routing"
import { refetchEntity } from "../../refetch-entity"
import { arrayDifference } from "@medusajs/utils"
// Selection of sales channels happens in the following priority:
// - If a publishable API key is passed, we take the sales channels attached to it and filter them down based on the query params
// - If a sales channel id is passed through query params, we use that
// - If not, we use the default sales channel for the store
export function filterByValidSalesChannels() {
return async (req: AuthenticatedMedusaRequest, _, next: NextFunction) => {
const publishableApiKey = req.get("x-publishable-api-key")
@@ -10,68 +15,61 @@ export function filterByValidSalesChannels() {
| string[]
| undefined
if (publishableApiKey) {
const apiKey = await refetchEntity(
"api_key",
{ token: publishableApiKey },
req.scope,
["id", "sales_channels_link.sales_channel_id"]
)
if (!apiKey) {
return next(
new MedusaError(
MedusaError.Types.INVALID_DATA,
`Publishable API key not found`
)
)
}
let result = apiKey.sales_channels_link.map(
(link) => link.sales_channel_id
)
if (salesChannelIds?.length) {
// If all sales channel ids are not in the publishable key, we throw an error
const uniqueInParams = arrayDifference(salesChannelIds, result)
if (uniqueInParams.length) {
return next(
new MedusaError(
MedusaError.Types.INVALID_DATA,
`Requested sales channel is not part of the publishable key mappings`
)
)
}
result = salesChannelIds
}
req.filterableFields.sales_channel_id = result
return next()
}
if (salesChannelIds?.length) {
req.filterableFields.sales_channel_id = salesChannelIds
return next()
}
const store = await refetchEntity("stores", {}, req.scope, [
"default_sales_channel_id",
])
if (!store) {
try {
throw new MedusaError(MedusaError.Types.INVALID_DATA, `Store not found`)
} catch (e) {
return next(e)
}
}
// Always set sales channels in the following priority
// - any existing sales chennel ids passed through query params
// - if none, we set the default sales channel
req.filterableFields.sales_channel_id = salesChannelIds ?? [
store.default_sales_channel_id,
]
// Return early if no publishable keys are found
if (!isPresent(publishableApiKey)) {
return next()
return next(
new MedusaError(MedusaError.Types.INVALID_DATA, `Store not found`)
)
}
// When publishable keys are present, we fetch for all sales chennels attached
// to the publishable key and validate the sales channel filter against it
const apiKey = await refetchEntity(
"api_key",
{ token: publishableApiKey },
req.scope,
["id", "sales_channels_link.sales_channel_id"]
)
if (!apiKey) {
try {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Publishable API key not found`
)
} catch (e) {
return next(e)
}
}
const validSalesChannelIds = apiKey.sales_channels_link.map(
(link) => link.sales_channel_id
)
const invalidSalesChannelIds = (salesChannelIds || []).filter(
(id) => !validSalesChannelIds.includes(id)
)
if (invalidSalesChannelIds.length) {
try {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Invalid sales channel filters provided - ${invalidSalesChannelIds.join(
", "
)}`
)
} catch (e) {
return next(e)
}
if (store.default_sales_channel_id) {
req.filterableFields.sales_channel_id = [store.default_sales_channel_id]
}
return next()