diff --git a/packages/api/src/platforms/vtex/clients/search/index.ts b/packages/api/src/platforms/vtex/clients/search/index.ts index c523cc4480..3cbfd77dfa 100644 --- a/packages/api/src/platforms/vtex/clients/search/index.ts +++ b/packages/api/src/platforms/vtex/clients/search/index.ts @@ -1,11 +1,14 @@ +import pLimit from 'p-limit' + import type { GraphqlContext } from '../../' -import type { IStoreSelectedFacet } from '../../../../__generated__/schema' import { getWithCookie } from '../../utils/cookies' -import type { - FuzzyFacet, - OperatorFacet, - SelectedFacet, -} from '../../utils/facets' +import type { SelectedFacet } from '../../utils/facets' +import { + buildIntelligentSearchRequest, + parseSegmentCookie, + type ProductIdentifierField, + type Sort, +} from '../../utils/intelligentSearchRequest' import { fetchAPI } from '../fetch' import type { Facet, @@ -14,19 +17,19 @@ import type { } from './types/FacetSearchResult' import type { ProductCountResult } from './types/ProductCountResult' import type { + Product, ProductSearchResult, Suggestion, } from './types/ProductSearchResult' -export type Sort = - | 'price:desc' - | 'price:asc' - | 'orders:desc' - | 'name:desc' - | 'name:asc' - | 'release:desc' - | 'discount:desc' - | '' +export type { + ProductIdentifierField, + Sort, +} from '../../utils/intelligentSearchRequest' + +// Bounds simultaneous IS v1 /products calls; values cardinality is capped +// upstream by DataLoader maxBatchSize (99) and cross-sell/products `first`. +const PRODUCTS_BY_IDENTIFIER_CONCURRENCY = 10 export interface SearchArgs { query?: string @@ -48,41 +51,37 @@ export interface ProductLocator { value: string } -const POLICY_KEY = 'trade-policy' -const REGION_KEY = 'region-id' -const FUZZY_KEY = 'fuzzy' -const OPERATOR_KEY = 'operator' -const PICKUP_POINT_KEY = 'pickupPoint' -const SHIPPING_KEY = 'shipping' -const DELIVERY_OPTIONS_KEY = 'delivery-options' -const IN_STOCK_KEY = 'in-stock' - -const EXTRA_FACETS_KEYS = new Set([ - POLICY_KEY, - REGION_KEY, - FUZZY_KEY, - OPERATOR_KEY, - PICKUP_POINT_KEY, - SHIPPING_KEY, - DELIVERY_OPTIONS_KEY, - IN_STOCK_KEY, -]) +export interface FetchProductArgs { + field: ProductIdentifierField + value: string + showInvisibleItems?: boolean + hideUnavailableItems?: boolean +} + +export interface ProductsByIdentifierArgs { + field: ProductIdentifierField + values: string[] + showInvisibleItems?: boolean + hideUnavailableItems?: boolean +} export const isFacetBoolean = ( facet: Facet ): facet is Facet => facet.type === 'TEXT' -const isFuzzyFacet = (facet: SelectedFacet): facet is FuzzyFacet => { - return ( - facet.key === 'fuzzy' && - (facet.value === '0' || facet.value === '1' || facet.value === 'auto') - ) +function getRegionIdFromContext(ctx: GraphqlContext): string | undefined { + const { regionId, seller } = ctx.storage.channel + const sellerRegionId = seller + ? Buffer.from(`SW#${seller}`).toString('base64') + : undefined + + return sellerRegionId ?? (regionId || undefined) } -const isOperatorFacet = (facet: SelectedFacet): facet is OperatorFacet => { - return ( - facet.key === 'operator' && (facet.value === 'and' || facet.value === 'or') - ) +function getSegmentLocale(ctx: GraphqlContext): string { + const segment = parseSegmentCookie(ctx.headers?.cookie) + + return (segment.cultureInfo as string | undefined) ?? ctx.storage.locale } export const IntelligentSearch = ( @@ -96,8 +95,18 @@ export const IntelligentSearch = ( }: Options, ctx: GraphqlContext ) => { - const base = `https://${account}.${environment}.com.br/api/io` + const base = `https://${account}.${environment}.com.br` const withCookie = getWithCookie(ctx) + const segment = parseSegmentCookie(ctx.headers?.cookie) + + const requestDefaults = () => ({ + salesChannel: ctx.storage.channel.salesChannel, + regionId: getRegionIdFromContext(ctx), + locale: getSegmentLocale(ctx), + hideUnavailableItems, + simulationBehavior, + showSponsored, + }) const host = new Headers(ctx.headers).get('x-forwarded-host') ?? ctx.headers?.host ?? '' @@ -115,188 +124,103 @@ export const IntelligentSearch = ( 'X-FORWARDED-HOST': forwardedHost, }) - const getPolicyFacet = (): IStoreSelectedFacet | null => { - const { salesChannel } = ctx.storage.channel - - if (!salesChannel) { - return null - } - - return { - key: POLICY_KEY, - value: salesChannel, - } - } - - const getRegionFacet = (): IStoreSelectedFacet | null => { - const { regionId, seller } = ctx.storage.channel - const sellerRegionId = seller - ? Buffer.from(`SW#${seller}`).toString('base64') - : null - const facet = sellerRegionId ?? regionId - - if (!facet) { - return null - } - - return { - key: REGION_KEY, - value: facet, - } - } + const search = ({ type, ...args }: SearchArgs): Promise => { + const endpoint = type === 'facets' ? 'facets' : 'product-search' + const request = buildIntelligentSearchRequest({ + endpoint, + segment, + defaults: requestDefaults(), + args, + }) - const addDefaultFacets = (facets: SelectedFacet[]) => { - const withDefaultFacets = facets.filter( - ({ key }) => !EXTRA_FACETS_KEYS.has(key) + return fetchAPI( + `${base}/api/intelligent-search/v1/${endpoint}/${request.path}?${request.params.toString()}`, + { headers } ) - - const shippingFacet = - facets.find( - ({ key, value }) => - key === SHIPPING_KEY && value !== 'all-delivery-methods' - ) ?? null - - const deliveryOptionsFacet = - facets.find( - ({ key, value }) => - key === DELIVERY_OPTIONS_KEY && value !== 'all-delivery-options' - ) ?? null - - const policyFacet = - facets.find(({ key }) => key === POLICY_KEY) ?? getPolicyFacet() - - const regionFacet = - facets.find(({ key }) => key === REGION_KEY) ?? getRegionFacet() - - if (shippingFacet !== null) { - withDefaultFacets.push(shippingFacet) - } - - if (deliveryOptionsFacet !== null) { - withDefaultFacets.push(deliveryOptionsFacet) - } - - if (policyFacet !== null) { - withDefaultFacets.push(policyFacet) - } - - if (regionFacet !== null) { - withDefaultFacets.push(regionFacet) - } - - return withDefaultFacets } - const addSearchParamsFacets = ( - facets: SelectedFacet[], - params: URLSearchParams - ) => { - const fuzzyFacet = facets.find(({ key }) => key === FUZZY_KEY) ?? null - const operatorFacet = facets.find(({ key }) => key === OPERATOR_KEY) ?? null - const pickupPointFacet = - facets.find(({ key }) => key === PICKUP_POINT_KEY) ?? null - - if (fuzzyFacet && isFuzzyFacet(fuzzyFacet)) { - params.append(FUZZY_KEY, fuzzyFacet.value) - } - - if (operatorFacet && isOperatorFacet(operatorFacet)) { - params.append(OPERATOR_KEY, operatorFacet.value) - } - - if (pickupPointFacet) { - params.append(PICKUP_POINT_KEY, pickupPointFacet.value) - } - } + const products = (args: Omit) => + search({ ...args, type: 'product_search' }) - const search = ({ - query = '', - page, - count, - sort = '', - selectedFacets = [], - type, + const fetchProduct = ({ + field, + value, + hideUnavailableItems: hideUnavailable, showInvisibleItems, - sponsoredCount, - hideUnavailableItems: searchHideUnavailableItems, - allowRedirect = false, - }: SearchArgs): Promise => { - const params = new URLSearchParams({ - page: (page + 1).toString(), - count: count !== 0 ? count.toString() : '1', - query, - sort, - locale: ctx.storage.locale, + }: FetchProductArgs): Promise => { + const request = buildIntelligentSearchRequest({ + endpoint: 'products', + segment, + defaults: requestDefaults(), + args: { + field, + value, + hideUnavailableItems: hideUnavailable, + showInvisibleItems, + }, }) - addSearchParamsFacets(selectedFacets, params) - - if (showInvisibleItems) { - params.append('show-invisible-items', 'true') - } - - if (hideUnavailableItems !== undefined) { - const inStockFacet = selectedFacets.find( - ({ key }) => key === IN_STOCK_KEY - ) - const shouldHideUnavailableItems = inStockFacet - ? inStockFacet.value - : (searchHideUnavailableItems?.toString() ?? - hideUnavailableItems.toString()) - - params.append('hideUnavailableItems', shouldHideUnavailableItems) - } - - if (simulationBehavior !== undefined) { - params.append('simulationBehavior', simulationBehavior.toString()) - } - - if (showSponsored !== undefined) { - params.append('showSponsored', showSponsored.toString()) - } - - if (sponsoredCount !== undefined) { - params.append('sponsoredCount', sponsoredCount.toString()) - } - - if (allowRedirect !== undefined) { - params.append('allowRedirect', allowRedirect.toString()) - } - - const pathname = addDefaultFacets(selectedFacets) - .map(({ key, value }) => `${key}/${value}`) - .join('/') - return fetchAPI( - `${base}/_v/api/intelligent-search/${type}/${pathname}?${params.toString()}`, + `${base}/api/intelligent-search/v1/products?${request.params.toString()}`, { headers } ) } - const products = (args: Omit) => - search({ ...args, type: 'product_search' }) + const productsByIdentifier = async ({ + field, + values, + showInvisibleItems, + hideUnavailableItems: hideUnavailable, + }: ProductsByIdentifierArgs): Promise => { + const limit = pLimit(PRODUCTS_BY_IDENTIFIER_CONCURRENCY) + + const productsResult = await Promise.all( + values.map((value) => + limit(async () => { + try { + return await fetchProduct({ + field, + value, + hideUnavailableItems: hideUnavailable, + showInvisibleItems, + }) + } catch { + return null + } + }) + ) + ) + + return productsResult.filter( + (product): product is Product => product !== null + ) + } const suggestedTerms = ( args: Omit ): Promise => { - const params = new URLSearchParams({ - query: args.query?.toString() ?? '', - locale: ctx.storage.locale, + const request = buildIntelligentSearchRequest({ + endpoint: 'search-suggestions', + segment, + defaults: requestDefaults(), + args: { query: args.query }, }) return fetchAPI( - `${base}/_v/api/intelligent-search/search_suggestions?${params.toString()}`, + `${base}/api/intelligent-search/v1/search-suggestions?${request.params.toString()}`, { headers } ) } const topSearches = (): Promise => { - const params = new URLSearchParams({ - locale: ctx.storage.locale, + const request = buildIntelligentSearchRequest({ + endpoint: 'top-searches', + segment, + defaults: requestDefaults(), }) return fetchAPI( - `${base}/_v/api/intelligent-search/top_searches?${params.toString()}`, + `${base}/api/intelligent-search/v1/top-searches?${request.params.toString()}`, { headers } ) } @@ -305,16 +229,17 @@ export const IntelligentSearch = ( search({ ...args, type: 'facets' }) const productCount = ( - args: Pick + args: Omit ): Promise => { - const params = new URLSearchParams() - - if (args?.query) { - params.append('query', args.query.toString()) - } + const request = buildIntelligentSearchRequest({ + endpoint: 'catalog-count', + segment, + defaults: requestDefaults(), + args, + }) return fetchAPI( - `${base}/_v/api/intelligent-search/catalog_count?${params.toString()}`, + `${base}/api/intelligent-search/v1/catalog-count/${request.path}?${request.params.toString()}`, { headers } ) } @@ -322,6 +247,8 @@ export const IntelligentSearch = ( return { facets, products, + fetchProduct, + productsByIdentifier, suggestedTerms, topSearches, productCount, diff --git a/packages/api/src/platforms/vtex/loaders/sku.ts b/packages/api/src/platforms/vtex/loaders/sku.ts index 3a78f0c5f9..f3f78c1363 100644 --- a/packages/api/src/platforms/vtex/loaders/sku.ts +++ b/packages/api/src/platforms/vtex/loaders/sku.ts @@ -12,10 +12,9 @@ export const getSkuLoader = ({ flags }: Options, clients: Clients) => { (key) => key.split('-')[1] === 'invisibleItems' ) - const { products } = await clients.search.products({ - query: `sku:${skuIds.join(';')}`, - page: 0, - count: skuIds.length, + const products = await clients.search.productsByIdentifier({ + field: 'sku', + values: skuIds, showInvisibleItems, ...(flags?.enableUnavailableItemsOnCart && { hideUnavailableItems: false, diff --git a/packages/api/src/platforms/vtex/resolvers/query.ts b/packages/api/src/platforms/vtex/resolvers/query.ts index 72cf5a66d6..601fd322ad 100644 --- a/packages/api/src/platforms/vtex/resolvers/query.ts +++ b/packages/api/src/platforms/vtex/resolvers/query.ts @@ -26,6 +26,7 @@ import { import type { CategoryTree } from '../clients/commerce/types/CategoryTree' import type { ProfileAddress } from '../clients/commerce/types/Profile' import type { SearchArgs } from '../clients/search' +import type { ProductSearchResult } from '../clients/search/types/ProductSearchResult' import type { GraphqlContext } from '../index' import { extractRuleForAuthorization } from '../utils/commercialAuth' import { mutateChannelContext, mutateLocaleContext } from '../utils/contex' @@ -44,6 +45,16 @@ import { SORT_MAP } from '../utils/sort' import { FACET_CROSS_SELLING_MAP } from './../utils/facets' import { StoreCollection } from './collection' +const INVALID_SKU_ID_ERROR = 'Invalid SkuId' +const SLUG_MISMATCH_ERROR = + 'Slug was set but the fetched sku does not satisfy the slug condition.' + +const shouldFallbackToProductRoute = (error: unknown) => + isNotFoundError(error) || + (error instanceof Error && + (error.message === INVALID_SKU_ID_ERROR || + error.message.startsWith(SLUG_MISMATCH_ERROR))) + export const Query = { product: async ( _: unknown, @@ -73,7 +84,7 @@ export const Query = { const skuId = id ?? slug?.split('-').pop() ?? '' if (!isValidSkuId(skuId)) { - throw new Error('Invalid SkuId') + throw new Error(INVALID_SKU_ID_ERROR) } const sku = await skuLoader.load(skuId) @@ -97,6 +108,10 @@ export const Query = { return sku } catch (err) { + if (!shouldFallbackToProductRoute(err)) { + throw err + } + if (slug == null) { throw new BadRequestError('Missing slug or id') } @@ -107,15 +122,12 @@ export const Query = { throw new NotFoundError(`No product found for slug ${slug}`) } - const { - products: [product], - } = await search.products({ - page: 0, - count: 1, - query: `product:${route.id}`, - // Manually disabling this flag to prevent regionalization issues - hideUnavailableItems: false, - }) + const product = await search + .fetchProduct({ + field: 'id', + value: String(route.id), + }) + .catch(() => null) if (!product) { throw new NotFoundError(`No product found for id ${route.id}`) @@ -162,39 +174,60 @@ export const Query = { mutateLocaleContext(ctx, locale) } - let query = term + const after = maybeAfter ? Number(maybeAfter) : 0 + const searchArgs: Omit = { + page: Math.ceil(after / first) || 0, + count: first, + query: term ?? undefined, + sort: SORT_MAP[sort ?? 'score_desc'] ?? SORT_MAP.score_desc, + selectedFacets: selectedFacets?.flatMap(transformSelectedFacet) ?? [], + sponsoredCount: sponsoredCount ?? undefined, + } /** - * In case we are using crossSelling, we need to modify the search - * we will be performing on our search engine. The idea in here - * is to use the cross selling API for fetching the productIds our - * search will return for us. - * Doing this two request workflow makes it possible to have cross - * selling with Search features, like pagination, internationalization - * etc + * In case we are using crossSelling, we fetch product IDs from the + * cross selling API and then hydrate them using the PDP endpoint + * via productsByIdentifier. */ if (crossSelling) { - const products = await ctx.clients.commerce.catalog.products.crossselling( - { + const crossSellingProducts = + await ctx.clients.commerce.catalog.products.crossselling({ type: FACET_CROSS_SELLING_MAP[crossSelling.key], productId: crossSelling.value, - } - ) + }) - query = `product:${products + const productIds = crossSellingProducts .map((x) => x.productId) .slice(0, first) - .join(';')}` - } - const after = maybeAfter ? Number(maybeAfter) : 0 - const searchArgs: Omit = { - page: Math.ceil(after / first) || 0, - count: first, - query: query ?? undefined, - sort: SORT_MAP[sort ?? 'score_desc'], - selectedFacets: selectedFacets?.flatMap(transformSelectedFacet) ?? [], - sponsoredCount: sponsoredCount ?? undefined, + const productSearchPromise: Promise = + ctx.clients.search + .productsByIdentifier({ field: 'id', values: productIds }) + .then((products) => ({ + products, + recordsFiltered: products.length, + pagination: { + count: products.length, + current: { index: 0, proxyURL: '' }, + before: [], + after: [], + perPage: first, + next: { index: 0, proxyURL: '' }, + previous: { index: 0, proxyURL: '' }, + first: { index: 0, proxyURL: '' }, + last: { index: 0, proxyURL: '' }, + }, + sampling: false, + options: { sorts: [], counts: [] }, + translated: false, + locale: '', + query: '', + operator: 'and', + fuzzy: '0', + searchId: '', + })) + + return { searchArgs, productSearchPromise } } const productSearchPromise = ctx.clients.search.products(searchArgs) @@ -250,14 +283,12 @@ export const Query = { return [] } - const query = `id:${productIds.join(';')}` - const products = await search.products({ - page: 0, - count: productIds.length, - query, + const products = await search.productsByIdentifier({ + field: 'id', + values: productIds, }) - return products.products + return products .flatMap((product) => product.items.map((sku) => enhanceSku(sku, product)) ) diff --git a/packages/api/src/platforms/vtex/utils/intelligentSearchRequest.ts b/packages/api/src/platforms/vtex/utils/intelligentSearchRequest.ts new file mode 100644 index 0000000000..ba5acca788 --- /dev/null +++ b/packages/api/src/platforms/vtex/utils/intelligentSearchRequest.ts @@ -0,0 +1,642 @@ +import { parse } from 'cookie' + +export interface IntelligentSearchFacet { + key: string + value: string +} + +type FuzzyFacet = { + key: 'fuzzy' + value: '0' | '1' | 'auto' +} + +type OperatorFacet = { + key: 'operator' + value: 'and' | 'or' +} + +type SegmentParams = { + sc?: string | number + regionId?: string + country?: string + locale?: string + 'zip-code'?: string + coordinates?: string + pickupPoint?: string + deliveryZonesHash?: string + pickupPointHash?: string + utmSource?: string + utmCampaign?: string + utmiCampaign?: string + campaigns?: string + priceTables?: string + productClusterIds?: string +} + +export type Sort = + | 'price:desc' + | 'price:asc' + | 'orders:desc' + | 'name:desc' + | 'name:asc' + | 'release:desc' + | 'discount:desc' + | '' + +export type ProductIdentifierField = 'id' | 'slug' | 'ean' | 'reference' | 'sku' + +export type IntelligentSearchEndpoint = + | 'product-search' + | 'facets' + | 'catalog-count' + | 'products' + | 'search-suggestions' + | 'top-searches' + +export interface IntelligentSearchDefaults { + salesChannel?: string | number + regionId?: string + locale: string + hideUnavailableItems?: boolean + simulationBehavior?: 'default' | 'skip' | 'only1P' + showSponsored?: boolean +} + +export interface IntelligentSearchRequestArgs { + query?: string + page?: number + count?: number + sort?: Sort + selectedFacets?: IntelligentSearchFacet[] + showInvisibleItems?: boolean + hideUnavailableItems?: boolean + sponsoredCount?: number + allowRedirect?: boolean + field?: ProductIdentifierField + value?: string +} + +export interface IntelligentSearchRequestInput { + endpoint: IntelligentSearchEndpoint + /** Decoded vtex_segment object (params + extraFacets are extracted internally). */ + segment?: Record + defaults?: IntelligentSearchDefaults + args?: IntelligentSearchRequestArgs +} + +export interface IntelligentSearchRequest { + /** Attribute path segment only (no base URL or endpoint prefix). */ + path: string + params: URLSearchParams + toString(): string +} + +const FUZZY_KEY = 'fuzzy' +const OPERATOR_KEY = 'operator' +const PICKUP_POINT_KEY = 'pickupPoint' +const SHIPPING_KEY = 'shipping' +const DELIVERY_OPTIONS_KEY = 'delivery-options' +const IN_STOCK_KEY = 'in-stock' +const POLICY_KEY = 'trade-policy' +const REGION_KEY = 'region-id' + +const PATH_EXCLUDED_KEYS = new Set([ + POLICY_KEY, + REGION_KEY, + FUZZY_KEY, + OPERATOR_KEY, + PICKUP_POINT_KEY, +]) + +const SHIPPING_FACET_KEYS = new Set([ + 'zip-code', + 'pickupPoint', + 'country', + 'coordinates', + 'deliveryZonesHash', + 'pickupPointsHash', +]) + +const QUERY_PARAM_FACET_KEYS = new Set(['productClusterIds']) + +const encodeSafeURI = (uri: string) => encodeURI(decodeURI(uri)) + +const removeDiacriticsFromURL = (url: string) => + encodeURIComponent( + decodeURIComponent(url) + .normalize('NFD') + // biome-ignore lint/suspicious/noMisleadingCharacterClass: After NFD normalization, combining marks are separated and can be matched individually + .replaceAll(/[\u0300-\u036f]/g, '') + ) + +const PICKUP_IN_POINT_SUFFIX_RE = /^pickup-in-point-(.+)$/ + +/** + * Reads and decodes the `vtex_segment` cookie. Does not create a segment. + * Returns an empty object when the cookie is absent or invalid. + */ +export function parseSegmentCookie( + cookieHeader?: string +): Record { + if (!cookieHeader) { + return {} + } + + try { + const cookies = parse(cookieHeader) + const segmentToken = cookies.vtex_segment + + if (!segmentToken) { + return {} + } + + const decoded = Buffer.from(segmentToken, 'base64').toString('utf-8') + + return JSON.parse(decoded) as Record + } catch { + return {} + } +} + +function parseSegmentFacetsString(facetsStr: string): { + shipping: Record + queryParams: Record + extraFacets: IntelligentSearchFacet[] +} { + const shipping: Record = {} + const queryParams: Record = {} + const extraFacets: IntelligentSearchFacet[] = [] + + for (const pair of facetsStr.split(';')) { + const eqIdx = pair.indexOf('=') + + if (eqIdx < 0) continue + + const key = pair.slice(0, eqIdx) + const value = pair.slice(eqIdx + 1) + + if (!key || !value) continue + + if (SHIPPING_FACET_KEYS.has(key)) { + shipping[key] = value + } else if (QUERY_PARAM_FACET_KEYS.has(key)) { + queryParams[key] = value + } else { + extraFacets.push({ key, value }) + } + } + + return { shipping, queryParams, extraFacets } +} + +function extractSegmentData(segment: Record): { + segmentParams: SegmentParams + extraFacets: IntelligentSearchFacet[] +} { + const facetsStr = typeof segment.facets === 'string' ? segment.facets : '' + const { shipping, queryParams, extraFacets } = + parseSegmentFacetsString(facetsStr) + + return { + segmentParams: { + sc: segment.channel as string | number | undefined, + regionId: segment.regionId as string | undefined, + country: (segment.countryCode as string | undefined) ?? shipping.country, + locale: segment.cultureInfo as string | undefined, + 'zip-code': shipping['zip-code'], + coordinates: shipping.coordinates, + pickupPoint: shipping.pickupPoint, + deliveryZonesHash: shipping.deliveryZonesHash, + pickupPointHash: shipping.pickupPointsHash, + utmSource: (segment.utm_source as string | undefined) ?? undefined, + utmCampaign: (segment.utm_campaign as string | undefined) ?? undefined, + utmiCampaign: (segment.utmi_campaign as string | undefined) ?? undefined, + campaigns: + typeof segment.campaigns === 'string' ? segment.campaigns : undefined, + priceTables: (segment.priceTables as string | undefined) ?? undefined, + productClusterIds: queryParams.productClusterIds, + }, + extraFacets, + } +} + +function appendSegmentParams( + params: URLSearchParams, + segmentParams: SegmentParams +) { + const entries: Array<[string, string | number | undefined]> = [ + ['sc', segmentParams.sc], + ['regionId', segmentParams.regionId], + ['country', segmentParams.country], + ['locale', segmentParams.locale], + ['zip-code', segmentParams['zip-code']], + ['coordinates', segmentParams.coordinates], + ['pickupPoint', segmentParams.pickupPoint], + ['deliveryZonesHash', segmentParams.deliveryZonesHash], + ['pickupPointHash', segmentParams.pickupPointHash], + ['utmSource', segmentParams.utmSource], + ['utmCampaign', segmentParams.utmCampaign], + ['utmiCampaign', segmentParams.utmiCampaign], + ['campaigns', segmentParams.campaigns], + ['priceTables', segmentParams.priceTables], + ['productClusterId', segmentParams.productClusterIds], + ] + + for (const [key, value] of entries) { + if (value !== undefined && value !== null && value !== '') { + params.append(key, String(value)) + } + } +} + +function extractPickupPointIdFromShippingFacetValue( + value: string +): string | undefined { + const match = PICKUP_IN_POINT_SUFFIX_RE.exec(value) + + return match?.[1] +} + +function extractPickupPointIdFromPathShippingFacet( + selectedFacetsFromPath: IntelligentSearchFacet[] +): string | undefined { + const shipping = selectedFacetsFromPath.find((f) => f.key === 'shipping') + const rawId = shipping + ? extractPickupPointIdFromShippingFacetValue(shipping.value) + : undefined + + if (rawId === undefined) { + return undefined + } + + try { + return decodeURIComponent(rawId) + } catch { + return rawId + } +} + +function mergeSegmentParamsWithPickupFromPath( + segmentParams: SegmentParams | undefined, + selectedFacetsFromPath: IntelligentSearchFacet[] +): SegmentParams | undefined { + const pathPickupId = extractPickupPointIdFromPathShippingFacet( + selectedFacetsFromPath + ) + + if (pathPickupId === undefined) { + return segmentParams + } + + return { + ...segmentParams, + pickupPoint: pathPickupId, + } +} + +function normalizePickupInPointShippingFacets( + selectedFacets: IntelligentSearchFacet[] +): IntelligentSearchFacet[] { + return selectedFacets.map((facet) => { + if (facet.key !== 'shipping') { + return facet + } + + const id = extractPickupPointIdFromShippingFacetValue(facet.value) + + if (id === undefined) { + return facet + } + + return { ...facet, value: 'pickup-in-point' } + }) +} + +function concatSelectedFacets( + selectedFacets: IntelligentSearchFacet[], + selectedFacetsFromSegment: IntelligentSearchFacet[] +): IntelligentSearchFacet[] { + let result = [...selectedFacets] + const hasShipping = result.some((f) => f.key === 'shipping') + + for (const facet of selectedFacetsFromSegment) { + if (!hasShipping || facet.key !== 'shipping') { + result.push(facet) + } + } + + if (result.some((f) => f.key === 'shipping' && f.value === 'ignore')) { + result = result.filter((f) => f.key !== 'shipping') + } + + return normalizePickupInPointShippingFacets(result) +} + +function buildAttributePath(selectedFacets: IntelligentSearchFacet[]) { + return selectedFacets.reduce((attributePath, facet) => { + let { key, value } = facet + + if (key === 'priceRange') { + key = 'price' + value = value.replace(' TO ', ':') + } + + return key === 'ft' + ? attributePath + : `${attributePath}${encodeSafeURI(key)}/${removeDiacriticsFromURL(encodeSafeURI(value)).replaceAll(/ |%20/g, '-')}/` + }, '') +} + +const isFuzzyFacet = (facet: IntelligentSearchFacet): facet is FuzzyFacet => + facet.key === 'fuzzy' && + (facet.value === '0' || facet.value === '1' || facet.value === 'auto') + +const isOperatorFacet = ( + facet: IntelligentSearchFacet +): facet is OperatorFacet => + facet.key === 'operator' && (facet.value === 'and' || facet.value === 'or') + +function resolveSegmentData( + segment: Record | undefined, + defaults: IntelligentSearchDefaults | undefined +): { segmentParams: SegmentParams; extraFacets: IntelligentSearchFacet[] } { + const { segmentParams, extraFacets } = extractSegmentData(segment ?? {}) + + // Server-provided defaults are authoritative for sales channel/region: the + // raw `vtex_segment` cookie is client-supplied base64 JSON and must not be + // able to override the trusted server context. Only fall back to the cookie + // values when no default is provided. + const sc = defaults?.salesChannel ?? segmentParams.sc + const regionId = defaults?.regionId ?? segmentParams.regionId + const locale = defaults?.locale ?? segmentParams.locale ?? '' + + return { + segmentParams: { + ...segmentParams, + ...(sc ? { sc } : {}), + ...(regionId ? { regionId } : {}), + locale, + }, + extraFacets, + } +} + +function preparePathFacets( + facets: IntelligentSearchFacet[], + extraFacets: IntelligentSearchFacet[] +): IntelligentSearchFacet[] { + const pathFacets = facets.filter(({ key }) => !PATH_EXCLUDED_KEYS.has(key)) + + const shippingFacet = + facets.find( + ({ key, value }) => + key === SHIPPING_KEY && value !== 'all-delivery-methods' + ) ?? null + + const deliveryOptionsFacet = + facets.find( + ({ key, value }) => + key === DELIVERY_OPTIONS_KEY && value !== 'all-delivery-options' + ) ?? null + + const withShippingFacets = [...pathFacets] + + // `pathFacets` may already contain the shipping/delivery-options facets, so + // only push them when they are not already present to avoid duplicated path + // segments like `shipping/delivery/shipping/delivery/`. + const hasShippingInPath = withShippingFacets.some( + ({ key }) => key === SHIPPING_KEY + ) + const hasDeliveryOptionsInPath = withShippingFacets.some( + ({ key }) => key === DELIVERY_OPTIONS_KEY + ) + + if (shippingFacet !== null && !hasShippingInPath) { + withShippingFacets.push(shippingFacet) + } + + if (deliveryOptionsFacet !== null && !hasDeliveryOptionsInPath) { + withShippingFacets.push(deliveryOptionsFacet) + } + + return concatSelectedFacets(withShippingFacets, extraFacets) +} + +function addSearchParamsFacets( + facets: IntelligentSearchFacet[], + params: URLSearchParams +) { + const fuzzyFacet = facets.find(({ key }) => key === FUZZY_KEY) ?? null + const operatorFacet = facets.find(({ key }) => key === OPERATOR_KEY) ?? null + const pickupPointFacet = + facets.find(({ key }) => key === PICKUP_POINT_KEY) ?? null + + if (fuzzyFacet && isFuzzyFacet(fuzzyFacet)) { + params.append(FUZZY_KEY, fuzzyFacet.value) + } + + if (operatorFacet && isOperatorFacet(operatorFacet)) { + params.append(OPERATOR_KEY, operatorFacet.value) + } + + if (pickupPointFacet) { + params.append(PICKUP_POINT_KEY, pickupPointFacet.value) + } +} + +interface SearchLikeParams { + includePagination: boolean + includeSort: boolean +} + +function buildSearchLikeParams( + args: IntelligentSearchRequestArgs, + defaults: IntelligentSearchDefaults | undefined, + segmentData: { + segmentParams: SegmentParams + extraFacets: IntelligentSearchFacet[] + }, + options: SearchLikeParams +): { params: URLSearchParams; path: string } { + const { + query = '', + page, + count, + sort, + selectedFacets = [], + showInvisibleItems, + sponsoredCount, + hideUnavailableItems: searchHideUnavailableItems, + allowRedirect = false, + } = args + + const params = new URLSearchParams() + + if (options.includePagination && page !== undefined && count !== undefined) { + const from = page * count + const to = count === 0 ? from : from + count - 1 + + params.append('from', from.toString()) + params.append('to', to.toString()) + } + + if (query) { + params.append('query', query) + } + + if (options.includeSort && sort) { + params.append('sort', sort) + } + + const mergedSegmentParams = mergeSegmentParamsWithPickupFromPath( + segmentData.segmentParams, + selectedFacets + ) + + appendSegmentParams(params, mergedSegmentParams ?? segmentData.segmentParams) + addSearchParamsFacets(selectedFacets, params) + + if (showInvisibleItems) { + params.append('show-invisible-items', 'true') + } + + if (defaults?.hideUnavailableItems !== undefined) { + const inStockFacet = selectedFacets.find(({ key }) => key === IN_STOCK_KEY) + const shouldHideUnavailableItems = inStockFacet + ? inStockFacet.value + : (searchHideUnavailableItems?.toString() ?? + defaults.hideUnavailableItems.toString()) + + params.append('hideUnavailableItems', shouldHideUnavailableItems) + } + + if (defaults?.simulationBehavior !== undefined) { + params.append('simulationBehavior', defaults.simulationBehavior.toString()) + } + + if (defaults?.showSponsored !== undefined) { + params.append('showSponsored', defaults.showSponsored.toString()) + } + + if (sponsoredCount !== undefined) { + params.append('sponsoredCount', sponsoredCount.toString()) + } + + if (allowRedirect !== undefined) { + params.append('allowRedirect', allowRedirect.toString()) + } + + const path = buildAttributePath( + preparePathFacets(selectedFacets, segmentData.extraFacets) + ) + + return { params, path } +} + +function buildProductsParams( + args: IntelligentSearchRequestArgs, + segmentParams: SegmentParams +): URLSearchParams { + const { field, value, hideUnavailableItems, showInvisibleItems } = args + + const params = new URLSearchParams({ + field: field ?? '', + value: value ?? '', + }) + + appendSegmentParams(params, segmentParams) + + if (hideUnavailableItems) { + params.append('hideUnavailableItems', 'true') + } + + if (showInvisibleItems) { + params.append('show-invisible-items', 'true') + } + + return params +} + +function createRequest( + path: string, + params: URLSearchParams +): IntelligentSearchRequest { + return { + path, + params, + toString() { + const qs = params.toString() + + if (!path) { + return qs ? `?${qs}` : '' + } + + return qs ? `${path}?${qs}` : path + }, + } +} + +/** + * Pure builder for intelligent-search v1 path + query params. + * Callers assemble the full URL: `/api/intelligent-search/v1/${endpoint}/${request.path}?${request.params}`. + */ +export function buildIntelligentSearchRequest( + input: IntelligentSearchRequestInput +): IntelligentSearchRequest { + const { endpoint, segment, defaults, args = {} } = input + const segmentData = resolveSegmentData(segment, defaults) + + switch (endpoint) { + case 'product-search': + case 'facets': { + const { params, path } = buildSearchLikeParams( + args, + defaults, + segmentData, + { includePagination: true, includeSort: true } + ) + + return createRequest(path, params) + } + + case 'catalog-count': { + const { params, path } = buildSearchLikeParams( + args, + defaults, + segmentData, + { includePagination: false, includeSort: false } + ) + + return createRequest(path, params) + } + + case 'products': { + const params = buildProductsParams(args, segmentData.segmentParams) + + return createRequest('', params) + } + + case 'search-suggestions': { + const params = new URLSearchParams({ + query: args.query?.toString() ?? '', + locale: defaults?.locale ?? segmentData.segmentParams.locale ?? '', + }) + + return createRequest('', params) + } + + case 'top-searches': { + const params = new URLSearchParams({ + locale: defaults?.locale ?? segmentData.segmentParams.locale ?? '', + }) + + return createRequest('', params) + } + + default: { + const _exhaustive: never = endpoint + + throw new Error(`Unknown intelligent-search endpoint: ${_exhaustive}`) + } + } +} diff --git a/packages/api/test/integration/mutations.test.ts b/packages/api/test/integration/mutations.test.ts index 9fe89e6430..13aa30cf07 100644 --- a/packages/api/test/integration/mutations.test.ts +++ b/packages/api/test/integration/mutations.test.ts @@ -7,11 +7,11 @@ import { ValidateCartMutation, checkoutOrderFormCustomDataInvalidFetch, checkoutOrderFormCustomDataStaleFetch, - checkoutOrderFormCustomDataValidFetch, checkoutOrderFormInvalidFetch, checkoutOrderFormItemsInvalidFetch, checkoutOrderFormStaleFetch, checkoutOrderFormValidFetch, + createProductFetchResultForSku, productSearchPage1Count1Fetch, } from '../mocks/ValidateCartMutation' import { salesChannelStaleFetch } from '../mocks/salesChannel' @@ -65,6 +65,14 @@ function pickFetchAPICallResult( _: RequestInit | undefined, expectedFetchAPICalls: Array> ) { + const url = String(info) + + if (url.includes('/api/intelligent-search/v1/products?')) { + const skuId = new URL(url).searchParams.get('value') ?? '' + + return createProductFetchResultForSku(skuId) + } + for (const call of expectedFetchAPICalls) { if (info === call.info) { return call.result @@ -93,21 +101,15 @@ beforeEach(() => { test('`validateCart` mutation should return `null` when a valid cart is passed', async () => { const run = await createRunner() mockedFetch.mockImplementation((info, init) => - pickFetchAPICallResult(info, init, [ - checkoutOrderFormValidFetch, - checkoutOrderFormCustomDataValidFetch, - salesChannelStaleFetch, - ]) + pickFetchAPICallResult(info, init, [checkoutOrderFormValidFetch]) ) const response = await run(ValidateCartMutation, { cart: ValidCart }) - // When cart is valid, the system will: - // 1. GET orderForm - // 2. PUT customData (update/set etag because cart might have been modified elsewhere) - // 3. GET saleschannel (to get currency info) - // Since the cart matches and etag is now correct, it returns null (no changes needed) - expect(mockedFetch).toHaveBeenCalledTimes(3) + // When cart is valid and etag is up to date: + // 1. GET orderForm (checkoutOrderFormValidFetch) + // 2. GET product data via v1/products (handled dynamically) + expect(mockedFetch).toHaveBeenCalledTimes(2) expect(response.data?.validateCart).toEqual(null) }) diff --git a/packages/api/test/integration/queries.test.ts b/packages/api/test/integration/queries.test.ts index a203065a4e..472282256d 100644 --- a/packages/api/test/integration/queries.test.ts +++ b/packages/api/test/integration/queries.test.ts @@ -22,7 +22,7 @@ import { pageTypeOfficeDesksFetch, pageTypeOfficeFetch, } from '../mocks/CollectionQuery' -import { ProductByIdQuery, productSearchFetch } from '../mocks/ProductQuery' +import { ProductByIdQuery, pdpFetch } from '../mocks/ProductQuery' import { RedirectQueryTermTech, redirectTermTechFetch, @@ -140,7 +140,7 @@ test('`collection` query', async () => { test('`product` query', async () => { const run = await createRunner() - const fetchAPICalls = [productSearchFetch, salesChannelStaleFetch] + const fetchAPICalls = [pdpFetch, salesChannelStaleFetch] mockedFetch.mockImplementation((info, init) => pickFetchAPICallResult(info, init, fetchAPICalls) diff --git a/packages/api/test/mocks/AllProductsQuery.ts b/packages/api/test/mocks/AllProductsQuery.ts index d1ccd54102..9d7c9d67ef 100644 --- a/packages/api/test/mocks/AllProductsQuery.ts +++ b/packages/api/test/mocks/AllProductsQuery.ts @@ -78,7 +78,7 @@ export const AllProductsQueryFirst5 = `query AllProducts { ` export const productSearchPage1Count5Fetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=1&count=5&query=&sort=&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false&allowRedirect=false', + info: 'https://storeframework.vtexcommercestable.com.br/api/intelligent-search/v1/product-search/?from=0&to=4&sc=1&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false&allowRedirect=false', init: { headers: { 'X-FORWARDED-HOST': '', 'content-type': 'application/json' }, }, diff --git a/packages/api/test/mocks/ProductQuery.ts b/packages/api/test/mocks/ProductQuery.ts index 3ee8b53a4e..900762663e 100644 --- a/packages/api/test/mocks/ProductQuery.ts +++ b/packages/api/test/mocks/ProductQuery.ts @@ -62,200 +62,166 @@ export const ProductByIdQuery = `query ProductQuery { } ` -export const productSearchFetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=1&count=1&query=sku%3A64953394&sort=&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false&allowRedirect=false', +export const pdpFetch = { + info: 'https://storeframework.vtexcommercestable.com.br/api/intelligent-search/v1/products?field=sku&value=64953394&sc=1&locale=en-US', init: { headers: { 'X-FORWARDED-HOST': '', 'content-type': 'application/json' }, }, options: undefined, result: { - products: [ + cacheId: 'sp-29913569', + productId: '29913569', + description: 'Aut omnis nobis tenetur.', + productName: 'Unbranded Concrete Table Small', + productReference: '4715709796003', + linkText: 'unbranded-concrete-table-small', + brand: 'Brand', + brandId: 9280, + link: '/unbranded-concrete-table-small/p', + categories: ['/Office/Desks/', '/Office/'], + categoryId: '9295', + categoriesIds: ['/9282/9295/', '/9282/'], + priceRange: { + sellingPrice: { + highPrice: 200.64, + lowPrice: 200.64, + }, + listPrice: { + highPrice: 297.7, + lowPrice: 297.7, + }, + }, + specificationGroups: [ { - cacheId: 'sp-29913569', - productId: '29913569', - description: 'Aut omnis nobis tenetur.', - productName: 'Unbranded Concrete Table Small', - productReference: '4715709796003', - linkText: 'unbranded-concrete-table-small', - brand: 'Brand', - brandId: 9280, - link: '/unbranded-concrete-table-small/p', - categories: ['/Office/Desks/', '/Office/'], - categoryId: '9295', - categoriesIds: ['/9282/9295/', '/9282/'], - priceRange: { - sellingPrice: { - highPrice: 200.64, - lowPrice: 200.64, - }, - listPrice: { - highPrice: 297.7, - lowPrice: 297.7, + originalName: 'allSpecifications', + name: 'allSpecifications', + specifications: [ + { + originalName: 'sellerId', + name: 'sellerId', + values: ['1'], }, - }, - specificationGroups: [ + ], + }, + ], + skuSpecifications: [], + productClusters: [], + clusterHighlights: [], + properties: [ + { + name: 'sellerId', + originalName: 'sellerId', + values: ['1'], + }, + ], + items: [ + { + sellers: [ { - originalName: 'allSpecifications', - name: 'allSpecifications', - specifications: [ - { - originalName: 'sellerId', - name: 'sellerId', - values: ['1'], - }, - ], + sellerId: '1', + sellerName: 'VTEX', + addToCartLink: '', + sellerDefault: true, + commertialOffer: { + DeliverySlaSamplesPerRegion: {}, + DeliverySlaSamples: [], + AvailableQuantity: 10000, + discountHighlights: [], + Installments: [ + { + Value: 200.64, + InterestRate: 0, + TotalValuePlusInterestRate: 200.64, + NumberOfInstallments: 1, + Name: 'Boleto Banc�rio � vista', + PaymentSystemName: 'Boleto Banc�rio', + }, + { + Value: 200.64, + InterestRate: 0, + TotalValuePlusInterestRate: 200.64, + NumberOfInstallments: 1, + Name: 'Free � vista', + PaymentSystemName: 'Free', + }, + ], + Price: 200.64, + ListPrice: 297.7, + spotPrice: 200.64, + taxPercentage: 0, + PriceWithoutDiscount: 200.64, + Tax: 0, + GiftSkuIds: [], + BuyTogether: [], + ItemMetadataAttachment: [], + RewardValue: 0, + PriceValidUntil: '2023-04-07T14:08:58Z', + GetInfoErrorMessage: null, + CacheVersionUsedToCallCheckout: '', + teasers: [], + }, }, ], - skuSpecifications: [], - productClusters: [], - clusterHighlights: [], - properties: [ + images: [ { - name: 'sellerId', - originalName: 'sellerId', - values: ['1'], + imageId: '186495', + cacheId: '186495', + imageTag: '', + imageLabel: 'et', + imageText: 'et', + imageUrl: + 'https://storeframework.vtexassets.com/arquivos/ids/186495/corporis.jpg?v=637755567185370000', + }, + { + imageId: '186492', + cacheId: '186492', + imageTag: '', + imageLabel: 'in', + imageText: 'in', + imageUrl: + 'https://storeframework.vtexassets.com/arquivos/ids/186492/qui.jpg?v=637755567174570000', + }, + { + imageId: '186493', + cacheId: '186493', + imageTag: '', + imageLabel: 'consectetur', + imageText: 'consectetur', + imageUrl: + 'https://storeframework.vtexassets.com/arquivos/ids/186493/possimus.jpg?v=637755567178470000', + }, + { + imageId: '186494', + cacheId: '186494', + imageTag: '', + imageLabel: 'ea', + imageText: 'ea', + imageUrl: + 'https://storeframework.vtexassets.com/arquivos/ids/186494/nihil.jpg?v=637755567181900000', }, ], - items: [ + itemId: '64953394', + name: 'fuchsia', + nameComplete: 'Unbranded Concrete Table Small fuchsia', + complementName: + 'Repellendus ipsum suscipit. Tempore consectetur illo dicta ducimus qui ut tempore. Consequatur non laboriosam aut deleniti doloribus nostrum ab et. Odio molestias hic dolor sunt ipsam non. Blanditiis rerum aut dolorum ratione eveniet voluptatibus. Laborum incidunt velit est est laudantium eos.', + referenceId: [ { - sellers: [ - { - sellerId: '1', - sellerName: 'VTEX', - addToCartLink: '', - sellerDefault: true, - commertialOffer: { - DeliverySlaSamplesPerRegion: {}, - DeliverySlaSamples: [], - AvailableQuantity: 10000, - discountHighlights: [], - Installments: [ - { - Value: 200.64, - InterestRate: 0, - TotalValuePlusInterestRate: 200.64, - NumberOfInstallments: 1, - Name: 'Boleto Banc�rio � vista', - PaymentSystemName: 'Boleto Banc�rio', - }, - { - Value: 200.64, - InterestRate: 0, - TotalValuePlusInterestRate: 200.64, - NumberOfInstallments: 1, - Name: 'Free � vista', - PaymentSystemName: 'Free', - }, - ], - Price: 200.64, - ListPrice: 297.7, - spotPrice: 200.64, - taxPercentage: 0, - PriceWithoutDiscount: 200.64, - Tax: 0, - GiftSkuIds: [], - BuyTogether: [], - ItemMetadataAttachment: [], - RewardValue: 0, - PriceValidUntil: '2023-04-07T14:08:58Z', - GetInfoErrorMessage: null, - CacheVersionUsedToCallCheckout: '', - teasers: [], - }, - }, - ], - images: [ - { - imageId: '186495', - cacheId: '186495', - imageTag: '', - imageLabel: 'et', - imageText: 'et', - imageUrl: - 'https://storeframework.vtexassets.com/arquivos/ids/186495/corporis.jpg?v=637755567185370000', - }, - { - imageId: '186492', - cacheId: '186492', - imageTag: '', - imageLabel: 'in', - imageText: 'in', - imageUrl: - 'https://storeframework.vtexassets.com/arquivos/ids/186492/qui.jpg?v=637755567174570000', - }, - { - imageId: '186493', - cacheId: '186493', - imageTag: '', - imageLabel: 'consectetur', - imageText: 'consectetur', - imageUrl: - 'https://storeframework.vtexassets.com/arquivos/ids/186493/possimus.jpg?v=637755567178470000', - }, - { - imageId: '186494', - cacheId: '186494', - imageTag: '', - imageLabel: 'ea', - imageText: 'ea', - imageUrl: - 'https://storeframework.vtexassets.com/arquivos/ids/186494/nihil.jpg?v=637755567181900000', - }, - ], - itemId: '64953394', - name: 'fuchsia', - nameComplete: 'Unbranded Concrete Table Small fuchsia', - complementName: - 'Repellendus ipsum suscipit. Tempore consectetur illo dicta ducimus qui ut tempore. Consequatur non laboriosam aut deleniti doloribus nostrum ab et. Odio molestias hic dolor sunt ipsam non. Blanditiis rerum aut dolorum ratione eveniet voluptatibus. Laborum incidunt velit est est laudantium eos.', - referenceId: [ - { - Key: 'RefId', - Value: '1346198062637', - }, - ], - measurementUnit: 'un', - unitMultiplier: 1, - variations: [], - ean: '', - modalType: '', - videos: [], - attachments: [], - isKit: false, - attributes: [], + Key: 'RefId', + Value: '1346198062637', }, ], - origin: 'intelligent-search', + measurementUnit: 'un', + unitMultiplier: 1, + variations: [], + ean: '', + modalType: '', + videos: [], + attachments: [], + isKit: false, + attributes: [], }, ], - recordsFiltered: 1, - correction: { - misspelled: true, - }, - fuzzy: '0', - operator: 'and', - translated: false, - pagination: { - count: 1, - current: { - index: 1, - proxyUrl: - 'search/trade-policy/1?page=1&count=1&query=sku:64953394&sort=&locale=en-US&hide-unavailable-items=false&operator=and&fuzzy=0', - }, - before: [], - after: [], - perPage: 1, - next: { - index: 0, - }, - previous: { - index: 0, - }, - first: { - index: 0, - }, - last: { - index: 0, - }, - }, + origin: 'intelligent-search', }, } diff --git a/packages/api/test/mocks/RedirectQuery.ts b/packages/api/test/mocks/RedirectQuery.ts index 41c3828e95..6a269aa16d 100644 --- a/packages/api/test/mocks/RedirectQuery.ts +++ b/packages/api/test/mocks/RedirectQuery.ts @@ -6,7 +6,7 @@ export const RedirectQueryTermTech = `query RedirectSearch { ` export const redirectTermTechFetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=2&count=1&query=tech&sort=&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false&allowRedirect=true', + info: 'https://storeframework.vtexcommercestable.com.br/api/intelligent-search/v1/product-search/?from=1&to=1&query=tech&sc=1&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false&allowRedirect=true', init: { headers: { 'X-FORWARDED-HOST': '', 'content-type': 'application/json' }, }, diff --git a/packages/api/test/mocks/SearchQuery.ts b/packages/api/test/mocks/SearchQuery.ts index a135c4a2ea..54aa6942ea 100644 --- a/packages/api/test/mocks/SearchQuery.ts +++ b/packages/api/test/mocks/SearchQuery.ts @@ -101,7 +101,7 @@ export const SearchQueryFirst5Products = `query SearchQuery { }` export const productSearchCategory1Fetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/category-1/office/trade-policy/1?page=1&count=5&query=&sort=&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false&allowRedirect=false', + info: 'https://storeframework.vtexcommercestable.com.br/api/intelligent-search/v1/product-search/category-1/office/?from=0&to=4&sc=1&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false&allowRedirect=false', init: { headers: { 'X-FORWARDED-HOST': '', 'content-type': 'application/json' }, }, @@ -1397,7 +1397,7 @@ export const productSearchCategory1Fetch = { } export const attributeSearchCategory1Fetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/facets/category-1/office/trade-policy/1?page=1&count=5&query=&sort=&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false&allowRedirect=false', + info: 'https://storeframework.vtexcommercestable.com.br/api/intelligent-search/v1/facets/category-1/office/?from=0&to=4&sc=1&locale=en-US&hideUnavailableItems=false&simulationBehavior=skip&showSponsored=false&allowRedirect=false', init: { headers: { 'X-FORWARDED-HOST': '', 'content-type': 'application/json' }, }, diff --git a/packages/api/test/mocks/ValidateCartMutation.ts b/packages/api/test/mocks/ValidateCartMutation.ts index 00bacdd8a9..e5620945fb 100644 --- a/packages/api/test/mocks/ValidateCartMutation.ts +++ b/packages/api/test/mocks/ValidateCartMutation.ts @@ -269,7 +269,7 @@ export const checkoutOrderFormValidFetch = { }, options: { storeCookies: expect.any(Function) }, result: JSON.parse( - '{"orderFormId":"edbe3b03c8c94827a37ec5a6a4648fd2","salesChannel":"1","loggedIn":false,"isCheckedIn":false,"storeId":null,"checkedInPickupPointId":null,"allowManualPrice":false,"canEditData":true,"userProfileId":null,"userType":null,"ignoreProfileData":false,"value":203006,"messages":[],"items":[{"uniqueId":"A7E3AD875A0543F4BF52BA9F70CE34FE","id":"18643698","productId":"55127871","productRefId":"1056559252082","refId":"0969910297117","ean":null,"name":"Tasty Granite Towels Tasty silver","skuName":"silver","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:51:11Z","tax":0,"price":4424,"listPrice":6914,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":4424,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Nike","brandId":"2000006","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9282/9295/","productCategories":{"9282":"Office","9295":"Desks"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/182417-55-55/aut.jpg?v=637755531474870000","detailUrl":"/tasty-granite-towels-tasty/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":4424,"total":4424,"sellingPrices":[{"value":4424,"quantity":1}]}},{"uniqueId":"612DEBE9BC834445A33D60F5BAF40AB3","id":"97907082","productId":"42751008","productRefId":"8528464810736","refId":"4454274563902","ean":null,"name":"Licensed Frozen Sausages ivory","skuName":"ivory","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:47:55Z","tax":0,"price":53154,"listPrice":76406,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":53154,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"iRobot","brandId":"2000003","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9282/9295/","productCategories":{"9282":"Office","9295":"Desks"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/166870-55-55/sit.jpg?v=637753013266530000","detailUrl":"/licensed-frozen-sausages/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":53154,"total":53154,"sellingPrices":[{"value":53154,"quantity":1}]}},{"uniqueId":"BF69B3E6BD724181B716350A53BCF4D0","id":"64953394","productId":"29913569","productRefId":"4715709796003","refId":"1346198062637","ean":null,"name":"Unbranded Concrete Table Small fuchsia","skuName":"fuchsia","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:47:55Z","tax":0,"price":20064,"listPrice":29770,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":20064,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Brand","brandId":"9280","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9282/9295/","productCategories":{"9282":"Office","9295":"Desks"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/186495-55-55/corporis.jpg?v=637755567185370000","detailUrl":"/unbranded-concrete-table-small/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":20064,"total":20064,"sellingPrices":[{"value":20064,"quantity":1}]}},{"uniqueId":"412B5DD9B47A4986848C55FB8CACBBD7","id":"85095548","productId":"32789477","productRefId":"4785818703589","refId":"8718443313149","ean":null,"name":"Small Concrete Tuna Incredible lime","skuName":"lime","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:47:55Z","tax":0,"price":65086,"listPrice":96830,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":65086,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Brand","brandId":"9280","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9282/9296/","productCategories":{"9282":"Office","9296":"Chairs"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/178893-55-55/nam.jpg?v=637755498809500000","detailUrl":"/small-concrete-tuna-incredible/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":65086,"total":65086,"sellingPrices":[{"value":65086,"quantity":1}]}},{"uniqueId":"87392CF7DC3F40B9BBD3750F532D78FC","id":"1191988","productId":"84484858","productRefId":"8905160026336","refId":"5987012953010","ean":null,"name":"Fantastic Soft Bacon fuchsia","skuName":"fuchsia","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:47:55Z","tax":0,"price":60278,"listPrice":83497,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":60278,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Brand","brandId":"9280","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9286/9292/","productCategories":{"9286":"Computer and Software","9292":"Gadgets"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/177382-55-55/assumenda.jpg?v=637753139967300000","detailUrl":"/fantastic-soft-bacon/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":60278,"total":60278,"sellingPrices":[{"value":60278,"quantity":1}]}}],"selectableGifts":[],"totalizers":[{"id":"Items","name":"Items Total","value":203006}],"shippingData":{"address":null,"logisticsInfo":[{"itemIndex":0,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"18643698","deliveryChannels":[{"id":"delivery"}]},{"itemIndex":1,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"97907082","deliveryChannels":[{"id":"delivery"}]},{"itemIndex":2,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"64953394","deliveryChannels":[{"id":"delivery"}]},{"itemIndex":3,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"85095548","deliveryChannels":[{"id":"delivery"}]},{"itemIndex":4,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"1191988","deliveryChannels":[{"id":"delivery"}]}],"selectedAddresses":[],"availableAddresses":[],"pickupPoints":[]},"clientProfileData":null,"paymentData":{"updateStatus":"updated","installmentOptions":[{"paymentSystem":"6","bin":null,"paymentName":null,"paymentGroupName":null,"value":203006,"installments":[{"count":1,"hasInterestRate":false,"interestRate":0,"value":203006,"total":203006,"sellerMerchantInstallments":[{"id":"STOREFRAMEWORK","count":1,"hasInterestRate":false,"interestRate":0,"value":203006,"total":203006}]}]},{"paymentSystem":"201","bin":null,"paymentName":null,"paymentGroupName":null,"value":203006,"installments":[{"count":1,"hasInterestRate":false,"interestRate":0,"value":203006,"total":203006,"sellerMerchantInstallments":[{"id":"STOREFRAMEWORK","count":1,"hasInterestRate":false,"interestRate":0,"value":203006,"total":203006}]}]}],"paymentSystems":[{"id":6,"name":"Boleto Bancário","groupName":"bankInvoicePaymentGroup","validator":{"regex":null,"mask":null,"cardCodeRegex":null,"cardCodeMask":null,"weights":null,"useCvv":false,"useExpirationDate":false,"useCardHolderName":false,"useBillingAddress":false},"stringId":"6","template":"bankInvoicePaymentGroup-template","requiresDocument":false,"isCustom":false,"description":null,"requiresAuthentication":false,"dueDate":"2022-04-05T14:40:00.2598674Z","availablePayments":null},{"id":201,"name":"Free","groupName":"custom201PaymentGroupPaymentGroup","validator":{"regex":null,"mask":null,"cardCodeRegex":null,"cardCodeMask":null,"weights":null,"useCvv":false,"useExpirationDate":false,"useCardHolderName":false,"useBillingAddress":false},"stringId":"201","template":"custom201PaymentGroupPaymentGroup-template","requiresDocument":false,"isCustom":true,"description":"Free pay to test checkout payments","requiresAuthentication":false,"dueDate":"2022-04-05T14:40:00.2598674Z","availablePayments":null}],"payments":[],"giftCards":[],"giftCardMessages":[],"availableAccounts":[],"availableTokens":[],"availableAssociations":{}},"marketingData":null,"sellers":[{"id":"1","name":"VTEX","logo":""}],"clientPreferencesData":{"locale":"en-US","optinNewsLetter":null},"commercialConditionData":null,"storePreferencesData":{"countryCode":"USA","saveUserData":true,"timeZone":"Central Standard Time","currencyCode":"USD","currencyLocale":1033,"currencySymbol":"$","currencyFormatInfo":{"currencyDecimalDigits":2,"currencyDecimalSeparator":".","currencyGroupSeparator":",","currencyGroupSize":3,"startsWithCurrencySymbol":true}},"giftRegistryData":null,"openTextField":null,"invoiceData":null,"customData":{"customApps":[{"fields":{"cartEtag":"a0477e6aabd5fb29ac0398b3f6b8d0b3"},"id":"faststore","major":1}]},"itemMetadata":{"items":[{"id":"18643698","seller":"1","name":"Tasty Granite Towels Tasty silver","skuName":"silver","productId":"55127871","refId":"0969910297117","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/182417-55-55/aut.jpg?v=637755531474870000","detailUrl":"/tasty-granite-towels-tasty/p","assemblyOptions":[]},{"id":"97907082","seller":"1","name":"Licensed Frozen Sausages ivory","skuName":"ivory","productId":"42751008","refId":"4454274563902","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/166870-55-55/sit.jpg?v=637753013266530000","detailUrl":"/licensed-frozen-sausages/p","assemblyOptions":[]},{"id":"64953394","seller":"1","name":"Unbranded Concrete Table Small fuchsia","skuName":"fuchsia","productId":"29913569","refId":"1346198062637","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/186495-55-55/corporis.jpg?v=637755567185370000","detailUrl":"/unbranded-concrete-table-small/p","assemblyOptions":[]},{"id":"85095548","seller":"1","name":"Small Concrete Tuna Incredible lime","skuName":"lime","productId":"32789477","refId":"8718443313149","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/178893-55-55/nam.jpg?v=637755498809500000","detailUrl":"/small-concrete-tuna-incredible/p","assemblyOptions":[]},{"id":"1191988","seller":"1","name":"Fantastic Soft Bacon fuchsia","skuName":"fuchsia","productId":"84484858","refId":"5987012953010","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/177382-55-55/assumenda.jpg?v=637753139967300000","detailUrl":"/fantastic-soft-bacon/p","assemblyOptions":[]}]},"hooksData":null,"ratesAndBenefitsData":{"rateAndBenefitsIdentifiers":[],"teaser":[]},"subscriptionData":null,"itemsOrdination":null}' + '{"orderFormId":"edbe3b03c8c94827a37ec5a6a4648fd2","salesChannel":"1","loggedIn":false,"isCheckedIn":false,"storeId":null,"checkedInPickupPointId":null,"allowManualPrice":false,"canEditData":true,"userProfileId":null,"userType":null,"ignoreProfileData":false,"value":203006,"messages":[],"items":[{"uniqueId":"A7E3AD875A0543F4BF52BA9F70CE34FE","id":"18643698","productId":"55127871","productRefId":"1056559252082","refId":"0969910297117","ean":null,"name":"Tasty Granite Towels Tasty silver","skuName":"silver","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:51:11Z","tax":0,"price":4424,"listPrice":6914,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":4424,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Nike","brandId":"2000006","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9282/9295/","productCategories":{"9282":"Office","9295":"Desks"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/182417-55-55/aut.jpg?v=637755531474870000","detailUrl":"/tasty-granite-towels-tasty/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":4424,"total":4424,"sellingPrices":[{"value":4424,"quantity":1}]}},{"uniqueId":"612DEBE9BC834445A33D60F5BAF40AB3","id":"97907082","productId":"42751008","productRefId":"8528464810736","refId":"4454274563902","ean":null,"name":"Licensed Frozen Sausages ivory","skuName":"ivory","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:47:55Z","tax":0,"price":53154,"listPrice":76406,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":53154,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"iRobot","brandId":"2000003","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9282/9295/","productCategories":{"9282":"Office","9295":"Desks"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/166870-55-55/sit.jpg?v=637753013266530000","detailUrl":"/licensed-frozen-sausages/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":53154,"total":53154,"sellingPrices":[{"value":53154,"quantity":1}]}},{"uniqueId":"BF69B3E6BD724181B716350A53BCF4D0","id":"64953394","productId":"29913569","productRefId":"4715709796003","refId":"1346198062637","ean":null,"name":"Unbranded Concrete Table Small fuchsia","skuName":"fuchsia","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:47:55Z","tax":0,"price":20064,"listPrice":29770,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":20064,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Brand","brandId":"9280","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9282/9295/","productCategories":{"9282":"Office","9295":"Desks"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/186495-55-55/corporis.jpg?v=637755567185370000","detailUrl":"/unbranded-concrete-table-small/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":20064,"total":20064,"sellingPrices":[{"value":20064,"quantity":1}]}},{"uniqueId":"412B5DD9B47A4986848C55FB8CACBBD7","id":"85095548","productId":"32789477","productRefId":"4785818703589","refId":"8718443313149","ean":null,"name":"Small Concrete Tuna Incredible lime","skuName":"lime","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:47:55Z","tax":0,"price":65086,"listPrice":96830,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":65086,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Brand","brandId":"9280","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9282/9296/","productCategories":{"9282":"Office","9296":"Chairs"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/178893-55-55/nam.jpg?v=637755498809500000","detailUrl":"/small-concrete-tuna-incredible/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":65086,"total":65086,"sellingPrices":[{"value":65086,"quantity":1}]}},{"uniqueId":"87392CF7DC3F40B9BBD3750F532D78FC","id":"1191988","productId":"84484858","productRefId":"8905160026336","refId":"5987012953010","ean":null,"name":"Fantastic Soft Bacon fuchsia","skuName":"fuchsia","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:47:55Z","tax":0,"price":60278,"listPrice":83497,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":60278,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Brand","brandId":"9280","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9286/9292/","productCategories":{"9286":"Computer and Software","9292":"Gadgets"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/177382-55-55/assumenda.jpg?v=637753139967300000","detailUrl":"/fantastic-soft-bacon/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":60278,"total":60278,"sellingPrices":[{"value":60278,"quantity":1}]}}],"selectableGifts":[],"totalizers":[{"id":"Items","name":"Items Total","value":203006}],"shippingData":{"address":null,"logisticsInfo":[{"itemIndex":0,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"18643698","deliveryChannels":[{"id":"delivery"}]},{"itemIndex":1,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"97907082","deliveryChannels":[{"id":"delivery"}]},{"itemIndex":2,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"64953394","deliveryChannels":[{"id":"delivery"}]},{"itemIndex":3,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"85095548","deliveryChannels":[{"id":"delivery"}]},{"itemIndex":4,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"1191988","deliveryChannels":[{"id":"delivery"}]}],"selectedAddresses":[],"availableAddresses":[],"pickupPoints":[]},"clientProfileData":null,"paymentData":{"updateStatus":"updated","installmentOptions":[{"paymentSystem":"6","bin":null,"paymentName":null,"paymentGroupName":null,"value":203006,"installments":[{"count":1,"hasInterestRate":false,"interestRate":0,"value":203006,"total":203006,"sellerMerchantInstallments":[{"id":"STOREFRAMEWORK","count":1,"hasInterestRate":false,"interestRate":0,"value":203006,"total":203006}]}]},{"paymentSystem":"201","bin":null,"paymentName":null,"paymentGroupName":null,"value":203006,"installments":[{"count":1,"hasInterestRate":false,"interestRate":0,"value":203006,"total":203006,"sellerMerchantInstallments":[{"id":"STOREFRAMEWORK","count":1,"hasInterestRate":false,"interestRate":0,"value":203006,"total":203006}]}]}],"paymentSystems":[{"id":6,"name":"Boleto Bancário","groupName":"bankInvoicePaymentGroup","validator":{"regex":null,"mask":null,"cardCodeRegex":null,"cardCodeMask":null,"weights":null,"useCvv":false,"useExpirationDate":false,"useCardHolderName":false,"useBillingAddress":false},"stringId":"6","template":"bankInvoicePaymentGroup-template","requiresDocument":false,"isCustom":false,"description":null,"requiresAuthentication":false,"dueDate":"2022-04-05T14:40:00.2598674Z","availablePayments":null},{"id":201,"name":"Free","groupName":"custom201PaymentGroupPaymentGroup","validator":{"regex":null,"mask":null,"cardCodeRegex":null,"cardCodeMask":null,"weights":null,"useCvv":false,"useExpirationDate":false,"useCardHolderName":false,"useBillingAddress":false},"stringId":"201","template":"custom201PaymentGroupPaymentGroup-template","requiresDocument":false,"isCustom":true,"description":"Free pay to test checkout payments","requiresAuthentication":false,"dueDate":"2022-04-05T14:40:00.2598674Z","availablePayments":null}],"payments":[],"giftCards":[],"giftCardMessages":[],"availableAccounts":[],"availableTokens":[],"availableAssociations":{}},"marketingData":null,"sellers":[{"id":"1","name":"VTEX","logo":""}],"clientPreferencesData":{"locale":"en-US","optinNewsLetter":null},"commercialConditionData":null,"storePreferencesData":{"countryCode":"USA","saveUserData":true,"timeZone":"Central Standard Time","currencyCode":"USD","currencyLocale":1033,"currencySymbol":"$","currencyFormatInfo":{"currencyDecimalDigits":2,"currencyDecimalSeparator":".","currencyGroupSeparator":",","currencyGroupSize":3,"startsWithCurrencySymbol":true}},"giftRegistryData":null,"openTextField":null,"invoiceData":null,"customData":{"customApps":[{"fields":{"cartEtag":"afb4f496d995ee4a2af3c66f8b39a7b8"},"id":"faststore","major":1}]},"itemMetadata":{"items":[{"id":"18643698","seller":"1","name":"Tasty Granite Towels Tasty silver","skuName":"silver","productId":"55127871","refId":"0969910297117","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/182417-55-55/aut.jpg?v=637755531474870000","detailUrl":"/tasty-granite-towels-tasty/p","assemblyOptions":[]},{"id":"97907082","seller":"1","name":"Licensed Frozen Sausages ivory","skuName":"ivory","productId":"42751008","refId":"4454274563902","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/166870-55-55/sit.jpg?v=637753013266530000","detailUrl":"/licensed-frozen-sausages/p","assemblyOptions":[]},{"id":"64953394","seller":"1","name":"Unbranded Concrete Table Small fuchsia","skuName":"fuchsia","productId":"29913569","refId":"1346198062637","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/186495-55-55/corporis.jpg?v=637755567185370000","detailUrl":"/unbranded-concrete-table-small/p","assemblyOptions":[]},{"id":"85095548","seller":"1","name":"Small Concrete Tuna Incredible lime","skuName":"lime","productId":"32789477","refId":"8718443313149","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/178893-55-55/nam.jpg?v=637755498809500000","detailUrl":"/small-concrete-tuna-incredible/p","assemblyOptions":[]},{"id":"1191988","seller":"1","name":"Fantastic Soft Bacon fuchsia","skuName":"fuchsia","productId":"84484858","refId":"5987012953010","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/177382-55-55/assumenda.jpg?v=637753139967300000","detailUrl":"/fantastic-soft-bacon/p","assemblyOptions":[]}]},"hooksData":null,"ratesAndBenefitsData":{"rateAndBenefitsIdentifiers":[],"teaser":[]},"subscriptionData":null,"itemsOrdination":null}' ), } @@ -278,11 +278,11 @@ export const checkoutOrderFormCustomDataValidFetch = { init: { method: 'PUT', headers: { 'content-type': 'application/json', 'X-FORWARDED-HOST': '' }, - body: '{"value":"a0477e6aabd5fb29ac0398b3f6b8d0b3"}', + body: '{"value":"afb4f496d995ee4a2af3c66f8b39a7b8"}', }, options: undefined, result: JSON.parse( - '{"orderFormId":"edbe3b03c8c94827a37ec5a6a4648fd2","salesChannel":"1","loggedIn":false,"isCheckedIn":false,"storeId":null,"checkedInPickupPointId":null,"allowManualPrice":false,"canEditData":true,"userProfileId":null,"userType":null,"ignoreProfileData":false,"value":203006,"messages":[],"items":[{"uniqueId":"A7E3AD875A0543F4BF52BA9F70CE34FE","id":"18643698","productId":"55127871","productRefId":"1056559252082","refId":"0969910297117","ean":null,"name":"Tasty Granite Towels Tasty silver","skuName":"silver","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:51:11Z","tax":0,"price":4424,"listPrice":6914,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":4424,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Nike","brandId":"2000006","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9282/9295/","productCategories":{"9282":"Office","9295":"Desks"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/182417-55-55/aut.jpg?v=637755531474870000","detailUrl":"/tasty-granite-towels-tasty/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":4424,"total":4424,"sellingPrices":[{"value":4424,"quantity":1}]}},{"uniqueId":"612DEBE9BC834445A33D60F5BAF40AB3","id":"97907082","productId":"42751008","productRefId":"8528464810736","refId":"4454274563902","ean":null,"name":"Licensed Frozen Sausages ivory","skuName":"ivory","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:47:55Z","tax":0,"price":53154,"listPrice":76406,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":53154,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"iRobot","brandId":"2000003","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9282/9295/","productCategories":{"9282":"Office","9295":"Desks"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/166870-55-55/sit.jpg?v=637753013266530000","detailUrl":"/licensed-frozen-sausages/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":53154,"total":53154,"sellingPrices":[{"value":53154,"quantity":1}]}},{"uniqueId":"BF69B3E6BD724181B716350A53BCF4D0","id":"64953394","productId":"29913569","productRefId":"4715709796003","refId":"1346198062637","ean":null,"name":"Unbranded Concrete Table Small fuchsia","skuName":"fuchsia","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:47:55Z","tax":0,"price":20064,"listPrice":29770,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":20064,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Brand","brandId":"9280","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9282/9295/","productCategories":{"9282":"Office","9295":"Desks"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/186495-55-55/corporis.jpg?v=637755567185370000","detailUrl":"/unbranded-concrete-table-small/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":20064,"total":20064,"sellingPrices":[{"value":20064,"quantity":1}]}},{"uniqueId":"412B5DD9B47A4986848C55FB8CACBBD7","id":"85095548","productId":"32789477","productRefId":"4785818703589","refId":"8718443313149","ean":null,"name":"Small Concrete Tuna Incredible lime","skuName":"lime","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:47:55Z","tax":0,"price":65086,"listPrice":96830,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":65086,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Brand","brandId":"9280","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9282/9296/","productCategories":{"9282":"Office","9296":"Chairs"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/178893-55-55/nam.jpg?v=637755498809500000","detailUrl":"/small-concrete-tuna-incredible/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":65086,"total":65086,"sellingPrices":[{"value":65086,"quantity":1}]}},{"uniqueId":"87392CF7DC3F40B9BBD3750F532D78FC","id":"1191988","productId":"84484858","productRefId":"8905160026336","refId":"5987012953010","ean":null,"name":"Fantastic Soft Bacon fuchsia","skuName":"fuchsia","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:47:55Z","tax":0,"price":60278,"listPrice":83497,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":60278,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Brand","brandId":"9280","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9286/9292/","productCategories":{"9286":"Computer and Software","9292":"Gadgets"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/177382-55-55/assumenda.jpg?v=637753139967300000","detailUrl":"/fantastic-soft-bacon/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":60278,"total":60278,"sellingPrices":[{"value":60278,"quantity":1}]}}],"selectableGifts":[],"totalizers":[{"id":"Items","name":"Items Total","value":203006}],"shippingData":{"address":null,"logisticsInfo":[{"itemIndex":0,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"18643698","deliveryChannels":[{"id":"delivery"}]},{"itemIndex":1,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"97907082","deliveryChannels":[{"id":"delivery"}]},{"itemIndex":2,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"64953394","deliveryChannels":[{"id":"delivery"}]},{"itemIndex":3,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"85095548","deliveryChannels":[{"id":"delivery"}]},{"itemIndex":4,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"1191988","deliveryChannels":[{"id":"delivery"}]}],"selectedAddresses":[],"availableAddresses":[],"pickupPoints":[]},"clientProfileData":null,"paymentData":{"updateStatus":"updated","installmentOptions":[{"paymentSystem":"6","bin":null,"paymentName":null,"paymentGroupName":null,"value":203006,"installments":[{"count":1,"hasInterestRate":false,"interestRate":0,"value":203006,"total":203006,"sellerMerchantInstallments":[{"id":"STOREFRAMEWORK","count":1,"hasInterestRate":false,"interestRate":0,"value":203006,"total":203006}]}]},{"paymentSystem":"201","bin":null,"paymentName":null,"paymentGroupName":null,"value":203006,"installments":[{"count":1,"hasInterestRate":false,"interestRate":0,"value":203006,"total":203006,"sellerMerchantInstallments":[{"id":"STOREFRAMEWORK","count":1,"hasInterestRate":false,"interestRate":0,"value":203006,"total":203006}]}]}],"paymentSystems":[{"id":6,"name":"Boleto Bancário","groupName":"bankInvoicePaymentGroup","validator":{"regex":null,"mask":null,"cardCodeRegex":null,"cardCodeMask":null,"weights":null,"useCvv":false,"useExpirationDate":false,"useCardHolderName":false,"useBillingAddress":false},"stringId":"6","template":"bankInvoicePaymentGroup-template","requiresDocument":false,"isCustom":false,"description":null,"requiresAuthentication":false,"dueDate":"2022-04-05T14:40:00.2598674Z","availablePayments":null},{"id":201,"name":"Free","groupName":"custom201PaymentGroupPaymentGroup","validator":{"regex":null,"mask":null,"cardCodeRegex":null,"cardCodeMask":null,"weights":null,"useCvv":false,"useExpirationDate":false,"useCardHolderName":false,"useBillingAddress":false},"stringId":"201","template":"custom201PaymentGroupPaymentGroup-template","requiresDocument":false,"isCustom":true,"description":"Free pay to test checkout payments","requiresAuthentication":false,"dueDate":"2022-04-05T14:40:00.2598674Z","availablePayments":null}],"payments":[],"giftCards":[],"giftCardMessages":[],"availableAccounts":[],"availableTokens":[],"availableAssociations":{}},"marketingData":null,"sellers":[{"id":"1","name":"VTEX","logo":""}],"clientPreferencesData":{"locale":"en-US","optinNewsLetter":null},"commercialConditionData":null,"storePreferencesData":{"countryCode":"USA","saveUserData":true,"timeZone":"Central Standard Time","currencyCode":"USD","currencyLocale":1033,"currencySymbol":"$","currencyFormatInfo":{"currencyDecimalDigits":2,"currencyDecimalSeparator":".","currencyGroupSeparator":",","currencyGroupSize":3,"startsWithCurrencySymbol":true}},"giftRegistryData":null,"openTextField":null,"invoiceData":null,"customData":{"customApps":[{"fields":{"cartEtag":"a0477e6aabd5fb29ac0398b3f6b8d0b3"},"id":"faststore","major":1}]},"itemMetadata":{"items":[{"id":"18643698","seller":"1","name":"Tasty Granite Towels Tasty silver","skuName":"silver","productId":"55127871","refId":"0969910297117","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/182417-55-55/aut.jpg?v=637755531474870000","detailUrl":"/tasty-granite-towels-tasty/p","assemblyOptions":[]},{"id":"97907082","seller":"1","name":"Licensed Frozen Sausages ivory","skuName":"ivory","productId":"42751008","refId":"4454274563902","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/166870-55-55/sit.jpg?v=637753013266530000","detailUrl":"/licensed-frozen-sausages/p","assemblyOptions":[]},{"id":"64953394","seller":"1","name":"Unbranded Concrete Table Small fuchsia","skuName":"fuchsia","productId":"29913569","refId":"1346198062637","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/186495-55-55/corporis.jpg?v=637755567185370000","detailUrl":"/unbranded-concrete-table-small/p","assemblyOptions":[]},{"id":"85095548","seller":"1","name":"Small Concrete Tuna Incredible lime","skuName":"lime","productId":"32789477","refId":"8718443313149","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/178893-55-55/nam.jpg?v=637755498809500000","detailUrl":"/small-concrete-tuna-incredible/p","assemblyOptions":[]},{"id":"1191988","seller":"1","name":"Fantastic Soft Bacon fuchsia","skuName":"fuchsia","productId":"84484858","refId":"5987012953010","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/177382-55-55/assumenda.jpg?v=637753139967300000","detailUrl":"/fantastic-soft-bacon/p","assemblyOptions":[]}]},"hooksData":null,"ratesAndBenefitsData":{"rateAndBenefitsIdentifiers":[],"teaser":[]},"subscriptionData":null,"itemsOrdination":null}' + '{"orderFormId":"edbe3b03c8c94827a37ec5a6a4648fd2","salesChannel":"1","loggedIn":false,"isCheckedIn":false,"storeId":null,"checkedInPickupPointId":null,"allowManualPrice":false,"canEditData":true,"userProfileId":null,"userType":null,"ignoreProfileData":false,"value":203006,"messages":[],"items":[{"uniqueId":"A7E3AD875A0543F4BF52BA9F70CE34FE","id":"18643698","productId":"55127871","productRefId":"1056559252082","refId":"0969910297117","ean":null,"name":"Tasty Granite Towels Tasty silver","skuName":"silver","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:51:11Z","tax":0,"price":4424,"listPrice":6914,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":4424,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Nike","brandId":"2000006","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9282/9295/","productCategories":{"9282":"Office","9295":"Desks"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/182417-55-55/aut.jpg?v=637755531474870000","detailUrl":"/tasty-granite-towels-tasty/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":4424,"total":4424,"sellingPrices":[{"value":4424,"quantity":1}]}},{"uniqueId":"612DEBE9BC834445A33D60F5BAF40AB3","id":"97907082","productId":"42751008","productRefId":"8528464810736","refId":"4454274563902","ean":null,"name":"Licensed Frozen Sausages ivory","skuName":"ivory","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:47:55Z","tax":0,"price":53154,"listPrice":76406,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":53154,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"iRobot","brandId":"2000003","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9282/9295/","productCategories":{"9282":"Office","9295":"Desks"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/166870-55-55/sit.jpg?v=637753013266530000","detailUrl":"/licensed-frozen-sausages/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":53154,"total":53154,"sellingPrices":[{"value":53154,"quantity":1}]}},{"uniqueId":"BF69B3E6BD724181B716350A53BCF4D0","id":"64953394","productId":"29913569","productRefId":"4715709796003","refId":"1346198062637","ean":null,"name":"Unbranded Concrete Table Small fuchsia","skuName":"fuchsia","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:47:55Z","tax":0,"price":20064,"listPrice":29770,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":20064,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Brand","brandId":"9280","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9282/9295/","productCategories":{"9282":"Office","9295":"Desks"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/186495-55-55/corporis.jpg?v=637755567185370000","detailUrl":"/unbranded-concrete-table-small/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":20064,"total":20064,"sellingPrices":[{"value":20064,"quantity":1}]}},{"uniqueId":"412B5DD9B47A4986848C55FB8CACBBD7","id":"85095548","productId":"32789477","productRefId":"4785818703589","refId":"8718443313149","ean":null,"name":"Small Concrete Tuna Incredible lime","skuName":"lime","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:47:55Z","tax":0,"price":65086,"listPrice":96830,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":65086,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Brand","brandId":"9280","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9282/9296/","productCategories":{"9282":"Office","9296":"Chairs"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/178893-55-55/nam.jpg?v=637755498809500000","detailUrl":"/small-concrete-tuna-incredible/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":65086,"total":65086,"sellingPrices":[{"value":65086,"quantity":1}]}},{"uniqueId":"87392CF7DC3F40B9BBD3750F532D78FC","id":"1191988","productId":"84484858","productRefId":"8905160026336","refId":"5987012953010","ean":null,"name":"Fantastic Soft Bacon fuchsia","skuName":"fuchsia","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:47:55Z","tax":0,"price":60278,"listPrice":83497,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":60278,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Brand","brandId":"9280","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9286/9292/","productCategories":{"9286":"Computer and Software","9292":"Gadgets"},"quantity":1,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/177382-55-55/assumenda.jpg?v=637753139967300000","detailUrl":"/fantastic-soft-bacon/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":60278,"total":60278,"sellingPrices":[{"value":60278,"quantity":1}]}}],"selectableGifts":[],"totalizers":[{"id":"Items","name":"Items Total","value":203006}],"shippingData":{"address":null,"logisticsInfo":[{"itemIndex":0,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"18643698","deliveryChannels":[{"id":"delivery"}]},{"itemIndex":1,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"97907082","deliveryChannels":[{"id":"delivery"}]},{"itemIndex":2,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"64953394","deliveryChannels":[{"id":"delivery"}]},{"itemIndex":3,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"85095548","deliveryChannels":[{"id":"delivery"}]},{"itemIndex":4,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"1191988","deliveryChannels":[{"id":"delivery"}]}],"selectedAddresses":[],"availableAddresses":[],"pickupPoints":[]},"clientProfileData":null,"paymentData":{"updateStatus":"updated","installmentOptions":[{"paymentSystem":"6","bin":null,"paymentName":null,"paymentGroupName":null,"value":203006,"installments":[{"count":1,"hasInterestRate":false,"interestRate":0,"value":203006,"total":203006,"sellerMerchantInstallments":[{"id":"STOREFRAMEWORK","count":1,"hasInterestRate":false,"interestRate":0,"value":203006,"total":203006}]}]},{"paymentSystem":"201","bin":null,"paymentName":null,"paymentGroupName":null,"value":203006,"installments":[{"count":1,"hasInterestRate":false,"interestRate":0,"value":203006,"total":203006,"sellerMerchantInstallments":[{"id":"STOREFRAMEWORK","count":1,"hasInterestRate":false,"interestRate":0,"value":203006,"total":203006}]}]}],"paymentSystems":[{"id":6,"name":"Boleto Bancário","groupName":"bankInvoicePaymentGroup","validator":{"regex":null,"mask":null,"cardCodeRegex":null,"cardCodeMask":null,"weights":null,"useCvv":false,"useExpirationDate":false,"useCardHolderName":false,"useBillingAddress":false},"stringId":"6","template":"bankInvoicePaymentGroup-template","requiresDocument":false,"isCustom":false,"description":null,"requiresAuthentication":false,"dueDate":"2022-04-05T14:40:00.2598674Z","availablePayments":null},{"id":201,"name":"Free","groupName":"custom201PaymentGroupPaymentGroup","validator":{"regex":null,"mask":null,"cardCodeRegex":null,"cardCodeMask":null,"weights":null,"useCvv":false,"useExpirationDate":false,"useCardHolderName":false,"useBillingAddress":false},"stringId":"201","template":"custom201PaymentGroupPaymentGroup-template","requiresDocument":false,"isCustom":true,"description":"Free pay to test checkout payments","requiresAuthentication":false,"dueDate":"2022-04-05T14:40:00.2598674Z","availablePayments":null}],"payments":[],"giftCards":[],"giftCardMessages":[],"availableAccounts":[],"availableTokens":[],"availableAssociations":{}},"marketingData":null,"sellers":[{"id":"1","name":"VTEX","logo":""}],"clientPreferencesData":{"locale":"en-US","optinNewsLetter":null},"commercialConditionData":null,"storePreferencesData":{"countryCode":"USA","saveUserData":true,"timeZone":"Central Standard Time","currencyCode":"USD","currencyLocale":1033,"currencySymbol":"$","currencyFormatInfo":{"currencyDecimalDigits":2,"currencyDecimalSeparator":".","currencyGroupSeparator":",","currencyGroupSize":3,"startsWithCurrencySymbol":true}},"giftRegistryData":null,"openTextField":null,"invoiceData":null,"customData":{"customApps":[{"fields":{"cartEtag":"afb4f496d995ee4a2af3c66f8b39a7b8"},"id":"faststore","major":1}]},"itemMetadata":{"items":[{"id":"18643698","seller":"1","name":"Tasty Granite Towels Tasty silver","skuName":"silver","productId":"55127871","refId":"0969910297117","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/182417-55-55/aut.jpg?v=637755531474870000","detailUrl":"/tasty-granite-towels-tasty/p","assemblyOptions":[]},{"id":"97907082","seller":"1","name":"Licensed Frozen Sausages ivory","skuName":"ivory","productId":"42751008","refId":"4454274563902","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/166870-55-55/sit.jpg?v=637753013266530000","detailUrl":"/licensed-frozen-sausages/p","assemblyOptions":[]},{"id":"64953394","seller":"1","name":"Unbranded Concrete Table Small fuchsia","skuName":"fuchsia","productId":"29913569","refId":"1346198062637","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/186495-55-55/corporis.jpg?v=637755567185370000","detailUrl":"/unbranded-concrete-table-small/p","assemblyOptions":[]},{"id":"85095548","seller":"1","name":"Small Concrete Tuna Incredible lime","skuName":"lime","productId":"32789477","refId":"8718443313149","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/178893-55-55/nam.jpg?v=637755498809500000","detailUrl":"/small-concrete-tuna-incredible/p","assemblyOptions":[]},{"id":"1191988","seller":"1","name":"Fantastic Soft Bacon fuchsia","skuName":"fuchsia","productId":"84484858","refId":"5987012953010","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/177382-55-55/assumenda.jpg?v=637753139967300000","detailUrl":"/fantastic-soft-bacon/p","assemblyOptions":[]}]},"hooksData":null,"ratesAndBenefitsData":{"rateAndBenefitsIdentifiers":[],"teaser":[]},"subscriptionData":null,"itemsOrdination":null}' ), } @@ -331,205 +331,188 @@ export const checkoutOrderFormCustomDataInvalidFetch = { } export const productSearchPage1Count1Fetch = { - info: 'https://storeframework.vtexcommercestable.com.br/api/io/_v/api/intelligent-search/product_search/trade-policy/1?page=1&count=1&query=sku%3A2737806&sort=&locale=en-US&show-invisible-items=true&hideUnavailableItems=false&allowRedirect=false', + info: 'https://storeframework.vtexcommercestable.com.br/api/intelligent-search/v1/products?field=sku&value=2737806&sc=1&locale=en-US', init: { headers: { 'content-type': 'application/json', 'X-FORWARDED-HOST': '' }, }, options: undefined, result: { - products: [ + cacheId: 'sp-43559243', + productId: '43559243', + description: 'Iure eum pariatur provident dolorem et.', + productName: 'Fantastic Soft Cheese', + productReference: '6327601885574', + linkText: 'fantastic-soft-cheese', + brand: 'Acer', + brandId: 2000002, + link: '/fantastic-soft-cheese/p', + categories: [ + '/Kitchen & Home Appliances/Appliances/', + '/Kitchen & Home Appliances/', + ], + categoryId: '9294', + categoriesIds: ['/9285/9294/', '/9285/'], + priceRange: { + sellingPrice: { + highPrice: 349.12, + lowPrice: 349.12, + }, + listPrice: { + highPrice: 557.57, + lowPrice: 557.57, + }, + }, + specificationGroups: [ { - cacheId: 'sp-43559243', - productId: '43559243', - description: 'Iure eum pariatur provident dolorem et.', - productName: 'Fantastic Soft Cheese', - productReference: '6327601885574', - linkText: 'fantastic-soft-cheese', - brand: 'Acer', - brandId: 2000002, - link: '/fantastic-soft-cheese/p', - categories: [ - '/Kitchen & Home Appliances/Appliances/', - '/Kitchen & Home Appliances/', + originalName: 'allSpecifications', + name: 'allSpecifications', + specifications: [ + { + originalName: 'sellerId', + name: 'sellerId', + values: ['1'], + }, ], - categoryId: '9294', - categoriesIds: ['/9285/9294/', '/9285/'], - priceRange: { - sellingPrice: { - highPrice: 349.12, - lowPrice: 349.12, + }, + ], + skuSpecifications: [], + productClusters: [], + clusterHighlights: [], + properties: [ + { + name: 'sellerId', + originalName: 'sellerId', + values: ['1'], + }, + ], + items: [ + { + sellers: [ + { + sellerId: '1', + sellerName: 'VTEX', + addToCartLink: '', + sellerDefault: true, + commertialOffer: { + DeliverySlaSamplesPerRegion: {}, + DeliverySlaSamples: [], + AvailableQuantity: 10000, + discountHighlights: [], + Installments: [ + { + Value: 349.12, + InterestRate: 0, + TotalValuePlusInterestRate: 349.12, + NumberOfInstallments: 1, + Name: 'Boleto Banc�rio � vista', + PaymentSystemName: 'Boleto Banc�rio', + }, + { + Value: 349.12, + InterestRate: 0, + TotalValuePlusInterestRate: 349.12, + NumberOfInstallments: 1, + Name: 'Free � vista', + PaymentSystemName: 'Free', + }, + ], + Price: 349.12, + ListPrice: 557.57, + spotPrice: 349.12, + taxPercentage: 0, + PriceWithoutDiscount: 349.12, + Tax: 0, + GiftSkuIds: [], + BuyTogether: [], + ItemMetadataAttachment: [], + RewardValue: 0, + PriceValidUntil: '2023-04-12T17:35:11Z', + GetInfoErrorMessage: null, + CacheVersionUsedToCallCheckout: '', + teasers: [], + }, }, - listPrice: { - highPrice: 557.57, - lowPrice: 557.57, + ], + images: [ + { + imageId: '168396', + cacheId: '168396', + imageTag: '', + imageLabel: 'et', + imageText: 'et', + imageUrl: + 'https://storeframework.vtexassets.com/arquivos/ids/168396/nihil.jpg?v=637753027573130000', }, - }, - specificationGroups: [ { - originalName: 'allSpecifications', - name: 'allSpecifications', - specifications: [ - { - originalName: 'sellerId', - name: 'sellerId', - values: ['1'], - }, - ], + imageId: '168393', + cacheId: '168393', + imageTag: '', + imageLabel: 'similique', + imageText: 'similique', + imageUrl: + 'https://storeframework.vtexassets.com/arquivos/ids/168393/dolore.jpg?v=637753027558270000', }, - ], - skuSpecifications: [], - productClusters: [], - clusterHighlights: [], - properties: [ { - name: 'sellerId', - originalName: 'sellerId', - values: ['1'], + imageId: '168394', + cacheId: '168394', + imageTag: '', + imageLabel: 'deleniti', + imageText: 'deleniti', + imageUrl: + 'https://storeframework.vtexassets.com/arquivos/ids/168394/delectus.jpg?v=637753027564530000', + }, + { + imageId: '168395', + cacheId: '168395', + imageTag: '', + imageLabel: 'sunt', + imageText: 'sunt', + imageUrl: + 'https://storeframework.vtexassets.com/arquivos/ids/168395/qui.jpg?v=637753027568900000', }, ], - items: [ + itemId: '2737806', + name: 'plum', + nameComplete: 'Fantastic Soft Cheese plum', + complementName: + 'Explicabo et quibusdam eius excepturi et rem dolorem et. Eligendi ratione et quod error nisi asperiores fugit omnis itaque. Vel officia sapiente autem non. Ut consequatur veniam perspiciatis doloribus nulla saepe.', + referenceId: [ { - sellers: [ - { - sellerId: '1', - sellerName: 'VTEX', - addToCartLink: '', - sellerDefault: true, - commertialOffer: { - DeliverySlaSamplesPerRegion: {}, - DeliverySlaSamples: [], - AvailableQuantity: 10000, - discountHighlights: [], - Installments: [ - { - Value: 349.12, - InterestRate: 0, - TotalValuePlusInterestRate: 349.12, - NumberOfInstallments: 1, - Name: 'Boleto Banc�rio � vista', - PaymentSystemName: 'Boleto Banc�rio', - }, - { - Value: 349.12, - InterestRate: 0, - TotalValuePlusInterestRate: 349.12, - NumberOfInstallments: 1, - Name: 'Free � vista', - PaymentSystemName: 'Free', - }, - ], - Price: 349.12, - ListPrice: 557.57, - spotPrice: 349.12, - taxPercentage: 0, - PriceWithoutDiscount: 349.12, - Tax: 0, - GiftSkuIds: [], - BuyTogether: [], - ItemMetadataAttachment: [], - RewardValue: 0, - PriceValidUntil: '2023-04-12T17:35:11Z', - GetInfoErrorMessage: null, - CacheVersionUsedToCallCheckout: '', - teasers: [], - }, - }, - ], - images: [ - { - imageId: '168396', - cacheId: '168396', - imageTag: '', - imageLabel: 'et', - imageText: 'et', - imageUrl: - 'https://storeframework.vtexassets.com/arquivos/ids/168396/nihil.jpg?v=637753027573130000', - }, - { - imageId: '168393', - cacheId: '168393', - imageTag: '', - imageLabel: 'similique', - imageText: 'similique', - imageUrl: - 'https://storeframework.vtexassets.com/arquivos/ids/168393/dolore.jpg?v=637753027558270000', - }, - { - imageId: '168394', - cacheId: '168394', - imageTag: '', - imageLabel: 'deleniti', - imageText: 'deleniti', - imageUrl: - 'https://storeframework.vtexassets.com/arquivos/ids/168394/delectus.jpg?v=637753027564530000', - }, - { - imageId: '168395', - cacheId: '168395', - imageTag: '', - imageLabel: 'sunt', - imageText: 'sunt', - imageUrl: - 'https://storeframework.vtexassets.com/arquivos/ids/168395/qui.jpg?v=637753027568900000', - }, - ], - itemId: '2737806', - name: 'plum', - nameComplete: 'Fantastic Soft Cheese plum', - complementName: - 'Explicabo et quibusdam eius excepturi et rem dolorem et. Eligendi ratione et quod error nisi asperiores fugit omnis itaque. Vel officia sapiente autem non. Ut consequatur veniam perspiciatis doloribus nulla saepe.', - referenceId: [ - { - Key: 'RefId', - Value: '6464716212392', - }, - ], - measurementUnit: 'un', - unitMultiplier: 1, - variations: [], - ean: '', - modalType: '', - videos: [], - attachments: [], - isKit: false, + Key: 'RefId', + Value: '6464716212392', }, ], - origin: 'intelligent-search', + measurementUnit: 'un', + unitMultiplier: 1, + variations: [], + ean: '', + modalType: '', + videos: [], + attachments: [], + isKit: false, }, ], - recordsFiltered: 1, - correction: { - misspelled: true, - }, - fuzzy: 'auto', - operator: 'and', - translated: false, - pagination: { - count: 1, - current: { - index: 1, - proxyUrl: - 'search/trade-policy/1?page=1&count=1&query=sku:2737806&sort=&locale=en-US&hide-unavailable-items=false&operator=and&fuzzy=0', - }, - before: [], - after: [], - perPage: 1, - next: { - index: 0, - }, - previous: { - index: 0, - }, - first: { - index: 0, - }, - last: { - index: 0, - }, - }, + origin: 'intelligent-search', }, } +export const createProductFetchResultForSku = ( + skuId: string +): typeof productSearchPage1Count1Fetch.result => { + const { items, ...product } = productSearchPage1Count1Fetch.result + + return { + ...product, + productId: `${skuId}-product`, + items: [ + { + ...items[0], + itemId: skuId, + }, + ], + } +} + // Stale Cart export const checkoutOrderFormStaleFetch = { info: 'https://storeframework.vtexcommercestable.com.br/api/checkout/pub/orderForm/edbe3b03c8c94827a37ec5a6a4648fd2?sc=1&refreshOutdatedData=true', diff --git a/packages/api/test/unit/platforms/vtex/utils/intelligentSearchRequest.test.ts b/packages/api/test/unit/platforms/vtex/utils/intelligentSearchRequest.test.ts new file mode 100644 index 0000000000..7823e8938b --- /dev/null +++ b/packages/api/test/unit/platforms/vtex/utils/intelligentSearchRequest.test.ts @@ -0,0 +1,506 @@ +import { describe, expect, it } from 'vitest' +import { + buildIntelligentSearchRequest, + parseSegmentCookie, + type IntelligentSearchFacet, +} from '../../../../../src/platforms/vtex/utils/intelligentSearchRequest' + +const baseDefaults = { + locale: 'pt-BR', + salesChannel: 1, + regionId: 'region-abc', + hideUnavailableItems: true, + simulationBehavior: 'default' as const, + showSponsored: false, +} + +function paramsToObject(params: URLSearchParams): Record { + return Object.fromEntries(params.entries()) +} + +describe('buildIntelligentSearchRequest', () => { + describe('product-search pagination and query', () => { + it('derives from/to from page and count', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'product-search', + defaults: baseDefaults, + args: { page: 2, count: 12, query: 'shoes' }, + }) + + expect(paramsToObject(request.params)).toMatchObject({ + from: '24', + to: '35', + query: 'shoes', + locale: 'pt-BR', + sc: '1', + regionId: 'region-abc', + hideUnavailableItems: 'true', + simulationBehavior: 'default', + showSponsored: 'false', + }) + }) + + it('omits query and sort when not provided', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'product-search', + defaults: baseDefaults, + args: { page: 0, count: 10 }, + }) + + const params = paramsToObject(request.params) + + expect(params.query).toBeUndefined() + expect(params.sort).toBeUndefined() + expect(params.from).toBe('0') + expect(params.to).toBe('9') + }) + + it('includes sort when provided', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'product-search', + defaults: baseDefaults, + args: { page: 0, count: 10, sort: 'price:desc' }, + }) + + expect(paramsToObject(request.params).sort).toBe('price:desc') + }) + + it('handles count=0 pagination edge case', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'product-search', + defaults: baseDefaults, + args: { page: 3, count: 0 }, + }) + + expect(paramsToObject(request.params)).toMatchObject({ + from: '0', + to: '0', + }) + }) + }) + + describe('segment parsing and defaults fallbacks', () => { + it('extracts shipping keys from segment facets into query params', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'product-search', + segment: { + channel: 2, + regionId: 'seg-region', + cultureInfo: 'en-US', + countryCode: 'USA', + facets: 'zip-code=12345;coordinates=-46,-23;department=electronics', + }, + defaults: { locale: 'en-US' }, + args: { page: 0, count: 10 }, + }) + + expect(paramsToObject(request.params)).toMatchObject({ + sc: '2', + regionId: 'seg-region', + locale: 'en-US', + 'zip-code': '12345', + coordinates: '-46,-23', + }) + expect(request.path).toContain('department/electronics/') + }) + + it('falls back to defaults for sc and regionId when absent in segment', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'facets', + segment: {}, + defaults: { + locale: 'pt-BR', + salesChannel: 3, + regionId: 'fallback-region', + hideUnavailableItems: false, + }, + args: { page: 0, count: 5 }, + }) + + expect(paramsToObject(request.params)).toMatchObject({ + sc: '3', + regionId: 'fallback-region', + locale: 'pt-BR', + hideUnavailableItems: 'false', + }) + }) + + it('prefers default sc/regionId over segment values', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'facets', + segment: { channel: 9, regionId: 'from-segment' }, + defaults: { + locale: 'pt-BR', + salesChannel: 1, + regionId: 'from-defaults', + }, + args: { page: 0, count: 5 }, + }) + + expect(paramsToObject(request.params)).toMatchObject({ + sc: '1', + regionId: 'from-defaults', + }) + }) + }) + + describe('facet handling', () => { + it('puts fuzzy, operator, and pickupPoint in query params', () => { + const selectedFacets: IntelligentSearchFacet[] = [ + { key: 'fuzzy', value: 'auto' }, + { key: 'operator', value: 'and' }, + { key: 'pickupPoint', value: 'store-123' }, + { key: 'brand', value: 'nike' }, + ] + + const request = buildIntelligentSearchRequest({ + endpoint: 'product-search', + defaults: baseDefaults, + args: { page: 0, count: 10, selectedFacets }, + }) + + expect(paramsToObject(request.params)).toMatchObject({ + fuzzy: 'auto', + operator: 'and', + pickupPoint: 'store-123', + }) + expect(request.path).toContain('brand/nike/') + expect(request.path).not.toContain('fuzzy/') + }) + + it('excludes trade-policy and region-id from path', () => { + const selectedFacets: IntelligentSearchFacet[] = [ + { key: 'trade-policy', value: '1' }, + { key: 'region-id', value: 'abc' }, + { key: 'category-1', value: 'shoes' }, + ] + + const request = buildIntelligentSearchRequest({ + endpoint: 'product-search', + defaults: baseDefaults, + args: { page: 0, count: 10, selectedFacets }, + }) + + expect(request.path).toBe('category-1/shoes/') + expect(request.path).not.toContain('trade-policy') + expect(request.path).not.toContain('region-id') + }) + + it('includes shipping facet in path when not all-delivery-methods', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'product-search', + defaults: baseDefaults, + args: { + page: 0, + count: 10, + selectedFacets: [{ key: 'shipping', value: 'delivery' }], + }, + }) + + expect(request.path).toBe('shipping/delivery/') + }) + + it('keeps all-delivery-methods in path from selectedFacets but does not re-add shipping', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'product-search', + defaults: baseDefaults, + args: { + page: 0, + count: 10, + selectedFacets: [ + { key: 'shipping', value: 'all-delivery-methods' }, + { key: 'brand', value: 'adidas' }, + ], + }, + }) + + expect(request.path).toBe('shipping/all-delivery-methods/brand/adidas/') + }) + + it('uses in-stock facet to override hideUnavailableItems', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'product-search', + defaults: { ...baseDefaults, hideUnavailableItems: true }, + args: { + page: 0, + count: 10, + selectedFacets: [{ key: 'in-stock', value: 'false' }], + }, + }) + + expect(paramsToObject(request.params).hideUnavailableItems).toBe('false') + }) + + it('normalizes pickup-in-point shipping facet and merges pickupPoint query param', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'product-search', + segment: { + facets: 'pickupPoint=segment-store', + }, + defaults: baseDefaults, + args: { + page: 0, + count: 10, + selectedFacets: [ + { key: 'shipping', value: 'pickup-in-point-my-store-id' }, + ], + }, + }) + + expect(request.path).toContain('shipping/pickup-in-point/') + expect(paramsToObject(request.params).pickupPoint).toBe('my-store-id') + }) + }) + + describe('catalog-count endpoint', () => { + it('omits from, to, and sort', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'catalog-count', + defaults: baseDefaults, + args: { + page: 1, + count: 20, + sort: 'price:asc', + query: 'jacket', + selectedFacets: [{ key: 'brand', value: 'puma' }], + }, + }) + + const params = paramsToObject(request.params) + + expect(params.from).toBeUndefined() + expect(params.to).toBeUndefined() + expect(params.sort).toBeUndefined() + expect(params.query).toBe('jacket') + expect(request.path).toBe('brand/puma/') + }) + }) + + describe('products endpoint', () => { + it('builds field, value, and segment params', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'products', + segment: { channel: 1, cultureInfo: 'pt-BR' }, + defaults: { locale: 'pt-BR', salesChannel: 1 }, + args: { + field: 'slug', + value: 'my-product', + hideUnavailableItems: true, + showInvisibleItems: true, + }, + }) + + expect(request.path).toBe('') + expect(paramsToObject(request.params)).toMatchObject({ + field: 'slug', + value: 'my-product', + sc: '1', + locale: 'pt-BR', + hideUnavailableItems: 'true', + 'show-invisible-items': 'true', + }) + }) + }) + + describe('search-suggestions and top-searches endpoints', () => { + it('builds search-suggestions with query and locale', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'search-suggestions', + defaults: { locale: 'es-AR' }, + args: { query: 'camisa' }, + }) + + expect(request.path).toBe('') + expect(paramsToObject(request.params)).toEqual({ + query: 'camisa', + locale: 'es-AR', + }) + expect(request.toString()).toBe('?query=camisa&locale=es-AR') + }) + + it('builds top-searches with locale only', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'top-searches', + defaults: { locale: 'pt-BR' }, + }) + + expect(request.path).toBe('') + expect(paramsToObject(request.params)).toEqual({ locale: 'pt-BR' }) + expect(request.toString()).toBe('?locale=pt-BR') + }) + }) + + describe('optional search params', () => { + it('includes sponsoredCount and allowRedirect when provided', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'product-search', + defaults: { ...baseDefaults, showSponsored: true }, + args: { + page: 0, + count: 10, + sponsoredCount: 5, + allowRedirect: true, + showInvisibleItems: true, + }, + }) + + expect(paramsToObject(request.params)).toMatchObject({ + sponsoredCount: '5', + allowRedirect: 'true', + showSponsored: 'true', + 'show-invisible-items': 'true', + }) + }) + }) + + describe('toString', () => { + it('returns path and query for search-like endpoints', () => { + const request = buildIntelligentSearchRequest({ + endpoint: 'product-search', + defaults: baseDefaults, + args: { + page: 0, + count: 10, + selectedFacets: [{ key: 'brand', value: 'nike' }], + }, + }) + + expect(request.toString()).toMatch(/^brand\/nike\/\?/) + expect(request.toString()).toContain('from=0') + expect(request.toString()).toContain('to=9') + }) + }) + + describe('real vtex_segment cookie examples', () => { + // '{"campaigns":null,"channel":"1",...,"facets":"zip-code=01002020;country=BRA;..."}' + const segmentWithShippingBase64 = + 'eyJjYW1wYWlnbnMiOm51bGwsImNoYW5uZWwiOiIxIiwicHJpY2VUYWJsZXMiOm51bGwsInJlZ2lvbklkIjpudWxsLCJ1dG1fY2FtcGFpZ24iOm51bGwsInV0bV9zb3VyY2UiOm51bGwsInV0bWlfY2FtcGFpZ24iOm51bGwsImN1cnJlbmN5Q29kZSI6IkJSTCIsImN1cnJlbmN5U3ltYm9sIjoiUiQiLCJjb3VudHJ5Q29kZSI6IkJSQSIsImN1bHR1cmVJbmZvIjoicHQtQlIiLCJhZG1pbl9jdWx0dXJlSW5mbyI6InB0LUJSIiwiY2hhbm5lbFByaXZhY3kiOiJwdWJsaWMiLCJmYWNldHMiOiJ6aXAtY29kZT0wMTAwMjAyMDtjb3VudHJ5PUJSQTtjb29yZGluYXRlcz0tNDYuNjM3MDQ2ODEzOTY0ODUsLTIzLjU0NzIwNDk3MTMxMzQ3NztwaWNrdXBQb2ludD1tdW5kb2RvY2FiZWxlaXJlaXJvbG9qYTU1XzI5O2RlbGl2ZXJ5Wm9uZXNIYXNoPTk3NTMzMTg0MjZjMTNjMTkwMGI1ZmU1N2I2Mzk4MjdlO3BpY2t1cFBvaW50c0hhc2g9ODQzNWIzMjI1Mjg0ZDRlNTdjNjNjZjgxNjA4NzA5NGQ7In0=' + + // '{"campaigns":null,"channel":"1",...,"facets":"productClusterIds=158;"}' + const segmentWithProductClusterBase64 = + 'eyJjYW1wYWlnbnMiOm51bGwsImNoYW5uZWwiOiIxIiwicHJpY2VUYWJsZXMiOm51bGwsInJlZ2lvbklkIjoidjIuN0RDQjdDRDUzMzk5MzU2MkEyRkM0RUFGRUM2QjNEQjQiLCJ1dG1fY2FtcGFpZ24iOm51bGwsInV0bV9zb3VyY2UiOm51bGwsInV0bWlfY2FtcGFpZ24iOm51bGwsImN1cnJlbmN5Q29kZSI6IlVTRCIsImN1cnJlbmN5U3ltYm9sIjoiJCIsImNvdW50cnlDb2RlIjoiVVNBIiwiY3VsdHVyZUluZm8iOiJlbi1VUyIsImFkbWluX2N1bHR1cmVJbmZvIjoiZW4tVVMiLCJjaGFubmVsUHJpdmFjeSI6InB1YmxpYyIsImZhY2V0cyI6InByb2R1Y3RDbHVzdGVySWRzPTE1ODsifQ==' + + function cookieHeader(segmentBase64: string) { + return `vtex_segment=${segmentBase64}` + } + + it('returns empty object when cookie header is missing', () => { + expect(parseSegmentCookie(undefined)).toEqual({}) + }) + + it('returns empty object for malformed base64/json cookie', () => { + expect(parseSegmentCookie('vtex_segment=not-base64')).toEqual({}) + }) + + it('decodes shipping/geo segment from base64 cookie', () => { + const segment = parseSegmentCookie( + cookieHeader(segmentWithShippingBase64) + ) + + expect(segment.channel).toBe('1') + expect(segment.regionId).toBeNull() + expect(segment.countryCode).toBe('BRA') + expect(segment.cultureInfo).toBe('pt-BR') + expect(segment.facets).toBe( + 'zip-code=01002020;country=BRA;coordinates=-46.63704681396485,-23.547204971313477;pickupPoint=mundodocabeleireiroloja55_29;deliveryZonesHash=9753318426c13c1900b5fe57b639827e;pickupPointsHash=8435b3225284d4e57c63cf816087094d;' + ) + }) + + it('decodes productClusterIds segment from base64 cookie', () => { + const segment = parseSegmentCookie( + cookieHeader(segmentWithProductClusterBase64) + ) + + expect(segment.channel).toBe('1') + expect(segment.regionId).toBe('v2.7DCB7CD533993562A2FC4EAFEC6B3DB4') + expect(segment.countryCode).toBe('USA') + expect(segment.cultureInfo).toBe('en-US') + expect(segment.facets).toBe('productClusterIds=158;') + }) + + it('builds product-search query from shipping/geo segment cookie', () => { + const segment = parseSegmentCookie( + cookieHeader(segmentWithShippingBase64) + ) + + const request = buildIntelligentSearchRequest({ + endpoint: 'product-search', + segment, + defaults: { + locale: 'pt-BR', + salesChannel: 1, + regionId: 'fallback-region-id', + hideUnavailableItems: true, + simulationBehavior: 'default', + showSponsored: false, + }, + args: { + page: 0, + count: 12, + query: 'shampoo', + selectedFacets: [{ key: 'category-1', value: 'cabelos' }], + }, + }) + + expect(request.path).toBe('category-1/cabelos/') + + expect(paramsToObject(request.params)).toEqual({ + from: '0', + to: '11', + query: 'shampoo', + sc: '1', + regionId: 'fallback-region-id', + country: 'BRA', + locale: 'pt-BR', + 'zip-code': '01002020', + coordinates: '-46.63704681396485,-23.547204971313477', + pickupPoint: 'mundodocabeleireiroloja55_29', + deliveryZonesHash: '9753318426c13c1900b5fe57b639827e', + pickupPointHash: '8435b3225284d4e57c63cf816087094d', + hideUnavailableItems: 'true', + simulationBehavior: 'default', + showSponsored: 'false', + allowRedirect: 'false', + }) + }) + + it('builds product-search query from productClusterIds segment cookie', () => { + const segment = parseSegmentCookie( + cookieHeader(segmentWithProductClusterBase64) + ) + + const request = buildIntelligentSearchRequest({ + endpoint: 'product-search', + segment, + defaults: { + locale: 'en-US', + salesChannel: 1, + hideUnavailableItems: false, + showSponsored: true, + }, + args: { + page: 1, + count: 24, + sort: 'price:asc', + selectedFacets: [{ key: 'brand', value: 'nike' }], + }, + }) + + expect(request.path).toBe('brand/nike/') + + expect(paramsToObject(request.params)).toEqual({ + from: '24', + to: '47', + sort: 'price:asc', + sc: '1', + regionId: 'v2.7DCB7CD533993562A2FC4EAFEC6B3DB4', + country: 'USA', + locale: 'en-US', + productClusterId: '158', + hideUnavailableItems: 'false', + showSponsored: 'true', + allowRedirect: 'false', + }) + }) + }) +})