mirror of
https://github.com/GothenburgBitFactory/timewarrior.git
synced 2025-06-26 10:54:28 +02:00
Add check whether requested intervals have been found when searching by ID
- Restores behaviour which got lost when switching to the new interval filtering in 9968b9e9
- Add test for each command
Signed-off-by: Thomas Lauf <thomas.lauf@tngtech.com>
This commit is contained in:
parent
c2e26a989e
commit
993ae85d5c
22 changed files with 329 additions and 8 deletions
|
@ -68,6 +68,27 @@ int CmdAnnotate (
|
|||
{
|
||||
auto filtering = IntervalFilterAllWithIds (ids);
|
||||
intervals = getTracked (database, rules, filtering);
|
||||
|
||||
if (intervals.size () != ids.size ())
|
||||
{
|
||||
for (auto& id: ids)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (auto& interval: intervals)
|
||||
{
|
||||
if (interval.id == id)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply annotation to intervals.
|
||||
|
|
|
@ -53,6 +53,28 @@ int CmdDelete (
|
|||
auto filtering = IntervalFilterAllWithIds (ids);
|
||||
auto intervals = getTracked (database, rules, filtering);
|
||||
|
||||
|
||||
if (intervals.size () != ids.size ())
|
||||
{
|
||||
for (auto& id: ids)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (auto& interval: intervals)
|
||||
{
|
||||
if (interval.id == id)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& interval : intervals)
|
||||
{
|
||||
database.deleteInterval (interval);
|
||||
|
|
|
@ -57,6 +57,27 @@ int CmdJoin (
|
|||
auto filtering = IntervalFilterAllWithIds (ids);
|
||||
auto intervals = getTracked (database, rules, filtering);
|
||||
|
||||
if (intervals.size () != ids.size ())
|
||||
{
|
||||
for (auto& id: ids)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (auto& interval: intervals)
|
||||
{
|
||||
if (interval.id == id)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Interval first = intervals[1];
|
||||
Interval second = intervals[0];
|
||||
|
||||
|
|
|
@ -58,6 +58,27 @@ int CmdLengthen (
|
|||
auto filtering = IntervalFilterAllWithIds (ids);
|
||||
auto intervals = getTracked (database, rules, filtering);
|
||||
|
||||
if (intervals.size () != ids.size ())
|
||||
{
|
||||
for (auto& id: ids)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (auto& interval: intervals)
|
||||
{
|
||||
if (interval.id == id)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Lengthen intervals specified by ids
|
||||
for (auto& interval : intervals)
|
||||
{
|
||||
|
|
|
@ -70,6 +70,28 @@ int CmdMove (
|
|||
|
||||
auto filtering = IntervalFilterAllWithIds (ids);
|
||||
auto intervals = getTracked (database, rules, filtering);
|
||||
|
||||
if (intervals.size () != ids.size ())
|
||||
{
|
||||
for (auto& id: ids)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (auto& interval: intervals)
|
||||
{
|
||||
if (interval.id == id)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Interval interval = intervals.at (0);
|
||||
|
||||
if (interval.synthetic)
|
||||
|
|
|
@ -56,7 +56,27 @@ int CmdResize (
|
|||
auto filtering = IntervalFilterAllWithIds (ids);
|
||||
auto intervals = getTracked (database, rules, filtering);
|
||||
|
||||
// Apply tags to ids.
|
||||
if (intervals.size () != ids.size ())
|
||||
{
|
||||
for (auto& id: ids)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (auto& interval: intervals)
|
||||
{
|
||||
if (interval.id == id)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& interval : intervals)
|
||||
{
|
||||
if (interval.is_open ())
|
||||
|
|
|
@ -57,6 +57,27 @@ int CmdShorten (
|
|||
auto filtering = IntervalFilterAllWithIds (ids);
|
||||
auto intervals = getTracked (database, rules, filtering);
|
||||
|
||||
if (intervals.size () != ids.size ())
|
||||
{
|
||||
for (auto& id: ids)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (auto& interval: intervals)
|
||||
{
|
||||
if (interval.id == id)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shorten intervals specified by ids
|
||||
for (auto& interval : intervals)
|
||||
{
|
||||
|
|
|
@ -54,7 +54,27 @@ int CmdSplit (
|
|||
auto filtering = IntervalFilterAllWithIds (ids);
|
||||
auto intervals = getTracked (database, rules, filtering);
|
||||
|
||||
// Apply tags to ids.
|
||||
if (intervals.size () != ids.size ())
|
||||
{
|
||||
for (auto& id: ids)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (auto& interval: intervals)
|
||||
{
|
||||
if (interval.id == id)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& interval : intervals)
|
||||
{
|
||||
Interval first = interval;
|
||||
|
|
|
@ -78,6 +78,27 @@ int CmdTag (
|
|||
{
|
||||
auto filtering = IntervalFilterAllWithIds (ids);
|
||||
intervals = getTracked (database, rules, filtering);
|
||||
|
||||
if (intervals.size () != ids.size ())
|
||||
{
|
||||
for (auto& id: ids)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (auto& interval: intervals)
|
||||
{
|
||||
if (interval.id == id)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply tags to intervals.
|
||||
|
|
|
@ -77,6 +77,27 @@ int CmdUntag (
|
|||
{
|
||||
auto filtering = IntervalFilterAllWithIds (ids);
|
||||
intervals = getTracked (database, rules, filtering);
|
||||
|
||||
if (intervals.size () != ids.size ())
|
||||
{
|
||||
for (auto& id: ids)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (auto& interval: intervals)
|
||||
{
|
||||
if (interval.id == id)
|
||||
{
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found)
|
||||
{
|
||||
throw format ("ID '@{1}' does not correspond to any tracking.", id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove tags from intervals.
|
||||
|
|
|
@ -233,6 +233,17 @@ class TestAnnotate(TestCase):
|
|||
expectedAnnotation="",
|
||||
description="unmodified interval")
|
||||
|
||||
def test_referencing_a_non_existent_interval_is_an_error(self):
|
||||
"""Calling annotate with a non-existent interval reference is an error"""
|
||||
code, out, err = self.t.runError("annotate @1 foo")
|
||||
self.assertIn("ID '@1' does not correspond to any tracking.", err)
|
||||
|
||||
self.t("start 1h ago bar")
|
||||
|
||||
code, out, err = self.t.runError("annotate @2 foo")
|
||||
self.assertIn("ID '@2' does not correspond to any tracking.", err)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from simpletap import TAPTestRunner
|
||||
|
||||
|
|
|
@ -519,6 +519,16 @@ class TestContinue(TestCase):
|
|||
code, out, err = self.t.runError("continue @2 from {:%Y-%m-%dT%H:%M:%S}Z".format(now_utc + timedelta(seconds=10)))
|
||||
self.assertIn("Time tracking cannot be set in the future", err)
|
||||
|
||||
def test_referencing_a_non_existent_interval_is_an_error(self):
|
||||
"""Calling continue with a non-existent interval reference is an error"""
|
||||
code, out, err = self.t.runError("continue @1")
|
||||
self.assertIn("ID '@1' does not correspond to any tracking.", err)
|
||||
|
||||
self.t("start 1h ago bar")
|
||||
|
||||
code, out, err = self.t.runError("continue @2")
|
||||
self.assertIn("ID '@2' does not correspond to any tracking.", err)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from simpletap import TAPTestRunner
|
||||
|
|
|
@ -149,6 +149,16 @@ class TestDelete(TestCase):
|
|||
|
||||
self.assertEqual(len(j), 0)
|
||||
|
||||
def test_referencing_a_non_existent_interval_is_an_error(self):
|
||||
"""Calling delete with a non-existent interval reference is an error"""
|
||||
code, out, err = self.t.runError("delete @1")
|
||||
self.assertIn("ID '@1' does not correspond to any tracking.", err)
|
||||
|
||||
self.t("start 1h ago bar")
|
||||
|
||||
code, out, err = self.t.runError("delete @2")
|
||||
self.assertIn("ID '@2' does not correspond to any tracking.", err)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from simpletap import TAPTestRunner
|
||||
|
|
10
test/join.t
10
test/join.t
|
@ -92,6 +92,16 @@ class TestJoin(TestCase):
|
|||
expectedStart="{:%Y%m%dT%H%M%S}Z".format(five_hours_before_utc),
|
||||
expectedTags=["foo", "bar"])
|
||||
|
||||
def test_referencing_a_non_existent_interval_is_an_error(self):
|
||||
"""Calling join with a non-existent interval reference is an error"""
|
||||
code, out, err = self.t.runError("join @1 @2")
|
||||
self.assertIn("ID '@1' does not correspond to any tracking.", err)
|
||||
|
||||
self.t("start 1h ago bar")
|
||||
|
||||
code, out, err = self.t.runError("join @1 @2")
|
||||
self.assertIn("ID '@2' does not correspond to any tracking.", err)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from simpletap import TAPTestRunner
|
||||
|
|
|
@ -97,7 +97,15 @@ class TestLengthen(TestCase):
|
|||
self.assertEqual(len(j), 1)
|
||||
self.assertClosedInterval(j[0])
|
||||
|
||||
# TODO Add :adjust tests.
|
||||
def test_referencing_a_non_existent_interval_is_an_error(self):
|
||||
"""Calling lengthen with a non-existent interval reference is an error"""
|
||||
code, out, err = self.t.runError("lengthen @1 @2 5min")
|
||||
self.assertIn("ID '@1' does not correspond to any tracking.", err)
|
||||
|
||||
self.t("start 1h ago bar")
|
||||
|
||||
code, out, err = self.t.runError("lengthen @2 5min")
|
||||
self.assertIn("ID '@2' does not correspond to any tracking.", err)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -274,6 +274,13 @@ class TestModify(TestCase):
|
|||
expectedStart=four_hours_before_utc + timedelta(minutes=10),
|
||||
expectedTags=['foo'])
|
||||
|
||||
def test_referencing_a_non_existent_interval_is_an_error(self):
|
||||
"""Calling modify with a non-existent interval reference is an error"""
|
||||
self.t("start 1h ago bar")
|
||||
|
||||
code, out, err = self.t.runError("modify start @2")
|
||||
self.assertIn("ID '@2' does not correspond to any tracking.", err)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from simpletap import TAPTestRunner
|
||||
|
|
15
test/move.t
15
test/move.t
|
@ -27,11 +27,11 @@
|
|||
###############################################################################
|
||||
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import sys
|
||||
|
||||
# Ensure python finds the local simpletap module
|
||||
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
|
@ -286,7 +286,6 @@ class TestMove(TestCase):
|
|||
expectedTags=[],
|
||||
description="unmodified interval")
|
||||
|
||||
|
||||
def test_move_interval_to_enclose_a_month_border(self):
|
||||
"""Move an interval to enclose a month border"""
|
||||
self.t("track 20180831T180000 - 20180831T230000 foo")
|
||||
|
@ -297,7 +296,15 @@ class TestMove(TestCase):
|
|||
self.assertEqual(len(j), 1)
|
||||
self.assertClosedInterval(j[0])
|
||||
|
||||
# TODO Add :adjust tests.
|
||||
def test_referencing_a_non_existent_interval_is_an_error(self):
|
||||
"""Calling move with a non-existent interval reference is an error"""
|
||||
code, out, err = self.t.runError("move @1 2h ago")
|
||||
self.assertIn("ID '@1' does not correspond to any tracking.", err)
|
||||
|
||||
self.t("start 1h ago bar")
|
||||
|
||||
code, out, err = self.t.runError("move @2 2h ago")
|
||||
self.assertIn("ID '@2' does not correspond to any tracking.", err)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -71,6 +71,16 @@ class TestResize(TestCase):
|
|||
self.assertEqual(len(j), 1)
|
||||
self.assertClosedInterval(j[0])
|
||||
|
||||
def test_referencing_a_non_existent_interval_is_an_error(self):
|
||||
"""Calling resize with a non-existent interval reference is an error"""
|
||||
code, out, err = self.t.runError("resize @1 @2 10min")
|
||||
self.assertIn("ID '@1' does not correspond to any tracking.", err)
|
||||
|
||||
self.t("start 1h ago bar")
|
||||
|
||||
code, out, err = self.t.runError("resize @2 10min")
|
||||
self.assertIn("ID '@2' does not correspond to any tracking.", err)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from simpletap import TAPTestRunner
|
||||
|
|
|
@ -128,6 +128,16 @@ class TestShorten(TestCase):
|
|||
self.assertEqual(len(j), 1)
|
||||
self.assertClosedInterval(j[0])
|
||||
|
||||
def test_referencing_a_non_existent_interval_is_an_error(self):
|
||||
"""Calling shorten with a non-existent interval reference is an error"""
|
||||
code, out, err = self.t.runError("shorten @1 @2 5min")
|
||||
self.assertIn("ID '@1' does not correspond to any tracking.", err)
|
||||
|
||||
self.t("start 1h ago bar")
|
||||
|
||||
code, out, err = self.t.runError("shorten @2 5min")
|
||||
self.assertIn("ID '@2' does not correspond to any tracking.", err)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from simpletap import TAPTestRunner
|
||||
|
|
10
test/split.t
10
test/split.t
|
@ -70,7 +70,15 @@ class TestSplit(TestCase):
|
|||
|
||||
self.assertEqual(j[0]['end'], j[1]['start'])
|
||||
|
||||
# TODO Add :adjust tests.
|
||||
def test_referencing_a_non_existent_interval_is_an_error(self):
|
||||
"""Calling split with a non-existent interval reference is an error"""
|
||||
code, out, err = self.t.runError("split @1 @2")
|
||||
self.assertIn("ID '@1' does not correspond to any tracking.", err)
|
||||
|
||||
self.t("start 1h ago bar")
|
||||
|
||||
code, out, err = self.t.runError("split @2")
|
||||
self.assertIn("ID '@2' does not correspond to any tracking.", err)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
10
test/tag.t
10
test/tag.t
|
@ -262,6 +262,16 @@ class TestTag(TestCase):
|
|||
self.t("stop")
|
||||
self.t("delete @1")
|
||||
|
||||
def test_referencing_a_non_existent_interval_is_an_error(self):
|
||||
"""Calling tag with a non-existent interval reference is an error"""
|
||||
code, out, err = self.t.runError("tag @1 @2 foo")
|
||||
self.assertIn("ID '@1' does not correspond to any tracking.", err)
|
||||
|
||||
self.t("start 1h ago bar")
|
||||
|
||||
code, out, err = self.t.runError("tag @2 foo")
|
||||
self.assertIn("ID '@2' does not correspond to any tracking.", err)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from simpletap import TAPTestRunner
|
||||
|
|
10
test/untag.t
10
test/untag.t
|
@ -219,6 +219,16 @@ class TestUntag(TestCase):
|
|||
self.assertEqual(len(j), 1)
|
||||
self.assertClosedInterval(j[0], expectedTags=["bar"])
|
||||
|
||||
def test_referencing_a_non_existent_interval_is_an_error(self):
|
||||
"""Calling untag with a non-existent interval reference is an error"""
|
||||
code, out, err = self.t.runError("untag @1 @2 bar")
|
||||
self.assertIn("ID '@1' does not correspond to any tracking.", err)
|
||||
|
||||
self.t("start 1h ago bar")
|
||||
|
||||
code, out, err = self.t.runError("untag @2 bar")
|
||||
self.assertIn("ID '@2' does not correspond to any tracking.", err)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from simpletap import TAPTestRunner
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue