Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[No QA] Add Pusher Automated Tests #1153

Merged
merged 31 commits into from
Jan 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
f0a9e60
change name
marcaaron Nov 6, 2020
1c37dd5
fix created being in the wrong order
marcaaron Nov 6, 2020
d4e9ef1
fix style
marcaaron Nov 10, 2020
29d02a1
fix style then get rid of async
marcaaron Nov 10, 2020
5970909
fix style
marcaaron Nov 10, 2020
c409ac3
Merge remote-tracking branch 'origin' into marcaaron-pusher-tests
marcaaron Nov 12, 2020
ccc3778
fix tests to use Onyx
marcaaron Nov 12, 2020
68f1d37
fix conflicts
marcaaron Dec 2, 2020
478f094
fix API.js
marcaaron Dec 2, 2020
918fa0a
tests broke somehow
marcaaron Dec 2, 2020
781f82b
fix conflicts
marcaaron Jan 1, 2021
da7746e
Fix this test up
marcaaron Jan 1, 2021
f65b202
remove some things
marcaaron Jan 1, 2021
fa53652
add comment
marcaaron Jan 1, 2021
6d59757
fix conflicts
marcaaron Jan 6, 2021
e8e0b3c
fix conflicts
marcaaron Jan 8, 2021
2f8dae6
fix ua mocks
marcaaron Jan 8, 2021
39684fe
remove uneeded
marcaaron Jan 8, 2021
9704aa9
Mock Pusher methods instead of adding the IS_JEST_RUNNING
marcaaron Jan 9, 2021
c6a235a
remove CONFIG dependency
marcaaron Jan 9, 2021
8e55d77
fix comment
marcaaron Jan 9, 2021
7298525
Merge remote-tracking branch 'origin' into marcaaron-pusher-tests
marcaaron Jan 12, 2021
cd18cdb
Fix up comment. Use Onyx.set instead of waitForPromisesToResolve()
marcaaron Jan 12, 2021
2ae40c9
Improve tests. Add TestHelper.
marcaaron Jan 12, 2021
fb69894
fix lint
marcaaron Jan 12, 2021
84bbfb6
Respond to Tims feedback.
marcaaron Jan 12, 2021
422ae85
update react-native-onyx
marcaaron Jan 13, 2021
739bbb7
dont test strict equal since the timestamp will make the test flaky
marcaaron Jan 13, 2021
fefa0ef
remove momnet
marcaaron Jan 13, 2021
b895dce
fix conflicts
marcaaron Jan 16, 2021
acb4b80
add comment
marcaaron Jan 16, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions __mocks__/@react-native-community/push-notification-ios.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export default {
addEventListener: jest.fn(),
requestPermissions: jest.fn(() => Promise.resolve()),
getInitialNotification: jest.fn(() => Promise.resolve()),
};
3 changes: 3 additions & 0 deletions __mocks__/pusher-js/react-native.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import {PusherMock} from 'pusher-js-mock';

export default PusherMock;
9 changes: 9 additions & 0 deletions __mocks__/urbanairship-react-native.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
export default {
setUserNotificationsEnabled: jest.fn(),
};

const EventType = {
NotificationResponse: 'notificationResponse',
PushReceived: 'pushReceived',
};

export {
EventType,
};
10 changes: 8 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@
"react-native-image-picker": "^2.3.3",
"react-native-keyboard-spacer": "^0.4.1",
"react-native-modal": "^11.5.6",
"react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#783806e7a3d82d78025bb9025767f42c3964eff9",
"react-native-onyx": "git+https://github.com/Expensify/react-native-onyx.git#bbed584f0e9f9ce128361c7138220738d8525a6a",
"react-native-pdf": "^6.2.2",
"react-native-render-html": "^6.0.0-alpha.10",
"react-native-safe-area-context": "^3.1.4",
Expand Down Expand Up @@ -111,6 +111,7 @@
"jest-circus": "^26.5.2",
"jest-cli": "^26.5.2",
"metro-react-native-babel-preset": "^0.61.0",
"pusher-js-mock": "^0.3.3",
"react-hot-loader": "^4.12.21",
"react-native-svg-transformer": "^0.14.3",
"react-native-version": "^4.0.0",
Expand Down
2 changes: 2 additions & 0 deletions src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@ function addAction(reportID, text, file) {
[newSequenceNumber]: {
actionName: 'ADDCOMMENT',
actorEmail: currentUserEmail,
actorAccountID: currentUserAccountID,
person: [
{
style: 'strong',
Expand All @@ -553,6 +554,7 @@ function addAction(reportID, text, file) {
isFirstItem: false,
isAttachment,
loading: true,
shouldShow: true,
},
});

Expand Down
100 changes: 100 additions & 0 deletions tests/actions/ReportTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import Onyx from 'react-native-onyx';
import ONYXKEYS from '../../src/ONYXKEYS';
import * as Pusher from '../../src/libs/Pusher/pusher';
import PusherConnectionManager from '../../src/libs/PusherConnectionManager';
import CONFIG from '../../src/CONFIG';
import {addAction, subscribeToReportCommentEvents} from '../../src/libs/actions/Report';
import waitForPromisesToResolve from '../utils/waitForPromisesToResolve';
import PushNotification from '../../src/libs/Notification/PushNotification';
import {signInWithTestUser, fetchPersonalDetailsForTestUser} from '../utils/TestHelper';

PushNotification.register = () => {};
PushNotification.deregister = () => {};

describe('actions/Report', () => {
it('should store a new report action in Onyx when one is handled via Pusher', () => {
const TEST_USER_ACCOUNT_ID = 1;
const TEST_USER_LOGIN = 'test@test.com';
const REPORT_ID = 1;
const ACTION_ID = 1;
const REPORT_ACTION = {
actionName: 'ADDCOMMENT',
actorAccountID: TEST_USER_ACCOUNT_ID,
actorEmail: TEST_USER_LOGIN,
automatic: false,
avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png',
message: [{type: 'COMMENT', html: 'Testing a comment', text: 'Testing a comment'}],
person: [{type: 'TEXT', style: 'strong', text: 'Test User'}],
sequenceNumber: ACTION_ID,
shouldShow: true,
};

// When using the Pusher mock the act of calling Pusher.isSubscribed will create a
// channel already in a subscribed state. These methods are normally used to prevent
// duplicated subscriptions, but we don't need them for this test so forcing them to
// return false will make the testing less complex.
Pusher.isSubscribed = jest.fn().mockReturnValue(false);
Pusher.isAlreadySubscribing = jest.fn().mockReturnValue(false);

// Connect to Pusher
PusherConnectionManager.init();
Pusher.init({
appKey: CONFIG.PUSHER.APP_KEY,
cluster: CONFIG.PUSHER.CLUSTER,
authEndpoint: `${CONFIG.EXPENSIFY.URL_API_ROOT}api?command=Push_Authenticate`,
});

let reportActions;
Onyx.connect({
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`,
callback: val => reportActions = val,
});

// Set up Onyx with some test user data
return signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN)
.then(() => {
subscribeToReportCommentEvents();
return waitForPromisesToResolve();
})
marcaaron marked this conversation as resolved.
Show resolved Hide resolved
.then(() => fetchPersonalDetailsForTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN, {
[TEST_USER_LOGIN]: {
accountID: TEST_USER_ACCOUNT_ID,
email: TEST_USER_LOGIN,
firstName: 'Test',
lastName: 'User',
},
}))
.then(() => {
// This is a fire and forget response, but once it completes we should be able to verify that we
// have an "optimistic" report action in Onyx.
addAction(REPORT_ID, 'Testing a comment');
return waitForPromisesToResolve();
})
.then(() => {
const resultAction = reportActions[ACTION_ID];
expect(resultAction.message).toEqual(REPORT_ACTION.message);
expect(resultAction.person).toEqual(REPORT_ACTION.person);
expect(resultAction.loading).toEqual(true);
})
.then(() => {
// We subscribed to the Pusher channel above and now we need to simulate a reportComment action
// Pusher event so we can verify that action was handled correctly and merged into the reportActions.
const channel = Pusher.getChannel('private-user-accountID-1');
channel.emit('reportComment', {
reportID: REPORT_ID,
reportAction: REPORT_ACTION,
});

// Once a reportComment event is emitted to the Pusher channel we should see the comment get processed
// by the Pusher callback and added to the storage so we must wait for promises to resolve again and
// then verify the data is in Onyx.
return waitForPromisesToResolve();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see how maybe this is necessary to prevent any race conditions with the local data stored in reportActions not being up-to-date with Onyx yet.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right. The callback should happen in realtime and then we set the data async via Onyx.

})
.then(() => {
const resultAction = reportActions[ACTION_ID];

// Verify that our action is no longer in the loading state
expect(resultAction.loading).toEqual(false);
});
});
});
46 changes: 3 additions & 43 deletions tests/actions/SessionTest.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Onyx from 'react-native-onyx';
import {fetchAccountDetails, signIn} from '../../src/libs/actions/Session';
import * as API from '../../src/libs/API';
import HttpUtils from '../../src/libs/HttpUtils';
import waitForPromisesToResolve from '../utils/waitForPromisesToResolve';
import ONYXKEYS from '../../src/ONYXKEYS';
import {signInWithTestUser} from '../utils/TestHelper';

// Set up manual mocks for methods used in the actions so our test does not fail.
jest.mock('../../src/libs/Notification/PushNotification', () => ({
Expand All @@ -23,15 +23,6 @@ test('Authenticate is called with saved credentials when a session expires', ()
const TEST_INITIAL_AUTH_TOKEN = 'initialAuthToken';
const TEST_REFRESHED_AUTH_TOKEN = 'refreshedAuthToken';

// Set up mock responses for all APIs that will be called. The next time this command is called it will return
// jsonCode: 200 and the response here.
HttpUtils.xhr.mockImplementation(() => Promise.resolve({
jsonCode: 200,
accountExists: true,
canAccessExpensifyCash: true,
requiresTwoFactorAuth: false,
}));

let credentials;
Onyx.connect({
key: ONYXKEYS.CREDENTIALS,
Expand All @@ -44,39 +35,8 @@ test('Authenticate is called with saved credentials when a session expires', ()
callback: val => session = val,
});

// When the user enters their login and calls GetAccountStatus
fetchAccountDetails(TEST_USER_LOGIN);

// Note: In order for this test to work we must return a promise! It will pass even with
// failing assertions if we remove the return keyword.
return waitForPromisesToResolve()
.then(() => {
// Then the login should exist in credentials
expect(credentials.login).toBe(TEST_USER_LOGIN);

// Note: Every time we add a mockImplementationOnce() we are altering the API response.
HttpUtils.xhr

// First call to Authenticate
.mockImplementationOnce(() => Promise.resolve({
jsonCode: 200,
accountID: TEST_USER_ACCOUNT_ID,
authToken: TEST_INITIAL_AUTH_TOKEN,
email: TEST_USER_LOGIN,
}))

// Next call to CreateLogin
.mockImplementationOnce(() => Promise.resolve({
jsonCode: 200,
accountID: TEST_USER_ACCOUNT_ID,
authToken: TEST_INITIAL_AUTH_TOKEN,
email: TEST_USER_LOGIN,
}));

// When we sign in
signIn('Password1');
return waitForPromisesToResolve();
})
// When we sign in with the test user
return signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN, 'Password1', TEST_INITIAL_AUTH_TOKEN)
.then(() => {
// Then our re-authentication credentials should be generated and our session data
// have the correct information + initial authToken.
Expand Down
82 changes: 82 additions & 0 deletions tests/utils/TestHelper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import {signIn, fetchAccountDetails} from '../../src/libs/actions/Session';
import {fetch as fetchPersonalDetails} from '../../src/libs/actions/PersonalDetails';
import HttpUtils from '../../src/libs/HttpUtils';
import waitForPromisesToResolve from './waitForPromisesToResolve';

/**
* Simulate signing in and make sure all API calls in this flow succeed. Every time we add
* a mockImplementationOnce() we are altering what Network.post() will return.
*
* @param {Number} accountID
* @param {String} login
* @param {String} password
* @param {String} authToken
* @return {Promise}
*/
function signInWithTestUser(accountID, login, password = 'Password1', authToken = 'asdfqwerty') {
HttpUtils.xhr = jest.fn();
HttpUtils.xhr.mockImplementation(() => Promise.resolve({
jsonCode: 200,
accountExists: true,
canAccessExpensifyCash: true,
requiresTwoFactorAuth: false,
}));

// Simulate user entering their login and populating the credentials.login
fetchAccountDetails(login);
return waitForPromisesToResolve()
.then(() => {
// First call to Authenticate
HttpUtils.xhr
.mockImplementationOnce(() => Promise.resolve({
jsonCode: 200,
accountID,
authToken,
email: login,
}))

// Next call to CreateLogin
.mockImplementationOnce(() => Promise.resolve({
jsonCode: 200,
accountID,
authToken,
email: login,
}));
signIn(password);
return waitForPromisesToResolve();
});
}

/**
* Fetch and set personal details with provided personalDetailsList
*
* @param {Number} accountID
* @param {String} email
* @param {Object} personalDetailsList
* @returns {Promise}
*/
function fetchPersonalDetailsForTestUser(accountID, email, personalDetailsList) {
// Mock xhr()
HttpUtils.xhr = jest.fn();

// Get the personalDetails
HttpUtils.xhr

// fetchPersonalDetails
.mockImplementationOnce(() => Promise.resolve({
accountID,
email,
personalDetailsList,
}))

// fetchTimezone
.mockImplementationOnce(() => Promise.resolve({}));

fetchPersonalDetails();
return waitForPromisesToResolve();
}

export {
signInWithTestUser,
fetchPersonalDetailsForTestUser,
};