# Copyright 2019 Mike Shoup # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import json from decimal import Decimal from io import BytesIO from humulus.couch import get_db, get_doc, put_doc from humulus.recipes import FermentableForm, HopForm, RecipeForm, YeastForm def test_index(client): """Test success in retrieving index.""" # Test for bad request response = client.get('/recipes/?sort_by=foobar') assert response.status_code == 400 # Verify defaults response = client.get('/recipes/') assert response.status_code == 200 # Assert recipes are returned assert b'Awesome Lager' in response.data assert b'Awesome Beer' in response.data assert ( b'"/recipes/?descending=true&sort_by=name">Name ↑' in response.data ) assert ( b'"/recipes/?descending=false&sort_by=date">Created On' in response.data ) # Test sort by name descending response = client.get('/recipes/?descending=true&sort_by=name') assert ( b'"/recipes/?descending=false&sort_by=name">Name ↓' in response.data ) assert ( b'"/recipes/?descending=false&sort_by=date">Created On' in response.data ) # Test sort by date ascending response = client.get('/recipes/?descending=false&sort_by=date') assert ( b'"/recipes/?descending=false&sort_by=name">Name' in response.data ) assert ( b'"/recipes/?descending=true&sort_by=date">Created On ↑' in response.data ) # Test sort by date descending response = client.get('/recipes/?descending=true&sort_by=date') assert ( b'"/recipes/?descending=false&sort_by=name">Name' in response.data ) assert ( b'"/recipes/?descending=false&sort_by=date">Created On ↓' in response.data ) # Test sort by volume ascending response = client.get('/recipes/?descending=false&sort_by=volume') assert ( b'"/recipes/?descending=false&sort_by=name">Name' in response.data ) assert ( b'"/recipes/?descending=true&sort_by=volume">Batch Size ↑' in response.data ) # Test sort by volume descending response = client.get('/recipes/?descending=true&sort_by=volume') assert ( b'"/recipes/?descending=false&sort_by=name">Name' in response.data ) assert ( b'"/recipes/?descending=false&sort_by=volume">Batch Size ↓' in response.data ) # Test sort by type ascending response = client.get('/recipes/?descending=false&sort_by=type') assert ( b'"/recipes/?descending=false&sort_by=name">Name' in response.data ) assert ( b'"/recipes/?descending=true&sort_by=type">Type ↑' in response.data ) # Test sort by type descending response = client.get('/recipes/?descending=true&sort_by=type') assert ( b'"/recipes/?descending=false&sort_by=name">Name' in response.data ) assert ( b'"/recipes/?descending=false&sort_by=type">Type ↓' in response.data ) def test_create(client, app, auth): """Test success in creating a recipe document.""" # Test GET without login response = client.get('/recipes/create') assert response.status_code == 302 # Test GET with login auth.login() response = client.get('/recipes/create') assert response.status_code == 200 # Test POST data = { 'efficiency': '65', 'name': 'Test', 'notes': 'Test', 'volume': '5.5', } response = client.post('/recipes/create', data=data) assert response.status_code == 302 with app.app_context(): doc = get_doc('test') assert doc['name'] == 'Test' assert doc['notes'] == 'Test' assert doc['volume'] == '5.5' assert doc['efficiency'] == '65' def test_update(client, app, auth): """Test success in updating a recipe document.""" # Test GET without login response = client.get('/recipes/update/awesome-lager') assert response.status_code == 302 auth.login() # Test GET on a bare minimum recipe response = client.get('/recipes/update/awesome-lager') assert response.status_code == 200 assert b'Awesome Lager' in response.data # Test GET on a more complete recipe response = client.get('/recipes/update/full-recipe') assert response.status_code == 200 test_items = [ b'Awesome Beer', b'2row', b'Dextrose', b'Nugget (US)', b'CTZ (US)', b'Northern California Ale' ] for item in test_items: assert item in response.data # Test GET on a recipe missing most yeast fields response = client.get('/recipes/update/partial-yeast-recipe') assert response.status_code == 200 test_items = [ b'Partial Beer', b'US-05' ] for item in test_items: assert item in response.data # Get a doc, make an update, and test a POST id = 'awesome-lager' with app.app_context(): doc = get_doc(id) # Remove unneeded fields doc.pop('_id') rev = doc.pop('_rev') response = client.post('/recipes/update/awesome-lager', query_string={'rev': rev}, data=doc) assert response.status_code == 302 # Test response without valid/conflicted rev response = client.post('/recipes/update/awesome-lager', query_string={'rev': ''}, data=doc) assert response.status_code == 302 with client.session_transaction() as session: flash_message = dict(session['_flashes']).pop('danger', None) assert 'Update conflict' in flash_message def test_info(client): """Test success in retrieving a recipe document.""" # Validate 404 response = client.get('/recipes/info/thisdoesnotexist') assert response.status_code == 404 # Validate response for existing doc response = client.get('/recipes/info/awesome-lager') assert response.status_code == 200 assert b'Awesome Lager' in response.data def test_info_json(client): """Test success in retrieving a JSON recipe.""" # Validate 404 response = client.get('/recipes/info/thisdoesnotexist/json') assert response.status_code == 404 # Validate response for existing doc response = client.get('/recipes/info/awesome-lager/json') assert response.status_code == 200 assert response.is_json assert response.get_json()['name'] == 'Awesome Lager' def test_yeast_form_doc(app): """Evaluates conditionals in generation of doc from a yeast form.""" yeast = YeastForm() yeast.name.data = 'Test' yeast.low_attenuation.data = Decimal('60') yeast.high_attenuation.data = Decimal('75') assert yeast.doc == { 'name': 'Test', 'low_attenuation': '60', 'high_attenuation': '75' } yeast.type.data = 'Dry' yeast.code.data = 'INIS-001' yeast.lab.data = 'Inland Island' yeast.flocculation.data = 'Low' yeast.min_temperature.data = Decimal('40') yeast.max_temperature.data = Decimal('50') yeast.abv_tolerance.data = Decimal('15') assert yeast.doc == { 'name': 'Test', 'low_attenuation': '60', 'high_attenuation': '75', 'flocculation': 'Low', 'type': 'Dry', 'code': 'INIS-001', 'lab': 'Inland Island', 'min_temperature': '40', 'max_temperature': '50', 'abv_tolerance': '15' } def test_recipe_form_doc(app): """Test if a recipeform can be turned into a document. This test also tests that subforms can be turned into a document. Subforms are not tested individually since they will never be used individually. """ with app.app_context(): recipe = RecipeForm() recipe.name.data = 'Test' recipe.efficiency.data = Decimal('65') recipe.volume.data = Decimal('5.5') recipe.notes.data = 'This is a test' recipe.type.data = 'All-Grain' assert recipe.doc == { 'name': 'Test', 'efficiency': '65', 'type': 'All-Grain', 'volume': '5.5', 'notes': 'This is a test', 'fermentables': [], 'hops': [], '$type': 'recipe', } ferm = FermentableForm() ferm.name.data = 'Test' ferm.type.data = 'Grain' ferm.amount.data = Decimal('5.5') ferm.ppg.data = Decimal('37') ferm.color.data = Decimal('1.8') hop = HopForm() hop.name.data = 'Test' hop.use.data = 'Boil' hop.alpha.data = Decimal('12.5') hop.duration.data = Decimal('60') hop.amount.data = Decimal('0.5') yeast = YeastForm() yeast.name.data = 'Test' yeast.low_attenuation.data = '70' yeast.high_attenuation.data = '75' recipe.fermentables = [ferm] recipe.hops = [hop] recipe.yeast = yeast assert recipe.doc == { 'name': 'Test', 'efficiency': '65', 'type': 'All-Grain', 'volume': '5.5', 'notes': 'This is a test', '$type': 'recipe', 'fermentables': [{ 'name': 'Test', 'type': 'Grain', 'amount': '5.5', 'ppg': '37', 'color': '1.8', }], 'hops': [{ 'name': 'Test', 'use': 'Boil', 'alpha': '12.5', 'duration': '60', 'amount': '0.5' }], 'yeast': { 'name': 'Test', 'low_attenuation': '70', 'high_attenuation': '75' } } def test_recipe_delete(client, auth): """Test success in deleting a document.""" # Try to delete a document without logging in response = client.post('/recipes/delete/awesome-lager') response = client.get('/recipes/info/awesome-lager') assert response.status_code == 200 # Delete document after login auth.login() # Try to delete a document without logging in response = client.post('/recipes/delete/awesome-lager') response = client.get('/recipes/info/awesome-lager') assert response.status_code == 404 def test_recipe_create_json(client, sample_recipes, auth): """Test uploading JSON recipe.""" # Test GET without logging in response = client.get('/recipes/create/json') assert response.status_code == 302 # Test GET after logging in auth.login() response = client.get('/recipes/create/json') assert response.status_code == 200 # Test upload some good data data = { 'upload': (BytesIO(json.dumps(sample_recipes['sweetstout']).encode()), 'sweetstout.json') } response = client.post('/recipes/create/json', buffered=True, content_type='multipart/form-data', data=data) assert response.status_code == 302 assert 'recipes/info/sweet-stout' in response.headers['Location'] # Test upload with some bad data data = {'upload': (BytesIO(b'NOT JSON'), 'file')} response = client.post('/recipes/create/json', buffered=True, content_type='multipart/form-data', data=data) assert response.status_code == 200