From 493a943e6e9d54451854d23f6947f247ba00bc63 Mon Sep 17 00:00:00 2001 From: Akihiro Motoki Date: Wed, 22 Nov 2017 11:54:56 +0000 Subject: [PATCH] horizon: Move test files to match corresponding module structure blueprint relocation-test-codes The current test file structure in horizon looks random. This is the first patch of blueprint relocation-test-codes. This commit proposes to change the structure to match the structure of test targets (i.e., main codes). More concretely, a unit test file for {TOP_MODULE}/{MODULE}/{FILENAME}.py will be located at {TOP_MODULE}/test/units/{MODULE}/test_{FILENAME}.py. When a module is test as a whole, a location of a corresponding test file will be {TOP_MODULE}/test/units/{MODULE}/test_{MODULE}.py. This clarifies locations of test files. In addition, this commit changes the base class of utils.test_secret_key from horizon.test.helper.TestCase to unittest.TestCase. This is because calling secret_key.generate_key() somehow interferes django.test.TestCase (which is a parent class of horizon.test.helper.TestCase). Change-Id: I48b9c317645e63a5819c52512b30f25969574817 --- .../{tests => selenium}/selenium_tests.py | 0 horizon/test/tests/forms.py | 229 ------- horizon/test/tests/utils.py | 611 ------------------ horizon/test/{tests => unit}/__init__.py | 0 horizon/test/unit/forms/__init__.py | 0 horizon/test/unit/forms/test_fields.py | 332 ++++++++++ horizon/test/unit/forms/test_forms.py | 106 +++ horizon/test/unit/hacking/__init__.py | 0 .../hacking/test_checks.py} | 0 horizon/test/unit/management/__init__.py | 0 .../test/unit/management/commands/__init__.py | 0 .../management/commands/test_startdash.py} | 14 - .../management/commands/test_startpanel.py | 36 ++ horizon/test/unit/middleware/__init__.py | 0 horizon/test/unit/middleware/test_base.py | 80 +++ .../middleware/test_operation_log.py} | 60 -- horizon/test/unit/tables/__init__.py | 0 .../tables.py => unit/tables/test_tables.py} | 0 horizon/test/unit/tabs/__init__.py | 0 .../{tests/tabs.py => unit/tabs/test_tabs.py} | 4 +- horizon/test/unit/templatetags/__init__.py | 0 .../templatetags/test_templatetags.py} | 0 .../test/{tests/base.py => unit/test_base.py} | 0 .../exceptions.py => unit/test_exceptions.py} | 0 .../messages.py => unit/test_messages.py} | 0 .../test_notifications.py} | 8 +- .../{tests/views.py => unit/test_views.py} | 0 horizon/test/unit/utils/__init__.py | 0 .../utils/test_babel_extract_angular.py} | 0 .../utils}/test_file_discovery.py | 0 horizon/test/unit/utils/test_filters.py | 100 +++ horizon/test/unit/utils/test_functions.py | 97 +++ horizon/test/unit/utils/test_memoized.py | 83 +++ horizon/test/unit/utils/test_secret_key.py | 39 ++ horizon/test/unit/utils/test_units.py | 74 +++ horizon/test/unit/utils/test_validators.py | 99 +++ horizon/test/unit/workflows/__init__.py | 0 .../workflows/test_workflows.py} | 3 +- 38 files changed, 1054 insertions(+), 921 deletions(-) rename horizon/test/{tests => selenium}/selenium_tests.py (100%) delete mode 100644 horizon/test/tests/forms.py delete mode 100644 horizon/test/tests/utils.py rename horizon/test/{tests => unit}/__init__.py (100%) create mode 100644 horizon/test/unit/forms/__init__.py create mode 100644 horizon/test/unit/forms/test_fields.py create mode 100644 horizon/test/unit/forms/test_forms.py create mode 100644 horizon/test/unit/hacking/__init__.py rename horizon/test/{test_hacking.py => unit/hacking/test_checks.py} (100%) create mode 100644 horizon/test/unit/management/__init__.py create mode 100644 horizon/test/unit/management/commands/__init__.py rename horizon/test/{tests/test_commands.py => unit/management/commands/test_startdash.py} (67%) create mode 100644 horizon/test/unit/management/commands/test_startpanel.py create mode 100644 horizon/test/unit/middleware/__init__.py create mode 100644 horizon/test/unit/middleware/test_base.py rename horizon/test/{tests/middleware.py => unit/middleware/test_operation_log.py} (76%) create mode 100644 horizon/test/unit/tables/__init__.py rename horizon/test/{tests/tables.py => unit/tables/test_tables.py} (100%) create mode 100644 horizon/test/unit/tabs/__init__.py rename horizon/test/{tests/tabs.py => unit/tabs/test_tabs.py} (99%) create mode 100644 horizon/test/unit/templatetags/__init__.py rename horizon/test/{tests/templatetags.py => unit/templatetags/test_templatetags.py} (100%) rename horizon/test/{tests/base.py => unit/test_base.py} (100%) rename horizon/test/{tests/exceptions.py => unit/test_exceptions.py} (100%) rename horizon/test/{tests/messages.py => unit/test_messages.py} (100%) rename horizon/test/{tests/notifications.py => unit/test_notifications.py} (89%) rename horizon/test/{tests/views.py => unit/test_views.py} (100%) create mode 100644 horizon/test/unit/utils/__init__.py rename horizon/test/{tests/babel_extract_angular.py => unit/utils/test_babel_extract_angular.py} (100%) rename horizon/test/{tests => unit/utils}/test_file_discovery.py (100%) create mode 100644 horizon/test/unit/utils/test_filters.py create mode 100644 horizon/test/unit/utils/test_functions.py create mode 100644 horizon/test/unit/utils/test_memoized.py create mode 100644 horizon/test/unit/utils/test_secret_key.py create mode 100644 horizon/test/unit/utils/test_units.py create mode 100644 horizon/test/unit/utils/test_validators.py create mode 100644 horizon/test/unit/workflows/__init__.py rename horizon/test/{tests/workflows.py => unit/workflows/test_workflows.py} (99%) diff --git a/horizon/test/tests/selenium_tests.py b/horizon/test/selenium/selenium_tests.py similarity index 100% rename from horizon/test/tests/selenium_tests.py rename to horizon/test/selenium/selenium_tests.py diff --git a/horizon/test/tests/forms.py b/horizon/test/tests/forms.py deleted file mode 100644 index 3b5bece72d..0000000000 --- a/horizon/test/tests/forms.py +++ /dev/null @@ -1,229 +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. - -from django import shortcuts - -from horizon import forms -from horizon.test import helpers as test - - -class FormMixinTests(test.TestCase): - - def _prepare_view(self, cls, request_headers, *args, **kwargs): - req = self.factory.get('/my_url/', **request_headers) - req.user = self.user - view = cls() - view.request = req - view.args = args - view.kwargs = kwargs - view.template_name = 'test_template' - # Note(Itxaka): ModalFormView requires a form_class to behave properly - view.form_class = TestForm - return view - - def test_modal_form_mixin_hide_true_if_ajax(self): - view = self._prepare_view( - forms.views.ModalFormView, - dict(HTTP_X_REQUESTED_WITH='XMLHttpRequest')) - context = view.get_context_data() - self.assertTrue(context['hide']) - - def test_modal_form_mixin_add_to_field_header_set(self): - return self._test_form_mixin_add_to_field_header(add_field=True) - - def test_modal_form_mixin_add_to_field_header_not_set(self): - return self._test_form_mixin_add_to_field_header(add_field=False) - - def _test_form_mixin_add_to_field_header(self, add_field=False): - options = dict(HTTP_X_REQUESTED_WITH='XMLHttpRequest') - if add_field: - options[forms.views.ADD_TO_FIELD_HEADER] = "keepme" - - view = self._prepare_view(forms.views.ModalFormView, options) - context = view.get_context_data() - - if add_field: - self.assertEqual("keepme", context['add_to_field']) - else: - self.assertNotIn('add_to_field', context) - - def test_template_name_change_based_on_ajax_request(self): - view = self._prepare_view( - forms.views.ModalFormView, - dict(HTTP_X_REQUESTED_WITH='XMLHttpRequest')) - self.assertEqual('_' + view.template_name, - view.get_template_names()) - - view = self._prepare_view(forms.views.ModalFormView, {}) - self.assertEqual(view.template_name, view.get_template_names()) - - -class TestForm(forms.SelfHandlingForm): - - name = forms.CharField(max_length=255) - - def handle(self, request, data): - return True - - -class FormErrorTests(test.TestCase): - - template = 'horizon/common/_form_fields.html' - - def setUp(self): - super(FormErrorTests, self).setUp() - # Note(Itxaka): We pass data to the form so its bound and has the - # proper cleaned_data fields - self.form = TestForm(self.request, data={'fake': 'data'}) - - def _render_form(self): - return shortcuts.render(self.request, self.template, - {'form': self.form}) - - def test_set_warning(self): - warning_text = 'WARNING 29380' - self.form.set_warning(warning_text) - self.assertEqual([warning_text], self.form.warnings) - resp = self._render_form() - self.assertIn(warning_text.encode('utf-8'), resp.content) - - def test_api_error(self): - error_text = 'ERROR 12938' - self.form.full_clean() - self.form.api_error(error_text) - self.assertEqual([error_text], self.form.non_field_errors()) - resp = self._render_form() - self.assertIn(error_text.encode('utf-8'), resp.content) - - -class TestChoiceFieldForm(forms.SelfHandlingForm): - title_dic = {"label1": {"title": "This is choice 1"}, - "label2": {"title": "This is choice 2"}, - "label3": {"title": "This is choice 3"}} - name = forms.CharField(max_length=255, - label="Test Name", - help_text="Please enter a name") - test_choices = forms.ChoiceField( - label="Test Choices", - required=False, - help_text="Testing drop down choices", - widget=forms.fields.SelectWidget( - attrs={ - 'class': 'switchable', - 'data-slug': 'source'}, - transform_html_attrs=title_dic.get)) - - def __init__(self, request, *args, **kwargs): - super(TestChoiceFieldForm, self).__init__(request, *args, - **kwargs) - choices = ([('choice1', 'label1'), - ('choice2', 'label2')]) - self.fields['test_choices'].choices = choices - - def handle(self, request, data): - return True - - -class ChoiceFieldTests(test.TestCase): - - template = 'horizon/common/_form_fields.html' - - def setUp(self): - super(ChoiceFieldTests, self).setUp() - self.form = TestChoiceFieldForm(self.request) - - def _render_form(self): - return shortcuts.render(self.request, self.template, - {'form': self.form}) - - def test_legacychoicefield_title(self): - resp = self._render_form() - self.assertContains( - resp, - '', - count=1, html=True) - self.assertContains( - resp, - '', - count=1, html=True) - - -class TestThemableChoiceFieldForm(forms.SelfHandlingForm): - # It's POSSIBLE to combine this with the test helper form above, but - # I fear we'd run into collisions where one test's desired output is - # actually within a separate widget's output. - - title_dic = {"label1": {"title": "This is choice 1"}, - "label2": {"title": "This is choice 2"}, - "label3": {"title": "This is choice 3"}} - name = forms.CharField(max_length=255, - label="Test Name", - help_text="Please enter a name") - test_choices = forms.ThemableChoiceField( - label="Test Choices", - required=False, - help_text="Testing drop down choices", - widget=forms.fields.ThemableSelectWidget( - attrs={ - 'class': 'switchable', - 'data-slug': 'source'}, - transform_html_attrs=title_dic.get)) - - def __init__(self, request, *args, **kwargs): - super(TestThemableChoiceFieldForm, self).__init__(request, *args, - **kwargs) - choices = ([('choice1', 'label1'), - ('choice2', 'label2')]) - self.fields['test_choices'].choices = choices - - def handle(self, request, data): - return True - - -class ThemableChoiceFieldTests(test.TestCase): - - template = 'horizon/common/_form_fields.html' - - def setUp(self): - super(ThemableChoiceFieldTests, self).setUp() - self.form = TestThemableChoiceFieldForm(self.request) - - def _render_form(self): - return shortcuts.render(self.request, self.template, - {'form': self.form}) - - def test_choicefield_labels_and_title_attr(self): - resp = self._render_form() - self.assertContains( - resp, - '' - 'label1', - count=1, - html=True) - self.assertContains( - resp, - '' - 'label2', - count=1, - html=True) - - def test_choicefield_title_select_compatible(self): - resp = self._render_form() - self.assertContains( - resp, - '', - count=1, html=True) - self.assertContains( - resp, - '', - count=1, html=True) diff --git a/horizon/test/tests/utils.py b/horizon/test/tests/utils.py deleted file mode 100644 index 1d9c9ecda0..0000000000 --- a/horizon/test/tests/utils.py +++ /dev/null @@ -1,611 +0,0 @@ -# Copyright 2012 Nebula, Inc. -# -# 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. - -import datetime -import os - -from django.core.exceptions import ValidationError -import django.template -from django.template import defaultfilters - -from horizon import forms -from horizon.test import helpers as test -from horizon.utils import filters -# we have to import the filter in order to register it -from horizon.utils.filters import parse_isotime # noqa: F401 -from horizon.utils import functions -from horizon.utils import memoized -from horizon.utils import secret_key -from horizon.utils import units -from horizon.utils import validators - - -class ValidatorsTests(test.TestCase): - def test_validate_ipv4_cidr(self): - GOOD_CIDRS = ("192.168.1.1/16", - "192.0.0.1/17", - "0.0.0.0/16", - "10.144.11.107/4", - "255.255.255.255/0", - "0.1.2.3/16", - "0.0.0.0/32", - # short form - "128.0/16", - "128/4") - BAD_CIDRS = ("255.255.255.256\\", - "256.255.255.255$", - "1.2.3.4.5/41", - "0.0.0.0/99", - "127.0.0.1/", - "127.0.0.1/33", - "127.0.0.1/-1", - "127.0.0.1/100", - # some valid IPv6 addresses - "fe80::204:61ff:254.157.241.86/4", - "fe80::204:61ff:254.157.241.86/0", - "2001:0DB8::CD30:0:0:0:0/60", - "2001:0DB8::CD30:0/90") - ip = forms.IPField(mask=True, version=forms.IPv4) - for cidr in GOOD_CIDRS: - self.assertIsNone(ip.validate(cidr)) - for cidr in BAD_CIDRS: - self.assertRaises(ValidationError, ip.validate, cidr) - - def test_validate_ipv6_cidr(self): - GOOD_CIDRS = ("::ffff:0:0/56", - "2001:0db8::1428:57ab/17", - "FEC0::/10", - "fe80::204:61ff:254.157.241.86/4", - "fe80::204:61ff:254.157.241.86/0", - "2001:0DB8::CD30:0:0:0:0/60", - "2001:0DB8::CD30:0/90", - "::1/128") - BAD_CIDRS = ("1111:2222:3333:4444:::/", - "::2222:3333:4444:5555:6666:7777:8888:\\", - ":1111:2222:3333:4444::6666:1.2.3.4/1000", - "1111:2222::4444:5555:6666::8888@", - "1111:2222::4444:5555:6666:8888/", - "::ffff:0:0/129", - "1.2.3.4:1111:2222::5555//22", - "fe80::204:61ff:254.157.241.86/200", - # some valid IPv4 addresses - "10.144.11.107/4", - "255.255.255.255/0", - "0.1.2.3/16") - ip = forms.IPField(mask=True, version=forms.IPv6) - for cidr in GOOD_CIDRS: - self.assertIsNone(ip.validate(cidr)) - for cidr in BAD_CIDRS: - self.assertRaises(ValidationError, ip.validate, cidr) - - def test_validate_mixed_cidr(self): - GOOD_CIDRS = ("::ffff:0:0/56", - "2001:0db8::1428:57ab/17", - "FEC0::/10", - "fe80::204:61ff:254.157.241.86/4", - "fe80::204:61ff:254.157.241.86/0", - "2001:0DB8::CD30:0:0:0:0/60", - "0.0.0.0/16", - "10.144.11.107/4", - "255.255.255.255/0", - "0.1.2.3/16", - # short form - "128.0/16", - "10/4") - BAD_CIDRS = ("1111:2222:3333:4444::://", - "::2222:3333:4444:5555:6666:7777:8888:", - ":1111:2222:3333:4444::6666:1.2.3.4/1/1", - "1111:2222::4444:5555:6666::8888\\2", - "1111:2222::4444:5555:6666:8888/", - "1111:2222::4444:5555:6666::8888/130", - "127.0.0.1/", - "127.0.0.1/33", - "127.0.0.1/-1") - ip = forms.IPField(mask=True, version=forms.IPv4 | forms.IPv6) - for cidr in GOOD_CIDRS: - self.assertIsNone(ip.validate(cidr)) - for cidr in BAD_CIDRS: - self.assertRaises(ValidationError, ip.validate, cidr) - - def test_validate_IPs(self): - GOOD_IPS_V4 = ("0.0.0.0", - "10.144.11.107", - "169.144.11.107", - "172.100.11.107", - "255.255.255.255", - "0.1.2.3") - GOOD_IPS_V6 = ("", - "::ffff:0:0", - "2001:0db8::1428:57ab", - "FEC0::", - "fe80::204:61ff:254.157.241.86", - "fe80::204:61ff:254.157.241.86", - "2001:0DB8::CD30:0:0:0:0") - BAD_IPS_V4 = ("1111:2222:3333:4444:::", - "::2222:3333:4444:5555:6666:7777:8888:", - ":1111:2222:3333:4444::6666:1.2.3.4", - "1111:2222::4444:5555:6666::8888", - "1111:2222::4444:5555:6666:8888/", - "1111:2222::4444:5555:6666::8888/130", - "127.0.0.1/", - "127.0.0.1/33", - "127.0.0.1/-1") - BAD_IPS_V6 = ("1111:2222:3333:4444:::", - "::2222:3333:4444:5555:6666:7777:8888:", - ":1111:2222:3333:4444::6666:1.2.3.4", - "1111:2222::4444:5555:6666::8888", - "1111:2222::4444:5555:6666:8888/", - "1111:2222::4444:5555:6666::8888/130") - ipv4 = forms.IPField(required=True, version=forms.IPv4) - ipv6 = forms.IPField(required=False, version=forms.IPv6) - ipmixed = forms.IPField(required=False, - version=forms.IPv4 | forms.IPv6) - - for ip_addr in GOOD_IPS_V4: - self.assertIsNone(ipv4.validate(ip_addr)) - self.assertIsNone(ipmixed.validate(ip_addr)) - - for ip_addr in GOOD_IPS_V6: - self.assertIsNone(ipv6.validate(ip_addr)) - self.assertIsNone(ipmixed.validate(ip_addr)) - - for ip_addr in BAD_IPS_V4: - self.assertRaises(ValidationError, ipv4.validate, ip_addr) - self.assertRaises(ValidationError, ipmixed.validate, ip_addr) - - for ip_addr in BAD_IPS_V6: - self.assertRaises(ValidationError, ipv6.validate, ip_addr) - self.assertRaises(ValidationError, ipmixed.validate, ip_addr) - - self.assertRaises(ValidationError, ipv4.validate, "") # required=True - - iprange = forms.IPField(required=False, - mask=True, - mask_range_from=10, - version=forms.IPv4 | forms.IPv6) - self.assertRaises(ValidationError, iprange.validate, - "fe80::204:61ff:254.157.241.86/6") - self.assertRaises(ValidationError, iprange.validate, - "169.144.11.107/8") - self.assertIsNone(iprange.validate("fe80::204:61ff:254.157.241.86/36")) - self.assertIsNone(iprange.validate("169.144.11.107/18")) - - def test_validate_multi_ip_field(self): - GOOD_CIDRS_INPUT = ("192.168.1.1/16, 192.0.0.1/17",) - BAD_CIDRS_INPUT = ("1.2.3.4.5/41,0.0.0.0/99", - "1.2.3.4.5/41;0.0.0.0/99", - "1.2.3.4.5/41 0.0.0.0/99", - "192.168.1.1/16 192.0.0.1/17") - - ip = forms.MultiIPField(mask=True, version=forms.IPv4) - for cidr in GOOD_CIDRS_INPUT: - self.assertIsNone(ip.validate(cidr)) - for cidr in BAD_CIDRS_INPUT: - self.assertRaises(ValidationError, ip.validate, cidr) - - def test_mac_address_validator(self): - GOOD_MAC_ADDRESSES = ( - "00:11:88:99:Aa:Ff", - "00-11-88-99-Aa-Ff", - "0011.8899.AaFf", - "00118899AaFf", - ) - BAD_MAC_ADDRESSES = ( - "not a mac", - "11:22:33:44:55", - "zz:11:22:33:44:55", - ) - - field = forms.MACAddressField() - for input in GOOD_MAC_ADDRESSES: - self.assertIsNone(field.validate(input)) - for input in BAD_MAC_ADDRESSES: - self.assertRaises(ValidationError, field.validate, input) - - def test_mac_address_normal_form(self): - field = forms.MACAddressField() - field.validate("00-11-88-99-Aa-Ff") - self.assertEqual(field.mac_address, "00:11:88:99:aa:ff") - - def test_port_validator(self): - VALID_PORTS = (1, 65535) - INVALID_PORTS = (-1, 65536) - - for port in VALID_PORTS: - self.assertIsNone(validators.validate_port_range(port)) - - for port in INVALID_PORTS: - self.assertRaises(ValidationError, - validators.validate_port_range, - port) - - def test_icmp_type_validator(self): - VALID_ICMP_TYPES = (1, 0, 255, -1) - INVALID_ICMP_TYPES = (256, None, -2) - - for icmp_type in VALID_ICMP_TYPES: - self.assertIsNone(validators.validate_icmp_type_range(icmp_type)) - - for icmp_type in INVALID_ICMP_TYPES: - self.assertRaises(ValidationError, - validators.validate_icmp_type_range, - icmp_type) - - def test_icmp_code_validator(self): - VALID_ICMP_CODES = (1, 0, 255, None, -1,) - INVALID_ICMP_CODES = (256, -2) - - for icmp_code in VALID_ICMP_CODES: - self.assertIsNone(validators.validate_icmp_code_range(icmp_code)) - - for icmp_code in INVALID_ICMP_CODES: - self.assertRaises(ValidationError, - validators.validate_icmp_code_range, - icmp_code) - - def test_ip_proto_validator(self): - VALID_PROTO = (0, 255, -1) - INVALID_PROTO = (-2, 256) - - for proto in VALID_PROTO: - self.assertIsNone(validators.validate_ip_protocol(proto)) - - for proto in INVALID_PROTO: - self.assertRaises(ValidationError, - validators.validate_ip_protocol, - proto) - - def test_port_range_validator(self): - VALID_RANGE = ('1:65535', - '1:1') - INVALID_RANGE = ('22:22:22:22', - '1:-1', - '-1:65535') - - test_call = validators.validate_port_or_colon_separated_port_range - for prange in VALID_RANGE: - self.assertIsNone(test_call(prange)) - - for prange in INVALID_RANGE: - self.assertRaises(ValidationError, test_call, prange) - - def test_metadata_validator(self): - VALID_METADATA = ( - "key1=val1", "key1=val1,key2=val2", - "key1=val1,key2=val2,key3=val3", "key1=" - ) - INVALID_METADATA = ( - "key1==val1", "key1=val1,", "=val1", - "=val1", " " - ) - - for mdata in VALID_METADATA: - self.assertIsNone(validators.validate_metadata(mdata)) - - for mdata in INVALID_METADATA: - self.assertRaises(ValidationError, - validators.validate_metadata, - mdata) - - -class SecretKeyTests(test.TestCase): - def test_generate_secret_key(self): - key = secret_key.generate_key(32) - self.assertEqual(32, len(key)) - self.assertNotEqual(key, secret_key.generate_key(32)) - - def test_generate_or_read_key_from_file(self): - key_file = ".test_secret_key_store" - key = secret_key.generate_or_read_from_file(key_file) - - # Consecutive reads should come from the already existing file: - self.assertEqual(secret_key.generate_or_read_from_file(key_file), key) - - # Key file only be read/writable by user: - self.assertEqual(0o600, os.stat(key_file).st_mode & 0o777) - os.chmod(key_file, 0o644) - self.assertRaises(secret_key.FilePermissionError, - secret_key.generate_or_read_from_file, key_file) - os.remove(key_file) - - -class FiltersTests(test.TestCase): - def test_replace_underscore_filter(self): - res = filters.replace_underscores("__under_score__") - self.assertEqual(" under score ", res) - - def test_parse_isotime_filter(self): - c = django.template.Context({'time': ''}) - t = django.template.Template('{{ time|parse_isotime }}') - output = u"" - - self.assertEqual(output, t.render(c)) - - c = django.template.Context({'time': 'error'}) - t = django.template.Template('{{ time|parse_isotime }}') - output = u"" - - self.assertEqual(output, t.render(c)) - - c = django.template.Context({'time': 'error'}) - t = django.template.Template('{{ time|parse_isotime:"test" }}') - output = u"test" - - self.assertEqual(output, t.render(c)) - - c = django.template.Context({'time': '2007-03-04T21:08:12'}) - t = django.template.Template('{{ time|parse_isotime:"test" }}') - output = u"March 4, 2007, 3:08 p.m." - - self.assertEqual(output, t.render(c)) - - adate = '2007-01-25T12:00:00Z' - result = filters.parse_isotime(adate) - self.assertIsInstance(result, datetime.datetime) - - -class TimeSinceNeverFilterTests(test.TestCase): - - default = u"Never" - - def test_timesince_or_never_returns_default_for_empty_string(self): - c = django.template.Context({'time': ''}) - t = django.template.Template('{{ time|timesince_or_never }}') - self.assertEqual(self.default, t.render(c)) - - def test_timesince_or_never_returns_default_for_none(self): - c = django.template.Context({'time': None}) - t = django.template.Template('{{ time|timesince_or_never }}') - self.assertEqual(self.default, t.render(c)) - - def test_timesince_or_never_returns_default_for_gibberish(self): - c = django.template.Context({'time': django.template.Context()}) - t = django.template.Template('{{ time|timesince_or_never }}') - self.assertEqual(self.default, t.render(c)) - - def test_timesince_or_never_returns_with_custom_default(self): - custom = "Hello world" - c = django.template.Context({'date': ''}) - t = django.template.Template('{{ date|timesince_or_never:"%s" }}' - % custom) - self.assertEqual(custom, t.render(c)) - - def test_timesince_or_never_returns_with_custom_empty_string_default(self): - c = django.template.Context({'date': ''}) - t = django.template.Template('{{ date|timesince_or_never:"" }}') - self.assertEqual("", t.render(c)) - - def test_timesince_or_never_returns_same_output_as_django_date(self): - d = datetime.date(year=2014, month=3, day=7) - c = django.template.Context({'date': d}) - t = django.template.Template('{{ date|timesince_or_never }}') - self.assertEqual(defaultfilters.timesince(d), t.render(c)) - - def test_timesince_or_never_returns_same_output_as_django_datetime(self): - now = datetime.datetime.now() - c = django.template.Context({'date': now}) - t = django.template.Template('{{ date|timesince_or_never }}') - self.assertEqual(defaultfilters.timesince(now), t.render(c)) - - -class MemoizedTests(test.TestCase): - def test_memoized_decorator_cache_on_next_call(self): - values_list = [] - - @memoized.memoized - def cache_calls(remove_from): - values_list.append(remove_from) - return True - - def non_cached_calls(remove_from): - values_list.append(remove_from) - return True - - for x in range(0, 5): - non_cached_calls(1) - self.assertEqual(5, len(values_list)) - - values_list = [] - for x in range(0, 5): - cache_calls(1) - self.assertEqual(1, len(values_list)) - - def test_memoized_with_request_call(self): - - chorus = [ - "I", - "Love", - "Rock 'n' Roll", - "put another coin", - "in the Jukebox Baby." - ] - - leader = 'Joan Jett' - group = 'Blackhearts' - - for position, chorus_line in enumerate(chorus): - - changed_args = False - - def some_func(some_param): - if not changed_args: - self.assertEqual(some_param, chorus_line) - else: - self.assertNotEqual(some_param, chorus_line) - self.assertEqual(some_param, group) - return leader - - @memoized.memoized_with_request(some_func, position) - def some_other_func(*args): - return args - - # check chorus_copy[position] is replaced by some_func's - # output - output1 = some_other_func(*chorus) - self.assertEqual(output1[position], leader) - - # Change args used to call the function - chorus_copy = list(chorus) - chorus_copy[position] = group - changed_args = True - # check that some_func is called with a different parameter, and - # that check chorus_copy[position] is replaced by some_func's - # output and some_other_func still called with the same parameters - output2 = some_other_func(*chorus_copy) - self.assertEqual(output2[position], leader) - # check that some_other_func returned a memoized list. - self.assertIs(output1, output2) - - -class GetConfigValueTests(test.TestCase): - key = 'key' - value = 'value' - requested_url = '/project/instances/' - int_default = 30 - str_default = 'default' - - def test_bad_session_value(self): - request = self.factory.get(self.requested_url) - request.session[self.key] = self.value - res = functions.get_config_value(request, self.key, self.int_default) - self.assertEqual(res, self.int_default) - - def test_bad_cookie_value(self): - request = self.factory.get(self.requested_url) - if self.key in request.session: - del request.session[self.key] - request.COOKIES[self.key] = self.value - res = functions.get_config_value(request, self.key, self.int_default) - self.assertEqual(res, self.int_default) - - def test_float_default_value(self): - default = 30.1 - request = self.factory.get(self.requested_url) - request.session[self.key] = self.value - res = functions.get_config_value(request, self.key, default) - self.assertEqual(res, self.value) - - def test_session_gets_set(self): - request = self.factory.get(self.requested_url) - request.session[self.key] = self.value - functions.get_config_value(request, self.key, self.int_default) - self.assertEqual(request.session[self.key], self.int_default) - - def test_found_in_session(self): - request = self.factory.get(self.requested_url) - request.session[self.key] = self.value - if request.COOKIES.get(self.key): - del request.COOKIES[self.key] - res = functions.get_config_value(request, self.key, self.str_default) - self.assertEqual(res, self.value) - - def test_found_in_cookie(self): - request = self.factory.get(self.requested_url) - if request.session.get(self.key): - del request.session[self.key] - request.COOKIES[self.key] = self.value - res = functions.get_config_value(request, self.key, self.str_default) - self.assertEqual(res, self.value) - - def test_found_in_config(self): - key = 'TESTSERVER' - value = 'http://testserver' - request = self.factory.get(self.requested_url) - if request.session.get(key): - del request.session[key] - if request.COOKIES.get(key): - del request.COOKIES[key] - res = functions.get_config_value(request, key, self.str_default) - self.assertEqual(res, value) - - def test_return_default(self): - key = 'NOT FOUND ANYWHERE' - request = self.factory.get(self.requested_url) - if request.session.get(key): - del request.session[key] - if request.COOKIES.get(key): - del request.COOKIES[key] - res = functions.get_config_value(request, key, self.str_default) - self.assertEqual(res, self.str_default) - - def test_return_default_no_settings(self): - key = 'TESTSERVER' - request = self.factory.get(self.requested_url) - if request.session.get(key): - del request.session[key] - if request.COOKIES.get(key): - del request.COOKIES[key] - res = functions.get_config_value(request, key, self.str_default, - search_in_settings=False) - self.assertEqual(res, self.str_default) - - -class UnitsTests(test.TestCase): - def test_is_supported(self): - self.assertTrue(units.is_supported('MB')) - self.assertTrue(units.is_supported('min')) - self.assertFalse(units.is_supported('KWh')) - self.assertFalse(units.is_supported('unknown_unit')) - - def test_is_larger(self): - self.assertTrue(units.is_larger('KB', 'B')) - self.assertTrue(units.is_larger('MB', 'B')) - self.assertTrue(units.is_larger('GB', 'B')) - self.assertTrue(units.is_larger('TB', 'B')) - self.assertTrue(units.is_larger('GB', 'MB')) - self.assertFalse(units.is_larger('B', 'KB')) - self.assertFalse(units.is_larger('MB', 'GB')) - - self.assertTrue(units.is_larger('min', 's')) - self.assertTrue(units.is_larger('hr', 'min')) - self.assertTrue(units.is_larger('hr', 's')) - self.assertFalse(units.is_larger('s', 'min')) - - def test_convert(self): - self.assertEqual(units.convert(4096, 'MB', 'GB'), (4, 'GB')) - self.assertEqual(units.convert(4, 'GB', 'MB'), (4096, 'MB')) - - self.assertEqual(units.convert(1.5, 'hr', 'min'), (90, 'min')) - self.assertEqual(units.convert(12, 'hr', 'day'), (0.5, 'day')) - - def test_normalize(self): - self.assertEqual(units.normalize(1, 'B'), (1, 'B')) - self.assertEqual(units.normalize(1000, 'B'), (1000, 'B')) - self.assertEqual(units.normalize(1024, 'B'), (1, 'KB')) - self.assertEqual(units.normalize(1024 * 1024, 'B'), (1, 'MB')) - self.assertEqual(units.normalize(10 * 1024 ** 3, 'B'), (10, 'GB')) - self.assertEqual(units.normalize(1000 * 1024 ** 4, 'B'), (1000, 'TB')) - self.assertEqual(units.normalize(1024, 'KB'), (1, 'MB')) - self.assertEqual(units.normalize(1024 ** 2, 'KB'), (1, 'GB')) - self.assertEqual(units.normalize(10 * 1024, 'MB'), (10, 'GB')) - self.assertEqual(units.normalize(0.5, 'KB'), (512, 'B')) - self.assertEqual(units.normalize(0.0001, 'MB'), (104.9, 'B')) - - self.assertEqual(units.normalize(1, 's'), (1, 's')) - self.assertEqual(units.normalize(120, 's'), (2, 'min')) - self.assertEqual(units.normalize(3600, 's'), (60, 'min')) - self.assertEqual(units.normalize(3600 * 24, 's'), (24, 'hr')) - self.assertEqual(units.normalize(10 * 3600 * 24, 's'), (10, 'day')) - self.assertEqual(units.normalize(90, 'min'), (90, 'min')) - self.assertEqual(units.normalize(150, 'min'), (2.5, 'hr')) - self.assertEqual(units.normalize(60 * 24, 'min'), (24, 'hr')) - self.assertEqual(units.normalize(0.5, 'day'), (12, 'hr')) - self.assertEqual(units.normalize(10800000000000, 'ns'), (3, 'hr')) - self.assertEqual(units.normalize(14, 'day'), (2, 'week')) - self.assertEqual(units.normalize(91, 'day'), (3, 'month')) - self.assertEqual(units.normalize(18, 'month'), (18, 'month')) - self.assertEqual(units.normalize(24, 'month'), (2, 'year')) - - self.assertEqual(units.normalize(1, 'unknown_unit'), - (1, 'unknown_unit')) diff --git a/horizon/test/tests/__init__.py b/horizon/test/unit/__init__.py similarity index 100% rename from horizon/test/tests/__init__.py rename to horizon/test/unit/__init__.py diff --git a/horizon/test/unit/forms/__init__.py b/horizon/test/unit/forms/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/horizon/test/unit/forms/test_fields.py b/horizon/test/unit/forms/test_fields.py new file mode 100644 index 0000000000..fd299be04f --- /dev/null +++ b/horizon/test/unit/forms/test_fields.py @@ -0,0 +1,332 @@ +# 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 django.core.exceptions import ValidationError +from django import shortcuts + +from horizon import forms +from horizon.test import helpers as test + + +class IPFieldTests(test.TestCase): + + def test_validate_ipv4_cidr(self): + GOOD_CIDRS = ("192.168.1.1/16", + "192.0.0.1/17", + "0.0.0.0/16", + "10.144.11.107/4", + "255.255.255.255/0", + "0.1.2.3/16", + "0.0.0.0/32", + # short form + "128.0/16", + "128/4") + BAD_CIDRS = ("255.255.255.256\\", + "256.255.255.255$", + "1.2.3.4.5/41", + "0.0.0.0/99", + "127.0.0.1/", + "127.0.0.1/33", + "127.0.0.1/-1", + "127.0.0.1/100", + # some valid IPv6 addresses + "fe80::204:61ff:254.157.241.86/4", + "fe80::204:61ff:254.157.241.86/0", + "2001:0DB8::CD30:0:0:0:0/60", + "2001:0DB8::CD30:0/90") + ip = forms.IPField(mask=True, version=forms.IPv4) + for cidr in GOOD_CIDRS: + self.assertIsNone(ip.validate(cidr)) + for cidr in BAD_CIDRS: + self.assertRaises(ValidationError, ip.validate, cidr) + + def test_validate_ipv6_cidr(self): + GOOD_CIDRS = ("::ffff:0:0/56", + "2001:0db8::1428:57ab/17", + "FEC0::/10", + "fe80::204:61ff:254.157.241.86/4", + "fe80::204:61ff:254.157.241.86/0", + "2001:0DB8::CD30:0:0:0:0/60", + "2001:0DB8::CD30:0/90", + "::1/128") + BAD_CIDRS = ("1111:2222:3333:4444:::/", + "::2222:3333:4444:5555:6666:7777:8888:\\", + ":1111:2222:3333:4444::6666:1.2.3.4/1000", + "1111:2222::4444:5555:6666::8888@", + "1111:2222::4444:5555:6666:8888/", + "::ffff:0:0/129", + "1.2.3.4:1111:2222::5555//22", + "fe80::204:61ff:254.157.241.86/200", + # some valid IPv4 addresses + "10.144.11.107/4", + "255.255.255.255/0", + "0.1.2.3/16") + ip = forms.IPField(mask=True, version=forms.IPv6) + for cidr in GOOD_CIDRS: + self.assertIsNone(ip.validate(cidr)) + for cidr in BAD_CIDRS: + self.assertRaises(ValidationError, ip.validate, cidr) + + def test_validate_mixed_cidr(self): + GOOD_CIDRS = ("::ffff:0:0/56", + "2001:0db8::1428:57ab/17", + "FEC0::/10", + "fe80::204:61ff:254.157.241.86/4", + "fe80::204:61ff:254.157.241.86/0", + "2001:0DB8::CD30:0:0:0:0/60", + "0.0.0.0/16", + "10.144.11.107/4", + "255.255.255.255/0", + "0.1.2.3/16", + # short form + "128.0/16", + "10/4") + BAD_CIDRS = ("1111:2222:3333:4444::://", + "::2222:3333:4444:5555:6666:7777:8888:", + ":1111:2222:3333:4444::6666:1.2.3.4/1/1", + "1111:2222::4444:5555:6666::8888\\2", + "1111:2222::4444:5555:6666:8888/", + "1111:2222::4444:5555:6666::8888/130", + "127.0.0.1/", + "127.0.0.1/33", + "127.0.0.1/-1") + ip = forms.IPField(mask=True, version=forms.IPv4 | forms.IPv6) + for cidr in GOOD_CIDRS: + self.assertIsNone(ip.validate(cidr)) + for cidr in BAD_CIDRS: + self.assertRaises(ValidationError, ip.validate, cidr) + + def test_validate_IPs(self): + GOOD_IPS_V4 = ("0.0.0.0", + "10.144.11.107", + "169.144.11.107", + "172.100.11.107", + "255.255.255.255", + "0.1.2.3") + GOOD_IPS_V6 = ("", + "::ffff:0:0", + "2001:0db8::1428:57ab", + "FEC0::", + "fe80::204:61ff:254.157.241.86", + "fe80::204:61ff:254.157.241.86", + "2001:0DB8::CD30:0:0:0:0") + BAD_IPS_V4 = ("1111:2222:3333:4444:::", + "::2222:3333:4444:5555:6666:7777:8888:", + ":1111:2222:3333:4444::6666:1.2.3.4", + "1111:2222::4444:5555:6666::8888", + "1111:2222::4444:5555:6666:8888/", + "1111:2222::4444:5555:6666::8888/130", + "127.0.0.1/", + "127.0.0.1/33", + "127.0.0.1/-1") + BAD_IPS_V6 = ("1111:2222:3333:4444:::", + "::2222:3333:4444:5555:6666:7777:8888:", + ":1111:2222:3333:4444::6666:1.2.3.4", + "1111:2222::4444:5555:6666::8888", + "1111:2222::4444:5555:6666:8888/", + "1111:2222::4444:5555:6666::8888/130") + ipv4 = forms.IPField(required=True, version=forms.IPv4) + ipv6 = forms.IPField(required=False, version=forms.IPv6) + ipmixed = forms.IPField(required=False, + version=forms.IPv4 | forms.IPv6) + + for ip_addr in GOOD_IPS_V4: + self.assertIsNone(ipv4.validate(ip_addr)) + self.assertIsNone(ipmixed.validate(ip_addr)) + + for ip_addr in GOOD_IPS_V6: + self.assertIsNone(ipv6.validate(ip_addr)) + self.assertIsNone(ipmixed.validate(ip_addr)) + + for ip_addr in BAD_IPS_V4: + self.assertRaises(ValidationError, ipv4.validate, ip_addr) + self.assertRaises(ValidationError, ipmixed.validate, ip_addr) + + for ip_addr in BAD_IPS_V6: + self.assertRaises(ValidationError, ipv6.validate, ip_addr) + self.assertRaises(ValidationError, ipmixed.validate, ip_addr) + + self.assertRaises(ValidationError, ipv4.validate, "") # required=True + + iprange = forms.IPField(required=False, + mask=True, + mask_range_from=10, + version=forms.IPv4 | forms.IPv6) + self.assertRaises(ValidationError, iprange.validate, + "fe80::204:61ff:254.157.241.86/6") + self.assertRaises(ValidationError, iprange.validate, + "169.144.11.107/8") + self.assertIsNone(iprange.validate("fe80::204:61ff:254.157.241.86/36")) + self.assertIsNone(iprange.validate("169.144.11.107/18")) + + def test_validate_multi_ip_field(self): + GOOD_CIDRS_INPUT = ("192.168.1.1/16, 192.0.0.1/17",) + BAD_CIDRS_INPUT = ("1.2.3.4.5/41,0.0.0.0/99", + "1.2.3.4.5/41;0.0.0.0/99", + "1.2.3.4.5/41 0.0.0.0/99", + "192.168.1.1/16 192.0.0.1/17") + + ip = forms.MultiIPField(mask=True, version=forms.IPv4) + for cidr in GOOD_CIDRS_INPUT: + self.assertIsNone(ip.validate(cidr)) + for cidr in BAD_CIDRS_INPUT: + self.assertRaises(ValidationError, ip.validate, cidr) + + +class MACAddressFieldTests(test.TestCase): + + def test_mac_address_validator(self): + GOOD_MAC_ADDRESSES = ( + "00:11:88:99:Aa:Ff", + "00-11-88-99-Aa-Ff", + "0011.8899.AaFf", + "00118899AaFf", + ) + BAD_MAC_ADDRESSES = ( + "not a mac", + "11:22:33:44:55", + "zz:11:22:33:44:55", + ) + + field = forms.MACAddressField() + for input in GOOD_MAC_ADDRESSES: + self.assertIsNone(field.validate(input)) + for input in BAD_MAC_ADDRESSES: + self.assertRaises(ValidationError, field.validate, input) + + def test_mac_address_normal_form(self): + field = forms.MACAddressField() + field.validate("00-11-88-99-Aa-Ff") + self.assertEqual(field.mac_address, "00:11:88:99:aa:ff") + + +class TestChoiceFieldForm(forms.SelfHandlingForm): + title_dic = {"label1": {"title": "This is choice 1"}, + "label2": {"title": "This is choice 2"}, + "label3": {"title": "This is choice 3"}} + name = forms.CharField(max_length=255, + label="Test Name", + help_text="Please enter a name") + test_choices = forms.ChoiceField( + label="Test Choices", + required=False, + help_text="Testing drop down choices", + widget=forms.fields.SelectWidget( + attrs={ + 'class': 'switchable', + 'data-slug': 'source'}, + transform_html_attrs=title_dic.get)) + + def __init__(self, request, *args, **kwargs): + super(TestChoiceFieldForm, self).__init__(request, *args, + **kwargs) + choices = ([('choice1', 'label1'), + ('choice2', 'label2')]) + self.fields['test_choices'].choices = choices + + def handle(self, request, data): + return True + + +class ChoiceFieldTests(test.TestCase): + + template = 'horizon/common/_form_fields.html' + + def setUp(self): + super(ChoiceFieldTests, self).setUp() + self.form = TestChoiceFieldForm(self.request) + + def _render_form(self): + return shortcuts.render(self.request, self.template, + {'form': self.form}) + + def test_legacychoicefield_title(self): + resp = self._render_form() + self.assertContains( + resp, + '', + count=1, html=True) + self.assertContains( + resp, + '', + count=1, html=True) + + +class TestThemableChoiceFieldForm(forms.SelfHandlingForm): + # It's POSSIBLE to combine this with the test helper form above, but + # I fear we'd run into collisions where one test's desired output is + # actually within a separate widget's output. + + title_dic = {"label1": {"title": "This is choice 1"}, + "label2": {"title": "This is choice 2"}, + "label3": {"title": "This is choice 3"}} + name = forms.CharField(max_length=255, + label="Test Name", + help_text="Please enter a name") + test_choices = forms.ThemableChoiceField( + label="Test Choices", + required=False, + help_text="Testing drop down choices", + widget=forms.fields.ThemableSelectWidget( + attrs={ + 'class': 'switchable', + 'data-slug': 'source'}, + transform_html_attrs=title_dic.get)) + + def __init__(self, request, *args, **kwargs): + super(TestThemableChoiceFieldForm, self).__init__(request, *args, + **kwargs) + choices = ([('choice1', 'label1'), + ('choice2', 'label2')]) + self.fields['test_choices'].choices = choices + + def handle(self, request, data): + return True + + +class ThemableChoiceFieldTests(test.TestCase): + + template = 'horizon/common/_form_fields.html' + + def setUp(self): + super(ThemableChoiceFieldTests, self).setUp() + self.form = TestThemableChoiceFieldForm(self.request) + + def _render_form(self): + return shortcuts.render(self.request, self.template, + {'form': self.form}) + + def test_choicefield_labels_and_title_attr(self): + resp = self._render_form() + self.assertContains( + resp, + '' + 'label1', + count=1, + html=True) + self.assertContains( + resp, + '' + 'label2', + count=1, + html=True) + + def test_choicefield_title_select_compatible(self): + resp = self._render_form() + self.assertContains( + resp, + '', + count=1, html=True) + self.assertContains( + resp, + '', + count=1, html=True) diff --git a/horizon/test/unit/forms/test_forms.py b/horizon/test/unit/forms/test_forms.py new file mode 100644 index 0000000000..8660ffb139 --- /dev/null +++ b/horizon/test/unit/forms/test_forms.py @@ -0,0 +1,106 @@ +# +# 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 django import shortcuts + +from horizon import forms +from horizon.test import helpers as test + + +class FormMixinTests(test.TestCase): + + def _prepare_view(self, cls, request_headers, *args, **kwargs): + req = self.factory.get('/my_url/', **request_headers) + req.user = self.user + view = cls() + view.request = req + view.args = args + view.kwargs = kwargs + view.template_name = 'test_template' + # Note(Itxaka): ModalFormView requires a form_class to behave properly + view.form_class = TestForm + return view + + def test_modal_form_mixin_hide_true_if_ajax(self): + view = self._prepare_view( + forms.views.ModalFormView, + dict(HTTP_X_REQUESTED_WITH='XMLHttpRequest')) + context = view.get_context_data() + self.assertTrue(context['hide']) + + def test_modal_form_mixin_add_to_field_header_set(self): + return self._test_form_mixin_add_to_field_header(add_field=True) + + def test_modal_form_mixin_add_to_field_header_not_set(self): + return self._test_form_mixin_add_to_field_header(add_field=False) + + def _test_form_mixin_add_to_field_header(self, add_field=False): + options = dict(HTTP_X_REQUESTED_WITH='XMLHttpRequest') + if add_field: + options[forms.views.ADD_TO_FIELD_HEADER] = "keepme" + + view = self._prepare_view(forms.views.ModalFormView, options) + context = view.get_context_data() + + if add_field: + self.assertEqual("keepme", context['add_to_field']) + else: + self.assertNotIn('add_to_field', context) + + def test_template_name_change_based_on_ajax_request(self): + view = self._prepare_view( + forms.views.ModalFormView, + dict(HTTP_X_REQUESTED_WITH='XMLHttpRequest')) + self.assertEqual('_' + view.template_name, + view.get_template_names()) + + view = self._prepare_view(forms.views.ModalFormView, {}) + self.assertEqual(view.template_name, view.get_template_names()) + + +class TestForm(forms.SelfHandlingForm): + + name = forms.CharField(max_length=255) + + def handle(self, request, data): + return True + + +class FormErrorTests(test.TestCase): + + template = 'horizon/common/_form_fields.html' + + def setUp(self): + super(FormErrorTests, self).setUp() + # Note(Itxaka): We pass data to the form so its bound and has the + # proper cleaned_data fields + self.form = TestForm(self.request, data={'fake': 'data'}) + + def _render_form(self): + return shortcuts.render(self.request, self.template, + {'form': self.form}) + + def test_set_warning(self): + warning_text = 'WARNING 29380' + self.form.set_warning(warning_text) + self.assertEqual([warning_text], self.form.warnings) + resp = self._render_form() + self.assertIn(warning_text.encode('utf-8'), resp.content) + + def test_api_error(self): + error_text = 'ERROR 12938' + self.form.full_clean() + self.form.api_error(error_text) + self.assertEqual([error_text], self.form.non_field_errors()) + resp = self._render_form() + self.assertIn(error_text.encode('utf-8'), resp.content) diff --git a/horizon/test/unit/hacking/__init__.py b/horizon/test/unit/hacking/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/horizon/test/test_hacking.py b/horizon/test/unit/hacking/test_checks.py similarity index 100% rename from horizon/test/test_hacking.py rename to horizon/test/unit/hacking/test_checks.py diff --git a/horizon/test/unit/management/__init__.py b/horizon/test/unit/management/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/horizon/test/unit/management/commands/__init__.py b/horizon/test/unit/management/commands/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/horizon/test/tests/test_commands.py b/horizon/test/unit/management/commands/test_startdash.py similarity index 67% rename from horizon/test/tests/test_commands.py rename to horizon/test/unit/management/commands/test_startdash.py index 9e5f9a2861..2b2a51446b 100644 --- a/horizon/test/tests/test_commands.py +++ b/horizon/test/unit/management/commands/test_startdash.py @@ -19,7 +19,6 @@ from django.core.management import CommandError from django.test import TestCase from horizon.management.commands import startdash -from horizon.management.commands import startpanel class CommandsTestCase(TestCase): @@ -36,16 +35,3 @@ class CommandsTestCase(TestCase): files=[], no_color=False, pythonpath=None, settings=None, skip_checks=True, target=None, template=None, traceback=False, verbosity=1) - - def test_startpanel_usage_empty(self): - self.assertRaises(CommandError, call_command, 'startpanel') - - @mock.patch.object(startpanel.Command, 'handle', return_value='') - def test_startpanel_usage_correct(self, handle): - call_command('startpanel', 'test_dash', '--dashboard=foo.bar') - - handle.assert_called_with(panel_name='test_dash', dashboard='foo.bar', - extensions=["py", "tmpl", "html"], - files=[], no_color=False, pythonpath=None, - settings=None, skip_checks=True, target=None, - template=None, traceback=False, verbosity=1) diff --git a/horizon/test/unit/management/commands/test_startpanel.py b/horizon/test/unit/management/commands/test_startpanel.py new file mode 100644 index 0000000000..eb4d08be69 --- /dev/null +++ b/horizon/test/unit/management/commands/test_startpanel.py @@ -0,0 +1,36 @@ +# Copyright 2015, Rackspace, US, Inc. +# +# 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. + +import mock + +from django.core.management import call_command +from django.core.management import CommandError +from django.test import TestCase + +from horizon.management.commands import startpanel + + +class CommandsTestCase(TestCase): + def test_startpanel_usage_empty(self): + self.assertRaises(CommandError, call_command, 'startpanel') + + @mock.patch.object(startpanel.Command, 'handle', return_value='') + def test_startpanel_usage_correct(self, handle): + call_command('startpanel', 'test_dash', '--dashboard=foo.bar') + + handle.assert_called_with(panel_name='test_dash', dashboard='foo.bar', + extensions=["py", "tmpl", "html"], + files=[], no_color=False, pythonpath=None, + settings=None, skip_checks=True, target=None, + template=None, traceback=False, verbosity=1) diff --git a/horizon/test/unit/middleware/__init__.py b/horizon/test/unit/middleware/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/horizon/test/unit/middleware/test_base.py b/horizon/test/unit/middleware/test_base.py new file mode 100644 index 0000000000..6e60272e08 --- /dev/null +++ b/horizon/test/unit/middleware/test_base.py @@ -0,0 +1,80 @@ +# Copyright 2012 OpenStack Foundation +# All Rights Reserved. +# +# 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. + +import django +from django.conf import settings +from django.http import HttpResponseRedirect +from django.utils import timezone + +from horizon import exceptions +from horizon import middleware +from horizon.test import helpers as test + + +class MiddlewareTests(test.TestCase): + + def setUp(self): + self._timezone_backup = timezone.get_current_timezone_name() + return super(MiddlewareTests, self).setUp() + + def tearDown(self): + timezone.activate(self._timezone_backup) + return super(MiddlewareTests, self).tearDown() + + def test_redirect_login_fail_to_login(self): + url = settings.LOGIN_URL + request = self.factory.post(url) + + mw = middleware.HorizonMiddleware() + resp = mw.process_exception(request, exceptions.NotAuthenticated()) + resp.client = self.client + + if django.VERSION >= (1, 9): + self.assertRedirects(resp, settings.TESTSERVER + url) + else: + self.assertRedirects(resp, url) + + def test_process_response_redirect_on_ajax_request(self): + url = settings.LOGIN_URL + mw = middleware.HorizonMiddleware() + + request = self.factory.post(url, + HTTP_X_REQUESTED_WITH='XMLHttpRequest') + request.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' + request.horizon = {'async_messages': + [('error', 'error_msg', 'extra_tag')]} + + response = HttpResponseRedirect(url) + response.client = self.client + + resp = mw.process_response(request, response) + self.assertEqual(200, resp.status_code) + self.assertEqual(url, resp['X-Horizon-Location']) + + def test_timezone_awareness(self): + url = settings.LOGIN_REDIRECT_URL + mw = middleware.HorizonMiddleware() + + request = self.factory.get(url) + request.session['django_timezone'] = 'America/Chicago' + mw.process_request(request) + self.assertEqual( + timezone.get_current_timezone_name(), 'America/Chicago') + request.session['django_timezone'] = 'Europe/Paris' + mw.process_request(request) + self.assertEqual(timezone.get_current_timezone_name(), 'Europe/Paris') + request.session['django_timezone'] = 'UTC' + mw.process_request(request) + self.assertEqual(timezone.get_current_timezone_name(), 'UTC') diff --git a/horizon/test/tests/middleware.py b/horizon/test/unit/middleware/test_operation_log.py similarity index 76% rename from horizon/test/tests/middleware.py rename to horizon/test/unit/middleware/test_operation_log.py index 9994650daf..1f6af561a1 100644 --- a/horizon/test/tests/middleware.py +++ b/horizon/test/unit/middleware/test_operation_log.py @@ -14,75 +14,15 @@ # under the License. from mock import patch -import django from django.conf import settings from django.core.exceptions import MiddlewareNotUsed from django.http import HttpResponseRedirect from django.test.utils import override_settings -from django.utils import timezone -from horizon import exceptions from horizon import middleware from horizon.test import helpers as test -class MiddlewareTests(test.TestCase): - - def setUp(self): - self._timezone_backup = timezone.get_current_timezone_name() - return super(MiddlewareTests, self).setUp() - - def tearDown(self): - timezone.activate(self._timezone_backup) - return super(MiddlewareTests, self).tearDown() - - def test_redirect_login_fail_to_login(self): - url = settings.LOGIN_URL - request = self.factory.post(url) - - mw = middleware.HorizonMiddleware() - resp = mw.process_exception(request, exceptions.NotAuthenticated()) - resp.client = self.client - - if django.VERSION >= (1, 9): - self.assertRedirects(resp, settings.TESTSERVER + url) - else: - self.assertRedirects(resp, url) - - def test_process_response_redirect_on_ajax_request(self): - url = settings.LOGIN_URL - mw = middleware.HorizonMiddleware() - - request = self.factory.post(url, - HTTP_X_REQUESTED_WITH='XMLHttpRequest') - request.META['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - request.horizon = {'async_messages': - [('error', 'error_msg', 'extra_tag')]} - - response = HttpResponseRedirect(url) - response.client = self.client - - resp = mw.process_response(request, response) - self.assertEqual(200, resp.status_code) - self.assertEqual(url, resp['X-Horizon-Location']) - - def test_timezone_awareness(self): - url = settings.LOGIN_REDIRECT_URL - mw = middleware.HorizonMiddleware() - - request = self.factory.get(url) - request.session['django_timezone'] = 'America/Chicago' - mw.process_request(request) - self.assertEqual( - timezone.get_current_timezone_name(), 'America/Chicago') - request.session['django_timezone'] = 'Europe/Paris' - mw.process_request(request) - self.assertEqual(timezone.get_current_timezone_name(), 'Europe/Paris') - request.session['django_timezone'] = 'UTC' - mw.process_request(request) - self.assertEqual(timezone.get_current_timezone_name(), 'UTC') - - class OperationLogMiddlewareTest(test.TestCase): http_host = u'test_host' diff --git a/horizon/test/unit/tables/__init__.py b/horizon/test/unit/tables/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/horizon/test/tests/tables.py b/horizon/test/unit/tables/test_tables.py similarity index 100% rename from horizon/test/tests/tables.py rename to horizon/test/unit/tables/test_tables.py diff --git a/horizon/test/unit/tabs/__init__.py b/horizon/test/unit/tabs/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/horizon/test/tests/tabs.py b/horizon/test/unit/tabs/test_tabs.py similarity index 99% rename from horizon/test/tests/tabs.py rename to horizon/test/unit/tabs/test_tabs.py index d69fa27643..91a3421b15 100644 --- a/horizon/test/tests/tabs.py +++ b/horizon/test/unit/tabs/test_tabs.py @@ -26,8 +26,8 @@ from horizon import middleware from horizon import tabs as horizon_tabs from horizon.test import helpers as test -from horizon.test.tests.tables import MyTable -from horizon.test.tests.tables import TEST_DATA +from horizon.test.unit.tables.test_tables import MyTable +from horizon.test.unit.tables.test_tables import TEST_DATA class BaseTestTab(horizon_tabs.Tab): diff --git a/horizon/test/unit/templatetags/__init__.py b/horizon/test/unit/templatetags/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/horizon/test/tests/templatetags.py b/horizon/test/unit/templatetags/test_templatetags.py similarity index 100% rename from horizon/test/tests/templatetags.py rename to horizon/test/unit/templatetags/test_templatetags.py diff --git a/horizon/test/tests/base.py b/horizon/test/unit/test_base.py similarity index 100% rename from horizon/test/tests/base.py rename to horizon/test/unit/test_base.py diff --git a/horizon/test/tests/exceptions.py b/horizon/test/unit/test_exceptions.py similarity index 100% rename from horizon/test/tests/exceptions.py rename to horizon/test/unit/test_exceptions.py diff --git a/horizon/test/tests/messages.py b/horizon/test/unit/test_messages.py similarity index 100% rename from horizon/test/tests/messages.py rename to horizon/test/unit/test_messages.py diff --git a/horizon/test/tests/notifications.py b/horizon/test/unit/test_notifications.py similarity index 89% rename from horizon/test/tests/notifications.py rename to horizon/test/unit/test_notifications.py index c89aabb040..82a3f8348d 100644 --- a/horizon/test/tests/notifications.py +++ b/horizon/test/unit/test_notifications.py @@ -17,7 +17,7 @@ import os from django.conf import settings from horizon import exceptions -from horizon.notifications import JSONMessage +from horizon import notifications from horizon.test import helpers as test @@ -27,7 +27,7 @@ class NotificationTests(test.TestCase): 'messages')) def _test_msg(self, path, expected_level, expected_msg=''): - msg = JSONMessage(path) + msg = notifications.JSONMessage(path) msg.load() self.assertEqual(expected_level, msg.level_name) @@ -47,13 +47,13 @@ class NotificationTests(test.TestCase): path = self.MESSAGES_PATH + '/test_invalid.json' with self.assertRaises(exceptions.MessageFailure): - msg = JSONMessage(path) + msg = notifications.JSONMessage(path) msg.load() def test_invalid_msg_file_fail_silently(self): path = self.MESSAGES_PATH + '/test_invalid.json' - msg = JSONMessage(path, fail_silently=True) + msg = notifications.JSONMessage(path, fail_silently=True) msg.load() self.assertTrue(msg.failed) diff --git a/horizon/test/tests/views.py b/horizon/test/unit/test_views.py similarity index 100% rename from horizon/test/tests/views.py rename to horizon/test/unit/test_views.py diff --git a/horizon/test/unit/utils/__init__.py b/horizon/test/unit/utils/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/horizon/test/tests/babel_extract_angular.py b/horizon/test/unit/utils/test_babel_extract_angular.py similarity index 100% rename from horizon/test/tests/babel_extract_angular.py rename to horizon/test/unit/utils/test_babel_extract_angular.py diff --git a/horizon/test/tests/test_file_discovery.py b/horizon/test/unit/utils/test_file_discovery.py similarity index 100% rename from horizon/test/tests/test_file_discovery.py rename to horizon/test/unit/utils/test_file_discovery.py diff --git a/horizon/test/unit/utils/test_filters.py b/horizon/test/unit/utils/test_filters.py new file mode 100644 index 0000000000..458ad40a58 --- /dev/null +++ b/horizon/test/unit/utils/test_filters.py @@ -0,0 +1,100 @@ +# 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. + +import datetime + +import django.template +from django.template import defaultfilters + +from horizon.test import helpers as test +from horizon.utils import filters +# we have to import the filter in order to register it +from horizon.utils.filters import parse_isotime # noqa: F401 + + +class FiltersTests(test.TestCase): + def test_replace_underscore_filter(self): + res = filters.replace_underscores("__under_score__") + self.assertEqual(" under score ", res) + + def test_parse_isotime_filter(self): + c = django.template.Context({'time': ''}) + t = django.template.Template('{{ time|parse_isotime }}') + output = u"" + + self.assertEqual(output, t.render(c)) + + c = django.template.Context({'time': 'error'}) + t = django.template.Template('{{ time|parse_isotime }}') + output = u"" + + self.assertEqual(output, t.render(c)) + + c = django.template.Context({'time': 'error'}) + t = django.template.Template('{{ time|parse_isotime:"test" }}') + output = u"test" + + self.assertEqual(output, t.render(c)) + + c = django.template.Context({'time': '2007-03-04T21:08:12'}) + t = django.template.Template('{{ time|parse_isotime:"test" }}') + output = u"March 4, 2007, 3:08 p.m." + + self.assertEqual(output, t.render(c)) + + adate = '2007-01-25T12:00:00Z' + result = filters.parse_isotime(adate) + self.assertIsInstance(result, datetime.datetime) + + +class TimeSinceNeverFilterTests(test.TestCase): + + default = u"Never" + + def test_timesince_or_never_returns_default_for_empty_string(self): + c = django.template.Context({'time': ''}) + t = django.template.Template('{{ time|timesince_or_never }}') + self.assertEqual(self.default, t.render(c)) + + def test_timesince_or_never_returns_default_for_none(self): + c = django.template.Context({'time': None}) + t = django.template.Template('{{ time|timesince_or_never }}') + self.assertEqual(self.default, t.render(c)) + + def test_timesince_or_never_returns_default_for_gibberish(self): + c = django.template.Context({'time': django.template.Context()}) + t = django.template.Template('{{ time|timesince_or_never }}') + self.assertEqual(self.default, t.render(c)) + + def test_timesince_or_never_returns_with_custom_default(self): + custom = "Hello world" + c = django.template.Context({'date': ''}) + t = django.template.Template('{{ date|timesince_or_never:"%s" }}' + % custom) + self.assertEqual(custom, t.render(c)) + + def test_timesince_or_never_returns_with_custom_empty_string_default(self): + c = django.template.Context({'date': ''}) + t = django.template.Template('{{ date|timesince_or_never:"" }}') + self.assertEqual("", t.render(c)) + + def test_timesince_or_never_returns_same_output_as_django_date(self): + d = datetime.date(year=2014, month=3, day=7) + c = django.template.Context({'date': d}) + t = django.template.Template('{{ date|timesince_or_never }}') + self.assertEqual(defaultfilters.timesince(d), t.render(c)) + + def test_timesince_or_never_returns_same_output_as_django_datetime(self): + now = datetime.datetime.now() + c = django.template.Context({'date': now}) + t = django.template.Template('{{ date|timesince_or_never }}') + self.assertEqual(defaultfilters.timesince(now), t.render(c)) diff --git a/horizon/test/unit/utils/test_functions.py b/horizon/test/unit/utils/test_functions.py new file mode 100644 index 0000000000..1bfd774ddc --- /dev/null +++ b/horizon/test/unit/utils/test_functions.py @@ -0,0 +1,97 @@ +# 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 horizon.test import helpers as test +from horizon.utils import functions + + +class GetConfigValueTests(test.TestCase): + key = 'key' + value = 'value' + requested_url = '/project/instances/' + int_default = 30 + str_default = 'default' + + def test_bad_session_value(self): + request = self.factory.get(self.requested_url) + request.session[self.key] = self.value + res = functions.get_config_value(request, self.key, self.int_default) + self.assertEqual(res, self.int_default) + + def test_bad_cookie_value(self): + request = self.factory.get(self.requested_url) + if self.key in request.session: + del request.session[self.key] + request.COOKIES[self.key] = self.value + res = functions.get_config_value(request, self.key, self.int_default) + self.assertEqual(res, self.int_default) + + def test_float_default_value(self): + default = 30.1 + request = self.factory.get(self.requested_url) + request.session[self.key] = self.value + res = functions.get_config_value(request, self.key, default) + self.assertEqual(res, self.value) + + def test_session_gets_set(self): + request = self.factory.get(self.requested_url) + request.session[self.key] = self.value + functions.get_config_value(request, self.key, self.int_default) + self.assertEqual(request.session[self.key], self.int_default) + + def test_found_in_session(self): + request = self.factory.get(self.requested_url) + request.session[self.key] = self.value + if request.COOKIES.get(self.key): + del request.COOKIES[self.key] + res = functions.get_config_value(request, self.key, self.str_default) + self.assertEqual(res, self.value) + + def test_found_in_cookie(self): + request = self.factory.get(self.requested_url) + if request.session.get(self.key): + del request.session[self.key] + request.COOKIES[self.key] = self.value + res = functions.get_config_value(request, self.key, self.str_default) + self.assertEqual(res, self.value) + + def test_found_in_config(self): + key = 'TESTSERVER' + value = 'http://testserver' + request = self.factory.get(self.requested_url) + if request.session.get(key): + del request.session[key] + if request.COOKIES.get(key): + del request.COOKIES[key] + res = functions.get_config_value(request, key, self.str_default) + self.assertEqual(res, value) + + def test_return_default(self): + key = 'NOT FOUND ANYWHERE' + request = self.factory.get(self.requested_url) + if request.session.get(key): + del request.session[key] + if request.COOKIES.get(key): + del request.COOKIES[key] + res = functions.get_config_value(request, key, self.str_default) + self.assertEqual(res, self.str_default) + + def test_return_default_no_settings(self): + key = 'TESTSERVER' + request = self.factory.get(self.requested_url) + if request.session.get(key): + del request.session[key] + if request.COOKIES.get(key): + del request.COOKIES[key] + res = functions.get_config_value(request, key, self.str_default, + search_in_settings=False) + self.assertEqual(res, self.str_default) diff --git a/horizon/test/unit/utils/test_memoized.py b/horizon/test/unit/utils/test_memoized.py new file mode 100644 index 0000000000..2c68352682 --- /dev/null +++ b/horizon/test/unit/utils/test_memoized.py @@ -0,0 +1,83 @@ +# 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 horizon.test import helpers as test +from horizon.utils import memoized + + +class MemoizedTests(test.TestCase): + def test_memoized_decorator_cache_on_next_call(self): + values_list = [] + + @memoized.memoized + def cache_calls(remove_from): + values_list.append(remove_from) + return True + + def non_cached_calls(remove_from): + values_list.append(remove_from) + return True + + for x in range(0, 5): + non_cached_calls(1) + self.assertEqual(5, len(values_list)) + + values_list = [] + for x in range(0, 5): + cache_calls(1) + self.assertEqual(1, len(values_list)) + + def test_memoized_with_request_call(self): + + chorus = [ + "I", + "Love", + "Rock 'n' Roll", + "put another coin", + "in the Jukebox Baby." + ] + + leader = 'Joan Jett' + group = 'Blackhearts' + + for position, chorus_line in enumerate(chorus): + + changed_args = False + + def some_func(some_param): + if not changed_args: + self.assertEqual(some_param, chorus_line) + else: + self.assertNotEqual(some_param, chorus_line) + self.assertEqual(some_param, group) + return leader + + @memoized.memoized_with_request(some_func, position) + def some_other_func(*args): + return args + + # check chorus_copy[position] is replaced by some_func's + # output + output1 = some_other_func(*chorus) + self.assertEqual(output1[position], leader) + + # Change args used to call the function + chorus_copy = list(chorus) + chorus_copy[position] = group + changed_args = True + # check that some_func is called with a different parameter, and + # that check chorus_copy[position] is replaced by some_func's + # output and some_other_func still called with the same parameters + output2 = some_other_func(*chorus_copy) + self.assertEqual(output2[position], leader) + # check that some_other_func returned a memoized list. + self.assertIs(output1, output2) diff --git a/horizon/test/unit/utils/test_secret_key.py b/horizon/test/unit/utils/test_secret_key.py new file mode 100644 index 0000000000..ac9caecd3c --- /dev/null +++ b/horizon/test/unit/utils/test_secret_key.py @@ -0,0 +1,39 @@ +# 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. + +import os +import unittest + +from horizon.utils import secret_key + + +class SecretKeyTests(unittest.TestCase): + pass + + def test_generate_secret_key(self): + key = secret_key.generate_key(32) + self.assertEqual(32, len(key)) + self.assertNotEqual(key, secret_key.generate_key(32)) + + def test_generate_or_read_key_from_file(self): + key_file = ".test_secret_key_store" + key = secret_key.generate_or_read_from_file(key_file) + + # Consecutive reads should come from the already existing file: + self.assertEqual(secret_key.generate_or_read_from_file(key_file), key) + + # Key file only be read/writable by user: + self.assertEqual(0o600, os.stat(key_file).st_mode & 0o777) + os.chmod(key_file, 0o644) + self.assertRaises(secret_key.FilePermissionError, + secret_key.generate_or_read_from_file, key_file) + os.remove(key_file) diff --git a/horizon/test/unit/utils/test_units.py b/horizon/test/unit/utils/test_units.py new file mode 100644 index 0000000000..207e673b2c --- /dev/null +++ b/horizon/test/unit/utils/test_units.py @@ -0,0 +1,74 @@ +# 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 horizon.test import helpers as test +from horizon.utils import units + + +class UnitsTests(test.TestCase): + def test_is_supported(self): + self.assertTrue(units.is_supported('MB')) + self.assertTrue(units.is_supported('min')) + self.assertFalse(units.is_supported('KWh')) + self.assertFalse(units.is_supported('unknown_unit')) + + def test_is_larger(self): + self.assertTrue(units.is_larger('KB', 'B')) + self.assertTrue(units.is_larger('MB', 'B')) + self.assertTrue(units.is_larger('GB', 'B')) + self.assertTrue(units.is_larger('TB', 'B')) + self.assertTrue(units.is_larger('GB', 'MB')) + self.assertFalse(units.is_larger('B', 'KB')) + self.assertFalse(units.is_larger('MB', 'GB')) + + self.assertTrue(units.is_larger('min', 's')) + self.assertTrue(units.is_larger('hr', 'min')) + self.assertTrue(units.is_larger('hr', 's')) + self.assertFalse(units.is_larger('s', 'min')) + + def test_convert(self): + self.assertEqual(units.convert(4096, 'MB', 'GB'), (4, 'GB')) + self.assertEqual(units.convert(4, 'GB', 'MB'), (4096, 'MB')) + + self.assertEqual(units.convert(1.5, 'hr', 'min'), (90, 'min')) + self.assertEqual(units.convert(12, 'hr', 'day'), (0.5, 'day')) + + def test_normalize(self): + self.assertEqual(units.normalize(1, 'B'), (1, 'B')) + self.assertEqual(units.normalize(1000, 'B'), (1000, 'B')) + self.assertEqual(units.normalize(1024, 'B'), (1, 'KB')) + self.assertEqual(units.normalize(1024 * 1024, 'B'), (1, 'MB')) + self.assertEqual(units.normalize(10 * 1024 ** 3, 'B'), (10, 'GB')) + self.assertEqual(units.normalize(1000 * 1024 ** 4, 'B'), (1000, 'TB')) + self.assertEqual(units.normalize(1024, 'KB'), (1, 'MB')) + self.assertEqual(units.normalize(1024 ** 2, 'KB'), (1, 'GB')) + self.assertEqual(units.normalize(10 * 1024, 'MB'), (10, 'GB')) + self.assertEqual(units.normalize(0.5, 'KB'), (512, 'B')) + self.assertEqual(units.normalize(0.0001, 'MB'), (104.9, 'B')) + + self.assertEqual(units.normalize(1, 's'), (1, 's')) + self.assertEqual(units.normalize(120, 's'), (2, 'min')) + self.assertEqual(units.normalize(3600, 's'), (60, 'min')) + self.assertEqual(units.normalize(3600 * 24, 's'), (24, 'hr')) + self.assertEqual(units.normalize(10 * 3600 * 24, 's'), (10, 'day')) + self.assertEqual(units.normalize(90, 'min'), (90, 'min')) + self.assertEqual(units.normalize(150, 'min'), (2.5, 'hr')) + self.assertEqual(units.normalize(60 * 24, 'min'), (24, 'hr')) + self.assertEqual(units.normalize(0.5, 'day'), (12, 'hr')) + self.assertEqual(units.normalize(10800000000000, 'ns'), (3, 'hr')) + self.assertEqual(units.normalize(14, 'day'), (2, 'week')) + self.assertEqual(units.normalize(91, 'day'), (3, 'month')) + self.assertEqual(units.normalize(18, 'month'), (18, 'month')) + self.assertEqual(units.normalize(24, 'month'), (2, 'year')) + + self.assertEqual(units.normalize(1, 'unknown_unit'), + (1, 'unknown_unit')) diff --git a/horizon/test/unit/utils/test_validators.py b/horizon/test/unit/utils/test_validators.py new file mode 100644 index 0000000000..f9fe58dae3 --- /dev/null +++ b/horizon/test/unit/utils/test_validators.py @@ -0,0 +1,99 @@ +# 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 django.core.exceptions import ValidationError + +from horizon.test import helpers as test +from horizon.utils import validators + + +class ValidatorsTests(test.TestCase): + + def test_port_validator(self): + VALID_PORTS = (1, 65535) + INVALID_PORTS = (-1, 65536) + + for port in VALID_PORTS: + self.assertIsNone(validators.validate_port_range(port)) + + for port in INVALID_PORTS: + self.assertRaises(ValidationError, + validators.validate_port_range, + port) + + def test_icmp_type_validator(self): + VALID_ICMP_TYPES = (1, 0, 255, -1) + INVALID_ICMP_TYPES = (256, None, -2) + + for icmp_type in VALID_ICMP_TYPES: + self.assertIsNone(validators.validate_icmp_type_range(icmp_type)) + + for icmp_type in INVALID_ICMP_TYPES: + self.assertRaises(ValidationError, + validators.validate_icmp_type_range, + icmp_type) + + def test_icmp_code_validator(self): + VALID_ICMP_CODES = (1, 0, 255, None, -1,) + INVALID_ICMP_CODES = (256, -2) + + for icmp_code in VALID_ICMP_CODES: + self.assertIsNone(validators.validate_icmp_code_range(icmp_code)) + + for icmp_code in INVALID_ICMP_CODES: + self.assertRaises(ValidationError, + validators.validate_icmp_code_range, + icmp_code) + + def test_ip_proto_validator(self): + VALID_PROTO = (0, 255, -1) + INVALID_PROTO = (-2, 256) + + for proto in VALID_PROTO: + self.assertIsNone(validators.validate_ip_protocol(proto)) + + for proto in INVALID_PROTO: + self.assertRaises(ValidationError, + validators.validate_ip_protocol, + proto) + + def test_port_range_validator(self): + VALID_RANGE = ('1:65535', + '1:1') + INVALID_RANGE = ('22:22:22:22', + '1:-1', + '-1:65535') + + test_call = validators.validate_port_or_colon_separated_port_range + for prange in VALID_RANGE: + self.assertIsNone(test_call(prange)) + + for prange in INVALID_RANGE: + self.assertRaises(ValidationError, test_call, prange) + + def test_metadata_validator(self): + VALID_METADATA = ( + "key1=val1", "key1=val1,key2=val2", + "key1=val1,key2=val2,key3=val3", "key1=" + ) + INVALID_METADATA = ( + "key1==val1", "key1=val1,", "=val1", + "=val1", " " + ) + + for mdata in VALID_METADATA: + self.assertIsNone(validators.validate_metadata(mdata)) + + for mdata in INVALID_METADATA: + self.assertRaises(ValidationError, + validators.validate_metadata, + mdata) diff --git a/horizon/test/unit/workflows/__init__.py b/horizon/test/unit/workflows/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/horizon/test/tests/workflows.py b/horizon/test/unit/workflows/test_workflows.py similarity index 99% rename from horizon/test/tests/workflows.py rename to horizon/test/unit/workflows/test_workflows.py index fd9350e3bd..6ac215e22b 100644 --- a/horizon/test/tests/workflows.py +++ b/horizon/test/unit/workflows/test_workflows.py @@ -110,7 +110,8 @@ class TestStepTwo(workflows.Step): contributes = ("instance_id",) connections = {"project_id": (local_callback_func, - "horizon.test.tests.workflows.other_callback_func")} + "horizon.test.unit.workflows.test_workflows." + "other_callback_func")} class TestExtraStep(workflows.Step):