From 5d07a1853dcb0e456577f125346593621e573a7c Mon Sep 17 00:00:00 2001 From: Arona Jones Date: Fri, 29 Jan 2021 18:24:37 +0000 Subject: [PATCH] Further template refactoring --- PyRIGS/tests/base.py | 5 +- PyRIGS/tests/regions.py | 4 +- RIGS/rigboard.py | 2 +- RIGS/tests/test_functional.py | 136 +++++++++++++++++++------------ RIGS/tests/test_interaction.py | 2 +- RIGS/tests/test_unit.py | 18 ++-- assets/tests/pages.py | 27 +----- assets/tests/test_interaction.py | 44 ++++++---- assets/tests/test_unit.py | 29 ++++--- conftest.py | 8 ++ 10 files changed, 149 insertions(+), 126 deletions(-) create mode 100644 conftest.py diff --git a/PyRIGS/tests/base.py b/PyRIGS/tests/base.py index a6fc7689..7056b1e8 100644 --- a/PyRIGS/tests/base.py +++ b/PyRIGS/tests/base.py @@ -32,7 +32,7 @@ class BaseTest(LiveServerTestCase): def setUp(self): super().setUpClass() self.driver = create_browser() - self.wait = WebDriverWait(self.driver, 5) + self.wait = WebDriverWait(self.driver, 15) def tearDown(self): super().tearDown() @@ -76,6 +76,3 @@ def screenshot_failure_cls(cls): def assert_times_equal(first_time, second_time): assert first_time.replace(microsecond=0) == second_time.replace(microsecond=0) - -def response_contains(response, needle): - return str(needle) in str(response.content) diff --git a/PyRIGS/tests/regions.py b/PyRIGS/tests/regions.py index 2523bfbf..f7ddbe28 100644 --- a/PyRIGS/tests/regions.py +++ b/PyRIGS/tests/regions.py @@ -8,7 +8,6 @@ from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.select import Select - def parse_bool_from_string(string): # Used to convert from attribute strings to boolean values, written after I found this: # >>> bool("false") @@ -75,8 +74,7 @@ class BootstrapSelectElement(Region): self.open() search_box.clear() search_box.send_keys(query) - status_text = self.find_element(*self._status_locator) - self.wait.until(expected_conditions.invisibility_of_element_located(self._status_locator)) + self.wait.until(expected_conditions.invisibility_of_element_located(*self._status_locator)) @property def options(self): diff --git a/RIGS/rigboard.py b/RIGS/rigboard.py index 44eadd41..7ce12a26 100644 --- a/RIGS/rigboard.py +++ b/RIGS/rigboard.py @@ -291,7 +291,7 @@ class EventAuthorise(generic.UpdateView): self.template_name = self.success_template messages.add_message(self.request, messages.SUCCESS, 'Success! Your event has been authorised. ' + - 'You will also receive email confirmation to %s.' % (self.object.email)) + 'You will also receive email confirmation to %s.' % self.object.email) return self.render_to_response(self.get_context_data()) @property diff --git a/RIGS/tests/test_functional.py b/RIGS/tests/test_functional.py index 2173732b..82ea36d3 100644 --- a/RIGS/tests/test_functional.py +++ b/RIGS/tests/test_functional.py @@ -1,6 +1,7 @@ import datetime from datetime import date +import pytest from django.conf import settings from django.core import mail, signing from django.http import HttpResponseBadRequest @@ -8,6 +9,7 @@ from django.test import TestCase from django.urls import reverse from RIGS import models +from pytest_django.asserts import assertContains, assertNotContains class BaseCase(TestCase): @@ -43,12 +45,32 @@ class TestEventValidation(BaseCase): def test_create(self): url = reverse('event_create') # end time before start access after start - response = self.client.post(url, {'start_date': datetime.date(2020, 1, 1), 'start_time': datetime.time(10, 00), 'end_time': datetime.time(9, 00), 'access_at': datetime.datetime(2020, 1, 5, 10)}) - self.assertFormError(response, 'form', 'end_time', "Unless you've invented time travel, the event can't finish before it has started.") - self.assertFormError(response, 'form', 'access_at', "Regardless of what some clients might think, access time cannot be after the event has started.") + response = self.client.post(url, {'start_date': datetime.date(2020, 1, 1), 'start_time': datetime.time(10, 00), + 'end_time': datetime.time(9, 00), + 'access_at': datetime.datetime(2020, 1, 5, 10)}) + self.assertFormError(response, 'form', 'end_time', + "Unless you've invented time travel, the event can't finish before it has started.") + self.assertFormError(response, 'form', 'access_at', + "Regardless of what some clients might think, access time cannot be after the event has started.") -class ClientEventAuthorisationTest(BaseCase): +def setup_event(): + models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1') + venue = models.Venue.objects.create(name='Authorisation Test Venue') + client = models.Person.objects.create(name='Authorisation Test Person', email='authorisation@functional.test') + organisation = models.Organisation.objects.create(name='Authorisation Test Organisation', union_account=True) + return models.Event.objects.create( + name='Authorisation Test', + start_date=date.today(), + venue=venue, + person=client, + organisation=organisation, + ) + + +def setup_mail(event, profile): + profile.email = "teccie@nottinghamtec.co.uk" + profile.save() auth_data = { 'name': 'Test ABC', 'po': '1234ABCZXY', @@ -56,72 +78,78 @@ class ClientEventAuthorisationTest(BaseCase): 'uni_id': 1234567890, 'tos': True } + hmac = signing.dumps({'pk': event.pk, 'email': 'authemail@function.test', + 'sent_by': profile.pk}) + url = reverse('event_authorise', kwargs={'pk': event.pk, 'hmac': hmac}) + return auth_data, hmac, url - def setUp(self): - super().setUp() - self.hmac = signing.dumps({'pk': self.event.pk, 'email': 'authemail@function.test', - 'sent_by': self.profile.pk}) - self.url = reverse('event_authorise', kwargs={'pk': self.event.pk, 'hmac': self.hmac}) - def test_requires_valid_hmac(self): - bad_hmac = self.hmac[:-1] - url = reverse('event_authorise', kwargs={'pk': self.event.pk, 'hmac': bad_hmac}) - response = self.client.get(url) - self.assertIsInstance(response, HttpResponseBadRequest) - # TODO: Add some form of sensbile user facing error - # self.assertIn(response.content, "new URL") # check there is some level of sane instruction +def test_requires_valid_hmac(client, admin_user): + event = setup_event() + auth_data, hmac, url = setup_mail(event, admin_user) + bad_hmac = hmac[:-1] + url = reverse('event_authorise', kwargs={'pk': event.pk, 'hmac': bad_hmac}) + response = client.get(url) + assert isinstance(response, HttpResponseBadRequest) + # TODO: Add some form of sensible user facing error + # self.assertIn(response.content, "new URL") # check there is some level of sane instruction + # response = client.get(url) + # assertContains(response, event.organisation.name) - response = self.client.get(self.url) - self.assertContains(response, self.event.organisation.name) - def test_validation(self): - response = self.client.get(self.url) - self.assertContains(response, "Terms of Hire") - self.assertContains(response, "Account code") - self.assertContains(response, "University ID") +def test_validation(client, admin_user): + event = setup_event() + auth_data, hmac, url = setup_mail(event, admin_user) + response = client.get(url) + assertContains(response, "Terms of Hire") + assertContains(response, "Account code") + assertContains(response, "University ID") - response = self.client.post(self.url) - self.assertContains(response, "This field is required.", 5) + response = client.post(url) + assertContains(response, "This field is required.", 5) - data = self.auth_data - data['amount'] = self.event.total + 1 + auth_data['amount'] = event.total + 1 - response = self.client.post(self.url, data) - self.assertContains(response, "The amount authorised must equal the total for the event") - self.assertNotContains(response, "This field is required.") + response = client.post(url, auth_data) + assertContains(response, "The amount authorised must equal the total for the event") + assertNotContains(response, "This field is required.") - data['amount'] = self.event.total - response = self.client.post(self.url, data) - self.assertContains(response, "Your event has been authorised") + auth_data['amount'] = event.total + response = client.post(url, auth_data) + assertContains(response, "Your event has been authorised") - self.event.refresh_from_db() - self.assertTrue(self.event.authorised) - self.assertEqual(self.event.authorisation.email, "authemail@function.test") + event.refresh_from_db() + assert event.authorised + assert str(event.authorisation.email) == "authemail@function.test" - def test_duplicate_warning(self): - auth = models.EventAuthorisation.objects.create(event=self.event, name='Test ABC', email='dupe@functional.test', - amount=self.event.total, sent_by=self.profile) - response = self.client.get(self.url) - self.assertContains(response, 'This event has already been authorised.') - auth.amount += 1 - auth.save() +def test_duplicate_warning(client, admin_user): + event = setup_event() + auth_data, hmac, url = setup_mail(event, admin_user) + auth = models.EventAuthorisation.objects.create(event=event, name='Test ABC', email='dupe@functional.test', + amount=event.total, sent_by=admin_user) + response = client.get(url) + assertContains(response, 'This event has already been authorised.') - response = self.client.get(self.url) - self.assertContains(response, 'amount has changed') + auth.amount += 1 + auth.save() - def test_email_sent(self): - mail.outbox = [] + response = client.get(url) + assertContains(response, 'amount has changed') - data = self.auth_data - data['amount'] = self.event.total - response = self.client.post(self.url, data) - self.assertContains(response, "Your event has been authorised.") - self.assertEqual(len(mail.outbox), 2) +@pytest.mark.django_db(transaction=True) +def test_email_sent(admin_client, admin_user, mailoutbox): + event = setup_event() + auth_data, hmac, url = setup_mail(event, admin_user) - self.assertEqual(mail.outbox[0].to, ['authemail@function.test']) - self.assertEqual(mail.outbox[1].to, [settings.AUTHORISATION_NOTIFICATION_ADDRESS]) + data = auth_data + data['amount'] = event.total + response = admin_client.post(url, data) + assertContains(response, "Your event has been authorised.") + assert len(mailoutbox) == 2 + assert mailoutbox[0].to == ['authemail@function.test'] + assert mailoutbox[1].to == [settings.AUTHORISATION_NOTIFICATION_ADDRESS] class TECEventAuthorisationTest(BaseCase): diff --git a/RIGS/tests/test_interaction.py b/RIGS/tests/test_interaction.py index 4048b730..b1e2b47e 100644 --- a/RIGS/tests/test_interaction.py +++ b/RIGS/tests/test_interaction.py @@ -239,7 +239,7 @@ class TestEventCreate(BaseRigboardTest): # Double-check we don't lose data when swapping self.page.select_event_type("Rig") self.wait.until(animation_is_finished()) - self.assertEquals(self.page.name, rig_name) + self.assertEqual(self.page.name, rig_name) self.wait.until(animation_is_finished()) self.page.select_event_type("Non-Rig") diff --git a/RIGS/tests/test_unit.py b/RIGS/tests/test_unit.py index 5a798570..a4288c19 100644 --- a/RIGS/tests/test_unit.py +++ b/RIGS/tests/test_unit.py @@ -6,9 +6,9 @@ from django.test import TestCase from django.test.utils import override_settings from django.urls import reverse, reverse_lazy from django.utils import timezone -from pytest_django.asserts import assertRedirects +from pytest_django.asserts import assertRedirects, assertNotContains, assertContains -from PyRIGS.tests.base import assert_times_equal, response_contains +from PyRIGS.tests.base import assert_times_equal from RIGS import models @@ -360,8 +360,8 @@ def search(client, url, found, notfound, arguments): query = getattr(found, argument) request_url = "%s?q=%s" % (reverse_lazy(url), query) response = client.get(request_url, follow=True) - assert response_contains(response, getattr(found, 'name')) - assert not response_contains(response, getattr(notfound, 'name')) + assertContains(response, getattr(found, 'name')) + assertNotContains(response, getattr(notfound, 'name')) def test_search(admin_client): @@ -428,17 +428,17 @@ def test_list(admin_client): venue, events = setup_for_hs() request_url = reverse('hs_list') response = admin_client.get(request_url, follow=True) - assert response_contains(response, events[1].name) - assert response_contains(response, events[2].name) - assert response_contains(response, 'Create') + assertContains(response, events[1].name) + assertContains(response, events[2].name) + assertContains(response, 'Create') def review(client, profile, obj, request_url): time = timezone.now() response = client.get(reverse(request_url, kwargs={'pk': obj.pk}), follow=True) obj.refresh_from_db() - assert response_contains(response, 'Reviewed by') - assert response_contains(response, profile.name) + assertContains(response, 'Reviewed by') + assertContains(response, profile.name) assert_times_equal(time, obj.reviewed_at) diff --git a/assets/tests/pages.py b/assets/tests/pages.py index a0d9d365..77dc4155 100644 --- a/assets/tests/pages.py +++ b/assets/tests/pages.py @@ -190,10 +190,10 @@ class AssetAuditList(AssetList): def query(self): return self.find_element(*self._search_text_locator).text - def set_query(self, queryString): + def set_query(self, query): element = self.find_element(*self._search_text_locator) element.clear() - element.send_keys(queryString) + element.send_keys(query) def search(self): self.find_element(*self._go_button_locator).click() @@ -206,7 +206,7 @@ class AssetAuditList(AssetList): except NoSuchElementException: return None - class AssetAuditModal(Region): + class AssetAuditModal(regions.Modal): _errors_selector = (By.CLASS_NAME, "alert-danger") # Don't use the usual success selector - that tries and fails to hit the '10m long cable' helper button... _submit_locator = (By.ID, "id_mark_audited") @@ -238,11 +238,6 @@ class AssetAuditList(AssetList): except NoSuchElementException: return None - def submit(self): - self.root.find_element(*self._submit_locator).click() - # self.wait.until(lambda x: not self.is_displayed) TODO - self.wait.until(expected_conditions.invisibility_of_element_located((By.ID, 'modal'))) - def close(self): self.page.find_element(*self._close_selector).click() self.wait.until(expected_conditions.invisibility_of_element_located((By.ID, 'modal'))) @@ -250,19 +245,3 @@ class AssetAuditList(AssetList): def remove_all_required(self): self.driver.execute_script("Array.from(document.getElementsByTagName(\"input\")).forEach(function (el, ind, arr) { el.removeAttribute(\"required\")});") self.driver.execute_script("Array.from(document.getElementsByTagName(\"select\")).forEach(function (el, ind, arr) { el.removeAttribute(\"required\")});") - - def __getattr__(self, name): - if name in self.form_items: - element = self.form_items[name] - form_element = element[0](self, self.find_element(*element[1])) - return form_element.value - else: - return super().__getattribute__(name) - - def __setattr__(self, name, value): - if name in self.form_items: - element = self.form_items[name] - form_element = element[0](self, self.find_element(*element[1])) - form_element.set_value(value) - else: - self.__dict__[name] = value diff --git a/assets/tests/test_interaction.py b/assets/tests/test_interaction.py index b9f53a1a..fff4afd3 100644 --- a/assets/tests/test_interaction.py +++ b/assets/tests/test_interaction.py @@ -21,10 +21,14 @@ class TestAssetList(AutoLoginTest): working = models.AssetStatus.objects.create(name="Working", should_show=True) broken = models.AssetStatus.objects.create(name="Broken", should_show=False) - models.Asset.objects.create(asset_id="1", description="Broken XLR", status=broken, category=sound, date_acquired=datetime.date(2020, 2, 1)) - models.Asset.objects.create(asset_id="10", description="Working Mic", status=working, category=sound, date_acquired=datetime.date(2020, 2, 1)) - models.Asset.objects.create(asset_id="2", description="A light", status=working, category=lighting, date_acquired=datetime.date(2020, 2, 1)) - models.Asset.objects.create(asset_id="C1", description="The pearl", status=broken, category=lighting, date_acquired=datetime.date(2020, 2, 1)) + models.Asset.objects.create(asset_id="1", description="Broken XLR", status=broken, category=sound, + date_acquired=datetime.date(2020, 2, 1)) + models.Asset.objects.create(asset_id="10", description="Working Mic", status=working, category=sound, + date_acquired=datetime.date(2020, 2, 1)) + models.Asset.objects.create(asset_id="2", description="A light", status=working, category=lighting, + date_acquired=datetime.date(2020, 2, 1)) + models.Asset.objects.create(asset_id="C1", description="The pearl", status=broken, category=lighting, + date_acquired=datetime.date(2020, 2, 1)) self.page = pages.AssetList(self.driver, self.live_server_url).open() def test_default_statuses_applied(self): @@ -96,9 +100,12 @@ class TestAssetForm(AutoLoginTest): self.category = models.AssetCategory.objects.create(name="Health & Safety") self.status = models.AssetStatus.objects.create(name="O.K.", should_show=True) self.supplier = models.Supplier.objects.create(name="Fullmetal Heavy Industry") - self.parent = models.Asset.objects.create(asset_id="9000", description="Shelf", status=self.status, category=self.category, date_acquired=datetime.date(2000, 1, 1)) - self.connector = models.Connector.objects.create(description="IEC", current_rating=10, voltage_rating=240, num_pins=3) - self.cable_type = models.CableType.objects.create(plug=self.connector, socket=self.connector, circuits=1, cores=3) + self.parent = models.Asset.objects.create(asset_id="9000", description="Shelf", status=self.status, + category=self.category, date_acquired=datetime.date(2000, 1, 1)) + self.connector = models.Connector.objects.create(description="IEC", current_rating=10, voltage_rating=240, + num_pins=3) + self.cable_type = models.CableType.objects.create(plug=self.connector, socket=self.connector, circuits=1, + cores=3) self.page = pages.AssetCreate(self.driver, self.live_server_url).open() def test_asset_create(self): @@ -109,7 +116,8 @@ class TestAssetForm(AutoLoginTest): self.page.asset_id = "XX$X" self.page.submit() self.assertFalse(self.page.success) - self.assertIn("An Asset ID can only consist of letters and numbers, with a final number", self.page.errors["Asset id"]) + self.assertIn("An Asset ID can only consist of letters and numbers, with a final number", + self.page.errors["Asset id"]) self.assertIn("This field is required.", self.page.errors["Description"]) self.page.open() @@ -271,11 +279,16 @@ class TestAssetAudit(AutoLoginTest): self.category = models.AssetCategory.objects.create(name="Haulage") self.status = models.AssetStatus.objects.create(name="Probably Fine", should_show=True) self.supplier = models.Supplier.objects.create(name="The Bazaar") - self.connector = models.Connector.objects.create(description="Trailer Socket", current_rating=1, voltage_rating=40, num_pins=13) - models.Asset.objects.create(asset_id="1", description="Trailer Cable", status=self.status, category=self.category, date_acquired=datetime.date(2020, 2, 1)) - models.Asset.objects.create(asset_id="11", description="Trailerboard", status=self.status, category=self.category, date_acquired=datetime.date(2020, 2, 1)) - models.Asset.objects.create(asset_id="111", description="Erms", status=self.status, category=self.category, date_acquired=datetime.date(2020, 2, 1)) - models.Asset.objects.create(asset_id="1111", description="A hammer", status=self.status, category=self.category, date_acquired=datetime.date(2020, 2, 1)) + self.connector = models.Connector.objects.create(description="Trailer Socket", current_rating=1, + voltage_rating=40, num_pins=13) + models.Asset.objects.create(asset_id="1", description="Trailer Cable", status=self.status, + category=self.category, date_acquired=datetime.date(2020, 2, 1)) + models.Asset.objects.create(asset_id="11", description="Trailerboard", status=self.status, + category=self.category, date_acquired=datetime.date(2020, 2, 1)) + models.Asset.objects.create(asset_id="111", description="Erms", status=self.status, category=self.category, + date_acquired=datetime.date(2020, 2, 1)) + models.Asset.objects.create(asset_id="1111", description="A hammer", status=self.status, category=self.category, + date_acquired=datetime.date(2020, 2, 1)) self.page = pages.AssetAuditList(self.driver, self.live_server_url).open() self.wait = WebDriverWait(self.driver, 20) @@ -288,12 +301,13 @@ class TestAssetAudit(AutoLoginTest): self.page.modal.remove_all_required() self.page.modal.description = "" self.page.modal.submit() + self.wait.until(animation_is_finished()) self.assertIn("This field is required.", self.page.modal.errors["Description"]) # Now do it properly self.page.modal.description = new_desc = "A BIG hammer" self.page.modal.submit() + self.wait.until(animation_is_finished()) submit_time = timezone.now() - self.assertFalse(self.driver.find_element_by_id('modal').is_displayed()) # Check data is correct audited = models.Asset.objects.get(asset_id=asset_id) self.assertEqual(audited.description, new_desc) @@ -320,4 +334,4 @@ class TestAssetAudit(AutoLoginTest): self.page.set_query("NOTFOUND") self.page.search() self.assertFalse(self.driver.find_element_by_id('modal').is_displayed()) - self.assertIn("Asset with that ID does not exist!", self.page.error.text) \ No newline at end of file + self.assertIn("Asset with that ID does not exist!", self.page.error.text) diff --git a/assets/tests/test_unit.py b/assets/tests/test_unit.py index 352afbf4..d629ee50 100644 --- a/assets/tests/test_unit.py +++ b/assets/tests/test_unit.py @@ -4,9 +4,8 @@ import pytest from django.core.management import call_command from django.test.utils import override_settings from django.urls import reverse -from pytest_django.asserts import assertFormError, assertRedirects +from pytest_django.asserts import assertFormError, assertRedirects, assertContains, assertNotContains -from PyRIGS.tests.base import response_contains from assets import models, urls pytestmark = pytest.mark.django_db # TODO @@ -107,17 +106,17 @@ def test_oembed(client): # Test the meta tag is in place response = client.get(asset_url, follow=True, HTTP_HOST='example.com') assert '{} | Rig Information Gathering System'.format(expected_title)) + assertContains(response, '{} | Rig Information Gathering System'.format(expected_title)) diff --git a/conftest.py b/conftest.py new file mode 100644 index 00000000..bc41506e --- /dev/null +++ b/conftest.py @@ -0,0 +1,8 @@ +from django.conf import settings +import django + +def pytest_configure(): + settings.PASSWORD_HASHERS = ( + 'django.contrib.auth.hashers.MD5PasswordHasher', + ) + django.setup()