diff --git a/.travis.yml b/.travis.yml index 958b7431e..17d468b54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,7 +4,7 @@ env: - MOZ_HEADLESS=1 addons: - firefox: "55.0" + firefox: "60.0" python: - "3.6.5" diff --git a/requirements.txt b/requirements.txt index 433f290a7..a57b0d0f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,7 @@ Django == 1.11.0 docopt == 0.6.2 flake8 == 3.4.1 psycopg2 == 2.7.3 +PyPDF2 == 1.26.0 PyYAML == 3.11 requests == 2.7.0 phonenumbers == 8.8.8 diff --git a/vms/pom/locators/volunteerProfilePageLocators.py b/vms/pom/locators/volunteerProfilePageLocators.py index 316e555b9..b98a0d47a 100644 --- a/vms/pom/locators/volunteerProfilePageLocators.py +++ b/vms/pom/locators/volunteerProfilePageLocators.py @@ -13,5 +13,5 @@ class VolunteerProfilePageLocators(object): RESUME_FILE = '//input[@name = "resume_file"]' DOWNLOAD_RESUME = './/*[@id="collapseResumeFile"]/div/form/button' EDIT_PROFILE_TEXT = 'Edit Profile' - INVALID_FORMAT_MESSAGE = 'html/body/div[2]/div[2]/form/fieldset/div[13]/div/p/strong' + INVALID_FORMAT_MESSAGE = '//fieldset[1]/div[14]//div[1]//p[1]//strong' SUBMIT_PATH = '//form[1]' diff --git a/vms/pom/pages/basePage.py b/vms/pom/pages/basePage.py index c4807aa06..6c01c11c0 100644 --- a/vms/pom/pages/basePage.py +++ b/vms/pom/pages/basePage.py @@ -2,6 +2,7 @@ class BasePage(object): """Base class to initialize the base page that will be called from all pages""" ENTER_VALID_VALUE = 'Enter a valid value.' FIELD_REQUIRED = 'This field is required.' + FIELD_CANNOT_LEFT_BLANK = 'This field cannot be blank.' START_BEFORE_END = 'Start date must be before the end date' def __init__(self, driver): diff --git a/vms/pom/pages/volunteerProfilePage.py b/vms/pom/pages/volunteerProfilePage.py index 14541ebd7..5125c6e6e 100644 --- a/vms/pom/pages/volunteerProfilePage.py +++ b/vms/pom/pages/volunteerProfilePage.py @@ -1,6 +1,6 @@ # local Django from pom.pages.basePage import BasePage -from pom.locators.volunteerProfilePageLocators import VolunteerProfilePageLocators +from pom.locators.volunteerProfilePageLocators import VolunteerProfilePageLocators from pom.pages.homePage import HomePage @@ -12,10 +12,11 @@ def __init__(self, driver): super(VolunteerProfilePage, self).__init__(driver) def navigate_to_profile(self): - self.home_page.get_volunteer_profile_link().send_keys("\n") + element = self.home_page.get_volunteer_profile_link() + self.execute_script('arguments[0].click();', element) def edit_profile(self): - self.find_link(self.elements.EDIT_PROFILE_TEXT).send_keys("\n") + self.find_link(self.elements.EDIT_PROFILE_TEXT).click() def fill_values(self, new_details): elements = self.elements diff --git a/vms/pom/pages/volunteerReportPage.py b/vms/pom/pages/volunteerReportPage.py index d10ced1d3..ccde1b156 100644 --- a/vms/pom/pages/volunteerReportPage.py +++ b/vms/pom/pages/volunteerReportPage.py @@ -3,7 +3,6 @@ # local Django from pom.pages.basePage import BasePage -from pom.pages.authenticationPage import AuthenticationPage from pom.locators.volunteerReportPageLocators import VolunteerReportPageLocators from pom.pages.homePage import HomePage @@ -15,25 +14,19 @@ class VolunteerReportPage(BasePage): def __init__(self, driver): self.driver = driver - self.authentication_page = AuthenticationPage(self.driver) self.home_page = HomePage(self.driver) self.elements = VolunteerReportPageLocators() super(VolunteerReportPage, self).__init__(driver) - def login_and_navigate_to_report_page(self): - self.authentication_page.server_url = self.live_server_url - self.authentication_page.login({ - 'username': 'volunteer', - 'password': 'volunteer' - }) - self.home_page.get_volunteer_report_link().send_keys("\n") + def navigate_to_report_page(self): + self.home_page.get_volunteer_report_link().click() def get_event_job_selectors(self): select1 = Select( self.element_by_xpath(self.elements.REPORT_EVENT_SELECTOR)) select2 = Select( self.element_by_xpath(self.elements.REPORT_JOB_SELECTOR)) - return (select1, select2) + return select1, select2 def fill_report_form(self, dates): self.element_by_xpath(self.elements.REPORT_START_DATE).clear() diff --git a/vms/pom/pages/volunteerSearchPage.py b/vms/pom/pages/volunteerSearchPage.py index 73dee9bd1..3a28e8d43 100644 --- a/vms/pom/pages/volunteerSearchPage.py +++ b/vms/pom/pages/volunteerSearchPage.py @@ -2,7 +2,7 @@ from pom.pages.basePage import BasePage # local Django -from pom.locators.volunteerSearchPageLocators import VolunteerSearchPageLocators +from pom.locators.volunteerSearchPageLocators import VolunteerSearchPageLocators from pom.pages.authenticationPage import AuthenticationPage from pom.pageUrls import PageUrls @@ -10,6 +10,7 @@ class VolunteerSearchPage(BasePage): volunteer_search_page = PageUrls.volunteer_search_page + live_server_url = '' def __init__(self, driver): self.driver = driver @@ -17,6 +18,9 @@ def __init__(self, driver): self.elements = VolunteerSearchPageLocators() super(VolunteerSearchPage, self).__init__(driver) + def navigate_to_volunteer_search_page(self): + self.get_page(self.live_server_url, self.volunteer_search_page) + def submit_form(self): self.element_by_class_name(self.elements.SUBMIT_PATH).click() @@ -41,7 +45,7 @@ def search_country_field(self, search_text): self.send_to_field(self.elements.COUNTRY_FIELD, search_text) def search_organization_field(self, search_text): - self.send_to_field(self.elements.ORG_FIELD, search_text) + self.element_by_id('select').send_keys(search_text) def get_help_block(self): return self.element_by_class_name(self.elements.HELP_BLOCK) @@ -50,7 +54,8 @@ def get_search_results(self): search_results = self.element_by_xpath(self.elements.RESULT_BODY) return search_results - def get_results_list(self, search_results): + @staticmethod + def get_results_list(search_results): result = [] for tr in search_results.find_elements_by_tag_name('tr'): diff --git a/vms/volunteer/models.py b/vms/volunteer/models.py index bc9949661..55201715a 100644 --- a/vms/volunteer/models.py +++ b/vms/volunteer/models.py @@ -1,100 +1,101 @@ -# Django -from django.contrib.auth.models import User -from django.core.validators import (RegexValidator, MaxValueValidator, - MinValueValidator) -from django.db import models - -# local Django -from organization.models import Organization - - -class Volunteer(models.Model): - id = models.AutoField(primary_key=True) - first_name = models.CharField( - max_length=30, - validators=[ - RegexValidator(r'^[(A-Z)|(a-z)|(\s)|(\-)]+$', ), - ], - ) - last_name = models.CharField( - max_length=30, - validators=[ - RegexValidator(r'^[(A-Z)|(a-z)|(\s)|(\-)]+$', ), - ], - ) - address = models.CharField( - max_length=75, - validators=[ - RegexValidator(r'^[(A-Z)|(a-z)|(0-9)|(\s)|(\-)|(\.)|(,)|(\:)]+$', ), - ], - ) - city = models.CharField( - max_length=75, - validators=[ - RegexValidator(r'^[(A-Z)|(a-z)|(\s)|(\-)]+$', ), - ], - ) - state = models.CharField( - max_length=50, - validators=[ - RegexValidator(r'^[(A-Z)|(a-z)|(\s)|(\-)]+$', ), - ], - ) - country = models.CharField( - max_length=75, - validators=[ - RegexValidator(r'^[(A-Z)|(a-z)|(\s)|(\-)]+$', ), - ], - ) - phone_number = models.CharField( - max_length=20, - validators=[ - RegexValidator( - r'^\s*(?:\+?(\d{1,3}))?([-. (]*(\d{3})[-. )]*)?((\d{3})[-. ]*(\d{2,4})(?:[-.x ]*(\d+))?)\s*$', - message="Please enter a valid phone number", - ), - ], - ) - unlisted_organization = models.CharField( - blank=True, - max_length=100, - validators=[ - RegexValidator(r'^[(A-Z)|(a-z)|(0-9)|(\s)|(\-)|(:)]+$', ), - ], - ) - # Organization to Volunteer is a one-to-many relationship - organization = models.ForeignKey(Organization, null=True) - # EmailField automatically checks if email address is a valid format - email = models.EmailField(max_length=45, unique=True) - websites = models.TextField( - blank=True, - validators=[ - RegexValidator( - r'^(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9]\.[^\s]{2,})+$', - ), - ], - ) - description = models.TextField( - blank=True, - validators=[ - RegexValidator(r'^[(A-Z)|(a-z)|(0-9)|(\s)|(\.)|(,)|(\-)|(!)]+$', ), - ], - ) - resume = models.TextField( - blank=True, - validators=[ - RegexValidator(r'^[(A-Z)|(a-z)|(0-9)|(\s)|(\.)|(,)|(\-)|(!)]+$', ), - ], - ) - # all resumes are stored in /srv/vms/resume/ - resume_file = models.FileField( - upload_to='vms/resume/', max_length=75, blank=True) - reminder_days = models.IntegerField( - default=1, - validators=[MaxValueValidator(50), - MinValueValidator(1)], - blank=True) - - user = models.OneToOneField(User) - def __str__(self): - return '{0} {1}'.format(self.first_name, self.last_name) +# Django +from django.contrib.auth.models import User +from django.core.validators import (RegexValidator, MaxValueValidator, + MinValueValidator) +from django.db import models + +# local Django +from organization.models import Organization + + +class Volunteer(models.Model): + id = models.AutoField(primary_key=True) + first_name = models.CharField( + max_length=30, + validators=[ + RegexValidator(r'^[(A-Z)|(a-z)|(\s)|(\-)]+$', ), + ], + ) + last_name = models.CharField( + max_length=30, + validators=[ + RegexValidator(r'^[(A-Z)|(a-z)|(\s)|(\-)]+$', ), + ], + ) + address = models.CharField( + max_length=75, + validators=[ + RegexValidator(r'^[(A-Z)|(a-z)|(0-9)|(\s)|(\-)|(\.)|(,)|(\:)]+$', ), + ], + ) + city = models.CharField( + max_length=75, + validators=[ + RegexValidator(r'^[(A-Z)|(a-z)|(\s)|(\-)]+$', ), + ], + ) + state = models.CharField( + max_length=50, + validators=[ + RegexValidator(r'^[(A-Z)|(a-z)|(\s)|(\-)]+$', ), + ], + ) + country = models.CharField( + max_length=75, + validators=[ + RegexValidator(r'^[(A-Z)|(a-z)|(\s)|(\-)]+$', ), + ], + ) + phone_number = models.CharField( + max_length=20, + validators=[ + RegexValidator( + r'^\s*(?:\+?(\d{1,3}))?([-. (]*(\d{3})[-. )]*)?((\d{3})[-. ]*(\d{2,4})(?:[-.x ]*(\d+))?)\s*$', + message="Please enter a valid phone number", + ), + ], + ) + unlisted_organization = models.CharField( + blank=True, + max_length=100, + validators=[ + RegexValidator(r'^[(A-Z)|(a-z)|(0-9)|(\s)|(\-)|(:)]+$', ), + ], + ) + # Organization to Volunteer is a one-to-many relationship + organization = models.ForeignKey(Organization, null=True) + # EmailField automatically checks if email address is a valid format + email = models.EmailField(max_length=45, unique=True) + websites = models.TextField( + blank=True, + validators=[ + RegexValidator( + r'^(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9]\.[^\s]{2,})+$', + ), + ], + ) + description = models.TextField( + blank=True, + validators=[ + RegexValidator(r'^[(A-Z)|(a-z)|(0-9)|(\s)|(\.)|(,)|(\-)|(!)]+$', ), + ], + ) + resume = models.TextField( + blank=True, + validators=[ + RegexValidator(r'^[(A-Z)|(a-z)|(0-9)|(\s)|(\.)|(,)|(\-)|(!)]+$', ), + ], + ) + # all resumes are stored in /srv/vms/resume/ + resume_file = models.FileField( + upload_to='vms/resume/', max_length=75, blank=True) + reminder_days = models.IntegerField( + default=1, + validators=[MaxValueValidator(50), + MinValueValidator(1)], + blank=True) + + user = models.OneToOneField(User) + + def __str__(self): + return '{0} {1}'.format(self.first_name, self.last_name) diff --git a/vms/volunteer/tests/test_searchVolunteer.py b/vms/volunteer/tests/test_searchVolunteer.py index a5fdd540a..304b1d8b1 100644 --- a/vms/volunteer/tests/test_searchVolunteer.py +++ b/vms/volunteer/tests/test_searchVolunteer.py @@ -1,6 +1,9 @@ # third party from selenium import webdriver from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.common.by import By # Django from django.contrib.staticfiles.testing import LiveServerTestCase @@ -8,14 +11,11 @@ # local Django from pom.pages.authenticationPage import AuthenticationPage from pom.pages.volunteerSearchPage import VolunteerSearchPage -from shift.utils import (create_admin, create_volunteer_with_details) +from shift.utils import (create_admin, create_volunteer_with_details, create_organization_with_details) -# Class contains failing test cases which have been documented -# Test class commented out to prevent travis build failure -""" class SearchVolunteer(LiveServerTestCase): - ''' + """ SearchVolunteer class contains tests to check '/voluneer/search/' view. Choices of parameters contains - First Name @@ -27,42 +27,76 @@ class SearchVolunteer(LiveServerTestCase): Class contains 7 tests to check each parameter separately and also to check if a combination of parameters entered, then intersection of all results is obtained. - ''' + """ @classmethod def setUpClass(cls): + """Method to initiate class level objects. + + This method initiates Firefox WebDriver, WebDriverWait and + the corresponding POM objects for this Test Class + """ cls.driver = webdriver.Firefox() cls.driver.implicitly_wait(5) cls.driver.maximize_window() cls.search_page = VolunteerSearchPage(cls.driver) cls.authentication_page = AuthenticationPage(cls.driver) + cls.wait = WebDriverWait(cls.driver, 10) super(SearchVolunteer, cls).setUpClass() def setUp(self): + """ + Method consists of statements to be executed before + start of each test. + """ create_admin() self.login_admin() - self.search_page.get_page(self.live_server_url, - self.search_page.volunteer_search_page) + self.wait_for_home_page() def tearDown(self): - pass + """ + Method consists of statements to be executed at + end of each test. + """ + self.authentication_page.logout() @classmethod def tearDownClass(cls): + """ + Class method to quit the Firefox WebDriver session after + execution of all tests in class. + """ cls.driver.quit() super(SearchVolunteer, cls).tearDownClass() def login_admin(self): - ''' + """ Utility function to login an admin user to perform all tests. - ''' + """ self.authentication_page.server_url = self.live_server_url self.authentication_page.login({ 'username': 'admin', 'password': 'admin' }) + def wait_for_home_page(self): + """ + Utility function to perform explicit wait for home page. + """ + self.wait.until( + EC.presence_of_element_located( + (By.XPATH, "//h1[contains(text(), 'Volunteer Management System')]") + ) + ) + def test_volunteer_first_name_field(self): + """ + Test volunteer search form using the first name of the volunteers. + """ + search_page = self.search_page + search_page.live_server_url = self.live_server_url + search_page.navigate_to_volunteer_search_page() + credentials_1 = [ 'volunteer-username', 'VOLUNTEER-FIRST-NAME', 'volunteer-last-name', 'volunteer-address', 'volunteer-city', @@ -70,7 +104,7 @@ def test_volunteer_first_name_field(self): 'volunteer-email@systers.org', 'volunteer-organization' ] - v1 = create_volunteer_with_details(credentials_1) + volunteer_1 = create_volunteer_with_details(credentials_1) credentials_2 = [ 'volunteer-usernameq', 'volunteer-first-name', @@ -79,12 +113,11 @@ def test_volunteer_first_name_field(self): 'volunteer-email2@systers.orgq', 'volunteer-organizationq' ] - v2 = create_volunteer_with_details(credentials_2) + volunteer_2 = create_volunteer_with_details(credentials_2) expected_result_one = credentials_1[1:-1] expected_result_two = credentials_2[1:-1] - search_page = self.search_page search_page.search_first_name_field('volunteer') search_page.submit_form() search_results = search_page.get_search_results() @@ -106,27 +139,36 @@ def test_volunteer_first_name_field(self): search_page.search_first_name_field('vol-') search_page.submit_form() - with self.assertRaises(NoSuchElementException): - search_results = search_page.get_search_results() + self.assertRaisesRegexp(NoSuchElementException, + 'Unable to locate element: //table//tbody', + search_page.get_search_results) search_page.search_first_name_field('volunteer-fail-test') search_page.submit_form() - with self.assertRaises(NoSuchElementException): - search_results = search_page.get_search_results() + self.assertRaisesRegexp(NoSuchElementException, + 'Unable to locate element: //table//tbody', + search_page.get_search_results) search_page.search_first_name_field('!@#$%^&*()_') search_page.submit_form() self.assertNotEqual(search_page.get_help_block(), None) def test_volunteer_last_name_field(self): + """ + Test volunteer search form using the last name of the volunteers. + """ + search_page = self.search_page + search_page.live_server_url = self.live_server_url + search_page.navigate_to_volunteer_search_page() + credentials_1 = [ 'volunteer-username', 'volunteer-first-name', 'VOLUNTEER-LAST-NAME', 'volunteer-address', 'volunteer-city', 'volunteer-state', 'volunteer-country', '9999999999', 'volunteer-email@systers.org', 'volunteer-organization' ] - v1 = create_volunteer_with_details(credentials_1) + volunteer_1 = create_volunteer_with_details(credentials_1) credentials_2 = [ 'volunteer-usernameq', 'volunteer-first-nameq', @@ -134,12 +176,11 @@ def test_volunteer_last_name_field(self): 'volunteer-stateq', 'volunteer-countryq', '9999999999', 'volunteer-email2@systers.orgq', 'volunteer-organizationq' ] - v2 = create_volunteer_with_details(credentials_2) + volunteer_2 = create_volunteer_with_details(credentials_2) expected_result_one = credentials_1[1:-1] expected_result_two = credentials_2[1:-1] - search_page = self.search_page search_page.search_last_name_field('volunteer') search_page.submit_form() @@ -161,20 +202,29 @@ def test_volunteer_last_name_field(self): search_page.search_last_name_field('vol-') search_page.submit_form() - with self.assertRaises(NoSuchElementException): - search_results = search_page.get_search_results() + self.assertRaisesRegexp(NoSuchElementException, + 'Unable to locate element: //table//tbody', + search_page.get_search_results) search_page.search_last_name_field('volunteer-fail-test') search_page.submit_form() - with self.assertRaises(NoSuchElementException): - search_results = search_page.get_search_results() + self.assertRaisesRegexp(NoSuchElementException, + 'Unable to locate element: //table//tbody', + search_page.get_search_results) search_page.search_last_name_field('!@#$%^&*()_') search_page.submit_form() self.assertNotEqual(search_page.get_help_block(), None) def test_volunteer_city_field(self): + """ + Test volunteer search form using the city field of the volunteers. + """ + search_page = self.search_page + search_page.live_server_url = self.live_server_url + search_page.navigate_to_volunteer_search_page() + credentials_1 = [ 'volunteer-username', 'volunteer-first-name', 'volunteer-last-name', 'volunteer-address', 'VOLUNTEER-CITY', @@ -182,7 +232,7 @@ def test_volunteer_city_field(self): 'volunteer-email@systers.org', 'volunteer-organization' ] - v1 = create_volunteer_with_details(credentials_1) + volunteer_1 = create_volunteer_with_details(credentials_1) credentials_2 = [ 'volunteer-usernameq', 'volunteer-first-nameq', @@ -191,9 +241,7 @@ def test_volunteer_city_field(self): 'volunteer-email2@systers.orgq', 'volunteer-organizationq' ] - v2 = create_volunteer_with_details(credentials_2) - - search_page = self.search_page + volunteer_2 = create_volunteer_with_details(credentials_2) expected_result_one = credentials_1[1:-1] expected_result_two = credentials_2[1:-1] @@ -219,20 +267,29 @@ def test_volunteer_city_field(self): search_page.search_city_field('vol-') search_page.submit_form() - with self.assertRaises(NoSuchElementException): - search_results = search_page.get_search_results() + self.assertRaisesRegexp(NoSuchElementException, + 'Unable to locate element: //table//tbody', + search_page.get_search_results) search_page.search_city_field('volunteer-fail-test') search_page.submit_form() - with self.assertRaises(NoSuchElementException): - search_results = search_page.get_search_results() + self.assertRaisesRegexp(NoSuchElementException, + 'Unable to locate element: //table//tbody', + search_page.get_search_results) search_page.search_city_field('!@#$%^&*()_') search_page.submit_form() self.assertNotEqual(search_page.get_help_block(), None) def test_volunteer_state_field(self): + """ + Test volunteer search form using the state field of the volunteers. + """ + search_page = self.search_page + search_page.live_server_url = self.live_server_url + search_page.navigate_to_volunteer_search_page() + credentials_1 = [ 'volunteer-username', 'volunteer-first-name', 'volunteer-last-name', 'volunteer-address', 'volunteer-city', @@ -240,7 +297,7 @@ def test_volunteer_state_field(self): 'volunteer-email@systers.org', 'volunteer-organization' ] - v1 = create_volunteer_with_details(credentials_1) + volunteer_1 = create_volunteer_with_details(credentials_1) credentials_2 = [ 'volunteer-usernameq', 'volunteer-first-nameq', @@ -249,9 +306,7 @@ def test_volunteer_state_field(self): 'volunteer-email2@systers.orgq', 'volunteer-organizationq' ] - v2 = create_volunteer_with_details(credentials_2) - - search_page = self.search_page + volunteer_2 = create_volunteer_with_details(credentials_2) expected_result_one = credentials_1[1:-1] expected_result_two = credentials_2[1:-1] @@ -277,20 +332,29 @@ def test_volunteer_state_field(self): search_page.search_state_field('vol-') search_page.submit_form() - with self.assertRaises(NoSuchElementException): - search_results = search_page.get_search_results() + self.assertRaisesRegexp(NoSuchElementException, + 'Unable to locate element: //table//tbody', + search_page.get_search_results) search_page.search_state_field('volunteer-fail-test') search_page.submit_form() - with self.assertRaises(NoSuchElementException): - search_results = search_page.get_search_results() + self.assertRaisesRegexp(NoSuchElementException, + 'Unable to locate element: //table//tbody', + search_page.get_search_results) search_page.search_state_field('!@#$%^&*()_') search_page.submit_form() self.assertNotEqual(search_page.get_help_block(), None) def test_volunteer_country_field(self): + """ + Test volunteer search form using the country field of the volunteers. + """ + search_page = self.search_page + search_page.live_server_url = self.live_server_url + search_page.navigate_to_volunteer_search_page() + credentials_1 = [ 'volunteer-username', 'volunteer-first-name', 'volunteer-last-name', 'volunteer-address', 'volunteer-city', @@ -298,7 +362,7 @@ def test_volunteer_country_field(self): 'volunteer-email@systers.org', 'volunteer-organization' ] - v1 = create_volunteer_with_details(credentials_1) + volunteer_1 = create_volunteer_with_details(credentials_1) credentials_2 = [ 'volunteer-usernameq', 'volunteer-first-nameq', @@ -307,9 +371,7 @@ def test_volunteer_country_field(self): 'volunteer-email2@systers.orgq', 'volunteer-organizationq' ] - v2 = create_volunteer_with_details(credentials_2) - - search_page = self.search_page + volunteer_2 = create_volunteer_with_details(credentials_2) expected_result_one = credentials_1[1:-1] expected_result_two = credentials_2[1:-1] @@ -335,20 +397,28 @@ def test_volunteer_country_field(self): search_page.search_country_field('vol-') search_page.submit_form() - with self.assertRaises(NoSuchElementException): - search_results = search_page.get_search_results() + self.assertRaisesRegexp(NoSuchElementException, + 'Unable to locate element: //table//tbody', + search_page.get_search_results) search_page.search_country_field('volunteer-fail-test') search_page.submit_form() - with self.assertRaises(NoSuchElementException): - search_results = search_page.get_search_results() + self.assertRaisesRegexp(NoSuchElementException, + 'Unable to locate element: //table//tbody', + search_page.get_search_results) search_page.search_country_field('!@#$%^&*()_') search_page.submit_form() self.assertNotEqual(search_page.get_help_block(), None) - def test_volunteer_organization_field(self): + def test_volunteer_valid_organization_field(self): + """ + Test volunteer search form using the organization field of the volunteers. + """ + search_page = self.search_page + search_page.live_server_url = self.live_server_url + credentials_1 = [ 'volunteer-username', 'volunteer-first-name', 'volunteer-last-name', 'volunteer-address', 'volunteer-city', @@ -356,7 +426,7 @@ def test_volunteer_organization_field(self): 'volunteer-email@systers.org' ] - v1 = create_volunteer_with_details(credentials_1) + volunteer_1 = create_volunteer_with_details(credentials_1) credentials_2 = [ 'volunteer-usernameq', 'volunteer-first-nameq', @@ -365,14 +435,14 @@ def test_volunteer_organization_field(self): 'volunteer-email2@systers.orgq' ] - v2 = create_volunteer_with_details(credentials_2) + volunteer_2 = create_volunteer_with_details(credentials_2) - v2.unlisted_organization = "volunteer-organization" - v1.unlisted_organization = "VOLUNTEER-ORGANIZATION" - v1.save() - v2.save() - - search_page = self.search_page + create_organization_with_details('volunteer-organization') + create_organization_with_details('VOLUNTEER-ORGANIZATION') + volunteer_2.unlisted_organization = "volunteer-organization" + volunteer_1.unlisted_organization = "VOLUNTEER-ORGANIZATION" + volunteer_1.save() + volunteer_2.save() expected_result_one = [ 'volunteer-first-nameq', 'volunteer-last-nameq', @@ -388,6 +458,7 @@ def test_volunteer_organization_field(self): 'volunteer-email@systers.org' ] + search_page.navigate_to_volunteer_search_page() search_page.search_organization_field('volunteer') search_page.submit_form() search_results = search_page.get_search_results() @@ -397,6 +468,7 @@ def test_volunteer_organization_field(self): self.assertTrue(expected_result_one in result) self.assertTrue(expected_result_two in result) + search_page.navigate_to_volunteer_search_page() search_page.search_organization_field('v') search_page.submit_form() search_results = search_page.get_search_results() @@ -406,23 +478,13 @@ def test_volunteer_organization_field(self): self.assertTrue(expected_result_one in result) self.assertTrue(expected_result_two in result) - search_page.search_organization_field('vol-') - search_page.submit_form() - - with self.assertRaises(NoSuchElementException): - search_results = search_page.get_search_results() - - search_page.search_organization_field('volunteer-fail-test') - search_page.submit_form() - - with self.assertRaises(NoSuchElementException): - search_results = search_page.get_search_results() - - search_page.search_organization_field('!@#$%^&*()_') - search_page.submit_form() - self.assertNotEqual(search_page.get_help_block(), None) - def test_intersection_of_all_fields(self): + """ + Test volunteer search form using multiple fields of the volunteers. + """ + search_page = self.search_page + search_page.live_server_url = self.live_server_url + credentials_1 = [ 'volunteer-username', 'volunteer-first-name', 'volunteer-last-name', 'volunteer-address', 'volunteer-city', @@ -430,7 +492,7 @@ def test_intersection_of_all_fields(self): 'volunteer-email@systers.org' ] - v1 = create_volunteer_with_details(credentials_1) + volunteer_1 = create_volunteer_with_details(credentials_1) credentials_2 = [ 'volunteer-usernameq', 'volunteer-first-nameq', @@ -439,14 +501,14 @@ def test_intersection_of_all_fields(self): 'volunteer-email2@systers.orgq' ] - v2 = create_volunteer_with_details(credentials_2) + volunteer_2 = create_volunteer_with_details(credentials_2) - v2.unlisted_organization = "volunteer-organization" - v1.unlisted_organization = "VOLUNTEER-ORGANIZATION" - v1.save() - v2.save() + volunteer_2.unlisted_organization = "volunteer-organization" + volunteer_1.unlisted_organization = "VOLUNTEER-ORGANIZATION" + volunteer_1.save() + volunteer_2.save() - search_page = self.search_page + search_page.navigate_to_volunteer_search_page() search_page.search_first_name_field('volunteer') search_page.search_last_name_field('volunteer') @@ -481,13 +543,16 @@ def test_intersection_of_all_fields(self): search_page.search_organization_field('org') search_page.submit_form() - with self.assertRaises(NoSuchElementException): - search_results = search_page.get_search_results() + self.assertRaisesRegexp(NoSuchElementException, + 'Unable to locate element: //table//tbody', + search_page.get_search_results) search_page.search_last_name_field('volunteer') search_page.search_city_field('wrong-city') search_page.submit_form() - with self.assertRaises(NoSuchElementException): - search_results = search_page.get_search_results() -""" + self.assertRaisesRegexp(NoSuchElementException, + 'Unable to locate element: //table//tbody', + search_page.get_search_results) + + diff --git a/vms/volunteer/tests/test_services.py b/vms/volunteer/tests/test_services.py index 0d47adc69..ea761ee7a 100644 --- a/vms/volunteer/tests/test_services.py +++ b/vms/volunteer/tests/test_services.py @@ -4,7 +4,6 @@ # local Django from organization.models import Organization from shift.utils import create_volunteer_with_details, clear_objects -from volunteer.models import Volunteer from volunteer.services import ( delete_volunteer, delete_volunteer_resume, get_all_volunteers, get_volunteer_by_id, get_volunteer_resume_file_url, diff --git a/vms/volunteer/tests/test_unit.py b/vms/volunteer/tests/test_unit.py index 353d0ccf9..5c9fc513e 100644 --- a/vms/volunteer/tests/test_unit.py +++ b/vms/volunteer/tests/test_unit.py @@ -1,3 +1,181 @@ +# third party + # Django -from django.test import TestCase -# Add tests here \ No newline at end of file +from django.core.exceptions import ValidationError +from django.db.models import Q +from django.test.testcases import TestCase + +# local Django +from pom.pages.basePage import BasePage +from shift.utils import create_volunteer_with_details +from volunteer.models import Volunteer + + +class VolunteerModelTests(TestCase): + """ + Contains database tests for + - volunteer create with valid and invalid values. + - volunteer edit with valid and invalid values. + - volunteer delete. + - volunteer model representation. + """ + + def setUp(self): + """ + Method consists of statements to be executed before + start of each test. + """ + pass + + def tearDown(self): + """ + Method consists of statements to be executed at + end of each test. + """ + pass + + @staticmethod + def create_valid_vol(): + """ + Utility function to create a valid volunteer. + :return: Volunteer type object + """ + vol = [ + 'Goku', "Son", "Goku", "Kame House", "East District", + "East District", "East District", "9999999999", "idonthave@gmail.com" + ] + return create_volunteer_with_details(vol) + + @staticmethod + def create_invalid_vol(): + """ + Utility function to create an invalid volunteer. + :return: Volunteer type object + """ + vol = [ + 'Goku~', "Son", "Goku", "Kame House", "East District", + "East District", "East District", "9999999999", "idonthave@gmail.com" + ] + return create_volunteer_with_details(vol) + + def test_valid_model_create(self): + """ + Database test for model creation with valid values. + """ + valid_volunteer = VolunteerModelTests.create_valid_vol() + + # Check DB for volunteer create. + self.assertEqual(len(Volunteer.objects.all()), 1) + + volunteer_in_db = Volunteer.objects.get(Q(first_name='Son')) + # Verify correctness. + self.assertEqual(valid_volunteer.first_name, volunteer_in_db.first_name) + self.assertEqual(valid_volunteer.last_name, volunteer_in_db.last_name) + self.assertEqual(valid_volunteer.email, volunteer_in_db.email) + self.assertEqual(valid_volunteer.phone_number, volunteer_in_db.phone_number) + + def test_invalid_model_create(self): + """ + Database test for model creation with invalid values. + """ + invalid_volunteer = VolunteerModelTests.create_invalid_vol() + self.assertRaisesRegexp(ValidationError, BasePage.FIELD_CANNOT_LEFT_BLANK, invalid_volunteer.full_clean) + + def test_model_edit_with_valid_values(self): + """ + Database test for model edit with valid values. + """ + valid_volunteer = VolunteerModelTests.create_valid_vol() + + # Check DB for volunteer create. + self.assertEqual(len(Volunteer.objects.all()), 1) + + volunteer_in_db = Volunteer.objects.get(Q(first_name='Son')) + # Verify correctness. + self.assertEqual(valid_volunteer.first_name, volunteer_in_db.first_name) + self.assertEqual(valid_volunteer.last_name, volunteer_in_db.last_name) + self.assertEqual(valid_volunteer.email, volunteer_in_db.email) + self.assertEqual(valid_volunteer.phone_number, volunteer_in_db.phone_number) + + valid_volunteer.first_name = 'Prince' + valid_volunteer.last_name = 'Vegeta' + valid_volunteer.email = 'iwishihadone@gmail.com' + valid_volunteer.phone_number = '1234567890' + valid_volunteer.save() + + # Check single instance + self.assertEqual(len(Volunteer.objects.all()), 1) + + volunteer_in_db = Volunteer.objects.get(Q(first_name='Prince')) + # Verify correctness. + self.assertEqual(valid_volunteer.first_name, volunteer_in_db.first_name) + self.assertEqual(valid_volunteer.last_name, volunteer_in_db.last_name) + self.assertEqual(valid_volunteer.email, volunteer_in_db.email) + self.assertEqual(valid_volunteer.phone_number, volunteer_in_db.phone_number) + + def test_model_edit_with_invalid_values(self): + """ + Database test for model edit with invalid values. + """ + valid_volunteer = VolunteerModelTests.create_valid_vol() + + # Check DB for volunteer create. + self.assertEqual(len(Volunteer.objects.all()), 1) + + volunteer_in_db = Volunteer.objects.get(Q(first_name='Son')) + # Verify correctness. + self.assertEqual(valid_volunteer.first_name, volunteer_in_db.first_name) + self.assertEqual(valid_volunteer.last_name, volunteer_in_db.last_name) + self.assertEqual(valid_volunteer.email, volunteer_in_db.email) + self.assertEqual(valid_volunteer.phone_number, volunteer_in_db.phone_number) + + valid_volunteer.first_name = 'Prince' + valid_volunteer.last_name = 'Vegeta' + valid_volunteer.email = 'iwishihadone@gmail.com' + valid_volunteer.phone_number = '1234567890' + + # Check save isn't working. + self.assertRaisesRegexp(ValidationError, BasePage.FIELD_CANNOT_LEFT_BLANK, valid_volunteer.full_clean) + # Check single instance + self.assertEqual(len(Volunteer.objects.all()), 1) + + def test_model_delete(self): + """ + Database test for model deletion. + """ + valid_volunteer = VolunteerModelTests.create_valid_vol() + + # Check DB for volunteer create. + self.assertEqual(len(Volunteer.objects.all()), 1) + + volunteer_in_db = Volunteer.objects.get(Q(first_name='Son')) + # Verify correctness. + self.assertEqual(valid_volunteer.first_name, volunteer_in_db.first_name) + self.assertEqual(valid_volunteer.last_name, volunteer_in_db.last_name) + self.assertEqual(valid_volunteer.email, volunteer_in_db.email) + self.assertEqual(valid_volunteer.phone_number, volunteer_in_db.phone_number) + + volunteer_in_db.delete() + + # Check 0 instance + self.assertEqual(len(Volunteer.objects.all()), 0) + + def test_model_representation(self): + """ + Database test for model representation. + """ + valid_volunteer = VolunteerModelTests.create_valid_vol() + + # Check DB for volunteer create. + self.assertEqual(len(Volunteer.objects.all()), 1) + + volunteer_in_db = Volunteer.objects.get(Q(first_name='Son')) + # Verify correctness. + self.assertEqual(valid_volunteer.first_name, volunteer_in_db.first_name) + self.assertEqual(valid_volunteer.last_name, volunteer_in_db.last_name) + self.assertEqual(valid_volunteer.email, volunteer_in_db.email) + self.assertEqual(valid_volunteer.phone_number, volunteer_in_db.phone_number) + + # Check representation + self.assertEqual(str(volunteer_in_db), 'Son Goku') + diff --git a/vms/volunteer/tests/test_volunteerProfile.py b/vms/volunteer/tests/test_volunteerProfile.py index 4fba93fb1..150f626e2 100644 --- a/vms/volunteer/tests/test_volunteerProfile.py +++ b/vms/volunteer/tests/test_volunteerProfile.py @@ -1,9 +1,16 @@ # standard library import re +from urllib.request import urlretrieve +import os + +import PyPDF2 +from PyPDF2.utils import PdfReadError # third party from selenium import webdriver -from selenium.common.exceptions import NoSuchElementException +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.common.by import By # Django from django.contrib.staticfiles.testing import LiveServerTestCase @@ -11,72 +18,138 @@ # local Django from pom.pages.authenticationPage import AuthenticationPage from pom.pages.volunteerProfilePage import VolunteerProfilePage -from volunteer.models import Volunteer from shift.utils import create_volunteer_with_details -# Class contains failing test cases which have been documented -# Test class commented out to prevent travis build failure -""" + class VolunteerProfile(LiveServerTestCase): - ''' - ''' + """ + Contains tests for: + - Details of volunteer on profile. + - Edit volunteer profile. + - Upload of invalid resume format. + - Upload of valid resume format. + - Checks for resume corrupt uploaded in profile. + """ @classmethod def setUpClass(cls): + """Method to initiate class level objects. + + This method initiates Firefox WebDriver, WebDriverWait and + the corresponding POM objects for this Test Class + """ cls.driver = webdriver.Firefox() cls.driver.implicitly_wait(5) cls.driver.maximize_window() cls.profile_page = VolunteerProfilePage(cls.driver) cls.authentication_page = AuthenticationPage(cls.driver) + cls.wait = WebDriverWait(cls.driver, 20) + cls.download_from_internet() super(VolunteerProfile, cls).setUpClass() def setUp(self): + """ + Method consists of statements to be executed before + start of each test. + """ vol = [ - 'Sherlock', "Sherlock", "Holmes", "221-B Baker Street", "London", - "London-State", "UK", "9999999999", "idonthave@gmail.com" + 'Goku', "Son", "Goku", "Kame House", "East District", + "East District", "East District", "9999999999", "idonthave@gmail.com" ] - self.v1 = create_volunteer_with_details(vol) - self.v1.unlisted_organization = 'Detective' - self.v1.save() + self.volunteer_1 = create_volunteer_with_details(vol) + self.volunteer_1.unlisted_organization = 'Detective' + self.volunteer_1.save() self.login_correctly() - self.profile_page.navigate_to_profile() def tearDown(self): - pass + """ + Method consists of statements to be executed at + end of each test. + """ + self.authentication_page.logout() @classmethod def tearDownClass(cls): + """ + Class method to quit the Firefox WebDriver session after + execution of all tests in class. + """ cls.driver.quit() + os.remove(os.getcwd() + '/DummyResume.pdf') + os.remove(os.getcwd() + '/DummyZip.zip') super(VolunteerProfile, cls).tearDownClass() def login_correctly(self): + """ + Utility function to login as volunteer user to perform all tests. + """ self.authentication_page.server_url = self.live_server_url self.authentication_page.login({ - 'username': "Sherlock", + 'username': "Goku", 'password': "volunteer" }) + def wait_for_profile_load(self, profile_name): + """ + Utility function to perform explicit wait for volunteer profile page. + """ + self.wait.until( + EC.presence_of_element_located( + (By.XPATH, "//h1[contains(text(), '" + profile_name + "')]") + ) + ) + + def wait_for_home_page(self): + """ + Utility function to perform explicit wait for home page. + """ + self.wait.until( + EC.presence_of_element_located( + (By.XPATH, "//h1[contains(text(), 'Volunteer Management System')]") + ) + ) + + @staticmethod + def download_from_internet(): + """ + Utility functions to download dummy resume uploaded to dropbox. + """ + urlretrieve('https://dl.dropboxusercontent.com/s/08wpfj4n9f9jdnk/DummyResume.pdf', + 'DummyResume.pdf') + urlretrieve('https://dl.dropboxusercontent.com/s/uydlhww0ekdy6j7/DummyZip.zip', + 'DummyZip.zip') + def test_details_tab(self): + """ + Test volunteer details on profile page. + """ profile_page = self.profile_page + profile_page.navigate_to_profile() + self.wait_for_profile_load('Son Goku') page_source = self.driver.page_source - found_email = re.search(self.v1.email, page_source) + found_email = re.search(self.volunteer_1.email, page_source) self.assertNotEqual(found_email, None) - found_city = re.search(self.v1.city, page_source) + found_city = re.search(self.volunteer_1.city, page_source) self.assertNotEqual(found_city, None) - found_state = re.search(self.v1.state, page_source) + found_state = re.search(self.volunteer_1.state, page_source) self.assertNotEqual(found_state, None) - found_country = re.search(self.v1.country, page_source) + found_country = re.search(self.volunteer_1.country, page_source) self.assertNotEqual(found_country, None) - found_org = re.search(self.v1.unlisted_organization, page_source) + found_org = re.search(self.volunteer_1.unlisted_organization, page_source) self.assertNotEqual(found_org, None) def test_edit_profile(self): + """ + Test profile edit in volunteer profile. + """ profile_page = self.profile_page + profile_page.navigate_to_profile() + self.wait_for_profile_load('Son Goku') profile_page.edit_profile() new_details = [ @@ -84,22 +157,23 @@ def test_edit_profile(self): 'NYC', 'New York', 'USA', '9999999998', 'None', 'Lawyer' ] profile_page.fill_values(new_details) + self.wait_for_profile_load('Harvey Specter') page_source = self.driver.page_source - found_email = re.search(self.v1.email, page_source) + found_email = re.search(self.volunteer_1.email, page_source) self.assertEqual(found_email, None) - found_city = re.search(self.v1.city, page_source) + found_city = re.search(self.volunteer_1.city, page_source) self.assertEqual(found_city, None) - found_state = re.search(self.v1.state, page_source) + found_state = re.search(self.volunteer_1.state, page_source) self.assertEqual(found_state, None) - found_country = re.search(self.v1.country, page_source) + found_country = re.search(self.volunteer_1.country, page_source) self.assertEqual(found_country, None) - found_org = re.search(self.v1.unlisted_organization, page_source) + found_org = re.search(self.volunteer_1.unlisted_organization, page_source) self.assertEqual(found_org, None) found_email = re.search(new_details[2], page_source) @@ -117,40 +191,89 @@ def test_edit_profile(self): found_org = re.search(new_details[9], page_source) self.assertNotEqual(found_org, None) - # database check to ensure that profile has been updated - self.assertEqual(len(Volunteer.objects.all()), 1) - self.assertNotEqual( - len( - Volunteer.objects.filter( - first_name=new_details[0], - last_name=new_details[1], - email=new_details[2], - address=new_details[3], - city=new_details[4], - state=new_details[5], - country=new_details[6], - phone_number=new_details[7])), 0) - - def test_upload_resume(self): - pass - ''' - #Tested locally + def test_invalid_resume_format(self): + """ + Test upload of invalid resume to profile. + """ + self.wait_for_home_page() + + path = os.getcwd() + '/DummyZip.zip' profile_page = self.profile_page + profile_page.navigate_to_profile() + self.wait_for_profile_load('Son Goku') profile_page.edit_profile() - profile_page.upload_resume('/home/jlahori/Downloads/water.pdf') + self.wait.until( + EC.presence_of_element_located( + (By.XPATH, "//legend[contains(text(), 'Edit Profile')]") + ) + ) + + profile_page.upload_resume(path) profile_page.submit_form() - self.assertEqual(profile_page.download_resume_text(),'Download Resume') - ''' + self.assertEqual(profile_page.get_invalid_format_error(), 'Uploaded file is invalid.') - def test_invalid_resume_format(self): - pass - ''' - #Tested locally +# Resume Upload is buggy, it is taking too long to be uploaded on travis. +# https://github.com/systers/vms/issues/776 + + +''' + def test_valid_upload_resume(self): + """ + Test upload of valid resume to profile. + """ + self.wait_for_home_page() + + path = os.getcwd() + '/DummyResume.pdf' + profile_page = self.profile_page + profile_page.navigate_to_profile() + self.wait_for_profile_load('Son Goku') + profile_page.edit_profile() + self.wait.until( + EC.presence_of_element_located( + (By.XPATH, "//legend[contains(text(), 'Edit Profile')]") + ) + ) + self.assertEqual(os.path.exists(path), True) + + profile_page.upload_resume(path) + profile_page.submit_form() + self.wait_for_profile_load('Son Goku') + self.assertEqual(profile_page.download_resume_text(), 'Download Resume') + + def test_corrupt_resume_uploaded(self): + """ + Test uploaded resume is not corrupt by performing a few checks on it. + """ + self.wait_for_home_page() + path = os.getcwd() + '/DummyResume.pdf' + size_before_upload = os.stat(path).st_size profile_page = self.profile_page + profile_page.navigate_to_profile() + self.wait_for_profile_load('Son Goku') profile_page.edit_profile() - profile_page.upload_resume('/home/jlahori/Downloads/ca.crt') + self.wait.until( + EC.presence_of_element_located( + (By.XPATH, "//legend[contains(text(), 'Edit Profile')]") + ) + ) + self.assertEqual(os.path.exists(path), True) + + profile_page.upload_resume(path) profile_page.submit_form() - self.assertEqual(profile_page.get_invalid_format_error(),'Uploaded file is invalid.') - ''' - """ + self.wait_for_profile_load('Son Goku') + self.assertEqual(profile_page.download_resume_text(), 'Download Resume') + path = os.getcwd() + '/srv/vms/resume/DummyResume.pdf' + size_after_upload = os.stat(path).st_size + + # Check via size + self.assertEqual(size_after_upload, size_before_upload) + + # Check via open + try: + PyPDF2.PdfFileReader(open(path, 'rb')) + except PdfReadError: + print('Some error while upload/download') + else: + pass +''' diff --git a/vms/volunteer/tests/test_volunteerReport.py b/vms/volunteer/tests/test_volunteerReport.py index 6e305ef01..f5d590455 100644 --- a/vms/volunteer/tests/test_volunteerReport.py +++ b/vms/volunteer/tests/test_volunteerReport.py @@ -1,62 +1,100 @@ # third party from selenium import webdriver -from selenium.common.exceptions import NoSuchElementException # Django from django.contrib.staticfiles.testing import LiveServerTestCase # local Django +from pom.pages.authenticationPage import AuthenticationPage from pom.pages.volunteerReportPage import VolunteerReportPage from shift.utils import (create_volunteer, register_event_utility, register_job_utility, register_shift_utility, log_hours_utility) -# Class contains failing test cases which have been documented -# Test class commented out to prevent travis build failure -""" + class VolunteerReport(LiveServerTestCase): - ''' - ''' + """ + Contains Tests for + - Report generation with no shifts. + - Report generation with data filled + - Report generation with data empty + - Only shift with logged hours are shown + - Report details verified against the filled details. + """ @classmethod def setUpClass(cls): + """Method to initiate class level objects. + + This method initiates Firefox WebDriver, WebDriverWait and + the corresponding POM objects for this Test Class + """ cls.driver = webdriver.Firefox() cls.driver.implicitly_wait(5) cls.driver.maximize_window() cls.report_page = VolunteerReportPage(cls.driver) + cls.authentication_page = AuthenticationPage(cls.driver) super(VolunteerReport, cls).setUpClass() def setUp(self): + """ + Method consists of statements to be executed before + start of each test. + """ create_volunteer() + self.login() def tearDown(self): - pass + """ + Method consists of statements to be executed at + end of each test. + """ + self.authentication_page.logout() @classmethod def tearDownClass(cls): + """ + Class method to quit the Firefox WebDriver session after + execution of all tests in class. + """ cls.driver.quit() super(VolunteerReport, cls).tearDownClass() def verify_shift_details(self, total_shifts, hours): - total_no_of_shifts = self.report_page.get_shift_summary().split(' ')[ - 10].strip('\nTotal') - total_no_of_hours = self.report_page.get_shift_summary().split(' ')[ - -1].strip('\n') + """ + Utility function to verify the shift details. + :param total_shifts: Total number of shifts as filled in form. + :param hours: Total number of hours as filled in form. + """ + total_no_of_shifts = self.report_page.get_shift_summary().split(' ')[10].strip('\nTotal') + total_no_of_hours = self.report_page.get_shift_summary().split(' ')[-1].strip('\n') self.assertEqual(total_no_of_shifts, total_shifts) self.assertEqual(total_no_of_hours, hours) + def login(self): + """ + Utility function to login as volunteer with correct credentials. + """ + self.authentication_page.server_url = self.live_server_url + self.authentication_page.login({ + 'username': 'volunteer', + 'password': 'volunteer' + }) + def test_report_without_any_created_shifts(self): + """ + Test report generation with no shifts performed by volunteer. + """ report_page = self.report_page report_page.live_server_url = self.live_server_url - report_page.login_and_navigate_to_report_page() + report_page.navigate_to_report_page() report_page.submit_form() - self.assertEqual(report_page.get_alert_box_text(), - report_page.no_results_message) - -# Failing test case which has been documented as per bug #327 -# Test commented out to prevent travis build failure + self.assertEqual(report_page.get_alert_box_text(), report_page.no_results_message) def test_report_with_empty_fields(self): + """ + Test report generation with no fields filled in form. + """ report_page = self.report_page report_page.live_server_url = self.live_server_url register_event_utility() @@ -64,11 +102,14 @@ def test_report_with_empty_fields(self): register_shift_utility() log_hours_utility() - report_page.login_and_navigate_to_report_page() + report_page.navigate_to_report_page() report_page.submit_form() - self.verify_shift_details('1','3.0') + self.verify_shift_details('1', '3.0') def test_only_logged_shifts_appear_in_report(self): + """ + Test only shifts with logged hours is shown in report. + """ report_page = self.report_page report_page.live_server_url = self.live_server_url @@ -76,16 +117,15 @@ def test_only_logged_shifts_appear_in_report(self): register_job_utility() register_shift_utility() - report_page.login_and_navigate_to_report_page() + report_page.navigate_to_report_page() report_page.submit_form() self.assertEqual(report_page.get_alert_box_text(), report_page.no_results_message) - -# Failing test cases which have been documented -# Tests commented out to prevent travis build failure - def test_date_field(self): + """ + Test report generation using date field. + """ report_page = self.report_page report_page.live_server_url = self.live_server_url @@ -94,15 +134,24 @@ def test_date_field(self): register_shift_utility() log_hours_utility() - report_page.login_and_navigate_to_report_page(self.live_server_url) - report_page.fill_report_form({ 'start' : '2015-06-11', 'end' : '2017-06-16'}) - self.verify_shift_details('1','3.0') + report_page.navigate_to_report_page() + report_page.fill_report_form({ + 'start': '2048-06-11', + 'end': '2050-06-16' + }) + self.verify_shift_details('1', '3.0') - #incorrect date - report_page.fill_report_form({ 'start' : '2015-05-10', 'end' : '2015-06-01'}) - self.assertEqual(report_page.get_alert_box_text(),report_page.no_results_message) + # Incorrect date + report_page.fill_report_form({ + 'start': '2050-05-10', + 'end': '2050-06-01' + }) + self.assertEqual(report_page.get_alert_box_text(), report_page.no_results_message) def test_event_field(self): + """ + Test event generation using event field. + """ report_page = self.report_page report_page.live_server_url = self.live_server_url @@ -111,14 +160,17 @@ def test_event_field(self): register_shift_utility() log_hours_utility() - report_page.login_and_navigate_to_report_page() + report_page.navigate_to_report_page() [select1, select2] = report_page.get_event_job_selectors() select1.select_by_visible_text('event') report_page.submit_form() - self.verify_shift_details('1','3.0') + self.verify_shift_details('1', '3.0') def test_job_field(self): + """ + Test event generation using job field. + """ report_page = self.report_page report_page.live_server_url = self.live_server_url @@ -127,13 +179,16 @@ def test_job_field(self): register_shift_utility() log_hours_utility() - report_page.login_and_navigate_to_report_page() + report_page.navigate_to_report_page() [select1, select2] = report_page.get_event_job_selectors() select2.select_by_visible_text('job') report_page.submit_form() - self.verify_shift_details('1','3.0') + self.verify_shift_details('1', '3.0') def test_intersection_of_fields(self): + """ + Test event generation using multiple fields at a time. + """ report_page = self.report_page report_page.live_server_url = self.live_server_url @@ -142,17 +197,22 @@ def test_intersection_of_fields(self): register_shift_utility() log_hours_utility() - report_page.login_and_navigate_to_report_page() + report_page.navigate_to_report_page() [select1, select2] = report_page.get_event_job_selectors() select1.select_by_visible_text('event') select2.select_by_visible_text('job') - report_page.fill_report_form({ 'start' : '2015-06-11', 'end' : '2017-06-16'}) - self.verify_shift_details('1','3.0') + report_page.fill_report_form({ + 'start': '2048-06-11', + 'end': '2050-06-16'}) + self.verify_shift_details('1', '3.0') - # event, job correct and date incorrect + # Event, Job correct and date incorrect [select1, select2] = report_page.get_event_job_selectors() select1.select_by_visible_text('event') select2.select_by_visible_text('job') - report_page.fill_report_form({ 'start' : '2015-05-10', 'end' : '2015-06-01'}) - self.assertEqual(report_page.get_alert_box_text(),report_page.no_results_message) -""" + report_page.fill_report_form({ + 'start': '2050-05-10', + 'end': '2050-06-01' + }) + self.assertEqual(report_page.get_alert_box_text(), report_page.no_results_message) + diff --git a/vms/volunteer/views.py b/vms/volunteer/views.py index f76633fd4..c01ee0b2b 100644 --- a/vms/volunteer/views.py +++ b/vms/volunteer/views.py @@ -1,256 +1,256 @@ -# standard library -import os - -# third party -from braces.views import LoginRequiredMixin - -# Django -from django.conf import settings -# from django.contrib.auth.models import User -from django.contrib.auth.decorators import login_required -from wsgiref.util import FileWrapper -from django.core.urlresolvers import reverse, reverse_lazy -from django.http import Http404, HttpResponse, HttpResponseRedirect -from django.shortcuts import render -from django.views.generic.detail import DetailView -from django.views.generic import ListView, View -from django.views.generic.edit import FormView, UpdateView -from django.utils.decorators import method_decorator - -# local Django -from administrator.utils import admin_required -from event.services import get_signed_up_events_for_volunteer -from job.services import get_signed_up_jobs_for_volunteer -from organization.services import get_organization_by_id, get_organizations_ordered_by_name -from shift.services import get_volunteer_report, calculate_total_report_hours -from volunteer.forms import ReportForm, SearchVolunteerForm, VolunteerForm -from volunteer.models import Volunteer -from volunteer.services import (delete_volunteer_resume, search_volunteers, - get_volunteer_resume_file_url, has_resume_file, - get_volunteer_by_id) -from volunteer.validation import validate_file -from volunteer.utils import vol_id_check -from vms.utils import check_correct_volunteer - - -@login_required -def download_resume(request, volunteer_id): - user = request.user - if int(user.volunteer.id) == int(volunteer_id): - if request.method == 'POST': - basename = get_volunteer_resume_file_url(volunteer_id) - if basename: - filename = settings.MEDIA_ROOT + basename - wrapper = FileWrapper(file(filename)) - response = HttpResponse(wrapper) - response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(filename) - response['Content-Length'] = os.path.getsize(filename) - return response - else: - raise Http404 - else: - return HttpResponse(status=403) - - -@login_required -def delete_resume(request, volunteer_id): - user = request.user - if int(user.volunteer.id) == int(volunteer_id): - if request.method == 'POST': - try: - delete_volunteer_resume(volunteer_id) - return HttpResponseRedirect( - reverse('volunteer:profile', args=(volunteer_id, ))) - except: - raise Http404 - else: - return HttpResponse(status=403) - - -''' - The View to edit Volunteer Profile -''' - - -class VolunteerUpdateView(LoginRequiredMixin, UpdateView, FormView): - @method_decorator(check_correct_volunteer) - def dispatch(self, *args, **kwargs): - return super(VolunteerUpdateView, self).dispatch(*args, **kwargs) - - form_class = VolunteerForm - template_name = 'volunteer/edit.html' - success_url = reverse_lazy('volunteer:profile') - - def get_context_data(self, **kwargs): - context = super(VolunteerUpdateView,self).get_context_data(**kwargs) - organization_list = get_organizations_ordered_by_name() - context['organization_list'] = organization_list - return context - - def get_object(self, queryset=None): - volunteer_id = self.kwargs['volunteer_id'] - obj = Volunteer.objects.get(pk=volunteer_id) - return obj - - def form_valid(self, form): - volunteer_id = self.kwargs['volunteer_id'] - volunteer = get_volunteer_by_id(volunteer_id) - organization_list = get_organizations_ordered_by_name() - if 'resume_file' in self.request.FILES: - my_file = form.cleaned_data['resume_file'] - if validate_file(my_file): - # delete an old uploaded resume if it exists - has_file = has_resume_file(volunteer_id) - if has_file: - try: - delete_volunteer_resume(volunteer_id) - except: - raise Http404 - else: - return render( - self.request, 'volunteer/edit.html', { - 'form': form, - 'organization_list': organization_list, - 'volunteer': volunteer, - 'resume_invalid': True, - }) - - volunteer_to_edit = form.save(commit=False) - - organization_id = self.request.POST.get('organization_name') - organization = get_organization_by_id(organization_id) - if organization: - volunteer_to_edit.organization = organization - else: - volunteer_to_edit.organization = None - - # update the volunteer - volunteer_to_edit.save() - return HttpResponseRedirect( - reverse('volunteer:profile', args=(volunteer_id, ))) - - -''' - The view to display Volunteer profile. - It uses DetailView which is a generic class-based views are designed to display data. -''' - - -class ProfileView(LoginRequiredMixin, DetailView): - template_name = 'volunteer/profile.html' - - @method_decorator(check_correct_volunteer) - @method_decorator(vol_id_check) - def dispatch(self, *args, **kwargs): - return super(ProfileView, self).dispatch(*args, **kwargs) - - def get_object(self, queryset=None): - volunteer_id = self.kwargs['volunteer_id'] - obj = Volunteer.objects.get(id=self.kwargs['volunteer_id']) - return obj - - -''' - The view generates Report. - GenerateReportView calls two other views(ShowFormView, ShowReportListView) within it. -''' - - -class GenerateReportView(LoginRequiredMixin, View): - @method_decorator(check_correct_volunteer) - @method_decorator(vol_id_check) - def dispatch(self, *args, **kwargs): - return super(GenerateReportView, self).dispatch(*args, **kwargs) - - def get(self, request, *args, **kwargs): - view = ShowFormView.as_view() - return view(request, *args, **kwargs) - - def post(self, request, *args, **kwargs): - view = ShowReportListView.as_view() - return view(request, *args, **kwargs) - - -class ShowFormView(LoginRequiredMixin, FormView): - """ - Displays the form - """ - model = Volunteer - form_class = ReportForm - template_name = "volunteer/report.html" - - def get(self, request, *args, **kwargs): - volunteer_id = self.kwargs['volunteer_id'] - event_list = get_signed_up_events_for_volunteer(volunteer_id) - job_list = get_signed_up_jobs_for_volunteer(volunteer_id) - - return render(request, 'volunteer/report.html', { - 'event_list': event_list, - 'job_list':job_list, - }) - - -class ShowReportListView(LoginRequiredMixin, ListView): - """ - Generate the report using ListView - """ - template_name = "volunteer/report.html" - - def post(self, request, *args, **kwargs): - volunteer_id = self.kwargs['volunteer_id'] - event_list = get_signed_up_events_for_volunteer(volunteer_id) - job_list = get_signed_up_jobs_for_volunteer(volunteer_id) - event_name = self.request.POST['event_name'] - job_name = self.request.POST['job_name'] - start_date = self.request.POST['start_date'] - end_date = self.request.POST['end_date'] - report_list = get_volunteer_report(volunteer_id, event_name, job_name, - start_date, end_date) - total_hours = calculate_total_report_hours(report_list) - return render( - request, 'volunteer/report.html', { - 'report_list': report_list, - 'total_hours': total_hours, - 'notification': True, - 'job_list': job_list, - 'event_list': event_list, - 'selected_event': event_name, - 'selected_job': job_name - }) - - -@login_required -@admin_required -def search(request): - organizations_list = [] - if request.method == 'POST': - form = SearchVolunteerForm(request.POST) - if form.is_valid(): - - first_name = form.cleaned_data['first_name'] - last_name = form.cleaned_data['last_name'] - city = form.cleaned_data['city'] - state = form.cleaned_data['state'] - country = form.cleaned_data['country'] - organization = form.cleaned_data['organization'] - organizations_list = get_organizations_ordered_by_name() - - search_result_list = search_volunteers( - first_name, last_name, city, state, country, organization) - return render( - request, 'volunteer/search.html', { - 'organizations_list': organizations_list, - 'form': form, - 'has_searched': True, - 'search_result_list': search_result_list - }) - else: - organizations_list = get_organizations_ordered_by_name() - form = SearchVolunteerForm() - - return render( - request, 'volunteer/search.html', { - 'organizations_list': organizations_list, - 'form': form, - 'has_searched': False - }) +# standard library +import os + +# third party +from braces.views import LoginRequiredMixin + +# Django +from django.conf import settings +# from django.contrib.auth.models import User +from django.contrib.auth.decorators import login_required +from wsgiref.util import FileWrapper +from django.core.urlresolvers import reverse, reverse_lazy +from django.http import Http404, HttpResponse, HttpResponseRedirect +from django.shortcuts import render +from django.views.generic.detail import DetailView +from django.views.generic import ListView, View +from django.views.generic.edit import FormView, UpdateView +from django.utils.decorators import method_decorator + +# local Django +from administrator.utils import admin_required +from event.services import get_signed_up_events_for_volunteer +from job.services import get_signed_up_jobs_for_volunteer +from organization.services import get_organization_by_id, get_organizations_ordered_by_name +from shift.services import get_volunteer_report, calculate_total_report_hours +from volunteer.forms import ReportForm, SearchVolunteerForm, VolunteerForm +from volunteer.models import Volunteer +from volunteer.services import (delete_volunteer_resume, search_volunteers, + get_volunteer_resume_file_url, has_resume_file, + get_volunteer_by_id) +from volunteer.validation import validate_file +from volunteer.utils import vol_id_check +from vms.utils import check_correct_volunteer + + +@login_required +def download_resume(request, volunteer_id): + user = request.user + if int(user.volunteer.id) == int(volunteer_id): + if request.method == 'POST': + basename = get_volunteer_resume_file_url(volunteer_id) + if basename: + filename = settings.MEDIA_ROOT + '/..' + basename + wrapper = FileWrapper(open(filename, 'rb')) + response = HttpResponse(wrapper) + response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(filename) + response['Content-Length'] = os.path.getsize(filename) + return response + else: + raise Http404 + else: + return HttpResponse(status=403) + + +@login_required +def delete_resume(request, volunteer_id): + user = request.user + if int(user.volunteer.id) == int(volunteer_id): + if request.method == 'POST': + try: + delete_volunteer_resume(volunteer_id) + return HttpResponseRedirect( + reverse('volunteer:profile', args=(volunteer_id, ))) + except: + raise Http404 + else: + return HttpResponse(status=403) + + +''' + The View to edit Volunteer Profile +''' + + +class VolunteerUpdateView(LoginRequiredMixin, UpdateView, FormView): + @method_decorator(check_correct_volunteer) + def dispatch(self, *args, **kwargs): + return super(VolunteerUpdateView, self).dispatch(*args, **kwargs) + + form_class = VolunteerForm + template_name = 'volunteer/edit.html' + success_url = reverse_lazy('volunteer:profile') + + def get_context_data(self, **kwargs): + context = super(VolunteerUpdateView,self).get_context_data(**kwargs) + organization_list = get_organizations_ordered_by_name() + context['organization_list'] = organization_list + return context + + def get_object(self, queryset=None): + volunteer_id = self.kwargs['volunteer_id'] + obj = Volunteer.objects.get(pk=volunteer_id) + return obj + + def form_valid(self, form): + volunteer_id = self.kwargs['volunteer_id'] + volunteer = get_volunteer_by_id(volunteer_id) + organization_list = get_organizations_ordered_by_name() + if 'resume_file' in self.request.FILES: + my_file = form.cleaned_data['resume_file'] + if validate_file(my_file): + # delete an old uploaded resume if it exists + has_file = has_resume_file(volunteer_id) + if has_file: + try: + delete_volunteer_resume(volunteer_id) + except: + raise Http404 + else: + return render( + self.request, 'volunteer/edit.html', { + 'form': form, + 'organization_list': organization_list, + 'volunteer': volunteer, + 'resume_invalid': True, + }) + + volunteer_to_edit = form.save(commit=False) + + organization_id = self.request.POST.get('organization_name') + organization = get_organization_by_id(organization_id) + if organization: + volunteer_to_edit.organization = organization + else: + volunteer_to_edit.organization = None + + # update the volunteer + volunteer_to_edit.save() + return HttpResponseRedirect( + reverse('volunteer:profile', args=(volunteer_id, ))) + + +''' + The view to display Volunteer profile. + It uses DetailView which is a generic class-based views are designed to display data. +''' + + +class ProfileView(LoginRequiredMixin, DetailView): + template_name = 'volunteer/profile.html' + + @method_decorator(check_correct_volunteer) + @method_decorator(vol_id_check) + def dispatch(self, *args, **kwargs): + return super(ProfileView, self).dispatch(*args, **kwargs) + + def get_object(self, queryset=None): + volunteer_id = self.kwargs['volunteer_id'] + obj = Volunteer.objects.get(id=self.kwargs['volunteer_id']) + return obj + + +''' + The view generates Report. + GenerateReportView calls two other views(ShowFormView, ShowReportListView) within it. +''' + + +class GenerateReportView(LoginRequiredMixin, View): + @method_decorator(check_correct_volunteer) + @method_decorator(vol_id_check) + def dispatch(self, *args, **kwargs): + return super(GenerateReportView, self).dispatch(*args, **kwargs) + + def get(self, request, *args, **kwargs): + view = ShowFormView.as_view() + return view(request, *args, **kwargs) + + def post(self, request, *args, **kwargs): + view = ShowReportListView.as_view() + return view(request, *args, **kwargs) + + +class ShowFormView(LoginRequiredMixin, FormView): + """ + Displays the form + """ + model = Volunteer + form_class = ReportForm + template_name = "volunteer/report.html" + + def get(self, request, *args, **kwargs): + volunteer_id = self.kwargs['volunteer_id'] + event_list = get_signed_up_events_for_volunteer(volunteer_id) + job_list = get_signed_up_jobs_for_volunteer(volunteer_id) + + return render(request, 'volunteer/report.html', { + 'event_list': event_list, + 'job_list': job_list, + }) + + +class ShowReportListView(LoginRequiredMixin, ListView): + """ + Generate the report using ListView + """ + template_name = "volunteer/report.html" + + def post(self, request, *args, **kwargs): + volunteer_id = self.kwargs['volunteer_id'] + event_list = get_signed_up_events_for_volunteer(volunteer_id) + job_list = get_signed_up_jobs_for_volunteer(volunteer_id) + event_name = self.request.POST['event_name'] + job_name = self.request.POST['job_name'] + start_date = self.request.POST['start_date'] + end_date = self.request.POST['end_date'] + report_list = get_volunteer_report(volunteer_id, event_name, job_name, + start_date, end_date) + total_hours = calculate_total_report_hours(report_list) + return render( + request, 'volunteer/report.html', { + 'report_list': report_list, + 'total_hours': total_hours, + 'notification': True, + 'job_list': job_list, + 'event_list': event_list, + 'selected_event': event_name, + 'selected_job': job_name + }) + + +@login_required +@admin_required +def search(request): + organizations_list = [] + if request.method == 'POST': + form = SearchVolunteerForm(request.POST) + if form.is_valid(): + + first_name = form.cleaned_data['first_name'] + last_name = form.cleaned_data['last_name'] + city = form.cleaned_data['city'] + state = form.cleaned_data['state'] + country = form.cleaned_data['country'] + organization = form.cleaned_data['organization'] + organizations_list = get_organizations_ordered_by_name() + + search_result_list = search_volunteers( + first_name, last_name, city, state, country, organization) + return render( + request, 'volunteer/search.html', { + 'organizations_list': organizations_list, + 'form': form, + 'has_searched': True, + 'search_result_list': search_result_list + }) + else: + organizations_list = get_organizations_ordered_by_name() + form = SearchVolunteerForm() + + return render( + request, 'volunteer/search.html', { + 'organizations_list': organizations_list, + 'form': form, + 'has_searched': False + })