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):