diff --git a/tests/patched/idty_tools.py b/tests/patched/idty_tools.py
index fd795275840f3fdfb1257988e93c208f4fbeccdf..c5522845f1e32c4dc7cc87ec4ce0ce88daea0d35 100644
--- a/tests/patched/idty_tools.py
+++ b/tests/patched/idty_tools.py
@@ -83,6 +83,45 @@ lookup_two = {
 }
 
 
+lookup_three = {
+    "partial": False,
+    "results": [
+        {
+            "pubkey": "6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
+            "uids": [
+                {
+                    "uid": "Claude",
+                    "meta": {
+                        "timestamp": "597334-002A45E751DCA7535D4F0A082F493E2C8EFF07612683525EB5DA92B6D17C30BD"
+                    },
+                    "revoked": False,
+                    "revoked_on": None,
+                    "revocation_sig": None,
+                    "self": "kFW2we2K3zx4PZODx0Wf+xdXAJTmYD+yqdyZBsPF7UwqdaCA4N+yHj7+09Gjsttl0i9GtWzodyJ6mBE1q7jcAw==",
+                    "others": [],
+                },
+            ],
+        },
+        {
+            "pubkey": "XXXXXXJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
+            "uids": [
+                {
+                    "uid": "Claude",
+                    "meta": {
+                        "timestamp": "597334-002A45E751DCA7535D4F0A082F493E2C8EFF07612683525EB5DA92B6D17C30BD"
+                    },
+                    "revoked": False,
+                    "revoked_on": None,
+                    "revocation_sig": None,
+                    "self": "kFW2we2K3zx4PZODx0Wf+xdXAJTmYD+yqdyZBsPF7UwqdaCA4N+yHj7+09Gjsttl0i9GtWzodyJ6mBE1q7jcAw==",
+                    "others": [],
+                },
+            ],
+        },
+    ],
+}
+
+
 idty2 = Identity(
     currency="g1-test",
     pubkey="969qRJs8KhsnkyzqarpL4RKZGMdVKNbZgu8fhsigM7Lj",
diff --git a/tests/test_idty_tools.py b/tests/test_idty_tools.py
new file mode 100644
index 0000000000000000000000000000000000000000..3769eee94c63da3aff19a545b22b0247c4b6123f
--- /dev/null
+++ b/tests/test_idty_tools.py
@@ -0,0 +1,400 @@
+# Copyright  2016-2021 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 re
+import urllib
+
+import pendulum
+import pytest
+from duniterpy.api import bma
+from duniterpy.api.client import DuniterError
+from duniterpy.documents.block_id import BlockID
+from duniterpy.documents.identity import Identity
+
+from patched.idty_tools import (
+    idty1,
+    idty2,
+    idty_block,
+    lookup_one,
+    lookup_three,
+    lookup_two,
+)
+from silkaj import idty_tools
+from silkaj.constants import ALL, PUBKEY_PATTERN
+
+## used test identities
+
+id_moul_test_1 = Identity(
+    currency="g1-test",
+    pubkey="5B8iMAzq1dNmFe3ZxFTBQkqhq4fsztg1gZvxHXCk1XYH",
+    uid="moul-test",
+    block_id=BlockID.from_str(
+        "167750-0000A51FF952B76AAA594A46CA0C8156A56988D2B2B57BE18ECB4F3CFC25CEC2"
+    ),
+)
+id_moul_test_1.signature = "/15YBc4JDPvKD4c8nWD6C0XN0krrS32uDRSH6rJvMFih/H5nPc8oiCgL27bA7P3NPnp+oCqbS12QygQRnhoDDQ=="
+
+id_elois_test = Identity(
+    currency="g1-test",
+    pubkey="D7CYHJXjaH4j7zRdWngUbsURPnSnjsCYtvo6f8dvW3C",
+    uid="elois",
+    block_id=BlockID.from_str(
+        "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
+    ),
+)
+id_elois_test.signature = "HFmZy01XSXSSsqRWYKR+nIsaReBruJUqYHqJh3EtdRkHV3i30S2lM32pM2w4GHYXNIiOf4+NXzcmuEQVkewBAA=="
+
+id_moul_test_3 = Identity(
+    version=10,
+    currency="g1-test",
+    pubkey="2M3Et298TDYXDcsixp82iEc4ijzuiyQtsiQac2QjCRgA",
+    uid="moul-test",
+    block_id=BlockID.from_str(
+        "287573-00003674E9A2B5127327542CFA36BCB95D05E8EBD8AAF9C684B19EB7502161D4"
+    ),
+)
+id_moul_test_3.signature = "BB1Ete898yN/ZwQn4H7o0gsS1JD05zZBSd7qdU2AmSSZLtjG199fN0Z5jKjQi7S2IVvrH0G5cft74sufVS3+Cw=="
+
+# idty lists for following tests
+ids_list_for_merge_lists = [
+    id_moul_test_1,
+    id_elois_test,
+    id_moul_test_3,
+]
+
+ids_list_for_lookups_test = [
+    id_moul_test_1,
+    id_elois_test,
+]
+
+# invalid identity
+id_claudius_maximus = Identity(
+    currency="g1-test",
+    pubkey="6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o",
+    uid="Claudius Maximus",
+    block_id=BlockID.from_str(
+        "7334-002A45E751DCA7535D4F0A082F493E2C8EFF07612683525EB5DA92B6D17C30BD"
+    ),
+)
+id_claudius_maximus.signature = (
+    "wrong_sig_x4PZODx0Wf+xdXAJTmYD+yqdyZBsPF7UwqdaCA4N+yHj7+09Gjsttl0i9GtWzodyJ6mBE1q7jcAw==",
+)
+
+
+def test_display_alternate_ids():
+    """
+    Tests the display of identities table.
+    When using pytest `-s` option, this test only works on terminal default width of 80.
+    """
+    ids_list = [
+        id_moul_test_1,
+        id_elois_test,
+        id_claudius_maximus,
+    ]
+
+    expected = "+------------------+--------------------------------------------+--------------+\n\
+|       uid        |                 public key                 |  timestamp   |\n\
++==================+============================================+==============+\n\
+| moul-test        | 5B8iMAzq1dNmFe3ZxFTBQkqhq4fsztg1gZvxHXCk1X | 167750-0000A |\n\
+|                  | YH:baK                                     |              |\n\
++------------------+--------------------------------------------+--------------+\n\
+| elois            | D7CYHJXjaH4j7zRdWngUbsURPnSnjsCYtvo6f8dvW3 | 0-E3B0C44298 |\n\
+|                  | C:6Ns                                      |              |\n\
++------------------+--------------------------------------------+--------------+\n\
+| Claudius Maximus | 6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj | 7334-002A45E |\n\
+|                  | 7o:7xo                                     |              |\n\
++------------------+--------------------------------------------+--------------+"
+
+    table = idty_tools.display_alternate_ids(ids_list)
+    assert expected == table.draw()
+
+
+@pytest.mark.parametrize(
+    "lookups_pubkey, lookups_uid, currency, expected",
+    [
+        (
+            [
+                {
+                    "pubkey": "5B8iMAzq1dNmFe3ZxFTBQkqhq4fsztg1gZvxHXCk1XYH",
+                    "uids": [
+                        {
+                            "uid": "moul-test",
+                            "meta": {
+                                "timestamp": "167750-0000A51FF952B76AAA594A46CA0C8156A56988D2B2B57BE18ECB4F3CFC25CEC2"
+                            },
+                            "revoked": False,
+                            "revoked_on": None,
+                            "revocation_sig": None,
+                            "self": "/15YBc4JDPvKD4c8nWD6C0XN0krrS32uDRSH6rJvMFih/H5nPc8oiCgL27bA7P3NPnp+oCqbS12QygQRnhoDDQ==",
+                            "others": [],
+                        }
+                    ],
+                    "signed": [],
+                },
+                {
+                    "pubkey": "D7CYHJXjaH4j7zRdWngUbsURPnSnjsCYtvo6f8dvW3C",
+                    "uids": [
+                        {
+                            "uid": "elois",
+                            "meta": {
+                                "timestamp": "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
+                            },
+                            "revoked": True,
+                            "revoked_on": 1540589179,
+                            "revocation_sig": None,
+                            "self": "HFmZy01XSXSSsqRWYKR+nIsaReBruJUqYHqJh3EtdRkHV3i30S2lM32pM2w4GHYXNIiOf4+NXzcmuEQVkewBAA==",
+                            "others": [],
+                        }
+                    ],
+                    "signed": [],
+                },
+            ],
+            [
+                {
+                    "pubkey": "5B8iMAzq1dNmFe3ZxFTBQkqhq4fsztg1gZvxHXCk1XYH",
+                    "uids": [
+                        {
+                            "uid": "moul-test",
+                            "meta": {
+                                "timestamp": "167750-0000A51FF952B76AAA594A46CA0C8156A56988D2B2B57BE18ECB4F3CFC25CEC2"
+                            },
+                            "revoked": False,
+                            "revoked_on": None,
+                            "revocation_sig": None,
+                            "self": "/15YBc4JDPvKD4c8nWD6C0XN0krrS32uDRSH6rJvMFih/H5nPc8oiCgL27bA7P3NPnp+oCqbS12QygQRnhoDDQ==",
+                            "others": [],
+                        }
+                    ],
+                    "signed": [],
+                },
+                {
+                    "pubkey": "2M3Et298TDYXDcsixp82iEc4ijzuiyQtsiQac2QjCRgA",
+                    "uids": [
+                        {
+                            "uid": "moul-test",
+                            "meta": {
+                                "timestamp": "287573-00003674E9A2B5127327542CFA36BCB95D05E8EBD8AAF9C684B19EB7502161D4"
+                            },
+                            "revoked": True,
+                            "revoked_on": 1544923049,
+                            "revocation_sig": "oRBunChfKUUMqZLCB+0QO8LpQcpx9FZJNXIt79Q/zRPpi9X1hNUPKV4myMxHBSVI6YbPB+gBw/Bb+n3kaIuRAg==",
+                            "self": "BB1Ete898yN/ZwQn4H7o0gsS1JD05zZBSd7qdU2AmSSZLtjG199fN0Z5jKjQi7S2IVvrH0G5cft74sufVS3+Cw==",
+                            "others": [],
+                        }
+                    ],
+                    "signed": [],
+                },
+            ],
+            "g1-test",
+            ids_list_for_merge_lists,
+        ),
+    ],
+)
+def test_merge_ids_lists(lookups_pubkey, lookups_uid, currency, expected):
+    for element, expect in zip(
+        idty_tools.merge_ids_lists(lookups_pubkey, lookups_uid, currency), expected
+    ):
+        assert element.signed_raw() == expect.signed_raw()
+
+
+@pytest.mark.parametrize(
+    "lookups, currency, expected",
+    [
+        (
+            [
+                {
+                    "pubkey": "5B8iMAzq1dNmFe3ZxFTBQkqhq4fsztg1gZvxHXCk1XYH",
+                    "uids": [
+                        {
+                            "uid": "moul-test",
+                            "meta": {
+                                "timestamp": "167750-0000A51FF952B76AAA594A46CA0C8156A56988D2B2B57BE18ECB4F3CFC25CEC2"
+                            },
+                            "revoked": False,
+                            "revoked_on": None,
+                            "revocation_sig": None,
+                            "self": "/15YBc4JDPvKD4c8nWD6C0XN0krrS32uDRSH6rJvMFih/H5nPc8oiCgL27bA7P3NPnp+oCqbS12QygQRnhoDDQ==",
+                            "others": [],
+                        }
+                    ],
+                    "signed": [],
+                },
+                {
+                    "pubkey": "D7CYHJXjaH4j7zRdWngUbsURPnSnjsCYtvo6f8dvW3C",
+                    "uids": [
+                        {
+                            "uid": "elois",
+                            "meta": {
+                                "timestamp": "0-E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855"
+                            },
+                            "revoked": True,
+                            "revoked_on": 1540589179,
+                            "revocation_sig": None,
+                            "self": "HFmZy01XSXSSsqRWYKR+nIsaReBruJUqYHqJh3EtdRkHV3i30S2lM32pM2w4GHYXNIiOf4+NXzcmuEQVkewBAA==",
+                            "others": [],
+                        }
+                    ],
+                    "signed": [],
+                },
+            ],
+            "g1-test",
+            ids_list_for_lookups_test,
+        ),
+    ],
+)
+def test_ids_list_from_lookups(lookups, currency, expected):
+    for element, expect in zip(
+        idty_tools.ids_list_from_lookups(lookups, currency), expected
+    ):
+        assert element.signed_raw() == expect.signed_raw()
+
+
+# test display_identity
+@pytest.mark.parametrize("idty", [(idty1)])
+def test_display_identity(idty, monkeypatch, capsys):
+    def patch_get_id_block(node, number):
+        return idty_block
+
+    monkeypatch.setattr(bma.blockchain, "block", patch_get_id_block)
+    idty_table = idty_tools.display_identity(idty)
+    result = idty_table.draw()
+    assert "| Public key | 6upqFiJ66WV6N3bPc8x8y7rXT3syqKRmwnVyunCtEj7o:7xo" in result
+    assert "| User ID    | Claude" in result
+    assert "| Blockstamp | 597334" in result
+    # idty_block returns a block at timestamp 1594980185,.
+    creation_time = pendulum.from_timestamp(idty_block["time"], tz="local").format(ALL)
+    assert creation_time in result
+
+
+@pytest.mark.parametrize(
+    "idty, doc_type, lookup_pk, lookup_uid, expected, expect_bool",
+    [
+        # normal case : 1 same lookup on pubkey and uid
+        (
+            idty1,
+            "Revocation",
+            lookup_one,
+            lookup_one,
+            None,
+            True,
+        ),
+        # 2 lookups on pubkey, one on uid, match similar.
+        (
+            idty1,
+            "Revocation",
+            lookup_one,
+            lookup_two,
+            [
+                "One matching identity!",
+                "Similar identities:",
+                "Claudia",
+            ],
+            True,
+        ),
+        # 2 lookups on pubkey, one on uid, no match similar.
+        (
+            idty2,
+            "Revocation",
+            lookup_one,
+            lookup_two,
+            [
+                "Revocation document does not match any valid identity.",
+                "Claude",
+                "Claudia",
+            ],
+            False,
+        ),
+        # 1 lookup on pubkey, 2 on uid, match.
+        (
+            idty1,
+            "Revocation",
+            lookup_one,
+            lookup_three,
+            [
+                "One matching identity!",
+                "Similar identities:",
+                "Claude",
+                "XXXX",
+            ],
+            True,
+        ),
+        # 1 lookup on pubkey, 2 on uid, no match.
+        (
+            idty2,
+            "Revocation",
+            lookup_one,
+            lookup_three,
+            [
+                "Revocation document does not match any valid identity.",
+                "Similar identities:",
+                "Claude",
+                "XXXX",
+            ],
+            False,
+        ),
+        # no lookup found.
+        (
+            idty1,
+            "Revocation",
+            None,
+            None,
+            [
+                "Revocation document does not match any valid identity. Error: HTTP Error 404: Not Found TEST"
+            ],
+            False,
+        ),
+    ],
+)
+def test_check_many_identities(
+    idty, doc_type, lookup_pk, lookup_uid, expected, expect_bool, monkeypatch, capsys
+):
+
+    # Patch BMA lookup so it returns different lookups if we request a pk or a uid
+    def patched_bma_lookup(whatever_node, lookup):
+        # raise errors if no matching identity
+        http_error = urllib.error.HTTPError(
+            url="this.is/a/test/url",
+            code=404,
+            msg="Not Found TEST",
+            hdrs={},
+            fp=None,
+        )
+        if re.search(PUBKEY_PATTERN, lookup) != None:
+            if not lookup_pk:
+                raise http_error
+            return lookup_pk
+        else:
+            if not lookup_uid:
+                raise http_error
+            return lookup_uid
+
+    monkeypatch.setattr(bma.wot, "lookup", patched_bma_lookup)
+
+    # identity does not exist
+    if expected == [
+        "Revocation document does not match any valid identity. Error: HTTP Error 404: Not Found TEST"
+    ]:
+        with pytest.raises(SystemExit) as pytest_exit:
+            result = idty_tools.check_many_identities(idty, doc_type)
+        assert pytest_exit.type == SystemExit
+        assert expected[0] == str(pytest_exit.value.code)
+    # test cases with an identity
+    else:
+        result = idty_tools.check_many_identities(idty, doc_type)
+        assert result == expect_bool
+        display_result = capsys.readouterr().out
+        if expected != None:
+            for expect in expected:
+                assert expect in display_result