Skip to content
Snippets Groups Projects
test_revocation.py 30.5 KiB
Newer Older
Moul's avatar
Moul committed
# Copyright  2016-2022 Maël Azimi <m.a@moul.re>
#
# Silkaj is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Silkaj is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with Silkaj. If not, see <https://www.gnu.org/licenses/>.


import urllib
from pathlib import Path
from unittest.mock import Mock

import click
import pytest
from click.testing import CliRunner
from duniterpy.api import bma
from duniterpy.documents.revocation import Revocation

from patched.auth import patched_auth_method
from patched.blockchain_tools import patched_get_head_block_gtest
from patched.idty_tools import idty1, idty2, idty_block, lookup_one, lookup_two
from silkaj import auth, blockchain_tools, idty_tools, revocation, wot
from silkaj.cli import cli
from silkaj.constants import FAILURE_EXIT_STATUS, SUCCESS_EXIT_STATUS
from silkaj.network_tools import client_instance
from silkaj.tui import gen_pubkey_checksum
Moul's avatar
Moul committed
# Useful function


def display_dry_options(display, dry_run):
    if display:
        return ["--display"]
    elif dry_run:
        return ["--dry-run"]
    else:
        return []


Moul's avatar
Moul committed
# Values
# idty1

REV_DOC = Revocation(
    version=10,
    currency="g1-test",
    identity=idty1,
)
REV_DOC.signature = "dTv6HHnyBsMXofOZFT21Y3gRzG/frseCaZFfpatpCWj\
YsNA8HPHjTibLUcJ3E9ZUgd0QUV7Bbu868xQE+j/yAg=="

REV_DOC_FALSE = Revocation(
    version=10,
    currency="g1-test",
    identity=idty1,
)
REV_DOC_FALSE.signature = "XXXXXXXXBsMXofOZFT21Y3gRzG/frseCaZFfp\
atpCWjYsNA8HPHjTibLUcJ3E9ZUgd0QUV7Bbu868xQE+j/yAg=="


# idty2

REV_2 = Revocation(
    version=10,
    currency="g1-test",
    identity=idty2,
)
REV_2.signature = "42D2vbIJnv2aGqUMbD+BF+eChzzGo4R3CVPAl5hpIGvoT\
cZQCfKBsRRlZDx6Gwn6lsJ3KLiIwPQeJKGYCW2YBg=="

REV_2_FALSE = Revocation(
    version=10,
    currency="g1-test",
    identity=idty2,
)
REV_2_FALSE.signature = "XXXXXXIJnv2aGqUMbD+BF+eChzzGo4R3CVPAl5hp\
IGvoTcZQCfKBsRRlZDx6Gwn6lsJ3KLiIwPQeJKGYCW2YBg=="


WRONG_FORMAT_REV = "ersion: 10\
Type: Revocation\
Currency: g1-test\
Issuer: 969qRJs8KhsnkyzqarpL4RKZGMdVKNbZgu8fhsigM7Lj\
IdtyUniqueID: aa_aa\
IdtyTimestamp: 703902-00002D6BC5E4FC540A4E188C3880A0ACCA06CD77017D26231A515312162B4070\
IdtySignature: 3RNQcKNI1VMmuCpK7wer8haOA959EQSDIR1v0U\
e/7TpTCOmsU2zYCpC+tqgLQFxDX4A79sB61c11J5C/3Z/TCw==\
42D2vbIJnv2aGqUMbD+BF+eChzzGo4R3CVPAl5hpIGvoTcZQCfKBsRRlZDx6Gwn6lsJ3KLiIwPQeJKGYCW2YBg=="


ERROR_CODE = 1005
ERROR_MESSAGE = "Document has unkown fields or wrong line ending format"


Moul's avatar
Moul committed
# patched functions
def patch_get_id_block(node, number):
    return idty_block


def patched_auth_method_Claude():
    return patched_auth_method("a")


def patch_check_many_identities(idty):
    return True


def patched_choose_identity(pubkey):
    return (
        {
            "uid": idty1.uid,
            "meta": {"timestamp": str(idty1.block_id)},
            "self": "kFW2we2K3zx4PZODx0Wf+xdXAJTmYD+yqdyZBsPF7UwqdaCA4N+yHj7+09\
Gjsttl0i9GtWzodyJ6mBE1q7jcAw==",
        },
        idty1.pubkey,
        None,
    )


def patched_send_bma_revoke_error(wot_useless, rev_doc_useless):
    raise urllib.error.HTTPError(
        url="this/is/a/test.url",
        code=ERROR_CODE,
        msg=ERROR_MESSAGE,
        hdrs={},
        fp=None,
    )


Moul's avatar
Moul committed
# tests

# test cli dry-run
@pytest.mark.parametrize(
    "subcommand, expected_warn",
    [
        (
            "save",
            False,
        ),
        (
            "verify",
            False,
        ),
        (
            "publish",
            True,
        ),
        (
            "revoke",
            True,
        ),
    ],
)
def test_revocation_cli_dry_run(subcommand, expected_warn, monkeypatch):
    """
    Tests dry-run option behavior when associated with other options
    """
    monkeypatch.setattr(auth, "auth_method", patched_auth_method_Claude)
    monkeypatch.setattr(
        blockchain_tools, "get_head_block", patched_get_head_block_gtest
    )
    monkeypatch.setattr(wot, "choose_identity", patched_choose_identity)
    monkeypatch.setattr(bma.blockchain, "block", patch_get_id_block)
    monkeypatch.setattr(
        idty_tools, "check_many_identities", patch_check_many_identities
    )

    print("subcommand: ", subcommand)  # debug

    warning = "WARNING: the document will only be displayed and will not be sent."

    command = ["--dry-run", "-gt", "revocation", subcommand]
    print("command: ", " ".join(command))  # debug
    file = "revocation.txt"
    runner = CliRunner()
    with runner.isolated_filesystem():
        with open(file, "w") as f:
            f.write(REV_DOC.signed_raw())
        result = runner.invoke(cli, args=command)
    assert "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o" in result.output
    assert "Version: 10" in result.output

    if expected_warn:
        assert warning in result.output
    else:
Moul's avatar
Moul committed
        assert warning not in result.output


# test cli save
@pytest.mark.parametrize(
    "display, dry_run, file, user_input, expected",
    [
        (
            False,
            False,
            "",
            "yes\n",
            [
                "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
                "Do you want to save the revocation document for this identity?",
            ],
        ),
        (
            True,
            False,
            "",
            "yes\n",
            [
                "Version: 10",
                "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
                "Do you want to save the revocation document for this identity?",
            ],
        ),
        (
            False,
            True,
            "",
            None,
            [
                "Version: 10",
                "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
            ],
        ),
        (
            False,
            False,
            "test_doc",
            "yes\n",
            [
                "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
                "Do you want to save the revocation document for this identity?",
            ],
        ),
        (
            True,
            False,
            "test_doc",
            "yes\n",
            [
                "Version: 10",
                "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
                "Do you want to save the revocation document for this identity?",
            ],
        ),
        (
            False,
            True,
            "test_doc",
            None,
            [
                "Version: 10",
                "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
            ],
        ),
        (
            False,
            False,
            "",
            "no\n",
            [
                "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
                "Do you want to save the revocation document for this identity?",
                "Ok, goodbye!",
            ],
        ),
        (
            True,
            False,
            "",
            "no\n",
            [
                "Version: 10",
                "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
                "Do you want to save the revocation document for this identity?",
                "Ok, goodbye!",
            ],
        ),
    ],
)
def test_revocation_cli_save(display, dry_run, file, user_input, expected, monkeypatch):
    monkeypatch.setattr(auth, "auth_method", patched_auth_method_Claude)
    monkeypatch.setattr(
        blockchain_tools, "get_head_block", patched_get_head_block_gtest
    )
    monkeypatch.setattr(wot, "choose_identity", patched_choose_identity)
    monkeypatch.setattr(bma.blockchain, "block", patch_get_id_block)
    patched_save_doc = Mock()
    monkeypatch.setattr(
        revocation,
        "save_doc",
        patched_save_doc,
    )

    command = display_dry_options(display, dry_run)
    subcommand = ["revocation", "save"]
    command.extend(subcommand)
    if file:
        command.extend([file])
    else:
        file = revocation.REVOCATION_LOCAL_PATH

    result = CliRunner().invoke(cli, args=command, input=user_input)
    if not dry_run and user_input == "yes\n":
        patched_save_doc.assert_called_with(file, REV_DOC.signed_raw(), idty1.pubkey)
    elif user_input == "no\n" or dry_run:
        patched_save_doc.assert_not_called()
    for expect in expected:
        assert expect in result.output


# test cli verify
@pytest.mark.parametrize(
    "display, dry_run, doc, lookup, file, expected, not_expected",
    [
        (
            False,
            False,
            REV_DOC,
            lookup_one,
            "",
            [
                "| Public key |",
                "Revocation document is valid.\n",
            ],
            ["Version: 10"],
        ),
        (
            False,
            False,
            REV_DOC,
            lookup_two,
            "",
            [
                "One matching identity!\nSimilar identities:",
                "uid",
                "| Public key |",
                "Revocation document is valid.\n",
            ],
            ["Version: 10"],
        ),
        (
            True,
            False,
            REV_DOC,
            lookup_one,
            "",
            [
                "Version: 10",
                "| Public key |",
                "Revocation document is valid.\n",
            ],
            [],
        ),
        (
            False,
            True,
            REV_DOC,
            lookup_one,
            "",
            [
                "Version: 10",
                "Revocation document is valid.\n",
            ],
        ),
        (
            False,
            False,
            REV_DOC_FALSE,
            lookup_one,
            "",
            ["Error: the signature of the revocation document is invalid."],
            [
                "Version: 10",
                "| Public key |",
                "Revocation document is valid.\n",
            ],
        ),
        (
            False,
            False,
            REV_2,
            lookup_two,
            "test_doc",
            [
                "Revocation document does not match any valid identity.\nSimilar identities:",
                "uid",
                "Claude",
                "Claudia",
            ],
            [
                "Revocation document is valid.\n",
                "| Public key |",
                "Version: 10",
            ],
        ),
    ],
)
def test_revocation_cli_verify(
    display, dry_run, doc, lookup, file, expected, not_expected, monkeypatch
):
    def patched_lookup(node, id_pubkey):
        return lookup

    monkeypatch.setattr(bma.wot, "lookup", patched_lookup)
    monkeypatch.setattr(bma.blockchain, "block", patch_get_id_block)
    monkeypatch.setattr(
        blockchain_tools, "get_head_block", patched_get_head_block_gtest
    )

    # prepare command
    command = display_dry_options(display, dry_run)
    command.extend(["revocation", "verify"])
    if file:
        command.extend([file])
    else:
        file = revocation.REVOCATION_LOCAL_PATH

    # verify file
    runner = CliRunner()
    with runner.isolated_filesystem():
        with open(file, "w") as f:
            f.write(doc.signed_raw())
        result = runner.invoke(cli, args=command)
        for expect in expected:
            assert expect in result.output
        for not_expect in not_expected:
Moul's avatar
Moul committed
            assert not_expect not in result.output


# test cli publish
@pytest.mark.parametrize(
    "display, dry_run, doc, lookup, file, user_input, expected",
    [
        (
            False,
            False,
            REV_DOC,
            lookup_one,
            "",
            "yes\n",
            [
                "| Public key |",
                "Do you confirm sending this revocation document immediately?",
            ],
        ),
        (
            False,
            False,
            REV_DOC,
            lookup_two,
            "",
            "yes\n",
            [
                "One matching identity!\nSimilar identities:",
                "Claudia",
                "| Public key |",
                "Do you confirm sending this revocation document immediately?",
            ],
        ),
        (
            True,
            False,
            REV_DOC,
            lookup_one,
            "",
            "yes\n",
            [
                "| Public key |",
                "Do you confirm sending this revocation document immediately?",
                "Version: 10",
            ],
        ),
        (
            True,
            False,
            REV_DOC,
            lookup_two,
            "",
            "yes\n",
            [
                "One matching identity!\nSimilar identities:",
                "| Public key |",
                "Do you confirm sending this revocation document immediately?",
                "Version: 10",
            ],
        ),
        (
            False,
            True,
            REV_DOC,
            lookup_one,
            "",
            None,
            [
                "WARNING: the document will only be displayed and will not be sent.",
                "Version: 10",
            ],
        ),
        (
            False,
            True,
            REV_DOC,
            lookup_two,
            "",
            None,
            [
                "One matching identity!\nSimilar identities:",
                "WARNING: the document will only be displayed and will not be sent.",
                "Version: 10",
            ],
        ),
        (
            False,
            False,
            REV_DOC,
            lookup_one,
            "",
            "no\n",
            [
                "| Public key |",
                "Do you confirm sending this revocation document immediately?",
            ],
        ),
        (
            True,
            False,
            REV_DOC,
            lookup_one,
            "",
            "no\n",
            [
                "| Public key |",
                "Do you confirm sending this revocation document immediately?",
                "Version: 10",
            ],
        ),
        (
            False,
            False,
            REV_DOC_FALSE,
            lookup_one,
            "",
            None,
            ["Error: the signature of the revocation document is invalid."],
        ),
        (
            True,
            False,
            REV_DOC_FALSE,
            lookup_one,
            "",
            None,
            ["Error: the signature of the revocation document is invalid."],
        ),
        (
            False,
            True,
            REV_DOC_FALSE,
            lookup_one,
            "",
            None,
            [
                "WARNING: the document will only be displayed and will not be sent.",
                "Error: the signature of the revocation document is invalid.",
            ],
        ),
        (
            False,
            False,
            REV_DOC,
            lookup_one,
            "test_doc",
            "yes\n",
            [
                "| Public key |",
                "Do you confirm sending this revocation document immediately?",
            ],
        ),
        (
            True,
            False,
            REV_DOC,
            lookup_one,
            "test_doc",
            "yes\n",
            [
                "| Public key |",
                "Do you confirm sending this revocation document immediately?",
            ],
        ),
        (
            False,
            True,
            REV_DOC,
            lookup_one,
            "test_doc",
            "yes\n",
            [
                "WARNING: the document will only be displayed and will not be sent.",
                "Version: 10",
            ],
        ),
        (
            False,
            False,
            REV_DOC_FALSE,
            lookup_one,
            "test_doc",
            None,
            ["Error: the signature of the revocation document is invalid."],
        ),
        (
            True,
            False,
            REV_DOC_FALSE,
            lookup_one,
            "test_doc",
            None,
            ["Error: the signature of the revocation document is invalid."],
        ),
        (
            False,
            True,
            REV_DOC_FALSE,
            lookup_one,
            "test_doc",
            None,
            [
                "Error: the signature of the revocation document is invalid.",
                "WARNING: the document will only be displayed and will not be sent.",
            ],
        ),
        (
            False,
            False,
            REV_2,
            lookup_two,
            "",
            "",
            [
                "Revocation document does not match any valid identity.\nSimilar identities:",
            ],
        ),
        (
            False,
            False,
            REV_DOC,
            False,
            "",
            "",
            ["Revocation document does not match any valid identity."],
        ),
    ],
)
def test_revocation_cli_publish(
    display, dry_run, doc, lookup, file, user_input, expected, monkeypatch
):
    def patched_lookup(node, id_pubkey):
Moul's avatar
Moul committed
        if not lookup:
            raise urllib.error.HTTPError(
                url="this/is/a/test.url",
                code=404,
                msg="(Test) Not Found",
                hdrs={},
                fp=None,
            )
        return lookup

    monkeypatch.setattr(bma.wot, "lookup", patched_lookup)
    monkeypatch.setattr(
        blockchain_tools, "get_head_block", patched_get_head_block_gtest
    )
    monkeypatch.setattr(bma.blockchain, "block", patch_get_id_block)

    patched_send_bma_revoke = Mock()
    monkeypatch.setattr(bma.wot, "revoke", patched_send_bma_revoke)

    # prepare command
    command = display_dry_options(display, dry_run)
    command.extend(["revocation", "publish"])
    if file:
        command.extend([file])
    else:
        file = revocation.REVOCATION_LOCAL_PATH

    # test publication
    runner = CliRunner()
    with runner.isolated_filesystem():
        with open(file, "w") as f:
            f.write(doc.signed_raw())
        result = runner.invoke(cli, args=command, input=user_input)
        if user_input == "yes\n" and not dry_run:
            patched_send_bma_revoke.assert_called_once_with(client, doc.signed_raw())
        elif dry_run or user_input == "no\n":
            patched_send_bma_revoke.assert_not_called
        for expect in expected:
            assert expect in result.output


# test cli publish send errors
@pytest.mark.parametrize(
    "display, file, user_input, expected",
    [
        (
            False,
            "",
            "yes\n",
            [
                "| Public key |",
                "Do you confirm sending this revocation document immediately?",
                "Error while publishing revocation",
            ],
        ),
        (
            True,
            "",
            "yes\n",
            [
                "| Public key |",
                "Do you confirm sending this revocation document immediately?",
                "Version: 10",
                "Error while publishing revocation",
            ],
        ),
        (
            False,
            "test_doc",
            "yes\n",
            [
                "| Public key |",
                "Do you confirm sending this revocation document immediately?",
                "Error while publishing revocation",
            ],
        ),
        (
            True,
            "test_doc",
            "yes\n",
            [
                "| Public key |",
                "Do you confirm sending this revocation document immediately?",
                "Version: 10",
                "Error while publishing revocation",
            ],
        ),
    ],
)
def test_revocation_cli_publish_send_errors(
    display, file, user_input, expected, monkeypatch
):
    def patched_lookup(node, id_pubkey):
        return lookup_one

    monkeypatch.setattr(bma.wot, "lookup", patched_lookup)
    monkeypatch.setattr(
        blockchain_tools, "get_head_block", patched_get_head_block_gtest
    )
    monkeypatch.setattr(bma.blockchain, "block", patch_get_id_block)
    monkeypatch.setattr(bma.wot, "revoke", patched_send_bma_revoke_error)

    # prepare command
    command = display_dry_options(display, False)
    command.extend(["revocation", "publish"])
    if file:
        command.extend([file])
    else:
        file = revocation.REVOCATION_LOCAL_PATH

    # test publication
    runner = CliRunner()
    with runner.isolated_filesystem():
        with open(file, "w") as f:
            f.write(REV_DOC.signed_raw())
        result = runner.invoke(cli, args=command, input=user_input)
        for expect in expected:
            assert expect in result.output
        assert result.exit_code == FAILURE_EXIT_STATUS


# test cli revoke
@pytest.mark.parametrize(
    "display, dry_run, user_input, doc, expected",
    [
        (
            False,
            False,
            "yes\n",
            REV_DOC,
            [
                "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
                "Do you confirm sending this revocation document immediately?",
            ],
        ),
        (
            True,
            False,
            "yes\n",
            REV_DOC,
            [
                "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
                "Do you confirm sending this revocation document immediately?",
                "Version: 10",
            ],
        ),
        (
            False,
            True,
            None,
            REV_DOC,
            [
                "WARNING: the document will only be displayed and will not be sent.",
                "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
                "Version: 10",
            ],
        ),
        (
            False,
            False,
            "no\n",
            REV_DOC,
            [
                "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
                "Do you confirm sending this revocation document immediately?",
            ],
        ),
        (
            True,
            False,
            "no\n",
            REV_DOC,
            [
                "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
                "Do you confirm sending this revocation document immediately?",
                "Version: 10",
            ],
        ),
    ],
)
def test_revocation_cli_revoke(
    display, dry_run, user_input, doc, expected, monkeypatch
):
    monkeypatch.setattr(auth, "auth_method", patched_auth_method_Claude)
    monkeypatch.setattr(
        blockchain_tools, "get_head_block", patched_get_head_block_gtest
    )
    monkeypatch.setattr(wot, "choose_identity", patched_choose_identity)
    monkeypatch.setattr(bma.blockchain, "block", patch_get_id_block)
    patched_send_bma_revoke = Mock()
    monkeypatch.setattr(bma.wot, "revoke", patched_send_bma_revoke)

    command = display_dry_options(display, dry_run)
    command.extend(["revocation", "revoke"])

    result = CliRunner().invoke(cli, args=command, input=user_input)
    for expect in expected:
        assert expect in result.output
    if not dry_run and user_input == "yes\n":
        patched_send_bma_revoke.assert_called_once_with(client, doc.signed_raw())
    if dry_run or user_input == "no\n":
        patched_send_bma_revoke.assert_not_called()


# test cli revoke errors
@pytest.mark.parametrize(
    "display, user_input, doc, expected",
    [
        (
            False,
            "yes\n",
            REV_DOC,
            [
                "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
                "Do you confirm sending this revocation document immediately?",
                "Error while publishing revocation",
            ],
        ),
        (
            True,
            "yes\n",
            REV_DOC,
            [
                "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
                "Do you confirm sending this revocation document immediately?",
                "Version: 10",
                "Error while publishing revocation",
            ],
        ),
    ],
)
def test_revocation_cli_revoke_errors(display, user_input, doc, expected, monkeypatch):

    monkeypatch.setattr(auth, "auth_method", patched_auth_method_Claude)
    monkeypatch.setattr(
        blockchain_tools, "get_head_block", patched_get_head_block_gtest
    )
    monkeypatch.setattr(wot, "choose_identity", patched_choose_identity)
    monkeypatch.setattr(bma.blockchain, "block", patch_get_id_block)
    monkeypatch.setattr(bma.wot, "revoke", patched_send_bma_revoke_error)

    command = display_dry_options(display, False)
    command.extend(["revocation", "revoke"])

    result = CliRunner().invoke(cli, args=command, input=user_input)
    for expect in expected:
        assert expect in result.output
    assert result.exit_code == FAILURE_EXIT_STATUS


# test create_revocation_doc
@pytest.mark.parametrize("idty, lookup", [(idty1, lookup_one)])
def test_create_revocation_doc(idty, lookup):
    test = revocation.create_revocation_doc(
        lookup["results"][0]["uids"][0],
        "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
        "g1-test",
    )
    expected = Revocation(
        version=10,
        currency="g1-test",
        identity=idty,
    )
    assert test == expected


# test save_doc
@pytest.mark.parametrize(
    "path, rev_1, rev_2, pubkey",
    [
        (
            "./test_doc.txt",
            REV_DOC,
            REV_2,
            "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
        ),
        ("revocation", REV_DOC, REV_2, "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o"),
    ],
)
def test_save_doc(path, rev_1, rev_2, pubkey, capsys, monkeypatch):
    def conf_true(confirm_question):
        return True

    def conf_false(confirm_question):
        return False

    runner = CliRunner()
    with runner.isolated_filesystem():
        # test file is written on en empty location
        test_path = Path(path)
        revocation.save_doc(path, rev_1.signed_raw(), pubkey)
        assert test_path.is_file()
        with open(path) as f:
            assert f.read() == rev_1.signed_raw()
        # test file is overwritten if confirm
        monkeypatch.setattr(click, "confirm", value=conf_true)
        revocation.save_doc(path, rev_2.signed_raw(), pubkey)
        expected_confirm = f"Revocation document file stored into `{path}` \
for following public key: {gen_pubkey_checksum(pubkey)}"
        assert expected_confirm in capsys.readouterr().out
        with open(path) as f:
            assert f.read() == rev_2.signed_raw()
        # test file is not overwritten if not confirm
        monkeypatch.setattr(click, "confirm", value=conf_false)
        with pytest.raises(SystemExit) as pytest_exit:
            revocation.save_doc(path, rev_1.signed_raw(), pubkey)
        assert pytest_exit.type == SystemExit
        assert pytest_exit.value.code == SUCCESS_EXIT_STATUS
        expected_confirm = "Ok, goodbye!"
        assert expected_confirm in capsys.readouterr().out
        with open(path) as f:
            assert f.read() == rev_2.signed_raw()


# test verify_document
@pytest.mark.parametrize(
    "doc, lookup",
    [
        (REV_DOC, lookup_one),
        (REV_DOC, lookup_two),
    ],
)
def test_verify_document(doc, lookup, capsys, monkeypatch):
    def patched_lookup(node, id_pubkey):
        return lookup

    # prepare test
    path = "test_file.txt"
    monkeypatch.setattr(bma.wot, "lookup", patched_lookup)