import.e2e-spec.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. import { omit } from '@vendure/common/lib/omit';
  2. import { User } from '@vendure/core';
  3. import { createTestEnvironment } from '@vendure/testing';
  4. import gql from 'graphql-tag';
  5. import path from 'path';
  6. import { initialData } from '../../../e2e-common/e2e-initial-data';
  7. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  8. describe('Import resolver', () => {
  9. const { server, adminClient } = createTestEnvironment({
  10. ...testConfig(),
  11. customFields: {
  12. Product: [
  13. { type: 'string', name: 'pageType' },
  14. {
  15. name: 'owner',
  16. public: true,
  17. nullable: true,
  18. type: 'relation',
  19. entity: User,
  20. eager: true,
  21. },
  22. {
  23. name: 'keywords',
  24. public: true,
  25. nullable: true,
  26. type: 'string',
  27. list: true,
  28. },
  29. {
  30. name: 'localName',
  31. type: 'localeString',
  32. },
  33. ],
  34. ProductVariant: [{ type: 'int', name: 'weight' }],
  35. },
  36. });
  37. beforeAll(async () => {
  38. await server.init({
  39. initialData,
  40. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-empty.csv'),
  41. customerCount: 0,
  42. });
  43. await adminClient.asSuperAdmin();
  44. }, TEST_SETUP_TIMEOUT_MS);
  45. afterAll(async () => {
  46. await server.destroy();
  47. });
  48. it('imports products', async () => {
  49. // TODO: waste a few more hours actually fixing this for real
  50. // Forgive me this abomination of a work-around.
  51. // On the inital run (as in CI), when the sqlite db has just been populated,
  52. // this test will fail due to an "out of memory" exception originating from
  53. // SqljsQueryRunner.ts:79:22, which is part of the findOne() operation on the
  54. // Session repository called from the AuthService.validateSession() method.
  55. // After several hours of fruitless hunting, I did what any desperate JavaScript
  56. // developer would do, and threw in a setTimeout. Which of course "works"...
  57. const timeout = process.env.CI ? 2000 : 1000;
  58. await new Promise(resolve => {
  59. setTimeout(resolve, timeout);
  60. });
  61. const csvFile = path.join(__dirname, 'fixtures', 'product-import.csv');
  62. const result = await adminClient.fileUploadMutation({
  63. mutation: gql`
  64. mutation ImportProducts($csvFile: Upload!) {
  65. importProducts(csvFile: $csvFile) {
  66. imported
  67. processed
  68. errors
  69. }
  70. }
  71. `,
  72. filePaths: [csvFile],
  73. mapVariables: () => ({ csvFile: null }),
  74. });
  75. expect(result.importProducts.errors).toEqual([
  76. 'Invalid Record Length: header length is 19, got 1 on line 8',
  77. ]);
  78. expect(result.importProducts.imported).toBe(4);
  79. expect(result.importProducts.processed).toBe(4);
  80. const productResult = await adminClient.query(
  81. gql`
  82. query GetProducts($options: ProductListOptions) {
  83. products(options: $options) {
  84. totalItems
  85. items {
  86. id
  87. name
  88. slug
  89. description
  90. featuredAsset {
  91. id
  92. name
  93. preview
  94. source
  95. }
  96. assets {
  97. id
  98. name
  99. preview
  100. source
  101. }
  102. optionGroups {
  103. id
  104. code
  105. name
  106. }
  107. facetValues {
  108. id
  109. name
  110. facet {
  111. id
  112. name
  113. }
  114. }
  115. customFields {
  116. pageType
  117. owner {
  118. id
  119. }
  120. keywords
  121. localName
  122. }
  123. variants {
  124. id
  125. name
  126. sku
  127. price
  128. taxCategory {
  129. id
  130. name
  131. }
  132. options {
  133. id
  134. code
  135. }
  136. assets {
  137. id
  138. name
  139. preview
  140. source
  141. }
  142. featuredAsset {
  143. id
  144. name
  145. preview
  146. source
  147. }
  148. facetValues {
  149. id
  150. code
  151. name
  152. facet {
  153. id
  154. name
  155. }
  156. }
  157. stockOnHand
  158. trackInventory
  159. stockMovements {
  160. items {
  161. ... on StockMovement {
  162. id
  163. type
  164. quantity
  165. }
  166. }
  167. }
  168. customFields {
  169. weight
  170. }
  171. }
  172. }
  173. }
  174. }
  175. `,
  176. {
  177. options: {},
  178. },
  179. );
  180. expect(productResult.products.totalItems).toBe(4);
  181. const paperStretcher = productResult.products.items.find(
  182. (p: any) => p.name === 'Perfect Paper Stretcher',
  183. );
  184. const easel = productResult.products.items.find((p: any) => p.name === 'Mabef M/02 Studio Easel');
  185. const pencils = productResult.products.items.find((p: any) => p.name === 'Giotto Mega Pencils');
  186. const smock = productResult.products.items.find((p: any) => p.name === 'Artists Smock');
  187. // Omit FacetValues & options due to variations in the ordering between different DB engines
  188. expect(omit(paperStretcher, ['facetValues', 'options'], true)).toMatchSnapshot();
  189. expect(omit(easel, ['facetValues', 'options'], true)).toMatchSnapshot();
  190. expect(omit(pencils, ['facetValues', 'options'], true)).toMatchSnapshot();
  191. expect(omit(smock, ['facetValues', 'options'], true)).toMatchSnapshot();
  192. const byName = (e: { name: string }) => e.name;
  193. const byCode = (e: { code: string }) => e.code;
  194. expect(paperStretcher.facetValues).toEqual([]);
  195. expect(easel.facetValues).toEqual([]);
  196. expect(pencils.facetValues).toEqual([]);
  197. expect(smock.facetValues.map(byName).sort()).toEqual(['Denim', 'clothes']);
  198. expect(paperStretcher.variants[0].facetValues.map(byName).sort()).toEqual(['Accessory', 'KB']);
  199. expect(paperStretcher.variants[1].facetValues.map(byName).sort()).toEqual(['Accessory', 'KB']);
  200. expect(paperStretcher.variants[2].facetValues.map(byName).sort()).toEqual(['Accessory', 'KB']);
  201. expect(paperStretcher.variants[0].options.map(byCode).sort()).toEqual(['half-imperial']);
  202. expect(paperStretcher.variants[1].options.map(byCode).sort()).toEqual(['quarter-imperial']);
  203. expect(paperStretcher.variants[2].options.map(byCode).sort()).toEqual(['full-imperial']);
  204. expect(easel.variants[0].facetValues.map(byName).sort()).toEqual(['Easel', 'Mabef']);
  205. expect(pencils.variants[0].facetValues.map(byName).sort()).toEqual(['Xmas Sale']);
  206. expect(pencils.variants[1].facetValues.map(byName).sort()).toEqual(['Xmas Sale']);
  207. expect(pencils.variants[0].options.map(byCode).sort()).toEqual(['box-of-8']);
  208. expect(pencils.variants[1].options.map(byCode).sort()).toEqual(['box-of-12']);
  209. expect(smock.variants[0].facetValues.map(byName).sort()).toEqual([]);
  210. expect(smock.variants[1].facetValues.map(byName).sort()).toEqual([]);
  211. expect(smock.variants[2].facetValues.map(byName).sort()).toEqual([]);
  212. expect(smock.variants[3].facetValues.map(byName).sort()).toEqual([]);
  213. expect(smock.variants[0].options.map(byCode).sort()).toEqual(['beige', 'small']);
  214. expect(smock.variants[1].options.map(byCode).sort()).toEqual(['beige', 'large']);
  215. expect(smock.variants[2].options.map(byCode).sort()).toEqual(['navy', 'small']);
  216. expect(smock.variants[3].options.map(byCode).sort()).toEqual(['large', 'navy']);
  217. // Import relation custom fields
  218. expect(paperStretcher.customFields.owner.id).toBe('T_1');
  219. expect(easel.customFields.owner.id).toBe('T_1');
  220. expect(pencils.customFields.owner.id).toBe('T_1');
  221. expect(smock.customFields.owner.id).toBe('T_1');
  222. // Import non-list custom fields
  223. expect(smock.variants[0].customFields.weight).toEqual(500);
  224. expect(smock.variants[1].customFields.weight).toEqual(500);
  225. expect(smock.variants[2].customFields.weight).toEqual(500);
  226. expect(smock.variants[3].customFields.weight).toEqual(500);
  227. expect(smock.variants[4].customFields.weight).toEqual(null);
  228. // Import list custom fields
  229. expect(paperStretcher.customFields.keywords).toEqual(['paper', 'stretching', 'watercolor']);
  230. expect(easel.customFields.keywords).toEqual([]);
  231. expect(pencils.customFields.keywords).toEqual([]);
  232. expect(smock.customFields.keywords).toEqual(['apron', 'clothing']);
  233. // Import localeString custom fields
  234. expect(paperStretcher.customFields.localName).toEqual('localPPS');
  235. expect(easel.customFields.localName).toEqual('localMabef');
  236. expect(pencils.customFields.localName).toEqual('localGiotto');
  237. expect(smock.customFields.localName).toEqual('localSmock');
  238. }, 20000);
  239. it('imports products with multiple languages', async () => {
  240. // TODO: see test above
  241. const timeout = process.env.CI ? 2000 : 1000;
  242. await new Promise(resolve => {
  243. setTimeout(resolve, timeout);
  244. });
  245. const csvFile = path.join(__dirname, 'fixtures', 'e2e-product-import-multi-languages.csv');
  246. const result = await adminClient.fileUploadMutation({
  247. mutation: gql`
  248. mutation ImportProducts($csvFile: Upload!) {
  249. importProducts(csvFile: $csvFile) {
  250. imported
  251. processed
  252. errors
  253. }
  254. }
  255. `,
  256. filePaths: [csvFile],
  257. mapVariables: () => ({ csvFile: null }),
  258. });
  259. expect(result.importProducts.errors).toEqual([]);
  260. expect(result.importProducts.imported).toBe(1);
  261. expect(result.importProducts.processed).toBe(1);
  262. const productResult = await adminClient.query(
  263. gql`
  264. query GetProducts($options: ProductListOptions) {
  265. products(options: $options) {
  266. totalItems
  267. items {
  268. id
  269. name
  270. slug
  271. description
  272. featuredAsset {
  273. id
  274. name
  275. preview
  276. source
  277. }
  278. assets {
  279. id
  280. name
  281. preview
  282. source
  283. }
  284. optionGroups {
  285. id
  286. code
  287. name
  288. }
  289. facetValues {
  290. id
  291. name
  292. facet {
  293. id
  294. name
  295. }
  296. }
  297. customFields {
  298. pageType
  299. owner {
  300. id
  301. }
  302. keywords
  303. localName
  304. }
  305. variants {
  306. id
  307. name
  308. sku
  309. price
  310. taxCategory {
  311. id
  312. name
  313. }
  314. options {
  315. id
  316. code
  317. name
  318. }
  319. assets {
  320. id
  321. name
  322. preview
  323. source
  324. }
  325. featuredAsset {
  326. id
  327. name
  328. preview
  329. source
  330. }
  331. facetValues {
  332. id
  333. code
  334. name
  335. facet {
  336. id
  337. name
  338. }
  339. }
  340. stockOnHand
  341. trackInventory
  342. stockMovements {
  343. items {
  344. ... on StockMovement {
  345. id
  346. type
  347. quantity
  348. }
  349. }
  350. }
  351. customFields {
  352. weight
  353. }
  354. }
  355. }
  356. }
  357. }
  358. `,
  359. {
  360. options: {},
  361. },
  362. {
  363. languageCode: 'zh_Hans',
  364. },
  365. );
  366. expect(productResult.products.totalItems).toBe(5);
  367. const paperStretcher = productResult.products.items.find((p: any) => p.name === '奇妙的纸张拉伸器');
  368. // Omit FacetValues & options due to variations in the ordering between different DB engines
  369. expect(omit(paperStretcher, ['facetValues', 'options'], true)).toMatchSnapshot();
  370. const byName = (e: { name: string }) => e.name;
  371. expect(paperStretcher.facetValues.map(byName).sort()).toEqual(['KB', '饰品']);
  372. expect(paperStretcher.variants[0].options.map(byName).sort()).toEqual(['半英制']);
  373. expect(paperStretcher.variants[1].options.map(byName).sort()).toEqual(['四分之一英制']);
  374. expect(paperStretcher.variants[2].options.map(byName).sort()).toEqual(['全英制']);
  375. // Import list custom fields
  376. expect(paperStretcher.customFields.keywords).toEqual(['paper, stretch']);
  377. // Import localeString custom fields
  378. expect(paperStretcher.customFields.localName).toEqual('纸张拉伸器');
  379. }, 20000);
  380. });