| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532 |
- /* eslint-disable @typescript-eslint/no-non-null-assertion */
- import { omit } from '@vendure/common/lib/omit';
- import { User } from '@vendure/core';
- import { createTestEnvironment } from '@vendure/testing';
- import * as fs from 'node:fs';
- import http from 'node:http';
- import path from 'node:path';
- import { afterAll, beforeAll, describe, expect, it } from 'vitest';
- import { initialData } from '../../../e2e-common/e2e-initial-data';
- import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
- import { graphql } from './graphql/graphql-admin';
- describe('Import resolver', () => {
- const { server, adminClient } = createTestEnvironment({
- ...testConfig(),
- customFields: {
- Product: [
- { type: 'string', name: 'pageType' },
- {
- name: 'owner',
- public: true,
- nullable: true,
- type: 'relation',
- entity: User,
- eager: true,
- },
- {
- name: 'keywords',
- public: true,
- nullable: true,
- type: 'string',
- list: true,
- },
- {
- name: 'localName',
- type: 'localeString',
- },
- ],
- ProductVariant: [
- { type: 'boolean', name: 'valid' },
- { type: 'int', name: 'weight' },
- ],
- },
- });
- beforeAll(async () => {
- await server.init({
- initialData,
- productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-empty.csv'),
- customerCount: 0,
- });
- await adminClient.asSuperAdmin();
- }, TEST_SETUP_TIMEOUT_MS);
- afterAll(async () => {
- await server.destroy();
- });
- it('imports products', async () => {
- // TODO: waste a few more hours actually fixing this for real
- // Forgive me this abomination of a work-around.
- // On the inital run (as in CI), when the sqlite db has just been populated,
- // this test will fail due to an "out of memory" exception originating from
- // SqljsQueryRunner.ts:79:22, which is part of the findOne() operation on the
- // Session repository called from the AuthService.validateSession() method.
- // After several hours of fruitless hunting, I did what any desperate JavaScript
- // developer would do, and threw in a setTimeout. Which of course "works"...
- const timeout = process.env.CI ? 2000 : 1000;
- await new Promise(resolve => {
- setTimeout(resolve, timeout);
- });
- const csvFile = path.join(__dirname, 'fixtures', 'product-import.csv');
- const result = await adminClient.fileUploadMutation({
- mutation: importProductsDocument1,
- filePaths: [csvFile],
- mapVariables: () => ({ csvFile: null }),
- });
- expect(result.importProducts.errors).toEqual([
- 'Invalid Record Length: header length is 20, got 1 on line 8',
- ]);
- expect(result.importProducts.imported).toBe(4);
- expect(result.importProducts.processed).toBe(4);
- const productResult = await adminClient.query(getProductsDocument1, {
- options: {},
- });
- expect(productResult.products.totalItems).toBe(4);
- const paperStretcher = productResult.products.items.find(
- (p: any) => p.name === 'Perfect Paper Stretcher',
- );
- const easel = productResult.products.items.find((p: any) => p.name === 'Mabef M/02 Studio Easel');
- const pencils = productResult.products.items.find((p: any) => p.name === 'Giotto Mega Pencils');
- const smock = productResult.products.items.find((p: any) => p.name === 'Artists Smock');
- if (!paperStretcher || !easel || !pencils || !smock) {
- throw new Error('Expected all products to be found');
- }
- // Omit FacetValues & options due to variations in the ordering between different DB engines
- expect(omit(paperStretcher, ['facetValues', 'options'], true)).toMatchSnapshot();
- expect(omit(easel, ['facetValues', 'options'], true)).toMatchSnapshot();
- expect(omit(pencils, ['facetValues', 'options'], true)).toMatchSnapshot();
- expect(omit(smock, ['facetValues', 'options'], true)).toMatchSnapshot();
- const byName = (e: { name: string }) => e.name;
- const byCode = (e: { code: string }) => e.code;
- expect(paperStretcher.facetValues).toEqual([]);
- expect(easel.facetValues).toEqual([]);
- expect(pencils.facetValues).toEqual([]);
- expect(smock.facetValues.map(byName).sort()).toEqual(['Denim', 'clothes']);
- expect(paperStretcher.variants[0].facetValues.map(byName).sort()).toEqual(['Accessory', 'KB']);
- expect(paperStretcher.variants[1].facetValues.map(byName).sort()).toEqual(['Accessory', 'KB']);
- expect(paperStretcher.variants[2].facetValues.map(byName).sort()).toEqual(['Accessory', 'KB']);
- expect(paperStretcher.variants[0].options.map(byCode).sort()).toEqual(['half-imperial']);
- expect(paperStretcher.variants[1].options.map(byCode).sort()).toEqual(['quarter-imperial']);
- expect(paperStretcher.variants[2].options.map(byCode).sort()).toEqual(['full-imperial']);
- expect(easel.variants[0].facetValues.map(byName).sort()).toEqual(['Easel', 'Mabef']);
- expect(pencils.variants[0].facetValues.map(byName).sort()).toEqual(['Xmas Sale']);
- expect(pencils.variants[1].facetValues.map(byName).sort()).toEqual(['Xmas Sale']);
- expect(pencils.variants[0].options.map(byCode).sort()).toEqual(['box-of-8']);
- expect(pencils.variants[1].options.map(byCode).sort()).toEqual(['box-of-12']);
- expect(smock.variants[0].facetValues.map(byName).sort()).toEqual([]);
- expect(smock.variants[1].facetValues.map(byName).sort()).toEqual([]);
- expect(smock.variants[2].facetValues.map(byName).sort()).toEqual([]);
- expect(smock.variants[3].facetValues.map(byName).sort()).toEqual([]);
- expect(smock.variants[0].options.map(byCode).sort()).toEqual(['beige', 'small']);
- expect(smock.variants[1].options.map(byCode).sort()).toEqual(['beige', 'large']);
- expect(smock.variants[2].options.map(byCode).sort()).toEqual(['navy', 'small']);
- expect(smock.variants[3].options.map(byCode).sort()).toEqual(['large', 'navy']);
- // Import relation custom fields
- expect(paperStretcher.customFields.owner.id).toBe('T_1');
- expect(easel.customFields.owner.id).toBe('T_1');
- expect(pencils.customFields.owner.id).toBe('T_1');
- expect(smock.customFields.owner.id).toBe('T_1');
- // Import non-list custom fields
- expect(smock.variants[0].customFields.valid).toEqual(true);
- expect(smock.variants[0].customFields.weight).toEqual(500);
- expect(smock.variants[1].customFields.valid).toEqual(false);
- expect(smock.variants[1].customFields.weight).toEqual(500);
- expect(smock.variants[2].customFields.valid).toEqual(null);
- expect(smock.variants[2].customFields.weight).toEqual(500);
- expect(smock.variants[3].customFields.valid).toEqual(true);
- expect(smock.variants[3].customFields.weight).toEqual(500);
- expect(smock.variants[4].customFields.valid).toEqual(false);
- expect(smock.variants[4].customFields.weight).toEqual(null);
- // Import list custom fields
- expect(paperStretcher.customFields.keywords).toEqual(['paper', 'stretching', 'watercolor']);
- expect(easel.customFields.keywords).toEqual([]);
- expect(pencils.customFields.keywords).toEqual([]);
- expect(smock.customFields.keywords).toEqual(['apron', 'clothing']);
- // Import localeString custom fields
- expect(paperStretcher.customFields.localName).toEqual('localPPS');
- expect(easel.customFields.localName).toEqual('localMabef');
- expect(pencils.customFields.localName).toEqual('localGiotto');
- expect(smock.customFields.localName).toEqual('localSmock');
- }, 20000);
- it('imports products with multiple languages', async () => {
- // TODO: see test above
- const timeout = process.env.CI ? 2000 : 1000;
- await new Promise(resolve => {
- setTimeout(resolve, timeout);
- });
- const csvFile = path.join(__dirname, 'fixtures', 'e2e-product-import-multi-languages.csv');
- const result = await adminClient.fileUploadMutation({
- mutation: importProductsDocument2,
- filePaths: [csvFile],
- mapVariables: () => ({ csvFile: null }),
- });
- expect(result.importProducts.errors).toEqual([]);
- expect(result.importProducts.imported).toBe(1);
- expect(result.importProducts.processed).toBe(1);
- const productResult = await adminClient.query(
- getProductsDocument2,
- {
- options: {},
- },
- {
- languageCode: 'zh_Hans',
- },
- );
- expect(productResult.products.totalItems).toBe(5);
- const paperStretcher = productResult.products.items.find((p: any) => p.name === '奇妙的纸张拉伸器');
- if (!paperStretcher) {
- throw new Error('Expected paperStretcher to be found');
- }
- // Omit FacetValues & options due to variations in the ordering between different DB engines
- expect(omit(paperStretcher, ['facetValues', 'options'], true)).toMatchSnapshot();
- const byName = (e: { name: string }) => e.name;
- expect(paperStretcher.facetValues.map(byName).sort()).toEqual(['KB', '饰品']);
- expect(paperStretcher.variants[0].options.map(byName).sort()).toEqual(['半英制']);
- expect(paperStretcher.variants[1].options.map(byName).sort()).toEqual(['四分之一英制']);
- expect(paperStretcher.variants[2].options.map(byName).sort()).toEqual(['全英制']);
- // Import list custom fields
- expect(paperStretcher.customFields.keywords).toEqual(['paper, stretch']);
- // Import localeString custom fields
- expect(paperStretcher.customFields.localName).toEqual('纸张拉伸器');
- }, 20000);
- describe('asset urls', () => {
- let staticServer: http.Server;
- beforeAll(() => {
- // Set up minimal static file server
- staticServer = http
- .createServer((req, res) => {
- const filePath = path.join(__dirname, 'fixtures/assets', req?.url ?? '');
- fs.readFile(filePath, (err, data) => {
- if (err) {
- res.writeHead(404);
- res.end(JSON.stringify(err));
- return;
- }
- res.writeHead(200);
- res.end(data);
- });
- })
- .listen(3456);
- });
- afterAll(() => {
- if (staticServer) {
- return new Promise<void>((resolve, reject) => {
- staticServer.close(err => {
- if (err) {
- reject(err);
- } else {
- resolve();
- }
- });
- });
- }
- });
- it('imports assets with url paths', async () => {
- const timeout = process.env.CI ? 2000 : 1000;
- await new Promise(resolve => {
- setTimeout(resolve, timeout);
- });
- const csvFile = path.join(__dirname, 'fixtures', 'e2e-product-import-asset-urls.csv');
- const result = await adminClient.fileUploadMutation({
- mutation: importProductsDocument3,
- filePaths: [csvFile],
- mapVariables: () => ({ csvFile: null }),
- });
- expect(result.importProducts.errors).toEqual([]);
- expect(result.importProducts.imported).toBe(1);
- expect(result.importProducts.processed).toBe(1);
- const productResult = await adminClient.query(getProductsDocument3, {
- options: {
- filter: {
- name: { contains: 'guitar' },
- },
- },
- });
- expect(productResult.products.items.length).toBe(1);
- expect(productResult.products.items[0].featuredAsset!.preview).toBe(
- 'test-url/test-assets/guitar__preview.jpg',
- );
- });
- });
- });
- const importProductsDocument1 = graphql(`
- mutation ImportProducts($csvFile: Upload!) {
- importProducts(csvFile: $csvFile) {
- imported
- processed
- errors
- }
- }
- `);
- const getProductsDocument1 = graphql(`
- query GetProducts($options: ProductListOptions) {
- products(options: $options) {
- totalItems
- items {
- id
- name
- slug
- description
- featuredAsset {
- id
- name
- preview
- source
- }
- assets {
- id
- name
- preview
- source
- }
- optionGroups {
- id
- code
- name
- }
- facetValues {
- id
- name
- facet {
- id
- name
- }
- }
- customFields {
- pageType
- owner {
- id
- }
- keywords
- localName
- }
- variants {
- id
- name
- sku
- price
- taxCategory {
- id
- name
- }
- options {
- id
- code
- }
- assets {
- id
- name
- preview
- source
- }
- featuredAsset {
- id
- name
- preview
- source
- }
- facetValues {
- id
- code
- name
- facet {
- id
- name
- }
- }
- stockOnHand
- trackInventory
- stockMovements {
- items {
- ... on StockMovement {
- id
- type
- quantity
- }
- }
- }
- customFields {
- valid
- weight
- }
- }
- }
- }
- }
- `);
- const importProductsDocument2 = graphql(`
- mutation ImportProducts($csvFile: Upload!) {
- importProducts(csvFile: $csvFile) {
- imported
- processed
- errors
- }
- }
- `);
- const getProductsDocument2 = graphql(`
- query GetProducts($options: ProductListOptions) {
- products(options: $options) {
- totalItems
- items {
- id
- name
- slug
- description
- featuredAsset {
- id
- name
- preview
- source
- }
- assets {
- id
- name
- preview
- source
- }
- optionGroups {
- id
- code
- name
- }
- facetValues {
- id
- name
- facet {
- id
- name
- }
- }
- customFields {
- pageType
- owner {
- id
- }
- keywords
- localName
- }
- variants {
- id
- name
- sku
- price
- taxCategory {
- id
- name
- }
- options {
- id
- code
- name
- }
- assets {
- id
- name
- preview
- source
- }
- featuredAsset {
- id
- name
- preview
- source
- }
- facetValues {
- id
- code
- name
- facet {
- id
- name
- }
- }
- stockOnHand
- trackInventory
- stockMovements {
- items {
- ... on StockMovement {
- id
- type
- quantity
- }
- }
- }
- customFields {
- weight
- }
- }
- }
- }
- }
- `);
- const importProductsDocument3 = graphql(`
- mutation ImportProducts($csvFile: Upload!) {
- importProducts(csvFile: $csvFile) {
- imported
- processed
- errors
- }
- }
- `);
- const getProductsDocument3 = graphql(`
- query GetProducts($options: ProductListOptions) {
- products(options: $options) {
- totalItems
- items {
- id
- name
- featuredAsset {
- id
- name
- preview
- }
- }
- }
- }
- `);
|