import csv
import json
import os
import shutil
import tempfile
import unittest
from unittest import mock
import extract
class _OutDirTestCase(unittest.TestCase):
def setUp(self):
self._tmp = tempfile.mkdtemp()
self._patch_out = mock.patch.object(extract, "OUT", self._tmp)
self._patch_out.start()
def tearDown(self):
self._patch_out.stop()
shutil.rmtree(self._tmp, ignore_errors=True)
def read_json(self, name):
with open(os.path.join(self._tmp, name)) as f:
return json.load(f)
def read_csv(self, name):
with open(os.path.join(self._tmp, name)) as f:
return list(csv.reader(f))
class CmdBalancesTests(_OutDirTestCase):
def test_writes_balances_csv_and_denom_traces_json(self):
traces = [{"denom_trace": "path/base", "base_denom": "boot"}]
def fake_paged(path, key, limit=1000, extra=""):
if path == "/cosmos/bank/v1beta1/supply":
return iter([]) if path == "/cosmos/bank/v1beta1/denom_owners/boot":
return iter([{"address": "bostrom1a", "balance": {"amount": "100"}}])
if path == "/ibc/apps/transfer/v1/denom_traces":
return iter(traces)
return iter([])
with mock.patch("extract.paged", side_effect=fake_paged):
extract.cmd_balances()
rows = self.read_csv("balances.csv")
self.assertEqual(rows[0], ["address", "denom", "amount"])
self.assertIn(["bostrom1a", "boot", "100"], rows)
self.assertEqual(self.read_json("denom_traces.json"), traces)
def test_extra_denom_from_supply_is_also_queried_for_owners(self):
seen_paths = []
def fake_paged(path, key, limit=1000, extra=""):
seen_paths.append(path)
if path == "/cosmos/bank/v1beta1/supply":
return iter([{"denom": "ibc/AAAA"}])
if path == "/ibc/apps/transfer/v1/denom_traces":
return iter([])
return iter([])
with mock.patch("extract.paged", side_effect=fake_paged):
extract.cmd_balances()
self.assertIn("/cosmos/bank/v1beta1/denom_owners/ibc%2FAAAA", seen_paths)
def test_no_owners_still_writes_header_only_csv(self):
with mock.patch("extract.paged", return_value=iter([])):
extract.cmd_balances()
self.assertEqual(self.read_csv("balances.csv"), [["address", "denom", "amount"]])
self.assertEqual(self.read_json("denom_traces.json"), [])
class CmdStakingTests(_OutDirTestCase):
def test_writes_validators_json_and_delegations_csv(self):
validators = [{"operator_address": "bostromvaloper1x", "description": {"moniker": "val1"}}]
delegations = [
{
"delegation": {"delegator_address": "bostrom1d", "shares": "1000.0"},
"balance": {"amount": "1000"},
}
]
def fake_paged(path, key, limit=1000, extra=""):
if path == "/cosmos/staking/v1beta1/validators":
return iter(validators)
if path == "/cosmos/staking/v1beta1/validators/bostromvaloper1x/delegations":
return iter(delegations)
return iter([])
with mock.patch("extract.paged", side_effect=fake_paged):
extract.cmd_staking()
self.assertEqual(self.read_json("validators.json"), validators)
rows = self.read_csv("delegations.csv")
self.assertEqual(rows[0], ["delegator", "validator", "shares", "balance_boot"])
self.assertIn(["bostrom1d", "bostromvaloper1x", "1000.0", "1000"], rows)
def test_multiple_validators_each_queried_by_own_operator_address(self):
validators = [
{"operator_address": "bostromvaloper1a", "description": {"moniker": "val-a"}},
{"operator_address": "bostromvaloper1b", "description": {"moniker": "val-b"}},
]
queried = []
def fake_paged(path, key, limit=1000, extra=""):
if path == "/cosmos/staking/v1beta1/validators":
return iter(validators)
if path.startswith("/cosmos/staking/v1beta1/validators/"):
queried.append(path)
return iter([])
with mock.patch("extract.paged", side_effect=fake_paged):
extract.cmd_staking()
self.assertEqual(
queried,
[
"/cosmos/staking/v1beta1/validators/bostromvaloper1a/delegations",
"/cosmos/staking/v1beta1/validators/bostromvaloper1b/delegations",
],
)
def test_no_validators_yields_empty_files_not_a_crash(self):
with mock.patch("extract.paged", return_value=iter([])):
extract.cmd_staking()
self.assertEqual(self.read_json("validators.json"), [])
self.assertEqual(
self.read_csv("delegations.csv"),
[["delegator", "validator", "shares", "balance_boot"]],
)
if __name__ == "__main__":
unittest.main()