Skip to content

Commit

Permalink
Fix unit tests
Browse files Browse the repository at this point in the history
This commit fixes existing unit tests following recent changes, it also
adds new ones where appropriate.

Coverage reports a small amount of missing coverage in users.js but I
have no idea how to cover that!
  • Loading branch information
Andrew Isherwood committed Dec 23, 2020
1 parent e8f2bfe commit 1df0c35
Show file tree
Hide file tree
Showing 12 changed files with 812 additions and 114 deletions.
5 changes: 0 additions & 5 deletions helpers/encryption.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ const encryption = {
encrypt: async (toEncrypt) => {
await _sodium.ready;
const sodium = _sodium;

// Get the key
const key = sodium.from_base64(process.env.KEY);

Expand All @@ -26,10 +25,6 @@ const encryption = {
const key = sodium.from_base64(process.env.KEY);
toDecryptWithNonce = sodium.from_base64(toDecryptWithNonce);

if (toDecryptWithNonce.length < sodium.crypto_secretbox_NONCEBYTES + sodium.crypto_secretbox_MACBYTES) {
throw 'Invalid cyphertext and/or nonce';
}

const nonce = toDecryptWithNonce.slice(0, sodium.crypto_secretbox_NONCEBYTES);
const ciphertext = toDecryptWithNonce.slice(sodium.crypto_secretbox_NONCEBYTES);
const decrypted = sodium.crypto_secretbox_open_easy(ciphertext, nonce, key);
Expand Down
30 changes: 20 additions & 10 deletions resolvers/folders.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,27 @@ const folderResolvers = {
},
deleteFolder: ({ params }) =>
pool.query('DELETE FROM folder WHERE id = $1', [params.id]),
folderCounts: async ({ user }) => {
// If we're being tested, the testing function will pass in mocked
// versions of allFolders, allQueries & getUserUnseenCounts
// so we accept those, or fall back to the real ones if they are not passed
// See https://github.com/magicmark/jest-how-do-i-mock-x/blob/master/src/function-in-same-module/README.md
// for details on this DI approach
folderCounts: async (
{ user },
_allFolders = folderResolvers.allFolders,
_allQueries = queries.allQueries,
_getUserUnseenCounts = queryuser.getUserUnseenCounts
) => {
try {
// Fetch all the folders and all this user's queries
const [allFolders, allQueries] = await Promise.all([
folderResolvers.allFolders(),
queries.allQueries({ query: {}, user })
const [allFoldersRes, allQueriesRes] = await Promise.all([
_allFolders(),
_allQueries({ query: {}, user })
]);
// We need an array of the user's queries
const queryIds = allQueries.rows.map((row) => row.id);
const queryIds = allQueriesRes.rows.map((row) => row.id);
// Get the unseen counts for all of the user's queries
const unseenCounts = await queryuser.getUserUnseenCounts({
const unseenCounts = await _getUserUnseenCounts({
query_ids: queryIds,
user_id: user.id
});
Expand All @@ -43,17 +53,17 @@ const folderResolvers = {
);
const result = {
UNREAD: queriesWithUnseen.length,
ALL_QUERIES: allQueries.rowCount - queriesWithUnseen.length
ALL_QUERIES: allQueriesRes.rowCount - queriesWithUnseen.length
};
allFolders.rows.forEach((folder) => {
const hasOccurences = allQueries.rows.filter(
allFoldersRes.rows.forEach((folder) => {
const hasOccurences = allQueriesRes.rows.filter(
(query) => query.folder === folder.code
).length;
result[folder.id] = hasOccurences;
});
return Promise.resolve(result);
} catch (err) {
Promise.reject(err);
return Promise.reject(err);
}
}
};
Expand Down
62 changes: 38 additions & 24 deletions resolvers/labels.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,30 +25,44 @@ const labelResolvers = {
},
deleteLabel: ({ params }) =>
pool.query('DELETE FROM label WHERE id = $1', [params.id]),
labelCounts: async ({ user }) => {
// Fetch all the labels and all this user's queries
const [allLabels, allQueries] = await Promise.all([
labelResolvers.allLabels(),
queries.allQueries({ query: {}, user })
]);
// Now retrieve all the label associations for this user's
// queries
const queryLabelsResult = await querylabel.allLabelsForQueries(
allQueries.rows.map((row) => row.id)
);
const queryLabels = queryLabelsResult.rows;
// Iterate each label and determine how many queries are
// assigned to it. We're going to create an object keyed on label
// ID, with a value of the associated query count
const toSend = {};
allLabels.rows.forEach((label) => {
const labelId = label.id;
const count = queryLabels.filter(
(queryLabel) => queryLabel.label_id === label.id
).length;
toSend[labelId] = count;
});
return toSend;
// If we're being tested, the testing function will pass in mocked
// versions of allLabels, allQueries & allLabelsForQueries
// so we accept those, or fall back to the real ones if they are not passed
// See https://github.com/magicmark/jest-how-do-i-mock-x/blob/master/src/function-in-same-module/README.md
// for details on this DI approach
labelCounts: async (
{ user },
_allLabels = labelResolvers.allLabels,
_allQueries = queries.allQueries,
_allLabelsForQueries = querylabel.allLabelsForQueries
) => {
try {
// Fetch all the labels and all this user's queries
const [allLabelsRes, allQueriesRes] = await Promise.all([
_allLabels(),
_allQueries({ query: {}, user })
]);
// Now retrieve all the label associations for this user's
// queries
const queryLabelsResult = await _allLabelsForQueries(
allQueriesRes.rows.map((row) => row.id)
);
const queryLabels = queryLabelsResult.rows;
// Iterate each label and determine how many queries are
// assigned to it. We're going to create an object keyed on label
// ID, with a value of the associated query count
const toSend = {};
allLabelsRes.rows.forEach((label) => {
const labelId = label.id;
const count = queryLabels.filter(
(queryLabel) => queryLabel.label_id === label.id
).length;
toSend[labelId] = count;
});
return toSend;
} catch(err) {
return Promise.reject(err);
}
}
};

Expand Down
67 changes: 56 additions & 11 deletions resolvers/queryuser.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,28 @@ const queryuserResolvers = {
'SELECT query_id, most_recent_seen FROM queryuser WHERE user_id = $1',
[id]
),
calculateUnseenCount: async ({ query_id, user_id, mostRecentSeen }) => {
const unseen = await pool.query(
getUnseenCounts: async ({ query_id, user_id, mostRecentSeen }) => {
return await pool.query(
'SELECT count(*) AS rowcount FROM message WHERE query_id = $1 AND creator_id != $2 AND id > $3',
[query_id, user_id, mostRecentSeen]
);
},
// If we're being tested, the testing function will pass in a mocked
// version of getUnseenCounts so we accept that, or fall back to the
// real ones if they are not passed
// See https://github.com/magicmark/jest-how-do-i-mock-x/blob/master/src/function-in-same-module/README.md
// for details on this DI approach
calculateUnseenCount: async ({
query_id,
user_id,
mostRecentSeen,
_getUnseenCounts = queryuserResolvers.getUnseenCounts
}) => {
const unseen = await _getUnseenCounts({
query_id,
user_id,
mostRecentSeen
});
return pool.query(
'UPDATE queryuser SET unseen_count = $1 WHERE query_id = $2 AND user_id = $3 RETURNING *',
[unseen.rows[0].rowcount, query_id, user_id]
Expand All @@ -39,10 +56,19 @@ const queryuserResolvers = {
},
// Upsert queryuser rows for a given query for all users except
// the passed one
upsertQueryUsers: async ({ query_id, creator }) => {
// If we're being tested, the testing function will pass in a mocked
// version of associated so we accept that, or fall back to the
// real ones if they are not passed
// See https://github.com/magicmark/jest-how-do-i-mock-x/blob/master/src/function-in-same-module/README.md
// for details on this DI approach
upsertQueryUsers: async ({
query_id,
creator,
_associated = queries.associated
}) => {
// Determine who needs adding / updating. This needs to be anyone
// who can see this query, i.e. participants & STAFF, except the sender
const users = await queries.associated([query_id]);
const users = await _associated([query_id]);
// Remove the sender from the list of users needing
// creating / updating
const creatorIdx = users.findIndex(u => u === creator);
Expand Down Expand Up @@ -85,14 +111,23 @@ const queryuserResolvers = {
},
// Following the deletion of a message, decrement the unseen_count
// of the appropriate users
decrementMessageDelete: async ({ message }) => {
const toDecrement = await queryuserResolvers.getUsersToDecrement({
// If we're being tested, the testing function will pass in a mocked
// version of getUsersToDecrement & decrementUnseenCount
// so we accept those, or fall back to the real ones if they are not passed
// See https://github.com/magicmark/jest-how-do-i-mock-x/blob/master/src/function-in-same-module/README.md
// for details on this DI approach
decrementMessageDelete: async ({
message,
_getUsersToDecrement = queryuserResolvers.getUsersToDecrement,
_decrementUnseenCount = queryuserResolvers.decrementUnseenCount
}) => {
const toDecrement = await _getUsersToDecrement({
query_id: message.query_id,
exclude: [message.creator_id],
most_recent_seen: message.id
});
for (let i = 0; i < toDecrement.length; i++) {
queryuserResolvers.decrementUnseenCount({
_decrementUnseenCount({
query_id: message.query_id,
user_id: toDecrement[i]
});
Expand All @@ -101,9 +136,19 @@ const queryuserResolvers = {
// Given a query_id and (optional) exclude list and (optional)
// most_recent_seen value, return the IDs of users to have their
// unseen_count decremented
getUsersToDecrement: async ({ query_id, exclude, most_recent_seen }) => {
// If we're being tested, the testing function will pass in a mocked
// version of getParticipantCounts, so we accept that,
// or fall back to the real ones if they are not passed
// See https://github.com/magicmark/jest-how-do-i-mock-x/blob/master/src/function-in-same-module/README.md
// for details on this DI approach
getUsersToDecrement: async ({
query_id,
exclude,
most_recent_seen,
_getParticipantCounts = queryuserResolvers.getParticipantCounts
}) => {
// Get all users involved in this query
const toDecrement = await queryuserResolvers.getParticipantCounts(
const toDecrement = await _getParticipantCounts(
{ query_id }
);
return toDecrement.rows
Expand All @@ -128,12 +173,12 @@ const queryuserResolvers = {
const placeholders = query_ids
.map((param, idx) => `$${idx + 1}`)
.join(', ');
optionalIn = `query_id IN (${placeholders}) AND`;
optionalIn = `query_id IN (${placeholders}) AND `;
parameters = [...query_ids];
}
parameters.push(user_id);
return pool.query(
`SELECT query_id, unseen_count FROM queryuser WHERE ${optionalIn} user_id = $${parameters.length}`,
`SELECT query_id, unseen_count FROM queryuser WHERE ${optionalIn}user_id = $${parameters.length}`,
parameters
);
},
Expand Down
23 changes: 14 additions & 9 deletions resolvers/users.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,11 @@ const userResolvers = {
'SELECT * FROM ems_user WHERE provider = $1 AND provider_id = $2',
[params.provider, params.providerId]
),
upsertUser: ({ params, body }) => {
upsertUser: ({
params,
body,
_getRoleByCode = roleResolvers.getRoleByCode
}) => {
// If we have an ID, we're updating
if (params && params.id) {
return pool.query(
Expand All @@ -59,8 +63,7 @@ const userResolvers = {
);
} else {
// Give the new user the CUSTOMER role
return roleResolvers
.getRoleByCode({ params: { code: 'CUSTOMER' } })
return _getRoleByCode({ params: { code: 'CUSTOMER' } })
.then((roles) => {
if (roles.rowCount === 1) {
const role = roles.rows[0].id;
Expand All @@ -86,20 +89,22 @@ const userResolvers = {
providerMeta,
name,
email,
avatar
avatar,
_encryption = encryption,
_getUserByProvider = userResolvers.getUserByProvider,
_upsertUser = userResolvers.upsertUser
}) => {
// If we've been provided with an email, we need to encrypt it
if (email) {
email = await encryption.encrypt(email);
email = await _encryption.encrypt(email);
}
// Check if this user already exists
return userResolvers
.getUserByProvider({ params: { provider, providerId } })
return _getUserByProvider({ params: { provider, providerId } })
.then((result) => {
if (result.rowCount === 1) {
// User exists, update them
const user = result.rows[0];
return userResolvers.upsertUser({
return _upsertUser({
params: {
id: user.id
},
Expand All @@ -113,7 +118,7 @@ const userResolvers = {
}
});
} else {
return userResolvers.upsertUser({
return _upsertUser({
body: {
name,
email,
Expand Down
30 changes: 30 additions & 0 deletions test/helpers/encryption.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// The thing we're testing
const { encrypt, decrypt } = require('../../helpers/encryption');

process.env.KEY = 'B4QEGpy_Ad_MjuEIAoSNWhegBHrNBItN2aV1Ua1g2A4';

describe('encypt', () => {
// For a given key, we receive something not null
// back. This is about as meaningful as we can hope to get with
// this test as what we get back is not predictable
it('should return a correctly encrpypted string', async (done) => {
const result = await encrypt('Death Star Plans');
expect(result).not.toBeNull();
done();
});
});

describe('decrypt', () => {
// For a given cipher text, check we get back what we expect
it('should return a correctly decrpypted string', async (done) => {
const result = await decrypt('9ec-OZZ6HZzE8gG9VheeNYlMT_rrvTD8Lg1LPjUjD1fyjq1F-4LcM4ufiqdlVCfzoW3k4sSp-O0');
expect(result).toEqual('Death Star Plans');
done();
});
it('should throw when passed an invalid cipher text', async (done) => {
await expect(
decrypt('9ac-OZZ6HZzE8gG9VheeNYlMT_rrvTD8Lg1LPjUjD1fyjq1F-4LcM4ufiqdlVCfzoW3k4sSp-O0')
).rejects.toBeTruthy();
done();
});
});
Loading

0 comments on commit 1df0c35

Please sign in to comment.