Skip to content

Commit

Permalink
tests
Browse files Browse the repository at this point in the history
  • Loading branch information
iartemiev committed Oct 15, 2024
1 parent c17e6f8 commit 41d1b73
Show file tree
Hide file tree
Showing 4 changed files with 198 additions and 98 deletions.
238 changes: 182 additions & 56 deletions packages/api-graphql/__tests__/events.test.ts
Original file line number Diff line number Diff line change
@@ -1,78 +1,204 @@
import { Subscription } from 'rxjs';
import { Amplify } from '@aws-amplify/core';
import { MESSAGE_TYPES } from '../src/Providers/constants';
import * as constants from '../src/Providers/constants';

import {
delay,
FakeWebSocketInterface,
replaceConstant,
} from '../__tests__/helpers';
import { ConnectionState as CS } from '../src/types/PubSub';

import { AWSAppSyncEventProvider } from '../src/Providers/AWSAppSyncEventsProvider';
import { AppSyncEventProvider } from '../src/Providers/AWSAppSyncEventsProvider';

import { events } from '../src/';
import { appsyncRequest } from '../src/internals/events/appsyncRequest';
import { GraphQLAuthMode } from '@aws-amplify/core/internals/utils';

/**
* TODO:
* 1. gen2 config
* 2. manual config
* 3. all auth modes
* 4. ensure auth works as expected for all modes/locations
*/

test('no configure()', async () => {
await expect(events.connect('/')).rejects.toThrow(
'Amplify configuration is missing. Have you called Amplify.configure()?',
);
const abortController = new AbortController();

var mockSubscribeObservable: any;

jest.mock('../src/Providers/AWSAppSyncEventsProvider', () => {
mockSubscribeObservable = jest.fn(() => ({
subscribe: jest.fn(),
}));

return {
AppSyncEventProvider: {
connect: jest.fn(),
subscribe: jest.fn(mockSubscribeObservable),
publish: jest.fn(),
close: jest.fn(),
},
};
});

jest.mock('../src/internals/events/appsyncRequest', () => {
return {
appsyncRequest: jest.fn().mockResolvedValue({}),
};
});

describe('Events Client', () => {
beforeEach(() => {
Amplify.configure({
custom: {
events: {
url: 'https://not-a-real.ddpg-api.us-west-2.amazonaws.com/event',
aws_region: 'us-west-2',
default_authorization_type: 'API_KEY',
api_key: 'da2-abcxyz321',
describe('Events', () => {
afterAll(() => {
jest.resetAllMocks();
jest.clearAllMocks();
});

describe('config', () => {
test('no configure()', async () => {
await expect(events.connect('/')).rejects.toThrow(
'Amplify configuration is missing. Have you called Amplify.configure()?',
);
});

test('manual (resource config)', async () => {
Amplify.configure({
API: {
Events: {
endpoint:
'https://not-a-real.ddpg-api.us-west-2.amazonaws.com/event',
region: 'us-west-2',
defaultAuthMode: 'apiKey',
apiKey: 'da2-abcxyz321',
},
},
});

await expect(events.connect('/')).resolves.not.toThrow();
});

test('outputs (amplify-backend config)', async () => {
Amplify.configure({
custom: {
events: {
url: 'https://not-a-real.ddpg-api.us-west-2.amazonaws.com/event',
aws_region: 'us-west-2',
default_authorization_type: 'API_KEY',
api_key: 'da2-abcxyz321',
},
},
},
version: '1.2',
version: '1.2',
});

await expect(events.connect('/')).resolves.not.toThrow();
});
});

const authModes: GraphQLAuthMode[] = [
'apiKey',
'userPool',
'oidc',
'iam',
'lambda',
'none',
];

describe('channel', () => {
test('happy connect', async () => {
const channel = await events.connect('/');

expect(channel.subscribe).toBeInstanceOf(Function);
expect(channel.close).toBeInstanceOf(Function);
describe('client', () => {
beforeEach(() => {
Amplify.configure({
API: {
Events: {
endpoint:
'https://not-a-real.ddpg-api.us-west-2.amazonaws.com/event',
region: 'us-west-2',
defaultAuthMode: 'apiKey',
apiKey: 'da2-abcxyz321',
},
},
});
});

describe('auth modes', () => {
let provider: typeof AWSAppSyncEventProvider;
let providerSpy: any;
const authModes: GraphQLAuthMode[] = [
'apiKey',
'userPool',
'oidc',
'iam',
'lambda',
'none',
];

describe('channel', () => {
test('happy connect', async () => {
const channel = await events.connect('/');

expect(channel.subscribe).toBeInstanceOf(Function);
expect(channel.close).toBeInstanceOf(Function);
});

describe('auth modes', () => {
let mockProvider: typeof AppSyncEventProvider;

beforeEach(() => {
mockProvider = AppSyncEventProvider;
});

for (const authMode of authModes) {
test(`auth override: ${authMode}`, async () => {
await events.connect('/', { authMode });

expect(mockProvider.connect).toHaveBeenCalledWith(
expect.objectContaining({ authenticationType: authMode }),
);
});
}
});
});

describe('subscribe', () => {
test('happy subscribe', async () => {
const channel = await events.connect('/');

channel.subscribe({
next: data => void data,
error: error => void error,
});
});

describe('auth modes', () => {
let mockProvider: typeof AppSyncEventProvider;

beforeEach(() => {
mockProvider = AppSyncEventProvider;
});

for (const authMode of authModes) {
test(`auth override: ${authMode}`, async () => {
const channel = await events.connect('/');

channel.subscribe(
{
next: data => void data,
error: error => void error,
},
{ authMode },
);

expect(mockSubscribeObservable).toHaveBeenCalledWith(
expect.objectContaining({ authenticationType: authMode }),
);
});
}
});
});

describe('post', () => {
let mockReq: typeof appsyncRequest;

beforeEach(() => {
provider = new AWSAppSyncEventProvider();
providerSpy = jest.spyOn(provider, 'connect');
mockReq = appsyncRequest;
});

test('happy post', async () => {
await events.post('/', { test: 'data' });

expect(mockReq).toHaveBeenCalledWith(
Amplify,
expect.objectContaining({
query: '/',
variables: ['{"test":"data"}'],
}),
{},
abortController,
);
});

for (const authMode of authModes) {
test(`auth override: ${authMode}`, async () => {
await events.connect('/', { authMode });
await events.post('/', { test: 'data' }, { authMode });

expect(mockReq).toHaveBeenCalledWith(
Amplify,
expect.objectContaining({
query: '/',
variables: ['{"test":"data"}'],
authenticationType: authMode,
}),
{},
abortController,
);
});
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { CustomHeaders } from '@aws-amplify/data-schema/runtime';
import { MESSAGE_TYPES } from '../constants';
import { AWSWebSocketProvider } from '../AWSWebSocketProvider';
import { awsRealTimeHeaderBasedAuth } from '../AWSWebSocketProvider/authHeaders';

// resolved/actual AuthMode values. identityPool gets resolves to IAM upstream in InternalGraphQLAPI._graphqlSubscribe
type ResolvedGraphQLAuthModes = Exclude<GraphQLAuthMode, 'identityPool'>;

Expand Down Expand Up @@ -42,7 +43,7 @@ interface DataResponse {

const PROVIDER_NAME = 'AWSAppSyncEventsProvider';

export class AWSAppSyncEventProvider extends AWSWebSocketProvider {
class AWSAppSyncEventProvider extends AWSWebSocketProvider {
constructor() {
super(PROVIDER_NAME);
}
Expand Down Expand Up @@ -182,3 +183,5 @@ export class AWSAppSyncEventProvider extends AWSWebSocketProvider {
};
}
}

export const AppSyncEventProvider = new AWSAppSyncEventProvider();
6 changes: 1 addition & 5 deletions packages/api-graphql/src/internals/events/appsyncRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@ import {
GraphQLAuthMode,
getAmplifyUserAgent,
} from '@aws-amplify/core/internals/utils';
import {
// cancel as cancelREST,
post,
// updateRequestToBeCancellable,
} from '@aws-amplify/api-rest/internals';
import { post } from '@aws-amplify/api-rest/internals';
import {
CustomHeaders,
RequestOptions,
Expand Down
47 changes: 11 additions & 36 deletions packages/api-graphql/src/internals/events/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { Subscription } from 'rxjs';
import { Amplify } from '@aws-amplify/core';
import { DocumentType } from '@aws-amplify/core/internals/utils';

import { AWSAppSyncEventProvider } from '../../Providers/AWSAppSyncEventsProvider';
import { AppSyncEventProvider as eventProvider } from '../../Providers/AWSAppSyncEventsProvider';

import { appsyncRequest } from './appsyncRequest';
import { configure, normalizeAuth } from './utils';
import { configure, normalizeAuth, serializeEvents } from './utils';
import type {
EventsChannel,
EventsOptions,
Expand All @@ -17,12 +17,9 @@ import type {
SubscriptionObserver,
} from './types';

const eventProvider = new AWSAppSyncEventProvider();

/**
* Establish a WebSocket connection to an Events channel
*
* @param channelName
* @param options
*/
async function connect(
channelName: string,
Expand Down Expand Up @@ -79,42 +76,20 @@ async function connect(
};

return {
// WS publish is not enabled in the service yet, will be a follow up feature
// publish: pub,
/**
* Subscribe to incoming events
*/
subscribe: sub,
/**
* Close channel and any active subscriptions
*/
close,
// publish: pub,
};
}

/**
* Event API expects and array of JSON strings
*
* @param events - JSON-serializable value or an array of values
* @returns array of JSON strings
*/
const serializeEvents = (events: DocumentType | DocumentType[]): string[] => {
if (Array.isArray(events)) {
return events.map((ev, idx) => {
const eventJson = JSON.stringify(ev);
if (eventJson === undefined) {
throw new Error(
`Event must be a valid JSON value. Received ${ev} at index ${idx}`,
);
}

return eventJson;
});
}

const eventJson = JSON.stringify(events);
if (eventJson === undefined) {
throw new Error(`Event must be a valid JSON value. Received ${events}`);
}

return [eventJson];
};

/**
* Publish events to a channel via REST request
*
* @param channelName - publish destination
* @param event - JSON-serializable value or an array of values
Expand Down

0 comments on commit 41d1b73

Please sign in to comment.