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_default_mode_is_question_first(mocker, question_prompt_proper_fill, answer_prompt_proper_fill): """ If the 'new' subcommand is invoked without arguments, like so: $ kn new the subcommand must work in the "question-first" mode. """ runner = CliRunner() with runner.isolated_filesystem(): # create the DB runner.invoke(knards.main, ['bootstrap-db']) mocker.patch('knards.util.open_in_editor', side_effect=iter([ question_prompt_proper_fill, answer_prompt_proper_fill ])) # invoke the subcommand without arguments runner.invoke(knards.main, ['new']) # fetch the saved card card_obj = api.get_card_by_id(1) # check if it has the standard question text in the question field assert 'Proper question text.' in card_obj.question # and the standard answer text in the answer field assert 'Proper answer text.' in card_obj.answer
def test_metadata_is_saved_from_the_second_buffer(mocker, question_prompt_proper_fill, answer_prompt_proper_fill): """ The card's metadata (markers, series, and position in series) is saved from the second prompted buffer. The metadata from the first buffer is ignored. """ runner = CliRunner() with runner.isolated_filesystem(): # create the DB runner.invoke(knards.main, ['bootstrap-db']) mocker.patch('knards.util.open_in_editor', side_effect=iter([ question_prompt_proper_fill, answer_prompt_proper_fill ])) # invoke the subcommand runner.invoke(knards.main, ['new']) # fetch the saved card card_obj = api.get_card_by_id(1) # check if it has got markers from the second buffer assert 'answer' in card_obj.markers assert 'question' not in card_obj.markers # check if it has got series from the second buffer assert 'answer' in card_obj.series assert 'question' not in card_obj.series # check if it has got position in series from the second buffer assert card_obj.pos_in_series == 3
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_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 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_if_nonexistent_id_passed_in_none_is_returned(init_db): """ get_card_by_id() returns None if a card with the passed in id wasn't found in the DB. """ result = api.get_card_by_id(1, init_db) assert result is None
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_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_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_card_id_is_passed_in_and_card_doesnt_exist_method_returns_false( mocker, init_db ): """ If card_id argument is passed to the delete_card() method and the respective card doesn't exist in the DB, method returns False and nothing is removed from the DB. """ mocker.patch( 'knards.api.get_card_by_id', return_value=api.get_card_by_id(1, init_db) ) result = api.delete_card(card_id=1, db_path=init_db) assert result is False
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 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 edit(card_id): """ Open a card for edit. Then update it in the DB. Input: card_id - target card's id. Must be a proper integer. Exit codes: 0 - success 1 - error 2 - CLI args misuse 3 - api method got wrong input 4 - sqlite operation error 5 - DB not found 6 - card not found in the DB 7 - user failed to fill in the buffer properly """ # Exit codes: # 0: success # 1: unknown error # 2: bad input arguments # 3: sqlite3 module exception # 4: api method got wrong input # 5: DB file not found # 6: object not found # try to fetch the card from the DB try: card_obj = api.get_card_by_id(card_id) except ValueError as e: print(e.args[0]) sys.exit(3) except sqlite3.OperationalError: print('Couldn\'t connect to the DB, check if the file exists and has \ proper permissions assigned.') sys.exit(4) except exceptions.DBFileNotFound as e: print(e.args[0]) sys.exit(5) except exceptions.CardNotFound as e: print(e.args[0]) sys.exit(6) prompt = 'Markers: [{}]\n'.format(card_obj.markers) prompt += 'Series: [{}]\n'.format(card_obj.series) prompt += 'No. in series: {}\n'.format(card_obj.pos_in_series) prompt += msg.DIVIDER_LINE + '\n' prompt += card_obj.question + '\n' prompt += msg.DIVIDER_LINE + '\n' prompt += card_obj.answer + '\n' valid = False retry_count = 1 submit = prompt while not valid: submit = util.open_in_editor(submit) try: submit, valid = util.check_buffer('edit', submit) except exceptions.BadBufferFormat as e: print(e.args[0]) if not valid: if not util.retry_buffer(retry_count): sys.exit(7) # remove redundant empty lines on either side submit_meta = submit.split(msg.DIVIDER_LINE)[0].strip('\n').split('\n') submit_question = submit.split(msg.DIVIDER_LINE)[1].strip('\n').split('\n') submit_answer = submit.split(msg.DIVIDER_LINE)[2].strip('\n').split('\n') for index, line in enumerate(submit_meta): 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:])) question_text = '' for index, line in enumerate(submit_question): question_text += line + '\n' else: card_obj = card_obj._replace(question=question_text) answer_text = '' for index, line in enumerate(submit_answer): answer_text += line + '\n' else: card_obj = card_obj._replace(answer=answer_text) try: updated_with_id = api.update_card(card_obj) except ValueError as e: print(e.args[0]) sys.exit(3) except sqlite3.OperationalError: click.secho( 'Error while trying to update the target record in the DB.', fg='red', bold=True) sys.exit(4) click.secho('Card #{} was successfully updated.'.format(updated_with_id), fg='green', bold=True)