From b9509edfe2947dd785648f647a57dbad02a0fdca Mon Sep 17 00:00:00 2001
From: Holger Nahrstaedt <holger@nahrstaedt.de>
Date: Tue, 8 May 2018 17:56:07 +0200
Subject: [PATCH] Prepare next version wth several improvemetns

Account
* Parameter accuracy renamed to stop_diff
* Doku for estimate_virtual_op_num improved
* Several improvements and fixes for estimate_virtual_op_num
CLI
* rewards command improved and more paramter added
Comments
* Assure Amount class for all amounts
* is_pending improved
Doku
* tutorial about showing all posts of an account added
Unit tests
* test_account, test_cli and test_comment improved
---
 README.rst                  |   9 ++
 beem/account.py             |  61 ++++++---
 beem/cli.py                 | 252 ++++++++++++++++++++++++------------
 beem/comment.py             |  25 ++--
 beem/version.py             |   2 +-
 beemapi/version.py          |   2 +-
 beembase/version.py         |   2 +-
 beemgrapheneapi/version.py  |   2 +-
 beemgraphenebase/version.py |   2 +-
 docs/tutorials.rst          |  20 +++
 setup.py                    |   2 +-
 tests/beem/test_account.py  |  45 +++++++
 tests/beem/test_cli.py      |  27 +++-
 tests/beem/test_comment.py  |   2 +
 14 files changed, 332 insertions(+), 121 deletions(-)

diff --git a/README.rst b/README.rst
index 81442125..b90d89ab 100644
--- a/README.rst
+++ b/README.rst
@@ -131,6 +131,15 @@ A command line tool is available. The help output shows the available commands:
 
 Changelog
 =========
+0.19.28
+-------
+* Improve rewards command in beempy
+* estimate_virtual_op_num improved and small bug fixed
+* SBD value in Comment always converted to Amount
+* accuracy renamed to stop_diff
+* Doku of estimate_virtual_op_num improved
+* Unit test for estimate_virtual_op_num added
+
 0.19.27
 -------
 * Block have only_ops and only_virtual_ops as parameter
diff --git a/beem/account.py b/beem/account.py
index f17dba22..91926013 100644
--- a/beem/account.py
+++ b/beem/account.py
@@ -811,7 +811,7 @@ class Account(BlockchainObject):
             :rtype: list
         """
         if until is not None:
-            return self.estimate_virtual_op_num(until, accuracy=1)
+            return self.estimate_virtual_op_num(until, stop_diff=1)
         else:
             try:
                 op_count = 0
@@ -837,14 +837,17 @@ class Account(BlockchainObject):
             ret = self.steem.rpc.get_account_history(account["name"], start, limit, api="database")
         return ret
 
-    def estimate_virtual_op_num(self, blocktime, accuracy=10, max_count=-1, reverse=False):
+    def estimate_virtual_op_num(self, blocktime, stop_diff=1, max_count=100, reverse=False):
         """ Returns an estimation of an virtual operation index for a given time or blockindex
 
             :param int/datetime blocktime: start time or start block index from which account
                 operation should be fetched
-            :param int accuracy: defines the estimation accuracy (default 10)
-            :param int max_count: sets the maximum number of iterations. -1 disables this (default -1)
+            :param int stop_diff: Sets the difference between last estimation and
+                new estimation at which the estimation stops. Must not be zero. (default is 1)
+            :param int max_count: sets the maximum number of iterations. -1 disables this (default 100)
             :param bool revers: Set to true when used in history_reverse (default is False)
+                When set to False, the optimum is found from the left side (earlier blocks).
+                When reverse set to True, the optimum is found from the right side (newer blocks).
 
             Example:::
 
@@ -863,35 +866,49 @@ class Account(BlockchainObject):
         """
         max_index = self.virtual_op_count()
         created = self["created"]
+        if stop_diff <= 0:
+            raise ValueError("stop_diff <= 0 is not allowed and would lead to an endless lopp...")
         if not isinstance(blocktime, datetime):
             b = Blockchain(steem_instance=self.steem)
             current_block_num = b.get_current_block_num()
             created_blocknum = b.get_estimated_block_num(created, accurate=True)
-            if blocktime < created_blocknum and not reverse:
+            if blocktime < created_blocknum:
                 return 0
-            elif blocktime < created_blocknum:
-                return max_index
         else:
-            if blocktime < created and not reverse:
+            if blocktime < created:
                 return 0
-            elif blocktime < created:
-                return max_index
-        if max_index < accuracy and not reverse:
+        if max_index < stop_diff and not reverse:
+            return 0
+        elif max_index < stop_diff:
+            return max_index
+
+        op_last = self._get_account_history(start=-1)
+        if isinstance(op_last, list) and len(op_last) > 0 and len(op_last[0]) > 0:
+            last_trx = op_last[0][1]
+        elif not reverse:
             return 0
-        elif max_index < accuracy:
+        else:
             return max_index
+
         if isinstance(blocktime, datetime):
             utc = pytz.timezone('UTC')
             now = utc.localize(datetime.utcnow())
             account_lifespan_sec = (now - created).total_seconds()
             blocktime = addTzInfo(blocktime)
+            if (formatTimeString(last_trx["timestamp"]) - blocktime).total_seconds() < 0:
+                return max_index
             estimated_op_num = int((blocktime - created).total_seconds() / account_lifespan_sec * max_index)
         else:
             account_lifespan_block = (current_block_num - created_blocknum)
+            if (last_trx["block"] - blocktime) < 0:
+                return max_index
             estimated_op_num = int((blocktime - created_blocknum) / account_lifespan_block * max_index)
-        op_diff = accuracy + 1
+        op_diff = stop_diff + 1
+        op_diff_1 = op_diff + 1
+        op_diff_2 = op_diff + 2
         cnt = 0
-        while op_diff > accuracy and (max_count < 0 or cnt < max_count):
+
+        while (op_diff > stop_diff or op_diff < -stop_diff) and (max_count < 0 or cnt < max_count) and op_diff_2 != op_diff:
             op_start = self._get_account_history(start=estimated_op_num)
             if isinstance(op_start, list) and len(op_start) > 0 and len(op_start[0]) > 0:
                 trx = op_start[0][1]
@@ -900,6 +917,8 @@ class Account(BlockchainObject):
                 return 0
             else:
                 return max_index
+            op_diff_2 = op_diff_1
+            op_diff_1 = op_diff
             if isinstance(blocktime, datetime):
                 diff_time = (now - formatTimeString(trx["timestamp"])).total_seconds()
                 op_diff = ((blocktime - formatTimeString(trx["timestamp"])).total_seconds() / diff_time * (max_index - estimated_op_num))
@@ -915,14 +934,14 @@ class Account(BlockchainObject):
             return 0
         elif estimated_op_num > max_index:
             return max_index
-        elif math.ceil(op_diff) == 0 and reverse:
+        elif int(op_diff) == 0 and reverse:
             return estimated_op_num
         elif int(op_diff) == 0 and not reverse:
             return estimated_op_num
-        elif estimated_op_num > accuracy and not reverse:
-            return estimated_op_num - accuracy
-        elif estimated_op_num + accuracy < max_index:
-            return estimated_op_num + accuracy
+        elif estimated_op_num > stop_diff and not reverse:
+            return estimated_op_num - math.ceil(stop_diff)
+        elif estimated_op_num + stop_diff < max_index:
+            return estimated_op_num + math.ceil(stop_diff)
         elif reverse:
             return max_index
         else:
@@ -1143,7 +1162,7 @@ class Account(BlockchainObject):
         if start is not None and not use_block_num and not isinstance(start, datetime):
             start_index = start
         elif start is not None:
-            start_index = self.estimate_virtual_op_num(start, accuracy=10)
+            start_index = self.estimate_virtual_op_num(start, stop_diff=1)
         else:
             start_index = 0
 
@@ -1285,7 +1304,7 @@ class Account(BlockchainObject):
         elif start is not None and isinstance(start, int) and not use_block_num:
             first = start
         elif start is not None:
-            first = self.estimate_virtual_op_num(start, accuracy=10, reverse=True)
+            first = self.estimate_virtual_op_num(start, stop_diff=1, reverse=True)
         if stop is not None and isinstance(stop, int) and stop < 0 and not use_block_num:
             stop += first
         start = addTzInfo(start)
diff --git a/beem/cli.py b/beem/cli.py
index 42d1ec41..748b452f 100644
--- a/beem/cli.py
+++ b/beem/cli.py
@@ -1772,102 +1772,188 @@ def curation(authorperm, payout):
 
 
 @cli.command()
-@click.argument('account', nargs=1, required=False)
+@click.argument('accounts', nargs=-1, required=False)
+@click.option('--only-sum', '-s', help='Show only the sum', is_flag=True, default=False)
 @click.option('--post', '-p', help='Show pending post payout', is_flag=True, default=False)
 @click.option('--comment', '-c', help='Show pending comments payout', is_flag=True, default=False)
 @click.option('--curation', '-v', help='Shows  pending curation', is_flag=True, default=False)
-def rewards(account, post, comment, curation):
+@click.option('--length', '-l', help='Limits the permlink character length', default=None)
+@click.option('--author', '-a', help='Show the author for each entry', is_flag=True, default=False)
+@click.option('--permlink', '-e', help='Show the permlink for each entry', is_flag=True, default=False)
+@click.option('--title', '-t', help='Show the title for each entry', is_flag=True, default=False)
+def rewards(accounts, only_sum, post, comment, curation, length, author, permlink, title):
     """ Lists outstanding rewards
     """
     stm = shared_steem_instance()
     if stm.rpc is not None:
         stm.rpc.rpcconnect()
-    if not account:
-        account = stm.config["default_account"]
-    if not comment and not curation:
+    if not accounts:
+        accounts = [stm.config["default_account"]]
+    if not comment and not curation and not post:
         post = True
+        permlink = True
 
     utc = pytz.timezone('UTC')
     limit_time = utc.localize(datetime.utcnow()) - timedelta(days=7)
-    sum_reward = [0, 0, 0, 0]
-    account = Account(account, steem_instance=stm)
-    median_price = Price(stm.get_current_median_history(), steem_instance=stm)
-    m = Market(steem_instance=stm)
-    latest = m.ticker()["latest"]
-    t = PrettyTable(["Description", "Cashout", "SBD", "SP", "Liquid USD", "Invested USD"])
-    t.align = "l"
-    rows = []
-    c_list = {}
-    start_op = account.estimate_virtual_op_num(limit_time)
-    length = (account.virtual_op_count() - start_op) / 1000
-    with click.progressbar(map(Comment, account.history(start=start_op, use_block_num=False, only_ops=["comment"])), length=length) as comment_hist:
-        for v in comment_hist:
-            v.refresh()
-            author_reward = v.get_author_rewards()
-            if float(author_reward["total_payout_SBD"]) < 0.001:
-                continue
-            if v.permlink in c_list:
-                continue
-            if not v.is_pending():
-                continue
-            if not post and not v.is_comment():
-                continue
-            if not comment and v.is_comment():
-                continue
-            c_list[v.permlink] = 1
-            payout_SBD = author_reward["payout_SBD"]
-            sum_reward[0] += float(payout_SBD)
-            payout_SP = author_reward["payout_SP"]
-            sum_reward[1] += float(payout_SP)
-            liquid_USD = float(author_reward["payout_SBD"]) / float(latest) * float(median_price)
-            sum_reward[2] += liquid_USD
-            invested_USD = float(author_reward["payout_SP"]) * float(median_price)
-            sum_reward[3] += invested_USD
-            if v.is_comment():
-                title = v.parent_permlink
+    for account in accounts:
+        sum_reward = [0, 0, 0, 0]
+        account = Account(account, steem_instance=stm)
+        median_price = Price(stm.get_current_median_history(), steem_instance=stm)
+        m = Market(steem_instance=stm)
+        latest = m.ticker()["latest"]
+        if author and permlink:
+            t = PrettyTable(["Author", "Permlink", "Cashout", "SBD", "SP", "Liquid USD", "Invested USD"])
+        elif author and not permlink:
+            t = PrettyTable(["Author", "Cashout", "SBD", "SP", "Liquid USD", "Invested USD"])
+        elif not author and permlink:
+            t = PrettyTable(["Permlink", "Cashout", "SBD", "SP", "Liquid USD", "Invested USD"])
+        else:
+            t = PrettyTable(["Cashout", "SBD", "SP", "Liquid USD", "Invested USD"])
+        t.align = "l"
+        rows = []
+        c_list = {}
+        start_op = account.estimate_virtual_op_num(limit_time)
+        progress_length = (account.virtual_op_count() - start_op) / 1000
+        with click.progressbar(map(Comment, account.history(start=start_op, use_block_num=False, only_ops=["comment"])), length=progress_length) as comment_hist:
+            for v in comment_hist:
+                v.refresh()
+                author_reward = v.get_author_rewards()
+                if float(author_reward["total_payout_SBD"]) < 0.001:
+                    continue
+                if v.permlink in c_list:
+                    continue
+                if not v.is_pending():
+                    continue
+                if not post and not v.is_comment():
+                    continue
+                if not comment and v.is_comment():
+                    continue
+                c_list[v.permlink] = 1
+                payout_SBD = author_reward["payout_SBD"]
+                sum_reward[0] += float(payout_SBD)
+                payout_SP = author_reward["payout_SP"]
+                sum_reward[1] += float(payout_SP)
+                liquid_USD = float(author_reward["payout_SBD"]) / float(latest) * float(median_price)
+                sum_reward[2] += liquid_USD
+                invested_USD = float(author_reward["payout_SP"]) * float(median_price)
+                sum_reward[3] += invested_USD
+                if v.is_comment():
+                    permlink_row = v.parent_permlink
+                else:
+                    if title:
+                        permlink_row = v.title
+                    else:
+                        permlink_row = v.permlink
+                rows.append(['@' + v["author"],
+                             permlink_row,
+                             ((v["created"] - limit_time).total_seconds() / 60 / 60 / 24),
+                             (payout_SBD),
+                             (payout_SP),
+                             (liquid_USD),
+                             (invested_USD)])
+        if curation:
+            votes = AccountVotes(account, start=limit_time, steem_instance=stm)
+            for vote in votes:
+                c = Comment(vote["authorperm"])
+                rewards = c.get_curation_rewards()
+                if not rewards["pending_rewards"]:
+                    continue
+                payout_SP = rewards["active_votes"][account["name"]]
+                liquid_USD = 0
+                invested_USD = float(payout_SP) * float(median_price)
+                sum_reward[1] += float(payout_SP)
+                sum_reward[3] += invested_USD
+                if title:
+                    permlink_row = c.title
+                else:
+                    permlink_row = c.permlink
+                rows.append(['@' + c["author"],
+                             permlink_row,
+                             ((c["created"] - limit_time).total_seconds() / 60 / 60 / 24),
+                             0.000,
+                             payout_SP,
+                             (liquid_USD),
+                             (invested_USD)])
+        sortedList = sorted(rows, key=lambda row: (row[2]), reverse=True)
+        if only_sum:
+            sortedList = []
+        for row in sortedList:
+            if length:
+                permlink_row = row[1][:int(length)]
             else:
-                title = v.permlink
-            rows.append([title,
-                         ((v["created"] - limit_time).total_seconds() / 60 / 60 / 24),
-                         (payout_SBD),
-                         (payout_SP),
-                         (liquid_USD),
-                         (invested_USD)])
-    if curation:
-        votes = AccountVotes(account, start=limit_time, steem_instance=stm)
-        for vote in votes:
-            c = Comment(vote["authorperm"])
-            rewards = c.get_curation_rewards()
-            if not rewards["pending_rewards"]:
-                continue
-            payout_SP = rewards["active_votes"][account["name"]]
-            liquid_USD = 0
-            invested_USD = float(payout_SP) * float(median_price)
-            sum_reward[1] += float(payout_SP)
-            sum_reward[3] += invested_USD
-            rows.append([c.permlink,
-                         ((c["created"] - limit_time).total_seconds() / 60 / 60 / 24),
-                         0.000,
-                         payout_SP,
-                         (liquid_USD),
-                         (invested_USD)])
-    sortedList = sorted(rows, key=lambda row: (row[1]), reverse=True)
-    for row in sortedList:
-        t.add_row([row[0][:30],
-                   "%.1f days" % row[1],
-                   "%.3f" % float(row[2]),
-                   "%.3f" % float(row[3]),
-                   "%.2f $" % (row[4]),
-                   "%.2f $" % (row[5])])
-
-    t.add_row(["", "", "", "", "", ""])
-    t.add_row(["Sum",
-               "-",
-               "%.2f SBD" % (sum_reward[0]),
-               "%.2f SP" % (sum_reward[1]),
-               "%.2f $" % (sum_reward[2]),
-               "%.2f $" % (sum_reward[3])])
-    print(t)
+                permlink_row = row[1]
+            if author and permlink:
+                t.add_row([row[0],
+                           permlink_row,
+                           "%.1f days" % row[2],
+                           "%.3f" % float(row[3]),
+                           "%.3f" % float(row[4]),
+                           "%.2f $" % (row[5]),
+                           "%.2f $" % (row[6])])
+            elif author and not permlink:
+                t.add_row([row[0],
+                           "%.1f days" % row[2],
+                           "%.3f" % float(row[3]),
+                           "%.3f" % float(row[4]),
+                           "%.2f $" % (row[5]),
+                           "%.2f $" % (row[6])])
+            elif not author and permlink:
+                t.add_row([permlink_row,
+                           "%.1f days" % row[2],
+                           "%.3f" % float(row[3]),
+                           "%.3f" % float(row[4]),
+                           "%.2f $" % (row[5]),
+                           "%.2f $" % (row[6])])
+            else:
+                t.add_row(["%.1f days" % row[2],
+                           "%.3f" % float(row[3]),
+                           "%.3f" % float(row[4]),
+                           "%.2f $" % (row[5]),
+                           "%.2f $" % (row[6])])
+
+        if author and permlink:
+            if not only_sum:
+                t.add_row(["", "", "", "", "", "", ""])
+            t.add_row(["Sum",
+                       "-",
+                       "-",
+                       "%.2f SBD" % (sum_reward[0]),
+                       "%.2f SP" % (sum_reward[1]),
+                       "%.2f $" % (sum_reward[2]),
+                       "%.2f $" % (sum_reward[3])])
+        elif not author and not permlink:
+            t.add_row(["", "", "", "", ""])
+            t.add_row(["Sum",
+                       "%.2f SBD" % (sum_reward[0]),
+                       "%.2f SP" % (sum_reward[1]),
+                       "%.2f $" % (sum_reward[2]),
+                       "%.2f $" % (sum_reward[3])])
+        else:
+            t.add_row(["", "", "", "", "", ""])
+            t.add_row(["Sum",
+                       "-",
+                       "%.2f SBD" % (sum_reward[0]),
+                       "%.2f SP" % (sum_reward[1]),
+                       "%.2f $" % (sum_reward[2]),
+                       "%.2f $" % (sum_reward[3])])
+        message = "\nShowing pending "
+        if post:
+            if comment + curation == 0:
+                message += "post "
+            elif comment + curation == 1:
+                message += "post and "
+            else:
+                message += "post, "
+        if comment:
+            if curation == 0:
+                message += "comment "
+            else:
+                message += "comment and "
+        if curation:
+            message += "curation "
+        message += "rewards for @%s" % account.name
+        print(message)
+        print(t)
 
 
 @cli.command()
diff --git a/beem/comment.py b/beem/comment.py
index 6c6352bb..182c1311 100644
--- a/beem/comment.py
+++ b/beem/comment.py
@@ -230,13 +230,18 @@ class Comment(BlockchainObject):
         """ Return the estimated total SBD reward.
         """
         a_zero = Amount("0 SBD", steem_instance=self.steem)
-        return self.get("total_payout_value", a_zero) + self.get("pending_payout_value", a_zero)
+        total = Amount(self.get("total_payout_value", a_zero), steem_instance=self.steem)
+        pending = Amount(self.get("pending_payout_value", a_zero), steem_instance=self.steem)
+        return total + pending
 
     def is_pending(self):
         """ Return if the payout is pending (the post/comment
             is younger than 7 days)
         """
-        return not float(self["total_payout_value"]) >= Amount("0.001 SBD", steem_instance=self.steem)
+        a_zero = Amount("0 SBD", steem_instance=self.steem)
+        total = Amount(self.get("total_payout_value", a_zero), steem_instance=self.steem)
+        post_age_days = self.time_elapsed().total_seconds() / 60 / 60 / 24
+        return post_age_days < 7.0 and float(total) == 0
 
     def time_elapsed(self):
         """Return a timedelta on how old the post is.
@@ -338,12 +343,12 @@ class Comment(BlockchainObject):
 
         """
         if self.is_pending():
-            total_payout = self["pending_payout_value"]
+            total_payout = Amount(self["pending_payout_value"], steem_instance=self.steem)
             author_payout = self.get_author_rewards()["author_payout"]
             curator_payout = total_payout - author_payout
         else:
-            total_payout = self["total_payout_value"]
-            curator_payout = self["curator_payout_value"]
+            total_payout = Amount(self["total_payout_value"], steem_instance=self.steem)
+            curator_payout = Amount(self["curator_payout_value"], steem_instance=self.steem)
             author_payout = total_payout - curator_payout
         return {"total_payout": total_payout, "author_payout": author_payout, "curator_payout": curator_payout}
 
@@ -361,8 +366,8 @@ class Comment(BlockchainObject):
 
         """
         if not self.is_pending():
-            total_payout = self["total_payout_value"]
-            curator_payout = self["curator_payout_value"]
+            total_payout = Amount(self["total_payout_value"], steem_instance=self.steem)
+            curator_payout = Amount(self["curator_payout_value"], steem_instance=self.steem)
             author_payout = total_payout - curator_payout
             return {'pending_rewards': False, "payout_SP": Amount("0 SBD", steem_instance=self.steem), "payout_SBD": Amount("0 SBD", steem_instance=self.steem), "total_payout_SBD": author_payout}
 
@@ -416,14 +421,14 @@ class Comment(BlockchainObject):
             max_rewards = Amount("0 STEEM", steem_instance=self.steem)
             unclaimed_rewards = max_rewards.copy()
         elif not self.is_pending():
-            max_rewards = self["curator_payout_value"]
-            unclaimed_rewards = self["total_payout_value"] * 0.25 - max_rewards
+            max_rewards = Amount(self["curator_payout_value"], steem_instance=self.steem)
+            unclaimed_rewards = Amount(self["total_payout_value"], steem_instance=self.steem) * 0.25 - max_rewards
             total_vote_weight = 0
             for vote in self["active_votes"]:
                 total_vote_weight += vote["weight"]
         else:
             if pending_payout_value is None:
-                pending_payout_value = self["pending_payout_value"]
+                pending_payout_value = Amount(self["pending_payout_value"], steem_instance=self.steem)
             elif isinstance(pending_payout_value, (float, int)):
                 pending_payout_value = Amount(pending_payout_value, "SBD", steem_instance=self.steem)
             elif isinstance(pending_payout_value, str):
diff --git a/beem/version.py b/beem/version.py
index f72aa5bb..e78b838c 100644
--- a/beem/version.py
+++ b/beem/version.py
@@ -1,2 +1,2 @@
 """THIS FILE IS GENERATED FROM beem SETUP.PY."""
-version = '0.19.27'
+version = '0.19.28'
diff --git a/beemapi/version.py b/beemapi/version.py
index f72aa5bb..e78b838c 100644
--- a/beemapi/version.py
+++ b/beemapi/version.py
@@ -1,2 +1,2 @@
 """THIS FILE IS GENERATED FROM beem SETUP.PY."""
-version = '0.19.27'
+version = '0.19.28'
diff --git a/beembase/version.py b/beembase/version.py
index f72aa5bb..e78b838c 100644
--- a/beembase/version.py
+++ b/beembase/version.py
@@ -1,2 +1,2 @@
 """THIS FILE IS GENERATED FROM beem SETUP.PY."""
-version = '0.19.27'
+version = '0.19.28'
diff --git a/beemgrapheneapi/version.py b/beemgrapheneapi/version.py
index f72aa5bb..e78b838c 100644
--- a/beemgrapheneapi/version.py
+++ b/beemgrapheneapi/version.py
@@ -1,2 +1,2 @@
 """THIS FILE IS GENERATED FROM beem SETUP.PY."""
-version = '0.19.27'
+version = '0.19.28'
diff --git a/beemgraphenebase/version.py b/beemgraphenebase/version.py
index f72aa5bb..e78b838c 100644
--- a/beemgraphenebase/version.py
+++ b/beemgraphenebase/version.py
@@ -1,2 +1,2 @@
 """THIS FILE IS GENERATED FROM beem SETUP.PY."""
-version = '0.19.27'
+version = '0.19.28'
diff --git a/docs/tutorials.rst b/docs/tutorials.rst
index 442bb0b6..675eb6c8 100644
--- a/docs/tutorials.rst
+++ b/docs/tutorials.rst
@@ -244,6 +244,26 @@ Lets calculate the curation reward from the last 7 days:
     curation_rewards_SP = acc.steem.vests_to_sp(reward_vests.amount)
     print("Rewards are %.3f SP" % curation_rewards_SP)
 
+Lets display all Posts from an account:
+
+.. code-block:: python
+
+    from beem.account import Account
+    from beem.comment import Comment
+    from beem.exceptions import ContentDoesNotExistsException
+    account = Account("holger80")
+    c_list = {}
+    for c in map(Comment, account.history(only_ops=["comment"])):
+        if c.permlink in c_list:
+          continue
+        try:
+             c.refresh()
+        except ContentDoesNotExistsException:
+             continue
+        c_list[c.permlink] = 1
+        if not c.is_comment():
+            print("%s " % c.title)
+
 Transactionbuilder
 ------------------
 Sign transactions with beem without using the wallet and build the transaction by hand.
diff --git a/setup.py b/setup.py
index d8ee4d61..ff3d17de 100755
--- a/setup.py
+++ b/setup.py
@@ -16,7 +16,7 @@ except LookupError:
     ascii = codecs.lookup('ascii')
     codecs.register(lambda name, enc=ascii: {True: enc}.get(name == 'mbcs'))
 
-VERSION = '0.19.27'
+VERSION = '0.19.28'
 
 tests_require = ['mock >= 2.0.0', 'pytest', 'pytest-mock', 'parameterized']
 
diff --git a/tests/beem/test_account.py b/tests/beem/test_account.py
index 586fbf39..80ec9c6e 100644
--- a/tests/beem/test_account.py
+++ b/tests/beem/test_account.py
@@ -10,6 +10,7 @@ from parameterized import parameterized
 from pprint import pprint
 from beem import Steem, exceptions
 from beem.account import Account
+from beem.block import Block
 from beem.amount import Amount
 from beem.asset import Asset
 from beem.utils import formatTimeString, get_node_list
@@ -450,3 +451,47 @@ class Testcases(unittest.TestCase):
         for k in keys:
             if k not in "json_metadata" and k != 'reputation' and k != 'active_votes':
                 self.assertEqual(content[k], json_content[k])
+
+    @parameterized.expand([
+        ("non_appbase"),
+        ("appbase"),
+    ])
+    def test_estimate_virtual_op_num(self, node_param):
+        if node_param == "non_appbase":
+            stm = self.bts
+        else:
+            stm = self.appbase
+        account = Account("gtg", steem_instance=stm)
+        block_num = 21248120
+        block = Block(block_num, steem_instance=stm)
+        op_num1 = account.estimate_virtual_op_num(block.time(), stop_diff=1, max_count=100)
+        op_num2 = account.estimate_virtual_op_num(block_num, stop_diff=1, max_count=100)
+        self.assertEqual(op_num1, op_num2)
+        block_diff = 0
+        block_diff1 = 0
+        block_diff2 = 0
+        for h in account.get_account_history(op_num1, 0):
+            block_diff = (block_num - h["block"])
+        for h in account.get_account_history(op_num1 - 1, 0):
+            block_diff1 = (block_num - h["block"])
+        for h in account.get_account_history(op_num1 + 1, 0):
+            block_diff2 = (block_num - h["block"])
+        self.assertTrue(block_diff > 0)
+        self.assertTrue(block_diff2 <= 0)
+        self.assertTrue(block_diff < block_diff1)
+
+        op_num1 = account.estimate_virtual_op_num(block.time(), stop_diff=1, max_count=100, reverse=True)
+        op_num2 = account.estimate_virtual_op_num(block_num, stop_diff=1, max_count=100, reverse=True)
+        self.assertEqual(op_num1, op_num2)
+        block_diff = 0
+        block_diff1 = 0
+        block_diff2 = 0
+        for h in account.get_account_history(op_num1, 0):
+            block_diff = (block_num - h["block"])
+        for h in account.get_account_history(op_num1 - 1, 0):
+            block_diff1 = (block_num - h["block"])
+        for h in account.get_account_history(op_num1 + 1, 0):
+            block_diff2 = (block_num - h["block"])
+        self.assertTrue(block_diff < 0)
+        self.assertTrue(block_diff1 >= 0)
+        self.assertTrue(block_diff > block_diff2)
diff --git a/tests/beem/test_cli.py b/tests/beem/test_cli.py
index c63ee46c..371064f0 100644
--- a/tests/beem/test_cli.py
+++ b/tests/beem/test_cli.py
@@ -342,12 +342,27 @@ class Testcases(unittest.TestCase):
         result = runner.invoke(cli, ['unfollow', 'beem1'], input="test\n")
         self.assertEqual(result.exit_code, 0)
 
+    def test_mute_unmute(self):
+        runner = CliRunner()
+        runner.invoke(cli, ['-o', 'set', 'nodes', 'wss://testnet.steem.vc'])
+        result = runner.invoke(cli, ['mute', 'beem1'], input="test\n")
+        self.assertEqual(result.exit_code, 0)
+        result = runner.invoke(cli, ['unfollow', 'beem1'], input="test\n")
+        self.assertEqual(result.exit_code, 0)
+
     def test_witnesscreate(self):
         runner = CliRunner()
         runner.invoke(cli, ['-o', 'set', 'nodes', 'wss://testnet.steem.vc'])
         result = runner.invoke(cli, ['-d', 'witnesscreate', 'beem', pub_key], input="test\n")
         self.assertEqual(result.exit_code, 0)
 
+    def test_witnessupdate(self):
+        runner = CliRunner()
+        runner.invoke(cli, ['-o', 'set', 'nodes', ''])
+        result = runner.invoke(cli, ['-o', 'nextnode'])
+        runner.invoke(cli, ['-o', 'witnessupdate', 'gtg', '--maximum_block_size', 65000, '--account_creation_fee', 0.1, '--sbd_interest_rate', 0, '--url', 'https://google.de', '--signing_key', wif])
+        self.assertEqual(result.exit_code, 0)
+
     def test_profile(self):
         runner = CliRunner()
         result = runner.invoke(cli, ['setprofile', 'url', 'https://google.de'], input="test\n")
@@ -407,9 +422,19 @@ class Testcases(unittest.TestCase):
     def test_rewards(self):
         runner = CliRunner()
         runner.invoke(cli, ['-o', 'set', 'nodes', ''])
+        result = runner.invoke(cli, ['rewards', 'test'])
+        self.assertEqual(result.exit_code, 0)
         result = runner.invoke(cli, ['rewards', '--post', '--comment', '--curation', 'test'])
-        runner.invoke(cli, ['-o', 'set', 'nodes', 'wss://testnet.steem.vc'])
         self.assertEqual(result.exit_code, 0)
+        result = runner.invoke(cli, ['rewards', '--post', '--comment', '--curation', '--permlink', 'test'])
+        self.assertEqual(result.exit_code, 0)
+        result = runner.invoke(cli, ['rewards', '--post', '--comment', '--curation', '--author', 'test'])
+        self.assertEqual(result.exit_code, 0)
+        result = runner.invoke(cli, ['rewards', '--post', '--comment', '--curation', '--author', '--title', 'test'])
+        self.assertEqual(result.exit_code, 0)
+        result = runner.invoke(cli, ['rewards', '--post', '--comment', '--curation', '--author', '--permlink', '--length', '30', 'test'])
+        self.assertEqual(result.exit_code, 0)
+        runner.invoke(cli, ['-o', 'set', 'nodes', 'wss://testnet.steem.vc'])
 
     def test_curation(self):
         runner = CliRunner()
diff --git a/tests/beem/test_comment.py b/tests/beem/test_comment.py
index b21f380b..b36b2b98 100644
--- a/tests/beem/test_comment.py
+++ b/tests/beem/test_comment.py
@@ -70,6 +70,8 @@ class Testcases(unittest.TestCase):
             self.assertIn(key, list(c.json_metadata.keys()))
         self.assertTrue(c.is_main_post())
         self.assertFalse(c.is_comment())
+        self.assertFalse(c.is_pending())
+        self.assertTrue((c.time_elapsed().total_seconds() / 60 / 60 / 24) > 7.0)
         self.assertTrue(isinstance(c.get_reblogged_by(), list))
         self.assertTrue(len(c.get_reblogged_by()) > 0)
         self.assertTrue(isinstance(c.get_votes(), list))
-- 
GitLab