Browse Source

refactor(testing): Populate customers without using graphql api

Michael Bromley 4 years ago
parent
commit
eefd659eaf

+ 2 - 2
packages/dev-server/load-testing/init-load-test.ts

@@ -1,6 +1,6 @@
 // tslint:disable-next-line:no-reference
 /// <reference path="../../core/typings.d.ts" />
-import { bootstrap, JobQueueService } from '@vendure/core';
+import { bootstrap, JobQueueService, Logger } from '@vendure/core';
 import { populate } from '@vendure/core/cli/populate';
 import { BaseProductRecord } from '@vendure/core/dist/data-import/providers/import-parser/import-parser';
 import { clearAllTables, populateCustomers } from '@vendure/testing';
@@ -51,7 +51,7 @@ if (require.main === module) {
                     )
                     .then(async app => {
                         console.log('populating customers...');
-                        await populateCustomers(10, config, true);
+                        await populateCustomers(app, 10, message => Logger.error(message));
                         return app.close();
                     });
             } else {

+ 2 - 2
packages/dev-server/populate-dev-server.ts

@@ -1,6 +1,6 @@
 // tslint:disable-next-line:no-reference
 /// <reference path="../core/typings.d.ts" />
-import { bootstrap, defaultConfig, JobQueueService, mergeConfig } from '@vendure/core';
+import { bootstrap, defaultConfig, JobQueueService, Logger, mergeConfig } from '@vendure/core';
 import { populate } from '@vendure/core/cli';
 import { clearAllTables, populateCustomers } from '@vendure/testing';
 import path from 'path';
@@ -43,7 +43,7 @@ if (require.main === module) {
         )
         .then(async app => {
             console.log('populating customers...');
-            await populateCustomers(10, populateConfig, true);
+            await populateCustomers(app, 10, message => Logger.error(message));
             return app.close();
         })
         .then(

+ 33 - 1
packages/testing/src/data-population/mock-data.service.ts

@@ -1,3 +1,4 @@
+import { CreateAddressInput, CreateCustomerInput } from '@vendure/common/lib/generated-types';
 import faker from 'faker/locale/en_GB';
 import gql from 'graphql-tag';
 
@@ -15,6 +16,37 @@ export class MockDataService {
         faker.seed(1);
     }
 
+    static getCustomers(
+        count: number,
+    ): Array<{ customer: CreateCustomerInput; address: CreateAddressInput }> {
+        faker.seed(1);
+        const results: Array<{ customer: CreateCustomerInput; address: CreateAddressInput }> = [];
+        for (let i = 0; i < count; i++) {
+            const firstName = faker.name.firstName();
+            const lastName = faker.name.lastName();
+            const customer: CreateCustomerInput = {
+                firstName,
+                lastName,
+                emailAddress: faker.internet.email(firstName, lastName),
+                phoneNumber: faker.phone.phoneNumber(),
+            };
+            const address: CreateAddressInput = {
+                fullName: `${firstName} ${lastName}`,
+                streetLine1: faker.address.streetAddress(),
+                city: faker.address.city(),
+                province: faker.address.county(),
+                postalCode: faker.address.zipCode(),
+                countryCode: 'GB',
+            };
+            results.push({ customer, address });
+        }
+        return results;
+    }
+
+    /**
+     * @deprecated
+     * Use `MockDataService.getCustomers()` and create customers directly with CustomerService.
+     */
     async populateCustomers(count: number = 5): Promise<any> {
         for (let i = 0; i < count; i++) {
             const firstName = faker.name.firstName();
@@ -50,7 +82,7 @@ export class MockDataService {
 
             if (customer) {
                 const query2 = gql`
-                    mutation($customerId: ID!, $input: CreateAddressInput!) {
+                    mutation ($customerId: ID!, $input: CreateAddressInput!) {
                         createCustomerAddress(customerId: $customerId, input: $input) {
                             id
                             streetLine1

+ 21 - 12
packages/testing/src/data-population/populate-customers.ts

@@ -1,6 +1,7 @@
-import { VendureConfig } from '@vendure/core';
+import { INestApplicationContext } from '@nestjs/common';
+import { CustomerService, isGraphQlErrorResult, RequestContext } from '@vendure/core';
 
-import { SimpleGraphQLClient } from '../simple-graphql-client';
+import { getSuperadminContext } from '../utils/get-superadmin-context';
 
 import { MockDataService } from './mock-data.service';
 
@@ -8,16 +9,24 @@ import { MockDataService } from './mock-data.service';
  * Creates customers with addresses by making API calls to the Admin API.
  */
 export async function populateCustomers(
+    app: INestApplicationContext,
     count: number,
-    config: Required<VendureConfig>,
-    logging: boolean = false,
-    simpleGraphQLClient = new SimpleGraphQLClient(
-        config,
-        `http://localhost:${config.apiOptions.port}/${config.apiOptions.adminApiPath}`,
-    ),
+    loggingFn: (message: string) => void,
 ) {
-    const client = simpleGraphQLClient;
-    await client.asSuperAdmin();
-    const mockDataService = new MockDataService(client, logging);
-    await mockDataService.populateCustomers(count);
+    const customerService = app.get(CustomerService);
+    const customerData = MockDataService.getCustomers(count);
+    const ctx = await getSuperadminContext(app);
+    const password = 'test';
+    for (const { customer, address } of customerData) {
+        try {
+            const createdCustomer = await customerService.create(ctx, customer, password);
+            if (isGraphQlErrorResult(createdCustomer)) {
+                loggingFn(`Failed to create customer: ${createdCustomer.message}`);
+                continue;
+            }
+            await customerService.createAddress(ctx, createdCustomer.id, address);
+        } catch (e) {
+            loggingFn(`Failed to create customer: ${e.message}`);
+        }
+    }
 }

+ 1 - 1
packages/testing/src/data-population/populate-for-testing.ts

@@ -29,7 +29,7 @@ export async function populateForTesting<T extends INestApplicationContext>(
     await populateInitialData(app, options.initialData, logFn);
     await populateProducts(app, options.productsCsvPath, logging);
     await populateCollections(app, options.initialData, logFn);
-    await populateCustomers(options.customerCount ?? 10, config, logging);
+    await populateCustomers(app, options.customerCount ?? 10, logFn);
 
     config.authOptions.requireVerification = originalRequireVerification;
     return app;

+ 1 - 0
packages/testing/src/index.ts

@@ -12,3 +12,4 @@ export * from './initializers/postgres-initializer';
 export * from './initializers/sqljs-initializer';
 export * from './testing-logger';
 export * from './types';
+export * from './utils/get-default-channel-token';

+ 9 - 1
packages/testing/src/initializers/sqljs-initializer.ts

@@ -9,7 +9,14 @@ import { TestDbInitializer } from './test-db-initializer';
 export class SqljsInitializer implements TestDbInitializer<SqljsConnectionOptions> {
     private dbFilePath: string;
     private connectionOptions: SqljsConnectionOptions;
-    constructor(private dataDir: string) {}
+
+    /**
+     * @param dataDir
+     * @param postPopulateTimeoutMs Allows you to specify a timeout to wait after the population
+     * step and before the server is shut down. Can resolve occasional race condition issues with
+     * the job queue.
+     */
+    constructor(private dataDir: string, private postPopulateTimeoutMs: number = 0) {}
 
     async init(
         testFileName: string,
@@ -30,6 +37,7 @@ export class SqljsInitializer implements TestDbInitializer<SqljsConnectionOption
             (this.connectionOptions as Mutable<SqljsConnectionOptions>).autoSave = true;
             (this.connectionOptions as Mutable<SqljsConnectionOptions>).synchronize = true;
             await populateFn();
+            await new Promise(resolve => setTimeout(resolve, this.postPopulateTimeoutMs));
             (this.connectionOptions as Mutable<SqljsConnectionOptions>).autoSave = false;
             (this.connectionOptions as Mutable<SqljsConnectionOptions>).synchronize = false;
         }

+ 37 - 0
packages/testing/src/utils/get-superadmin-context.ts

@@ -0,0 +1,37 @@
+import { INestApplicationContext } from '@nestjs/common';
+import { ChannelService, ConfigService, RequestContext, TransactionalConnection, User } from '@vendure/core';
+
+/**
+ * @description
+ * Creates a {@link RequestContext} configured for the default Channel with the activeUser set
+ * as the superadmin user. Useful for populating data.
+ *
+ * @docsCategory testing
+ */
+export async function getSuperadminContext(app: INestApplicationContext): Promise<RequestContext> {
+    const defaultChannel = await app.get(ChannelService).getDefaultChannel();
+    const connection = app.get(TransactionalConnection);
+    const configService = app.get(ConfigService);
+    const { superadminCredentials } = configService.authOptions;
+    const superAdminUser = await connection
+        .getRepository(User)
+        .findOneOrFail({ where: { identifier: superadminCredentials.identifier } });
+    return new RequestContext({
+        channel: defaultChannel,
+        apiType: 'admin',
+        isAuthorized: true,
+        authorizedAsOwnerOnly: false,
+        session: {
+            id: '',
+            token: '',
+            expires: new Date(),
+            cacheExpiry: 999999,
+            user: {
+                id: superAdminUser.id,
+                identifier: superAdminUser.identifier,
+                verified: true,
+                channelPermissions: [],
+            },
+        },
+    });
+}