From 462a16ec421d8eb161a4a07e48063c87590c1876 Mon Sep 17 00:00:00 2001 From: Arona Jones Date: Thu, 4 Feb 2021 15:44:07 +0000 Subject: [PATCH] Move user/group setup into new generateSampleUserData command --- PyRIGS/tests/base.py | 9 +- PyRIGS/tests/test_unit.py | 18 ++- RIGS/management/commands/deleteSampleData.py | 3 +- .../management/commands/generateSampleData.py | 4 +- .../commands/generateSampleRIGSData.py | 98 +----------- RIGS/models.py | 13 +- RIGS/tests/conftest.py | 6 + RIGS/tests/pages.py | 2 +- RIGS/tests/test_interaction.py | 2 +- RIGS/tests/test_models.py | 6 +- RIGS/tests/test_unit.py | 22 +-- .../commands/generateSampleAssetsData.py | 4 +- .../templates/partials/asset_list_table.html | 2 + assets/tests/conftest.py | 20 +++ assets/tests/test_access.py | 70 ++++++++ assets/tests/test_interaction.py | 4 +- assets/tests/test_unit.py | 150 ++++-------------- users/management/__init__.py | 0 users/management/commands/__init__.py | 0 .../commands/generateSampleUserData.py | 120 ++++++++++++++ 20 files changed, 300 insertions(+), 253 deletions(-) create mode 100644 assets/tests/conftest.py create mode 100644 assets/tests/test_access.py create mode 100644 users/management/__init__.py create mode 100644 users/management/commands/__init__.py create mode 100644 users/management/commands/generateSampleUserData.py diff --git a/PyRIGS/tests/base.py b/PyRIGS/tests/base.py index 399a9afa..79005757 100644 --- a/PyRIGS/tests/base.py +++ b/PyRIGS/tests/base.py @@ -86,14 +86,14 @@ def screenshot_failure_cls(cls): return cls -def assert_times_equal(first_time, second_time): +def assert_times_almost_equal(first_time, second_time): assert first_time.replace(microsecond=0, second=0) == second_time.replace(microsecond=0, second=0) def assert_oembed(alt_event_embed_url, alt_oembed_url, client, event_embed_url, event_url, oembed_url): # Test the meta tag is in place response = client.get(event_url, follow=True, HTTP_HOST='example.com') - assertContains(response, '{} | Rig Information Gathering System'.format(expected_title), response.content.decode()) + assertInHTML('{} | Rig Information Gathering System'.format(expected_title), + response.content.decode()) print("{} | {}".format(request_url, expected_title)) # If test fails, tell me where! # except: # print(response.content.decode(), file=open('output.html', 'w')) + + +@pytest.mark.django_db(transaction=True) +def test_delete_sample_data(settings): + settings.DEBUG = True + call_command('deleteSampleData') + + assert models.Asset.objects.all().count() == 0 + assert models.Supplier.objects.all().count() == 0 diff --git a/RIGS/management/commands/deleteSampleData.py b/RIGS/management/commands/deleteSampleData.py index 54c8b580..4abc2f76 100644 --- a/RIGS/management/commands/deleteSampleData.py +++ b/RIGS/management/commands/deleteSampleData.py @@ -4,13 +4,14 @@ from django.contrib.auth.models import Group from assets import models from RIGS import models as rigsmodels + class Command(BaseCommand): help = 'Deletes testing sample data' def handle(self, *args, **kwargs): from django.conf import settings - if not (settings.DEBUG): + if not settings.DEBUG: raise CommandError('You cannot run this command in production') self.delete_objects(models.AssetCategory) diff --git a/RIGS/management/commands/generateSampleData.py b/RIGS/management/commands/generateSampleData.py index fdcec461..d269cf79 100644 --- a/RIGS/management/commands/generateSampleData.py +++ b/RIGS/management/commands/generateSampleData.py @@ -3,11 +3,13 @@ from django.core.management.base import BaseCommand from RIGS import models + class Command(BaseCommand): help = 'Adds sample data to use for testing' can_import_settings = True def handle(self, *args, **options): + call_command('generateSampleUserData') call_command('generateSampleRIGSData') call_command('generateSampleAssetsData') - # call_command('createinitialrevisions') TODO + call_command('createinitialrevisions') diff --git a/RIGS/management/commands/generateSampleRIGSData.py b/RIGS/management/commands/generateSampleRIGSData.py index 5394035e..75adc6db 100644 --- a/RIGS/management/commands/generateSampleRIGSData.py +++ b/RIGS/management/commands/generateSampleRIGSData.py @@ -17,7 +17,6 @@ class Command(BaseCommand): people = [] organisations = [] venues = [] - profiles = [] events = [] event_items = [] invoices = [] @@ -25,10 +24,6 @@ class Command(BaseCommand): ras = [] checklists = [] - keyholder_group = None - finance_group = None - hs_group = None - def handle(self, *args, **options): from django.conf import settings @@ -41,19 +36,12 @@ class Command(BaseCommand): with transaction.atomic(): models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1') - # self.setupGenericProfiles() - self.setupUsefulProfiles() - models.Profile.objects.bulk_create(self.profiles) - self.setupPeople() models.Person.objects.bulk_create(self.people) self.setupOrganisations() models.Organisation.objects.bulk_create(self.organisations) self.setupVenues() models.Venue.objects.bulk_create(self.venues) - - self.setupGroups() - self.setupEvents() models.Event.objects.bulk_create(self.events) models.EventItem.objects.bulk_create(self.event_items) @@ -175,88 +163,6 @@ class Command(BaseCommand): self.venues.append(newVenue) - def setupGroups(self): - self.keyholder_group = Group.objects.create(name='Keyholders') - self.finance_group = Group.objects.create(name='Finance') - self.hs_group = Group.objects.create(name='H&S') - - keyholderPerms = ["add_event", "change_event", "view_event", - "add_eventitem", "change_eventitem", "delete_eventitem", - "add_organisation", "change_organisation", "view_organisation", - "add_person", "change_person", "view_person", "view_profile", - "add_venue", "change_venue", "view_venue", - "add_asset", "change_asset", "delete_asset", - "view_asset", "view_supplier", "change_supplier", "asset_finance", - "add_supplier", "view_cabletype", "change_cabletype", - "add_cabletype", "view_eventchecklist", "change_eventchecklist", - "add_eventchecklist", "view_riskassessment", "change_riskassessment", - "add_riskassessment", "add_eventchecklistcrew", "change_eventchecklistcrew", - "delete_eventchecklistcrew", "view_eventchecklistcrew", "add_eventchecklistvehicle", - "change_eventchecklistvehicle", - "delete_eventchecklistvehicle", "view_eventchecklistvehicle", ] - financePerms = keyholderPerms + ["add_invoice", "change_invoice", "view_invoice", - "add_payment", "change_payment", "delete_payment"] - hsPerms = keyholderPerms + ["review_riskassessment", "review_eventchecklist"] - - for permId in keyholderPerms: - self.keyholder_group.permissions.add(Permission.objects.get(codename=permId)) - - for permId in financePerms: - self.finance_group.permissions.add(Permission.objects.get(codename=permId)) - - for permId in hsPerms: - self.hs_group.permissions.add(Permission.objects.get(codename=permId)) - - def setupGenericProfiles(self): - names = ["Clara Oswin Oswald", "Rory Williams", "Amy Pond", "River Song", "Martha Jones", "Donna Noble", - "Jack Harkness", "Mickey Smith", "Rose Tyler"] - for i, name in enumerate(names): - pk = i + 1 - newProfile = models.Profile(pk=pk, username=name.replace(" ", ""), first_name=name.split(" ")[0], - last_name=name.split(" ")[-1], - email=name.replace(" ", "") + "@example.com", - initials="".join([j[0].upper() for j in name.split()])) - if i % 2 == 0: - newProfile.phone = "01234 567894" - - self.profiles.append(newProfile) - - def setupUsefulProfiles(self): - superUser = models.Profile(pk=995, username="superuser", first_name="Super", last_name="User", - initials="SU", - email="superuser@example.com", is_superuser=True, is_active=True, - is_staff=True) - superUser.set_password('superuser') - self.profiles.append(superUser) - - financeUser = models.Profile(pk=996, username="finance", first_name="Finance", last_name="User", - initials="FU", - email="financeuser@example.com", is_active=True, is_approved=True) - financeUser.groups.add(self.finance_group) - financeUser.groups.add(self.keyholder_group) - financeUser.set_password('finance') - self.profiles.append(financeUser) - - hsUser = models.Profile(pk=997, username="hs", first_name="HS", last_name="User", - initials="HSU", - email="hsuser@example.com", is_active=True, is_approved=True) - hsUser.groups.add(self.hs_group) - hsUser.groups.add(self.keyholder_group) - hsUser.set_password('hs') - self.profiles.append(hsUser) - - keyholderUser = models.Profile(pk=998, username="keyholder", first_name="Keyholder", last_name="User", - initials="KU", - email="keyholderuser@example.com", is_active=True, is_approved=True) - keyholderUser.groups.add(self.keyholder_group) - keyholderUser.set_password('keyholder') - self.profiles.append(keyholderUser) - - basicUser = models.Profile(pk=999, username="basic", first_name="Basic", last_name="User", initials="BU", - email="basicuser@example.com", is_active=True, is_approved=True) - basicUser.set_password('basic') - self.profiles.append(basicUser) - def setupEvents(self): names = ["Outdoor Concert", "Hall Open Mic Night", "Festival", "Weekend Event", "Magic Show", "Society Ball", "Evening Show", "Talent Show", "Acoustic Evening", "Hire of Things", "SU Event", @@ -308,7 +214,7 @@ class Command(BaseCommand): newEvent.end_date = newEvent.start_date + datetime.timedelta(days=random.randint(1, 4)) if random.randint(0, 6) > 0: # 5 in 6 have MIC - newEvent.mic = random.choice(self.profiles) + newEvent.mic = random.choice(models.Profile.objects.all()) if random.randint(0, 6) > 0: # 5 in 6 have organisation newEvent.organisation = random.choice(self.organisations) @@ -379,7 +285,7 @@ class Command(BaseCommand): outside=bool(random.getrandbits(1)))) if i == 0 or random.randint(0, 1) > 0: # Event 1 and 1 in 10 have a Checklist self.checklists.append( - models.EventChecklist(pk=pk, event=newEvent, power_mic=random.choice(self.profiles), + models.EventChecklist(pk=pk, event=newEvent, power_mic=random.choice(models.Profile.objects.all()), safe_parking=bool(random.getrandbits(1)), safe_packing=bool(random.getrandbits(1)), exits=bool(random.getrandbits(1)), diff --git a/RIGS/models.py b/RIGS/models.py index 9894a72c..599053e7 100644 --- a/RIGS/models.py +++ b/RIGS/models.py @@ -178,7 +178,6 @@ class VatManager(models.Manager): return self.find_rate(timezone.now()) def find_rate(self, date): - # return self.filter(startAt__lte=date).latest() try: return self.filter(start_at__lte=date).latest() except VatRate.DoesNotExist: @@ -235,15 +234,15 @@ class Venue(models.Model, RevisionMixin): class EventManager(models.Manager): def current_events(self): events = self.filter( - (models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False) & ~models.Q( + (models.Q(start_date__gte=timezone.now(), end_date__isnull=True, dry_hire=False) & ~models.Q( status=Event.CANCELLED)) | # Starts after with no end (models.Q(end_date__gte=timezone.now().date(), dry_hire=False) & ~models.Q( status=Event.CANCELLED)) | # Ends after - (models.Q(dry_hire=True, start_date__gte=timezone.now().date()) & ~models.Q( + (models.Q(dry_hire=True, start_date__gte=timezone.now()) & ~models.Q( status=Event.CANCELLED)) | # Active dry hire (models.Q(dry_hire=True, checked_in_by__isnull=True) & ( models.Q(status=Event.BOOKED) | models.Q(status=Event.CONFIRMED))) | # Active dry hire GT - models.Q(status=Event.CANCELLED, start_date__gte=timezone.now().date()) # Canceled but not started + models.Q(status=Event.CANCELLED, start_date__gte=timezone.now()) # Canceled but not started ).order_by('start_date', 'end_date', 'start_time', 'end_time', 'meet_at').select_related('person', 'organisation', 'venue', 'mic') @@ -269,12 +268,12 @@ class EventManager(models.Manager): def rig_count(self): event_count = self.filter( - (models.Q(start_date__gte=timezone.now().date(), end_date__isnull=True, dry_hire=False, + (models.Q(start_date__gte=timezone.now(), end_date__isnull=True, dry_hire=False, is_rig=True) & ~models.Q( status=Event.CANCELLED)) | # Starts after with no end - (models.Q(end_date__gte=timezone.now().date(), dry_hire=False, is_rig=True) & ~models.Q( + (models.Q(end_date__gte=timezone.now(), dry_hire=False, is_rig=True) & ~models.Q( status=Event.CANCELLED)) | # Ends after - (models.Q(dry_hire=True, start_date__gte=timezone.now().date(), is_rig=True) & ~models.Q( + (models.Q(dry_hire=True, start_date__gte=timezone.now(), is_rig=True) & ~models.Q( status=Event.CANCELLED)) # Active dry hire ).count() return event_count diff --git a/RIGS/tests/conftest.py b/RIGS/tests/conftest.py index cc38e2e4..09a891c4 100644 --- a/RIGS/tests/conftest.py +++ b/RIGS/tests/conftest.py @@ -1,7 +1,13 @@ from RIGS import models import pytest +from django.utils import timezone @pytest.fixture(autouse=True) def vat_rate(db): return models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1') + + +@pytest.fixture() +def basic_event(db): + return models.Event.objects.create(name="TE E1", start_date=timezone.now()) diff --git a/RIGS/tests/pages.py b/RIGS/tests/pages.py index 5c99a022..97d0f69a 100644 --- a/RIGS/tests/pages.py +++ b/RIGS/tests/pages.py @@ -52,7 +52,7 @@ class EventDetail(BasePage): URL_TEMPLATE = 'event/{event_id}' # TODO Refactor into regions to match template fragmentation - _event_name_selector = (By.XPATH, '//h1') + _event_name_selector = (By.XPATH, '//h2') _person_panel_selector = (By.XPATH, '//div[contains(text(), "Contact Details")]/..') _name_selector = (By.XPATH, '//dt[text()="Person"]/following-sibling::dd[1]') _email_selector = (By.XPATH, '//dt[text()="Email"]/following-sibling::dd[1]') diff --git a/RIGS/tests/test_interaction.py b/RIGS/tests/test_interaction.py index b1e2b47e..039063d4 100644 --- a/RIGS/tests/test_interaction.py +++ b/RIGS/tests/test_interaction.py @@ -313,7 +313,7 @@ class TestEventDuplicate(BaseRigboardTest): self.assertFalse(newEvent.authorised) - self.assertNotIn("N%05d" % self.testEvent.pk, self.driver.find_element_by_xpath('//h1').text) + self.assertNotIn("N%05d" % self.testEvent.pk, self.driver.find_element_by_xpath('//h2').text) self.assertNotIn("Event data duplicated but not yet saved", self.page.warning) # Check info message not visible # Check the new items are visible diff --git a/RIGS/tests/test_models.py b/RIGS/tests/test_models.py index fb706686..5f7fb3a1 100644 --- a/RIGS/tests/test_models.py +++ b/RIGS/tests/test_models.py @@ -20,8 +20,10 @@ def test_str(): @pytest.mark.django_db def test_find_correct(vat_rate): new_rate = models.VatRate.objects.create(start_at='2016-03-01', rate=0.15, comment='test2') - assert str(models.VatRate.objects.find_rate('2015-03-01')) == vat_rate - assert str(models.VatRate.objects.find_rate('2016-03-01')) == new_rate + r = models.VatRate.objects.find_rate('2015-03-01') + assert r == vat_rate + r = models.VatRate.objects.find_rate('2016-03-01') + assert r == new_rate def test_percent_correct(vat_rate): diff --git a/RIGS/tests/test_unit.py b/RIGS/tests/test_unit.py index a6fcc994..4a2d1b7d 100644 --- a/RIGS/tests/test_unit.py +++ b/RIGS/tests/test_unit.py @@ -9,7 +9,7 @@ from django.utils import timezone from pytest_django.asserts import assertRedirects, assertNotContains, assertContains import PyRIGS.tests.test_unit -from PyRIGS.tests.base import assert_times_equal, assert_oembed, login +from PyRIGS.tests.base import assert_times_almost_equal, assert_oembed, login from RIGS import models import pytest @@ -262,10 +262,6 @@ class TestPrintPaperwork(TestCase): self.assertEqual(response.status_code, 200) -def create_event(): - return models.Event.objects.create(name="TE E1", start_date=date.today()) - - def test_login_redirect(client, django_user_model): request_url = reverse('event_embed', kwargs={'pk': 1}) expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url) @@ -288,9 +284,8 @@ def test_login_cookie_warning(client): assertContains(response, "Cookies do not seem to be enabled") -def test_xframe_headers(admin_client): - event = create_event() - event_url = reverse('event_embed', kwargs={'pk': event.pk}) +def test_xframe_headers(admin_client, basic_event): + event_url = reverse('event_embed', kwargs={'pk': basic_event.pk}) login_url = reverse('login_embed') response = admin_client.get(event_url, follow=True) @@ -302,11 +297,10 @@ def test_xframe_headers(admin_client): response._headers["X-Frame-Options"] -def test_oembed(client): - event = create_event() - event_url = reverse('event_detail', kwargs={'pk': event.pk}) - event_embed_url = reverse('event_embed', kwargs={'pk': event.pk}) - oembed_url = reverse('event_oembed', kwargs={'pk': event.pk}) +def test_oembed(client, basic_event): + event_url = reverse('event_detail', kwargs={'pk': basic_event.pk}) + event_embed_url = reverse('event_embed', kwargs={'pk': basic_event.pk}) + oembed_url = reverse('event_oembed', kwargs={'pk': basic_event.pk}) alt_oembed_url = reverse('event_oembed', kwargs={'pk': 999}) alt_event_embed_url = reverse('event_embed', kwargs={'pk': 999}) @@ -413,7 +407,7 @@ def review(client, profile, obj, request_url): obj.refresh_from_db() assertContains(response, 'Reviewed by') assertContains(response, profile.name) - assert_times_equal(time, obj.reviewed_at) + assert_times_almost_equal(time, obj.reviewed_at) def test_ra_review(admin_client, admin_user): diff --git a/assets/management/commands/generateSampleAssetsData.py b/assets/management/commands/generateSampleAssetsData.py index 32cce0a5..622fed28 100644 --- a/assets/management/commands/generateSampleAssetsData.py +++ b/assets/management/commands/generateSampleAssetsData.py @@ -64,7 +64,7 @@ class Command(BaseCommand): def create_assets(self): asset_description = ['Large cable', 'Shiny thing', 'New lights', 'Really expensive microphone', 'Box of fuse flaps', 'Expensive tool we didn\'t agree to buy', 'Cable drums', 'Boring amount of tape', 'Video stuff no one knows how to use', 'More amplifiers', 'Heatshrink'] - pk = 9000 + pk = 1 for i in range(100): asset = models.Asset( pk=pk, @@ -89,7 +89,7 @@ class Command(BaseCommand): csas = [0.75, 1.00, 1.25, 2.5, 4] lengths = [1, 2, 5, 10, 15, 20, 25, 30, 50, 100] - pk = 200 # Offset to avoid other asset IDs + pk = 9000 # Offset to avoid other asset IDs for i in range(100): asset = models.Asset( pk=pk, diff --git a/assets/templates/partials/asset_list_table.html b/assets/templates/partials/asset_list_table.html index a559bdd0..43405bd9 100644 --- a/assets/templates/partials/asset_list_table.html +++ b/assets/templates/partials/asset_list_table.html @@ -25,6 +25,8 @@ {% button 'view' url='asset_detail' pk=item.asset_id clazz="btn-sm" %} {% if perms.assets.change_asset %} {% button 'edit' url='asset_update' pk=item.asset_id clazz="btn-sm" %} + {% endif %} + {% if perms.assets.add_asset %} {% button 'duplicate' url='asset_duplicate' pk=item.asset_id clazz="btn-sm" %} {% endif %} </div> diff --git a/assets/tests/conftest.py b/assets/tests/conftest.py new file mode 100644 index 00000000..6c764917 --- /dev/null +++ b/assets/tests/conftest.py @@ -0,0 +1,20 @@ +import pytest +from assets import models +import datetime + + +@pytest.fixture +def test_cable(db): + category = models.AssetCategory.objects.create(name="Sound") + status = models.AssetStatus.objects.create(name="Broken", should_show=True) + connector = models.Connector.objects.create(description="16A IEC", current_rating=16, voltage_rating=240, num_pins=3) + cable_type = models.CableType.objects.create(circuits=11, cores=3, plug=connector, socket=connector) + return models.Asset.objects.create(asset_id="666", description="125A -> Jack", comments="The cable from Hell...", status=status, category=category, date_acquired=datetime.date(2006, 6, 6), is_cable=True, cable_type=cable_type, length=10, csa="1.5") + + +@pytest.fixture +def test_asset(db): + working = models.AssetStatus.objects.create(name="Working", should_show=True) + lighting = models.AssetCategory.objects.create(name="Lighting") + asset = models.Asset.objects.create(asset_id="1991", description="Spaceflower", status=working, category=lighting, date_acquired=datetime.date(1991, 12, 26)) + return asset diff --git a/assets/tests/test_access.py b/assets/tests/test_access.py new file mode 100644 index 00000000..f5bcf00f --- /dev/null +++ b/assets/tests/test_access.py @@ -0,0 +1,70 @@ +import datetime + +import pytest +from django.core.management import call_command +from django.test import override_settings +from django.test.utils import override_settings +from django.urls import reverse +from pytest_django.asserts import assertFormError, assertRedirects, assertContains, assertNotContains + +from PyRIGS.tests.base import assert_oembed, login + +from assets import models + +from django.utils import timezone + +pytestmark = pytest.mark.django_db + + +@pytest.fixture(scope='session') +def django_db_setup(django_db_setup, django_db_blocker): # We need stuff setup so we don't get 404 errors everywhere + with django_db_blocker.unblock(): + from django.conf import settings + settings.DEBUG = True + call_command('generateSampleUserData') + call_command('generateSampleAssetsData') + settings.DEBUG = False + + +def test_basic_access(client): + assert client.login(username="basic", password="basic") + + url = reverse('asset_list') + response = client.get(url) + # Check edit and duplicate buttons NOT shown in list + assertNotContains(response, 'Edit') + assertNotContains(response, 'Duplicate') + + url = reverse('asset_detail', kwargs={'pk': 1}) + response = client.get(url) + assertNotContains(response, 'Purchase Details') + assertNotContains(response, 'View Revision History') + + urls = {'asset_history', 'asset_update', 'asset_duplicate'} + for url_name in urls: + request_url = reverse(url_name, kwargs={'pk': 1}) + response = client.get(request_url, follow=True) + assert response.status_code == 403 + + request_url = reverse('supplier_create') + response = client.get(request_url, follow=True) + assert response.status_code == 403 + + request_url = reverse('supplier_update', kwargs={'pk': 1}) + response = client.get(request_url, follow=True) + assert response.status_code == 403 + + +def test_keyholder_access(client, django_user_model): + assert client.login(username="keyholder", password="keyholder") + + url = reverse('asset_list') + response = client.get(url) + # Check edit and duplicate buttons shown in list + assertContains(response, 'Edit') + assertContains(response, 'Duplicate') + + url = reverse('asset_detail', kwargs={'pk': 1}) + response = client.get(url) + assertContains(response, 'Purchase Details') + assertContains(response, 'View Revision History') diff --git a/assets/tests/test_interaction.py b/assets/tests/test_interaction.py index 7440455a..31c43d38 100644 --- a/assets/tests/test_interaction.py +++ b/assets/tests/test_interaction.py @@ -5,7 +5,7 @@ from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as ec from selenium.webdriver.support.ui import WebDriverWait -from PyRIGS.tests.base import AutoLoginTest, screenshot_failure_cls, assert_times_equal +from PyRIGS.tests.base import AutoLoginTest, screenshot_failure_cls, assert_times_almost_equal from PyRIGS.tests.pages import animation_is_finished from assets import models from . import pages @@ -319,7 +319,7 @@ class TestAssetAudit(AutoLoginTest): self.assertEqual(self.asset.description, new_desc) # Make sure audit 'log' was filled out self.assertEqual(self.profile.initials, self.asset.last_audited_by.initials) - assert_times_equal(submit_time, self.asset.last_audited_at) + assert_times_almost_equal(submit_time, self.asset.last_audited_at) # Check we've removed it from the 'needing audit' list self.assertNotIn(self.asset.asset_id, self.page.assets) diff --git a/assets/tests/test_unit.py b/assets/tests/test_unit.py index 66331d05..fc662c93 100644 --- a/assets/tests/test_unit.py +++ b/assets/tests/test_unit.py @@ -11,35 +11,34 @@ from PyRIGS.tests.base import assert_oembed, login from assets import models +from django.utils import timezone + pytestmark = pytest.mark.django_db -def test_supplier_create(client, django_user_model): - login(client, django_user_model) +def test_supplier_create(admin_client): url = reverse('supplier_create') - response = client.post(url) + response = admin_client.post(url) assertFormError(response, 'form', 'name', 'This field is required.') -def test_supplier_edit(client, django_user_model): - login(client, django_user_model) +def test_supplier_edit(admin_client): supplier = models.Supplier.objects.create(name="Gadgetron Corporation") url = reverse('supplier_update', kwargs={'pk': supplier.pk}) - response = client.post(url, {'name': ""}) + response = admin_client.post(url, {'name': ""}) assertFormError(response, 'form', 'name', 'This field is required.') -def test_404(client, django_user_model): - login(client, django_user_model) +def test_404(admin_client): urls = {'asset_detail', 'asset_update', 'asset_duplicate', 'supplier_detail', 'supplier_update'} for url_name in urls: request_url = reverse(url_name, kwargs={'pk': "0000"}) - response = client.get(request_url, follow=True) + response = admin_client.get(request_url, follow=True) assert response.status_code == 404 -def test_embed_login_redirect(client, django_user_model): - request_url = reverse('asset_embed', kwargs={'pk': create_test_asset().asset_id}) +def test_embed_login_redirect(client, django_user_model, test_asset): + request_url = reverse('asset_embed', kwargs={'pk': test_asset.asset_id}) expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url) # Request the page and check it redirects @@ -60,8 +59,8 @@ def test_login_cookie_warning(client, django_user_model): assert "Cookies do not seem to be enabled" in str(response.content) -def test_x_frame_headers(client, django_user_model): - asset_url = reverse('asset_embed', kwargs={'pk': create_test_asset().asset_id}) +def test_x_frame_headers(client, django_user_model, test_asset): + asset_url = reverse('asset_embed', kwargs={'pk': test_asset.asset_id}) login_url = reverse('login_embed') login(client, django_user_model) @@ -75,11 +74,11 @@ def test_x_frame_headers(client, django_user_model): response._headers["X-Frame-Options"] -def test_oembed(client): - asset = create_test_asset() - asset_url = reverse('asset_detail', kwargs={'pk': asset.asset_id}) - asset_embed_url = reverse('asset_embed', kwargs={'pk': asset.asset_id}) - oembed_url = reverse('asset_oembed', kwargs={'pk': asset.asset_id}) +def test_oembed(client, test_asset): + client.logout() + asset_url = reverse('asset_detail', kwargs={'pk': test_asset.asset_id}) + asset_embed_url = reverse('asset_embed', kwargs={'pk': test_asset.asset_id}) + oembed_url = reverse('asset_oembed', kwargs={'pk': test_asset.asset_id}) alt_oembed_url = reverse('asset_oembed', kwargs={'pk': 999}) alt_asset_embed_url = reverse('asset_embed', kwargs={'pk': 999}) @@ -87,8 +86,9 @@ def test_oembed(client): assert_oembed(alt_asset_embed_url, alt_oembed_url, client, asset_embed_url, asset_url, oembed_url) -@override_settings(DEBUG=True) -def test_generate_sample_data(client): +@pytest.mark.django_db(transaction=True) +def test_generate_sample_data(settings): + settings.DEBUG = True # Run the management command and check there are no exceptions call_command('generateSampleAssetsData') @@ -97,14 +97,6 @@ def test_generate_sample_data(client): assert models.Supplier.objects.all().count() > 50 -@override_settings(DEBUG=True) -def test_delete_sample_data(client): - call_command('deleteSampleData') - - assert models.Asset.objects.all().count() == 0 - assert models.Supplier.objects.all().count() == 0 - - def test_production_exception(client): from django.core.management.base import CommandError @@ -113,34 +105,30 @@ def test_production_exception(client): call_command('deleteSampleData') -def test_asset_create(client, django_user_model): - login(client, django_user_model) - response = client.post(reverse('asset_create'), {'date_sold': '2000-01-01', 'date_acquired': '2020-01-01', 'purchase_price': '-30', 'salvage_value': '-30'}) +def test_asset_create(admin_client): + response = admin_client.post(reverse('asset_create'), {'date_sold': '2000-01-01', 'date_acquired': '2020-01-01', 'purchase_price': '-30', 'salvage_value': '-30'}) assertFormError(response, 'form', 'asset_id', 'This field is required.') assert_asset_form_errors(response) -def test_cable_create(client, django_user_model): - login(client, django_user_model) - response = client.post(reverse('asset_create'), {'asset_id': 'X$%A', 'is_cable': True}) +def test_cable_create(admin_client): + response = admin_client.post(reverse('asset_create'), {'asset_id': 'X$%A', 'is_cable': True}) assertFormError(response, 'form', 'asset_id', 'An Asset ID can only consist of letters and numbers, with a final number') assertFormError(response, 'form', 'cable_type', 'A cable must have a type') assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0') assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0') -def test_asset_edit(client, django_user_model): - login(client, django_user_model) - url = reverse('asset_update', kwargs={'pk': create_test_asset().asset_id}) - response = client.post(url, {'date_sold': '2000-12-01', 'date_acquired': '2020-12-01', 'purchase_price': '-50', 'salvage_value': '-50', 'description': "", 'status': "", 'category': ""}) +def test_asset_edit(admin_client, test_asset): + url = reverse('asset_update', kwargs={'pk': test_asset.asset_id}) + response = admin_client.post(url, {'date_sold': '2000-12-01', 'date_acquired': '2020-12-01', 'purchase_price': '-50', 'salvage_value': '-50', 'description': "", 'status': "", 'category': ""}) assert_asset_form_errors(response) -def test_cable_edit(client, django_user_model): - login(client, django_user_model) - url = reverse('asset_update', kwargs={'pk': create_test_cable().asset_id}) +def test_cable_edit(admin_client, test_cable): + url = reverse('asset_update', kwargs={'pk': test_cable.asset_id}) # TODO Why do I have to send is_cable=True here? - response = client.post(url, {'is_cable': True, 'length': -3, 'csa': -3}) + response = admin_client.post(url, {'is_cable': True, 'length': -3, 'csa': -3}) # TODO Can't figure out how to select the 'none' option... # assertFormError(response, 'form', 'cable_type', 'A cable must have a type') @@ -148,71 +136,14 @@ def test_cable_edit(client, django_user_model): assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0') -def test_asset_duplicate(client, django_user_model): - login(client, django_user_model) - url = reverse('asset_duplicate', kwargs={'pk': create_test_cable().asset_id}) - response = client.post(url, {'is_cable': True, 'length': 0, 'csa': 0}) +def test_asset_duplicate(admin_client, test_cable): + url = reverse('asset_duplicate', kwargs={'pk': test_cable.asset_id}) + response = admin_client.post(url, {'is_cable': True, 'length': 0, 'csa': 0}) assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0') assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0') -def test_basic_access(client, django_user_model): - create_asset_one() - client.login(username="basic", password="basic") - - url = reverse('asset_list') - response = client.get(url) - # Check edit and duplicate buttons NOT shown in list - assertNotContains(response, 'Edit') - assertNotContains(response, 'Duplicate') - - url = reverse('asset_detail', kwargs={'pk': "9000"}) - response = client.get(url) - assertNotContains(response, 'Purchase Details') - assertNotContains(response, 'View Revision History') - - urls = {'asset_history', 'asset_update', 'asset_duplicate'} - for url_name in urls: - request_url = reverse(url_name, kwargs={'pk': "9000"}) - response = client.get(request_url, follow=True) - assert response.status_code == 403 - - request_url = reverse('supplier_create') - response = client.get(request_url, follow=True) - assert response.status_code == 403 - - request_url = reverse('supplier_update', kwargs={'pk': "1"}) - response = client.get(request_url, follow=True) - assert response.status_code == 403 - - -def test_keyholder_access(client, django_user_model): - create_asset_one() - client.login(username="keyholder", password="keyholder") - - url = reverse('asset_list') - response = client.get(url) - # Check edit and duplicate buttons shown in list - assertContains(response, 'Edit') - assertContains(response, 'Duplicate') - - url = reverse('asset_detail', kwargs={'pk': "9000"}) - response = client.get(url) - assertContains(response, 'Purchase Details') - assertContains(response, 'View Revision History') - - -@override_settings(DEBUG=True) -def create_asset_one(): - # Shortcut to create the levels - bonus side effect of testing the command (hopefully) matches production - call_command('generateSampleAssetsData') - # Create an asset with ID 1 to make things easier in loops (we can always use pk=1) - category = models.AssetCategory.objects.create(name="Number One") - status = models.AssetStatus.objects.create(name="Probably Fine", should_show=True) - return models.Asset.objects.create(asset_id="1", description="Half Price Fish", status=status, category=category, date_acquired=datetime.date(2020, 2, 1)) - - def assert_asset_form_errors(response): assertFormError(response, 'form', 'description', 'This field is required.') assertFormError(response, 'form', 'status', 'This field is required.') @@ -220,18 +151,3 @@ def assert_asset_form_errors(response): assertFormError(response, 'form', 'date_sold', 'Cannot sell an item before it is acquired') assertFormError(response, 'form', 'purchase_price', 'A price cannot be negative') assertFormError(response, 'form', 'salvage_value', 'A price cannot be negative') - - -def create_test_cable(): - category = models.AssetCategory.objects.create(name="Sound") - status = models.AssetStatus.objects.create(name="Broken", should_show=True) - connector = models.Connector.objects.create(description="16A IEC", current_rating=16, voltage_rating=240, num_pins=3) - cable_type = models.CableType.objects.create(circuits=11, cores=3, plug=connector, socket=connector) - return models.Asset.objects.create(asset_id="666", description="125A -> Jack", comments="The cable from Hell...", status=status, category=category, date_acquired=datetime.date(2006, 6, 6), is_cable=True, cable_type=cable_type, length=10, csa="1.5") - - -def create_test_asset(): - working = models.AssetStatus.objects.create(name="Working", should_show=True) - lighting = models.AssetCategory.objects.create(name="Lighting") - asset = models.Asset.objects.create(asset_id="1991", description="Spaceflower", status=working, category=lighting, date_acquired=datetime.date(1991, 12, 26)) - return asset diff --git a/users/management/__init__.py b/users/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/users/management/commands/__init__.py b/users/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/users/management/commands/generateSampleUserData.py b/users/management/commands/generateSampleUserData.py new file mode 100644 index 00000000..3e07e593 --- /dev/null +++ b/users/management/commands/generateSampleUserData.py @@ -0,0 +1,120 @@ +import datetime +import random + +from django.contrib.auth.models import Group, Permission +from django.core.management.base import BaseCommand, CommandError +from django.db import transaction +from django.utils import timezone +from reversion import revisions as reversion + +from RIGS import models + + +class Command(BaseCommand): + help = 'Adds sample data to use for testing' + can_import_settings = True + + profiles = [] + keyholder_group = None + finance_group = None + hs_group = None + + def handle(self, *args, **options): + from django.conf import settings + + if not (settings.DEBUG or settings.STAGING): + raise CommandError('You cannot run this command in production') + + random.seed( + 'Some object to seed the random number generator') # otherwise it is done by time, which could lead to inconsistent tests + + with transaction.atomic(): + self.setup_groups() + self.setup_useful_profiles() + + # self.setup_generic_profiles() + # models.Profile.objects.bulk_create(self.profiles) + + def setup_groups(self): + self.keyholder_group = Group.objects.create(name='Keyholders') + self.finance_group = Group.objects.create(name='Finance') + self.hs_group = Group.objects.create(name='H&S') + + keyholder_perms = ["add_event", "change_event", "view_event", + "add_eventitem", "change_eventitem", "delete_eventitem", + "add_organisation", "change_organisation", "view_organisation", + "add_person", "change_person", "view_person", "view_profile", + "add_venue", "change_venue", "view_venue", + "add_asset", "change_asset", "delete_asset", + "view_asset", "view_supplier", "change_supplier", "asset_finance", + "add_supplier", "view_cabletype", "change_cabletype", + "add_cabletype", "view_eventchecklist", "change_eventchecklist", + "add_eventchecklist", "view_riskassessment", "change_riskassessment", + "add_riskassessment", "add_eventchecklistcrew", "change_eventchecklistcrew", + "delete_eventchecklistcrew", "view_eventchecklistcrew", "add_eventchecklistvehicle", + "change_eventchecklistvehicle", + "delete_eventchecklistvehicle", "view_eventchecklistvehicle", ] + finance_perms = keyholder_perms + ["add_invoice", "change_invoice", "view_invoice", + "add_payment", "change_payment", "delete_payment"] + hs_perms = keyholder_perms + ["review_riskassessment", "review_eventchecklist"] + + for permId in keyholder_perms: + self.keyholder_group.permissions.add(Permission.objects.get(codename=permId)) + + for permId in finance_perms: + self.finance_group.permissions.add(Permission.objects.get(codename=permId)) + + for permId in hs_perms: + self.hs_group.permissions.add(Permission.objects.get(codename=permId)) + + def setup_generic_profiles(self): + names = ["Clara Oswin Oswald", "Rory Williams", "Amy Pond", "River Song", "Martha Jones", "Donna Noble", + "Jack Harkness", "Mickey Smith", "Rose Tyler"] + for i, name in enumerate(names): + newProfile = models.Profile.objects.create(username=name.replace(" ", ""), first_name=name.split(" ")[0], + last_name=name.split(" ")[-1], + email=name.replace(" ", "") + "@example.com", + initials="".join([j[0].upper() for j in name.split()])) + if i % 2 == 0: + newProfile.phone = "01234 567894" + + newProfile.save() + self.profiles.append(newProfile) + + def setup_useful_profiles(self): + super_user = models.Profile.objects.create(username="superuser", first_name="Super", last_name="User", + initials="SU", + email="superuser@example.com", is_superuser=True, is_active=True, + is_staff=True) + super_user.set_password('superuser') + super_user.save() + + finance_user = models.Profile.objects.create(username="finance", first_name="Finance", last_name="User", + initials="FU", + email="financeuser@example.com", is_active=True, is_approved=True) + finance_user.groups.add(self.finance_group) + finance_user.groups.add(self.keyholder_group) + finance_user.set_password('finance') + finance_user.save() + + hs_user = models.Profile.objects.create(username="hs", first_name="HS", last_name="User", + initials="HSU", + email="hsuser@example.com", is_active=True, is_approved=True) + hs_user.groups.add(self.hs_group) + hs_user.groups.add(self.keyholder_group) + hs_user.set_password('hs') + hs_user.save() + + keyholder_user = models.Profile.objects.create(username="keyholder", first_name="Keyholder", last_name="User", + initials="KU", + email="keyholderuser@example.com", is_active=True, + is_approved=True) + keyholder_user.groups.add(self.keyholder_group) + keyholder_user.set_password('keyholder') + keyholder_user.save() + + basic_user = models.Profile.objects.create(username="basic", first_name="Basic", last_name="User", + initials="BU", + email="basicuser@example.com", is_active=True, is_approved=True) + basic_user.set_password('basic') + basic_user.save()