def test_get_credentials(self): """ Tests credential property method as well as set_credentials """ with Database(DB) as db: db.set_credentials('a.potts.bot', 'sample_password', 'mellandru') self.assertEqual(('a.potts.bot', 'sample_password', 'mellandru'), db.credentials) db.set_credentials('a', 'b', 'c') self.assertEqual(('a', 'b', 'c'), db.credentials)
def test_get_hits(self) -> None: """ Confirms that the fetch searches method works as expected """ with Database(DB) as db: db.add_search('google.com', 'test_name') self.assertEqual([], db.get_hits('google.com')) db.cursor.execute('UPDATE searches SET hits = ? WHERE url = ?', ('hello,friend', 'google.com')) self.assertEqual(['hello', 'friend'], db.get_hits('google.com'))
def test_get_url_name(self) -> None: """ Tests getting name / URLs from database """ with Database(DB) as db: db.add_search(URL, 'test_name') db.add_search('youtube', 'asdf') db.add_search('4chan', 'cuck') res = db.get_url_name() self.assertEqual([(URL, 'test_name'), ('youtube', 'asdf'), ('4chan', 'cuck')], res)
def test_remove_search(self) -> None: """ Tests removing searches """ with Database(DB) as db: db.add_search('google.com', 'test_name') db.add_search('yahoo.com', 'second_test') db.add_search('yahoo1.com', 'second_test') db.remove_search('google.com') db.cursor.execute('SELECT * from searches') self.assertEqual(2, len(db.cursor.fetchall()))
def test_add_search(self) -> None: """ Tests adding search to database """ with Database(DB) as db: db.add_search('google.com', 'test_name') with self.assertRaises(sqlite3.IntegrityError): db.add_search('google.com', 'test_name') db.cursor.execute('SELECT url, name FROM searches') url, name = db.cursor.fetchone() self.assertEqual('google.com', url) self.assertEqual('test_name', name)
def setUp(self) -> None: with open('tests/cred.txt') as file: sender, pw, recipient = file.read().split('\n') with Database(DB) as db: db.create_database() db.set_credentials(sender, pw, recipient) with Run(DB) as run: for make in MAKES: run.city = 'denver' run.seller_type = 'both' run.vehicle_type = 'motorcycle' run.make_model = f'auto_make_model={make.replace(" ", "+")}' run.do_add_search()
def test_update_time(self) -> None: """ Tests time update incrementation """ with Database(DB) as db: db.add_search(URL, 'test_name') db.cursor.execute('SELECT updated FROM searches WHERE url = ?', (URL, )) time_1 = db.cursor.fetchone()[0] db.update_time(URL) db.cursor.execute('SELECT updated FROM searches WHERE url = ?', (URL, )) time_2 = db.cursor.fetchone()[0] self.assertGreater(time_2, time_1)
def test_update_hits(self) -> None: """ Tests that updating hits works properly """ with Database(DB) as db: db.add_search(URL, 'test_name') db.update_hits(URL, '123', '456', '789') db.cursor.execute('SELECT hits FROM searches WHERE url = ?', (URL, )) self.assertEqual('123,456,789', db.cursor.fetchone()[0]) db.update_hits(URL, '10', '11', '12') db.cursor.execute('SELECT hits FROM searches WHERE url = ?', (URL, )) self.assertEqual('123,456,789,10,11,12', db.cursor.fetchone()[0])
def test_get_urls_time_limited(self) -> None: """ Database.get_urls() is time limited to only return URLs that have been not been updated in the last hour, this is testing that functionality :return: """ with Database(DB) as db: db.add_search(URL, 'test_name') db.add_search('yahoo', 'name2') db.add_search('msn.com', 'name3') urls = db.get_urls() self.assertEqual([URL, 'yahoo', 'msn.com'], urls) db.update_time(URL) urls = db.get_urls() self.assertEqual(['yahoo', 'msn.com'], urls) db.update_time('msn.com') urls = db.get_urls() self.assertEqual(['yahoo'], urls) db.update_time('yahoo') urls = db.get_urls() self.assertEqual([], urls)
def setUp(self) -> None: with Database(DB) as db: db.create_database()
def __init__(self, database: str = Config.database): super(Run, self).__init__() self.seller_abbrev = None self.db_file = database self.database = Database(self.db_file) self.database.create_database()
class Run(CarShell): """ Final child of shell class hierarchy, implements the methods used to build the search URL, as well as CRUD methods for managing searches """ def __init__(self, database: str = Config.database): super(Run, self).__init__() self.seller_abbrev = None self.db_file = database self.database = Database(self.db_file) self.database.create_database() def create_seller_abbrev(self) -> None: """ Checks that self.vehicle_type and self.seller_type have been defined. If they have, sets seller_abbrev to proper value. :return: None """ if self.seller_type and self.vehicle_type: if self.vehicle_type == 'motorcycle': self.seller_abbrev = MOTO_SELLER[self.seller_type] elif self.vehicle_type == 'cars/trucks': self.seller_abbrev = CAR_SELLER[self.seller_type] @property def search_url(self) -> str or None: """ Creates search url :return: search URL string or None """ self.create_seller_abbrev() non_options = 'stdin', 'stdout', 'name', 'mode', 'encoding', 'cmdqueue', \ 'completekey', 'city', 'vehicle_type', 'seller_type', \ 'seller_abbrev', 'database', 'lastcmd', 'completion_matches' options = { key: value for key, value in self.__dict__.items() if key not in non_options and value } sel_options = [] if self.city and self.seller_abbrev and self.make_model: base_url = f'{self.CITY_DICT[self.city]}{self.seller_abbrev}?format=rss&' for key, value in options.items(): if key in BOOL_OPTIONS: # In order for toggle functionality to work, its value has to # remain a boolean up until it's added to the selected options list sel_options.append(BOOL_OPTIONS[key]) else: # All other options are in the completed form sel_options.append(value) return base_url + '&'.join(sel_options) else: msg = 'At a minimum, you must set the city, seller type,' \ ' vehicle type and a make_model.' print(msg) def do_credentials(self, *args) -> None: """ Allows user to store credentials in the database. Uses utilities.credential_verification to verify that supplied credentials actually work. :param args: :return: """ recipient = input('Recipient address: ') sender = input('Sender: ') password = getpass.getpass('Sender\'s password: '******'Verification failure! Incorrect username / password?') @staticmethod def help_credentials() -> None: """ Displays help message for credentials command """ initial_desc = 'Used to set credentials for sending email messages' usage = 'Input the credentials as directed', 'Be sure that they are correct' help_message(initial_desc, usage, long_desc=None) @property def credentials(self) -> bool: """ Determines whether or not credentials have been set. Returns True if they have, False otherwise. """ try: username, password, recipient = self.database.credentials if username and password and recipient: return True except TypeError: return False def do_add_search(self, *args) -> None: """ Adds search URL to database :return: """ url = self.search_url if url: try: name = self.make_model.split('=')[1].replace('+', ' ') print(f'Added {name} search.') self.database.add_search(url, name=name) self.reset_search_options() except sqlite3.IntegrityError: print('Each search must be unique!') def do_run_search(self, *args) -> None: """ Run search and send email notification :param args: :return: None """ try: user, password, recipient = self.database.credentials except TypeError: print('Ensure that credentials have been set successfully first.') return if user and password and recipient: hits = run_search(self.db_file) if hits: print('New hits found!') Message(user, password, recipient, hits).send() else: print('No new search hits.') @staticmethod def help_run_search() -> None: """ Displays help message for run_message """ initial_desc = 'Used to run search for all URLs in database' usage = ' simply run `run_search`', help_message(initial_desc, usage, long_desc=None) @staticmethod def help_add_search() -> None: """ Displays help message for add_search command """ initial_desc = 'Used to add search URL to database' usage = 'type `add_search`', long_desc = 'Remember, each search must be unique!', help_message(initial_desc, usage, long_desc) def do_delete_search(self, *args) -> None: """ Draws deletion menu """ # Creates a dictionary with a number corresponding to each search url_name = { num: item for num, item in enumerate(self.database.get_url_name()) } if url_name: for key, value in url_name.items(): # value[0] is the url, value[1] is the name print(f'{key}: {value[1]}') choice = input('Search to delete: ') try: choice = int(choice) self.database.remove_search(url_name[choice][0]) except ValueError: print('Invalid choice') except KeyError: print('Invalid choice') else: print('No active searches') @staticmethod def help_delete_search() -> None: """ Displays help menu for deletion menu """ initial_desc = 'Used to delete searches from database' usage = 'Usage: type `delete_search`', 'Follow the prompts to remove a ' \ 'particular search' long_desc = 'Type the number listed next to the name to remove a search from ' \ 'the database',\ 'This process cannot be undone, so be careful!' help_message(initial_desc, usage, long_desc) def reset_search_options(self) -> None: """ Sets search values to None :return: None """ non_options = 'stdin', 'stdout', 'name', 'mode', 'encoding', 'cmdqueue', \ 'completekey', 'city', 'vehicle_type', 'seller_type', \ 'seller_abbrev', 'database', 'lastcmd', 'completion_matches', \ 'db_file' for key in self.__dict__: if key not in non_options: self.__dict__[key] = None # `both` is the default option for seller_type self.__dict__['seller_type'] = 'both' def do_print_searches(self, *args) -> None: """ Prints out names of all searches in the database """ searches = self.database.get_url_name() if searches: print('Current searches') print('*' * 40) for item in searches: print(item[1]) print('*' * 40) else: print('No active searches.') @staticmethod def help_print_searches() -> None: """ Prints out help menu for print_searches """ initial = 'Used to print out the current searches' usage = 'Usage: type `print_searches`', help_message(initial, usage, long_desc=None)