Browse Source

test(core): Add e2e tests for AuthenticationStrategy

Michael Bromley 5 years ago
parent
commit
b3630d4693

+ 130 - 0
packages/core/e2e/authentication-strategy.e2e-spec.ts

@@ -0,0 +1,130 @@
+import { mergeConfig } from '@vendure/core';
+import { createTestEnvironment } from '@vendure/testing';
+import gql from 'graphql-tag';
+import path from 'path';
+
+import { initialData } from '../../../e2e-common/e2e-initial-data';
+import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
+
+import { TestAuthenticationStrategy, VALID_AUTH_TOKEN } from './fixtures/test-authentication-strategies';
+import { Authenticate, GetCustomers, Me } from './graphql/generated-e2e-admin-types';
+import { ME } from './graphql/shared-definitions';
+import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
+
+describe('AuthenticationStrategy', () => {
+    const { server, adminClient, shopClient } = createTestEnvironment(
+        mergeConfig(testConfig, {
+            authOptions: {
+                shopAuthenticationStrategy: [new TestAuthenticationStrategy()],
+            },
+        }),
+    );
+
+    beforeAll(async () => {
+        await server.init({
+            initialData,
+            productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
+            customerCount: 1,
+        });
+        await adminClient.asSuperAdmin();
+    }, TEST_SETUP_TIMEOUT_MS);
+
+    afterAll(async () => {
+        await server.destroy();
+    });
+
+    describe('external auth', () => {
+        const userData = {
+            email: 'test@email.com',
+            firstName: 'Cixin',
+            lastName: 'Liu',
+        };
+
+        it(
+            'fails with a bad token',
+            assertThrowsWithMessage(async () => {
+                await shopClient.query(AUTHENTICATE, {
+                    input: {
+                        test_strategy: {
+                            token: 'bad-token',
+                        },
+                    },
+                });
+            }, 'The credentials did not match. Please check and try again'),
+        );
+
+        it('creates a new Customer with valid token', async () => {
+            const { customers: before } = await adminClient.query<GetCustomers.Query>(GET_CUSTOMERS);
+            expect(before.totalItems).toBe(1);
+
+            const result = await shopClient.query<Authenticate.Mutation>(AUTHENTICATE, {
+                input: {
+                    test_strategy: {
+                        token: VALID_AUTH_TOKEN,
+                        userData,
+                    },
+                },
+            });
+            expect(result.authenticate.user.identifier).toEqual(userData.email);
+
+            const { customers: after } = await adminClient.query<GetCustomers.Query>(GET_CUSTOMERS);
+            expect(after.totalItems).toBe(2);
+            expect(after.items.map(i => i.emailAddress)).toEqual([
+                'hayden.zieme12@hotmail.com',
+                userData.email,
+            ]);
+        });
+
+        it('creates authenticated session', async () => {
+            const { me } = await shopClient.query<Me.Query>(ME);
+
+            expect(me?.identifier).toBe(userData.email);
+        });
+
+        it('log out', async () => {
+            await shopClient.asAnonymousUser();
+        });
+
+        it('logging in again re-uses created User & Customer', async () => {
+            const result = await shopClient.query<Authenticate.Mutation>(AUTHENTICATE, {
+                input: {
+                    test_strategy: {
+                        token: VALID_AUTH_TOKEN,
+                        userData,
+                    },
+                },
+            });
+            expect(result.authenticate.user.identifier).toEqual(userData.email);
+
+            const { customers: after } = await adminClient.query<GetCustomers.Query>(GET_CUSTOMERS);
+            expect(after.totalItems).toBe(2);
+            expect(after.items.map(i => i.emailAddress)).toEqual([
+                'hayden.zieme12@hotmail.com',
+                userData.email,
+            ]);
+        });
+    });
+});
+
+const AUTHENTICATE = gql`
+    mutation Authenticate($input: AuthenticationInput!) {
+        authenticate(input: $input) {
+            user {
+                id
+                identifier
+            }
+        }
+    }
+`;
+
+const GET_CUSTOMERS = gql`
+    query GetCustomers {
+        customers {
+            totalItems
+            items {
+                id
+                emailAddress
+            }
+        }
+    }
+`;

+ 98 - 0
packages/core/e2e/fixtures/test-authentication-strategies.ts

@@ -0,0 +1,98 @@
+import {
+    AuthenticationStrategy,
+    Customer,
+    ExternalAuthenticationMethod,
+    Injector,
+    RequestContext,
+    RoleService,
+    User,
+} from '@vendure/core';
+import { DocumentNode } from 'graphql';
+import gql from 'graphql-tag';
+import { Connection } from 'typeorm';
+
+export const VALID_AUTH_TOKEN = 'valid-auth-token';
+
+export type TestAuthPayload = {
+    token: string;
+    userData: {
+        email: string;
+        firstName: string;
+        lastName: string;
+    };
+};
+
+export class TestAuthenticationStrategy implements AuthenticationStrategy<TestAuthPayload> {
+    readonly name = 'test_strategy';
+    private connection: Connection;
+    private roleService: RoleService;
+
+    init(injector: Injector) {
+        this.connection = injector.getConnection();
+        this.roleService = injector.get(RoleService);
+    }
+
+    defineInputType(): DocumentNode {
+        return gql`
+            input TestAuthInput {
+                token: String!
+                userData: UserDataInput
+            }
+
+            input UserDataInput {
+                email: String!
+                firstName: String!
+                lastName: String!
+            }
+        `;
+    }
+
+    async authenticate(ctx: RequestContext, data: TestAuthPayload): Promise<User | false> {
+        const { token, userData } = data;
+        if (data.token !== VALID_AUTH_TOKEN) {
+            return false;
+        }
+        const user = await this.connection
+            .getRepository(User)
+            .createQueryBuilder('user')
+            .leftJoinAndSelect('user.authenticationMethods', 'authMethod')
+            .where('authMethod.externalIdentifier = :token', { token: data.token })
+            .getOne();
+
+        if (user) {
+            return user;
+        }
+        return this.createNewCustomerAndUser(data);
+    }
+
+    private async createNewCustomerAndUser(data: TestAuthPayload) {
+        const { token, userData } = data;
+        const customerRole = await this.roleService.getCustomerRole();
+        const newUser = new User({
+            identifier: data.userData.email,
+            roles: [customerRole],
+            verified: true,
+        });
+
+        const authMethod = await this.connection.manager.save(
+            new ExternalAuthenticationMethod({
+                externalIdentifier: data.token,
+                provider: this.name,
+            }),
+        );
+
+        newUser.authenticationMethods = [authMethod];
+        const savedUser = await this.connection.manager.save(newUser);
+
+        const customer = await this.connection.manager.save(
+            new Customer({
+                emailAddress: userData.email,
+                firstName: userData.firstName,
+                lastName: userData.lastName,
+                user: savedUser,
+            }),
+        );
+
+        return savedUser;
+    }
+}

+ 39 - 0
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -1771,7 +1771,9 @@ export type Mutation = {
     updateAsset: Asset;
     /** Delete an Asset */
     deleteAsset: DeletionResponse;
+    /** @deprecated Use `authenticate` mutation with the 'native' strategy instead. */
     login: LoginResult;
+    authenticate: LoginResult;
     logout: Scalars['Boolean'];
     /** Create a new Channel */
     createChannel: Channel;
@@ -1944,6 +1946,11 @@ export type MutationLoginArgs = {
     rememberMe?: Maybe<Scalars['Boolean']>;
 };
 
+export type MutationAuthenticateArgs = {
+    input: AuthenticationInput;
+    rememberMe?: Maybe<Scalars['Boolean']>;
+};
+
 export type MutationCreateChannelArgs = {
     input: CreateChannelInput;
 };
@@ -3683,6 +3690,24 @@ export type GetCustomerCountQuery = { __typename?: 'Query' } & {
     customers: { __typename?: 'CustomerList' } & Pick<CustomerList, 'totalItems'>;
 };
 
+export type AuthenticateMutationVariables = {
+    input: AuthenticationInput;
+};
+
+export type AuthenticateMutation = { __typename?: 'Mutation' } & {
+    authenticate: { __typename?: 'LoginResult' } & {
+        user: { __typename?: 'CurrentUser' } & Pick<CurrentUser, 'id' | 'identifier'>;
+    };
+};
+
+export type GetCustomersQueryVariables = {};
+
+export type GetCustomersQuery = { __typename?: 'Query' } & {
+    customers: { __typename?: 'CustomerList' } & Pick<CustomerList, 'totalItems'> & {
+            items: Array<{ __typename?: 'Customer' } & Pick<Customer, 'id' | 'emailAddress'>>;
+        };
+};
+
 export type GetChannelsQueryVariables = {};
 
 export type GetChannelsQuery = { __typename?: 'Query' } & {
@@ -5747,6 +5772,20 @@ export namespace GetCustomerCount {
     export type Customers = GetCustomerCountQuery['customers'];
 }
 
+export namespace Authenticate {
+    export type Variables = AuthenticateMutationVariables;
+    export type Mutation = AuthenticateMutation;
+    export type Authenticate = AuthenticateMutation['authenticate'];
+    export type User = AuthenticateMutation['authenticate']['user'];
+}
+
+export namespace GetCustomers {
+    export type Variables = GetCustomersQueryVariables;
+    export type Query = GetCustomersQuery;
+    export type Customers = GetCustomersQuery['customers'];
+    export type Items = NonNullable<GetCustomersQuery['customers']['items'][0]>;
+}
+
 export namespace GetChannels {
     export type Variables = GetChannelsQueryVariables;
     export type Query = GetChannelsQuery;