mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-01-17 05:22:16 +00:00
Use pytest as our test runner for better parallelism
Also rewrote some asset tests to be in the pytest style. May do some more. Some warnings cleaned up in the process.
This commit is contained in:
17
.github/workflows/django.yml
vendored
17
.github/workflows/django.yml
vendored
@@ -14,13 +14,7 @@ jobs:
|
||||
if: "!contains(github.event.head_commit.message, '[ci skip]')"
|
||||
strategy:
|
||||
matrix:
|
||||
test-group: ["RIGS.tests.test_unit RIGS.tests.test_models RIGS.tests.test_functional", "versioning.tests.test_versioning", "users.tests.test_users"]
|
||||
parallel: [true]
|
||||
include:
|
||||
- test-group: "assets.tests.test_assets"
|
||||
parallel: false
|
||||
- test-group: "RIGS.tests.test_interaction"
|
||||
parallel: false
|
||||
test-group: ["RIGS/tests/", "versioning/tests/", "users/tests/", "assets/tests/"]
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up Python
|
||||
@@ -52,17 +46,14 @@ jobs:
|
||||
python manage.py check
|
||||
python manage.py makemigrations --check --dry-run
|
||||
- name: Run Tests
|
||||
if: \!${{ matrix.parallel }}
|
||||
run: coverage run manage.py test ${{ matrix.test-group }} --verbosity=2
|
||||
if: ${{ matrix.parallel }}
|
||||
run: coverage run -m pytest -n 8 ${{ matrix.test-group }}
|
||||
- uses: actions/upload-artifact@v2
|
||||
if: failure() # Screenshots only make sense for the non-parallel, i.e. interaction tests anyway
|
||||
if: failure()
|
||||
with:
|
||||
name: failure-screenshots ${{ matrix.test-group }}
|
||||
path: screenshots/
|
||||
retention-days: 5
|
||||
- name: Run Tests (Parallel)
|
||||
if: ${{ matrix.parallel }}
|
||||
run: coverage run manage.py test ${{ matrix.test-group }} --parallel --verbosity=2
|
||||
- name: Upload Coverage
|
||||
run: coveralls --service=github
|
||||
env:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
from django.urls import path
|
||||
from django.conf.urls import include, url
|
||||
from django.urls import path, re_path
|
||||
from django.conf.urls import include
|
||||
from django.contrib import admin
|
||||
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
|
||||
from django.contrib.auth.decorators import login_required
|
||||
@@ -39,6 +39,6 @@ if settings.DEBUG:
|
||||
|
||||
import debug_toolbar
|
||||
urlpatterns = [
|
||||
url(r'^__debug__/', include(debug_toolbar.urls)),
|
||||
re_path(r'^__debug__/', include(debug_toolbar.urls)),
|
||||
path('bootstrap/', TemplateView.as_view(template_name="bootstrap.html")),
|
||||
] + urlpatterns
|
||||
|
||||
@@ -23,6 +23,8 @@ from functools import reduce
|
||||
from django.views.decorators.cache import never_cache, cache_page
|
||||
from django.utils.decorators import method_decorator
|
||||
|
||||
def is_ajax(request):
|
||||
return request.headers.get('x-requested-with') == 'XMLHttpRequest'
|
||||
|
||||
# Displays the current rig count along with a few other bits and pieces
|
||||
class Index(generic.TemplateView):
|
||||
@@ -151,7 +153,7 @@ class SecureAPIRequest(generic.View):
|
||||
|
||||
class ModalURLMixin:
|
||||
def get_close_url(self, update, detail):
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
url = reverse_lazy('closemodal')
|
||||
update_url = str(reverse_lazy(update, kwargs={'pk': self.object.pk}))
|
||||
messages.info(self.request, "modalobject=" + serializers.serialize("json", [self.object]))
|
||||
@@ -170,7 +172,7 @@ class GenericListView(generic.ListView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GenericListView, self).get_context_data(**kwargs)
|
||||
context['page_title'] = self.model.__name__ + "s"
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
return context
|
||||
|
||||
@@ -202,7 +204,7 @@ class GenericDetailView(generic.DetailView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GenericDetailView, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "{} | {}".format(self.model.__name__, self.object.name)
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
return context
|
||||
|
||||
@@ -213,7 +215,7 @@ class GenericUpdateView(generic.UpdateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GenericUpdateView, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Edit {}".format(self.model.__name__)
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
return context
|
||||
|
||||
@@ -224,7 +226,7 @@ class GenericCreateView(generic.CreateView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(GenericCreateView, self).get_context_data(**kwargs)
|
||||
context['page_title'] = "Create {}".format(self.model.__name__)
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
return context
|
||||
|
||||
|
||||
10
RIGS/urls.py
10
RIGS/urls.py
@@ -1,6 +1,6 @@
|
||||
from django.conf.urls import url
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.urls import path
|
||||
from django.urls import path, re_path
|
||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
from django.views.generic import RedirectView
|
||||
from PyRIGS.decorators import (api_key_required, has_oembed,
|
||||
@@ -45,9 +45,9 @@ urlpatterns = [
|
||||
path('rigboard/', login_required(rigboard.RigboardIndex.as_view()), name='rigboard'),
|
||||
path('rigboard/calendar/', login_required()(rigboard.WebCalendar.as_view()),
|
||||
name='web_calendar'),
|
||||
url(r'^rigboard/calendar/(?P<view>(month|week|day))/$',
|
||||
re_path(r'^rigboard/calendar/(?P<view>(month|week|day))/$',
|
||||
login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
||||
url(r'^rigboard/calendar/(?P<view>(month|week|day))/(?P<date>(\d{4}-\d{2}-\d{2}))/$',
|
||||
re_path(r'^rigboard/calendar/(?P<view>(month|week|day))/(?P<date>(\d{4}-\d{2}-\d{2}))/$',
|
||||
login_required()(rigboard.WebCalendar.as_view()), name='web_calendar'),
|
||||
path('rigboard/archive/', RedirectView.as_view(permanent=True, pattern_name='event_archive')),
|
||||
|
||||
@@ -130,11 +130,11 @@ urlpatterns = [
|
||||
path('event/<int:pk>/auth/preview/',
|
||||
permission_required_with_403('RIGS.change_event')(rigboard.EventAuthoriseRequestEmailPreview.as_view()),
|
||||
name='event_authorise_preview'),
|
||||
url(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/$', rigboard.EventAuthorise.as_view(),
|
||||
re_path(r'^event/(?P<pk>\d+)/(?P<hmac>[-:\w]+)/$', rigboard.EventAuthorise.as_view(),
|
||||
name='event_authorise'),
|
||||
|
||||
# ICS Calendar - API key authentication
|
||||
url(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(ical.CalendarICS()),
|
||||
re_path(r'^ical/(?P<api_pk>\d+)/(?P<api_key>\w+)/rigs.ics$', api_key_required(ical.CalendarICS()),
|
||||
name="ics_calendar"),
|
||||
|
||||
|
||||
|
||||
@@ -338,293 +338,3 @@ class TestAssetAudit(AutoLoginTest):
|
||||
self.wait.until(animation_is_finished())
|
||||
self.assertFalse(self.driver.find_element_by_id('modal').is_displayed())
|
||||
self.assertIn("Asset with that ID does not exist!", self.page.error.text)
|
||||
|
||||
|
||||
class TestSupplierValidation(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.profile = rigsmodels.Profile.objects.create(username="SupplierValidationTest", email="SVT@test.com", is_superuser=True, is_active=True, is_staff=True)
|
||||
cls.supplier = models.Supplier.objects.create(name="Gadgetron Corporation")
|
||||
|
||||
def setUp(self):
|
||||
self.profile.set_password('testuser')
|
||||
self.profile.save()
|
||||
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
|
||||
|
||||
def test_create(self):
|
||||
url = reverse('supplier_create')
|
||||
response = self.client.post(url)
|
||||
self.assertFormError(response, 'form', 'name', 'This field is required.')
|
||||
|
||||
def test_edit(self):
|
||||
url = reverse('supplier_update', kwargs={'pk': self.supplier.pk})
|
||||
response = self.client.post(url, {'name': ""})
|
||||
self.assertFormError(response, 'form', 'name', 'This field is required.')
|
||||
|
||||
|
||||
class Test404(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.profile = rigsmodels.Profile.objects.create(username="404Test", email="404@test.com", is_superuser=True, is_active=True, is_staff=True)
|
||||
|
||||
def setUp(self):
|
||||
self.profile.set_password('testuser')
|
||||
self.profile.save()
|
||||
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
|
||||
|
||||
def test(self):
|
||||
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 = self.client.get(request_url, follow=True)
|
||||
self.assertEqual(response.status_code, 404)
|
||||
|
||||
|
||||
# TODO refactor this for all of RIGS
|
||||
class TestAccessLevels(TestCase):
|
||||
@override_settings(DEBUG=True)
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
# Shortcut to create the levels - bonus side effect of testing the command (hopefully) matches production
|
||||
call_command('generateSampleData')
|
||||
# Create an asset with ID 1 to make things easier in loops (we can always use pk=1)
|
||||
self.category = models.AssetCategory.objects.create(name="Number One")
|
||||
self.status = models.AssetStatus.objects.create(name="Probably Fine", should_show=True)
|
||||
models.Asset.objects.create(asset_id="1", description="Half Price Fish", status=self.status, category=self.category, date_acquired=datetime.date(2020, 2, 1))
|
||||
|
||||
# Nothing should be available to the unauthenticated
|
||||
def test_unauthenticated(self):
|
||||
self.client.logout()
|
||||
for url in filter(lambda url: url.name is not None and "json" not in str(url), urls.urlpatterns):
|
||||
pattern = str(url.pattern)
|
||||
request_url = ""
|
||||
if ":pk>" in pattern:
|
||||
request_url = reverse(url.name, kwargs={'pk': 1})
|
||||
else:
|
||||
request_url = reverse(url.name)
|
||||
if request_url:
|
||||
response = self.client.get(request_url, follow=True, HTTP_HOST='example.com')
|
||||
self.assertContains(response, 'Login')
|
||||
|
||||
def test_basic_access(self):
|
||||
self.assertTrue(self.client.login(username="basic", password="basic"))
|
||||
|
||||
url = reverse('asset_list')
|
||||
response = self.client.get(url)
|
||||
# Check edit and duplicate buttons not shown in list
|
||||
self.assertNotContains(response, 'Edit')
|
||||
self.assertNotContains(response, 'Duplicate')
|
||||
|
||||
url = reverse('asset_detail', kwargs={'pk': "9000"})
|
||||
response = self.client.get(url)
|
||||
self.assertNotContains(response, 'Purchase Details')
|
||||
self.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 = self.client.get(request_url, follow=True)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
request_url = reverse('supplier_create')
|
||||
response = self.client.get(request_url, follow=True)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
request_url = reverse('supplier_update', kwargs={'pk': "1"})
|
||||
response = self.client.get(request_url, follow=True)
|
||||
self.assertEqual(response.status_code, 403)
|
||||
|
||||
def test_keyholder_access(self):
|
||||
self.assertTrue(self.client.login(username="keyholder", password="keyholder"))
|
||||
|
||||
url = reverse('asset_list')
|
||||
response = self.client.get(url)
|
||||
# Check edit and duplicate buttons shown in list
|
||||
self.assertContains(response, 'Edit')
|
||||
self.assertContains(response, 'Duplicate')
|
||||
|
||||
url = reverse('asset_detail', kwargs={'pk': "9000"})
|
||||
response = self.client.get(url)
|
||||
self.assertContains(response, 'Purchase Details')
|
||||
self.assertContains(response, 'View Revision History')
|
||||
|
||||
# def test_finance_access(self): Level not used in assets currently
|
||||
|
||||
def test_page_titles(self):
|
||||
self.assertTrue(self.client.login(username="superuser", password="superuser"))
|
||||
for url in filter(lambda url: url.name is not None and not any(s in url.name for s in ["json", "embed"]), urls.urlpatterns):
|
||||
request_url = ""
|
||||
if ":pk>" in str(url.pattern):
|
||||
request_url = reverse(url.name, kwargs={'pk': "1"})
|
||||
else:
|
||||
request_url = reverse(url.name)
|
||||
response = self.client.get(request_url)
|
||||
if hasattr(response, "context_data") and "page_title" in response.context_data:
|
||||
expected_title = response.context_data["page_title"]
|
||||
self.assertContains(response, '<title>{} | Rig Information Gathering System</title>'.format(expected_title))
|
||||
|
||||
|
||||
class TestFormValidation(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.profile = rigsmodels.Profile.objects.create(username="AssetCreateValidationTest", email="acvt@test.com", is_superuser=True, is_active=True, is_staff=True)
|
||||
cls.category = models.AssetCategory.objects.create(name="Sound")
|
||||
cls.status = models.AssetStatus.objects.create(name="Broken", should_show=True)
|
||||
cls.asset = models.Asset.objects.create(asset_id="9999", description="The Office", status=cls.status, category=cls.category, date_acquired=datetime.date(2018, 6, 15))
|
||||
cls.connector = models.Connector.objects.create(description="16A IEC", current_rating=16, voltage_rating=240, num_pins=3)
|
||||
cls.cable_type = models.CableType.objects.create(circuits=11, cores=3, plug=cls.connector, socket=cls.connector)
|
||||
cls.cable_asset = models.Asset.objects.create(asset_id="666", description="125A -> Jack", comments="The cable from Hell...", status=cls.status, category=cls.category, date_acquired=datetime.date(2006, 6, 6), is_cable=True, cable_type=cls.cable_type, length=10, csa="1.5")
|
||||
|
||||
def setUp(self):
|
||||
self.profile.set_password('testuser')
|
||||
self.profile.save()
|
||||
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
|
||||
|
||||
def test_asset_create(self):
|
||||
url = reverse('asset_create')
|
||||
response = self.client.post(url, {'date_sold': '2000-01-01', 'date_acquired': '2020-01-01', 'purchase_price': '-30', 'salvage_value': '-30'})
|
||||
self.assertFormError(response, 'form', 'asset_id', 'This field is required.')
|
||||
self.assertFormError(response, 'form', 'description', 'This field is required.')
|
||||
self.assertFormError(response, 'form', 'status', 'This field is required.')
|
||||
self.assertFormError(response, 'form', 'category', 'This field is required.')
|
||||
|
||||
self.assertFormError(response, 'form', 'date_sold', 'Cannot sell an item before it is acquired')
|
||||
self.assertFormError(response, 'form', 'purchase_price', 'A price cannot be negative')
|
||||
self.assertFormError(response, 'form', 'salvage_value', 'A price cannot be negative')
|
||||
|
||||
def test_cable_create(self):
|
||||
url = reverse('asset_create')
|
||||
response = self.client.post(url, {'asset_id': 'X$%A', 'is_cable': True})
|
||||
self.assertFormError(response, 'form', 'asset_id', 'An Asset ID can only consist of letters and numbers, with a final number')
|
||||
|
||||
self.assertFormError(response, 'form', 'cable_type', 'A cable must have a type')
|
||||
self.assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
||||
self.assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
||||
|
||||
# Given that validation is done at model level it *shouldn't* need retesting...gonna do it anyway!
|
||||
def test_asset_edit(self):
|
||||
url = reverse('asset_update', kwargs={'pk': self.asset.asset_id})
|
||||
response = self.client.post(url, {'date_sold': '2000-12-01', 'date_acquired': '2020-12-01', 'purchase_price': '-50', 'salvage_value': '-50', 'description': "", 'status': "", 'category': ""})
|
||||
# self.assertFormError(response, 'form', 'asset_id', 'This field is required.')
|
||||
self.assertFormError(response, 'form', 'description', 'This field is required.')
|
||||
self.assertFormError(response, 'form', 'status', 'This field is required.')
|
||||
self.assertFormError(response, 'form', 'category', 'This field is required.')
|
||||
|
||||
self.assertFormError(response, 'form', 'date_sold', 'Cannot sell an item before it is acquired')
|
||||
self.assertFormError(response, 'form', 'purchase_price', 'A price cannot be negative')
|
||||
self.assertFormError(response, 'form', 'salvage_value', 'A price cannot be negative')
|
||||
|
||||
def test_cable_edit(self):
|
||||
url = reverse('asset_update', kwargs={'pk': self.cable_asset.asset_id})
|
||||
# TODO Why do I have to send is_cable=True here?
|
||||
response = self.client.post(url, {'is_cable': True, 'length': -3, 'csa': -3})
|
||||
|
||||
# TODO Can't figure out how to select the 'none' option...
|
||||
# self.assertFormError(response, 'form', 'cable_type', 'A cable must have a type')
|
||||
self.assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
||||
self.assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
||||
|
||||
def test_asset_duplicate(self):
|
||||
url = reverse('asset_duplicate', kwargs={'pk': self.cable_asset.asset_id})
|
||||
response = self.client.post(url, {'is_cable': True, 'length': 0, 'csa': 0})
|
||||
|
||||
self.assertFormError(response, 'form', 'length', 'The length of a cable must be more than 0')
|
||||
self.assertFormError(response, 'form', 'csa', 'The CSA of a cable must be more than 0')
|
||||
|
||||
|
||||
class TestSampleDataGenerator(TestCase):
|
||||
@override_settings(DEBUG=True)
|
||||
def test_generate_sample_data(self):
|
||||
# Run the management command and check there are no exceptions
|
||||
call_command('generateSampleAssetsData')
|
||||
|
||||
# Check there are lots
|
||||
self.assertTrue(models.Asset.objects.all().count() > 50)
|
||||
self.assertTrue(models.Supplier.objects.all().count() > 50)
|
||||
|
||||
@override_settings(DEBUG=True)
|
||||
def test_delete_sample_data(self):
|
||||
call_command('deleteSampleData')
|
||||
|
||||
self.assertTrue(models.Asset.objects.all().count() == 0)
|
||||
self.assertTrue(models.Supplier.objects.all().count() == 0)
|
||||
|
||||
def test_production_exception(self):
|
||||
from django.core.management.base import CommandError
|
||||
|
||||
self.assertRaisesRegex(CommandError, ".*production", call_command, 'generateSampleAssetsData')
|
||||
self.assertRaisesRegex(CommandError, ".*production", call_command, 'deleteSampleData')
|
||||
|
||||
|
||||
class TestEmbeddedViews(TestCase):
|
||||
@classmethod
|
||||
def setUpTestData(cls):
|
||||
cls.profile = rigsmodels.Profile.objects.create(username="EmbeddedViewsTest", email="embedded@test.com", is_superuser=True, is_active=True, is_staff=True)
|
||||
|
||||
working = models.AssetStatus.objects.create(name="Working", should_show=True)
|
||||
lighting = models.AssetCategory.objects.create(name="Lighting")
|
||||
|
||||
cls.assets = {
|
||||
1: models.Asset.objects.create(asset_id="1991", description="Spaceflower", status=working, category=lighting, date_acquired=datetime.date(1991, 12, 26))
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
self.profile.set_password('testuser')
|
||||
self.profile.save()
|
||||
|
||||
def testLoginRedirect(self):
|
||||
request_url = reverse('asset_embed', kwargs={'pk': self.assets[1].asset_id})
|
||||
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
|
||||
|
||||
# Request the page and check it redirects
|
||||
response = self.client.get(request_url, follow=True)
|
||||
self.assertRedirects(response, expected_url, status_code=302, target_status_code=200)
|
||||
|
||||
# Now login
|
||||
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
|
||||
|
||||
# And check that it no longer redirects
|
||||
response = self.client.get(request_url, follow=True)
|
||||
self.assertEqual(len(response.redirect_chain), 0)
|
||||
|
||||
def testLoginCookieWarning(self):
|
||||
login_url = reverse('login_embed')
|
||||
response = self.client.post(login_url, follow=True)
|
||||
self.assertContains(response, "Cookies do not seem to be enabled")
|
||||
|
||||
def testXFrameHeaders(self):
|
||||
asset_url = reverse('asset_embed', kwargs={'pk': self.assets[1].asset_id})
|
||||
login_url = reverse('login_embed')
|
||||
|
||||
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
|
||||
|
||||
response = self.client.get(asset_url, follow=True)
|
||||
with self.assertRaises(KeyError):
|
||||
response._headers["X-Frame-Options"]
|
||||
|
||||
response = self.client.get(login_url, follow=True)
|
||||
with self.assertRaises(KeyError):
|
||||
response._headers["X-Frame-Options"]
|
||||
|
||||
def testOEmbed(self):
|
||||
asset_url = reverse('asset_detail', kwargs={'pk': self.assets[1].asset_id})
|
||||
asset_embed_url = reverse('asset_embed', kwargs={'pk': self.assets[1].asset_id})
|
||||
oembed_url = reverse('asset_oembed', kwargs={'pk': self.assets[1].asset_id})
|
||||
|
||||
alt_oembed_url = reverse('asset_oembed', kwargs={'pk': 999})
|
||||
alt_asset_embed_url = reverse('asset_embed', kwargs={'pk': 999})
|
||||
|
||||
# Test the meta tag is in place
|
||||
response = self.client.get(asset_url, follow=True, HTTP_HOST='example.com')
|
||||
self.assertContains(response, '<link rel="alternate" type="application/json+oembed"')
|
||||
self.assertContains(response, oembed_url)
|
||||
|
||||
# Test that the JSON exists
|
||||
response = self.client.get(oembed_url, follow=True, HTTP_HOST='example.com')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, asset_embed_url)
|
||||
|
||||
# Should also work for non-existant
|
||||
response = self.client.get(alt_oembed_url, follow=True, HTTP_HOST='example.com')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertContains(response, alt_asset_embed_url)
|
||||
277
assets/tests/test_unit.py
Normal file
277
assets/tests/test_unit.py
Normal file
@@ -0,0 +1,277 @@
|
||||
from django.core.management import call_command
|
||||
from assets import models
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from urllib.parse import urlparse
|
||||
from assets import models, urls
|
||||
from reversion import revisions as reversion
|
||||
import datetime
|
||||
from django.utils import timezone
|
||||
from django.test import tag
|
||||
import pytest
|
||||
from pytest_django.asserts import assertFormError, assertRedirects
|
||||
|
||||
pytestmark = pytest.mark.django_db # TODO
|
||||
|
||||
def response_contains(response, needle):
|
||||
return needle in str(response.content)
|
||||
|
||||
def login(client, django_user_model):
|
||||
pwd = 'testuser'
|
||||
usr = "TestUser"
|
||||
profile = django_user_model.objects.create_user(username=usr, email="TestUser@test.com", password=pwd, is_superuser=True, is_active=True, is_staff=True)
|
||||
assert client.login(username=usr, password=pwd)
|
||||
|
||||
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
|
||||
|
||||
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 test_supplier_create(client, django_user_model):
|
||||
login(client, django_user_model)
|
||||
url = reverse('supplier_create')
|
||||
response = client.post(url)
|
||||
assertFormError(response, 'form', 'name', 'This field is required.')
|
||||
|
||||
def test_supplier_edit(client, django_user_model):
|
||||
login(client, django_user_model)
|
||||
supplier = models.Supplier.objects.create(name="Gadgetron Corporation")
|
||||
url = reverse('supplier_update', kwargs={'pk': supplier.pk})
|
||||
response = client.post(url, {'name': ""})
|
||||
assertFormError(response, 'form', 'name', 'This field is required.')
|
||||
|
||||
def test_404(client, django_user_model):
|
||||
login(client, django_user_model)
|
||||
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)
|
||||
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})
|
||||
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
|
||||
|
||||
# Request the page and check it redirects
|
||||
response = client.get(request_url, follow=True)
|
||||
assertRedirects(response, expected_url, status_code=302, target_status_code=200)
|
||||
|
||||
# Now login
|
||||
login(client, django_user_model)
|
||||
|
||||
# And check that it no longer redirects
|
||||
response = client.get(request_url, follow=True)
|
||||
assert len(response.redirect_chain) == 0
|
||||
|
||||
def test_login_cookie_warning(client, django_user_model):
|
||||
login_url = reverse('login_embed')
|
||||
response = client.post(login_url, follow=True)
|
||||
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})
|
||||
login_url = reverse('login_embed')
|
||||
|
||||
login(client, django_user_model)
|
||||
|
||||
response = client.get(asset_url, follow=True)
|
||||
with pytest.raises(KeyError):
|
||||
response._headers["X-Frame-Options"]
|
||||
|
||||
response = client.get(login_url, follow=True)
|
||||
with pytest.raises(KeyError):
|
||||
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})
|
||||
|
||||
alt_oembed_url = reverse('asset_oembed', kwargs={'pk': 999})
|
||||
alt_asset_embed_url = reverse('asset_embed', kwargs={'pk': 999})
|
||||
|
||||
# Test the meta tag is in place
|
||||
response = client.get(asset_url, follow=True, HTTP_HOST='example.com')
|
||||
assert '<link rel="alternate" type="application/json+oembed"' in str(response.content)
|
||||
assert oembed_url in str(response.content)
|
||||
|
||||
# Test that the JSON exists
|
||||
response = client.get(oembed_url, follow=True, HTTP_HOST='example.com')
|
||||
assert response.status_code == 200
|
||||
assert asset_embed_url in str(response.content)
|
||||
|
||||
# Should also work for non-existant
|
||||
response = client.get(alt_oembed_url, follow=True, HTTP_HOST='example.com')
|
||||
assert response.status_code == 200
|
||||
assert alt_asset_embed_url in str(response.content)
|
||||
|
||||
@override_settings(DEBUG=True)
|
||||
|
||||
def test_generate_sample_data(client):
|
||||
# Run the management command and check there are no exceptions
|
||||
call_command('generateSampleAssetsData')
|
||||
|
||||
# Check there are lots
|
||||
assert models.Asset.objects.all().count() > 50
|
||||
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
|
||||
|
||||
with pytest.raises(CommandError, match=".*production"):
|
||||
call_command('generateSampleAssetsData')
|
||||
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'})
|
||||
assertFormError(response, 'form', 'asset_id', 'This field is required.')
|
||||
assertFormError(response, 'form', 'description', 'This field is required.')
|
||||
assertFormError(response, 'form', 'status', 'This field is required.')
|
||||
assertFormError(response, 'form', 'category', 'This field is required.')
|
||||
|
||||
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 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})
|
||||
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')
|
||||
|
||||
# Given that validation is done at model level it *shouldn't* need retesting...gonna do it anyway!
|
||||
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': ""})
|
||||
# assertFormError(response, 'form', 'asset_id', 'This field is required.')
|
||||
assertFormError(response, 'form', 'description', 'This field is required.')
|
||||
assertFormError(response, 'form', 'status', 'This field is required.')
|
||||
assertFormError(response, 'form', 'category', 'This field is required.')
|
||||
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 test_cable_edit(client, django_user_model):
|
||||
login(client, django_user_model)
|
||||
url = reverse('asset_update', kwargs={'pk': create_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})
|
||||
|
||||
# TODO Can't figure out how to select the 'none' option...
|
||||
# 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_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})
|
||||
|
||||
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')
|
||||
|
||||
@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('generateSampleData')
|
||||
# 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))
|
||||
|
||||
# Nothing should be available to the unauthenticated
|
||||
def test_unauthenticated(client):
|
||||
for url in filter(lambda url: url.name is not None and "json" not in str(url), urls.urlpatterns):
|
||||
pattern = str(url.pattern)
|
||||
request_url = ""
|
||||
if ":pk>" in pattern:
|
||||
request_url = reverse(url.name, kwargs={'pk': 1})
|
||||
else:
|
||||
request_url = reverse(url.name)
|
||||
if request_url:
|
||||
response = client.get(request_url, follow=True, HTTP_HOST='example.com')
|
||||
# TODO Check the URL here
|
||||
assert response_contains(response, 'Login')
|
||||
|
||||
def test_basic_access(client):
|
||||
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
|
||||
assert not response_contains(response, 'Edit')
|
||||
assert not response_contains(response, 'Duplicate')
|
||||
|
||||
url = reverse('asset_detail', kwargs={'pk': "9000"})
|
||||
response = client.get(url)
|
||||
assert not response_contains(response, 'Purchase Details')
|
||||
assert not response_contains(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):
|
||||
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
|
||||
assert response_contains(response, 'Edit')
|
||||
assert response_contains(response, 'Duplicate')
|
||||
|
||||
url = reverse('asset_detail', kwargs={'pk': "9000"})
|
||||
response = client.get(url)
|
||||
assert response_contains(response, 'Purchase Details')
|
||||
assert response_contains(response, 'View Revision History')
|
||||
|
||||
def test_page_titles(admin_client):
|
||||
for url in filter(lambda url: url.name is not None and not any(s in url.name for s in ["json", "embed"]), urls.urlpatterns):
|
||||
request_url = ""
|
||||
if ":pk>" in str(url.pattern):
|
||||
request_url = reverse(url.name, kwargs={'pk': "1"})
|
||||
else:
|
||||
request_url = reverse(url.name)
|
||||
response = admin_client.get(request_url)
|
||||
if hasattr(response, "context_data") and "page_title" in response.context_data:
|
||||
expected_title = response.context_data["page_title"]
|
||||
assert response_contains(response, '<title>{} | Rig Information Gathering System</title>'.format(expected_title))
|
||||
@@ -14,10 +14,9 @@ from django.utils.decorators import method_decorator
|
||||
from django.views import generic
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from versioning import versioning
|
||||
from PyRIGS.views import GenericListView, GenericDetailView, GenericUpdateView, GenericCreateView, ModalURLMixin
|
||||
from PyRIGS.views import GenericListView, GenericDetailView, GenericUpdateView, GenericCreateView, ModalURLMixin, is_ajax
|
||||
from itertools import chain
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
class AssetList(LoginRequiredMixin, generic.ListView):
|
||||
model = models.Asset
|
||||
@@ -121,7 +120,7 @@ class AssetEdit(LoginRequiredMixin, AssetIDUrlMixin, generic.UpdateView):
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
url = reverse_lazy('closemodal')
|
||||
update_url = str(reverse_lazy('asset_update', kwargs={'pk': self.object.pk}))
|
||||
messages.info(self.request, "modalobject=" + serializers.serialize("json", [self.object]))
|
||||
@@ -235,7 +234,7 @@ class SupplierList(GenericListView):
|
||||
context['edit'] = 'supplier_update'
|
||||
context['can_edit'] = self.request.user.has_perm('assets.change_supplier')
|
||||
context['detail'] = 'supplier_detail'
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
else:
|
||||
context['override'] = 'base_assets.html'
|
||||
@@ -263,7 +262,7 @@ class SupplierDetail(GenericDetailView):
|
||||
context['detail_link'] = 'supplier_detail'
|
||||
context['associated'] = 'partials/associated_assets.html'
|
||||
context['associated2'] = ''
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
else:
|
||||
context['override'] = 'base_assets.html'
|
||||
@@ -277,7 +276,7 @@ class SupplierCreate(GenericCreateView, ModalURLMixin):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(SupplierCreate, self).get_context_data(**kwargs)
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
else:
|
||||
context['override'] = 'base_assets.html'
|
||||
@@ -293,7 +292,7 @@ class SupplierUpdate(GenericUpdateView, ModalURLMixin):
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(SupplierUpdate, self).get_context_data(**kwargs)
|
||||
if self.request.is_ajax():
|
||||
if is_ajax(self.request):
|
||||
context['override'] = "base_ajax.html"
|
||||
else:
|
||||
context['override'] = 'base_assets.html'
|
||||
|
||||
@@ -1,2 +1,3 @@
|
||||
[pytest]
|
||||
DJANGO_SETTINGS_MODULE = myproject.settings
|
||||
DJANGO_SETTINGS_MODULE = PyRIGS.settings
|
||||
# FAIL_INVALID_TEMPLATE_VARS = True
|
||||
|
||||
@@ -42,7 +42,6 @@ premailer==3.7.0
|
||||
progress==1.5
|
||||
psutil==5.8.0
|
||||
psycopg2==2.8.6
|
||||
psycopg2-binary==2.8.6
|
||||
Pygments==2.7.4
|
||||
pyparsing==2.4.7
|
||||
PyPDF2==1.26.0
|
||||
@@ -50,6 +49,8 @@ PyPOM==2.2.0
|
||||
python-dateutil==2.8.1
|
||||
pytoml==0.1.21
|
||||
pytz==2020.5
|
||||
pytest_django==4.1.0
|
||||
pytest-xdist==2.2.0
|
||||
raven==6.10.0
|
||||
reportlab==3.5.60
|
||||
requests==2.25.1
|
||||
|
||||
@@ -305,6 +305,3 @@ class TestVersioningViews(TestCase):
|
||||
response = self.client.get(request_url, follow=True)
|
||||
self.assertContains(response, "Test Person")
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
|
||||
# Functional Tests
|
||||
|
||||
Reference in New Issue
Block a user