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