Merge branch 'master' into markdown

# Conflicts:
#	RIGS/static/scss/screen.scss
#	RIGS/templates/RIGS/event_detail.html
#	RIGS/templates/RIGS/event_form.html
#	RIGS/templates/RIGS/event_table.html
#	RIGS/templates/RIGS/item_row.html
#	RIGS/templates/RIGS/item_table.html
#	RIGS/templates/RIGS/organisation_detail.html
#	RIGS/templates/RIGS/organisation_form.html
#	RIGS/templates/RIGS/person_detail.html
#	RIGS/templates/RIGS/person_form.html
#	RIGS/templates/RIGS/rigboard.html
#	RIGS/templates/RIGS/venue_detail.html
#	RIGS/templates/RIGS/venue_form.html
#	RIGS/templates/event_print.xml
#	RIGS/templates/event_print_page.xml
#	RIGS/templates/item_modal.html
#	RIGS/tests/test_unit.py
#	assets/templates/asset_create.html
#	assets/templates/asset_update.html
#	assets/templates/partials/asset_detail_form.html
#	requirements.txt
#	templates/base.html
This commit is contained in:
2021-12-22 11:41:18 +00:00
407 changed files with 24735 additions and 37429 deletions

0
RIGS/tests/__init__.py Normal file
View File

108
RIGS/tests/conftest.py Normal file
View File

@@ -0,0 +1,108 @@
from RIGS import models
import pytest
from django.utils import timezone
@pytest.fixture
def basic_event(db):
event = models.Event.objects.create(name="TE E1", start_date=timezone.now())
yield event
event.delete()
@pytest.fixture
def ra(basic_event, admin_user):
ra = models.RiskAssessment.objects.create(event=basic_event, nonstandard_equipment=False, nonstandard_use=False,
contractors=False, other_companies=False, crew_fatigue=False,
big_power=False, power_mic=admin_user, generators=False,
other_companies_power=False, nonstandard_equipment_power=False,
multiple_electrical_environments=False, noise_monitoring=False,
known_venue=True, safe_loading=True, safe_storage=True,
area_outside_of_control=True, barrier_required=True,
nonstandard_emergency_procedure=True, special_structures=False,
suspended_structures=False, outside=False)
yield ra
ra.delete()
@pytest.fixture
def medium_ra(ra):
ra.big_power = True
ra.save()
yield ra
ra.big_power = False
ra.save()
@pytest.fixture
def venue(db):
venue = models.Venue.objects.create(name="Venue 1")
yield venue
venue.delete()
@pytest.fixture # TODO parameterise with Event sizes
def checklist(basic_event, venue, admin_user, ra):
checklist = models.EventChecklist.objects.create(event=basic_event, power_mic=admin_user, safe_parking=False,
safe_packing=False, exits=False, trip_hazard=False, warning_signs=False,
ear_plugs=False, hs_location="Locked away safely",
extinguishers_location="Somewhere, I forgot", earthing=False, pat=False,
date=timezone.now(), venue=venue)
yield checklist
checklist.delete()
@pytest.fixture
def many_events(db, admin_user, scope="class"):
many_events = {
# produce 7 normal events - 5 current
1: models.Event.objects.create(name="TE E1", start_date=date.today() + timedelta(days=6),
description="start future no end"),
2: models.Event.objects.create(name="TE E2", start_date=date.today(), description="start today no end"),
3: models.Event.objects.create(name="TE E3", start_date=date.today(), end_date=date.today(),
description="start today with end today"),
4: models.Event.objects.create(name="TE E4", start_date='2014-03-20', description="start past no end"),
5: models.Event.objects.create(name="TE E5", start_date='2014-03-20', end_date='2014-03-21',
description="start past with end past"),
6: models.Event.objects.create(name="TE E6", start_date=date.today() - timedelta(days=2),
end_date=date.today() + timedelta(days=2),
description="start past, end future"),
7: models.Event.objects.create(name="TE E7", start_date=date.today() + timedelta(days=2),
end_date=date.today() + timedelta(days=2),
description="start + end in future"),
# 2 cancelled - 1 current
8: models.Event.objects.create(name="TE E8", start_date=date.today() + timedelta(days=2),
end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED,
description="cancelled in future"),
9: models.Event.objects.create(name="TE E9", start_date=date.today() - timedelta(days=1),
end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED,
description="cancelled and started"),
# 5 dry hire - 3 current
10: models.Event.objects.create(name="TE E10", start_date=date.today(), dry_hire=True,
description="dryhire today"),
11: models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True,
checked_in_by=admin_user,
description="dryhire today, checked in"),
12: models.Event.objects.create(name="TE E12", start_date=date.today() - timedelta(days=1), dry_hire=True,
status=models.Event.BOOKED, description="dryhire past"),
13: models.Event.objects.create(name="TE E13", start_date=date.today() - timedelta(days=2), dry_hire=True,
checked_in_by=admin_user, description="dryhire past checked in"),
14: models.Event.objects.create(name="TE E14", start_date=date.today(), dry_hire=True,
status=models.Event.CANCELLED, description="dryhire today cancelled"),
# 4 non rig - 3 current
15: models.Event.objects.create(name="TE E15", start_date=date.today(), is_rig=False,
description="non rig today"),
16: models.Event.objects.create(name="TE E16", start_date=date.today() + timedelta(days=1), is_rig=False,
description="non rig tomorrow"),
17: models.Event.objects.create(name="TE E17", start_date=date.today() - timedelta(days=1), is_rig=False,
description="non rig yesterday"),
18: models.Event.objects.create(name="TE E18", start_date=date.today(), is_rig=False,
status=models.Event.CANCELLED,
description="non rig today cancelled"),
}
yield many_events
for event in many_events:
event.delete()

396
RIGS/tests/pages.py Normal file
View File

@@ -0,0 +1,396 @@
from django.urls import reverse
from pypom import Region
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from PyRIGS.tests import regions
from PyRIGS.tests.pages import BasePage, FormPage
from RIGS.tests import regions as rigs_regions
class Index(BasePage):
URL_TEMPLATE = reverse('index')
class Rigboard(BasePage):
URL_TEMPLATE = reverse('rigboard')
_add_item_selector = (By.XPATH, "//a[contains(@class,'btn-primary') and contains(., 'New')]")
_event_row_locator = (By.ID, 'event_row')
def add(self):
self.find_element(*self._add_item_selector).click()
class EventListRow(Region):
_event_number_locator = (By.ID, "event_number")
_event_dates_locator = (By.ID, "event_dates")
_event_details_locator = (By.ID, "event_details")
_event_mic_locator = (By.ID, "event_mic")
@property
def id(self):
return self.find_element(*self._event_number_locator).text
@property
def dates(self):
return self.find_element(*self._event_dates_locator).text
@property
def details(self):
return self.find_element(*self._event_details_locator).text
@property
def mic(self):
return self.find_element(*self._event_mic_locator).text
@property
def events(self):
return [self.EventListRow(self, i) for i in self.find_elements(*self._event_row_locator)]
class EventDetail(BasePage):
URL_TEMPLATE = 'event/{event_id}'
# TODO Refactor into regions to match template fragmentation
_event_name_selector = (By.XPATH, '//h2')
_person_panel_selector = (By.XPATH, '//div[contains(text(), "Person Details")]/..')
_name_selector = (By.XPATH, '//dt[text()="Person"]/following-sibling::dd[1]')
_email_selector = (By.XPATH, '//dt[text()="Email"]/following-sibling::dd[1]')
_phone_selector = (By.XPATH, '//dt[text()="Phone Number"]/following-sibling::dd[1]')
_event_table_selector = (By.ID, 'item-table')
@property
def event_name(self):
return self.find_element(*self._event_name_selector).text
@property
def name(self):
return self.find_element(*self._person_panel_selector).find_element(*self._name_selector).text
@property
def email(self):
return self.find_element(*self._person_panel_selector).find_element(*self._email_selector).text
@property
def phone(self):
return self.find_element(*self._person_panel_selector).find_element(*self._phone_selector).text
@property
def item_table(self):
return self.find_element(*self._event_table_selector)
class CreateEvent(FormPage):
URL_TEMPLATE = reverse('event_create')
_is_rig_selector = (By.ID, 'is_rig-selector')
_submit_locator = (By.XPATH, "//button[@type='submit' and contains(., 'Save')]")
_person_selector_selector = (By.XPATH, '//*[@id="id_person"]/..')
_venue_selector_selector = (By.XPATH, '//*[@id="id_venue"]/..')
_mic_selector_selector = (By.XPATH, '//*[@id="form-hws"]/div[7]/div[1]/div/div')
_add_person_selector = (By.XPATH, '//a[@data-target="#id_person" and contains(@href, "add")]')
_add_item_selector = (By.XPATH, '//button[contains(@class, "item-add")]')
_event_table_selector = (By.ID, 'item-table')
_warning_selector = (By.XPATH, '/html/body/div[1]/div[1]')
form_items = {
'description': (regions.TextBox, (By.ID, 'id_description')),
'name': (regions.TextBox, (By.ID, 'id_name')),
'start_date': (regions.DatePicker, (By.ID, 'id_start_date')),
'start_time': (regions.TimePicker, (By.ID, 'id_start_time')),
'end_date': (regions.DatePicker, (By.ID, 'id_end_date')),
'end_time': (regions.TimePicker, (By.ID, 'id_end_time')),
'access_at': (regions.DateTimePicker, (By.ID, 'id_access_at')),
'meet_at': (regions.DateTimePicker, (By.ID, 'id_meet_at')),
'dry_hire': (regions.CheckBox, (By.ID, 'id_dry_hire')),
'status': (regions.SingleSelectPicker, (By.ID, 'id_status')),
'collected_by': (regions.TextBox, (By.ID, 'id_collector')),
'po': (regions.TextBox, (By.ID, 'id_purchase_order')),
'notes': (regions.TextBox, (By.ID, 'id_notes'))
}
def select_event_type(self, type_name):
self.find_element(By.XPATH, '//button[.="{}"]'.format(type_name)).click()
def item_row(self, ID):
return rigs_regions.ItemRow(self, self.find_element(By.ID, "item-" + ID))
@property
def item_table(self):
return self.find_element(*self._event_table_selector)
@property
def warning(self):
return self.find_element(*self._warning_selector).text
@property
def is_expanded(self):
return self.find_element(*self._submit_locator).is_displayed()
@property
def person_selector(self):
return regions.BootstrapSelectElement(self, self.find_element(*self._person_selector_selector))
@property
def venue_selector(self):
return regions.BootstrapSelectElement(self, self.find_element(*self._venue_selector_selector))
@property
def mic_selector(self):
return regions.BootstrapSelectElement(self, self.find_element(*self._mic_selector_selector))
def add_person(self):
self.find_element(*self._add_person_selector).click()
return regions.Modal(self, self.driver.find_element_by_id('modal'))
def add_event_item(self):
self.find_element(*self._add_item_selector).click()
element = self.driver.find_element_by_id('itemModal')
self.wait.until(EC.visibility_of(element))
return rigs_regions.ItemModal(self, element)
@property
def success(self):
return '/create' not in self.driver.current_url
class DuplicateEvent(CreateEvent):
URL_TEMPLATE = 'event/{event_id}/duplicate'
@property
def success(self):
return '/duplicate' not in self.driver.current_url
class EditEvent(CreateEvent):
URL_TEMPLATE = 'event/{event_id}/edit'
@property
def success(self):
return '/edit' not in self.driver.current_url
class CreateRiskAssessment(FormPage):
URL_TEMPLATE = 'event/{event_id}/ra/'
_submit_locator = (By.XPATH, "//button[@type='submit' and contains(., 'Save')]")
_power_mic_selector = (By.CSS_SELECTOR, ".bootstrap-select")
form_items = {
'nonstandard_equipment': (regions.RadioSelect, (By.ID, 'id_nonstandard_equipment')),
'nonstandard_use': (regions.RadioSelect, (By.ID, 'id_nonstandard_use')),
'contractors': (regions.RadioSelect, (By.ID, 'id_contractors')),
'other_companies': (regions.RadioSelect, (By.ID, 'id_other_companies')),
'crew_fatigue': (regions.RadioSelect, (By.ID, 'id_crew_fatigue')),
'general_notes': (regions.TextBox, (By.ID, 'id_general_notes')),
'big_power': (regions.RadioSelect, (By.ID, 'id_big_power')),
'outside': (regions.RadioSelect, (By.ID, 'id_outside')),
'generators': (regions.RadioSelect, (By.ID, 'id_generators')),
'other_companies_power': (regions.RadioSelect, (By.ID, 'id_other_companies_power')),
'nonstandard_equipment_power': (regions.RadioSelect, (By.ID, 'id_nonstandard_equipment_power')),
'multiple_electrical_environments': (regions.RadioSelect, (By.ID, 'id_multiple_electrical_environments')),
'power_notes': (regions.TextBox, (By.ID, 'id_power_notes')),
'noise_monitoring': (regions.RadioSelect, (By.ID, 'id_noise_monitoring')),
'sound_notes': (regions.TextBox, (By.ID, 'id_sound_notes')),
'known_venue': (regions.RadioSelect, (By.ID, 'id_known_venue')),
'safe_loading': (regions.RadioSelect, (By.ID, 'id_safe_loading')),
'safe_storage': (regions.RadioSelect, (By.ID, 'id_safe_storage')),
'area_outside_of_control': (regions.RadioSelect, (By.ID, 'id_area_outside_of_control')),
'barrier_required': (regions.RadioSelect, (By.ID, 'id_barrier_required')),
'nonstandard_emergency_procedure': (regions.RadioSelect, (By.ID, 'id_nonstandard_emergency_procedure')),
'special_structures': (regions.RadioSelect, (By.ID, 'id_special_structures')),
'persons_responsible_structures': (regions.TextBox, (By.ID, 'id_persons_responsible_structures')),
'suspended_structures': (regions.RadioSelect, (By.ID, 'id_suspended_structures')),
'supervisor_consulted': (regions.CheckBox, (By.ID, 'id_supervisor_consulted')),
'rigging_plan': (regions.TextBox, (By.ID, 'id_rigging_plan')),
}
@property
def power_mic(self):
return regions.BootstrapSelectElement(self, self.find_element(*self._power_mic_selector))
@property
def success(self):
return '/event/ra' in self.driver.current_url
class EditRiskAssessment(CreateRiskAssessment):
URL_TEMPLATE = 'event/ra/{pk}/edit'
@property
def success(self):
return '/edit' not in self.driver.current_url
class CreateEventChecklist(FormPage):
URL_TEMPLATE = 'event/{event_id}/checklist'
_submit_locator = (By.XPATH, "//button[@type='submit' and contains(., 'Save')]")
_power_mic_selector = (By.XPATH, "//div[select[@id='id_power_mic']]")
_add_vehicle_locator = (By.XPATH, "//button[contains(., 'Vehicle')]")
_add_crew_locator = (By.XPATH, "//button[contains(., 'Crew')]")
_vehicle_row_locator = ('xpath', "//tr[@id[starts-with(., 'vehicle') and not(contains(.,'new'))]]")
_crew_row_locator = ('xpath', "//tr[@id[starts-with(., 'crew') and not(contains(.,'new'))]]")
form_items = {
'safe_parking': (regions.CheckBox, (By.ID, 'id_safe_parking')),
'safe_packing': (regions.CheckBox, (By.ID, 'id_safe_packing')),
'exits': (regions.CheckBox, (By.ID, 'id_exits')),
'trip_hazard': (regions.CheckBox, (By.ID, 'id_trip_hazard')),
'warning_signs': (regions.CheckBox, (By.ID, 'id_warning_signs')),
'ear_plugs': (regions.CheckBox, (By.ID, 'id_ear_plugs')),
'hs_location': (regions.TextBox, (By.ID, 'id_hs_location')),
'extinguishers_location': (regions.TextBox, (By.ID, 'id_extinguishers_location')),
'rcds': (regions.CheckBox, (By.ID, 'id_rcds')),
'supply_test': (regions.CheckBox, (By.ID, 'id_supply_test')),
'earthing': (regions.CheckBox, (By.ID, 'id_earthing')),
'pat': (regions.CheckBox, (By.ID, 'id_pat')),
'source_rcd': (regions.CheckBox, (By.ID, 'id_source_rcd')),
'labelling': (regions.CheckBox, (By.ID, 'id_labelling')),
'fd_voltage_l1': (regions.TextBox, (By.ID, 'id_fd_voltage_l1')),
'fd_voltage_l2': (regions.TextBox, (By.ID, 'id_fd_voltage_l2')),
'fd_voltage_l3': (regions.TextBox, (By.ID, 'id_fd_voltage_l3')),
'fd_phase_rotation': (regions.CheckBox, (By.ID, 'id_fd_phase_rotation')),
'fd_earth_fault': (regions.TextBox, (By.ID, 'id_fd_earth_fault')),
'fd_pssc': (regions.TextBox, (By.ID, 'id_fd_pssc')),
'w1_description': (regions.TextBox, (By.ID, 'id_w1_description')),
'w1_polarity': (regions.CheckBox, (By.ID, 'id_w1_polarity')),
'w1_voltage': (regions.TextBox, (By.ID, 'id_w1_voltage')),
'w1_earth_fault': (regions.TextBox, (By.ID, 'id_w1_earth_fault')),
}
def add_vehicle(self):
self.find_element(*self._add_vehicle_locator).click()
def add_crew(self):
self.find_element(*self._add_crew_locator).click()
@property
def power_mic(self):
return regions.BootstrapSelectElement(self, self.find_element(*self._power_mic_selector))
@property
def vehicles(self):
return [self.VehicleRow(self, el) for el in self.find_elements(*self._vehicle_row_locator)]
class VehicleRow(Region):
_name_locator = ('xpath', ".//input")
_select_locator = ('xpath', ".//div[contains(@class,'bootstrap-select')]/..")
@property
def name(self):
return regions.TextBox(self, self.root.find_element(*self._name_locator))
@property
def vehicle(self):
return regions.BootstrapSelectElement(self, self.root.find_element(*self._select_locator))
@property
def crew(self):
return [self.CrewRow(self, el) for el in self.find_elements(*self._crew_row_locator)]
class CrewRow(Region):
_select_locator = ('xpath', ".//div[contains(@class,'bootstrap-select')]/..")
_start_time_locator = ('xpath', ".//input[@name[starts-with(., 'start') and not(contains(.,'new'))]]")
_end_time_locator = ('xpath', ".//input[@name[starts-with(., 'end') and not(contains(.,'new'))]]")
_role_locator = ('xpath', ".//input[@name[starts-with(., 'role') and not(contains(.,'new'))]]")
@property
def crewmember(self):
return regions.BootstrapSelectElement(self, self.root.find_element(*self._select_locator))
@property
def start_time(self):
return regions.DateTimePicker(self, self.root.find_element(*self._start_time_locator))
@property
def end_time(self):
return regions.DateTimePicker(self, self.root.find_element(*self._end_time_locator))
@property
def role(self):
return regions.TextBox(self, self.root.find_element(*self._role_locator))
@property
def success(self):
return '{event_id}' not in self.driver.current_url
class EditEventChecklist(CreateEventChecklist):
URL_TEMPLATE = '/event/checklist/{pk}/edit'
@property
def success(self):
return 'edit' not in self.driver.current_url
class GenericList(BasePage):
_search_selector = (By.CSS_SELECTOR, 'div.input-group:nth-child(2) > input:nth-child(1)')
_search_go_selector = (By.ID, 'id_search')
_add_item_selector = (By.CSS_SELECTOR, '.btn-success')
class UserPage(BasePage):
URL_TEMPLATE = 'user/'
_api_key_selector = (By.ID, 'api-key')
_cal_url_selector = (By.ID, 'cal-url')
_generation_button_selector = (By.LINK_TEXT, 'Generate API Key')
@property
def api_key(self):
return self.find_element(*self._api_key_selector).text
@property
def cal_url(self):
return self.find_element(*self._cal_url_selector).text
def toggle_filter(self, type_name):
self.find_element(By.XPATH, "//input[@value='" + type_name + "']").click()
def generate_key(self):
self.find_element(*self._generation_button_selector).click()
class CalendarPage(BasePage):
URL_TEMPLATE = 'rigboard/calendar'
_go_locator = (By.ID, 'go-to-date-button')
_today_selector = (By.ID, 'today-button')
_prev_selector = (By.ID, 'prev-button')
_next_selector = (By.ID, 'next-button')
_month_selector = (By.ID, 'month-button')
_week_selector = (By.ID, 'week-button')
_day_selector = (By.ID, 'day-button')
def go(self):
return self.find_element(*self._go_locator).click()
@property
def target_date(self):
return regions.DatePicker(self, self.find_element(By.ID, 'go-to-date-input'))
def today(self):
return self.find_element(*self._today_selector).click()
def prev(self):
return self.find_element(*self._prev_selector).click()
def next(self):
return self.find_element(*self._next_selector).click()
def month(self):
return self.find_element(*self._month_selector).click()
def week(self):
return self.find_element(*self._week_selector).click()
def day(self):
return self.find_element(*self._day_selector).click()

48
RIGS/tests/regions.py Normal file
View File

@@ -0,0 +1,48 @@
from pypom import Region
from selenium.webdriver.common.by import By
from PyRIGS.tests.regions import TextBox, Modal
class Header(Region):
def find_link(self, link_text):
return self.driver.find_element_by_partial_link_text(link_text)
class ItemRow(Region):
_name_locator = (By.XPATH, '//span[@class="name"]')
_description_locator = (By.XPATH, '//div[@class="item-description"]')
_price_locator = (By.XPATH, '//span[@class="cost"]')
_quantity_locator = (By.XPATH, '//td[@class="quantity"]')
_subtotal_locator = (By.XPATH, '//span[@class="sub-total"]')
@property
def name(self):
return self.find_element(*self._name_locator).text
@property
def description(self):
return self.find_element(*self._description_locator).text
@property
def price(self):
return self.find_element(*self._price_locator).text
@property
def quantity(self):
return self.find_element(*self._quantity_locator).text
@property
def subtotal(self):
return self.find_element(*self._subtotal_locator).text
class ItemModal(Modal):
_header_selector = (By.TAG_NAME, 'h4')
form_items = {
'name': (TextBox, (By.ID, 'item_name')),
'description': (TextBox, (By.ID, 'item_description')),
'quantity': (TextBox, (By.ID, 'item_quantity')),
'price': (TextBox, (By.ID, 'item_cost'))
}

View File

@@ -0,0 +1,157 @@
import datetime
from datetime import date
import pytest
from django.conf import settings
from django.core import mail, signing
from django.http import HttpResponseBadRequest
from django.test import TestCase
from django.urls import reverse
import PyRIGS.tests.base
from RIGS import models
from pytest_django.asserts import assertContains, assertNotContains, assertFormError
def setup_event():
venue = models.Venue.objects.create(name='Authorisation Test Venue')
client = models.Person.objects.create(name='Authorisation Test Person', email='authorisation@functional.test')
organisation = models.Organisation.objects.create(name='Authorisation Test Organisation', union_account=True)
return models.Event.objects.create(
name='Authorisation Test',
start_date=date.today(),
venue=venue,
person=client,
organisation=organisation,
)
def setup_mail(event, profile):
profile.email = "teccie@nottinghamtec.co.uk"
profile.save()
auth_data = {
'name': 'Test ABC',
'po': '1234ABCZXY',
'account_code': 'ABC TEST 12345',
'uni_id': 1234567890,
'tos': True
}
hmac = signing.dumps({'pk': event.pk, 'email': 'authemail@function.test',
'sent_by': profile.pk})
url = reverse('event_authorise', kwargs={'pk': event.pk, 'hmac': hmac})
return auth_data, hmac, url
def test_create(admin_client):
url = reverse('event_create')
# end time before start access after start
response = admin_client.post(url, {'start_date': datetime.date(2020, 1, 1), 'start_time': datetime.time(10, 00),
'end_time': datetime.time(9, 00),
'access_at': datetime.datetime(2020, 1, 5, 10)})
assertFormError(response, 'form', 'end_time',
"Unless you've invented time travel, the event can't finish before it has started.")
assertFormError(response, 'form', 'access_at',
"Regardless of what some clients might think, access time cannot be after the event has started.")
def test_requires_valid_hmac(client, admin_user):
event = setup_event()
auth_data, hmac, url = setup_mail(event, admin_user)
bad_hmac = hmac[:-1]
url = reverse('event_authorise', kwargs={'pk': event.pk, 'hmac': bad_hmac})
response = client.get(url)
assert isinstance(response, HttpResponseBadRequest)
# TODO: Add some form of sensible user facing error
# self.assertIn(response.content, "new URL") # check there is some level of sane instruction
# response = client.get(url)
# assertContains(response, event.organisation.name)
def test_validation(client, admin_user):
event = setup_event()
auth_data, hmac, url = setup_mail(event, admin_user)
response = client.get(url)
assertContains(response, "Terms of Hire")
assertContains(response, "Account code")
assertContains(response, "University ID")
response = client.post(url)
assertContains(response, "This field is required.", 5)
auth_data['amount'] = event.total + 1
response = client.post(url, auth_data)
assertContains(response, "The amount authorised must equal the total for the event")
assertNotContains(response, "This field is required.")
auth_data['amount'] = event.total
response = client.post(url, auth_data)
assertContains(response, "Your event has been authorised")
event.refresh_from_db()
assert event.authorised
assert str(event.authorisation.email) == "authemail@function.test"
def test_duplicate_warning(client, admin_user):
event = setup_event()
auth_data, hmac, url = setup_mail(event, admin_user)
auth = models.EventAuthorisation.objects.create(event=event, name='Test ABC', email='dupe@functional.test',
amount=event.total, sent_by=admin_user)
response = client.get(url)
assertContains(response, 'This event has already been authorised.')
auth.amount += 1
auth.save()
response = client.get(url)
assertContains(response, 'amount has changed')
@pytest.mark.django_db
def test_email_sent(admin_client, admin_user, mailoutbox):
event = setup_event()
auth_data, hmac, url = setup_mail(event, admin_user)
data = auth_data
data['amount'] = event.total
response = admin_client.post(url, data)
assertContains(response, "Your event has been authorised.")
assert len(mailoutbox) == 2
assert mailoutbox[0].to == ['authemail@function.test']
assert mailoutbox[1].to == [settings.AUTHORISATION_NOTIFICATION_ADDRESS]
def test_email_check(admin_client, admin_user):
event = setup_event()
url = reverse('event_authorise_request', kwargs={'pk': event.pk})
admin_user.email = 'teccie@someotherdomain.com'
admin_user.save()
response = admin_client.post(url)
assertContains(response, 'must have an @nottinghamtec.co.uk email address')
def test_request_send(admin_client, admin_user):
event = setup_event()
url = reverse('event_authorise_request', kwargs={'pk': event.pk})
admin_user.email = 'teccie@nottinghamtec.co.uk'
admin_user.save()
response = admin_client.post(url)
assertContains(response, 'This field is required.')
mail.outbox = []
response = admin_client.post(url, {'email': 'client@functional.test'})
assert response.status_code == 302
assert len(mail.outbox) == 1
email = mail.outbox[0]
assert 'client@functional.test' in email.to
assert '/event/%d/' % event.pk in email.body
# Check sent by details are populated
event.refresh_from_db()
assert event.auth_request_by == admin_user
assert event.auth_request_to == 'client@functional.test'
assert event.auth_request_at is not None

View File

@@ -0,0 +1,827 @@
import datetime
from datetime import date, time, timedelta
from django.test.client import Client
from django.utils import timezone
from selenium.webdriver.common.action_chains import ActionChains
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 import base
from PyRIGS.tests import regions as base_regions
from PyRIGS.tests.base import (AutoLoginTest, screenshot_failure_cls)
from PyRIGS.tests.pages import animation_is_finished
from RIGS import models
from RIGS.tests import regions
from . import pages
import pytest
import time as t
pytestmark = pytest.mark.django_db(transaction=True)
@screenshot_failure_cls
class BaseRigboardTest(AutoLoginTest):
def setUp(self):
self.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
super().setUp()
self.client = models.Person.objects.create(name='Rigboard Test Person', email='rigboard@functional.test')
self.wait = WebDriverWait(self.driver, 10)
def select_event_type(self, event_type):
self.wait.until(animation_is_finished())
self.assertFalse(self.page.is_expanded)
self.page.select_event_type(event_type)
self.wait.until(animation_is_finished())
self.assertTrue(self.page.is_expanded)
@screenshot_failure_cls
class TestRigboard(BaseRigboardTest):
def setUp(self):
super().setUp()
self.testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL,
start_date=date.today() + timedelta(days=6),
description="start future no end",
purchase_order='TESTPO',
person=self.client,
auth_request_by=self.profile,
auth_request_at=base.create_datetime(2015, 0o6, 0o4, 10, 00),
auth_request_to="some@email.address")
item1 = models.EventItem(
event=self.testEvent,
name="Test Item 1",
cost="10.00",
quantity="1",
order=1
).save()
item2 = models.EventItem(
event=self.testEvent,
name="Test Item 2",
description="Foo",
cost="9.72",
quantity="3",
order=2,
).save()
self.testEvent2 = models.Event.objects.create(name="TE E2", status=models.Event.PROVISIONAL,
start_date=date.today() + timedelta(days=8),
description="start future no end, later",
purchase_order='TESTPO',
person=self.client,
auth_request_by=self.profile,
auth_request_at=base.create_datetime(2015, 0o6, 0o4, 10, 00),
auth_request_to="some@email.address")
self.page = pages.Rigboard(self.driver, self.live_server_url).open()
def test_buttons(self):
header = regions.Header(self.page, self.driver.find_element(By.CSS_SELECTOR, '.navbar'))
# TODO Switch to checking reversed links (difficult because of arguments)
header.find_link("Rigboard").click()
self.assertEqual(
self.live_server_url + '/rigboard/', self.driver.current_url)
header.find_link("Archive").click()
self.assertEqual(
self.live_server_url + '/event/archive/', self.driver.current_url)
# TODO - This fails for some reason
# header.find_link("New").click()
# self.assertEqual(
# self.live_server_url + '/event/create/', self.driver.current_url)
def test_event_order(self):
self.assertIn(self.testEvent.start_date.strftime('%a %d/%m/%Y'), self.page.events[0].dates)
self.assertIn(self.testEvent2.start_date.strftime('%a %d/%m/%Y'), self.page.events[1].dates)
def test_add_button(self):
self.page.add()
self.assertIn('create', self.driver.current_url)
# Ideally get a response object to assert 200 on
@screenshot_failure_cls
class TestEventCreate(BaseRigboardTest):
def setUp(self):
super().setUp()
self.page = pages.CreateEvent(self.driver, self.live_server_url).open()
def test_rig_creation(self):
self.select_event_type("Rig")
self.page.person_selector.search(self.client.name)
self.page.person_selector.set_option(self.client.name, True)
self.page.person_selector.toggle()
self.assertFalse(self.page.person_selector.is_open)
self.page.name = "Test Rig"
# Both dates, no times, end before start
self.page.start_date = datetime.date(2020, 1, 10)
self.page.end_date = datetime.date(2020, 1, 1)
# Expected to fail
self.page.submit()
self.assertFalse(self.page.success)
self.assertIn("Unless you've invented time travel, the event can't finish before it has started.", self.page.errors["End date"])
self.wait.until(animation_is_finished())
# Fix it
self.page.end_date = datetime.date(2020, 1, 11)
self.page.access_at = datetime.datetime(2020, 1, 1, 9)
self.page.dry_hire = True
self.page.status = "Booked"
self.page.collected_by = "Fred"
self.page.po = "1234"
self.page.notes = "A note!"
self.page.submit()
self.assertTrue(self.page.success)
# TODO
def test_modals(self):
self.select_event_type("Rig")
# Create new person
modal = self.page.add_person()
# animation_is_finished doesn't work for whatever reason...
self.wait.until(EC.visibility_of_element_located((By.ID, 'modal')))
self.assertTrue(modal.is_open)
self.assertIn("Create Person", modal.header)
# Fill person form out and submit
person_name = "Test Person"
modal.name = person_name
modal.submit()
self.wait.until(EC.invisibility_of_element_located((By.ID, 'modal')))
self.assertFalse(modal.is_open)
# See new person selected
self.page.person_selector.toggle()
self.assertEqual(self.page.person_selector.options[0].name, person_name)
self.page.person_selector.toggle()
# Change mind and add another
self.wait.until(animation_is_finished())
modal = self.page.add_person()
self.wait.until(EC.visibility_of_element_located((By.ID, 'modal')))
self.assertTrue(modal.is_open)
self.assertIn("Create Person", modal.header)
person_name = "Test Person 2"
modal.name = person_name
modal.submit()
self.wait.until(EC.invisibility_of_element_located((By.ID, 'modal')))
self.assertFalse(modal.is_open)
self.page.person_selector.toggle()
self.assertEqual(self.page.person_selector.options[0].name, person_name)
self.page.person_selector.toggle()
# TODO
def test_event_item_creation(self):
self.select_event_type("Rig")
self.page.name = "Test Event with Items"
self.page.person_selector.search(self.client.name)
self.page.person_selector.set_option(self.client.name, True)
# TODO This should not be necessary, normally closes automatically
self.page.person_selector.toggle()
self.assertFalse(self.page.person_selector.is_open)
# Note to self, don't set dates before 2014, which is the beginning of VAT as far as the tests are concerned...
self.page.start_date = datetime.date(2084, 1, 1)
modal = self.page.add_event_item()
self.wait.until(animation_is_finished())
# See modal has opened
self.assertTrue(modal.is_open)
self.assertIn("New Event", modal.header)
modal.name = "Test Item 1"
modal.description = "This is an item description\nthat for reasons unknown spans two lines"
modal.quantity = "2"
modal.price = "23.95"
modal.submit()
self.wait.until(animation_is_finished())
# Confirm item has been saved to json field
objectitems = self.driver.execute_script("return objectitems;")
self.assertEqual(1, len(objectitems))
testitem = objectitems["-1"]['fields'] # as we are deliberately creating this we know the ID
self.assertEqual("Test Item 1", testitem['name'])
self.assertEqual("2", testitem['quantity']) # test a couple of "worse case" fields
total = self.driver.find_element_by_id('total')
ActionChains(self.driver).move_to_element(total).perform()
# See new item appear in table
row = self.page.item_row("-1") # ID number is known, see above
self.assertIn("Test Item 1", row.name)
self.assertIn("This is an item description",
row.description)
self.assertEqual('23.95', row.price)
self.assertEqual("2", row.quantity)
self.assertEqual('47.90', row.subtotal)
# Check totals TODO convert to page properties
self.assertEqual("47.90", self.driver.find_element_by_id('sumtotal').text)
self.assertIn("(TBC)", self.driver.find_element_by_id('vat-rate').text)
self.assertEqual("9.58", self.driver.find_element_by_id('vat').text)
self.assertEqual("57.48", total.text)
self.page.submit()
# TODO Testing of internal rig (approval system interface)
def test_non_rig_creation(self):
self.select_event_type("Non-Rig")
self.assertFalse(self.page.person_selector.is_open)
rig_name = "Test Non-Rig"
self.page.name = rig_name
# Double-check we don't lose data when swapping
self.page.select_event_type("Rig")
self.wait.until(animation_is_finished())
self.assertEqual(self.page.name, rig_name)
self.wait.until(animation_is_finished())
self.page.select_event_type("Non-Rig")
self.page.start_date = datetime.date(2020, 1, 1)
self.page.status = "Confirmed"
self.page.submit()
self.assertTrue(self.page.success)
@screenshot_failure_cls
class TestEventDuplicate(BaseRigboardTest):
def setUp(self):
super().setUp()
self.testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL,
start_date=date.today() + timedelta(days=6),
description="start future no end",
purchase_order='TESTPO',
person=self.client,
auth_request_by=self.profile,
auth_request_at=base.create_datetime(2015, 0o6, 0o4, 10, 00),
auth_request_to="some@email.address")
item1 = models.EventItem(
event=self.testEvent,
name="Test Item 1",
cost="10.00",
quantity="1",
order=1
).save()
item2 = models.EventItem(
event=self.testEvent,
name="Test Item 2",
description="Foo",
cost="9.72",
quantity="3",
order=2,
).save()
self.page = pages.DuplicateEvent(self.driver, self.live_server_url, event_id=self.testEvent.pk).open()
def test_rig_duplicate(self):
table = self.page.item_table
self.assertIn("Test Item 1", table.text)
self.assertIn("Test Item 2", table.text)
# Check the info message is visible
self.assertIn("Event data duplicated but not yet saved", self.page.warning)
modal = self.page.add_event_item()
self.wait.until(animation_is_finished())
# See modal has opened
self.assertTrue(modal.is_open)
self.assertIn(self.testEvent.name, modal.header)
modal.name = "Test Item 3"
modal.description = "This is an item description\nthat for reasons unknown spans two lines"
modal.quantity = "2"
modal.price = "23.95"
modal.submit()
self.wait.until(animation_is_finished())
# Attempt to save
self.page.submit()
# TODO Rewrite when EventDetail page is implemented
newEvent = models.Event.objects.latest('pk')
assert newEvent.auth_request_to == ''
self.assertEqual(newEvent.auth_request_by, None)
self.assertEqual(newEvent.auth_request_at, None)
self.assertFalse(newEvent.authorised)
self.assertNotIn("N%05d" % self.testEvent.pk, self.driver.find_element_by_xpath('//h2').text)
self.assertNotIn("Event data duplicated but not yet saved", self.page.warning) # Check info message not visible
# Check the new items are visible
table = self.page.item_table
self.assertIn("Test Item 1", table.text)
self.assertIn("Test Item 2", table.text)
self.assertIn("Test Item 3", table.text)
infoPanel = self.driver.find_element_by_xpath('//div[contains(text(), "Event Info")]/..')
self.assertIn("N%05d" % self.testEvent.pk,
infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text)
# Check the PO hasn't carried through
self.assertNotIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text)
self.assertIn("N%05d" % self.testEvent.pk,
infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text)
self.driver.get(self.live_server_url + '/event/' + str(self.testEvent.pk)) # Go back to the old event
# Check that based-on hasn't crept into the old event
infoPanel = self.driver.find_element_by_xpath('//div[contains(text(), "Event Info")]/..')
self.assertNotIn("N%05d" % self.testEvent.pk,
infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text)
# Check the PO remains on the old event
self.assertIn("TESTPO", infoPanel.find_element_by_xpath('//dt[text()="PO"]/following-sibling::dd[1]').text)
self.assertNotIn("N%05d" % self.testEvent.pk,
infoPanel.find_element_by_xpath('//dt[text()="Based On"]/following-sibling::dd[1]').text)
# Check the items are as they were
table = self.page.item_table # ID number is known, see above
self.assertIn("Test Item 1", table.text)
self.assertIn("Test Item 2", table.text)
self.assertNotIn("Test Item 3", table.text)
@screenshot_failure_cls
class TestEventEdit(BaseRigboardTest):
def setUp(self):
super().setUp()
self.testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL,
start_date=date.today() + timedelta(days=6),
description="start future no end",
purchase_order='TESTPO',
person=self.client,
auth_request_by=self.profile,
auth_request_at=base.create_datetime(2015, 0o6, 0o4, 10, 00),
auth_request_to="some@email.address")
item1 = models.EventItem(
event=self.testEvent,
name="Test Item 1",
cost="10.00",
quantity="1",
order=1
).save()
item2 = models.EventItem(
event=self.testEvent,
name="Test Item 2",
description="Foo",
cost="9.72",
quantity="3",
order=2,
).save()
self.page = pages.EditEvent(self.driver, self.live_server_url, event_id=self.testEvent.pk).open()
def test_rig_edit(self):
self.page.name = "Edited Event"
modal = self.page.add_event_item()
self.wait.until(animation_is_finished())
# See modal has opened
self.assertTrue(modal.is_open)
self.assertIn(self.testEvent.name, modal.header)
modal.name = "Test Item 3"
modal.description = "This is an item description\nthat for reasons unknown spans two lines"
modal.quantity = "2"
modal.price = "23.95"
modal.submit()
self.wait.until(animation_is_finished())
# Attempt to save
ActionChains(self.driver).move_to_element(self.page.item_table).perform()
self.page.submit()
self.assertTrue(self.page.success)
self.page = pages.EventDetail(self.driver, self.live_server_url, event_id=self.testEvent.pk).open()
self.testEvent.refresh_from_db()
self.assertIn(self.testEvent.name, self.page.event_name)
self.assertEqual(self.page.name, self.testEvent.person.name)
# Check the new items are visible
table = self.page.item_table
self.assertIn("Test Item 3", table.text)
@screenshot_failure_cls
class TestEventDetail(BaseRigboardTest):
def setUp(self):
super().setUp()
self.testEvent = models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL,
start_date=date.today() + timedelta(days=6),
description="start future no end",
purchase_order='TESTPO',
person=self.client,
auth_request_by=self.profile,
auth_request_at=base.create_datetime(2015, 0o6, 0o4, 10, 00),
auth_request_to="some@email.address")
item1 = models.EventItem(
event=self.testEvent,
name="Test Item 1",
cost="10.00",
quantity="1",
order=1
).save()
item2 = models.EventItem(
event=self.testEvent,
name="Test Item 2",
description="Foo",
cost="9.72",
quantity="3",
order=2,
).save()
self.page = pages.EventDetail(self.driver, self.live_server_url, event_id=self.testEvent.pk).open()
def test_rig_detail(self):
self.assertIn("N%05d | %s" % (self.testEvent.pk, self.testEvent.name), self.page.event_name)
self.assertEqual(self.client.name, self.page.name)
self.assertEqual(self.client.email, self.page.email)
assert self.client.phone == ''
@screenshot_failure_cls
class TestCalendar(BaseRigboardTest):
def setUp(self):
super().setUp()
self.all_events = set(range(1, 18))
self.current_events = (1, 2, 3, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18)
self.not_current_events = set(self.all_events) - set(self.current_events)
# produce 7 normal events - 5 current - 1 last week - 1 two years ago - 2 provisional - 2 confirmed - 3 booked
models.Event.objects.create(name="TE E1", status=models.Event.PROVISIONAL,
start_date=date.today() + timedelta(days=6), description="start future no end")
models.Event.objects.create(name="TE E2", status=models.Event.PROVISIONAL, start_date=date.today(),
description="start today no end")
models.Event.objects.create(name="TE E3", status=models.Event.CONFIRMED, start_date=date.today(),
end_date=date.today(), description="start today with end today")
models.Event.objects.create(name="TE E4", status=models.Event.CONFIRMED,
start_date=date.today() - timedelta(weeks=104),
description="start past 2 years no end")
models.Event.objects.create(name="TE E5", status=models.Event.BOOKED,
start_date=date.today() - timedelta(days=7),
end_date=date.today() - timedelta(days=1),
description="start past 1 week with end past")
models.Event.objects.create(name="TE E6", status=models.Event.BOOKED,
start_date=date.today() - timedelta(days=2),
start_time=time(8, 00),
end_date=date.today() + timedelta(days=2),
end_time=time(23, 00), description="start past, end future")
models.Event.objects.create(name="TE E7", status=models.Event.BOOKED,
start_date=date.today() + timedelta(days=2),
end_date=date.today() + timedelta(days=2), description="start + end in future")
# 2 cancelled - 1 current
models.Event.objects.create(name="TE E8", start_date=date.today() + timedelta(days=2),
end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED,
description="cancelled in future")
models.Event.objects.create(name="TE E9", start_date=date.today() - timedelta(days=1),
end_date=date.today() + timedelta(days=2), status=models.Event.CANCELLED,
description="cancelled and started")
# 5 dry hire - 3 current - 1 cancelled
models.Event.objects.create(name="TE E10", start_date=date.today(), dry_hire=True, description="dryhire today")
models.Event.objects.create(name="TE E11", start_date=date.today(), dry_hire=True, checked_in_by=self.profile,
description="dryhire today, checked in")
models.Event.objects.create(name="TE E12", start_date=date.today() - timedelta(days=1), dry_hire=True,
status=models.Event.BOOKED, description="dryhire past")
models.Event.objects.create(name="TE E13", start_date=date.today() - timedelta(days=2), dry_hire=True,
checked_in_by=self.profile, description="dryhire past checked in")
models.Event.objects.create(name="TE E14", start_date=date.today(), dry_hire=True,
status=models.Event.CANCELLED, description="dryhire today cancelled")
# 4 non rig - 3 current
models.Event.objects.create(name="TE E15", start_date=date.today(), is_rig=False, description="non rig today")
models.Event.objects.create(name="TE E16", start_date=date.today() + timedelta(days=1), is_rig=False,
description="non rig tomorrow")
models.Event.objects.create(name="TE E17", start_date=date.today() - timedelta(days=1), is_rig=False,
description="non rig yesterday")
models.Event.objects.create(name="TE E18", start_date=date.today(), is_rig=False, status=models.Event.CANCELLED,
description="non rig today cancelled")
self.page = pages.UserPage(self.driver, self.live_server_url).open()
def test_api_key_generation(self):
# Completes and comes back to /user/
# Checks that no api key is displayed
self.assertEqual("No API Key Generated", self.page.api_key)
# Now creates an API key, and check a URL is displayed one
self.page.generate_key()
self.assertIn("rigs.ics", self.page.cal_url)
self.assertNotIn("?", self.page.cal_url)
# Lets change everything so it's not the default value
self.page.toggle_filter('rig')
self.page.toggle_filter('non-rig')
self.page.toggle_filter('dry-hire')
self.page.toggle_filter('cancelled')
self.page.toggle_filter('provisional')
self.page.toggle_filter('confirmed')
# and then check the url is correct
self.assertIn(
"rigs.ics?rig=false&non-rig=false&dry-hire=false&cancelled=true&provisional=false&confirmed=false",
self.page.cal_url)
# Awesome - all seems to work
def test_ics_files(self):
specialEvent = models.Event.objects.get(name="TE E6")
# Now creates an API key, and check a URL is displayed one
self.page.generate_key()
c = Client()
# Default settings - should have all non-cancelled events
# Get the ical file (can't do this in selanium because reasons)
icalUrl = self.page.cal_url
response = c.get(self.page.cal_url)
self.assertEqual(200, response.status_code)
# content = response.content.decode('utf-8')
# Check has entire file
self.assertContains(response, "BEGIN:VCALENDAR")
self.assertContains(response, "END:VCALENDAR")
expectedIn = [1, 2, 3, 5, 6, 7, 10, 11, 12, 13, 15, 16, 17]
for test in range(1, 18):
if test in expectedIn:
self.assertContains(response, "TE E" + str(test) + " ")
else:
self.assertNotContains(response, "TE E" + str(test) + " ")
# Check that times have been included correctly
self.assertContains(response,
specialEvent.start_date.strftime('%Y%m%d') + 'T' + specialEvent.start_time.strftime(
'%H%M%S'))
self.assertContains(response,
specialEvent.end_date.strftime('%Y%m%d'))
self.assertContains(response,
specialEvent.end_time.strftime('%H%M%S'))
# Only non rigs
self.page.toggle_filter('rig')
self.page.toggle_filter('non-rig')
icalUrl = self.page.cal_url
response = c.get(icalUrl)
self.assertEqual(200, response.status_code)
expectedIn = [10, 11, 12, 13]
for test in range(1, 18):
if test in expectedIn:
self.assertContains(response, "TE E" + str(test) + " ")
else:
self.assertNotContains(response, "TE E" + str(test) + " ")
# Only provisional rigs
self.page.toggle_filter('rig')
self.page.toggle_filter('dry-hire')
self.page.toggle_filter('confirmed')
icalUrl = self.page.cal_url
response = c.get(icalUrl)
self.assertEqual(200, response.status_code)
expectedIn = [1, 2]
for test in range(1, 18):
if test in expectedIn:
self.assertContains(response, "TE E" + str(test) + " ")
else:
self.assertNotContains(response, "TE E" + str(test) + " ")
# Only cancelled non-rigs
self.page.toggle_filter('rig')
self.page.toggle_filter('non-rig')
self.page.toggle_filter('provisional')
self.page.toggle_filter('cancelled')
icalUrl = self.page.cal_url
response = c.get(icalUrl)
self.assertEqual(200, response.status_code)
expectedIn = [18]
for test in range(1, 18):
if test in expectedIn:
self.assertContains(response, "TE E" + str(test) + " ")
else:
self.assertNotContains(response, "TE E" + str(test) + " ")
# Nothing selected
self.page.toggle_filter('non-rig')
self.page.toggle_filter('cancelled')
icalUrl = self.page.cal_url
response = c.get(icalUrl)
self.assertEqual(200, response.status_code)
expectedIn = []
for test in range(1, 18):
if test in expectedIn:
self.assertContains(response, "TE E" + str(test) + " ")
else:
self.assertNotContains(response, "TE E" + str(test) + " ")
def test_calendar_buttons(logged_in_browser, live_server): # If FullCalendar fails to load for whatever reason, the buttons don't work
page = pages.CalendarPage(logged_in_browser.driver, live_server.url).open()
assert timezone.now().strftime("%Y-%m") in logged_in_browser.url
target_date = datetime.date(2020, 1, 1)
page.target_date.set_value(target_date)
page.go()
assert page.target_date.value.strftime("%Y-%m") in logged_in_browser.url
page.next()
target_date += datetime.timedelta(days=32)
assert target_date.strftime("%m") in logged_in_browser.url
def test_ra_edit(logged_in_browser, live_server, ra):
page = pages.EditRiskAssessment(logged_in_browser.driver, live_server.url, pk=ra.pk).open()
page.nonstandard_equipment = nse = True
page.general_notes = gn = "There are some notes, but I've not written them here as that would be helpful"
page.submit()
assert not page.success
page.supervisor_consulted = True
page.submit()
assert page.success
# Check that data is right
ra = models.RiskAssessment.objects.get(pk=ra.pk)
assert ra.general_notes == gn
assert ra.nonstandard_equipment == nse
def small_ec(page, admin_user):
page.safe_parking = True
page.safe_packing = True
page.exits = True
page.trip_hazard = True
page.warning_signs = True
page.ear_plugs = True
page.hs_location = "The Moon"
page.extinguishers_location = "With the rest of the fire"
# If we do this first the search fails, for ... reasons
page.power_mic.search(admin_user.name)
page.power_mic.toggle()
assert not page.power_mic.is_open
page.earthing = True
page.rcds = True
page.supply_test = True
page.pat = True
def test_ec_create_small(logged_in_browser, live_server, admin_user, ra):
page = pages.CreateEventChecklist(logged_in_browser.driver, live_server.url, event_id=ra.event.pk).open()
small_ec(page, admin_user)
page.submit()
assert page.success
def test_ec_create_medium(logged_in_browser, live_server, admin_user, medium_ra):
page = pages.CreateEventChecklist(logged_in_browser.driver, live_server.url, event_id=medium_ra.event.pk).open()
page.safe_parking = True
page.safe_packing = True
page.exits = True
page.trip_hazard = True
page.warning_signs = True
page.ear_plugs = True
page.hs_location = "Death Valley"
page.extinguishers_location = "With the rest of the fire"
# If we do this first the search fails, for ... reasons
page.power_mic.search(admin_user.name)
page.power_mic.toggle()
assert not page.power_mic.is_open
# Gotta scroll to make the button clickable
logged_in_browser.driver.execute_script("window.scrollTo(0, document.body.scrollHeight);")
page.earthing = True
page.pat = True
page.source_rcd = True
page.labelling = True
page.fd_voltage_l1 = 240
page.fd_voltage_l2 = 235
page.fd_voltage_l3 = 0
page.fd_phase_rotation = True
page.fd_earth_fault = "1.21"
page.fd_pssc = 1984
page.w1_description = "In the carpark, by the bins"
page.w1_polarity = True
page.w1_voltage = 240
page.w1_earth_fault = "0.42"
page.submit()
assert page.success
def test_ec_create_vehicle(logged_in_browser, live_server, admin_user, checklist):
page = pages.EditEventChecklist(logged_in_browser.driver, live_server.url, pk=checklist.pk).open()
small_ec(page, admin_user)
page.add_vehicle()
assert len(page.vehicles) == 1
vehicle_name = 'Brian'
page.vehicles[0].name.set_value(vehicle_name)
# Appears we're moving too fast for javascript...
t.sleep(1)
page.vehicles[0].vehicle.search(admin_user.first_name)
t.sleep(1)
page.submit()
assert page.success
# Check data is correct
checklist.refresh_from_db()
vehicle = models.EventChecklistVehicle.objects.get(checklist=checklist.pk)
assert vehicle_name == vehicle.vehicle
# TODO Test validation of end before start
def test_ec_create_crew(logged_in_browser, live_server, admin_user, checklist):
page = pages.EditEventChecklist(logged_in_browser.driver, live_server.url, pk=checklist.pk).open()
small_ec(page, admin_user)
page.add_crew()
assert len(page.crew) == 1
role = "MIC"
start_time = timezone.make_aware(datetime.datetime(2015, 1, 1, 9, 0))
end_time = timezone.make_aware(datetime.datetime(2015, 1, 1, 10, 30))
crew = page.crew[0]
t.sleep(2)
crew.crewmember.search(admin_user.first_name)
t.sleep(2)
crew.role.set_value(role)
crew.start_time.set_value(start_time)
crew.end_time.set_value(end_time)
page.submit()
assert page.success
# Check data is correct
crew_obj = models.EventChecklistCrew.objects.get(checklist=checklist.pk)
assert admin_user.pk == crew_obj.crewmember.pk
assert role == crew_obj.role
assert start_time == crew_obj.start
assert end_time == crew_obj.end
# TODO Can I loop through all the boolean fields and test them at once?
def test_ra_creation(logged_in_browser, live_server, admin_user, basic_event):
page = pages.CreateRiskAssessment(logged_in_browser.driver, live_server.url, event_id=basic_event.pk).open()
# Check there are no defaults
assert page.nonstandard_equipment is None
# No database side validation, only HTML5.
page.nonstandard_equipment = False
page.nonstandard_use = False
page.contractors = False
page.other_companies = False
page.crew_fatigue = False
page.general_notes = "There are no notes."
page.big_power = False
page.outside = False
page.power_mic.search(admin_user.first_name)
page.generators = False
page.other_companies_power = False
page.nonstandard_equipment_power = False
page.multiple_electrical_environments = False
page.power_notes = "Remember to bring some power"
page.noise_monitoring = False
page.sound_notes = "Loud, but not too loud"
page.known_venue = False
page.safe_loading = False
page.safe_storage = False
page.area_outside_of_control = False
page.barrier_required = False
page.nonstandard_emergency_procedure = False
page.special_structures = False
# self.page.persons_responsible_structures = "Nobody and her cat, She"
page.suspended_structures = True
# TODO Test for this proper
page.rigging_plan = "https://nottinghamtec.sharepoint.com/test/"
page.submit()
assert not page.success
page.suspended_structures = False
page.submit()
assert page.success
def test_ra_no_duplicates(logged_in_browser, live_server, ra):
# Test that we can't make another one
page = pages.CreateRiskAssessment(logged_in_browser.driver, live_server.url, event_id=ra.event.pk).open()
assert 'edit' in logged_in_browser.url

343
RIGS/tests/test_models.py Normal file
View File

@@ -0,0 +1,343 @@
from datetime import date, timedelta, datetime, time
from decimal import *
import pytz
import pytest
from django.conf import settings
from django.test import TestCase
from reversion import revisions as reversion
from RIGS import models
def assert_decimal_equality(d1, d2):
assert float(d1) == pytest.approx(float(d2))
def test_str():
profile = models.Profile(first_name='Test', last_name='Case')
assert str(profile) == 'Test Case'
profile.initials = 'TC'
assert str(profile) == 'Test Case "TC"'
@pytest.mark.django_db
def test_find_correct(vat_rate):
new_rate = models.VatRate.objects.create(start_at='2016-03-01', rate=0.15, comment='test2')
r = models.VatRate.objects.find_rate('2015-03-01')
assert_decimal_equality(r.rate, vat_rate.rate)
r = models.VatRate.objects.find_rate('2016-03-01')
assert_decimal_equality(r.rate, new_rate.rate)
def test_percent_correct(vat_rate):
assert vat_rate.as_percent == 20
def test_related_vatrate(basic_event, vat_rate):
assert_decimal_equality(vat_rate.rate, basic_event.vat_rate.rate)
class EventTest():
def test_count(many_events):
# Sanity check we have the expected events created
assert models.Event.objects.count() == 18
def test_rig_count(many_events):
# Changed to not include unreturned dry hires in rig count
assert models.Event.objects.rig_count() == 7
def test_current_events(many_events):
all_events = set(range(1, 18))
current_events = (1, 2, 3, 6, 7, 8, 10, 11, 12, 14, 15, 16, 18)
not_current_events = set(cls.all_events) - set(cls.current_events)
current_events = models.Event.objects.current_events()
assert len(current_events) == len(self.current_events)
for eid in current_events:
assert models.Event.objects.get(name="TE E%d" % eid) in current_events
for eid in not_current_events:
assert models.Event.objects.get(name="TE E%d" % eid) not in current_events
def test_related(many_events):
v1 = models.Venue.objects.create(name="TE V1")
v2 = models.Venue.objects.create(name="TE V2")
e1 = []
e2 = []
for (key, event) in self.events.items():
if event.pk % 2:
event.venue = v1
e1.append(event)
else:
event.venue = v2
e2.append(event)
event.save()
assert set(e1) == set(v1.latest_events)
assert set(e2) == set(v2.latest_events)
# Cleanup
v1.delete()
v2.delete()
def test_related_person(many_events):
p1 = models.Person.objects.create(name="TE P1")
p2 = models.Person.objects.create(name="TE P2")
e1 = []
e2 = []
for (key, event) in self.events.items():
if event.pk % 2:
event.person = p1
e1.append(event)
else:
event.person = p2
e2.append(event)
event.save()
assert set(e1) == set(p1.latest_events)
assert set(e2) == set(p2.latest_events)
p1.delete()
p2.delete()
def test_related_organisation(many_events):
o1 = models.Organisation.objects.create(name="TE O1")
o2 = models.Organisation.objects.create(name="TE O2")
e1 = []
e2 = []
for (key, event) in self.events.items():
if event.pk % 2:
event.organisation = o1
e1.append(event)
else:
event.organisation = o2
e2.append(event)
event.save()
assert set(e1) == set(o1.latest_events)
assert set(e1) == set(o2.latest_events)
for (key, event) in self.events.items():
event.organisation = None
def test_organisation_person_join(many_events):
p1 = models.Person.objects.create(name="TE P1")
p2 = models.Person.objects.create(name="TE P2")
o1 = models.Organisation.objects.create(name="TE O1")
o2 = models.Organisation.objects.create(name="TE O2")
events = models.Event.objects.all()
# p1 in o1 + o2, p2 in o1
for event in events[:2]:
event.person = p1
event.organisation = o1
event.save()
for event in events[3:4]:
event.person = p1
event.organisation = o2
event.save()
for event in events[5:7]:
event.person = p2
event.organisation = o1
event.save()
events = models.Event.objects.all()
# Check person's organisations
assert (o1, 2) in p1.organisations
assert (o2, 1) in p1.organisations
assert (o1, 2) in p2.organisations
assert len(p2.organisations) == 1
# Check organisation's persons
assert (p1, 2) in o1.persons
assert (p2, 2) in o1.persons
assert (p1, 1) in o2.persons
assert len(o2.persons) == 1
def test_cancelled_property(many_events):
edit = many_events[1]
edit.status = models.Event.CANCELLED
edit.save()
event = models.Event.objects.get(pk=edit.pk)
assert event.status == models.Event.CANCELLED
assert event.cancelled
event.status = models.Event.PROVISIONAL
event.save()
def test_confirmed_property(many_events):
edit = many_events[1]
edit.status = models.Event.CONFIRMED
edit.save()
event = models.Event.objects.get(pk=edit.pk)
assert event.status == models.Event.CONFIRMED
assert event.confirmed
event.status = models.Event.PROVISIONAL
event.save()
def test_earliest_time():
event = models.Event(name="TE ET", start_date=date(2016, 0o1, 0o1))
# Just a start date
assert event.earliest_time == date(2016, 0o1, 0o1)
# With start time
event.start_time = time(9, 00)
assert event.earliest_time == create_datetime(2016, 1, 1, 9, 00)
# With access time
event.access_at = create_datetime(2015, 12, 0o3, 9, 57)
assert event.earliest_time == event.access_at
# With meet time
event.meet_at = create_datetime(2015, 12, 0o3, 9, 55)
assert event.earliest_time == event.meet_at
# Check order isn't important
event.start_date = date(2015, 12, 0o3)
assert event.earliest_time == create_datetime(2015, 12, 0o3, 9, 00)
def test_latest_time():
event = models.Event(name="TE LT", start_date=date(2016, 0o1, 0o1))
# Just start date
assert event.latest_time == event.start_date
# Just end date
event.end_date = date(2016, 1, 2)
assert event.latest_time == event.end_date
# With end time
event.end_time = time(23, 00)
assert event.latest_time == create_datetime(2016, 1, 2, 23, 00)
def test_in_bounds():
manager = models.Event.objects
events = [
manager.create(name="TE IB0", start_date='2016-01-02'), # yes no
manager.create(name="TE IB1", start_date='2015-12-31', end_date='2016-01-04'),
# basic checks
manager.create(name='TE IB2', start_date='2016-01-02', end_date='2016-01-04'),
manager.create(name='TE IB3', start_date='2015-12-31', end_date='2016-01-03'),
manager.create(name='TE IB4', start_date='2016-01-04',
access_at=create_datetime(2016, 0o1, 0o3, 00, 00)),
manager.create(name='TE IB5', start_date='2016-01-04',
meet_at=create_datetime(2016, 0o1, 0o2, 00, 00)),
# negative check
manager.create(name='TE IB6', start_date='2015-12-31', end_date='2016-01-01'),
]
in_bounds = manager.events_in_bounds(create_datetime(2016, 1, 2, 0, 0),
create_datetime(2016, 1, 3, 0, 0))
assert events[0] in in_bounds
assert events[1], in_bounds
assert events[2], in_bounds
assert events[3], in_bounds
assert events[4], in_bounds
assert events[5], in_bounds
assert events[6] not in in_bounds
def create_datetime(year, month, day, hour, minute):
tz = pytz.timezone(settings.TIME_ZONE)
return tz.localize(datetime(year, month, day, hour, minute))
class EventItemTestCase(TestCase):
def setUp(self):
self.e1 = models.Event.objects.create(name="TI E1", start_date=date.today())
self.e2 = models.Event.objects.create(name="TI E2", start_date=date.today())
def test_item_cost(self):
item = models.EventItem.objects.create(event=self.e1, name="TI I1", quantity=1, cost=1.00, order=1)
self.assertEqual(item.total_cost, 1.00)
item.cost = 2.50
self.assertEqual(item.total_cost, 2.50)
item.quantity = 4
self.assertEqual(item.total_cost, 10.00)
# need to tidy up
item.delete()
def test_item_order(self):
i1 = models.EventItem.objects.create(event=self.e1, name="TI I1", quantity=1, cost=1.00, order=1)
i2 = models.EventItem.objects.create(event=self.e1, name="TI I2", quantity=1, cost=1.00, order=2)
items = self.e1.items.all()
self.assertListEqual([i1, i2], list(items))
class EventPricingTestCase(TestCase):
def setUp(self):
models.VatRate.objects.create(rate=0.10, comment="TP V2", start_at=date.today() - timedelta(days=1))
self.e1 = models.Event.objects.create(name="TP E1", start_date=date.today() - timedelta(days=2))
self.e2 = models.Event.objects.create(name="TP E2", start_date=date.today())
# Create some items E1, total 70.40
# Create some items E2, total 381.20
self.i1 = models.EventItem.objects.create(event=self.e1, name="TP I1", quantity=1, cost=50.00, order=1)
self.i2 = models.EventItem.objects.create(event=self.e1, name="TP I2", quantity=2, cost=3.20, order=2)
self.i3 = models.EventItem.objects.create(event=self.e1, name="TP I3", quantity=7, cost=2.00, order=3)
self.i4 = models.EventItem.objects.create(event=self.e2, name="TP I4", quantity=2, cost=190.60, order=1)
# Decimal type is needed here as that is what is returned from the model.
# Using anything else results in a failure due to floating point arritmetic
def test_sum_totals(self):
self.assertEqual(self.e1.sum_total, Decimal('70.40'))
self.assertEqual(self.e2.sum_total, Decimal('381.20'))
def test_vat_rate(self):
self.assertEqual(self.e1.vat_rate.rate, Decimal('0.20'))
self.assertEqual(self.e2.vat_rate.rate, Decimal('0.10'))
def test_vat_ammount(self):
self.assertEqual(self.e1.vat, Decimal('14.08'))
self.assertEqual(self.e2.vat, Decimal('38.12'))
def test_grand_total(self):
self.assertEqual(self.e1.total, Decimal('84.48'))
self.assertEqual(self.e2.total, Decimal('419.32'))
class EventAuthorisationTestCase(TestCase):
def setUp(self):
self.profile = models.Profile.objects.get_or_create(
first_name='Test',
last_name='TEC User',
username='eventauthtest',
email='teccie@functional.test',
is_superuser=True # lazily grant all permissions
)[0]
self.person = models.Person.objects.create(name='Authorisation Test Person')
self.organisation = models.Organisation.objects.create(name='Authorisation Test Organisation', union_account=True)
self.event = models.Event.objects.create(name="AuthorisationTestCase", person=self.person, organisation=self.organisation,
start_date=date.today())
# Add some items
models.EventItem.objects.create(event=self.event, name="Authorisation test item", quantity=2, cost=123.45,
order=1)
def test_event_property(self):
auth1 = models.EventAuthorisation.objects.create(event=self.event, email="authorisation@model.test.case",
name="Test Auth 1", amount=self.event.total - 1,
sent_by=self.profile)
self.assertFalse(self.event.authorised)
auth1.amount = self.event.total
auth1.save()
self.assertTrue(self.event.authorised)
def test_last_edited(self):
with reversion.create_revision():
auth = models.EventAuthorisation.objects.create(event=self.event, email="authorisation@model.test.case",
name="Test Auth", amount=self.event.total,
sent_by=self.profile)
self.assertIsNotNone(auth.last_edited_at)

580
RIGS/tests/test_unit.py Normal file
View File

@@ -0,0 +1,580 @@
from datetime import date
from django.core.exceptions import ObjectDoesNotExist
from django.test import TestCase
from django.test.utils import override_settings
from django.utils.safestring import SafeText
from RIGS.templatetags.markdown_tags import markdown_filter
from django.urls import reverse, reverse_lazy
from django.utils import timezone
from pytest_django.asserts import assertRedirects, assertNotContains, assertContains
from PyRIGS.tests.base import assert_times_almost_equal, assert_oembed, login
from RIGS import models
import pytest
pytestmark = pytest.mark.django_db
class TestAdminMergeObjects(TestCase):
@classmethod
def setUpTestData(cls):
cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True,
is_active=True, is_staff=True)
cls.persons = {
1: models.Person.objects.create(name="Person 1"),
2: models.Person.objects.create(name="Person 2"),
3: models.Person.objects.create(name="Person 3"),
}
cls.organisations = {
1: models.Organisation.objects.create(name="Organisation 1"),
2: models.Organisation.objects.create(name="Organisation 2"),
3: models.Organisation.objects.create(name="Organisation 3"),
}
cls.venues = {
1: models.Venue.objects.create(name="Venue 1"),
2: models.Venue.objects.create(name="Venue 2"),
3: models.Venue.objects.create(name="Venue 3"),
}
cls.events = {
1: models.Event.objects.create(name="TE E1", start_date=date.today(), person=cls.persons[1],
organisation=cls.organisations[3], venue=cls.venues[2]),
2: models.Event.objects.create(name="TE E2", start_date=date.today(), person=cls.persons[2],
organisation=cls.organisations[2], venue=cls.venues[3]),
3: models.Event.objects.create(name="TE E3", start_date=date.today(), person=cls.persons[3],
organisation=cls.organisations[1], venue=cls.venues[1]),
4: models.Event.objects.create(name="TE E4", start_date=date.today(), person=cls.persons[3],
organisation=cls.organisations[3], venue=cls.venues[3]),
}
def setUp(self):
self.profile.set_password('testuser')
self.profile.save()
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
def test_merge_confirmation(self):
change_url = reverse('admin:RIGS_venue_changelist')
data = {
'action': 'merge',
'_selected_action': [str(val.pk) for key, val in self.venues.items()]
}
response = self.client.post(change_url, data, follow=True)
self.assertContains(response, "The following objects will be merged")
for key, venue in self.venues.items():
self.assertContains(response, venue.name)
def test_merge_no_master(self):
change_url = reverse('admin:RIGS_venue_changelist')
data = {'action': 'merge',
'_selected_action': [str(val.pk) for key, val in self.venues.items()],
'post': 'yes',
}
response = self.client.post(change_url, data, follow=True)
self.assertContains(response, "An error occured")
def test_venue_merge(self):
change_url = reverse('admin:RIGS_venue_changelist')
data = {'action': 'merge',
'_selected_action': [str(self.venues[1].pk), str(self.venues[2].pk)],
'post': 'yes',
'master': self.venues[1].pk
}
response = self.client.post(change_url, data, follow=True)
self.assertContains(response, "Objects successfully merged")
self.assertContains(response, self.venues[1].name)
# Check the master copy still exists
self.assertTrue(models.Venue.objects.get(pk=self.venues[1].pk))
# Check the un-needed venue has been disposed of
self.assertRaises(ObjectDoesNotExist, models.Venue.objects.get, pk=self.venues[2].pk)
# Check the one we didn't delete is still there
self.assertEqual(models.Venue.objects.get(pk=self.venues[3].pk), self.venues[3])
# Check the events have been moved to the master venue
for key, event in self.events.items():
updatedEvent = models.Event.objects.get(pk=event.pk)
if event.venue == self.venues[3]: # The one we left in place
continue
self.assertEqual(updatedEvent.venue, self.venues[1])
def test_person_merge(self):
change_url = reverse('admin:RIGS_person_changelist')
data = {'action': 'merge',
'_selected_action': [str(self.persons[1].pk), str(self.persons[2].pk)],
'post': 'yes',
'master': self.persons[1].pk
}
response = self.client.post(change_url, data, follow=True)
self.assertContains(response, "Objects successfully merged")
self.assertContains(response, self.persons[1].name)
# Check the master copy still exists
self.assertTrue(models.Person.objects.get(pk=self.persons[1].pk))
# Check the un-needed people have been disposed of
self.assertRaises(ObjectDoesNotExist, models.Person.objects.get, pk=self.persons[2].pk)
# Check the one we didn't delete is still there
self.assertEqual(models.Person.objects.get(pk=self.persons[3].pk), self.persons[3])
# Check the events have been moved to the master person
for key, event in self.events.items():
updatedEvent = models.Event.objects.get(pk=event.pk)
if event.person == self.persons[3]: # The one we left in place
continue
self.assertEqual(updatedEvent.person, self.persons[1])
def test_organisation_merge(self):
change_url = reverse('admin:RIGS_organisation_changelist')
data = {'action': 'merge',
'_selected_action': [str(self.organisations[1].pk), str(self.organisations[2].pk)],
'post': 'yes',
'master': self.organisations[1].pk
}
response = self.client.post(change_url, data, follow=True)
self.assertContains(response, "Objects successfully merged")
self.assertContains(response, self.organisations[1].name)
# Check the master copy still exists
self.assertTrue(models.Organisation.objects.get(pk=self.organisations[1].pk))
# Check the un-needed organisations have been disposed of
self.assertRaises(ObjectDoesNotExist, models.Organisation.objects.get, pk=self.organisations[2].pk)
# Check the one we didn't delete is still there
self.assertEqual(models.Organisation.objects.get(pk=self.organisations[3].pk), self.organisations[3])
# Check the events have been moved to the master organisation
for key, event in self.events.items():
updatedEvent = models.Event.objects.get(pk=event.pk)
if event.organisation == self.organisations[3]: # The one we left in place
continue
self.assertEqual(updatedEvent.organisation, self.organisations[1])
class TestInvoiceDelete(TestCase):
@classmethod
def setUpTestData(cls):
cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True,
is_active=True, is_staff=True)
cls.vatrate = models.VatRate.objects.create(start_at='2014-03-05', rate=0.20, comment='test1')
cls.events = {
1: models.Event.objects.create(name="TE E1", start_date=date.today()),
2: models.Event.objects.create(name="TE E2", start_date=date.today())
}
cls.invoices = {
1: models.Invoice.objects.create(event=cls.events[1]),
2: models.Invoice.objects.create(event=cls.events[2])
}
cls.payments = {
1: models.Payment.objects.create(invoice=cls.invoices[1], date=date.today(), amount=12.34,
method=models.Payment.CASH)
}
def setUp(self):
self.profile.set_password('testuser')
self.profile.save()
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
def test_invoice_delete_allowed(self):
request_url = reverse('invoice_delete', kwargs={'pk': self.invoices[2].pk})
response = self.client.get(request_url, follow=True)
self.assertContains(response, "Are you sure")
# Check the invoice still exists
self.assertTrue(models.Invoice.objects.get(pk=self.invoices[2].pk))
# Actually delete it
self.client.post(request_url, follow=True)
# Check the invoice is deleted
self.assertRaises(ObjectDoesNotExist, models.Invoice.objects.get, pk=self.invoices[2].pk)
def test_invoice_delete_not_allowed(self):
request_url = reverse('invoice_delete', kwargs={'pk': self.invoices[1].pk})
response = self.client.get(request_url, follow=True)
self.assertContains(response, "To delete an invoice, delete the payments first.")
# Check the invoice still exists
self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk))
# Try to actually delete it
self.client.post(request_url, follow=True)
# Check this didn't work
self.assertTrue(models.Invoice.objects.get(pk=self.invoices[1].pk))
class TestPrintPaperwork(TestCase):
@classmethod
def setUpTestData(cls):
cls.profile = models.Profile.objects.create(username="testuser1", email="1@test.com", is_superuser=True,
is_active=True, is_staff=True)
cls.events = {
1: models.Event.objects.create(name="TE E1", start_date=date.today(),
description="This is an event description\nthat for a very specific reason spans two lines."),
}
cls.invoices = {
1: models.Invoice.objects.create(event=cls.events[1]),
}
def setUp(self):
self.profile.set_password('testuser')
self.profile.save()
self.assertTrue(self.client.login(username=self.profile.username, password='testuser'))
def test_print_paperwork_success(self):
request_url = reverse('event_print', kwargs={'pk': self.events[1].pk})
response = self.client.get(request_url, follow=True)
self.assertEqual(response.status_code, 200)
def test_print_invoice_success(self):
request_url = reverse('invoice_print', kwargs={'pk': self.invoices[1].pk})
response = self.client.get(request_url, follow=True)
self.assertEqual(response.status_code, 200)
def test_login_redirect(client, django_user_model):
request_url = reverse('event_embed', kwargs={'pk': 1})
expected_url = "{0}?next={1}".format(reverse('login_embed'), request_url)
# 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):
login_url = reverse('login_embed')
response = client.post(login_url, follow=True)
assertContains(response, "Cookies do not seem to be enabled")
def test_xframe_headers(admin_client, basic_event):
event_url = reverse('event_embed', kwargs={'pk': basic_event.pk})
login_url = reverse('login_embed')
response = admin_client.get(event_url, follow=True)
with pytest.raises(KeyError):
response._headers["X-Frame-Options"]
response = admin_client.get(login_url, follow=True)
with pytest.raises(KeyError):
response._headers["X-Frame-Options"]
def test_oembed(client, basic_event):
event_url = reverse('event_detail', kwargs={'pk': basic_event.pk})
event_embed_url = reverse('event_embed', kwargs={'pk': basic_event.pk})
oembed_url = reverse('event_oembed', kwargs={'pk': basic_event.pk})
alt_oembed_url = reverse('event_oembed', kwargs={'pk': 999})
alt_event_embed_url = reverse('event_embed', kwargs={'pk': 999})
assert_oembed(alt_event_embed_url, alt_oembed_url, client, event_embed_url, event_url, oembed_url)
def search(client, url, found, notfound, arguments):
for argument in arguments:
query = getattr(found, argument)
request_url = "%s?q=%s" % (reverse_lazy(url), query)
response = client.get(request_url, follow=True)
assertContains(response, getattr(found, 'name'))
assertNotContains(response, getattr(notfound, 'name'))
def test_search(admin_client):
persons = {
1: models.Person.objects.create(name="Right Person", phone="1234"),
2: models.Person.objects.create(name="Wrong Person", phone="5678"),
}
organisations = {
1: models.Organisation.objects.create(name="Right Organisation", email="test@example.com"),
2: models.Organisation.objects.create(name="Wrong Organisation", email="check@fake.co.uk"),
}
venues = {
1: models.Venue.objects.create(name="Right Venue", address="1 Test Street, EX1"),
2: models.Venue.objects.create(name="Wrong Venue", address="2 Check Way, TS2"),
}
events = {
1: models.Event.objects.create(name="Right Event", start_date=date.today(), venue=venues[1], person=persons[1],
organisation=organisations[1]),
2: models.Event.objects.create(name="Wrong Event", start_date=date.today(), venue=venues[2], person=persons[2],
organisation=organisations[2]),
}
search(admin_client, 'event_archive', events[1], events[2], ['name', 'id'])
search(admin_client, 'person_list', persons[1], persons[2], ['name', 'id', 'phone'])
search(admin_client, 'organisation_list', organisations[1], organisations[2],
['name', 'id', 'email'])
search(admin_client, 'venue_list', venues[1], venues[2],
['name', 'id', 'address'])
def test_hs_list(admin_client, basic_event):
request_url = reverse('hs_list')
response = admin_client.get(request_url, follow=True)
assertContains(response, basic_event.name)
# assertContains(response, events[2].name)
assertContains(response, 'Create')
def review(client, profile, obj, request_url):
time = timezone.now()
response = client.get(reverse(request_url, kwargs={'pk': obj.pk}), follow=True)
obj.refresh_from_db()
assertContains(response, 'Reviewed by')
assertContains(response, profile.name)
assert_times_almost_equal(time, obj.reviewed_at)
def test_ra_review(admin_client, admin_user, ra):
review(admin_client, admin_user, ra, 'ra_review')
def test_checklist_review(admin_client, admin_user, checklist):
review(admin_client, admin_user, checklist, 'ec_review')
def test_ra_redirect(admin_client, admin_user, ra):
request_url = reverse('event_ra', kwargs={'pk': ra.event.pk})
expected_url = reverse('ra_edit', kwargs={'pk': ra.pk})
response = admin_client.get(request_url, follow=True)
assertRedirects(response, expected_url, status_code=302, target_status_code=200)
class TestMarkdownTemplateTags(TestCase):
markdown = """
An h1 header
============
Paragraphs are separated by a blank line.
2nd paragraph. *Italic*, **bold**, and `monospace`. Itemized lists
look like:
* this one
* that one
* the other one
Note that --- not considering the asterisk --- the actual text
content starts at 4-columns in.
> Block quotes are
> written like so.
>
> They can span multiple paragraphs,
> if you like.
Use 3 dashes for an em-dash. Use 2 dashes for ranges (ex., "it's all
in chapters 12--14"). Three dots ... will be converted to an ellipsis.
Unicode is supported.
An h2 header
------------
Here's a numbered list:
1. first item
2. second item
3. third item
Note again how the actual text starts at 4 columns in (4 characters
from the left side). Here's a code sample:
# Let me re-iterate ...
for i in 1 .. 10 { do-something(i) }
As you probably guessed, indented 4 spaces. By the way, instead of
indenting the block, you can use delimited blocks, if you like:
~~~
define foobar() {
print "Welcome to flavor country!";
}
~~~
(which makes copying & pasting easier). You can optionally mark the
delimited block for Pandoc to syntax highlight it:
~~~python
import time
# Quick, count to ten!
for i in range(10):
# (but not *too* quick)
time.sleep(0.5)
print i
~~~
### An h3 header ###
Now a nested list:
1. First, get these ingredients:
* carrots
* celery
* lentils
2. Boil some water.
3. Dump everything in the pot and follow
this algorithm:
find wooden spoon
uncover pot
stir
cover pot
balance wooden spoon precariously on pot handle
wait 10 minutes
goto first step (or shut off burner when done)
Do not bump wooden spoon or it will fall.
Notice again how text always lines up on 4-space indents (including
that last line which continues item 3 above).
Here's a link to [a website](http://foo.bar). Here's a footnote [^1].
[^1]: Footnote text goes here.
Tables can look like this:
size material color
---- ------------ ------------
9 leather brown
10 hemp canvas natural
11 glass transparent
Table: Shoes, their sizes, and what they're made of
(The above is the caption for the table.) Pandoc also supports
multi-line tables:
-------- -----------------------
keyword text
-------- -----------------------
red Sunsets, apples, and
other red or reddish
things.
green Leaves, grass, frogs
and other things it's
not easy being.
-------- -----------------------
A horizontal rule follows.
***
Here's a definition list:
apples
: Good for making applesauce.
oranges
: Citrus!
tomatoes
: There's no "e" in tomatoe.
Again, text is indented 4 spaces. (Put a blank line between each
term/definition pair to spread things out more.)
Here's a "line block":
| Line one
| Line too
| Line tree
and images can be specified like so:
![example image](example-image.jpg "An exemplary image")
Inline math equations go in like so: $\omega = d\phi / dt$. Display
math should get its own line and be put in in double-dollarsigns:
$$I = \int \rho R^{2} dV$$
And note that you can backslash-escape any punctuation characters
which you wish to be displayed literally, ex.: \`foo\`, \*bar\*, etc.
"""
def test_html_safe(self):
html = markdown_filter(self.markdown)
self.assertIsInstance(html, SafeText)
def test_img_strip(self):
rml = markdown_filter(self.markdown, 'rml')
self.assertNotIn("<img", rml)
def test_code(self):
rml = markdown_filter(self.markdown, 'rml')
self.assertIn('<font face="Courier">monospace</font>', rml)
def test_blockquote(self):
rml = markdown_filter(self.markdown, 'rml')
self.assertIn("<pre>\nBlock quotes", rml)
def test_lists(self):
rml = markdown_filter(self.markdown, 'rml')
self.assertIn("<li><para>second item</para></li>", rml) # <ol>
self.assertIn("<li><para>that one</para></li>", rml) # <ul>
def test_in_print(self):
event = models.Event.objects.create(
name="MD Print Test",
description=self.markdown,
start_date='2016-01-01',
)
user = models.Profile.objects.create(
username='RML test',
is_superuser=True, # Don't care about permissions
is_active=True,
)
user.set_password('rmltester')
user.save()
self.assertTrue(self.client.login(username=user.username, password='rmltester'))
response = self.client.get(reverse('event_print', kwargs={'pk': event.pk}))
self.assertEqual(response.status_code, 200)
# By the time we have a PDF it should be larger than the original by some margin
# RML hard fails if something doesn't work
self.assertGreater(len(response.content), len(self.markdown))
def test_nonetype(self):
html = markdown_filter(None)
self.assertIsNone(html)
def test_linebreaks(self):
html = markdown_filter(self.markdown)
self.assertIn("Itemized lists<br />\nlook like", html)