0
0
mirror of https://github.com/naturalcrit/homebrewery.git synced 2026-01-07 12:02:44 +00:00

add initial set of tests for api

This commit is contained in:
Charlie Humphreys
2022-12-06 00:01:38 -06:00
parent 2e6fcafc68
commit a451e562fb
2 changed files with 674 additions and 309 deletions

View File

@@ -15,7 +15,11 @@ const { nanoid } = require('nanoid');
// }); // });
// }; // };
const getId = (req)=>{ const MAX_TITLE_LENGTH = 100;
const api = {
homebrewApi : router,
getId : (req)=>{
// Set the id and initial potential google id, where the google id is present on the existing brew. // Set the id and initial potential google id, where the google id is present on the existing brew.
let id = req.params.id, googleId = req.body?.googleId; let id = req.params.id, googleId = req.body?.googleId;
@@ -25,13 +29,12 @@ const getId = (req)=>{
id = id.slice(-12); id = id.slice(-12);
} }
return { id, googleId }; return { id, googleId };
}; },
getBrew : (accessType, fetchGoogle = true)=>{
const getBrew = (accessType, fetchGoogle = true)=>{
// Create middleware with the accessType passed in as part of the scope // Create middleware with the accessType passed in as part of the scope
return async (req, res, next)=>{ return async (req, res, next)=>{
// Get relevant IDs for the brew // Get relevant IDs for the brew
const { id, googleId } = getId(req); const { id, googleId } = api.getId(req);
// Try to find the document in the Homebrewery database -- if it doesn't exist, that's fine. // Try to find the document in the Homebrewery database -- if it doesn't exist, that's fine.
let stub = await HomebrewModel.get(accessType === 'edit' ? { editId: id } : { shareId: id }) let stub = await HomebrewModel.get(accessType === 'edit' ? { editId: id } : { shareId: id })
@@ -43,7 +46,7 @@ const getBrew = (accessType, fetchGoogle = true)=>{
} }
}); });
stub = stub?.toObject(); stub = stub?.toObject();
if(stub?.authors && !stub?.authors.includes(req.account.username)) { if(accessType === 'edit' && stub?.authors?.length > 0 && !stub?.authors.includes(req.account?.username)) {
throw 'Current logged in user does not have access to this brew.'; throw 'Current logged in user does not have access to this brew.';
} }
@@ -58,7 +61,7 @@ const getBrew = (accessType, fetchGoogle = true)=>{
// If we can't find the google brew and there is a google id for the brew, throw an error. // If we can't find the google brew and there is a google id for the brew, throw an error.
if(!googleBrew) throw googleError; if(!googleBrew) throw googleError;
// Combine the Homebrewery stub with the google brew, or if the stub doesn't exist just use the google brew // Combine the Homebrewery stub with the google brew, or if the stub doesn't exist just use the google brew
stub = stub ? _.assign({ ...excludeStubProps(stub), stubbed: true }, excludeGoogleProps(googleBrew)) : googleBrew; stub = stub ? _.assign({ ...api.excludeStubProps(stub), stubbed: true }, api.excludeGoogleProps(googleBrew)) : googleBrew;
} }
// If after all of that we still don't have a brew, throw an exception // If after all of that we still don't have a brew, throw an exception
@@ -73,9 +76,8 @@ const getBrew = (accessType, fetchGoogle = true)=>{
next(); next();
}; };
}; },
mergeBrewText : (brew)=>{
const mergeBrewText = (brew)=>{
let text = brew.text; let text = brew.text;
if(brew.style !== undefined) { if(brew.style !== undefined) {
text = `\`\`\`css\n` + text = `\`\`\`css\n` +
@@ -89,17 +91,13 @@ const mergeBrewText = (brew)=>{
`\`\`\`\n\n` + `\`\`\`\n\n` +
`${text}`; `${text}`;
return text; return text;
}; },
getGoodBrewTitle : (text)=>{
const MAX_TITLE_LENGTH = 100;
const getGoodBrewTitle = (text)=>{
const tokens = Markdown.marked.lexer(text); const tokens = Markdown.marked.lexer(text);
return (tokens.find((token)=>token.type === 'heading' || token.type === 'paragraph')?.text || 'No Title') return (tokens.find((token)=>token.type === 'heading' || token.type === 'paragraph')?.text || 'No Title')
.slice(0, MAX_TITLE_LENGTH); .slice(0, MAX_TITLE_LENGTH);
}; },
excludePropsFromUpdate : (brew)=>{
const excludePropsFromUpdate = (brew)=>{
// Remove undesired properties // Remove undesired properties
const modified = _.clone(brew); const modified = _.clone(brew);
const propsToExclude = ['_id', 'views', 'lastViewed', 'editId', 'shareId', 'googleId']; const propsToExclude = ['_id', 'views', 'lastViewed', 'editId', 'shareId', 'googleId'];
@@ -107,43 +105,38 @@ const excludePropsFromUpdate = (brew)=>{
delete modified[prop]; delete modified[prop];
} }
return modified; return modified;
}; },
excludeGoogleProps : (brew)=>{
const excludeGoogleProps = (brew)=>{
const modified = _.clone(brew); const modified = _.clone(brew);
const propsToExclude = ['tags', 'systems', 'published', 'authors', 'owner', 'views', 'thumbnail']; const propsToExclude = ['tags', 'systems', 'published', 'authors', 'owner', 'views', 'thumbnail'];
for (const prop of propsToExclude) { for (const prop of propsToExclude) {
delete modified[prop]; delete modified[prop];
} }
return modified; return modified;
}; },
excludeStubProps : (brew)=>{
const excludeStubProps = (brew)=>{
const propsToExclude = ['text', 'textBin', 'renderer', 'pageCount', 'version']; const propsToExclude = ['text', 'textBin', 'renderer', 'pageCount', 'version'];
for (const prop of propsToExclude) { for (const prop of propsToExclude) {
brew[prop] = undefined; brew[prop] = undefined;
} }
return brew; return brew;
}; },
beforeNewSave : (account, brew)=>{
const beforeNewSave = (account, brew)=>{
if(!brew.title) { if(!brew.title) {
brew.title = getGoodBrewTitle(brew.text); brew.title = api.getGoodBrewTitle(brew.text);
} }
brew.authors = (account) ? [account.username] : []; brew.authors = (account) ? [account.username] : [];
brew.text = mergeBrewText(brew); brew.text = api.mergeBrewText(brew);
}; },
newGoogleBrew : async (account, brew, res)=>{
const newGoogleBrew = async (account, brew, res)=>{
const oAuth2Client = GoogleActions.authCheck(account, res); const oAuth2Client = GoogleActions.authCheck(account, res);
const newBrew = excludeGoogleProps(brew); const newBrew = api.excludeGoogleProps(brew);
return await GoogleActions.newGoogleBrew(oAuth2Client, newBrew); return await GoogleActions.newGoogleBrew(oAuth2Client, newBrew);
}; },
newBrew : async (req, res)=>{
const newBrew = async (req, res)=>{
const brew = req.body; const brew = req.body;
const { saveToGoogle } = req.query; const { saveToGoogle } = req.query;
@@ -151,7 +144,7 @@ const newBrew = async (req, res)=>{
delete brew.shareId; delete brew.shareId;
delete brew.googleId; delete brew.googleId;
beforeNewSave(req.account, brew); api.beforeNewSave(req.account, brew);
const newHomebrew = new HomebrewModel(brew); const newHomebrew = new HomebrewModel(brew);
newHomebrew.editId = nanoid(12); newHomebrew.editId = nanoid(12);
@@ -159,13 +152,13 @@ const newBrew = async (req, res)=>{
let googleId, saved; let googleId, saved;
if(saveToGoogle) { if(saveToGoogle) {
googleId = await newGoogleBrew(req.account, newHomebrew, res) googleId = await api.newGoogleBrew(req.account, newHomebrew, res)
.catch((err)=>{ .catch((err)=>{
console.error(err); console.error(err);
res.status(err?.status || err?.response?.status || 500).send(err?.message || err); res.status(err?.status || err?.response?.status || 500).send(err?.message || err);
}); });
if(!googleId) return; if(!googleId) return;
excludeStubProps(newHomebrew); api.excludeStubProps(newHomebrew);
newHomebrew.googleId = googleId; newHomebrew.googleId = googleId;
} else { } else {
// Compress brew text to binary before saving // Compress brew text to binary before saving
@@ -183,21 +176,20 @@ const newBrew = async (req, res)=>{
saved = saved.toObject(); saved = saved.toObject();
res.status(200).send(saved); res.status(200).send(saved);
}; },
updateBrew : async (req, res)=>{
const updateBrew = async (req, res)=>{
// Initialize brew from request and body, destructure query params, set a constant for the google id, and set the initial value for the after-save method // Initialize brew from request and body, destructure query params, set a constant for the google id, and set the initial value for the after-save method
let brew = _.assign(req.brew, excludePropsFromUpdate(req.body)); let brew = _.assign(req.brew, api.excludePropsFromUpdate(req.body));
const { saveToGoogle, removeFromGoogle } = req.query; const { saveToGoogle, removeFromGoogle } = req.query;
const googleId = brew.googleId; const googleId = brew.googleId;
let afterSave = async ()=>true; let afterSave = async ()=>true;
brew.text = mergeBrewText(brew); brew.text = api.mergeBrewText(brew);
if(brew.googleId && removeFromGoogle) { if(brew.googleId && removeFromGoogle) {
// If the google id exists and we're removing it from google, set afterSave to delete the google brew and mark the brew's google id as undefined // If the google id exists and we're removing it from google, set afterSave to delete the google brew and mark the brew's google id as undefined
afterSave = async ()=>{ afterSave = async ()=>{
return await deleteGoogleBrew(req.account, googleId, brew.editId, res) return await api.deleteGoogleBrew(req.account, googleId, brew.editId, res)
.catch((err)=>{ .catch((err)=>{
console.error(err); console.error(err);
res.status(err?.status || err?.response?.status || 500).send(err.message || err); res.status(err?.status || err?.response?.status || 500).send(err.message || err);
@@ -207,7 +199,7 @@ const updateBrew = async (req, res)=>{
brew.googleId = undefined; brew.googleId = undefined;
} else if(!brew.googleId && saveToGoogle) { } else if(!brew.googleId && saveToGoogle) {
// If we don't have a google id and the user wants to save to google, create the google brew and set the google id on the brew // If we don't have a google id and the user wants to save to google, create the google brew and set the google id on the brew
brew.googleId = await newGoogleBrew(req.account, excludeGoogleProps(brew), res) brew.googleId = await api.newGoogleBrew(req.account, api.excludeGoogleProps(brew), res)
.catch((err)=>{ .catch((err)=>{
console.error(err); console.error(err);
res.status(err.status || err.response.status).send(err.message || err); res.status(err.status || err.response.status).send(err.message || err);
@@ -215,7 +207,7 @@ const updateBrew = async (req, res)=>{
if(!brew.googleId) return; if(!brew.googleId) return;
} else if(brew.googleId) { } else if(brew.googleId) {
// If the google id exists and no other actions are being performed, update the google brew // If the google id exists and no other actions are being performed, update the google brew
const updated = await GoogleActions.updateGoogleBrew(excludeGoogleProps(brew)) const updated = await GoogleActions.updateGoogleBrew(api.excludeGoogleProps(brew))
.catch((err)=>{ .catch((err)=>{
console.error(err); console.error(err);
res.status(err?.response?.status || 500).send(err); res.status(err?.response?.status || 500).send(err);
@@ -225,7 +217,7 @@ const updateBrew = async (req, res)=>{
if(brew.googleId) { if(brew.googleId) {
// If the google id exists after all those actions, exclude the props that are stored in google and aren't needed for rendering the brew items // If the google id exists after all those actions, exclude the props that are stored in google and aren't needed for rendering the brew items
excludeStubProps(brew); api.excludeStubProps(brew);
} else { } else {
// Compress brew text to binary before saving // Compress brew text to binary before saving
brew.textBin = zlib.deflateRawSync(brew.text); brew.textBin = zlib.deflateRawSync(brew.text);
@@ -262,20 +254,18 @@ const updateBrew = async (req, res)=>{
if(!after) return; if(!after) return;
res.status(200).send(brew); res.status(200).send(brew);
}; },
deleteGoogleBrew : async (account, id, editId, res)=>{
const deleteGoogleBrew = async (account, id, editId, res)=>{
const auth = await GoogleActions.authCheck(account, res); const auth = await GoogleActions.authCheck(account, res);
await GoogleActions.deleteGoogleBrew(auth, id, editId); await GoogleActions.deleteGoogleBrew(auth, id, editId);
return true; return true;
}; },
deleteBrew : async (req, res, next)=>{
const deleteBrew = async (req, res, next)=>{
// Delete an orphaned stub if its Google brew doesn't exist // Delete an orphaned stub if its Google brew doesn't exist
try { try {
await getBrew('edit')(req, res, ()=>{}); await api.getBrew('edit')(req, res, ()=>{});
} catch (err) { } catch (err) {
const { id, googleId } = getId(req); const { id, googleId } = api.getId(req);
console.warn(`No google brew found for id ${googleId}, the stub with id ${id} will be deleted.`); console.warn(`No google brew found for id ${googleId}, the stub with id ${id} will be deleted.`);
await HomebrewModel.deleteOne({ editId: id }); await HomebrewModel.deleteOne({ editId: id });
return next(); return next();
@@ -319,7 +309,7 @@ const deleteBrew = async (req, res, next)=>{
} }
} }
if(shouldDeleteGoogleBrew) { if(shouldDeleteGoogleBrew) {
const deleted = await deleteGoogleBrew(account, googleId, editId, res) const deleted = await api.deleteGoogleBrew(account, googleId, editId, res)
.catch((err)=>{ .catch((err)=>{
console.error(err); console.error(err);
res.status(500).send(err); res.status(500).send(err);
@@ -328,15 +318,13 @@ const deleteBrew = async (req, res, next)=>{
} }
res.status(204).send(); res.status(204).send();
}
}; };
router.post('/api', asyncHandler(newBrew)); router.post('/api', asyncHandler(api.newBrew));
router.put('/api/:id', asyncHandler(getBrew('edit', false)), asyncHandler(updateBrew)); router.put('/api/:id', asyncHandler(api.getBrew('edit', false)), asyncHandler(api.updateBrew));
router.put('/api/update/:id', asyncHandler(getBrew('edit', false)), asyncHandler(updateBrew)); router.put('/api/update/:id', asyncHandler(api.getBrew('edit', false)), asyncHandler(api.updateBrew));
router.delete('/api/:id', asyncHandler(deleteBrew)); router.delete('/api/:id', asyncHandler(api.deleteBrew));
router.get('/api/remove/:id', asyncHandler(deleteBrew)); router.get('/api/remove/:id', asyncHandler(api.deleteBrew));
module.exports = { module.exports = api;
homebrewApi : router,
getBrew
};

377
server/homebrew.api.spec.js Normal file
View File

@@ -0,0 +1,377 @@
/* eslint-disable max-lines */
describe('Tests for api', ()=>{
let api;
let google;
let model;
let hbBrew;
let googleBrew;
beforeEach(()=>{
google = require('./googleActions.js');
model = require('./homebrew.model.js').model;
jest.mock('./googleActions.js');
jest.mock('./homebrew.model.js');
api = require('./homebrew.api');
hbBrew = {
text : `brew text`,
style : 'hello yes i am css',
title : 'some title',
description : 'this is a description',
tags : ['something', 'fun'],
systems : ['D&D 5e'],
renderer : 'v3',
theme : 'phb',
published : true,
authors : ['1', '2'],
owner : '1',
thumbnail : '',
_id : 'mongoid',
editId : 'abcdefg',
shareId : 'hijklmnop',
views : 1,
lastViewed : new Date(),
version : 1,
pageCount : 1,
textBin : ''
};
googleBrew = {
...hbBrew,
googleId : '12345'
};
});
afterEach(()=>{
jest.restoreAllMocks();
});
describe('getId', ()=>{
it('should return only id if google id is not present', ()=>{
const { id, googleId } = api.getId({
params : {
id : 'abcdefgh'
}
});
expect(id).toEqual('abcdefgh');
expect(googleId).toBeUndefined();
});
it('should return id and google id from request body', ()=>{
const { id, googleId } = api.getId({
params : {
id : 'abcdefgh'
},
body : {
googleId : '12345'
}
});
expect(id).toEqual('abcdefgh');
expect(googleId).toEqual('12345');
});
it('should return id and google id params', ()=>{
const { id, googleId } = api.getId({
params : {
id : '123456789012abcdefghijkl'
}
});
expect(id).toEqual('abcdefghijkl');
expect(googleId).toEqual('123456789012');
});
});
describe('getBrew', ()=>{
const toBrewPromise = (brew)=>new Promise((res)=>res({ toObject: ()=>brew }));
const notFoundError = 'Brew not found in Homebrewery database or Google Drive';
it('returns middleware', ()=>{
const getFn = api.getBrew('share');
expect(getFn).toBeInstanceOf(Function);
});
it('should fetch from mongoose', async ()=>{
const testBrew = { title: 'test brew', authors: [] };
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
model.get = jest.fn(()=>toBrewPromise(testBrew));
const fn = api.getBrew('share', true);
const req = { brew: {} };
const next = jest.fn();
await fn(req, null, next);
expect(req.brew).toEqual(testBrew);
expect(next).toHaveBeenCalled();
expect(api.getId).toHaveBeenCalledWith(req);
expect(model.get).toHaveBeenCalledWith({ shareId: '1' });
});
it('should handle mongoose error', async ()=>{
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
model.get = jest.fn(()=>new Promise((_, rej)=>rej('Unable to find brew')));
const fn = api.getBrew('share', true);
const req = { brew: {} };
const next = jest.fn();
let err;
try {
await fn(req, null, next);
} catch (e) {
err = e;
}
expect(err).toEqual(notFoundError);
expect(req.brew).toEqual({});
expect(next).not.toHaveBeenCalled();
expect(api.getId).toHaveBeenCalledWith(req);
expect(model.get).toHaveBeenCalledWith({ shareId: '1' });
});
it('changes tags from string to array', async ()=>{
const testBrew = { title: 'test brew', authors: [], tags: 'tag' };
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
model.get = jest.fn(()=>toBrewPromise(testBrew));
const fn = api.getBrew('share', true);
const req = { brew: {} };
const next = jest.fn();
await fn(req, null, next);
expect(req.brew.tags).toEqual([]);
expect(next).toHaveBeenCalled();
});
it('throws if invalid author', async ()=>{
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', authors: ['a'] }));
const fn = api.getBrew('edit', true);
const req = { brew: {} };
let err;
try {
await fn(req, null, null);
} catch (e) {
err = e;
}
expect(err).toEqual('Current logged in user does not have access to this brew.');
});
it('does not throw if no authors', async ()=>{
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', authors: [] }));
const fn = api.getBrew('edit', true);
const req = { brew: {} };
const next = jest.fn();
await fn(req, null, next);
expect(next).toHaveBeenCalled();
});
it('does not throw if valid author', async ()=>{
api.getId = jest.fn(()=>({ id: '1', googleId: undefined }));
model.get = jest.fn(()=>toBrewPromise({ title: 'test brew', authors: ['a'] }));
const fn = api.getBrew('edit', true);
const req = { brew: {}, account: { username: 'a' } };
const next = jest.fn();
await fn(req, null, next);
expect(next).toHaveBeenCalled();
});
it('fetches google brew if needed', async()=>{
const stubBrew = { title: 'test brew', authors: ['a'] };
const googleBrew = { title: 'test google brew', text: 'brew text' };
api.getId = jest.fn(()=>({ id: '1', googleId: '2' }));
model.get = jest.fn(()=>toBrewPromise(stubBrew));
google.getGoogleBrew = jest.fn(()=>new Promise((res)=>res(googleBrew)));
const fn = api.getBrew('share', true);
const req = { brew: {} };
const next = jest.fn();
await fn(req, null, next);
expect(req.brew).toEqual({
title : 'test google brew',
authors : ['a'],
text : 'brew text',
stubbed : true
});
expect(next).toHaveBeenCalled();
expect(api.getId).toHaveBeenCalledWith(req);
expect(model.get).toHaveBeenCalledWith({ shareId: '1' });
expect(google.getGoogleBrew).toHaveBeenCalledWith('2', '1', 'share');
});
});
describe('mergeBrewText', ()=>{
it('should set metadata and no style if it is not present', ()=>{
const result = api.mergeBrewText({
text : `brew`,
title : 'some title',
description : 'this is a description',
tags : ['something', 'fun'],
systems : ['D&D 5e'],
renderer : 'v3',
theme : 'phb',
googleId : '12345'
});
expect(result).toEqual(`\`\`\`metadata
title: some title
description: this is a description
tags:
- something
- fun
systems:
- D&D 5e
renderer: v3
theme: phb
\`\`\`
brew`);
});
it('should set metadata and style', ()=>{
const result = api.mergeBrewText({
text : `brew`,
style : 'hello yes i am css',
title : 'some title',
description : 'this is a description',
tags : ['something', 'fun'],
systems : ['D&D 5e'],
renderer : 'v3',
theme : 'phb',
googleId : '12345'
});
expect(result).toEqual(`\`\`\`metadata
title: some title
description: this is a description
tags:
- something
- fun
systems:
- D&D 5e
renderer: v3
theme: phb
\`\`\`
\`\`\`css
hello yes i am css
\`\`\`
brew`);
});
});
describe('exclusion methods', ()=>{
it('excludePropsFromUpdate removes the correct keys', ()=>{
const sent = Object.assign({}, googleBrew);
const result = api.excludePropsFromUpdate(sent);
expect(sent).toEqual(googleBrew);
expect(result._id).toBeUndefined();
expect(result.views).toBeUndefined();
expect(result.lastViewed).toBeUndefined();
expect(result.editId).toBeUndefined();
expect(result.shareId).toBeUndefined();
expect(result.googleId).toBeUndefined();
});
it('excludeGoogleProps removes the correct keys', ()=>{
const sent = Object.assign({}, googleBrew);
const result = api.excludeGoogleProps(sent);
expect(sent).toEqual(googleBrew);
expect(result.tags).toBeUndefined();
expect(result.systems).toBeUndefined();
expect(result.published).toBeUndefined();
expect(result.authors).toBeUndefined();
expect(result.owner).toBeUndefined();
expect(result.views).toBeUndefined();
expect(result.thumbnail).toBeUndefined();
});
it('excludeStubProps removes the correct keys from the original object', ()=>{
const sent = Object.assign({}, googleBrew);
const result = api.excludeStubProps(sent);
expect(sent).not.toEqual(googleBrew);
expect(result.text).toBeUndefined();
expect(result.textBin).toBeUndefined();
expect(result.renderer).toBeUndefined();
expect(result.pageCount).toBeUndefined();
expect(result.version).toBeUndefined();
});
});
describe('beforeNewSave', ()=>{
it('sets the title if none', ()=>{
const brew = {
...hbBrew,
title : undefined
};
api.beforeNewSave({}, brew);
expect(brew.title).toEqual('brew text');
});
it('does not set the title if present', ()=>{
const brew = {
...hbBrew,
title : 'test'
};
api.beforeNewSave({}, brew);
expect(brew.title).toEqual('test');
});
it('does not set authors if account missing username', ()=>{
api.beforeNewSave({}, hbBrew);
expect(hbBrew.authors).toEqual([]);
});
it('sets authors if account has username', ()=>{
api.beforeNewSave({ username: 'hi' }, hbBrew);
expect(hbBrew.authors).toEqual(['hi']);
});
it('merges brew text', ()=>{
api.mergeBrewText = jest.fn(()=>'merged');
api.beforeNewSave({}, hbBrew);
expect(api.mergeBrewText).toHaveBeenCalled();
expect(hbBrew.text).toEqual('merged');
});
});
describe('newGoogleBrew', ()=>{
it('should call the correct methods', ()=>{
google.authCheck = jest.fn().mockImplementation(()=>'client');
api.excludeGoogleProps = jest.fn(()=>'newBrew');
google.newGoogleBrew = jest.fn(()=>'id');
const acct = { username: 'test' };
const brew = { title: 'test title' };
const res = { send: jest.fn(()=>{}) };
api.newGoogleBrew(acct, brew, res);
expect(google.authCheck).toHaveBeenCalledWith(acct, res);
expect(api.excludeGoogleProps).toHaveBeenCalledWith(brew);
expect(google.newGoogleBrew).toHaveBeenCalledWith('client', 'newBrew');
});
});
});