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

#356 Database should be connected to Profile Page #367

Closed
wants to merge 39 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
feeabb5
#356 Connected api data to Profile component, added GithubAccounts in…
SiriusLL Jun 1, 2022
6adb5fe
Merge branch 'main' into 356-Database-should-be-connected-to-Profile-…
denizedaza Jun 1, 2022
39df7e6
Revert "#356 Connected api data to Profile component, added GithubAcc…
seidior Jun 2, 2022
7db01ce
#356: Bring branch up to date
seidior Jun 2, 2022
bfd0dc0
WIP on tests
seidior Jun 2, 2022
9dc6287
#356: WIP on tests
seidior Jun 2, 2022
f73449a
#356: Add PUT route function stubs
seidior Jun 2, 2022
3c7e273
Merge branch '356-Database-should-be-connected-to-Profile-Page' of gi…
denizedaza Jun 2, 2022
134263c
#356 WIP Add data modifier methods
tenzinlekphell Jun 3, 2022
59f2f44
#356 Connect api data to profile page
SiriusLL Jun 3, 2022
7d32216
Merge branches '356-Database-should-be-connected-to-Profile-Page' and…
SiriusLL Jun 3, 2022
c578d0e
#356 Add post route to insert profile information and HTTP status codes
denizedaza Jun 3, 2022
e584a0f
Merge branch '356-Database-should-be-connected-to-Profile-Page' of gi…
denizedaza Jun 3, 2022
6dca6ab
#356 WIP Add some methods to compareAndUpdatePrincipals
Ayako-Yokoe Jun 3, 2022
adc037e
#356 WIP Add some methods to compareAndUpdatePrincipals
Ayako-Yokoe Jun 3, 2022
3f8ca9f
#356: Restructure files, vanquish stub errors
seidior Jun 3, 2022
8daf4f3
#356: Logic for comparing social profiles
seidior Jun 3, 2022
478c5b7
#356: Always return unmodified object
seidior Jun 3, 2022
0eac6c4
#356: Switch from principal_id to id
seidior Jun 3, 2022
faa7ca7
#356: Capitalize Social Networks
seidior Jun 3, 2022
141460c
#356: Better type casting for ISocialProfile
seidior Jun 3, 2022
eef5ead
#356: Fixes after testing HTTP PUT
seidior Jun 4, 2022
4a7fbd0
#356: Clean up code with yarn delint
seidior Jun 6, 2022
3821c20
#356: First time all Jest tests have passed
seidior Jun 7, 2022
f1bbec0
#356: Also mock console.error
seidior Jun 7, 2022
69e4559
#356: Update caniuse-lite
seidior Jun 7, 2022
ed64bf4
#356: Finish another test
seidior Jun 7, 2022
be5d7b6
#356: Ignore some 'any' linting
seidior Jun 7, 2022
fdbfe66
#356: Add new HTTP error
seidior Jun 9, 2022
a336b02
#356: Finish off tests for middleware directory
seidior Jun 9, 2022
2ccb52d
#356: Private/public social profile support
seidior Jun 9, 2022
5eff172
#356: Email address privacy
seidior Jun 9, 2022
9c19a45
#356: Transaction rollback error test
seidior Jun 9, 2022
7376193
#356: All tests passing
seidior Jun 10, 2022
c18e461
#356: Full code coverage with tests
seidior Jun 10, 2022
8749fc5
#356: Pre-populate principals table
seidior Jun 10, 2022
3b8d1c0
#356: Fix testing regression
seidior Jun 10, 2022
d1c0fa5
#356: Revert changes to webapp
seidior Jun 10, 2022
f9934f5
#356: Revert commit updating Yarn lockfile
seidior Jun 12, 2022
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 api/src/jest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,8 @@ console.log = (...args) => {
originalConsoleLog(...args);
throw new Error('Unexpected call to console.log.');
};
const originalConsoleError = console.error;
console.error = (...args) => {
originalConsoleError(...args);
throw new Error('Unexpected call to console.error.');
};
83 changes: 60 additions & 23 deletions api/src/middleware/__tests__/authRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,41 +72,78 @@ describe('authRouter', () => {
appAgent.get('/auth/github/callback').expect(400, done);
});

it('should respond with a server error for requests that do not get a GitHub access token', done => {
mockGetGithubAccessToken.mockResolvedValue(null);
appAgent.get('/auth/github/callback?code=MOCK_CODE').expect(500, done);
});

it('should respond with a server error if no user data is returned from authorized GitHub request', done => {
mockGetGithubAccessToken.mockResolvedValue('MOCK_ACCESS_TOKEN');
mockGetGithubUser.mockResolvedValue(null);
appAgent.get('/auth/github/callback?code=MOCK_CODE').expect(500, done);
});

it('should redirect to the web app for a principal with a known GitHub user id', done => {
mockGetGithubAccessToken.mockResolvedValue('MOCK_ACCESS_TOKEN');
mockGetGithubUser.mockResolvedValue({ id: 1000 });
// TODO: fix later
// mockFindGithubUserByGithubId.mockResolvedValue({ principal_id: 1 });
// appAgent
// .get('/auth/github/callback?code=MOCK_CODE')
// .expect('Location', process.env.WEBAPP_ORIGIN)
// .expect(302, () => {
// expect(getGithubAccessToken).toHaveBeenCalledWith('MOCK_CODE');
// expect(getGithubUser).toHaveBeenCalledWith('MOCK_ACCESS_TOKEN');
// expect(mockFindGithubUserByGithubId).toHaveBeenCalledWith(1000);
// expect(mockCreateGithubUser).not.toHaveBeenCalled();
// done();
// });
mockGetGithubUser.mockResolvedValue({
id: 1000,
login: '',
name: '',
bio: '',
avatar_url: '',
});
mockFindGithubUserByGithubId.mockResolvedValue({
github_id: 1000,
username: '',
full_name: '',
avatar_url: '',
bio: '',
principal_id: 10,
});
appAgent
.get('/auth/github/callback?code=MOCK_CODE')
.expect('Location', process.env.WEBAPP_ORIGIN)
.expect(302, () => {
expect(getGithubAccessToken).toHaveBeenCalledWith('MOCK_CODE');
expect(getGithubUser).toHaveBeenCalledWith('MOCK_ACCESS_TOKEN');
expect(mockFindGithubUserByGithubId).toHaveBeenCalledWith(1000);
expect(mockCreateGithubUser).not.toHaveBeenCalled();
done();
});
});

it('should insert a new principal and GitHub user and then redirect to the web app for a principal with an unknown GitHub user id', done => {
mockGetGithubAccessToken.mockResolvedValue('MOCK_ACCESS_TOKEN');
mockGetGithubUser.mockResolvedValue({ id: 1000 });
mockFindGithubUserByGithubId.mockResolvedValue(null);
// TODO: fix later
// mockCreateGithubUser.mockResolvedValue({
// id: 10,
// github_id: 1000,
// principal_id: 1,
// });
mockGetGithubUser.mockResolvedValue({
id: 1001,
login: '',
name: '',
bio: '',
avatar_url: '',
});
mockFindGithubUserByGithubId.mockRejectedValue(null);
mockCreateGithubUser.mockResolvedValue({
github_id: 1001,
username: '',
full_name: '',
avatar_url: '',
bio: '',
principal_id: 1,
});
appAgent
.get('/auth/github/callback?code=MOCK_CODE')
.expect('Location', process.env.WEBAPP_ORIGIN)
.expect(302, () => {
expect(getGithubAccessToken).toHaveBeenCalledWith('MOCK_CODE');
expect(getGithubUser).toHaveBeenCalledWith('MOCK_ACCESS_TOKEN');
expect(mockFindGithubUserByGithubId).toHaveBeenCalledWith(1000);
expect(mockCreateGithubUser).toHaveBeenCalledWith(1000);
expect(mockFindGithubUserByGithubId).toHaveBeenCalledWith(1001);
expect(mockCreateGithubUser).toHaveBeenCalledWith({
github_id: 1001,
username: '',
full_name: '',
avatar_url: '',
bio: '',
});
done();
});
});
Expand Down
190 changes: 190 additions & 0 deletions api/src/middleware/__tests__/profileRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import profileRouter from '../profileRouter';
import { createAppAgentForRouter, mockPrincipalId } from '../routerTestUtils';
import { getUserProfileData } from '../../services/getUserProfileDataService';
import {
compareAndUpdatePrincipals,
parsePutSubmission,
} from '../../services/principalsService';
import { collectionEnvelope } from '../responseEnvelope';
import { NotFoundError } from '../httpErrors';

jest.mock('../../services/getUserProfileDataService');
jest.mock('../../services/principalsService');

const mockGetUserProfileData = jest.mocked(getUserProfileData);
const mockCompareAndUpdatePrincipals = jest.mocked(compareAndUpdatePrincipals);
const mockParsePutSubmission = jest.mocked(parsePutSubmission);

describe('profileRouter', () => {
const appAgent = createAppAgentForRouter(profileRouter);
const valid_user_principal_id = 1;
const valid_user_full_name = 'OpenTree Education';
const valid_user_email = '[email protected]';
const valid_user_github_id = 1000;
const valid_user_github_username = 'OpenTree-Education';
const valid_user_other_data = '';
const valid_user_data_same = {
id: valid_user_principal_id,
full_name: valid_user_full_name,
email_address: valid_user_email,
bio: valid_user_other_data,
github_accounts: [
{
github_id: valid_user_github_id,
username: valid_user_github_username,
full_name: valid_user_full_name,
bio: valid_user_other_data,
avatar_url: valid_user_other_data,
principal_id: valid_user_principal_id,
},
],
social_profiles: [
{
network_name: 'GitHub',
user_name: valid_user_github_username,
profile_url: `//github.com/${valid_user_github_username}`,
public: true,
},
{
network_name: 'email',
user_name: valid_user_email,
profile_url: `mailto:${valid_user_email}`,
public: false,
},
],
};
const valid_user_data_different = {
id: valid_user_principal_id + 1,
full_name: valid_user_full_name,
email_address: '',
bio: valid_user_other_data,
github_accounts: [
{
github_id: valid_user_github_id,
username: valid_user_github_username,
full_name: valid_user_full_name,
bio: valid_user_other_data,
avatar_url: valid_user_other_data,
principal_id: valid_user_principal_id,
},
],
social_profiles: [
{
network_name: 'GitHub',
user_name: valid_user_github_username,
profile_url: `//github.com/${valid_user_github_username}`,
public: true,
},
],
};

describe('GET /', () => {
it('should reject requests for profiles without specifying a principal ID', done => {
appAgent.get('/').expect(400, done);
});
});

describe('GET /:id', () => {
const appAgent = createAppAgentForRouter(profileRouter);

it('should respond with valid profile data for our own principal ID', done => {
mockPrincipalId(valid_user_principal_id);
mockGetUserProfileData.mockResolvedValue(valid_user_data_same);
appAgent
.get('/1')
.expect(200, collectionEnvelope([valid_user_data_same], 1), err => {
done(err);
});
});

it('should respect privacy info with profile data for a different principal ID than our own', done => {
mockPrincipalId(null);
mockGetUserProfileData.mockResolvedValue(valid_user_data_different);
appAgent
.get('/2')
.expect(
200,
collectionEnvelope([valid_user_data_different], 1),
err => {
done(err);
}
);
});

it('should reject requests for principal IDs that are NaN', done => {
appAgent.get('/opentree').expect(400, done);
});

it('should show errors for non-existent numerical principal IDs', done => {
mockGetUserProfileData.mockRejectedValue(new NotFoundError());
appAgent.get('/3').expect(404, done);
});
});

describe('PUT /:id', () => {
const appAgent = createAppAgentForRouter(profileRouter);

it('should respond with valid profile data for updates for our own principal ID', done => {
mockPrincipalId(valid_user_principal_id);
mockParsePutSubmission.mockReturnValue(valid_user_data_same);
mockGetUserProfileData.mockResolvedValue(valid_user_data_same);
mockCompareAndUpdatePrincipals.mockResolvedValue(true);
appAgent
.put('/1')
.send(collectionEnvelope([valid_user_data_same], 1))
.expect(200, collectionEnvelope([valid_user_data_same], 1), err => {
done(err);
});
});

it('should respond with valid profile data for data for our own principal ID that requires no updates', done => {
mockPrincipalId(valid_user_principal_id);
mockParsePutSubmission.mockReturnValue(valid_user_data_same);
mockGetUserProfileData.mockResolvedValue(valid_user_data_same);
mockCompareAndUpdatePrincipals.mockResolvedValue(false);
appAgent
.put('/1')
.send(collectionEnvelope([valid_user_data_same], 1))
.expect(200, collectionEnvelope([valid_user_data_same], 1), err => {
done(err);
});
});

it('should reject updates for profile data from unauthenticated users', done => {
mockPrincipalId(null);
appAgent
.put('/2')
.send(collectionEnvelope([valid_user_data_same], 1))
.expect(401, done);
});

it('should reject updates for profile data for a different principal ID than our own', done => {
mockPrincipalId(valid_user_principal_id);
appAgent
.put('/2')
.send(collectionEnvelope([valid_user_data_same], 1))
.expect(403, done);
});

it('should reject requests for principal IDs that are NaN', done => {
appAgent
.put('/opentree')
.send(collectionEnvelope([valid_user_data_same], 1))
.expect(400, done);
});

it('should show errors for authorized requests for non-existent numerical principal IDs', done => {
mockPrincipalId(valid_user_principal_id);
mockGetUserProfileData.mockRejectedValue(new NotFoundError());
appAgent
.put('/3')
.send(collectionEnvelope([valid_user_data_same], 1))
.expect(403, done);
});

it('should show errors for authorized requests not in Entity format', done => {
mockPrincipalId(valid_user_principal_id);
appAgent.put('/1').send(valid_user_data_same).expect(400, done);
});
});
});
34 changes: 34 additions & 0 deletions api/src/middleware/__tests__/socialNetworksRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import socialNetworksRouter from '../socialNetworksRouter';
import { listSocialNetworks } from '../../services/socialNetworksService';
import { createAppAgentForRouter } from '../routerTestUtils';

jest.mock('../../services/socialNetworksService');
const mockListSocialNetworks = jest.mocked(listSocialNetworks);

describe('socialNetworksRouter', () => {
const appAgent = createAppAgentForRouter(socialNetworksRouter);

describe('GET /', () => {
it('should list all available social networks', done => {
mockListSocialNetworks.mockResolvedValue([
{
id: 1,
network_name: 'GitHub',
protocol: '//',
base_url: 'github.com/',
},
]);
appAgent.get('/').expect(200, () => {
expect(listSocialNetworks).toHaveBeenCalled();
done();
});
});
it('should properly throw an error if it cannot talk to the database', done => {
mockListSocialNetworks.mockRejectedValue(new Error());
appAgent.get('/').expect(500, () => {
expect(listSocialNetworks).toHaveBeenCalled();
done();
});
});
});
});
Loading