UNPKG

@apptus/esales-api

Version:

Library for making requests to Elevate 4 API v3

669 lines (618 loc) 23.4 kB
// deno-lint-ignore-file import { describe, it, stub, assertSpyCalls, assertSpyCallArgs } from '#test'; import { Query } from './query.ts'; import type * as m from './models/mod.ts'; import type { AddToCartPopupParams, AddToCartPopupBody } from './models/add-to-cart-popup.ts'; import type { AutocompleteParams, AutocompleteBody } from './models/autocomplete.ts'; import type { NavigationTreeParams } from './models/navigation-tree.ts'; import type { SearchPageParams, SearchPageBody } from './models/search-page.ts'; let fetchSpy: ReturnType<typeof createSpy>; let response: Promise<Response>; const createSpy = () => stub(globalThis, 'fetch', () => response); const suite = describe({ name: 'query', beforeEach() { fetchSpy = createSpy(); response = Promise.resolve(new Response('{}', { status: 200 })); }, afterEach() { fetchSpy.restore(); } }); const addToCartPopupSuite = describe(suite, 'addToCartPopup()'); const autocompleteSuite = describe(suite, 'autocomplete()'); const navigationTreeSuite = describe(suite, 'navigationTree()'); const searchPageSuite = describe(suite, 'searchPage()'); const productPageSuite = describe(suite, 'productPage()'); const cartPageSuite = describe(suite, 'cartPage()'); const landingPageSuite = describe(suite, 'landingPage()'); const sponsoredPageSuite = describe(suite, 'sponsoredPage()'); const contentInformationSuite = describe(suite, 'contentInformation()'); const contentSearchPageSuite = describe(suite, 'contentSearchPage()'); // TODO(csv): Use a single method for checking `ProductFilter` type and values? it.ignore('should allow all valid productFilter values for page bodies', () => {}); it(addToCartPopupSuite, 'should pass on correct endpoint, params and body parameters to Request', async () => { const query = new Query({ clusterUrl: 'http://localhost:13', market: 'fr', locale: 'fr-FR', touchpoint: 'mobile', session: () => ({ customerKey: 'c13', sessionKey: 's13' }) }); const params = Object.freeze<AddToCartPopupParams>({ variantKey: 'some_special_key' }); const body = Object.freeze<AddToCartPopupBody>([{ id: '1313', algorithm: 'ADD_TO_CART_RECS' }]); await query.addToCartPopup(params, body as AddToCartPopupBody); const expectedUrl = new URL('http://localhost:13/api/storefront/v3/queries/add-to-cart-popup?market=fr&customerKey=c13&sessionKey=s13&locale=fr-FR&touchpoint=mobile&variantKey=some_special_key'); const expectedBody = '{"recommendationLists":[{"id":"1313","algorithm":"ADD_TO_CART_RECS"}]}'; assertSpyCalls(fetchSpy, 1); assertSpyCallArgs(fetchSpy, 0, [ expectedUrl, { method: 'POST', body: expectedBody, headers: { 'Content-Type': 'text/plain' } } ]); }); it(addToCartPopupSuite, 'should allow all API parameters and body options (kitchen sink)', async () => { const query = new Query({ clusterUrl: 'http://localhost:13', market: 'fr', locale: 'fr-FR', touchpoint: 'mobile', session: () => ({ customerKey: 'c13', sessionKey: 's13' }) }); const result = await query.addToCartPopup({ variantKey: 'very_special_key', cart: ['cart-one'], channels: 'ONLINE|STORE', notify: true, presentCustom: ['something', 'anything'], presentPrices: ['low', 'high'], priceId: 'highPrice', stores: 'store-one|store-two', templateId: 'tpl-2', viewId: 'preview', }, [{ id: '1313', algorithm: 'ADD_TO_CART_RECS', label: 'Do you have everything you need?', limit: 9, params: { cart: ['cart-one'], productKey: 'pk1234' }, productRules: 'rule incl newness 10d ', showMoreLink: '/add-to-cart-popup', visualization: 'GRID' }] ); result satisfies m.RecommendationListPage; result.recommendationLists satisfies m.RecommendationList[]; }); it(autocompleteSuite, 'should pass on correct endpoint, params, and body parameters to Request', async() => { const query = new Query({ clusterUrl: 'http://localhost:10', market: 'se', locale: 'sv-SE', touchpoint: 'desktop', session: () => ({ customerKey: 'c10', sessionKey: 's10' }) }); const params = Object.freeze<AutocompleteParams>({ q: 'foo', notify: false, viewId: 'production', priceId: 'default', channels: 'ONLINE', stores: 'lund', presentCustom: ['material'] }); const body = Object.freeze<AutocompleteBody>({ contentLists: [{ id: '1', }], productFilter: { size: ['XL'] } }); await query.autocomplete(params, body); const expectedUrl = new URL('http://localhost:10/api/storefront/v3/queries/autocomplete?market=se&customerKey=c10&sessionKey=s10&locale=sv-SE&touchpoint=desktop&q=foo&notify=false&viewId=production&priceId=default&channels=ONLINE&stores=lund&presentCustom=material'); const expectedBody = '{"contentLists":[{"id":"1"}],"productFilter":{"size":["XL"]}}'; assertSpyCalls(fetchSpy, 1); assertSpyCallArgs(fetchSpy, 0, [ expectedUrl, { method: 'POST', body: expectedBody, headers: { 'Content-Type': 'text/plain' } } ]); }); it(autocompleteSuite, 'should allow all API parameters and body options (kitchen sink)', async() => { const query = new Query({ clusterUrl: 'http://localhost:11', market: 'se2', locale: 'sv-SE2', touchpoint: 'desktop', session: () => ({ customerKey: 'c11', sessionKey: 's11' }) }); const result = await query.autocomplete({ q: 'robe', notify: false, viewId: 'production', priceId: 'default', channels: 'ONLINE', stores: 'storeA|storeB', templateId: 'my-data-template', presentCustom: ['foo', 'bar'], presentPrices: ['discount'] }, { contentLists: [{ id: 'asd', algorithm: 'NEWEST_CONTENT', limit: 2, contentFilter: { type: 'article' } }], productFilter: { brand: 'Nike', color: ['RED', 'GREEN'], price: { min: 10, max: 100 } }, productRules: 'rule incl newness 10d ' }); result satisfies m.Autocomplete; result.contentSuggestions satisfies m.ContentSuggestion[]; result.phraseSuggestions satisfies m.PhraseSuggestion[]; result.productSuggestions satisfies m.ProductGroup[]; result.contentLists satisfies m.ContentList[]; result.totalHits satisfies number; result.redirectLink satisfies string | undefined; }); it(navigationTreeSuite, 'should pass on correct endpoint, and params, parameters to Request', async() => { const query = new Query({ clusterUrl: 'http://localhost:20', market: 'en', locale: 'en-US', touchpoint: 'desktop', session: () => ({ customerKey: 'c20', sessionKey: 's20' }) }); const params = Object.freeze<NavigationTreeParams>({ priceId: 'SEK', notify: true }); await query.navigationTree(params); const expectedUrl = new URL('http://localhost:20/api/storefront/v3/queries/navigation-tree?market=en&customerKey=c20&sessionKey=s20&locale=en-US&touchpoint=desktop&priceId=SEK&notify=true'); assertSpyCalls(fetchSpy, 1); assertSpyCallArgs(fetchSpy, 0, [expectedUrl, { method: 'GET' }]); }); it(navigationTreeSuite, 'should allow all API parameters and body options (kitchen sink)', async () => { const query = new Query({ clusterUrl: 'http://localhost:21', market: 'en2', locale: 'en-US2', touchpoint: 'desktop', session: () => ({ customerKey: 'c21', sessionKey: 's21' }) }); const result = await query.navigationTree({ notify: false, viewId: 'production', priceId: 'default', channels: 'STORE', stores: 'storeZ|storeD' }); result satisfies m.NavigationTree; result.tree satisfies m.NavigationNode; }); it(searchPageSuite, 'should pass on correct endpoint, facets, params, and body parameters to Request', async() => { const query = new Query({ clusterUrl: 'http://localhost:30', market: 'dk', locale: 'da-DK', touchpoint: 'mobile', session: () => ({ customerKey: 'c30', sessionKey: 's30' }) }); const params = Object.freeze<SearchPageParams>({ q: 'shirt', limit: 11, facets: { color: ['steelblue'] } }); const body = Object.freeze<SearchPageBody>({ primaryList: { include: true } }); await query.searchPage(params, body); const expectedUrl = new URL('http://localhost:30/api/storefront/v3/queries/search-page?market=dk&customerKey=c30&sessionKey=s30&locale=da-DK&touchpoint=mobile&q=shirt&limit=11&f.color=steelblue'); const expectedBody = '{"primaryList":{"include":true}}'; assertSpyCalls(fetchSpy, 1); assertSpyCallArgs(fetchSpy, 0, [ expectedUrl, { method: 'POST', body: expectedBody, headers: { 'Content-Type': 'text/plain' } } ]); }); it(searchPageSuite, 'should allow all API parameters and body options (kitchen sink)', async() => { const sort: 'RELEVANCE' | 'NEWEST_FIRST' | 'PRICE_ASCENDING' | 'PRICE_DESCENDING' | 'DISCOUNT' | 'RATING' = 'RELEVANCE'; const origin: 'ORGANIC' | 'PHRASE_SUGGEST' | 'DID_YOU_MEAN' | 'UNDO_AUTO_CORRECT' | 'RELATED_SEARCHES' = 'ORGANIC'; const query = new Query({ clusterUrl: 'http://localhost:31', market: 'dk2', locale: 'da-DK2', touchpoint: 'mobile', session: () => ({ customerKey: 'c31', sessionKey: 's31' }) }); const result = await query.searchPage({ q: 'robe', sort, origin, limit: 20, skip: 10, notify: false, viewId: 'production', priceId: 'default', channels: 'ONLINE|STORE', stores: 'storeF', selectedCategory: 'women', templateId: 'list-template', viewAllSecondary: true, presentCustom: ['lorem', 'ipsum'], presentPrices: ['VIP'], facets: { color: ['firebrick'], brand: ['nike', 'adidas'], price: { min: 10, max: 500 }, coolFactor: { min: 9001 }, length: { max: 180 } } }, { navigation: { include: true }, primaryList: { include: true, productFilter: { brand: 'Nike', color: ['RED', 'GREEN'], price: { min: 10, max: 100 } }, productRules: 'rule incl newness 10d ' }, contentLists: [ { id: 'jgf', limit: 11, algorithm: 'TOP_CONTENT', contentFilter: { content_key: 'ck12345' } } ] }); result satisfies m.SearchPage; result.q satisfies string; result.primaryList satisfies m.PrimaryList; result.didYouMean satisfies m.DidYouMean[]; result.autoCorrect satisfies m.AutoCorrect | undefined; result.navigation satisfies m.Navigation | undefined; result.contentLists satisfies m.ContentList[]; result.secondaryList satisfies m.SecondaryList | undefined; result.relatedSearches satisfies m.RelatedSearch[] | undefined; }); it(productPageSuite, 'should pass on correct endpoint, facets, params, and body parameters to Request', async () => { const query = new Query({ clusterUrl: 'http://localhost:40', market: 'no', locale: 'no-NO', touchpoint: 'desktop', session: () => ({ customerKey: 'c40', sessionKey: 's40' }) }); const params = Object.freeze({ productKey: 'p123456', viewId: 'production' as const }); const body = Object.freeze({ productGroup: { include: true } }); await query.productPage(params, body); const expectedUrl = new URL('http://localhost:40/api/storefront/v3/queries/product-page?market=no&customerKey=c40&sessionKey=s40&locale=no-NO&touchpoint=desktop&productKey=p123456&viewId=production'); const expectedBody = '{"productGroup":{"include":true}}'; assertSpyCalls(fetchSpy, 1); assertSpyCallArgs(fetchSpy, 0, [ expectedUrl, { method: 'POST', body: expectedBody, headers: { 'Content-Type': 'text/plain' } } ]); }); it(productPageSuite, 'should allow all API parameters and body options (kitchen sink)', async() => { const query = new Query({ clusterUrl: 'http://localhost:41', market: 'no2', locale: 'no-NO2', touchpoint: 'desktop', session: () => ({ customerKey: 'c41', sessionKey: 's41' }) }); const result = await query.productPage({ productKey: 'pk1234567', notify: false, viewId: 'production', priceId: 'default', channels: 'STORE|ONLINE', stores: '123|abc|xyz', templateId: 'rec-list-template', presentCustom: ['season', 'age'], presentPrices: ['member'] }, { productGroup: { include: true }, recommendationLists: [ { id: 'top', label: 'Top sellers', limit: 8, algorithm: 'TOP_PRODUCTS', visualization: 'GRID', showMoreLink: '/top', params: { productKey: 'pk1234' } } ] }); result satisfies m.ProductPage; result.productGroup satisfies m.ProductGroup | undefined; result.recommendationLists satisfies m.RecommendationList[]; }); it(cartPageSuite, 'should pass on correct endpoint, facets, params, and body parameters to Request', async () => { const query = new Query({ clusterUrl: 'http://localhost:50', market: 'fi', locale: 'fi-FI', touchpoint: 'desktop', session: () => ({ customerKey: 'c50', sessionKey: 's50' }) }); const params = Object.freeze({ cart: ['p123456', 'p234567'], viewId: 'production' as const }); const body = Object.freeze({ cart: { include: true } }); await query.cartPage(params, body); const expectedUrl = new URL('http://localhost:50/api/storefront/v3/queries/cart-page?market=fi&customerKey=c50&sessionKey=s50&locale=fi-FI&touchpoint=desktop&cart=p123456%7Cp234567&viewId=production'); const expectedBody = '{"cart":{"include":true}}'; assertSpyCalls(fetchSpy, 1); assertSpyCallArgs(fetchSpy, 0, [ expectedUrl, { method: 'POST', body: expectedBody, headers: { 'Content-Type': 'text/plain' } } ]); }); it(cartPageSuite, 'should allow all API parameters and body options (kitchen sink)', async() => { const query = new Query({ clusterUrl: 'http://localhost:51', market: 'fi2', locale: 'fi-FI2', touchpoint: 'desktop', session: () => ({ customerKey: 'c51', sessionKey: 's51' }) }); const result = await query.cartPage({ cart: ['pk1234567', 'pk2345678'], notify: true, viewId: 'preview', priceId: 'default', channels: 'ONLINE', stores: 'storeA|storeB', templateId: 'rec-list-template', presentCustom: ['red', 'blue'], presentPrices: ['SEK', 'EURO'] }, { cart: { include: true }, recommendationLists: [ { id: 'cart', label: 'Cart recs', limit: 8, algorithm: 'CART', visualization: 'GRID', showMoreLink: '/top', params: { cart: ['pk1234'] } } ] }); result satisfies m.CartPage; result.cart satisfies m.ProductGroup[]; result.recommendationLists satisfies m.RecommendationList[]; }); it(landingPageSuite, 'should allow being called without parameters', async () => { const query = new Query({ clusterUrl: 'http://localhost:60', market: 'de', locale: 'de-DE', touchpoint: 'desktop', session: () => ({ customerKey: 'c60', sessionKey: 's60' }) }); await query.landingPage(); const expectedUrl = new URL('http://localhost:60/api/storefront/v3/queries/landing-page?market=de&customerKey=c60&sessionKey=s60&locale=de-DE&touchpoint=desktop'); assertSpyCalls(fetchSpy, 1); assertSpyCallArgs(fetchSpy, 0, [expectedUrl, { method: 'GET' }]); }); it(landingPageSuite, 'should pass on correct endpoint, facets, params, and body parameters to Request', async () => { const query = new Query({ clusterUrl: 'http://localhost:61', market: 'de2', locale: 'de-DE2', touchpoint: 'desktop', session: () => ({ customerKey: 'c61', sessionKey: 's61' }) }); const params = Object.freeze({ sort: 'RELEVANCE' as const, limit: 11, facets: { color: ['steelblue'], size: ['XL'] } }); const body = Object.freeze({ primaryList: { include: true } }); await query.landingPage(params, body); const expectedUrl = new URL('http://localhost:61/api/storefront/v3/queries/landing-page?market=de2&customerKey=c61&sessionKey=s61&locale=de-DE2&touchpoint=desktop&sort=RELEVANCE&limit=11&f.color=steelblue&f.size=XL'); const expectedBody = '{"primaryList":{"include":true}}'; assertSpyCalls(fetchSpy, 1); assertSpyCallArgs(fetchSpy, 0, [ expectedUrl, { method: 'POST', body: expectedBody, headers: { 'Content-Type': 'text/plain' } } ]); }); it(landingPageSuite, 'should allow all API parameters and body options (kitchen sink)', async() => { const sort: 'RELEVANCE' | 'NEWEST_FIRST' | 'PRICE_ASCENDING' | 'PRICE_DESCENDING' | 'DISCOUNT' | 'RATING' = 'RELEVANCE'; const query = new Query({ clusterUrl: 'http://localhost:62', market: 'de3', locale: 'de-DE3', touchpoint: 'desktop', session: () => ({ customerKey: 'c62', sessionKey: 's62' }) }); const result = await query.landingPage({ sort, limit: 20, skip: 10, q: 'red dress', notify: false, pageReference: '/women/jackets', includeNavigation: true, viewId: 'production', priceId: 'default', channels: 'ONLINE', stores: 'se|de|no', templateId: 'list-template', presentCustom: ['cut'], presentPrices: ['foo', 'bar'], facets: { color: ['firebrick'], brand: ['nike', 'adidas'], price: { min: 10, max: 500 }, coolFactor: { min: 9001 }, length: { max: 180 } } }, { navigation: { include: true }, primaryList: { include: true, productFilter: { brand: 'Nike', color: ['RED', 'GREEN'], price: { min: 10, max: 100 } }, productRules: 'rule incl newness 10d ' }, recommendationLists: [ { id: 'peronsal', label: 'For you', algorithm: 'PERSONAL', limit: 8, visualization: 'CAROUSEL' } ], contentLists: [ { id: 'blogs', limit: 3, algorithm: 'NEWEST_CONTENT', contentFilter: { type: 'blog' } } ] }); result satisfies m.LandingPage; result.primaryList satisfies m.PrimaryList | undefined; result.recommendationLists satisfies m.RecommendationList[]; result.contentLists satisfies m.ContentList[]; result.navigation satisfies m.Navigation | undefined; result.seo satisfies m.SearchEngineOptimization; }); it(sponsoredPageSuite, 'should allow being called without parameters', async () => { const query = new Query({ clusterUrl: 'http://localhost:82', market: 'fr', locale: 'fr-EN', touchpoint: 'desktop', session: () => ({ customerKey: 'c82', sessionKey: 's82' }) }); await query.sponsoredPage({ pageReference: '/' }); const expectedUrl = new URL('http://localhost:82/api/storefront/v3/queries/sponsored-page?market=fr&customerKey=c82&sessionKey=s82&locale=fr-EN&touchpoint=desktop&pageReference=%2F'); assertSpyCalls(fetchSpy, 1); assertSpyCallArgs(fetchSpy, 0, [expectedUrl, { method: 'GET' }]); }); // it(sponsoredPageSuite, 'should pass on correct endpoint, facets, params, and body parameters to Request', async () => { // const query = new Query({ // clusterUrl: 'http://localhost:83', // market: 'fr2', // locale: 'fr-EN2', // touchpoint: 'desktop', // session: () => ({ customerKey: 'c83', sessionKey: 's83' }) // }); // const params = Object.freeze({ // sort: 'RELEVANCE' as const, // limit: 11, // facets: { // color: ['steelblue'], // size: ['XL'] // } // }); // const body = Object.freeze({ primaryList: { include: true } }); // await query.sponsoredPage(params, body); // const expectedUrl = new URL('http://localhost:61/api/storefront/v3/queries/landing-page?market=de2&customerKey=c61&sessionKey=s61&locale=de-DE2&touchpoint=desktop&sort=RELEVANCE&limit=11&f.color=steelblue&f.size=XL'); // const expectedBody = '{"primaryList":{"include":true}}'; // assertSpyCalls(fetchSpy, 1); // assertSpyCallArgs(fetchSpy, 0, [ // expectedUrl, // { method: 'POST', body: expectedBody, headers: { 'Content-Type': 'text/plain' } } // ]); // }); it(sponsoredPageSuite, 'should allow all API parameters and body options (kitchen sink)', async () => { const query = new Query({ clusterUrl: 'http://localhost:84', market: 'fr3', locale: 'fr-EN3', touchpoint: 'desktop', session: () => ({ customerKey: 'c84', sessionKey: 's84' }) }); const result = await query.sponsoredPage({ notify: false, pageReference: '/women/jackets/sponsored', viewId: 'production', templateId: 'sponsored-template' }); result satisfies m.SponsoredPage; result.sponsoredLists satisfies m.SponsoredList[]; }); it(contentInformationSuite, 'should pass on correct endpoint, and params, parameters to Request', async () => { const query = new Query({ clusterUrl: 'http://localhost:70', market: 'fr', locale: 'fr-FR', touchpoint: 'mobile', session: () => ({ customerKey: 'c70', sessionKey: 's70' }) }); const params = Object.freeze({ contentKeys: ['ck1234', 'ck9876'] }); await query.contentInformation(params); const expectedUrl = new URL('http://localhost:70/api/storefront/v3/queries/content-information?market=fr&customerKey=c70&sessionKey=s70&locale=fr-FR&touchpoint=mobile&contentKeys=ck1234%7Cck9876'); assertSpyCalls(fetchSpy, 1); assertSpyCallArgs(fetchSpy, 0, [ expectedUrl, { method: 'GET' } ]); }); it(contentInformationSuite, 'should allow all API parameters and body options (kitchen sink)', async () => { const query = new Query({ clusterUrl: 'http://localhost:71', market: 'fr2', locale: 'fr-FR2', touchpoint: 'mobile', session: () => ({ customerKey: 'c71', sessionKey: 's71' }) }); const result = await query.contentInformation({ contentKeys: ['ck123456', 'ck234567'], notify: false, viewId: 'production' }); result satisfies m.ContentInformation; result.items satisfies m.ContentItem[]; }); it(contentSearchPageSuite, 'should pass on correct endpoint, facets, params, and body parameters to Request', async() => { const query = new Query({ clusterUrl: 'http://localhost:80', market: 'es', locale: 'es-ES', touchpoint: 'desktop', session: () => ({ customerKey: 'c80', sessionKey: 's80' }) }); const params = Object.freeze({ q: 'sizeguide', limit: 11 }); const body = Object.freeze({ primaryList: { contentFilter: { type: 'article' } } }); await query.contentSearchPage(params, body); const expectedUrl = new URL('http://localhost:80/api/storefront/v3/queries/content-search-page?market=es&customerKey=c80&sessionKey=s80&locale=es-ES&touchpoint=desktop&q=sizeguide&limit=11'); const expectedBody = '{"primaryList":{"contentFilter":{"type":"article"}}}'; assertSpyCalls(fetchSpy, 1); assertSpyCallArgs(fetchSpy, 0, [ expectedUrl, { method: 'POST', body: expectedBody, headers: { 'Content-Type': 'text/plain' } } ]); }); it(contentSearchPageSuite, 'should allow all API parameters and body options (kitchen sink)', async() => { const query = new Query({ clusterUrl: 'http://localhost:81', market: 'es2', locale: 'es-ES2', touchpoint: 'mobile', session: () => ({ customerKey: 'c81', sessionKey: 's81' }) }); const result = await query.contentSearchPage({ q: 'faq', sort: 'RELEVANCE', origin: 'UNDO_AUTO_CORRECT', skip: 10, limit: 30, notify: false, viewId: 'preview' }, { primaryList: { contentFilter: { type: 'article', content_key: ['ck123', 'ck234', 'ck345'], 'custom.foo': 'bar' } } }); result satisfies m.ContentSearchPage; result.q satisfies string; result.primaryList satisfies m.PrimaryContentList; result.didYouMean satisfies m.DidYouMean[]; result.autoCorrect satisfies m.AutoCorrect | undefined; });