mirror of
https://github.com/nottinghamtec/PyRIGS.git
synced 2026-03-03 10:38:23 +00:00
Toolchain/Dependency Upgrade (#418)
* Upgrade to heroku-20 stack * Move some gulp deps to dev rather than prod * npm upgrade * Fix audit time check in asset audit test * Attempt at parallelising tests where possible * Add basic calendar button test Mainly to pickup on FullCalendar loading errors * Upgrade python deps * Tends to help if I push valid yaml * You valid now? * Fix whoops in requirements.txt * Change python ver * Define service in coveralls task * Run parallelised RIGS tests as one matrix job * Update python version in tests * Cache python dependencies Should majorly speedup parallelillelelised testing * Purge old vagrant config * No Ruby compass bodge, no need for rubocop! * Purge old .idea config * Switch to gh-a artifact uploading instead of imgur 'hack' For test failure screenshots. Happy now @mattysmith22? ;p * Oops, remove unused import * Exclude tests from the coverage stats Seems to be artifically deflating our stats * Refactor asset audit tests with better selectors Also fixed a silly title error with the modal * Add title checking to the slightly insane assets test * Fix unauth test to not just immediately pass out * Upload failure screenshots as individual artifacts not a zip Turns out I can't unzip things from my phone, which is a pain * Should fix asset test on CI * What about this? * What about this? Swear I spend my life jiggerypokerying the damn test suite... * Does this help the coverage be less weird? * Revert "Does this help the coverage be less weird?" This reverts commit39ab9df836. * 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. * Bah, codestyle * Oops, remove obsolete if check * Fix screenshot uploading on CI (again) * Try this way of parallel coverage * Add codeclimate maintainability badge * Remove some unused gulp dependencies * Run asset building serverside * Still helps if I commit valid YAML * See below * Different approach to CI dependencies * Exclude node_modules from codestyle * Does this work? * Parallel parallel builds were giving me a headache, try this * Update codeclimate settings, purge some config files * Well the YAML was *syntactically* valid.... * Switch back to old coveralls method * Fix codeclimate config, mark 2 * Attempt to bodge asset test * Oops, again Probably bedtime.. * Might fix heroku building * Attempt #2 at fixing heroku * Belt and braces approach to coverage * Github, you need a Actions YAML validator! * Might fix actions? * Try ignoring some third party deprecation warnings * Another go at making coverage show up * Some template cleanup * Minor python cleanup * Import optimisation * Revert "Minor python cleanup" This reverts commit6a4620a2e5. * Add format arg to coverage command * Ignore test directories from Heroku slug * Maybe this works to purge deps postbuild * Bunch of test refactoring * Restore signals import, screw you import optimisation * Further template refactoring * Add support for running tests with geckodriver, do this on CI * Screw you codestyle * Disable firefox tests for now That was way more errors than I expected * Run cleanup script from the right location * Plausibly fix tests * Helps if I don't delete the pipeline folder prior to collectstatic * Enable whitenoise * Can I delete pipeline here? * Allow seconds difference in assert_times_equal * Disable codeclimate * Remove not working rm command * Maybe this fixes coverage? * Try different coverage reporter * Fix search_help to need login * Made versioning magic a bit less expansive We have more apps than I thought... * Fix IDI0T error in Assets URLS * Refactor 'no access to unauthed' test to cover all of PyRIGS * Add RAs/Checklists to sample data generator * Fix some HTML errors in templates Which apparently only Django's HTML parser cares about, browsers DGAF... * Port title test to project level * Fix more HTML * Fix cable type detail
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
from django.contrib import admin
|
||||
from reversion.admin import VersionAdmin
|
||||
|
||||
from assets import models as assets
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from django import forms
|
||||
from django.db.models import Q
|
||||
|
||||
from assets import models
|
||||
from django.db.models import Q
|
||||
|
||||
|
||||
class AssetForm(forms.ModelForm):
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import random
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.utils import timezone
|
||||
from reversion import revisions as reversion
|
||||
from assets import models
|
||||
|
||||
from RIGS import models as rigsmodels
|
||||
from assets import models
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
import re
|
||||
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.db import models, connection
|
||||
from django.urls import reverse
|
||||
|
||||
from django.db.models.signals import pre_save
|
||||
from django.dispatch.dispatcher import receiver
|
||||
|
||||
from django.urls import reverse
|
||||
from reversion import revisions as reversion
|
||||
from reversion.models import Version
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
{% extends request.is_ajax|yesno:'base_ajax.html,base_assets.html' %}
|
||||
{% load widget_tweaks %}
|
||||
{% block title %}Audit Asset {{ object.asset_id }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<script>
|
||||
@@ -15,11 +14,7 @@
|
||||
$('#' + String(ID)).val(CSA);
|
||||
}
|
||||
function checkIfCableHidden() {
|
||||
if (document.getElementById("id_is_cable").checked) {
|
||||
document.getElementById("cable-table").hidden = false;
|
||||
} else {
|
||||
document.getElementById("cable-table").hidden = true;
|
||||
}
|
||||
document.getElementById("cable-table").hidden = !document.getElementById("id_is_cable").checked;
|
||||
}
|
||||
checkIfCableHidden();
|
||||
</script>
|
||||
@@ -45,8 +40,8 @@
|
||||
<div class="form-group form-row">
|
||||
{% include 'partials/form_field.html' with field=form.date_acquired col="col-6" %}
|
||||
<div class="col-sm-4">
|
||||
<btn class="btn btn-info" onclick="setAcquired(true);" tabindex="-1">Today</btn>
|
||||
<btn class="btn btn-warning" onclick="setAcquired(false);" tabindex="-1">Unknown</btn>
|
||||
<button class="btn btn-info" onclick="setAcquired(true);" tabindex="-1">Today</button>
|
||||
<button class="btn btn-warning" onclick="setAcquired(false);" tabindex="-1">Unknown</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group form-row">
|
||||
@@ -69,16 +64,16 @@
|
||||
<div class="form-group form-row">
|
||||
{% include 'partials/form_field.html' with field=form.length append=form.length.help_text col="col-6" %}
|
||||
<div class="col-4">
|
||||
<btn class="btn btn-danger" onclick="setFieldValue('{{ form.length.id_for_label }}','5');" tabindex="-1">5{{ form.length.help_text }}</btn>
|
||||
<btn class="btn btn-success" onclick="setFieldValue('{{ form.length.id_for_label }}','10');" tabindex="-1">10{{ form.length.help_text }}</btn>
|
||||
<btn class="btn btn-info" onclick="setFieldValue('{{ form.length.id_for_label }}','20');" tabindex="-1">20{{ form.length.help_text }}</btn>
|
||||
<button class="btn btn-danger" onclick="setFieldValue('{{ form.length.id_for_label }}','5');" tabindex="-1">5{{ form.length.help_text }}</button>
|
||||
<button class="btn btn-success" onclick="setFieldValue('{{ form.length.id_for_label }}','10');" tabindex="-1">10{{ form.length.help_text }}</button>
|
||||
<button class="btn btn-info" onclick="setFieldValue('{{ form.length.id_for_label }}','20');" tabindex="-1">20{{ form.length.help_text }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group form-row">
|
||||
{% include 'partials/form_field.html' with field=form.csa append=form.csa.help_text title='CSA' col="col-6" %}
|
||||
<div class="col-4">
|
||||
<btn class="btn btn-secondary" onclick="setFieldValue('{{ form.csa.id_for_label }}', '1.5');" tabindex="-1">1.5{{ form.csa.help_text }}</btn>
|
||||
<btn class="btn btn-secondary" onclick="setFieldValue('{{ form.csa.id_for_label }}', '2.5');" tabindex="-1">2.5{{ form.csa.help_text }}</btn>
|
||||
<button class="btn btn-secondary" onclick="setFieldValue('{{ form.csa.id_for_label }}', '1.5');" tabindex="-1">1.5{{ form.csa.help_text }}</button>
|
||||
<button class="btn btn-secondary" onclick="setFieldValue('{{ form.csa.id_for_label }}', '2.5');" tabindex="-1">2.5{{ form.csa.help_text }}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
{% load widget_tweaks %}
|
||||
|
||||
{% block js %}
|
||||
<script src="//code.jquery.com/ui/1.10.4/jquery-ui.js"></script>
|
||||
<script src="{% static 'js/jquery-ui.js' %}"></script>
|
||||
<script src="{% static "js/interaction.js" %}"></script>
|
||||
<script src="{% static "js/modal.js" %}"></script>
|
||||
<script>
|
||||
@@ -23,7 +23,7 @@
|
||||
success: function(){
|
||||
$link = $(this);
|
||||
// Anti modal inception
|
||||
if ($link.parents('#modal').length == 0) {
|
||||
if ($link.parents('#modal').length === 0) {
|
||||
modaltarget = $link.data('target');
|
||||
modalobject = "";
|
||||
$('#modal').load(url, function (e) {
|
||||
|
||||
@@ -2,37 +2,35 @@
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<a href="/assets">
|
||||
<span class="source"> TEC Asset Database</span>
|
||||
</a>
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<a href="/assets">
|
||||
<span class="source"> TEC Asset Database</span>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-sm-12">
|
||||
<h3><a href="{% url 'asset_detail' object.asset_id %}">Asset: {{ object.asset_id }} | {{ object.description }} </a></h3>
|
||||
<h4>
|
||||
<span class="label label-default">
|
||||
<strong>Category:</strong>
|
||||
{{ object.category }}
|
||||
</span>
|
||||
|
||||
<span class="label label-{{ object.status.display_class|default:'default' }}">
|
||||
<strong>Status:</strong>
|
||||
{{ object.status }}
|
||||
</span>
|
||||
</h4>
|
||||
<dl>
|
||||
{% if object.serial_number %}
|
||||
<dt>Serial Number: </dt>
|
||||
<dd>{{ object.serial_number }}</dd>
|
||||
{% endif %}
|
||||
{% if object.comments %}
|
||||
<dt>Comments: </dt>
|
||||
<dd class="dont-break-out">{{ object.comments|linebreaksbr }}<dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-12">
|
||||
<h3><a href="{% url 'asset_detail' object.asset_id %}">Asset: {{ object.asset_id }} | {{ object.description }} </a></h3>
|
||||
<h4>
|
||||
<span class="label label-default">
|
||||
<strong>Category:</strong>
|
||||
{{ object.category }}
|
||||
</span>
|
||||
|
||||
<span class="label label-{{ object.status.display_class|default:'default' }}">
|
||||
<strong>Status:</strong>
|
||||
{{ object.status }}
|
||||
</span>
|
||||
</h4>
|
||||
<dl>
|
||||
{% if object.serial_number %}
|
||||
<dt>Serial Number: </dt>
|
||||
<dd>{{ object.serial_number }}</dd>
|
||||
{% endif %}
|
||||
{% if object.comments %}
|
||||
<dt>Comments: </dt>
|
||||
<dd class="dont-break-out">{{ object.comments|linebreaksbr }}<dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
url: '{% url 'asset_search_json' %}',
|
||||
type: "GET",
|
||||
data: function () {
|
||||
var params = {
|
||||
let params = {
|
||||
{% verbatim %}query: '{{{q}}}'{% endverbatim %}
|
||||
};
|
||||
return params;
|
||||
|
||||
@@ -30,19 +30,19 @@
|
||||
//Get query string value
|
||||
var searchUrl=location.search;
|
||||
|
||||
if(key!="") {
|
||||
if(key!=="") {
|
||||
oldValue = getParameterByName(key);
|
||||
removeVal=key+"="+oldValue;
|
||||
if(searchUrl.indexOf('?'+removeVal+'&')!= "-1") {
|
||||
if(searchUrl.indexOf('?'+removeVal+'&')!== "-1") {
|
||||
urlValue=urlValue.replace('?'+removeVal+'&','?');
|
||||
}
|
||||
else if(searchUrl.indexOf('&'+removeVal+'&')!= "-1") {
|
||||
else if(searchUrl.indexOf('&'+removeVal+'&')!== "-1") {
|
||||
urlValue=urlValue.replace('&'+removeVal+'&','&');
|
||||
}
|
||||
else if(searchUrl.indexOf('?'+removeVal)!= "-1") {
|
||||
else if(searchUrl.indexOf('?'+removeVal)!== "-1") {
|
||||
urlValue=urlValue.replace('?'+removeVal,'');
|
||||
}
|
||||
else if(searchUrl.indexOf('&'+removeVal)!= "-1") {
|
||||
else if(searchUrl.indexOf('&'+removeVal)!== "-1") {
|
||||
urlValue=urlValue.replace('&'+removeVal,'');
|
||||
}
|
||||
}
|
||||
|
||||
40
assets/templates/cable_type_detail.html
Normal file
40
assets/templates/cable_type_detail.html
Normal file
@@ -0,0 +1,40 @@
|
||||
{% extends 'base_assets.html' %}
|
||||
{% load button from filters %}
|
||||
{% load cache %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<dl class="card-body row">
|
||||
<dt class="col-6">Socket</dt>
|
||||
<dd>{{ object.socket|default_if_none:'-' }}</dd>
|
||||
|
||||
<dt class="col-6">Plug</dt>
|
||||
<dd>{{ object.plug|default_if_none:'-' }}</dd>
|
||||
|
||||
<dt class="col-6">Circuits</dt>
|
||||
<dd>{{ object.circuits|default_if_none:'-' }}</dd>
|
||||
|
||||
<dt class="col-6">Cores</dt>
|
||||
<dd>{{ object.cores|default_if_none:'-' }}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col mt-2 text-right">
|
||||
{% button 'edit' url='cable_type_update' pk=object.id %}
|
||||
</div>
|
||||
{% cache None cable_type_assets object %}
|
||||
<div class="col mt-2">
|
||||
<div class="card">
|
||||
<div class="card-header">Associated Assets</div>
|
||||
{% with object.asset_set.all as object_list %}
|
||||
{% include 'partials/asset_list_table.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
{% endcache %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -4,58 +4,25 @@
|
||||
{% load cache %}
|
||||
|
||||
{% block content %}
|
||||
{% if create %}
|
||||
<form method="POST" action="{% url 'cable_type_create'%}">
|
||||
{% elif edit %}
|
||||
<form method="POST" action="{% url 'cable_type_update' object.id %}">
|
||||
{% endif %}
|
||||
{% if create %}
|
||||
<form method="POST" action="{% url 'cable_type_create'%}">
|
||||
{% elif edit %}
|
||||
<form method="POST" action="{% url 'cable_type_update' object.id %}">
|
||||
{% endif %}
|
||||
{% include 'form_errors.html' %}
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="id" value="{{ object.id|default:0 }}" hidden=true>
|
||||
<input type="hidden" name="id" value="{{ object.id|default:0 }}" hidden="">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
{% if create or edit %}
|
||||
{% for field in form %}
|
||||
<div class="form-group">
|
||||
{% include 'partials/form_field.html' with field=field %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="text-right">
|
||||
{% button 'submit' %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<dl class="card-body row">
|
||||
<dt class="col-6">Socket</dt>
|
||||
<dd>{{ object.socket|default_if_none:'-' }}</dd>
|
||||
|
||||
<dt class="col-6">Plug</dt>
|
||||
<dd>{{ object.plug|default_if_none:'-' }}</dd>
|
||||
|
||||
<dt class="col-6">Circuits</dt>
|
||||
<dd>{{ object.circuits|default_if_none:'-' }}</dd>
|
||||
|
||||
<dt class="col-6">Cores</dt>
|
||||
<dd>{{ object.cores|default_if_none:'-' }}</dd>
|
||||
</dl>
|
||||
<div class="col-sm-12">
|
||||
{% for field in form %}
|
||||
<div class="form-group">
|
||||
{% include 'partials/form_field.html' with field=field %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="text-right">
|
||||
{% button 'submit' %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col mt-2 text-right">
|
||||
{% button 'edit' url='cable_type_update' pk=object.id %}
|
||||
</div>
|
||||
{% cache None cable_type_assets object %}
|
||||
<div class="col mt-2">
|
||||
<div class="card">
|
||||
<div class="card-header">Associated Assets</div>
|
||||
{% with object.asset_set.all as object_list %}
|
||||
{% include 'partials/asset_list_table.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
{% endcache %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Collection of page object models for use within tests.
|
||||
from pypom import Page, Region
|
||||
from django.urls import reverse
|
||||
from pypom import Region
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support import expected_conditions
|
||||
from selenium.webdriver import Chrome
|
||||
from django.urls import reverse
|
||||
|
||||
from PyRIGS.tests import regions
|
||||
from PyRIGS.tests.pages import BasePage, FormPage
|
||||
from selenium.common.exceptions import NoSuchElementException
|
||||
from PyRIGS.tests.pages import BasePage, FormPage, animation_is_finished
|
||||
|
||||
|
||||
class AssetList(BasePage):
|
||||
@@ -190,13 +190,14 @@ 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()
|
||||
self.wait.until(animation_is_finished())
|
||||
|
||||
@property
|
||||
def error(self):
|
||||
@@ -205,10 +206,12 @@ 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")
|
||||
_close_selector = (By.XPATH, "//button[@data-dismiss='modal']")
|
||||
|
||||
form_items = {
|
||||
'asset_id': (regions.TextBox, (By.ID, 'id_asset_id')),
|
||||
'description': (regions.TextBox, (By.ID, 'id_description')),
|
||||
@@ -235,27 +238,10 @@ class AssetAuditList(AssetList):
|
||||
except NoSuchElementException:
|
||||
return None
|
||||
|
||||
def submit(self):
|
||||
previous_errors = self.errors
|
||||
self.root.find_element(*self._submit_locator).click()
|
||||
# self.wait.until(lambda x: not self.is_displayed) TODO
|
||||
def close(self):
|
||||
self.page.find_element(*self._close_selector).click()
|
||||
self.wait.until(expected_conditions.invisibility_of_element_located((By.ID, 'modal')))
|
||||
|
||||
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
|
||||
|
||||
@@ -1,622 +0,0 @@
|
||||
from . import pages
|
||||
from django.core.management import call_command
|
||||
from django.test import TestCase
|
||||
from assets import models
|
||||
from django.test.utils import override_settings
|
||||
from django.urls import reverse
|
||||
from urllib.parse import urlparse
|
||||
from RIGS import models as rigsmodels
|
||||
from PyRIGS.tests.base import BaseTest, AutoLoginTest, screenshot_failure_cls
|
||||
from assets import models, urls
|
||||
from reversion import revisions as reversion
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.common.keys import Keys
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from PyRIGS.tests.base import animation_is_finished
|
||||
import datetime
|
||||
from django.utils import timezone
|
||||
from selenium.webdriver.common.action_chains import ActionChains
|
||||
from django.test import tag
|
||||
|
||||
|
||||
@screenshot_failure_cls
|
||||
class TestAssetList(AutoLoginTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
sound = models.AssetCategory.objects.create(name="Sound")
|
||||
lighting = models.AssetCategory.objects.create(name="Lighting")
|
||||
|
||||
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))
|
||||
self.page = pages.AssetList(self.driver, self.live_server_url).open()
|
||||
|
||||
def test_default_statuses_applied(self):
|
||||
# Only the working stuff should be shown initially
|
||||
assetDescriptions = list(map(lambda x: x.description, self.page.assets))
|
||||
self.assertEqual(2, len(assetDescriptions))
|
||||
self.assertIn("A light", assetDescriptions)
|
||||
self.assertIn("Working Mic", assetDescriptions)
|
||||
|
||||
def test_asset_order(self):
|
||||
# Only the working stuff should be shown initially
|
||||
self.page.status_selector.open()
|
||||
self.page.status_selector.set_option("Broken", True)
|
||||
self.page.status_selector.close()
|
||||
|
||||
self.page.search()
|
||||
|
||||
assetIDs = list(map(lambda x: x.id, self.page.assets))
|
||||
self.assertEqual("1", assetIDs[0])
|
||||
self.assertEqual("2", assetIDs[1])
|
||||
self.assertEqual("10", assetIDs[2])
|
||||
self.assertEqual("C1", assetIDs[3])
|
||||
|
||||
def test_search(self):
|
||||
self.page.set_query("10")
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.assets) == 1)
|
||||
self.assertEqual("Working Mic", self.page.assets[0].description)
|
||||
self.assertEqual("10", self.page.assets[0].id)
|
||||
|
||||
self.page.set_query("light")
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.assets) == 1)
|
||||
self.assertEqual("A light", self.page.assets[0].description)
|
||||
|
||||
self.page.set_query("Random string")
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.assets) == 0)
|
||||
|
||||
self.page.set_query("")
|
||||
self.page.search()
|
||||
# Only working stuff shown by default
|
||||
self.assertTrue(len(self.page.assets) == 2)
|
||||
|
||||
self.page.status_selector.toggle()
|
||||
self.assertTrue(self.page.status_selector.is_open)
|
||||
self.page.status_selector.select_all()
|
||||
self.page.status_selector.toggle()
|
||||
self.assertFalse(self.page.status_selector.is_open)
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.assets) == 4)
|
||||
|
||||
self.page.category_selector.toggle()
|
||||
self.assertTrue(self.page.category_selector.is_open)
|
||||
self.page.category_selector.set_option("Sound", True)
|
||||
self.page.category_selector.close()
|
||||
self.assertFalse(self.page.category_selector.is_open)
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.assets) == 2)
|
||||
assetIDs = list(map(lambda x: x.id, self.page.assets))
|
||||
self.assertEqual("1", assetIDs[0])
|
||||
self.assertEqual("10", assetIDs[1])
|
||||
|
||||
|
||||
@screenshot_failure_cls
|
||||
class TestAssetForm(AutoLoginTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
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.wait = WebDriverWait(self.driver, 5)
|
||||
self.page = pages.AssetCreate(self.driver, self.live_server_url).open()
|
||||
|
||||
def test_asset_create(self):
|
||||
# Test that ID is automatically assigned and properly incremented
|
||||
self.assertIn(self.page.asset_id, "9001")
|
||||
|
||||
self.page.remove_all_required()
|
||||
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("This field is required.", self.page.errors["Description"])
|
||||
|
||||
self.page.open()
|
||||
|
||||
self.page.description = desc = "Bodge Lead"
|
||||
self.page.category = cat = "Health & Safety"
|
||||
self.page.status = status = "O.K."
|
||||
self.page.serial_number = sn = "0124567890-SAUSAGE"
|
||||
self.page.comments = comments = "This is actually a sledgehammer, not a cable..."
|
||||
|
||||
self.page.purchased_from_selector.toggle()
|
||||
self.assertTrue(self.page.purchased_from_selector.is_open)
|
||||
self.page.purchased_from_selector.search(self.supplier.name[:-8])
|
||||
self.page.purchased_from_selector.set_option(self.supplier.name, True)
|
||||
self.page.purchase_price = "12.99"
|
||||
self.page.salvage_value = "99.12"
|
||||
self.page.date_acquired = acquired = datetime.date(2020, 5, 2)
|
||||
|
||||
self.page.parent_selector.toggle()
|
||||
self.assertTrue(self.page.parent_selector.is_open)
|
||||
self.page.parent_selector.search(self.parent.asset_id)
|
||||
# Needed here but not earlier for whatever reason
|
||||
self.driver.implicitly_wait(1)
|
||||
self.page.parent_selector.set_option(self.parent.asset_id + " | " + self.parent.description, True)
|
||||
self.assertTrue(self.page.parent_selector.options[0].selected)
|
||||
self.page.parent_selector.toggle()
|
||||
|
||||
self.assertFalse(self.driver.find_element_by_id('cable-table').is_displayed())
|
||||
|
||||
self.page.submit()
|
||||
self.wait.until(animation_is_finished())
|
||||
self.assertTrue(self.page.success)
|
||||
# Check that data is right
|
||||
asset = models.Asset.objects.get(asset_id="9001")
|
||||
self.assertEqual(asset.description, desc)
|
||||
self.assertEqual(asset.category.name, cat)
|
||||
self.assertEqual(asset.status.name, status)
|
||||
self.assertEqual(asset.serial_number, sn)
|
||||
self.assertEqual(asset.comments, comments)
|
||||
# This one is important as it defaults to today's date
|
||||
self.assertEqual(asset.date_acquired, acquired)
|
||||
|
||||
def test_cable_create(self):
|
||||
self.page.description = "IEC -> IEC"
|
||||
self.page.category = "Health & Safety"
|
||||
self.page.status = "O.K."
|
||||
self.page.serial_number = "MELON-MELON-MELON"
|
||||
self.page.comments = "You might need that"
|
||||
self.page.is_cable = True
|
||||
|
||||
self.assertTrue(self.driver.find_element_by_id('cable-table').is_displayed())
|
||||
self.wait.until(animation_is_finished())
|
||||
self.page.cable_type = "IEC → IEC"
|
||||
self.page.socket = "IEC"
|
||||
self.page.length = 10
|
||||
self.page.csa = "1.5"
|
||||
|
||||
self.page.submit()
|
||||
self.assertTrue(self.page.success)
|
||||
|
||||
def test_asset_edit(self):
|
||||
self.page = pages.AssetEdit(self.driver, self.live_server_url, asset_id=self.parent.asset_id).open()
|
||||
|
||||
self.assertTrue(self.driver.find_element_by_id('id_asset_id').get_attribute('readonly') is not None)
|
||||
|
||||
new_description = "Big Shelf"
|
||||
self.page.description = new_description
|
||||
|
||||
self.page.submit()
|
||||
self.assertTrue(self.page.success)
|
||||
|
||||
self.assertEqual(models.Asset.objects.get(asset_id=self.parent.asset_id).description, new_description)
|
||||
|
||||
def test_asset_duplicate(self):
|
||||
self.page = pages.AssetDuplicate(self.driver, self.live_server_url, asset_id=self.parent.asset_id).open()
|
||||
|
||||
self.assertNotEqual(self.parent.asset_id, self.page.asset_id)
|
||||
self.assertEqual(self.parent.description, self.page.description)
|
||||
self.assertEqual(self.parent.status.name, self.page.status)
|
||||
self.assertEqual(self.parent.category.name, self.page.category)
|
||||
self.assertEqual(self.parent.date_acquired, self.page.date_acquired.date())
|
||||
|
||||
self.page.submit()
|
||||
self.assertTrue(self.page.success)
|
||||
self.assertEqual(models.Asset.objects.last().description, self.parent.description)
|
||||
|
||||
|
||||
@screenshot_failure_cls
|
||||
class TestSupplierList(AutoLoginTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
models.Supplier.objects.create(name="Fullmetal Heavy Industry")
|
||||
models.Supplier.objects.create(name="Acme.")
|
||||
models.Supplier.objects.create(name="TEC PA & Lighting")
|
||||
models.Supplier.objects.create(name="Caterpillar Inc.")
|
||||
models.Supplier.objects.create(name="N.E.R.D")
|
||||
models.Supplier.objects.create(name="Khumalo")
|
||||
models.Supplier.objects.create(name="1984 Incorporated")
|
||||
self.page = pages.SupplierList(self.driver, self.live_server_url).open()
|
||||
|
||||
# Should be sorted alphabetically
|
||||
def test_order(self):
|
||||
names = list(map(lambda x: x.name, self.page.suppliers))
|
||||
self.assertEqual("1984 Incorporated", names[0])
|
||||
self.assertEqual("Acme.", names[1])
|
||||
self.assertEqual("Caterpillar Inc.", names[2])
|
||||
self.assertEqual("Fullmetal Heavy Industry", names[3])
|
||||
self.assertEqual("Khumalo", names[4])
|
||||
self.assertEqual("N.E.R.D", names[5])
|
||||
self.assertEqual("TEC PA & Lighting", names[6])
|
||||
|
||||
def test_search(self):
|
||||
self.page.set_query("TEC")
|
||||
self.page.search()
|
||||
|
||||
self.assertTrue(len(self.page.suppliers) == 1)
|
||||
self.assertEqual("TEC PA & Lighting", self.page.suppliers[0].name)
|
||||
|
||||
self.page.set_query("")
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.suppliers) == 7)
|
||||
|
||||
self.page.set_query("This is not a supplier")
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.suppliers) == 0)
|
||||
|
||||
|
||||
@screenshot_failure_cls
|
||||
class TestSupplierCreateAndEdit(AutoLoginTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.supplier = models.Supplier.objects.create(name="Fullmetal Heavy Industry")
|
||||
|
||||
def test_supplier_create(self):
|
||||
self.page = pages.SupplierCreate(self.driver, self.live_server_url).open()
|
||||
|
||||
self.page.remove_all_required()
|
||||
self.page.submit()
|
||||
self.assertFalse(self.page.success)
|
||||
self.assertIn("This field is required.", self.page.errors["Name"])
|
||||
|
||||
self.page.name = "Optican Health Supplies"
|
||||
self.page.submit()
|
||||
self.assertTrue(self.page.success)
|
||||
|
||||
def test_supplier_edit(self):
|
||||
self.page = pages.SupplierEdit(self.driver, self.live_server_url, supplier_id=self.supplier.pk).open()
|
||||
|
||||
self.assertEqual("Fullmetal Heavy Industry", self.page.name)
|
||||
new_name = "Cyberdyne Systems"
|
||||
self.page.name = new_name
|
||||
self.page.submit()
|
||||
self.assertTrue(self.page.success)
|
||||
|
||||
|
||||
@screenshot_failure_cls
|
||||
class TestAssetAudit(AutoLoginTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
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.page = pages.AssetAuditList(self.driver, self.live_server_url).open()
|
||||
self.wait = WebDriverWait(self.driver, 5)
|
||||
|
||||
def test_audit_process(self):
|
||||
asset_id = "1111"
|
||||
self.page.set_query(asset_id)
|
||||
self.page.search()
|
||||
mdl = self.page.modal
|
||||
self.wait.until(EC.visibility_of_element_located((By.ID, 'modal')))
|
||||
# Do it wrong on purpose to check error display
|
||||
mdl.remove_all_required()
|
||||
mdl.description = ""
|
||||
mdl.submit()
|
||||
# self.wait.until(EC.visibility_of_element_located((By.ID, 'modal')))
|
||||
self.wait.until(animation_is_finished())
|
||||
# self.assertTrue(self.driver.find_element_by_id('modal').is_displayed())
|
||||
self.assertIn("This field is required.", mdl.errors["Description"])
|
||||
# Now do it properly
|
||||
new_desc = "A BIG hammer"
|
||||
mdl.description = new_desc
|
||||
mdl.submit()
|
||||
self.wait.until(EC.invisibility_of_element_located((By.ID, 'modal')))
|
||||
self.assertFalse(self.driver.find_element_by_id('modal').is_displayed())
|
||||
|
||||
# Check data is correct
|
||||
audited = models.Asset.objects.get(asset_id="1111")
|
||||
self.assertEqual(audited.description, new_desc)
|
||||
# Make sure audit 'log' was filled out
|
||||
self.assertEqual(self.profile.initials, audited.last_audited_by.initials)
|
||||
self.assertEqual(timezone.now().date(), audited.last_audited_at.date())
|
||||
self.assertEqual(timezone.now().hour, audited.last_audited_at.hour)
|
||||
self.assertEqual(timezone.now().minute, audited.last_audited_at.minute)
|
||||
# Check we've removed it from the 'needing audit' list
|
||||
self.assertNotIn(asset_id, self.page.assets)
|
||||
|
||||
def test_audit_list(self):
|
||||
self.assertEqual(len(models.Asset.objects.filter(last_audited_at=None)), len(self.page.assets))
|
||||
|
||||
asset_row = self.page.assets[0]
|
||||
self.driver.find_element(By.XPATH, "//*[@id='asset_table_body']/tr[1]/td[4]/a").click()
|
||||
self.wait.until(EC.visibility_of_element_located((By.ID, 'modal')))
|
||||
self.assertEqual(self.page.modal.asset_id, asset_row.id)
|
||||
|
||||
# First close button is for the not found error
|
||||
self.page.find_element(By.XPATH, '//*[@id="modal"]/div/div/div[1]/button').click()
|
||||
self.wait.until(EC.invisibility_of_element_located((By.ID, 'modal')))
|
||||
self.assertFalse(self.driver.find_element_by_id('modal').is_displayed())
|
||||
# Make sure audit log was NOT filled out
|
||||
audited = models.Asset.objects.get(asset_id=asset_row.id)
|
||||
self.assertEqual(None, audited.last_audited_by)
|
||||
|
||||
# Check that a failed search works
|
||||
self.page.set_query("NOTFOUND")
|
||||
self.page.search()
|
||||
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)
|
||||
|
||||
|
||||
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')
|
||||
|
||||
# Nothing should be available to the unauthenticated
|
||||
def test_unauthenticated(self):
|
||||
for url in urls.urlpatterns:
|
||||
if url.name is not None:
|
||||
pattern = str(url.pattern)
|
||||
if "json" in url.name or pattern:
|
||||
# TODO
|
||||
pass
|
||||
elif ":pk>" in pattern:
|
||||
request_url = reverse(url.name, kwargs={'pk': 9})
|
||||
else:
|
||||
request_url = reverse(url.name)
|
||||
response = self.client.get(request_url, HTTP_HOST='example.com')
|
||||
self.assertEqual(response.status_code, 302)
|
||||
response = self.client.get(request_url, follow=True, HTTP_HOST='example.com')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
343
assets/tests/test_interaction.py
Normal file
343
assets/tests/test_interaction.py
Normal file
@@ -0,0 +1,343 @@
|
||||
import datetime
|
||||
|
||||
from django.utils import timezone
|
||||
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.pages import animation_is_finished
|
||||
from assets import models
|
||||
from . import pages
|
||||
|
||||
|
||||
@screenshot_failure_cls
|
||||
class TestAssetList(AutoLoginTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
sound = models.AssetCategory.objects.create(name="Sound")
|
||||
lighting = models.AssetCategory.objects.create(name="Lighting")
|
||||
|
||||
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))
|
||||
self.page = pages.AssetList(self.driver, self.live_server_url).open()
|
||||
|
||||
def test_default_statuses_applied(self):
|
||||
# Only the working stuff should be shown initially
|
||||
asset_descriptions = list(map(lambda x: x.description, self.page.assets))
|
||||
self.assertEqual(2, len(asset_descriptions))
|
||||
self.assertIn("A light", asset_descriptions)
|
||||
self.assertIn("Working Mic", asset_descriptions)
|
||||
|
||||
def test_asset_order(self):
|
||||
# Only the working stuff should be shown initially
|
||||
self.page.status_selector.open()
|
||||
self.page.status_selector.set_option("Broken", True)
|
||||
self.page.status_selector.close()
|
||||
|
||||
self.page.search()
|
||||
|
||||
asset_ids = list(map(lambda x: x.id, self.page.assets))
|
||||
self.assertEqual("1", asset_ids[0])
|
||||
self.assertEqual("2", asset_ids[1])
|
||||
self.assertEqual("10", asset_ids[2])
|
||||
self.assertEqual("C1", asset_ids[3])
|
||||
|
||||
def test_search(self):
|
||||
self.page.set_query("10")
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.assets) == 1)
|
||||
self.assertEqual("Working Mic", self.page.assets[0].description)
|
||||
self.assertEqual("10", self.page.assets[0].id)
|
||||
|
||||
self.page.set_query("light")
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.assets) == 1)
|
||||
self.assertEqual("A light", self.page.assets[0].description)
|
||||
|
||||
self.page.set_query("Random string")
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.assets) == 0)
|
||||
|
||||
self.page.set_query("")
|
||||
self.page.search()
|
||||
# Only working stuff shown by default
|
||||
self.assertTrue(len(self.page.assets) == 2)
|
||||
|
||||
self.page.status_selector.toggle()
|
||||
self.assertTrue(self.page.status_selector.is_open)
|
||||
self.page.status_selector.select_all()
|
||||
self.page.status_selector.toggle()
|
||||
self.assertFalse(self.page.status_selector.is_open)
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.assets) == 4)
|
||||
|
||||
self.page.category_selector.toggle()
|
||||
self.assertTrue(self.page.category_selector.is_open)
|
||||
self.page.category_selector.set_option("Sound", True)
|
||||
self.page.category_selector.close()
|
||||
self.assertFalse(self.page.category_selector.is_open)
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.assets) == 2)
|
||||
asset_ids = list(map(lambda x: x.id, self.page.assets))
|
||||
self.assertEqual("1", asset_ids[0])
|
||||
self.assertEqual("10", asset_ids[1])
|
||||
|
||||
|
||||
@screenshot_failure_cls
|
||||
class TestAssetForm(AutoLoginTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
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.page = pages.AssetCreate(self.driver, self.live_server_url).open()
|
||||
|
||||
def test_asset_create(self):
|
||||
# Test that ID is automatically assigned and properly incremented
|
||||
self.assertIn(self.page.asset_id, "9001")
|
||||
|
||||
self.page.remove_all_required()
|
||||
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("This field is required.", self.page.errors["Description"])
|
||||
|
||||
self.page.open()
|
||||
|
||||
self.page.description = desc = "Bodge Lead"
|
||||
self.page.category = cat = "Health & Safety"
|
||||
self.page.status = status = "O.K."
|
||||
self.page.serial_number = sn = "0124567890-SAUSAGE"
|
||||
self.page.comments = comments = "This is actually a sledgehammer, not a cable..."
|
||||
|
||||
self.page.purchased_from_selector.toggle()
|
||||
self.assertTrue(self.page.purchased_from_selector.is_open)
|
||||
self.page.purchased_from_selector.search(self.supplier.name[:-8])
|
||||
self.page.purchased_from_selector.set_option(self.supplier.name, True)
|
||||
self.page.purchase_price = "12.99"
|
||||
self.page.salvage_value = "99.12"
|
||||
self.page.date_acquired = acquired = datetime.date(2020, 5, 2)
|
||||
|
||||
self.page.parent_selector.toggle()
|
||||
self.assertTrue(self.page.parent_selector.is_open)
|
||||
self.page.parent_selector.search(self.parent.asset_id)
|
||||
# Needed here but not earlier for whatever reason
|
||||
self.driver.implicitly_wait(1)
|
||||
self.page.parent_selector.set_option(self.parent.asset_id + " | " + self.parent.description, True)
|
||||
self.assertTrue(self.page.parent_selector.options[0].selected)
|
||||
self.page.parent_selector.toggle()
|
||||
|
||||
self.assertFalse(self.driver.find_element_by_id('cable-table').is_displayed())
|
||||
|
||||
self.page.submit()
|
||||
self.assertTrue(self.page.success)
|
||||
# Check that data is right
|
||||
asset = models.Asset.objects.get(asset_id="9001")
|
||||
self.assertEqual(asset.description, desc)
|
||||
self.assertEqual(asset.category.name, cat)
|
||||
self.assertEqual(asset.status.name, status)
|
||||
self.assertEqual(asset.serial_number, sn)
|
||||
self.assertEqual(asset.comments, comments)
|
||||
# This one is important as it defaults to today's date
|
||||
self.assertEqual(asset.date_acquired, acquired)
|
||||
|
||||
def test_cable_create(self):
|
||||
self.page.description = "IEC -> IEC"
|
||||
self.page.category = "Health & Safety"
|
||||
self.page.status = "O.K."
|
||||
self.page.serial_number = "MELON-MELON-MELON"
|
||||
self.page.comments = "You might need that"
|
||||
self.page.is_cable = True
|
||||
|
||||
self.assertTrue(self.driver.find_element_by_id('cable-table').is_displayed())
|
||||
self.wait.until(animation_is_finished())
|
||||
self.page.cable_type = "IEC → IEC"
|
||||
self.page.socket = "IEC"
|
||||
self.page.length = 10
|
||||
self.page.csa = "1.5"
|
||||
|
||||
self.page.submit()
|
||||
self.assertTrue(self.page.success)
|
||||
|
||||
def test_asset_edit(self):
|
||||
self.page = pages.AssetEdit(self.driver, self.live_server_url, asset_id=self.parent.asset_id).open()
|
||||
|
||||
self.assertTrue(self.driver.find_element_by_id('id_asset_id').get_attribute('readonly') is not None)
|
||||
|
||||
new_description = "Big Shelf"
|
||||
self.page.description = new_description
|
||||
|
||||
self.page.submit()
|
||||
self.assertTrue(self.page.success)
|
||||
|
||||
self.assertEqual(models.Asset.objects.get(asset_id=self.parent.asset_id).description, new_description)
|
||||
|
||||
def test_asset_duplicate(self):
|
||||
self.page = pages.AssetDuplicate(self.driver, self.live_server_url, asset_id=self.parent.asset_id).open()
|
||||
|
||||
self.assertNotEqual(self.parent.asset_id, self.page.asset_id)
|
||||
self.assertEqual(self.parent.description, self.page.description)
|
||||
self.assertEqual(self.parent.status.name, self.page.status)
|
||||
self.assertEqual(self.parent.category.name, self.page.category)
|
||||
self.assertEqual(self.parent.date_acquired, self.page.date_acquired.date())
|
||||
|
||||
self.page.submit()
|
||||
self.assertTrue(self.page.success)
|
||||
self.assertEqual(models.Asset.objects.last().description, self.parent.description)
|
||||
|
||||
|
||||
@screenshot_failure_cls
|
||||
class TestSupplierList(AutoLoginTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
models.Supplier.objects.create(name="Fullmetal Heavy Industry")
|
||||
models.Supplier.objects.create(name="Acme.")
|
||||
models.Supplier.objects.create(name="TEC PA & Lighting")
|
||||
models.Supplier.objects.create(name="Caterpillar Inc.")
|
||||
models.Supplier.objects.create(name="N.E.R.D")
|
||||
models.Supplier.objects.create(name="Khumalo")
|
||||
models.Supplier.objects.create(name="1984 Incorporated")
|
||||
self.page = pages.SupplierList(self.driver, self.live_server_url).open()
|
||||
|
||||
# Should be sorted alphabetically
|
||||
def test_order(self):
|
||||
names = list(map(lambda x: x.name, self.page.suppliers))
|
||||
self.assertEqual("1984 Incorporated", names[0])
|
||||
self.assertEqual("Acme.", names[1])
|
||||
self.assertEqual("Caterpillar Inc.", names[2])
|
||||
self.assertEqual("Fullmetal Heavy Industry", names[3])
|
||||
self.assertEqual("Khumalo", names[4])
|
||||
self.assertEqual("N.E.R.D", names[5])
|
||||
self.assertEqual("TEC PA & Lighting", names[6])
|
||||
|
||||
def test_search(self):
|
||||
self.page.set_query("TEC")
|
||||
self.page.search()
|
||||
|
||||
self.assertTrue(len(self.page.suppliers) == 1)
|
||||
self.assertEqual("TEC PA & Lighting", self.page.suppliers[0].name)
|
||||
|
||||
self.page.set_query("")
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.suppliers) == 7)
|
||||
|
||||
self.page.set_query("This is not a supplier")
|
||||
self.page.search()
|
||||
self.assertTrue(len(self.page.suppliers) == 0)
|
||||
|
||||
|
||||
@screenshot_failure_cls
|
||||
class TestSupplierCreateAndEdit(AutoLoginTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.supplier = models.Supplier.objects.create(name="Fullmetal Heavy Industry")
|
||||
|
||||
def test_supplier_create(self):
|
||||
self.page = pages.SupplierCreate(self.driver, self.live_server_url).open()
|
||||
|
||||
self.page.remove_all_required()
|
||||
self.page.submit()
|
||||
self.assertFalse(self.page.success)
|
||||
self.assertIn("This field is required.", self.page.errors["Name"])
|
||||
|
||||
self.page.name = "Optican Health Supplies"
|
||||
self.page.submit()
|
||||
self.assertTrue(self.page.success)
|
||||
|
||||
def test_supplier_edit(self):
|
||||
self.page = pages.SupplierEdit(self.driver, self.live_server_url, supplier_id=self.supplier.pk).open()
|
||||
|
||||
self.assertEqual("Fullmetal Heavy Industry", self.page.name)
|
||||
new_name = "Cyberdyne Systems"
|
||||
self.page.name = new_name
|
||||
self.page.submit()
|
||||
self.assertTrue(self.page.success)
|
||||
|
||||
|
||||
@screenshot_failure_cls
|
||||
class TestAssetAudit(AutoLoginTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
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))
|
||||
self.asset = 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)
|
||||
|
||||
def test_audit_fail(self):
|
||||
self.page.set_query(self.asset.asset_id)
|
||||
self.page.search()
|
||||
self.wait.until(ec.visibility_of_element_located((By.ID, 'modal')))
|
||||
# Do it wrong on purpose to check error display
|
||||
self.page.modal.remove_all_required()
|
||||
self.page.modal.description = ""
|
||||
self.page.modal.submit()
|
||||
self.wait.until(animation_is_finished())
|
||||
self.driver.implicitly_wait(4)
|
||||
self.assertIn("This field is required.", self.page.modal.errors["Description"])
|
||||
|
||||
def test_audit_success(self):
|
||||
self.page.set_query(self.asset.asset_id)
|
||||
self.page.search()
|
||||
self.wait.until(ec.visibility_of_element_located((By.ID, 'modal')))
|
||||
# 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()
|
||||
# Check data is correct
|
||||
self.asset.refresh_from_db()
|
||||
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)
|
||||
# Check we've removed it from the 'needing audit' list
|
||||
self.assertNotIn(self.asset.asset_id, self.page.assets)
|
||||
|
||||
def test_audit_list(self):
|
||||
self.assertEqual(len(models.Asset.objects.filter(last_audited_at=None)), len(self.page.assets))
|
||||
asset_row = self.page.assets[0]
|
||||
self.driver.find_element(By.XPATH, "//a[contains(@class,'btn') and contains(., 'Audit')]").click()
|
||||
self.wait.until(ec.visibility_of_element_located((By.ID, 'modal')))
|
||||
self.assertEqual(self.page.modal.asset_id, asset_row.id)
|
||||
self.page.modal.close()
|
||||
self.assertFalse(self.driver.find_element_by_id('modal').is_displayed())
|
||||
# Make sure audit log was NOT filled out
|
||||
audited = models.Asset.objects.get(asset_id=asset_row.id)
|
||||
assert audited.last_audited_by is None
|
||||
|
||||
def test_audit_search(self):
|
||||
# Check that a failed search works
|
||||
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)
|
||||
260
assets/tests/test_unit.py
Normal file
260
assets/tests/test_unit.py
Normal file
@@ -0,0 +1,260 @@
|
||||
import datetime
|
||||
|
||||
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, assertContains, assertNotContains
|
||||
|
||||
from assets import models, urls
|
||||
|
||||
pytestmark = pytest.mark.django_db # TODO
|
||||
|
||||
|
||||
def login(client, django_user_model):
|
||||
pwd = 'testuser'
|
||||
usr = "TestUser"
|
||||
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)
|
||||
assertContains(response, oembed_url)
|
||||
|
||||
# Test that the JSON exists
|
||||
response = client.get(oembed_url, follow=True, HTTP_HOST='example.com')
|
||||
assert response.status_code == 200
|
||||
assertContains(response, asset_embed_url)
|
||||
|
||||
# Should also work for non-existant
|
||||
response = client.get(alt_oembed_url, follow=True, HTTP_HOST='example.com')
|
||||
assert response.status_code == 200
|
||||
assertContains(response, alt_asset_embed_url)
|
||||
|
||||
|
||||
@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))
|
||||
|
||||
|
||||
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
|
||||
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):
|
||||
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')
|
||||
@@ -1,11 +1,9 @@
|
||||
from django.conf.urls import url
|
||||
from django.urls import path
|
||||
from assets import views, models
|
||||
from versioning import versioning
|
||||
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.urls import path
|
||||
from django.views.decorators.clickjacking import xframe_options_exempt
|
||||
|
||||
from PyRIGS.decorators import has_oembed, permission_required_with_403
|
||||
from assets import views
|
||||
|
||||
urlpatterns = [
|
||||
path('', login_required(views.AssetList.as_view()), name='asset_index'),
|
||||
@@ -18,12 +16,12 @@ urlpatterns = [
|
||||
path('asset/id/<str:pk>/duplicate/', permission_required_with_403('assets.add_asset')
|
||||
(views.AssetDuplicate.as_view()), name='asset_duplicate'),
|
||||
|
||||
path('cabletype/list/', views.CableTypeList.as_view(), name='cable_type_list'),
|
||||
path('cabletype/list/', login_required(views.CableTypeList.as_view()), name='cable_type_list'),
|
||||
path('cabletype/create/', permission_required_with_403('assets.add_cable_type')(views.CableTypeCreate.as_view()), name='cable_type_create'),
|
||||
path('cabletype/<int:pk>/update/', permission_required_with_403('assets.change_cable_type')(views.CableTypeUpdate.as_view()), name='cable_type_update'),
|
||||
path('cabletype/<int:pk>/detail/', views.CableTypeDetail.as_view(), name='cable_type_detail'),
|
||||
path('cabletype/<int:pk>/detail/', login_required(views.CableTypeDetail.as_view()), name='cable_type_detail'),
|
||||
|
||||
path('asset/search/', views.AssetSearch.as_view(), name='asset_search_json'),
|
||||
path('asset/search/', login_required(views.AssetSearch.as_view()), name='asset_search_json'),
|
||||
path('asset/id/<str:pk>/embed/',
|
||||
xframe_options_exempt(
|
||||
login_required(login_url='/user/login/embed/')(views.AssetEmbed.as_view())),
|
||||
@@ -35,12 +33,12 @@ urlpatterns = [
|
||||
path('asset/audit/', permission_required_with_403('assets.change_asset')(views.AssetAuditList.as_view()), name='asset_audit_list'),
|
||||
path('asset/id/<str:pk>/audit/', permission_required_with_403('assets.change_asset')(views.AssetAudit.as_view()), name='asset_audit'),
|
||||
|
||||
path('supplier/list/', views.SupplierList.as_view(), name='supplier_list'),
|
||||
path('supplier/<int:pk>/', views.SupplierDetail.as_view(), name='supplier_detail'),
|
||||
path('supplier/list/', login_required(views.SupplierList.as_view()), name='supplier_list'),
|
||||
path('supplier/<int:pk>/', login_required(views.SupplierDetail.as_view()), name='supplier_detail'),
|
||||
path('supplier/create/', permission_required_with_403('assets.add_supplier')
|
||||
(views.SupplierCreate.as_view()), name='supplier_create'),
|
||||
path('supplier/<int:pk>/edit/', permission_required_with_403('assets.change_supplier')
|
||||
(views.SupplierUpdate.as_view()), name='supplier_update'),
|
||||
|
||||
path('supplier/search/', views.SupplierSearch.as_view(), name='supplier_search_json'),
|
||||
path('supplier/search/', login_required(views.SupplierSearch.as_view()), name='supplier_search_json'),
|
||||
]
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
import datetime
|
||||
|
||||
import simplejson
|
||||
from assets import forms, models
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||||
from django.core import serializers
|
||||
from django.db.models import Q
|
||||
from django.http import Http404, HttpResponse, JsonResponse
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.utils import timezone
|
||||
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 itertools import chain
|
||||
|
||||
from PyRIGS.views import GenericListView, GenericDetailView, GenericUpdateView, GenericCreateView, ModalURLMixin, \
|
||||
is_ajax
|
||||
from assets import forms, models
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name='dispatch')
|
||||
@@ -121,7 +118,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]))
|
||||
@@ -211,6 +208,11 @@ class AssetAudit(AssetEdit):
|
||||
template_name = 'asset_audit.html'
|
||||
form_class = forms.AssetAuditForm
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context["page_title"] = "Audit Asset: {}".format(self.object.display_id)
|
||||
return context
|
||||
|
||||
def get_success_url(self):
|
||||
# TODO For some reason this doesn't stick when done in form_valid??
|
||||
asset = self.get_object()
|
||||
@@ -230,7 +232,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'
|
||||
@@ -258,7 +260,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'
|
||||
@@ -272,7 +274,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'
|
||||
@@ -288,7 +290,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'
|
||||
@@ -312,7 +314,7 @@ class CableTypeList(generic.ListView):
|
||||
|
||||
class CableTypeDetail(generic.DetailView):
|
||||
model = models.CableType
|
||||
template_name = 'cable_type_form.html'
|
||||
template_name = 'cable_type_detail.html'
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CableTypeDetail, self).get_context_data(**kwargs)
|
||||
|
||||
Reference in New Issue
Block a user