14930: Assumes end-of-day datetime when --trash-at YYYY-MM-DD only.
authorLucas Di Pentima <ldipentima@veritasgenetics.com>
Fri, 7 Jun 2019 15:00:15 +0000 (12:00 -0300)
committerLucas Di Pentima <ldipentima@veritasgenetics.com>
Fri, 7 Jun 2019 15:00:15 +0000 (12:00 -0300)
Also:
* Refuses day-less dates on --trash-at
* Enhances readability on utc offset usage
* Adds tests

Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <ldipentima@veritasgenetics.com>

sdk/python/arvados/commands/put.py
sdk/python/tests/test_arv_put.py

index defd3c3fd5463760bc9836ae9d79f4473d53df85..219db5da81cf2b409ec4d76848d40b9afdd4ceb8 100644 (file)
@@ -1120,6 +1120,14 @@ def main(arguments=None, stdout=sys.stdout, stderr=sys.stderr,
     # Trash arguments validation
     trash_at = None
     if args.trash_at is not None:
+        # ciso8601 considers YYYYMM as invalid but YYYY-MM as valid, so here we
+        # make sure the user provides a complete YYYY-MM-DD date.
+        if not re.match(r'^\d{4}(?P<dash>-?)\d{2}?(?P=dash)\d{2}', args.trash_at):
+            logger.error("--trash-at argument format invalid, use --help to see examples.")
+            sys.exit(1)
+        # Check if no time information was provided. In that case, assume end-of-day.
+        if re.match(r'^\d{4}(?P<dash>-?)\d{2}?(?P=dash)\d{2}$', args.trash_at):
+            args.trash_at += 'T23:59:59'
         try:
             trash_at = ciso8601.parse_datetime(args.trash_at)
         except:
@@ -1128,19 +1136,19 @@ def main(arguments=None, stdout=sys.stdout, stderr=sys.stderr,
         else:
             if trash_at.tzinfo is not None:
                 # Timezone aware datetime provided.
-                utcoffset = trash_at.utcoffset()
+                utcoffset = -trash_at.utcoffset()
             else:
                 # Timezone naive datetime provided. Assume is local.
-                utcoffset = datetime.timedelta(hours=-time.timezone/3600)
+                utcoffset = datetime.timedelta(seconds=time.timezone)
             # Convert to UTC timezone naive datetime.
-            trash_at = trash_at.replace(tzinfo=None) - utcoffset
+            trash_at = trash_at.replace(tzinfo=None) + utcoffset
 
         if trash_at <= datetime.datetime.utcnow():
-            logger.error("--trash-at argument should be set in the future")
+            logger.error("--trash-at argument must be set in the future")
             sys.exit(1)
     if args.trash_after is not None:
         if args.trash_after < 1:
-            logger.error("--trash-after argument should be >= 1")
+            logger.error("--trash-after argument must be >= 1")
             sys.exit(1)
         trash_at = datetime.timedelta(seconds=(args.trash_after * 24 * 60 * 60))
 
index 42ab242351c96199b504a2dd38fc4671cd4cfa50..d27ac9be070632b30bf34d7d87b6e500d476abed 100644 (file)
@@ -1185,7 +1185,7 @@ class ArvPutIntegrationTest(run_test_server.TestCaseWithServers,
         self.assertNotEqual(None, col['uuid'])
         c = arv_put.api_client.collections().get(uuid=col['uuid']).execute()
         self.assertEqual(
-            ciso8601.parse_datetime(trash_at).replace(tzinfo=None)+datetime.timedelta(hours=3),
+            ciso8601.parse_datetime(trash_at).replace(tzinfo=None) + datetime.timedelta(hours=3),
             ciso8601.parse_datetime(c['trash_at']).replace(tzinfo=None))
 
     def test_put_collection_with_timezone_naive_expiring_datetime(self):
@@ -1199,17 +1199,34 @@ class ArvPutIntegrationTest(run_test_server.TestCaseWithServers,
         self.assertNotEqual(None, col['uuid'])
         c = arv_put.api_client.collections().get(uuid=col['uuid']).execute()
         self.assertEqual(
-            ciso8601.parse_datetime(trash_at) - datetime.timedelta(hours=-time.timezone/3600),
+            ciso8601.parse_datetime(trash_at) + datetime.timedelta(seconds=time.timezone),
             ciso8601.parse_datetime(c['trash_at']).replace(tzinfo=None))
 
-    def test_put_collection_with_invalid_absolute_expiring_datetime(self):
+    def test_put_collection_with_timezone_expiring_date_only(self):
         tmpdir = self.make_tmpdir()
+        trash_at = '2140-01-01'
+        end_of_day = datetime.timedelta(hours=23, minutes=59, seconds=59)
         with open(os.path.join(tmpdir, 'file1'), 'w') as f:
             f.write('Relaxing in basins at the end of inlets terminates the endless tests from the box')
-        with self.assertRaises(AssertionError):
-            self.run_and_find_collection(
-                "",
-                ['--no-progress', '--trash-at', 'tomorrow at noon', tmpdir])
+        col = self.run_and_find_collection(
+            "",
+            ['--no-progress', '--trash-at', trash_at, tmpdir])
+        self.assertNotEqual(None, col['uuid'])
+        c = arv_put.api_client.collections().get(uuid=col['uuid']).execute()
+        self.assertEqual(
+            ciso8601.parse_datetime(trash_at) + end_of_day + datetime.timedelta(seconds=time.timezone),
+            ciso8601.parse_datetime(c['trash_at']).replace(tzinfo=None))
+
+    def test_put_collection_with_invalid_absolute_expiring_datetimes(self):
+        cases = ['2100', '210010','2100-10', '2100-Oct']
+        tmpdir = self.make_tmpdir()
+        with open(os.path.join(tmpdir, 'file1'), 'w') as f:
+            f.write('Relaxing in basins at the end of inlets terminates the endless tests from the box')
+        for test_datetime in cases:
+            with self.assertRaises(AssertionError):
+                self.run_and_find_collection(
+                    "",
+                    ['--no-progress', '--trash-at', test_datetime, tmpdir])
 
     def test_put_collection_with_relative_expiring_datetime(self):
         expire_after = 7
@@ -1228,7 +1245,7 @@ class ArvPutIntegrationTest(run_test_server.TestCaseWithServers,
         self.assertTrue(dt_after > trash_at)
 
     def test_put_collection_with_invalid_relative_expiring_datetime(self):
-        expire_after = 0 # Should be >= 1
+        expire_after = 0 # Must be >= 1
         tmpdir = self.make_tmpdir()
         with open(os.path.join(tmpdir, 'file1'), 'w') as f:
             f.write('Relaxing in basins at the end of inlets terminates the endless tests from the box')