def get_series_set(series_name, db_path=config.get_DB_name()): """Returns all cards that belong to the specified series. Args: series_name (str): The series name db_path (str): The path to the DB (optional, defaults to what's defined in config module) Raises: TODO Returns: A set of cards all of which belong to the series """ if not isinstance(series_name, str): raise TypeError('\'series_name\' argument must be a list.') with util.db_connect(db_path) as connection: cursor = connection.cursor() cursor.execute(""" SELECT * FROM cards WHERE series = '{}' """.format(series_name)) card_set = cursor.fetchall() if not card_set: raise exceptions.EmptyDB( 'No cards adhere to the specified constraints.') sorted_card_set = {} for card in card_set: sorted_card_set[knards.Card(*card).pos_in_series] = knards.Card(*card) return sorted_card_set
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_a_single_card_is_properly_deleted_if_card_id_is_specified(mocker): """ If 'card id' arg is specified, delete the card that has that id from the DB. """ card_obj1 = knards.Card() card_obj2 = knards.Card() 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) # check that cards were successfully stored assert len(api.get_card_set()) == 2 # invoke the subcommand result = runner.invoke(knards.main, ['delete', '--id', 1]) assert result.exit_code == 0 # one card should be deleted, the one that's left must have an id = 2 assert len(api.get_card_set()) == 1 assert api.get_card_set()[0].id == 2
def test_create_card_index_generation(mocker, init_db): """ Create multiple cards to test how new id are generated by the create_card() Also, it doesn't matter what ids do we explicitly assign to cards being passed to the method, those are being overwritten inside the method and this is expected behavior. """ card_obj1 = knards.Card(id=1) card_obj2 = knards.Card(id=1) card_obj3 = knards.Card(id=1) card_id = api.create_card(card_obj1, init_db) assert card_id == 1 card_id = api.create_card(card_obj2, init_db) assert card_id == 2 card_id = api.create_card(card_obj3, init_db) assert card_id == 3 # the removal of a card from the DB must free up its id and it should be # immediately available for new cards to take mocker.patch('knards.api.get_card_by_id', return_value=api.get_card_by_id(card_id=2, db_path=init_db)) assert api.delete_card(card_id=2, db_path=init_db) card_id = api.create_card(card_obj1, init_db) assert card_id == 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_card_object_defaults(): """ Using no parameters should invoke defaults. """ card_1 = knards.Card() card_2 = knards.Card(None, 0, 'Here, type in the question text for the new card.', 'Here, type in the answer text for the new card.', '', None, datetime.today().strftime('%Y-%m-%d'), None, 0) assert card_1 == card_2
def test_copy_last_argument_invokes_first_prompt_with_copy_of_last_cards_text( mocker, init_db): """ $ kn new --qf --copy-last must prompt for a new card, "question-first" mode; the prompt must be prepopulated with data copied from the last stored card. """ runner = CliRunner() with runner.isolated_filesystem(): # create the DB runner.invoke(knards.main, ['bootstrap-db']) # create some card objs card_obj_first = knards.Card(markers='pytest', series='', pos_in_series=1, question='some_text', answer='some_text_2') card_obj_last = knards.Card(markers='python test', series='test_series', pos_in_series=3, question='test_quest', answer='test_answer') api.create_card(card_obj_first, init_db) api.create_card(card_obj_last, init_db) # trigger method fail upon first invocation of util.open_in_editor() mocker.patch('knards.util.open_in_editor', side_effect=ValueError) mocker.patch('knards.api.get_last_card', return_value=api.get_last_card(db_path=init_db)) # invoke the subcommand with respective options, "question-first" mode runner.invoke(knards.main, ['new', '--qf', '--copy-last']) assert api.get_last_card.call_count == 1 assert util.open_in_editor.call_count == 1 assert 'Markers: [{}]'.format(card_obj_last.markers) in \ util.open_in_editor.call_args_list[0][0][0] assert 'Series: [{}]'.format(card_obj_last.series) in \ util.open_in_editor.call_args_list[0][0][0] assert 'No. in series: {}'.format(card_obj_last.pos_in_series) in \ util.open_in_editor.call_args_list[0][0][0] assert 'test_quest' in util.open_in_editor.call_args_list[0][0][0] assert 'test_answer' not in util.open_in_editor.call_args_list[0][0][0] # invoke the subcommand with respective options, "answer-first" mode runner.invoke(knards.main, ['new', '--af', '--copy-last']) assert api.get_last_card.call_count == 2 assert util.open_in_editor.call_count == 2 assert 'test_answer' in util.open_in_editor.call_args_list[1][0][0] assert 'test_quest' not in util.open_in_editor.call_args_list[1][0][0]
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_if_q_and_a_are_false_question_and_answer_texts_are_not_in_output( mocker ): """ If specified explicitly to omit the question/answer text, like so: $ kn list --no-q --no-a the question and/or answer texts must not be present in the output. """ card_obj = 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_obj, db_path) mocker.patch('knards.util.open_in_editor') runner.invoke(knards.main, ['list', '--no-q']) assert 'question' not in util.open_in_editor.call_args_list[0][0][0].lower() assert 'answer' in util.open_in_editor.call_args_list[0][0][0].lower() runner.invoke(knards.main, ['list', '--no-q', '--no-a']) assert 'question' not in util.open_in_editor.call_args_list[1][0][0].lower() assert 'answer' not in util.open_in_editor.call_args_list[1][0][0].lower()
def test_revisable_only_today_show_question_and_show_answer_must_be_boolean( init_db ): """ We don't expect revisable_only/today/show question/show answer to be anything other than a boolean. """ card_obj = knards.Card() api.create_card(card_obj, init_db) # a bunch of different combinations of input args (at least one of them is # not of an expected type) assert api.get_card_set(revisable_only=1, db_path=init_db) == [] assert api.get_card_set(revisable_only=True, today=1, db_path=init_db) == [] assert api.get_card_set(show_question=1, db_path=init_db) == [] assert api.get_card_set(show_question=1, show_answer=1, db_path=init_db) == [] assert api.get_card_set( revisable_only='not boolean', show_question=True, today=True ) == [] assert api.get_card_set( revisable_only='not boolean', show_question=True, today=card_obj ) == []
def test_date_updated_is_updated_to_todays(mocker, edit_prompt_proper_fill): """ After successful edit, the .date_updated of the card is updated to be equal to the today's date. """ card_obj = knards.Card(date_created=datetime.now() - timedelta(days=1), date_updated=datetime.now() - timedelta(days=1)) 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_obj) mocker.patch('knards.util.open_in_editor', return_value=edit_prompt_proper_fill) # invoke the subcommand result = runner.invoke(knards.main, ['edit', '--id', 1]) assert result.exit_code == 0 card_obj = api.get_card_by_id(1) assert card_obj.date_created.strftime('%Y-%m-%d %H:%M:%S') == \ (datetime.now() - timedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S') assert card_obj.date_updated.strftime('%Y-%m-%d %H:%M:%S') == \ datetime.now().strftime('%Y-%m-%d %H:%M:%S')
def get_card_by_id(card_id, db_path=config.get_DB_name()): """ Takes in: 1. An integer that represents target card's id. 2. A path to the DB file (optional, defaults to config.DB) Returns an object of type knards.Card representing the card data stored in the DB. """ try: int(card_id) except ValueError: raise ValueError('id must be a proper number.') with util.db_connect(db_path) as connection: cursor = connection.cursor() cursor.execute(""" SELECT * FROM cards WHERE id = {} """.format(card_id)) card = cursor.fetchone() if not card: raise exceptions.CardNotFound( 'Card #{} was not found in the DB.'.format(card_id)) card_obj = knards.Card(*card) return card_obj
def test_card_is_successfully_saved_into_db(mocker, edit_prompt_proper_fill): """ After the opened buffer edited and saved, the card is successfully save into the DB. """ card_obj = 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 mocker.patch('knards.config.get_DB_name', return_value=db_path) api.create_card(card_obj) mocker.patch('knards.util.open_in_editor', return_value=edit_prompt_proper_fill) # invoke the subcommand result = runner.invoke(knards.main, ['edit', '--id', 1]) assert result.exit_code == 0 card_obj = api.get_card_by_id(1) assert card_obj.markers == 'edited marker' assert card_obj.series == 'edited series' assert card_obj.pos_in_series == 2 assert 'Edited question text.' in card_obj.question assert 'Edited answer text.' in card_obj.answer
def test_method_returns_none_if_the_DB_is_empty_or_no_cards_found(init_db): """ get_last_card() returns none if the DB is empty or, in case markers were passed to the method, no cards were found that contain all the specified markers. """ result = api.get_last_card(db_path=init_db) assert result is None card_obj1 = knards.Card(markers='python specific') card_obj2 = knards.Card(markers='javascript specific') api.create_card(card_obj1, init_db) api.create_card(card_obj2, init_db) result = api.get_last_card(markers=['python', 'javascript'], db_path=init_db) assert result is None
def test_create_card_return_valid_id(init_db): """ api.create_card() takes in a knards.Card object, stores it in the DB and returns the id it was created with. """ new_card = knards.Card() new_card_id = api.create_card(new_card, init_db) assert isinstance(new_card_id, int)
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_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_dates_are_cast_to_convenient_format(init_db): """ sqlite3 keeps dates in UNIX timestamp format, upon each get_card_set() we, among other things, cast all dates to 'YYYY-mm-dd' format. """ card_obj = knards.Card(date_created='2019-08-31', date_updated='2019-08-31') api.create_card(card_obj, init_db) return_value = api.get_card_set(db_path=init_db) assert return_value[0].date_created == card_obj.date_created assert return_value[0].date_updated == card_obj.date_updated
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_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_returns_list_of_proper_type_objects(init_db): """ get_card_set() must always return either a list of objects of type knards.Card or [] """ assert api.get_card_set(db_path=init_db) == [] card_obj = knards.Card() api.create_card(card_obj, init_db) assert isinstance(api.get_card_set(db_path=init_db), list) assert isinstance(api.get_card_set(db_path=init_db)[0], knards.Card)
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
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_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_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_card_object_member_access(): """ Check .field functionality of namedtuple. """ card = knards.Card(question='test', score=1) assert card.id is None assert card.pos_in_series == 0 assert card.question == 'test' assert card.answer == 'Here, type in the answer text for the new card.' assert card.markers == '' assert card.series is None assert card.date_created == datetime.today().strftime('%Y-%m-%d') assert card.date_updated is None assert card.score == 1
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_input_arg_must_be_a_card_obj(init_db): """ create_card() takes in only one argument and it must be of type knards.Card create_card() returns False upon failure. create_card() returns created card's id upon success. """ result = api.create_card(111, init_db) assert result is False result = api.create_card('111', init_db) assert result is False card_obj = knards.Card() result = api.create_card(card_obj, init_db) assert result == 1