Пример #1
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_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
Пример #4
0
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
Пример #5
0
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')
Пример #6
0
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
Пример #7
0
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
Пример #8
0
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
Пример #9
0
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
Пример #10
0
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
Пример #11
0
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
Пример #12
0
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
Пример #13
0
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
Пример #14
0
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)
Пример #15
0
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)