def test_card_id_input_arg_must_be_integer(init_db): """ get_card_by_id() takes in a card_id input argument, that must be integer. """ card_obj = knards.Card() card_id = api.create_card(card_obj, init_db) result = api.get_card_by_id('1', init_db) assert result is None
def test_markers_arg_must_be_a_list_of_strings(init_db): """ get_last_card() takes in an optional argument, markers, that must be a list of strings. """ card_obj1 = knards.Card() api.create_card(card_obj1, init_db) result = api.get_last_card(markers=111, db_path=init_db) assert result is None result = api.get_last_card(markers='python', db_path=init_db) assert result is None result = api.get_last_card(markers=['python', 111], db_path=init_db) assert result is None result = api.get_last_card(markers=[], db_path=init_db) assert result.id == 1
def test_markers_arg_must_be_a_list_of_strings(init_db): """ One of the arguments delete_card() takes in is markers, which must be a list of strings. """ card_obj = knards.Card() api.create_card(card_obj, init_db) result = api.delete_card(markers='python', db_path=init_db) assert result is False result = api.delete_card(markers=1, db_path=init_db) assert result is False result = api.delete_card(markers=[1, 2, 3], db_path=init_db) assert result is False result = api.delete_card(markers=['python'], db_path=init_db) assert result is True
def test_returns_object_of_proper_type_and_has_id_set(init_db): """ get_card_by_id() is expected to return an object of type knards.Card with the proper id set (one it was stored with) """ card_obj = knards.Card() card_id = api.create_card(card_obj, init_db) result = api.get_card_by_id(card_id, init_db) assert isinstance(result, knards.Card) assert result.id == card_id
def test_if_both_id_and_markers_are_specified_ignores_markers(mocker): """ If both 'card id' and 'markers' args are specified, 'markers' are ignored. """ card_obj1 = knards.Card(markers='python specific test') card_obj2 = knards.Card(markers='python') card_obj3 = knards.Card(markers='python specific test') runner = CliRunner() with runner.isolated_filesystem(): # create the DB runner.invoke(knards.main, ['bootstrap-db']) db_path = os.getcwd() + '/' + config.DB mocker.patch('knards.config.get_DB_name', return_value=db_path) # create cards api.create_card(card_obj1) api.create_card(card_obj2) api.create_card(card_obj3) # check that cards were successfully stored assert len(api.get_card_set()) == 3 # invoke the subcommand result = runner.invoke(knards.main, ['delete', '--id', 1, '--m', 'python']) assert result.exit_code == 0 assert len(api.get_card_set()) == 2
def test_if_card_has_date_updated_none_its_swapped_with_never_in_output( mocker ): """ If a card in the DB has its .date_updated equal None, it's displayed as "Never" in the subcommand's output. .date_updated equals None by default for every new card. """ card_obj1 = knards.Card() card_obj2 = knards.Card(date_updated='2019-08-30') card_obj3 = knards.Card() runner = CliRunner() with runner.isolated_filesystem(): # create the DB runner.invoke(knards.main, ['bootstrap-db']) # store the card db_path = os.getcwd() + '/' + config.DB api.create_card(card_obj1, db_path) api.create_card(card_obj2, db_path) api.create_card(card_obj3, db_path) mocker.patch('knards.util.open_in_editor') runner.invoke(knards.main, ['list']) assert util.open_in_editor.call_args_list[0][0][0].count('Never') == 2
def test_performance_on_11000_cards(init_db): """ In this test we simulate a creation of 11000 cards (and storing them in the DB) and the following fetching for them in the DB, with respect to different constraints specified. """ for i in range(10000): api.create_card(knards.Card(markers='first second third'), init_db) for i in range(1000): api.create_card(knards.Card(markers='first second fourth'), init_db) set1 = api.get_card_set(include_markers=['second'], db_path=init_db) set2 = api.get_card_set(include_markers=['second', 'third'], db_path=init_db) set3 = api.get_card_set(include_markers=['second'], exclude_markers=['third'], db_path=init_db) assert len(set1) == 11000 assert len(set2) == 10000 assert len(set3) == 1000
def test_buffer_contains_metadata_question_and_answer(mocker): """ The buffer that is opened in the editor contains all card's metadata, the question text, and the answer text. """ card_obj1 = knards.Card() card_obj2 = knards.Card(markers='python specific', series='Some Series', pos_in_series=3, question='Some Text For The Question', answer='A sample answer') runner = CliRunner() with runner.isolated_filesystem(): # create the DB runner.invoke(knards.main, ['bootstrap-db']) # store the card db_path = os.getcwd() + '/' + config.DB mocker.patch('knards.config.get_DB_name', return_value=db_path) api.create_card(card_obj1) api.create_card(card_obj2) mocker.patch('knards.util.open_in_editor', side_effect=Exception) # invoke the subcommand result = runner.invoke(knards.main, ['edit', '--id', 2]) assert result.exit_code == 1 assert card_obj2.markers in util.open_in_editor.call_args_list[0][0][0] assert card_obj2.series in util.open_in_editor.call_args_list[0][0][0] assert str(card_obj2.pos_in_series) in \ util.open_in_editor.call_args_list[0][0][0] assert card_obj2.question in util.open_in_editor.call_args_list[0][0][ 0] assert card_obj2.answer in util.open_in_editor.call_args_list[0][0][0] assert \ util.open_in_editor.call_args_list[0][0][0].count(msg.DIVIDER_LINE) == 2
def test_fail_if_input_arg_is_not_a_proper_card_object(init_db): """ update_card() takes in only one argument and it must be of type knards.Card update_card() always returns a boolean value. """ card_obj = knards.Card() card_id = api.create_card(card_obj, init_db) result = api.update_card(111, init_db) assert result == False result = api.update_card('not a card', init_db) assert result == False result = api.update_card(card_obj, init_db) assert result == True
def test_if_no_args_passed_in_method_returns_the_last_stored_card(init_db): """ If no arguments are passed to the get_last_card() method, the method returns the card with the largest id from the set of cards with the most recent .date_created """ card_obj1 = knards.Card(date_created=(datetime.today() - timedelta(1)).strftime('%Y-%m-%d'), ) card_obj2 = knards.Card() card_obj3 = knards.Card() card_obj4 = knards.Card(date_created=(datetime.today() - timedelta(1)).strftime('%Y-%m-%d'), ) api.create_card(card_obj1, init_db) api.create_card(card_obj2, init_db) api.create_card(card_obj3, init_db) api.create_card(card_obj4, init_db) result = api.get_last_card(db_path=init_db) assert result.id == 3
def test_all_cards_containing_all_markers_are_deleted_if_markers_is_specified( mocker): """ If 'markers' arg is specified, delete ALL the cards that have ALL the markers that are specified. """ card_obj1 = knards.Card(markers='python specific test') card_obj2 = knards.Card(markers='python') card_obj3 = knards.Card(markers='python specific test') card_obj4 = knards.Card(markers='python specifico test') runner = CliRunner() with runner.isolated_filesystem(): # create the DB runner.invoke(knards.main, ['bootstrap-db']) db_path = os.getcwd() + '/' + config.DB mocker.patch('knards.config.get_DB_name', return_value=db_path) # create cards api.create_card(card_obj1) api.create_card(card_obj2) api.create_card(card_obj3) api.create_card(card_obj4) # check that cards were successfully stored assert len(api.get_card_set()) == 4 # invoke the subcommand result = runner.invoke(knards.main, ['delete', '--m', 'python,specific test']) assert result.exit_code == 0 # two cards should be deleted, two that's left have ids 2 and 4 assert len(api.get_card_set()) == 2 assert api.get_card_set()[0].id == 2 assert api.get_card_set()[1].id == 4 # in development we don't allow to delete all cards at once using markers result = runner.invoke(knards.main, ['delete', '--m', 'python']) assert result.exit_code == 1 assert len(api.get_card_set()) == 2
def test_card_id_arg_must_be_a_number(mocker, init_db): """ One of the arguments delete_card() takes in is card_id, which must be a proper integer number. """ card_obj = knards.Card() card_id = api.create_card(card_obj, init_db) assert card_id == 1 mocker.patch( 'knards.api.get_card_by_id', return_value=api.get_card_by_id(card_id, init_db) ) result = api.delete_card(card_id='1', db_path=init_db) assert result is False result = api.delete_card(card_id=1, db_path=init_db) assert result is True
def test_if_card_id_and_markers_are_passed_in_markers_are_ignored( mocker, init_db ): """ If both card_id and markers are passed to the delete_card() method, markers are ignored and only the card with the card_id id is removed from the DB. """ card_obj1 = knards.Card(markers='python specific') card_obj2 = knards.Card(markers='python nonspecific') card_obj3 = knards.Card(markers='javascript specific') api.create_card(card_obj1, init_db) api.create_card(card_obj2, init_db) api.create_card(card_obj3, init_db) assert len(api.get_card_set(db_path=init_db)) == 3 mocker.patch( 'knards.api.get_card_by_id', return_value=api.get_card_by_id(1, db_path=init_db) ) mocker.patch( 'knards.api.get_card_set', return_value=api.get_card_set(db_path=init_db) ) assert api.delete_card( card_id=4, markers=['specific'], db_path=init_db ) is True mocker.stopall() assert len(api.get_card_set(db_path=init_db)) == 3 mocker.patch( 'knards.api.get_card_by_id', return_value=api.get_card_by_id(1, db_path=init_db) ) mocker.patch( 'knards.api.get_card_set', return_value=api.get_card_set(db_path=init_db) ) assert api.delete_card( card_id=1, markers=['specific'], db_path=init_db ) is True mocker.stopall() assert len(api.get_card_set(db_path=init_db)) == 2
def test_card_obj_sent_to_update_card_gets_properly_updated(init_db): """ update_card() should take in an object of type knards.Card and update it in the DB. """ initial_card_obj = knards.Card() initial_card_id = api.create_card(initial_card_obj, init_db) stored_card_obj = api.get_card_by_id(initial_card_id, init_db) stored_card_obj = stored_card_obj._replace(question='new contents', score=12) api.update_card(stored_card_obj, init_db) updated_card_obj = api.get_card_by_id(initial_card_id, init_db) assert updated_card_obj.id == initial_card_id assert updated_card_obj.question == 'new contents' assert updated_card_obj.answer == initial_card_obj.answer assert updated_card_obj.markers == initial_card_obj.markers assert updated_card_obj.date_created == initial_card_obj.date_created assert updated_card_obj.date_updated == datetime.today().strftime( '%Y-%m-%d') assert initial_card_obj.date_updated is None
def test_if_card_id_is_passed_in_method_deletes_the_card_with_that_id( mocker, init_db ): """ If card_id argument is passed to the delete_card() method, the card with that id is removed from the DB. """ card_obj = knards.Card() card_id = api.create_card(card_obj, init_db) result = api.get_card_by_id(card_id, init_db) assert result.id == card_id mocker.patch( 'knards.api.get_card_by_id', return_value=api.get_card_by_id(card_id, init_db) ) api.delete_card(card_id=card_id, db_path=init_db) mocker.stopall() result = api.get_card_by_id(card_id, init_db) assert result is None
def test_if_today_is_true_return_only_cards_that_were_reviewed_today(init_db): """ If today=True is passed to get_card_set(), return cards that have .date_updated equal to today's date. """ card_obj1 = knards.Card() card_obj2 = knards.Card( date_updated=datetime.today().strftime('%Y-%m-%d'), score=1 ) card_obj3 = knards.Card( date_created=(datetime.today() - timedelta(10)).strftime('%Y-%m-%d'), score=2 ) api.create_card(card_obj1, init_db) api.create_card(card_obj2, init_db) api.create_card(card_obj3, init_db) assert len(api.get_card_set(today=True, db_path=init_db)) == 1 assert len(api.get_card_set(today=False, db_path=init_db)) == 3
def test_if_revisable_only_is_true_return_only_cards_ready_for_revision(init_db): """ If revisable_only=True is passed to get_card_set(), return cards that either don't have .date_updated set (None) or have their .score less than or equal to the difference between today and its .date_updated in days. """ card_obj1 = knards.Card() card_obj2 = knards.Card( date_updated=(datetime.today() - timedelta(1)).strftime('%Y-%m-%d'), score=1 ) card_obj3 = knards.Card( date_updated=(datetime.today() - timedelta(1)).strftime('%Y-%m-%d'), score=2 ) api.create_card(card_obj1, init_db) api.create_card(card_obj2, init_db) api.create_card(card_obj3, init_db) assert len(api.get_card_set(revisable_only=False, db_path=init_db)) == 3 assert len(api.get_card_set(revisable_only=True, db_path=init_db)) == 2
def test_if_markers_passed_in_method_returns_true_always(mocker, init_db): """ If no cards contain the specified set of markers, no cards are removed from the DB and True is returned. """ card_obj1 = knards.Card(markers='python specific') card_obj2 = knards.Card(markers='python nonspecific') card_obj3 = knards.Card(markers='javascript specific') api.create_card(card_obj1, init_db) api.create_card(card_obj2, init_db) api.create_card(card_obj3, init_db) assert len(api.get_card_set(db_path=init_db)) == 3 mocker.patch( 'knards.api.get_card_set', return_value=api.get_card_set(db_path=init_db) ) assert api.delete_card(markers=['specific', 'test'], db_path=init_db) is True mocker.stopall() assert len(api.get_card_set(db_path=init_db)) == 3
def test_method_returns_a_proper_card_obj_upon_success(mocker, init_db): """ get_last_card() returns an object of type knards.Card that is a copy of the last stored card object. If markers argument is passed to the method, it returns an object of type knards.Card that is a copy of the last stored card that has ALL of the specified markers. """ card_obj1 = knards.Card(markers='python specific test') card_obj2 = knards.Card(markers='javascript specific') card_obj3 = knards.Card(markers='javascript specific test', date_created=(datetime.today() - timedelta(1)).strftime('%Y-%m-%d')) api.create_card(card_obj1, init_db) api.create_card(card_obj2, init_db) api.create_card(card_obj3, init_db) mocker.patch('knards.api.get_card_set', return_value=api.get_card_set(db_path=init_db)) result = api.get_last_card(db_path=init_db) assert result.id == 2 result = api.get_last_card(markers=['specific', 'test'], db_path=init_db) assert result.id == 1
def test_if_markers_passed_return_cards_with_respect_to_constraints(init_db): """ If get_card_set() is passed include_markers=[...], return only cards that have ALL of the specified markers. If get_card_set() is passed exclude_markers=[...], return only cards that have NONE of the specified markers. """ card_obj1 = knards.Card(markers='python specific') card_obj2 = knards.Card(markers='javascript specific') card_obj3 = knards.Card(markers='python nonspecific') card_obj4 = knards.Card(markers='javascript test') card_obj5 = knards.Card(markers='javascript test special') card_obj6 = knards.Card(markers='javascript python') api.create_card(card_obj1, init_db) api.create_card(card_obj2, init_db) api.create_card(card_obj3, init_db) api.create_card(card_obj4, init_db) api.create_card(card_obj5, init_db) api.create_card(card_obj6, init_db) assert len(api.get_card_set( include_markers=['test'], db_path=init_db )) == 2 assert len(api.get_card_set( include_markers=['specific'], db_path=init_db )) == 2 assert len(api.get_card_set( include_markers=['spec'], db_path=init_db )) == 0 assert len(api.get_card_set( include_markers=['javascript'], db_path=init_db )) == 4 assert len(api.get_card_set( include_markers=['special', 'javascript'], db_path=init_db )) == 1 assert len(api.get_card_set( exclude_markers=['javascript'], db_path=init_db )) == 2 assert len(api.get_card_set( exclude_markers=['python', 'test'], db_path=init_db )) == 1 assert len(api.get_card_set( exclude_markers=['specific'], db_path=init_db )) == 4 assert len(api.get_card_set( exclude_markers=['specific', 'test'], db_path=init_db )) == 2
def merge(db_file): """ TODO """ # check if merge file exists and is a proper DB file try: with util.db_connect(db_file) as connection: cursor = connection.cursor() cursor.execute(""" SELECT * FROM cards """) card_set = cursor.fetchall() card_set_as_objects = [] for card in card_set: card_set_as_objects.append(Card(*card)) full_card_set = api.get_card_set() dates = [] for card in full_card_set: dates.append(card.date_created) except exceptions.DBFileNotFound as e: print(e.args[0]) sys.exit(1) except sqlite3.DatabaseError: print('{} is not a proper DB file to merge.'.format(db_file)) sys.exit(1) # backup both DB files try: copyfile( config.get_DB_name(), config.get_backup_path() + 'main_{}'.format(datetime.now().strftime('%Y_%m_%d_%H_%M_%S_%f'))) except IOError as e: print("Unable to copy file. %s" % e) sys.exit(1) except: print("Unexpected error:", sys.exc_info()) sys.exit(1) try: copyfile( db_file, config.get_backup_path() + 'merge_{}'.format(datetime.now().strftime('%Y_%m_%d_%H_%M_%S_%f'))) except IOError as e: print("Unable to copy file. %s" % e) sys.exit(1) except: print("Unexpected error:", sys.exc_info()) sys.exit(1) # merge merged = 0 skipped = 0 for card in card_set_as_objects: if card.date_created in dates: skipped += 1 continue merged += 1 api.create_card(card) if skipped == 0: click.secho( 'DB file ({}) was successfully merged into the default DB file.'. format(db_file), fg='green', bold=True) os.remove(db_file) else: click.secho( '{} cards merged; {} cards skipped; You might be trying to merge a DB \ file that was already merged.\nMerged DB file wasn\'t removed in case \ something went wrong, please check manually and then remove the file.'. format(merged, skipped), fg='red', bold=True)
def new(qf, copy_last, copy_from_id, markers): """ Prompt to create a new card. $ kn new Opens a "question-first" buffer in editor, processes what's in the buffer after "save & exit", sends into the second buffer (for answer), processes what's returned by that, checks if the format is OK, splits up metadata and card's text, generates an object of type knards.Card and feeds it to the create_card() $ kn new --af Opens a "answer-first" buffer in editor, processes what's in the buffer after "save & exit", sends into the second buffer (for question), processes what's returned by that, checks if the format is OK, splits up metadata and card's text, generates an object of type knards.Card and feeds it to the create_card() """ if markers is not None: markers = markers.split(',') else: markers = [] if copy_last: card_obj = api.get_last_card(markers=markers) prompt = 'Markers: [{}]\n'.format(card_obj.markers) prompt += 'Series: [{}]\n'.format(card_obj.series) elif copy_from_id: card_obj = api.get_card_by_id(copy_from_id) prompt = 'Markers: [{}]\n'.format(card_obj.markers) prompt += 'Series: [{}]\n'.format(card_obj.series) else: card_obj = Card() prompt = 'Markers: []\n' prompt += 'Series: []\n' card_obj = card_obj._replace(date_created=datetime.now()) card_obj = card_obj._replace(date_updated=None) if card_obj.series: card_obj = card_obj._replace(pos_in_series=card_obj.pos_in_series + 1) else: card_obj = card_obj._replace(pos_in_series=card_obj.pos_in_series) card_obj = card_obj._replace(score=0) prompt += 'No. in series: {}\n'.format(card_obj.pos_in_series) prompt += msg.DIVIDER_LINE + '\n' if qf: prompt += card_obj.question + '\n' valid = False retry_count = 1 submit_question = prompt while not valid: submit_question = util.open_in_editor(submit_question) try: submit_question, valid = util.check_buffer( 'new', submit_question) except exceptions.BadBufferFormat as e: print(e.args[0]) if not valid: if not util.retry_buffer(retry_count): sys.exit(7) valid = False retry_count = 1 submit_answer = submit_question while not valid: submit_answer = util.open_in_editor(submit_answer) try: submit_answer, valid = util.check_buffer('new', submit_answer) except exceptions.BadBufferFormat as e: print(e.args[0]) if not valid: if not util.retry_buffer(retry_count): sys.exit(7) question_text = '' for index, line in enumerate(submit_question.split('\n')): if index > 3: question_text += line + '\n' else: card_obj = card_obj._replace(question=question_text) answer_text = '' for index, line in enumerate(submit_answer.split('\n')): if index == 0: card_obj = card_obj._replace( markers=line.split('[')[1].split(']')[0]) if index == 1: card_obj = card_obj._replace( series=line.split('[')[1].split(']')[0]) if index == 2: card_obj = card_obj._replace( pos_in_series=int(line.split(':')[1][1:])) if index > 3: answer_text += line + '\n' else: card_obj = card_obj._replace(answer=answer_text) card_id = api.create_card(card_obj) if card_id: click.secho(msg.NEW_CARD_SUCCESS.format(card_id), fg='green', bold=True) else: click.secho(msg.NEW_CARD_FAILURE, fg='red', bold=True) else: prompt += card_obj.answer + '\n' valid = False retry_count = 1 submit_answer = prompt while not valid: submit_answer = util.open_in_editor(submit_answer) try: submit_answer, valid = util.check_buffer('new', submit_answer) except exceptions.BadBufferFormat as e: print(e.args[0]) if not valid: if not util.retry_buffer(retry_count): sys.exit(7) valid = False retry_count = 1 submit_question = submit_answer while not valid: submit_question = util.open_in_editor(submit_question) try: submit_question, valid = util.check_buffer( 'new', submit_question) except exceptions.BadBufferFormat as e: print(e.args[0]) if not valid: if not util.retry_buffer(retry_count): sys.exit(7) question_text = '' for index, line in enumerate(submit_question.split('\n')): if index == 0: card_obj = card_obj._replace( markers=line.split('[')[1].split(']')[0]) if index == 1: card_obj = card_obj._replace( series=line.split('[')[1].split(']')[0]) if index == 2: card_obj = card_obj._replace( pos_in_series=int(line.split(':')[1][1:])) if index > 3: question_text += line + '\n' else: card_obj = card_obj._replace(question=question_text) answer_text = '' for index, line in enumerate(submit_answer.split('\n')): if index > 3: answer_text += line + '\n' else: card_obj = card_obj._replace(answer=answer_text) card_id = api.create_card(card_obj) if card_id: click.secho(msg.NEW_CARD_SUCCESS.format(card_id), fg='green', bold=True) else: click.secho(msg.NEW_CARD_FAILURE, fg='red', bold=True)
def test_if_markers_passed_in_all_cards_with_those_markers_are_removed( mocker, init_db ): """ If markers list is passed to the delete_card() method, all cards that contain ALL of the specified markers are being removed from the DB. """ card_obj1 = knards.Card(markers='python specific') card_obj2 = knards.Card(markers='python nonspecific') card_obj3 = knards.Card(markers='javascript specific test') card_obj4 = knards.Card(markers='python special') card_obj5 = knards.Card(markers='python specific test') card_obj6 = knards.Card(markers='specifically test') api.create_card(card_obj1, init_db) api.create_card(card_obj2, init_db) api.create_card(card_obj3, init_db) api.create_card(card_obj4, init_db) api.create_card(card_obj5, init_db) api.create_card(card_obj6, init_db) assert len(api.get_card_set(db_path=init_db)) == 6 mocker.patch( 'knards.api.get_card_set', return_value=api.get_card_set(db_path=init_db) ) assert api.delete_card(markers=['specific', 'test'], db_path=init_db) is True mocker.stopall() assert len(api.get_card_set(db_path=init_db)) == 4