elasticsearch-plugin.e2e-spec.ts 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796
  1. /* tslint:disable:no-non-null-assertion */
  2. import { SortOrder } from '@vendure/common/lib/generated-types';
  3. import { pick } from '@vendure/common/lib/pick';
  4. import { DefaultLogger, LogLevel, mergeConfig } from '@vendure/core';
  5. import { facetValueCollectionFilter } from '@vendure/core/dist/config/collection/default-collection-filters';
  6. import { createTestEnvironment, E2E_DEFAULT_CHANNEL_TOKEN, SimpleGraphQLClient } from '@vendure/testing';
  7. import gql from 'graphql-tag';
  8. import path from 'path';
  9. import { initialData } from '../../../e2e-common/e2e-initial-data';
  10. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  11. import {
  12. AssignProductsToChannel,
  13. CreateChannel,
  14. CreateCollection,
  15. CreateFacet,
  16. CurrencyCode,
  17. DeleteProduct,
  18. DeleteProductVariant,
  19. LanguageCode,
  20. RemoveProductsFromChannel,
  21. SearchFacetValues,
  22. SearchGetPrices,
  23. SearchInput,
  24. UpdateCollection,
  25. UpdateProduct,
  26. UpdateProductVariants,
  27. UpdateTaxRate,
  28. } from '../../core/e2e/graphql/generated-e2e-admin-types';
  29. import { SearchProductsShop } from '../../core/e2e/graphql/generated-e2e-shop-types';
  30. import {
  31. ASSIGN_PRODUCT_TO_CHANNEL,
  32. CREATE_CHANNEL,
  33. CREATE_COLLECTION,
  34. CREATE_FACET,
  35. DELETE_PRODUCT,
  36. DELETE_PRODUCT_VARIANT,
  37. REMOVE_PRODUCT_FROM_CHANNEL,
  38. UPDATE_COLLECTION,
  39. UPDATE_PRODUCT,
  40. UPDATE_PRODUCT_VARIANTS,
  41. UPDATE_TAX_RATE,
  42. } from '../../core/e2e/graphql/shared-definitions';
  43. import { ElasticsearchPlugin } from '../src/plugin';
  44. import { SEARCH_PRODUCTS_SHOP } from './../../core/e2e/graphql/shop-definitions';
  45. import { awaitRunningJobs } from './../../core/e2e/utils/await-running-jobs';
  46. import { GetJobInfo, JobState, Reindex } from './graphql/generated-e2e-elasticsearch-plugin-types';
  47. describe('Elasticsearch plugin', () => {
  48. const { server, adminClient, shopClient } = createTestEnvironment(
  49. mergeConfig(testConfig, {
  50. plugins: [
  51. ElasticsearchPlugin.init({
  52. indexPrefix: 'e2e-tests',
  53. port: process.env.CI ? +(process.env.ELASTICSEARCH_PORT || 9200) : 9200,
  54. host: process.env.CI
  55. ? process.env.ELASTICSEARCH_HOST || 'elasticsearch'
  56. : 'http://192.168.99.100',
  57. }),
  58. ],
  59. }),
  60. );
  61. beforeAll(async () => {
  62. await server.init({
  63. dataDir: path.join(__dirname, '__data__'),
  64. initialData,
  65. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  66. customerCount: 1,
  67. });
  68. await adminClient.asSuperAdmin();
  69. await adminClient.query(REINDEX);
  70. await awaitRunningJobs(adminClient);
  71. }, TEST_SETUP_TIMEOUT_MS);
  72. afterAll(async () => {
  73. await server.destroy();
  74. });
  75. function doAdminSearchQuery(input: SearchInput) {
  76. return adminClient.query<SearchProductsShop.Query, SearchProductsShop.Variables>(SEARCH_PRODUCTS, {
  77. input,
  78. });
  79. }
  80. async function testGroupByProduct(client: SimpleGraphQLClient) {
  81. const result = await client.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
  82. SEARCH_PRODUCTS_SHOP,
  83. {
  84. input: {
  85. groupByProduct: true,
  86. },
  87. },
  88. );
  89. expect(result.search.totalItems).toBe(20);
  90. }
  91. async function testNoGrouping(client: SimpleGraphQLClient) {
  92. const result = await client.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
  93. SEARCH_PRODUCTS_SHOP,
  94. {
  95. input: {
  96. groupByProduct: false,
  97. },
  98. },
  99. );
  100. expect(result.search.totalItems).toBe(34);
  101. }
  102. async function testMatchSearchTerm(client: SimpleGraphQLClient) {
  103. const result = await client.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
  104. SEARCH_PRODUCTS_SHOP,
  105. {
  106. input: {
  107. term: 'camera',
  108. groupByProduct: true,
  109. },
  110. },
  111. );
  112. expect(result.search.items.map(i => i.productName)).toEqual([
  113. 'Instant Camera',
  114. 'Camera Lens',
  115. 'SLR Camera',
  116. ]);
  117. }
  118. async function testMatchFacetValueIds(client: SimpleGraphQLClient) {
  119. const result = await client.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
  120. SEARCH_PRODUCTS_SHOP,
  121. {
  122. input: {
  123. facetValueIds: ['T_1', 'T_2'],
  124. groupByProduct: true,
  125. sort: {
  126. name: SortOrder.ASC,
  127. },
  128. },
  129. },
  130. );
  131. expect(result.search.items.map(i => i.productName)).toEqual([
  132. 'Clacky Keyboard',
  133. 'Curvy Monitor',
  134. 'Gaming PC',
  135. 'Hard Drive',
  136. 'Laptop',
  137. 'USB Cable',
  138. ]);
  139. }
  140. async function testMatchCollectionId(client: SimpleGraphQLClient) {
  141. const result = await client.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
  142. SEARCH_PRODUCTS_SHOP,
  143. {
  144. input: {
  145. collectionId: 'T_2',
  146. groupByProduct: true,
  147. },
  148. },
  149. );
  150. expect(result.search.items.map(i => i.productName)).toEqual([
  151. 'Spiky Cactus',
  152. 'Orchid',
  153. 'Bonsai Tree',
  154. ]);
  155. }
  156. async function testSinglePrices(client: SimpleGraphQLClient) {
  157. const result = await client.query<SearchGetPrices.Query, SearchGetPrices.Variables>(
  158. SEARCH_GET_PRICES,
  159. {
  160. input: {
  161. groupByProduct: false,
  162. take: 3,
  163. sort: {
  164. price: SortOrder.ASC,
  165. },
  166. },
  167. },
  168. );
  169. expect(result.search.items).toEqual([
  170. {
  171. price: { value: 799 },
  172. priceWithTax: { value: 959 },
  173. },
  174. {
  175. price: { value: 1498 },
  176. priceWithTax: { value: 1798 },
  177. },
  178. {
  179. price: { value: 1550 },
  180. priceWithTax: { value: 1860 },
  181. },
  182. ]);
  183. }
  184. async function testPriceRanges(client: SimpleGraphQLClient) {
  185. const result = await client.query<SearchGetPrices.Query, SearchGetPrices.Variables>(
  186. SEARCH_GET_PRICES,
  187. {
  188. input: {
  189. groupByProduct: true,
  190. take: 3,
  191. term: 'laptop',
  192. },
  193. },
  194. );
  195. expect(result.search.items).toEqual([
  196. {
  197. price: { min: 129900, max: 229900 },
  198. priceWithTax: { min: 155880, max: 275880 },
  199. },
  200. ]);
  201. }
  202. describe('shop api', () => {
  203. it('group by product', () => testGroupByProduct(shopClient));
  204. it('no grouping', () => testNoGrouping(shopClient));
  205. it('matches search term', () => testMatchSearchTerm(shopClient));
  206. it('matches by facetValueId', () => testMatchFacetValueIds(shopClient));
  207. it('matches by collectionId', () => testMatchCollectionId(shopClient));
  208. it('single prices', () => testSinglePrices(shopClient));
  209. it('price ranges', () => testPriceRanges(shopClient));
  210. it('returns correct facetValues when not grouped by product', async () => {
  211. const result = await shopClient.query<SearchFacetValues.Query, SearchFacetValues.Variables>(
  212. SEARCH_GET_FACET_VALUES,
  213. {
  214. input: {
  215. groupByProduct: false,
  216. },
  217. },
  218. );
  219. expect(result.search.facetValues).toEqual([
  220. { count: 21, facetValue: { id: 'T_1', name: 'electronics' } },
  221. { count: 17, facetValue: { id: 'T_2', name: 'computers' } },
  222. { count: 4, facetValue: { id: 'T_3', name: 'photo' } },
  223. { count: 10, facetValue: { id: 'T_4', name: 'sports equipment' } },
  224. { count: 3, facetValue: { id: 'T_5', name: 'home & garden' } },
  225. { count: 3, facetValue: { id: 'T_6', name: 'plants' } },
  226. ]);
  227. });
  228. it('returns correct facetValues when grouped by product', async () => {
  229. const result = await shopClient.query<SearchFacetValues.Query, SearchFacetValues.Variables>(
  230. SEARCH_GET_FACET_VALUES,
  231. {
  232. input: {
  233. groupByProduct: true,
  234. },
  235. },
  236. );
  237. expect(result.search.facetValues).toEqual([
  238. { count: 10, facetValue: { id: 'T_1', name: 'electronics' } },
  239. { count: 6, facetValue: { id: 'T_2', name: 'computers' } },
  240. { count: 4, facetValue: { id: 'T_3', name: 'photo' } },
  241. { count: 7, facetValue: { id: 'T_4', name: 'sports equipment' } },
  242. { count: 3, facetValue: { id: 'T_5', name: 'home & garden' } },
  243. { count: 3, facetValue: { id: 'T_6', name: 'plants' } },
  244. ]);
  245. });
  246. it('omits facetValues of private facets', async () => {
  247. const { createFacet } = await adminClient.query<CreateFacet.Mutation, CreateFacet.Variables>(
  248. CREATE_FACET,
  249. {
  250. input: {
  251. code: 'profit-margin',
  252. isPrivate: true,
  253. translations: [{ languageCode: LanguageCode.en, name: 'Profit Margin' }],
  254. values: [
  255. {
  256. code: 'massive',
  257. translations: [{ languageCode: LanguageCode.en, name: 'massive' }],
  258. },
  259. ],
  260. },
  261. },
  262. );
  263. await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  264. input: {
  265. id: 'T_2',
  266. // T_1 & T_2 are the existing facetValues (electronics & photo)
  267. facetValueIds: ['T_1', 'T_2', createFacet.values[0].id],
  268. },
  269. });
  270. const result = await shopClient.query<SearchFacetValues.Query, SearchFacetValues.Variables>(
  271. SEARCH_GET_FACET_VALUES,
  272. {
  273. input: {
  274. groupByProduct: true,
  275. },
  276. },
  277. );
  278. expect(result.search.facetValues).toEqual([
  279. { count: 10, facetValue: { id: 'T_1', name: 'electronics' } },
  280. { count: 6, facetValue: { id: 'T_2', name: 'computers' } },
  281. { count: 4, facetValue: { id: 'T_3', name: 'photo' } },
  282. { count: 7, facetValue: { id: 'T_4', name: 'sports equipment' } },
  283. { count: 3, facetValue: { id: 'T_5', name: 'home & garden' } },
  284. { count: 3, facetValue: { id: 'T_6', name: 'plants' } },
  285. ]);
  286. });
  287. it('encodes the productId and productVariantId', async () => {
  288. const result = await shopClient.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
  289. SEARCH_PRODUCTS_SHOP,
  290. {
  291. input: {
  292. groupByProduct: false,
  293. take: 1,
  294. },
  295. },
  296. );
  297. expect(pick(result.search.items[0], ['productId', 'productVariantId'])).toEqual({
  298. productId: 'T_1',
  299. productVariantId: 'T_1',
  300. });
  301. });
  302. it('omits results for disabled ProductVariants', async () => {
  303. await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  304. UPDATE_PRODUCT_VARIANTS,
  305. {
  306. input: [{ id: 'T_3', enabled: false }],
  307. },
  308. );
  309. await awaitRunningJobs(adminClient);
  310. const result = await shopClient.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
  311. SEARCH_PRODUCTS_SHOP,
  312. {
  313. input: {
  314. groupByProduct: false,
  315. take: 3,
  316. },
  317. },
  318. );
  319. expect(result.search.items.map(i => i.productVariantId)).toEqual(['T_1', 'T_2', 'T_4']);
  320. });
  321. it('encodes collectionIds', async () => {
  322. const result = await shopClient.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
  323. SEARCH_PRODUCTS_SHOP,
  324. {
  325. input: {
  326. groupByProduct: false,
  327. term: 'cactus',
  328. take: 1,
  329. },
  330. },
  331. );
  332. expect(result.search.items[0].collectionIds).toEqual(['T_2']);
  333. });
  334. });
  335. describe('admin api', () => {
  336. it('group by product', () => testGroupByProduct(adminClient));
  337. it('no grouping', () => testNoGrouping(adminClient));
  338. it('matches search term', () => testMatchSearchTerm(adminClient));
  339. it('matches by facetValueId', () => testMatchFacetValueIds(adminClient));
  340. it('matches by collectionId', () => testMatchCollectionId(adminClient));
  341. it('single prices', () => testSinglePrices(adminClient));
  342. it('price ranges', () => testPriceRanges(adminClient));
  343. describe('updating the index', () => {
  344. it('updates index when ProductVariants are changed', async () => {
  345. await awaitRunningJobs(adminClient);
  346. const { search } = await doAdminSearchQuery({ term: 'drive', groupByProduct: false });
  347. expect(search.items.map(i => i.sku)).toEqual([
  348. 'IHD455T1',
  349. 'IHD455T2',
  350. 'IHD455T3',
  351. 'IHD455T4',
  352. 'IHD455T6',
  353. ]);
  354. await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  355. UPDATE_PRODUCT_VARIANTS,
  356. {
  357. input: search.items.map(i => ({
  358. id: i.productVariantId,
  359. sku: i.sku + '_updated',
  360. })),
  361. },
  362. );
  363. await awaitRunningJobs(adminClient);
  364. const { search: search2 } = await doAdminSearchQuery({
  365. term: 'drive',
  366. groupByProduct: false,
  367. });
  368. expect(search2.items.map(i => i.sku)).toEqual([
  369. 'IHD455T1_updated',
  370. 'IHD455T2_updated',
  371. 'IHD455T3_updated',
  372. 'IHD455T4_updated',
  373. 'IHD455T6_updated',
  374. ]);
  375. });
  376. it('updates index when ProductVariants are deleted', async () => {
  377. await awaitRunningJobs(adminClient);
  378. const { search } = await doAdminSearchQuery({ term: 'drive', groupByProduct: false });
  379. await adminClient.query<DeleteProductVariant.Mutation, DeleteProductVariant.Variables>(
  380. DELETE_PRODUCT_VARIANT,
  381. {
  382. id: search.items[0].productVariantId,
  383. },
  384. );
  385. await awaitRunningJobs(adminClient);
  386. const { search: search2 } = await doAdminSearchQuery({
  387. term: 'drive',
  388. groupByProduct: false,
  389. });
  390. expect(search2.items.map(i => i.sku)).toEqual([
  391. 'IHD455T2_updated',
  392. 'IHD455T3_updated',
  393. 'IHD455T4_updated',
  394. 'IHD455T6_updated',
  395. ]);
  396. });
  397. it('updates index when a Product is changed', async () => {
  398. await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  399. input: {
  400. id: 'T_1',
  401. facetValueIds: [],
  402. },
  403. });
  404. await awaitRunningJobs(adminClient);
  405. const result = await doAdminSearchQuery({ facetValueIds: ['T_2'], groupByProduct: true });
  406. expect(result.search.items.map(i => i.productName)).toEqual([
  407. 'Gaming PC',
  408. 'Clacky Keyboard',
  409. 'USB Cable',
  410. 'Curvy Monitor',
  411. 'Hard Drive',
  412. ]);
  413. });
  414. it('updates index when a Product is deleted', async () => {
  415. const { search } = await doAdminSearchQuery({ facetValueIds: ['T_2'], groupByProduct: true });
  416. expect(search.items.map(i => i.productId)).toEqual(['T_3', 'T_5', 'T_6', 'T_2', 'T_4']);
  417. await adminClient.query<DeleteProduct.Mutation, DeleteProduct.Variables>(DELETE_PRODUCT, {
  418. id: 'T_5',
  419. });
  420. await awaitRunningJobs(adminClient);
  421. const { search: search2 } = await doAdminSearchQuery({
  422. facetValueIds: ['T_2'],
  423. groupByProduct: true,
  424. });
  425. expect(search2.items.map(i => i.productId)).toEqual(['T_3', 'T_6', 'T_2', 'T_4']);
  426. });
  427. it('updates index when a Collection is changed', async () => {
  428. await adminClient.query<UpdateCollection.Mutation, UpdateCollection.Variables>(
  429. UPDATE_COLLECTION,
  430. {
  431. input: {
  432. id: 'T_2',
  433. filters: [
  434. {
  435. code: facetValueCollectionFilter.code,
  436. arguments: [
  437. {
  438. name: 'facetValueIds',
  439. value: `["T_4"]`,
  440. type: 'facetValueIds',
  441. },
  442. {
  443. name: 'containsAny',
  444. value: `false`,
  445. type: 'boolean',
  446. },
  447. ],
  448. },
  449. ],
  450. },
  451. },
  452. );
  453. await awaitRunningJobs(adminClient);
  454. const result = await doAdminSearchQuery({ collectionId: 'T_2', groupByProduct: true });
  455. expect(result.search.items.map(i => i.productName)).toEqual([
  456. 'Road Bike',
  457. 'Skipping Rope',
  458. 'Boxing Gloves',
  459. 'Tent',
  460. 'Cruiser Skateboard',
  461. 'Football',
  462. 'Running Shoe',
  463. ]);
  464. });
  465. it('updates index when a Collection created', async () => {
  466. const { createCollection } = await adminClient.query<
  467. CreateCollection.Mutation,
  468. CreateCollection.Variables
  469. >(CREATE_COLLECTION, {
  470. input: {
  471. translations: [
  472. {
  473. languageCode: LanguageCode.en,
  474. name: 'Photo',
  475. description: '',
  476. },
  477. ],
  478. filters: [
  479. {
  480. code: facetValueCollectionFilter.code,
  481. arguments: [
  482. {
  483. name: 'facetValueIds',
  484. value: `["T_3"]`,
  485. type: 'facetValueIds',
  486. },
  487. {
  488. name: 'containsAny',
  489. value: `false`,
  490. type: 'boolean',
  491. },
  492. ],
  493. },
  494. ],
  495. },
  496. });
  497. await awaitRunningJobs(adminClient);
  498. const result = await doAdminSearchQuery({
  499. collectionId: createCollection.id,
  500. groupByProduct: true,
  501. });
  502. expect(result.search.items.map(i => i.productName)).toEqual([
  503. 'Instant Camera',
  504. 'Camera Lens',
  505. 'Tripod',
  506. 'SLR Camera',
  507. ]);
  508. });
  509. it('updates index when a taxRate is changed', async () => {
  510. await adminClient.query<UpdateTaxRate.Mutation, UpdateTaxRate.Variables>(UPDATE_TAX_RATE, {
  511. input: {
  512. // Default Channel's defaultTaxZone is Europe (id 2) and the id of the standard TaxRate
  513. // to Europe is 2.
  514. id: 'T_2',
  515. value: 50,
  516. },
  517. });
  518. await awaitRunningJobs(adminClient);
  519. const result = await adminClient.query<SearchGetPrices.Query, SearchGetPrices.Variables>(
  520. SEARCH_GET_PRICES,
  521. {
  522. input: {
  523. groupByProduct: true,
  524. term: 'laptop',
  525. } as SearchInput,
  526. },
  527. );
  528. expect(result.search.items).toEqual([
  529. {
  530. price: { min: 129900, max: 229900 },
  531. priceWithTax: { min: 194850, max: 344850 },
  532. },
  533. ]);
  534. });
  535. it('returns disabled field when not grouped', async () => {
  536. const result = await doAdminSearchQuery({ groupByProduct: false, term: 'laptop' });
  537. expect(result.search.items.map(pick(['productVariantId', 'enabled']))).toEqual([
  538. { productVariantId: 'T_1', enabled: true },
  539. { productVariantId: 'T_2', enabled: true },
  540. { productVariantId: 'T_3', enabled: false },
  541. { productVariantId: 'T_4', enabled: true },
  542. ]);
  543. });
  544. it('when grouped, disabled is false if at least one variant is enabled', async () => {
  545. await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  546. UPDATE_PRODUCT_VARIANTS,
  547. {
  548. input: [{ id: 'T_1', enabled: false }, { id: 'T_2', enabled: false }],
  549. },
  550. );
  551. await awaitRunningJobs(adminClient);
  552. const result = await doAdminSearchQuery({ groupByProduct: true, term: 'laptop' });
  553. expect(result.search.items.map(pick(['productId', 'enabled']))).toEqual([
  554. { productId: 'T_1', enabled: true },
  555. ]);
  556. });
  557. it('when grouped, disabled is true if all variants are disabled', async () => {
  558. await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  559. UPDATE_PRODUCT_VARIANTS,
  560. {
  561. input: [{ id: 'T_4', enabled: false }],
  562. },
  563. );
  564. await awaitRunningJobs(adminClient);
  565. const result = await doAdminSearchQuery({ groupByProduct: true, take: 3, term: 'laptop' });
  566. expect(result.search.items.map(pick(['productId', 'enabled']))).toEqual([
  567. { productId: 'T_1', enabled: false },
  568. ]);
  569. });
  570. it('when grouped, disabled is true product is disabled', async () => {
  571. await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  572. input: {
  573. id: 'T_3',
  574. enabled: false,
  575. },
  576. });
  577. await awaitRunningJobs(adminClient);
  578. const result = await doAdminSearchQuery({ groupByProduct: true, term: 'gaming' });
  579. expect(result.search.items.map(pick(['productId', 'enabled']))).toEqual([
  580. { productId: 'T_3', enabled: false },
  581. ]);
  582. });
  583. });
  584. describe('channel handling', () => {
  585. const SECOND_CHANNEL_TOKEN = 'second-channel-token';
  586. let secondChannel: CreateChannel.CreateChannel;
  587. beforeAll(async () => {
  588. const { createChannel } = await adminClient.query<
  589. CreateChannel.Mutation,
  590. CreateChannel.Variables
  591. >(CREATE_CHANNEL, {
  592. input: {
  593. code: 'second-channel',
  594. token: SECOND_CHANNEL_TOKEN,
  595. defaultLanguageCode: LanguageCode.en,
  596. currencyCode: CurrencyCode.GBP,
  597. pricesIncludeTax: true,
  598. defaultTaxZoneId: 'T_2',
  599. defaultShippingZoneId: 'T_1',
  600. },
  601. });
  602. secondChannel = createChannel;
  603. });
  604. it('adding product to channel', async () => {
  605. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  606. await adminClient.query<AssignProductsToChannel.Mutation, AssignProductsToChannel.Variables>(
  607. ASSIGN_PRODUCT_TO_CHANNEL,
  608. {
  609. input: { channelId: secondChannel.id, productIds: ['T_1', 'T_2'] },
  610. },
  611. );
  612. await awaitRunningJobs(adminClient);
  613. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  614. const { search } = await doAdminSearchQuery({ groupByProduct: true });
  615. expect(search.items.map(i => i.productId).sort()).toEqual(['T_1', 'T_2']);
  616. });
  617. it('removing product from channel', async () => {
  618. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  619. const { removeProductsFromChannel } = await adminClient.query<
  620. RemoveProductsFromChannel.Mutation,
  621. RemoveProductsFromChannel.Variables
  622. >(REMOVE_PRODUCT_FROM_CHANNEL, {
  623. input: {
  624. productIds: ['T_2'],
  625. channelId: secondChannel.id,
  626. },
  627. });
  628. await awaitRunningJobs(adminClient);
  629. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  630. const { search } = await doAdminSearchQuery({ groupByProduct: true });
  631. expect(search.items.map(i => i.productId)).toEqual(['T_1']);
  632. });
  633. it('reindexes in channel', async () => {
  634. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  635. const { reindex } = await adminClient.query<Reindex.Mutation>(REINDEX);
  636. await awaitRunningJobs(adminClient);
  637. const { job } = await adminClient.query<GetJobInfo.Query, GetJobInfo.Variables>(
  638. GET_JOB_INFO,
  639. { id: reindex.id },
  640. );
  641. expect(job!.state).toBe(JobState.COMPLETED);
  642. const { search } = await doAdminSearchQuery({ groupByProduct: true });
  643. expect(search.items.map(i => i.productId).sort()).toEqual(['T_1']);
  644. });
  645. });
  646. });
  647. });
  648. export const SEARCH_PRODUCTS = gql`
  649. query SearchProductsAdmin($input: SearchInput!) {
  650. search(input: $input) {
  651. totalItems
  652. items {
  653. enabled
  654. productId
  655. productName
  656. productPreview
  657. productVariantId
  658. productVariantName
  659. productVariantPreview
  660. sku
  661. }
  662. }
  663. }
  664. `;
  665. export const SEARCH_GET_FACET_VALUES = gql`
  666. query SearchFacetValues($input: SearchInput!) {
  667. search(input: $input) {
  668. totalItems
  669. facetValues {
  670. count
  671. facetValue {
  672. id
  673. name
  674. }
  675. }
  676. }
  677. }
  678. `;
  679. export const SEARCH_GET_PRICES = gql`
  680. query SearchGetPrices($input: SearchInput!) {
  681. search(input: $input) {
  682. items {
  683. price {
  684. ... on PriceRange {
  685. min
  686. max
  687. }
  688. ... on SinglePrice {
  689. value
  690. }
  691. }
  692. priceWithTax {
  693. ... on PriceRange {
  694. min
  695. max
  696. }
  697. ... on SinglePrice {
  698. value
  699. }
  700. }
  701. }
  702. }
  703. }
  704. `;
  705. const REINDEX = gql`
  706. mutation Reindex {
  707. reindex {
  708. id
  709. name
  710. state
  711. progress
  712. duration
  713. result
  714. }
  715. }
  716. `;
  717. const GET_JOB_INFO = gql`
  718. query GetJobInfo($id: String!) {
  719. job(jobId: $id) {
  720. id
  721. name
  722. state
  723. progress
  724. duration
  725. result
  726. }
  727. }
  728. `;