def setup_method(self): """ Setup method : - Sets database to test mode and populates it - Launches Flask in test mode """ # Database Database.TEST_MODE = True Database.on() # Create test database models.Params(autoload=False).create_table() models.Products().create_table() models.Groceries().create_table() # Params : user config self.password_raw = 'abcdefg' self.password_sha1 = bytearray(self.password_raw, encoding='utf-8') self.password_sha1 = hashlib.sha1(self.password_sha1).hexdigest() params = models.Params() params.add_item('user_password', self.password_sha1) params.add_item('lang', 'en') # Products : 1 sample item self.default_barcode = '1234567890123' self.default_name = 'Lorem Ipsum' models.Products().add_item(self.default_barcode, self.default_name, True) # Groceries : 1 sample item models.Groceries().add_item(self.default_barcode, 1)
def test_run_valid(self): """ Tests FindProduct process with both valid and invalid inputs Success conditions : - Valid input : an item has been added to the Products and Groceries databases - Invalid input : an "unknown" item has been added to the Products and Groceries databases """ # Launch threads thread_valid = utils.FindProduct(self.valid_barcode) thread_valid.start() thread_invalid = utils.FindProduct(self.invalid_barcode) thread_invalid.start() thread_valid.join() thread_invalid.join() # Tests groceries = models.Groceries( ) # Re-opens the DB connexion too, closed by FindProduct valid = groceries.get_item(self.valid_barcode) invalid = groceries.get_item(self.invalid_barcode) assert valid assert valid['name'] != '???' assert invalid assert invalid['name'] == '???'
def test_api_groceries_list(self): """ Tests the "api_groceries_list" API method. Success conditions : - The API can't be accessed without being logged : HTTP 401 - Returns HTTP 200 when logged with the expected content as JSON """ with webapp.test_client() as app: # Tests access without being authenticated response = app.get('/api/groceries_list') assert response.status_code == 401 # Authenticate data = {'password': self.password_raw} response = app.post('/', data=data, follow_redirects=True) # Does the API returns the expected data ? # JSON "item" entry must contain the same thing as Groceries > get_list response = app.get('/api/groceries_list') expected_data = models.Groceries().get_list() given_data = str(response.data, encoding='utf-8') given_data = json.loads(given_data) given_data = given_data['items'].keys() assert response.status_code == 200 assert set(expected_data) == set(given_data)
def setup_method(self): """ Creates a dummy database for tests """ # Creates database Database.TEST_MODE = True Database.on() self.cursor = Database.CURSOR # Create tables self.params = models.Params(autoload=False) self.products = models.Products() self.groceries = models.Groceries() self.params.create_table() self.products.create_table() self.groceries.create_table() # Insert dummy data self.default_barcode = '123456789ABCD' self.default_name = 'Lorem Ipsum' self.default_pic = 'PIC' self.params.add_item('foo', 'bar') self.products.add_item(self.default_barcode, self.default_name, self.default_pic) self.groceries.add_item(self.default_barcode, 1)
def test_api_groceries_edit(self): """ Tests the "api_groceries_edit" API method. Success conditions : - The API can't be accessed without being logged : HTTP 401 - Returns HTTP 200 when logged with the expected content as JSON - Perfoms correctly ADD / EDIT / DELETE operations """ with webapp.test_client() as app: # Tests access without being authenticated response = app.get('/api/groceries_edit/' + self.default_barcode + '/1') assert response.status_code == 401 # Authenticate data = {'password': self.password_raw} response = app.post('/', data=data, follow_redirects=True) # Test : delete, valid input response = app.get('/api/groceries_edit/' + self.default_barcode + '/0') assert response.status_code == 200 assert not models.Groceries().get_item(self.default_barcode) # Test : add, valid input response = app.get('/api/groceries_edit/' + self.default_barcode + '/2') entry = models.Groceries().get_item(self.default_barcode) assert response.status_code == 200 assert entry assert entry['barcode'] == self.default_barcode assert entry['quantity'] == 2 # Test : edit, valid input response = app.get('/api/groceries_edit/' + self.default_barcode + '/4') entry = models.Groceries().get_item(self.default_barcode) assert response.status_code == 200 assert entry assert entry['barcode'] == self.default_barcode assert entry['quantity'] == 4
def delete_item(self, barcode): """ Deletes a product from the database :param barcode: barcode :type barcode: str :rtype: bool """ # Deletion query = """ DELETE FROM Products WHERE `barcode` = ?; """ params = (barcode, ) Database.LINK.execute(query, params) Database.LINK.commit() # Side effect : delete it from the grocery list models.Groceries().delete_item(barcode) return True
def setup_method(self): """ Setup method, creates a dummy database """ # Creates test database Database.TEST_MODE = True Database.on() self.cursor = Database.CURSOR # Create tables models.Params(autoload=False).create_table() models.Products().create_table() models.Groceries().create_table() # Defaults barcodes self.valid_barcode = '3017620424403' self.invalid_barcode = '123456789ABCD' # Deconnects database to let threads use them Database.off()
def api_groceries_list(): """ API method : Gets all items from the grocery list. Outputs JSON. Returns the latest version of the grocery list. JSON format : - {"status": ..., "items" ...} Possible return status : - OK """ # AJAX Auth check if not ('is_logged' in session and session['is_logged']): return render_template('json.html', json="{}"), 401 # Output data = {"status": "OK", "items": []} # Get info Database.on() data['items'] = models.Groceries().get_list() Database.off() # Render return render_template('json.html', json=json.dumps(data))
def execute(cls, language='en'): """ Creates database, tables and ask for user parameters. :param language: Set language for this menu :type language: str Defined parameters : - buzzer_on - buzzer_port - camera_res_x - camera_res_y - user_password - lang """ # Language file lang = Lang(language) # Intro print("-" * 80) print(lang.config_intro) print("-" * 80) # Create / connects to the database Database.on() print(lang.config_database_created) # Create table : params params = models.Params(autoload=False) params.create_table() print(lang.config_table_params_set) # Create table : groceries groceries = models.Groceries() groceries.create_table() print(lang.config_table_groceries_set) # Create table : products products = models.Products() products.create_table() print(lang.config_table_products_set) # Set language params.delete_item('lang') language = input(lang.config_language_set) if language not in Lang.available(): language = 'en' params.add_item('lang', language) # Ask for : use buzzer / on which port ? params.delete_item('buzzer_on') params.delete_item('buzzer_port') buzzer_on = input(lang.config_buzzer_on) # Yes if buzzer_on.upper() == "Y": buzzer_port = input(lang.config_buzzer_port) if re.findall('([0-9]+)', buzzer_port): params.add_item('buzzer_on', '1') params.add_item('buzzer_port', buzzer_port) else: print(lang.config_buzzer_invalid_port) params.add_item('buzzer_on', '0') params.add_item('buzzer_port', '0') # No else: params.add_item('buzzer_on', '0') params.add_item('buzzer_port', '0') # Ask for : camera resolution params.delete_item('camera_res_x') params.delete_item('camera_res_y') for axis in ['x', 'y']: if axis == 'x': question = lang.config_camera_res_x else: question = lang.config_camera_res_y resolution = input(question) if not re.findall('([0-9]+)', resolution): resolution = 500 params.add_item('camera_res_{}'.format(axis), resolution) # Ask for : user password params.delete_item('user_password') user_password = getpass.getpass(lang.config_password) if not user_password: user_password = '******' user_password = bytearray(user_password, encoding='utf-8') user_password = hashlib.sha1(user_password).hexdigest() params.add_item('user_password', user_password) # Close connection to the database Database.off() # Bye ! print(lang.config_done)
def api_groceries_edit(barcode, quantity): """ API method : Add/Edit/Delete items from the grocery list. If the item doesn't exist, it will be created. Outputs JSON. :param barcode: A known product barcode :param quantity: The quantity defines the operation (quantity 0 = delete) JSON format : - {"status": ..., "barcode" ..., "quantity": ...} Possible return status : - PRODUCT NOT FOUND (404) - ADDED (200) / ADD ERROR (400) - EDITED (200) / EDIT ERROR (400) - DELETED (200) / DELETE ERROR (400) """ # AJAX Auth check if not ('is_logged' in session and session['is_logged']): return render_template('json.html', json="{}"), 401 # Remove unwanted chars to_escape = ['"', "<", ">", "&", "'"] for char in to_escape: barcode = barcode.replace(char, '') quantity = int(quantity) # Output data = {"status": "", "barcode": barcode, "quantity": quantity} # Database access Database.on() products_db = models.Products() groceries_db = models.Groceries() # Try to get the product associated with the barcode product = products_db.get_item(barcode) if not product: data['status'] = 'PRODUCT NOT FOUND' return render_template('json.html', json=json.dumps(data)), 404 # Try to get the entry in the grocery list exists = groceries_db.get_item(barcode) status_code = 200 # If it doesn't exist : add it if not exists: try: if not quantity: quantity = 1 groceries_db.add_item(barcode, quantity) data['status'] = 'ADDED' except Exception as trace: data['status'] = 'ADD ERROR' status_code = 400 # If it exists : else: # If quantity = 0 : Delete if quantity <= 0: try: groceries_db.delete_item(barcode) data['status'] = 'DELETED' except Exception as trace: data['status'] = 'DELETE ERROR' status_code = 400 # If quantity > 0 : Edit quantity else: try: groceries_db.edit_item(barcode, abs(quantity)) data['status'] = 'EDITED' except Exception as trace: data['status'] = 'EDIT ERROR' status_code = 400 # Database : off and outputs data Database.off() return render_template('json.html', json=json.dumps(data)), status_code
def run(self): """ Fetch, gathers, insert product infos and adds it to the grocery list. Threaded :rtype: bool """ with FindProduct.LOCK: # Database connection Database.on() # Marks found = False cache = False products = models.Products() groceries = models.Groceries() message = "" # Try to find the product in the local products database cache = products.get_item(self.barcode) if cache: found = True self.name = cache['name'] self.barcode = cache['barcode'] message += "{barcode} : Found {name} from local products database.\n" # If not found locally : Try to find the product in the OpenFoodFacts API if not found: # If found on OpenFoodFacts : add it to the local database if self.__fetch_openfoodfacts(): found = True products.add_item(self.barcode, self.name, self.pic) message += "{barcode} : Found {name} from OpenFoodFacts.\n" message += "{barcode} : {name} added to cache.\n" else: message += "{barcode} : Not found locally nor on OpenFoodFacts.\n" # If not found : add as unknown item (name = ???) if not found: self.name = '???' self.pic = False products.add_item(self.barcode, self.name, self.pic) message += "{barcode} : Unknown product added to the database.\n" # If the product's already present in the grocery list: increase its quantity by 1 previous = groceries.get_item(self.barcode) if previous: self.quantity = previous['quantity'] + 1 groceries.edit_item(self.barcode, self.quantity) message += "{barcode} : {quantity} {name} now in grocery list.\n" # Otherwise : add it else: groceries.add_item(self.barcode, 1) message += "{barcode} : 1 {name} added to the grocery list.\n" # Disconnect the database, allowing it to be used by another thread Database.off() # Print message message = message.format(barcode=self.barcode, name=self.name, quantity=self.quantity) print(message)
def execute(cls, language='en'): """ Creates database, tables and ask for user parameters. :param language: Set language for this menu :type language: str Defined parameters : - buzzer_on - buzzer_port - camera_res_x - camera_res_y - user_password - lang - api_key """ # Language file lang = Lang(language) # Intro print("-" * 80) print(lang.config_intro) print("-" * 80) # Create / connects to the database Database.on() print(lang.config_database_created) # Create table : params params = models.Params(autoload=False) params.create_table() print(lang.config_table_params_set) # Create table : groceries groceries = models.Groceries() groceries.create_table() print(lang.config_table_groceries_set) # Create table : products products = models.Products() products.create_table() print(lang.config_table_products_set) # Set language params.delete_item('lang') language = input(lang.config_language_set) if language not in Lang.available(): language = 'en' params.add_item('lang', language) # Ask for : use buzzer / on which port ? params.delete_item('buzzer_on') params.delete_item('buzzer_port') buzzer_on = input(lang.config_buzzer_on) # Yes if buzzer_on.upper() == "Y": buzzer_port = input(lang.config_buzzer_port) if re.findall('([0-9]+)', buzzer_port): params.add_item('buzzer_on', '1') params.add_item('buzzer_port', buzzer_port) else: print(lang.config_buzzer_invalid_port) params.add_item('buzzer_on', '0') params.add_item('buzzer_port', '0') # No else: params.add_item('buzzer_on', '0') params.add_item('buzzer_port', '0') # Ask for : camera resolution params.delete_item('camera_res_x') params.delete_item('camera_res_y') for axis in ['x', 'y']: if axis == 'x': question = lang.config_camera_res_x else: question = lang.config_camera_res_y resolution = input(question) if not re.findall('([0-9]+)', resolution): resolution = 500 params.add_item('camera_res_{}'.format(axis), resolution) # Ask for : user password params.delete_item('user_password') user_password = getpass.getpass(lang.config_password) if not user_password: user_password = '******' user_password = bytearray(user_password, encoding='utf-8') user_password = hashlib.sha1(user_password).hexdigest() params.add_item('user_password', user_password) import random random = random.SystemRandom() def get_random_string(length=12, allowed_chars='abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'): return ''.join(random.choice(allowed_chars) for i in range(length)) def get_secret_key(): """ Create a random secret key. Taken from the Django project. """ chars = 'abcdefghijklmnopqrstuvwxyz0123456789!@#$^*(-_=+)' return get_random_string(24, chars) # Ask for : api key params.delete_item('api_key') api_key = getpass.getpass(lang.config_api) if not api_key: api_key = (get_secret_key()) params.add_item('api_key', api_key) print('Api-Key: ' + api_key) # Close connection to the database Database.off() # Bye ! print(lang.config_done)