Skip to content

Commit

Permalink
chore(inapp): reduce todos, update error message, update tests and ad…
Browse files Browse the repository at this point in the history
…d thresholds (#12419)

* reduce todos and update error message for no userpoolId

* chore: add tests and update thresholds

* test to it

* update import path and error string
  • Loading branch information
Samaritan1011001 authored Oct 31, 2023
1 parent 5e48cb1 commit 3b7a1f3
Show file tree
Hide file tree
Showing 10 changed files with 348 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ jest.mock('@aws-amplify/core', () => ({
getCredentialsForIdentity: jest.fn(),
}));

jest.mock('@aws-amplify/core/lib/awsClients/cognitoIdentity');

jest.mock(
'./../../../src/providers/cognito/credentialsProvider/IdentityIdProvider',
() => ({
Expand Down Expand Up @@ -51,7 +49,7 @@ const validAuthConfig: ResourcesConfig = {
},
};
const inValidAuthConfig: ResourcesConfig = {
Auth: {},
Auth: undefined,
};
const disallowGuestAccessConfig: ResourcesConfig = {
Auth: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@
import { authAPITestParams } from './testUtils/authApiTestParams';
import { Amplify, Identity, ResourcesConfig } from '@aws-amplify/core';
import { DefaultIdentityIdStore } from '../../../src/providers/cognito/credentialsProvider/IdentityIdStore';

// TODO(V6): import these from top level core/ and not lib/
import * as cogId from '@aws-amplify/core/lib/awsClients/cognitoIdentity';
import * as cogId from '@aws-amplify/core/internals/aws-clients/cognitoIdentity';
import { cognitoIdentityIdProvider } from '../../../src/providers/cognito/credentialsProvider/IdentityIdProvider';
jest.mock('@aws-amplify/core/lib/awsClients/cognitoIdentity');
import { CognitoIdentityPoolConfig } from '@aws-amplify/core/internals/utils';

jest.mock('@aws-amplify/core/internals/aws-clients/cognitoIdentity');
jest.mock('../../../src/providers/cognito/credentialsProvider/IdentityIdStore');

type ArgumentTypes<F extends Function> = F extends (...args: infer A) => any
? A
: never;
const ampConfig: ResourcesConfig = {
Auth: {
Cognito: {
Expand Down Expand Up @@ -63,6 +60,7 @@ describe('Cognito IdentityId Provider Happy Path Cases:', () => {
);
expect(
await cognitoIdentityIdProvider({
authConfig: ampConfig.Auth!.Cognito as CognitoIdentityPoolConfig,
identityIdStore: mockDefaultIdentityIdStoreInstance,
})
).toBe(authAPITestParams.GuestIdentityId.id);
Expand Down Expand Up @@ -98,6 +96,7 @@ describe('Cognito IdentityId Provider Happy Path Cases:', () => {
);
expect(
await cognitoIdentityIdProvider({
authConfig: ampConfig.Auth!.Cognito as CognitoIdentityPoolConfig,
tokens: authAPITestParams.ValidAuthTokens,
identityIdStore: mockDefaultIdentityIdStoreInstance,
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import {
AuthConfig,
ConsoleLogger,
Identity,
KeyValueStorageInterface,
} from '@aws-amplify/core';
Expand All @@ -11,6 +12,8 @@ import { IdentityIdStorageKeys, IdentityIdStore } from './types';
import { getAuthStorageKeys } from '../tokenProvider/TokenStore';
import { AuthKeys } from '../tokenProvider/types';

const logger = new ConsoleLogger('DefaultIdentityIdStore');

export class DefaultIdentityIdStore implements IdentityIdStore {
keyValueStorage: KeyValueStorageInterface;
authConfig?: AuthConfig;
Expand Down Expand Up @@ -53,7 +56,7 @@ export class DefaultIdentityIdStore implements IdentityIdStore {
return null;
}
} catch (err) {
// TODO(v6): validate partial results with mobile implementation
logger.log('Error getting stored IdentityId.', err);
return null;
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"name": "@aws-amplify/core/internals/aws-clients/cognitoIdentity",
"types": "../../../lib-esm/awsClients/cognitoIdentity/index.d.ts",
"main": "../../../lib/awsClients/cognitoIdentity/index.js",
"module": "../../../lib-esm/awsClients/cognitoIdentity/index.js",
"react-native": "../../../lib-esm/awsClients/cognitoIdentity/index.js",
"sideEffects": false
}
6 changes: 6 additions & 0 deletions packages/core/src/singleton/Auth/utils/errorHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AmplifyErrorMap, AssertionFunction } from '../../../types';
export enum AuthConfigurationErrorCode {
AuthTokenConfigException = 'AuthTokenConfigException',
AuthUserPoolAndIdentityPoolException = 'AuthUserPoolAndIdentityPoolException',
AuthUserPoolException = 'AuthUserPoolException',
InvalidIdentityPoolIdException = 'InvalidIdentityPoolIdException',
OAuthNotConfigureException = 'OAuthNotConfigureException',
}
Expand All @@ -21,6 +22,11 @@ const authConfigurationErrorMap: AmplifyErrorMap<AuthConfigurationErrorCode> = {
recoverySuggestion:
'Make sure to call Amplify.configure in your app with UserPoolId and IdentityPoolId.',
},
[AuthConfigurationErrorCode.AuthUserPoolException]: {
message: 'Auth UserPool not configured.',
recoverySuggestion:
'Make sure to call Amplify.configure in your app with userPoolId and userPoolClientId.',
},
[AuthConfigurationErrorCode.InvalidIdentityPoolIdException]: {
message: 'Invalid identity pool id provided.',
recoverySuggestion:
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/singleton/Auth/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function assertTokenProviderConfig(

return assert(
assertionValid,
AuthConfigurationErrorCode.AuthTokenConfigException
AuthConfigurationErrorCode.AuthUserPoolException
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,274 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import type { InAppMessageCampaign as PinpointInAppMessage } from '@aws-amplify/core/internals/aws-clients/pinpoint';
import cloneDeep from 'lodash/cloneDeep';

import {
clearMemo,
extractContent,
extractMetadata,
getStartOfDay,
isBeforeEndDate,
matchesAttributes,
matchesEventType,
matchesMetrics,
} from '../../../../../src/inAppMessaging/providers/pinpoint/utils/helpers';

import {
extractedContent,
extractedMetadata,
pinpointInAppMessage,
} from '../../../../testUtils/data';
import { InAppMessagingEvent } from '../../../../../src/inAppMessaging/types';

jest.mock('@aws-amplify/core');
jest.mock('@aws-amplify/core/internals/providers/pinpoint');
jest.mock('../../../../../src/inAppMessaging/providers/pinpoint/utils');

const HOUR_IN_MS = 1000 * 60 * 60;

describe('InAppMessaging Provider Utils', () => {
beforeEach(() => {
jest.clearAllMocks();
});

it('getStartOfDay returns a date string for the start of day', () => {
const dateString = getStartOfDay();
const date = new Date(dateString);

expect(date.getHours()).toBe(0);
expect(date.getMinutes()).toBe(0);
expect(date.getSeconds()).toBe(0);
expect(date.getMilliseconds()).toBe(0);
});

describe('matchesEventType', () => {
let message: PinpointInAppMessage;
beforeEach(() => {
message = cloneDeep(pinpointInAppMessage);
clearMemo();
});

it('checks if an event name matches a Pinpoint message', () => {
const clickEvent: InAppMessagingEvent = { name: 'clicked' };
const swipeEvent: InAppMessagingEvent = { name: 'swiped' };
const dragEvent: InAppMessagingEvent = { name: 'dragged' };

expect(matchesEventType(message, clickEvent)).toBe(true);
expect(matchesEventType(message, swipeEvent)).toBe(true);
expect(matchesEventType(message, dragEvent)).toBe(false);
});

it('memoizes matches', () => {
const clickEvent: InAppMessagingEvent = { name: 'clicked' };
message!.Schedule!.EventFilter!.Dimensions!.EventType!.Values = [
'clicked',
];

expect(matchesEventType(message, clickEvent)).toBe(true);

// This is a contrived way of validating the memo logic and should never happen in practice
message!.Schedule!.EventFilter!.Dimensions!.EventType!.Values = [];

expect(matchesEventType(message, clickEvent)).toBe(true);

clearMemo();

expect(matchesEventType(message, clickEvent)).toBe(false);
});
});

describe('matchesAttributes', () => {
let message: PinpointInAppMessage;
beforeEach(() => {
message = cloneDeep(pinpointInAppMessage);
clearMemo();
});

it('checks if event attributes matches a Pinpoint message', () => {
const matchingEvent: InAppMessagingEvent = {
name: 'action.taken',
attributes: {
favoriteFood: 'pizza',
favoriteAnimal: 'dog',
favoriteHobby: 'skydiving',
},
};
const nonMatchingEvent: InAppMessagingEvent = {
name: 'action.taken',
attributes: {
favoriteFood: 'pizza',
favoriteAnimal: 'monkey',
},
};
const missingAttributesEvent: InAppMessagingEvent = {
name: 'action.taken',
attributes: { favoriteFood: 'sushi' },
};
const noAttributesEvent: InAppMessagingEvent = { name: 'action.taken' };

// Everything matches if there are no attributes on the message
expect(matchesAttributes(message, matchingEvent)).toBe(true);
expect(matchesAttributes(message, nonMatchingEvent)).toBe(true);
expect(matchesAttributes(message, missingAttributesEvent)).toBe(true);
expect(matchesAttributes(message, noAttributesEvent)).toBe(true);

clearMemo();

message!.Schedule!.EventFilter!.Dimensions!.Attributes = {
favoriteFood: { Values: ['pizza', 'sushi'] },
favoriteAnimal: { Values: ['dog', 'giraffe'] },
};

expect(matchesAttributes(message, matchingEvent)).toBe(true);
expect(matchesAttributes(message, nonMatchingEvent)).toBe(false);
expect(matchesAttributes(message, missingAttributesEvent)).toBe(false);
expect(matchesAttributes(message, noAttributesEvent)).toBe(false);
});

it('memoizes matches', () => {
const event: InAppMessagingEvent = {
name: 'action.taken',
attributes: { favoriteFood: 'sushi' },
};
message!.Schedule!.EventFilter!.Dimensions!.Attributes = {
favoriteFood: { Values: ['pizza', 'sushi'] },
};

expect(matchesAttributes(message, event)).toBe(true);

// This is a contrived way of validating the memo logic and should never happen in practice
message!.Schedule!.EventFilter!.Dimensions!.Attributes = {
favoriteFood: { Values: ['pizza'] },
};

expect(matchesAttributes(message, event)).toBe(true);

clearMemo();

expect(matchesAttributes(message, event)).toBe(false);
});
});

describe('matchesMetrics', () => {
let message: PinpointInAppMessage;
beforeEach(() => {
message = cloneDeep(pinpointInAppMessage);
clearMemo();
});

it('checks if event metrics matches a Pinpoint message', () => {
const matchingEvent: InAppMessagingEvent = {
name: 'action.taken',
metrics: {
lotSize: 2000,
yearBuilt: 2000,
bedrooms: 3,
bathrooms: 2,
listPrice: 600000,
viewed: 300,
},
};
const nonMatchingEvent: InAppMessagingEvent = {
name: 'action.taken',
metrics: {
lotSize: 2000,
yearBuilt: 2000,
bedrooms: 3,
bathrooms: 2,
listPrice: 700000,
},
};
const missingMetricsEvent: InAppMessagingEvent = {
name: 'action.taken',
metrics: {
lotSize: 2000,
yearBuilt: 2000,
},
};
const noMetricsEvent: InAppMessagingEvent = { name: 'action.taken' };

// Everything matches if there are no metrics on the message
expect(matchesMetrics(message, matchingEvent)).toBe(true);
expect(matchesMetrics(message, nonMatchingEvent)).toBe(true);
expect(matchesMetrics(message, missingMetricsEvent)).toBe(true);
expect(matchesMetrics(message, noMetricsEvent)).toBe(true);

clearMemo();

message!.Schedule!.EventFilter!.Dimensions!.Metrics = {
lotSize: { ComparisonOperator: 'GREATER_THAN', Value: 1000 },
yearBuilt: { ComparisonOperator: 'EQUAL', Value: 2000 },
bedrooms: { ComparisonOperator: 'LESS_THAN_OR_EQUAL', Value: 3 },
bathrooms: { ComparisonOperator: 'GREATER_THAN_OR_EQUAL', Value: 1 },
listPrice: { ComparisonOperator: 'LESS_THAN', Value: 700000 },
};

expect(matchesMetrics(message, matchingEvent)).toBe(true);
expect(matchesMetrics(message, nonMatchingEvent)).toBe(false);
expect(matchesMetrics(message, missingMetricsEvent)).toBe(false);
expect(matchesMetrics(message, noMetricsEvent)).toBe(false);

clearMemo();

message!.Schedule!.EventFilter!.Dimensions!.Metrics = {
lotSize: { ComparisonOperator: 'GREATER_OR_LESS_THAN', Value: 1000 },
};

expect(matchesMetrics(message, matchingEvent)).toBe(false);
});

it('memoizes matches', () => {
const event: InAppMessagingEvent = {
name: 'action.taken',
metrics: { lotSize: 2000 },
};
message!.Schedule!.EventFilter!.Dimensions!.Metrics = {
lotSize: { ComparisonOperator: 'GREATER_THAN', Value: 1000 },
};

expect(matchesMetrics(message, event)).toBe(true);

// This is a contrived way of validating the memo logic and should never happen in practice
message!.Schedule!.EventFilter!.Dimensions!.Metrics = {
lotSize: { ComparisonOperator: 'LESS_THAN', Value: 1000 },
};

expect(matchesMetrics(message, event)).toBe(true);

clearMemo();

expect(matchesMetrics(message, event)).toBe(false);
});
});

it('isBeforeEndDate checks if a message is still not yet at its end', () => {
const message = cloneDeep(pinpointInAppMessage);

expect(isBeforeEndDate(message)).toBe(false);

// Set the end date to 24 hours from now
message!.Schedule!.EndDate = new Date(
new Date().getTime() + HOUR_IN_MS * 24
).toISOString();

expect(isBeforeEndDate(message)).toBe(true);

message!.Schedule!.EndDate = undefined;

expect(isBeforeEndDate(message)).toBe(true);
});

it('extractContent extracts Pinpoint content into a normalized shape', () => {
const message = cloneDeep(pinpointInAppMessage);

expect(extractContent(message)).toStrictEqual(extractedContent);
});

it('extractMetadata extracts Pinpoint metadata into a flat object', () => {
const message = cloneDeep(pinpointInAppMessage);

expect(extractMetadata(message)).toStrictEqual(extractedMetadata);
});
});
Loading

0 comments on commit 3b7a1f3

Please sign in to comment.