Repository: fedora-infra/bodhi

Issue:
The Days to Stable calculation in an update seems backwards
I've noticed that updates don't show "Days to Stable" until they are already past the date they can be pushed to stable, and that it seems to be counting how many days it's been *since* the update was eligible to be pushed to stable. It seems like it's doing the math backwards. [Here is the code](https://github.com/fedora-infra/bodhi/blob/2.4.0/bodhi/server/models/models.py#L1856-L1864) that does this:

```
    @property
    def days_to_stable(self):
        """ Return the number of days until an update can be pushed to stable """
        if self.meets_testing_requirements:
            return (
                self.release.mandatory_days_in_testing -
                (datetime.utcnow() - self.date_testing).days)
        else:
            return 0
```

First, it doesn't make sense to me to guard against whether the update meets testing requirements. We want to show how many days the update has left to get to stable, and if it hasn't met those days it might very well not meet testing requirements. This explains why updates never show this label until after they can be pushed to stable.

Furthermore, the math is actually not entirely wrong, but the template is stripping out the ```-``` symbol on updates after they are able to be pushed to stable. This code is called from the ```update.html``` template like this:

```
        % if update.days_to_stable and update.status.description == 'testing':
        <tr>
          <td>Days to Stable</td>
          <td>${str(update.days_to_stable).strip('-')}</td>
        </tr>
        % endif
```

I think we probably want to fix the logic so it doesn't test for ```meets_testing_requirements```, and so that it returns 0 if the calculation returns a negative result. With that last bit, we can drop the ```.strip('-')``` from the template as well.


Discussion:
It actually might be good to do something like this:

```
if not self.meets_testing_requirements():
    # Do a bunch of math stuff here, checking the negative result, etc.
    if result > 0:
        return result
return 0
```

Maybe that was the original intention of calling that method?
Since I practically wrote the fix, I'm going to assign this to myself.

Resolving patch:
diff --git a/bodhi/server/models.py b/bodhi/server/models.py
--- a/bodhi/server/models.py
+++ b/bodhi/server/models.py
@@ -1970,13 +1970,17 @@ def met_testing_requirements(self):
 
     @property
     def days_to_stable(self):
-        """ Return the number of days until an update can be pushed to stable """
-        if self.meets_testing_requirements:
+        """Return the number of days until an update can be pushed to stable.
+
+        This method will return the number of days until an update can be pushed to stable, or 0.
+        0 is returned if the update meets testing requirements already, if it doesn't have a
+        "truthy" date_testing attribute, or if it's been in testing for the release's
+        mandatory_days_in_testing or longer."""
+        if not self.meets_testing_requirements and self.date_testing:
             return (
                 self.release.mandatory_days_in_testing -
                 (datetime.utcnow() - self.date_testing).days)
-        else:
-            return 0
+        return 0
 
     @property
     def days_in_testing(self):


Test patch:
diff --git a/bodhi/tests/server/models/test_models.py b/bodhi/tests/server/models/test_models.py
--- a/bodhi/tests/server/models/test_models.py
+++ b/bodhi/tests/server/models/test_models.py
@@ -219,6 +219,46 @@ def test_builds(self):
         eq_(self.obj.builds[0].release.name, u'F11')
         eq_(self.obj.builds[0].package.name, u'TurboGears')
 
+    def test_days_to_stable_meets_testing_requirements(self):
+        """
+        The Update.days_to_stable() method should return 0 if Update.meets_testing_requirements()
+        returns True.
+        """
+        update = self.obj
+        update.autokarma = False
+        update.stable_karma = 1
+        update.comment(self.db, u'I found $100 after applying this update.', karma=1,
+                       author=u'bowlofeggs')
+        # Assert that our preconditions from the docblock are correct.
+        eq_(update.meets_testing_requirements, True)
+
+        eq_(update.days_to_stable, 0)
+
+    def test_days_to_stable_not_meets_testing_requirements_no_date_testing(self):
+        """
+        The Update.days_to_stable() method should return 0 if Update.meets_testing_requirements()
+        returns False but the Update's date_testing attribute is not truthy.
+        """
+        update = self.get_update()
+        # Assert that our preconditions from the docblock are correct.
+        eq_(update.meets_testing_requirements, False)
+        eq_(update.date_testing, None)
+
+        eq_(update.days_to_stable, 0)
+
+    def test_days_to_stable_not_meets_testing_requirements_with_date_testing(self):
+        """
+        The Update.days_to_stable() method should return a positive integer if
+        Update.meets_testing_requirements() returns False and the Update's date_testing attribute is
+        truthy.
+        """
+        update = self.get_update()
+        update.date_testing = datetime.utcnow() + timedelta(days=-4)
+        # Assert that our preconditions from the docblock are correct.
+        eq_(update.meets_testing_requirements, False)
+
+        eq_(update.days_to_stable, 3)
+
     @mock.patch('bodhi.server.models.bugtracker.close')
     @mock.patch('bodhi.server.models.bugtracker.comment')
     def test_modify_bugs_stable_close(self, comment, close):