diff --git a/requirements.txt b/requirements.txt index 7736c61..cd08538 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,3 +8,4 @@ cliff>=2.6.0 # Apache-2.0 osc-lib>=1.5.1 # Apache-2.0 oslo.utils>=3.20.0 # Apache-2.0 keystoneauth1>=2.21.0 # Apache-2.0 +iso8601>=0.1.11 # MIT \ No newline at end of file diff --git a/test-requirements.txt b/test-requirements.txt index 7ba13b2..0405df2 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -13,3 +13,4 @@ testrepository>=0.0.18 # Apache-2.0/BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=1.4.0 # MIT reno!=2.3.1,>=1.8.0 # Apache-2.0 +mock>=2.0 # BSD diff --git a/tox.ini b/tox.ini index 2687bba..3d87bf5 100644 --- a/tox.ini +++ b/tox.ini @@ -10,7 +10,7 @@ install_command = setenv = VIRTUAL_ENV={envdir} deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt -commands = rm -f .testrepository/times.dbm +commands = /bin/rm -f .testrepository/times.dbm python setup.py test --slowest --testr-args='{posargs}' [testenv:pep8] diff --git a/vitrageclient/client.py b/vitrageclient/client.py index 9f54e93..049841c 100644 --- a/vitrageclient/client.py +++ b/vitrageclient/client.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. -import exc +from vitrageclient import exc from keystoneauth1 import adapter as keystoneauth from oslo_utils import importutils diff --git a/vitrageclient/shell.py b/vitrageclient/shell.py index 2ad6965..f47e2b5 100644 --- a/vitrageclient/shell.py +++ b/vitrageclient/shell.py @@ -28,15 +28,15 @@ from cliff import app from cliff import commandmanager from keystoneauth1 import loading -import client - -from v1.cli import alarm -from v1.cli import event -from v1.cli import rca -from v1.cli import resource -from v1.cli import template -from v1.cli import topology from vitrageclient import __version__ +from vitrageclient import client + +from vitrageclient.v1.cli import alarm +from vitrageclient.v1.cli import event +from vitrageclient.v1.cli import rca +from vitrageclient.v1.cli import resource +from vitrageclient.v1.cli import template +from vitrageclient.v1.cli import topology class VitrageCommandManager(commandmanager.CommandManager): diff --git a/vitrageclient/tests/cli/__init__.py b/vitrageclient/tests/cli/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vitrageclient/tests/base.py b/vitrageclient/tests/cli/base.py similarity index 67% rename from vitrageclient/tests/base.py rename to vitrageclient/tests/cli/base.py index c8bc467..4530069 100644 --- a/vitrageclient/tests/base.py +++ b/vitrageclient/tests/cli/base.py @@ -11,10 +11,18 @@ # License for the specific language governing permissions and limitations # under the License. -# noinspection PyPackageRequirements +import argparse + from oslotest import base -class TestCase(base.BaseTestCase): +class CliTestCase(base.BaseTestCase): """Test case base class for all unit tests.""" + + # original error method of argparse uses exit + # I just want to raise an exception and get the error message + # that exit outputs + @staticmethod + def _my_parser_error_func(message): + raise argparse.ArgumentTypeError(message) diff --git a/vitrageclient/tests/cli/test_event_post.py b/vitrageclient/tests/cli/test_event_post.py new file mode 100644 index 0000000..a1f2824 --- /dev/null +++ b/vitrageclient/tests/cli/test_event_post.py @@ -0,0 +1,53 @@ +# Copyright 2017 Nokia +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from argparse import ArgumentParser +from argparse import ArgumentTypeError + +# noinspection PyPackageRequirements +import mock +from testtools import ExpectedException + +from vitrageclient.tests.cli.base import CliTestCase +from vitrageclient.v1.cli.event import EventPost + + +# noinspection PyAttributeOutsideInit +class EventPostTest(CliTestCase): + + def setUp(self): + super(EventPostTest, self).setUp() + self.event_post = EventPost(mock.Mock(), mock.Mock()) + + def test_parsing_iso8601_with_not_a_date_string(self): + self.assertRaises(ArgumentTypeError, self.event_post.iso8601, 'bla') + + def test_parsing_iso8601_in_a_good_format(self): + self.event_post.iso8601('2014-12-13T12:44:21.123456') + + def test_iso8601_parsing_with_wrong_date_format(self): + self.assertRaises(ArgumentTypeError, self.event_post.iso8601, + '2014/12/13 12:44:21') + + @mock.patch.object(ArgumentParser, "error") + def test_parser_event_post_with_not_a_date_string(self, mock_parser): + mock_parser.side_effect = self._my_parser_error_func + parser = self.event_post.get_parser('vitrage event post') + + # noinspection PyCallByClass + with ExpectedException(ArgumentTypeError, + 'argument --time: -5 must be an iso8601 date'): + parser.parse_args(args=['--type', 'bla', + '--time', '-5', + '--details', 'blabla']) diff --git a/vitrageclient/tests/cli/test_topology_show.py b/vitrageclient/tests/cli/test_topology_show.py new file mode 100644 index 0000000..e32198c --- /dev/null +++ b/vitrageclient/tests/cli/test_topology_show.py @@ -0,0 +1,71 @@ +# Copyright 2017 Nokia +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from argparse import ArgumentParser +from argparse import ArgumentTypeError + +# noinspection PyPackageRequirements +import mock +# noinspection PyPackageRequirements +from testtools import ExpectedException + +from vitrageclient.tests.cli.base import CliTestCase +from vitrageclient.v1.cli.topology import TopologyShow + + +# noinspection PyAttributeOutsideInit +class TopologyShowTest(CliTestCase): + + def setUp(self): + super(TopologyShowTest, self).setUp() + self.topology_show = TopologyShow(mock.Mock(), mock.Mock()) + + def test_positive_integer_validation_with_negative(self): + self.assertRaises(ArgumentTypeError, + self.topology_show.positive_non_zero_int, -1) + + def test_positive_integer_validation_with_zero(self): + self.assertRaises(ArgumentTypeError, + self.topology_show.positive_non_zero_int, 0) + + def test_positive_integer_validation_with_string(self): + self.assertRaises(ArgumentTypeError, + self.topology_show.positive_non_zero_int, 'bla') + + def test_positive_integer_validation_with_positive(self): + self.topology_show.positive_non_zero_int(1) + + @mock.patch.object(ArgumentParser, "error") + def test_parser_topology_limit_with_a_negative_number(self, mock_parser): + mock_parser.side_effect = self._my_parser_error_func + parser = self.topology_show.get_parser('vitrage topology show') + + with ExpectedException(ArgumentTypeError, + 'argument --limit: -5 must be greater than 0'): + parser.parse_args(args=['--filter', 'bla', + '--limit', '-5', + '--root', 'blabla', + '--graph-type', 'tree']) + + @mock.patch.object(ArgumentParser, "error") + def test_parser_topology_limit_with_a_string(self, mock_parser): + mock_parser.side_effect = self._my_parser_error_func + parser = self.topology_show.get_parser('vitrage topology show') + + with ExpectedException(ArgumentTypeError, + 'argument --limit: spam must be an integer'): + parser.parse_args(args=['--filter', 'bla', + '--limit', 'spam', + '--root', 'blabla', + '--graph-type', 'tree']) diff --git a/vitrageclient/tests/test_vitrageclient.py b/vitrageclient/tests/test_vitrageclient.py deleted file mode 100644 index 3a15319..0000000 --- a/vitrageclient/tests/test_vitrageclient.py +++ /dev/null @@ -1,26 +0,0 @@ -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -""" -test_vitrageclient ----------------------------------- - -Tests for `vitrageclient` module. -""" - -from vitrageclient.tests import base - - -class TestVitrageclient(base.TestCase): - - def test_something(self): - pass diff --git a/vitrageclient/v1/cli/event.py b/vitrageclient/v1/cli/event.py index fe1dae2..eb2c529 100644 --- a/vitrageclient/v1/cli/event.py +++ b/vitrageclient/v1/cli/event.py @@ -14,8 +14,11 @@ import json +from cliff import argparse from cliff import command from datetime import datetime +from iso8601 import iso8601 +from iso8601 import ParseError from vitrageclient.common import exc @@ -24,6 +27,14 @@ from vitrageclient.common import exc class EventPost(command.Command): """Show the event of the system""" + @staticmethod + def iso8601(argument_value): + try: + iso8601.parse_date(argument_value) + except ParseError: + msg = "%s must be an iso8601 date" % argument_value + raise argparse.ArgumentTypeError(msg) + def get_parser(self, prog_name): parser = super(EventPost, self).get_parser(prog_name) @@ -32,6 +43,7 @@ class EventPost(command.Command): parser.add_argument('--time', default='', + type=self.iso8601, help='The timestamp of the event in ISO 8601 ' 'format: YYYY-MM-DDTHH:MM:SS.mmmmmm. ' 'If not specified, the current time is used') diff --git a/vitrageclient/v1/cli/topology.py b/vitrageclient/v1/cli/topology.py index 840bafd..4b2811c 100644 --- a/vitrageclient/v1/cli/topology.py +++ b/vitrageclient/v1/cli/topology.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from cliff import argparse from cliff import show from vitrageclient.common import exc from vitrageclient.common import utils @@ -19,6 +20,20 @@ from vitrageclient.common import utils class TopologyShow(show.ShowOne): """Show the topology of the system""" + @staticmethod + def positive_non_zero_int(argument_value): + if argument_value is None: + return None + try: + value = int(argument_value) + except ValueError: + msg = "%s must be an integer" % argument_value + raise argparse.ArgumentTypeError(msg) + if value <= 0: + msg = "%s must be greater than 0" % argument_value + raise argparse.ArgumentTypeError(msg) + return value + def get_parser(self, prog_name): parser = super(TopologyShow, self).get_parser(prog_name) parser.add_argument('--filter', @@ -26,7 +41,7 @@ class TopologyShow(show.ShowOne): help='query for the graph)') parser.add_argument('--limit', - type=int, + type=self.positive_non_zero_int, metavar='', help='the depth of the topology graph')