Skip to content
Snippets Groups Projects
Select Git revision
  • a7c628ba54f1e759ba5968a447525f95f5933f1c
  • master default protected
  • fix_picked_up_file_in_runtime_release
  • network/gtest-1000 protected
  • upgradable-multisig
  • runtime/gtest-1000
  • network/gdev-800 protected
  • cgeek/issue-297-cpu
  • gdev-800-tests
  • update-docker-compose-rpc-squid-names
  • fix-252
  • 1000i100-test
  • hugo/tmp-0.9.1
  • network/gdev-803 protected
  • hugo/endpoint-gossip
  • network/gdev-802 protected
  • hugo/distance-precompute
  • network/gdev-900 protected
  • tuxmain/anonymous-tx
  • debug/podman
  • hugo/195-doc
  • gtest-1000-0.11.1 protected
  • gtest-1000-0.11.0 protected
  • gtest-1000 protected
  • gdev-900-0.10.1 protected
  • gdev-900-0.10.0 protected
  • gdev-900-0.9.2 protected
  • gdev-800-0.8.0 protected
  • gdev-900-0.9.1 protected
  • gdev-900-0.9.0 protected
  • gdev-803 protected
  • gdev-802 protected
  • runtime-801 protected
  • gdev-800 protected
  • runtime-800-bis protected
  • runtime-800 protected
  • runtime-800-backup protected
  • runtime-701 protected
  • runtime-700 protected
  • runtime-600 protected
  • runtime-500 protected
41 results

gtest.rs

Blame
  • test_revocation.py 25.73 KiB
    # Copyright  2016-2025 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 pytest
    import rich_click as click
    from click.testing import CliRunner
    from duniterpy.api import bma
    from duniterpy.documents.revocation import Revocation
    
    from silkaj import auth
    from silkaj.account_storage import AccountStorage
    from silkaj.blockchain import tools as bc_tools
    from silkaj.cli import cli
    from silkaj.constants import FAILURE_EXIT_STATUS, SUCCESS_EXIT_STATUS
    from silkaj.network import client_instance
    from silkaj.public_key import gen_pubkey_checksum
    from silkaj.wot import idty_tools, revocation
    from silkaj.wot import tools as w_tools
    from tests.patched.auth import patched_auth_method
    from tests.patched.blockchain_tools import (
        patched_get_currency_gtest,
        patched_get_head_block_gtest,
    )
    from tests.patched.idty_tools import idty1, idty2, idty_block, lookup_one, lookup_two
    
    REVOCATION_PATH = Path("revocation.txt")
    
    # Useful function
    
    
    def display_dry_options(display, dry_run):
        if display:
            return ["--display"]
        if dry_run:
            return ["--dry-run"]
        return []
    
    
    # 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"
    
    
    # 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,
        )
    
    
    def patched_account_storage_revocation_path(self):
        return REVOCATION_PATH
    
    
    # tests
    
    
    # test cli dry-run
    @pytest.mark.parametrize(
        ("subcommand"),
        [
            ("publish"),
            ("revoke"),
        ],
    )
    def test_revocation_cli_dry_run(subcommand, monkeypatch):
        """
        Tests dry-run option behavior when associated with other options
        """
        monkeypatch.setattr(auth, "auth_method", patched_auth_method_Claude)
        monkeypatch.setattr(
            AccountStorage,
            "revocation_path",
            patched_account_storage_revocation_path,
        )
        monkeypatch.setattr(bc_tools, "get_head_block", patched_get_head_block_gtest)
        monkeypatch.setattr(w_tools, "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,
        )
        monkeypatch.setattr(Path, "mkdir", Mock())
    
        warning = "WARNING: the document will only be displayed and will not be sent."
        command = ("--dry-run", "-gt", "--account", "test", "wot", "revocation", subcommand)
        print("command: ", " ".join(command))  # debug
    
        runner = CliRunner()
        with runner.isolated_filesystem():
            REVOCATION_PATH.write_text(REV_DOC.signed_raw(), encoding="utf-8")
            result = runner.invoke(cli, args=command)
        assert idty1.pubkey in result.output
        assert "Version: 10" in result.output
        assert warning in result.output
    
    
    CREATE_STRINGS = [
        idty1.pubkey,
        "Do you want to save the revocation document for this identity?",
    ]
    
    
    # test cli create
    @pytest.mark.parametrize(
        ("user_input", "expected"),
        [
            (
                "yes\n",
                CREATE_STRINGS,
            ),
            (
                None,
                CREATE_STRINGS,
            ),
            (
                "no\n",
                [*CREATE_STRINGS, "Ok, goodbye!"],
            ),
        ],
    )
    def test_revocation_cli_create(user_input, expected, monkeypatch):
        monkeypatch.setattr(auth, "auth_method", patched_auth_method_Claude)
        monkeypatch.setattr(bc_tools, "get_head_block", patched_get_head_block_gtest)
        monkeypatch.setattr(w_tools, "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,
        )
        monkeypatch.setattr(Path, "mkdir", Mock())
    
        command = ("--account", "test", "wot", "revocation", "create")
        result = CliRunner().invoke(cli, args=command, input=user_input)
        if user_input == "yes\n":
            patched_save_doc.assert_called_once()
        elif user_input == "no\n":
            patched_save_doc.assert_not_called()
        for expect in expected:
            assert expect in result.output
    
    
    # test cli verify
    @pytest.mark.parametrize(
        ("doc", "lookup", "expected", "not_expected"),
        [
            (
                REV_DOC,
                lookup_one,
                [
                    "| Public key |",
                    "Revocation document is valid.\n",
                ],
                [],
            ),
            (
                REV_DOC,
                lookup_two,
                [
                    "One matching identity!\nSimilar identities:",
                    "uid",
                    "| Public key |",
                    "Revocation document is valid.\n",
                ],
                [],
            ),
            (
                REV_DOC_FALSE,
                lookup_one,
                ["Error: the signature of the revocation document is invalid."],
                [
                    "| Public key |",
                    "Revocation document is valid.\n",
                ],
            ),
            (
                REV_2,
                lookup_two,
                [
                    "Revocation document does not match any valid identity.\nSimilar identities:",
                    "uid",
                    "Claude",
                    "Claudia",
                ],
                [
                    "Revocation document is valid.\n",
                    "| Public key |",
                ],
            ),
        ],
    )
    def test_revocation_cli_verify(
        doc,
        lookup,
        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(bc_tools, "get_head_block", patched_get_head_block_gtest)
        monkeypatch.setattr(
            AccountStorage,
            "revocation_path",
            patched_account_storage_revocation_path,
        )
        monkeypatch.setattr(Path, "mkdir", Mock())
    
        command = ("--account", "test", "wot", "revocation", "verify")
        runner = CliRunner()
        with runner.isolated_filesystem():
            REVOCATION_PATH.write_text(doc.signed_raw(), encoding="utf-8")
            result = CliRunner().invoke(cli, args=command)
    
        for expect in expected:
            assert expect in result.output
        for not_expect in not_expected:
            assert not_expect not in result.output
    
    
    # test cli publish
    @pytest.mark.parametrize(
        ("display", "dry_run", "doc", "lookup", "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?",
                ],
            ),
            (
                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.",
                ],
            ),
            (
                True,
                False,
                REV_DOC,
                lookup_one,
                "yes\n",
                [
                    "| Public key |",
                    "Do you confirm sending this revocation document immediately?",
                ],
            ),
            (
                False,
                True,
                REV_DOC,
                lookup_one,
                "yes\n",
                [
                    "WARNING: the document will only be displayed and will not be sent.",
                    "Version: 10",
                ],
            ),
            (
                False,
                True,
                REV_DOC_FALSE,
                lookup_one,
                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,
        user_input,
        expected,
        monkeypatch,
    ):
        def patched_lookup(node, id_pubkey):
            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(bc_tools, "get_head_block", patched_get_head_block_gtest)
        monkeypatch.setattr(bma.blockchain, "block", patch_get_id_block)
        monkeypatch.setattr(
            AccountStorage,
            "revocation_path",
            patched_account_storage_revocation_path,
        )
    
        patched_send_bma_revoke = Mock()
        monkeypatch.setattr(bma.wot, "revoke", patched_send_bma_revoke)
        monkeypatch.setattr(Path, "mkdir", Mock())
    
        # prepare command
        command = display_dry_options(display, dry_run)
        command.extend(["--account", "test", "wot", "revocation", "publish"])
    
        # test publication
        runner = CliRunner()
        with runner.isolated_filesystem():
            REVOCATION_PATH.write_text(doc.signed_raw(), encoding="utf-8")
            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_instance(),
                    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", "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",
                ],
            ),
        ],
    )
    def test_revocation_cli_publish_send_errors(
        display,
        user_input,
        expected,
        monkeypatch,
    ):
        def patched_lookup(node, id_pubkey):
            return lookup_one
    
        monkeypatch.setattr(
            AccountStorage,
            "revocation_path",
            patched_account_storage_revocation_path,
        )
        monkeypatch.setattr(bma.wot, "lookup", patched_lookup)
        monkeypatch.setattr(bc_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)
        monkeypatch.setattr(Path, "mkdir", Mock())
    
        # prepare command
        command = display_dry_options(display, False)
        command.extend(["--account", "test", "wot", "revocation", "publish"])
    
        # test publication
        runner = CliRunner()
        with runner.isolated_filesystem():
            REVOCATION_PATH.write_text(REV_DOC.signed_raw(), encoding="utf-8")
            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,
                [
                    idty1.pubkey,
                    "Do you confirm sending this revocation document immediately?",
                ],
            ),
            (
                True,
                False,
                "yes\n",
                REV_DOC,
                [
                    idty1.pubkey,
                    "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.",
                    idty1.pubkey,
                    "Version: 10",
                ],
            ),
            (
                False,
                False,
                "no\n",
                REV_DOC,
                [
                    idty1.pubkey,
                    "Do you confirm sending this revocation document immediately?",
                ],
            ),
            (
                True,
                False,
                "no\n",
                REV_DOC,
                [
                    idty1.pubkey,
                    "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(bc_tools, "get_currency", patched_get_currency_gtest)
        monkeypatch.setattr(w_tools, "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(["wot", "revocation", "revoke"])
        client = client_instance()
    
        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,
                [
                    idty1.pubkey,
                    "Do you confirm sending this revocation document immediately?",
                    "Error while publishing revocation",
                ],
            ),
            (
                True,
                "yes\n",
                REV_DOC,
                [
                    idty1.pubkey,
                    "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(bc_tools, "get_head_block", patched_get_head_block_gtest)
        monkeypatch.setattr(w_tools, "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(["wot", "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],
            idty1.pubkey,
            "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"),
        [
            (
                Path("./test_doc.txt"),
                REV_DOC,
                REV_2,
                idty1.pubkey,
            ),
            (
                Path("revocation"),
                REV_DOC,
                REV_2,
                idty1.pubkey,
            ),
        ],
    )
    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
            revocation.save_doc(path, rev_1.signed_raw(), pubkey)
            assert path.is_file()
            assert path.read_text(encoding="utf-8") == rev_1.signed_raw()
    
            # test file has 600 permission
            assert oct(path.stat().st_mode)[-3:] == "600"
    
            # 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
            assert path.read_text(encoding="utf-8") == 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
            assert path.read_text(encoding="utf-8") == 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 = Path("test_file.txt")
        monkeypatch.setattr(bma.wot, "lookup", patched_lookup)
    
        # test
        runner = CliRunner()
        with runner.isolated_filesystem():
            path.write_text(doc.signed_raw(), encoding="utf-8")
            result = revocation.verify_document(path)
            display = capsys.readouterr().out
            if len(lookup["results"]) > 1:
                assert "One matching identity!\n" in display
                assert "Similar identities:" in display
            assert result == doc
    
    
    # test verify_document: no matching identity
    @pytest.mark.parametrize(
        ("doc", "lookup"),
        [
            (REV_2, lookup_one),
            (REV_2, lookup_two),
            (REV_2, False),
        ],
    )
    def test_verify_document_missing_id(doc, lookup, capsys, monkeypatch):
        def patched_lookup(node, id_pubkey):
            if not lookup:
                http_error = urllib.error.HTTPError(
                    url="this.is/a/test/url",
                    code=2001,
                    msg="No matching identity",
                    hdrs={},
                    fp=None,
                )
                raise http_error
            return lookup
    
        # prepare test
        path = Path("test_file.txt")
        monkeypatch.setattr(bma.wot, "lookup", patched_lookup)
    
        # test
        runner = CliRunner()
        with runner.isolated_filesystem():
            path.write_text(doc.signed_raw(), encoding="utf-8")
            with pytest.raises(SystemExit) as pytest_exit:
                revocation.verify_document(path)
            assert pytest_exit.type == SystemExit
            display = capsys.readouterr().out
            if not lookup:
                assert "Revocation document does not match any valid identity." in str(
                    pytest_exit.value.code,
                )
            else:
                assert pytest_exit.value.code == FAILURE_EXIT_STATUS
                assert (
                    "Revocation document does not match any valid identity.\nSimilar identities:"
                    in display
                )
    
    
    # test verify_document : format and sign errors
    @pytest.mark.parametrize(
        ("doc", "currency"),
        [
            (REV_DOC_FALSE, REV_DOC_FALSE.currency),
            (REV_2_FALSE, REV_2_FALSE.currency),
            (WRONG_FORMAT_REV, "g1-test"),
        ],
    )
    def test_verify_document_sign_errors(doc, currency, monkeypatch):
        # prepare test
        file = Path("test_file.txt")
        patched_lookup = Mock()
        monkeypatch.setattr(bma.wot, "lookup", patched_lookup)
        # test
        runner = CliRunner()
        with runner.isolated_filesystem():
            if isinstance(doc, str):
                file.write_text(doc, encoding="utf-8")
            elif isinstance(doc, Revocation):
                file.write_text(doc.signed_raw(), encoding="utf-8")
            with pytest.raises(SystemExit) as pytest_exit:
                revocation.verify_document(file)
            assert pytest_exit.type == SystemExit
            if isinstance(doc, str):
                assert (
                    "is not a revocation document, or is not correctly formatted."
                    in pytest_exit.value.code
                )
            elif isinstance(doc, Revocation):
                assert (
                    pytest_exit.value.code
                    == "Error: the signature of the revocation document is invalid."
                )
            patched_lookup.assert_not_called()