def test_DuplicateTagsAreNotIndexed(self): user = UserApi.UserApi.create('*****@*****.**', 'asdfqwer') tag1 = 'asdf' tag2 = 'qwer' tag3 = 'uiop' recipe1 = Models.Recipe( name='test recipe', description='hello world, goodbye mars', private=False, owner=user.id, tags=[tag1], ).save() recipe2 = Models.Recipe( name='test recipe', description='hello world, goodbye mars', private=False, owner=user.id, tags=[tag1, tag2], ).save() recipe3 = Models.Recipe( name='test recipe', description='hello world, goodbye mars', private=False, owner=user.id, tags=[tag2, tag3], ).save() UserTagsApi.UserTagsApi.indexRecipeTags(recipe1) UserTagsApi.UserTagsApi.indexRecipeTags(recipe2) UserTagsApi.UserTagsApi.indexRecipeTags(recipe3) userTags = UserTagsApi.UserTagsApi.getTagsForUser(user.id) self.assertEquals(sorted([tag1, tag2, tag3]), sorted(userTags.tags))
def _parseStepsFromScraper(cls, scraper) -> [Models.RecipeStep]: rcpSteps = [] step: str for step in scraper.instructions().split('\n'): stepLen = Models.RecipeStepLength( time=0, unit='m' ) potentialLenMatches = re.search(cls.lenPattern, step.strip(), re.IGNORECASE) if potentialLenMatches is not None: foundMatches = potentialLenMatches.groups() if len(foundMatches) >= 2: timeUnit = None rawUnit = foundMatches[1].lower() if rawUnit.find('minute') != -1: timeUnit = 'm' elif rawUnit.find('hour') != -1: timeUnit = 'h' elif rawUnit.find('second') != -1: timeUnit = 's' elif rawUnit.find('week') != -1: timeUnit = 'w' elif rawUnit.find('day') != -1: timeUnit = 'd' if timeUnit is not None: stepLen = Models.RecipeStepLength( time=int(foundMatches[0]), unit=timeUnit, ) rcpSteps.append(Models.RecipeStep( content=step.strip(), length=stepLen, )) return rcpSteps
def test_TagsAreUpdatedOnUpdate(self): user = UserApi.UserApi.create('*****@*****.**', 'asdfqwer') UserTagsApi.UserTagsApi.createDefaultUserTags(user.id) recipe = Models.Recipe( owner=user.id, name='test tags recipe', description='asdfqwer', private=False, sections=[ Models.RecipeSection( name='s1', steps=[Models.RecipeStep(content='asdfqwer')], ingredients=[ Models.RecipeIngredient(name='asdf', amount='qwer') ]) ], tags=['asdf', 'qwer']) # save the recipe recipe = RecipeApi.RecipeApi.createRecipe(recipe.toJson(), user.id) recipe.tags = ['zxcv', 'uiop'] RecipeApi.RecipeApi.updateRecipe(recipe.toJson(), recipe) # check there are now tags userTags = UserTagsApi.UserTagsApi.getAllTagsForUser(user.id) self.assertEquals(1, len(userTags)) # all the tags should be present self.assertEquals(['asdf', 'qwer', 'zxcv', 'uiop'].sort(), userTags[0].tags.sort())
def test_ConvertIngredientInDifferentCategoryBase(self): ing = Models.RecipeIngredient(name='Flour', amount='1 1/2', unit='c') # convert to grams converted = RecipeOperations.RecipeOperations.convertIngredientToUnit( ing, 'g') convertedAmount = Utilities.convert_to_float(converted.amount) self.assertAlmostEqual(180, round(convertedAmount))
def _parseIngsFromScraper(cls, scraper) -> [Models.RecipeIngredient]: rcpIngs = [] parser = ingreedypy.Ingreedy() for ingredient in scraper.ingredients(): ingToParse = ingredient.lower() ingToParse = cls._stripAndedFractions(ingToParse) parsedIngredient = parser.parse(ingToParse) ing = ingToParse amount = '' unit = None if parsedIngredient.get('ingredient', None) is not None: ing = parsedIngredient['ingredient'] if len(parsedIngredient.get('quantity', [])) > 0: quantity = parsedIngredient['quantity'][0] amount = quantity.get('amount', '') unit = quantity.get('unit', None) rcpIngs.append(Models.RecipeIngredient( name=ing, amount=str(amount), unit=unit, )) return rcpIngs
def createShoppingList(cls, entity: dict, userId: str) -> Models.ShoppingList: entity['owner'] = userId shoppingList = Models.ShoppingList(**entity) shoppingList.save() UserCountsApi.UserCountsApi.incrementShoppingLists(1, userId) return shoppingList
def test_GetForUserWithPrivateRecipes(self): user = UserApi.UserApi.create('*****@*****.**', 'asdfqwer') for i in range(0, DatabaseConstants.PAGE_SIZE): sectionId = str(uuid.uuid4()) RecipeApi.RecipeApi.createRecipe( dict( name=f'Test Recipe {i}', description=f'Test Des {i}', owner=user.id, private=i % 2 == 0, sections=[ dict(id=sectionId, name='Test Section', ingredients=[ dict( name=f'ing {i}', amount='100', unit='grams', ) ], steps=[ dict( content='asdf' * 20, length=Models.RecipeStepLength(time=1, unit='m'), ) ]) ], ), user.id) firstPage = RecipeApi.RecipeApi.listForUser(user.id, 0, None, allowPrivate=False) self.assertEqual(int(DatabaseConstants.PAGE_SIZE / 2), len(firstPage.data))
def test_ConvertIngredientInDifferentCategoryUnit(self): ing = Models.RecipeIngredient(name='Flour', amount='10', unit='tbsps') # convert to ounces converted = RecipeOperations.RecipeOperations.convertIngredientToUnit( ing, 'oz') convertedAmount = Utilities.convert_to_float(converted.amount) self.assertAlmostEqual(2.65, round(convertedAmount, 2))
def _createShoppingList(self) -> (Models.User, Models.ShoppingList): user = UserApi.UserApi.create('*****@*****.**', 'asdfqwer') shoppingList = Models.ShoppingList(owner=user.id, created=int(time.time()), listItems=[ Models.ShoppingListItem( name='Test Item', notes='Test Notes', ordinal=0, completed=False) ], name='Test List', allowed=[]) return user, ShoppingListApi.ShoppingListApi.createShoppingList( shoppingList.toJson(), user.id)
def test_ConvertIngredientInSameCategory(self): ing = Models.RecipeIngredient(name='Test Ingredient', amount='1 1/2', unit='cups') # convert to tbsp converted = RecipeOperations.RecipeOperations.convertIngredientToUnit( ing, 'tbsp') convertedAmount = Utilities.convert_to_float(converted.amount) self.assertAlmostEqual(24, round(convertedAmount))
def createRecipeBook(cls, entity: dict, userId: str) -> Models.RecipeBook: userCounts = UserCountsApi.UserCountsApi.getForUser(userId) if not PermissionsApi.PermissionsApi.hasProTierPermission( userId) and userCounts.recipeBooks >= 10: raise ApiExceptions.PaymentRequiredException entity['owner'] = userId createdEntity = Models.RecipeBook(**entity) createdEntity.save() UserCountsApi.UserCountsApi.incrementRecipeBooks(1, userId) return createdEntity
def createRecipeBooks(cls, num: int, user: Models.User) -> List[Models.RecipeBook]: books = [] for i in range(0, num): recipeBook = RecipeBookApi.RecipeBookApi.createRecipeBook(Models.RecipeBook( owner=str(user.id), name=f'Recipe Book {i}', description=lorem.paragraph(), recipes=[], ).toJson(), str(user.id)) books.append(recipeBook) return books
def deserializeSession(cls, token: str) -> Models.Session: decodedSession = base64.b64decode(token.encode('utf-8')).decode('utf-8') parts = decodedSession.split('.') if len(parts) == 3: parts.append('login') # noinspection PyTypeChecker return Models.Session( id=parts[0], owner=parts[1], created=int(parts[2]), sessionType=parts[3], )
def testGrantAccessToOtherUser(self): ownerUser, shoppingList = self._createShoppingList() sharingUserEmail = '*****@*****.**' sharingUser = UserApi.UserApi.create(sharingUserEmail, 'asdfqwer') shoppingList = ShoppingListApi.ShoppingListApi.grantUserAccess( shoppingList, sharingUserEmail, ownerUser.id) self.assertEquals(1, len(shoppingList.allowed)) self.assertEquals( Models.AllowedListUser(userId=sharingUser.id, username=sharingUser.email), shoppingList.allowed[0])
def requestForgottenPasswordReset(cls, email: str) -> str: user = UserApi.UserApi.getByUsername(email) if user is None: raise ApiExceptions.NotFoundException forgottenPasswordToken = Models.ForgotPasswordToken( token=str(uuid.uuid4()), owner=user.id, created=int(time.time()) ) forgottenPasswordToken.save() EmailSender.EmailSender.sendForgotPasswordEmail(user.email, forgottenPasswordToken.token) return forgottenPasswordToken.token
def _createRecipe(self) -> (Models.User, Models.Recipe): user = UserApi.UserApi.create('*****@*****.**', 'asdfqwer') recipeBody = Models.Recipe( name='Test Recipe', description='A really cool recipe that we can test with', owner=user.id, private=False, sections=[ Models.RecipeSection( name='test-section', id=Models.DEFAULT_SECTION_ID, steps=[ Models.RecipeStep(content='asdfqwer', length=Models.RecipeStepLength( time=10, unit='m')), Models.RecipeStep(content='zxcvbnm,', length=Models.RecipeStepLength( time=1, unit='d')), ], ingredients=[ Models.RecipeIngredient(name='milk', amount='1/2', unit='cup'), Models.RecipeIngredient(name='egg', amount='3'), Models.RecipeIngredient(name='water', amount='150', unit='ml') ], ) ], tags=[], advanced=Models.RecipeAdvancedOptions()) recipe = RecipeApi.RecipeApi.createRecipe(recipeBody.toJson(), user.id) self.assertEqual('Test Recipe', recipe.name) self.assertEqual('A really cool recipe that we can test with', recipe.description) self.assertEqual(2, len(recipe.sections[0].steps)) self.assertEqual(3, len(recipe.sections[0].ingredients)) return user, recipe
def parseScraperIntoRecipe(cls, url: str, userId: str) -> Models.Recipe: scraper = recipe_scrapers.scrape_me(url, wild_mode=True) return Models.Recipe( name=scraper.title(), description=cls._parseDescriptionFromScraper(url, scraper), private=False, created=int(time.time()), owner=userId, advanced=Models.RecipeAdvancedOptions( highAltitude=False, altitude=0 ), tags=[], sections=[ Models.RecipeSection( id=Models.DEFAULT_SECTION_ID, ingredients=cls._parseIngsFromScraper(scraper), steps=cls._parseStepsFromScraper(scraper), name=scraper.title() ) ], )
def test_UpdateRecipe(self): _, recipe = self._createRecipe() recipe.name = 'A new name' recipe.sections[0].ingredients.append( Models.RecipeIngredient(name='A new ingredient', amount='1.25', unit='T')) recipe.sections[0].steps = recipe.sections[0].steps[0:2] updatedRecipe = RecipeApi.RecipeApi.updateRecipe( recipe.toJson(), recipe) self.assertEqual(4, len(updatedRecipe.sections[0].ingredients)) self.assertEqual(2, len(updatedRecipe.sections[0].steps)) self.assertIsNotNone(updatedRecipe.id) self.assertEqual(recipe.name, updatedRecipe.name)
def test_IndexRecipeWithUniqueTags(self): user = UserApi.UserApi.create('*****@*****.**', 'asdfqwer') recipe = Models.Recipe( name='test recipe', description='hello world, goodbye mars', private=False, owner=user.id, tags=['asdf', 'qwer'], ).save() UserTagsApi.UserTagsApi.indexRecipeTags(recipe) userTags = UserTagsApi.UserTagsApi.getTagsForUser(user.id) self.assertEquals(sorted(userTags.tags), sorted(recipe.tags))
def test_DeleteUser(self): user = self._createUser() createdId = user.id sessions = [] recipes = [] books = [] for i in range(0, 20): sessions.append(SessionApi.SessionApi.createSessionForUser( user.id)) recipes.append( RecipeApi.RecipeApi.createRecipe( Models.Recipe( name=f'Recipe {i}', description=f'Recipe number {i}', sections=[ Models.RecipeSection( id=str(uuid.uuid4()), name='test-section', ingredients=[ Models.RecipeIngredient( name=f'test ing {i}', amount=str(i), ) ], steps=[ Models.RecipeStep( content='asdfqwer', length=Models.RecipeStepLength()) ], ) ], private=False, owner=createdId).toJson(), createdId)) books.append( RecipeBookApi.RecipeBookApi.createRecipeBook( Models.RecipeBook(recipes=[], description=f'Test book {i} asdfqwer', name=f'Test Book {i}', owner=user.id).toJson(), createdId)) # delete the user thread = UserApi.UserApi.delete(createdId) thread.join() # try to get the user -- it shouldn't be there fetchedUser = UserApi.UserApi.getById(createdId) self.assertIsNone(fetchedUser) # there should be no sessions self.assertEquals(0, len(list(Models.Session.getByOwner(user.id)))) # there should be no recipes self.assertEquals(0, len(list(Models.Recipe.getByOwner(user.id)))) # there should be no recipe books self.assertEquals(0, len(list(Models.RecipeBook.getByOwner(user.id)))) # there should be no user tags self.assertEquals(0, len(list(Models.UserTags.getByOwner(user.id))))
def create(cls, username: str, password: str, createDefaults=True) -> Models.User: # expecting an email and password here, but we need to salt/hash the password passwordHash, salt, nonce = Encryption.encryptPassword(password) user = Models.User(email=username, password=passwordHash, salt=salt, nonce=nonce, created=int(time.time()), preferences={}, permissions=[], subscriptionId=None) user.save() if createDefaults: cls._createDefaults(user.id) return user
def stageUser(cls, email: str, password: str) -> Models.StagedUser: passwordHash, salt, nonce = Encryption.encryptPassword(password) stagedUser = Models.StagedUser(email=email, password=passwordHash, salt=salt, nonce=nonce, created=int(time.time()), token=str(uuid.uuid4())) try: stagedUser.save() except pymongo.errors.DuplicateKeyError: raise ApiExceptions.ConflictException() # check that this name doesn't clash with another user matchingUser = cls.getByUsername(email) if matchingUser is not None: raise ApiExceptions.ConflictException() EmailSender.EmailSender.sendConfirmSignupToken(email, stagedUser.token) return stagedUser
def createFromStagedUser(cls, token: str) -> Models.User: stagedUser: Models.StagedUser = next( Models.StagedUser.query({'token': token})) if stagedUser is None: raise ApiExceptions.NotFoundException user = Models.User(email=stagedUser.email, password=stagedUser.password, salt=stagedUser.salt, nonce=stagedUser.nonce, created=int(time.time()), preferences={}, permissions=[], subscriptionId=None) # create the user entity user.save() cls._createDefaults(user.id) # destroy the staged user stagedUser.delete() return user
def createRecipes(cls, num: int, user: Models.User, books: List[Models.RecipeBook]) -> List[Models.Recipe]: ingNames = CommonIngredientApi.CommonIngredientApi.getCommonIngredientNames() measureNames = list(map(lambda km: km.name.p, RecipeApi.RecipeApi.getKnownMeasures())) recipes = [] for i in range(0, num): sections = [] for sectionNum in range(0, random.randint(1, 6)): steps = [] for _ in range(0, random.randint(1, 20)): steps.append(Models.RecipeStep( content=lorem.paragraph(), length=Models.RecipeStepLength( time=random.randint(0, 60), unit='m' ) )) ingredients = [] for _ in range(0, random.randint(1, 10)): ingredients.append(Models.RecipeIngredient( name=random.choice(ingNames), amount=str(random.random() * 100), unit=random.choice(measureNames) )) sections.append(Models.RecipeSection( name=f'Test Section {sectionNum}', steps=steps, ingredients=ingredients, id=str(uuid.uuid4()) )) recipe = RecipeApi.RecipeApi.createRecipe(Models.Recipe( name=f'Test Recipe {i}', description=lorem.paragraph(), private=random.randint(0, 100) % 2 == 0, advanced=Models.RecipeAdvancedOptions(), sections=sections, tags=[], owner=str(user.id) ).toJson(), str(user.id)) recipes.append(recipe) addToBook = random.randint(0, 100) % 2 == 0 if addToBook: recipeBook = random.choice(books) recipeBook.recipes.append(recipe.id) for recipeBook in books: recipeBook.save() return recipes
def createRecipe(cls, entity: dict, userId: str) -> Models.Recipe: for section in entity['sections']: if len(section.get('steps', [])) == 0 or len( section.get('ingredients', [])) == 0: raise ApiExceptions.BadRequestException userCounts = UserCountsApi.UserCountsApi.getForUser(userId) user = Models.User.getById(userId) if not PermissionsApi.PermissionsApi.hasProTierPermission( user.id) and userCounts.recipes >= 50: raise ApiExceptions.PaymentRequiredException createdEntity = Models.Recipe(**entity) createdEntity.owner = userId createdEntity.save() # index the tags on this recipe TaskRunner.TaskRunner.runTask(RecipeTasks.RecipeTasks.indexRecipeTags, createdEntity) UserCountsApi.UserCountsApi.incrementRecipes(1, userId) return createdEntity
def loginUser(cls, username: str, password: str) -> (str, Models.User): userLookup = cls.getByUsername(username) if userLookup is not None: expectedPassword = userLookup.password expectedSalt = userLookup.salt expectedNonce = userLookup.nonce if Encryption.comparePasswords(expectedPassword, expectedNonce, expectedSalt, password): # generate a session for the user session = Models.Session( owner=userLookup.id, created=int(time.time()), sessionType='login', ) session.save() return SessionApi.SessionApi.serializeSession( session), userLookup else: raise ApiExceptions.ForbiddenException() else: raise ApiExceptions.NotFoundException()
def grantUserAccess(cls, shoppingList: Models.ShoppingList, emailAddress: str, requestingId: str) -> Models.ShoppingList: if requestingId != shoppingList.owner: raise ApiExceptions.ForbiddenException userToAdd = UserApi.UserApi.getByUsername(emailAddress) if userToAdd is None: raise ApiExceptions.NotFoundException allowedUsers = shoppingList.allowed or [] allowedUser: Models.AllowedListUser mappedIds = [allowedUser.userId for allowedUser in allowedUsers] if userToAdd.id not in mappedIds: allowedUser = Models.AllowedListUser(userId=userToAdd.id, username=userToAdd.email) allowedUsers.append(allowedUser) shoppingList.allowed = allowedUsers shoppingList.save() owner = Models.User.getById(shoppingList.owner) EmailSender.EmailSender.sendSharedShoppingList( emailAddress, owner.email, shoppingList.id) return shoppingList return shoppingList
def test_QueryIngredient(self): ingredientName = 'Query Ingredient' user, recipe = self._createRecipe() recipe.sections[0].ingredients.append( Models.RecipeIngredient(name=ingredientName, unit='cups', amount='1 1/2')) recipe = RecipeApi.RecipeApi.updateRecipe(recipe.toJson(), recipe) recipes = RecipeApi.RecipeApi.listForUser( user.id, 0, {'ingredients': [ingredientName]}, allowPrivate=True) self.assertEquals(1, len(recipes.data)) self.assertEquals(recipe.id, str(recipes.data[0].id)) recipes = RecipeApi.RecipeApi.listForUser( user.id, 0, {'ingredients': ['unknown name']}, allowPrivate=True) self.assertEquals(0, len(recipes.data)) recipes = RecipeApi.RecipeApi.listForUser( user.id, 0, {'ingredients': ['unknown name', ingredientName.upper()]}, allowPrivate=True) self.assertEquals(1, len(recipes.data))
def test_PrunedTagsAreNotIndexed(self): user = UserApi.UserApi.create('*****@*****.**', 'asdfqwer') userTags = UserTagsApi.UserTagsApi.getTagsForUser(user.id) tagToPrune = 'uiop' userTags.pruned = [tagToPrune] userTags.save() # try to index a recipe with uiop as the tag recipe = Models.Recipe( name='test recipe', description='hello world, goodbye mars', private=False, owner=user.id, tags=['asdf', 'qwer', tagToPrune], ).save() updatedTags = UserTagsApi.UserTagsApi.indexRecipeTags(recipe) self.assertIn('asdf', updatedTags.tags) self.assertIn('qwer', updatedTags.tags) self.assertNotIn(tagToPrune, updatedTags.tags) self.assertIn(tagToPrune, updatedTags.pruned)
def createDefaultUserCounts(cls, userId: str) -> Models.UserCounts: model = Models.UserCounts(owner=userId, recipes=0, recipeBooks=0, shoppingLists=0) return model.save()