Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
5968455
CHECKPOINT jest
outoftime Feb 16, 2019
01116f1
Fix unnecessary addition of an eslint-disable directive
jwang1919 Mar 6, 2019
2214081
Initial transition to react-logic. Moved over unlickGithubIdentity lo…
jwang1919 Mar 9, 2019
a3e7c65
Got redux-saga and redux-logic working at the same time.
jwang1919 Mar 10, 2019
cd69fcc
Attempt to get jest working on IntelliJ
jwang1919 Mar 11, 2019
1fd6c2e
Make jest config more consistent; transform lodash-es
outoftime Mar 11, 2019
32cc3d3
Mocks for bugsnag and firebase libraries
outoftime Mar 11, 2019
d515b5c
Revert unnecessary change to karma config
outoftime Mar 11, 2019
8e6b2db
Add some jest rules to eslint config
outoftime Mar 11, 2019
3e1babc
Write test for unlinkGithubIdentityTest.js
jwang1919 Mar 14, 2019
d5bce83
Small refactor
jwang1919 Mar 14, 2019
e9ed80c
Fix yarn.lock with deduplicate
jwang1919 Mar 14, 2019
25b6603
Fix yarn.lock again?
jwang1919 Mar 14, 2019
8aa9e02
Refactored test, installed jest-extended (but didn't use it)
jwang1919 Mar 15, 2019
785dbde
Fix package.json after rebase
jwang1919 Mar 15, 2019
7d59962
Fix yarn.lock again
jwang1919 Mar 17, 2019
41914c3
Fix incorrect merge with .eslintrc
jwang1919 Mar 17, 2019
0f4523b
Add jest override to .eslintrc
outoftime Mar 17, 2019
ca43651
Lint fixing
outoftime Mar 17, 2019
89081bd
Start redux-logic transition for linkGithubIdentity code
jwang1919 Mar 21, 2019
cc93549
Completed redux-logic transition for linkGithubIdentity code
jwang1919 Mar 25, 2019
8db03c2
Merge remote-tracking branch 'upstream/master' into jest
jwang1919 Mar 25, 2019
f0302a7
Fix easy nit-pits. Will work on Rosie and factory approach later.
jwang1919 Mar 26, 2019
d3db32e
Fix eslint warnings triggered by Travis
jwang1919 Mar 30, 2019
86abf45
Add support for factories
outoftime Apr 8, 2019
2de5d78
Attempt to use Rosie into jest testing
jwang1919 Apr 17, 2019
feb77a2
Label all factory objects with a suffix of "Factory". Added mock user…
jwang1919 Apr 22, 2019
dfd9bd4
Merge branch 'master' into jest
jwang1919 Apr 22, 2019
bfa3636
Fix eslint failing in Travis
jwang1919 May 5, 2019
c67aa17
Merge remote-tracking branch 'origin/jest' into jest
jwang1919 May 5, 2019
1d9f486
2nd attempt at fixing eslint failing in Travis
jwang1919 May 5, 2019
58d7cdd
Added more realistic mock data for githubProfileFactory. Revert chang…
jwang1919 May 8, 2019
19fa7e5
Merge branch 'master' into jest
outoftime May 9, 2019
86d033e
Upgrade eslint-plugin-import, fix import order
outoftime May 9, 2019
02fedd8
Merge branch 'master' into jest
outoftime May 9, 2019
68123c1
Created new client factory github.js and moved relevant code there. A…
jwang1919 May 14, 2019
abb5d5d
outoftime requested changes
jwang1919 May 17, 2019
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
14 changes: 12 additions & 2 deletions .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"jest": true
},
"files": [
"**/__tests__/*.js"
"**/{__factories__,__mocks__,__tests__}/*.js{,x}"
],
"rules": {
"jest/no-alias-methods": "warn",
Expand All @@ -64,7 +64,17 @@
"react",
"promise",
"private-props"
]
],
"settings": {
"import/resolver": {
"@popcodeorg/eslint-import-resolver-jest": {
"jestConfigFile": "./jest.config.js"
},
"node": {
"extensions": [".js", ".jsx"]
}
}
}
},
{
"files": "src/**/*",
Expand Down
51 changes: 51 additions & 0 deletions __factories__/clients/firebase.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {Factory} from 'rosie';

class FirebaseError extends Error {
constructor(args) {
super(args.name);
this.code = args.name;
this.credential = args.credential;
Error.captureStackTrace(this, FirebaseError);
}
}

export const credentialFactory = new Factory().attrs({
providerId: 'github.com',
accessToken: 'abc123',
});

export const firebaseErrorFactory = Factory.define(
'firebaseError',
FirebaseError,
Copy link
Contributor

Choose a reason for hiding this comment

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

This seems like the right approach to me! 👍

).attrs({
name: 'some other error',
});

export const credentialInUseErrorFactory = new Factory().extend(
firebaseErrorFactory,
).attrs({
name: 'auth/credential-already-in-use',
credential: () => credentialFactory.build(),
});

export const userProviderDataFactory = new Factory().attrs({
displayName: 'popcoder',
email: null,
phoneNumber: null,
photoUrl: null,
providerId: 'github.com',
uid: '1234567',
});

export const userFactory = new Factory().extend(
userProviderDataFactory,
).attrs({
emailVerified: false,
isAnonymous: false,
metadata: {
creationTime: Date.now(),
lastSignInTime: Date.now(),
},
providerData: () => [userProviderDataFactory.build()],
refreshToken: 'token123',
});
47 changes: 47 additions & 0 deletions __factories__/clients/github.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {Factory} from 'rosie';

export const githubProfileFactory = new Factory().attrs({
login: 'popcoder',
id: 123456,
node_id: 'ABC123',
avatar_url: null,
gravatar_id: null,
url: 'https://api.github.com/users/popcoder',
html_url: 'https://github.com/popcoder',
followers_url: 'https://api.github.com/users/popcoder/followers',
following_url: 'https://api.github.com/users/popcoder/following{/other_user}',
gists_url: 'https://api.github.com/users/popcoder/gists{/gist_id}',
starred_url: 'https://api.github.com/users/popcoder/starred{/owner}{/repo}',
subscriptions_url: 'https://api.github.com/users/popcoder/subscriptions',
organizations_url: 'https://api.github.com/users/popcoder/orgs',
repos_url: 'https://api.github.com/users/popcoder/repos',
events_url: 'https://api.github.com/users/popcoder/events{/privacy}',
received_events_url: 'https://api.github.com/users/popcoder/received_events',
type: 'User',
site_admin: false,
name: 'Popcoder',
company: null,
blog: '',
location: null,
email: null,
hireable: null,
bio: null,
public_repos: 0,
public_gists: 0,
followers: 0,
following: 0,
created_at: '2014-09-12T15:02:43Z',
updated_at: '2019-05-02T13:50:32Z',
private_gists: 0,
total_private_repos: 0,
owned_private_repos: 0,
disk_usage: 0,
collaborators: 0,
two_factor_authentication: false,
plan: {
name: 'free',
space: 1234567,
collaborators: 0,
private_repos: 10000,
},
});
4 changes: 4 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
// https://jestjs.io/docs/en/configuration.html

module.exports = {
clearMocks: true,
moduleNameMapper: {
'@factories/(.*)$': '<rootDir>/__factories__/$1',
},
testPathIgnorePatterns: [
'/node_modules/',
'/bower_components/',
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@
"@babel/polyfill": "^7.2.5",
"@babel/preset-env": "^7.3.4",
"@babel/preset-react": "^7.0.0",
"@popcodeorg/eslint-import-resolver-jest": "^2.1.1-popcode.1",
"@redux-saga/testing-utils": "^1.0.1",
"almost-equal": "^1.1.0",
"babel-eslint": "^10.0.1",
Expand All @@ -306,7 +307,7 @@
"eslint": "^5.6.0",
"eslint-import-resolver-webpack": "^0.10.1",
"eslint-loader": "^2.1.1",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-import": "^2.17.2",
"eslint-plugin-jest": "^22.3.0",
"eslint-plugin-private-props": "^0.3.0",
"eslint-plugin-promise": "^4.0.1",
Expand Down Expand Up @@ -342,6 +343,7 @@
"postcss-preset-env": "^5.3.0",
"raw-loader": "^0.5.1",
"redux-saga-test-plan": "^4.0.0-beta.2",
"rosie": "^2.0.1",
"script-ext-html-webpack-plugin": "^2.0.1",
"sinon": "^5.0.7",
"source-map-explorer": "^1.4.0",
Expand Down
89 changes: 89 additions & 0 deletions src/logic/__tests__/linkGithubIdentityTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import linkGithubIdentity from '../linkGithubIdentity';
import {
linkGithub,
saveCredentialForCurrentUser,
} from '../../clients/firebase';
import {getProfileForAuthenticatedUser} from '../../clients/github';
import {bugsnagClient} from '../../util/bugsnag';

import {
credentialFactory,
credentialInUseErrorFactory,
firebaseErrorFactory,
userFactory,
} from '@factories/clients/firebase';

import {githubProfileFactory} from '@factories/clients/github';

jest.mock('../../clients/firebase.js');
jest.mock('../../clients/github.js');
jest.mock('../../util/bugsnag.js');

describe('linkGithubIdentity', () => {
test('success', async() => {
const mockCredential = credentialFactory.build();
const mockUser = userFactory.build();

linkGithub.mockResolvedValue({
user: mockUser,
credential: mockCredential,
});

const {
type,
payload: {
credential: {providerId},
user,
},
} = await linkGithubIdentity.process();

expect(linkGithub).toHaveBeenCalledWith();
expect(saveCredentialForCurrentUser).toHaveBeenCalledWith(mockCredential);
expect(type).toBe('IDENTITY_LINKED');
expect(providerId).toBe(mockCredential.providerId);
expect(user).toEqual(mockUser);
});

test('credential already in use', async() => {
const error = credentialInUseErrorFactory.build();
const githubProfile = githubProfileFactory.build();

linkGithub.mockRejectedValue(error);
getProfileForAuthenticatedUser.mockResolvedValue(githubProfile);

const {
type,
payload: {
credential: {
providerId,
accessToken,
},
},
} = await linkGithubIdentity.process();
expect(linkGithub).toHaveBeenCalledWith();
expect(getProfileForAuthenticatedUser).toHaveBeenCalledWith(
error.credential.accessToken,
);
expect(type).toBe('ACCOUNT_MIGRATION_NEEDED');
expect(providerId).toBe(error.credential.providerId);
expect(accessToken).toBe(error.credential.accessToken);
});

test('other error', async() => {
const otherError = firebaseErrorFactory.build();

linkGithub.mockRejectedValue(otherError);
bugsnagClient.notify.mockResolvedValue();

const {
type,
error,
payload: {message},
} = await linkGithubIdentity.process();
expect(linkGithub).toHaveBeenCalledWith();
expect(bugsnagClient.notify).toHaveBeenCalledWith(otherError);
expect(type).toBe('LINK_IDENTITY_FAILED');
expect(error).toBe(true);
expect(message).toBe(otherError.code);
});
});
6 changes: 5 additions & 1 deletion src/logic/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import linkGithubIdentity from './linkGithubIdentity';
import unlinkGithubIdentity from './unlinkGithubIdentity';

export default [unlinkGithubIdentity];
export default [
linkGithubIdentity,
unlinkGithubIdentity,
];
33 changes: 33 additions & 0 deletions src/logic/linkGithubIdentity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {createLogic} from 'redux-logic';

import {
linkGithub,
saveCredentialForCurrentUser,
} from '../clients/firebase';
import {
accountMigrationNeeded,
identityLinked,
linkIdentityFailed,
} from '../actions/user';
import {getProfileForAuthenticatedUser} from '../clients/github';
import {bugsnagClient} from '../util/bugsnag';

export default createLogic({
type: 'LINK_GITHUB_IDENTITY',
async process() {
try {
const {user: userData, credential} = await linkGithub();
await saveCredentialForCurrentUser(credential);
return identityLinked(userData, credential);
} catch (e) {
if (e.code === 'auth/credential-already-in-use') {
const {data: githubProfile} = await getProfileForAuthenticatedUser(
e.credential.accessToken,
);
return accountMigrationNeeded(githubProfile, e.credential);
}
await bugsnagClient.notify(e);
return linkIdentityFailed(e);
}
},
});
30 changes: 0 additions & 30 deletions src/sagas/user.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,43 +11,14 @@ import {
} from 'redux-saga/effects';
import {
accountMigrationComplete,
accountMigrationNeeded,
accountMigrationUndoPeriodExpired,
identityLinked,
linkIdentityFailed,
accountMigrationError,
} from '../actions/user';
import {getCurrentAccountMigration} from '../selectors';
import {
linkGithub,
migrateAccount,
signOut,
saveCredentialForCurrentUser,
} from '../clients/firebase';
import {getProfileForAuthenticatedUser} from '../clients/github';

export function* linkGithubIdentity() {
try {
const {user: userData, credential} = yield call(linkGithub);
yield call(saveCredentialForCurrentUser, credential);
yield put(identityLinked(userData, credential));
} catch (e) {
switch (e.code) {
case 'auth/credential-already-in-use': {
const {data: githubProfile} = yield call(
getProfileForAuthenticatedUser,
e.credential.accessToken,
);
yield put(accountMigrationNeeded(githubProfile, e.credential));
break;
}

default:
yield call([bugsnagClient, 'notify'], e);
yield put(linkIdentityFailed(e));
}
}
}

export function* startAccountMigration() {
const {shouldContinue} = yield race({
Expand Down Expand Up @@ -82,7 +53,6 @@ export function* logOut() {

export default function* user() {
yield all([
takeEvery('LINK_GITHUB_IDENTITY', linkGithubIdentity),
takeEvery('LOG_OUT', logOut),
takeEvery('START_ACCOUNT_MIGRATION', startAccountMigration),
]);
Expand Down
Loading